Skip to content

Commit 5bf33bd

Browse files
committed
Audiofilter: Add support for block biquads
& test by filtering some noise with a pair of BlockBiquads
1 parent 7f74061 commit 5bf33bd

File tree

7 files changed

+102487
-52
lines changed

7 files changed

+102487
-52
lines changed

shared-bindings/audiofilters/Filter.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
//|
2424
//| def __init__(
2525
//| self,
26-
//| filter: Optional[synthio.Biquad | Tuple[synthio.Biquad]] = None,
26+
//| filter: Optional[synthio.AnyBiquad | Tuple[synthio.AnyBiquad]] = None,
2727
//| mix: synthio.BlockInput = 1.0,
2828
//| buffer_size: int = 512,
2929
//| sample_rate: int = 8000,
@@ -38,7 +38,7 @@
3838
//| The mix parameter allows you to change how much of the unchanged sample passes through to
3939
//| the output to how much of the effect audio you hear as the output.
4040
//|
41-
//| :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.
41+
//| :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.
4242
//| :param synthio.BlockInput mix: The mix as a ratio of the sample (0.0) to the effect (1.0).
4343
//| :param int buffer_size: The total size in bytes of each of the two playback buffers to use
4444
//| :param int sample_rate: The sample rate to be used
@@ -129,7 +129,7 @@ static mp_obj_t audiofilters_filter_obj___exit__(size_t n_args, const mp_obj_t *
129129
static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(audiofilters_filter___exit___obj, 4, 4, audiofilters_filter_obj___exit__);
130130

131131

132-
//| filter: synthio.Biquad | Tuple[synthio.Biquad] | None
132+
//| filter: synthio.AnyBiquad | Tuple[synthio.AnyBiquad] | None
133133
//| """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."""
134134
//|
135135
static mp_obj_t audiofilters_filter_obj_get_filter(mp_obj_t self_in) {

shared-module/audiofilters/Filter.c

Lines changed: 50 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
// SPDX-License-Identifier: MIT
66
#include "shared-bindings/audiofilters/Filter.h"
77

8+
#include "shared-module/synthio/BlockBiquad.h"
89
#include <stdint.h>
910
#include "py/runtime.h"
1011

@@ -66,65 +67,62 @@ void common_hal_audiofilters_filter_deinit(audiofilters_filter_obj_t *self) {
6667
}
6768
self->buffer[0] = NULL;
6869
self->buffer[1] = NULL;
70+
self->filter = mp_const_none;
6971
self->filter_buffer = NULL;
7072
self->filter_states = NULL;
7173
}
7274

73-
void reset_filter_states(audiofilters_filter_obj_t *self) {
74-
self->filter_states_len = 0;
75-
self->filter_states = NULL;
76-
75+
void common_hal_audiofilters_filter_set_filter(audiofilters_filter_obj_t *self, mp_obj_t filter_in) {
76+
size_t n_items;
7777
mp_obj_t *items;
78-
if (mp_obj_is_type(self->filter, (const mp_obj_type_t *)&synthio_biquad_type_obj)) {
79-
self->filter_states_len = 1;
80-
items = self->filter;
81-
} else if (mp_obj_is_tuple_compatible(self->filter)) {
82-
mp_obj_tuple_get(self->filter, &self->filter_states_len, &items);
83-
}
84-
85-
if (!self->filter_states_len) {
86-
return;
87-
}
88-
89-
self->filter_states = m_malloc(self->filter_states_len * sizeof(biquad_filter_state));
90-
91-
if (mp_obj_is_type(items, (const mp_obj_type_t *)&synthio_biquad_type_obj)) {
92-
synthio_biquad_filter_assign(&self->filter_states[0], items);
93-
} else {
94-
for (size_t i = 0; i < self->filter_states_len; i++) {
95-
synthio_biquad_filter_assign(&self->filter_states[i], items[i]);
78+
mp_obj_t *filter_objs;
79+
80+
if (filter_in == mp_const_none) {
81+
n_items = 0;
82+
filter_objs = NULL;
83+
} else if (MP_OBJ_TYPE_HAS_SLOT(mp_obj_get_type(filter_in), iter)) {
84+
// convert object to tuple if it wasn't before
85+
filter_in = MP_OBJ_TYPE_GET_SLOT(&mp_type_tuple, make_new)(
86+
&mp_type_tuple, 1, 0, &filter_in);
87+
mp_obj_tuple_get(filter_in, &n_items, &items);
88+
for (size_t i = 0; i < n_items; i++) {
89+
if (!synthio_is_any_biquad(items[i])) {
90+
mp_raise_TypeError_varg(
91+
MP_ERROR_TEXT("%q in %q must be of type %q, not %q"),
92+
MP_QSTR_object,
93+
MP_QSTR_filter,
94+
MP_QSTR_AnyBiquad,
95+
mp_obj_get_type(items[i])->name);
96+
}
9697
}
97-
}
98-
}
99-
100-
mp_obj_t common_hal_audiofilters_filter_get_filter(audiofilters_filter_obj_t *self) {
101-
if (mp_obj_is_type(self->filter, (const mp_obj_type_t *)&synthio_biquad_type_obj) || mp_obj_is_tuple_compatible(self->filter)) {
102-
return self->filter;
98+
filter_objs = items;
10399
} else {
104-
return mp_const_none;
100+
n_items = 1;
101+
if (!synthio_is_any_biquad(filter_in)) {
102+
mp_raise_TypeError_varg(
103+
MP_ERROR_TEXT("%q must be of type %q or %q, not %q"),
104+
MP_QSTR_filter, MP_QSTR_AnyBiquad, MP_QSTR_iterable, mp_obj_get_type(filter_in)->name);
105+
}
106+
filter_objs = &self->filter;
105107
}
106-
}
107-
108-
void common_hal_audiofilters_filter_set_filter(audiofilters_filter_obj_t *self, mp_obj_t arg) {
109-
if (arg == mp_const_none || mp_obj_is_type(arg, (const mp_obj_type_t *)&synthio_biquad_type_obj)) {
110-
self->filter = arg;
111-
} else if (mp_obj_is_tuple_compatible(arg) || mp_obj_is_type(arg, &mp_type_list)) {
112-
size_t tuple_len;
113-
mp_obj_t *tuple_items = NULL;
114108

115-
mp_obj_get_array(arg, &tuple_len, &tuple_items);
109+
// everything has been checked, so we can do the following without fear
116110

117-
mp_obj_t *biquad_objects[tuple_len];
118-
for (size_t i = 0; i < tuple_len; i++) {
119-
biquad_objects[i] = mp_arg_validate_type_in(tuple_items[i], (const mp_obj_type_t *)&synthio_biquad_type_obj, MP_QSTR_filter);
120-
}
111+
self->filter = filter_in;
112+
self->filter_objs = filter_objs;
113+
self->filter_states = m_renew(biquad_filter_state,
114+
self->filter_states,
115+
self->filter_states_len,
116+
n_items);
117+
self->filter_states_len = n_items;
121118

122-
self->filter = mp_obj_new_tuple(tuple_len, (const mp_obj_t *)biquad_objects);
123-
} else {
124-
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);
119+
for (size_t i = 0; i < n_items; i++) {
120+
synthio_biquad_filter_assign(&self->filter_states[i], items[i]);
125121
}
122+
}
126123

127-
reset_filter_states(self);
124+
mp_obj_t common_hal_audiofilters_filter_get_filter(audiofilters_filter_obj_t *self) {
125+
return self->filter;
128126
}
129127

130128
mp_obj_t common_hal_audiofilters_filter_get_mix(audiofilters_filter_obj_t *self) {
@@ -243,6 +241,8 @@ audioio_get_buffer_result_t audiofilters_filter_get_buffer(audiofilters_filter_o
243241
channel = 0;
244242
}
245243

244+
shared_bindings_synthio_lfo_tick(self->sample_rate);
245+
246246
// get the effect values we need from the BlockInput. These may change at run time so you need to do bounds checking if required
247247
mp_float_t mix = MIN(1.0, MAX(synthio_block_slot_get(&self->mix), 0.0));
248248

@@ -327,6 +327,10 @@ audioio_get_buffer_result_t audiofilters_filter_get_buffer(audiofilters_filter_o
327327

328328
// Process biquad filters
329329
for (uint8_t j = 0; j < self->filter_states_len; j++) {
330+
mp_obj_t filter_obj = self->filter_objs[j];
331+
if (mp_obj_is_type(filter_obj, &synthio_block_biquad_type_obj)) {
332+
common_hal_synthio_block_biquad_tick(filter_obj, &self->filter_states[j]);
333+
}
330334
synthio_biquad_filter_samples(&self->filter_states[j], self->filter_buffer, n_samples);
331335
}
332336

shared-module/audiofilters/Filter.h

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,10 @@ extern const mp_obj_type_t audiofilters_filter_type;
1616

1717
typedef struct {
1818
mp_obj_base_t base;
19-
mp_obj_t *filter;
19+
mp_obj_t filter;
2020
synthio_block_slot_t mix;
2121

22+
mp_obj_t *filter_objs;
2223
size_t filter_states_len;
2324
biquad_filter_state *filter_states;
2425

@@ -42,8 +43,6 @@ typedef struct {
4243
mp_obj_t sample;
4344
} audiofilters_filter_obj_t;
4445

45-
void reset_filter_states(audiofilters_filter_obj_t *self);
46-
4746
void audiofilters_filter_reset_buffer(audiofilters_filter_obj_t *self,
4847
bool single_channel_output,
4948
uint8_t channel);

shared-module/synthio/__init__.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@
1414
#define SYNTHIO_NOTE_IS_PLAYING(synth, i) ((synth)->envelope_state[(i)].state != SYNTHIO_ENVELOPE_STATE_RELEASE)
1515
#define SYNTHIO_FREQUENCY_SHIFT (16)
1616

17+
#include "shared-bindings/synthio/Biquad.h"
18+
#include "shared-bindings/synthio/BlockBiquad.h"
19+
1720
#include "shared-module/audiocore/__init__.h"
1821
#include "shared-bindings/synthio/__init__.h"
1922

@@ -91,3 +94,8 @@ int synthio_sweep_in_step(synthio_lfo_state_t *state, uint16_t dur);
9194
extern mp_float_t synthio_global_rate_scale, synthio_global_W_scale;
9295
extern uint8_t synthio_global_tick;
9396
void shared_bindings_synthio_lfo_tick(uint32_t sample_rate);
97+
98+
static inline bool synthio_is_any_biquad(mp_obj_t biquad_maybe) {
99+
return mp_obj_is_type(biquad_maybe, &synthio_block_biquad_type_obj)
100+
|| mp_obj_is_type(biquad_maybe, (const mp_obj_type_t *)&synthio_biquad_type_obj);
101+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
from audiofilters import Filter
2+
from audiofilterhelper import synth_test, white8k
3+
from synthio import BlockBiquad, FilterMode
4+
5+
@synth_test
6+
def basic_filter():
7+
effect = Filter(
8+
filter=[
9+
BlockBiquad(FilterMode.LOW_PASS, 400),
10+
BlockBiquad(FilterMode.HIGH_PASS, 300, Q=8),
11+
],
12+
bits_per_sample=16,
13+
samples_signed=True,
14+
)
15+
yield effect, []
16+
17+
effect.play(white8k, loop=True)
18+
yield 400

0 commit comments

Comments
 (0)