Skip to content

Commit 76008ce

Browse files
committed
Introduce audioio.Mixer which can mix multiple audio samples
to produce a single sample. Only works with 16 bit samples on the M4. Fixes #987
1 parent 7b95818 commit 76008ce

File tree

11 files changed

+880
-78
lines changed

11 files changed

+880
-78
lines changed

ports/atmel-samd/Makefile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,8 @@ ifneq ($(CHIP_VARIANT),SAMR21G18A)
407407
audioio/__init__.c \
408408
audioio/AudioOut.c
409409
SRC_SHARED_MODULE += \
410+
audioio/__init__.c \
411+
audioio/Mixer.c \
410412
audioio/RawSample.c \
411413
audioio/WaveFile.c
412414
endif

ports/atmel-samd/audio_dma.c

Lines changed: 0 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -40,82 +40,6 @@ static audio_dma_t* audio_dma_state[AUDIO_DMA_CHANNEL_COUNT];
4040
// This cannot be in audio_dma_state because it's volatile.
4141
static volatile bool audio_dma_pending[AUDIO_DMA_CHANNEL_COUNT];
4242

43-
uint32_t audiosample_sample_rate(mp_obj_t sample_obj) {
44-
if (MP_OBJ_IS_TYPE(sample_obj, &audioio_rawsample_type)) {
45-
audioio_rawsample_obj_t* sample = MP_OBJ_TO_PTR(sample_obj);
46-
return sample->sample_rate;
47-
}
48-
if (MP_OBJ_IS_TYPE(sample_obj, &audioio_wavefile_type)) {
49-
audioio_wavefile_obj_t* file = MP_OBJ_TO_PTR(sample_obj);
50-
return file->sample_rate;
51-
}
52-
return 16000;
53-
}
54-
55-
uint8_t audiosample_bits_per_sample(mp_obj_t sample_obj) {
56-
if (MP_OBJ_IS_TYPE(sample_obj, &audioio_rawsample_type)) {
57-
audioio_rawsample_obj_t* sample = MP_OBJ_TO_PTR(sample_obj);
58-
return sample->bits_per_sample;
59-
}
60-
if (MP_OBJ_IS_TYPE(sample_obj, &audioio_wavefile_type)) {
61-
audioio_wavefile_obj_t* file = MP_OBJ_TO_PTR(sample_obj);
62-
return file->bits_per_sample;
63-
}
64-
return 8;
65-
}
66-
67-
uint8_t audiosample_channel_count(mp_obj_t sample_obj) {
68-
if (MP_OBJ_IS_TYPE(sample_obj, &audioio_rawsample_type)) {
69-
audioio_rawsample_obj_t* sample = MP_OBJ_TO_PTR(sample_obj);
70-
return sample->channel_count;
71-
}
72-
if (MP_OBJ_IS_TYPE(sample_obj, &audioio_wavefile_type)) {
73-
audioio_wavefile_obj_t* file = MP_OBJ_TO_PTR(sample_obj);
74-
return file->channel_count;
75-
}
76-
return 1;
77-
}
78-
79-
static void audiosample_reset_buffer(mp_obj_t sample_obj, bool single_channel, uint8_t audio_channel) {
80-
if (MP_OBJ_IS_TYPE(sample_obj, &audioio_rawsample_type)) {
81-
audioio_rawsample_obj_t* sample = MP_OBJ_TO_PTR(sample_obj);
82-
audioio_rawsample_reset_buffer(sample, single_channel, audio_channel);
83-
}
84-
if (MP_OBJ_IS_TYPE(sample_obj, &audioio_wavefile_type)) {
85-
audioio_wavefile_obj_t* file = MP_OBJ_TO_PTR(sample_obj);
86-
audioio_wavefile_reset_buffer(file, single_channel, audio_channel);
87-
}
88-
}
89-
90-
static audioio_get_buffer_result_t audiosample_get_buffer(mp_obj_t sample_obj,
91-
bool single_channel,
92-
uint8_t channel,
93-
uint8_t** buffer, uint32_t* buffer_length) {
94-
if (MP_OBJ_IS_TYPE(sample_obj, &audioio_rawsample_type)) {
95-
audioio_rawsample_obj_t* sample = MP_OBJ_TO_PTR(sample_obj);
96-
return audioio_rawsample_get_buffer(sample, single_channel, channel, buffer, buffer_length);
97-
}
98-
if (MP_OBJ_IS_TYPE(sample_obj, &audioio_wavefile_type)) {
99-
audioio_wavefile_obj_t* file = MP_OBJ_TO_PTR(sample_obj);
100-
return audioio_wavefile_get_buffer(file, single_channel, channel, buffer, buffer_length);
101-
}
102-
return GET_BUFFER_DONE;
103-
}
104-
105-
static void audiosample_get_buffer_structure(mp_obj_t sample_obj, bool single_channel,
106-
bool* single_buffer, bool* samples_signed,
107-
uint32_t* max_buffer_length, uint8_t* spacing) {
108-
if (MP_OBJ_IS_TYPE(sample_obj, &audioio_rawsample_type)) {
109-
audioio_rawsample_obj_t* sample = MP_OBJ_TO_PTR(sample_obj);
110-
audioio_rawsample_get_buffer_structure(sample, single_channel, single_buffer,
111-
samples_signed, max_buffer_length, spacing);
112-
} else if (MP_OBJ_IS_TYPE(sample_obj, &audioio_wavefile_type)) {
113-
audioio_wavefile_obj_t* file = MP_OBJ_TO_PTR(sample_obj);
114-
audioio_wavefile_get_buffer_structure(file, single_channel, single_buffer, samples_signed,
115-
max_buffer_length, spacing);
116-
}
117-
}
118-
11943
uint8_t find_free_audio_dma_channel(void) {
12044
uint8_t channel;
12145
for (channel = 0; channel < AUDIO_DMA_CHANNEL_COUNT; channel++) {

shared-bindings/audioio/Mixer.c

Lines changed: 249 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,249 @@
1+
/*
2+
* This file is part of the Micro Python project, http://micropython.org/
3+
*
4+
* The MIT License (MIT)
5+
*
6+
* Copyright (c) 2018 Scott Shawcroft for Adafruit Industries
7+
*
8+
* Permission is hereby granted, free of charge, to any person obtaining a copy
9+
* of this software and associated documentation files (the "Software"), to deal
10+
* in the Software without restriction, including without limitation the rights
11+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12+
* copies of the Software, and to permit persons to whom the Software is
13+
* furnished to do so, subject to the following conditions:
14+
*
15+
* The above copyright notice and this permission notice shall be included in
16+
* all copies or substantial portions of the Software.
17+
*
18+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24+
* THE SOFTWARE.
25+
*/
26+
#include "shared-bindings/audioio/Mixer.h"
27+
28+
#include <stdint.h>
29+
30+
#include "lib/utils/context_manager_helpers.h"
31+
#include "py/binary.h"
32+
#include "py/objproperty.h"
33+
#include "py/runtime.h"
34+
#include "shared-bindings/microcontroller/Pin.h"
35+
#include "shared-bindings/audioio/RawSample.h"
36+
#include "shared-bindings/util.h"
37+
#include "supervisor/shared/translate.h"
38+
39+
//| .. currentmodule:: audioio
40+
//|
41+
//| :class:`Mixer` -- Mixes one or more audio samples together
42+
//| ===========================================================
43+
//|
44+
//| Mixer mixes multiple samples into one sample.
45+
//|
46+
//| .. class:: Mixer(channel_count=2, buffer_size=1024)
47+
//|
48+
//| Create a Mixer object that can mix multiple channels with the same sample rate.
49+
//|
50+
//| :param int channel_count: The maximum number of samples to mix at once
51+
//| :param int buffer_size: The total size in bytes of the buffers to mix into
52+
//|
53+
//| Playing a wave file from flash::
54+
//|
55+
//| import board
56+
//| import audioio
57+
//| import digitalio
58+
//|
59+
//| # Required for CircuitPlayground Express
60+
//| speaker_enable = digitalio.DigitalInOut(board.SPEAKER_ENABLE)
61+
//| speaker_enable.switch_to_output(value=True)
62+
//|
63+
//| music = audioio.WaveFile(open("cplay-5.1-16bit-16khz.wav", "rb"))
64+
//| drum = audioio.WaveFile(open("drum.wav", "rb"))
65+
//| mixer = audioio.Mixer(voice_count=2, sample_rate=16000, channel_count=1, bits_per_sample=16, samples_signed=True)
66+
//| a = audioio.AudioOut(board.A0)
67+
//|
68+
//| print("playing")
69+
//| a.play(mixer)
70+
//| mixer.play(music, voice=0)
71+
//| while mixer.playing:
72+
//| mixer.play(drum, voice=1)
73+
//| time.sleep(1)
74+
//| print("stopped")
75+
//|
76+
STATIC mp_obj_t audioio_mixer_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *pos_args) {
77+
mp_arg_check_num(n_args, n_kw, 0, 2, true);
78+
mp_map_t kw_args;
79+
mp_map_init_fixed_table(&kw_args, n_kw, pos_args + n_args);
80+
enum { ARG_voice_count, ARG_buffer_size, ARG_channel_count, ARG_bits_per_sample, ARG_samples_signed, ARG_sample_rate };
81+
static const mp_arg_t allowed_args[] = {
82+
{ MP_QSTR_voice_count, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 2} },
83+
{ MP_QSTR_buffer_size, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 1024} },
84+
{ MP_QSTR_channel_count, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 2} },
85+
{ MP_QSTR_bits_per_sample, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 16} },
86+
{ MP_QSTR_samples_signed, MP_ARG_BOOL | MP_ARG_KW_ONLY, {.u_bool = true} },
87+
{ MP_QSTR_sample_rate, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 8000} },
88+
};
89+
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
90+
mp_arg_parse_all(n_args, pos_args, &kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
91+
92+
mp_int_t voice_count = args[ARG_voice_count].u_int;
93+
if (voice_count < 1 || voice_count > 255) {
94+
mp_raise_ValueError(translate("Invalid voice count"));
95+
}
96+
97+
mp_int_t channel_count = args[ARG_channel_count].u_int;
98+
if (channel_count < 1 || channel_count > 2) {
99+
mp_raise_ValueError(translate("Invalid channel count"));
100+
}
101+
mp_int_t sample_rate = args[ARG_sample_rate].u_int;
102+
if (sample_rate < 1) {
103+
mp_raise_ValueError(translate("Sample rate must be positive"));
104+
}
105+
mp_int_t bits_per_sample = args[ARG_bits_per_sample].u_int;
106+
if (bits_per_sample != 8 && bits_per_sample != 16) {
107+
mp_raise_ValueError(translate("bits_per_sample must be 8 or 16"));
108+
}
109+
audioio_mixer_obj_t *self = m_new_obj_var(audioio_mixer_obj_t, audioio_mixer_voice_t, voice_count);
110+
self->base.type = &audioio_mixer_type;
111+
common_hal_audioio_mixer_construct(self, voice_count, args[ARG_buffer_size].u_int, bits_per_sample, args[ARG_samples_signed].u_bool, channel_count, sample_rate);
112+
113+
return MP_OBJ_FROM_PTR(self);
114+
}
115+
116+
//| .. method:: deinit()
117+
//|
118+
//| Deinitialises the Mixer and releases any hardware resources for reuse.
119+
//|
120+
STATIC mp_obj_t audioio_mixer_deinit(mp_obj_t self_in) {
121+
audioio_mixer_obj_t *self = MP_OBJ_TO_PTR(self_in);
122+
common_hal_audioio_mixer_deinit(self);
123+
return mp_const_none;
124+
}
125+
STATIC MP_DEFINE_CONST_FUN_OBJ_1(audioio_mixer_deinit_obj, audioio_mixer_deinit);
126+
127+
//| .. method:: __enter__()
128+
//|
129+
//| No-op used by Context Managers.
130+
//|
131+
// Provided by context manager helper.
132+
133+
//| .. method:: __exit__()
134+
//|
135+
//| Automatically deinitializes the hardware when exiting a context. See
136+
//| :ref:`lifetime-and-contextmanagers` for more info.
137+
//|
138+
STATIC mp_obj_t audioio_mixer_obj___exit__(size_t n_args, const mp_obj_t *args) {
139+
(void)n_args;
140+
common_hal_audioio_mixer_deinit(args[0]);
141+
return mp_const_none;
142+
}
143+
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(audioio_mixer___exit___obj, 4, 4, audioio_mixer_obj___exit__);
144+
145+
146+
//| .. method:: play(sample, *, voice=0, loop=False)
147+
//|
148+
//| Plays the sample once when loop=False and continuously when loop=True.
149+
//| Does not block. Use `playing` to block.
150+
//|
151+
//| Sample must be an `audioio.WaveFile`, `audioio.Mixer` or `audioio.RawSample`.
152+
//|
153+
//| If other samples are already playing, the encodings must match.
154+
//|
155+
STATIC mp_obj_t audioio_mixer_obj_play(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
156+
enum { ARG_sample, ARG_voice, ARG_loop };
157+
static const mp_arg_t allowed_args[] = {
158+
{ MP_QSTR_sample, MP_ARG_OBJ | MP_ARG_REQUIRED },
159+
{ MP_QSTR_voice, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 0} },
160+
{ MP_QSTR_loop, MP_ARG_BOOL | MP_ARG_KW_ONLY, {.u_bool = false} },
161+
};
162+
audioio_mixer_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]);
163+
raise_error_if_deinited(common_hal_audioio_mixer_deinited(self));
164+
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
165+
mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
166+
167+
mp_obj_t sample = args[ARG_sample].u_obj;
168+
common_hal_audioio_mixer_play(self, sample, args[ARG_voice].u_int, args[ARG_loop].u_bool);
169+
170+
return mp_const_none;
171+
}
172+
MP_DEFINE_CONST_FUN_OBJ_KW(audioio_mixer_play_obj, 1, audioio_mixer_obj_play);
173+
174+
//| .. method:: stop(voice=0)
175+
//|
176+
//| Stops playback and resets to the start of the sample on the given channel.
177+
//|
178+
STATIC mp_obj_t audioio_mixer_obj_stop(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
179+
enum { ARG_voice };
180+
static const mp_arg_t allowed_args[] = {
181+
{ MP_QSTR_voice, MP_ARG_INT | MP_ARG_KW_ONLY, {.u_int = 0} },
182+
};
183+
audioio_mixer_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]);
184+
raise_error_if_deinited(common_hal_audioio_mixer_deinited(self));
185+
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
186+
mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
187+
188+
common_hal_audioio_mixer_stop(self, args[ARG_voice].u_int);
189+
return mp_const_none;
190+
}
191+
MP_DEFINE_CONST_FUN_OBJ_KW(audioio_mixer_stop_obj, 1, audioio_mixer_obj_stop);
192+
193+
//| .. attribute:: playing
194+
//|
195+
//| True when an audio sample is being output even if `paused`. (read-only)
196+
//|
197+
STATIC mp_obj_t audioio_mixer_obj_get_playing(mp_obj_t self_in) {
198+
audioio_mixer_obj_t *self = MP_OBJ_TO_PTR(self_in);
199+
raise_error_if_deinited(common_hal_audioio_mixer_deinited(self));
200+
return mp_obj_new_bool(common_hal_audioio_mixer_get_playing(self));
201+
}
202+
MP_DEFINE_CONST_FUN_OBJ_1(audioio_mixer_get_playing_obj, audioio_mixer_obj_get_playing);
203+
204+
const mp_obj_property_t audioio_mixer_playing_obj = {
205+
.base.type = &mp_type_property,
206+
.proxy = {(mp_obj_t)&audioio_mixer_get_playing_obj,
207+
(mp_obj_t)&mp_const_none_obj,
208+
(mp_obj_t)&mp_const_none_obj},
209+
};
210+
211+
//| .. attribute:: sample_rate
212+
//|
213+
//| 32 bit value that dictates how quickly samples are played in Hertz (cycles per second).
214+
//|
215+
STATIC mp_obj_t audioio_mixer_obj_get_sample_rate(mp_obj_t self_in) {
216+
audioio_mixer_obj_t *self = MP_OBJ_TO_PTR(self_in);
217+
raise_error_if_deinited(common_hal_audioio_mixer_deinited(self));
218+
return MP_OBJ_NEW_SMALL_INT(common_hal_audioio_mixer_get_sample_rate(self));
219+
}
220+
MP_DEFINE_CONST_FUN_OBJ_1(audioio_mixer_get_sample_rate_obj, audioio_mixer_obj_get_sample_rate);
221+
222+
223+
const mp_obj_property_t audioio_mixer_sample_rate_obj = {
224+
.base.type = &mp_type_property,
225+
.proxy = {(mp_obj_t)&audioio_mixer_get_sample_rate_obj,
226+
(mp_obj_t)&mp_const_none_obj,
227+
(mp_obj_t)&mp_const_none_obj},
228+
};
229+
230+
STATIC const mp_rom_map_elem_t audioio_mixer_locals_dict_table[] = {
231+
// Methods
232+
{ MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&audioio_mixer_deinit_obj) },
233+
{ MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&default___enter___obj) },
234+
{ MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&audioio_mixer___exit___obj) },
235+
{ MP_ROM_QSTR(MP_QSTR_play), MP_ROM_PTR(&audioio_mixer_play_obj) },
236+
{ MP_ROM_QSTR(MP_QSTR_stop), MP_ROM_PTR(&audioio_mixer_stop_obj) },
237+
238+
// Properties
239+
{ MP_ROM_QSTR(MP_QSTR_playing), MP_ROM_PTR(&audioio_mixer_playing_obj) },
240+
{ MP_ROM_QSTR(MP_QSTR_sample_rate), MP_ROM_PTR(&audioio_mixer_sample_rate_obj) },
241+
};
242+
STATIC MP_DEFINE_CONST_DICT(audioio_mixer_locals_dict, audioio_mixer_locals_dict_table);
243+
244+
const mp_obj_type_t audioio_mixer_type = {
245+
{ &mp_type_type },
246+
.name = MP_QSTR_Mixer,
247+
.make_new = audioio_mixer_make_new,
248+
.locals_dict = (mp_obj_dict_t*)&audioio_mixer_locals_dict,
249+
};

shared-bindings/audioio/Mixer.h

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
* This file is part of the Micro Python project, http://micropython.org/
3+
*
4+
* The MIT License (MIT)
5+
*
6+
* Copyright (c) 2018 Scott Shawcroft for Adafruit Industries
7+
*
8+
* Permission is hereby granted, free of charge, to any person obtaining a copy
9+
* of this software and associated documentation files (the "Software"), to deal
10+
* in the Software without restriction, including without limitation the rights
11+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12+
* copies of the Software, and to permit persons to whom the Software is
13+
* furnished to do so, subject to the following conditions:
14+
*
15+
* The above copyright notice and this permission notice shall be included in
16+
* all copies or substantial portions of the Software.
17+
*
18+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24+
* THE SOFTWARE.
25+
*/
26+
27+
#ifndef MICROPY_INCLUDED_SHARED_BINDINGS_AUDIOIO_MIXER_H
28+
#define MICROPY_INCLUDED_SHARED_BINDINGS_AUDIOIO_MIXER_H
29+
30+
#include "common-hal/microcontroller/Pin.h"
31+
#include "shared-module/audioio/Mixer.h"
32+
#include "shared-bindings/audioio/RawSample.h"
33+
34+
extern const mp_obj_type_t audioio_mixer_type;
35+
36+
void common_hal_audioio_mixer_construct(audioio_mixer_obj_t* self,
37+
uint8_t voice_count,
38+
uint32_t buffer_size,
39+
uint8_t bits_per_sample,
40+
bool samples_signed,
41+
uint8_t channel_count,
42+
uint32_t sample_rate);
43+
44+
void common_hal_audioio_mixer_deinit(audioio_mixer_obj_t* self);
45+
bool common_hal_audioio_mixer_deinited(audioio_mixer_obj_t* self);
46+
void common_hal_audioio_mixer_play(audioio_mixer_obj_t* self, mp_obj_t sample, uint8_t voice, bool loop);
47+
void common_hal_audioio_mixer_stop(audioio_mixer_obj_t* self, uint8_t voice);
48+
49+
bool common_hal_audioio_mixer_get_playing(audioio_mixer_obj_t* self);
50+
uint32_t common_hal_audioio_mixer_get_sample_rate(audioio_mixer_obj_t* self);
51+
52+
#endif // MICROPY_INCLUDED_SHARED_BINDINGS_AUDIOIO_MIXER_H

0 commit comments

Comments
 (0)