38
38
#include "sam/sam.h"
39
39
40
40
// 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 .
42
42
#define USE_DEDICATED_AUDIO_CHANNEL (1)
43
43
44
44
#if USE_DEDICATED_AUDIO_CHANNEL
@@ -70,12 +70,26 @@ volatile bool exhausted = false;
70
70
static unsigned int glitches ;
71
71
72
72
#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
75
78
static volatile bool audio_output_ready = false;
79
+ #endif
76
80
77
81
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
78
91
audio_output_ready = true;
92
+ #endif
79
93
}
80
94
81
95
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) {
88
102
last_frame = false;
89
103
exhausted = false;
90
104
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
91
110
audio_output_ready = true;
111
+ #endif
92
112
}
93
113
94
114
STATIC void speech_wait_output_drained (void ) {
95
115
#if USE_DEDICATED_AUDIO_CHANNEL
96
- while (! audio_output_ready ) {
116
+ while (speech_output_read >= 0 ) {
97
117
mp_handle_pending (true);
98
118
}
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
+ }
102
127
#else
103
128
rendering = true;
104
129
mp_handle_pending (true);
@@ -107,6 +132,16 @@ STATIC void speech_wait_output_drained(void) {
107
132
#endif
108
133
}
109
134
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
+
110
145
// Table to map SAM value `b>>4` to an output value for the PWM.
111
146
// This tries to maximise output volume with minimal distortion.
112
147
static const uint8_t sam_sample_remap [16 ] = {
@@ -170,6 +205,9 @@ void SamOutputByte(unsigned int pos, unsigned char b) {
170
205
if (synth_mode == 0 ) {
171
206
// Traditional micro:bit v1
172
207
208
+ #if USE_DEDICATED_AUDIO_CHANNEL
209
+ // Not supported.
210
+ #else
173
211
unsigned int actual_pos = SCALE_RATE (pos );
174
212
if (buf_start_pos > actual_pos ) {
175
213
glitches ++ ;
@@ -183,14 +221,11 @@ void SamOutputByte(unsigned int pos, unsigned char b) {
183
221
// write a little bit in advance
184
222
unsigned int end = MIN (offset + 8 , OUT_CHUNK_SIZE );
185
223
while (offset < end ) {
186
- #if USE_DEDICATED_AUDIO_CHANNEL
187
- sam_output_buffer [offset ] = b ;
188
- #else
189
224
sam_output_frame -> data [offset ] = b ;
190
- #endif
191
225
offset ++ ;
192
226
}
193
227
last_pos = actual_pos ;
228
+ #endif
194
229
} else {
195
230
unsigned int idx_full ;
196
231
if (synth_mode == 1 || synth_mode == 2 ) {
@@ -200,6 +235,38 @@ void SamOutputByte(unsigned int pos, unsigned char b) {
200
235
// more fidelity
201
236
idx_full = pos >> 5 ;
202
237
}
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
+
203
270
if (buf_start_pos > idx_full ) {
204
271
glitches ++ ;
205
272
buf_start_pos -= OUT_CHUNK_SIZE ;
@@ -232,15 +299,13 @@ void SamOutputByte(unsigned int pos, unsigned char b) {
232
299
// smoothing
233
300
sample = cur_b ;
234
301
}
235
- #if USE_DEDICATED_AUDIO_CHANNEL
236
- sam_output_buffer [last_idx ] = sample ;
237
- #else
238
302
sam_output_frame -> data [last_idx ] = sample ;
239
- #endif
240
303
}
241
304
}
242
305
last_idx = idx ;
243
306
last_b = b ;
307
+
308
+ #endif
244
309
}
245
310
}
246
311
@@ -318,9 +383,9 @@ STATIC mp_obj_t articulate(mp_obj_t phonemes, mp_uint_t n_args, const mp_obj_t *
318
383
{ MP_QSTR_speed , MP_ARG_KW_ONLY | MP_ARG_INT , {.u_int = DEFAULT_SPEED } },
319
384
{ MP_QSTR_mouth , MP_ARG_KW_ONLY | MP_ARG_INT , {.u_int = DEFAULT_MOUTH } },
320
385
{ 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 } },
324
389
{ MP_QSTR_pin , MP_ARG_KW_ONLY | MP_ARG_OBJ , {.u_rom_obj = MP_ROM_PTR (& microbit_pin_default_audio_obj )} },
325
390
};
326
391
@@ -347,7 +412,7 @@ STATIC mp_obj_t articulate(mp_obj_t phonemes, mp_uint_t n_args, const mp_obj_t *
347
412
if (synth_mode == 0 ) {
348
413
sample_rate = 15625 ;
349
414
} else if (synth_mode <= 2 ) {
350
- sample_rate = 16000 ;
415
+ sample_rate = 19000 ;
351
416
} else {
352
417
sample_rate = 38000 ;
353
418
}
@@ -370,9 +435,9 @@ STATIC mp_obj_t articulate(mp_obj_t phonemes, mp_uint_t n_args, const mp_obj_t *
370
435
}
371
436
372
437
#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 );
376
441
}
377
442
#else
378
443
last_frame = true;
0 commit comments