summaryrefslogtreecommitdiff
path: root/apps/talk.c
diff options
context:
space:
mode:
Diffstat (limited to 'apps/talk.c')
-rw-r--r--apps/talk.c398
1 files changed, 398 insertions, 0 deletions
diff --git a/apps/talk.c b/apps/talk.c
new file mode 100644
index 0000000000..d208c7296c
--- /dev/null
+++ b/apps/talk.c
@@ -0,0 +1,398 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2004 Jörg Hohensohn
11 *
12 * This module collects the Talkbox and voice UI functions.
13 * (Talkbox reads directory names from mp3 clips called thumbnails,
14 * the voice UI lets menus and screens "talk" from a voicefont in memory.
15 *
16 * All files in this archive are subject to the GNU General Public License.
17 * See the file COPYING in the source tree root for full license agreement.
18 *
19 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
20 * KIND, either express or implied.
21 *
22 ****************************************************************************/
23
24#include <stdio.h>
25#include <stddef.h>
26#include "file.h"
27#include "buffer.h"
28#include "system.h"
29#include "mp3_playback.h"
30#include "mpeg.h"
31#include "lang.h"
32#include "talk.h"
33#include "screens.h" /* test hack */
34extern void bitswap(unsigned char *data, int length); /* no header for this */
35
36/***************** Constants *****************/
37
38#define VOICEFONT_FILENAME "/.rockbox/langs/english.voice"
39#define QUEUE_SIZE 32
40
41
42/***************** Data types *****************/
43
44struct clip_entry /* one entry of the index table */
45{
46 int offset; /* offset from start of voicefont file */
47 int size; /* size of the clip */
48};
49
50struct voicefont /* file format of our "voicefont" */
51{
52 int version; /* version of the voicefont */
53 int headersize; /* size of the header, =offset to index */
54 int id_max; /* number of clips contained */
55 struct clip_entry index[]; /* followed by the index table */
56 /* and finally the bitswapped mp3 clips, not visible here */
57};
58
59
60struct queue_entry /* one entry of the internal queue */
61{
62 unsigned char* buf;
63 int len;
64};
65
66
67
68/***************** Globals *****************/
69
70static unsigned char* p_thumbnail; /* buffer for thumbnail */
71static long size_for_thumbnail; /* leftover buffer size for it */
72static struct voicefont* p_voicefont; /* loaded voicefont */
73static bool has_voicefont; /* a voicefont file is present */
74static bool is_playing; /* we're currently playing */
75static struct queue_entry queue[QUEUE_SIZE]; /* queue of scheduled clips */
76static int queue_write; /* write index of queue, by application */
77static int queue_read; /* read index of queue, by ISR context */
78
79
80
81/***************** Private implementation *****************/
82
83static int load_voicefont(void)
84{
85 int fd;
86 int size;
87
88 p_voicefont = NULL; /* indicate no voicefont if we fail below */
89
90 fd = open(VOICEFONT_FILENAME, O_RDONLY);
91 if (fd < 0) /* failed to open */
92 {
93 p_voicefont = NULL; /* indicate no voicefont */
94 has_voicefont = false; /* don't try again */
95 return 0;
96 }
97
98 size = read(fd, mp3buf, mp3end - mp3buf);
99 if (size > 1000
100 && ((struct voicefont*)mp3buf)->headersize
101 == offsetof(struct voicefont, index))
102 {
103 p_voicefont = (struct voicefont*)mp3buf;
104
105 /* thumbnail buffer is the remaining space behind */
106 p_thumbnail = mp3buf + size;
107 p_thumbnail += (int)p_thumbnail % 2; /* 16-bit align */
108 size_for_thumbnail = mp3end - p_thumbnail;
109 }
110 else
111 {
112 has_voicefont = false; /* don't try again */
113 }
114 close(fd);
115
116 return size;
117}
118
119
120/* called in ISR context if mp3 data got consumed */
121static void mp3_callback(unsigned char** start, int* size)
122{
123 int play_now;
124
125 if (queue[queue_read].len > 0) /* current clip not finished? */
126 { /* feed the next 64K-1 chunk */
127 play_now = MIN(queue[queue_read].len, 0xFFFF);
128 *start = queue[queue_read].buf;
129 *size = play_now;
130 queue[queue_read].buf += play_now;
131 queue[queue_read].len -= play_now;
132 return;
133 }
134 else /* go to next entry */
135 {
136 queue_read++;
137 if (queue_read >= QUEUE_SIZE)
138 queue_read = 0;
139 }
140
141 if (queue_read != queue_write) /* queue is not empty? */
142 { /* start next clip */
143 play_now = MIN(queue[queue_read].len, 0xFFFF);
144 *start = queue[queue_read].buf;
145 *size = play_now;
146 queue[queue_read].buf += play_now;
147 queue[queue_read].len -= play_now;
148 }
149 else
150 {
151 *size = 0; /* end of data */
152 is_playing = false;
153 mp3_play_stop(); /* fixme: should be done by caller */
154 }
155}
156
157
158/* stop the playback and the pending clips, but at frame boundary */
159static int shutup(void)
160{
161 mp3_play_pause(false); /* pause */
162
163 /* ToDo: search next frame boundary and continue up to there */
164
165 queue_write = queue_read;
166 is_playing = false;
167 mp3_play_stop();
168
169 return 0;
170}
171
172
173/* schedule a clip, at the end or discard the existing queue */
174static int queue_clip(unsigned char* buf, int size, bool enqueue)
175{
176 if (!enqueue)
177 shutup(); /* cut off all the pending stuff */
178
179 queue[queue_write].buf = buf;
180 queue[queue_write].len = size;
181
182 /* FixMe: make this IRQ-safe */
183
184 if (!is_playing)
185 { /* queue empty, we have to do the initial start */
186 int size_now = MIN(size, 0xFFFF); /* DMA can do no more */
187 is_playing = true;
188 mp3_play_data(buf, size_now, mp3_callback);
189 mp3_play_pause(true); /* kickoff audio */
190 queue[queue_write].buf += size_now;
191 queue[queue_write].len -= size_now;
192 }
193
194 queue_write++;
195 if (queue_write >= QUEUE_SIZE)
196 queue_write = 0;
197
198 return 0;
199}
200
201
202/***************** Public implementation *****************/
203
204void talk_init(void)
205{
206 has_voicefont = true; /* unless we fail later, assume we have one */
207 talk_buffer_steal();
208 queue_write = queue_read = 0;
209}
210
211
212/* somebody else claims the mp3 buffer, e.g. for regular play/record */
213int talk_buffer_steal(void)
214{
215 p_voicefont = NULL; /* indicate no voicefont (trashed) */
216 p_thumbnail = mp3buf; /* whole space for thumbnail */
217 size_for_thumbnail = mp3end - mp3buf;
218 return 0;
219}
220
221
222/* play a voice ID from voicefont */
223int talk_id(int id, bool enqueue)
224{
225 int clipsize;
226 unsigned char* clipbuf;
227 int unit;
228
229 if (mpeg_status()) /* busy, buffer in use */
230 return -1;
231
232 if (p_voicefont == NULL && has_voicefont)
233 load_voicefont(); /* reload needed */
234
235 if (p_voicefont == NULL) /* still no voices? */
236 return -1;
237
238 /* check if this is a special ID, with a value */
239 unit = ((unsigned)id) >> UNIT_SHIFT;
240 if (id != -1 && unit)
241 { /* sign-extend the value */
242 //splash(200, true,"unit=%d", unit);
243 id = (unsigned)id << (32-UNIT_SHIFT);
244 id >>= (32-UNIT_SHIFT);
245 talk_value(id, unit, enqueue); /* speak it */
246 return 0; /* and stop, end of special case */
247 }
248
249 if (id < 0 || id >= p_voicefont->id_max)
250 return -1;
251
252 clipsize = p_voicefont->index[id].size;
253 if (clipsize == 0) /* clip not included in voicefont */
254 return -1;
255
256 clipbuf = mp3buf + p_voicefont->index[id].offset;
257
258 queue_clip(clipbuf, clipsize, enqueue);
259
260 return 0;
261}
262
263
264/* play a thumbnail from file */
265int talk_file(char* filename, bool enqueue)
266{
267 int fd;
268 int size;
269
270 if (mpeg_status()) /* busy, buffer in use */
271 return -1;
272
273 if (p_thumbnail == NULL || size_for_thumbnail <= 0)
274 return -1;
275
276 fd = open(filename, O_RDONLY);
277 if (fd < 0) /* failed to open */
278 {
279 return 0;
280 }
281
282 size = read(fd, p_thumbnail, size_for_thumbnail);
283 close(fd);
284
285 /* ToDo: find audio, skip ID headers and trailers */
286
287 if (size)
288 {
289 bitswap(p_thumbnail, size);
290 queue_clip(p_thumbnail, size, enqueue);
291 }
292
293 return size;
294}
295
296
297/* say a numeric value, this works for english,
298 but not necessarily for other languages */
299int talk_number(int n, bool enqueue)
300{
301 int level = 0; // mille count
302 int mil = 1000000000; // highest possible "-illion"
303
304 if (!enqueue)
305 shutup(); /* cut off all the pending stuff */
306
307 if (n==0)
308 { // special case
309 talk_id(VOICE_ZERO, true);
310 return 0;
311 }
312
313 if (n<0)
314 {
315 talk_id(VOICE_MINUS, true);
316 n = -n;
317 }
318
319 while (n)
320 {
321 int segment = n / mil; // extract in groups of 3 digits
322 n -= segment * mil; // remove the used digits from number
323 mil /= 1000; // digit place for next round
324
325 if (segment)
326 {
327 int hundreds = segment / 100;
328 int ones = segment % 100;
329
330 if (hundreds)
331 {
332 talk_id(VOICE_ZERO + hundreds, true);
333 talk_id(VOICE_HUNDRED, true);
334 }
335
336 // combination indexing
337 if (ones > 20)
338 {
339 int tens = ones/10 + 18;
340 talk_id(VOICE_ZERO + tens, true);
341 ones %= 10;
342 }
343
344 // direct indexing
345 if (ones)
346 talk_id(VOICE_ZERO + ones, true);
347
348 // add billion, million, thousand
349 if (mil)
350 talk_id(VOICE_BILLION + level, true);
351 }
352 level++;
353 }
354
355 return 0;
356}
357
358int talk_value(int n, int unit, bool enqueue)
359{
360 int unit_id;
361 const int unit_voiced[] =
362 { /* lookup table for the voice ID of the units */
363 -1, -1, -1, /* regular ID, int, signed */
364 VOICE_MILLISECONDS, /* here come the "real" units */
365 VOICE_SECONDS,
366 VOICE_MINUTES,
367 VOICE_HOURS,
368 VOICE_KHZ,
369 VOICE_DB,
370 VOICE_PERCENT,
371 VOICE_MEGABYTE,
372 VOICE_GIGABYTE
373 };
374
375 if (unit < 0 || unit >= UNIT_LAST)
376 unit_id = -1;
377 else
378 unit_id = unit_voiced[unit];
379
380 if ((n==1 || n==-1) // singular?
381 && unit_id >= VOICE_SECONDS && unit_id <= VOICE_HOURS)
382 {
383 unit_id--; /* use the singular for those units which have */
384 }
385
386 /* special case with a "plus" before */
387 if (n > 0 && (unit == UNIT_SIGNED || unit == UNIT_DB))
388 {
389 talk_id(VOICE_PLUS, enqueue);
390 enqueue = true;
391 }
392
393 talk_number(n, enqueue); /* say the number */
394 talk_id(unit_id, true); /* say the unit, if any */
395
396 return 0;
397}
398