Skip to content

Commit d55f725

Browse files
committed
codal_port/modaudio: Rework audio playing to play a long AudioFrame.
- Audio playback now supports AudioFrames with arbitrary size. - Allows passing a single AudioFrame to audio.play. Signed-off-by: Damien George <damien@micropython.org>
1 parent 0741cc7 commit d55f725

File tree

3 files changed

+106
-57
lines changed

3 files changed

+106
-57
lines changed

src/codal_app/microbithal.h

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -173,9 +173,10 @@ bool microbit_hal_audio_is_expression_active(void);
173173
void microbit_hal_audio_play_expression(const char *expr);
174174
void microbit_hal_audio_stop_expression(void);
175175

176-
void microbit_hal_audio_init(uint32_t sample_rate);
177-
void microbit_hal_audio_write_data(const uint8_t *buf, size_t num_samples);
178-
void microbit_hal_audio_ready_callback(void);
176+
void microbit_hal_audio_raw_init(uint32_t sample_rate);
177+
void microbit_hal_audio_raw_set_rate(uint32_t sample_rate);
178+
void microbit_hal_audio_raw_write_data(const uint8_t *buf, size_t num_samples);
179+
void microbit_hal_audio_raw_ready_callback(void);
179180

180181
void microbit_hal_audio_speech_init(uint32_t sample_rate);
181182
void microbit_hal_audio_speech_write_data(const uint8_t *buf, size_t num_samples);

src/codal_app/microbithal_audio.cpp

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ class AudioSource : public DataSource {
3333
DataSink *sink;
3434
ManagedBuffer buf;
3535
void (*callback)(void);
36+
MixerChannel *channel;
3637

3738
AudioSource()
3839
: started(false) {
@@ -52,7 +53,7 @@ class AudioSource : public DataSource {
5253
}
5354
};
5455

55-
static AudioSource data_source;
56+
static AudioSource raw_source;
5657
static AudioSource speech_source;
5758

5859
extern "C" {
@@ -104,29 +105,35 @@ void microbit_hal_audio_stop_expression(void) {
104105
uBit.audio.soundExpressions.stop();
105106
}
106107

107-
void microbit_hal_audio_init(uint32_t sample_rate) {
108-
if (!data_source.started) {
108+
void microbit_hal_audio_raw_init(uint32_t sample_rate) {
109+
if (!raw_source.started) {
109110
MicroBitAudio::requestActivation();
110-
data_source.started = true;
111-
data_source.callback = microbit_hal_audio_ready_callback;
112-
uBit.audio.mixer.addChannel(data_source, sample_rate, 255);
111+
raw_source.started = true;
112+
raw_source.callback = microbit_hal_audio_raw_ready_callback;
113+
raw_source.channel = uBit.audio.mixer.addChannel(raw_source, sample_rate, 255);
114+
} else {
115+
raw_source.channel->setSampleRate(sample_rate);
113116
}
114117
}
115118

116-
void microbit_hal_audio_write_data(const uint8_t *buf, size_t num_samples) {
117-
if ((size_t)data_source.buf.length() != num_samples) {
118-
data_source.buf = ManagedBuffer(num_samples);
119+
void microbit_hal_audio_raw_set_rate(uint32_t sample_rate) {
120+
raw_source.channel->setSampleRate(sample_rate);
121+
}
122+
123+
void microbit_hal_audio_raw_write_data(const uint8_t *buf, size_t num_samples) {
124+
if ((size_t)raw_source.buf.length() != num_samples) {
125+
raw_source.buf = ManagedBuffer(num_samples);
119126
}
120-
memcpy(data_source.buf.getBytes(), buf, num_samples);
121-
data_source.sink->pullRequest();
127+
memcpy(raw_source.buf.getBytes(), buf, num_samples);
128+
raw_source.sink->pullRequest();
122129
}
123130

124131
void microbit_hal_audio_speech_init(uint32_t sample_rate) {
125132
if (!speech_source.started) {
126133
MicroBitAudio::requestActivation();
127134
speech_source.started = true;
128135
speech_source.callback = microbit_hal_audio_speech_ready_callback;
129-
uBit.audio.mixer.addChannel(speech_source, sample_rate, 255);
136+
speech_source.channel = uBit.audio.mixer.addChannel(speech_source, sample_rate, 255);
130137
}
131138
}
132139

src/codal_port/modaudio.c

Lines changed: 83 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,9 @@
3030
#include "modaudio.h"
3131
#include "modmicrobit.h"
3232

33-
#define audio_source_iter MP_STATE_PORT(audio_source)
33+
// Convenience macros to access the root-pointer state.
34+
#define audio_source_frame MP_STATE_PORT(audio_source_frame_state)
35+
#define audio_source_iter MP_STATE_PORT(audio_source_iter_state)
3436

3537
#define LOG_AUDIO_CHUNK_SIZE (5)
3638
#define AUDIO_CHUNK_SIZE (1 << LOG_AUDIO_CHUNK_SIZE)
@@ -47,15 +49,18 @@ typedef enum {
4749
static uint8_t audio_output_buffer[OUT_CHUNK_SIZE];
4850
static volatile audio_output_state_t audio_output_state;
4951
static volatile bool audio_fetcher_scheduled;
52+
static size_t audio_raw_offset;
5053

5154
microbit_audio_frame_obj_t *microbit_audio_frame_make_new(size_t size);
5255

5356
static inline bool audio_is_running(void) {
54-
return audio_source_iter != NULL;
57+
return audio_source_frame != NULL || audio_source_iter != MP_OBJ_NULL;
5558
}
5659

5760
void microbit_audio_stop(void) {
61+
audio_source_frame = NULL;
5862
audio_source_iter = NULL;
63+
audio_raw_offset = 0;
5964
}
6065

6166
STATIC void audio_buffer_ready(void) {
@@ -64,50 +69,77 @@ STATIC void audio_buffer_ready(void) {
6469
audio_output_state = AUDIO_OUTPUT_STATE_DATA_READY;
6570
MICROPY_END_ATOMIC_SECTION(atomic_state);
6671
if (old_state == AUDIO_OUTPUT_STATE_IDLE) {
67-
microbit_hal_audio_ready_callback();
72+
microbit_hal_audio_raw_ready_callback();
6873
}
6974
}
7075

7176
STATIC void audio_data_fetcher(void) {
7277
audio_fetcher_scheduled = false;
73-
if (audio_source_iter == NULL) {
74-
return;
75-
}
76-
mp_obj_t buffer_obj;
77-
nlr_buf_t nlr;
78-
if (nlr_push(&nlr) == 0) {
79-
buffer_obj = mp_iternext_allow_raise(audio_source_iter);
80-
nlr_pop();
81-
} else {
82-
if (!mp_obj_is_subclass_fast(MP_OBJ_FROM_PTR(((mp_obj_base_t*)nlr.ret_val)->type),
83-
MP_OBJ_FROM_PTR(&mp_type_StopIteration))) {
84-
mp_sched_exception(MP_OBJ_FROM_PTR(nlr.ret_val));
78+
79+
if (audio_source_frame != NULL) {
80+
// An existing AudioFrame is being played, see if there's any data left.
81+
if (audio_raw_offset >= audio_source_frame->size) {
82+
// AudioFrame is exhausted.
83+
audio_source_frame = NULL;
8584
}
86-
buffer_obj = MP_OBJ_STOP_ITERATION;
8785
}
88-
if (buffer_obj == MP_OBJ_STOP_ITERATION) {
89-
// End of audio iterator
90-
microbit_audio_stop();
91-
} else if (mp_obj_get_type(buffer_obj) != &microbit_audio_frame_type) {
92-
// Audio iterator did not return an AudioFrame
93-
microbit_audio_stop();
94-
mp_sched_exception(mp_obj_new_exception_msg(&mp_type_TypeError, MP_ERROR_TEXT("not an AudioFrame")));
95-
} else {
96-
microbit_audio_frame_obj_t *buffer = (microbit_audio_frame_obj_t *)buffer_obj;
97-
uint8_t *dest = &audio_output_buffer[0];
98-
uint32_t last = dest[BUFFER_EXPANSION * AUDIO_CHUNK_SIZE - 1];
99-
for (int i = 0; i < AUDIO_CHUNK_SIZE; ++i) {
100-
uint32_t cur = buffer->data[i];
101-
for (int j = 0; j < BUFFER_EXPANSION; ++j) {
102-
// Get next sample with linear interpolation.
103-
uint32_t sample = ((BUFFER_EXPANSION - 1 - j) * last + (j + 1) * cur) / BUFFER_EXPANSION;
104-
// Write sample to the buffer.
105-
*dest++ = sample;
86+
87+
if (audio_source_frame == NULL) {
88+
// There is no AudioFrame, so try to get one from the audio iterator.
89+
90+
if (audio_source_iter == MP_OBJ_NULL) {
91+
// Audio iterator is already exhausted.
92+
microbit_audio_stop();
93+
return;
94+
}
95+
96+
// Get the next item from the audio iterator.
97+
nlr_buf_t nlr;
98+
mp_obj_t frame_obj;
99+
if (nlr_push(&nlr) == 0) {
100+
frame_obj = mp_iternext_allow_raise(audio_source_iter);
101+
nlr_pop();
102+
} else {
103+
if (!mp_obj_is_subclass_fast(MP_OBJ_FROM_PTR(((mp_obj_base_t*)nlr.ret_val)->type),
104+
MP_OBJ_FROM_PTR(&mp_type_StopIteration))) {
105+
mp_sched_exception(MP_OBJ_FROM_PTR(nlr.ret_val));
106106
}
107-
last = cur;
107+
frame_obj = MP_OBJ_STOP_ITERATION;
108+
}
109+
if (frame_obj == MP_OBJ_STOP_ITERATION) {
110+
// End of audio iterator.
111+
microbit_audio_stop();
112+
return;
113+
}
114+
if (!mp_obj_is_type(frame_obj, &microbit_audio_frame_type)) {
115+
// Audio iterator did not return an AudioFrame.
116+
microbit_audio_stop();
117+
mp_sched_exception(mp_obj_new_exception_msg(&mp_type_TypeError, MP_ERROR_TEXT("not an AudioFrame")));
118+
return;
108119
}
109-
audio_buffer_ready();
120+
121+
// We have the next AudioFrame.
122+
audio_source_frame = MP_OBJ_TO_PTR(frame_obj);
123+
audio_raw_offset = 0;
110124
}
125+
126+
const uint8_t *src = &audio_source_frame->data[audio_raw_offset];
127+
audio_raw_offset += AUDIO_CHUNK_SIZE;
128+
129+
uint8_t *dest = &audio_output_buffer[0];
130+
uint32_t last = dest[OUT_CHUNK_SIZE - 1];
131+
for (int i = 0; i < AUDIO_CHUNK_SIZE; ++i) {
132+
uint32_t cur = src[i];
133+
for (int j = 0; j < BUFFER_EXPANSION; ++j) {
134+
// Get next sample with linear interpolation.
135+
uint32_t sample = ((BUFFER_EXPANSION - 1 - j) * last + (j + 1) * cur) / BUFFER_EXPANSION;
136+
// Write sample to the buffer.
137+
*dest++ = sample;
138+
}
139+
last = cur;
140+
}
141+
142+
audio_buffer_ready();
111143
}
112144

113145
STATIC mp_obj_t audio_data_fetcher_wrapper(mp_obj_t arg) {
@@ -116,10 +148,10 @@ STATIC mp_obj_t audio_data_fetcher_wrapper(mp_obj_t arg) {
116148
}
117149
STATIC MP_DEFINE_CONST_FUN_OBJ_1(audio_data_fetcher_wrapper_obj, audio_data_fetcher_wrapper);
118150

119-
void microbit_hal_audio_ready_callback(void) {
151+
void microbit_hal_audio_raw_ready_callback(void) {
120152
if (audio_output_state == AUDIO_OUTPUT_STATE_DATA_READY) {
121153
// there is data ready to send out to the audio pipeline, so send it
122-
microbit_hal_audio_write_data(&audio_output_buffer[0], OUT_CHUNK_SIZE);
154+
microbit_hal_audio_raw_write_data(&audio_output_buffer[0], OUT_CHUNK_SIZE);
123155
audio_output_state = AUDIO_OUTPUT_STATE_DATA_WRITTEN;
124156
} else {
125157
// no data ready, need to call this function later when data is ready
@@ -134,7 +166,7 @@ void microbit_hal_audio_ready_callback(void) {
134166
static void audio_init(uint32_t sample_rate) {
135167
audio_fetcher_scheduled = false;
136168
audio_output_state = AUDIO_OUTPUT_STATE_IDLE;
137-
microbit_hal_audio_init(BUFFER_EXPANSION * sample_rate);
169+
microbit_hal_audio_raw_init(BUFFER_EXPANSION * sample_rate);
138170
}
139171

140172
void microbit_audio_play_source(mp_obj_t src, mp_obj_t pin_select, bool wait, uint32_t sample_rate) {
@@ -150,6 +182,9 @@ void microbit_audio_play_source(mp_obj_t src, mp_obj_t pin_select, bool wait, ui
150182
sound_expr_data = sound->name;
151183
} else if (mp_obj_is_type(src, &microbit_soundeffect_type)) {
152184
sound_expr_data = microbit_soundeffect_get_sound_expr_data(src);
185+
} else if (mp_obj_is_type(src, &microbit_audio_frame_type)) {
186+
audio_source_frame = MP_OBJ_TO_PTR(src);
187+
audio_raw_offset = 0;
153188
} else if (mp_obj_is_type(src, &mp_type_tuple) || mp_obj_is_type(src, &mp_type_list)) {
154189
// A tuple/list passed in. Need to check if it contains SoundEffect instances.
155190
size_t len;
@@ -167,7 +202,13 @@ void microbit_audio_play_source(mp_obj_t src, mp_obj_t pin_select, bool wait, ui
167202
}
168203
// Replace last "," with a string null terminator.
169204
data[-1] = '\0';
205+
} else {
206+
// A tuple/list of AudioFrame instances.
207+
audio_source_iter = mp_getiter(src, NULL);
170208
}
209+
} else {
210+
// An iterator of AudioFrame instances.
211+
audio_source_iter = mp_getiter(src, NULL);
171212
}
172213

173214
if (sound_expr_data != NULL) {
@@ -190,9 +231,8 @@ void microbit_audio_play_source(mp_obj_t src, mp_obj_t pin_select, bool wait, ui
190231
return;
191232
}
192233

193-
// Get the iterator and start the audio running.
234+
// Start the audio running.
194235
// The scheduler must be locked because audio_data_fetcher() can also be called from the scheduler.
195-
audio_source_iter = mp_getiter(src, NULL);
196236
mp_sched_lock();
197237
audio_data_fetcher();
198238
mp_sched_unlock();
@@ -440,4 +480,5 @@ microbit_audio_frame_obj_t *microbit_audio_frame_make_new(size_t size) {
440480
return res;
441481
}
442482

443-
MP_REGISTER_ROOT_POINTER(void *audio_source);
483+
MP_REGISTER_ROOT_POINTER(struct _microbit_audio_frame_obj_t *audio_source_frame_state);
484+
MP_REGISTER_ROOT_POINTER(mp_obj_t audio_source_iter_state);

0 commit comments

Comments
 (0)