1 /*
2 * festival.c - Speech Dispatcher backend for Festival
3 *
4 * Copyright (C) 2003, 2007 Brailcom, o.p.s.
5 *
6 * This is free software; you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2, or (at your option)
9 * any later version.
10 *
11 * This software is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 *
19 * $Id: festival.c,v 1.82 2008-06-09 10:33:38 hanke Exp $
20 */
21
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25
26 #include <stdio.h>
27 #include <semaphore.h>
28
29 #include <speechd_types.h>
30 #include "fdsetconv.h"
31
32 #include "festival_client.h"
33 #include "module_utils.h"
34
35 #define MODULE_NAME "festival"
36 #define MODULE_VERSION "0.5"
37
38 DECLARE_DEBUG()
39
40 /* Thread and process control */
41 static pthread_t festival_speak_thread;
42 static sem_t festival_semaphore;
43 static int festival_speaking = 0;
44 static int festival_pause_requested = 0;
45
46 static char *festival_message;
47 static SPDMessageType festival_message_type;
48 signed int festival_volume = 0;
49
50 int festival_stop_request = 0;
51 int festival_stop = 0;
52
53 int festival_process_pid = 0;
54
55 FT_Info *festival_info = NULL;
56
57 SPDVoice **festival_voice_list = NULL;
58
59 enum {
60 FCT_SOCKET = 0,
61 FCT_LOCAL = 1,
62 } FestivalComType;
63
64 struct {
65 int pipe_in[2];
66 int pipe_out[2];
67 int pid;
68 } module_p;
69
70 #define COM_SOCKET ((FestivalComType == FCT_SOCKET) ? 1 : 0)
71 #define COM_LOCAL ((FestivalComType == FCT_LOCAL) ? 1 : 0)
72
73 /* --- SETTINGS COMMANDS --- */
74
75 #define FEST_SET_STR(name, fest_param) \
76 int \
77 name(FT_Info *info, char *param, char **resp) \
78 { \
79 char *r; \
80 int ret; \
81 char *f; \
82 if (festival_check_info(info, #name)) return -1; \
83 if (param == NULL){ \
84 FEST_SEND_CMD("("fest_param" nil)"); \
85 }else{ \
86 f = g_ascii_strdown(param, -1); \
87 FEST_SEND_CMDA("("fest_param" \"%s\")", f); \
88 g_free(f); \
89 } \
90 ret = festival_read_response(info, &r); \
91 if (ret != 0) return -1; \
92 if (r != NULL){ \
93 if (resp != NULL) \
94 *resp = r; \
95 else \
96 g_free(r); \
97 } \
98 return ret; \
99 }
100
101 #define FEST_SET_SYMB(name, fest_param) \
102 int \
103 name(FT_Info *info, char *param) \
104 { \
105 char *f = NULL; \
106 if (festival_check_info(info, #name)) return -1; \
107 if (param == NULL) return -1; \
108 FEST_SEND_CMDA("("fest_param" '%s)", f = g_ascii_strdown(param, -1)); \
109 g_free(f); \
110 return festival_read_response(info, NULL); \
111 }
112
113 #define FEST_SET_INT(name, fest_param) \
114 int \
115 name(FT_Info *info, int param) \
116 { \
117 if (festival_check_info(info, #name)) return -1; \
118 FEST_SEND_CMDA("("fest_param" %d)", param); \
119 return festival_read_response(info, NULL); \
120 }
121
122 FEST_SET_SYMB(FestivalSetMultiMode, "speechd-enable-multi-mode")
123
124 FEST_SET_INT(FestivalSetRate, "speechd-set-rate")
125 FEST_SET_INT(FestivalSetPitch, "speechd-set-pitch")
126 FEST_SET_SYMB(FestivalSetPunctuationMode, "speechd-set-punctuation-mode")
127 FEST_SET_STR(FestivalSetCapLetRecogn,
128 "speechd-set-capital-character-recognition-mode")
129 FEST_SET_STR(FestivalSetLanguage, "speechd-set-language")
130 FEST_SET_STR(FestivalSetVoice, "speechd-set-voice")
131 FEST_SET_SYMB(FestivalSetSynthesisVoice, "speechd-set-festival-voice")
132
133 /* Internal functions prototypes */
134 static SPDVoice **festivalGetVoices(FT_Info * info);
135 void *_festival_speak(void *);
136
137 void festival_parent_clean();
138
139 void festival_set_rate(signed int rate);
140 void festival_set_pitch(signed int pitch);
141 void festival_set_voice(SPDVoiceType voice);
142 void festival_set_synthesis_voice(char *synthesis_voice);
143 void festival_set_language(char *language);
144 void festival_set_punctuation_mode(SPDPunctuation punct);
145 void festival_set_cap_let_recogn(SPDCapitalLetters recogn);
146 void festival_set_volume(signed int volume);
147
148 int init_festival_standalone();
149 int init_festival_socket();
150
151 int is_text(SPDMessageType msg_type);
152
153 MOD_OPTION_1_INT(FestivalComunicationType)
154
155 MOD_OPTION_1_INT(FestivalMaxChunkLength)
156 MOD_OPTION_1_STR(FestivalDelimiters)
157 MOD_OPTION_1_STR(FestivalServerHost)
158 MOD_OPTION_1_STR(FestivalStripPunctChars)
159 MOD_OPTION_1_INT(FestivalServerPort)
160 MOD_OPTION_1_INT(FestivalPitchDeviation)
161 MOD_OPTION_1_INT(FestivalDebugSaveOutput)
162 MOD_OPTION_1_STR(FestivalRecodeFallback)
163
164 MOD_OPTION_1_INT(FestivalCacheOn)
165 MOD_OPTION_1_INT(FestivalCacheMaxKBytes)
166 MOD_OPTION_1_INT(FestivalCacheDistinguishVoices)
167 MOD_OPTION_1_INT(FestivalCacheDistinguishRate)
168 MOD_OPTION_1_INT(FestivalCacheDistinguishPitch)
169
170 MOD_OPTION_1_INT(FestivalReopenSocket)
171
172 typedef struct {
173 size_t size;
174 GHashTable *caches;
175 GList *cache_counter;
176 } TCache;
177
178 typedef struct {
179 time_t start;
180 int count;
181 size_t size;
182 GHashTable *p_caches;
183 char *key;
184 } TCounterEntry;
185
186 typedef struct {
187 TCounterEntry *p_counter_entry;
188 FT_Wave *fwave;
189 } TCacheEntry;
190
191 TCache FestivalCache;
192
193 int cache_init();
194 int cache_reset();
195 int cache_insert(char *key, SPDMessageType msgtype, FT_Wave * value);
196 FT_Wave *cache_lookup(const char *key, SPDMessageType msgtype, int add_counter);
197
198 pthread_mutex_t sound_output_mutex;
199
200 /* Public functions */
201
module_load(void)202 int module_load(void)
203 {
204
205 INIT_SETTINGS_TABLES();
206
207 REGISTER_DEBUG();
208
209 MOD_OPTION_1_INT_REG(FestivalComunicationType, 0);
210
211 MOD_OPTION_1_STR_REG(FestivalServerHost, "localhost");
212 MOD_OPTION_1_INT_REG(FestivalServerPort, 1314);
213
214 MOD_OPTION_1_INT_REG(FestivalDebugSaveOutput, 0);
215
216 MOD_OPTION_1_STR_REG(FestivalRecodeFallback, "?");
217
218 MOD_OPTION_1_INT_REG(FestivalCacheOn, 1);
219 MOD_OPTION_1_INT_REG(FestivalCacheMaxKBytes, 5120);
220 MOD_OPTION_1_INT_REG(FestivalCacheDistinguishVoices, 0);
221 MOD_OPTION_1_INT_REG(FestivalCacheDistinguishRate, 0);
222 MOD_OPTION_1_INT_REG(FestivalCacheDistinguishPitch, 0);
223
224 /* TODO: Maybe switch this option to 1 when the bug with the 40ms delay
225 in Festival is fixed */
226 MOD_OPTION_1_INT_REG(FestivalReopenSocket, 0);
227
228 return 0;
229 }
230
231 #define ABORT(msg) g_string_append(info, msg); \
232 *status_info = info->str; \
233 g_string_free(info, 0); \
234 return -1;
235
module_init(char ** status_info)236 int module_init(char **status_info)
237 {
238 int ret;
239
240 GString *info;
241
242 info = g_string_new("");
243
244 DBG("module_init()");
245
246 INIT_INDEX_MARKING();
247
248 /* Initialize appropriate communication mechanism */
249 FestivalComType = FestivalComunicationType;
250 if (COM_SOCKET) {
251 g_string_append(info,
252 "Communicating with Festival through a socket. ");
253 ret = init_festival_socket();
254 if (ret == -1) {
255 ABORT
256 ("Can't connect to Festival server. Check your configuration "
257 "in etc/speech-dispatcher/modules/festival.conf for the specified host and port "
258 "and check if Festival is really running there, e.g. with telnet. "
259 "Please see documentation for more info.");
260 } else if (ret == -2) {
261 ABORT("Connect to the Festival server was successful, "
262 "but I got disconnected immediately. This is most likely "
263 "because of authorization problems. Check the variable "
264 "server_access_list in etc/festival.scm and consult documentation "
265 "for more information.");
266 }
267 }
268 if (COM_LOCAL) {
269 g_string_append(info,
270 "Communicating with Festival through a local child process.");
271 if (init_festival_standalone()) {
272 ABORT
273 ("Local connect to Festival failed for unknown reasons.");
274 }
275 }
276
277 /* Get festival voice list */
278 festival_voice_list = festivalGetVoices(festival_info);
279
280 /* Initialize global variables */
281 festival_message = NULL;
282
283 /* Initialize festival_speak thread to handle communication
284 with festival in a separate thread (to be faster in communication
285 with Speech Dispatcher) */
286
287 sem_init(&festival_semaphore, 0, 0);
288
289 DBG("Festival: creating new thread for festival_speak\n");
290 festival_speaking = 0;
291 ret =
292 pthread_create(&festival_speak_thread, NULL, _festival_speak, NULL);
293 if (ret != 0) {
294 DBG("Festival: thread failed\n");
295 g_string_append(info, "The module couldn't initialize threads"
296 "This can be either an internal problem or an"
297 "architecture problem. If you are sure your architecture"
298 "supports threads, please report a bug.");
299 *status_info = info->str;
300 g_string_free(info, 0);
301 return -1;
302 }
303
304 pthread_mutex_init(&sound_output_mutex, NULL);
305
306 *status_info = info->str;
307 g_string_free(info, 0);
308
309 return 0;
310 }
311
312 #undef ABORT
313
module_list_voices(void)314 SPDVoice **module_list_voices(void)
315 {
316 return festival_voice_list;
317 }
318
module_speak(char * data,size_t bytes,SPDMessageType msgtype)319 int module_speak(char *data, size_t bytes, SPDMessageType msgtype)
320 {
321 int ret;
322
323 DBG("module_speak()\n");
324
325 if (data == NULL)
326 return -1;
327
328 if (festival_speaking) {
329 DBG("Speaking when requested to write\n");
330 return -1;
331 }
332
333 festival_stop_request = 0;
334
335 festival_message_type = msgtype;
336 if ((msgtype == SPD_MSGTYPE_TEXT)
337 && (msg_settings.spelling_mode == SPD_SPELL_ON))
338 festival_message_type = SPD_MSGTYPE_SPELL;
339
340 /* If the connection crashed or language or voice
341 change, we will need to set all the parameters again */
342 if (COM_SOCKET) {
343 if (festival_connection_crashed) {
344 DBG("Recovering after a connection loss");
345 CLEAN_OLD_SETTINGS_TABLE();
346 festival_info = festivalOpen(festival_info);
347 if (festival_info)
348 festival_connection_crashed = 0;
349 else {
350 DBG("Can't recover. Not possible to open connection to Festival.");
351 return -1;
352 }
353 ret = FestivalSetMultiMode(festival_info, "t");
354 if (ret != 0)
355 return -1;
356 }
357 }
358
359 /* If the voice was changed, re-set all the parameters */
360 // TODO: Handle synthesis_voice change too
361 if ((msg_settings.voice_type != msg_settings_old.voice_type)
362 || ((msg_settings.voice.language != NULL)
363 && (msg_settings_old.voice.language != NULL)
364 &&
365 (strcmp
366 (msg_settings.voice.language,
367 msg_settings_old.voice.language)))) {
368 DBG("Cleaning old settings table");
369 CLEAN_OLD_SETTINGS_TABLE();
370 }
371
372 /* Setting voice parameters */
373 DBG("Updating parameters");
374 UPDATE_STRING_PARAMETER(voice.language, festival_set_language);
375 UPDATE_PARAMETER(voice_type, festival_set_voice);
376 UPDATE_STRING_PARAMETER(voice.name, festival_set_synthesis_voice);
377 UPDATE_PARAMETER(rate, festival_set_rate);
378 UPDATE_PARAMETER(pitch, festival_set_pitch);
379 UPDATE_PARAMETER(volume, festival_set_volume);
380 UPDATE_PARAMETER(punctuation_mode, festival_set_punctuation_mode);
381 UPDATE_PARAMETER(cap_let_recogn, festival_set_cap_let_recogn);
382
383 if (festival_connection_crashed) {
384 DBG("ERROR: Festival connection not working!");
385 return -1;
386 }
387
388 DBG("Requested data: |%s| \n", data);
389
390 g_free(festival_message);
391 festival_message = g_strdup(data);
392 if (festival_message == NULL) {
393 DBG("Error: Copying data unsuccessful.");
394 return -1;
395 }
396
397 /* Send semaphore signal to the speaking thread */
398 festival_speaking = 1;
399 sem_post(&festival_semaphore);
400
401 DBG("Festival: leaving write() normally\n\r");
402 return bytes;
403 }
404
module_stop(void)405 int module_stop(void)
406 {
407 DBG("stop()\n");
408
409 if (festival_speaking) {
410 /* if(COM_SOCKET) */
411 if (0) {
412 if (festival_info != 0)
413 if ((festival_info->server_fd != -1)
414 && FestivalReopenSocket) {
415 /* TODO: Maybe use shutdown here? */
416 close(festival_info->server_fd);
417 festival_info->server_fd = -1;
418 festival_connection_crashed = 1;
419 DBG("festival socket closed by module_stop()");
420 }
421 }
422 if (COM_LOCAL) {
423 DBG("festival local stopped by sending SIGINT");
424 /* TODO: Write this function for local communication */
425 // festival_stop_local();
426 }
427
428 if (!festival_stop) {
429 pthread_mutex_lock(&sound_output_mutex);
430 festival_stop = 1;
431 if (festival_speaking && module_audio_id) {
432 spd_audio_stop(module_audio_id);
433 }
434 pthread_mutex_unlock(&sound_output_mutex);
435 }
436 }
437
438 return 0;
439 }
440
module_pause(void)441 size_t module_pause(void)
442 {
443 DBG("pause requested\n");
444 if (festival_speaking) {
445 DBG("Sending request for pause to child\n");
446 festival_pause_requested = 1;
447 DBG("Signalled to pause");
448 return 0;
449 } else {
450 return -1;
451 }
452 }
453
module_close(void)454 int module_close(void)
455 {
456
457 DBG("festival: close()\n");
458
459 DBG("Stopping the module");
460 while (festival_speaking) {
461 module_stop();
462 usleep(50);
463 }
464
465 // DBG("festivalClose()");
466 // festivalClose(festival_info);
467
468 DBG("Terminating threads");
469 if (festival_speak_thread)
470 module_terminate_thread(festival_speak_thread);
471
472 if (festival_info)
473 delete_FT_Info(festival_info);
474
475 /* TODO: Solve this */
476 // DBG("Removing junk files in tmp/");
477 // system("rm -f /tmp/est* 2> /dev/null");
478
479 sem_destroy(&festival_semaphore);
480 return 0;
481 }
482
483 /* Internal functions */
484
485 #define CLEAN_UP(code, im) \
486 { \
487 if(!wave_cached) if (fwave) delete_FT_Wave(fwave); \
488 pthread_mutex_lock(&sound_output_mutex); \
489 festival_stop = 0; \
490 festival_speaking = 0; \
491 pthread_mutex_unlock(&sound_output_mutex); \
492 im(); \
493 goto sem_wait; \
494 }
495
496 #define CLP(code, im) \
497 { \
498 pthread_mutex_lock(&sound_output_mutex); \
499 festival_stop = 0; \
500 festival_speaking = 0; \
501 pthread_mutex_unlock(&sound_output_mutex); \
502 im(); \
503 goto sem_wait; \
504 }
505
festivalGetVoices(FT_Info * info)506 static SPDVoice **festivalGetVoices(FT_Info * info)
507 {
508 char *reply;
509 char **voices;
510 char *lang;
511 char *region;
512 int i, j;
513 int num_voices = 0;
514 SPDVoice **result;
515
516 FEST_SEND_CMD("(apply append (voice-list-language-codes))");
517 festival_read_response(info, &reply);
518 if (reply == NULL) {
519 DBG("ERROR: Invalid reply for voice-list");
520 return NULL;
521 }
522 /* Remove trailing newline */
523 reply[strlen(reply) - 1] = 0;
524 DBG("Voice list reply: |%s|", reply);
525 voices = lisp_list_get_vect(reply);
526 if (voices == NULL) {
527 DBG("ERROR: Can't parse voice listing reply into vector");
528 return NULL;
529 }
530
531 /* Compute number of voices */
532 for (i = 0;; i++, num_voices++)
533 if (voices[i] == NULL)
534 break;
535 num_voices /= 3;
536
537 result = g_malloc((num_voices + 1) * sizeof(SPDVoice *));
538
539 for (i = 0, j = 0;; j++) {
540 if (voices[i] == NULL)
541 break;
542 else if (strlen(voices[i]) == 0)
543 continue;
544 else {
545 result[j] = g_malloc(sizeof(SPDVoice));
546 result[j]->name = voices[i];
547 lang = voices[i + 1];
548 if (lang && !strcmp(lang, "nil"))
549 lang = NULL;
550 region = voices[i + 2];
551 if (region && !strcmp(region, "nil"))
552 region = NULL;
553 if (lang && region)
554 result[j]->language = g_strdup_printf("%s-%s", lang, region);
555 else if (lang)
556 result[j]->language = g_strdup(lang);
557 else if (region)
558 result[j]->language = g_strdup(region);
559 else
560 result[j]->language = NULL;
561 result[j]->variant = NULL;
562 i += 3;
563 }
564 }
565 result[j] = NULL;
566 return result;
567 }
568
festival_send_to_audio(FT_Wave * fwave)569 int festival_send_to_audio(FT_Wave * fwave)
570 {
571 AudioTrack track;
572 #if defined(BYTE_ORDER) && (BYTE_ORDER == BIG_ENDIAN)
573 AudioFormat format = SPD_AUDIO_BE;
574 #else
575 AudioFormat format = SPD_AUDIO_LE;
576 #endif
577 int ret = 0;
578
579 if (fwave->samples == NULL)
580 return 0;
581
582 track.num_samples = fwave->num_samples;
583 track.num_channels = 1;
584 track.sample_rate = fwave->sample_rate;
585 track.bits = 16;
586 track.samples = fwave->samples;
587
588 DBG("Sending to audio");
589
590 ret = module_tts_output(track, format);
591 if (ret < 0)
592 DBG("ERROR: Can't play track for unknown reason.");
593 DBG("Sent to audio.");
594
595 return 0;
596 }
597
_festival_speak(void * nothing)598 void *_festival_speak(void *nothing)
599 {
600
601 int ret;
602 int bytes;
603 int wave_cached;
604 FT_Wave *fwave;
605 int debug_count = 0;
606 int r;
607 int terminate = 0;
608
609 char *callback;
610
611 DBG("festival: speaking thread starting.......\n");
612
613 cache_init();
614
615 set_speaking_thread_parameters();
616
617 while (1) {
618 sem_wait:
619 sem_wait(&festival_semaphore);
620 DBG("Semaphore on, speaking\n");
621
622 festival_stop = 0;
623 festival_speaking = 1;
624 wave_cached = 0;
625 fwave = NULL;
626
627 terminate = 0;
628
629 bytes = strlen(festival_message);
630
631 module_report_event_begin();
632
633 DBG("Going to synthesize: |%s|", festival_message);
634 if (bytes > 0) {
635 if (!is_text(festival_message_type)) { /* it is a raw text */
636 DBG("Cache mechanisms...");
637 fwave =
638 cache_lookup(festival_message,
639 festival_message_type, 1);
640 if (fwave != NULL) {
641 wave_cached = 1;
642 if (fwave->num_samples != 0) {
643 if (FestivalDebugSaveOutput) {
644 char filename_debug
645 [256];
646 sprintf(filename_debug,
647 "/tmp/debug-festival-%d.snd",
648 debug_count++);
649 save_FT_Wave_snd(fwave,
650 filename_debug);
651 }
652
653 festival_send_to_audio(fwave);
654
655 if (!festival_stop) {
656 CLEAN_UP(0,
657 module_report_event_end);
658 } else {
659 CLEAN_UP(0,
660 module_report_event_stop);
661 }
662
663 } else {
664 CLEAN_UP(0,
665 module_report_event_end);
666 }
667 }
668 }
669
670 /* Set multi-mode for appropriate kind of events */
671 if (is_text(festival_message_type)) { /* it is a raw text */
672 ret = FestivalSetMultiMode(festival_info, "t");
673 if (ret != 0)
674 CLP(0, module_report_event_stop);
675 } else { /* it is some kind of event */
676 ret =
677 FestivalSetMultiMode(festival_info, "nil");
678 if (ret != 0)
679 CLP(0, module_report_event_stop);
680 }
681
682 switch (festival_message_type) {
683 case SPD_MSGTYPE_TEXT:
684 r = festivalStringToWaveRequest(festival_info,
685 festival_message);
686 break;
687 case SPD_MSGTYPE_SOUND_ICON:
688 r = festivalSoundIcon(festival_info,
689 festival_message);
690 break;
691 case SPD_MSGTYPE_CHAR:
692 r = festivalCharacter(festival_info,
693 festival_message);
694 break;
695 case SPD_MSGTYPE_KEY:
696 /* TODO: make sure all SSIP cases are supported */
697 r = festivalKey(festival_info,
698 festival_message);
699 break;
700 case SPD_MSGTYPE_SPELL:
701 r = festivalSpell(festival_info,
702 festival_message);
703 break;
704 default:
705 r = -1;
706 }
707 if (r < 0) {
708 DBG("Couldn't process the request to say the object.");
709 CLP(0, module_report_event_stop);
710 }
711 }
712
713 while (1) {
714
715 wave_cached = 0;
716 DBG("Retrieving data\n");
717
718 /* (speechd-next) */
719 if (is_text(festival_message_type)) {
720
721 if (festival_stop) {
722 DBG("Module stopped 1");
723 CLEAN_UP(0, module_report_event_stop);
724 }
725
726 DBG("Getting data in multi mode");
727 fwave =
728 festivalGetDataMulti(festival_info,
729 &callback,
730 &festival_stop_request,
731 FestivalReopenSocket);
732
733 if (callback != NULL) {
734 if ((festival_pause_requested)
735 &&
736 (!strncmp
737 (callback, INDEX_MARK_BODY,
738 INDEX_MARK_BODY_LEN))) {
739 DBG("Pause requested, pausing.");
740 module_report_index_mark
741 (callback);
742 g_free(callback);
743 festival_pause_requested = 0;
744 CLEAN_UP(0,
745 module_report_event_pause);
746 } else {
747 module_report_index_mark
748 (callback);
749 g_free(callback);
750 continue;
751 }
752 }
753 } else { /* is event */
754 DBG("Getting data in single mode");
755 fwave =
756 festivalStringToWaveGetData(festival_info);
757 terminate = 1;
758 callback = NULL;
759 }
760
761 if (fwave == NULL) {
762 DBG("End of sound samples, terminating this message...");
763 CLEAN_UP(0, module_report_event_end);
764 }
765
766 if (festival_message_type == SPD_MSGTYPE_CHAR
767 || festival_message_type == SPD_MSGTYPE_KEY
768 || festival_message_type ==
769 SPD_MSGTYPE_SOUND_ICON) {
770 DBG("Storing record for %s in cache\n",
771 festival_message);
772 /* cache_insert takes care of not inserting the same
773 message again */
774 cache_insert(g_strdup(festival_message),
775 festival_message_type, fwave);
776 wave_cached = 1;
777 }
778
779 if (festival_stop) {
780 DBG("Module stopped 2");
781 CLEAN_UP(0, module_report_event_stop);
782 }
783
784 if (fwave->num_samples != 0) {
785 DBG("Sending message to audio: %lu bytes\n",
786 (long unsigned)((fwave->num_samples) *
787 sizeof(short)));
788
789 if (FestivalDebugSaveOutput) {
790 char filename_debug[256];
791 sprintf(filename_debug,
792 "/tmp/debug-festival-%d.snd",
793 debug_count++);
794 save_FT_Wave_snd(fwave, filename_debug);
795 }
796
797 DBG("Playing sound samples");
798 festival_send_to_audio(fwave);
799
800 if (!wave_cached)
801 delete_FT_Wave(fwave);
802 DBG("End of playing sound samples");
803 }
804
805 if (terminate) {
806 DBG("Ok, end of samples, returning");
807 CLP(0, module_report_event_end);
808 }
809
810 if (festival_stop) {
811 DBG("Module stopped 3");
812 CLP(0, module_report_event_stop);
813 }
814 }
815
816 }
817
818 festival_stop = 0;
819 festival_speaking = 0;
820
821 DBG("festival: speaking thread ended.......\n");
822
823 pthread_exit(NULL);
824 }
825
is_text(SPDMessageType msg_type)826 int is_text(SPDMessageType msg_type)
827 {
828 if (msg_type == SPD_MSGTYPE_TEXT || msg_type == SPD_MSGTYPE_SPELL)
829 return 1;
830 else
831 return 0;
832 }
833
festival_set_language(char * language)834 void festival_set_language(char *language)
835 {
836 FestivalSetLanguage(festival_info, language, NULL);
837 g_free(festival_voice_list);
838 festival_voice_list = festivalGetVoices(festival_info);
839 }
840
festival_set_voice(SPDVoiceType voice)841 void festival_set_voice(SPDVoiceType voice)
842 {
843 char *voice_name;
844
845 voice_name = EVoice2str(voice);
846 FestivalSetVoice(festival_info, voice_name, NULL);
847 g_free(voice_name);
848 }
849
festival_set_synthesis_voice(char * voice_name)850 void festival_set_synthesis_voice(char *voice_name)
851 {
852
853 FestivalSetSynthesisVoice(festival_info, voice_name);
854 }
855
festival_set_rate(signed int rate)856 void festival_set_rate(signed int rate)
857 {
858 FestivalSetRate(festival_info, rate);
859 }
860
festival_set_pitch(signed int pitch)861 void festival_set_pitch(signed int pitch)
862 {
863 FestivalSetPitch(festival_info, pitch);
864 }
865
festival_set_volume(signed int volume)866 void festival_set_volume(signed int volume)
867 {
868 festival_volume = volume;
869 }
870
festival_set_punctuation_mode(SPDPunctuation punct)871 void festival_set_punctuation_mode(SPDPunctuation punct)
872 {
873 char *punct_mode;
874 punct_mode = EPunctMode2str(punct);
875 FestivalSetPunctuationMode(festival_info, punct_mode);
876 g_free(punct_mode);
877 }
878
festival_set_cap_let_recogn(SPDCapitalLetters recogn)879 void festival_set_cap_let_recogn(SPDCapitalLetters recogn)
880 {
881 char *recogn_mode;
882
883 if (recogn == SPD_CAP_NONE)
884 recogn_mode = NULL;
885 else
886 recogn_mode = ECapLetRecogn2str(recogn);
887 FestivalSetCapLetRecogn(festival_info, recogn_mode, NULL);
888 g_free(recogn_mode);
889 }
890
891 /* --- Cache related functions --- */
892
cache_destroy_entry(gpointer data)893 void cache_destroy_entry(gpointer data)
894 {
895 TCacheEntry *entry = data;
896 g_free(entry->fwave);
897 g_free(entry);
898 }
899
cache_destroy_table_entry(gpointer data)900 void cache_destroy_table_entry(gpointer data)
901 {
902 g_hash_table_destroy(data);
903 }
904
cache_free_counter_entry(gpointer data,gpointer user_data)905 void cache_free_counter_entry(gpointer data, gpointer user_data)
906 {
907 g_free(((TCounterEntry *) data)->key);
908 g_free(data);
909 }
910
cache_init()911 int cache_init()
912 {
913
914 if (FestivalCacheOn == 0)
915 return 0;
916
917 FestivalCache.size = 0;
918 FestivalCache.caches =
919 g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
920 cache_destroy_table_entry);
921 FestivalCache.cache_counter = NULL;
922 DBG("Cache: initialized");
923 return 0;
924 }
925
cache_destroy()926 int cache_destroy()
927 {
928 g_hash_table_destroy(FestivalCache.caches);
929 g_list_foreach(FestivalCache.cache_counter, cache_free_counter_entry,
930 NULL);
931 g_list_free(FestivalCache.cache_counter);
932 return 0;
933 }
934
cache_reset()935 int cache_reset()
936 {
937 /* TODO: it could free everything in the cache and go from start,
938 but currently it isn't called by anybody */
939 return 0;
940 }
941
942 /* Compare two cache entries according to their score (how many
943 times the entry was requested divided by the time it exists
944 in the database) */
cache_counter_comp(gconstpointer a,gconstpointer b)945 gint cache_counter_comp(gconstpointer a, gconstpointer b)
946 {
947 const TCounterEntry *A = a;
948 const TCounterEntry *B = b;
949 time_t t;
950 float ret;
951
952 t = time(NULL);
953 ret = (((float)A->count / (float)(t - A->start))
954 - ((float)B->count / (float)(t - B->start)));
955
956 if (ret > 0)
957 return -1;
958 if (ret == 0)
959 return 0;
960 if (ret < 0)
961 return 1;
962
963 return 0;
964 }
965
966 /* List scores of all entries in the cache*/
cache_debug_foreach_list_score(gpointer a,gpointer user)967 void cache_debug_foreach_list_score(gpointer a, gpointer user)
968 {
969 const TCounterEntry *A = a;
970
971 DBG("key: %s -> score %f (count: %d, dtime: %d)", A->key,
972 ((float)A->count / (float)(time(NULL) - A->start)), (int)A->count,
973 (int)(time(NULL) - A->start));
974 }
975
976 /* Remove 1/3 of the least used (according to cache_counter_comp) entries
977 (measured by size) */
cache_clean(size_t new_element_size)978 int cache_clean(size_t new_element_size)
979 {
980 size_t req_size;
981 GList *gl;
982 TCounterEntry *centry;
983
984 DBG("Cache: cleaning, cache size %lu kbytes (>max %d).",
985 (unsigned long)(FestivalCache.size / 1024), FestivalCacheMaxKBytes);
986
987 req_size = 2 * FestivalCache.size / 3;
988
989 FestivalCache.cache_counter =
990 g_list_sort(FestivalCache.cache_counter, cache_counter_comp);
991 g_list_foreach(FestivalCache.cache_counter,
992 cache_debug_foreach_list_score, NULL);
993
994 while ((FestivalCache.size + new_element_size) > req_size) {
995 gl = g_list_last(FestivalCache.cache_counter);
996 if (gl == NULL)
997 break;
998 if (gl->data == NULL) {
999 DBG("Error: Cache: gl->data in cache_clean is NULL, but shouldn't be.");
1000 return -1;
1001 }
1002 centry = gl->data;
1003 FestivalCache.size -= centry->size;
1004 DBG("Cache: Removing element with key '%s'", centry->key);
1005 if (FestivalCache.size < 0) {
1006 DBG("Error: Cache: FestivalCache.size < 0, this shouldn't be.");
1007 return -1;
1008 }
1009 /* Remove the data itself from the hash table */
1010 g_hash_table_remove(centry->p_caches, centry->key);
1011 /* Remove the associated entry in the counter list */
1012 cache_free_counter_entry(centry, NULL);
1013 FestivalCache.cache_counter =
1014 g_list_delete_link(FestivalCache.cache_counter, gl);
1015 }
1016
1017 return 0;
1018 }
1019
1020 /* Generate a key for searching between the different hash tables */
cache_gen_key(SPDMessageType type)1021 char *cache_gen_key(SPDMessageType type)
1022 {
1023 char *key;
1024 char ktype;
1025 int kpitch = 0, krate = 0, kvoice = 0;
1026
1027 if (msg_settings.voice.language == NULL)
1028 return NULL;
1029
1030 DBG("v, p, r = %d %d %d", FestivalCacheDistinguishVoices,
1031 FestivalCacheDistinguishPitch, FestivalCacheDistinguishRate);
1032
1033 if (FestivalCacheDistinguishVoices)
1034 kvoice = msg_settings.voice_type;
1035 if (FestivalCacheDistinguishPitch)
1036 kpitch = msg_settings.pitch;
1037 if (FestivalCacheDistinguishRate)
1038 krate = msg_settings.rate;
1039
1040 if (type == SPD_MSGTYPE_CHAR)
1041 ktype = 'c';
1042 else if (type == SPD_MSGTYPE_KEY)
1043 ktype = 'k';
1044 else if (type == SPD_MSGTYPE_SOUND_ICON)
1045 ktype = 's';
1046 else {
1047 DBG("Invalid message type for cache_gen_key()");
1048 return NULL;
1049 }
1050
1051 key =
1052 g_strdup_printf("%c_%s_%d_%d_%d", ktype,
1053 msg_settings.voice.language, kvoice, krate, kpitch);
1054
1055 return key;
1056 }
1057
1058 /* Insert one entry into the cache */
cache_insert(char * key,SPDMessageType msgtype,FT_Wave * fwave)1059 int cache_insert(char *key, SPDMessageType msgtype, FT_Wave * fwave)
1060 {
1061 GHashTable *cache;
1062 TCacheEntry *entry;
1063 TCounterEntry *centry;
1064 char *key_table;
1065
1066 if (FestivalCacheOn == 0)
1067 return 0;
1068
1069 if (key == NULL)
1070 return -1;
1071 if (fwave == NULL)
1072 return -1;
1073
1074 /* Check if the entry isn't present already */
1075 if (cache_lookup(key, msgtype, 0) != NULL)
1076 return 0;
1077
1078 key_table = cache_gen_key(msgtype);
1079
1080 DBG("Cache: Inserting wave with key:'%s' into table '%s'", key,
1081 key_table);
1082
1083 /* Clean less used cache entries if the size would exceed max. size */
1084 if ((FestivalCache.size + fwave->num_samples * sizeof(short))
1085 > (FestivalCacheMaxKBytes * 1024))
1086 if (cache_clean(fwave->num_samples * sizeof(short)) != 0)
1087 return -1;
1088
1089 /* Select the right table according to language, voice, etc. or create a new one */
1090 cache = g_hash_table_lookup(FestivalCache.caches, key_table);
1091 if (cache == NULL) {
1092 cache = g_hash_table_new(g_str_hash, g_str_equal);
1093 g_hash_table_insert(FestivalCache.caches, key_table, cache);
1094 } else {
1095 g_free(key_table);
1096 }
1097
1098 /* Fill the CounterEntry structure that will later allow us to remove
1099 the less used entries from cache */
1100 centry = (TCounterEntry *) g_malloc(sizeof(TCounterEntry));
1101 centry->start = time(NULL);
1102 centry->count = 1;
1103 centry->size = fwave->num_samples * sizeof(short);
1104 centry->p_caches = cache;
1105 centry->key = g_strdup(key);
1106 FestivalCache.cache_counter =
1107 g_list_append(FestivalCache.cache_counter, centry);
1108
1109 entry = (TCacheEntry *) g_malloc(sizeof(TCacheEntry));
1110 entry->p_counter_entry = centry;
1111 entry->fwave = fwave;
1112
1113 FestivalCache.size += centry->size;
1114 g_hash_table_insert(cache, g_strdup(key), entry);
1115
1116 return 0;
1117 }
1118
1119 /* Retrieve wave from the cache */
cache_lookup(const char * key,SPDMessageType msgtype,int add_counter)1120 FT_Wave *cache_lookup(const char *key, SPDMessageType msgtype, int add_counter)
1121 {
1122 GHashTable *cache;
1123 TCacheEntry *entry;
1124 char *key_table;
1125
1126 if (FestivalCacheOn == 0)
1127 return NULL;
1128 if (key == NULL)
1129 return NULL;
1130
1131 key_table = cache_gen_key(msgtype);
1132
1133 if (add_counter)
1134 DBG("Cache: looking up a wave with key '%s' in '%s'", key,
1135 key_table);
1136
1137 if (key_table == NULL)
1138 return NULL;
1139 cache = g_hash_table_lookup(FestivalCache.caches, key_table);
1140 g_free(key_table);
1141 if (cache == NULL)
1142 return NULL;
1143
1144 entry = g_hash_table_lookup(cache, key);
1145 if (entry == NULL)
1146 return NULL;
1147 entry->p_counter_entry->count++;
1148
1149 DBG("Cache: corresponding wave found: %s", key);
1150
1151 return entry->fwave;
1152 }
1153
init_festival_standalone()1154 int init_festival_standalone()
1155 {
1156 int ret;
1157 int fr;
1158
1159 if ((pipe(module_p.pipe_in) != 0)
1160 || (pipe(module_p.pipe_out) != 0)) {
1161 DBG("Can't open pipe! Module not loaded.");
1162 return -1;
1163 }
1164
1165 DBG("Starting Festival as a child process");
1166
1167 fr = fork();
1168 switch (fr) {
1169 case -1:
1170 DBG("ERROR: Can't fork! Module not loaded.");
1171 return -1;
1172 case 0:
1173 ret = dup2(module_p.pipe_in[0], 0);
1174 close(module_p.pipe_in[0]);
1175 close(module_p.pipe_in[1]);
1176
1177 ret = dup2(module_p.pipe_out[1], 1);
1178 close(module_p.pipe_out[1]);
1179 close(module_p.pipe_out[0]);
1180
1181 /* TODO: fix festival hardcoded path */
1182 if (execlp("festival", "", (char *)0) == -1)
1183 exit(1);
1184
1185 default:
1186 festival_process_pid = fr;
1187 close(module_p.pipe_in[0]);
1188 close(module_p.pipe_out[1]);
1189
1190 usleep(100); /* So that the other child has at least time to fail
1191 with the execlp */
1192 ret = waitpid(module_p.pid, NULL, WNOHANG);
1193 if (ret != 0) {
1194 DBG("Can't execute festival. Bad filename in configuration?");
1195 return -1;
1196 }
1197
1198 return 0;
1199 }
1200
1201 assert(0);
1202 }
1203
init_festival_socket()1204 int init_festival_socket()
1205 {
1206 int r;
1207
1208 /* Init festival and register a new voice */
1209 festival_info = festivalDefaultInfo();
1210 festival_info->server_host = FestivalServerHost;
1211 festival_info->server_port = FestivalServerPort;
1212
1213 festival_info = festivalOpen(festival_info);
1214 if (festival_info == NULL)
1215 return -1;
1216 r = FestivalSetMultiMode(festival_info, "t");
1217 if (r != 0)
1218 return -2;
1219
1220 DBG("FestivalServerHost = %s\n", FestivalServerHost);
1221 DBG("FestivalServerPort = %d\n", FestivalServerPort);
1222
1223 return 0;
1224 }
1225
stop_festival_local()1226 int stop_festival_local()
1227 {
1228 if (festival_process_pid > 0)
1229 kill(festival_process_pid, SIGINT);
1230 return 0;
1231 }
1232