Skip to content

Commit a999e40

Browse files
committed
synthio: reorganize the order of operations
Apply envelope & panning after biquad filtering. This may fix the weird popping problem. It also reduces the number of operations that are done "in stereo", so it could help performance. It also fixes a previously unnoticed problem where a ring-modulated waveform had 2x the amplitude of an un-modulated waveform. The test differences look large but it's because some values got changed in the LSB after the mathematical divisions were moved around.
1 parent 30b69a8 commit a999e40

File tree

10 files changed

+5127
-5142
lines changed

10 files changed

+5127
-5142
lines changed

shared-module/synthio/Biquad.c

Lines changed: 20 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -111,38 +111,33 @@ void synthio_biquad_filter_assign(biquad_filter_state *st, mp_obj_t biquad_obj)
111111
}
112112

113113
void synthio_biquad_filter_reset(biquad_filter_state *st) {
114-
memset(&st->x, 0, 8 * sizeof(int16_t));
114+
memset(&st->x, 0, 4 * sizeof(int16_t));
115115
}
116116

117-
void synthio_biquad_filter_samples(biquad_filter_state *st, int32_t *out0, const int32_t *in0, size_t n0, size_t n_channels) {
117+
void synthio_biquad_filter_samples(biquad_filter_state *st, int32_t *buffer, size_t n_samples) {
118118
int32_t a1 = st->a1;
119119
int32_t a2 = st->a2;
120120
int32_t b0 = st->b0;
121121
int32_t b1 = st->b1;
122122
int32_t b2 = st->b2;
123123

124-
for (size_t i = 0; i < n_channels; i++) {
125-
const int32_t *in = in0 + i;
126-
int32_t *out = out0 + i;
127-
128-
int32_t x0 = st->x[i][0];
129-
int32_t x1 = st->x[i][1];
130-
int32_t y0 = st->y[i][0];
131-
int32_t y1 = st->y[i][1];
132-
133-
for (size_t n = n0; n; --n, in += n_channels, out += n_channels) {
134-
int32_t input = *in;
135-
int32_t output = (b0 * input + b1 * x0 + b2 * x1 - a1 * y0 - a2 * y1 + (1 << (BIQUAD_SHIFT - 1))) >> BIQUAD_SHIFT;
136-
137-
x1 = x0;
138-
x0 = input;
139-
y1 = y0;
140-
y0 = output;
141-
*out += output;
142-
}
143-
st->x[i][0] = x0;
144-
st->x[i][1] = x1;
145-
st->y[i][0] = y0;
146-
st->y[i][1] = y1;
124+
int32_t x0 = st->x[0];
125+
int32_t x1 = st->x[1];
126+
int32_t y0 = st->y[0];
127+
int32_t y1 = st->y[1];
128+
129+
for (size_t n = n_samples; n; --n, ++buffer) {
130+
int32_t input = *buffer;
131+
int32_t output = (b0 * input + b1 * x0 + b2 * x1 - a1 * y0 - a2 * y1 + (1 << (BIQUAD_SHIFT - 1))) >> BIQUAD_SHIFT;
132+
133+
x1 = x0;
134+
x0 = input;
135+
y1 = y0;
136+
y0 = output;
137+
*buffer = output;
147138
}
139+
st->x[0] = x0;
140+
st->x[1] = x1;
141+
st->y[0] = y0;
142+
st->y[1] = y1;
148143
}

shared-module/synthio/Biquad.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@
3030

3131
typedef struct {
3232
int32_t a1, a2, b0, b1, b2;
33-
int32_t x[2][2], y[2][2];
33+
int32_t x[2], y[2];
3434
} biquad_filter_state;
3535

3636
void synthio_biquad_filter_assign(biquad_filter_state *st, mp_obj_t biquad_obj);
3737
void synthio_biquad_filter_reset(biquad_filter_state *st);
38-
void synthio_biquad_filter_samples(biquad_filter_state *st, int32_t *out, const int32_t *in, size_t n_samples, size_t n_channels);
38+
void synthio_biquad_filter_samples(biquad_filter_state *st, int32_t *buffer, size_t n_samples);

shared-module/synthio/__init__.c

Lines changed: 70 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -172,24 +172,11 @@ int16_t mix_down_sample(int32_t sample) {
172172
return sample;
173173
}
174174

175-
static void synth_note_into_buffer(synthio_synth_t *synth, int chan, int32_t *out_buffer32, int16_t dur) {
175+
static bool synth_note_into_buffer(synthio_synth_t *synth, int chan, int32_t *out_buffer32, int16_t dur, uint16_t loudness[2]) {
176176
mp_obj_t note_obj = synth->span.note_obj[chan];
177177

178-
if (note_obj == SYNTHIO_SILENCE) {
179-
synth->accum[chan] = 0;
180-
return;
181-
}
182-
183-
if (synth->envelope_state[chan].level == 0) {
184-
// note is truly finished, but we only just noticed
185-
synth->span.note_obj[chan] = SYNTHIO_SILENCE;
186-
return;
187-
}
188-
189178
int32_t sample_rate = synth->sample_rate;
190179

191-
// adjust loudness by envelope
192-
uint16_t loudness[2] = {synth->envelope_state[chan].level,synth->envelope_state[chan].level};
193180

194181
uint32_t dds_rate;
195182
const int16_t *waveform = synth->waveform_bufinfo.buf;
@@ -228,33 +215,37 @@ static void synth_note_into_buffer(synthio_synth_t *synth, int chan, int32_t *ou
228215
}
229216
}
230217

231-
int synth_chan = synth->channel_count;
232-
if (ring_dds_rate) {
233-
uint32_t lim = waveform_length << SYNTHIO_FREQUENCY_SHIFT;
234-
uint32_t accum = synth->accum[chan];
218+
uint32_t lim = waveform_length << SYNTHIO_FREQUENCY_SHIFT;
219+
uint32_t accum = synth->accum[chan];
235220

236-
if (dds_rate > lim / 2) {
237-
// beyond nyquist, can't play note
238-
return;
239-
}
221+
if (dds_rate > lim / 2) {
222+
// beyond nyquist, can't play note
223+
return false;
224+
}
240225

241-
// can happen if note waveform gets set mid-note, but the expensive modulo is usually avoided
226+
// can happen if note waveform gets set mid-note, but the expensive modulo is usually avoided
227+
if (accum > lim) {
228+
accum %= lim;
229+
}
230+
231+
// first, fill with waveform
232+
for (uint16_t i = 0; i < dur; i++) {
233+
accum += dds_rate;
234+
// because dds_rate is low enough, the subtraction is guaranteed to go back into range, no expensive modulo needed
242235
if (accum > lim) {
243-
accum %= lim;
236+
accum -= lim;
244237
}
238+
int16_t idx = accum >> SYNTHIO_FREQUENCY_SHIFT;
239+
out_buffer32[i] = waveform[idx];
240+
}
241+
synth->accum[chan] = accum;
245242

246-
int32_t ring_buffer[dur];
247-
// first, fill with waveform
248-
for (uint16_t i = 0; i < dur; i++) {
249-
accum += dds_rate;
250-
// because dds_rate is low enough, the subtraction is guaranteed to go back into range, no expensive modulo needed
251-
if (accum > lim) {
252-
accum -= lim;
253-
}
254-
int16_t idx = accum >> SYNTHIO_FREQUENCY_SHIFT;
255-
ring_buffer[i] = waveform[idx];
243+
if (ring_dds_rate) {
244+
if (ring_dds_rate > lim / 2) {
245+
// beyond nyquist, can't play ring (but did synth main sound so
246+
// return true)
247+
return true;
256248
}
257-
synth->accum[chan] = accum;
258249

259250
// now modulate by ring and accumulate
260251
accum = synth->ring_accum[chan];
@@ -265,49 +256,19 @@ static void synth_note_into_buffer(synthio_synth_t *synth, int chan, int32_t *ou
265256
accum %= lim;
266257
}
267258

268-
for (uint16_t i = 0, j = 0; i < dur; i++) {
259+
for (uint16_t i = 0; i < dur; i++) {
269260
accum += ring_dds_rate;
270261
// because dds_rate is low enough, the subtraction is guaranteed to go back into range, no expensive modulo needed
271262
if (accum > lim) {
272263
accum -= lim;
273264
}
274265
int16_t idx = accum >> SYNTHIO_FREQUENCY_SHIFT;
275-
int16_t wi = (ring_waveform[idx] * ring_buffer[i]) / 32768;
276-
for (int c = 0; c < synth_chan; c++) {
277-
out_buffer32[j] += (wi * loudness[c]) / 32768;
278-
j++;
279-
}
266+
int16_t wi = (ring_waveform[idx] * out_buffer32[i]) / 32768;
267+
out_buffer32[i] = wi;
280268
}
281269
synth->ring_accum[chan] = accum;
282-
} else {
283-
uint32_t lim = waveform_length << SYNTHIO_FREQUENCY_SHIFT;
284-
uint32_t accum = synth->accum[chan];
285-
286-
if (dds_rate > lim / 2) {
287-
// beyond nyquist, can't play note
288-
return;
289-
}
290-
291-
// can happen if note waveform gets set mid-note, but the expensive modulo is usually avoided
292-
if (accum > lim) {
293-
accum %= lim;
294-
}
295-
296-
for (uint16_t i = 0, j = 0; i < dur; i++) {
297-
accum += dds_rate;
298-
// because dds_rate is low enough, the subtraction is guaranteed to go back into range, no expensive modulo needed
299-
if (accum > lim) {
300-
accum -= lim;
301-
}
302-
int16_t idx = accum >> SYNTHIO_FREQUENCY_SHIFT;
303-
int16_t wi = waveform[idx];
304-
for (int c = 0; c < synth_chan; c++) {
305-
out_buffer32[j] += (wi * loudness[c]) / 65536;
306-
j++;
307-
}
308-
}
309-
synth->accum[chan] = accum;
310270
}
271+
return true;
311272
}
312273

313274
STATIC mp_obj_t synthio_synth_get_note_filter(mp_obj_t note_obj) {
@@ -321,6 +282,19 @@ STATIC mp_obj_t synthio_synth_get_note_filter(mp_obj_t note_obj) {
321282
return mp_const_none;
322283
}
323284

285+
STATIC void sum_with_loudness(int32_t *out_buffer32, int32_t *tmp_buffer32, uint16_t loudness[2], size_t dur, int synth_chan) {
286+
if (synth_chan == 1) {
287+
for (size_t i = 0; i < dur; i++) {
288+
*out_buffer32++ += (*tmp_buffer32++ *loudness[0]) >> 16;
289+
}
290+
} else {
291+
for (size_t i = 0; i < dur; i++) {
292+
*out_buffer32++ += (*tmp_buffer32 * loudness[0]) >> 16;
293+
*out_buffer32++ += (*tmp_buffer32++ *loudness[1]) >> 16;
294+
}
295+
}
296+
}
297+
324298
void synthio_synth_synthesize(synthio_synth_t *synth, uint8_t **bufptr, uint32_t *buffer_length, uint8_t channel) {
325299

326300
if (channel == synth->other_channel) {
@@ -338,28 +312,44 @@ void synthio_synth_synthesize(synthio_synth_t *synth, uint8_t **bufptr, uint32_t
338312
uint16_t dur = MIN(SYNTHIO_MAX_DUR, synth->span.dur);
339313
synth->span.dur -= dur;
340314

341-
int32_t out_buffer32[dur * synth->channel_count];
342-
memset(out_buffer32, 0, sizeof(out_buffer32));
315+
int32_t out_buffer32[SYNTHIO_MAX_DUR * synth->channel_count];
316+
int32_t tmp_buffer32[SYNTHIO_MAX_DUR];
317+
memset(out_buffer32, 0, synth->channel_count * dur * sizeof(int32_t));
343318

344319
for (int chan = 0; chan < CIRCUITPY_SYNTHIO_MAX_CHANNELS; chan++) {
345320
mp_obj_t note_obj = synth->span.note_obj[chan];
321+
if (note_obj == SYNTHIO_SILENCE) {
322+
continue;
323+
}
324+
325+
if (synth->envelope_state[chan].level == 0) {
326+
// note is truly finished, but we only just noticed
327+
synth->span.note_obj[chan] = SYNTHIO_SILENCE;
328+
continue;
329+
}
330+
331+
uint16_t loudness[2] = {synth->envelope_state[chan].level,synth->envelope_state[chan].level};
332+
333+
if (!synth_note_into_buffer(synth, chan, tmp_buffer32, dur, loudness)) {
334+
// for some other reason, such as being above nyquist, note
335+
// couldn't be synthed, so don't filter or sum it in
336+
continue;
337+
}
338+
346339
mp_obj_t filter_obj = synthio_synth_get_note_filter(note_obj);
347-
if (filter_obj == mp_const_none) {
348-
synth_note_into_buffer(synth, chan, out_buffer32, dur);
349-
} else {
340+
if (filter_obj != mp_const_none) {
350341
synthio_note_obj_t *note = MP_OBJ_TO_PTR(note_obj);
351-
int32_t filter_buffer32[dur * synth->channel_count];
352-
memset(filter_buffer32, 0, sizeof(filter_buffer32));
353-
354-
synth_note_into_buffer(synth, chan, filter_buffer32, dur);
355-
synthio_biquad_filter_samples(&note->filter_state, out_buffer32, filter_buffer32, dur, synth->channel_count);
342+
synthio_biquad_filter_samples(&note->filter_state, tmp_buffer32, dur);
356343
}
344+
345+
// adjust loudness by envelope
346+
sum_with_loudness(out_buffer32, tmp_buffer32, loudness, dur, synth->channel_count);
357347
}
358348

359349
int16_t *out_buffer16 = (int16_t *)(void *)synth->buffers[synth->buffer_index];
360350

361351
// mix down audio
362-
for (size_t i = 0; i < MP_ARRAY_SIZE(out_buffer32); i++) {
352+
for (size_t i = 0; i < dur * synth->channel_count; i++) {
363353
int32_t sample = out_buffer32[i];
364354
out_buffer16[i] = mix_down_sample(sample);
365355
}

tests/circuitpython/miditrack.py.exp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
(0, 1, 512, 1)
2-
1 [-16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, -16383, 16383, 16383]
2+
1 [-16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 16383, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, -16384, 16383, 16383]
33
(0, 1, 512, 1)
4-
1 [0, 0, 0, 0, 0, 0, 16383, 16383, 16383, 16383, 16383, 16383, 0, 0, 0, 0, 0, 0, -16383, -16383, -16383, -16383, -16383, -16383, 0, 0, 0, 0, 0, 0, 16383, 16383, 16383, 16383, 16383, 16383, 0, 0, 0, 0, 0, 0, -16383, -16383, -16383, -16383, -16383, -16383, 0, 0, 0, 0, 0, 0, 16383, 16383, 16383, 16383, 16383, 16383, 0, 0, 0, 0, 0, 0, -16383, -16383, -16383, -16383, -16383, -16383, 0, 0, 0, 0, 0, 0, 16383, 16383, 16383, 16383, 16383, 16383, 0, 0, 0, 0, 0, 0, 0, -16383, -16383, -16383, -16383, -16383, -16383, 0, 0, 0, 0, 0, 0, 16383, 16383, 16383, 16383, 16383, 16383, 0, 0, 0, 0, 0, 0, -16383, -16383, -16383, -16383, -16383, -16383, 0, 0, 0, 0, 0, 0, 16383, 16383, 16383, 16383, 16383, 16383, 0, 0, 0, 0, 0, 0, -16383, -16383, -16383, -16383, -16383, -16383, 0, 0, 0, 0, 0, 0, 16383, 16383, 16383, 16383, 16383, 16383, 0, 0, 0, 0, 0, 0, -16383, -16383, -16383, -16383, -16383, -16383, 0, 0, 0, 0, 0, 0, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 0, 0, 0, 0, 0, 0, -16383, -16383, -16383, -16383, -16383, -16383, 0, 0, 0, 0, 0, 0, 16383, 16383, 16383, 16383, 16383, 16383, 0, 0, 0, 0, 0, 0, -16383, -16383, -16383, -16383, -16383, -16383, 0, 0, 0, 0, 0, 0, 16383, 16383, 16383, 16383, 16383, 16383, 0, 0, 0, 0, 0, 0, -16383, -16383, -16383, -16383, -16383, -16383, 0, 0, 0, 0, 0, 0, 16383, 16383, 16383, 16383, 16383, 16383, 0, 0]
4+
1 [0, 0, 0, 0, 0, 0, 16383, 16383, 16383, 16383, 16383, 16383, 0, 0, 0, 0, 0, 0, -16384, -16384, -16384, -16384, -16384, -16384, 0, 0, 0, 0, 0, 0, 16383, 16383, 16383, 16383, 16383, 16383, 0, 0, 0, 0, 0, 0, -16384, -16384, -16384, -16384, -16384, -16384, 0, 0, 0, 0, 0, 0, 16383, 16383, 16383, 16383, 16383, 16383, 0, 0, 0, 0, 0, 0, -16384, -16384, -16384, -16384, -16384, -16384, 0, 0, 0, 0, 0, 0, 16383, 16383, 16383, 16383, 16383, 16383, 0, 0, 0, 0, 0, 0, 0, -16384, -16384, -16384, -16384, -16384, -16384, 0, 0, 0, 0, 0, 0, 16383, 16383, 16383, 16383, 16383, 16383, 0, 0, 0, 0, 0, 0, -16384, -16384, -16384, -16384, -16384, -16384, 0, 0, 0, 0, 0, 0, 16383, 16383, 16383, 16383, 16383, 16383, 0, 0, 0, 0, 0, 0, -16384, -16384, -16384, -16384, -16384, -16384, 0, 0, 0, 0, 0, 0, 16383, 16383, 16383, 16383, 16383, 16383, 0, 0, 0, 0, 0, 0, -16384, -16384, -16384, -16384, -16384, -16384, 0, 0, 0, 0, 0, 0, 16383, 16383, 16383, 16383, 16383, 16383, 16383, 0, 0, 0, 0, 0, 0, -16384, -16384, -16384, -16384, -16384, -16384, 0, 0, 0, 0, 0, 0, 16383, 16383, 16383, 16383, 16383, 16383, 0, 0, 0, 0, 0, 0, -16384, -16384, -16384, -16384, -16384, -16384, 0, 0, 0, 0, 0, 0, 16383, 16383, 16383, 16383, 16383, 16383, 0, 0, 0, 0, 0, 0, -16384, -16384, -16384, -16384, -16384, -16384, 0, 0, 0, 0, 0, 0, 16383, 16383, 16383, 16383, 16383, 16383, 0, 0]

0 commit comments

Comments
 (0)