synthio: Add ring modulation

This commit is contained in:
Jeff Epler 2023-05-10 12:07:01 -05:00
parent e87e7ee54f
commit 095e020809
No known key found for this signature in database
GPG Key ID: D5BF15AB975AB4DE
6 changed files with 172 additions and 24 deletions

View File

@ -44,6 +44,8 @@ static const mp_arg_t note_properties[] = {
{ MP_QSTR_bend_mode, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = (mp_obj_t)MP_ROM_PTR(&bend_mode_VIBRATO_obj) } },
{ MP_QSTR_waveform, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_ROM_NONE } },
{ MP_QSTR_envelope, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_ROM_NONE } },
{ MP_QSTR_ring_frequency, MP_ARG_OBJ, {.u_obj = NULL } },
{ MP_QSTR_ring_waveform, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_ROM_NONE } },
};
//| class Note:
//| def __init__(
@ -265,6 +267,49 @@ MP_PROPERTY_GETSET(synthio_note_envelope_obj,
(mp_obj_t)&synthio_note_get_envelope_obj,
(mp_obj_t)&synthio_note_set_envelope_obj);
//| ring_frequency: float
//| """The ring frequency of the note, in Hz. Zero disables.
//|
//| For ring to take effect, both ring_frequency and ring_wavefor must be set."""
STATIC mp_obj_t synthio_note_get_ring_frequency(mp_obj_t self_in) {
synthio_note_obj_t *self = MP_OBJ_TO_PTR(self_in);
return mp_obj_new_float(common_hal_synthio_note_get_ring_frequency(self));
}
MP_DEFINE_CONST_FUN_OBJ_1(synthio_note_get_ring_frequency_obj, synthio_note_get_ring_frequency);
STATIC mp_obj_t synthio_note_set_ring_frequency(mp_obj_t self_in, mp_obj_t arg) {
synthio_note_obj_t *self = MP_OBJ_TO_PTR(self_in);
common_hal_synthio_note_set_ring_frequency(self, mp_obj_get_float(arg));
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_2(synthio_note_set_ring_frequency_obj, synthio_note_set_ring_frequency);
MP_PROPERTY_GETSET(synthio_note_ring_frequency_obj,
(mp_obj_t)&synthio_note_get_ring_frequency_obj,
(mp_obj_t)&synthio_note_set_ring_frequency_obj);
//| ring_waveform: Optional[ReadableBuffer]
//| """The ring waveform of this note. Setting the ring_waveform to a buffer of a different size resets the note's phase.
//|
//| For ring to take effect, both ring_frequency and ring_wavefor must be set."""
//|
STATIC mp_obj_t synthio_note_get_ring_waveform(mp_obj_t self_in) {
synthio_note_obj_t *self = MP_OBJ_TO_PTR(self_in);
return common_hal_synthio_note_get_ring_waveform_obj(self);
}
MP_DEFINE_CONST_FUN_OBJ_1(synthio_note_get_ring_waveform_obj, synthio_note_get_ring_waveform);
STATIC mp_obj_t synthio_note_set_ring_waveform(mp_obj_t self_in, mp_obj_t arg) {
synthio_note_obj_t *self = MP_OBJ_TO_PTR(self_in);
common_hal_synthio_note_set_ring_waveform(self, arg);
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_2(synthio_note_set_ring_waveform_obj, synthio_note_set_ring_waveform);
MP_PROPERTY_GETSET(synthio_note_ring_waveform_obj,
(mp_obj_t)&synthio_note_get_ring_waveform_obj,
(mp_obj_t)&synthio_note_set_ring_waveform_obj);
static void note_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
(void)kind;
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[] = {
{ MP_ROM_QSTR(MP_QSTR_bend_depth), MP_ROM_PTR(&synthio_note_bend_depth_obj) },
{ MP_ROM_QSTR(MP_QSTR_bend_rate), MP_ROM_PTR(&synthio_note_bend_rate_obj) },
{ MP_ROM_QSTR(MP_QSTR_bend_mode), MP_ROM_PTR(&synthio_note_bend_mode_obj) },
{ MP_ROM_QSTR(MP_QSTR_ring_frequency), MP_ROM_PTR(&synthio_note_ring_frequency_obj) },
{ MP_ROM_QSTR(MP_QSTR_ring_waveform), MP_ROM_PTR(&synthio_note_ring_waveform_obj) },
};
STATIC MP_DEFINE_CONST_DICT(synthio_note_locals_dict, synthio_note_locals_dict_table);

View File

@ -9,6 +9,9 @@ typedef enum synthio_bend_mode_e synthio_bend_mode_t;
mp_float_t common_hal_synthio_note_get_frequency(synthio_note_obj_t *self);
void common_hal_synthio_note_set_frequency(synthio_note_obj_t *self, mp_float_t value);
mp_float_t common_hal_synthio_note_get_ring_frequency(synthio_note_obj_t *self);
void common_hal_synthio_note_set_ring_frequency(synthio_note_obj_t *self, mp_float_t value);
mp_float_t common_hal_synthio_note_get_panning(synthio_note_obj_t *self);
void common_hal_synthio_note_set_panning(synthio_note_obj_t *self, mp_float_t value);
@ -30,5 +33,8 @@ void common_hal_synthio_note_set_bend_depth(synthio_note_obj_t *self, mp_float_t
mp_obj_t common_hal_synthio_note_get_waveform_obj(synthio_note_obj_t *self);
void common_hal_synthio_note_set_waveform(synthio_note_obj_t *self, mp_obj_t value);
mp_obj_t common_hal_synthio_note_get_ring_waveform_obj(synthio_note_obj_t *self);
void common_hal_synthio_note_set_ring_waveform(synthio_note_obj_t *self, mp_obj_t value);
mp_obj_t common_hal_synthio_note_get_envelope_obj(synthio_note_obj_t *self);
void common_hal_synthio_note_set_envelope(synthio_note_obj_t *self, mp_obj_t value);

View File

@ -44,6 +44,16 @@ void common_hal_synthio_note_set_frequency(synthio_note_obj_t *self, mp_float_t
self->frequency_scaled = synthio_frequency_convert_float_to_scaled(val);
}
mp_float_t common_hal_synthio_note_get_ring_frequency(synthio_note_obj_t *self) {
return self->ring_frequency;
}
void common_hal_synthio_note_set_ring_frequency(synthio_note_obj_t *self, mp_float_t value_in) {
mp_float_t val = mp_arg_validate_float_range(value_in, 0, 32767, MP_QSTR_ring_frequency);
self->ring_frequency = val;
self->ring_frequency_scaled = synthio_frequency_convert_float_to_scaled(val);
}
mp_float_t common_hal_synthio_note_get_panning(synthio_note_obj_t *self) {
return self->panning;
}
@ -142,6 +152,21 @@ void common_hal_synthio_note_set_waveform(synthio_note_obj_t *self, mp_obj_t wav
self->waveform_obj = waveform_in;
}
mp_obj_t common_hal_synthio_note_get_ring_waveform_obj(synthio_note_obj_t *self) {
return self->ring_waveform_obj;
}
void common_hal_synthio_note_set_ring_waveform(synthio_note_obj_t *self, mp_obj_t ring_waveform_in) {
if (ring_waveform_in == mp_const_none) {
memset(&self->ring_waveform_buf, 0, sizeof(self->ring_waveform_buf));
} else {
mp_buffer_info_t bufinfo_ring_waveform;
synthio_synth_parse_waveform(&bufinfo_ring_waveform, ring_waveform_in);
self->ring_waveform_buf = bufinfo_ring_waveform;
}
self->ring_waveform_obj = ring_waveform_in;
}
void synthio_note_recalculate(synthio_note_obj_t *self, int32_t sample_rate) {
if (sample_rate == self->sample_rate) {
return;

View File

@ -32,13 +32,14 @@
typedef struct synthio_note_obj {
mp_obj_base_t base;
mp_float_t frequency;
mp_float_t frequency, ring_frequency;
mp_float_t panning;
mp_obj_t waveform_obj, envelope_obj;
mp_obj_t waveform_obj, envelope_obj, ring_waveform_obj;
int32_t sample_rate;
int32_t frequency_scaled;
int32_t ring_frequency_scaled;
int32_t amplitude_scaled;
int32_t left_panning_scaled, right_panning_scaled;
synthio_bend_mode_t bend_mode;
@ -46,6 +47,7 @@ typedef struct synthio_note_obj {
synthio_lfo_state_t tremolo_state, bend_state;
mp_buffer_info_t waveform_buf;
mp_buffer_info_t ring_waveform_buf;
synthio_envelope_definition_t envelope_def;
} synthio_note_obj_t;

View File

@ -209,6 +209,11 @@ void synthio_synth_synthesize(synthio_synth_t *synth, uint8_t **bufptr, uint32_t
uint32_t dds_rate;
const int16_t *waveform = synth->waveform;
uint32_t waveform_length = synth->waveform_length;
uint32_t ring_dds_rate = 0;
const int16_t *ring_waveform = NULL;
uint32_t ring_waveform_length = 0;
if (mp_obj_is_small_int(note_obj)) {
uint8_t note = mp_obj_get_int(note_obj);
uint8_t octave = note / 12;
@ -227,35 +232,97 @@ void synthio_synth_synthesize(synthio_synth_t *synth, uint8_t **bufptr, uint32_t
waveform_length = note->waveform_buf.len / 2;
}
dds_rate = synthio_frequency_convert_scaled_to_dds((uint64_t)frequency_scaled * waveform_length, sample_rate);
}
uint32_t accum = synth->accum[chan];
uint32_t lim = waveform_length << SYNTHIO_FREQUENCY_SHIFT;
if (dds_rate > lim / 2) {
// beyond nyquist, can't play note
continue;
}
// can happen if note waveform gets set mid-note, but the expensive modulo is usually avoided
if (accum > lim) {
accum %= lim;
if (note->ring_frequency_scaled != 0 && note->ring_waveform_buf.buf) {
ring_waveform = note->ring_waveform_buf.buf;
ring_waveform_length = note->ring_waveform_buf.len / 2;
ring_dds_rate = synthio_frequency_convert_scaled_to_dds((uint64_t)note->ring_frequency_scaled * ring_waveform_length, sample_rate);
uint32_t lim = ring_waveform_length << SYNTHIO_FREQUENCY_SHIFT;
if (ring_dds_rate > lim / 2) {
ring_dds_rate = 0; // can't ring at that frequency
}
}
}
int synth_chan = synth->channel_count;
for (uint16_t i = 0, j = 0; i < dur; i++) {
accum += dds_rate;
// because dds_rate is low enough, the subtraction is guaranteed to go back into range, no expensive modulo needed
if (ring_dds_rate) {
uint32_t lim = waveform_length << SYNTHIO_FREQUENCY_SHIFT;
uint32_t accum = synth->accum[chan];
if (dds_rate > lim / 2) {
// beyond nyquist, can't play note
continue;
}
// can happen if note waveform gets set mid-note, but the expensive modulo is usually avoided
if (accum > lim) {
accum -= lim;
accum %= lim;
}
int16_t idx = accum >> SYNTHIO_FREQUENCY_SHIFT;
int16_t wi = waveform[idx];
for (int c = 0; c < synth_chan; c++) {
out_buffer32[j] += (wi * loudness[c]) / 65536;
j++;
int32_t ring_buffer[dur];
// first, fill with waveform
for (uint16_t i = 0; i < dur; i++) {
accum += dds_rate;
// because dds_rate is low enough, the subtraction is guaranteed to go back into range, no expensive modulo needed
if (accum > lim) {
accum -= lim;
}
int16_t idx = accum >> SYNTHIO_FREQUENCY_SHIFT;
ring_buffer[i] = waveform[idx];
}
synth->accum[chan] = accum;
// now modulate by ring and accumulate
accum = synth->ring_accum[chan];
lim = ring_waveform_length << SYNTHIO_FREQUENCY_SHIFT;
// can happen if note waveform gets set mid-note, but the expensive modulo is usually avoided
if (accum > lim) {
accum %= lim;
}
for (uint16_t i = 0, j = 0; i < dur; i++) {
accum += ring_dds_rate;
// because dds_rate is low enough, the subtraction is guaranteed to go back into range, no expensive modulo needed
if (accum > lim) {
accum -= lim;
}
int16_t idx = accum >> SYNTHIO_FREQUENCY_SHIFT;
int16_t wi = (ring_waveform[idx] * ring_buffer[i]) / 32768;
for (int c = 0; c < synth_chan; c++) {
out_buffer32[j] += (wi * loudness[c]) / 32768;
j++;
}
}
synth->ring_accum[chan] = accum;
} else {
uint32_t lim = waveform_length << SYNTHIO_FREQUENCY_SHIFT;
uint32_t accum = synth->accum[chan];
if (dds_rate > lim / 2) {
// beyond nyquist, can't play note
continue;
}
// can happen if note waveform gets set mid-note, but the expensive modulo is usually avoided
if (accum > lim) {
accum %= lim;
}
for (uint16_t i = 0, j = 0; i < dur; i++) {
accum += dds_rate;
// because dds_rate is low enough, the subtraction is guaranteed to go back into range, no expensive modulo needed
if (accum > lim) {
accum -= lim;
}
int16_t idx = accum >> SYNTHIO_FREQUENCY_SHIFT;
int16_t wi = waveform[idx];
for (int c = 0; c < synth_chan; c++) {
out_buffer32[j] += (wi * loudness[c]) / 65536;
j++;
}
}
synth->accum[chan] = accum;
}
synth->accum[chan] = accum;
}
int16_t *out_buffer16 = (int16_t *)(void *)synth->buffers[synth->buffer_index];

View File

@ -74,6 +74,7 @@ typedef struct synthio_synth {
mp_obj_t envelope_obj;
synthio_midi_span_t span;
uint32_t accum[CIRCUITPY_SYNTHIO_MAX_CHANNELS];
uint32_t ring_accum[CIRCUITPY_SYNTHIO_MAX_CHANNELS];
synthio_envelope_state_t envelope_state[CIRCUITPY_SYNTHIO_MAX_CHANNELS];
} synthio_synth_t;