Skip to content

Commit bc7feb3

Browse files
committed
synthio: implement FIR filtering
A 37-tap filter worked fine on RP2040 (prop maker) at 48kHz, so you can probably pretty much go nuts on M7.
1 parent f52bb65 commit bc7feb3

File tree

1 file changed

+58
-8
lines changed

1 file changed

+58
-8
lines changed

shared-module/synthio/__init__.c

Lines changed: 58 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ static void synth_note_into_buffer(synthio_synth_t *synth, int chan, int32_t *ou
192192

193193
uint32_t dds_rate;
194194
const int16_t *waveform = synth->waveform_bufinfo.buf;
195-
uint32_t waveform_length = synth->waveform_bufinfo.len / 2;
195+
uint32_t waveform_length = synth->waveform_bufinfo.len / sizeof(int16_t);
196196

197197
uint32_t ring_dds_rate = 0;
198198
const int16_t *ring_waveform = NULL;
@@ -213,15 +213,15 @@ static void synth_note_into_buffer(synthio_synth_t *synth, int chan, int32_t *ou
213213
int32_t frequency_scaled = synthio_note_step(note, sample_rate, dur, loudness);
214214
if (note->waveform_buf.buf) {
215215
waveform = note->waveform_buf.buf;
216-
waveform_length = note->waveform_buf.len / 2;
216+
waveform_length = note->waveform_buf.len / sizeof(int16_t);
217217
}
218218
dds_rate = synthio_frequency_convert_scaled_to_dds((uint64_t)frequency_scaled * waveform_length, sample_rate);
219219
if (note->ring_frequency_scaled != 0 && note->ring_waveform_buf.buf) {
220220
ring_waveform = note->ring_waveform_buf.buf;
221-
ring_waveform_length = note->ring_waveform_buf.len / 2;
221+
ring_waveform_length = note->ring_waveform_buf.len / sizeof(int16_t);
222222
ring_dds_rate = synthio_frequency_convert_scaled_to_dds((uint64_t)note->ring_frequency_scaled * ring_waveform_length, sample_rate);
223223
uint32_t lim = ring_waveform_length << SYNTHIO_FREQUENCY_SHIFT;
224-
if (ring_dds_rate > lim / 2) {
224+
if (ring_dds_rate > lim / sizeof(int16_t)) {
225225
ring_dds_rate = 0; // can't ring at that frequency
226226
}
227227
}
@@ -309,6 +309,37 @@ static void synth_note_into_buffer(synthio_synth_t *synth, int chan, int32_t *ou
309309
}
310310
}
311311

312+
STATIC void run_fir(synthio_synth_t *synth, int32_t *out_buffer32, uint16_t dur) {
313+
int16_t *coeff = (int16_t *)synth->filter_bufinfo.buf;
314+
size_t fir_len = synth->filter_bufinfo.len / sizeof(int16_t);
315+
int32_t *in_buf = synth->filter_buffer;
316+
317+
// FIR and copy values to output buffer
318+
for (int16_t i = 0; i < dur; i++) {
319+
int32_t acc = 0;
320+
for (size_t j = 0; j < fir_len; j++) {
321+
// shift 5 here is good for up to 32 filtered voices, else might wrap
322+
acc = acc + (in_buf[j] * (coeff[j] >> 5));
323+
}
324+
*out_buffer32++ = acc >> 10;
325+
in_buf++;
326+
}
327+
328+
// Move values down so that they get filtered next time
329+
memmove(synth->filter_buffer, &synth->filter_buffer[dur], fir_len * sizeof(int32_t));
330+
}
331+
332+
STATIC bool synthio_synth_get_note_filtered(mp_obj_t note_obj) {
333+
if (note_obj == mp_const_none) {
334+
return false;
335+
}
336+
if (!mp_obj_is_small_int(note_obj)) {
337+
synthio_note_obj_t *note = MP_OBJ_TO_PTR(note_obj);
338+
return note->filter;
339+
}
340+
return true;
341+
}
342+
312343
void synthio_synth_synthesize(synthio_synth_t *synth, uint8_t **bufptr, uint32_t *buffer_length, uint8_t channel) {
313344

314345
if (channel == synth->other_channel) {
@@ -326,12 +357,31 @@ void synthio_synth_synthesize(synthio_synth_t *synth, uint8_t **bufptr, uint32_t
326357

327358
int32_t out_buffer32[dur * synth->channel_count];
328359

329-
memset(out_buffer32, 0, sizeof(out_buffer32));
360+
if (synth->filter_buffer) {
361+
int32_t *filter_start = &synth->filter_buffer[synth->filter_bufinfo.len * synth->channel_count / sizeof(int16_t)];
362+
memset(filter_start, 0, dur * sizeof(int32_t));
363+
364+
for (int chan = 0; chan < CIRCUITPY_SYNTHIO_MAX_CHANNELS; chan++) {
365+
mp_obj_t note_obj = synth->span.note_obj[chan];
366+
if (!synthio_synth_get_note_filtered(note_obj)) {
367+
continue;
368+
}
369+
synth_note_into_buffer(synth, chan, filter_start, dur);
370+
}
371+
372+
run_fir(synth, out_buffer32, dur);
373+
} else {
374+
memset(out_buffer32, 0, sizeof(out_buffer32));
375+
}
376+
330377
for (int chan = 0; chan < CIRCUITPY_SYNTHIO_MAX_CHANNELS; chan++) {
378+
mp_obj_t note_obj = synth->span.note_obj[chan];
379+
if (synth->filter_buffer && synthio_synth_get_note_filtered(note_obj)) {
380+
continue;
381+
}
331382
synth_note_into_buffer(synth, chan, out_buffer32, dur);
332383
}
333384

334-
335385
int16_t *out_buffer16 = (int16_t *)(void *)synth->buffers[synth->buffer_index];
336386

337387
// mix down audio
@@ -387,7 +437,7 @@ void synthio_synth_init(synthio_synth_t *synth, uint32_t sample_rate, int channe
387437
synth->buffers[0] = m_malloc(synth->buffer_length, false);
388438
synth->buffers[1] = m_malloc(synth->buffer_length, false);
389439
if (synth->filter_bufinfo.len) {
390-
synth->filter_buffer_length = (synth->filter_bufinfo.len + SYNTHIO_MAX_DUR) * channel_count * sizeof(int32_t);
440+
synth->filter_buffer_length = (synth->filter_bufinfo.len / 2 + SYNTHIO_MAX_DUR) * channel_count * sizeof(int32_t);
391441
synth->filter_buffer = m_malloc(synth->filter_buffer_length, false);
392442
}
393443
synth->channel_count = channel_count;
@@ -419,7 +469,7 @@ STATIC void parse_common(mp_buffer_info_t *bufinfo, mp_obj_t o, int16_t what, mp
419469
if (bufinfo->typecode != 'h') {
420470
mp_raise_ValueError_varg(translate("%q must be array of type 'h'"), what);
421471
}
422-
mp_arg_validate_length_range(bufinfo->len / 2, 2, max_len, what);
472+
mp_arg_validate_length_range(bufinfo->len / sizeof(int16_t), 2, max_len, what);
423473
}
424474
}
425475

0 commit comments

Comments
 (0)