Skip to content

Commit 081bb4e

Browse files
committed
codal_port/modspeech: Use double buffering for speech output.
Signed-off-by: Damien George <damien@micropython.org>
1 parent 130a254 commit 081bb4e

File tree

1 file changed

+87
-22
lines changed

1 file changed

+87
-22
lines changed

src/codal_port/modspeech.c

Lines changed: 87 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
#include "sam/sam.h"
3939

4040
// If disabled, pipe speech through audio module output.
41-
// If enabled, use a dedicated audio mixer channer.
41+
// If enabled, use a dedicated audio mixer channer with a double buffer.
4242
#define USE_DEDICATED_AUDIO_CHANNEL (1)
4343

4444
#if USE_DEDICATED_AUDIO_CHANNEL
@@ -70,12 +70,26 @@ volatile bool exhausted = false;
7070
static unsigned int glitches;
7171

7272
#if USE_DEDICATED_AUDIO_CHANNEL
73-
static uint8_t sam_output_buffer[OUT_CHUNK_SIZE];
74-
#endif
73+
static uint8_t speech_output_buffer[2 * OUT_CHUNK_SIZE];
74+
static unsigned int speech_output_buffer_idx;
75+
static volatile int speech_output_write;
76+
static volatile int speech_output_read;
77+
#else
7578
static volatile bool audio_output_ready = false;
79+
#endif
7680

7781
void microbit_hal_audio_speech_ready_callback(void) {
82+
#if USE_DEDICATED_AUDIO_CHANNEL
83+
if (speech_output_read >= 0) {
84+
microbit_hal_audio_speech_write_data(&speech_output_buffer[OUT_CHUNK_SIZE * speech_output_read], OUT_CHUNK_SIZE);
85+
speech_output_read = -1;
86+
} else {
87+
// missed
88+
speech_output_read = -2;
89+
}
90+
#else
7891
audio_output_ready = true;
92+
#endif
7993
}
8094

8195
STATIC void sam_output_reset(microbit_audio_frame_obj_t *src_frame) {
@@ -88,17 +102,28 @@ STATIC void sam_output_reset(microbit_audio_frame_obj_t *src_frame) {
88102
last_frame = false;
89103
exhausted = false;
90104
glitches = 0;
105+
#if USE_DEDICATED_AUDIO_CHANNEL
106+
speech_output_buffer_idx = 0;
107+
speech_output_write = 0;
108+
speech_output_read = -2;
109+
#else
91110
audio_output_ready = true;
111+
#endif
92112
}
93113

94114
STATIC void speech_wait_output_drained(void) {
95115
#if USE_DEDICATED_AUDIO_CHANNEL
96-
while (!audio_output_ready) {
116+
while (speech_output_read >= 0) {
97117
mp_handle_pending(true);
98118
}
99-
audio_output_ready = false;
100-
microbit_hal_audio_speech_write_data(sam_output_buffer, OUT_CHUNK_SIZE);
101-
buf_start_pos += OUT_CHUNK_SIZE;
119+
uint32_t atomic_state = MICROPY_BEGIN_ATOMIC_SECTION();
120+
int x = speech_output_read;
121+
speech_output_read = speech_output_write;
122+
MICROPY_END_ATOMIC_SECTION(atomic_state);
123+
speech_output_write = 1 - speech_output_write;
124+
if (x == -2) {
125+
microbit_hal_audio_speech_ready_callback();
126+
}
102127
#else
103128
rendering = true;
104129
mp_handle_pending(true);
@@ -107,6 +132,16 @@ STATIC void speech_wait_output_drained(void) {
107132
#endif
108133
}
109134

135+
#if USE_DEDICATED_AUDIO_CHANNEL
136+
STATIC void speech_output_sample(uint8_t b) {
137+
speech_output_buffer[OUT_CHUNK_SIZE * speech_output_write + speech_output_buffer_idx++] = b;
138+
if (speech_output_buffer_idx >= OUT_CHUNK_SIZE) {
139+
speech_wait_output_drained();
140+
speech_output_buffer_idx = 0;
141+
}
142+
}
143+
#endif
144+
110145
// Table to map SAM value `b>>4` to an output value for the PWM.
111146
// This tries to maximise output volume with minimal distortion.
112147
static const uint8_t sam_sample_remap[16] = {
@@ -170,6 +205,9 @@ void SamOutputByte(unsigned int pos, unsigned char b) {
170205
if (synth_mode == 0) {
171206
// Traditional micro:bit v1
172207

208+
#if USE_DEDICATED_AUDIO_CHANNEL
209+
// Not supported.
210+
#else
173211
unsigned int actual_pos = SCALE_RATE(pos);
174212
if (buf_start_pos > actual_pos) {
175213
glitches++;
@@ -183,14 +221,11 @@ void SamOutputByte(unsigned int pos, unsigned char b) {
183221
// write a little bit in advance
184222
unsigned int end = MIN(offset+8, OUT_CHUNK_SIZE);
185223
while (offset < end) {
186-
#if USE_DEDICATED_AUDIO_CHANNEL
187-
sam_output_buffer[offset] = b;
188-
#else
189224
sam_output_frame->data[offset] = b;
190-
#endif
191225
offset++;
192226
}
193227
last_pos = actual_pos;
228+
#endif
194229
} else {
195230
unsigned int idx_full;
196231
if (synth_mode == 1 || synth_mode == 2) {
@@ -200,6 +235,38 @@ void SamOutputByte(unsigned int pos, unsigned char b) {
200235
// more fidelity
201236
idx_full = pos >> 5;
202237
}
238+
239+
// Need to output sample b at position idx_full.
240+
241+
#if USE_DEDICATED_AUDIO_CHANNEL
242+
243+
if (synth_mode == 1 || synth_mode == 3) {
244+
// No smoothing, just output b as many times as needed to get to idx_full.
245+
while (last_idx < idx_full) {
246+
last_idx += 1;
247+
speech_output_sample(b);
248+
}
249+
} else {
250+
// Apply linear interpolation from last_b to b.
251+
unsigned int delta_idx = idx_full - last_idx;
252+
if (delta_idx > 0) {
253+
int cur_b = last_b;
254+
int delta_b = ((int)b - (int)last_b) / (int)delta_idx;
255+
while (last_idx < idx_full) {
256+
last_idx += 1;
257+
if (last_idx == idx_full) {
258+
cur_b = b;
259+
} else {
260+
cur_b += delta_b;
261+
}
262+
speech_output_sample(cur_b);
263+
}
264+
}
265+
last_b = b;
266+
}
267+
268+
#else
269+
203270
if (buf_start_pos > idx_full) {
204271
glitches++;
205272
buf_start_pos -= OUT_CHUNK_SIZE;
@@ -232,15 +299,13 @@ void SamOutputByte(unsigned int pos, unsigned char b) {
232299
// smoothing
233300
sample = cur_b;
234301
}
235-
#if USE_DEDICATED_AUDIO_CHANNEL
236-
sam_output_buffer[last_idx] = sample;
237-
#else
238302
sam_output_frame->data[last_idx] = sample;
239-
#endif
240303
}
241304
}
242305
last_idx = idx;
243306
last_b = b;
307+
308+
#endif
244309
}
245310
}
246311

@@ -318,9 +383,9 @@ STATIC mp_obj_t articulate(mp_obj_t phonemes, mp_uint_t n_args, const mp_obj_t *
318383
{ MP_QSTR_speed, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = DEFAULT_SPEED} },
319384
{ MP_QSTR_mouth, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = DEFAULT_MOUTH} },
320385
{ MP_QSTR_throat, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = DEFAULT_THROAT} },
321-
{ MP_QSTR_debug, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = false} },
322-
{ MP_QSTR_mode, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 2} },
323-
{ MP_QSTR_volume, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 4} },
386+
{ MP_QSTR_debug, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = false} },
387+
{ MP_QSTR_mode, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 1} },
388+
{ MP_QSTR_volume, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 4} },
324389
{ MP_QSTR_pin, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_PTR(&microbit_pin_default_audio_obj)} },
325390
};
326391

@@ -347,7 +412,7 @@ STATIC mp_obj_t articulate(mp_obj_t phonemes, mp_uint_t n_args, const mp_obj_t *
347412
if (synth_mode == 0) {
348413
sample_rate = 15625;
349414
} else if (synth_mode <= 2) {
350-
sample_rate = 16000;
415+
sample_rate = 19000;
351416
} else {
352417
sample_rate = 38000;
353418
}
@@ -370,9 +435,9 @@ STATIC mp_obj_t articulate(mp_obj_t phonemes, mp_uint_t n_args, const mp_obj_t *
370435
}
371436

372437
#if USE_DEDICATED_AUDIO_CHANNEL
373-
if (last_idx > 0) {
374-
memset(sam_output_buffer + last_idx, 128, OUT_CHUNK_SIZE - last_idx);
375-
speech_wait_output_drained();
438+
// Finish writing out current buffer.
439+
while (speech_output_buffer_idx != 0) {
440+
speech_output_sample(128);
376441
}
377442
#else
378443
last_frame = true;

0 commit comments

Comments
 (0)