Skip to content

Commit

Permalink
Merge pull request adafruit#9772 from relic-se/audiofilters_filterlist
Browse files Browse the repository at this point in the history
  • Loading branch information
jepler authored Nov 10, 2024
2 parents 6d32c56 + c96d142 commit de9b0b6
Show file tree
Hide file tree
Showing 4 changed files with 91 additions and 34 deletions.
11 changes: 9 additions & 2 deletions locale/circuitpython.pot
Original file line number Diff line number Diff line change
Expand Up @@ -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 ""
Expand Down Expand Up @@ -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 ""

Expand Down Expand Up @@ -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 ""

Expand Down Expand Up @@ -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 ""

Expand Down
22 changes: 13 additions & 9 deletions shared-bindings/audiofilters/Filter.c
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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
Expand All @@ -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:
Expand All @@ -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} },
Expand Down Expand Up @@ -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);

Expand Down
84 changes: 63 additions & 21 deletions shared-module/audiofilters/Filter.c
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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) {
Expand All @@ -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) {
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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];
Expand All @@ -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++) {
Expand Down
8 changes: 6 additions & 2 deletions shared-module/audiofilters/Filter.h
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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;
Expand All @@ -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);
Expand Down

0 comments on commit de9b0b6

Please sign in to comment.