Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

synthio: Add a form of biquad filter that uses BlockInputs #9756

Merged
merged 15 commits into from
Nov 10, 2024
Merged
1 change: 1 addition & 0 deletions conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,7 @@ def autoapi_prepare_jinja_env(jinja_env):
"ports/nordic/peripherals",
"ports/nordic/usb",
"ports/raspberrypi/sdk",
"ports/raspberrypi/pioasm",
gamblor21 marked this conversation as resolved.
Show resolved Hide resolved
"ports/raspberrypi/lib",
"ports/silabs/gecko_sdk",
"ports/silabs/tools",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ CHIP_FAMILY = rp2
EXTERNAL_FLASH_DEVICES = "W25Q32JVxQ"

CIRCUITPY__EVE = 1
CIRCUITPY_SSL = 1
gamblor21 marked this conversation as resolved.
Show resolved Hide resolved
2 changes: 2 additions & 0 deletions ports/unix/variants/coverage/mpconfigvariant.mk
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ SRC_BITMAP := \
shared-bindings/synthio/LFO.c \
shared-bindings/synthio/Note.c \
shared-bindings/synthio/Biquad.c \
shared-bindings/synthio/BlockBiquad.c \
shared-bindings/synthio/Synthesizer.c \
shared-bindings/traceback/__init__.c \
shared-bindings/util.c \
Expand Down Expand Up @@ -87,6 +88,7 @@ SRC_BITMAP := \
shared-module/synthio/LFO.c \
shared-module/synthio/Note.c \
shared-module/synthio/Biquad.c \
shared-module/synthio/BlockBiquad.c \
shared-module/synthio/Synthesizer.c \
shared-module/traceback/__init__.c \
shared-module/zlib/__init__.c \
Expand Down
1 change: 1 addition & 0 deletions py/circuitpy_defns.mk
Original file line number Diff line number Diff line change
Expand Up @@ -717,6 +717,7 @@ SRC_SHARED_MODULE_ALL = \
supervisor/__init__.c \
supervisor/StatusBar.c \
synthio/Biquad.c \
synthio/BlockBiquad.c \
synthio/LFO.c \
synthio/Math.c \
synthio/MidiTrack.c \
Expand Down
156 changes: 156 additions & 0 deletions shared-bindings/synthio/BlockBiquad.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
// This file is part of the CircuitPython project: https://circuitpython.org
//
// SPDX-FileCopyrightText: Copyright (c) 2023 Jeff Epler for Adafruit Industries
//
// SPDX-License-Identifier: MIT

#include "py/enum.h"
#include "py/objproperty.h"
#include "py/runtime.h"
#include "shared-bindings/synthio/BlockBiquad.h"
#include "shared-bindings/util.h"

//| class FilterMode:
//| """The type of filter"""
//|
//| LOW_PASS: FilterMode
//| """A low-pass filter"""
//| HIGH_PASS: FilterMode
//| """A high-pass filter"""
//| BAND_PASS: FilterMode
//| """A band-pass filter"""
//|
gamblor21 marked this conversation as resolved.
Show resolved Hide resolved

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_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),
};

static MP_DEFINE_CONST_DICT(synthio_filter_mode_locals_dict, synthio_filter_mode_locals_table);

MAKE_PRINTER(synthio, synthio_filter_mode);

MAKE_ENUM_TYPE(synthio, FilterMode, synthio_filter_mode);

static synthio_filter_mode validate_synthio_filter_mode(mp_obj_t obj, qstr arg_name) {
return cp_enum_value(&synthio_filter_mode_type, obj, arg_name);
}

//| class BlockBiquad:
//| def __init__(
//| self,
//| mode: FilterMode,
//| frequency: BlockInput,
//| Q: BlockInput = 0.7071067811865475,
//| ) -> None:
//| """Construct a biquad filter object with dynamic center frequency & q factor
//|
//| Since ``frequency`` and ``Q`` are `BlockInput` objects, they can
//| be varied dynamically. Internally, this is evaluated as "direct form 1"
//| biquad filter.
//|
//| The internal filter state x[] and y[] is not updated when the filter
//| coefficients change, and there is no theoretical justification for why
//| this should result in a stable filter output. However, in practice,
//| slowly varying the filter's characteristic frequency and sharpness
//| appears to work as you'd expect."""

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 } },
};

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) {
enum { ARG_mode, ARG_frequency, ARG_Q };

mp_arg_val_t args[MP_ARRAY_SIZE(block_biquad_properties)];
mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(block_biquad_properties), block_biquad_properties, args);

if (args[ARG_Q].u_obj == MP_OBJ_NULL) {
args[ARG_Q].u_obj = mp_obj_new_float(MICROPY_FLOAT_CONST(0.7071067811865475));
}

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);
}

//|
//| mode: FilterMode
//| """The mode of filter (read-only)"""
static mp_obj_t synthio_block_biquad_get_mode(mp_obj_t self_in) {
synthio_block_biquad_t *self = MP_OBJ_TO_PTR(self_in);
return cp_enum_find(&synthio_filter_mode_type, common_hal_synthio_block_biquad_get_mode(self));
}
MP_DEFINE_CONST_FUN_OBJ_1(synthio_block_biquad_get_mode_obj, synthio_block_biquad_get_mode);

MP_PROPERTY_GETTER(synthio_block_biquad_mode_obj,
(mp_obj_t)&synthio_block_biquad_get_mode_obj);

//|
//| frequency: BlockInput
//| """The central frequency (in Hz) of the filter"""
static mp_obj_t synthio_block_biquad_get_frequency(mp_obj_t self_in) {
synthio_block_biquad_t *self = MP_OBJ_TO_PTR(self_in);
return common_hal_synthio_block_biquad_get_frequency(self);
}
MP_DEFINE_CONST_FUN_OBJ_1(synthio_block_biquad_get_frequency_obj, synthio_block_biquad_get_frequency);

static mp_obj_t synthio_block_biquad_set_frequency(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_frequency(self, arg);
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_2(synthio_block_biquad_set_frequency_obj, synthio_block_biquad_set_frequency);
MP_PROPERTY_GETSET(synthio_block_biquad_frequency_obj,
(mp_obj_t)&synthio_block_biquad_get_frequency_obj,
(mp_obj_t)&synthio_block_biquad_set_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);
}
MP_DEFINE_CONST_FUN_OBJ_1(synthio_block_biquad_get_Q_obj, synthio_block_biquad_get_Q);

static mp_obj_t synthio_block_biquad_set_Q(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_Q(self, arg);
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_2(synthio_block_biquad_set_Q_obj, synthio_block_biquad_set_Q);
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);

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) },
};
static MP_DEFINE_CONST_DICT(synthio_block_biquad_locals_dict, synthio_block_biquad_locals_dict_table);

static void block_biquad_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
(void)kind;
properties_print_helper(print, self_in, block_biquad_properties, MP_ARRAY_SIZE(block_biquad_properties));
}

MP_DEFINE_CONST_OBJ_TYPE(
synthio_block_biquad_type_obj,
MP_QSTR_BlockBiquad,
MP_TYPE_FLAG_HAS_SPECIAL_ACCESSORS,
make_new, synthio_block_biquad_make_new,
locals_dict, &synthio_block_biquad_locals_dict,
print, block_biquad_print
);
28 changes: 28 additions & 0 deletions shared-bindings/synthio/BlockBiquad.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// This file is part of the CircuitPython project: https://circuitpython.org
//
// SPDX-FileCopyrightText: Copyright (c) 2023 Jeff Epler for Adafruit Industries
//
// SPDX-License-Identifier: MIT

#pragma once

#include "py/obj.h"

extern const mp_obj_type_t synthio_block_biquad_type_obj;
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_filter_mode;


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);

mp_obj_t common_hal_synthio_block_biquad_get_frequency(synthio_block_biquad_t *self);
void common_hal_synthio_block_biquad_set_frequency(synthio_block_biquad_t *self, mp_obj_t frequency);

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);
16 changes: 6 additions & 10 deletions shared-bindings/synthio/Synthesizer.c
Original file line number Diff line number Diff line change
Expand Up @@ -287,13 +287,13 @@ MP_PROPERTY_GETTER(synthio_synthesizer_blocks_obj,
//| """Maximum polyphony of the synthesizer (read-only class property)"""
//|

//| def low_pass_filter(cls, frequency: float, q_factor: float = 0.7071067811865475) -> Biquad:
//| def low_pass_filter(cls, frequency: float, Q: float = 0.7071067811865475) -> Biquad:
//| """Construct a low-pass filter with the given parameters.
//|
//| ``frequency``, called f0 in the cookbook, is the corner frequency in Hz
//| of the filter.
//|
//| ``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.
//| ``Q`` controls how peaked the response will be at the cutoff frequency. A large value makes the response more peaked.
//| """

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

MP_DEFINE_CONST_FUN_OBJ_KW(synthio_synthesizer_lpf_fun_obj, 1, synthio_synthesizer_lpf);

//| def high_pass_filter(
//| cls, frequency: float, q_factor: float = 0.7071067811865475
//| ) -> Biquad:
//| def high_pass_filter(cls, frequency: float, Q: float = 0.7071067811865475) -> Biquad:
//| """Construct a high-pass filter with the given parameters.
//|
//| ``frequency``, called f0 in the cookbook, is the corner frequency in Hz
//| of the filter.
//|
//| ``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.
//| ``Q`` controls how peaked the response will be at the cutoff frequency. A large value makes the response more peaked.
//| """

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

}

//| def band_pass_filter(
//| cls, frequency: float, q_factor: float = 0.7071067811865475
//| ) -> Biquad:
//| def band_pass_filter(cls, frequency: float, Q: float = 0.7071067811865475) -> Biquad:
//| """Construct a band-pass filter with the given parameters.
//|
//| ``frequency``, called f0 in the cookbook, is the center frequency in Hz
//| of the filter.
//|
//| ``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.
//| ``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.
//| """
Expand Down
3 changes: 3 additions & 0 deletions shared-bindings/synthio/__init__.c
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

#include "shared-bindings/synthio/__init__.h"
#include "shared-bindings/synthio/Biquad.h"
#include "shared-bindings/synthio/BlockBiquad.h"
#include "shared-bindings/synthio/LFO.h"
#include "shared-bindings/synthio/Math.h"
#include "shared-bindings/synthio/MidiTrack.h"
Expand Down Expand Up @@ -307,6 +308,8 @@ MP_DEFINE_CONST_FUN_OBJ_VAR(synthio_lfo_tick_obj, 1, synthio_lfo_tick);
static const mp_rom_map_elem_t synthio_module_globals_table[] = {
{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_synthio) },
{ MP_ROM_QSTR(MP_QSTR_Biquad), MP_ROM_PTR(&synthio_biquad_type_obj) },
{ MP_ROM_QSTR(MP_QSTR_BlockBiquad), MP_ROM_PTR(&synthio_block_biquad_type_obj) },
{ MP_ROM_QSTR(MP_QSTR_FilterMode), MP_ROM_PTR(&synthio_filter_mode_type) },
{ MP_ROM_QSTR(MP_QSTR_Math), MP_ROM_PTR(&synthio_math_type) },
{ MP_ROM_QSTR(MP_QSTR_MathOperation), MP_ROM_PTR(&synthio_math_operation_type) },
{ MP_ROM_QSTR(MP_QSTR_MidiTrack), MP_ROM_PTR(&synthio_miditrack_type) },
Expand Down
14 changes: 11 additions & 3 deletions shared-module/synthio/Biquad.c
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

#include <math.h>
#include "shared-bindings/synthio/Biquad.h"
#include "shared-bindings/synthio/BlockBiquad.h"
#include "shared-module/synthio/Biquad.h"

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

#define BIQUAD_SHIFT (15)
static int32_t biquad_scale_arg_obj(mp_obj_t arg) {
return (int32_t)MICROPY_FLOAT_C_FUN(round)(MICROPY_FLOAT_C_FUN(ldexp)(mp_obj_get_float(arg), BIQUAD_SHIFT));
}
void synthio_biquad_filter_assign(biquad_filter_state *st, mp_obj_t biquad_obj) {
if (biquad_obj != mp_const_none) {
mp_arg_validate_type(biquad_obj, (const mp_obj_type_t *)&synthio_biquad_type_obj, MP_QSTR_filter);
if (biquad_obj == mp_const_none) {
return;
}
if (mp_obj_is_type(biquad_obj, &synthio_block_biquad_type_obj)) {
return;
}
if (mp_obj_is_type(biquad_obj, (const mp_obj_type_t *)&synthio_biquad_type_obj)) {
mp_obj_tuple_t *biquad = (mp_obj_tuple_t *)MP_OBJ_TO_PTR(biquad_obj);
st->a1 = biquad_scale_arg_obj(biquad->items[0]);
st->a2 = biquad_scale_arg_obj(biquad->items[1]);
st->b0 = biquad_scale_arg_obj(biquad->items[2]);
st->b1 = biquad_scale_arg_obj(biquad->items[3]);
st->b2 = biquad_scale_arg_obj(biquad->items[4]);
return;
}
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);

}

void synthio_biquad_filter_reset(biquad_filter_state *st) {
Expand Down
2 changes: 2 additions & 0 deletions shared-module/synthio/Biquad.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

#include "py/obj.h"

#define BIQUAD_SHIFT (15)

typedef struct {
int32_t a1, a2, b0, b1, b2;
int32_t x[2], y[2];
Expand Down
Loading