1 /**
2 * Copyright 2017 Cut Through Recordings
3 * License: MIT License
4 * Author(s): Ethan Reker
5 */
6 module ddsp.util.envelope;
7 
8 import std.math;
9 import std.algorithm;
10 import ddsp.util.buffer;
11 import dplug.core.nogc;
12 import dplug.core.ringbuf;
13 
14 /// Envelop Detector with adjustable attack and release times. Great for compressors
15 /// and meters.
16 /+
17 http://www.musicdsp.org/showArchiveComment.php?ArchiveID=97
18 +/
19 class EnvelopeDetector(T)
20 {
21 public:
22 nothrow:
23 @nogc:
24 
25     this()
26     {
27         _envelope = 0f;
28         setTimeConstant(0.01);
29     }
30     
31     void setSampleRate(const float sampleRate)
32     {
33         _sampleRate = sampleRate;
34     }
35 
36 
37     void setTimeConstant(const float timeConstant) {
38         _timeConstant = log10(timeConstant);
39     }
40     
41     /**
42      * Set the attack and release times of the envelope detector in milliseconds
43      */
44     void setEnvelope(const float attackTime, const float releaseTime)
45     {
46         _ga = exp( _timeConstant /(_sampleRate * attackTime * 0.001));
47         _gr = exp( _timeConstant / (_sampleRate * releaseTime * 0.001));
48     }
49     
50     T detect(T input)
51     {
52         T envIn = processInput(input);
53         
54         if(_envelope < envIn)
55             _envelope = _envelope * _ga + (1 - _ga) * envIn;
56         else
57             _envelope = _envelope * _gr + (1 - _gr) * envIn;
58 
59         if(isNaN(_envelope)) {
60             _envelope = 0.0f;
61         }
62         return _envelope;
63     }
64     
65     T getEnvelope()
66     {
67         return _envelope;
68     }
69 
70     void reset()
71     {
72         _envelope = 0;
73     }
74 
75     abstract T processInput(T input);
76     
77 private:
78     /// Attack coefficient
79     float _ga;
80     
81     /// Release coefficient
82     float _gr;
83     
84     /// stores the current value of the envelope;
85     float _envelope;
86     
87     /// Sample Rate
88     float _sampleRate;
89 
90     /// time constant used to calculate attack and release times
91     float _timeConstant;
92 }
93 
94 /// Simple Peak envelope follower, useful for meters.
95 /+
96 http://www.musicdsp.org/archive.php?classid=2#19
97 +/
98 class PeakDetector(T): EnvelopeDetector!T
99 {
100 public:
101 nothrow:
102 @nogc:
103 
104     this()
105     {
106         super();
107     }
108 
109     override T processInput(T input)
110     {
111         return abs(input);
112     }
113     
114 private:    
115     float _decay;
116     
117     float _sampleRate;
118 }
119 
120 unittest
121 {
122     PeakDetector!float peakDetector = new PeakDetector!float();
123 }
124 
125 class RMSDetector(T): EnvelopeDetector!T
126 {
127     public:
128     nothrow:
129     @nogc:
130 
131         this(int windowSize)
132         {
133             super();
134             _windowSize = windowSize;
135             _buffer = mallocNew!(Buffer!T)(_windowSize);
136             _counter = 0;
137             runningMean = 0;
138         }
139 
140         override T processInput(T input)
141         {
142             immutable T poppedVal = _buffer.read();
143 
144             if(_counter < _windowSize)
145             {
146                 ++_counter;
147             }
148             else
149             {
150                 runningMean -= (poppedVal * poppedVal) / _windowSize;
151             }
152             runningMean += (input * input) / _windowSize;
153             _buffer.write(input);
154             return sqrt(runningMean);
155         }
156 
157     private:
158         int _windowSize;
159         int _counter;
160         Buffer!T _buffer;
161         T runningMean;
162 }
163 
164 unittest
165 {
166     import std.stdio;
167     float[] values = [0, 0, 1, 1, 0, 1, 0, 1, 1, 1];
168 
169     auto rmsDetector = new RMSDetector!float(10);
170     rmsDetector.setSampleRate(44100);
171     rmsDetector.setEnvelope(0, 0);
172     for(int i = 0; i < 10; ++i)
173     {
174         foreach(val; values)
175         {
176             auto result = rmsDetector.detect(val);
177             // writeln(result);
178         }
179     }
180 
181     auto peakDetector = new PeakDetector!float();
182     peakDetector.setSampleRate(44100);
183     peakDetector.setEnvelope(0.1, 1);
184     for(int i = 0; i < 10; ++i)
185     {
186         foreach(val; values)
187         {
188             auto result = peakDetector.detect(val);
189             // writeln(result);
190         }
191     }
192 }
193 
194 /**
195  * This class is used for calculating a moving average.  It's useful for things such
196  * as smoothing meter values.
197  */
198 class MovingAverage(T)
199 {
200 public:
201 nothrow:
202 @nogc:
203 
204     this(int windowSize)
205     {
206         _windowSize = windowSize;
207         _buffer = makeRingBufferNoGC!T(_windowSize);
208 		_avg = 0;
209     }
210 
211     T process(double sample)
212     {
213         T prevSample = _buffer.isFull() ? _buffer.popFront() : 0;
214         _avg -= prevSample / _windowSize;
215         _avg += sample / _windowSize;
216         _buffer.pushBack(sample);
217         return _avg;
218     }
219 
220     T getAverage()
221     {
222         return _avg;
223     }
224 
225 private:
226     RingBufferNoGC!T _buffer;
227     int _windowSize;
228     T _avg;
229 
230 }
231 
232 unittest
233 {
234     auto movingAverage = mallocNew!(MovingAverage!float)(100);
235 }