Skip to content

Commit 92ef410

Browse files
authored
Merge pull request adafruit#9756 from jepler/synthio-blockbiquad
synthio: Add a form of biquad filter that uses BlockInputs
2 parents c111b2f + 411ba7a commit 92ef410

File tree

16 files changed

+1097
-16
lines changed

16 files changed

+1097
-16
lines changed

conf.py

+1
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,7 @@ def autoapi_prepare_jinja_env(jinja_env):
225225
"ports/nordic/peripherals",
226226
"ports/nordic/usb",
227227
"ports/raspberrypi/sdk",
228+
"ports/raspberrypi/pioasm",
228229
"ports/raspberrypi/lib",
229230
"ports/silabs/gecko_sdk",
230231
"ports/silabs/tools",

ports/unix/variants/coverage/mpconfigvariant.mk

+2
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ SRC_BITMAP := \
6060
shared-bindings/synthio/LFO.c \
6161
shared-bindings/synthio/Note.c \
6262
shared-bindings/synthio/Biquad.c \
63+
shared-bindings/synthio/BlockBiquad.c \
6364
shared-bindings/synthio/Synthesizer.c \
6465
shared-bindings/traceback/__init__.c \
6566
shared-bindings/util.c \
@@ -100,6 +101,7 @@ SRC_BITMAP := \
100101
shared-module/synthio/LFO.c \
101102
shared-module/synthio/Note.c \
102103
shared-module/synthio/Biquad.c \
104+
shared-module/synthio/BlockBiquad.c \
103105
shared-module/synthio/Synthesizer.c \
104106
shared-bindings/vectorio/Circle.c \
105107
shared-module/vectorio/Circle.c \

py/circuitpy_defns.mk

+1
Original file line numberDiff line numberDiff line change
@@ -717,6 +717,7 @@ SRC_SHARED_MODULE_ALL = \
717717
supervisor/__init__.c \
718718
supervisor/StatusBar.c \
719719
synthio/Biquad.c \
720+
synthio/BlockBiquad.c \
720721
synthio/LFO.c \
721722
synthio/Math.c \
722723
synthio/MidiTrack.c \

shared-bindings/synthio/BlockBiquad.c

+158
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
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 FilterMode:
14+
//| """The type of filter"""
15+
//|
16+
//| LOW_PASS: FilterMode
17+
//| """A low-pass filter"""
18+
//| HIGH_PASS: FilterMode
19+
//| """A high-pass filter"""
20+
//| BAND_PASS: FilterMode
21+
//| """A band-pass filter"""
22+
//| NOTCH: FilterMode
23+
//| """A notch filter"""
24+
//|
25+
26+
MAKE_ENUM_VALUE(synthio_filter_mode_type, mode, LOW_PASS, SYNTHIO_LOW_PASS);
27+
MAKE_ENUM_VALUE(synthio_filter_mode_type, mode, HIGH_PASS, SYNTHIO_HIGH_PASS);
28+
MAKE_ENUM_VALUE(synthio_filter_mode_type, mode, BAND_PASS, SYNTHIO_BAND_PASS);
29+
MAKE_ENUM_VALUE(synthio_filter_mode_type, mode, NOTCH, SYNTHIO_NOTCH);
30+
31+
MAKE_ENUM_MAP(synthio_filter_mode) {
32+
MAKE_ENUM_MAP_ENTRY(mode, LOW_PASS),
33+
MAKE_ENUM_MAP_ENTRY(mode, HIGH_PASS),
34+
MAKE_ENUM_MAP_ENTRY(mode, BAND_PASS),
35+
MAKE_ENUM_MAP_ENTRY(mode, NOTCH),
36+
};
37+
38+
static MP_DEFINE_CONST_DICT(synthio_filter_mode_locals_dict, synthio_filter_mode_locals_table);
39+
40+
MAKE_PRINTER(synthio, synthio_filter_mode);
41+
42+
MAKE_ENUM_TYPE(synthio, FilterMode, synthio_filter_mode);
43+
44+
static synthio_filter_mode validate_synthio_filter_mode(mp_obj_t obj, qstr arg_name) {
45+
return cp_enum_value(&synthio_filter_mode_type, obj, arg_name);
46+
}
47+
48+
//| class BlockBiquad:
49+
//| def __init__(
50+
//| self,
51+
//| mode: FilterMode,
52+
//| frequency: BlockInput,
53+
//| Q: BlockInput = 0.7071067811865475,
54+
//| ) -> None:
55+
//| """Construct a biquad filter object with dynamic center frequency & q factor
56+
//|
57+
//| Since ``frequency`` and ``Q`` are `BlockInput` objects, they can
58+
//| be varied dynamically. Internally, this is evaluated as "direct form 1"
59+
//| biquad filter.
60+
//|
61+
//| The internal filter state x[] and y[] is not updated when the filter
62+
//| coefficients change, and there is no theoretical justification for why
63+
//| this should result in a stable filter output. However, in practice,
64+
//| slowly varying the filter's characteristic frequency and sharpness
65+
//| appears to work as you'd expect."""
66+
67+
static const mp_arg_t block_biquad_properties[] = {
68+
{ MP_QSTR_mode, MP_ARG_OBJ | MP_ARG_REQUIRED, {.u_obj = MP_OBJ_NULL } },
69+
{ MP_QSTR_frequency, MP_ARG_OBJ | MP_ARG_REQUIRED, {.u_obj = MP_OBJ_NULL } },
70+
{ MP_QSTR_Q, MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL } },
71+
};
72+
73+
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) {
74+
enum { ARG_mode, ARG_frequency, ARG_Q };
75+
76+
mp_arg_val_t args[MP_ARRAY_SIZE(block_biquad_properties)];
77+
mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(block_biquad_properties), block_biquad_properties, args);
78+
79+
if (args[ARG_Q].u_obj == MP_OBJ_NULL) {
80+
args[ARG_Q].u_obj = mp_obj_new_float(MICROPY_FLOAT_CONST(0.7071067811865475));
81+
}
82+
83+
synthio_filter_mode mode = validate_synthio_filter_mode(args[ARG_mode].u_obj, MP_QSTR_mode);
84+
return common_hal_synthio_block_biquad_new(mode, args[ARG_frequency].u_obj, args[ARG_Q].u_obj);
85+
}
86+
87+
//|
88+
//| mode: FilterMode
89+
//| """The mode of filter (read-only)"""
90+
static mp_obj_t synthio_block_biquad_get_mode(mp_obj_t self_in) {
91+
synthio_block_biquad_t *self = MP_OBJ_TO_PTR(self_in);
92+
return cp_enum_find(&synthio_filter_mode_type, common_hal_synthio_block_biquad_get_mode(self));
93+
}
94+
MP_DEFINE_CONST_FUN_OBJ_1(synthio_block_biquad_get_mode_obj, synthio_block_biquad_get_mode);
95+
96+
MP_PROPERTY_GETTER(synthio_block_biquad_mode_obj,
97+
(mp_obj_t)&synthio_block_biquad_get_mode_obj);
98+
99+
//|
100+
//| frequency: BlockInput
101+
//| """The central frequency (in Hz) of the filter"""
102+
static mp_obj_t synthio_block_biquad_get_frequency(mp_obj_t self_in) {
103+
synthio_block_biquad_t *self = MP_OBJ_TO_PTR(self_in);
104+
return common_hal_synthio_block_biquad_get_frequency(self);
105+
}
106+
MP_DEFINE_CONST_FUN_OBJ_1(synthio_block_biquad_get_frequency_obj, synthio_block_biquad_get_frequency);
107+
108+
static mp_obj_t synthio_block_biquad_set_frequency(mp_obj_t self_in, mp_obj_t arg) {
109+
synthio_block_biquad_t *self = MP_OBJ_TO_PTR(self_in);
110+
common_hal_synthio_block_biquad_set_frequency(self, arg);
111+
return mp_const_none;
112+
}
113+
MP_DEFINE_CONST_FUN_OBJ_2(synthio_block_biquad_set_frequency_obj, synthio_block_biquad_set_frequency);
114+
MP_PROPERTY_GETSET(synthio_block_biquad_frequency_obj,
115+
(mp_obj_t)&synthio_block_biquad_get_frequency_obj,
116+
(mp_obj_t)&synthio_block_biquad_set_frequency_obj);
117+
118+
119+
//|
120+
//| Q: BlockInput
121+
//| """The sharpness (Q) of the filter"""
122+
//|
123+
static mp_obj_t synthio_block_biquad_get_Q(mp_obj_t self_in) {
124+
synthio_block_biquad_t *self = MP_OBJ_TO_PTR(self_in);
125+
return common_hal_synthio_block_biquad_get_Q(self);
126+
}
127+
MP_DEFINE_CONST_FUN_OBJ_1(synthio_block_biquad_get_Q_obj, synthio_block_biquad_get_Q);
128+
129+
static mp_obj_t synthio_block_biquad_set_Q(mp_obj_t self_in, mp_obj_t arg) {
130+
synthio_block_biquad_t *self = MP_OBJ_TO_PTR(self_in);
131+
common_hal_synthio_block_biquad_set_Q(self, arg);
132+
return mp_const_none;
133+
}
134+
MP_DEFINE_CONST_FUN_OBJ_2(synthio_block_biquad_set_Q_obj, synthio_block_biquad_set_Q);
135+
MP_PROPERTY_GETSET(synthio_block_biquad_Q_obj,
136+
(mp_obj_t)&synthio_block_biquad_get_Q_obj,
137+
(mp_obj_t)&synthio_block_biquad_set_Q_obj);
138+
139+
static const mp_rom_map_elem_t synthio_block_biquad_locals_dict_table[] = {
140+
{ MP_ROM_QSTR(MP_QSTR_mode), MP_ROM_PTR(&synthio_block_biquad_mode_obj) },
141+
{ MP_ROM_QSTR(MP_QSTR_frequency), MP_ROM_PTR(&synthio_block_biquad_frequency_obj) },
142+
{ MP_ROM_QSTR(MP_QSTR_Q), MP_ROM_PTR(&synthio_block_biquad_Q_obj) },
143+
};
144+
static MP_DEFINE_CONST_DICT(synthio_block_biquad_locals_dict, synthio_block_biquad_locals_dict_table);
145+
146+
static void block_biquad_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
147+
(void)kind;
148+
properties_print_helper(print, self_in, block_biquad_properties, MP_ARRAY_SIZE(block_biquad_properties));
149+
}
150+
151+
MP_DEFINE_CONST_OBJ_TYPE(
152+
synthio_block_biquad_type_obj,
153+
MP_QSTR_BlockBiquad,
154+
MP_TYPE_FLAG_HAS_SPECIAL_ACCESSORS,
155+
make_new, synthio_block_biquad_make_new,
156+
locals_dict, &synthio_block_biquad_locals_dict,
157+
print, block_biquad_print
158+
);

shared-bindings/synthio/BlockBiquad.h

+28
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_mode_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, SYNTHIO_NOTCH
17+
} synthio_filter_mode;
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_frequency(synthio_block_biquad_t *self);
24+
void common_hal_synthio_block_biquad_set_frequency(synthio_block_biquad_t *self, mp_obj_t frequency);
25+
26+
synthio_filter_mode common_hal_synthio_block_biquad_get_mode(synthio_block_biquad_t *self);
27+
28+
mp_obj_t common_hal_synthio_block_biquad_new(synthio_filter_mode mode, mp_obj_t frequency, mp_obj_t Q);

shared-bindings/synthio/Synthesizer.c

+6-10
Original file line numberDiff line numberDiff line change
@@ -287,13 +287,13 @@ MP_PROPERTY_GETTER(synthio_synthesizer_blocks_obj,
287287
//| """Maximum polyphony of the synthesizer (read-only class property)"""
288288
//|
289289

290-
//| def low_pass_filter(cls, frequency: float, q_factor: float = 0.7071067811865475) -> Biquad:
290+
//| def low_pass_filter(cls, frequency: float, Q: float = 0.7071067811865475) -> Biquad:
291291
//| """Construct a low-pass filter with the given parameters.
292292
//|
293293
//| ``frequency``, called f0 in the cookbook, is the corner frequency in Hz
294294
//| of the filter.
295295
//|
296-
//| ``q_factor``, called ``Q`` in the cookbook. Controls how peaked the response will be at the cutoff frequency. A large value makes the response more peaked.
296+
//| ``Q`` controls how peaked the response will be at the cutoff frequency. A large value makes the response more peaked.
297297
//| """
298298

299299
enum passfilter_arg_e { ARG_f0, ARG_Q };
@@ -328,15 +328,13 @@ static mp_obj_t synthio_synthesizer_lpf(size_t n_pos, const mp_obj_t *pos_args,
328328

329329
MP_DEFINE_CONST_FUN_OBJ_KW(synthio_synthesizer_lpf_fun_obj, 1, synthio_synthesizer_lpf);
330330

331-
//| def high_pass_filter(
332-
//| cls, frequency: float, q_factor: float = 0.7071067811865475
333-
//| ) -> Biquad:
331+
//| def high_pass_filter(cls, frequency: float, Q: float = 0.7071067811865475) -> Biquad:
334332
//| """Construct a high-pass filter with the given parameters.
335333
//|
336334
//| ``frequency``, called f0 in the cookbook, is the corner frequency in Hz
337335
//| of the filter.
338336
//|
339-
//| ``q_factor``, called ``Q`` in the cookbook. Controls how peaked the response will be at the cutoff frequency. A large value makes the response more peaked.
337+
//| ``Q`` controls how peaked the response will be at the cutoff frequency. A large value makes the response more peaked.
340338
//| """
341339

342340
static mp_obj_t synthio_synthesizer_hpf(size_t n_pos, const mp_obj_t *pos_args, mp_map_t *kw_args) {
@@ -358,15 +356,13 @@ static mp_obj_t synthio_synthesizer_hpf(size_t n_pos, const mp_obj_t *pos_args,
358356

359357
}
360358

361-
//| def band_pass_filter(
362-
//| cls, frequency: float, q_factor: float = 0.7071067811865475
363-
//| ) -> Biquad:
359+
//| def band_pass_filter(cls, frequency: float, Q: float = 0.7071067811865475) -> Biquad:
364360
//| """Construct a band-pass filter with the given parameters.
365361
//|
366362
//| ``frequency``, called f0 in the cookbook, is the center frequency in Hz
367363
//| of the filter.
368364
//|
369-
//| ``q_factor``, called ``Q`` in the cookbook. Controls how peaked the response will be at the cutoff frequency. A large value makes the response more peaked.
365+
//| ``Q`` Controls how peaked the response will be at the cutoff frequency. A large value makes the response more peaked.
370366
//|
371367
//| The coefficients are scaled such that the filter has a 0dB peak gain.
372368
//| """

shared-bindings/synthio/__init__.c

+3
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_FilterMode), MP_ROM_PTR(&synthio_filter_mode_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

+11-3
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

+2
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];

0 commit comments

Comments
 (0)