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 }