Skip to content

Commit 8b20c84

Browse files
committed
Audiofilter: Add support for block biquads
& test by filtering some noise with a pair of BlockBiquads
1 parent ffae82d commit 8b20c84

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
@@ -24,7 +24,7 @@
2424
//|
2525
//| def __init__(
2626
//| self,
27-
//| filter: Optional[synthio.Biquad | Tuple[synthio.Biquad]] = None,
27+
//| filter: Optional[synthio.AnyBiquad | Tuple[synthio.AnyBiquad]] = None,
2828
//| mix: synthio.BlockInput = 1.0,
2929
//| buffer_size: int = 512,
3030
//| sample_rate: int = 8000,
@@ -39,7 +39,7 @@
3939
//| The mix parameter allows you to change how much of the unchanged sample passes through to
4040
//| the output to how much of the effect audio you hear as the output.
4141
//|
42-
//| :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.
42+
//| :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.
4343
//| :param synthio.BlockInput mix: The mix as a ratio of the sample (0.0) to the effect (1.0).
4444
//| :param int buffer_size: The total size in bytes of each of the two playback buffers to use
4545
//| :param int sample_rate: The sample rate to be used
@@ -132,7 +132,7 @@ static mp_obj_t audiofilters_filter_obj___exit__(size_t n_args, const mp_obj_t *
132132
static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(audiofilters_filter___exit___obj, 4, 4, audiofilters_filter_obj___exit__);
133133

134134

135-
//| filter: synthio.Biquad | Tuple[synthio.Biquad] | None
135+
//| filter: synthio.AnyBiquad | Tuple[synthio.AnyBiquad] | None
136136
//| """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."""
137137
//|
138138
static mp_obj_t audiofilters_filter_obj_get_filter(mp_obj_t self_in) {

shared-module/audiofilters/Filter.c

Lines changed: 51 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

@@ -68,65 +69,62 @@ void common_hal_audiofilters_filter_deinit(audiofilters_filter_obj_t *self) {
6869
}
6970
self->buffer[0] = NULL;
7071
self->buffer[1] = NULL;
72+
self->filter = mp_const_none;
7173
self->filter_buffer = NULL;
7274
self->filter_states = NULL;
7375
}
7476

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

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

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

124-
self->filter = mp_obj_new_tuple(tuple_len, (const mp_obj_t *)biquad_objects);
125-
} else {
126-
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);
121+
for (size_t i = 0; i < n_items; i++) {
122+
synthio_biquad_filter_assign(&self->filter_states[i], items[i]);
127123
}
124+
}
128125

129-
reset_filter_states(self);
126+
mp_obj_t common_hal_audiofilters_filter_get_filter(audiofilters_filter_obj_t *self) {
127+
return self->filter;
130128
}
131129

132130
mp_obj_t common_hal_audiofilters_filter_get_mix(audiofilters_filter_obj_t *self) {
@@ -187,6 +185,9 @@ audioio_get_buffer_result_t audiofilters_filter_get_buffer(audiofilters_filter_o
187185
channel = 0;
188186
}
189187

188+
// get the effect values we need from the BlockInput. These may change at run time so you need to do bounds checking if required
189+
mp_float_t mix = MIN(1.0, MAX(synthio_block_slot_get(&self->mix), 0.0));
190+
190191
// Switch our buffers to the other buffer
191192
self->last_buf_idx = !self->last_buf_idx;
192193

@@ -276,6 +277,10 @@ audioio_get_buffer_result_t audiofilters_filter_get_buffer(audiofilters_filter_o
276277

277278
// Process biquad filters
278279
for (uint8_t j = 0; j < self->filter_states_len; j++) {
280+
mp_obj_t filter_obj = self->filter_objs[j];
281+
if (mp_obj_is_type(filter_obj, &synthio_block_biquad_type_obj)) {
282+
common_hal_synthio_block_biquad_tick(filter_obj, &self->filter_states[j]);
283+
}
279284
synthio_biquad_filter_samples(&self->filter_states[j], self->filter_buffer, n_samples);
280285
}
281286

shared-module/audiofilters/Filter.h

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

1818
typedef struct {
1919
audiosample_base_t base;
20-
mp_obj_t *filter;
20+
mp_obj_t filter;
2121
synthio_block_slot_t mix;
2222

23+
mp_obj_t *filter_objs;
2324
size_t filter_states_len;
2425
biquad_filter_state *filter_states;
2526

@@ -38,8 +39,6 @@ typedef struct {
3839
mp_obj_t sample;
3940
} audiofilters_filter_obj_t;
4041

41-
void reset_filter_states(audiofilters_filter_obj_t *self);
42-
4342
void audiofilters_filter_reset_buffer(audiofilters_filter_obj_t *self,
4443
bool single_channel_output,
4544
uint8_t channel);

shared-module/synthio/__init__.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020

2121
#include "shared-module/audiocore/__init__.h"
2222
#include "shared-bindings/synthio/__init__.h"
23+
#include "shared-bindings/synthio/Biquad.h"
24+
#include "shared-bindings/synthio/BlockBiquad.h"
2325

2426
typedef struct {
2527
uint16_t dur;
@@ -94,3 +96,8 @@ int synthio_sweep_in_step(synthio_lfo_state_t *state, uint16_t dur);
9496
extern mp_float_t synthio_global_rate_scale, synthio_global_W_scale;
9597
extern uint8_t synthio_global_tick;
9698
void shared_bindings_synthio_lfo_tick(uint32_t sample_rate, uint16_t num_samples);
99+
100+
static inline bool synthio_is_any_biquad(mp_obj_t biquad_maybe) {
101+
return mp_obj_is_type(biquad_maybe, &synthio_block_biquad_type_obj)
102+
|| mp_obj_is_type(biquad_maybe, (const mp_obj_type_t *)&synthio_biquad_type_obj);
103+
}
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)