Skip to content

Commit

Permalink
Merge pull request #9804 from jepler/synthio-blockbiquad-morefilters
Browse files Browse the repository at this point in the history
Add BlockBiquad shelf & peaking, AudioFilter support
  • Loading branch information
tannewt authored Feb 14, 2025
2 parents 886d225 + ad87adb commit 3f2c1e0
Show file tree
Hide file tree
Showing 19 changed files with 102,676 additions and 92 deletions.
10 changes: 10 additions & 0 deletions ports/atmel-samd/boards/pybadge/mpconfigboard.mk
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,13 @@ CIRCUITPY_SPITARGET = 0
CIRCUITPY_STAGE = 1

FROZEN_MPY_DIRS += $(TOP)/frozen/circuitpython-stage/pybadge

# We don't have room for the fonts for terminalio for certain languages,
# so turn off terminalio, and if it's off and displayio is on,
# force a clean build.
# Note that we cannot test $(CIRCUITPY_DISPLAYIO) directly with an
# ifeq, because it's not set yet.
ifneq (,$(filter $(TRANSLATION),ja ko ru))
CIRCUITPY_TERMINALIO = 0
RELEASE_NEEDS_CLEAN_BUILD = $(CIRCUITPY_DISPLAYIO)
endif
10 changes: 10 additions & 0 deletions ports/atmel-samd/boards/pygamer/mpconfigboard.mk
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,13 @@ CIRCUITPY_SPITARGET = 0
CIRCUITPY_STAGE = 1

FROZEN_MPY_DIRS += $(TOP)/frozen/circuitpython-stage/pygamer

# We don't have room for the fonts for terminalio for certain languages,
# so turn off terminalio, and if it's off and displayio is on,
# force a clean build.
# Note that we cannot test $(CIRCUITPY_DISPLAYIO) directly with an
# ifeq, because it's not set yet.
ifneq (,$(filter $(TRANSLATION),ja ko ru))
CIRCUITPY_TERMINALIO = 0
RELEASE_NEEDS_CLEAN_BUILD = $(CIRCUITPY_DISPLAYIO)
endif
8 changes: 4 additions & 4 deletions shared-bindings/audiofilters/Filter.c
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
//|
//| def __init__(
//| self,
//| filter: Optional[synthio.Biquad | Tuple[synthio.Biquad]] = None,
//| filter: Optional[synthio.AnyBiquad | Tuple[synthio.AnyBiquad]] = None,
//| mix: synthio.BlockInput = 1.0,
//| buffer_size: int = 512,
//| sample_rate: int = 8000,
Expand All @@ -39,7 +39,7 @@
//| The mix parameter allows you to change how much of the unchanged sample passes through to
//| the output to how much of the effect audio you hear as the output.
//|
//| :param Optional[synthio.Biquad|Tuple[synthio.Biquad]] filter: A normalized biquad filter object or tuple of normalized biquad filter objects. The sample is processed sequentially by each filter to produce the output samples.
//| :param Optional[synthio.AnyBiquad|Tuple[synthio.AnyBiquad]] filter: A normalized biquad filter object or tuple of normalized biquad filter objects. The sample is processed sequentially by each filter to produce the output samples.
//| :param synthio.BlockInput mix: The mix as a ratio of the sample (0.0) to the effect (1.0).
//| :param int buffer_size: The total size in bytes of each of the two playback buffers to use
//| :param int sample_rate: The sample rate to be used
Expand Down Expand Up @@ -73,7 +73,7 @@
static mp_obj_t audiofilters_filter_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) {
enum { ARG_filter, ARG_mix, ARG_buffer_size, ARG_sample_rate, ARG_bits_per_sample, ARG_samples_signed, ARG_channel_count, };
static const mp_arg_t allowed_args[] = {
{ MP_QSTR_filter, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_OBJ_NULL} },
{ MP_QSTR_filter, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_ROM_NONE } },
{ MP_QSTR_mix, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_ROM_INT(1)} },
{ MP_QSTR_buffer_size, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 512} },
{ MP_QSTR_sample_rate, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 8000} },
Expand Down Expand Up @@ -127,7 +127,7 @@ static void check_for_deinit(audiofilters_filter_obj_t *self) {
// Provided by context manager helper.


//| filter: synthio.Biquad | Tuple[synthio.Biquad] | None
//| filter: synthio.AnyBiquad | Tuple[synthio.AnyBiquad] | None
//| """A normalized biquad filter object or tuple of normalized biquad filter objects. The sample is processed sequentially by each filter to produce the output samples."""
//|
static mp_obj_t audiofilters_filter_obj_get_filter(mp_obj_t self_in) {
Expand Down
2 changes: 2 additions & 0 deletions shared-bindings/synthio/Biquad.c
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ static const mp_arg_t biquad_properties[] = {
//| rather than directly from coefficients.
//|
//| https://github.com/WebAudio/Audio-EQ-Cookbook/blob/main/Audio-EQ-Cookbook.txt
//|
//| .. note:: This is deprecated in ``9.x.x`` and will be removed in ``10.0.0``. Use `BlockBiquad` objects instead.
//| """
//|
//|
Expand Down
55 changes: 52 additions & 3 deletions shared-bindings/synthio/BlockBiquad.c
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,31 @@
//| """A band-pass filter"""
//| NOTCH: FilterMode
//| """A notch filter"""
//| LOW_SHELF: FilterMode
//| """A low shelf filter"""
//| HIGH_SHELF: FilterMode
//| """A high shelf filter"""
//| PEAKING_EQ: FilterMode
//| """A peaking equalizer filter"""
//|
//|

MAKE_ENUM_VALUE(synthio_filter_mode_type, mode, LOW_PASS, SYNTHIO_LOW_PASS);
MAKE_ENUM_VALUE(synthio_filter_mode_type, mode, HIGH_PASS, SYNTHIO_HIGH_PASS);
MAKE_ENUM_VALUE(synthio_filter_mode_type, mode, BAND_PASS, SYNTHIO_BAND_PASS);
MAKE_ENUM_VALUE(synthio_filter_mode_type, mode, NOTCH, SYNTHIO_NOTCH);
MAKE_ENUM_VALUE(synthio_filter_mode_type, mode, LOW_SHELF, SYNTHIO_LOW_SHELF);
MAKE_ENUM_VALUE(synthio_filter_mode_type, mode, HIGH_SHELF, SYNTHIO_HIGH_SHELF);
MAKE_ENUM_VALUE(synthio_filter_mode_type, mode, PEAKING_EQ, SYNTHIO_PEAKING_EQ);

MAKE_ENUM_MAP(synthio_filter_mode) {
MAKE_ENUM_MAP_ENTRY(mode, LOW_PASS),
MAKE_ENUM_MAP_ENTRY(mode, HIGH_PASS),
MAKE_ENUM_MAP_ENTRY(mode, BAND_PASS),
MAKE_ENUM_MAP_ENTRY(mode, NOTCH),
MAKE_ENUM_MAP_ENTRY(mode, LOW_SHELF),
MAKE_ENUM_MAP_ENTRY(mode, HIGH_SHELF),
MAKE_ENUM_MAP_ENTRY(mode, PEAKING_EQ),
};

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

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) {
Expand All @@ -83,7 +105,9 @@ static mp_obj_t synthio_block_biquad_make_new(const mp_obj_type_t *type_in, size
}

synthio_filter_mode mode = validate_synthio_filter_mode(args[ARG_mode].u_obj, MP_QSTR_mode);
return common_hal_synthio_block_biquad_new(mode, args[ARG_frequency].u_obj, args[ARG_Q].u_obj);
mp_obj_t result = common_hal_synthio_block_biquad_new(mode);
properties_construct_helper(result, block_biquad_properties + 1, args + 1, MP_ARRAY_SIZE(block_biquad_properties) - 1);
return result;
}

//|
Expand Down Expand Up @@ -122,7 +146,6 @@ MP_PROPERTY_GETSET(synthio_block_biquad_frequency_obj,
//| Q: BlockInput
//| """The sharpness (Q) of the filter"""
//|
//|
static mp_obj_t synthio_block_biquad_get_Q(mp_obj_t self_in) {
synthio_block_biquad_t *self = MP_OBJ_TO_PTR(self_in);
return common_hal_synthio_block_biquad_get_Q(self);
Expand All @@ -139,10 +162,36 @@ MP_PROPERTY_GETSET(synthio_block_biquad_Q_obj,
(mp_obj_t)&synthio_block_biquad_get_Q_obj,
(mp_obj_t)&synthio_block_biquad_set_Q_obj);

//|
//| A: BlockInput
//| """The gain (A) of the filter
//|
//| This setting only has an effect for peaking and shelving EQ filters. It is related
//| to the filter gain according to the formula ``A = 10^(dBgain/40)``.
//| """
//|
//|
static mp_obj_t synthio_block_biquad_get_A(mp_obj_t self_in) {
synthio_block_biquad_t *self = MP_OBJ_TO_PTR(self_in);
return common_hal_synthio_block_biquad_get_A(self);
}
MP_DEFINE_CONST_FUN_OBJ_1(synthio_block_biquad_get_A_obj, synthio_block_biquad_get_A);

static mp_obj_t synthio_block_biquad_set_A(mp_obj_t self_in, mp_obj_t arg) {
synthio_block_biquad_t *self = MP_OBJ_TO_PTR(self_in);
common_hal_synthio_block_biquad_set_A(self, arg);
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_2(synthio_block_biquad_set_A_obj, synthio_block_biquad_set_A);
MP_PROPERTY_GETSET(synthio_block_biquad_A_obj,
(mp_obj_t)&synthio_block_biquad_get_A_obj,
(mp_obj_t)&synthio_block_biquad_set_A_obj);

static const mp_rom_map_elem_t synthio_block_biquad_locals_dict_table[] = {
{ MP_ROM_QSTR(MP_QSTR_mode), MP_ROM_PTR(&synthio_block_biquad_mode_obj) },
{ MP_ROM_QSTR(MP_QSTR_frequency), MP_ROM_PTR(&synthio_block_biquad_frequency_obj) },
{ MP_ROM_QSTR(MP_QSTR_Q), MP_ROM_PTR(&synthio_block_biquad_Q_obj) },
{ MP_ROM_QSTR(MP_QSTR_A), MP_ROM_PTR(&synthio_block_biquad_A_obj) },
};
static MP_DEFINE_CONST_DICT(synthio_block_biquad_locals_dict, synthio_block_biquad_locals_dict_table);

Expand Down
9 changes: 7 additions & 2 deletions shared-bindings/synthio/BlockBiquad.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,15 @@ extern const mp_obj_type_t synthio_filter_mode_type;
typedef struct synthio_block_biquad synthio_block_biquad_t;

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


mp_obj_t common_hal_synthio_block_biquad_get_A(synthio_block_biquad_t *self);
void common_hal_synthio_block_biquad_set_A(synthio_block_biquad_t *self, mp_obj_t A);

mp_obj_t common_hal_synthio_block_biquad_get_Q(synthio_block_biquad_t *self);
void common_hal_synthio_block_biquad_set_Q(synthio_block_biquad_t *self, mp_obj_t Q);

Expand All @@ -25,4 +30,4 @@ void common_hal_synthio_block_biquad_set_frequency(synthio_block_biquad_t *self,

synthio_filter_mode common_hal_synthio_block_biquad_get_mode(synthio_block_biquad_t *self);

mp_obj_t common_hal_synthio_block_biquad_new(synthio_filter_mode mode, mp_obj_t frequency, mp_obj_t Q);
mp_obj_t common_hal_synthio_block_biquad_new(synthio_filter_mode mode);
2 changes: 1 addition & 1 deletion shared-bindings/synthio/LFO.c
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ static const uint16_t triangle[] = {0, 32767, 0, -32767};
//| including indirectly via a `Note` or another intermediate LFO.
//|
//| Using the same LFO as an input to multiple other LFOs or Notes is OK, but
//| the result if an LFO is tied to multiple Synthtesizer objects is undefined.
//| the result if an LFO is tied to multiple `Synthesizer` objects is undefined.
//|
//| In the current implementation, LFOs are updated every 256 samples. This
//| should be considered an implementation detail, though it affects how LFOs
Expand Down
2 changes: 1 addition & 1 deletion shared-bindings/synthio/Math.c
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ MAKE_ENUM_TYPE(synthio, MathOperation, synthio_math_operation,
//| including indirectly via a `Note` or another intermediate Math.
//|
//| Using the same Math as an input to multiple other Maths or Notes is OK, but
//| the result if an Math is tied to multiple Synthtesizer objects is undefined.
//| the result if an Math is tied to multiple `Synthesizer` objects is undefined.
//|
//| In the current implementation, Maths are updated every 256 samples. This
//| should be considered an implementation detail.
Expand Down
4 changes: 2 additions & 2 deletions shared-bindings/synthio/Note.c
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ static const mp_arg_t note_properties[] = {
//| envelope: Optional[Envelope] = None,
//| amplitude: BlockInput = 1.0,
//| bend: BlockInput = 0.0,
//| filter: Optional[Biquad] = None,
//| filter: Optional[AnyBiquad] = None,
//| ring_frequency: float = 0.0,
//| ring_bend: float = 0.0,
//| ring_waveform: Optional[ReadableBuffer] = None,
Expand Down Expand Up @@ -86,7 +86,7 @@ MP_PROPERTY_GETSET(synthio_note_frequency_obj,
(mp_obj_t)&synthio_note_get_frequency_obj,
(mp_obj_t)&synthio_note_set_frequency_obj);

//| filter: Optional[Biquad]
//| filter: Optional[AnyBiquad]
//| """If not None, the output of this Note is filtered according to the provided coefficients.
//|
//| Construct an appropriate filter by calling a filter-making method on the
Expand Down
6 changes: 6 additions & 0 deletions shared-bindings/synthio/Synthesizer.c
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,8 @@ MP_PROPERTY_GETTER(synthio_synthesizer_blocks_obj,
//| of the filter.
//|
//| ``Q`` controls how peaked the response will be at the cutoff frequency. A large value makes the response more peaked.
//|
//| .. note:: This is deprecated in ``9.x.x`` and will be removed in ``10.0.0``. Use `BlockBiquad` objects instead.
//| """
//|

Expand Down Expand Up @@ -332,6 +334,8 @@ MP_DEFINE_CONST_FUN_OBJ_KW(synthio_synthesizer_lpf_fun_obj, 1, synthio_synthesiz
//| of the filter.
//|
//| ``Q`` controls how peaked the response will be at the cutoff frequency. A large value makes the response more peaked.
//|
//| .. note:: This is deprecated in ``9.x.x`` and will be removed in ``10.0.0``. Use `BlockBiquad` objects instead.
//| """
//|

Expand Down Expand Up @@ -363,6 +367,8 @@ static mp_obj_t synthio_synthesizer_hpf(size_t n_pos, const mp_obj_t *pos_args,
//| ``Q`` Controls how peaked the response will be at the cutoff frequency. A large value makes the response more peaked.
//|
//| The coefficients are scaled such that the filter has a 0dB peak gain.
//|
//| .. note:: This is deprecated in ``9.x.x`` and will be removed in ``10.0.0``. Use `BlockBiquad` objects instead.
//| """
//|
//|
Expand Down
4 changes: 3 additions & 1 deletion shared-bindings/synthio/__init__.c
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,10 @@
//| At least 2 simultaneous notes are supported. samd5x, mimxrt10xx and rp2040 platforms support up to 12 notes.
//| """
//|
//|

//| AnyBiquad = Union["Biquad", "BlockBiquad"]
//|
//|
//| class EnvelopeState:
//| ATTACK: EnvelopeState
//| """The note is in its attack phase"""
Expand Down
Loading

0 comments on commit 3f2c1e0

Please sign in to comment.