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