Skip to content

Commit e87e7ee

Browse files
committed
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.
1 parent 2062b2b commit e87e7ee

File tree

10 files changed

+73
-43
lines changed

10 files changed

+73
-43
lines changed

shared-bindings/synthio/Note.c

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636

3737
static const mp_arg_t note_properties[] = {
3838
{ MP_QSTR_frequency, MP_ARG_OBJ | MP_ARG_REQUIRED, {.u_obj = NULL } },
39-
{ MP_QSTR_amplitude, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_ROM_INT(1) } },
39+
{ MP_QSTR_panning, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = MP_ROM_INT(0) } },
4040
{ MP_QSTR_tremolo_rate, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = NULL } },
4141
{ MP_QSTR_tremolo_depth, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = NULL } },
4242
{ 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[] = {
5050
//| self,
5151
//| *,
5252
//| frequency: float,
53-
//| amplitude: float = 1.0,
53+
//| panning: float = 0.0,
5454
//| waveform: Optional[ReadableBuffer] = None,
5555
//| envelope: Optional[Envelope] = None,
5656
//| tremolo_depth: float = 0.0,
5757
//| tremolo_rate: float = 0.0,
5858
//| bend_depth: float = 0.0,
5959
//| bend_rate: float = 0.0,
60+
//| bend_mode: BendMode = BendMode.VIBRATO,
6061
//| ) -> None:
61-
//| """Construct a Note object, with a frequency in Hz, and optional amplitude (volume), waveform, envelope, tremolo (volume change) and bend (frequency change).
62+
//| """Construct a Note object, with a frequency in Hz, and optional panning, waveform, envelope, tremolo (volume change) and bend (frequency change).
6263
//|
6364
//| If waveform or envelope are `None` the synthesizer object's default waveform or envelope are used.
6465
//|
@@ -99,23 +100,28 @@ MP_PROPERTY_GETSET(synthio_note_frequency_obj,
99100
(mp_obj_t)&synthio_note_get_frequency_obj,
100101
(mp_obj_t)&synthio_note_set_frequency_obj);
101102

102-
//| amplitude: float
103-
//| """The base amplitude of the note, from 0 to 1"""
104-
STATIC mp_obj_t synthio_note_get_amplitude(mp_obj_t self_in) {
103+
//| panning: float
104+
//| """Defines the channel(s) in which the note appears.
105+
//|
106+
//| -1 is left channel only, 0 is both channels, and 1 is right channel.
107+
//| For fractional values, the note plays at full amplitude in one channel
108+
//| and partial amplitude in the other channel. For instance -.5 plays at full
109+
//| amplitude in the left channel and 1/2 amplitude in the right channel."""
110+
STATIC mp_obj_t synthio_note_get_panning(mp_obj_t self_in) {
105111
synthio_note_obj_t *self = MP_OBJ_TO_PTR(self_in);
106-
return mp_obj_new_float(common_hal_synthio_note_get_amplitude(self));
112+
return mp_obj_new_float(common_hal_synthio_note_get_panning(self));
107113
}
108-
MP_DEFINE_CONST_FUN_OBJ_1(synthio_note_get_amplitude_obj, synthio_note_get_amplitude);
114+
MP_DEFINE_CONST_FUN_OBJ_1(synthio_note_get_panning_obj, synthio_note_get_panning);
109115

110-
STATIC mp_obj_t synthio_note_set_amplitude(mp_obj_t self_in, mp_obj_t arg) {
116+
STATIC mp_obj_t synthio_note_set_panning(mp_obj_t self_in, mp_obj_t arg) {
111117
synthio_note_obj_t *self = MP_OBJ_TO_PTR(self_in);
112-
common_hal_synthio_note_set_amplitude(self, mp_obj_get_float(arg));
118+
common_hal_synthio_note_set_panning(self, mp_obj_get_float(arg));
113119
return mp_const_none;
114120
}
115-
MP_DEFINE_CONST_FUN_OBJ_2(synthio_note_set_amplitude_obj, synthio_note_set_amplitude);
116-
MP_PROPERTY_GETSET(synthio_note_amplitude_obj,
117-
(mp_obj_t)&synthio_note_get_amplitude_obj,
118-
(mp_obj_t)&synthio_note_set_amplitude_obj);
121+
MP_DEFINE_CONST_FUN_OBJ_2(synthio_note_set_panning_obj, synthio_note_set_panning);
122+
MP_PROPERTY_GETSET(synthio_note_panning_obj,
123+
(mp_obj_t)&synthio_note_get_panning_obj,
124+
(mp_obj_t)&synthio_note_set_panning_obj);
119125

120126

121127
//| tremolo_depth: float
@@ -266,7 +272,7 @@ static void note_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_
266272

267273
STATIC const mp_rom_map_elem_t synthio_note_locals_dict_table[] = {
268274
{ MP_ROM_QSTR(MP_QSTR_frequency), MP_ROM_PTR(&synthio_note_frequency_obj) },
269-
{ MP_ROM_QSTR(MP_QSTR_amplitude), MP_ROM_PTR(&synthio_note_amplitude_obj) },
275+
{ MP_ROM_QSTR(MP_QSTR_panning), MP_ROM_PTR(&synthio_note_panning_obj) },
270276
{ MP_ROM_QSTR(MP_QSTR_waveform), MP_ROM_PTR(&synthio_note_waveform_obj) },
271277
{ MP_ROM_QSTR(MP_QSTR_envelope), MP_ROM_PTR(&synthio_note_envelope_obj) },
272278
{ MP_ROM_QSTR(MP_QSTR_tremolo_depth), MP_ROM_PTR(&synthio_note_tremolo_depth_obj) },

shared-bindings/synthio/Note.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ 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_amplitude(synthio_note_obj_t *self);
13-
void common_hal_synthio_note_set_amplitude(synthio_note_obj_t *self, mp_float_t value);
12+
mp_float_t common_hal_synthio_note_get_panning(synthio_note_obj_t *self);
13+
void common_hal_synthio_note_set_panning(synthio_note_obj_t *self, mp_float_t value);
1414

1515
mp_float_t common_hal_synthio_note_get_tremolo_rate(synthio_note_obj_t *self);
1616
void common_hal_synthio_note_set_tremolo_rate(synthio_note_obj_t *self, mp_float_t value);

shared-bindings/synthio/Synthesizer.c

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
//| self,
4545
//| *,
4646
//| sample_rate: int = 11025,
47+
//| channel_count: int = 1,
4748
//| waveform: Optional[ReadableBuffer] = None,
4849
//| envelope: Optional[Envelope] = None,
4950
//| ) -> None:
@@ -56,13 +57,15 @@
5657
//| and do not support advanced features like tremolo or vibrato.
5758
//|
5859
//| :param int sample_rate: The desired playback sample rate; higher sample rate requires more memory
60+
//| :param int channel_count: The number of output channels (1=mono, 2=stereo)
5961
//| :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)
6062
//| :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.
6163
//| """
6264
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) {
63-
enum { ARG_sample_rate, ARG_waveform, ARG_envelope };
65+
enum { ARG_sample_rate, ARG_channel_count, ARG_waveform, ARG_envelope };
6466
static const mp_arg_t allowed_args[] = {
6567
{ MP_QSTR_sample_rate, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 11025} },
68+
{ MP_QSTR_channel_count, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 1} },
6669
{ MP_QSTR_waveform, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = mp_const_none } },
6770
{ MP_QSTR_envelope, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = mp_const_none } },
6871
};
@@ -77,6 +80,7 @@ STATIC mp_obj_t synthio_synthesizer_make_new(const mp_obj_type_t *type, size_t n
7780

7881
common_hal_synthio_synthesizer_construct(self,
7982
args[ARG_sample_rate].u_int,
83+
args[ARG_channel_count].u_int,
8084
bufinfo_waveform.buf,
8185
bufinfo_waveform.len / 2,
8286
args[ARG_envelope].u_obj);

shared-bindings/synthio/Synthesizer.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
extern const mp_obj_type_t synthio_synthesizer_type;
3333

3434
void common_hal_synthio_synthesizer_construct(synthio_synthesizer_obj_t *self,
35-
uint32_t sample_rate, const int16_t *waveform, uint16_t waveform_length,
35+
uint32_t sample_rate, int channel_count, const int16_t *waveform, uint16_t waveform_length,
3636
mp_obj_t envelope);
3737
void common_hal_synthio_synthesizer_deinit(synthio_synthesizer_obj_t *self);
3838
bool common_hal_synthio_synthesizer_deinited(synthio_synthesizer_obj_t *self);

shared-module/synthio/MidiTrack.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ void common_hal_synthio_miditrack_construct(synthio_miditrack_obj_t *self,
123123
self->track.buf = (void *)buffer;
124124
self->track.len = len;
125125

126-
synthio_synth_init(&self->synth, sample_rate, waveform, waveform_length, envelope);
126+
synthio_synth_init(&self->synth, sample_rate, 1, waveform, waveform_length, envelope);
127127

128128
start_parse(self);
129129
}

shared-module/synthio/Note.c

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -44,14 +44,20 @@ 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_amplitude(synthio_note_obj_t *self) {
48-
return self->amplitude;
47+
mp_float_t common_hal_synthio_note_get_panning(synthio_note_obj_t *self) {
48+
return self->panning;
4949
}
5050

51-
void common_hal_synthio_note_set_amplitude(synthio_note_obj_t *self, mp_float_t value_in) {
52-
mp_float_t val = mp_arg_validate_float_range(value_in, 0, 1, MP_QSTR_amplitude);
53-
self->amplitude = val;
54-
self->amplitude_scaled = round_float_to_int(val * 32767);
51+
void common_hal_synthio_note_set_panning(synthio_note_obj_t *self, mp_float_t value_in) {
52+
mp_float_t val = mp_arg_validate_float_range(value_in, -1, 1, MP_QSTR_panning);
53+
self->panning = val;
54+
if (val >= 0) {
55+
self->left_panning_scaled = 32768;
56+
self->right_panning_scaled = 32768 - round_float_to_int(val * 32768);
57+
} else {
58+
self->right_panning_scaled = 32768;
59+
self->left_panning_scaled = 32768 + round_float_to_int(val * 32768);
60+
}
5561
}
5662

5763
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) {
200206

201207
uint32_t synthio_note_step(synthio_note_obj_t *self, int32_t sample_rate, int16_t dur, uint16_t *loudness) {
202208
int tremolo_value = synthio_lfo_step(&self->tremolo_state, dur);
203-
*loudness = (*loudness * tremolo_value) >> 15;
209+
loudness[0] = (((loudness[0] * tremolo_value) >> 15) * self->left_panning_scaled) >> 15;
210+
loudness[1] = (((loudness[1] * tremolo_value) >> 15) * self->right_panning_scaled) >> 15;
204211
int bend_value = synthio_bend_value(self, dur);
205212
uint32_t frequency_scaled = pitch_bend(self->frequency_scaled, bend_value);
206213
return frequency_scaled;

shared-module/synthio/Note.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,14 @@ typedef struct synthio_note_obj {
3333
mp_obj_base_t base;
3434

3535
mp_float_t frequency;
36-
mp_float_t amplitude;
36+
mp_float_t panning;
3737
mp_obj_t waveform_obj, envelope_obj;
3838

3939
int32_t sample_rate;
4040

4141
int32_t frequency_scaled;
4242
int32_t amplitude_scaled;
43+
int32_t left_panning_scaled, right_panning_scaled;
4344
synthio_bend_mode_t bend_mode;
4445
synthio_lfo_descr_t tremolo_descr, bend_descr;
4546
synthio_lfo_state_t tremolo_state, bend_state;
@@ -49,7 +50,7 @@ typedef struct synthio_note_obj {
4950
} synthio_note_obj_t;
5051

5152
void synthio_note_recalculate(synthio_note_obj_t *self, int32_t sample_rate);
52-
uint32_t synthio_note_step(synthio_note_obj_t *self, int32_t sample_rate, int16_t dur, uint16_t *loudness);
53+
uint32_t synthio_note_step(synthio_note_obj_t *self, int32_t sample_rate, int16_t dur, uint16_t loudness[2]);
5354
void synthio_note_start(synthio_note_obj_t *self, int32_t sample_rate);
5455
bool synthio_note_playing(synthio_note_obj_t *self);
5556
uint32_t synthio_note_envelope(synthio_note_obj_t *self);

shared-module/synthio/Synthesizer.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,10 @@
3232

3333

3434
void common_hal_synthio_synthesizer_construct(synthio_synthesizer_obj_t *self,
35-
uint32_t sample_rate, const int16_t *waveform, uint16_t waveform_length,
35+
uint32_t sample_rate, int channel_count, const int16_t *waveform, uint16_t waveform_length,
3636
mp_obj_t envelope) {
3737

38-
synthio_synth_init(&self->synth, sample_rate, waveform, waveform_length, envelope);
38+
synthio_synth_init(&self->synth, sample_rate, channel_count, waveform, waveform_length, envelope);
3939
}
4040

4141
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
5252
return SYNTHIO_BITS_PER_SAMPLE;
5353
}
5454
uint8_t common_hal_synthio_synthesizer_get_channel_count(synthio_synthesizer_obj_t *self) {
55-
return 1;
55+
return self->synth.channel_count;
5656
}
5757

5858
void synthio_synthesizer_reset_buffer(synthio_synthesizer_obj_t *self,

shared-module/synthio/__init__.c

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ void synthio_synth_synthesize(synthio_synth_t *synth, uint8_t **bufptr, uint32_t
187187
synth->span.dur -= dur;
188188

189189
int32_t sample_rate = synth->sample_rate;
190-
int32_t out_buffer32[dur];
190+
int32_t out_buffer32[dur * synth->channel_count];
191191

192192
memset(out_buffer32, 0, sizeof(out_buffer32));
193193
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
204204
}
205205

206206
// adjust loudness by envelope
207-
uint16_t loudness = synth->envelope_state[chan].level;
207+
uint16_t loudness[2] = {synth->envelope_state[chan].level,synth->envelope_state[chan].level};
208208

209209
uint32_t dds_rate;
210210
const int16_t *waveform = synth->waveform;
@@ -221,7 +221,7 @@ void synthio_synth_synthesize(synthio_synth_t *synth, uint8_t **bufptr, uint32_t
221221
dds_rate = (sample_rate / 2 + ((uint64_t)(base_freq * waveform_length) << (SYNTHIO_FREQUENCY_SHIFT - 10 + octave))) / sample_rate;
222222
} else {
223223
synthio_note_obj_t *note = MP_OBJ_TO_PTR(note_obj);
224-
int32_t frequency_scaled = synthio_note_step(note, sample_rate, dur, &loudness);
224+
int32_t frequency_scaled = synthio_note_step(note, sample_rate, dur, loudness);
225225
if (note->waveform_buf.buf) {
226226
waveform = note->waveform_buf.buf;
227227
waveform_length = note->waveform_buf.len / 2;
@@ -241,22 +241,27 @@ void synthio_synth_synthesize(synthio_synth_t *synth, uint8_t **bufptr, uint32_t
241241
accum %= lim;
242242
}
243243

244-
for (uint16_t i = 0; i < dur; i++) {
244+
int synth_chan = synth->channel_count;
245+
for (uint16_t i = 0, j = 0; i < dur; i++) {
245246
accum += dds_rate;
246247
// because dds_rate is low enough, the subtraction is guaranteed to go back into range, no expensive modulo needed
247248
if (accum > lim) {
248249
accum -= lim;
249250
}
250251
int16_t idx = accum >> SYNTHIO_FREQUENCY_SHIFT;
251-
out_buffer32[i] += (waveform[idx] * loudness) / 65536;
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++;
256+
}
252257
}
253258
synth->accum[chan] = accum;
254259
}
255260

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

258263
// mix down audio
259-
for (size_t i = 0; i < dur; i++) {
264+
for (size_t i = 0; i < MP_ARRAY_SIZE(out_buffer32); i++) {
260265
int32_t sample = out_buffer32[i];
261266
out_buffer16[i] = mix_down_sample(sample);
262267
}
@@ -270,7 +275,7 @@ void synthio_synth_synthesize(synthio_synth_t *synth, uint8_t **bufptr, uint32_t
270275
synthio_envelope_state_step(&synth->envelope_state[chan], synthio_synth_get_note_envelope(synth, note_obj), dur);
271276
}
272277

273-
*buffer_length = synth->last_buffer_length = dur * SYNTHIO_BYTES_PER_SAMPLE;
278+
*buffer_length = synth->last_buffer_length = dur * SYNTHIO_BYTES_PER_SAMPLE * synth->channel_count;
274279
*bufptr = (uint8_t *)out_buffer16;
275280
}
276281

@@ -301,10 +306,12 @@ mp_obj_t synthio_synth_envelope_get(synthio_synth_t *synth) {
301306
return synth->envelope_obj;
302307
}
303308

304-
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) {
305-
synth->buffer_length = SYNTHIO_MAX_DUR * SYNTHIO_BYTES_PER_SAMPLE;
309+
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) {
310+
mp_arg_validate_int_range(channel_count, 1, 2, MP_QSTR_channel_count);
311+
synth->buffer_length = SYNTHIO_MAX_DUR * SYNTHIO_BYTES_PER_SAMPLE * channel_count;
306312
synth->buffers[0] = m_malloc(synth->buffer_length, false);
307313
synth->buffers[1] = m_malloc(synth->buffer_length, false);
314+
synth->channel_count = channel_count;
308315
synth->other_channel = -1;
309316
synth->waveform = waveform;
310317
synth->waveform_length = waveform_length;
@@ -321,7 +328,11 @@ void synthio_synth_get_buffer_structure(synthio_synth_t *synth, bool single_chan
321328
*single_buffer = false;
322329
*samples_signed = true;
323330
*max_buffer_length = synth->buffer_length;
324-
*spacing = 1;
331+
if (single_channel_output) {
332+
*spacing = synth->channel_count;
333+
} else {
334+
*spacing = 1;
335+
}
325336
}
326337

327338
STATIC bool parse_common(mp_buffer_info_t *bufinfo, mp_obj_t o, int16_t what) {

shared-module/synthio/__init__.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ typedef struct synthio_synth {
6565
uint32_t total_envelope;
6666
int16_t *buffers[2];
6767
const int16_t *waveform;
68+
uint8_t channel_count;
6869
uint16_t buffer_length;
6970
uint16_t last_buffer_length;
7071
uint8_t other_channel, buffer_index, other_buffer_index;
@@ -89,7 +90,7 @@ typedef struct {
8990
void synthio_synth_synthesize(synthio_synth_t *synth, uint8_t **buffer, uint32_t *buffer_length, uint8_t channel);
9091
void synthio_synth_deinit(synthio_synth_t *synth);
9192
bool synthio_synth_deinited(synthio_synth_t *synth);
92-
void synthio_synth_init(synthio_synth_t *synth, uint32_t sample_rate, const int16_t *waveform, uint16_t waveform_length,
93+
void synthio_synth_init(synthio_synth_t *synth, uint32_t sample_rate, int channel_count, const int16_t *waveform, uint16_t waveform_length,
9394
mp_obj_t envelope);
9495
void synthio_synth_get_buffer_structure(synthio_synth_t *synth, bool single_channel_output,
9596
bool *single_buffer, bool *samples_signed, uint32_t *max_buffer_length, uint8_t *spacing);

0 commit comments

Comments
 (0)