1 /**
2     Filters.
3 
4     Based on code from STK library
5          https://github.com/thestk/stk
6          https://ccrma.stanford.edu/software/stk/
7 */
8 module soundtab.audio.filters;
9 
10 import std.math : log2, exp2, sin, cos, pow, PI;
11 
12 immutable float TWO_PI = PI * 2;
13 
14 
15 //float linearToDb(float f) {
16 //
17 //}
18 /// decibels to linear
19 float dbToLinear(float f) {
20     return pow(10.0f, f / 20);
21 }
22 
23 
24 /// digital filter - base interface
25 interface Filter {
26     // set sample rate (frames per second)
27     void setSampleRate(int samplesPerSecond);
28     // feed single input sample and get filtered result
29     float tick(float input);
30     // clear state
31     void clear();
32 }
33 
34 abstract class FilterBase  : Filter {
35     int _sampleRate = 44100;
36     void setSampleRate(int samplesPerSecond) {
37         _sampleRate = samplesPerSecond;
38     }
39 }
40 
41 class OnePole : FilterBase {
42     this(float thePole = 0.9f) {
43         setPole(thePole);
44     }
45     void setPole(float thePole) {
46         // Normalize coefficients for peak unity gain.
47         if (thePole > 0.0f)
48             _b[0] = 1.0f - thePole;
49         else
50             _b[0] = 1.0f + thePole;
51 
52         _a[1] = -thePole;
53     }
54     float tick(float input) {
55         _inputs[0] = _gain * input;
56         _lastFrame = _b[0] * _inputs[0] - _a[1] * _outputs[1];
57         _outputs[1] = _lastFrame;
58         return _lastFrame;
59     }
60     // clear state
61     void clear() {
62         _inputs = [0,0];
63         _outputs = [0,0];
64         _lastFrame = 0;
65     }
66     float _gain = 1.0f;
67     float[2] _a = [0,0];
68     float[2] _b = [0,0];
69     float[2] _inputs = [0,0];
70     float[2] _outputs = [0,0];
71     float _lastFrame = 0;
72 }
73 
74 class OneZero : FilterBase {
75     this(float theZero = -1.0f) {
76         setZero(theZero);
77     }
78     void setZero(float theZero) {
79         // Normalize coefficients for unity gain.
80         if ( theZero > 0.0 )
81             _b[0] = 1 / (1 + theZero);
82         else
83             _b[0] = 1 / (1 - theZero);
84 
85         _b[1] = -theZero * _b[0];
86     }
87     float tick(float input) {
88         _inputs[0] = _gain * input;
89         _lastFrame = _b[1] * _inputs[1] + _b[0] * _inputs[0];
90         _inputs[1] = _inputs[0];
91         return _lastFrame;
92     }
93     // clear state
94     void clear() {
95         _inputs = [0,0];
96         _outputs = [0,0];
97         _lastFrame = 0;
98     }
99     float _gain = 1.0f;
100     float[2] _a = [0,0];
101     float[2] _b = [0,0];
102     float[2] _inputs = [0,0];
103     float[2] _outputs = [0,0];
104     float _lastFrame = 0;
105 }
106 
107 class Formant  : FilterBase {
108     this() {
109     }
110     void setParams(float frequency, float radius, float gain) {
111         _gain = gain;
112         setResonance(frequency, radius);
113     }
114     override void setSampleRate(int samplesPerSecond) {
115         super.setSampleRate(samplesPerSecond);
116         setResonance(_frequency, _radius);
117     }
118     void setResonance(float frequency, float radius) {
119         _radius = radius;
120         _frequency = frequency;
121         _a[2] = radius * radius;
122         _a[1] = -2.0f * radius * cos( TWO_PI * frequency / _sampleRate );
123         // Use zeros at +- 1 and normalize the filter peak gain.
124         _b[0] = 0.5 - 0.5 * _a[2];
125         _b[1] = 0.0;
126         _b[2] = -_b[0];
127     }
128     float tick(float input) {
129         _inputs[0] = _gain * input;
130         _lastFrame = _b[0] * _inputs[0] + _b[1] * _inputs[1] + _b[2] * _inputs[2];
131         _lastFrame -= _a[2] * _outputs[2] + _a[1] * _outputs[1];
132         _inputs[2] = _inputs[1];
133         _inputs[1] = _inputs[0];
134         _outputs[2] = _outputs[1];
135         _outputs[1] = _lastFrame;
136         return _lastFrame;
137     }
138     // clear state
139     void clear() {
140         _inputs = [0,0,0,0];
141         _outputs = [0,0,0,0];
142         _lastFrame = 0;
143     }
144     float _frequency = 400;
145     float _radius = 0;
146     float _gain = 1.0f;
147     float[4] _a = [0,0,0,0];
148     float[4] _b = [0,0,0,0];
149     float[4] _inputs = [0,0,0,0];
150     float[4] _outputs = [0,0,0,0];
151     float _lastFrame = 0;
152 }
153 
154 class PhonemeFormantsFilter : Filter {
155     Formant[4] _formants;
156     float _formantFreqMult;
157     this(PhonemeType phoneme = PhonemeType.ahh, float formantFreqMult = 1) {
158         _formantFreqMult = formantFreqMult;
159         for(int i = 0; i < 4; i++)
160             _formants[i] = new Formant();
161         setPhoneme(phoneme, formantFreqMult);
162     }
163 
164     void setPhoneme(PhonemeType type, float formantFreqMult = 1) {
165         _formantFreqMult = formantFreqMult;
166         for (int i = 0; i < 4; i ++) {
167             _formants[i].setParams(_formantFreqMult * phonemes[type][i].frequency, phonemes[type][i].radius, dbToLinear(phonemes[type][i].gainDb));
168         }
169     }
170 
171     void setSampleRate(int samplesPerSecond) {
172         for(int i = 0; i < 4; i++)
173             _formants[i].setSampleRate(samplesPerSecond);
174     }
175 
176     float tick(float input) {
177         float temp = input;
178 
179         _lastFrame = _formants[0].tick(temp);
180         _lastFrame += _formants[1].tick(temp);
181         _lastFrame += _formants[2].tick(temp);
182         _lastFrame += _formants[3].tick(temp);
183         return _lastFrame;
184     }
185     // clear state
186     void clear() {
187         for(int i = 0; i < 4; i++)
188             _formants[i].clear();
189     }
190     float _lastFrame = 0;
191 }
192 
193 struct FormantParams {
194     float frequency;
195     float radius;
196     float gainDb;
197 }
198 
199 alias PhonemeParams = FormantParams[4];
200 
201 enum PhonemeType {
202     eee,
203     ihh,
204     ehh,
205     aaa,
206 
207     ahh,
208     aww,
209     ohh,
210     uhh,
211 
212     uuu,
213     ooo,
214     rrr,
215     lll,
216 }
217 
218 PhonemeParams[12] phonemes = [
219     [ 
220         //// PhonemeType.eee (beet)
221         FormantParams(273, 0.996,  10),
222         FormantParams(2086, 0.945, -16),
223         FormantParams(2754, 0.979, -12),
224         FormantParams(3270, 0.440, -17)
225     ],
226     [
227         //// PhonemeType.ihh (bit)
228         FormantParams(385, 0.987,  10),
229         FormantParams(2056, 0.930, -20),
230         FormantParams(2587, 0.890, -20),
231         FormantParams(3150, 0.400, -20)
232     ],
233     [
234         //// PhonemeType.ehh (bet)
235         FormantParams(515, 0.977,  10),
236         FormantParams(1805, 0.810, -10),
237         FormantParams(2526, 0.875, -10),
238         FormantParams(3103, 0.400, -13)
239     ],
240     [
241         //// PhonemeType.aaa (bat)
242         FormantParams(773, 0.950,  10),
243         FormantParams(1676, 0.830,  -6),
244         FormantParams(2380, 0.880, -20),
245         FormantParams(3027, 0.600, -20)
246     ],
247     [
248         //// PhonemeType.ahh (father)
249         FormantParams(770, 0.950,   0),
250         FormantParams(1153, 0.970,  -9),
251         FormantParams(2450, 0.780, -29),
252         FormantParams(3140, 0.800, -39)
253     ],
254     [
255         //// PhonemeType.aww (bought)
256         FormantParams(637, 0.910,   0),
257         FormantParams(895, 0.900,  -3),
258         FormantParams(2556, 0.950, -17),
259         FormantParams(3070, 0.910, -20)
260     ],
261     [ 
262         //// PhonemeType.ohh (bone)  NOTE::  same as aww (bought)
263         FormantParams(637, 0.910,   0),
264         FormantParams(895, 0.900,  -3),
265         FormantParams(2556, 0.950, -17),
266         FormantParams(3070, 0.910, -20)
267     ],
268     [
269         //// PhonemeType.uhh (but)
270         FormantParams(561, 0.965,   0),
271         FormantParams(1084, 0.930, -10),
272         FormantParams(2541, 0.930, -15),
273         FormantParams(3345, 0.900, -20)
274     ],
275     [
276         //// PhonemeType.uuu (foot)
277         FormantParams(515, 0.976,   0),
278         FormantParams(1031, 0.950,  -3),
279         FormantParams(2572, 0.960, -11),
280         FormantParams(3345, 0.960, -20)
281     ],
282     [
283         //// PhonemeType.ooo (boot)
284         FormantParams(349, 0.986, -10),
285         FormantParams(918, 0.940, -20),
286         FormantParams(2350, 0.960, -27),
287         FormantParams(2731, 0.950, -33)
288     ],
289     [
290         //// PhonemeType.rrr (bird)
291         FormantParams(394, 0.959, -10),
292         FormantParams(1297, 0.780, -16),
293         FormantParams(1441, 0.980, -16),
294         FormantParams(2754, 0.950, -40)
295     ],
296     [
297         //// PhonemeType.lll (lull)
298         FormantParams(462, 0.990,  +5),
299         FormantParams(1200, 0.640, -10),
300         FormantParams(2500, 0.200, -20),
301         FormantParams(3000, 0.100, -30)
302     ],
303 ];