synthio: add stereo & Note.panning

A note can be placed in the center (panning=0) or moved to just the left
(panning=1) or right (panning=-1) channels. Fractional panning values
place it partially in both channels.
This commit is contained in:
Jeff Epler 2023-05-10 11:16:22 -05:00
parent 2062b2bfb7
commit e87e7ee54f
No known key found for this signature in database
GPG Key ID: D5BF15AB975AB4DE
10 changed files with 73 additions and 43 deletions

View File

@ -36,7 +36,7 @@
static const mp_arg_t note_properties[] = {
{ MP_QSTR_frequency, MP_ARG_OBJ | MP_ARG_REQUIRED, {.u_obj = NULL } },
{ MP_QSTR_amplitude, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_ROM_INT(1) } },
{ MP_QSTR_panning, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_ROM_INT(0) } },
{ MP_QSTR_tremolo_rate, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = NULL } },
{ MP_QSTR_tremolo_depth, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = NULL } },
{ MP_QSTR_bend_rate, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = NULL } },
@ -50,15 +50,16 @@ static const mp_arg_t note_properties[] = {
//| self,
//| *,
//| frequency: float,
//| amplitude: float = 1.0,
//| panning: float = 0.0,
//| waveform: Optional[ReadableBuffer] = None,
//| envelope: Optional[Envelope] = None,
//| tremolo_depth: float = 0.0,
//| tremolo_rate: float = 0.0,
//| bend_depth: float = 0.0,
//| bend_rate: float = 0.0,
//| bend_mode: BendMode = BendMode.VIBRATO,
//| ) -> None:
//| """Construct a Note object, with a frequency in Hz, and optional amplitude (volume), waveform, envelope, tremolo (volume change) and bend (frequency change).
//| """Construct a Note object, with a frequency in Hz, and optional panning, waveform, envelope, tremolo (volume change) and bend (frequency change).
//|
//| If waveform or envelope are `None` the synthesizer object's default waveform or envelope are used.
//|
@ -99,23 +100,28 @@ MP_PROPERTY_GETSET(synthio_note_frequency_obj,
(mp_obj_t)&synthio_note_get_frequency_obj,
(mp_obj_t)&synthio_note_set_frequency_obj);
//| amplitude: float
//| """The base amplitude of the note, from 0 to 1"""
STATIC mp_obj_t synthio_note_get_amplitude(mp_obj_t self_in) {
//| panning: float
//| """Defines the channel(s) in which the note appears.
//|
//| -1 is left channel only, 0 is both channels, and 1 is right channel.
//| For fractional values, the note plays at full amplitude in one channel
//| and partial amplitude in the other channel. For instance -.5 plays at full
//| amplitude in the left channel and 1/2 amplitude in the right channel."""
STATIC mp_obj_t synthio_note_get_panning(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_amplitude(self));
return mp_obj_new_float(common_hal_synthio_note_get_panning(self));
}
MP_DEFINE_CONST_FUN_OBJ_1(synthio_note_get_amplitude_obj, synthio_note_get_amplitude);
MP_DEFINE_CONST_FUN_OBJ_1(synthio_note_get_panning_obj, synthio_note_get_panning);
STATIC mp_obj_t synthio_note_set_amplitude(mp_obj_t self_in, mp_obj_t arg) {
STATIC mp_obj_t synthio_note_set_panning(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_amplitude(self, mp_obj_get_float(arg));
common_hal_synthio_note_set_panning(self, mp_obj_get_float(arg));
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_2(synthio_note_set_amplitude_obj, synthio_note_set_amplitude);
MP_PROPERTY_GETSET(synthio_note_amplitude_obj,
(mp_obj_t)&synthio_note_get_amplitude_obj,
(mp_obj_t)&synthio_note_set_amplitude_obj);
MP_DEFINE_CONST_FUN_OBJ_2(synthio_note_set_panning_obj, synthio_note_set_panning);
MP_PROPERTY_GETSET(synthio_note_panning_obj,
(mp_obj_t)&synthio_note_get_panning_obj,
(mp_obj_t)&synthio_note_set_panning_obj);
//| tremolo_depth: float
@ -266,7 +272,7 @@ static void note_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_
STATIC const mp_rom_map_elem_t synthio_note_locals_dict_table[] = {
{ MP_ROM_QSTR(MP_QSTR_frequency), MP_ROM_PTR(&synthio_note_frequency_obj) },
{ MP_ROM_QSTR(MP_QSTR_amplitude), MP_ROM_PTR(&synthio_note_amplitude_obj) },
{ MP_ROM_QSTR(MP_QSTR_panning), MP_ROM_PTR(&synthio_note_panning_obj) },
{ MP_ROM_QSTR(MP_QSTR_waveform), MP_ROM_PTR(&synthio_note_waveform_obj) },
{ MP_ROM_QSTR(MP_QSTR_envelope), MP_ROM_PTR(&synthio_note_envelope_obj) },
{ MP_ROM_QSTR(MP_QSTR_tremolo_depth), MP_ROM_PTR(&synthio_note_tremolo_depth_obj) },

View File

@ -9,8 +9,8 @@ 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_amplitude(synthio_note_obj_t *self);
void common_hal_synthio_note_set_amplitude(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);
mp_float_t common_hal_synthio_note_get_tremolo_rate(synthio_note_obj_t *self);
void common_hal_synthio_note_set_tremolo_rate(synthio_note_obj_t *self, mp_float_t value);

View File

@ -44,6 +44,7 @@
//| self,
//| *,
//| sample_rate: int = 11025,
//| channel_count: int = 1,
//| waveform: Optional[ReadableBuffer] = None,
//| envelope: Optional[Envelope] = None,
//| ) -> None:
@ -56,13 +57,15 @@
//| and do not support advanced features like tremolo or vibrato.
//|
//| :param int sample_rate: The desired playback sample rate; higher sample rate requires more memory
//| :param int channel_count: The number of output channels (1=mono, 2=stereo)
//| :param ReadableBuffer waveform: A single-cycle waveform. Default is a 50% duty cycle square wave. If specified, must be a ReadableBuffer of type 'h' (signed 16 bit)
//| :param Optional[Envelope] envelope: An object that defines the loudness of a note over time. The default envelope, `None` provides no ramping, voices turn instantly on and off.
//| """
STATIC mp_obj_t synthio_synthesizer_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) {
enum { ARG_sample_rate, ARG_waveform, ARG_envelope };
enum { ARG_sample_rate, ARG_channel_count, ARG_waveform, ARG_envelope };
static const mp_arg_t allowed_args[] = {
{ MP_QSTR_sample_rate, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 11025} },
{ MP_QSTR_channel_count, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 1} },
{ MP_QSTR_waveform, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = mp_const_none } },
{ MP_QSTR_envelope, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = mp_const_none } },
};
@ -77,6 +80,7 @@ STATIC mp_obj_t synthio_synthesizer_make_new(const mp_obj_type_t *type, size_t n
common_hal_synthio_synthesizer_construct(self,
args[ARG_sample_rate].u_int,
args[ARG_channel_count].u_int,
bufinfo_waveform.buf,
bufinfo_waveform.len / 2,
args[ARG_envelope].u_obj);

View File

@ -32,7 +32,7 @@
extern const mp_obj_type_t synthio_synthesizer_type;
void common_hal_synthio_synthesizer_construct(synthio_synthesizer_obj_t *self,
uint32_t sample_rate, const int16_t *waveform, uint16_t waveform_length,
uint32_t sample_rate, int channel_count, const int16_t *waveform, uint16_t waveform_length,
mp_obj_t envelope);
void common_hal_synthio_synthesizer_deinit(synthio_synthesizer_obj_t *self);
bool common_hal_synthio_synthesizer_deinited(synthio_synthesizer_obj_t *self);

View File

@ -123,7 +123,7 @@ void common_hal_synthio_miditrack_construct(synthio_miditrack_obj_t *self,
self->track.buf = (void *)buffer;
self->track.len = len;
synthio_synth_init(&self->synth, sample_rate, waveform, waveform_length, envelope);
synthio_synth_init(&self->synth, sample_rate, 1, waveform, waveform_length, envelope);
start_parse(self);
}

View File

@ -44,14 +44,20 @@ 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_amplitude(synthio_note_obj_t *self) {
return self->amplitude;
mp_float_t common_hal_synthio_note_get_panning(synthio_note_obj_t *self) {
return self->panning;
}
void common_hal_synthio_note_set_amplitude(synthio_note_obj_t *self, mp_float_t value_in) {
mp_float_t val = mp_arg_validate_float_range(value_in, 0, 1, MP_QSTR_amplitude);
self->amplitude = val;
self->amplitude_scaled = round_float_to_int(val * 32767);
void common_hal_synthio_note_set_panning(synthio_note_obj_t *self, mp_float_t value_in) {
mp_float_t val = mp_arg_validate_float_range(value_in, -1, 1, MP_QSTR_panning);
self->panning = val;
if (val >= 0) {
self->left_panning_scaled = 32768;
self->right_panning_scaled = 32768 - round_float_to_int(val * 32768);
} else {
self->right_panning_scaled = 32768;
self->left_panning_scaled = 32768 + round_float_to_int(val * 32768);
}
}
mp_float_t common_hal_synthio_note_get_tremolo_depth(synthio_note_obj_t *self) {
@ -200,7 +206,8 @@ STATIC int synthio_bend_value(synthio_note_obj_t *self, int16_t dur) {
uint32_t synthio_note_step(synthio_note_obj_t *self, int32_t sample_rate, int16_t dur, uint16_t *loudness) {
int tremolo_value = synthio_lfo_step(&self->tremolo_state, dur);
*loudness = (*loudness * tremolo_value) >> 15;
loudness[0] = (((loudness[0] * tremolo_value) >> 15) * self->left_panning_scaled) >> 15;
loudness[1] = (((loudness[1] * tremolo_value) >> 15) * self->right_panning_scaled) >> 15;
int bend_value = synthio_bend_value(self, dur);
uint32_t frequency_scaled = pitch_bend(self->frequency_scaled, bend_value);
return frequency_scaled;

View File

@ -33,13 +33,14 @@ typedef struct synthio_note_obj {
mp_obj_base_t base;
mp_float_t frequency;
mp_float_t amplitude;
mp_float_t panning;
mp_obj_t waveform_obj, envelope_obj;
int32_t sample_rate;
int32_t frequency_scaled;
int32_t amplitude_scaled;
int32_t left_panning_scaled, right_panning_scaled;
synthio_bend_mode_t bend_mode;
synthio_lfo_descr_t tremolo_descr, bend_descr;
synthio_lfo_state_t tremolo_state, bend_state;
@ -49,7 +50,7 @@ typedef struct synthio_note_obj {
} synthio_note_obj_t;
void synthio_note_recalculate(synthio_note_obj_t *self, int32_t sample_rate);
uint32_t synthio_note_step(synthio_note_obj_t *self, int32_t sample_rate, int16_t dur, uint16_t *loudness);
uint32_t synthio_note_step(synthio_note_obj_t *self, int32_t sample_rate, int16_t dur, uint16_t loudness[2]);
void synthio_note_start(synthio_note_obj_t *self, int32_t sample_rate);
bool synthio_note_playing(synthio_note_obj_t *self);
uint32_t synthio_note_envelope(synthio_note_obj_t *self);

View File

@ -32,10 +32,10 @@
void common_hal_synthio_synthesizer_construct(synthio_synthesizer_obj_t *self,
uint32_t sample_rate, const int16_t *waveform, uint16_t waveform_length,
uint32_t sample_rate, int channel_count, const int16_t *waveform, uint16_t waveform_length,
mp_obj_t envelope) {
synthio_synth_init(&self->synth, sample_rate, waveform, waveform_length, envelope);
synthio_synth_init(&self->synth, sample_rate, channel_count, waveform, waveform_length, envelope);
}
void common_hal_synthio_synthesizer_deinit(synthio_synthesizer_obj_t *self) {
@ -52,7 +52,7 @@ uint8_t common_hal_synthio_synthesizer_get_bits_per_sample(synthio_synthesizer_o
return SYNTHIO_BITS_PER_SAMPLE;
}
uint8_t common_hal_synthio_synthesizer_get_channel_count(synthio_synthesizer_obj_t *self) {
return 1;
return self->synth.channel_count;
}
void synthio_synthesizer_reset_buffer(synthio_synthesizer_obj_t *self,

View File

@ -187,7 +187,7 @@ void synthio_synth_synthesize(synthio_synth_t *synth, uint8_t **bufptr, uint32_t
synth->span.dur -= dur;
int32_t sample_rate = synth->sample_rate;
int32_t out_buffer32[dur];
int32_t out_buffer32[dur * synth->channel_count];
memset(out_buffer32, 0, sizeof(out_buffer32));
for (int chan = 0; chan < CIRCUITPY_SYNTHIO_MAX_CHANNELS; chan++) {
@ -204,7 +204,7 @@ void synthio_synth_synthesize(synthio_synth_t *synth, uint8_t **bufptr, uint32_t
}
// adjust loudness by envelope
uint16_t loudness = synth->envelope_state[chan].level;
uint16_t loudness[2] = {synth->envelope_state[chan].level,synth->envelope_state[chan].level};
uint32_t dds_rate;
const int16_t *waveform = synth->waveform;
@ -221,7 +221,7 @@ void synthio_synth_synthesize(synthio_synth_t *synth, uint8_t **bufptr, uint32_t
dds_rate = (sample_rate / 2 + ((uint64_t)(base_freq * waveform_length) << (SYNTHIO_FREQUENCY_SHIFT - 10 + octave))) / sample_rate;
} else {
synthio_note_obj_t *note = MP_OBJ_TO_PTR(note_obj);
int32_t frequency_scaled = synthio_note_step(note, sample_rate, dur, &loudness);
int32_t frequency_scaled = synthio_note_step(note, sample_rate, dur, loudness);
if (note->waveform_buf.buf) {
waveform = note->waveform_buf.buf;
waveform_length = note->waveform_buf.len / 2;
@ -241,14 +241,19 @@ void synthio_synth_synthesize(synthio_synth_t *synth, uint8_t **bufptr, uint32_t
accum %= lim;
}
for (uint16_t i = 0; i < dur; i++) {
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 (accum > lim) {
accum -= lim;
}
int16_t idx = accum >> SYNTHIO_FREQUENCY_SHIFT;
out_buffer32[i] += (waveform[idx] * loudness) / 65536;
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;
}
@ -256,7 +261,7 @@ void synthio_synth_synthesize(synthio_synth_t *synth, uint8_t **bufptr, uint32_t
int16_t *out_buffer16 = (int16_t *)(void *)synth->buffers[synth->buffer_index];
// mix down audio
for (size_t i = 0; i < dur; i++) {
for (size_t i = 0; i < MP_ARRAY_SIZE(out_buffer32); i++) {
int32_t sample = out_buffer32[i];
out_buffer16[i] = mix_down_sample(sample);
}
@ -270,7 +275,7 @@ void synthio_synth_synthesize(synthio_synth_t *synth, uint8_t **bufptr, uint32_t
synthio_envelope_state_step(&synth->envelope_state[chan], synthio_synth_get_note_envelope(synth, note_obj), dur);
}
*buffer_length = synth->last_buffer_length = dur * SYNTHIO_BYTES_PER_SAMPLE;
*buffer_length = synth->last_buffer_length = dur * SYNTHIO_BYTES_PER_SAMPLE * synth->channel_count;
*bufptr = (uint8_t *)out_buffer16;
}
@ -301,10 +306,12 @@ mp_obj_t synthio_synth_envelope_get(synthio_synth_t *synth) {
return synth->envelope_obj;
}
void synthio_synth_init(synthio_synth_t *synth, uint32_t sample_rate, const int16_t *waveform, uint16_t waveform_length, mp_obj_t envelope_obj) {
synth->buffer_length = SYNTHIO_MAX_DUR * SYNTHIO_BYTES_PER_SAMPLE;
void synthio_synth_init(synthio_synth_t *synth, uint32_t sample_rate, int channel_count, const int16_t *waveform, uint16_t waveform_length, mp_obj_t envelope_obj) {
mp_arg_validate_int_range(channel_count, 1, 2, MP_QSTR_channel_count);
synth->buffer_length = SYNTHIO_MAX_DUR * SYNTHIO_BYTES_PER_SAMPLE * channel_count;
synth->buffers[0] = m_malloc(synth->buffer_length, false);
synth->buffers[1] = m_malloc(synth->buffer_length, false);
synth->channel_count = channel_count;
synth->other_channel = -1;
synth->waveform = waveform;
synth->waveform_length = waveform_length;
@ -321,7 +328,11 @@ void synthio_synth_get_buffer_structure(synthio_synth_t *synth, bool single_chan
*single_buffer = false;
*samples_signed = true;
*max_buffer_length = synth->buffer_length;
*spacing = 1;
if (single_channel_output) {
*spacing = synth->channel_count;
} else {
*spacing = 1;
}
}
STATIC bool parse_common(mp_buffer_info_t *bufinfo, mp_obj_t o, int16_t what) {

View File

@ -65,6 +65,7 @@ typedef struct synthio_synth {
uint32_t total_envelope;
int16_t *buffers[2];
const int16_t *waveform;
uint8_t channel_count;
uint16_t buffer_length;
uint16_t last_buffer_length;
uint8_t other_channel, buffer_index, other_buffer_index;
@ -89,7 +90,7 @@ typedef struct {
void synthio_synth_synthesize(synthio_synth_t *synth, uint8_t **buffer, uint32_t *buffer_length, uint8_t channel);
void synthio_synth_deinit(synthio_synth_t *synth);
bool synthio_synth_deinited(synthio_synth_t *synth);
void synthio_synth_init(synthio_synth_t *synth, uint32_t sample_rate, const int16_t *waveform, uint16_t waveform_length,
void synthio_synth_init(synthio_synth_t *synth, uint32_t sample_rate, int channel_count, const int16_t *waveform, uint16_t waveform_length,
mp_obj_t envelope);
void synthio_synth_get_buffer_structure(synthio_synth_t *synth, bool single_channel_output,
bool *single_buffer, bool *samples_signed, uint32_t *max_buffer_length, uint8_t *spacing);