1
2 /*
3 * ivona.c - Speech Dispatcher backend for Ivona (IVO Software)
4 *
5 * Copyright (C) 2001, 2002, 2003, 2007 Brailcom, o.p.s.
6 *
7 * This is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2, or (at your option)
10 * any later version.
11 *
12 * This software is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <https://www.gnu.org/licenses/>.
19 *
20 * $Id: ivona.c,v 1.3 2008-06-27 12:29:32 hanke Exp $
21 */
22
23 /* this file is strictly based on flite.c */
24
25 #ifdef HAVE_CONFIG_H
26 #include <config.h>
27 #endif
28
29 #include <semaphore.h>
30
31 #include <libdumbtts.h>
32 #include "spd_audio.h"
33
34 #include <speechd_types.h>
35
36 #include "module_utils.h"
37 #include "ivona_client.h"
38
39 #include <sndfile.h>
40
41 #define MODULE_NAME "ivona"
42 #define MODULE_VERSION "0.2"
43
44 #define DEBUG_MODULE 1
45 DECLARE_DEBUG();
46
47 /* Thread and process control */
48 static int ivona_speaking = 0;
49
50 static pthread_t ivona_speak_thread;
51 static sem_t ivona_semaphore;
52
53 static char *ivona_message;
54 static SPDMessageType ivona_message_type;
55
56 signed int ivona_volume = 0;
57 signed int ivona_cap_mode = 0;
58 int ivona_punct_mode = 0;
59
60 /* Internal functions prototypes */
61 static int ivona_get_msgpart(struct dumbtts_conf *conf, SPDMessageType type,
62 char **msg, char *icon, char **buf, int *len,
63 int cap_mode, char *delimeters, int punct_mode,
64 char *punct_some);
65 static void ivona_set_volume(signed int volume);
66 static void ivona_set_punctuation_mode(SPDPunctuation punct_mode);
67 static void ivona_set_cap_let_recogn(SPDCapitalLetters cap_mode);
68
69 static void *_ivona_speak(void *);
70
71 int ivona_stop = 0;
72
73 MOD_OPTION_1_STR(IvonaDelimiters);
74 MOD_OPTION_1_STR(IvonaPunctuationSome);
75 MOD_OPTION_1_INT(IvonaMinCapLet);
76 MOD_OPTION_1_STR(IvonaSoundIconPath);
77 MOD_OPTION_1_STR(IvonaServerHost);
78 MOD_OPTION_1_INT(IvonaServerPort);
79 MOD_OPTION_1_INT(IvonaSampleFreq);
80
81 MOD_OPTION_1_STR(IvonaSpeakerLanguage);
82 MOD_OPTION_1_STR(IvonaSpeakerName);
83
84 static struct dumbtts_conf *ivona_conf;
85
86 /* Public functions */
87
module_load(void)88 int module_load(void)
89 {
90 INIT_SETTINGS_TABLES();
91
92 REGISTER_DEBUG();
93
94 MOD_OPTION_1_STR_REG(IvonaDelimiters, ".;:,!?");
95 MOD_OPTION_1_INT_REG(IvonaMinCapLet, 0);
96 MOD_OPTION_1_STR_REG(IvonaSoundIconPath,
97 "/usr/share/sound/sound-icons/");
98
99 MOD_OPTION_1_STR_REG(IvonaServerHost, "127.0.0.1");
100 MOD_OPTION_1_INT_REG(IvonaServerPort, 9123);
101 MOD_OPTION_1_INT_REG(IvonaSampleFreq, 16000);
102
103 MOD_OPTION_1_STR_REG(IvonaSpeakerLanguage, "pl");
104 MOD_OPTION_1_STR_REG(IvonaSpeakerName, "Jacek");
105
106 MOD_OPTION_1_STR_REG(IvonaPunctuationSome, "()");
107 ivona_init_cache();
108
109 return 0;
110 }
111
112 #define ABORT(msg) g_string_append(info, msg); \
113 DBG("FATAL ERROR:", info->str); \
114 *status_info = info->str; \
115 g_string_free(info, 0); \
116 return -1;
117
module_init(char ** status_info)118 int module_init(char **status_info)
119 {
120 int ret;
121 GString *info;
122
123 DBG("Module init");
124
125 *status_info = NULL;
126 info = g_string_new("");
127
128 /* Init Ivona */
129 if (ivona_init_sock(IvonaServerHost, IvonaServerPort)) {
130 DBG("Couldn't init socket parameters");
131 *status_info = g_strdup("Can't initialize socket. "
132 "Check server host/port.");
133 return -1;
134 }
135 ivona_conf = dumbtts_TTSInit(IvonaSpeakerLanguage);
136
137 DBG("IvonaDelimiters = %s\n", IvonaDelimiters);
138
139 ivona_message = NULL;
140
141 sem_init(&ivona_semaphore, 0, 0);
142
143 DBG("Ivona: creating new thread for ivona_speak\n");
144 ivona_speaking = 0;
145 ret = pthread_create(&ivona_speak_thread, NULL, _ivona_speak, NULL);
146 if (ret != 0) {
147 DBG("Ivona: thread failed\n");
148 *status_info =
149 g_strdup("The module couldn't initialize threads "
150 "This could be either an internal problem or an "
151 "architecture problem. If you are sure your architecture "
152 "supports threads, please report a bug.");
153 return -1;
154 }
155
156 *status_info = g_strdup("Ivona initialized successfully.");
157
158 return 0;
159 }
160
161 #undef ABORT
162
163 static SPDVoice voice_jacek;
164 static SPDVoice *voice_ivona[] = { &voice_jacek, NULL };
165
module_list_voices(void)166 SPDVoice **module_list_voices(void)
167 {
168 voice_jacek.name = IvonaSpeakerName;
169 voice_jacek.language = IvonaSpeakerLanguage;
170 return voice_ivona;
171 }
172
module_speak(gchar * data,size_t bytes,SPDMessageType msgtype)173 int module_speak(gchar * data, size_t bytes, SPDMessageType msgtype)
174 {
175 DBG("write()\n");
176
177 if (ivona_speaking) {
178 DBG("Speaking when requested to write");
179 return 0;
180 }
181
182 DBG("Requested data: |%s|\n", data);
183
184 if (ivona_message != NULL) {
185 g_free(ivona_message);
186 ivona_message = NULL;
187 }
188 ivona_message = module_strip_ssml(data);
189 ivona_message_type = msgtype;
190 if ((msgtype == SPD_MSGTYPE_TEXT)
191 && (msg_settings.spelling_mode == SPD_SPELL_ON))
192 ivona_message_type = SPD_MSGTYPE_SPELL;
193
194 /* Setting voice */
195 UPDATE_PARAMETER(volume, ivona_set_volume);
196 UPDATE_PARAMETER(cap_let_recogn, ivona_set_cap_let_recogn);
197 UPDATE_PARAMETER(punctuation_mode, ivona_set_punctuation_mode);
198
199 /* Send semaphore signal to the speaking thread */
200 ivona_speaking = 1;
201 sem_post(&ivona_semaphore);
202
203 DBG("Ivona: leaving write() normally\n\r");
204 return bytes;
205 }
206
module_stop(void)207 int module_stop(void)
208 {
209 int ret;
210 DBG("ivona: stop()\n");
211
212 ivona_stop = 1;
213 if (module_audio_id) {
214 DBG("Stopping audio");
215 ret = spd_audio_stop(module_audio_id);
216 if (ret != 0)
217 DBG("WARNING: Non 0 value from spd_audio_stop: %d",
218 ret);
219 }
220
221 return 0;
222 }
223
module_pause(void)224 size_t module_pause(void)
225 {
226 DBG("pause requested\n");
227 if (ivona_speaking) {
228 DBG("Ivona doesn't support pause, stopping\n");
229
230 module_stop();
231
232 return -1;
233 } else {
234 return 0;
235 }
236 }
237
module_close(void)238 int module_close(void)
239 {
240
241 DBG("ivona: close()\n");
242
243 DBG("Stopping speech");
244 if (ivona_speaking) {
245 module_stop();
246 }
247
248 DBG("Terminating threads");
249 if (module_terminate_thread(ivona_speak_thread) != 0)
250 return -1;
251
252 sem_destroy(&ivona_semaphore);
253 return 0;
254 }
255
256 /* Internal functions */
get_unichar(char ** str)257 static int get_unichar(char **str)
258 {
259 wchar_t wc;
260 int n;
261 wc = *(*str)++ & 255;
262 if ((wc & 0xe0) == 0xc0) {
263 wc &= 0x1f;
264 n = 1;
265 } else if ((wc & 0xf0) == 0xe0) {
266 wc &= 0x0f;
267 n = 2;
268 } else if ((wc & 0xf8) == 0xf0) {
269 wc &= 0x07;
270 n = 3;
271 } else if ((wc & 0xfc) == 0xf8) {
272 wc &= 0x03;
273 n = 4;
274 } else if ((wc & 0xfe) == 0xfc) {
275 wc &= 0x01;
276 n = 5;
277 } else
278 return wc;
279 while (n--) {
280 if ((**str & 0xc0) != 0x80) {
281 wc = '?';
282 break;
283 }
284 wc = (wc << 6) | ((*(*str)++) & 0x3f);
285 }
286 return wc;
287 }
288
ivona_get_msgpart(struct dumbtts_conf * conf,SPDMessageType type,char ** msg,char * icon,char ** buf,int * len,int cap_mode,char * delimeters,int punct_mode,char * punct_some)289 static int ivona_get_msgpart(struct dumbtts_conf *conf, SPDMessageType type,
290 char **msg, char *icon, char **buf, int *len,
291 int cap_mode, char *delimeters, int punct_mode,
292 char *punct_some)
293 {
294 int rc;
295 int isicon;
296 int n, bytes;
297 unsigned int pos;
298 wchar_t wc;
299 char xbuf[1024];
300
301 if (!*msg)
302 return 1;
303 if (!**msg)
304 return 1;
305 isicon = 0;
306 icon[0] = 0;
307 if (*buf)
308 **buf = 0;
309 DBG("Ivona message %s type %d\n", *msg, type);
310 switch (type) {
311 case SPD_MSGTYPE_SOUND_ICON:
312 if (strlen(*msg) < 63) {
313 strcpy(icon, *msg);
314 rc = 0;
315 } else {
316 rc = 1;
317 }
318 *msg = NULL;
319 return rc;
320
321 case SPD_MSGTYPE_SPELL:
322 wc = get_unichar(msg);
323 if (!wc) {
324 *msg = NULL;
325 return 1;
326 }
327 n = dumbtts_WCharString(conf, wc, *buf, *len, cap_mode,
328 &isicon);
329 if (n > 0) {
330 *len = n + 128;
331 *buf = g_realloc(*buf, *len);
332 n = dumbtts_WCharString(conf, wc, *buf, *len, cap_mode,
333 &isicon);
334 }
335 if (n) {
336 *msg = NULL;
337 return 1;
338 }
339 if (isicon)
340 strcpy(icon, "capital");
341 return 0;
342
343 case SPD_MSGTYPE_KEY:
344 case SPD_MSGTYPE_CHAR:
345
346 if (type == SPD_MSGTYPE_KEY) {
347 /* TODO: make sure all SSIP cases are supported */
348 n = dumbtts_KeyString(conf, *msg, *buf, *len, cap_mode,
349 &isicon);
350 } else {
351 n = dumbtts_CharString(conf, *msg, *buf, *len, cap_mode,
352 &isicon);
353 }
354 DBG("Got n=%d", n);
355 if (n > 0) {
356 *len = n + 128;
357 *buf = g_realloc(*buf, *len);
358 if (type == SPD_MSGTYPE_KEY) {
359 n = dumbtts_KeyString(conf, *msg, *buf, *len,
360 cap_mode, &isicon);
361 } else {
362 n = dumbtts_CharString(conf, *msg, *buf, *len,
363 cap_mode, &isicon);
364 }
365 }
366 *msg = NULL;
367
368 if (!n && isicon)
369 strcpy(icon, "capital");
370 return n;
371
372 case SPD_MSGTYPE_TEXT:
373 pos = 0;
374 bytes =
375 module_get_message_part(*msg, xbuf, &pos, 1023, delimeters);
376 DBG("Got bytes %d, %s", bytes, xbuf);
377 if (bytes <= 0) {
378 *msg = NULL;
379 return 1;
380 }
381 *msg += pos;
382 xbuf[bytes] = 0;
383
384 n = dumbtts_GetString(conf, xbuf, *buf, *len, punct_mode,
385 punct_some, ",.;:!?");
386
387 if (n > 0) {
388 *len = n + 128;
389 *buf = g_realloc(*buf, *len);
390 n = dumbtts_GetString(conf, xbuf, *buf, *len,
391 punct_mode, punct_some, ",.;:!?");
392 }
393 if (n) {
394 *msg = NULL;
395 return 1;
396 }
397 DBG("Returning to Ivona |%s|", *buf);
398 return 0;
399
400 default:
401
402 *msg = NULL;
403 DBG("Unknown message type\n");
404 return 1;
405 }
406 }
407
_ivona_speak(void * nothing)408 void *_ivona_speak(void *nothing)
409 {
410 AudioTrack track;
411 char *buf;
412 int len;
413 char *msg, *audio;
414 char icon[64];
415 int samples, offset;
416 int fd;
417 char *next_audio;
418 int next_samples, next_offset;
419 char next_icon[64];
420 char next_cache[16];
421
422 DBG("ivona: speaking thread starting.......\n");
423
424 set_speaking_thread_parameters();
425
426 while (1) {
427 sem_wait(&ivona_semaphore);
428 DBG("Semaphore on\n");
429
430 ivona_stop = 0;
431 ivona_speaking = 1;
432
433 module_report_event_begin();
434 msg = ivona_message;
435 DBG("To say: %s\n", msg);
436 buf = NULL;
437 len = 0;
438 fd = -1;
439 audio = NULL;
440 next_audio = NULL;
441 next_icon[0] = 0;
442 while (1) {
443 if (ivona_stop) {
444 DBG("Stop in child, terminating");
445 ivona_speaking = 0;
446 module_report_event_stop();
447 break;
448 }
449 audio = NULL;
450 if (next_audio) {
451 audio = next_audio;
452 samples = next_samples;
453 offset = next_offset;
454 strcpy(icon, next_icon);
455 next_audio = NULL;
456 DBG("Got wave from next_audio");
457 } else if (fd >= 0) {
458 audio =
459 ivona_get_wave_fd(fd, &samples, &offset);
460 strcpy(icon, next_icon);
461 if (audio && next_cache[0]) {
462 ivona_store_wave_in_cache(next_cache,
463 audio +
464 2 * offset,
465 samples);
466 }
467
468 fd = -1;
469 DBG("Got wave from fd");
470 } else if (next_icon[0]) {
471 strcpy(icon, next_icon);
472 DBG("Got icon");
473 }
474 if (!audio && !icon[0]) {
475 if (!msg || !*msg
476 || ivona_get_msgpart(ivona_conf,
477 ivona_message_type,
478 &msg, icon, &buf, &len,
479 ivona_cap_mode,
480 IvonaDelimiters,
481 ivona_punct_mode,
482 IvonaPunctuationSome))
483 {
484 ivona_speaking = 0;
485 if (ivona_stop)
486 module_report_event_stop();
487 else
488 module_report_event_end();
489 break;
490 }
491 if (buf && *buf) {
492 audio =
493 ivona_get_wave(buf, &samples,
494 &offset);
495 DBG("Got wave from direct");
496 }
497 }
498
499 /* tu mamy audio albo icon, mozna gadac */
500 if (ivona_stop) {
501 DBG("Stop in child, terminating");
502 ivona_speaking = 0;
503 module_report_event_stop();
504 break;
505 }
506
507 next_icon[0] = 0;
508 if (msg && *msg) {
509 if (!ivona_get_msgpart
510 (ivona_conf, ivona_message_type, &msg,
511 next_icon, &buf, &len, ivona_cap_mode,
512 IvonaDelimiters, ivona_punct_mode,
513 IvonaPunctuationSome)) {
514 if (buf && *buf) {
515 next_offset = 0;
516 next_audio =
517 ivona_get_wave_from_cache
518 (buf, &next_samples);
519 if (!next_audio) {
520 DBG("Sending %s to ivona", buf);
521 next_cache[0] = 0;
522 if (strlen(buf) <=
523 IVONA_CACHE_MAX_STRLEN)
524 strcpy
525 (next_cache,
526 buf);
527 fd = ivona_send_string
528 (buf);
529 }
530 }
531 }
532 }
533 if (ivona_stop) {
534 DBG("Stop in child, terminating");
535 ivona_speaking = 0;
536 module_report_event_stop();
537 break;
538 }
539 if (icon[0]) {
540 play_icon(IvonaSoundIconPath, icon);
541 if (ivona_stop) {
542 ivona_speaking = 0;
543 module_report_event_stop();
544 break;
545 }
546 icon[0] = 0;
547 }
548 if (audio) {
549 track.num_samples = samples;
550 track.num_channels = 1;
551 track.sample_rate = IvonaSampleFreq;
552 track.bits = 16;
553 track.samples = ((short *)audio) + offset;
554 DBG("Got %d samples", track.num_samples);
555 module_tts_output(track, SPD_AUDIO_LE);
556 g_free(audio);
557 audio = NULL;
558 }
559 if (ivona_stop) {
560 ivona_speaking = 0;
561 module_report_event_stop();
562 break;
563 }
564 }
565 ivona_stop = 0;
566 g_free(buf);
567 g_free(audio);
568 g_free(next_audio);
569 if (fd >= 0)
570 close(fd);
571 fd = -1;
572 audio = NULL;
573 next_audio = NULL;
574 }
575 ivona_speaking = 0;
576
577 DBG("Ivona: speaking thread ended.......\n");
578
579 pthread_exit(NULL);
580 }
581
ivona_set_volume(signed int volume)582 static void ivona_set_volume(signed int volume)
583 {
584 assert(volume >= -100 && volume <= +100);
585 ivona_volume = volume;
586 }
587
ivona_set_cap_let_recogn(SPDCapitalLetters cap_mode)588 static void ivona_set_cap_let_recogn(SPDCapitalLetters cap_mode)
589 {
590 ivona_cap_mode = 0;
591 switch (cap_mode) {
592 case SPD_CAP_SPELL:
593 ivona_cap_mode = 2;
594 break;
595 case SPD_CAP_ICON:
596 ivona_cap_mode = 1;
597 break;
598 case SPD_CAP_NONE:
599 ivona_cap_mode = 0;
600 break;
601 }
602 if (ivona_cap_mode < IvonaMinCapLet) {
603 ivona_cap_mode = IvonaMinCapLet;
604 }
605 }
606
ivona_set_punctuation_mode(SPDPunctuation punct_mode)607 static void ivona_set_punctuation_mode(SPDPunctuation punct_mode)
608 {
609 ivona_punct_mode = 1;
610 switch (punct_mode) {
611 case SPD_PUNCT_ALL:
612 ivona_punct_mode = 2;
613 break;
614 case SPD_PUNCT_MOST:
615 /* XXX approximation */
616 ivona_punct_mode = 1;
617 break;
618 case SPD_PUNCT_SOME:
619 ivona_punct_mode = 1;
620 break;
621 case SPD_PUNCT_NONE:
622 ivona_punct_mode = 0;
623 break;
624 }
625 }
626