Skip to content

Commit e7c02bd

Browse files
committed
Biquad filter processing.
1 parent db540c6 commit e7c02bd

File tree

2 files changed

+83
-20
lines changed

2 files changed

+83
-20
lines changed

shared-module/audiofilters/Filter.c

Lines changed: 81 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,22 @@ void common_hal_audiofilters_filter_construct(audiofilters_filter_obj_t *self,
4343

4444
self->last_buf_idx = 1; // Which buffer to use first, toggle between 0 and 1
4545

46+
// This buffer will be used to process samples through the biquad filter
47+
self->filter_buffer[0] = m_malloc(SYNTHIO_MAX_DUR * sizeof(int32_t));
48+
if (self->filter_buffer[0] == NULL) {
49+
common_hal_audiofilters_filter_deinit(self);
50+
m_malloc_fail(SYNTHIO_MAX_DUR * sizeof(int32_t));
51+
}
52+
memset(self->filter_buffer[0], 0, SYNTHIO_MAX_DUR * sizeof(int32_t));
53+
54+
// This buffer will be used to mix original sample with processed signal
55+
self->filter_buffer[1] = m_malloc(SYNTHIO_MAX_DUR * sizeof(int32_t));
56+
if (self->filter_buffer[1] == NULL) {
57+
common_hal_audiofilters_filter_deinit(self);
58+
m_malloc_fail(SYNTHIO_MAX_DUR * sizeof(int32_t));
59+
}
60+
memset(self->filter_buffer[1], 0, SYNTHIO_MAX_DUR * sizeof(int32_t));
61+
4662
// Initialize other values most effects will need.
4763
self->sample = NULL; // The current playing sample
4864
self->sample_remaining_buffer = NULL; // Pointer to the start of the sample buffer we have not played
@@ -78,6 +94,8 @@ void common_hal_audiofilters_filter_deinit(audiofilters_filter_obj_t *self) {
7894
}
7995
self->buffer[0] = NULL;
8096
self->buffer[1] = NULL;
97+
self->filter_buffer[0] = NULL;
98+
self->filter_buffer[1] = NULL;
8199
}
82100

83101
mp_obj_t common_hal_audiofilters_filter_get_filter(audiofilters_filter_obj_t *self) {
@@ -115,6 +133,8 @@ void audiofilters_filter_reset_buffer(audiofilters_filter_obj_t *self,
115133

116134
memset(self->buffer[0], 0, self->buffer_len);
117135
memset(self->buffer[1], 0, self->buffer_len);
136+
memset(self->filter_buffer[0], 0, SYNTHIO_MAX_DUR * sizeof(int32_t));
137+
memset(self->filter_buffer[1], 0, SYNTHIO_MAX_DUR * sizeof(int32_t));
118138

119139
synthio_biquad_filter_reset(&self->filter_state);
120140
}
@@ -166,6 +186,32 @@ void common_hal_audiofilters_filter_stop(audiofilters_filter_obj_t *self) {
166186
return;
167187
}
168188

189+
#define RANGE_LOW_16 (-28000)
190+
#define RANGE_HIGH_16 (28000)
191+
#define RANGE_SHIFT_16 (16)
192+
#define RANGE_SCALE_16 (0xfffffff / (32768 * 2 - RANGE_HIGH_16)) // 2 for echo+sample
193+
194+
// dynamic range compression via a downward compressor with hard knee
195+
//
196+
// When the output value is within the range +-28000 (about 85% of full scale),
197+
// it is unchanged. Otherwise, it undergoes a gain reduction so that the
198+
// largest possible values, (+32768,-32767) * 2 (2 for echo and sample),
199+
// still fit within the output range
200+
//
201+
// This produces a much louder overall volume with multiple voices, without
202+
// much additional processing.
203+
//
204+
// https://en.wikipedia.org/wiki/Dynamic_range_compression
205+
static
206+
int16_t mix_down_sample(int32_t sample) {
207+
if (sample < RANGE_LOW_16) {
208+
sample = (((sample - RANGE_LOW_16) * RANGE_SCALE_16) >> RANGE_SHIFT_16) + RANGE_LOW_16;
209+
} else if (sample > RANGE_HIGH_16) {
210+
sample = (((sample - RANGE_HIGH_16) * RANGE_SCALE_16) >> RANGE_SHIFT_16) + RANGE_HIGH_16;
211+
}
212+
return sample;
213+
}
214+
169215
audioio_get_buffer_result_t audiofilters_filter_get_buffer(audiofilters_filter_obj_t *self, bool single_channel_output, uint8_t channel,
170216
uint8_t **buffer, uint32_t *buffer_length) {
171217

@@ -221,34 +267,49 @@ audioio_get_buffer_result_t audiofilters_filter_get_buffer(audiofilters_filter_o
221267
}
222268
}
223269
} else {
224-
for (uint32_t i = 0; i < n; i++) {
225-
int32_t sample_word = 0;
226-
if (MP_LIKELY(self->bits_per_sample == 16)) {
227-
sample_word = sample_src[i];
228-
} else {
229-
if (self->samples_signed) {
230-
sample_word = sample_hsrc[i];
270+
uint32_t i = 0;
271+
while (i < n) {
272+
uint32_t n_samples = MIN(SYNTHIO_MAX_DUR, n - i);
273+
274+
// Fill filter buffer with samples
275+
for (uint32_t j = 0; j < n_samples; j++) {
276+
if (MP_LIKELY(self->bits_per_sample == 16)) {
277+
self->filter_buffer[0][j] = sample_src[i + j];
231278
} else {
232-
// Be careful here changing from an 8 bit unsigned to signed into a 32-bit signed
233-
sample_word = (int8_t)(((uint8_t)sample_hsrc[i]) ^ 0x80);
279+
if (self->samples_signed) {
280+
self->filter_buffer[0][j] = sample_hsrc[i + j];
281+
} else {
282+
// Be careful here changing from an 8 bit unsigned to signed into a 32-bit signed
283+
self->filter_buffer[0][j] = (int8_t)(((uint8_t)sample_hsrc[i + j]) ^ 0x80);
284+
}
234285
}
235286
}
236287

237-
// TODO: Filter through synthio_biquad_filter_samples
288+
// Copy original signal for mixing back in later
289+
memcpy(self->filter_buffer[1], self->filter_buffer[0], n_samples);
238290

239-
if (MP_LIKELY(self->bits_per_sample == 16)) {
240-
word_buffer[i] = (sample_word * (1.0 - mix)) + (word * mix);
241-
if (!self->samples_signed) {
242-
word_buffer[i] ^= 0x8000;
243-
}
244-
} else {
245-
int8_t mixed = (sample_word * (1.0 - mix)) + (word * mix);
246-
if (self->samples_signed) {
247-
hword_buffer[i] = mixed;
291+
// Process biquad filter
292+
synthio_biquad_filter_samples(&self->filter_state, self->filter_buffer[0], n_samples);
293+
294+
// Mix processed signal with original sample and transfer to output buffer
295+
for (uint32_t j = 0; j < n_samples; j++) {
296+
int32_t word = (self->filter_buffer[1][j] * (1.0 - mix)) + (self->filter_buffer[0][j] * mix);
297+
if (MP_LIKELY(self->bits_per_sample == 16)) {
298+
word_buffer[i + j] = mix_down_sample(word);
299+
if (!self->samples_signed) {
300+
word_buffer[i + j] ^= 0x8000;
301+
}
248302
} else {
249-
hword_buffer[i] = (uint8_t)mixed ^ 0x80;
303+
int8_t mixed = word;
304+
if (self->samples_signed) {
305+
hword_buffer[i + j] = mixed;
306+
} else {
307+
hword_buffer[i + j] = (uint8_t)mixed ^ 0x80;
308+
}
250309
}
251310
}
311+
312+
i += n_samples;
252313
}
253314
}
254315

shared-module/audiofilters/Filter.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ typedef struct {
3232
uint8_t *sample_remaining_buffer;
3333
uint32_t sample_buffer_length;
3434

35+
int32_t *filter_buffer[2];
36+
3537
bool loop;
3638
bool more_data;
3739

0 commit comments

Comments
 (0)