8000 Add BlockBiquad · adafruit/circuitpython@ce0c1c7 · GitHub
[go: up one dir, main page]

Skip to content

Commit ce0c1c7

Browse files
committed
Add BlockBiquad
BlockBiquad takes kind, f0 (center frequency) & Q (sharpness) block type arguments and calculates the actual filter coefficients every frame. This allows the filter characteristics f0 and Q to be changed dynamically from LFOs & arithmetic blocks. A new manual test demonstrates this on a host computer, playing a simple tone that is dynamically filtered.
1 parent 01dc1db commit ce0c1c7

File tree

10 files changed

+381
-6
lines changed

10 files changed

+381
-6
lines changed

shared-bindings/synthio/BlockBiquad.c

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
// This file is part of the CircuitPython project: https://circuitpython.org
2+
//
3+
// SPDX-FileCopyrightText: Copyright (c) 2023 Jeff Epler for Adafruit Industries
4+
//
5+
// SPDX-License-Identifier: MIT
6+
7+
#include "py/enum.h"
8+
#include "py/objproperty.h"
9+
#include "py/runtime.h"
10+
#include "shared-bindings/synthio/BlockBiquad.h"
11+
#include "shared-bindings/util.h"
12+
13+
//| class FilterType:
14+
//| LOW_PASS: FilterType
15+
//| HIGH_PASS: FilterType
16+
//| BAND_PASS: FilterType
17+
//|
18+
19+
MAKE_ENUM_VALUE(synthio_filter_type, kind, LOW_PASS, SYNTHIO_LOW_PASS);
20+
MAKE_ENUM_VALUE(synthio_filter_type, kind, HIGH_PASS, SYNTHIO_HIGH_PASS);
21+
MAKE_ENUM_VALUE(synthio_filter_type, kind, BAND_PASS, SYNTHIO_BAND_PASS);
22+
23+
MAKE_ENUM_MAP(synthio_filter) {
24+
MAKE_ENUM_MAP_ENTRY(kind, LOW_PASS),
25+
MAKE_ENUM_MAP_ENTRY(kind, HIGH_PASS),
26+
MAKE_ENUM_MAP_ENTRY(kind, BAND_PASS),
27+
};
28+
29+
static MP_DEFINE_CONST_DICT(synthio_filter_locals_dict, synthio_filter_locals_table);
30+
31+
MAKE_PRINTER(synthio, synthio_filter);
32+
33+
MAKE_ENUM_TYPE(synthio, FilterKind, synthio_filter);
34+
35+
static synthio_filter_e validate_synthio_filter(mp_obj_t obj, qstr arg_name) {
36+
return cp_enum_value(&synthio_filter_type, obj, arg_name);
37+
}
38+
39+
//| class BlockBiquad:
40+
//| def _init__(kind: FilterKind, f0: BlockInput, Q: BlockInput = 0.7071067811865475): ...
41+
42+
static const mp_arg_t block_biquad_properties[] = {
43+
{ MP_QSTR_kind, MP_ARG_OBJ | MP_ARG_REQUIRED, {.u_obj = MP_OBJ_NULL } },
44+
{ MP_QSTR_f0, MP_ARG_OBJ | MP_ARG_REQUIRED, {.u_obj = MP_OBJ_NULL } },
45+
{ MP_QSTR_Q, MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL } },
46+
};
47+
48+
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) {
49+
enum { ARG_kind, ARG_f0, ARG_Q };
50+
51+
mp_arg_val_t args[MP_ARRAY_SIZE(block_biquad_properties)];
52+
mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(block_biquad_properties), block_biquad_properties, args);
53+
54+
if (args[ARG_Q].u_obj == MP_OBJ_NULL) {
55+
args[ARG_Q].u_obj = mp_obj_new_float(MICROPY_FLOAT_CONST(0.7071067811865475));
56+
}
57+
58+
synthio_filter_e kind = validate_synthio_filter(args[ARG_kind].u_obj, MP_QSTR_kind);
59+
return common_hal_synthio_block_biquad_new(kind, args[ARG_f0].u_obj, args[ARG_Q].u_obj);
60+
}
61+
62+
//|
63+
//| kind: BiquadKind
64+
//| """The kind of filter (read-only)"""
65+
static mp_obj_t synthio_block_biquad_get_kind(mp_obj_t self_in) {
66+
synthio_block_biquad_t *self = MP_OBJ_TO_PTR(self_in);
67+
return cp_enum_find(&synthio_filter_type, common_hal_synthio_block_biquad_get_kind(self));
68+
}
69+
MP_DEFINE_CONST_FUN_OBJ_1(synthio_block_biquad_get_kind_obj, synthio_block_biquad_get_kind);
70+
71+
MP_PROPERTY_GETTER(synthio_block_biquad_kind_obj,
72+
(mp_obj_t)&synthio_block_biquad_get_kind_obj);
73+
74+
//|
75+
//| f0: BlockInput
76+
//| """The central frequency (in Hz) of the filter"""
77+
static mp_obj_t synthio_block_biquad_get_f0(mp_obj_t self_in) {
78+
synthio_block_biquad_t *self = MP_OBJ_TO_PTR(self_in);
79+
return common_hal_synthio_block_biquad_get_f0(self);
80+
}
81+
MP_DEFINE_CONST_FUN_OBJ_1(synthio_block_biquad_get_f0_obj, synthio_block_biquad_get_f0);
82+
83+
static mp_obj_t synthio_block_biquad_set_f0(mp_obj_t self_in, mp_obj_t arg) {
84+
synthio_block_biquad_t *self = MP_OBJ_TO_PTR(self_in);
85+
common_hal_synthio_block_biquad_set_f0(self, arg);
86+
return mp_const_none;
87+
}
88+
MP_DEFINE_CONST_FUN_OBJ_2(synthio_block_biquad_set_f0_obj, synthio_block_biquad_set_f0);
89+
MP_PROPERTY_GETSET(synthio_block_biquad_f0_obj,
90+
(mp_obj_t)&synthio_block_biquad_get_f0_obj,
91+
(mp_obj_t)&synthio_block_biquad_set_f0_obj);
92+
93+
94+
//|
95+
//| Q: BlockInput
96+
//| """The sharpness (Q) of the filter"""
97+
//|
98+
static mp_obj_t synthio_block_biquad_get_Q(mp_obj_t self_in) {
99+
synthio_block_biquad_t *self = MP_OBJ_TO_PTR(self_in);
100+
return common_hal_synthio_block_biquad_get_Q(self);
101+
}
102+
MP_DEFINE_CONST_FUN_OBJ_1(synthio_block_biquad_get_Q_obj, synthio_block_biquad_get_Q);
103+
104+
static mp_obj_t synthio_block_biquad_set_Q(mp_obj_t self_in, mp_obj_t arg) {
105+
synthio_block_biquad_t *self = MP_OBJ_TO_PTR(self_in);
106+
common_hal_synthio_block_biquad_set_Q(self, arg);
107+
return mp_const_none;
108+
}
109+
MP_DEFINE_CONST_FUN_OBJ_2(synthio_block_biquad_set_Q_obj, synthio_block_biquad_set_Q);
110+
MP_PROPERTY_GETSET(synthio_block_biquad_Q_obj,
111+
(mp_obj_t)&synthio_block_biquad_get_Q_obj,
112+
(mp_obj_t)&synthio_block_biquad_set_Q_obj);
113+
114+
static const mp_rom_map_elem_t synthio_block_biquad_locals_dict_table[] = {
115+
{ MP_ROM_QSTR(MP_QSTR_kind), MP_ROM_PTR(&synthio_block_biquad_kind_obj) },
116< 10000 code class="diff-text syntax-highlighted-line addition">+
{ MP_ROM_QSTR(MP_QSTR_f0), MP_ROM_PTR(&synthio_block_biquad_f0_obj) },
117+
{ MP_ROM_QSTR(MP_QSTR_Q), MP_ROM_PTR(&synthio_block_biquad_Q_obj) },
118+
};
119+
static MP_DEFINE_CONST_DICT(synthio_block_biquad_locals_dict, synthio_block_biquad_locals_dict_table);
120+
121+
static void block_biquad_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
122+
(void)kind;
123+
properties_print_helper(print, self_in, block_biquad_properties, MP_ARRAY_SIZE(block_biquad_properties));
124+
}
125+
126+
MP_DEFINE_CONST_OBJ_TYPE(
127+
synthio_block_biquad_type_obj,
128+
MP_QSTR_BlockBiquad,
129+
MP_TYPE_FLAG_HAS_SPECIAL_ACCESSORS,
130+
make_new, synthio_block_biquad_make_new,
131+
locals_dict, &synthio_block_biquad_locals_dict,
132+
print, block_biquad_print
133+
);

shared-bindings/synthio/BlockBiquad.h

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// This file is part of the CircuitPython project: https://circuitpython.org
2+
//
3+
// SPDX-FileCopyrightText: Copyright (c) 2023 Jeff Epler for Adafruit Industries
4+
//
5+
// SPDX-License-Identifier: MIT
6+
7+
#pragma once
8+
9+
#include "py/obj.h"
10+
11+
extern const mp_obj_type_t synthio_block_biquad_type_obj;
12+
extern const mp_obj_type_t synthio_filter_type;
13+
typedef struct synthio_block_biquad synthio_block_biquad_t;
14+
15+
typedef enum {
16+
SYNTHIO_LOW_PASS, SYNTHIO_HIGH_PASS, SYNTHIO_BAND_PASS
17+
} synthio_filter_e;
18+
19+
20+
mp_obj_t common_hal_synthio_block_biquad_get_Q(synthio_block_biquad_t *self);
21+
void common_hal_synthio_block_biquad_set_Q(synthio_block_biquad_t *self, mp_obj_t Q);
22+
23+
mp_obj_t common_hal_synthio_block_biquad_get_f0(synthio_block_biquad_t *self);
24+
void common_hal_synthio_block_biquad_set_f0(synthio_block_biquad_t *self, mp_obj_t f0);
25+
26+
synthio_filter_e common_hal_synthio_block_biquad_get_kind(synthio_block_biquad_t *self);
27+
28+
mp_obj_t common_hal_synthio_block_biquad_new(synthio_filter_e kind, mp_obj_t f0, mp_obj_t Q);

shared-bindings/synthio/__init__.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
#include "shared-bindings/synthio/__init__.h"
1919
#include "shared-bindings/synthio/Biquad.h"
20+
#include "shared-bindings/synthio/BlockBiquad.h"
2021
#include "shared-bindings/synthio/LFO.h"
2122
#include "shared-bindings/synthio/Math.h"
2223
#include "shared-bindings/synthio/MidiTrack.h"
@@ -307,6 +308,8 @@ MP_DEFINE_CONST_FUN_OBJ_VAR(synthio_lfo_tick_obj, 1, synthio_lfo_tick);
307308
static const mp_rom_map_elem_t synthio_module_globals_table[] = {
308309
{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_synthio) },
309310
{ MP_ROM_QSTR(MP_QSTR_Biquad), MP_ROM_PTR(&synthio_biquad_type_obj) },
311+
{ MP_ROM_QSTR(MP_QSTR_BlockBiquad), MP_ROM_PTR(&synthio_block_biquad_type_obj) },
312+
{ MP_ROM_QSTR(MP_QSTR_FilterType), MP_ROM_PTR(&synthio_filter_type) },
310313
{ MP_ROM_QSTR(MP_QSTR_Math), MP_ROM_PTR(&synthio_math_type) },
311314
{ MP_ROM_QSTR(MP_QSTR_MathOperation), MP_ROM_PTR(&synthio_math_operation_type) },
312315
{ MP_ROM_QSTR(MP_QSTR_MidiTrack), MP_ROM_PTR(&synthio_miditrack_type) },

shared-module/synthio/Biquad.c

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
#include <math.h>
88
#include "shared-bindings/synthio/Biquad.h"
9+
#include "shared-bindings/synthio/BlockBiquad.h"
910
#include "shared-module/synthio/Biquad.h"
1011

1112
mp_obj_t common_hal_synthio_new_lpf(mp_float_t w0, mp_float_t Q) {
@@ -74,20 +75,27 @@ mp_obj_t common_hal_synthio_new_bpf(mp_float_t w0, mp_float_t Q) {
7475
return namedtuple_make_new((const mp_obj_type_t *)&synthio_biquad_type_obj, MP_ARRAY_SIZE(out_args), 0, out_args);
7576
}
7677

77-
#define BIQUAD_SHIFT (15)
7878
static int32_t biquad_scale_arg_obj(mp_obj_t arg) {
7979
return (int32_t)MICROPY_FLOAT_C_FUN(round)(MICROPY_FLOAT_C_FUN(ldexp)(mp_obj_get_float(arg), BIQUAD_SHIFT));
8080
}
8181
void synthio_biquad_filter_assign(biquad_filter_state *st, mp_obj_t biquad_obj) {
82-
if (biquad_obj != mp_const_none) {
83-
mp_arg_validate_type(biquad_obj, (const mp_obj_type_t *)&synthio_biquad_type_obj, MP_QSTR_filter);
82+
if (biquad_obj == mp_const_none) {
83+
return;
84+
}
85+
if (mp_obj_is_type(biquad_obj, &synthio_block_biquad_type_obj)) {
86+
return;
87+
}
88+
if (mp_obj_is_type(biquad_obj, (const mp_obj_type_t *)&synthio_biquad_type_obj)) {
8489
mp_obj_tuple_t *biquad = (mp_obj_tuple_t *)MP_OBJ_TO_PTR(biquad_obj);
8590
st->a1 = biquad_scale_arg_obj(biquad->items[0]);
8691
st->a2 = biquad_scale_arg_obj(biquad->items[1]);
8792
st->b0 = biquad_scale_arg_obj(biquad->items[2]);
8893
st->b1 = biquad_scale_arg_obj(biquad->items[3]);
8994
st->b2 = biquad_scale_arg_obj(biquad->items[4]);
95+
return;
9096
}
97+
mp_raise_TypeError_varg(MP_ERROR_TEXT("%q must be of type %q or %q, not %q"), MP_QSTR_filter, MP_QSTR_Biquad, MP_QSTR_BlockBiquad, mp_obj_get_type(biquad_obj)->name);
98+
9199
}
92100

93101
void synthio_biquad_filter_reset(biquad_filter_state *st) {

shared-module/synthio/Biquad.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88

99
#include "py/obj.h"
1010

11+
#define BIQUAD_SHIFT (15)
12+
1113
typedef struct {
1214
int32_t a1, a2, b0, b1, b2;
1315
int32_t x[2], y[2];

shared-module/synthio/BlockBiquad.c

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
2+
// This file is part of the CircuitPython project: https://circuitpython.org
3 F438 +
//
4+
// SPDX-FileCopyrightText: Copyright (c) 2023 Jeff Epler for Adafruit Industries
5+
//
6+
// SPDX-License-Identifier: MIT
7+
8+
#include <math.h>
9+
#include "shared-bindings/synthio/BlockBiquad.h"
10+
#include "shared-module/synthio/BlockBiquad.h"
11+
#include "shared-module/synthio/block.h"
12+
13+
typedef struct {
14+
mp_float_t s, c;
15+
} sincos_result_t;
16+
17+
#define FOUR_OVER_PI (4 / M_PI)
18+
static void fast_sincos(mp_float_t theta, sincos_result_t *result) {
19+
mp_float_t x = (theta * FOUR_OVER_PI) - 1;
20+
mp_float_t x2 = x * x, x3 = x2 * x, x4 = x2 * x2, x5 = x2 * x3;
21+
mp_float_t c0 = 0.70708592,
22+
c1x = -0.55535724 * x,
23+
c2x2 = -0.21798592 * x2,
24+
c3x3 = 0.05707685 * x3,
25+
c4x4 = 0.0109 * x4,
26+
c5x5 = -0.00171961 * x5;
27+
28+
mp_float_t evens = c4x4 + c2x2 + c0, odds = c5x5 + c3x3 + c1x;
29+
result->c = evens + odds;
30+
result->s = evens - odds;
31+
}
32+
33+
mp_obj_t common_hal_synthio_block_biquad_new(synthio_filter_e kind, mp_obj_t f0, mp_obj_t Q) {
34+
synthio_block_biquad_t *self = mp_obj_malloc(synthio_block_biquad_t, &synthio_block_biquad_type_obj);
35+
self->kind = kind;
36+
synthio_block_assign_slot(f0, &self->f0, MP_QSTR_f0);
37+
synthio_block_assign_slot(Q, &self->Q, MP_QSTR_Q);
38+
return MP_OBJ_FROM_PTR(self);
39+
}
40+
41+
synthio_filter_e common_hal_synthio_block_biquad_get_kind(synthio_block_biquad_t *self) {
42+
return self->kind;
43+
}
44+
45+
mp_obj_t common_hal_synthio_block_biquad_get_Q(synthio_block_biquad_t *self) {
46+
return self->Q.obj;
47+
}
48+
49+
void common_hal_synthio_block_biquad_set_Q(synthio_block_biquad_t *self, mp_obj_t Q) {
50+
synthio_block_assign_slot(Q, &self->Q, MP_QSTR_Q);
51+
}
52+
53+
mp_obj_t common_hal_synthio_block_biquad_get_f0(synthio_block_biquad_t *self) {
54+
return self->f0.obj;
55+
}
56+
57+
void common_hal_synthio_block_biquad_set_f0(synthio_block_biquad_t *self, mp_obj_t f0) {
58+
synthio_block_assign_slot(f0, &self->f0, MP_QSTR_f0);
59+
}
60+
61+
static int32_t biquad_scale_arg_float(mp_float_t arg) {
62+
return (int32_t)MICROPY_FLOAT_C_FUN(round)(MICROPY_FLOAT_C_FUN(ldexp)(arg, BIQUAD_SHIFT));
63+
}
64+
65+
void common_hal_synthio_block_biquad_tick(mp_obj_t self_in, biquad_filter_state *filter_state) {
66+
synthio_block_biquad_t *self = MP_OBJ_TO_PTR(self_in);
67+
68+
mp_float_t W0 = synthio_block_slot_get(&self->f0) * synthio_global_W_scale;
69+
mp_float_t Q = synthio_block_slot_get(&self->Q);
70+
71+
sincos_result_t sc;
72+
fast_sincos(W0, &sc);
73+
74+
mp_float_t alpha = sc.s / (2 * Q);
75+
76+
mp_float_t a0, a1, a2, b0, b1, b2;
77+
78+
a0 = 1 + alpha;
79+
a1 = -2 * sc.c;
80+
a2 = 1 - alpha;
81+
82+
switch (self->kind) {
83+
default:
84+
case SYNTHIO_LOW_PASS:
85+
b2 = b0 = (1 - sc.c) * .5;
86+
b1 = 1 - sc.c;
87+
break;
88+
89+
case SYNTHIO_HIGH_PASS:
90+
b2 = b0 = (1 + sc.c) * .5;
91+
b1 = -(1 + sc.c);
92+
break;
93+
94+
case SYNTHIO_BAND_PASS:
95+
b0 = alpha;
96+
b1 = 0;
97+
b2 = -b0;
98+
}
99+
100+
mp_float_t recip_a0 = 1 / a0;
101+
102+
filter_state->a1 = biquad_scale_arg_float(a1 * recip_a0);
103+
filter_state->a2 = biquad_scale_arg_float(a2 * recip_a0);
104+
filter_state->b0 = biquad_scale_arg_float(b0 * recip_a0);
105+
filter_state->b1 = biquad_scale_arg_float(b1 * recip_a0);
106+
filter_state->b2 = biquad_scale_arg_float(b2 * recip_a0);
107+
}

shared-module/synthio/BlockBiquad.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
2+
// This file is part of the CircuitPython project: https://circuitpython.org
3+
//
4+
// SPDX-FileCopyrightText: Copyright (c) 2023 Jeff Epler for Adafruit Industries
5+
//
6+
// SPDX-License-Identifier: MIT
7+
8+
#pragma once
9+
10+
#include "shared-bindings/synthio/BlockBiquad.h"
11+
#include "shared-module/synthio/Biquad.h"
12+
#include "shared-module/synthio/block.h"
13+
14+
typedef struct synthio_block_biquad {
15+
mp_obj_base_t base;
16+
synthio_filter_e kind;
17+
synthio_block_slot_t f0, Q;
18+
} synthio_block_biquad_t;
19+
20+
void common_hal_synthio_block_biquad_tick(mp_obj_t self_in, biquad_filter_state *filter_state);

< CDA2 a class="Link--primary prc-Link-Link-85e08" href="#diff-a1625340f4b32691139941c449954155f65de68e45455bdea310721cced9f3e4" data-analytics-opt-out="true">shared-module/synthio/__init__.c

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,15 @@
88
#include "shared-module/synthio/__init__.h"
99
#include "shared-bindings/synthio/__init__.h"
1010
#include "shared-module/synthio/Biquad.h"
11+
#include "shared-module/synthio/BlockBiquad.h"
1112
#include "shared-module/synthio/Note.h"
1213
#include "py/runtime.h"
1314
#include <math.h>
1415
#include <stdlib.h>
1516

16-
mp_float_t synthio_global_rate_scale;
17+
#define MP_PI MICROPY_FLOAT_CONST(3.14159265358979323846)
18+
19+
mp_float_t synthio_global_rate_scale, synthio_global_W_scale;
1720
uint8_t synthio_global_tick;
1821

1922
static const int16_t square_wave[] = {-32768, 32767};
@@ -335,6 +338,9 @@ void synthio_synth_synthesize(synthio_synth_t *synth, uint8_t **bufptr, uint32_t
335338
mp_obj_t filter_obj = synthio_synth_get_note_filter(note_obj);
336339
if (filter_obj != mp_const_none) {
337340
synthio_note_obj_t *note = MP_OBJ_TO_PTR(note_obj);
341+
if (mp_obj_is_type(filter_obj, &synthio_block_biquad_type_obj)) {
342+
common_hal_synthio_block_biquad_tick(filter_obj, &note->filter_state);
343+
}
338344
synthio_biquad_filter_samples(&note->filter_state, tmp_buffer32, dur);
339345
}
340346

@@ -490,7 +496,9 @@ uint32_t synthio_frequency_convert_scaled_to_dds(uint64_t frequency_scaled, int3
490496
}
491497

492498
void shared_bindings_synthio_lfo_tick(uint32_t sample_rate) {
493-
synthio_global_rate_scale = (mp_float_t)SYNTHIO_MAX_DUR / sample_rate;
499+
mp_float_t recip_sample_rate = MICROPY_FLOAT_CONST(1.) / sample_rate;
500+
synthio_global_rate_scale = SYNTHIO_MAX_DUR * recip_sample_rate;
501+
synthio_global_W_scale = (2 * MP_PI) * recip_sample_rate;
494502
synthio_global_tick++;
495503
}
496504

0 commit comments

Comments
 (0)
0