Skip to content

Commit 095e020

Browse files
committed
synthio: Add ring modulation
1 parent e87e7ee commit 095e020

File tree

6 files changed

+171
-23
lines changed

6 files changed

+171
-23
lines changed

shared-bindings/synthio/Note.c

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ static const mp_arg_t note_properties[] = {
4444
{ MP_QSTR_bend_mode, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = (mp_obj_t)MP_ROM_PTR(&bend_mode_VIBRATO_obj) } },
4545
{ MP_QSTR_waveform, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_ROM_NONE } },
4646
{ MP_QSTR_envelope, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_ROM_NONE } },
47+
{ MP_QSTR_ring_frequency, MP_ARG_OBJ, {.u_obj = NULL } },
48+
{ MP_QSTR_ring_waveform, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_ROM_NONE } },
4749
};
4850
//| class Note:
4951
//| def __init__(
@@ -265,6 +267,49 @@ MP_PROPERTY_GETSET(synthio_note_envelope_obj,
265267
(mp_obj_t)&synthio_note_get_envelope_obj,
266268
(mp_obj_t)&synthio_note_set_envelope_obj);
267269

270+
//| ring_frequency: float
271+
//| """The ring frequency of the note, in Hz. Zero disables.
272+
//|
273+
//| For ring to take effect, both ring_frequency and ring_wavefor must be set."""
274+
STATIC mp_obj_t synthio_note_get_ring_frequency(mp_obj_t self_in) {
275+
synthio_note_obj_t *self = MP_OBJ_TO_PTR(self_in);
276+
return mp_obj_new_float(common_hal_synthio_note_get_ring_frequency(self));
277+
}
278+
MP_DEFINE_CONST_FUN_OBJ_1(synthio_note_get_ring_frequency_obj, synthio_note_get_ring_frequency);
279+
280+
STATIC mp_obj_t synthio_note_set_ring_frequency(mp_obj_t self_in, mp_obj_t arg) {
281+
synthio_note_obj_t *self = MP_OBJ_TO_PTR(self_in);
282+
common_hal_synthio_note_set_ring_frequency(self, mp_obj_get_float(arg));
283+
return mp_const_none;
284+
}
285+
MP_DEFINE_CONST_FUN_OBJ_2(synthio_note_set_ring_frequency_obj, synthio_note_set_ring_frequency);
286+
MP_PROPERTY_GETSET(synthio_note_ring_frequency_obj,
287+
(mp_obj_t)&synthio_note_get_ring_frequency_obj,
288+
(mp_obj_t)&synthio_note_set_ring_frequency_obj);
289+
290+
//| ring_waveform: Optional[ReadableBuffer]
291+
//| """The ring waveform of this note. Setting the ring_waveform to a buffer of a different size resets the note's phase.
292+
//|
293+
//| For ring to take effect, both ring_frequency and ring_wavefor must be set."""
294+
//|
295+
STATIC mp_obj_t synthio_note_get_ring_waveform(mp_obj_t self_in) {
296+
synthio_note_obj_t *self = MP_OBJ_TO_PTR(self_in);
297+
return common_hal_synthio_note_get_ring_waveform_obj(self);
298+
}
299+
MP_DEFINE_CONST_FUN_OBJ_1(synthio_note_get_ring_waveform_obj, synthio_note_get_ring_waveform);
300+
301+
STATIC mp_obj_t synthio_note_set_ring_waveform(mp_obj_t self_in, mp_obj_t arg) {
302+
synthio_note_obj_t *self = MP_OBJ_TO_PTR(self_in);
303+
common_hal_synthio_note_set_ring_waveform(self, arg);
304+
return mp_const_none;
305+
}
306+
MP_DEFINE_CONST_FUN_OBJ_2(synthio_note_set_ring_waveform_obj, synthio_note_set_ring_waveform);
307+
MP_PROPERTY_GETSET(synthio_note_ring_waveform_obj,
308+
(mp_obj_t)&synthio_note_get_ring_waveform_obj,
309+
(mp_obj_t)&synthio_note_set_ring_waveform_obj);
310+
311+
312+
268313
static void note_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
269314
(void)kind;
270315
properties_print_helper(print, self_in, note_properties, MP_ARRAY_SIZE(note_properties));
@@ -280,6 +325,8 @@ STATIC const mp_rom_map_elem_t synthio_note_locals_dict_table[] = {
280325
{ MP_ROM_QSTR(MP_QSTR_bend_depth), MP_ROM_PTR(&synthio_note_bend_depth_obj) },
281326
{ MP_ROM_QSTR(MP_QSTR_bend_rate), MP_ROM_PTR(&synthio_note_bend_rate_obj) },
282327
{ MP_ROM_QSTR(MP_QSTR_bend_mode), MP_ROM_PTR(&synthio_note_bend_mode_obj) },
328+
{ MP_ROM_QSTR(MP_QSTR_ring_frequency), MP_ROM_PTR(&synthio_note_ring_frequency_obj) },
329+
{ MP_ROM_QSTR(MP_QSTR_ring_waveform), MP_ROM_PTR(&synthio_note_ring_waveform_obj) },
283330
};
284331
STATIC MP_DEFINE_CONST_DICT(synthio_note_locals_dict, synthio_note_locals_dict_table);
285332

shared-bindings/synthio/Note.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ typedef enum synthio_bend_mode_e synthio_bend_mode_t;
99
mp_float_t common_hal_synthio_note_get_frequency(synthio_note_obj_t *self);
1010
void common_hal_synthio_note_set_frequency(synthio_note_obj_t *self, mp_float_t value);
1111

12+
mp_float_t common_hal_synthio_note_get_ring_frequency(synthio_note_obj_t *self);
13+
void common_hal_synthio_note_set_ring_frequency(synthio_note_obj_t *self, mp_float_t value);
14+
1215
mp_float_t common_hal_synthio_note_get_panning(synthio_note_obj_t *self);
1316
void common_hal_synthio_note_set_panning(synthio_note_obj_t *self, mp_float_t value);
1417

@@ -30,5 +33,8 @@ void common_hal_synthio_note_set_bend_depth(synthio_note_obj_t *self, mp_float_t
3033
mp_obj_t common_hal_synthio_note_get_waveform_obj(synthio_note_obj_t *self);
3134
void common_hal_synthio_note_set_waveform(synthio_note_obj_t *self, mp_obj_t value);
3235

36+
mp_obj_t common_hal_synthio_note_get_ring_waveform_obj(synthio_note_obj_t *self);
37+
void common_hal_synthio_note_set_ring_waveform(synthio_note_obj_t *self, mp_obj_t value);
38+
3339
mp_obj_t common_hal_synthio_note_get_envelope_obj(synthio_note_obj_t *self);
3440
void common_hal_synthio_note_set_envelope(synthio_note_obj_t *self, mp_obj_t value);

shared-module/synthio/Note.c

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,16 @@ void common_hal_synthio_note_set_frequency(synthio_note_obj_t *self, mp_float_t
4444
self->frequency_scaled = synthio_frequency_convert_float_to_scaled(val);
4545
}
4646

47+
mp_float_t common_hal_synthio_note_get_ring_frequency(synthio_note_obj_t *self) {
48+
return self->ring_frequency;
49+
}
50+
51+
void common_hal_synthio_note_set_ring_frequency(synthio_note_obj_t *self, mp_float_t value_in) {
52+
mp_float_t val = mp_arg_validate_float_range(value_in, 0, 32767, MP_QSTR_ring_frequency);
53+
self->ring_frequency = val;
54+
self->ring_frequency_scaled = synthio_frequency_convert_float_to_scaled(val);
55+
}
56+
4757
mp_float_t common_hal_synthio_note_get_panning(synthio_note_obj_t *self) {
4858
return self->panning;
4959
}
@@ -142,6 +152,21 @@ void common_hal_synthio_note_set_waveform(synthio_note_obj_t *self, mp_obj_t wav
142152
self->waveform_obj = waveform_in;
143153
}
144154

155+
mp_obj_t common_hal_synthio_note_get_ring_waveform_obj(synthio_note_obj_t *self) {
156+
return self->ring_waveform_obj;
157+
}
158+
159+
void common_hal_synthio_note_set_ring_waveform(synthio_note_obj_t *self, mp_obj_t ring_waveform_in) {
160+
if (ring_waveform_in == mp_const_none) {
161+
memset(&self->ring_waveform_buf, 0, sizeof(self->ring_waveform_buf));
162+
} else {
163+
mp_buffer_info_t bufinfo_ring_waveform;
164+
synthio_synth_parse_waveform(&bufinfo_ring_waveform, ring_waveform_in);
165+
self->ring_waveform_buf = bufinfo_ring_waveform;
166+
}
167+
self->ring_waveform_obj = ring_waveform_in;
168+
}
169+
145170
void synthio_note_recalculate(synthio_note_obj_t *self, int32_t sample_rate) {
146171
if (sample_rate == self->sample_rate) {
147172
return;

shared-module/synthio/Note.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,20 +32,22 @@
3232
typedef struct synthio_note_obj {
3333
mp_obj_base_t base;
3434

35-
mp_float_t frequency;
35+
mp_float_t frequency, ring_frequency;
3636
mp_float_t panning;
37-
mp_obj_t waveform_obj, envelope_obj;
37+
mp_obj_t waveform_obj, envelope_obj, ring_waveform_obj;
3838

3939
int32_t sample_rate;
4040

4141
int32_t frequency_scaled;
42+
int32_t ring_frequency_scaled;
4243
int32_t amplitude_scaled;
4344
int32_t left_panning_scaled, right_panning_scaled;
4445
synthio_bend_mode_t bend_mode;
4546
synthio_lfo_descr_t tremolo_descr, bend_descr;
4647
synthio_lfo_state_t tremolo_state, bend_state;
4748

4849
mp_buffer_info_t waveform_buf;
50+
mp_buffer_info_t ring_waveform_buf;
4951
synthio_envelope_definition_t envelope_def;
5052
} synthio_note_obj_t;
5153

shared-module/synthio/__init__.c

Lines changed: 88 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,11 @@ void synthio_synth_synthesize(synthio_synth_t *synth, uint8_t **bufptr, uint32_t
209209
uint32_t dds_rate;
210210
const int16_t *waveform = synth->waveform;
211211
uint32_t waveform_length = synth->waveform_length;
212+
213+
uint32_t ring_dds_rate = 0;
214+
const int16_t *ring_waveform = NULL;
215+
uint32_t ring_waveform_length = 0;
216+
212217
if (mp_obj_is_small_int(note_obj)) {
213218
uint8_t note = mp_obj_get_int(note_obj);
214219
uint8_t octave = note / 12;
@@ -227,35 +232,97 @@ void synthio_synth_synthesize(synthio_synth_t *synth, uint8_t **bufptr, uint32_t
227232
waveform_length = note->waveform_buf.len / 2;
228233
}
229234
dds_rate = synthio_frequency_convert_scaled_to_dds((uint64_t)frequency_scaled * waveform_length, sample_rate);
235+
if (note->ring_frequency_scaled != 0 && note->ring_waveform_buf.buf) {
236+
ring_waveform = note->ring_waveform_buf.buf;
237+
ring_waveform_length = note->ring_waveform_buf.len / 2;
238+
ring_dds_rate = synthio_frequency_convert_scaled_to_dds((uint64_t)note->ring_frequency_scaled * ring_waveform_length, sample_rate);
239+
uint32_t lim = ring_waveform_length << SYNTHIO_FREQUENCY_SHIFT;
240+
if (ring_dds_rate > lim / 2) {
241+
ring_dds_rate = 0; // can't ring at that frequency
242+
}
243+
}
230244
}
231245

232-
uint32_t accum = synth->accum[chan];
233-
uint32_t lim = waveform_length << SYNTHIO_FREQUENCY_SHIFT;
234-
if (dds_rate > lim / 2) {
235-
// beyond nyquist, can't play note
236-
continue;
237-
}
246+
int synth_chan = synth->channel_count;
247+
if (ring_dds_rate) {
248+
uint32_t lim = waveform_length << SYNTHIO_FREQUENCY_SHIFT;
249+
uint32_t accum = synth->accum[chan];
238250

239-
// can happen if note waveform gets set mid-note, but the expensive modulo is usually avoided
240-
if (accum > lim) {
241-
accum %= lim;
242-
}
251+
if (dds_rate > lim / 2) {
252+
// beyond nyquist, can't play note
253+
continue;
254+
}
243255

244-
int synth_chan = synth->channel_count;
245-
for (uint16_t i = 0, j = 0; i < dur; i++) {
246-
accum += dds_rate;
247-
// because dds_rate is low enough, the subtraction is guaranteed to go back into range, no expensive modulo needed
256+
// can happen if note waveform gets set mid-note, but the expensive modulo is usually avoided
257+
if (accum > lim) {
258+
accum %= lim;
259+
}
260+
261+
int32_t ring_buffer[dur];
262+
// first, fill with waveform
263+
for (uint16_t i = 0; i < dur; i++) {
264+
accum += dds_rate;
265+
// because dds_rate is low enough, the subtraction is guaranteed to go back into range, no expensive modulo needed
266+
if (accum > lim) {
267+
accum -= lim;
268+
}
269+
int16_t idx = accum >> SYNTHIO_FREQUENCY_SHIFT;
270+
ring_buffer[i] = waveform[idx];
271+
}
272+
synth->accum[chan] = accum;
273+
274+
// now modulate by ring and accumulate
275+
accum = synth->ring_accum[chan];
276+
lim = ring_waveform_length << SYNTHIO_FREQUENCY_SHIFT;
277+
278+
// can happen if note waveform gets set mid-note, but the expensive modulo is usually avoided
248279
if (accum > lim) {
249-
accum -= lim;
280+
accum %= lim;
281+
}
282+
283+
for (uint16_t i = 0, j = 0; i < dur; i++) {
284+
accum += ring_dds_rate;
285+
// because dds_rate is low enough, the subtraction is guaranteed to go back into range, no expensive modulo needed
286+
if (accum > lim) {
287+
accum -= lim;
288+
}
289+
int16_t idx = accum >> SYNTHIO_FREQUENCY_SHIFT;
290+
int16_t wi = (ring_waveform[idx] * ring_buffer[i]) / 32768;
291+
for (int c = 0; c < synth_chan; c++) {
292+
out_buffer32[j] += (wi * loudness[c]) / 32768;
293+
j++;
294+
}
250295
}
251-
int16_t idx = accum >> SYNTHIO_FREQUENCY_SHIFT;
252-
int16_t wi = waveform[idx];
253-
for (int c = 0; c < synth_chan; c++) {
254-
out_buffer32[j] += (wi * loudness[c]) / 65536;
255-
j++;
296+
synth->ring_accum[chan] = accum;
297+
} else {
298+
uint32_t lim = waveform_length << SYNTHIO_FREQUENCY_SHIFT;
299+
uint32_t accum = synth->accum[chan];
300+
301+
if (dds_rate > lim / 2) {
302+
// beyond nyquist, can't play note
303+
continue;
304+
}
305+
306+
// can happen if note waveform gets set mid-note, but the expensive modulo is usually avoided
307+
if (accum > lim) {
308+
accum %= lim;
309+
}
310+
311+
for (uint16_t i = 0, j = 0; i < dur; i++) {
312+
accum += dds_rate;
313+
// because dds_rate is low enough, the subtraction is guaranteed to go back into range, no expensive modulo needed
314+
if (accum > lim) {
315+
accum -= lim;
316+
}
317+
int16_t idx = accum >> SYNTHIO_FREQUENCY_SHIFT;
318+
int16_t wi = waveform[idx];
319+
for (int c = 0; c < synth_chan; c++) {
320+
out_buffer32[j] += (wi * loudness[c]) / 65536;
321+
j++;
322+
}
256323
}
324+
synth->accum[chan] = accum;
257325
}
258-
synth->accum[chan] = accum;
259326
}
260327

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

shared-module/synthio/__init__.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ typedef struct synthio_synth {
7474
mp_obj_t envelope_obj;
7575
synthio_midi_span_t span;
7676
uint32_t accum[CIRCUITPY_SYNTHIO_MAX_CHANNELS];
77+
uint32_t ring_accum[CIRCUITPY_SYNTHIO_MAX_CHANNELS];
7778
synthio_envelope_state_t envelope_state[CIRCUITPY_SYNTHIO_MAX_CHANNELS];
7879
} synthio_synth_t;
7980

0 commit comments

Comments
 (0)