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 457897909e0d..e8e6fbfa5e7f 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, +//| 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[synthio.Biquad] filter: The normalized biquad filter object 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 @@ -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.filter = synth.low_pass_filter(frequency=2000, Q=1.25) +//| effect.play(synth) +//| audio.play(effect) //| //| note = synthio.Note(261) //| while True: @@ -70,7 +71,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_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} }, @@ -128,10 +129,13 @@ 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.""" +//| 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_filter(mp_obj_t self_in) { - return common_hal_audiofilters_filter_get_filter(self_in); + audiofilters_filter_obj_t *self = MP_OBJ_TO_PTR(self_in); + check_for_deinit(self); + return common_hal_audiofilters_filter_get_filter(self); } MP_DEFINE_CONST_FUN_OBJ_1(audiofilters_filter_get_filter_obj, audiofilters_filter_obj_get_filter); diff --git a/shared-module/audiofilters/Filter.c b/shared-module/audiofilters/Filter.c index 6b8fc85dd4e2..dc3efe18ef49 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. @@ -63,8 +51,7 @@ void common_hal_audiofilters_filter_construct(audiofilters_filter_obj_t *self, if (filter == MP_OBJ_NULL) { filter = mp_const_none; } - synthio_biquad_filter_assign(&self->filter_state, filter); - self->filter_obj = filter; + 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) { @@ -87,15 +74,64 @@ 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 = 0; + self->filter_states = NULL; + + 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 (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_filter(audiofilters_filter_obj_t *self) { - return self->filter_obj; + 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_filter(audiofilters_filter_obj_t *self, mp_obj_t arg) { - synthio_biquad_filter_assign(&self->filter_state, arg); - self->filter_obj = 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_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); + } + + reset_filter_states(self); } mp_obj_t common_hal_audiofilters_filter_get_mix(audiofilters_filter_obj_t *self) { @@ -126,7 +162,11 @@ 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); + if (self->filter_states) { + 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) { @@ -265,7 +305,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) { // 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]; @@ -292,8 +332,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..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,11 @@ extern const mp_obj_type_t audiofilters_filter_type; typedef struct { mp_obj_base_t base; - mp_obj_t filter_obj; + mp_obj_t *filter; synthio_block_slot_t mix; - biquad_filter_state filter_state; + size_t filter_states_len; + biquad_filter_state *filter_states; uint8_t bits_per_sample; bool samples_signed; @@ -40,6 +42,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);