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/local/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