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:
parent
2062b2bfb7
commit
e87e7ee54f
|
@ -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) },
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue