-
Notifications
You must be signed in to change notification settings - Fork 69
/
biquad.c
317 lines (279 loc) · 8.77 KB
/
biquad.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
//
// sndfilter - Algorithms for sound filters, like reverb, lowpass, etc
// by Sean Connelly (@velipso), https://sean.fun
// Project Home: https://github.com/velipso/sndfilter
// SPDX-License-Identifier: 0BSD
//
#include "biquad.h"
#include <math.h>
// biquad filtering is based on a small sliding window, where the different filters are a result of
// simply changing the coefficients used while processing the samples
//
// the biquad filter processes a sound using 10 parameters:
// b0, b1, b2, a1, a2 transformation coefficients
// xn0, xn1, xn2 the unfiltered sample at position x[n], x[n-1], and x[n-2]
// yn1, yn2 the filtered sample at position y[n-1] and y[n-2]
void sf_biquad_process(sf_biquad_state_st *state, int size, sf_sample_st *input,
sf_sample_st *output){
// pull out the state into local variables
float b0 = state->b0;
float b1 = state->b1;
float b2 = state->b2;
float a1 = state->a1;
float a2 = state->a2;
sf_sample_st xn1 = state->xn1;
sf_sample_st xn2 = state->xn2;
sf_sample_st yn1 = state->yn1;
sf_sample_st yn2 = state->yn2;
// loop for each sample
for (int n = 0; n < size; n++){
// get the current sample
sf_sample_st xn0 = input[n];
// the formula is the same for each channel
float L =
b0 * xn0.L +
b1 * xn1.L +
b2 * xn2.L -
a1 * yn1.L -
a2 * yn2.L;
float R =
b0 * xn0.R +
b1 * xn1.R +
b2 * xn2.R -
a1 * yn1.R -
a2 * yn2.R;
// save the result
output[n] = (sf_sample_st){ L, R };
// slide everything down one sample
xn2 = xn1;
xn1 = xn0;
yn2 = yn1;
yn1 = output[n];
}
// save the state for future processing
state->xn1 = xn1;
state->xn2 = xn2;
state->yn1 = yn1;
state->yn2 = yn2;
}
// each type of filter just has some magic math to setup the coefficients
//
// the math is quite complicated to understand, but the *implementation* is quite simple
//
// I have no insight into the genius of the math -- you're on your own for that. You might find
// some help in some of the articles here:
// http://www.musicdsp.org/showmany.php
//
// formulas extracted and massaged from Chromium source, Biquad.cpp, here:
// https://git.io/v10H2
// clear the samples saved across process boundaries
static inline void state_reset(sf_biquad_state_st *state){
state->xn1 = (sf_sample_st){ 0, 0 };
state->xn2 = (sf_sample_st){ 0, 0 };
state->yn1 = (sf_sample_st){ 0, 0 };
state->yn2 = (sf_sample_st){ 0, 0 };
}
// set the coefficients so that the output is the input scaled by `amt`
static inline void state_scale(sf_biquad_state_st *state, float amt){
state->b0 = amt;
state->b1 = 0.0f;
state->b2 = 0.0f;
state->a1 = 0.0f;
state->a2 = 0.0f;
}
// set the coefficients so that the output is an exact copy of the input
static inline void state_passthrough(sf_biquad_state_st *state){
state_scale(state, 1.0f);
}
// set the coefficients so that the output is zeroed out
static inline void state_zero(sf_biquad_state_st *state){
state_scale(state, 0.0f);
}
// initialize the biquad state to be a lowpass filter
void sf_lowpass(sf_biquad_state_st *state, int rate, float cutoff, float resonance){
state_reset(state);
float nyquist = rate * 0.5f;
cutoff /= nyquist;
if (cutoff >= 1.0f)
state_passthrough(state);
else if (cutoff <= 0.0f)
state_zero(state);
else{
resonance = powf(10.0f, resonance * 0.05f); // convert resonance from dB to linear
float theta = (float)M_PI * 2.0f * cutoff;
float alpha = sinf(theta) / (2.0f * resonance);
float cosw = cosf(theta);
float beta = (1.0f - cosw) * 0.5f;
float a0inv = 1.0f / (1.0f + alpha);
state->b0 = a0inv * beta;
state->b1 = a0inv * 2.0f * beta;
state->b2 = a0inv * beta;
state->a1 = a0inv * -2.0f * cosw;
state->a2 = a0inv * (1.0f - alpha);
}
}
void sf_highpass(sf_biquad_state_st *state, int rate, float cutoff, float resonance){
state_reset(state);
float nyquist = rate * 0.5f;
cutoff /= nyquist;
if (cutoff >= 1.0f)
state_zero(state);
else if (cutoff <= 0.0f)
state_passthrough(state);
else{
resonance = powf(10.0f, resonance * 0.05f); // convert resonance from dB to linear
float theta = (float)M_PI * 2.0f * cutoff;
float alpha = sinf(theta) / (2.0f * resonance);
float cosw = cosf(theta);
float beta = (1.0f + cosw) * 0.5f;
float a0inv = 1.0f / (1.0f + alpha);
state->b0 = a0inv * beta;
state->b1 = a0inv * -2.0f * beta;
state->b2 = a0inv * beta;
state->a1 = a0inv * -2.0f * cosw;
state->a2 = a0inv * (1.0f - alpha);
}
}
void sf_bandpass(sf_biquad_state_st *state, int rate, float freq, float Q){
state_reset(state);
float nyquist = rate * 0.5f;
freq /= nyquist;
if (freq <= 0.0f || freq >= 1.0f)
state_zero(state);
else if (Q <= 0.0f)
state_passthrough(state);
else{
float w0 = (float)M_PI * 2.0f * freq;
float alpha = sinf(w0) / (2.0f * Q);
float k = cosf(w0);
float a0inv = 1.0f / (1.0f + alpha);
state->b0 = a0inv * alpha;
state->b1 = 0;
state->b2 = a0inv * -alpha;
state->a1 = a0inv * -2.0f * k;
state->a2 = a0inv * (1.0f - alpha);
}
}
void sf_notch(sf_biquad_state_st *state, int rate, float freq, float Q){
state_reset(state);
float nyquist = rate * 0.5f;
freq /= nyquist;
if (freq <= 0.0f || freq >= 1.0f)
state_passthrough(state);
else if (Q <= 0.0f)
state_zero(state);
else{
float w0 = (float)M_PI * 2.0f * freq;
float alpha = sinf(w0) / (2.0f * Q);
float k = cosf(w0);
float a0inv = 1.0f / (1.0f + alpha);
state->b0 = a0inv;
state->b1 = a0inv * -2.0f * k;
state->b2 = a0inv;
state->a1 = a0inv * -2.0f * k;
state->a2 = a0inv * (1.0f - alpha);
}
}
void sf_peaking(sf_biquad_state_st *state, int rate, float freq, float Q, float gain){
state_reset(state);
float nyquist = rate * 0.5f;
freq /= nyquist;
if (freq <= 0.0f || freq >= 1.0f){
state_passthrough(state);
return;
}
float A = powf(10.0f, gain * 0.025f); // square root of gain converted from dB to linear
if (Q <= 0.0f){
state_scale(state, A * A); // scale by A squared
return;
}
float w0 = (float)M_PI * 2.0f * freq;
float alpha = sinf(w0) / (2.0f * Q);
float k = cosf(w0);
float a0inv = 1.0f / (1.0f + alpha / A);
state->b0 = a0inv * (1.0f + alpha * A);
state->b1 = a0inv * -2.0f * k;
state->b2 = a0inv * (1.0f - alpha * A);
state->a1 = a0inv * -2.0f * k;
state->a2 = a0inv * (1.0f - alpha / A);
}
void sf_allpass(sf_biquad_state_st *state, int rate, float freq, float Q){
state_reset(state);
float nyquist = rate * 0.5f;
freq /= nyquist;
if (freq <= 0.0f || freq >= 1.0f)
state_passthrough(state);
else if (Q <= 0.0f)
state_scale(state, -1.0f); // invert the sample
else{
float w0 = (float)M_PI * 2.0f * freq;
float alpha = sinf(w0) / (2.0f * Q);
float k = cosf(w0);
float a0inv = 1.0f / (1.0f + alpha);
state->b0 = a0inv * (1.0f - alpha);
state->b1 = a0inv * -2.0f * k;
state->b2 = a0inv * (1.0f + alpha);
state->a1 = a0inv * -2.0f * k;
state->a2 = a0inv * (1.0f - alpha);
}
}
// WebAudio hardcodes Q=1
void sf_lowshelf(sf_biquad_state_st *state, int rate, float freq, float Q, float gain){
state_reset(state);
float nyquist = rate * 0.5f;
freq /= nyquist;
if (freq <= 0.0f || Q == 0.0f){
state_passthrough(state);
return;
}
float A = powf(10.0f, gain * 0.025f); // square root of gain converted from dB to linear
if (freq >= 1.0f){
state_scale(state, A * A); // scale by A squared
return;
}
float w0 = (float)M_PI * 2.0f * freq;
float ainn = (A + 1.0f / A) * (1.0f / Q - 1.0f) + 2.0f;
if (ainn < 0)
ainn = 0;
float alpha = 0.5f * sinf(w0) * sqrtf(ainn);
float k = cosf(w0);
float k2 = 2.0f * sqrtf(A) * alpha;
float Ap1 = A + 1.0f;
float Am1 = A - 1.0f;
float a0inv = 1.0f / (Ap1 + Am1 * k + k2);
state->b0 = a0inv * A * (Ap1 - Am1 * k + k2);
state->b1 = a0inv * 2.0f * A * (Am1 - Ap1 * k);
state->b2 = a0inv * A * (Ap1 - Am1 * k - k2);
state->a1 = a0inv * -2.0f * (Am1 + Ap1 * k);
state->a2 = a0inv * (Ap1 + Am1 * k - k2);
}
// WebAudio hardcodes Q=1
void sf_highshelf(sf_biquad_state_st *state, int rate, float freq, float Q, float gain){
state_reset(state);
float nyquist = rate * 0.5f;
freq /= nyquist;
if (freq >= 1.0f || Q == 0.0f){
state_passthrough(state);
return;
}
float A = powf(10.0f, gain * 0.025f); // square root of gain converted from dB to linear
if (freq <= 0.0f){
state_scale(state, A * A); // scale by A squared
return;
}
float w0 = (float)M_PI * 2.0f * freq;
float ainn = (A + 1.0f / A) * (1.0f / Q - 1.0f) + 2.0f;
if (ainn < 0)
ainn = 0;
float alpha = 0.5f * sinf(w0) * sqrtf(ainn);
float k = cosf(w0);
float k2 = 2.0f * sqrtf(A) * alpha;
float Ap1 = A + 1.0f;
float Am1 = A - 1.0f;
float a0inv = 1.0f / (Ap1 - Am1 * k + k2);
state->b0 = a0inv * A * (Ap1 + Am1 * k + k2);
state->b1 = a0inv * -2.0f * A * (Am1 + Ap1 * k);
state->b2 = a0inv * A * (Ap1 + Am1 * k - k2);
state->a1 = a0inv * 2.0f * (Am1 - Ap1 * k);
state->a2 = a0inv * (Ap1 - Am1 * k - k2);
}