A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita
audio
rust
zig
deno
mpris
rockbox
mpd
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 *
9 * Copyright (C) 2004 Jörg Hohensohn
10 *
11 * This module collects the Talkbox and voice UI functions.
12 * (Talkbox reads directory names from mp3 clips called thumbnails,
13 * the voice UI lets menus and screens "talk" from a voicefile in memory.
14 *
15 * This program is free software; you can redistribute it and/or
16 * modify it under the terms of the GNU General Public License
17 * as published by the Free Software Foundation; either version 2
18 * of the License, or (at your option) any later version.
19 *
20 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
21 * KIND, either express or implied.
22 *
23 ****************************************************************************/
24
25#include <stdio.h>
26#include <stddef.h>
27#include "string-extra.h"
28#include "file.h"
29#include "system.h"
30#include "kernel.h"
31#include "settings.h"
32#include "settings_list.h"
33#include "splash.h"
34#include "voice_thread.h"
35#include "audio.h"
36#include "lang.h"
37#include "language.h"
38#include "talk.h"
39#include "metadata.h"
40/*#define LOGF_ENABLE*/
41#include "logf.h"
42#include "bitswap.h"
43#include "plugin.h" /* plugin_get_buffer() */
44#include "debug.h"
45#include "panic.h"
46#include "misc.h" /* time_split_units() */
47#include "mv.h"
48
49/***************** Constants *****************/
50
51#if MEMORYSIZE <= 2
52#define QUEUE_SIZE 64
53#else
54#define QUEUE_SIZE 128 /* must be a power of two */
55#endif
56
57#define QUEUE_MASK (QUEUE_SIZE-1)
58const char* const dir_thumbnail_name = "_dirname.talk";
59const char* const file_thumbnail_ext = ".talk";
60
61/***************** Functional Macros *****************/
62
63#define QUEUE_LEVEL ((queue_write - queue_read) & QUEUE_MASK)
64
65#define LOADED_MASK 0x80000000 /* MSB */
66
67#ifndef DEFAULT_VOICE_LANG
68#define DEFAULT_VOICE_LANG "english"
69#endif
70
71/***************** Data types *****************/
72
73struct clip_entry /* one entry of the index table */
74{
75 int offset; /* offset from start of voicefile file */
76 int size; /* size of the clip */
77};
78
79struct voicefile_header /* file format of our voice file */
80{
81 int version; /* version of the voicefile */
82 int target_id; /* the rockbox target the file was made for */
83 int table; /* offset to index table, (=header size) */
84 int id1_max; /* number of "normal" clips contained in above index */
85 int id2_max; /* number of "voice only" clips contained in above index */
86 /* The header is folled by the index tables (n*struct clip_entry),
87 * which is followed by the mp3/speex encoded clip data */
88};
89
90/***************** Globals *****************/
91
92#if MEMORYSIZE <= 8
93/* On low memory swcodec targets the entire voice file wouldn't fit in memory
94 * together with codecs, so we load clips each time they are accessed. */
95#define TALK_PROGRESSIVE_LOAD
96/* 70+ clips should fit into 100k */
97#define MAX_CLIP_BUFFER_SIZE (100000)
98#endif
99
100#ifndef MAX_CLIP_BUFFER_SIZE
101/* 1GB should be enough for everybody; will be capped to voicefile size */
102#define MAX_CLIP_BUFFER_SIZE (1<<30)
103#endif
104#define THUMBNAIL_RESERVE (50000)
105#define NULL_TERMINATED ((size_t)-1)
106/* Multiple thumbnails can be loaded back-to-back in this buffer. */
107static volatile int thumbnail_buf_used SHAREDBSS_ATTR; /* length of data in
108 thumbnail buffer */
109static struct voicefile_header voicefile; /* loaded voicefile */
110static bool has_voicefile; /* a voicefile file is present */
111static bool need_shutup; /* is there possibly any voice playing to be shutup */
112static bool force_enqueue_next; /* enqueue next utterance even if enqueue is false */
113static int queue_write; /* write index of queue, by application */
114static int queue_read; /* read index of queue, by ISR context */
115static enum talk_status talk_status = TALK_STATUS_OK;
116/* protects queue_read, queue_write and thumbnail_buf_used */
117static struct mutex queue_mutex SHAREDBSS_ATTR;
118#define talk_queue_lock() ({ mutex_lock(&queue_mutex); })
119#define talk_queue_unlock() ({ mutex_unlock(&queue_mutex); })
120static int sent; /* how many bytes handed over to playback, owned by ISR */
121static unsigned char curr_hd[3]; /* current frame header, for re-sync */
122static unsigned char last_lang[MAX_FILENAME+1]; /* name of last used lang file (in talk_init) */
123static bool talk_initialized; /* true if talk_init has been called */
124static bool give_buffer_away; /* true if we should give the buffers away in shrink_callback if requested */
125static int talk_temp_disable_count; /* if positive, temporarily disable voice UI (not saved) */
126
127 /* size of the voice data in the voice file and the actually allocated buffer
128 * for it. voicebuf_size is always smaller or equal to voicefile_size */
129static unsigned long voicefile_size, voicebuf_size;
130
131struct queue_entry /* one entry of the internal queue */
132{
133 int handle; /* buflib handle to the clip data */
134 int length; /* total length of the clip */
135 int remaining; /* bytes that still need to be deoded */
136};
137
138static struct buflib_context clip_ctx;
139
140struct clip_cache_metadata {
141 long tick;
142 int handle, voice_id;
143};
144
145static int metadata_table_handle;
146static unsigned max_clips;
147static int cache_hits, cache_misses;
148
149static struct queue_entry queue[QUEUE_SIZE]; /* queue of scheduled clips */
150static struct queue_entry silence, *last_clip;
151
152/***************** Private implementation *****************/
153
154static int index_handle, talk_handle;
155
156static int move_callback(int handle, void *current, void *new)
157{
158 (void)current;
159 if (handle == talk_handle && !buflib_context_relocate(&clip_ctx, new))
160 return BUFLIB_CB_CANNOT_MOVE;
161 return BUFLIB_CB_OK;
162}
163
164
165static struct mutex read_buffer_mutex;
166
167static inline bool check_audio_status(void)
168{
169 return true;
170}
171
172/* ISR (voice_callback()) must not run during moving of the clip buffer,
173 * because the MAS may get out-of-sync */
174static void sync_callback(int handle, bool sync_on)
175{
176 (void) handle;
177 if (sync_on)
178 mutex_lock(&read_buffer_mutex);
179 else
180 mutex_unlock(&read_buffer_mutex);
181}
182
183static ssize_t read_to_handle_ex(int fd, struct buflib_context *ctx, int handle,
184 int handle_offset, size_t count)
185{
186 unsigned char *buf;
187 ssize_t ret;
188 mutex_lock(&read_buffer_mutex);
189
190 if (!ctx)
191 buf = core_get_data(handle);
192 else
193 buf = buflib_get_data(ctx, handle);
194
195 buf += handle_offset;
196 ret = read(fd, buf, count);
197
198 mutex_unlock(&read_buffer_mutex);
199
200 return ret;
201}
202
203static ssize_t read_to_handle(int fd, int handle, int handle_offset, size_t count)
204{
205 return read_to_handle_ex(fd, NULL, handle, handle_offset, count);
206}
207
208
209static int shrink_callback(int handle, unsigned hints, void *start, size_t old_size)
210{
211 (void)start;(void)old_size;
212 int *h;
213#if (MAX_CLIP_BUFFER_SIZE < (MEMORYSIZE<<20) || (MEMORYSIZE > 2))
214 /* on low-mem and when the voice buffer size is not limited (i.e.
215 * on 2MB HWCODEC) we effectively own the entire buffer because
216 * the voicefile takes up all RAM. This blocks other Rockbox parts
217 * from allocating, especially during bootup. Therefore always give
218 * up the buffer and reload when clips are played back. On high-mem
219 * or when the clip buffer is limited to a few 100K this provision is
220 * not necessary. */
221 if (give_buffer_away
222 && (hints & BUFLIB_SHRINK_POS_MASK) == BUFLIB_SHRINK_POS_MASK)
223#else
224 (void)hints;
225#endif
226 {
227 if (handle == talk_handle)
228 h = &talk_handle;
229 else if (handle == index_handle)
230 h = &index_handle;
231 else h = NULL;
232
233 mutex_lock(&read_buffer_mutex);
234 /* the clip buffer isn't usable without index table */
235 if (handle == index_handle)
236 talk_handle = core_free(talk_handle);
237 if (h)
238 *h = core_free(handle);
239 mutex_unlock(&read_buffer_mutex);
240
241 return BUFLIB_CB_OK;
242 }
243 return BUFLIB_CB_CANNOT_SHRINK;
244}
245
246static struct buflib_callbacks talk_ops = {
247 .move_callback = move_callback,
248 .sync_callback = sync_callback,
249 .shrink_callback = shrink_callback,
250};
251
252
253static int open_voicefile(void)
254{
255 char fname[MAX_PATH];
256 char* p_lang = DEFAULT_VOICE_LANG; /* default */
257
258 if ( global_settings.lang_file[0] &&
259 global_settings.lang_file[0] != 0xff )
260 { /* try to open the voice file of the selected language */
261 p_lang = (char *)global_settings.lang_file;
262 }
263
264 return open_pathfmt(fname, sizeof(fname),
265 O_RDONLY, LANG_DIR "/%s.voice", p_lang);
266}
267
268
269static int id2index(int id)
270{
271 int index = id;
272 if (id > VOICEONLY_DELIMITER)
273 { /* voice-only entries use the second part of the table.
274 The first string comes after VOICEONLY_DELIMITER so we need to
275 substract VOICEONLY_DELIMITER + 1 */
276 index -= VOICEONLY_DELIMITER + 1;
277 if (index >= voicefile.id2_max)
278 return -1; /* must be newer than we have */
279 index += voicefile.id1_max; /* table 2 is behind table 1 */
280 }
281 else
282 { /* normal use of the first table */
283 if (id >= voicefile.id1_max)
284 return -1; /* must be newer than we have */
285 }
286 return index;
287}
288
289#ifndef TALK_PROGRESSIVE_LOAD
290static int index2id(int index)
291{
292 int id = index;
293
294 if (index >= voicefile.id2_max + voicefile.id1_max)
295 return -1;
296
297 if (index >= voicefile.id1_max)
298 { /* must be voice-only if it exceeds table 1 */
299 id -= voicefile.id1_max;
300 /* The first string comes after VOICEONLY_DELIMITER so we need to
301 add VOICEONLY_DELIMITER + 1 */
302 id += VOICEONLY_DELIMITER + 1;
303 }
304
305 return id;
306}
307#endif
308
309static int free_oldest_clip(void)
310{
311 unsigned i;
312 int oldest = 0;
313 bool thumb = false;
314 long age, now, next_age;
315 struct clip_entry* clipbuf;
316 struct clip_cache_metadata *cc = buflib_get_data(&clip_ctx, metadata_table_handle);
317 for(age = i = 0, now = current_tick; i < max_clips; i++)
318 {
319 if (cc[i].handle)
320 {
321 next_age = (now - cc[i].tick);
322 if (thumb && cc[i].voice_id == VOICEONLY_DELIMITER && next_age > age)
323 {
324 /* thumb clips are freed first */
325 age = now - cc[i].tick;
326 oldest = i;
327 }
328 else if (!thumb)
329 {
330 if (cc[i].voice_id == VOICEONLY_DELIMITER)
331 {
332 age = next_age;
333 oldest = i;
334 thumb = true;
335 }
336 else if (next_age > age && cc[i].voice_id != VOICE_PAUSE)
337 {
338 /* find the last-used clip but never consider silence */
339 age = next_age;
340 oldest = i;
341 }
342 }
343 }
344 }
345 /* free the last one if no oldest one could be determined */
346 cc = &cc[oldest];
347 cc->handle = buflib_free(&clip_ctx, cc->handle);
348 /* need to clear the LOADED bit too (not for thumb clips) */
349 if (cc->voice_id != VOICEONLY_DELIMITER)
350 {
351 clipbuf = core_get_data(index_handle);
352 clipbuf[id2index(cc->voice_id)].size &= ~LOADED_MASK;
353 }
354 return oldest;
355}
356
357/* common code for load_initial_clips() and get_clip() */
358static void add_cache_entry(int clip_handle, int table_index, int id)
359{
360 unsigned i;
361 struct clip_cache_metadata *cc = buflib_get_data(&clip_ctx, metadata_table_handle);
362
363 if (table_index != -1)
364 {
365 /* explicit slot; use that */
366 cc = &cc[table_index];
367 if (cc->handle > 0) panicf("%s(): Slot already used", __func__);
368 }
369 else
370 { /* find an empty slot */
371 for(i = 0; cc[i].handle && i < max_clips; i++) ;
372 if (i == max_clips) /* no free slot in the cache table? */
373 i = free_oldest_clip();
374 cc = &cc[i];
375 }
376 cc->handle = clip_handle;
377 cc->tick = current_tick;
378 cc->voice_id = id;
379}
380
381static ssize_t read_clip_data(int fd, int index, int clip_handle)
382{
383 struct clip_entry* clipbuf;
384 size_t clipsize;
385 ssize_t ret = -1;
386
387 if (fd < 0)
388 {
389 buflib_free(&clip_ctx, clip_handle);
390 return -1; /* open error */
391 }
392
393 clipbuf = core_get_data(index_handle);
394 /* this must not be called with LOADED_MASK set in clipsize */
395 clipsize = clipbuf[index].size;
396 if (lseek(fd, clipbuf[index].offset, SEEK_SET) >= 0)
397 ret = read_to_handle_ex(fd, &clip_ctx, clip_handle, 0, clipsize);
398
399 if (ret < 0 || clipsize != (size_t)ret)
400 {
401 buflib_free(&clip_ctx, clip_handle);
402 return -2; /* read error */
403 }
404
405 clipbuf = core_get_data(index_handle);
406 clipbuf[index].size |= LOADED_MASK; /* mark as loaded */
407
408 return ret;
409}
410
411static void load_initial_clips(int fd)
412{
413#if defined(TALK_PROGRESSIVE_LOAD)
414 (void) fd;
415#else
416 unsigned index, i;
417 unsigned num_clips = voicefile.id1_max + voicefile.id2_max;
418
419 for(index = i = 0; index < num_clips && i < max_clips; index++)
420 {
421 int handle;
422 struct clip_entry* clipbuf = core_get_data(index_handle);
423 size_t clipsize = clipbuf[index].size;
424 ssize_t ret;
425
426 if (clipsize == 0) /* clip not included in voicefile */
427 continue;
428
429 handle = buflib_alloc(&clip_ctx, clipsize);
430 if (handle < 0)
431 break;
432
433 ret = read_clip_data(fd, index, handle);
434 if (ret < 0)
435 break;
436
437 add_cache_entry(handle, i++, index2id(index));
438 }
439#endif
440}
441
442/* fetch a clip from the voice file */
443static int get_clip(long id, struct queue_entry *q)
444{
445 int index;
446 int retval = -1;
447 struct clip_entry* clipbuf;
448 size_t clipsize;
449
450 index = id2index(id);
451 if (index == -1 || index_handle <= 0)
452 return -1;
453
454 clipbuf = core_get_data(index_handle);
455 clipsize = clipbuf[index].size;
456 if (clipsize == 0) /* clip not included in voicefile */
457 return -1;
458
459 if (!(clipsize & LOADED_MASK))
460 { /* clip needs loading */
461 int fd, handle, oldest = -1;
462 ssize_t ret;
463 cache_misses++;
464 /* free clips from cache until this one succeeds to allocate */
465 while ((handle = buflib_alloc(&clip_ctx, clipsize)) < 0)
466 oldest = free_oldest_clip();
467 /* handle should now hold a valid alloc. Load from disk
468 * and insert into cache */
469 fd = open_voicefile();
470 ret = read_clip_data(fd, index, handle);
471 close(fd);
472 if (ret < 0)
473 return ret;
474 /* finally insert into metadata table */
475 add_cache_entry(handle, oldest, id);
476 retval = handle;
477 }
478 else
479 { /* clip is in memory already; find where it was loaded */
480 cache_hits++;
481 struct clip_cache_metadata *cc;
482 static int i;
483 cc = buflib_get_data(&clip_ctx, metadata_table_handle);
484 for (i = 0; cc[i].voice_id != id || !cc[i].handle; i++) ;
485 cc[i].tick = current_tick; /* reset age */
486 clipsize &= ~LOADED_MASK; /* without the extra bit gives true size */
487 retval = cc[i].handle;
488 }
489
490 q->handle = retval;
491 q->length = clipsize;
492 q->remaining = clipsize;
493 return 0;
494}
495
496static bool load_index_table(int fd, const struct voicefile_header *hdr)
497{
498 ssize_t ret;
499
500 if (index_handle > 0) /* nothing to do? */
501 return true;
502
503 ssize_t alloc_size = (hdr->id1_max + hdr->id2_max) * sizeof(struct clip_entry);
504 index_handle = core_alloc_ex(alloc_size, &talk_ops);
505 if (index_handle < 0)
506 return false;
507
508 ret = read_to_handle(fd, index_handle, 0, alloc_size);
509 if (ret != alloc_size)
510 {
511 index_handle = core_free(index_handle);
512 return false;
513 }
514
515#ifdef ROCKBOX_LITTLE_ENDIAN
516 struct clip_entry *buf, *end;
517 buf = core_get_data(index_handle);
518 end = buf + hdr->id1_max + hdr->id2_max;
519 for (; buf != end; buf++)
520 {
521 buf->offset = swap32(buf->offset);
522 buf->size = swap32(buf->size);
523 }
524#endif
525
526 return true;
527}
528
529static bool load_header(int fd, struct voicefile_header *hdr)
530{
531 ssize_t got_size = read(fd, hdr, sizeof(*hdr));
532 if (got_size != sizeof(*hdr))
533 return false;
534
535#ifdef ROCKBOX_LITTLE_ENDIAN
536 hdr->version = swap32(hdr->version);
537 hdr->target_id = swap32(hdr->target_id);
538 hdr->table = swap32(hdr->table);
539 hdr->id1_max = swap32(hdr->id1_max);
540 hdr->id2_max = swap32(hdr->id2_max);
541#endif
542 return true;
543}
544
545static bool create_clip_buffer(size_t max_size)
546{
547 /* just allocate, populate on an as-needed basis later */
548 talk_handle = core_alloc_ex(max_size, &talk_ops);
549 if (talk_handle < 0)
550 goto alloc_err;
551
552 buflib_init(&clip_ctx, core_get_data(talk_handle), max_size);
553
554 return true;
555
556alloc_err:
557 talk_status = TALK_STATUS_ERR_ALLOC;
558 index_handle = core_free(index_handle);
559 return false;
560}
561
562static inline int load_voicefile_failure(int fd)
563{
564 if (fd >= 0)
565 close(fd);
566 return -1;
567}
568
569/* load the voice file into the mp3 buffer */
570static bool load_voicefile_index(int fd)
571{
572 if (fd < 0) /* failed to open */
573 {
574 talk_status = TALK_STATUS_ERR_NOFILE;
575 return false;
576 }
577
578 /* load the header first */
579 if (!load_header(fd, &voicefile))
580 {
581 talk_status = TALK_STATUS_ERR_INCOMPATIBLE;
582 return false;
583 }
584
585 /* format check */
586 if (voicefile.table == sizeof(struct voicefile_header))
587 {
588 if (voicefile.version == VOICE_VERSION &&
589 voicefile.target_id == TARGET_ID &&
590 voicefile.id1_max == TALK_FINAL_ID &&
591 voicefile.id2_max == TALK_FINAL_ID_VOICEONLY - VOICEONLY_DELIMITER - 1)
592 {
593 if (load_index_table(fd, &voicefile))
594 return true;
595 }
596 }
597
598 talk_status = TALK_STATUS_ERR_INCOMPATIBLE;
599 logf("Incompatible voice file");
600 logf("version %d expected %d", voicefile.version, VOICE_VERSION);
601 logf("target_id %d expected %d", voicefile.target_id, TARGET_ID);
602 logf("id1_max %d expected %d", voicefile.id1_max, TALK_FINAL_ID);
603 logf("id2_max %d expected %d",
604 voicefile.id2_max, TALK_FINAL_ID_VOICEONLY - VOICEONLY_DELIMITER);
605 return false;
606}
607
608/* this function caps the voicefile buffer and allocates it. It can
609 * be called after talk_init(), e.g. when the voice was temporarily disabled.
610 * The buffer size has to be capped again each time because the available
611 * audio buffer changes over time */
612static bool load_voicefile_data(int fd)
613{
614 voicebuf_size = voicefile_size;
615 /* cap to the max. number of clips or the size of the available audio
616 * buffer which we grab. We leave some to the rest of the system.
617 * While that reduces our buffer size it improves the chance that
618 * other allocs succeed without disabling voice which would require
619 * reloading the voice from disk (as we do not shrink our buffer when
620 * other code attempts new allocs these would fail) */
621 size_t metadata_alloc_size;
622 ssize_t cap = MIN(MAX_CLIP_BUFFER_SIZE, audio_buffer_available() - (64<<10));
623 if (UNLIKELY(cap < 0))
624 {
625 logf("Not enough memory for voice. Disabling...\n");
626 talk_status = TALK_STATUS_ERR_OOM;
627 return false;
628 }
629 else if (voicebuf_size > (size_t)cap)
630 voicebuf_size = cap;
631
632 /* just allocate, populate on an as-needed basis later
633 * re-create the clip buffer to ensure clip_ctx is up-to-date */
634 talk_handle = core_free(talk_handle);
635 if (!create_clip_buffer(voicebuf_size))
636 return false;
637
638 /* the first alloc is the clip metadata table */
639 metadata_alloc_size = max_clips * sizeof(struct clip_cache_metadata);
640 metadata_table_handle = buflib_alloc(&clip_ctx, metadata_alloc_size);
641 if (metadata_table_handle <= 0)
642 {
643 talk_status = TALK_STATUS_ERR_OOM;
644 return false;
645 }
646 memset(buflib_get_data(&clip_ctx, metadata_table_handle), 0, metadata_alloc_size);
647
648 load_initial_clips(fd);
649 /* make sure to have the silence clip, if available return value can
650 * be cached globally even for TALK_PROGRESSIVE_LOAD because the
651 * VOICE_PAUSE clip is specially handled */
652 get_clip(VOICE_PAUSE, &silence);
653
654 return true;
655}
656
657/* Use a static buffer to avoid difficulties with buflib during
658 * buffer passing to the voice_thread (swcodec). Clips can be played
659 in chunks so the size is not that important */
660static unsigned char commit_buffer[2<<10];
661
662static void* commit_transfer(struct queue_entry *qe, size_t *size)
663{
664 void *buf = NULL; /* shut up gcc */
665 static unsigned char *bufpos = commit_buffer;
666 sent = qe->remaining;
667 sent = MIN((size_t)sent, sizeof(commit_buffer));
668 buf = buflib_get_data(&clip_ctx, qe->handle);
669 /* adjust buffer position to what has been played already */
670 buf += (qe->length - qe->remaining);
671 memcpy(bufpos, buf, sent);
672 *size = sent;
673
674 return commit_buffer;
675}
676
677static inline bool is_silence(struct queue_entry *qe)
678{
679 if (silence.handle > 0) /* silence clip available? */
680 return (qe->handle == silence.handle);
681 else
682 return false;
683}
684
685/* called in ISR context (on HWCODEC) if mp3 data got consumed */
686static void mp3_callback(const void** start, size_t* size)
687{
688 struct queue_entry *qe = &queue[queue_read];
689 /* voice_thread.c hints us how many of the buffer we provided it actually
690 * consumed. Because buffers have to be frame-aligned for speex
691 * it might be less than what we presented */
692 if (*size)
693 sent = *size;
694 qe->remaining -= sent; /* we completed this */
695
696 if (qe->remaining > 0) /* current clip not finished? */
697 { /* feed the next 64K-1 chunk */
698 *start = commit_transfer(qe, size);
699 return;
700 }
701
702 talk_queue_lock();
703
704 /* increment read position for the just played clip */
705 queue_read = (queue_read + 1) & QUEUE_MASK;
706
707 if (QUEUE_LEVEL == 0)
708 {
709 if (!is_silence(last_clip))
710 { /* add silence clip when queue runs empty playing a voice clip,
711 * only if the previous clip wasn't already silence */
712 queue[queue_write] = silence;
713 queue_write = (queue_write + 1) & QUEUE_MASK;
714 }
715 else
716 {
717 *size = 0; /* end of data */
718 }
719 }
720
721 if (QUEUE_LEVEL != 0) /* queue is not empty? */
722 { /* start next clip */
723 last_clip = &queue[queue_read];
724 *start = commit_transfer(last_clip, size);
725 curr_hd[0] = commit_buffer[1];
726 curr_hd[1] = commit_buffer[2];
727 curr_hd[2] = commit_buffer[3];
728 }
729
730 talk_queue_unlock();
731}
732
733/***************** Private routines *****************/
734
735/* return if a voice codec is required or not */
736static bool talk_voice_required(void)
737{
738 return (has_voicefile) /* Voice file is available */
739 || (global_settings.talk_dir_clip) /* Thumbnail clips are required */
740 || (global_settings.talk_file_clip);
741}
742
743static bool talk_is_disabled(void)
744{
745 if (talk_temp_disable_count > 0 || (!check_audio_status()))
746 return true;
747 return false;
748}
749
750static void do_enqueue(bool enqueue)
751{
752 if (!enqueue)
753 talk_shutup(); /* cut off all the pending stuff */
754}
755
756/* spell a string */
757static int _talk_spell(const char* spell, size_t len, bool enqueue)
758{
759 ucschar_t c; /* currently processed char */
760 int button = BUTTON_NONE;
761
762 if (talk_is_disabled())
763 return -1;
764
765 do_enqueue(enqueue); /* cut off all the pending stuff */
766
767 const char *last = spell;
768 size_t len0 = len - 1;
769 /* Tokenize into UTF8 codepoints */
770 while ((spell = utf8decode(spell, &c)), c != '\0')
771 {
772 len0 -= (spell - last);
773 if (len0 >= len) /* ie we underflow and wrap */
774 break;
775 last = spell;
776
777 /* NOTE: This is COMPLETLY BROKEN for NON-ENGLISH */
778
779 /* if this grows into too many cases, I should use a table */
780 if (c >= 'A' && c <= 'Z')
781 talk_id(VOICE_CHAR_A + c - 'A', true);
782 else if (c >= 'a' && c <= 'z')
783 talk_id(VOICE_CHAR_A + c - 'a', true);
784 else if (c >= '0' && c <= '9')
785 talk_id(VOICE_ZERO + c - '0', true);
786 else if (c == '-')
787 talk_id(VOICE_MINUS, true);
788 else if (c == '+')
789 talk_id(VOICE_PLUS, true);
790 else if (c == '.')
791 talk_id(VOICE_DOT, true);
792 else if (c == ' ')
793 talk_id(VOICE_PAUSE, true);
794 else if (c == PATH_SEPCH)
795 talk_id(VOICE_CHAR_SLASH, true);
796
797 if (QUEUE_LEVEL == QUEUE_SIZE - 1)
798 button = button_get(false); /* prevent UI unresponsiveness */
799
800 if (button == BUTTON_NONE || IS_SYSEVENT(button))
801 while (QUEUE_LEVEL == QUEUE_SIZE - 1) /* queue full - busy loop */
802 yield();
803 else
804 return 0; /* truncate spelled-out string */
805 }
806 return 0;
807}
808
809static int talk_spell_basename(const char *path,
810 const long *prefix_ids, bool enqueue)
811{
812 if(prefix_ids)
813 {
814 talk_idarray(prefix_ids, enqueue);
815 enqueue = true;
816 }
817 const char *basename;
818 size_t len = path_basename(path, &basename);
819
820 return _talk_spell(basename, len, enqueue);
821}
822
823/* Say year like "nineteen ninety nine" instead of "one thousand 9
824 hundred ninety nine". */
825static int talk_year(long year, bool enqueue)
826{
827 int rem;
828 if(year < 1100 || (year >=2000 && year < 2100))
829 /* just say it as a regular number */
830 return talk_number(year, enqueue);
831 /* Say century */
832 talk_number(year/100, enqueue);
833 rem = year%100;
834 if(rem == 0)
835 /* as in 1900 */
836 return talk_id(VOICE_HUNDRED, true);
837 if(rem <10)
838 /* as in 1905 */
839 talk_id(VOICE_ZERO, true);
840 /* sub-century year */
841 return talk_number(rem, true);
842}
843
844/***************** Public routines *****************/
845
846/* stop the playback and the pending clips */
847void talk_force_shutup(void)
848{
849 /* Had nothing to do (was frame boundary or not our clip) */
850 voice_play_stop();
851 talk_queue_lock();
852 queue_write = queue_read = 0; /* reset the queue */
853 thumbnail_buf_used = 0;
854 talk_queue_unlock();
855 need_shutup = false;
856}
857
858/* Shutup the voice, except if force_enqueue_next is set. */
859void talk_shutup(void)
860{
861 if (need_shutup && !force_enqueue_next)
862 talk_force_shutup();
863}
864
865/* schedule a clip, at the end or discard the existing queue */
866static void queue_clip(struct queue_entry *clip, bool enqueue)
867{
868 struct queue_entry *qe;
869 int queue_level;
870
871 do_enqueue(enqueue); /* cut off all the pending stuff */
872
873 /* Something is being enqueued, force_enqueue_next override is no
874 longer in effect. */
875 force_enqueue_next = false;
876
877 if (!clip->length)
878 return; /* safety check */
879 talk_queue_lock();
880 queue_level = QUEUE_LEVEL; /* check old level */
881 qe = &queue[queue_write];
882
883 if (queue_level < QUEUE_SIZE - 1) /* space left? */
884 {
885 queue[queue_write] = *clip;
886 queue_write = (queue_write + 1) & QUEUE_MASK;
887 }
888 talk_queue_unlock();
889
890 if (queue_level == 0)
891 { /* queue was empty, we have to do the initial start */
892 size_t size;
893 void *buf = commit_transfer(qe, &size);
894 last_clip = qe;
895 voice_play_data(buf, size, mp3_callback);
896 curr_hd[0] = commit_buffer[1];
897 curr_hd[1] = commit_buffer[2];
898 curr_hd[2] = commit_buffer[3];
899 }
900
901 need_shutup = true;
902
903 return;
904}
905
906/***************** Public implementation *****************/
907
908void talk_init(void)
909{
910 int filehandle;
911 talk_temp_disable_count = 0;
912 if (talk_initialized && !strcasecmp(last_lang, global_settings.lang_file))
913 {
914 /* not a new file, nothing to do */
915 return;
916 }
917
918 if(!talk_initialized)
919 {
920 mutex_init(&queue_mutex);
921 mutex_init(&read_buffer_mutex);
922 }
923
924 talk_force_shutup(); /* In case we have something speaking! */
925
926 talk_initialized = true;
927 strmemccpy((char *)last_lang, (char *)global_settings.lang_file,
928 MAX_FILENAME);
929
930 /* reset some states */
931 queue_write = queue_read = 0; /* reset the queue */
932 memset(&voicefile, 0, sizeof(voicefile));
933
934 silence.handle = -1; /* pause clip not accessible */
935 voicefile_size = has_voicefile = 0;
936 /* need to free these as their size depends on the voice file, and
937 * this function is called when the talk voice file changes */
938 index_handle = core_free(index_handle);
939 talk_handle = core_free(talk_handle);
940 /* don't free thumb handle, it doesn't depend on the actual voice file
941 * and so we can re-use it if it's already allocated in any event */
942
943 filehandle = open_voicefile();
944 if (filehandle >= 0)
945 {
946 if (!load_voicefile_index(filehandle))
947 {
948 if (global_settings.talk_menu)
949 splashf(HZ, ID2P(LANG_READ_FAILED), ".voice");
950 goto out;
951 }
952 /* Now determine the maximum buffer size needed for the voicefile.
953 * The below pretends the entire voicefile would be loaded. The buffer
954 * size is eventually capped later on in load_voicefile_data() */
955 int num_clips = voicefile.id1_max + voicefile.id2_max;
956 int non_empty = num_clips;
957 int total_size = 0, avg_size;
958 struct clip_entry *clips = core_get_data(index_handle);
959 /* check for the average clip size to estimate the maximum number of
960 * clips the buffer can hold */
961 for (int i = 0; i<num_clips; i++)
962 {
963 if (clips[i].size)
964 total_size += ALIGN_UP(clips[i].size, sizeof(void *));
965 else
966 non_empty -= 1;
967 }
968 avg_size = total_size / non_empty;
969 max_clips = MIN((int)(MAX_CLIP_BUFFER_SIZE/avg_size) + 1, non_empty);
970 /* account for possible thumb clips */
971 total_size += THUMBNAIL_RESERVE;
972 max_clips += 16;
973 voicefile_size = total_size;
974 has_voicefile = true;
975 }
976 else if (talk_voice_required())
977 {
978 /* create buffer just for thumb clips */
979 max_clips = 16;
980 voicefile_size = THUMBNAIL_RESERVE;
981 }
982 /* additionally to the clip we need a table to record the age of the clips
983 * so that, when memory is tight, only the most recently used ones are kept */
984 voicefile_size += sizeof(struct clip_cache_metadata) * max_clips;
985 /* compensate a bit for buflib alloc overhead. */
986 voicefile_size += BUFLIB_ALLOC_OVERHEAD * max_clips + 64;
987
988 load_voicefile_data(filehandle);
989
990 /* Initialize the actual voice clip playback engine as well */
991 if (talk_voice_required())
992 voice_thread_init();
993
994out:
995 if (filehandle >= 0)
996 close(filehandle); /* close again, this was just to detect presence */
997}
998
999/* somebody else claims the mp3 buffer, e.g. for regular play/record */
1000void talk_buffer_set_policy(int policy)
1001{
1002#ifdef DEBUG
1003 switch(policy)
1004 {
1005 case TALK_BUFFER_DEFAULT:
1006 case TALK_BUFFER_HOLD: give_buffer_away = false; break;
1007 case TALK_BUFFER_LOOSE: give_buffer_away = true; break;
1008 default: DEBUGF("Ignoring unknown policy\n"); break;
1009 }
1010#else
1011 give_buffer_away = (policy == TALK_BUFFER_LOOSE);
1012#endif
1013}
1014
1015/* play a voice ID from voicefile */
1016int talk_id(int32_t id, bool enqueue)
1017{
1018 int32_t unit;
1019 int decimals;
1020 struct queue_entry clip;
1021 bool isloaded = true;
1022
1023 if (!has_voicefile)
1024 return 0; /* no voicefile loaded, not an error -> pretend success */
1025 if (talk_is_disabled())
1026 return -1;
1027
1028 if (talk_handle <= 0 || index_handle <= 0) /* reload needed? */
1029 {
1030 int fd = open_voicefile();
1031 if (fd < 0 || !load_voicefile_index(fd))
1032 return load_voicefile_failure(fd);
1033 isloaded = load_voicefile_data(fd);
1034 close(fd);
1035 }
1036
1037 if (id == -1 || !isloaded) /* -1 is an indication for silence */
1038 return -1;
1039
1040 decimals = (((uint32_t)id) >> DECIMAL_SHIFT) & 0x7;
1041
1042 /* check if this is a special ID, with a value */
1043 unit = ((uint32_t)id) >> UNIT_SHIFT;
1044 if (unit || decimals)
1045 { /* sign-extend the value */
1046 id = (uint32_t)id << (32-DECIMAL_SHIFT);
1047 id >>= (32-DECIMAL_SHIFT);
1048
1049 talk_value_decimal(id, unit, decimals, enqueue); /* speak it */
1050 return 0; /* and stop, end of special case */
1051 }
1052
1053 if (get_clip(id, &clip) < 0)
1054 return -1; /* not present */
1055
1056#ifdef LOGF_ENABLE
1057 if (id > VOICEONLY_DELIMITER)
1058 logf("\ntalk_id: Say voice clip 0x%x\n", id);
1059 else
1060 logf("\ntalk_id: Say '%s'\n", str(id));
1061#endif
1062
1063 queue_clip(&clip, enqueue);
1064
1065 return 0;
1066}
1067
1068/* Speaks zero or more IDs (from an array). */
1069int talk_idarray(const long *ids, bool enqueue)
1070{
1071 int r;
1072 if(!ids)
1073 return 0;
1074 while(*ids != TALK_FINAL_ID)
1075 {
1076 if((r = talk_id(*ids++, enqueue)) <0)
1077 return r;
1078 enqueue = true;
1079 }
1080 return 0;
1081}
1082
1083/* Make sure the current utterance is not interrupted by the next one. */
1084void talk_force_enqueue_next(void)
1085{
1086 force_enqueue_next = true;
1087}
1088
1089/* play a thumbnail from file */
1090/* Returns size of spoken thumbnail, so >0 means something is spoken,
1091 <=0 means something went wrong. */
1092static int _talk_file(const char* filename,
1093 const long *prefix_ids, bool enqueue)
1094{
1095 int fd;
1096 int size;
1097 int handle = -1;
1098 int oldest = -1;
1099
1100 /* reload needed? */
1101 if (talk_is_disabled())
1102 return -1;
1103
1104 if (talk_handle <= 0 || index_handle <= 0)
1105 {
1106 fd = open_voicefile();
1107 if (fd < 0 || !load_voicefile_index(fd))
1108 return load_voicefile_failure(fd);
1109 load_voicefile_data(fd);
1110 close(fd);
1111 }
1112
1113 do_enqueue(enqueue); /* shutup now to free the thumbnail buffer */
1114
1115 fd = open(filename, O_RDONLY);
1116 if (fd < 0) /* failed to open */
1117 {
1118 return 0;
1119 }
1120 size = filesize(fd);
1121
1122 if (size > 0)
1123 {
1124 /* free clips from cache until this one succeeds to allocate */
1125 while ((handle = buflib_alloc(&clip_ctx, size)) < 0)
1126 oldest = free_oldest_clip();
1127
1128 size = read_to_handle_ex(fd, &clip_ctx, handle, 0, size);
1129 }
1130
1131 close(fd);
1132
1133 /* ToDo: find audio, skip ID headers and trailers */
1134
1135 if (size > 0) /* Don't play missing clips */
1136 {
1137 struct queue_entry clip;
1138 clip.handle = handle;
1139 clip.length = clip.remaining = size;
1140 if(prefix_ids)
1141 /* prefix thumbnail by speaking these ids, but only now
1142 that we know there's actually a thumbnail to be
1143 spoken. */
1144 talk_idarray(prefix_ids, true);
1145 /* finally insert into metadata table. thumb clips go under the
1146 * VOICEONLY_DELIMITER id so the cache can distinguish them from
1147 * normal clips */
1148 add_cache_entry(handle, oldest, VOICEONLY_DELIMITER);
1149 queue_clip(&clip, true);
1150 }
1151 else
1152 buflib_free(&clip_ctx, handle);
1153
1154 return size;
1155}
1156
1157int talk_file(const char *root, const char *dir, const char *file,
1158 const char *ext, const long *prefix_ids, bool enqueue)
1159/* Play a thumbnail file */
1160{
1161 char buf[MAX_PATH];
1162 const char *fmt = "%s%s%s%s%s";
1163 /* Does root end with a slash? */
1164 if(root && root[0] && root[strlen(root)-1] != PATH_SEPCH)
1165 fmt = "%s" PATH_SEPSTR "%s%s%s%s";
1166 snprintf(buf, MAX_PATH, fmt,
1167 root ? root : "",
1168 dir ? dir : "", dir ? PATH_SEPSTR : "",
1169 file ? file : "",
1170 ext ? ext : "");
1171 return _talk_file(buf, prefix_ids, enqueue);
1172}
1173
1174/* Play a file's .talk thumbnail, fallback to spelling the filename, or
1175 go straight to spelling depending on settings. */
1176int talk_file_or_spell(const char *dirname, const char *filename,
1177 const long *prefix_ids, bool enqueue)
1178{
1179 if (global_settings.talk_file_clip)
1180 { /* .talk clips enabled */
1181 if(talk_file(dirname, NULL, filename, file_thumbnail_ext,
1182 prefix_ids, enqueue) >0)
1183 return 0;
1184 }
1185 if (global_settings.talk_file == TALK_SPEAK_SPELL)
1186 /* Either .talk clips are disabled, or as a fallback */
1187 return talk_spell_basename(filename, prefix_ids, enqueue);
1188 return 0;
1189}
1190
1191#ifdef HAVE_MULTIVOLUME
1192int talk_volume_id(int volume)
1193{
1194 if (volume == -1)
1195 return 0;
1196
1197 int drive = volume_drive(volume);
1198 // XXX voice "VOLUME" or something like that?
1199
1200 talk_id(drive? LANG_DISK_NAME_MMC : LANG_DISK_NAME_INTERNAL, true);
1201 talk_value(volume, UNIT_INT, true);
1202 return 1;
1203}
1204#endif
1205
1206/* Play a directory's .talk thumbnail, fallback to spelling the filename, or
1207 go straight to spelling depending on settings. */
1208int talk_dir_or_spell(const char* dirname,
1209 const long *prefix_ids, bool enqueue)
1210{
1211 if (global_settings.talk_dir_clip)
1212 { /* .talk clips enabled */
1213 if (talk_file(dirname, NULL, dir_thumbnail_name, NULL,
1214 prefix_ids, enqueue) >0)
1215 return 0;
1216 }
1217 if (global_settings.talk_dir == TALK_SPEAK_SPELL) {
1218 /* Either .talk clips disabled or as a fallback */
1219 return talk_spell_basename(dirname, prefix_ids, enqueue);
1220 }
1221 return 0;
1222}
1223
1224/* Speak thumbnail for each component of a full path, again falling
1225 back or going straight to spelling depending on settings. */
1226int talk_fullpath(const char* path, bool enqueue)
1227{
1228 do_enqueue(enqueue); /* cut off all the pending stuff */
1229
1230 if(path[0] != PATH_SEPCH)
1231 /* path ought to start with /... */
1232 return talk_spell(path, true);
1233 talk_id(VOICE_CHAR_SLASH, true);
1234 char buf[MAX_PATH];
1235 strmemccpy(buf, path, MAX_PATH);
1236 char *start = buf+1; /* start of current component */
1237 char *ptr = strchr(start, PATH_SEPCH); /* end of current component */
1238 while(ptr) { /* There are more slashes ahead */
1239 /* temporarily poke a NULL at end of component to truncate string */
1240 *ptr = '\0';
1241#ifdef HAVE_MULTIVOLUME
1242 if (start == buf+1) {
1243 int vol = path_get_volume_id(buf+1);
1244 if (!talk_volume_id(vol))
1245 talk_dir_or_spell(buf, NULL, true);
1246 } else
1247#endif
1248 talk_dir_or_spell(buf, NULL, true);
1249 *ptr = PATH_SEPCH; /* restore string */
1250 talk_id(VOICE_CHAR_SLASH, true);
1251 start = ptr+1; /* setup for next component */
1252 ptr = strchr(start, PATH_SEPCH);
1253 }
1254
1255 /* no more slashes, figure out final component */
1256 if (!*start) {
1257 return 1;
1258 }
1259
1260 DIR* dir = opendir(buf);
1261 if (dir) {
1262 closedir(dir);
1263 return talk_dir_or_spell(buf, NULL, true);
1264 } else {
1265 return talk_file_or_spell(NULL, buf, NULL, true);
1266 }
1267}
1268
1269/* say a numeric value, this word ordering works for english,
1270 but not necessarily for other languages (e.g. german) */
1271int talk_number(long n, bool enqueue)
1272{
1273 int level = 2; /* mille count */
1274 long mil = 1000000000; /* highest possible "-illion" */
1275
1276 if (talk_is_disabled())
1277 return -1;
1278
1279 do_enqueue(enqueue); /* cut off all the pending stuff */
1280
1281 if (n==0)
1282 { /* special case */
1283 talk_id(VOICE_ZERO, true);
1284 return 0;
1285 }
1286
1287 if (n<0)
1288 {
1289 talk_id(VOICE_MINUS, true);
1290 n = -n;
1291 }
1292
1293 while (n)
1294 {
1295 if (mil < 1)
1296 {
1297 talk_id(VOICE_CHAR_E, true);
1298 return 0;
1299 }
1300 int segment = n / mil; /* extract in groups of 3 digits */
1301 n -= segment * mil; /* remove the used digits from number */
1302 mil /= 1000; /* digit place for next round */
1303
1304 if (segment)
1305 {
1306 int hundreds = segment / 100;
1307 int ones = segment % 100;
1308
1309 if (hundreds)
1310 {
1311 talk_id(VOICE_ZERO + hundreds, true);
1312 talk_id(VOICE_HUNDRED, true);
1313 }
1314
1315 struct queue_entry tens_swap;
1316 if (get_clip(VOICE_NUMERIC_TENS_SWAP_SEPARATOR, &tens_swap) >= 0)
1317 {
1318 /* direct indexing */
1319 if (ones <= 20)
1320 {
1321 talk_id(VOICE_ZERO + ones, true);
1322 }
1323 else if (ones)
1324 {
1325 int tmp = ones % 10;
1326 if (tmp)
1327 {
1328 talk_id(VOICE_ZERO + tmp, true);
1329 talk_id(VOICE_NUMERIC_TENS_SWAP_SEPARATOR, true);
1330 }
1331 }
1332 /* combination indexing */
1333 if (ones > 20)
1334 {
1335 int tens = ones/10 + 18;
1336 talk_id(VOICE_ZERO + tens, true);
1337 }
1338 }
1339 else
1340 {
1341 /* combination indexing */
1342 if (ones > 20)
1343 {
1344 int tens = ones/10 + 18;
1345 talk_id(VOICE_ZERO + tens, true);
1346 ones %= 10;
1347 }
1348
1349 /* direct indexing */
1350 if (ones)
1351 talk_id(VOICE_ZERO + ones, true);
1352 }
1353
1354 /* add billion, million, thousand */
1355 if (mil)
1356 talk_id(VOICE_THOUSAND + level, true);
1357 }
1358 level--;
1359 }
1360
1361 return 0;
1362}
1363
1364/* Say time duration/interval. Input is time in seconds,
1365 say hours,minutes,seconds. */
1366static int talk_time_unit(long secs, bool enqueue)
1367{
1368 return talk_time_intervals(secs, UNIT_SEC, enqueue);
1369}
1370
1371void talk_fractional(char *tbuf, int value, int unit)
1372{
1373 int i;
1374 /* strip trailing zeros from the fraction */
1375 for (i = strlen(tbuf) - 1; (i >= 0) && (tbuf[i] == '0'); i--)
1376 tbuf[i] = '\0';
1377
1378 talk_number(value, true);
1379 if (tbuf[0] != 0)
1380 {
1381 talk_id(LANG_POINT, true);
1382 talk_spell(tbuf, true);
1383 }
1384 talk_id(unit, true);
1385}
1386
1387int talk_value(long n, int unit, bool enqueue)
1388{
1389 return talk_value_decimal(n, unit, 0, enqueue);
1390}
1391
1392/* singular/plural aware saying of a value */
1393int talk_value_decimal(long n, int unit, int decimals, bool enqueue)
1394{
1395 int unit_id;
1396 static const int unit_voiced[] =
1397 { /* lookup table for the voice ID of the units */
1398 [0 ... UNIT_LAST-1] = -1, /* regular ID, int, signed */
1399 [UNIT_MS]
1400 = VOICE_MILLISECONDS, /* here come the "real" units */
1401 [UNIT_SEC]
1402 = VOICE_SECONDS,
1403 [UNIT_MIN]
1404 = VOICE_MINUTES,
1405 [UNIT_HOUR]
1406 = VOICE_HOURS,
1407 [UNIT_KHZ]
1408 = VOICE_KHZ,
1409 [UNIT_DB]
1410 = VOICE_DB,
1411 [UNIT_PERCENT]
1412 = VOICE_PERCENT,
1413 [UNIT_MAH]
1414 = VOICE_MILLIAMPHOURS,
1415 [UNIT_PIXEL]
1416 = VOICE_PIXEL,
1417 [UNIT_PER_SEC]
1418 = VOICE_PER_SEC,
1419 [UNIT_HERTZ]
1420 = VOICE_HERTZ,
1421 [UNIT_MB]
1422 = LANG_MEBIBYTE,
1423 [UNIT_KBIT]
1424 = VOICE_KBIT_PER_SEC,
1425 [UNIT_PM_TICK]
1426 = VOICE_PM_UNITS_PER_TICK,
1427 };
1428
1429 static const int pow10[] = { /* 10^0 - 10^7 */
1430 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000
1431 };
1432
1433 char tbuf[8];
1434 char fmt[] = "%0nd";
1435
1436 if (talk_is_disabled())
1437 return -1;
1438
1439 /* special pronounciation for year number */
1440 if (unit == UNIT_DATEYEAR)
1441 return talk_year(n, enqueue);
1442 /* special case for time duration */
1443 if (unit == UNIT_TIME)
1444 return talk_time_unit(n, enqueue);
1445
1446 if (unit < 0 || unit >= UNIT_LAST)
1447 unit_id = -1;
1448 else
1449 unit_id = unit_voiced[unit];
1450
1451 if ((n==1 || n==-1) /* singular? */
1452 && unit_id >= VOICE_SECONDS && unit_id <= VOICE_HOURS)
1453 {
1454 unit_id--; /* use the singular for those units which have */
1455 }
1456
1457 /* special case with a "plus" before */
1458 if (n > 0 && (unit == UNIT_SIGNED || unit == UNIT_DB))
1459 {
1460 talk_id(VOICE_PLUS, enqueue);
1461 enqueue = true;
1462 }
1463
1464 if (decimals)
1465 {
1466 /* needed for the "-0.5" corner case */
1467 if (n < 0)
1468 {
1469 talk_id(VOICE_MINUS, enqueue);
1470 n = -n;
1471 }
1472
1473 fmt[2] = '0' + decimals;
1474
1475 snprintf(tbuf, sizeof(tbuf), fmt, n % pow10[decimals]);
1476 talk_fractional(tbuf, n / pow10[decimals], unit_id);
1477
1478 return 0;
1479 }
1480
1481 if (lang_units_first())
1482 talk_id(unit_id, true); /* say the unit, if any */
1483 talk_number(n, enqueue); /* say the number */
1484 if (!lang_units_first())
1485 talk_id(unit_id, true); /* say the unit, if any */
1486
1487 return 0;
1488}
1489
1490static inline void talk_time_value(long n, int unit, bool enqueue)
1491{
1492 if (n != 0)
1493 talk_value_decimal(n, unit, 0, enqueue);
1494}
1495
1496/* Say time duration/interval. Input is time unit specifies base unit,
1497 say hours,minutes,seconds, milliseconds. or any combination thereof */
1498int talk_time_intervals(long time, int unit_idx, bool enqueue)
1499{
1500 unsigned long units_in[UNIT_IDX_TIME_COUNT];
1501
1502 if (talk_is_disabled())
1503 return -1;
1504
1505 if (talk_handle <= 0 || index_handle <= 0) /* reload needed? */
1506 {
1507 int fd = open_voicefile();
1508 if (fd < 0 || !load_voicefile_index(fd))
1509 return load_voicefile_failure(fd);
1510 load_voicefile_data(fd);
1511 close(fd);
1512 }
1513
1514 do_enqueue(enqueue); /* cut off all the pending stuff */
1515
1516 time_split_units(unit_idx, labs(time), &units_in);
1517
1518 if (time < 0)
1519 talk_id(VOICE_MINUS, true);
1520
1521 if (time == 0)
1522 talk_value(0, unit_idx, true);
1523 else
1524 {
1525 talk_time_value(units_in[UNIT_IDX_HR], UNIT_HOUR, true);
1526 talk_time_value(units_in[UNIT_IDX_MIN], UNIT_MIN, true);
1527 talk_time_value(units_in[UNIT_IDX_SEC], UNIT_SEC, true);
1528 talk_time_value(units_in[UNIT_IDX_MS], UNIT_MS, true);
1529 }
1530
1531 return -1;
1532}
1533
1534/* spell a string */
1535int talk_spell(const char* spell, bool enqueue)
1536{
1537 return _talk_spell(spell, NULL_TERMINATED, enqueue);
1538}
1539
1540void talk_disable(bool disable)
1541{
1542 if (disable)
1543 talk_temp_disable_count++;
1544 else
1545 talk_temp_disable_count--;
1546}
1547
1548void talk_setting(const void *global_settings_variable)
1549{
1550 const struct settings_list *setting;
1551 if (!global_settings.talk_menu)
1552 return;
1553 setting = find_setting(global_settings_variable);
1554 if (setting == NULL)
1555 return;
1556 if (setting->lang_id)
1557 talk_id(setting->lang_id,false);
1558}
1559
1560void talk_date(const struct tm *tm, bool enqueue)
1561{
1562 const char *format = str(LANG_VOICED_DATE_FORMAT);
1563 const char *ptr;
1564
1565 do_enqueue(enqueue); /* cut off all the pending stuff */
1566
1567 for (ptr = format ; *ptr ; ptr++) {
1568 switch(*ptr) {
1569 case 'Y':
1570 talk_number(1900 + tm->tm_year, true);
1571 break;
1572 case 'A':
1573 talk_id(LANG_MONTH_JANUARY + tm->tm_mon, true);
1574 break;
1575 case 'm':
1576 talk_number(tm->tm_mon + 1, true);
1577 break;
1578 case 'd':
1579 talk_number(tm->tm_mday, true);
1580 break;
1581 default:
1582 break;
1583 }
1584 }
1585}
1586
1587void talk_time(const struct tm *tm, bool enqueue)
1588{
1589 if (global_settings.timeformat == 1)
1590 {
1591 /* Voice the hour */
1592 long am_pm_id = VOICE_AM;
1593 int hour = tm->tm_hour;
1594 if (hour >= 12)
1595 {
1596 am_pm_id = VOICE_PM;
1597 hour -= 12;
1598 }
1599 if (hour == 0)
1600 hour = 12;
1601 talk_number(hour, enqueue);
1602
1603 /* Voice the minutes */
1604 if (tm->tm_min == 0)
1605 {
1606 /* Say o'clock if the minute is 0. */
1607 talk_id(VOICE_OCLOCK, true);
1608 }
1609 else
1610 {
1611 /* Pronounce the leading 0 */
1612 if(tm->tm_min < 10)
1613 talk_id(VOICE_OH, true);
1614 talk_number(tm->tm_min, true);
1615 }
1616 talk_id(am_pm_id, true);
1617 }
1618 else
1619 {
1620 /* Voice the time in 24 hour format */
1621 if(tm->tm_hour < 10)
1622 talk_id(VOICE_OH, true);
1623 talk_number(tm->tm_hour, enqueue);
1624 if (tm->tm_min == 0)
1625 {
1626 talk_ids(true, VOICE_HUNDRED, VOICE_HOURS);
1627 }
1628 else
1629 {
1630 /* Pronounce the leading 0 */
1631 if(tm->tm_min < 10)
1632 talk_id(VOICE_OH, true);
1633 talk_number(tm->tm_min, true);
1634 }
1635 }
1636}
1637
1638void talk_announce_voice_invalid(void)
1639{
1640 int voice_fd;
1641 int voice_sz;
1642 int buf_handle;
1643 struct queue_entry qe;
1644
1645 char talkfile[MAX_PATH];
1646 const char* const p_lang_def = DEFAULT_VOICE_LANG; /* default */
1647 const char* p_lang = p_lang_def;
1648
1649 /* attempt to load Invalid voice clip in proper lang otherwise use default */
1650 if (global_settings.lang_file[0] && global_settings.lang_file[0] != 0xff)
1651 p_lang = global_settings.lang_file;
1652
1653 while(1)
1654 {
1655 snprintf(talkfile, sizeof(talkfile), LANG_DIR "/InvalidVoice_%s.talk", p_lang);
1656 if (file_exists(talkfile) || p_lang == p_lang_def)
1657 break; /* if it exists or default talk file doesn't exist give up */
1658 p_lang = p_lang_def;
1659 }
1660
1661 if (global_settings.talk_menu && talk_status != TALK_STATUS_OK)
1662 {
1663 voice_fd = open(talkfile, O_RDONLY);
1664 if (voice_fd < 0)
1665 goto out; /* can't open */
1666
1667 voice_sz= lseek(voice_fd, 0, SEEK_END);
1668 if (voice_sz == 0 || voice_sz > (64<<10))
1669 goto out; /* nothing here or too big */
1670
1671 lseek(voice_fd, 0, SEEK_SET);
1672 /* add a bit extra for buflib overhead (2K) */
1673 if (!create_clip_buffer(ALIGN_UP(voice_sz, sizeof(long)) + (2<<10)))
1674 goto out;
1675 mutex_lock(&read_buffer_mutex);
1676 buf_handle = buflib_alloc(&clip_ctx, ALIGN_UP(voice_sz, sizeof(long)));
1677
1678 if (buf_handle < 0)
1679 goto out;
1680
1681 if (read_to_handle_ex(voice_fd, &clip_ctx, buf_handle, 0, voice_sz) > 0)
1682 {
1683 voice_thread_init();
1684 qe.handle = buf_handle;
1685 qe.length = qe.remaining = voice_sz;
1686 queue_clip(&qe, false);
1687 voice_wait();
1688 }
1689
1690 mutex_unlock(&read_buffer_mutex);
1691
1692 buf_handle = buflib_free(&clip_ctx, buf_handle);
1693
1694 out:
1695 close(voice_fd);
1696 return;
1697 }
1698}
1699
1700bool talk_get_debug_data(struct talk_debug_data *data)
1701{
1702 char* p_lang = DEFAULT_VOICE_LANG; /* default */
1703 struct clip_cache_metadata *cc;
1704
1705 memset(data, 0, sizeof(*data));
1706
1707 data->status = talk_status;
1708
1709 if (global_settings.lang_file[0] && global_settings.lang_file[0] != 0xff)
1710 p_lang = (char *)global_settings.lang_file;
1711
1712 strmemccpy(data->voicefile, p_lang, sizeof(data->voicefile));
1713
1714 if (!has_voicefile || index_handle <= 0)
1715 {
1716 if (data->status == TALK_STATUS_OK)
1717 data->status = TALK_STATUS_ERR_NOFILE;
1718
1719 return false;
1720 }
1721
1722 struct clip_entry *clips = core_get_data(index_handle);
1723 int cached = 0;
1724 int real_clips = 0;
1725
1726 data->num_clips = voicefile.id1_max + voicefile.id2_max;
1727 data->avg_clipsize = data->max_clipsize = 0;
1728 data->min_clipsize = INT_MAX;
1729 for(int i = 0; i < data->num_clips; i++)
1730 {
1731 int size = clips[i].size & (~LOADED_MASK);
1732 if (!size) continue;
1733 real_clips += 1;
1734 if (size < data->min_clipsize)
1735 data->min_clipsize = size;
1736 if (size > data->max_clipsize)
1737 data->max_clipsize = size;
1738 data->avg_clipsize += size;
1739 }
1740 if (!(real_clips > 0))
1741 {
1742 if (data->status == TALK_STATUS_OK)
1743 data->status = TALK_STATUS_ERR_NOFILE;
1744
1745 return false;
1746 }
1747
1748 cc = buflib_get_data(&clip_ctx, metadata_table_handle);
1749 for (int i = 0; i < (int) max_clips; i++)
1750 {
1751 if (cc[i].handle > 0)
1752 cached += 1;
1753 }
1754 data->avg_clipsize /= real_clips;
1755 data->num_empty_clips = data->num_clips - real_clips;
1756 data->memory_allocated = sizeof(commit_buffer) + sizeof(voicefile)
1757 + data->num_clips * sizeof(struct clip_entry)
1758 + voicebuf_size;
1759 data->memory_used = 0;
1760 if (talk_handle > 0)
1761 data->memory_used = data->memory_allocated - buflib_available(&clip_ctx);
1762 data->cached_clips = cached;
1763 data->cache_hits = cache_hits;
1764 data->cache_misses = cache_misses;
1765
1766 return true;
1767}