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 }