blob: fb16e80417cb0d6b7bdc83a9a57b3c45f6731632 [file] [log] [blame]
Jörg Hohensohnfa97f162004-03-19 22:15:53 +00001/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
Nicolas Pennequin357ffb32008-05-05 10:32:46 +000010 * Copyright (C) 2004 Jörg Hohensohn
Jörg Hohensohnfa97f162004-03-19 22:15:53 +000011 *
12 * This module collects the Talkbox and voice UI functions.
13 * (Talkbox reads directory names from mp3 clips called thumbnails,
Jörg Hohensohn2fef5b72004-05-09 09:41:23 +000014 * the voice UI lets menus and screens "talk" from a voicefile in memory.
Jörg Hohensohnfa97f162004-03-19 22:15:53 +000015 *
Daniel Stenberg2acc0ac2008-06-28 18:10:04 +000016 * This program is free software; you can redistribute it and/or
17 * modify it under the terms of the GNU General Public License
18 * as published by the Free Software Foundation; either version 2
19 * of the License, or (at your option) any later version.
Jörg Hohensohnfa97f162004-03-19 22:15:53 +000020 *
21 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
22 * KIND, either express or implied.
23 *
24 ****************************************************************************/
25
26#include <stdio.h>
27#include <stddef.h>
Thomas Martitz50a6ca32010-05-06 21:04:40 +000028#include "string-extra.h"
Jörg Hohensohnfa97f162004-03-19 22:15:53 +000029#include "file.h"
Jörg Hohensohnfa97f162004-03-19 22:15:53 +000030#include "system.h"
Linus Nielsen Feltzing198245a2007-03-09 09:21:11 +000031#include "kernel.h"
Jörg Hohensohn6e77d1f2004-04-06 07:06:59 +000032#include "settings.h"
Steve Bavincd88e2a2008-03-25 15:24:03 +000033#include "settings_list.h"
Michael Sevakisda6cebb2012-05-02 17:22:28 -040034#if CONFIG_CODEC == SWCODEC
35#include "voice_thread.h"
36#else
Jörg Hohensohnfa97f162004-03-19 22:15:53 +000037#include "mp3_playback.h"
Michael Sevakisda6cebb2012-05-02 17:22:28 -040038#endif
Linus Nielsen Feltzing8a237a82005-04-04 12:06:29 +000039#include "audio.h"
Jörg Hohensohnfa97f162004-03-19 22:15:53 +000040#include "lang.h"
41#include "talk.h"
Björn Stenberg51b45d52008-10-15 06:38:51 +000042#include "metadata.h"
Jeffrey Goode7c52a172010-05-26 03:14:30 +000043/*#define LOGF_ENABLE*/
Miika Pekkarinen159c52d2005-08-20 11:13:19 +000044#include "logf.h"
Jens Arnold43412112004-09-26 09:25:59 +000045#include "bitswap.h"
Miika Pekkarinen7a395a22007-02-14 19:20:13 +000046#include "structec.h"
Thomas Martitz8a701e52011-08-14 15:37:05 +000047#include "plugin.h" /* plugin_get_buffer() */
Linus Nielsen Feltzing97b56a62006-08-23 08:21:15 +000048#include "debug.h"
Thomas Martitz03f373c2014-02-02 14:48:32 +010049#include "panic.h"
William Wilgusa06d9c82018-12-17 22:27:55 -060050#include "misc.h" /* time_split_units() */
Jörg Hohensohnfa97f162004-03-19 22:15:53 +000051
Linus Nielsen Feltzing30c618c2006-08-01 22:02:47 +000052/* Memory layout varies between targets because the
53 Archos (MASCODEC) devices cannot mix voice and audio playback
54
55 MASCODEC | MASCODEC | SWCODEC
56 (playing) | (stopped) |
Thomas Martitzd1322b72011-08-14 15:13:00 +000057 voicebuf-----------+-----------+------------
Thomas Martitz29abe812011-08-21 15:05:57 +000058 audio | voice | voice
Michael Sevakis0f5cb942006-11-06 18:07:30 +000059 |-----------|------------
Thomas Martitz29abe812011-08-21 15:05:57 +000060 | thumbnail | thumbnail
Michael Sevakis0f5cb942006-11-06 18:07:30 +000061 | |------------
62 | | filebuf
63 | |------------
Linus Nielsen Feltzing30c618c2006-08-01 22:02:47 +000064 | | audio
Thomas Martitzd1322b72011-08-14 15:13:00 +000065 voicebufend----------+-----------+------------
Linus Nielsen Feltzing30c618c2006-08-01 22:02:47 +000066
Thomas Martitz29abe812011-08-21 15:05:57 +000067 SWCODEC allocates dedicated buffers (except voice and thumbnail are together
68 in the talkbuf), MASCODEC reuses audiobuf. */
Linus Nielsen Feltzing30c618c2006-08-01 22:02:47 +000069
70
Jörg Hohensohnfa97f162004-03-19 22:15:53 +000071/***************** Constants *****************/
72
Jörg Hohensohn2fef5b72004-05-09 09:41:23 +000073#define QUEUE_SIZE 64 /* must be a power of two */
74#define QUEUE_MASK (QUEUE_SIZE-1)
Jens Arnold839067b2004-08-01 23:34:44 +000075const char* const dir_thumbnail_name = "_dirname.talk";
Jörg Hohensohn40ae63b2004-10-21 18:34:48 +000076const char* const file_thumbnail_ext = ".talk";
Jörg Hohensohnfa97f162004-03-19 22:15:53 +000077
Jörg Hohensohn2fef5b72004-05-09 09:41:23 +000078/***************** Functional Macros *****************/
79
80#define QUEUE_LEVEL ((queue_write - queue_read) & QUEUE_MASK)
81
Jörg Hohensohna7aa17a2004-10-12 16:43:22 +000082#define LOADED_MASK 0x80000000 /* MSB */
83
Thomas Martitzc46f9be2013-11-21 11:44:04 +010084#define DEFAULT_VOICE_LANG "english"
85
Jörg Hohensohnfa97f162004-03-19 22:15:53 +000086/***************** Data types *****************/
87
88struct clip_entry /* one entry of the index table */
89{
Jörg Hohensohn2fef5b72004-05-09 09:41:23 +000090 int offset; /* offset from start of voicefile file */
Jörg Hohensohnfa97f162004-03-19 22:15:53 +000091 int size; /* size of the clip */
92};
93
Thomas Martitz22e802e2013-05-30 11:24:16 +020094struct voicefile_header /* file format of our voice file */
Jörg Hohensohnfa97f162004-03-19 22:15:53 +000095{
Jörg Hohensohn2fef5b72004-05-09 09:41:23 +000096 int version; /* version of the voicefile */
Nils Wallméniusb3113672007-08-05 19:19:39 +000097 int target_id; /* the rockbox target the file was made for */
Jörg Hohensohnf9495cb2004-04-03 20:52:24 +000098 int table; /* offset to index table, (=header size) */
99 int id1_max; /* number of "normal" clips contained in above index */
100 int id2_max; /* number of "voice only" clips contained in above index */
Thomas Martitz22e802e2013-05-30 11:24:16 +0200101 /* The header is folled by the index tables (n*struct clip_entry),
102 * which is followed by the mp3/speex encoded clip data */
Jörg Hohensohnfa97f162004-03-19 22:15:53 +0000103};
104
Jörg Hohensohnfa97f162004-03-19 22:15:53 +0000105/***************** Globals *****************/
106
Jens Arnoldae601952011-11-06 14:25:33 +0000107#if (CONFIG_CODEC == SWCODEC && MEMORYSIZE <= 2) || defined(ONDIO_SERIES)
Jens Arnold544fb6a2011-11-06 13:58:17 +0000108/* On low memory swcodec targets the entire voice file wouldn't fit in memory
109 * together with codecs, so we load clips each time they are accessed.
110 * The Ondios have slow storage access and loading the entire voice file would
111 * take several seconds, so we use the same mechanism. */
Thomas Martitz03f373c2014-02-02 14:48:32 +0100112#define TALK_PROGRESSIVE_LOAD
113#if !defined(ONDIO_SERIES)
114/* 70+ clips should fit into 100k */
115#define MAX_CLIP_BUFFER_SIZE (100000)
116#endif
117#endif
118
119#ifndef MAX_CLIP_BUFFER_SIZE
Thomas Martitzaf02a672014-02-02 16:58:28 +0100120/* 1GB should be enough for everybody; will be capped to voicefile size */
121#define MAX_CLIP_BUFFER_SIZE (1<<30)
Rafaël Carréca91d0f2010-09-01 00:08:50 +0000122#endif
Thomas Martitzaf02a672014-02-02 16:58:28 +0100123#define THUMBNAIL_RESERVE (50000)
Rafaël Carréca91d0f2010-09-01 00:08:50 +0000124
Stéphane Doyonc893aff2008-07-15 14:06:11 +0000125/* Multiple thumbnails can be loaded back-to-back in this buffer. */
126static volatile int thumbnail_buf_used SHAREDBSS_ATTR; /* length of data in
127 thumbnail buffer */
Thomas Martitz22e802e2013-05-30 11:24:16 +0200128static struct voicefile_header voicefile; /* loaded voicefile */
Jörg Hohensohn2fef5b72004-05-09 09:41:23 +0000129static bool has_voicefile; /* a voicefile file is present */
Stéphane Doyonc893aff2008-07-15 14:06:11 +0000130static bool need_shutup; /* is there possibly any voice playing to be shutup */
Steve Bavin32a95752007-10-19 15:31:42 +0000131static bool force_enqueue_next; /* enqueue next utterance even if enqueue is false */
Jörg Hohensohnfa97f162004-03-19 22:15:53 +0000132static int queue_write; /* write index of queue, by application */
133static int queue_read; /* read index of queue, by ISR context */
Stéphane Doyonc893aff2008-07-15 14:06:11 +0000134#if CONFIG_CODEC == SWCODEC
Bertrik Sikken2ac91dc2010-02-24 19:40:45 +0000135/* protects queue_read, queue_write and thumbnail_buf_used */
136static struct mutex queue_mutex SHAREDBSS_ATTR;
Stéphane Doyonc893aff2008-07-15 14:06:11 +0000137#define talk_queue_lock() ({ mutex_lock(&queue_mutex); })
138#define talk_queue_unlock() ({ mutex_unlock(&queue_mutex); })
139#else
140#define talk_queue_lock() ({ })
141#define talk_queue_unlock() ({ })
142#endif /* CONFIG_CODEC */
Jörg Hohensohn2fef5b72004-05-09 09:41:23 +0000143static int sent; /* how many bytes handed over to playback, owned by ISR */
Jörg Hohensohnc898a022004-04-20 21:47:07 +0000144static unsigned char curr_hd[3]; /* current frame header, for re-sync */
Magnus Holmgrenb1c56762006-02-23 21:29:29 +0000145static unsigned char last_lang[MAX_FILENAME+1]; /* name of last used lang file (in talk_init) */
Magnus Holmgren7ac73172006-02-26 16:07:34 +0000146static bool talk_initialized; /* true if talk_init has been called */
Thomas Martitz22e802e2013-05-30 11:24:16 +0200147static bool give_buffer_away; /* true if we should give the buffers away in shrink_callback if requested */
Steve Bavin32a95752007-10-19 15:31:42 +0000148static int talk_temp_disable_count; /* if positive, temporarily disable voice UI (not saved) */
Thomas Martitz03f373c2014-02-02 14:48:32 +0100149 /* size of the voice data in the voice file and the actually allocated buffer
150 * for it. voicebuf_size is always smaller or equal to voicefile_size */
151static unsigned long voicefile_size, voicebuf_size;
Thomas Martitz22e802e2013-05-30 11:24:16 +0200152
153struct queue_entry /* one entry of the internal queue */
154{
Thomas Martitzaf02a672014-02-02 16:58:28 +0100155 int handle; /* buflib handle to the clip data */
156 int length; /* total length of the clip */
Thomas Martitzc46f9be2013-11-21 11:44:04 +0100157 int remaining; /* bytes that still need to be deoded */
Thomas Martitz22e802e2013-05-30 11:24:16 +0200158};
159
Thomas Martitzc46f9be2013-11-21 11:44:04 +0100160static struct buflib_context clip_ctx;
Jörg Hohensohnfa97f162004-03-19 22:15:53 +0000161
Thomas Martitzc46f9be2013-11-21 11:44:04 +0100162struct clip_cache_metadata {
163 long tick;
164 int handle, voice_id;
165};
166
167static int metadata_table_handle;
168static unsigned max_clips;
169static int cache_hits, cache_misses;
Thomas Martitzc46f9be2013-11-21 11:44:04 +0100170
171static struct queue_entry queue[QUEUE_SIZE]; /* queue of scheduled clips */
172static struct queue_entry silence, *last_clip;
Jörg Hohensohnfa97f162004-03-19 22:15:53 +0000173
174/***************** Private implementation *****************/
175
Thomas Martitzaf02a672014-02-02 16:58:28 +0100176static int index_handle, talk_handle;
Thomas Martitz22e802e2013-05-30 11:24:16 +0200177
Thomas Martitzc46f9be2013-11-21 11:44:04 +0100178static int move_callback(int handle, void *current, void *new)
179{
Solomon Peachy3b7ae5e2019-08-04 22:46:09 -0400180 (void)current;
Thomas Martitz03f373c2014-02-02 14:48:32 +0100181 if (handle == talk_handle && !buflib_context_relocate(&clip_ctx, new))
182 return BUFLIB_CB_CANNOT_MOVE;
Thomas Martitzc46f9be2013-11-21 11:44:04 +0100183 return BUFLIB_CB_OK;
184}
185
186
187static struct mutex read_buffer_mutex;
188
Thomas Martitz22e802e2013-05-30 11:24:16 +0200189
190/* on HWCODEC only voice xor audio can be active at a time */
191static bool check_audio_status(void)
192{
Thomas Martitzc46f9be2013-11-21 11:44:04 +0100193#if CONFIG_CODEC != SWCODEC
Thomas Martitz22e802e2013-05-30 11:24:16 +0200194 if (audio_status()) /* busy, buffer in use */
195 return false;
196 /* ensure playback is given up on the buffer */
197 audio_hard_stop();
Thomas Martitzc46f9be2013-11-21 11:44:04 +0100198#endif
Thomas Martitz22e802e2013-05-30 11:24:16 +0200199 return true;
200}
201
202/* ISR (mp3_callback()) must not run during moving of the clip buffer,
203 * because the MAS may get out-of-sync */
204static void sync_callback(int handle, bool sync_on)
205{
206 (void) handle;
Thomas Martitz22e802e2013-05-30 11:24:16 +0200207 if (sync_on)
Thomas Martitzc46f9be2013-11-21 11:44:04 +0100208 mutex_lock(&read_buffer_mutex);
Thomas Martitz22e802e2013-05-30 11:24:16 +0200209 else
Thomas Martitzc46f9be2013-11-21 11:44:04 +0100210 mutex_unlock(&read_buffer_mutex);
Thomas Martitz0b7a9072014-02-03 12:43:05 +0100211#if CONFIG_CPU == SH7034
212 /* DMA must not interrupt during buffer move or commit_buffer copies
213 * from inconsistent buflib buffer */
214 if (sync_on)
215 CHCR3 &= ~0x0001; /* disable the DMA (and therefore the interrupt also) */
216 else
217 CHCR3 |= 0x0001; /* re-enable the DMA */
218#endif
Thomas Martitz22e802e2013-05-30 11:24:16 +0200219}
Thomas Martitz22e802e2013-05-30 11:24:16 +0200220
Thomas Martitzc46f9be2013-11-21 11:44:04 +0100221static ssize_t read_to_handle_ex(int fd, struct buflib_context *ctx, int handle,
222 int handle_offset, size_t count)
Thomas Martitz22e802e2013-05-30 11:24:16 +0200223{
Thomas Martitzc46f9be2013-11-21 11:44:04 +0100224 unsigned char *buf;
225 ssize_t ret;
226 mutex_lock(&read_buffer_mutex);
227
228 if (!ctx)
229 buf = core_get_data(handle);
230 else
231 buf = buflib_get_data(ctx, handle);
232
233 buf += handle_offset;
234 ret = read(fd, buf, count);
235
236 mutex_unlock(&read_buffer_mutex);
237
238 return ret;
Thomas Martitz22e802e2013-05-30 11:24:16 +0200239}
240
Thomas Martitzc46f9be2013-11-21 11:44:04 +0100241static ssize_t read_to_handle(int fd, int handle, int handle_offset, size_t count)
242{
243 return read_to_handle_ex(fd, NULL, handle, handle_offset, count);
244}
245
246
247static int shrink_callback(int handle, unsigned hints, void *start, size_t old_size)
Thomas Martitz22e802e2013-05-30 11:24:16 +0200248{
Solomon Peachy3b7ae5e2019-08-04 22:46:09 -0400249 (void)start;(void)old_size;
Thomas Martitzc46f9be2013-11-21 11:44:04 +0100250 int *h;
251#if (MAX_CLIP_BUFFER_SIZE < (MEMORYSIZE<<20) || (MEMORYSIZE > 2))
Thomas Martitz60dea952014-01-29 07:08:42 +0100252 /* on low-mem and when the voice buffer size is not limited (i.e.
253 * on 2MB HWCODEC) we effectively own the entire buffer because
254 * the voicefile takes up all RAM. This blocks other Rockbox parts
255 * from allocating, especially during bootup. Therefore always give
256 * up the buffer and reload when clips are played back. On high-mem
257 * or when the clip buffer is limited to a few 100K this provision is
258 * not necessary. */
Thomas Martitzc46f9be2013-11-21 11:44:04 +0100259 if (give_buffer_away
Thomas Martitz22e802e2013-05-30 11:24:16 +0200260 && (hints & BUFLIB_SHRINK_POS_MASK) == BUFLIB_SHRINK_POS_MASK)
Solomon Peachy3b7ae5e2019-08-04 22:46:09 -0400261#else
262 (void)hints;
Thomas Martitz60dea952014-01-29 07:08:42 +0100263#endif
Thomas Martitz22e802e2013-05-30 11:24:16 +0200264 {
Thomas Martitzc46f9be2013-11-21 11:44:04 +0100265 if (handle == talk_handle)
266 h = &talk_handle;
Solomon Peachy3b7ae5e2019-08-04 22:46:09 -0400267 else if (handle == index_handle)
Thomas Martitzc46f9be2013-11-21 11:44:04 +0100268 h = &index_handle;
Solomon Peachy3b7ae5e2019-08-04 22:46:09 -0400269 else h = NULL;
Thomas Martitzc46f9be2013-11-21 11:44:04 +0100270
271 mutex_lock(&read_buffer_mutex);
272 /* the clip buffer isn't usable without index table */
273 if (handle == index_handle && talk_handle > 0)
274 talk_handle = core_free(talk_handle);
Solomon Peachy3b7ae5e2019-08-04 22:46:09 -0400275 if (h)
276 *h = core_free(handle);
Thomas Martitzc46f9be2013-11-21 11:44:04 +0100277 mutex_unlock(&read_buffer_mutex);
278
Thomas Martitz22e802e2013-05-30 11:24:16 +0200279 return BUFLIB_CB_OK;
280 }
281 return BUFLIB_CB_CANNOT_SHRINK;
282}
283
Thomas Martitzc46f9be2013-11-21 11:44:04 +0100284static struct buflib_callbacks talk_ops = {
Thomas Martitz22e802e2013-05-30 11:24:16 +0200285 .move_callback = move_callback,
Thomas Martitz22e802e2013-05-30 11:24:16 +0200286 .sync_callback = sync_callback,
Thomas Martitzc46f9be2013-11-21 11:44:04 +0100287 .shrink_callback = shrink_callback,
Thomas Martitz54302752014-01-26 19:14:47 +0100288};
289
Thomas Martitz22e802e2013-05-30 11:24:16 +0200290
Jörg Hohensohn6e77d1f2004-04-06 07:06:59 +0000291static int open_voicefile(void)
292{
293 char buf[64];
Thomas Martitzdac40fd2013-07-02 08:24:00 +0200294 char* p_lang = DEFAULT_VOICE_LANG; /* default */
Jörg Hohensohn6e77d1f2004-04-06 07:06:59 +0000295
296 if ( global_settings.lang_file[0] &&
297 global_settings.lang_file[0] != 0xff )
298 { /* try to open the voice file of the selected language */
Daniel Stenbergf981ea92005-12-05 22:44:42 +0000299 p_lang = (char *)global_settings.lang_file;
Jörg Hohensohn6e77d1f2004-04-06 07:06:59 +0000300 }
301
Jonathan Gordon7ae8e442006-10-03 10:38:27 +0000302 snprintf(buf, sizeof(buf), LANG_DIR "/%s.voice", p_lang);
Miika Pekkarinen159c52d2005-08-20 11:13:19 +0000303
Jörg Hohensohn6e77d1f2004-04-06 07:06:59 +0000304 return open(buf, O_RDONLY);
305}
306
Miika Pekkarinen159c52d2005-08-20 11:13:19 +0000307
Thomas Martitzc46f9be2013-11-21 11:44:04 +0100308static int id2index(int id)
Steve Bavin32a95752007-10-19 15:31:42 +0000309{
Thomas Martitzc46f9be2013-11-21 11:44:04 +0100310 int index = id;
Steve Bavin32a95752007-10-19 15:31:42 +0000311 if (id > VOICEONLY_DELIMITER)
Dominik Riebelingde6f9622012-06-05 22:46:16 +0200312 { /* voice-only entries use the second part of the table.
313 The first string comes after VOICEONLY_DELIMITER so we need to
314 substract VOICEONLY_DELIMITER + 1 */
Thomas Martitzc46f9be2013-11-21 11:44:04 +0100315 index -= VOICEONLY_DELIMITER + 1;
316 if (index >= voicefile.id2_max)
Thomas Martitz22e802e2013-05-30 11:24:16 +0200317 return -1; /* must be newer than we have */
Thomas Martitzc46f9be2013-11-21 11:44:04 +0100318 index += voicefile.id1_max; /* table 2 is behind table 1 */
Steve Bavin32a95752007-10-19 15:31:42 +0000319 }
320 else
321 { /* normal use of the first table */
Thomas Martitz22e802e2013-05-30 11:24:16 +0200322 if (id >= voicefile.id1_max)
323 return -1; /* must be newer than we have */
Steve Bavin32a95752007-10-19 15:31:42 +0000324 }
Thomas Martitzc46f9be2013-11-21 11:44:04 +0100325 return index;
326}
327
Thomas Martitz10b3bc72014-02-03 12:33:33 +0100328#ifndef TALK_PROGRESSIVE_LOAD
Thomas Martitz03f373c2014-02-02 14:48:32 +0100329static int index2id(int index)
330{
331 int id = index;
332
333 if (index >= voicefile.id2_max + voicefile.id1_max)
334 return -1;
335
336 if (index >= voicefile.id1_max)
337 { /* must be voice-only if it exceeds table 1 */
338 id -= voicefile.id1_max;
339 /* The first string comes after VOICEONLY_DELIMITER so we need to
340 add VOICEONLY_DELIMITER + 1 */
341 id += VOICEONLY_DELIMITER + 1;
342 }
343
344 return id;
345}
Thomas Martitz10b3bc72014-02-03 12:33:33 +0100346#endif
Thomas Martitz03f373c2014-02-02 14:48:32 +0100347
Thomas Martitzc46f9be2013-11-21 11:44:04 +0100348static int free_oldest_clip(void)
349{
350 unsigned i;
351 int oldest = 0;
Solomon Peachye6b03ff2019-07-21 09:03:38 -0400352 bool thumb = false;
Thomas Martitzc46f9be2013-11-21 11:44:04 +0100353 long age, now;
354 struct clip_entry* clipbuf;
355 struct clip_cache_metadata *cc = buflib_get_data(&clip_ctx, metadata_table_handle);
356 for(age = i = 0, now = current_tick; i < max_clips; i++)
357 {
Thomas Martitzaf02a672014-02-02 16:58:28 +0100358 if (cc[i].handle)
Thomas Martitzc46f9be2013-11-21 11:44:04 +0100359 {
Solomon Peachye6b03ff2019-07-21 09:03:38 -0400360 if (thumb && cc[i].voice_id == VOICEONLY_DELIMITER && (now - cc[i].tick) > age)
Thomas Martitzaf02a672014-02-02 16:58:28 +0100361 {
Solomon Peachye6b03ff2019-07-21 09:03:38 -0400362 /* thumb clips are freed first */
Thomas Martitzaf02a672014-02-02 16:58:28 +0100363 age = now - cc[i].tick;
364 oldest = i;
365 }
Solomon Peachye6b03ff2019-07-21 09:03:38 -0400366 else if (!thumb)
Thomas Martitzaf02a672014-02-02 16:58:28 +0100367 {
Solomon Peachye6b03ff2019-07-21 09:03:38 -0400368 if (cc[i].voice_id == VOICEONLY_DELIMITER)
369 {
370 age = now - cc[i].tick;
371 oldest = i;
372 thumb = true;
373 }
374 else if ((now - cc[i].tick) > age && cc[i].voice_id != VOICE_PAUSE)
375 {
376 /* find the last-used clip but never consider silence */
377 age = now - cc[i].tick;
378 oldest = i;
379 }
Thomas Martitzaf02a672014-02-02 16:58:28 +0100380 }
Thomas Martitzc46f9be2013-11-21 11:44:04 +0100381 }
382 }
Thomas Martitzaf02a672014-02-02 16:58:28 +0100383 /* free the last one if no oldest one could be determined */
Thomas Martitzc46f9be2013-11-21 11:44:04 +0100384 cc = &cc[oldest];
385 cc->handle = buflib_free(&clip_ctx, cc->handle);
Thomas Martitzaf02a672014-02-02 16:58:28 +0100386 /* need to clear the LOADED bit too (not for thumb clips) */
387 if (cc->voice_id != VOICEONLY_DELIMITER)
388 {
389 clipbuf = core_get_data(index_handle);
390 clipbuf[id2index(cc->voice_id)].size &= ~LOADED_MASK;
391 }
Thomas Martitzc46f9be2013-11-21 11:44:04 +0100392 return oldest;
393}
Thomas Martitz03f373c2014-02-02 14:48:32 +0100394
395
396/* common code for load_initial_clips() and get_clip() */
397static void add_cache_entry(int clip_handle, int table_index, int id)
398{
399 unsigned i;
400 struct clip_cache_metadata *cc = buflib_get_data(&clip_ctx, metadata_table_handle);
401
402 if (table_index != -1)
403 {
404 /* explicit slot; use that */
405 cc = &cc[table_index];
406 if (cc->handle > 0) panicf("%s(): Slot already used", __func__);
407 }
408 else
409 { /* find an empty slot */
410 for(i = 0; cc[i].handle && i < max_clips; i++) ;
411 if (i == max_clips) /* no free slot in the cache table? */
412 i = free_oldest_clip();
413 cc = &cc[i];
414 }
415 cc->handle = clip_handle;
416 cc->tick = current_tick;
417 cc->voice_id = id;
418}
419
420static ssize_t read_clip_data(int fd, int index, int clip_handle)
421{
422 struct clip_entry* clipbuf;
423 size_t clipsize;
424 ssize_t ret;
425
426 if (fd < 0)
427 {
428 buflib_free(&clip_ctx, clip_handle);
429 return -1; /* open error */
430 }
431
432 clipbuf = core_get_data(index_handle);
433 /* this must not be called with LOADED_MASK set in clipsize */
434 clipsize = clipbuf[index].size;
435 lseek(fd, clipbuf[index].offset, SEEK_SET);
436 ret = read_to_handle_ex(fd, &clip_ctx, clip_handle, 0, clipsize);
437
438 if (ret < 0 || clipsize != (size_t)ret)
439 {
440 buflib_free(&clip_ctx, clip_handle);
441 return -2; /* read error */
442 }
443
444 clipbuf = core_get_data(index_handle);
445 clipbuf[index].size |= LOADED_MASK; /* mark as loaded */
446
447 return ret;
448}
449
450static void load_initial_clips(int fd)
451{
Solomon Peachy3b7ae5e2019-08-04 22:46:09 -0400452#if defined(TALK_PROGRESSIVE_LOAD)
Thomas Martitz03f373c2014-02-02 14:48:32 +0100453 (void) fd;
Solomon Peachy3b7ae5e2019-08-04 22:46:09 -0400454#else
Thomas Martitz03f373c2014-02-02 14:48:32 +0100455 unsigned index, i;
456 unsigned num_clips = voicefile.id1_max + voicefile.id2_max;
457
458 for(index = i = 0; index < num_clips && i < max_clips; index++)
459 {
460 int handle;
461 struct clip_entry* clipbuf = core_get_data(index_handle);
462 size_t clipsize = clipbuf[index].size;
Thomas Martitzaf02a672014-02-02 16:58:28 +0100463 ssize_t ret;
464
Thomas Martitz03f373c2014-02-02 14:48:32 +0100465 if (clipsize == 0) /* clip not included in voicefile */
466 continue;
467
468 handle = buflib_alloc(&clip_ctx, clipsize);
469 if (handle < 0)
470 break;
471
Thomas Martitzaf02a672014-02-02 16:58:28 +0100472 ret = read_clip_data(fd, index, handle);
473 if (ret < 0)
Thomas Martitz03f373c2014-02-02 14:48:32 +0100474 break;
475
476 add_cache_entry(handle, i++, index2id(index));
477 }
Thomas Martitzc46f9be2013-11-21 11:44:04 +0100478#endif
Thomas Martitz03f373c2014-02-02 14:48:32 +0100479}
480
Thomas Martitzc46f9be2013-11-21 11:44:04 +0100481/* fetch a clip from the voice file */
482static int get_clip(long id, struct queue_entry *q)
483{
484 int index;
485 int retval = -1;
486 struct clip_entry* clipbuf;
487 size_t clipsize;
Thomas Martitzc46f9be2013-11-21 11:44:04 +0100488
489 index = id2index(id);
490 if (index == -1)
491 return -1;
Thomas Martitz22e802e2013-05-30 11:24:16 +0200492
493 clipbuf = core_get_data(index_handle);
Thomas Martitzc46f9be2013-11-21 11:44:04 +0100494 clipsize = clipbuf[index].size;
Steve Bavin32a95752007-10-19 15:31:42 +0000495 if (clipsize == 0) /* clip not included in voicefile */
Thomas Martitz22e802e2013-05-30 11:24:16 +0200496 return -1;
Steve Bavin32a95752007-10-19 15:31:42 +0000497
Steve Bavin32a95752007-10-19 15:31:42 +0000498 if (!(clipsize & LOADED_MASK))
Rafaël Carréca91d0f2010-09-01 00:08:50 +0000499 { /* clip needs loading */
Thomas Martitzc46f9be2013-11-21 11:44:04 +0100500 int fd, handle, oldest = -1;
Thomas Martitz22e802e2013-05-30 11:24:16 +0200501 ssize_t ret;
Thomas Martitzdac40fd2013-07-02 08:24:00 +0200502 cache_misses++;
Thomas Martitzc46f9be2013-11-21 11:44:04 +0100503 /* free clips from cache until this one succeeds to allocate */
504 while ((handle = buflib_alloc(&clip_ctx, clipsize)) < 0)
505 oldest = free_oldest_clip();
506 /* handle should now hold a valid alloc. Load from disk
507 * and insert into cache */
Thomas Martitz22e802e2013-05-30 11:24:16 +0200508 fd = open_voicefile();
Thomas Martitz03f373c2014-02-02 14:48:32 +0100509 ret = read_clip_data(fd, index, handle);
Thomas Martitz22e802e2013-05-30 11:24:16 +0200510 close(fd);
Thomas Martitz03f373c2014-02-02 14:48:32 +0100511 if (ret < 0)
512 return ret;
Thomas Martitzc46f9be2013-11-21 11:44:04 +0100513 /* finally insert into metadata table */
Thomas Martitz03f373c2014-02-02 14:48:32 +0100514 add_cache_entry(handle, oldest, id);
Thomas Martitzc46f9be2013-11-21 11:44:04 +0100515 retval = handle;
Steve Bavin32a95752007-10-19 15:31:42 +0000516 }
517 else
Thomas Martitzc46f9be2013-11-21 11:44:04 +0100518 { /* clip is in memory already; find where it was loaded */
Thomas Martitzdac40fd2013-07-02 08:24:00 +0200519 cache_hits++;
Thomas Martitzc46f9be2013-11-21 11:44:04 +0100520 struct clip_cache_metadata *cc;
521 static int i;
522 cc = buflib_get_data(&clip_ctx, metadata_table_handle);
523 for (i = 0; cc[i].voice_id != id || !cc[i].handle; i++) ;
524 cc[i].tick = current_tick; /* reset age */
Steve Bavin32a95752007-10-19 15:31:42 +0000525 clipsize &= ~LOADED_MASK; /* without the extra bit gives true size */
Thomas Martitzc46f9be2013-11-21 11:44:04 +0100526 retval = cc[i].handle;
Steve Bavin32a95752007-10-19 15:31:42 +0000527 }
Steve Bavin32a95752007-10-19 15:31:42 +0000528
Thomas Martitzaf02a672014-02-02 16:58:28 +0100529 q->handle = retval;
Thomas Martitzc46f9be2013-11-21 11:44:04 +0100530 q->length = clipsize;
531 q->remaining = clipsize;
Thomas Martitzc46f9be2013-11-21 11:44:04 +0100532 return 0;
Steve Bavin32a95752007-10-19 15:31:42 +0000533}
534
Thomas Martitz22e802e2013-05-30 11:24:16 +0200535static bool load_index_table(int fd, const struct voicefile_header *hdr)
Jörg Hohensohnfa97f162004-03-19 22:15:53 +0000536{
Thomas Martitz22e802e2013-05-30 11:24:16 +0200537 ssize_t ret;
538 struct clip_entry *buf;
Thomas Martitzd1322b72011-08-14 15:13:00 +0000539
Thomas Martitz22e802e2013-05-30 11:24:16 +0200540 if (index_handle > 0) /* nothing to do? */
541 return true;
542
543 ssize_t alloc_size = (hdr->id1_max + hdr->id2_max) * sizeof(struct clip_entry);
Thomas Martitzc46f9be2013-11-21 11:44:04 +0100544 index_handle = core_alloc_ex("voice index", alloc_size, &talk_ops);
Thomas Martitz22e802e2013-05-30 11:24:16 +0200545 if (index_handle < 0)
546 return false;
547
Thomas Martitzc46f9be2013-11-21 11:44:04 +0100548 ret = read_to_handle(fd, index_handle, 0, alloc_size);
Thomas Martitz22e802e2013-05-30 11:24:16 +0200549
Thomas Martitz22e802e2013-05-30 11:24:16 +0200550 if (ret == alloc_size)
Thomas Martitzc46f9be2013-11-21 11:44:04 +0100551 {
552 buf = core_get_data(index_handle);
Thomas Martitz22e802e2013-05-30 11:24:16 +0200553 for (int i = 0; i < hdr->id1_max + hdr->id2_max; i++)
554 {
Nils Wallménius443a7652007-08-06 18:52:37 +0000555#ifdef ROCKBOX_LITTLE_ENDIAN
Thomas Martitzc46f9be2013-11-21 11:44:04 +0100556 /* doesn't yield() */
Thomas Martitz22e802e2013-05-30 11:24:16 +0200557 structec_convert(&buf[i], "ll", 1, true);
Miika Pekkarinen159c52d2005-08-20 11:13:19 +0000558#endif
Thomas Martitz22e802e2013-05-30 11:24:16 +0200559 }
Thomas Martitzc46f9be2013-11-21 11:44:04 +0100560 }
561 else
Thomas Martitz22e802e2013-05-30 11:24:16 +0200562 index_handle = core_free(index_handle);
563
564 return ret == alloc_size;
565}
566
567static bool load_header(int fd, struct voicefile_header *hdr)
568{
569 ssize_t got_size = read(fd, hdr, sizeof(*hdr));
570 if (got_size != sizeof(*hdr))
571 return false;
Thomas Martitzd1322b72011-08-14 15:13:00 +0000572
Jens Arnoldf383cc12006-03-19 16:09:41 +0000573#ifdef ROCKBOX_LITTLE_ENDIAN
Miika Pekkarinen159c52d2005-08-20 11:13:19 +0000574 logf("Byte swapping voice file");
Thomas Martitz22e802e2013-05-30 11:24:16 +0200575 structec_convert(&voicefile, "lllll", 1, true);
Miika Pekkarinen159c52d2005-08-20 11:13:19 +0000576#endif
Thomas Martitz22e802e2013-05-30 11:24:16 +0200577 return true;
578}
579
Thomas Martitz03f373c2014-02-02 14:48:32 +0100580static bool create_clip_buffer(size_t max_size)
Thomas Martitz22e802e2013-05-30 11:24:16 +0200581{
Thomas Martitz03f373c2014-02-02 14:48:32 +0100582 size_t alloc_size;
583 /* just allocate, populate on an as-needed basis later */
584 talk_handle = core_alloc_ex("voice data", max_size, &talk_ops);
Thomas Martitz22e802e2013-05-30 11:24:16 +0200585 if (talk_handle < 0)
Thomas Martitz03f373c2014-02-02 14:48:32 +0100586 goto alloc_err;
Thomas Martitz22e802e2013-05-30 11:24:16 +0200587
Thomas Martitz03f373c2014-02-02 14:48:32 +0100588 buflib_init(&clip_ctx, core_get_data(talk_handle), max_size);
Thomas Martitz22e802e2013-05-30 11:24:16 +0200589
Thomas Martitz03f373c2014-02-02 14:48:32 +0100590 /* the first alloc is the clip metadata table */
591 alloc_size = max_clips * sizeof(struct clip_cache_metadata);
592 metadata_table_handle = buflib_alloc(&clip_ctx, alloc_size);
593 memset(buflib_get_data(&clip_ctx, metadata_table_handle), 0, alloc_size);
Thomas Martitz22e802e2013-05-30 11:24:16 +0200594
Thomas Martitz03f373c2014-02-02 14:48:32 +0100595 return true;
596
597alloc_err:
598 index_handle = core_free(index_handle);
599 return false;
Thomas Martitz22e802e2013-05-30 11:24:16 +0200600}
Thomas Martitz22e802e2013-05-30 11:24:16 +0200601
602/* load the voice file into the mp3 buffer */
603static bool load_voicefile_index(int fd)
604{
605 if (fd < 0) /* failed to open */
606 return false;
607
608 /* load the header first */
609 if (!load_header(fd, &voicefile))
610 return false;
Miika Pekkarinen159c52d2005-08-20 11:13:19 +0000611
Thomas Martitzd1322b72011-08-14 15:13:00 +0000612 /* format check */
Thomas Martitz22e802e2013-05-30 11:24:16 +0200613 if (voicefile.table == sizeof(struct voicefile_header))
Jörg Hohensohnfa97f162004-03-19 22:15:53 +0000614 {
Thomas Martitz22e802e2013-05-30 11:24:16 +0200615 if (voicefile.version == VOICE_VERSION &&
616 voicefile.target_id == TARGET_ID)
Nils Wallméniusb3113672007-08-05 19:19:39 +0000617 {
Thomas Martitz22e802e2013-05-30 11:24:16 +0200618 if (load_index_table(fd, &voicefile))
619 return true;
Nils Wallméniusb3113672007-08-05 19:19:39 +0000620 }
Jörg Hohensohnfa97f162004-03-19 22:15:53 +0000621 }
Jörg Hohensohnfa97f162004-03-19 22:15:53 +0000622
Thomas Martitz22e802e2013-05-30 11:24:16 +0200623 logf("Incompatible voice file");
624 return false;
625}
626
Thomas Martitz03f373c2014-02-02 14:48:32 +0100627/* this function caps the voicefile buffer and allocates it. It can
628 * be called after talk_init(), e.g. when the voice was temporarily disabled.
629 * The buffer size has to be capped again each time because the available
630 * audio buffer changes over time */
631static bool load_voicefile_data(int fd)
Thomas Martitz22e802e2013-05-30 11:24:16 +0200632{
Thomas Martitz03f373c2014-02-02 14:48:32 +0100633 voicebuf_size = voicefile_size;
634 /* cap to the max. number of clips or the size of the available audio
635 * buffer which we grab. We leave some to the rest of the system.
636 * While that reduces our buffer size it improves the chance that
637 * other allocs succeed without disabling voice which would require
638 * reloading the voice from disk (as we do not shrink our buffer when
639 * other code attempts new allocs these would fail) */
640 ssize_t cap = MIN(MAX_CLIP_BUFFER_SIZE, audio_buffer_available() - (64<<10));
641 if (UNLIKELY(cap < 0))
642 {
643 logf("Not enough memory for voice. Disabling...\n");
644 return false;
645 }
646 else if (voicebuf_size > (size_t)cap)
647 voicebuf_size = cap;
648
Thomas Martitzc46f9be2013-11-21 11:44:04 +0100649 /* just allocate, populate on an as-needed basis later
650 * re-create the clip buffer to ensure clip_ctx is up-to-date */
651 if (talk_handle > 0)
652 talk_handle = core_free(talk_handle);
Thomas Martitz03f373c2014-02-02 14:48:32 +0100653 if (!create_clip_buffer(voicebuf_size))
654 return false;
Thomas Martitzc46f9be2013-11-21 11:44:04 +0100655
Thomas Martitz03f373c2014-02-02 14:48:32 +0100656 load_initial_clips(fd);
657 /* make sure to have the silence clip, if available return value can
658 * be cached globally even for TALK_PROGRESSIVE_LOAD because the
659 * VOICE_PAUSE clip is specially handled */
660 get_clip(VOICE_PAUSE, &silence);
Jörg Hohensohn24e6dff2004-11-17 22:30:38 +0000661
Thomas Martitz22e802e2013-05-30 11:24:16 +0200662 return true;
Jörg Hohensohnfa97f162004-03-19 22:15:53 +0000663}
664
Thomas Martitze5eb7452014-01-21 23:22:37 +0100665/* Use a static buffer to avoid difficulties with buflib during DMA
666 * (hwcodec)/buffer passing to the voice_thread (swcodec). Clips
667 * can be played in chunks so the size is not that important */
Thomas Martitz0b7a9072014-02-03 12:43:05 +0100668static unsigned char commit_buffer[2<<10];
Thomas Martitzc46f9be2013-11-21 11:44:04 +0100669
670static void* commit_transfer(struct queue_entry *qe, size_t *size)
671{
672 void *buf = NULL; /* shut up gcc */
673 static unsigned char *bufpos = commit_buffer;
Thomas Martitzc46f9be2013-11-21 11:44:04 +0100674#if CONFIG_CODEC != SWCODEC
675 sent = MIN(qe->remaining, 0xFFFF);
676#else
677 sent = qe->remaining;
678#endif
679 sent = MIN((size_t)sent, sizeof(commit_buffer));
Thomas Martitzaf02a672014-02-02 16:58:28 +0100680 buf = buflib_get_data(&clip_ctx, qe->handle);
Thomas Martitzc46f9be2013-11-21 11:44:04 +0100681 /* adjust buffer position to what has been played already */
682 buf += (qe->length - qe->remaining);
683 memcpy(bufpos, buf, sent);
684 *size = sent;
685
Thomas Martitzc46f9be2013-11-21 11:44:04 +0100686 return commit_buffer;
687}
688
689static inline bool is_silence(struct queue_entry *qe)
690{
Thomas Martitzaf02a672014-02-02 16:58:28 +0100691 if (silence.handle > 0) /* silence clip available? */
692 return (qe->handle == silence.handle);
Thomas Martitzc46f9be2013-11-21 11:44:04 +0100693 else
694 return false;
695}
696
Michael Sevakisd18a5ca2012-03-04 14:44:43 -0500697/* called in ISR context (on HWCODEC) if mp3 data got consumed */
698static void mp3_callback(const void** start, size_t* size)
Jörg Hohensohnfa97f162004-03-19 22:15:53 +0000699{
Thomas Martitzc46f9be2013-11-21 11:44:04 +0100700 struct queue_entry *qe = &queue[queue_read];
Thomas Martitze5eb7452014-01-21 23:22:37 +0100701#if CONFIG_CODEC == SWCODEC
702 /* voice_thread.c hints us how many of the buffer we provided it actually
703 * consumed. Because buffers have to be frame-aligned for speex
704 * it might be less than what we presented */
705 if (*size)
706 sent = *size;
707#endif
Thomas Martitzc46f9be2013-11-21 11:44:04 +0100708 qe->remaining -= sent; /* we completed this */
Jörg Hohensohnfa97f162004-03-19 22:15:53 +0000709
Thomas Martitzc46f9be2013-11-21 11:44:04 +0100710 if (qe->remaining > 0) /* current clip not finished? */
Jörg Hohensohnfa97f162004-03-19 22:15:53 +0000711 { /* feed the next 64K-1 chunk */
Thomas Martitzc46f9be2013-11-21 11:44:04 +0100712 *start = commit_transfer(qe, size);
Jörg Hohensohnfa97f162004-03-19 22:15:53 +0000713 return;
714 }
Thomas Martitzc46f9be2013-11-21 11:44:04 +0100715
Stéphane Doyonc893aff2008-07-15 14:06:11 +0000716 talk_queue_lock();
Jörg Hohensohnfa97f162004-03-19 22:15:53 +0000717
Thomas Martitzc46f9be2013-11-21 11:44:04 +0100718 /* increment read position for the just played clip */
719 queue_read = (queue_read + 1) & QUEUE_MASK;
720
721 if (QUEUE_LEVEL == 0)
722 {
Thomas Martitzaf02a672014-02-02 16:58:28 +0100723 if (!is_silence(last_clip))
Thomas Martitzc46f9be2013-11-21 11:44:04 +0100724 { /* add silence clip when queue runs empty playing a voice clip,
Thomas Martitzaf02a672014-02-02 16:58:28 +0100725 * only if the previous clip wasn't already silence */
Thomas Martitzc46f9be2013-11-21 11:44:04 +0100726 queue[queue_write] = silence;
727 queue_write = (queue_write + 1) & QUEUE_MASK;
728 }
729 else
730 {
731 *size = 0; /* end of data */
732 }
733 }
Jens Arnold1a479d62004-11-18 22:59:20 +0000734
Steve Bavin32a95752007-10-19 15:31:42 +0000735 if (QUEUE_LEVEL != 0) /* queue is not empty? */
Jörg Hohensohnfa97f162004-03-19 22:15:53 +0000736 { /* start next clip */
Thomas Martitzc46f9be2013-11-21 11:44:04 +0100737 last_clip = &queue[queue_read];
738 *start = commit_transfer(last_clip, size);
739 curr_hd[0] = commit_buffer[1];
740 curr_hd[1] = commit_buffer[2];
741 curr_hd[2] = commit_buffer[3];
Jörg Hohensohnfa97f162004-03-19 22:15:53 +0000742 }
Thomas Martitzc46f9be2013-11-21 11:44:04 +0100743
Stéphane Doyonc893aff2008-07-15 14:06:11 +0000744 talk_queue_unlock();
Jörg Hohensohnfa97f162004-03-19 22:15:53 +0000745}
746
Steve Bavin32a95752007-10-19 15:31:42 +0000747/***************** Public routines *****************/
748
Steve Bavin71dd94a2006-10-25 08:54:25 +0000749/* stop the playback and the pending clips */
Steve Bavin32a95752007-10-19 15:31:42 +0000750void talk_force_shutup(void)
Jörg Hohensohnfa97f162004-03-19 22:15:53 +0000751{
Steve Bavin32a95752007-10-19 15:31:42 +0000752 /* Most of this is MAS only */
Steve Bavinf5125582006-10-25 06:19:27 +0000753#if CONFIG_CODEC != SWCODEC
Jonathan Gordonc14430a2007-11-07 09:28:07 +0000754#ifdef SIMULATOR
755 return;
756#endif
Jörg Hohensohn590e6af2004-03-21 17:45:45 +0000757 unsigned char* pos;
758 unsigned char* search;
759 unsigned char* end;
Thomas Martitz22e802e2013-05-30 11:24:16 +0200760 int len;
Jörg Hohensohn2fef5b72004-05-09 09:41:23 +0000761 if (QUEUE_LEVEL == 0) /* has ended anyway */
Steve Bavin32a95752007-10-19 15:31:42 +0000762 return;
763
Daniel Stenberg003c0d22005-02-02 21:50:24 +0000764#if CONFIG_CPU == SH7034
Jörg Hohensohn2fef5b72004-05-09 09:41:23 +0000765 CHCR3 &= ~0x0001; /* disable the DMA (and therefore the interrupt also) */
Steve Bavin32a95752007-10-19 15:31:42 +0000766#endif /* CONFIG_CPU == SH7034 */
Jörg Hohensohn590e6af2004-03-21 17:45:45 +0000767 /* search next frame boundary and continue up to there */
768 pos = search = mp3_get_pos();
Thomas Martitzaf02a672014-02-02 16:58:28 +0100769 end = buflib_get_data(&clip_ctx, queue[queue_read].handle);
Thomas Martitz22e802e2013-05-30 11:24:16 +0200770 len = queue[queue_read].length;
Jörg Hohensohnfa97f162004-03-19 22:15:53 +0000771
Thomas Martitz22e802e2013-05-30 11:24:16 +0200772 if (pos >= end && pos <= (end+len)) /* really our clip? */
Jörg Hohensohn2fef5b72004-05-09 09:41:23 +0000773 { /* (for strange reasons this isn't nesessarily the case) */
774 /* find the next frame boundary */
Thomas Martitz22e802e2013-05-30 11:24:16 +0200775 while (search < (end+len)) /* search the remaining data */
Jörg Hohensohnb109c1e2004-03-29 08:19:47 +0000776 {
Jörg Hohensohn2fef5b72004-05-09 09:41:23 +0000777 if (*search++ != 0xFF) /* quick search for frame sync byte */
778 continue; /* (this does the majority of the job) */
779
780 /* look at the (bitswapped) rest of header candidate */
781 if (search[0] == curr_hd[0] /* do the quicker checks first */
782 && search[2] == curr_hd[2]
783 && (search[1] & 0x30) == (curr_hd[1] & 0x30)) /* sample rate */
784 {
785 search--; /* back to the sync byte */
786 break; /* From looking at it, this is our header. */
787 }
788 }
789
790 if (search-pos)
791 { /* play old data until the frame end, to keep the MAS in sync */
792 sent = search-pos;
793
Jens Arnold1a479d62004-11-18 22:59:20 +0000794 queue_write = (queue_read + 1) & QUEUE_MASK; /* will be empty after next callback */
Thomas Martitz22e802e2013-05-30 11:24:16 +0200795 queue[queue_read].length = sent; /* current one ends after this */
Jörg Hohensohn2fef5b72004-05-09 09:41:23 +0000796
Daniel Stenberg003c0d22005-02-02 21:50:24 +0000797#if CONFIG_CPU == SH7034
Jörg Hohensohn2fef5b72004-05-09 09:41:23 +0000798 DTCR3 = sent; /* let the DMA finish this frame */
799 CHCR3 |= 0x0001; /* re-enable DMA */
Steve Bavin32a95752007-10-19 15:31:42 +0000800#endif /* CONFIG_CPU == SH7034 */
Stéphane Doyonc893aff2008-07-15 14:06:11 +0000801 thumbnail_buf_used = 0;
Steve Bavin32a95752007-10-19 15:31:42 +0000802 return;
Jörg Hohensohnb109c1e2004-03-29 08:19:47 +0000803 }
Jörg Hohensohn590e6af2004-03-21 17:45:45 +0000804 }
Steve Bavin32a95752007-10-19 15:31:42 +0000805#endif /* CONFIG_CODEC != SWCODEC */
Jörg Hohensohnc49129c2004-04-20 22:11:20 +0000806
Steve Bavin32a95752007-10-19 15:31:42 +0000807 /* Either SWCODEC, or MAS had nothing to do (was frame boundary or not our clip) */
Steve Bavinff5fd002006-10-25 07:08:00 +0000808 mp3_play_stop();
Stéphane Doyonc893aff2008-07-15 14:06:11 +0000809 talk_queue_lock();
Jörg Hohensohn2fef5b72004-05-09 09:41:23 +0000810 queue_write = queue_read = 0; /* reset the queue */
Stéphane Doyonc893aff2008-07-15 14:06:11 +0000811 thumbnail_buf_used = 0;
812 talk_queue_unlock();
813 need_shutup = false;
Jörg Hohensohnfa97f162004-03-19 22:15:53 +0000814}
815
Nils Wallménius5b769362007-08-06 13:08:36 +0000816/* Shutup the voice, except if force_enqueue_next is set. */
Steve Bavin32a95752007-10-19 15:31:42 +0000817void talk_shutup(void)
Nils Wallménius5b769362007-08-06 13:08:36 +0000818{
Stéphane Doyonc893aff2008-07-15 14:06:11 +0000819 if (need_shutup && !force_enqueue_next)
Steve Bavin32a95752007-10-19 15:31:42 +0000820 talk_force_shutup();
Nils Wallménius5b769362007-08-06 13:08:36 +0000821}
Jörg Hohensohnfa97f162004-03-19 22:15:53 +0000822
823/* schedule a clip, at the end or discard the existing queue */
Thomas Martitzc46f9be2013-11-21 11:44:04 +0100824static void queue_clip(struct queue_entry *clip, bool enqueue)
Jörg Hohensohnfa97f162004-03-19 22:15:53 +0000825{
Thomas Martitzc46f9be2013-11-21 11:44:04 +0100826 struct queue_entry *qe;
Jörg Hohensohn2fef5b72004-05-09 09:41:23 +0000827 int queue_level;
828
Jörg Hohensohnfa97f162004-03-19 22:15:53 +0000829 if (!enqueue)
Steve Bavin32a95752007-10-19 15:31:42 +0000830 talk_shutup(); /* cut off all the pending stuff */
Nils Wallménius5b769362007-08-06 13:08:36 +0000831 /* Something is being enqueued, force_enqueue_next override is no
832 longer in effect. */
833 force_enqueue_next = false;
Jörg Hohensohnfa97f162004-03-19 22:15:53 +0000834
Thomas Martitzc46f9be2013-11-21 11:44:04 +0100835 if (!clip->length)
Steve Bavin32a95752007-10-19 15:31:42 +0000836 return; /* safety check */
Daniel Stenberg003c0d22005-02-02 21:50:24 +0000837#if CONFIG_CPU == SH7034
Jörg Hohensohn2fef5b72004-05-09 09:41:23 +0000838 /* disable the DMA temporarily, to be safe of race condition */
839 CHCR3 &= ~0x0001;
Daniel Stenberg003c0d22005-02-02 21:50:24 +0000840#endif
Stéphane Doyonc893aff2008-07-15 14:06:11 +0000841 talk_queue_lock();
Jörg Hohensohn2fef5b72004-05-09 09:41:23 +0000842 queue_level = QUEUE_LEVEL; /* check old level */
Thomas Martitzc46f9be2013-11-21 11:44:04 +0100843 qe = &queue[queue_write];
Jörg Hohensohn2fef5b72004-05-09 09:41:23 +0000844
845 if (queue_level < QUEUE_SIZE - 1) /* space left? */
846 {
Thomas Martitzc46f9be2013-11-21 11:44:04 +0100847 queue[queue_write] = *clip;
Jens Arnold1a479d62004-11-18 22:59:20 +0000848 queue_write = (queue_write + 1) & QUEUE_MASK;
Jörg Hohensohn2fef5b72004-05-09 09:41:23 +0000849 }
Stéphane Doyonc893aff2008-07-15 14:06:11 +0000850 talk_queue_unlock();
851
Jörg Hohensohn2fef5b72004-05-09 09:41:23 +0000852 if (queue_level == 0)
853 { /* queue was empty, we have to do the initial start */
Thomas Martitzc46f9be2013-11-21 11:44:04 +0100854 size_t size;
855 void *buf = commit_transfer(qe, &size);
856 last_clip = qe;
857 mp3_play_data(buf, size, mp3_callback);
858 curr_hd[0] = commit_buffer[1];
859 curr_hd[1] = commit_buffer[2];
860 curr_hd[2] = commit_buffer[3];
Jörg Hohensohnfa97f162004-03-19 22:15:53 +0000861 mp3_play_pause(true); /* kickoff audio */
Jörg Hohensohnfa97f162004-03-19 22:15:53 +0000862 }
Jörg Hohensohn2fef5b72004-05-09 09:41:23 +0000863 else
864 {
Daniel Stenberg003c0d22005-02-02 21:50:24 +0000865#if CONFIG_CPU == SH7034
Jörg Hohensohn2fef5b72004-05-09 09:41:23 +0000866 CHCR3 |= 0x0001; /* re-enable DMA */
Daniel Stenberg003c0d22005-02-02 21:50:24 +0000867#endif
Jörg Hohensohn2fef5b72004-05-09 09:41:23 +0000868 }
Jörg Hohensohnfa97f162004-03-19 22:15:53 +0000869
Stéphane Doyonc893aff2008-07-15 14:06:11 +0000870 need_shutup = true;
871
Steve Bavin32a95752007-10-19 15:31:42 +0000872 return;
Jörg Hohensohn24e6dff2004-11-17 22:30:38 +0000873}
874
Thomas Martitz57000b52014-01-29 07:14:58 +0100875/* return if a voice codec is required or not */
876static bool talk_voice_required(void)
877{
878 return (has_voicefile) /* Voice file is available */
879 || (global_settings.talk_dir_clip) /* Thumbnail clips are required */
880 || (global_settings.talk_file_clip);
881}
Thomas Martitz57000b52014-01-29 07:14:58 +0100882
Jörg Hohensohnfa97f162004-03-19 22:15:53 +0000883/***************** Public implementation *****************/
884
885void talk_init(void)
886{
Thomas Martitz22e802e2013-05-30 11:24:16 +0200887 int filehandle;
Steve Bavin32a95752007-10-19 15:31:42 +0000888 talk_temp_disable_count = 0;
Magnus Holmgren7ac73172006-02-26 16:07:34 +0000889 if (talk_initialized && !strcasecmp(last_lang, global_settings.lang_file))
Magnus Holmgrenb1c56762006-02-23 21:29:29 +0000890 {
891 /* not a new file, nothing to do */
892 return;
893 }
894
Stéphane Doyonc893aff2008-07-15 14:06:11 +0000895 if(!talk_initialized)
Thomas Martitzc46f9be2013-11-21 11:44:04 +0100896 {
897#if CONFIG_CODEC == SWCODEC
Stéphane Doyonc893aff2008-07-15 14:06:11 +0000898 mutex_init(&queue_mutex);
899#endif /* CONFIG_CODEC == SWCODEC */
Thomas Martitzc46f9be2013-11-21 11:44:04 +0100900 mutex_init(&read_buffer_mutex);
901 }
Magnus Holmgren7ac73172006-02-26 16:07:34 +0000902 talk_initialized = true;
Nils Wallménius3d4701a2009-07-14 13:57:45 +0000903 strlcpy((char *)last_lang, (char *)global_settings.lang_file,
Magnus Holmgrenb1c56762006-02-23 21:29:29 +0000904 MAX_FILENAME);
905
Thomas Martitz22e802e2013-05-30 11:24:16 +0200906 /* reset some states */
907 queue_write = queue_read = 0; /* reset the queue */
908 memset(&voicefile, 0, sizeof(voicefile));
Rafaël Carréca91d0f2010-09-01 00:08:50 +0000909
Thomas Martitzaf02a672014-02-02 16:58:28 +0100910 silence.handle = -1; /* pause clip not accessible */
Thomas Martitz22e802e2013-05-30 11:24:16 +0200911 voicefile_size = has_voicefile = 0;
912 /* need to free these as their size depends on the voice file, and
913 * this function is called when the talk voice file changes */
914 if (index_handle > 0) index_handle = core_free(index_handle);
915 if (talk_handle > 0) talk_handle = core_free(talk_handle);
916 /* don't free thumb handle, it doesn't depend on the actual voice file
917 * and so we can re-use it if it's already allocated in any event */
918
919 filehandle = open_voicefile();
Thomas Martitzaf02a672014-02-02 16:58:28 +0100920 if (filehandle > 0)
Thomas Martitzc46f9be2013-11-21 11:44:04 +0100921 {
Thomas Martitzaf02a672014-02-02 16:58:28 +0100922 if (!load_voicefile_index(filehandle))
923 goto out;
924
925 /* Now determine the maximum buffer size needed for the voicefile.
926 * The below pretends the entire voicefile would be loaded. The buffer
927 * size is eventually capped later on in load_voicefile_data() */
928 int num_clips = voicefile.id1_max + voicefile.id2_max;
929 int non_empty = num_clips;
930 int total_size = 0, avg_size;
931 struct clip_entry *clips = core_get_data(index_handle);
932 /* check for the average clip size to estimate the maximum number of
933 * clips the buffer can hold */
934 for (int i = 0; i<num_clips; i++)
935 {
936 if (clips[i].size)
937 total_size += ALIGN_UP(clips[i].size, sizeof(void *));
938 else
939 non_empty -= 1;
940 }
941 avg_size = total_size / non_empty;
942 max_clips = MIN((int)(MAX_CLIP_BUFFER_SIZE/avg_size) + 1, non_empty);
943 /* account for possible thumb clips */
944 total_size += THUMBNAIL_RESERVE;
945 max_clips += 16;
946 voicefile_size = total_size;
947 has_voicefile = true;
Rafaël Carréca91d0f2010-09-01 00:08:50 +0000948 }
Thomas Martitzaf02a672014-02-02 16:58:28 +0100949 else if (talk_voice_required())
950 {
951 /* create buffer just for thumb clips */
952 max_clips = 16;
953 voicefile_size = THUMBNAIL_RESERVE;
954 }
955 /* additionally to the clip we need a table to record the age of the clips
Thomas Martitzc46f9be2013-11-21 11:44:04 +0100956 * so that, when memory is tight, only the most recently used ones are kept */
957 voicefile_size += sizeof(struct clip_cache_metadata) * max_clips;
Thomas Martitz03f373c2014-02-02 14:48:32 +0100958 /* compensate a bit for buflib alloc overhead. */
959 voicefile_size += BUFLIB_ALLOC_OVERHEAD * max_clips + 64;
Thomas Martitzc46f9be2013-11-21 11:44:04 +0100960
Thomas Martitz03f373c2014-02-02 14:48:32 +0100961 load_voicefile_data(filehandle);
962
Michael Sevakisda6cebb2012-05-02 17:22:28 -0400963#if CONFIG_CODEC == SWCODEC
Thomas Martitz57000b52014-01-29 07:14:58 +0100964 /* Initialize the actual voice clip playback engine as well */
965 if (talk_voice_required())
966 voice_thread_init();
Michael Sevakisda6cebb2012-05-02 17:22:28 -0400967#endif
Thomas Martitz22e802e2013-05-30 11:24:16 +0200968
969out:
970 close(filehandle); /* close again, this was just to detect presence */
Jörg Hohensohnfa97f162004-03-19 22:15:53 +0000971}
972
Jörg Hohensohnfa97f162004-03-19 22:15:53 +0000973/* somebody else claims the mp3 buffer, e.g. for regular play/record */
Thomas Martitz22e802e2013-05-30 11:24:16 +0200974void talk_buffer_set_policy(int policy)
Jörg Hohensohnfa97f162004-03-19 22:15:53 +0000975{
Thomas Martitz22e802e2013-05-30 11:24:16 +0200976 switch(policy)
Jörg Hohensohna7aa17a2004-10-12 16:43:22 +0000977 {
Thomas Martitz22e802e2013-05-30 11:24:16 +0200978 case TALK_BUFFER_DEFAULT:
979 case TALK_BUFFER_HOLD: give_buffer_away = false; break;
980 case TALK_BUFFER_LOOSE: give_buffer_away = true; break;
981 default: DEBUGF("Ignoring unknown policy\n"); break;
Jörg Hohensohna7aa17a2004-10-12 16:43:22 +0000982 }
Jörg Hohensohnfa97f162004-03-19 22:15:53 +0000983}
984
Jörg Hohensohn2fef5b72004-05-09 09:41:23 +0000985/* play a voice ID from voicefile */
Nils Wallméniusacbd7802007-11-20 19:50:52 +0000986int talk_id(int32_t id, bool enqueue)
Jörg Hohensohnfa97f162004-03-19 22:15:53 +0000987{
Nils Wallméniusacbd7802007-11-20 19:50:52 +0000988 int32_t unit;
Nils Wallménius05d2bfd2008-04-19 13:19:04 +0000989 int decimals;
Thomas Martitzc46f9be2013-11-21 11:44:04 +0100990 struct queue_entry clip;
Jörg Hohensohnfa97f162004-03-19 22:15:53 +0000991
Thomas Martitzefc20ec2014-01-27 10:05:48 +0100992 if (!has_voicefile)
993 return 0; /* no voicefile loaded, not an error -> pretent success */
Steve Bavin32a95752007-10-19 15:31:42 +0000994 if (talk_temp_disable_count > 0)
995 return -1; /* talking has been disabled */
Thomas Martitz22e802e2013-05-30 11:24:16 +0200996 if (!check_audio_status())
Nils Wallméniusacbd7802007-11-20 19:50:52 +0000997 return -1;
Jörg Hohensohnfa97f162004-03-19 22:15:53 +0000998
Thomas Martitzefc20ec2014-01-27 10:05:48 +0100999 if (talk_handle <= 0 || index_handle <= 0) /* reload needed? */
Thomas Martitz22e802e2013-05-30 11:24:16 +02001000 {
1001 int fd = open_voicefile();
Thomas Martitzaf02a672014-02-02 16:58:28 +01001002 if (fd < 0 || !load_voicefile_index(fd))
Thomas Martitz22e802e2013-05-30 11:24:16 +02001003 return -1;
Thomas Martitzaf02a672014-02-02 16:58:28 +01001004 load_voicefile_data(fd);
1005 close(fd);
Thomas Martitz22e802e2013-05-30 11:24:16 +02001006 }
Jörg Hohensohnfa97f162004-03-19 22:15:53 +00001007
Jörg Hohensohnf9495cb2004-04-03 20:52:24 +00001008 if (id == -1) /* -1 is an indication for silence */
1009 return -1;
1010
Nils Wallménius05d2bfd2008-04-19 13:19:04 +00001011 decimals = (((uint32_t)id) >> DECIMAL_SHIFT) & 0x7;
Nils Wallméniuse510bc92008-04-19 13:35:23 +00001012
Jörg Hohensohnfa97f162004-03-19 22:15:53 +00001013 /* check if this is a special ID, with a value */
Nils Wallméniusacbd7802007-11-20 19:50:52 +00001014 unit = ((uint32_t)id) >> UNIT_SHIFT;
Nils Wallménius05d2bfd2008-04-19 13:19:04 +00001015 if (unit || decimals)
Jörg Hohensohnfa97f162004-03-19 22:15:53 +00001016 { /* sign-extend the value */
Nils Wallménius05d2bfd2008-04-19 13:19:04 +00001017 id = (uint32_t)id << (32-DECIMAL_SHIFT);
1018 id >>= (32-DECIMAL_SHIFT);
1019
1020 talk_value_decimal(id, unit, decimals, enqueue); /* speak it */
Jörg Hohensohnfa97f162004-03-19 22:15:53 +00001021 return 0; /* and stop, end of special case */
1022 }
1023
Thomas Martitzc46f9be2013-11-21 11:44:04 +01001024 if (get_clip(id, &clip) < 0)
Jörg Hohensohn24e6dff2004-11-17 22:30:38 +00001025 return -1; /* not present */
Jörg Hohensohna7aa17a2004-10-12 16:43:22 +00001026
Jeffrey Goodefe0f5ba2010-05-26 03:11:00 +00001027#ifdef LOGF_ENABLE
1028 if (id > VOICEONLY_DELIMITER)
Jeffrey Goode9e288632010-05-26 03:51:13 +00001029 logf("\ntalk_id: Say voice clip 0x%x\n", id);
Jeffrey Goodefe0f5ba2010-05-26 03:11:00 +00001030 else
1031 logf("\ntalk_id: Say '%s'\n", str(id));
1032#endif
1033
Thomas Martitzc46f9be2013-11-21 11:44:04 +01001034 queue_clip(&clip, enqueue);
Jörg Hohensohnfa97f162004-03-19 22:15:53 +00001035
1036 return 0;
1037}
Nils Wallménius5b769362007-08-06 13:08:36 +00001038/* Speaks zero or more IDs (from an array). */
Nils Wallméniusb82fd562008-09-29 16:29:51 +00001039int talk_idarray(const long *ids, bool enqueue)
Nils Wallménius5b769362007-08-06 13:08:36 +00001040{
1041 int r;
1042 if(!ids)
1043 return 0;
1044 while(*ids != TALK_FINAL_ID)
1045 {
1046 if((r = talk_id(*ids++, enqueue)) <0)
1047 return r;
1048 enqueue = true;
1049 }
1050 return 0;
1051}
1052
1053/* Make sure the current utterance is not interrupted by the next one. */
1054void talk_force_enqueue_next(void)
1055{
1056 force_enqueue_next = true;
1057}
Jörg Hohensohnfa97f162004-03-19 22:15:53 +00001058
1059/* play a thumbnail from file */
Stéphane Doyon3eb76d02008-07-15 14:55:31 +00001060/* Returns size of spoken thumbnail, so >0 means something is spoken,
1061 <=0 means something went wrong. */
Nils Wallméniusb82fd562008-09-29 16:29:51 +00001062static int _talk_file(const char* filename,
1063 const long *prefix_ids, bool enqueue)
Jörg Hohensohnfa97f162004-03-19 22:15:53 +00001064{
1065 int fd;
1066 int size;
Thomas Martitzaf02a672014-02-02 16:58:28 +01001067 int handle, oldest = -1;
Michael Sevakis99617d72007-11-18 17:12:19 +00001068#if CONFIG_CODEC != SWCODEC
Jörg Hohensohn3aa99e12004-03-27 00:11:01 +00001069 struct mp3entry info;
Michael Sevakis99617d72007-11-18 17:12:19 +00001070#endif
Jörg Hohensohnfa97f162004-03-19 22:15:53 +00001071
Thomas Martitzc46f9be2013-11-21 11:44:04 +01001072 /* reload needed? */
Steve Bavin32a95752007-10-19 15:31:42 +00001073 if (talk_temp_disable_count > 0)
1074 return -1; /* talking has been disabled */
Thomas Martitz22e802e2013-05-30 11:24:16 +02001075 if (!check_audio_status())
1076 return -1;
Solomon Peachy3b7ae5e2019-08-04 22:46:09 -04001077 if (talk_handle <= 0 || index_handle <= 0)
1078 {
1079 int fd = open_voicefile();
1080 if (fd < 0 || !load_voicefile_index(fd))
1081 return -1;
1082 load_voicefile_data(fd);
1083 close(fd);
1084 }
Jörg Hohensohnfa97f162004-03-19 22:15:53 +00001085
Michael Sevakis99617d72007-11-18 17:12:19 +00001086#if CONFIG_CODEC != SWCODEC
Thom Johansen294ec1d2007-09-19 10:40:55 +00001087 if(mp3info(&info, filename)) /* use this to find real start */
Jörg Hohensohn3aa99e12004-03-27 00:11:01 +00001088 {
1089 return 0; /* failed to open, or invalid */
1090 }
Michael Sevakis99617d72007-11-18 17:12:19 +00001091#endif
Jörg Hohensohn3aa99e12004-03-27 00:11:01 +00001092
Stéphane Doyonc893aff2008-07-15 14:06:11 +00001093 if (!enqueue)
1094 /* shutup now to free the thumbnail buffer */
1095 talk_shutup();
1096
Jörg Hohensohnfa97f162004-03-19 22:15:53 +00001097 fd = open(filename, O_RDONLY);
1098 if (fd < 0) /* failed to open */
1099 {
1100 return 0;
1101 }
Thomas Martitzaf02a672014-02-02 16:58:28 +01001102 size = filesize(fd);
Stéphane Doyonc893aff2008-07-15 14:06:11 +00001103
Michael Sevakis99617d72007-11-18 17:12:19 +00001104#if CONFIG_CODEC != SWCODEC
Thomas Martitzaf02a672014-02-02 16:58:28 +01001105 size -= lseek(fd, info.first_frame_offset, SEEK_SET); /* behind ID data */
Michael Sevakis99617d72007-11-18 17:12:19 +00001106#endif
Jörg Hohensohn3aa99e12004-03-27 00:11:01 +00001107
Thomas Martitzaf02a672014-02-02 16:58:28 +01001108 /* free clips from cache until this one succeeds to allocate */
1109 while ((handle = buflib_alloc(&clip_ctx, size)) < 0)
1110 oldest = free_oldest_clip();
1111
1112 size = read_to_handle_ex(fd, &clip_ctx, handle, 0, size);
Jörg Hohensohnfa97f162004-03-19 22:15:53 +00001113 close(fd);
1114
1115 /* ToDo: find audio, skip ID headers and trailers */
1116
Stéphane Doyonc893aff2008-07-15 14:06:11 +00001117 if (size > 0) /* Don't play missing clips */
Jörg Hohensohnfa97f162004-03-19 22:15:53 +00001118 {
Thomas Martitzc46f9be2013-11-21 11:44:04 +01001119 struct queue_entry clip;
Thomas Martitzaf02a672014-02-02 16:58:28 +01001120 clip.handle = handle;
1121 clip.length = clip.remaining = size;
Jonathan Gordonc14430a2007-11-07 09:28:07 +00001122#if CONFIG_CODEC != SWCODEC && !defined(SIMULATOR)
Thomas Martitz22e802e2013-05-30 11:24:16 +02001123 /* bitswap doesnt yield() */
Thomas Martitzaf02a672014-02-02 16:58:28 +01001124 bitswap(buflib_get_data(&clip_ctx, handle), size);
Linus Nielsen Feltzinga12ccab2005-06-06 00:34:07 +00001125#endif
Stéphane Doyon3eb76d02008-07-15 14:55:31 +00001126 if(prefix_ids)
1127 /* prefix thumbnail by speaking these ids, but only now
1128 that we know there's actually a thumbnail to be
1129 spoken. */
1130 talk_idarray(prefix_ids, true);
Thomas Martitzaf02a672014-02-02 16:58:28 +01001131 /* finally insert into metadata table. thumb clips go under the
1132 * VOICEONLY_DELIMITER id so the cache can distinguish them from
1133 * normal clips */
1134 add_cache_entry(handle, oldest, VOICEONLY_DELIMITER);
Thomas Martitzc46f9be2013-11-21 11:44:04 +01001135 queue_clip(&clip, true);
Jörg Hohensohnfa97f162004-03-19 22:15:53 +00001136 }
Thomas Martitzaf02a672014-02-02 16:58:28 +01001137 else
1138 buflib_free(&clip_ctx, handle);
Jörg Hohensohnfa97f162004-03-19 22:15:53 +00001139
1140 return size;
1141}
1142
Nils Wallméniusb82fd562008-09-29 16:29:51 +00001143int talk_file(const char *root, const char *dir, const char *file,
1144 const char *ext, const long *prefix_ids, bool enqueue)
Stéphane Doyon3eb76d02008-07-15 14:55:31 +00001145/* Play a thumbnail file */
1146{
1147 char buf[MAX_PATH];
1148 /* Does root end with a slash */
1149 char *slash = (root && root[0]
1150 && root[strlen(root)-1] != '/') ? "/" : "";
1151 snprintf(buf, MAX_PATH, "%s%s%s%s%s%s",
1152 root ? root : "", slash,
1153 dir ? dir : "", dir ? "/" : "",
1154 file ? file : "",
1155 ext ? ext : "");
1156 return _talk_file(buf, prefix_ids, enqueue);
1157}
1158
Nils Wallméniusb82fd562008-09-29 16:29:51 +00001159static int talk_spell_basename(const char *path,
1160 const long *prefix_ids, bool enqueue)
Stéphane Doyon3eb76d02008-07-15 14:55:31 +00001161{
1162 if(prefix_ids)
1163 {
1164 talk_idarray(prefix_ids, enqueue);
1165 enqueue = true;
1166 }
1167 char buf[MAX_PATH];
1168 /* Spell only the path component after the last slash */
Nils Wallménius3d4701a2009-07-14 13:57:45 +00001169 strlcpy(buf, path, sizeof(buf));
Stéphane Doyon3eb76d02008-07-15 14:55:31 +00001170 if(strlen(buf) >1 && buf[strlen(buf)-1] == '/')
1171 /* strip trailing slash */
1172 buf[strlen(buf)-1] = '\0';
1173 char *ptr = strrchr(buf, '/');
1174 if(ptr && strlen(buf) >1)
1175 ++ptr;
1176 else ptr = buf;
1177 return talk_spell(ptr, enqueue);
1178}
1179
1180/* Play a file's .talk thumbnail, fallback to spelling the filename, or
1181 go straight to spelling depending on settings. */
1182int talk_file_or_spell(const char *dirname, const char *filename,
Nils Wallméniusb82fd562008-09-29 16:29:51 +00001183 const long *prefix_ids, bool enqueue)
Stéphane Doyon3eb76d02008-07-15 14:55:31 +00001184{
1185 if (global_settings.talk_file_clip)
1186 { /* .talk clips enabled */
1187 if(talk_file(dirname, NULL, filename, file_thumbnail_ext,
1188 prefix_ids, enqueue) >0)
1189 return 0;
1190 }
1191 if (global_settings.talk_file == 2)
1192 /* Either .talk clips are disabled, or as a fallback */
1193 return talk_spell_basename(filename, prefix_ids, enqueue);
1194 return 0;
1195}
1196
1197/* Play a directory's .talk thumbnail, fallback to spelling the filename, or
1198 go straight to spelling depending on settings. */
1199int talk_dir_or_spell(const char* dirname,
Nils Wallméniusb82fd562008-09-29 16:29:51 +00001200 const long *prefix_ids, bool enqueue)
Stéphane Doyon3eb76d02008-07-15 14:55:31 +00001201{
1202 if (global_settings.talk_dir_clip)
1203 { /* .talk clips enabled */
1204 if(talk_file(dirname, NULL, dir_thumbnail_name, NULL,
1205 prefix_ids, enqueue) >0)
1206 return 0;
1207 }
1208 if (global_settings.talk_dir == 2)
1209 /* Either .talk clips disabled or as a fallback */
1210 return talk_spell_basename(dirname, prefix_ids, enqueue);
1211 return 0;
1212}
1213
Solomon Peachy4adad0b2018-10-30 09:45:26 -04001214/* Speak thumbnail for each component of a full path, again falling
1215 back or going straight to spelling depending on settings. */
1216int talk_fullpath(const char* path, bool enqueue)
1217{
1218 if (!enqueue)
1219 talk_shutup();
1220 if(path[0] != '/')
1221 /* path ought to start with /... */
1222 return talk_spell(path, true);
1223 talk_id(VOICE_CHAR_SLASH, true);
1224 char buf[MAX_PATH];
1225 strlcpy(buf, path, MAX_PATH);
1226 char *start = buf+1; /* start of current component */
1227 char *ptr = strchr(start, '/'); /* end of current component */
1228 while(ptr) { /* There are more slashes ahead */
1229 /* temporarily poke a NULL at end of component to truncate string */
1230 *ptr = '\0';
1231 talk_dir_or_spell(buf, NULL, true);
1232 *ptr = '/'; /* restore string */
1233 talk_id(VOICE_CHAR_SLASH, true);
1234 start = ptr+1; /* setup for next component */
1235 ptr = strchr(start, '/');
1236 }
1237 /* no more slashes, final component is a filename */
1238 return talk_file_or_spell(NULL, buf, NULL, true);
1239}
1240
Jörg Hohensohnbeec2e92004-03-20 16:49:58 +00001241/* say a numeric value, this word ordering works for english,
1242 but not necessarily for other languages (e.g. german) */
Jean-Philippe Bernardy45e0de32005-02-12 11:26:36 +00001243int talk_number(long n, bool enqueue)
Jörg Hohensohnfa97f162004-03-19 22:15:53 +00001244{
Jens Arnold2311eea2006-05-25 12:45:57 +00001245 int level = 2; /* mille count */
Jean-Philippe Bernardy45e0de32005-02-12 11:26:36 +00001246 long mil = 1000000000; /* highest possible "-illion" */
Jörg Hohensohnfa97f162004-03-19 22:15:53 +00001247
Steve Bavin32a95752007-10-19 15:31:42 +00001248 if (talk_temp_disable_count > 0)
1249 return -1; /* talking has been disabled */
Thomas Martitz22e802e2013-05-30 11:24:16 +02001250 if (!check_audio_status())
1251 return -1;
Linus Nielsen Feltzing9e11ff82004-03-22 12:34:06 +00001252
Jörg Hohensohnfa97f162004-03-19 22:15:53 +00001253 if (!enqueue)
Steve Bavin32a95752007-10-19 15:31:42 +00001254 talk_shutup(); /* cut off all the pending stuff */
Jörg Hohensohnfa97f162004-03-19 22:15:53 +00001255
1256 if (n==0)
Jörg Hohensohnbeec2e92004-03-20 16:49:58 +00001257 { /* special case */
Jörg Hohensohnfa97f162004-03-19 22:15:53 +00001258 talk_id(VOICE_ZERO, true);
1259 return 0;
1260 }
1261
1262 if (n<0)
1263 {
1264 talk_id(VOICE_MINUS, true);
1265 n = -n;
1266 }
1267
1268 while (n)
1269 {
Jörg Hohensohnbeec2e92004-03-20 16:49:58 +00001270 int segment = n / mil; /* extract in groups of 3 digits */
1271 n -= segment * mil; /* remove the used digits from number */
1272 mil /= 1000; /* digit place for next round */
Jörg Hohensohnfa97f162004-03-19 22:15:53 +00001273
1274 if (segment)
1275 {
1276 int hundreds = segment / 100;
1277 int ones = segment % 100;
1278
1279 if (hundreds)
1280 {
1281 talk_id(VOICE_ZERO + hundreds, true);
1282 talk_id(VOICE_HUNDRED, true);
1283 }
1284
Jörg Hohensohnbeec2e92004-03-20 16:49:58 +00001285 /* combination indexing */
Jörg Hohensohnfa97f162004-03-19 22:15:53 +00001286 if (ones > 20)
1287 {
1288 int tens = ones/10 + 18;
1289 talk_id(VOICE_ZERO + tens, true);
1290 ones %= 10;
1291 }
1292
Jörg Hohensohnbeec2e92004-03-20 16:49:58 +00001293 /* direct indexing */
Jörg Hohensohnfa97f162004-03-19 22:15:53 +00001294 if (ones)
1295 talk_id(VOICE_ZERO + ones, true);
1296
Jörg Hohensohnbeec2e92004-03-20 16:49:58 +00001297 /* add billion, million, thousand */
Jörg Hohensohnfa97f162004-03-19 22:15:53 +00001298 if (mil)
Jens Arnold2311eea2006-05-25 12:45:57 +00001299 talk_id(VOICE_THOUSAND + level, true);
Jörg Hohensohnfa97f162004-03-19 22:15:53 +00001300 }
Jens Arnold2311eea2006-05-25 12:45:57 +00001301 level--;
Jörg Hohensohnfa97f162004-03-19 22:15:53 +00001302 }
1303
1304 return 0;
1305}
1306
Solomon Peachy4adad0b2018-10-30 09:45:26 -04001307/* Say year like "nineteen ninety nine" instead of "one thousand 9
1308 hundred ninety nine". */
1309static int talk_year(long year, bool enqueue)
1310{
1311 int rem;
1312 if(year < 1100 || year >=2000)
1313 /* just say it as a regular number */
1314 return talk_number(year, enqueue);
1315 /* Say century */
1316 talk_number(year/100, enqueue);
1317 rem = year%100;
1318 if(rem == 0)
1319 /* as in 1900 */
1320 return talk_id(VOICE_HUNDRED, true);
1321 if(rem <10)
1322 /* as in 1905 */
1323 talk_id(VOICE_ZERO, true);
1324 /* sub-century year */
1325 return talk_number(rem, true);
1326}
1327
Stéphane Doyon8b8785b2007-11-03 03:43:12 +00001328/* Say time duration/interval. Input is time in seconds,
1329 say hours,minutes,seconds. */
Andree Buschmann460d5492011-02-20 15:23:18 +00001330static int talk_time_unit(long secs, bool enqueue)
Stéphane Doyon8b8785b2007-11-03 03:43:12 +00001331{
William Wilgusa06d9c82018-12-17 22:27:55 -06001332 return talk_time_intervals(secs, UNIT_SEC, enqueue);
Stéphane Doyon8b8785b2007-11-03 03:43:12 +00001333}
1334
Nils Wallménius2521bf52008-04-20 11:02:42 +00001335void talk_fractional(char *tbuf, int value, int unit)
1336{
1337 int i;
1338 /* strip trailing zeros from the fraction */
1339 for (i = strlen(tbuf) - 1; (i >= 0) && (tbuf[i] == '0'); i--)
1340 tbuf[i] = '\0';
1341
1342 talk_number(value, true);
1343 if (tbuf[0] != 0)
1344 {
1345 talk_id(LANG_POINT, true);
1346 talk_spell(tbuf, true);
1347 }
1348 talk_id(unit, true);
1349}
1350
Jean-Philippe Bernardy45e0de32005-02-12 11:26:36 +00001351int talk_value(long n, int unit, bool enqueue)
Jörg Hohensohnfa97f162004-03-19 22:15:53 +00001352{
Nils Wallménius05d2bfd2008-04-19 13:19:04 +00001353 return talk_value_decimal(n, unit, 0, enqueue);
1354}
1355
1356/* singular/plural aware saying of a value */
1357int talk_value_decimal(long n, int unit, int decimals, bool enqueue)
1358{
Jörg Hohensohnfa97f162004-03-19 22:15:53 +00001359 int unit_id;
Jens Arnold033ba162004-08-26 20:30:22 +00001360 static const int unit_voiced[] =
Jörg Hohensohnfa97f162004-03-19 22:15:53 +00001361 { /* lookup table for the voice ID of the units */
Michael Sevakis287deb02006-09-26 17:44:43 +00001362 [0 ... UNIT_LAST-1] = -1, /* regular ID, int, signed */
1363 [UNIT_MS]
1364 = VOICE_MILLISECONDS, /* here come the "real" units */
1365 [UNIT_SEC]
1366 = VOICE_SECONDS,
1367 [UNIT_MIN]
1368 = VOICE_MINUTES,
1369 [UNIT_HOUR]
1370 = VOICE_HOURS,
1371 [UNIT_KHZ]
1372 = VOICE_KHZ,
1373 [UNIT_DB]
1374 = VOICE_DB,
1375 [UNIT_PERCENT]
1376 = VOICE_PERCENT,
1377 [UNIT_MAH]
1378 = VOICE_MILLIAMPHOURS,
1379 [UNIT_PIXEL]
1380 = VOICE_PIXEL,
1381 [UNIT_PER_SEC]
1382 = VOICE_PER_SEC,
1383 [UNIT_HERTZ]
1384 = VOICE_HERTZ,
1385 [UNIT_MB]
William Wilgus62a5ed42018-12-09 12:09:40 -06001386 = LANG_MEBIBYTE,
Michael Sevakis287deb02006-09-26 17:44:43 +00001387 [UNIT_KBIT]
1388 = VOICE_KBIT_PER_SEC,
Peter D'Hoyeebcf06d2007-08-18 23:03:03 +00001389 [UNIT_PM_TICK]
1390 = VOICE_PM_UNITS_PER_TICK,
Jörg Hohensohnfa97f162004-03-19 22:15:53 +00001391 };
1392
Nils Wallménius2521bf52008-04-20 11:02:42 +00001393 static const int pow10[] = { /* 10^0 - 10^7 */
1394 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000
1395 };
1396
1397 char tbuf[8];
Nils Wallméniusdbffb912008-04-20 14:07:21 +00001398 char fmt[] = "%0nd";
Nils Wallménius2521bf52008-04-20 11:02:42 +00001399
Steve Bavin32a95752007-10-19 15:31:42 +00001400 if (talk_temp_disable_count > 0)
1401 return -1; /* talking has been disabled */
Thomas Martitz22e802e2013-05-30 11:24:16 +02001402 if (!check_audio_status())
1403 return -1;
Linus Nielsen Feltzing9e11ff82004-03-22 12:34:06 +00001404
Solomon Peachy4adad0b2018-10-30 09:45:26 -04001405 /* special pronounciation for year number */
1406 if (unit == UNIT_DATEYEAR)
1407 return talk_year(n, enqueue);
Stéphane Doyon8b8785b2007-11-03 03:43:12 +00001408 /* special case for time duration */
Andree Buschmann460d5492011-02-20 15:23:18 +00001409 if (unit == UNIT_TIME)
1410 return talk_time_unit(n, enqueue);
Stéphane Doyon8b8785b2007-11-03 03:43:12 +00001411
Jörg Hohensohnfa97f162004-03-19 22:15:53 +00001412 if (unit < 0 || unit >= UNIT_LAST)
1413 unit_id = -1;
1414 else
1415 unit_id = unit_voiced[unit];
1416
Jörg Hohensohnbeec2e92004-03-20 16:49:58 +00001417 if ((n==1 || n==-1) /* singular? */
Jörg Hohensohnfa97f162004-03-19 22:15:53 +00001418 && unit_id >= VOICE_SECONDS && unit_id <= VOICE_HOURS)
1419 {
1420 unit_id--; /* use the singular for those units which have */
1421 }
1422
1423 /* special case with a "plus" before */
1424 if (n > 0 && (unit == UNIT_SIGNED || unit == UNIT_DB))
1425 {
1426 talk_id(VOICE_PLUS, enqueue);
1427 enqueue = true;
1428 }
1429
Nils Wallménius05d2bfd2008-04-19 13:19:04 +00001430 if (decimals)
1431 {
1432 /* needed for the "-0.5" corner case */
1433 if (n < 0)
1434 {
1435 talk_id(VOICE_MINUS, enqueue);
1436 n = -n;
Nils Wallménius05d2bfd2008-04-19 13:19:04 +00001437 }
1438
Nils Wallméniusdbffb912008-04-20 14:07:21 +00001439 fmt[2] = '0' + decimals;
1440
1441 snprintf(tbuf, sizeof(tbuf), fmt, n % pow10[decimals]);
Nils Wallménius2521bf52008-04-20 11:02:42 +00001442 talk_fractional(tbuf, n / pow10[decimals], unit_id);
1443
1444 return 0;
Nils Wallménius05d2bfd2008-04-19 13:19:04 +00001445 }
1446
Jörg Hohensohnfa97f162004-03-19 22:15:53 +00001447 talk_number(n, enqueue); /* say the number */
1448 talk_id(unit_id, true); /* say the unit, if any */
Nils Wallménius2521bf52008-04-20 11:02:42 +00001449
Jörg Hohensohnfa97f162004-03-19 22:15:53 +00001450 return 0;
1451}
1452
William Wilgusa06d9c82018-12-17 22:27:55 -06001453/* Say time duration/interval. Input is time unit specifies base unit,
1454 say hours,minutes,seconds, milliseconds. or any combination thereof */
1455int talk_time_intervals(long time, int unit_idx, bool enqueue)
1456{
1457 unsigned long units_in[UNIT_IDX_TIME_COUNT];
1458
1459 if (talk_temp_disable_count > 0)
1460 return -1; /* talking has been disabled */
1461 if (!check_audio_status())
1462 return -1;
1463
1464 if (talk_handle <= 0 || index_handle <= 0) /* reload needed? */
1465 {
1466 int fd = open_voicefile();
1467 if (fd < 0 || !load_voicefile_index(fd))
1468 return -1;
1469 load_voicefile_data(fd);
1470 close(fd);
1471 }
1472
1473 if (!enqueue)
1474 talk_shutup(); /* cut off all the pending stuff */
1475
1476 time_split_units(unit_idx, labs(time), &units_in);
1477
1478 if (time < 0)
1479 talk_id(VOICE_MINUS, true);
1480
1481 if (time == 0)
1482 talk_value(0, unit_idx, true);
1483 else
1484 {
1485 if (units_in[UNIT_IDX_HR] != 0)
1486 {
1487 talk_value(units_in[UNIT_IDX_HR], UNIT_HOUR, true);
1488 }
1489 if (units_in[UNIT_IDX_MIN] != 0)
1490 {
1491 talk_value(units_in[UNIT_IDX_MIN], UNIT_MIN, true);
1492 }
1493 if (units_in[UNIT_IDX_SEC] != 0)
1494 {
1495 talk_value(units_in[UNIT_IDX_SEC], UNIT_SEC, true);
1496 }
1497 if (units_in[UNIT_IDX_MS] != 0)
1498 {
1499 talk_value(units_in[UNIT_IDX_MS], UNIT_MS, true);
1500 }
1501 }
1502
1503 return -1;
1504}
1505
Jörg Hohensohnc8592ba2004-04-04 19:08:44 +00001506/* spell a string */
Jens Arnold033ba162004-08-26 20:30:22 +00001507int talk_spell(const char* spell, bool enqueue)
Jörg Hohensohnc8592ba2004-04-04 19:08:44 +00001508{
1509 char c; /* currently processed char */
1510
Steve Bavin32a95752007-10-19 15:31:42 +00001511 if (talk_temp_disable_count > 0)
1512 return -1; /* talking has been disabled */
Thomas Martitz22e802e2013-05-30 11:24:16 +02001513 if (!check_audio_status())
1514 return -1;
Jörg Hohensohnc8592ba2004-04-04 19:08:44 +00001515
1516 if (!enqueue)
Steve Bavin32a95752007-10-19 15:31:42 +00001517 talk_shutup(); /* cut off all the pending stuff */
Jörg Hohensohnc8592ba2004-04-04 19:08:44 +00001518
1519 while ((c = *spell++) != '\0')
1520 {
1521 /* if this grows into too many cases, I should use a table */
1522 if (c >= 'A' && c <= 'Z')
1523 talk_id(VOICE_CHAR_A + c - 'A', true);
1524 else if (c >= 'a' && c <= 'z')
1525 talk_id(VOICE_CHAR_A + c - 'a', true);
1526 else if (c >= '0' && c <= '9')
1527 talk_id(VOICE_ZERO + c - '0', true);
1528 else if (c == '-')
1529 talk_id(VOICE_MINUS, true);
1530 else if (c == '+')
1531 talk_id(VOICE_PLUS, true);
1532 else if (c == '.')
Jörg Hohensohnae34f642004-10-25 20:44:37 +00001533 talk_id(VOICE_DOT, true);
Jörg Hohensohnde79e682004-10-23 17:58:38 +00001534 else if (c == ' ')
1535 talk_id(VOICE_PAUSE, true);
Stéphane Doyon3eb76d02008-07-15 14:55:31 +00001536 else if (c == '/')
1537 talk_id(VOICE_CHAR_SLASH, true);
Jörg Hohensohnc8592ba2004-04-04 19:08:44 +00001538 }
1539
1540 return 0;
1541}
Steve Bavin24d9f592007-06-11 08:28:38 +00001542
Steve Bavin32a95752007-10-19 15:31:42 +00001543void talk_disable(bool disable)
Steve Bavin24d9f592007-06-11 08:28:38 +00001544{
Steve Bavin32a95752007-10-19 15:31:42 +00001545 if (disable)
1546 talk_temp_disable_count++;
1547 else
1548 talk_temp_disable_count--;
Steve Bavin24d9f592007-06-11 08:28:38 +00001549}
Jonathan Gordond7d6b782007-10-07 08:12:01 +00001550
Steve Bavincd88e2a2008-03-25 15:24:03 +00001551void talk_setting(const void *global_settings_variable)
1552{
1553 const struct settings_list *setting;
1554 if (!global_settings.talk_menu)
1555 return;
1556 setting = find_setting(global_settings_variable, NULL);
1557 if (setting == NULL)
1558 return;
1559 if (setting->lang_id)
1560 talk_id(setting->lang_id,false);
1561}
1562
1563
Nils Wallméniusb82fd562008-09-29 16:29:51 +00001564void talk_date(const struct tm *tm, bool enqueue)
Jonathan Gordond7d6b782007-10-07 08:12:01 +00001565{
Steve Bavin072a3c52007-10-24 12:32:12 +00001566 talk_id(LANG_MONTH_JANUARY + tm->tm_mon, enqueue);
1567 talk_number(tm->tm_mday, true);
1568 talk_number(1900 + tm->tm_year, true);
1569}
1570
Nils Wallméniusb82fd562008-09-29 16:29:51 +00001571void talk_time(const struct tm *tm, bool enqueue)
Steve Bavin072a3c52007-10-24 12:32:12 +00001572{
1573 if (global_settings.timeformat == 1)
Jonathan Gordond7d6b782007-10-07 08:12:01 +00001574 {
Steve Bavin072a3c52007-10-24 12:32:12 +00001575 /* Voice the hour */
1576 long am_pm_id = VOICE_AM;
1577 int hour = tm->tm_hour;
1578 if (hour >= 12)
Jonathan Gordond7d6b782007-10-07 08:12:01 +00001579 {
Steve Bavin072a3c52007-10-24 12:32:12 +00001580 am_pm_id = VOICE_PM;
1581 hour -= 12;
1582 }
1583 if (hour == 0)
1584 hour = 12;
1585 talk_number(hour, enqueue);
Jonathan Gordond7d6b782007-10-07 08:12:01 +00001586
Steve Bavin072a3c52007-10-24 12:32:12 +00001587 /* Voice the minutes */
1588 if (tm->tm_min == 0)
1589 {
1590 /* Say o'clock if the minute is 0. */
1591 talk_id(VOICE_OCLOCK, true);
Jonathan Gordond7d6b782007-10-07 08:12:01 +00001592 }
1593 else
1594 {
Steve Bavin072a3c52007-10-24 12:32:12 +00001595 /* Pronounce the leading 0 */
1596 if(tm->tm_min < 10)
1597 talk_id(VOICE_OH, true);
1598 talk_number(tm->tm_min, true);
Jonathan Gordond7d6b782007-10-07 08:12:01 +00001599 }
Steve Bavin072a3c52007-10-24 12:32:12 +00001600 talk_id(am_pm_id, true);
1601 }
1602 else
1603 {
1604 /* Voice the time in 24 hour format */
1605 talk_number(tm->tm_hour, enqueue);
1606 if (tm->tm_min == 0)
Solomon Peachy4adad0b2018-10-30 09:45:26 -04001607 talk_ids(true, VOICE_HUNDRED, VOICE_HOUR);
Steve Bavin072a3c52007-10-24 12:32:12 +00001608 else
1609 {
1610 /* Pronounce the leading 0 */
1611 if(tm->tm_min < 10)
1612 talk_id(VOICE_OH, true);
1613 talk_number(tm->tm_min, true);
1614 }
Jonathan Gordond7d6b782007-10-07 08:12:01 +00001615 }
1616}
Steve Bavin072a3c52007-10-24 12:32:12 +00001617
Thomas Martitzdac40fd2013-07-02 08:24:00 +02001618bool talk_get_debug_data(struct talk_debug_data *data)
1619{
1620 char* p_lang = DEFAULT_VOICE_LANG; /* default */
Thomas Martitzaf02a672014-02-02 16:58:28 +01001621 struct clip_cache_metadata *cc;
Thomas Martitzdac40fd2013-07-02 08:24:00 +02001622
1623 memset(data, 0, sizeof(*data));
1624
1625 if (!has_voicefile || index_handle <= 0)
1626 return false;
1627
1628 if (global_settings.lang_file[0] && global_settings.lang_file[0] != 0xff)
1629 p_lang = (char *)global_settings.lang_file;
1630
1631 struct clip_entry *clips = core_get_data(index_handle);
Thomas Martitzdac40fd2013-07-02 08:24:00 +02001632 int cached = 0;
Thomas Martitzdac40fd2013-07-02 08:24:00 +02001633 int real_clips = 0;
1634
1635 strlcpy(data->voicefile, p_lang, sizeof(data->voicefile));
1636 data->num_clips = voicefile.id1_max + voicefile.id2_max;
1637 data->avg_clipsize = data->max_clipsize = 0;
1638 data->min_clipsize = INT_MAX;
1639 for(int i = 0; i < data->num_clips; i++)
1640 {
1641 int size = clips[i].size & (~LOADED_MASK);
1642 if (!size) continue;
1643 real_clips += 1;
1644 if (size < data->min_clipsize)
1645 data->min_clipsize = size;
1646 if (size > data->max_clipsize)
1647 data->max_clipsize = size;
1648 data->avg_clipsize += size;
Thomas Martitzaf02a672014-02-02 16:58:28 +01001649 }
1650 cc = buflib_get_data(&clip_ctx, metadata_table_handle);
1651 for (int i = 0; i < (int) max_clips; i++)
1652 {
1653 if (cc[i].handle > 0)
1654 cached += 1;
Thomas Martitzdac40fd2013-07-02 08:24:00 +02001655 }
1656 data->avg_clipsize /= real_clips;
1657 data->num_empty_clips = data->num_clips - real_clips;
Thomas Martitzc46f9be2013-11-21 11:44:04 +01001658 data->memory_allocated = sizeof(commit_buffer) + sizeof(voicefile)
1659 + data->num_clips * sizeof(struct clip_entry)
Thomas Martitzaf02a672014-02-02 16:58:28 +01001660 + voicebuf_size;
1661 data->memory_used = 0;
Thomas Martitzc46f9be2013-11-21 11:44:04 +01001662 if (talk_handle > 0)
Thomas Martitzaf02a672014-02-02 16:58:28 +01001663 data->memory_used = data->memory_allocated - buflib_available(&clip_ctx);
Thomas Martitzdac40fd2013-07-02 08:24:00 +02001664 data->cached_clips = cached;
1665 data->cache_hits = cache_hits;
1666 data->cache_misses = cache_misses;
Thomas Martitzdac40fd2013-07-02 08:24:00 +02001667
1668 return true;
1669}