1 /**
2 * Copyright: Copyright Auburn Sounds 2015 and later.
3 * License:   $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
4 * Authors:   Guillaume Piolat
5 */
6 module dplug.dsp.envelope;
7 
8 import std.math;
9 import dplug.dsp.iir;
10 import dplug.dsp.smooth;
11 
12 // Various envelope followers
13 
14 
15 /// Simple envelope follower, filters the envelope with 24db/oct lowpass.
16 struct EnvelopeFollower(T) if (is(T == float) || is(T == double))
17 {
18 public:
19 
20     // typical frequency would be is 10-30hz
21     void initialize(float cutoffInHz, float samplerate) nothrow @nogc
22     {
23         _coeff = lowpassFilterRBJ!double(cutoffInHz, samplerate);
24         _delay0.initialize();
25         _delay1.initialize();
26     }
27 
28     // takes on sample, return mean amplitude
29     T nextSample(T x) nothrow @nogc
30     {
31         T l = abs(x);
32         l = _delay0.nextSample(l, _coeff);
33         l = _delay1.nextSample(l, _coeff);
34         return l;
35     }
36 
37     void nextBuffer(const(T)* input, T* output, int frames) nothrow @nogc
38     {
39         for(int i = 0; i < frames; ++i)
40             output[i] = abs(input[i]);
41 
42         _delay0.nextBuffer(output, output, frames, _coeff);
43         _delay1.nextBuffer(output, output, frames, _coeff);
44     }
45 
46 private:
47     BiquadCoeff!T _coeff;
48     BiquadDelay!T _delay0;
49     BiquadDelay!T _delay1;
50 }
51 
52 unittest
53 {
54     EnvelopeFollower!float a;
55     EnvelopeFollower!double b;
56 }
57 
58 /// Get the module of estimate of analytic signal.
59 /// Phase response depends a lot on input signal, it's not great for bass but gets
60 /// better in medium frequencies.
61 struct AnalyticSignal(T) if (is(T == float) || is(T == double))
62 {
63 public:
64     void initialize(T samplerate) nothrow @nogc
65     {
66         _hilbert.initialize(samplerate);
67     }
68 
69     T nextSample(T input) nothrow @nogc
70     {
71         T outSine, outCosine;
72         _hilbert.nextSample(input, outCosine, outSine);
73         return sqrt(input * input + outSine * outSine);
74     }
75 
76     void nextBuffer(const(T)* input, T* output, int frames) nothrow @nogc
77     {
78         // FUTURE: pass maxFrames and allocate buffers
79         for (int i = 0; i < frames; ++i)
80             output[i] = nextSample(input[i]);
81     }
82 
83 private:
84     HilbertTransformer!T _hilbert;
85 }
86 
87 unittest
88 {
89     AnalyticSignal!float a;
90     AnalyticSignal!double b;
91 }
92 
93 
94 /**
95 *
96 * Copyright 1999, by Sean M. Costello
97 *
98 * hilbert is an implementation of an IIR Hilbert transformer.
99 * The structure is based on two 6th-order allpass filters in
100 * parallel, with a constant phase difference of 90 degrees
101 * (+- some small amount of error) between the two outputs.
102 * Allpass coefficients are calculated at i-time.
103 *
104 * "Feel free to use the code under whatever license you wish." - Sean Costello
105 */
106 
107 /// Estimate amplitude.
108 struct HilbertTransformer(T) if (is(T == float) || is(T == double))
109 {
110 public:
111     void initialize(float sampleRate) nothrow @nogc
112     {
113         // pole values taken from Bernie Hutchins, "Musical Engineer's Handbook"
114         static immutable double[12] poles =
115         [
116             0.3609, 2.7412, 11.1573, 44.7581, 179.6242, 798.4578,
117             1.2524, 5.5671, 22.3423, 89.6271, 364.7914, 2770.1114
118         ];
119 
120         float onedsr = 1 / sampleRate;
121 
122         // calculate coefficients for allpass filters, based on sampling rate
123         for (int j = 0; j < 12; ++j)
124         {
125             const double polefreq = poles[j] * 15.0;
126             const double alpha = (2.0 * PI * polefreq);
127             const double beta = (1.0 - (alpha * 0.5 * onedsr)) / (1.0 + (alpha * 0.5 * onedsr));
128             _coef[j] = -beta;
129         }
130 
131         for (int j = 0; j < 12; ++j)
132         {
133             _xnm1[j] = 0;
134             _ynm1[j] = 0;
135         }
136     }
137 
138     void nextSample(T input, out T out1, out T out2) nothrow @nogc
139     {
140         T yn1, yn2;
141         T xn1 = input;
142 
143         /* 6th order allpass filter for sine output. Structure is
144         * 6 first-order allpass sections in series. Coefficients
145         * taken from arrays calculated at i-time.
146         */
147 
148         for (int j=0; j < 6; j++)
149         {
150             yn1 = _coef[j] * (xn1 - _ynm1[j]) + _xnm1[j];
151             _xnm1[j] = xn1;
152             _ynm1[j] = yn1;
153             xn1 = yn1;
154         }
155 
156         T xn2 = input;
157 
158         /* 6th order allpass filter for cosine output. Structure is
159         * 6 first-order allpass sections in series. Coefficients
160         * taken from arrays calculated at i-time.
161         */
162         for (int j = 6; j < 12; j++)
163         {
164             yn2 = _coef[j] * (xn2 - _ynm1[j]) + _xnm1[j];
165             _xnm1[j] = xn2;
166             _ynm1[j] = yn2;
167             xn2 = yn2;
168         }
169         out1 = cast(T)yn2;
170         out2 = cast(T)yn1;
171     }
172 
173     void nextBuffer(const(T)* input, T* output1, T* output2, int frames) nothrow @nogc
174     {
175         for (int i = 0; i < frames; ++i)
176         {
177             T yn1;
178             T xn1 = input[i];
179 
180             /* 6th order allpass filter for sine output. Structure is
181             * 6 first-order allpass sections in series. Coefficients
182             * taken from arrays calculated at i-time.
183             */
184             for (int j=0; j < 6; j++)
185             {
186                 yn1 = _coef[j] * (xn1 - _ynm1[j]) + _xnm1[j];
187                 _xnm1[j] = xn1;
188                 _ynm1[j] = yn1;
189                 xn1 = yn1;
190             }
191             output2[i] = cast(T)yn1;
192         }
193 
194         for (int i = 0; i < frames; ++i)
195         {
196             T yn2;
197             T xn2 = input[i];
198 
199             /* 6th order allpass filter for cosine output. Structure is
200             * 6 first-order allpass sections in series. Coefficients
201             * taken from arrays calculated at i-time.
202             */
203             for (int j = 6; j < 12; j++)
204             {
205                 yn2 = _coef[j] * (xn2 - _ynm1[j]) + _xnm1[j];
206                 _xnm1[j] = xn2;
207                 _ynm1[j] = yn2;
208                 xn2 = yn2;
209             }
210 
211             output1[i] = cast(T)yn2;
212         }
213 
214         // Remove small signals
215         for (int j = 0; j < 12; ++j)
216         {
217             _xnm1[j] += 1e-10f;
218             _xnm1[j] -= 1e-10f;
219             _ynm1[j] += 1e-10f;
220             _ynm1[j] -= 1e-10f;
221         }
222     }
223 
224 private:
225     T[12] _coef;
226     T[12] _xnm1;
227     T[12] _ynm1;
228 }
229 
230 unittest
231 {
232     HilbertTransformer!float a;
233     HilbertTransformer!double b;
234 }
235 
236 /// Sliding RMS computation
237 /// To use for coarse grained levels for visual display.
238 struct CoarseRMS(T) if (is(T == float) || is(T == double))
239 {
240 public:
241     void initialize(double sampleRate) nothrow @nogc
242     {
243         // In Reaper, default RMS window is 500 ms
244         _envelope.initialize(20, sampleRate);
245 
246         _last = 0;
247     }
248 
249     /// Process a chunk of samples and return a value in dB (could be -infinity)
250     void nextSample(T input) nothrow @nogc
251     {
252         _last = _envelope.nextSample(input * input);
253     }
254 
255     void nextBuffer(T* input, int frames) nothrow @nogc
256     {
257         if (frames == 0)
258             return;
259 
260         for (int i = 0; i < frames - 1; ++i)
261             _envelope.nextSample(input[i] * input[i]);
262 
263         _last = _envelope.nextSample(input[frames - 1] * input[frames - 1]);
264     }
265 
266     T RMS() nothrow @nogc
267     {
268         return sqrt(_last);
269     }
270 
271 private:
272     EnvelopeFollower!T _envelope;
273     T _last;
274 }
275 
276 unittest
277 {
278     CoarseRMS!float a;
279     CoarseRMS!double b;
280 }