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;
7070static 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
7578static volatile bool audio_output_ready = false;
79+ #endif
7680
7781void 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
8195STATIC 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
94114STATIC 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.
112147static 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