1 /** 2 * Copyright 2017 Cut Through Recordings 3 * License: MIT License 4 * Author(s): Ethan Reker 5 */ 6 module ddsp.filter.lowpass; 7 8 import ddsp.filter.biquad; 9 import ddsp.effect.effect : AudioEffect; 10 11 import std.math; 12 import dplug.core.nogc; 13 14 /// First order lowpass filter 15 class LowpassO1(T) : BiQuad!T 16 { 17 public: 18 override void calcCoefficients() nothrow @nogc 19 { 20 _thetac = 2 * PI * _frequency / _sampleRate; 21 _gamma = cos(_thetac) / (1 + sin(_thetac)); 22 _a0 = (1 - _gamma) / 2; 23 _a1 = _a0; 24 _a2 = 0.0; 25 _b1 = -_gamma; 26 _b2 = 0.0; 27 } 28 29 private: 30 float _thetac; 31 float _gamma; 32 33 } 34 35 unittest 36 { 37 LowpassO1!float lowpassQ1 = new LowpassO1!float(); 38 } 39 40 /// Second order lowpass filter 41 class LowpassO2(T) : BiQuad!T 42 { 43 public: 44 void setQualityFactor(float Q) nothrow @nogc 45 { 46 if(Q != _q) 47 { 48 _q = Q; 49 calcCoefficients(); 50 } 51 } 52 53 override void calcCoefficients() nothrow @nogc 54 { 55 _thetac = 2 * PI * _frequency / _sampleRate; 56 _d = 1 / _q; 57 _beta = 0.5 * (1 - (_d / 2) * sin(_thetac)) / (1 + (_d / 2) * sin(_thetac)); 58 _gamma = (0.5 + _beta) * cos(_thetac); 59 _a0 = (0.5 + _beta - _gamma) / 2; 60 _a1 = 0.5 + _beta - _gamma; 61 _a2 = _a1 / 2; 62 _b1 = -2.0 * _gamma; 63 _b2 = 2.0 * _beta; 64 } 65 66 private: 67 float _thetac; 68 float _q = 0.707f; 69 float _beta; 70 float _gamma; 71 float _d; 72 } 73 74 unittest 75 { 76 LowpassO2!float lowpassQ2 = new LowpassO2!float(); 77 } 78 79 /// Second order butterworth lowpass filter 80 class ButterworthLP(T) : BiQuad!T 81 { 82 public: 83 override void calcCoefficients() nothrow @nogc 84 { 85 _C = 1.0 / tan(PI * _frequency / _sampleRate); 86 _a0 = 1.0 / (1 + sqrt(2.0f) * _C + (_C * _C)); 87 _a1 = 2.0 * _a0; 88 _a2 = _a0; 89 _b1 = 2.0 * _a0 * (1 - _C * _C); 90 _b2 = _a0 * (1.0f - sqrt(2.0f) * _C + _C * _C); 91 } 92 private: 93 float _C; 94 } 95 96 unittest 97 { 98 ButterworthLP!float butterworthLP = new ButterworthLP!float(); 99 } 100 101 //Second order linkwitz-riley lowpass filter 102 class LinkwitzRileyLP(T) : BiQuad!T 103 { 104 public: 105 nothrow: 106 @nogc: 107 this() 108 { 109 super(); 110 } 111 112 override void calcCoefficients() nothrow @nogc 113 { 114 _theta = pi * _frequency / _sampleRate; 115 _omega = pi * _frequency; 116 _kappa = _omega / tan(_theta); 117 _delta = _kappa * _kappa + _omega * _omega + 2 * _kappa * _omega; 118 119 _a0 = (_omega * _omega) / _delta; 120 _a1 = 2 * _a0; 121 122 _a2 = _a0; 123 _b1 = (-2 * _kappa * _kappa + 2 * _omega * _omega) / _delta; 124 _b2 = (-2 * _kappa * _omega + _kappa * _kappa + _omega * _omega) / _delta; 125 } 126 127 // Useful in determining if setSampleRate and setFrequency need to be called 128 // Uses short circuiting to squeeze a little more efficiency out of check 129 bool isInitialized() 130 { 131 return !(isNaN(_theta) || isNaN(_omega) || isNaN(_kappa) || isNaN(_delta)); 132 } 133 134 private: 135 float _theta; 136 float _omega; 137 float _kappa; 138 float _delta; 139 } 140 141 unittest 142 { 143 LinkwitzRileyLP!float linkwitzRileyLP = new LinkwitzRileyLP!float(); 144 } 145 146 147 class LinkwitzRileyLPNthOrder(T) : AudioEffect!T 148 { 149 public: 150 nothrow: 151 @nogc: 152 153 this(int order) 154 { 155 assert(order % 2 == 0, "LR filter order must be even"); 156 _order = order; 157 158 _bw1 = mallocNew!(ButterworthLPNthOrder!T)(order / 2); 159 _bw2 = mallocNew!(ButterworthLPNthOrder!T)(order / 2); 160 } 161 162 void setFrequency(float frequency) 163 { 164 _frequency = frequency; 165 _bw1.setFrequency(_frequency); 166 _bw2.setFrequency(_frequency); 167 } 168 169 override float getNextSample(const(float) input) 170 { 171 return _bw2.getNextSample(_bw1.getNextSample(input)); 172 } 173 174 override void reset() 175 { 176 _bw1.reset(); 177 _bw2.reset(); 178 } 179 180 override void setSampleRate(float sampleRate) 181 { 182 _sampleRate = sampleRate; 183 _bw1.setSampleRate(_sampleRate); 184 _bw2.setSampleRate(_sampleRate); 185 } 186 187 private: 188 ButterworthLPNthOrder!T _bw1; 189 ButterworthLPNthOrder!T _bw2; 190 191 int _order; 192 float _frequency; 193 } 194 195 unittest 196 { 197 LinkwitzRileyLPNthOrder!float lr8thOrder = mallocNew!(LinkwitzRileyLPNthOrder!float)(8); 198 lr8thOrder.setSampleRate(44100); 199 lr8thOrder.setFrequency(400); 200 201 } 202 203 204 float[] calculateQValuesForButterworth(int filterOrder) nothrow @nogc 205 { 206 float[] qValues = mallocSlice!float(filterOrder / 2); 207 immutable int denominator = 4 * filterOrder / 2; 208 int numerator = 1; 209 foreach(fIndex; 0..(filterOrder / 2)) 210 { 211 qValues[fIndex] = abs(1 / (2 * cos(numerator * PI / denominator))); 212 numerator += 2; 213 } 214 return qValues; 215 } 216 217 class ButterworthLPNthOrder(T) : AudioEffect!T 218 { 219 public: 220 nothrow: 221 @nogc: 222 this(int order) 223 { 224 _order = order; 225 _secondOrderLowpasses = mallocSlice!(LowpassO2!float)(order / 2); 226 foreach(index; 0..(order / 2)) 227 { 228 _secondOrderLowpasses[index] = mallocNew!(LowpassO2!float)(); 229 } 230 } 231 232 void setFrequency(float frequency) 233 { 234 if(frequency != _frequency) 235 { 236 _frequency = frequency; 237 float[] qValues = calculateQValuesForButterworth(_order); 238 foreach(index, lpf; _secondOrderLowpasses) 239 { 240 float qValue = qValues[index]; 241 lpf.setFrequency(frequency); 242 lpf.setQualityFactor(qValue); 243 } 244 } 245 } 246 247 override float getNextSample(const(float) input) 248 { 249 float output = input; 250 foreach(lpf; _secondOrderLowpasses) 251 { 252 output = lpf.getNextSample(output); 253 } 254 return output; 255 } 256 257 override void reset() 258 { 259 foreach(lpf; _secondOrderLowpasses) 260 { 261 if(lpf) 262 { 263 lpf.reset(); 264 } 265 } 266 } 267 268 override void setSampleRate(float sampleRate) 269 { 270 if(sampleRate != _sampleRate) 271 { 272 _sampleRate = sampleRate; 273 foreach(index; 0.._secondOrderLowpasses.length) 274 { 275 _secondOrderLowpasses[index].setSampleRate(sampleRate); 276 } 277 } 278 } 279 280 private: 281 LowpassO2!float[] _secondOrderLowpasses; 282 283 int _order; 284 float _frequency; 285 } 286 287 unittest 288 { 289 import std.stdio; 290 writeln("****************************"); 291 writeln("* Butterworth Filter tests *"); 292 writeln("****************************"); 293 294 writeln("Q Value Calculations"); 295 float[] actual = calculateQValuesForButterworth(4); 296 float[] expected = [0.541196100146197, 1.3065629648763764]; 297 assert( actual == expected, "Failed for order = 4"); 298 writeln("passed for order = 4"); 299 300 ButterworthLPNthOrder!float butterworth4 = mallocNew!(ButterworthLPNthOrder!float)(4); 301 butterworth4.setSampleRate(44100.0f); 302 butterworth4.setFrequency(10); 303 304 float[] impulse = [1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f]; 305 float[] lpfOutput = []; 306 foreach(sample; impulse) 307 { 308 lpfOutput ~= butterworth4.getNextSample(sample); 309 } 310 311 writeln("Butterworth N=4 Output:"); 312 writeln(lpfOutput); 313 314 }