Skip to content

Commit 6b82498

Browse files
committed
BlockBiquad: Add shelf & peaking eq filters with "A" parameter
The formulae are untested but taken directly from the Cookbook. The `sqrt(A)` term is given a quick approximation via the famous Quake reciprocal square root routine.
1 parent 8b20c84 commit 6b82498

File tree

4 files changed

+142
-25
lines changed

4 files changed

+142
-25
lines changed

shared-bindings/synthio/BlockBiquad.c

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,19 +21,31 @@
2121
//| """A band-pass filter"""
2222
//| NOTCH: FilterMode
2323
//| """A notch filter"""
24+
//| LOW_SHELF: FilterMode
25+
//| """A notch filter"""
26+
//| HIGH_SHELF: FilterMode
27+
//| """A notch filter"""
28+
//| PEAKING_EQ: FilterMode
29+
//| """A notch filter"""
2430
//|
2531
//|
2632

2733
MAKE_ENUM_VALUE(synthio_filter_mode_type, mode, LOW_PASS, SYNTHIO_LOW_PASS);
2834
MAKE_ENUM_VALUE(synthio_filter_mode_type, mode, HIGH_PASS, SYNTHIO_HIGH_PASS);
2935
MAKE_ENUM_VALUE(synthio_filter_mode_type, mode, BAND_PASS, SYNTHIO_BAND_PASS);
3036
MAKE_ENUM_VALUE(synthio_filter_mode_type, mode, NOTCH, SYNTHIO_NOTCH);
37+
MAKE_ENUM_VALUE(synthio_filter_mode_type, mode, LOW_SHELF, SYNTHIO_LOW_SHELF);
38+
MAKE_ENUM_VALUE(synthio_filter_mode_type, mode, HIGH_SHELF, SYNTHIO_HIGH_SHELF);
39+
MAKE_ENUM_VALUE(synthio_filter_mode_type, mode, PEAKING_EQ, SYNTHIO_PEAKING_EQ);
3140

3241
MAKE_ENUM_MAP(synthio_filter_mode) {
3342
MAKE_ENUM_MAP_ENTRY(mode, LOW_PASS),
3443
MAKE_ENUM_MAP_ENTRY(mode, HIGH_PASS),
3544
MAKE_ENUM_MAP_ENTRY(mode, BAND_PASS),
3645
MAKE_ENUM_MAP_ENTRY(mode, NOTCH),
46+
MAKE_ENUM_MAP_ENTRY(mode, LOW_SHELF),
47+
MAKE_ENUM_MAP_ENTRY(mode, HIGH_SHELF),
48+
MAKE_ENUM_MAP_ENTRY(mode, PEAKING_EQ),
3749
};
3850

3951
static MP_DEFINE_CONST_DICT(synthio_filter_mode_locals_dict, synthio_filter_mode_locals_table);
@@ -52,8 +64,17 @@ static synthio_filter_mode validate_synthio_filter_mode(mp_obj_t obj, qstr arg_n
5264
//| mode: FilterMode,
5365
//| frequency: BlockInput,
5466
//| Q: BlockInput = 0.7071067811865475,
67+
//| A: BlockInput = None,
5568
//| ) -> None:
56-
//| """Construct a biquad filter object with dynamic center frequency & q factor
69+
//| """Construct a biquad filter object with given settings.
70+
//|
71+
//| ``frequency`` gives the center frequency or corner frequency of the filter,
72+
//| depending on the mode.
73+
//|
74+
//| ``Q`` gives the gain or sharpness of the filter.
75+
//|
76+
//| ``A`` controls the gain of peaking and shelving filters according to the
77+
//| formula ``A = 10^(dBgain/40)``. For other filter types it is ignored.
5778
//|
5879
//| Since ``frequency`` and ``Q`` are `BlockInput` objects, they can
5980
//| be varied dynamically. Internally, this is evaluated as "direct form 1"
@@ -70,6 +91,7 @@ static const mp_arg_t block_biquad_properties[] = {
7091
{ MP_QSTR_mode, MP_ARG_OBJ | MP_ARG_REQUIRED, {.u_obj = MP_OBJ_NULL } },
7192
{ MP_QSTR_frequency, MP_ARG_OBJ | MP_ARG_REQUIRED, {.u_obj = MP_OBJ_NULL } },
7293
{ MP_QSTR_Q, MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL } },
94+
{ MP_QSTR_A, MP_ARG_OBJ, {.u_obj = MP_ROM_NONE } },
7395
};
7496

7597
static mp_obj_t synthio_block_biquad_make_new(const mp_obj_type_t *type_in, size_t n_args, size_t n_kw, const mp_obj_t *all_args) {
@@ -141,10 +163,35 @@ MP_PROPERTY_GETSET(synthio_block_biquad_Q_obj,
141163
(mp_obj_t)&synthio_block_biquad_get_Q_obj,
142164
(mp_obj_t)&synthio_block_biquad_set_Q_obj);
143165

166+
//|
167+
//| A: BlockInput
168+
//| """The gain (A) of the filter
169+
//|
170+
//| This setting only has an effect for peaking and shelving EQ filters. It is related
171+
//| to the filter gain according to the formula ``A = 10^(dBgain/40)``.
172+
//| """
173+
//|
174+
static mp_obj_t synthio_block_biquad_get_A(mp_obj_t self_in) {
175+
synthio_block_biquad_t *self = MP_OBJ_TO_PTR(self_in);
176+
return common_hal_synthio_block_biquad_get_A(self);
177+
}
178+
MP_DEFINE_CONST_FUN_OBJ_1(synthio_block_biquad_get_A_obj, synthio_block_biquad_get_A);
179+
180+
static mp_obj_t synthio_block_biquad_set_A(mp_obj_t self_in, mp_obj_t arg) {
181+
synthio_block_biquad_t *self = MP_OBJ_TO_PTR(self_in);
182+
common_hal_synthio_block_biquad_set_A(self, arg);
183+
return mp_const_none;
184+
}
185+
MP_DEFINE_CONST_FUN_OBJ_2(synthio_block_biquad_set_A_obj, synthio_block_biquad_set_A);
186+
MP_PROPERTY_GETSET(synthio_block_biquad_A_obj,
187+
(mp_obj_t)&synthio_block_biquad_get_A_obj,
188+
(mp_obj_t)&synthio_block_biquad_set_A_obj);
189+
144190
static const mp_rom_map_elem_t synthio_block_biquad_locals_dict_table[] = {
145191
{ MP_ROM_QSTR(MP_QSTR_mode), MP_ROM_PTR(&synthio_block_biquad_mode_obj) },
146192
{ MP_ROM_QSTR(MP_QSTR_frequency), MP_ROM_PTR(&synthio_block_biquad_frequency_obj) },
147193
{ MP_ROM_QSTR(MP_QSTR_Q), MP_ROM_PTR(&synthio_block_biquad_Q_obj) },
194+
{ MP_ROM_QSTR(MP_QSTR_A), MP_ROM_PTR(&synthio_block_biquad_A_obj) },
148195
};
149196
static MP_DEFINE_CONST_DICT(synthio_block_biquad_locals_dict, synthio_block_biquad_locals_dict_table);
150197

shared-bindings/synthio/BlockBiquad.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,15 @@ extern const mp_obj_type_t synthio_filter_mode_type;
1313
typedef struct synthio_block_biquad synthio_block_biquad_t;
1414

1515
typedef enum {
16-
SYNTHIO_LOW_PASS, SYNTHIO_HIGH_PASS, SYNTHIO_BAND_PASS, SYNTHIO_NOTCH
16+
SYNTHIO_LOW_PASS, SYNTHIO_HIGH_PASS, SYNTHIO_BAND_PASS, SYNTHIO_NOTCH,
17+
// filters beyond this line use the "A" parameter (in addition to f0 and Q)
18+
SYNTHIO_PEAKING_EQ, SYNTHIO_LOW_SHELF, SYNTHIO_HIGH_SHELF
1719
} synthio_filter_mode;
1820

1921

22+
mp_obj_t common_hal_synthio_block_biquad_get_A(synthio_block_biquad_t *self);
23+
void common_hal_synthio_block_biquad_set_A(synthio_block_biquad_t *self, mp_obj_t A);
24+
2025
mp_obj_t common_hal_synthio_block_biquad_get_Q(synthio_block_biquad_t *self);
2126
void common_hal_synthio_block_biquad_set_Q(synthio_block_biquad_t *self, mp_obj_t Q);
2227

shared-module/synthio/BlockBiquad.c

Lines changed: 86 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,24 @@ typedef struct {
1414
mp_float_t s, c;
1515
} sincos_result_t;
1616

17+
#include <stdint.h> // uint32_t
18+
19+
// The famous Quake approximate square root function
20+
static mp_float_t Q_rsqrt(mp_float_t number_in) {
21+
float number = (float)number_in;
22+
union {
23+
float f;
24+
uint32_t i;
25+
} conv = { .f = (float)number };
26+
conv.i = 0x5f3759df - (conv.i >> 1);
27+
conv.f *= 1.5F - (number * 0.5F * conv.f * conv.f);
28+
return (mp_float_t)conv.f;
29+
}
30+
31+
static mp_float_t fast_sqrt(mp_float_t number) {
32+
return number * Q_rsqrt(number);
33+
}
34+
1735
#define FOUR_OVER_PI (4 / M_PI)
1836
static void fast_sincos(mp_float_t theta, sincos_result_t *result) {
1937
mp_float_t x = (theta * FOUR_OVER_PI) - 1;
@@ -48,6 +66,14 @@ void common_hal_synthio_block_biquad_set_Q(synthio_block_biquad_t *self, mp_obj_
4866
synthio_block_assign_slot(Q, &self->Q, MP_QSTR_Q);
4967
}
5068

69+
mp_obj_t common_hal_synthio_block_biquad_get_A(synthio_block_biquad_t *self) {
70+
return self->A.obj;
71+
}
72+
73+
void common_hal_synthio_block_biquad_set_A(synthio_block_biquad_t *self, mp_obj_t A) {
74+
synthio_block_assign_slot(A, &self->A, MP_QSTR_A);
75+
}
76+
5177
mp_obj_t common_hal_synthio_block_biquad_get_frequency(synthio_block_biquad_t *self) {
5278
return self->f0.obj;
5379
}
@@ -76,10 +102,14 @@ void common_hal_synthio_block_biquad_tick(mp_obj_t self_in, biquad_filter_state
76102

77103
mp_float_t W0 = synthio_block_slot_get(&self->f0) * synthio_global_W_scale;
78104
mp_float_t Q = synthio_block_slot_get(&self->Q);
105+
mp_float_t A =
106+
(self->mode >= SYNTHIO_PEAKING_EQ) ? synthio_block_slot_get(&self->A) : 0;
79107

80108
// n.b., assumes that the `mode` field is read-only
81109
// n.b., use of `&` is deliberate, avoids short-circuiting behavior
82-
if (float_equal_or_update(&self->cached_W0, W0) & float_equal_or_update(&self->cached_Q, Q)) {
110+
if (float_equal_or_update(&self->cached_W0, W0)
111+
& float_equal_or_update(&self->cached_Q, Q)
112+
& float_equal_or_update(&self->cached_A, A)) {
83113
return;
84114
}
85115

@@ -90,34 +120,69 @@ void common_hal_synthio_block_biquad_tick(mp_obj_t self_in, biquad_filter_state
90120

91121
mp_float_t a0, a1, a2, b0, b1, b2;
92122

93-
a0 = 1 + alpha;
94-
a1 = -2 * sc.c;
95-
a2 = 1 - alpha;
96-
97123
switch (self->mode) {
98124
default:
99-
case SYNTHIO_LOW_PASS:
100-
b2 = b0 = (1 - sc.c) * .5;
101-
b1 = 1 - sc.c;
102-
break;
125+
a0 = 1 + alpha;
126+
a1 = -2 * sc.c;
127+
a2 = 1 - alpha;
128+
129+
switch (self->mode) {
130+
default:
131+
case SYNTHIO_LOW_PASS:
132+
b2 = b0 = (1 - sc.c) * .5;
133+
b1 = 1 - sc.c;
134+
break;
135+
136+
case SYNTHIO_HIGH_PASS:
137+
b2 = b0 = (1 + sc.c) * .5;
138+
b1 = -(1 + sc.c);
139+
break;
140+
141+
case SYNTHIO_BAND_PASS:
142+
b0 = alpha;
143+
b1 = 0;
144+
b2 = -b0;
145+
break;
146+
147+
case SYNTHIO_NOTCH:
148+
b0 = 1;
149+
b1 = -2 * sc.c;
150+
b2 = 1;
151+
}
103152

104-
case SYNTHIO_HIGH_PASS:
105-
b2 = b0 = (1 + sc.c) * .5;
106-
b1 = -(1 + sc.c);
107153
break;
108154

109-
case SYNTHIO_BAND_PASS:
110-
b0 = alpha;
111-
b1 = 0;
112-
b2 = -b0;
155+
case SYNTHIO_PEAKING_EQ:
156+
b0 = 1 + alpha * A;
157+
b1 = -2 * sc.c;
158+
b2 = 1 + alpha * A;
159+
a0 = 1 + alpha / A;
160+
a1 = -2 * sc.c;
161+
a2 = 1 - alpha / A;
113162
break;
114163

115-
case SYNTHIO_NOTCH:
116-
b0 = 1;
117-
b1 = -2 * sc.c;
118-
b2 = 1;
164+
case SYNTHIO_LOW_SHELF: {
165+
mp_float_t sqrt_A = fast_sqrt(A);
166+
b0 = A * ((A + 1) - (A - 1) * sc.c + 2 * sqrt_A * alpha);
167+
b1 = 2 * A * ((A - 1) - (A + 1) * sc.c);
168+
b2 = A * ((A + 1) - (A - 1) * sc.c - 2 * sqrt_A * alpha);
169+
a0 = (A + 1) + (A - 1) * sc.c + 2 * sqrt_A * alpha;
170+
a1 = -2 * ((A - 1) + (A + 1) * sc.c);
171+
a2 = (A + 1) + (A - 1) * sc.c - 2 * sqrt_A * alpha;
172+
}
173+
break;
174+
175+
case SYNTHIO_HIGH_SHELF: {
176+
mp_float_t sqrt_A = fast_sqrt(A);
177+
b0 = A * ((A + 1) + (A - 1) * sc.c + 2 * sqrt_A * alpha);
178+
b1 = -2 * A * ((A - 1) + (A + 1) * sc.c);
179+
b2 = A * ((A + 1) + (A - 1) * sc.c - 2 * sqrt_A * alpha);
180+
a0 = (A + 1) - (A - 1) * sc.c + 2 * sqrt_A * alpha;
181+
a1 = 2 * ((A - 1) - (A + 1) * sc.c);
182+
a2 = (A + 1) - (A - 1) * sc.c - 2 * sqrt_A * alpha;
183+
}
184+
break;
119185
}
120-
121186
mp_float_t recip_a0 = 1 / a0;
122187

123188
filter_state->a1 = biquad_scale_arg_float(a1 * recip_a0);

shared-module/synthio/BlockBiquad.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@
1414
typedef struct synthio_block_biquad {
1515
mp_obj_base_t base;
1616
synthio_filter_mode mode;
17-
synthio_block_slot_t f0, Q;
18-
mp_float_t cached_W0, cached_Q;
17+
synthio_block_slot_t f0, Q, A;
18+
mp_float_t cached_W0, cached_Q, cached_A;
1919
} synthio_block_biquad_t;
2020

2121
void common_hal_synthio_block_biquad_tick(mp_obj_t self_in, biquad_filter_state *filter_state);

0 commit comments

Comments
 (0)