From 6badb92de8b8d5cef96ca8dee8aa0f7b3bdc1847 Mon Sep 17 00:00:00 2001 From: dcooperdalrymple Date: Tue, 29 Oct 2024 12:52:27 -0500 Subject: [PATCH 1/5] Add support for list of Biquad objects within `audiofilters.Filter`. --- shared-bindings/audiofilters/Filter.c | 48 +++++++++++---------- shared-bindings/audiofilters/Filter.h | 6 +-- shared-module/audiofilters/Filter.c | 61 ++++++++++++++++++++------- shared-module/audiofilters/Filter.h | 7 ++- 4 files changed, 80 insertions(+), 42 deletions(-) diff --git a/shared-bindings/audiofilters/Filter.c b/shared-bindings/audiofilters/Filter.c index 457897909e0d..53c36b2d64d5 100644 --- a/shared-bindings/audiofilters/Filter.c +++ b/shared-bindings/audiofilters/Filter.c @@ -23,7 +23,7 @@ //| //| def __init__( //| self, -//| filter: Optional[synthio.Biquad] = None, +//| filters: Optional[List[synthio.Biquad]] = None, //| mix: synthio.BlockInput = 1.0, //| buffer_size: int = 512, //| sample_rate: int = 8000, @@ -38,7 +38,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] filter: The normalized biquad filter object used to process the signal. +//| :param Optional[List[synthio.Biquad]] filters: A list of normalized biquad filter objects used to process the signal. //| :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 @@ -56,9 +56,10 @@ //| //| audio = audiobusio.I2SOut(bit_clock=board.GP20, word_select=board.GP21, data=board.GP22) //| synth = synthio.Synthesizer(channel_count=1, sample_rate=44100) -//| filter = audiofilters.Filter(filter=synth.low_pass_filter(frequency=2000, Q=1.25), buffer_size=1024, channel_count=1, sample_rate=44100, mix=1.0) -//| filter.play(synth) -//| audio.play(filter) +//| effect = audiofilters.Filter(buffer_size=1024, channel_count=1, sample_rate=44100, mix=1.0) +//| effect.filters.append(synth.low_pass_filter(frequency=2000, Q=1.25)) +//| effect.play(synth) +//| audio.play(effect) //| //| note = synthio.Note(261) //| while True: @@ -68,9 +69,9 @@ //| time.sleep(5)""" //| ... 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, }; + enum { ARG_filters, 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_filters, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_OBJ_NULL} }, { MP_QSTR_mix, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_OBJ_NULL} }, { 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} }, @@ -90,7 +91,7 @@ static mp_obj_t audiofilters_filter_make_new(const mp_obj_type_t *type, size_t n } audiofilters_filter_obj_t *self = mp_obj_malloc(audiofilters_filter_obj_t, &audiofilters_filter_type); - common_hal_audiofilters_filter_construct(self, args[ARG_filter].u_obj, args[ARG_mix].u_obj, args[ARG_buffer_size].u_int, bits_per_sample, args[ARG_samples_signed].u_bool, channel_count, sample_rate); + common_hal_audiofilters_filter_construct(self, args[ARG_filters].u_obj, args[ARG_mix].u_obj, args[ARG_buffer_size].u_int, bits_per_sample, args[ARG_samples_signed].u_bool, channel_count, sample_rate); return MP_OBJ_FROM_PTR(self); } @@ -128,31 +129,34 @@ static mp_obj_t audiofilters_filter_obj___exit__(size_t n_args, const mp_obj_t * static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(audiofilters_filter___exit___obj, 4, 4, audiofilters_filter_obj___exit__); -//| filter: Optional[synthio.Biquad] -//| """The normalized biquad filter object used to process the signal.""" -static mp_obj_t audiofilters_filter_obj_get_filter(mp_obj_t self_in) { - return common_hal_audiofilters_filter_get_filter(self_in); +//| filters: List[synthio.Biquad] +//| """A list of normalized biquad filter objects used to process the signal.""" +//| +static mp_obj_t audiofilters_filter_obj_get_filters(mp_obj_t self_in) { + audiofilters_filter_obj_t *self = MP_OBJ_TO_PTR(self_in); + check_for_deinit(self); + return common_hal_audiofilters_filter_get_filters(self); } -MP_DEFINE_CONST_FUN_OBJ_1(audiofilters_filter_get_filter_obj, audiofilters_filter_obj_get_filter); +MP_DEFINE_CONST_FUN_OBJ_1(audiofilters_filter_get_filters_obj, audiofilters_filter_obj_get_filters); -static mp_obj_t audiofilters_filter_obj_set_filter(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - enum { ARG_filter }; +static mp_obj_t audiofilters_filter_obj_set_filters(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_filters }; static const mp_arg_t allowed_args[] = { - { MP_QSTR_filter, MP_ARG_OBJ | MP_ARG_REQUIRED, {} }, + { MP_QSTR_filters, MP_ARG_OBJ | MP_ARG_REQUIRED, {} }, }; audiofilters_filter_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); - common_hal_audiofilters_filter_set_filter(self, args[ARG_filter].u_obj); + common_hal_audiofilters_filter_set_filters(self, args[ARG_filters].u_obj); return mp_const_none; } -MP_DEFINE_CONST_FUN_OBJ_KW(audiofilters_filter_set_filter_obj, 1, audiofilters_filter_obj_set_filter); +MP_DEFINE_CONST_FUN_OBJ_KW(audiofilters_filter_set_filters_obj, 1, audiofilters_filter_obj_set_filters); -MP_PROPERTY_GETSET(audiofilters_filter_filter_obj, - (mp_obj_t)&audiofilters_filter_get_filter_obj, - (mp_obj_t)&audiofilters_filter_set_filter_obj); +MP_PROPERTY_GETSET(audiofilters_filter_filters_obj, + (mp_obj_t)&audiofilters_filter_get_filters_obj, + (mp_obj_t)&audiofilters_filter_set_filters_obj); //| mix: synthio.BlockInput @@ -241,7 +245,7 @@ static const mp_rom_map_elem_t audiofilters_filter_locals_dict_table[] = { // Properties { MP_ROM_QSTR(MP_QSTR_playing), MP_ROM_PTR(&audiofilters_filter_playing_obj) }, - { MP_ROM_QSTR(MP_QSTR_filter), MP_ROM_PTR(&audiofilters_filter_filter_obj) }, + { MP_ROM_QSTR(MP_QSTR_filters), MP_ROM_PTR(&audiofilters_filter_filters_obj) }, { MP_ROM_QSTR(MP_QSTR_mix), MP_ROM_PTR(&audiofilters_filter_mix_obj) }, }; static MP_DEFINE_CONST_DICT(audiofilters_filter_locals_dict, audiofilters_filter_locals_dict_table); diff --git a/shared-bindings/audiofilters/Filter.h b/shared-bindings/audiofilters/Filter.h index 739b625ee6c5..3cbca2c9ee28 100644 --- a/shared-bindings/audiofilters/Filter.h +++ b/shared-bindings/audiofilters/Filter.h @@ -11,7 +11,7 @@ extern const mp_obj_type_t audiofilters_filter_type; void common_hal_audiofilters_filter_construct(audiofilters_filter_obj_t *self, - mp_obj_t filter, mp_obj_t mix, + mp_obj_t filters, mp_obj_t mix, uint32_t buffer_size, uint8_t bits_per_sample, bool samples_signed, uint8_t channel_count, uint32_t sample_rate); @@ -22,8 +22,8 @@ uint32_t common_hal_audiofilters_filter_get_sample_rate(audiofilters_filter_obj_ uint8_t common_hal_audiofilters_filter_get_channel_count(audiofilters_filter_obj_t *self); uint8_t common_hal_audiofilters_filter_get_bits_per_sample(audiofilters_filter_obj_t *self); -mp_obj_t common_hal_audiofilters_filter_get_filter(audiofilters_filter_obj_t *self); -void common_hal_audiofilters_filter_set_filter(audiofilters_filter_obj_t *self, mp_obj_t arg); +mp_obj_t common_hal_audiofilters_filter_get_filters(audiofilters_filter_obj_t *self); +void common_hal_audiofilters_filter_set_filters(audiofilters_filter_obj_t *self, mp_obj_t arg); mp_obj_t common_hal_audiofilters_filter_get_mix(audiofilters_filter_obj_t *self); void common_hal_audiofilters_filter_set_mix(audiofilters_filter_obj_t *self, mp_obj_t arg); diff --git a/shared-module/audiofilters/Filter.c b/shared-module/audiofilters/Filter.c index 86edb6cb0418..ed53f7fce834 100644 --- a/shared-module/audiofilters/Filter.c +++ b/shared-module/audiofilters/Filter.c @@ -9,7 +9,7 @@ #include "py/runtime.h" void common_hal_audiofilters_filter_construct(audiofilters_filter_obj_t *self, - mp_obj_t filter, mp_obj_t mix, + mp_obj_t filters, mp_obj_t mix, uint32_t buffer_size, uint8_t bits_per_sample, bool samples_signed, uint8_t channel_count, uint32_t sample_rate) { @@ -60,12 +60,12 @@ void common_hal_audiofilters_filter_construct(audiofilters_filter_obj_t *self, // The below section sets up the effect's starting values. - if (filter == MP_OBJ_NULL) { - filter = mp_const_none; + if (filters == MP_OBJ_NULL) { + filters = mp_obj_new_list(0, NULL); } - synthio_biquad_filter_assign(&self->filter_state, filter); - self->filter_obj = filter; - + self->filters = filters; + reset_filter_states(self); + // If we did not receive a BlockInput we need to create a default float value if (mix == MP_OBJ_NULL) { mix = mp_obj_new_float(1.0); @@ -87,15 +87,37 @@ void common_hal_audiofilters_filter_deinit(audiofilters_filter_obj_t *self) { self->buffer[0] = NULL; self->buffer[1] = NULL; self->filter_buffer = NULL; + self->filter_states = NULL; +} + +void reset_filter_states(audiofilters_filter_obj_t *self) { + self->filter_states_len = self->filters->len; + self->filter_states = NULL; + + if (self->filter_states_len) { + self->filter_states = m_malloc(self->filter_states_len * sizeof(biquad_filter_state)); + if (self->filter_states == NULL) { + common_hal_audiofilters_filter_deinit(self); + m_malloc_fail(self->filter_states_len * sizeof(biquad_filter_state)); + } + + mp_obj_iter_buf_t iter_buf; + mp_obj_t iterable = mp_getiter(self->filters, &iter_buf); + mp_obj_t item; + uint8_t i = 0; + while ((item = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) { + synthio_biquad_filter_assign(&self->filter_states[i++], item); + } + } } -mp_obj_t common_hal_audiofilters_filter_get_filter(audiofilters_filter_obj_t *self) { - return self->filter_obj; +mp_obj_t common_hal_audiofilters_filter_get_filters(audiofilters_filter_obj_t *self) { + return self->filters; } -void common_hal_audiofilters_filter_set_filter(audiofilters_filter_obj_t *self, mp_obj_t arg) { - synthio_biquad_filter_assign(&self->filter_state, arg); - self->filter_obj = arg; +void common_hal_audiofilters_filter_set_filters(audiofilters_filter_obj_t *self, mp_obj_t arg) { + self->filters = arg; + reset_filter_states(self); } mp_obj_t common_hal_audiofilters_filter_get_mix(audiofilters_filter_obj_t *self) { @@ -126,7 +148,9 @@ void audiofilters_filter_reset_buffer(audiofilters_filter_obj_t *self, memset(self->buffer[1], 0, self->buffer_len); memset(self->filter_buffer, 0, SYNTHIO_MAX_DUR * sizeof(int32_t)); - synthio_biquad_filter_reset(&self->filter_state); + for (uint8_t i = 0; i < self->filter_states_len; i++) { + synthio_biquad_filter_reset(&self->filter_states[i]); + } } bool common_hal_audiofilters_filter_get_playing(audiofilters_filter_obj_t *self) { @@ -209,6 +233,11 @@ audioio_get_buffer_result_t audiofilters_filter_get_buffer(audiofilters_filter_o channel = 0; } + // Update filter_states if filters list has been appended or removed + if (self->filters->len != self->filter_states_len) { + reset_filter_states(self); + } + // get the effect values we need from the BlockInput. These may change at run time so you need to do bounds checking if required mp_float_t mix = MIN(1.0, MAX(synthio_block_slot_get(&self->mix), 0.0)); @@ -248,7 +277,7 @@ audioio_get_buffer_result_t audiofilters_filter_get_buffer(audiofilters_filter_o int16_t *sample_src = (int16_t *)self->sample_remaining_buffer; // for 16-bit samples int8_t *sample_hsrc = (int8_t *)self->sample_remaining_buffer; // for 8-bit samples - if (mix <= 0.01 || self->filter_obj == mp_const_none) { // if mix is zero pure sample only or no biquad filter object is provided + if (mix <= 0.01 || !self->filter_states_len) { // if mix is zero pure sample only or no biquad filter objects are provided for (uint32_t i = 0; i < n; i++) { if (MP_LIKELY(self->bits_per_sample == 16)) { word_buffer[i] = sample_src[i]; @@ -275,8 +304,10 @@ audioio_get_buffer_result_t audiofilters_filter_get_buffer(audiofilters_filter_o } } - // Process biquad filter - synthio_biquad_filter_samples(&self->filter_state, self->filter_buffer, n_samples); + // Process biquad filters + for (uint8_t j = 0; j < self->filter_states_len; j++) { + synthio_biquad_filter_samples(&self->filter_states[j], self->filter_buffer, n_samples); + } // Mix processed signal with original sample and transfer to output buffer for (uint32_t j = 0; j < n_samples; j++) { diff --git a/shared-module/audiofilters/Filter.h b/shared-module/audiofilters/Filter.h index b5d743a6c191..7d1ee4db1a39 100644 --- a/shared-module/audiofilters/Filter.h +++ b/shared-module/audiofilters/Filter.h @@ -15,10 +15,11 @@ extern const mp_obj_type_t audiofilters_filter_type; typedef struct { mp_obj_base_t base; - mp_obj_t filter_obj; + mp_obj_list_t *filters; synthio_block_slot_t mix; - biquad_filter_state filter_state; + uint8_t filter_states_len; + biquad_filter_state *filter_states; uint8_t bits_per_sample; bool samples_signed; @@ -40,6 +41,8 @@ typedef struct { mp_obj_t sample; } audiofilters_filter_obj_t; +void reset_filter_states(audiofilters_filter_obj_t *self); + void audiofilters_filter_reset_buffer(audiofilters_filter_obj_t *self, bool single_channel_output, uint8_t channel); From 14b1383b6b03c672942f2a5d096b2ad878a2133e Mon Sep 17 00:00:00 2001 From: dcooperdalrymple Date: Tue, 5 Nov 2024 08:47:24 -0600 Subject: [PATCH 2/5] Remove trailing whitespace to fix pre-commit. --- shared-module/audiofilters/Filter.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared-module/audiofilters/Filter.c b/shared-module/audiofilters/Filter.c index ed53f7fce834..e22133a25b61 100644 --- a/shared-module/audiofilters/Filter.c +++ b/shared-module/audiofilters/Filter.c @@ -65,7 +65,7 @@ void common_hal_audiofilters_filter_construct(audiofilters_filter_obj_t *self, } self->filters = filters; reset_filter_states(self); - + // If we did not receive a BlockInput we need to create a default float value if (mix == MP_OBJ_NULL) { mix = mp_obj_new_float(1.0); From 941e1228f2cfe1602a1d98034ad2413a127dbb46 Mon Sep 17 00:00:00 2001 From: dcooperdalrymple Date: Thu, 7 Nov 2024 13:03:59 -0600 Subject: [PATCH 3/5] Rename `filters` back to `filter` and support individual Biquad object and tuple of Biquad objects. --- locale/circuitpython.pot | 11 +++- shared-bindings/audiofilters/Filter.c | 40 ++++++------- shared-bindings/audiofilters/Filter.h | 6 +- shared-module/audiofilters/Filter.c | 83 +++++++++++++++++---------- shared-module/audiofilters/Filter.h | 5 +- 5 files changed, 88 insertions(+), 57 deletions(-) diff --git a/locale/circuitpython.pot b/locale/circuitpython.pot index a6acb8a644fb..b604ed388375 100644 --- a/locale/circuitpython.pot +++ b/locale/circuitpython.pot @@ -187,6 +187,10 @@ msgstr "" msgid "%q must be >= %d" msgstr "" +#: shared-module/audiofilters/Filter.c +msgid "%q must be a %q object, %q, or %q" +msgstr "" + #: shared-bindings/analogbufio/BufferedIn.c msgid "%q must be a bytearray or array of type 'H' or 'B'" msgstr "" @@ -1274,6 +1278,7 @@ msgid "Invalid socket for TLS" msgstr "" #: ports/espressif/common-hal/espidf/__init__.c +#: ports/nordic/common-hal/_bleio/__init__.c msgid "Invalid state" msgstr "" @@ -1970,7 +1975,8 @@ msgstr "" msgid "The length of rgb_pins must be 6, 12, 18, 24, or 30" msgstr "" -#: shared-module/audiodelays/Echo.c shared-module/audiomixer/MixerVoice.c +#: shared-module/audiodelays/Echo.c shared-module/audiofilters/Filter.c +#: shared-module/audiomixer/MixerVoice.c msgid "The sample's %q does not match" msgstr "" @@ -2504,7 +2510,8 @@ msgstr "" msgid "bits must be 32 or less" msgstr "" -#: shared-bindings/audiodelays/Echo.c shared-bindings/audiomixer/Mixer.c +#: shared-bindings/audiodelays/Echo.c shared-bindings/audiofilters/Filter.c +#: shared-bindings/audiomixer/Mixer.c msgid "bits_per_sample must be 8 or 16" msgstr "" diff --git a/shared-bindings/audiofilters/Filter.c b/shared-bindings/audiofilters/Filter.c index 53c36b2d64d5..e8e6fbfa5e7f 100644 --- a/shared-bindings/audiofilters/Filter.c +++ b/shared-bindings/audiofilters/Filter.c @@ -23,7 +23,7 @@ //| //| def __init__( //| self, -//| filters: Optional[List[synthio.Biquad]] = None, +//| filter: Optional[synthio.Biquad | Tuple[synthio.Biquad]] = None, //| mix: synthio.BlockInput = 1.0, //| buffer_size: int = 512, //| sample_rate: int = 8000, @@ -38,7 +38,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[List[synthio.Biquad]] filters: A list of normalized biquad filter objects used to process the signal. +//| :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 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 @@ -57,7 +57,7 @@ //| audio = audiobusio.I2SOut(bit_clock=board.GP20, word_select=board.GP21, data=board.GP22) //| synth = synthio.Synthesizer(channel_count=1, sample_rate=44100) //| effect = audiofilters.Filter(buffer_size=1024, channel_count=1, sample_rate=44100, mix=1.0) -//| effect.filters.append(synth.low_pass_filter(frequency=2000, Q=1.25)) +//| effect.filter = synth.low_pass_filter(frequency=2000, Q=1.25) //| effect.play(synth) //| audio.play(effect) //| @@ -69,9 +69,9 @@ //| time.sleep(5)""" //| ... 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_filters, ARG_mix, ARG_buffer_size, ARG_sample_rate, ARG_bits_per_sample, ARG_samples_signed, ARG_channel_count, }; + 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_filters, 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_OBJ_NULL} }, { MP_QSTR_mix, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_OBJ_NULL} }, { 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} }, @@ -91,7 +91,7 @@ static mp_obj_t audiofilters_filter_make_new(const mp_obj_type_t *type, size_t n } audiofilters_filter_obj_t *self = mp_obj_malloc(audiofilters_filter_obj_t, &audiofilters_filter_type); - common_hal_audiofilters_filter_construct(self, args[ARG_filters].u_obj, args[ARG_mix].u_obj, args[ARG_buffer_size].u_int, bits_per_sample, args[ARG_samples_signed].u_bool, channel_count, sample_rate); + common_hal_audiofilters_filter_construct(self, args[ARG_filter].u_obj, args[ARG_mix].u_obj, args[ARG_buffer_size].u_int, bits_per_sample, args[ARG_samples_signed].u_bool, channel_count, sample_rate); return MP_OBJ_FROM_PTR(self); } @@ -129,34 +129,34 @@ static mp_obj_t audiofilters_filter_obj___exit__(size_t n_args, const mp_obj_t * static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(audiofilters_filter___exit___obj, 4, 4, audiofilters_filter_obj___exit__); -//| filters: List[synthio.Biquad] -//| """A list of normalized biquad filter objects used to process the signal.""" +//| filter: synthio.Biquad | Tuple[synthio.Biquad] | 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_filters(mp_obj_t self_in) { +static mp_obj_t audiofilters_filter_obj_get_filter(mp_obj_t self_in) { audiofilters_filter_obj_t *self = MP_OBJ_TO_PTR(self_in); check_for_deinit(self); - return common_hal_audiofilters_filter_get_filters(self); + return common_hal_audiofilters_filter_get_filter(self); } -MP_DEFINE_CONST_FUN_OBJ_1(audiofilters_filter_get_filters_obj, audiofilters_filter_obj_get_filters); +MP_DEFINE_CONST_FUN_OBJ_1(audiofilters_filter_get_filter_obj, audiofilters_filter_obj_get_filter); -static mp_obj_t audiofilters_filter_obj_set_filters(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - enum { ARG_filters }; +static mp_obj_t audiofilters_filter_obj_set_filter(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_filter }; static const mp_arg_t allowed_args[] = { - { MP_QSTR_filters, MP_ARG_OBJ | MP_ARG_REQUIRED, {} }, + { MP_QSTR_filter, MP_ARG_OBJ | MP_ARG_REQUIRED, {} }, }; audiofilters_filter_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); - common_hal_audiofilters_filter_set_filters(self, args[ARG_filters].u_obj); + common_hal_audiofilters_filter_set_filter(self, args[ARG_filter].u_obj); return mp_const_none; } -MP_DEFINE_CONST_FUN_OBJ_KW(audiofilters_filter_set_filters_obj, 1, audiofilters_filter_obj_set_filters); +MP_DEFINE_CONST_FUN_OBJ_KW(audiofilters_filter_set_filter_obj, 1, audiofilters_filter_obj_set_filter); -MP_PROPERTY_GETSET(audiofilters_filter_filters_obj, - (mp_obj_t)&audiofilters_filter_get_filters_obj, - (mp_obj_t)&audiofilters_filter_set_filters_obj); +MP_PROPERTY_GETSET(audiofilters_filter_filter_obj, + (mp_obj_t)&audiofilters_filter_get_filter_obj, + (mp_obj_t)&audiofilters_filter_set_filter_obj); //| mix: synthio.BlockInput @@ -245,7 +245,7 @@ static const mp_rom_map_elem_t audiofilters_filter_locals_dict_table[] = { // Properties { MP_ROM_QSTR(MP_QSTR_playing), MP_ROM_PTR(&audiofilters_filter_playing_obj) }, - { MP_ROM_QSTR(MP_QSTR_filters), MP_ROM_PTR(&audiofilters_filter_filters_obj) }, + { MP_ROM_QSTR(MP_QSTR_filter), MP_ROM_PTR(&audiofilters_filter_filter_obj) }, { MP_ROM_QSTR(MP_QSTR_mix), MP_ROM_PTR(&audiofilters_filter_mix_obj) }, }; static MP_DEFINE_CONST_DICT(audiofilters_filter_locals_dict, audiofilters_filter_locals_dict_table); diff --git a/shared-bindings/audiofilters/Filter.h b/shared-bindings/audiofilters/Filter.h index 3cbca2c9ee28..739b625ee6c5 100644 --- a/shared-bindings/audiofilters/Filter.h +++ b/shared-bindings/audiofilters/Filter.h @@ -11,7 +11,7 @@ extern const mp_obj_type_t audiofilters_filter_type; void common_hal_audiofilters_filter_construct(audiofilters_filter_obj_t *self, - mp_obj_t filters, mp_obj_t mix, + mp_obj_t filter, mp_obj_t mix, uint32_t buffer_size, uint8_t bits_per_sample, bool samples_signed, uint8_t channel_count, uint32_t sample_rate); @@ -22,8 +22,8 @@ uint32_t common_hal_audiofilters_filter_get_sample_rate(audiofilters_filter_obj_ uint8_t common_hal_audiofilters_filter_get_channel_count(audiofilters_filter_obj_t *self); uint8_t common_hal_audiofilters_filter_get_bits_per_sample(audiofilters_filter_obj_t *self); -mp_obj_t common_hal_audiofilters_filter_get_filters(audiofilters_filter_obj_t *self); -void common_hal_audiofilters_filter_set_filters(audiofilters_filter_obj_t *self, mp_obj_t arg); +mp_obj_t common_hal_audiofilters_filter_get_filter(audiofilters_filter_obj_t *self); +void common_hal_audiofilters_filter_set_filter(audiofilters_filter_obj_t *self, mp_obj_t arg); mp_obj_t common_hal_audiofilters_filter_get_mix(audiofilters_filter_obj_t *self); void common_hal_audiofilters_filter_set_mix(audiofilters_filter_obj_t *self, mp_obj_t arg); diff --git a/shared-module/audiofilters/Filter.c b/shared-module/audiofilters/Filter.c index e22133a25b61..dd2ccb8ff881 100644 --- a/shared-module/audiofilters/Filter.c +++ b/shared-module/audiofilters/Filter.c @@ -9,7 +9,7 @@ #include "py/runtime.h" void common_hal_audiofilters_filter_construct(audiofilters_filter_obj_t *self, - mp_obj_t filters, mp_obj_t mix, + mp_obj_t filter, mp_obj_t mix, uint32_t buffer_size, uint8_t bits_per_sample, bool samples_signed, uint8_t channel_count, uint32_t sample_rate) { @@ -60,11 +60,10 @@ void common_hal_audiofilters_filter_construct(audiofilters_filter_obj_t *self, // The below section sets up the effect's starting values. - if (filters == MP_OBJ_NULL) { - filters = mp_obj_new_list(0, NULL); + if (filter == MP_OBJ_NULL) { + filter = mp_const_none; } - self->filters = filters; - reset_filter_states(self); + common_hal_audiofilters_filter_set_filter(self, filter); // If we did not receive a BlockInput we need to create a default float value if (mix == MP_OBJ_NULL) { @@ -91,32 +90,59 @@ void common_hal_audiofilters_filter_deinit(audiofilters_filter_obj_t *self) { } void reset_filter_states(audiofilters_filter_obj_t *self) { - self->filter_states_len = self->filters->len; + self->filter_states_len = 0; self->filter_states = NULL; - if (self->filter_states_len) { - self->filter_states = m_malloc(self->filter_states_len * sizeof(biquad_filter_state)); - if (self->filter_states == NULL) { - common_hal_audiofilters_filter_deinit(self); - m_malloc_fail(self->filter_states_len * sizeof(biquad_filter_state)); - } + mp_obj_t *items; + if (mp_obj_is_type(self->filter, (const mp_obj_type_t *)&synthio_biquad_type_obj)) { + self->filter_states_len = 1; + items = self->filter; + } else if (mp_obj_is_tuple_compatible(self->filter)) { + mp_obj_tuple_get(self->filter, &self->filter_states_len, &items); + } + + if (!self->filter_states_len) { + return; + } + + self->filter_states = m_malloc(self->filter_states_len * sizeof(biquad_filter_state)); + if (self->filter_states == NULL) { + common_hal_audiofilters_filter_deinit(self); + m_malloc_fail(self->filter_states_len * sizeof(biquad_filter_state)); + } - mp_obj_iter_buf_t iter_buf; - mp_obj_t iterable = mp_getiter(self->filters, &iter_buf); - mp_obj_t item; - uint8_t i = 0; - while ((item = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) { - synthio_biquad_filter_assign(&self->filter_states[i++], item); + if (mp_obj_is_type(items, (const mp_obj_type_t *)&synthio_biquad_type_obj)) { + synthio_biquad_filter_assign(&self->filter_states[0], items); + } else { + for (size_t i = 0; i < self->filter_states_len; i++) { + synthio_biquad_filter_assign(&self->filter_states[i], items[i]); } } } -mp_obj_t common_hal_audiofilters_filter_get_filters(audiofilters_filter_obj_t *self) { - return self->filters; +mp_obj_t common_hal_audiofilters_filter_get_filter(audiofilters_filter_obj_t *self) { + if (mp_obj_is_type(self->filter, (const mp_obj_type_t *)&synthio_biquad_type_obj) || mp_obj_is_tuple_compatible(self->filter)) { + return self->filter; + } else { + return mp_const_none; + } } -void common_hal_audiofilters_filter_set_filters(audiofilters_filter_obj_t *self, mp_obj_t arg) { - self->filters = arg; +void common_hal_audiofilters_filter_set_filter(audiofilters_filter_obj_t *self, mp_obj_t arg) { + if (arg == mp_const_none || mp_obj_is_type(arg, (const mp_obj_type_t *)&synthio_biquad_type_obj)) { + self->filter = arg; + } else if (mp_obj_is_tuple_compatible(arg)) { + mp_obj_tuple_t *tuple_obj = (mp_obj_tuple_t *)MP_OBJ_TO_PTR(arg); + self->filter = mp_obj_new_tuple(tuple_obj->len, tuple_obj->items); + } else if (mp_obj_is_type(arg, &mp_type_list)) { + size_t list_len; + mp_obj_t *list_items; + mp_obj_list_get(arg, &list_len, &list_items); + self->filter = mp_obj_new_tuple(list_len, list_items); + } else { + mp_raise_ValueError_varg(MP_ERROR_TEXT("%q must be a %q object, %q, or %q"), MP_QSTR_filter, MP_QSTR_Biquad, MP_QSTR_tuple, MP_QSTR_None); + } + reset_filter_states(self); } @@ -148,8 +174,10 @@ void audiofilters_filter_reset_buffer(audiofilters_filter_obj_t *self, memset(self->buffer[1], 0, self->buffer_len); memset(self->filter_buffer, 0, SYNTHIO_MAX_DUR * sizeof(int32_t)); - for (uint8_t i = 0; i < self->filter_states_len; i++) { - synthio_biquad_filter_reset(&self->filter_states[i]); + if (self->filter_states) { + for (uint8_t i = 0; i < self->filter_states_len; i++) { + synthio_biquad_filter_reset(&self->filter_states[i]); + } } } @@ -233,11 +261,6 @@ audioio_get_buffer_result_t audiofilters_filter_get_buffer(audiofilters_filter_o channel = 0; } - // Update filter_states if filters list has been appended or removed - if (self->filters->len != self->filter_states_len) { - reset_filter_states(self); - } - // get the effect values we need from the BlockInput. These may change at run time so you need to do bounds checking if required mp_float_t mix = MIN(1.0, MAX(synthio_block_slot_get(&self->mix), 0.0)); @@ -277,7 +300,7 @@ audioio_get_buffer_result_t audiofilters_filter_get_buffer(audiofilters_filter_o int16_t *sample_src = (int16_t *)self->sample_remaining_buffer; // for 16-bit samples int8_t *sample_hsrc = (int8_t *)self->sample_remaining_buffer; // for 8-bit samples - if (mix <= 0.01 || !self->filter_states_len) { // if mix is zero pure sample only or no biquad filter objects are provided + if (mix <= 0.01 || !self->filter_states) { // if mix is zero pure sample only or no biquad filter objects are provided for (uint32_t i = 0; i < n; i++) { if (MP_LIKELY(self->bits_per_sample == 16)) { word_buffer[i] = sample_src[i]; diff --git a/shared-module/audiofilters/Filter.h b/shared-module/audiofilters/Filter.h index 7d1ee4db1a39..e2ac4e5b7276 100644 --- a/shared-module/audiofilters/Filter.h +++ b/shared-module/audiofilters/Filter.h @@ -7,6 +7,7 @@ #include "py/obj.h" +#include "shared-bindings/synthio/Biquad.h" #include "shared-module/audiocore/__init__.h" #include "shared-module/synthio/block.h" #include "shared-module/synthio/Biquad.h" @@ -15,10 +16,10 @@ extern const mp_obj_type_t audiofilters_filter_type; typedef struct { mp_obj_base_t base; - mp_obj_list_t *filters; + mp_obj_t *filter; synthio_block_slot_t mix; - uint8_t filter_states_len; + size_t filter_states_len; biquad_filter_state *filter_states; uint8_t bits_per_sample; From 5f8ec0afc6f9d1e461abb01da3341050e339e122 Mon Sep 17 00:00:00 2001 From: dcooperdalrymple Date: Thu, 7 Nov 2024 13:58:01 -0600 Subject: [PATCH 4/5] Validate type of filter tuple items. --- shared-module/audiofilters/Filter.c | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/shared-module/audiofilters/Filter.c b/shared-module/audiofilters/Filter.c index dd2ccb8ff881..82a9c9f81edd 100644 --- a/shared-module/audiofilters/Filter.c +++ b/shared-module/audiofilters/Filter.c @@ -131,14 +131,18 @@ mp_obj_t common_hal_audiofilters_filter_get_filter(audiofilters_filter_obj_t *se void common_hal_audiofilters_filter_set_filter(audiofilters_filter_obj_t *self, mp_obj_t arg) { if (arg == mp_const_none || mp_obj_is_type(arg, (const mp_obj_type_t *)&synthio_biquad_type_obj)) { self->filter = arg; - } else if (mp_obj_is_tuple_compatible(arg)) { - mp_obj_tuple_t *tuple_obj = (mp_obj_tuple_t *)MP_OBJ_TO_PTR(arg); - self->filter = mp_obj_new_tuple(tuple_obj->len, tuple_obj->items); - } else if (mp_obj_is_type(arg, &mp_type_list)) { - size_t list_len; - mp_obj_t *list_items; - mp_obj_list_get(arg, &list_len, &list_items); - self->filter = mp_obj_new_tuple(list_len, list_items); + } else if (mp_obj_is_tuple_compatible(arg) || mp_obj_is_type(arg, &mp_type_list)) { + size_t tuple_len; + mp_obj_t *tuple_items = NULL; + + mp_obj_get_array(arg, &tuple_len, &tuple_items); + + mp_obj_t *biquad_objects[tuple_len]; + for (size_t i = 0; i < tuple_len; i++) { + biquad_objects[i] = mp_arg_validate_type_in(tuple_items[i], (const mp_obj_type_t *)&synthio_biquad_type_obj, MP_QSTR_filter); + } + + self->filter = mp_obj_new_tuple(tuple_len, (const mp_obj_t *)biquad_objects); } else { mp_raise_ValueError_varg(MP_ERROR_TEXT("%q must be a %q object, %q, or %q"), MP_QSTR_filter, MP_QSTR_Biquad, MP_QSTR_tuple, MP_QSTR_None); } From c96d1428e1cef185675370d761ef6fccdb0edae8 Mon Sep 17 00:00:00 2001 From: dcooperdalrymple Date: Thu, 7 Nov 2024 19:06:25 -0600 Subject: [PATCH 5/5] Remove unnecessary `m_malloc_fail` and deinit from memory allocation routines. --- shared-module/audiofilters/Filter.c | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/shared-module/audiofilters/Filter.c b/shared-module/audiofilters/Filter.c index 82a9c9f81edd..ea0a2058f2e8 100644 --- a/shared-module/audiofilters/Filter.c +++ b/shared-module/audiofilters/Filter.c @@ -28,27 +28,15 @@ void common_hal_audiofilters_filter_construct(audiofilters_filter_obj_t *self, self->buffer_len = buffer_size; // in bytes self->buffer[0] = m_malloc(self->buffer_len); - if (self->buffer[0] == NULL) { - common_hal_audiofilters_filter_deinit(self); - m_malloc_fail(self->buffer_len); - } memset(self->buffer[0], 0, self->buffer_len); self->buffer[1] = m_malloc(self->buffer_len); - if (self->buffer[1] == NULL) { - common_hal_audiofilters_filter_deinit(self); - m_malloc_fail(self->buffer_len); - } memset(self->buffer[1], 0, self->buffer_len); self->last_buf_idx = 1; // Which buffer to use first, toggle between 0 and 1 // This buffer will be used to process samples through the biquad filter self->filter_buffer = m_malloc(SYNTHIO_MAX_DUR * sizeof(int32_t)); - if (self->filter_buffer == NULL) { - common_hal_audiofilters_filter_deinit(self); - m_malloc_fail(SYNTHIO_MAX_DUR * sizeof(int32_t)); - } memset(self->filter_buffer, 0, SYNTHIO_MAX_DUR * sizeof(int32_t)); // Initialize other values most effects will need. @@ -106,10 +94,6 @@ void reset_filter_states(audiofilters_filter_obj_t *self) { } self->filter_states = m_malloc(self->filter_states_len * sizeof(biquad_filter_state)); - if (self->filter_states == NULL) { - common_hal_audiofilters_filter_deinit(self); - m_malloc_fail(self->filter_states_len * sizeof(biquad_filter_state)); - } if (mp_obj_is_type(items, (const mp_obj_type_t *)&synthio_biquad_type_obj)) { synthio_biquad_filter_assign(&self->filter_states[0], items);