Skip to content

Commit c92efd6

Browse files
committed
Add MixerVoice.panning
1 parent fd2627c commit c92efd6

File tree

5 files changed

+88
-29
lines changed

5 files changed

+88
-29
lines changed

shared-bindings/audiomixer/MixerVoice.c

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,31 @@ MP_PROPERTY_GETSET(audiomixer_mixervoice_level_obj,
114114
(mp_obj_t)&audiomixer_mixervoice_get_level_obj,
115115
(mp_obj_t)&audiomixer_mixervoice_set_level_obj);
116116

117+
//| panning: synthio.BlockInput
118+
//| """Defines the channel(s) in which the voice appears, as a floating point number between
119+
//| -1 and 1. If your board does not support synthio, this property will only accept a float
120+
//| value. This property is ignored if `audiomixer.Mixer.channel_count=1`.
121+
//|
122+
//| -1 is left channel only, 0 is both channels, and 1 is right channel. For fractional values,
123+
//| the note plays at full amplitude in one channel and partial amplitude in the other channel.
124+
//| For instance -.5 plays at full amplitude in the left channel and 1/2 amplitude in the right
125+
//| channel."""
126+
static mp_obj_t audiomixer_mixervoice_obj_get_panning(mp_obj_t self_in) {
127+
return common_hal_audiomixer_mixervoice_get_panning(self_in);
128+
}
129+
MP_DEFINE_CONST_FUN_OBJ_1(audiomixer_mixervoice_get_panning_obj, audiomixer_mixervoice_obj_get_panning);
130+
131+
static mp_obj_t audiomixer_mixervoice_obj_set_panning(mp_obj_t self_in, mp_obj_t panning_in) {
132+
audiomixer_mixervoice_obj_t *self = MP_OBJ_TO_PTR(self_in);
133+
common_hal_audiomixer_mixervoice_set_panning(self, panning_in);
134+
return mp_const_none;
135+
}
136+
MP_DEFINE_CONST_FUN_OBJ_2(audiomixer_mixervoice_set_panning_obj, audiomixer_mixervoice_obj_set_panning);
137+
138+
MP_PROPERTY_GETSET(audiomixer_mixervoice_panning_obj,
139+
(mp_obj_t)&audiomixer_mixervoice_get_panning_obj,
140+
(mp_obj_t)&audiomixer_mixervoice_set_panning_obj);
141+
117142
//| loop: bool
118143
//| """Get or set the loop status of the currently playing sample."""
119144
static mp_obj_t audiomixer_mixervoice_obj_get_loop(mp_obj_t self_in) {
@@ -158,6 +183,7 @@ static const mp_rom_map_elem_t audiomixer_mixervoice_locals_dict_table[] = {
158183
// Properties
159184
{ MP_ROM_QSTR(MP_QSTR_playing), MP_ROM_PTR(&audiomixer_mixervoice_playing_obj) },
160185
{ MP_ROM_QSTR(MP_QSTR_level), MP_ROM_PTR(&audiomixer_mixervoice_level_obj) },
186+
{ MP_ROM_QSTR(MP_QSTR_panning), MP_ROM_PTR(&audiomixer_mixervoice_panning_obj) },
161187
{ MP_ROM_QSTR(MP_QSTR_loop), MP_ROM_PTR(&audiomixer_mixervoice_loop_obj) },
162188
};
163189
static MP_DEFINE_CONST_DICT(audiomixer_mixervoice_locals_dict, audiomixer_mixervoice_locals_dict_table);

shared-bindings/audiomixer/MixerVoice.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ void common_hal_audiomixer_mixervoice_stop(audiomixer_mixervoice_obj_t *self);
1818
void common_hal_audiomixer_mixervoice_end(audiomixer_mixervoice_obj_t *self);
1919
mp_obj_t common_hal_audiomixer_mixervoice_get_level(audiomixer_mixervoice_obj_t *self);
2020
void common_hal_audiomixer_mixervoice_set_level(audiomixer_mixervoice_obj_t *self, mp_obj_t gain);
21+
mp_obj_t common_hal_audiomixer_mixervoice_get_panning(audiomixer_mixervoice_obj_t *self);
22+
void common_hal_audiomixer_mixervoice_set_panning(audiomixer_mixervoice_obj_t *self, mp_obj_t value);
2123

2224
bool common_hal_audiomixer_mixervoice_get_playing(audiomixer_mixervoice_obj_t *self);
2325

shared-module/audiomixer/Mixer.c

Lines changed: 41 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -92,22 +92,22 @@ static inline uint32_t add16signed(uint32_t a, uint32_t b) {
9292
}
9393

9494
__attribute__((always_inline))
95-
static inline uint32_t mult16signed(uint32_t val, int32_t mul) {
95+
static inline uint32_t mult16signed(uint32_t val, int32_t mul[2]) {
9696
#if (defined(__ARM_ARCH_7EM__) && (__ARM_ARCH_7EM__ == 1))
9797
mul <<= 16;
9898
int32_t hi, lo;
9999
enum { bits = 16 }; // saturate to 16 bits
100100
enum { shift = 15 }; // shift is done automatically
101-
asm volatile ("smulwb %0, %1, %2" : "=r" (lo) : "r" (mul), "r" (val));
102-
asm volatile ("smulwt %0, %1, %2" : "=r" (hi) : "r" (mul), "r" (val));
101+
asm volatile ("smulwb %0, %1, %2" : "=r" (lo) : "r" (mul[0]), "r" (val));
102+
asm volatile ("smulwt %0, %1, %2" : "=r" (hi) : "r" (mul[1]), "r" (val));
103103
asm volatile ("ssat %0, %1, %2, asr %3" : "=r" (lo) : "I" (bits), "r" (lo), "I" (shift));
104104
asm volatile ("ssat %0, %1, %2, asr %3" : "=r" (hi) : "I" (bits), "r" (hi), "I" (shift));
105105
asm volatile ("pkhbt %0, %1, %2, lsl #16" : "=r" (val) : "r" (lo), "r" (hi)); // pack
106106
return val;
107107
#else
108108
uint32_t result = 0;
109-
float mod_mul = (float)mul / (float)((1 << 15) - 1);
110109
for (int8_t i = 0; i < 2; i++) {
110+
float mod_mul = (float)mul[i] / (float)((1 << 15) - 1);
111111
int16_t ai = (val >> (sizeof(uint16_t) * 8 * i));
112112
int32_t intermediate = (int32_t)(ai * mod_mul);
113113
if (intermediate > SHRT_MAX) {
@@ -174,6 +174,8 @@ static inline uint32_t copy8msb(uint32_t val) {
174174
return val | (val >> 8);
175175
}
176176

177+
#define ALMOST_ONE (MICROPY_FLOAT_CONST(32767.) / 32768)
178+
177179
static void mix_down_one_voice(audiomixer_mixer_obj_t *self,
178180
audiomixer_mixervoice_obj_t *voice, bool voices_active,
179181
uint32_t *word_buffer, uint32_t length) {
@@ -210,6 +212,7 @@ static void mix_down_one_voice(audiomixer_mixer_obj_t *self,
210212
// Get the current level from the BlockInput. These may change at run time so you need to do bounds checking if required.
211213
shared_bindings_synthio_lfo_tick(self->base.sample_rate, n / self->base.channel_count);
212214
uint16_t level = (uint16_t)(synthio_block_slot_get_limited(&voice->level, MICROPY_FLOAT_CONST(0.0), MICROPY_FLOAT_CONST(1.0)) * (1 << 15));
215+
int16_t panning = synthio_block_slot_get_scaled(&voice->panning, -ALMOST_ONE, ALMOST_ONE);
213216
#else
214217
uint32_t n;
215218
if (MP_LIKELY(self->base.channel_count == sample->channel_count)) {
@@ -218,39 +221,53 @@ static void mix_down_one_voice(audiomixer_mixer_obj_t *self,
218221
n = MIN(voice->buffer_length << 1, length);
219222
}
220223
uint16_t level = voice->level;
224+
int16_t panning = voice->panning;
221225
#endif
222226

227+
uint16_t left_panning_scaled = 32768, right_panning_scaled = 32768;
228+
if (MP_LIKELY(self->base.channel_count == 2)) {
229+
if (panning >= 0) {
230+
right_panning_scaled = 32767 - panning;
231+
} else {
232+
left_panning_scaled = 32767 + panning;
233+
}
234+
}
235+
236+
int32_t loudness[2] = { level, level };
237+
if (MP_LIKELY(self->base.channel_count == 2)) {
238+
loudness[0] = (left_panning_scaled * loudness[0]) >> 15;
239+
loudness[1] = (right_panning_scaled * loudness[1]) >> 15;
240+
}
241+
223242
// First active voice gets copied over verbatim.
224243
if (!voices_active) {
225244
if (MP_LIKELY(self->base.bits_per_sample == 16)) {
226245
if (MP_LIKELY(self->base.samples_signed)) {
227246
if (MP_LIKELY(self->base.channel_count == sample->channel_count)) {
228247
for (uint32_t i = 0; i < n; i++) {
229248
uint32_t v = src[i];
230-
word_buffer[i] = mult16signed(v, level);
249+
word_buffer[i] = mult16signed(v, loudness);
231250
}
232251
} else {
233252
for (uint32_t i = 0; i < n; i += 2) {
234253
uint32_t v = src[i >> 1];
235-
v = mult16signed(v, level);
236-
word_buffer[i] = copy16lsb(v);
237-
word_buffer[i + 1] = copy16msb(v);
254+
word_buffer[i] = mult16signed(copy16lsb(v), loudness);
255+
word_buffer[i + 1] = mult16signed(copy16msb(v), loudness);
238256
}
239257
}
240258
} else {
241259
if (MP_LIKELY(self->base.channel_count == sample->channel_count)) {
242260
for (uint32_t i = 0; i < n; i++) {
243261
uint32_t v = src[i];
244262
v = tosigned16(v);
245-
word_buffer[i] = mult16signed(v, level);
263+
word_buffer[i] = mult16signed(v, loudness);
246264
}
247265
} else {
248266
for (uint32_t i = 0; i + 1 < n; i += 2) {
249267
uint32_t v = src[i >> 1];
250268
v = tosigned16(v);
251-
v = mult16signed(v, level);
252-
word_buffer[i] = copy16lsb(v);
253-
word_buffer[i + 1] = copy16msb(v);
269+
word_buffer[i] = mult16signed(copy16lsb(v), loudness);
270+
word_buffer[i + 1] = mult16signed(copy16msb(v), loudness);
254271
}
255272
}
256273
}
@@ -263,7 +280,7 @@ static void mix_down_one_voice(audiomixer_mixer_obj_t *self,
263280
if (MP_LIKELY(!self->base.samples_signed)) {
264281
word = tosigned16(word);
265282
}
266-
word = mult16signed(word, level);
283+
word = mult16signed(word, loudness);
267284
hword_buffer[i] = pack8(word);
268285
}
269286
} else {
@@ -272,10 +289,8 @@ static void mix_down_one_voice(audiomixer_mixer_obj_t *self,
272289
if (MP_LIKELY(!self->base.samples_signed)) {
273290
word = tosigned16(word);
274291
}
275-
word = mult16signed(word, level);
276-
word = pack8(word);
277-
hword_buffer[i] = copy8lsb(word);
278-
hword_buffer[i + 1] = copy8msb(word);
292+
hword_buffer[i] = pack8(mult16signed(copy16lsb(word), loudness));
293+
hword_buffer[i + 1] = pack8(mult16signed(copy16msb(word), loudness));
279294
}
280295
}
281296
}
@@ -285,30 +300,28 @@ static void mix_down_one_voice(audiomixer_mixer_obj_t *self,
285300
if (MP_LIKELY(self->base.channel_count == sample->channel_count)) {
286301
for (uint32_t i = 0; i < n; i++) {
287302
uint32_t word = src[i];
288-
word_buffer[i] = add16signed(mult16signed(word, level), word_buffer[i]);
303+
word_buffer[i] = add16signed(mult16signed(word, loudness), word_buffer[i]);
289304
}
290305
} else {
291306
for (uint32_t i = 0; i + 1 < n; i += 2) {
292307
uint32_t word = src[i >> 1];
293-
word = mult16signed(word, level);
294-
word_buffer[i] = add16signed(copy16lsb(word), word_buffer[i]);
295-
word_buffer[i + 1] = add16signed(copy16msb(word), word_buffer[i + 1]);
308+
word_buffer[i] = add16signed(mult16signed(copy16lsb(word), loudness), word_buffer[i]);
309+
word_buffer[i + 1] = add16signed(mult16signed(copy16msb(word), loudness), word_buffer[i + 1]);
296310
}
297311
}
298312
} else {
299313
if (MP_LIKELY(self->base.channel_count == sample->channel_count)) {
300314
for (uint32_t i = 0; i < n; i++) {
301315
uint32_t word = src[i];
302316
word = tosigned16(word);
303-
word_buffer[i] = add16signed(mult16signed(word, level), word_buffer[i]);
317+
word_buffer[i] = add16signed(mult16signed(word, loudness), word_buffer[i]);
304318
}
305319
} else {
306320
for (uint32_t i = 0; i + 1 < n; i += 2) {
307321
uint32_t word = src[i >> 1];
308322
word = tosigned16(word);
309-
word = mult16signed(word, level);
310-
word_buffer[i] = add16signed(copy16lsb(word), word_buffer[i]);
311-
word_buffer[i + 1] = add16signed(copy16msb(word), word_buffer[i + 1]);
323+
word_buffer[i] = add16signed(mult16signed(copy16lsb(word), loudness), word_buffer[i]);
324+
word_buffer[i + 1] = add16signed(mult16signed(copy16msb(word), loudness), word_buffer[i + 1]);
312325
}
313326
}
314327
}
@@ -321,7 +334,7 @@ static void mix_down_one_voice(audiomixer_mixer_obj_t *self,
321334
if (MP_LIKELY(!self->base.samples_signed)) {
322335
word = tosigned16(word);
323336
}
324-
word = mult16signed(word, level);
337+
word = mult16signed(word, loudness);
325338
word = add16signed(word, unpack8(hword_buffer[i]));
326339
hword_buffer[i] = pack8(word);
327340
}
@@ -331,9 +344,8 @@ static void mix_down_one_voice(audiomixer_mixer_obj_t *self,
331344
if (MP_LIKELY(!self->base.samples_signed)) {
332345
word = tosigned16(word);
333346
}
334-
word = mult16signed(word, level);
335-
hword_buffer[i] = pack8(add16signed(copy16lsb(word), unpack8(hword_buffer[i])));
336-
hword_buffer[i + 1] = pack8(add16signed(copy16msb(word), unpack8(hword_buffer[i + 1])));
347+
hword_buffer[i] = pack8(add16signed(mult16signed(copy16lsb(word), loudness), unpack8(hword_buffer[i])));
348+
hword_buffer[i + 1] = pack8(add16signed(mult16signed(copy16msb(word), loudness), unpack8(hword_buffer[i + 1])));
337349
}
338350
}
339351
}

shared-module/audiomixer/MixerVoice.c

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
void common_hal_audiomixer_mixervoice_construct(audiomixer_mixervoice_obj_t *self) {
1717
self->sample = NULL;
1818
common_hal_audiomixer_mixervoice_set_level(self, mp_obj_new_float(1.0));
19+
common_hal_audiomixer_mixervoice_set_panning(self, mp_obj_new_float(0.0));
1920
}
2021

2122
void common_hal_audiomixer_mixervoice_set_parent(audiomixer_mixervoice_obj_t *self, audiomixer_mixer_obj_t *parent) {
@@ -38,6 +39,22 @@ void common_hal_audiomixer_mixervoice_set_level(audiomixer_mixervoice_obj_t *sel
3839
#endif
3940
}
4041

42+
mp_obj_t common_hal_audiomixer_mixervoice_get_panning(audiomixer_mixervoice_obj_t *self) {
43+
#if CIRCUITPY_SYNTHIO
44+
return self->panning.obj;
45+
#else
46+
return mp_obj_new_float((mp_float_t)self->panning / ((1 << 15) - 1));
47+
#endif
48+
}
49+
50+
void common_hal_audiomixer_mixervoice_set_panning(audiomixer_mixervoice_obj_t *self, mp_obj_t arg) {
51+
#if CIRCUITPY_SYNTHIO
52+
synthio_block_assign_slot(arg, &self->panning, MP_QSTR_panning);
53+
#else
54+
self->panning = (uint16_t)(mp_arg_validate_obj_float_range(arg, -1, 1, MP_QSTR_panning) * ((1 << 15) - 1));
55+
#endif
56+
}
57+
4158
bool common_hal_audiomixer_mixervoice_get_loop(audiomixer_mixervoice_obj_t *self) {
4259
return self->loop;
4360
}

shared-module/audiomixer/MixerVoice.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@ typedef struct {
2323
uint32_t buffer_length;
2424
#if CIRCUITPY_SYNTHIO
2525
synthio_block_slot_t level;
26+
synthio_block_slot_t panning;
2627
#else
2728
uint16_t level;
29+
int16_t panning;
2830
#endif
2931
} audiomixer_mixervoice_obj_t;

0 commit comments

Comments
 (0)