1 
2 
3 /*
4  * kali.cpp - Speech Dispatcher backend for Kali
5  *
6  * Copyright (C)2016 Hypra
7  *
8  * This is free software; you can redistribute it and/or modify it
9  * under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2, or (at your option)
11  * any later version.
12  *
13  * This software is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
20  *
21  * $Id: kali.c,v 1.59 2008-06-09 10:38:02 hanke Exp $
22  */
23 
24 #include <stdio.h>
25 #include <string.h>
26 #include <unistd.h>
27 #include <kali/Kali/kali.h>
28 extern "C" {
29 #ifdef HAVE_CONFIG_H
30 #include <config.h>
31 #endif
32 #include <semaphore.h>
33 #include "spd_audio.h"
34 
35 #include <speechd_types.h>
36 
37 #include "module_utils.h"
38 }
39 #define MODULE_NAME     "kali"
40 #define MODULE_VERSION  "0.0"
41 #define DEBUG_MODULE 1
42 DECLARE_DEBUG();
43 
44 /* Thread and process control */
45 static int kali_speaking = 0;
46 
47 static pthread_t kali_speak_thread;
48 static sem_t kali_semaphore;
49 
50 static char **kali_message;
51 static SPDMessageType kali_message_type;
52 
53 static int kali_position = 0;
54 static int kali_pause_requested = 0;
55 
56 signed int kali_volume = 0;
57 SPDVoice **kali_voice_list = NULL;
58 
59 /* Internal functions prototypes */
60 static void kali_set_rate(signed int rate);
61 static void kali_set_pitch(signed int pitch);
62 static void kali_set_volume(signed int volume);
63 static void kali_set_punctuation_mode(SPDPunctuation punct);
64 static void kali_set_voice(char *voice);
65 
66 static SPDVoice **kali_get_voices();
67 static void *_kali_speak(void *);
68 
69 int kali_stop = 0;
70 
71 MOD_OPTION_1_INT(KaliMaxChunkLength);
72 MOD_OPTION_1_STR(KaliDelimiters);
73 MOD_OPTION_1_INT(KaliNormalRate);
74 MOD_OPTION_1_INT(KaliNormalVolume);
75 MOD_OPTION_1_INT(KaliNormalPitch);
76 MOD_OPTION_1_STR(KaliVoiceParameters);
77 MOD_OPTION_1_INT(KaliExpandAbbreviations);
78 
79 /* Public functions */
80 
module_load(void)81 int module_load(void)
82 {
83 	INIT_SETTINGS_TABLES();
84 
85 	REGISTER_DEBUG();
86 
87 	MOD_OPTION_1_INT_REG(KaliMaxChunkLength, 4999);
88 	MOD_OPTION_1_STR_REG(KaliDelimiters, ".");
89 	MOD_OPTION_1_INT_REG(KaliNormalRate, 70);
90 	MOD_OPTION_1_INT_REG(KaliNormalVolume, 10);
91 	MOD_OPTION_1_INT_REG(KaliNormalPitch, 6);
92 	MOD_OPTION_1_STR_REG(KaliVoiceParameters, "Patrick");
93 	MOD_OPTION_1_INT_REG(KaliExpandAbbreviations, 1);
94 
95 	return 0;
96 }
97 
98 #define ABORT(msg) g_string_append(info, msg); \
99 	DBG("FATAL ERROR:", info->str); \
100 	*status_info = info->str; \
101 	g_string_free(info, 0); \
102 	return -1;
103 
module_init(char ** status_info)104 int module_init(char **status_info)
105 {
106 	int ret;
107 	GString *info;
108 
109 	DBG("Module init");
110 	INIT_INDEX_MARKING();
111 
112 	*status_info = NULL;
113 	info = g_string_new("");
114 
115 	/* Init kali and register a new voice */
116 	initGlobal();
117 	initParle();
118 	initTrans();
119 	initAnalyse();
120 	initKali();
121 	SetSortieSonMultiKaliStd(0, false);	//sound output
122 	SetSortieBufMultiKaliStd(0, true);	//Buffer output
123 	SetDebitKali(KaliNormalRate);
124 	SetVolumeKali(KaliNormalVolume);
125 	SetHauteurKali(KaliNormalPitch);
126 	kali_voice_list = kali_get_voices();
127 	kali_set_voice(KaliVoiceParameters);
128 
129 	DBG("KaliMaxChunkLength = %d\n", KaliMaxChunkLength);
130 	DBG("KaliDelimiters = %s\n", KaliDelimiters);
131 	DBG("KaliExpandAbbreviations = %d\n", KaliExpandAbbreviations);
132 
133 	kali_message = (char **)g_malloc(sizeof(char *));
134 	*kali_message = NULL;
135 
136 	sem_init(&kali_semaphore, 0, 0);
137 
138 	DBG("Kali: creating new thread for kali_speak\n");
139 	kali_speaking = 0;
140 	ret = pthread_create(&kali_speak_thread, NULL, _kali_speak, NULL);
141 	if (ret != 0) {
142 		DBG("Kali: thread failed\n");
143 		*status_info =
144 		    g_strdup("The module couldn't initialize threads "
145 			     "This could be either an internal problem or an "
146 			     "architecture problem. If you are sure your architecture "
147 			     "supports threads, please report a bug.");
148 		return -1;
149 	}
150 
151 	*status_info = g_strdup("Kali initialized successfully.");
152 
153 	return 0;
154 }
155 
156 #undef ABORT
157 
module_list_voices(void)158 SPDVoice **module_list_voices(void)
159 {
160 	return kali_voice_list;
161 }
162 
module_speak(gchar * data,size_t bytes,SPDMessageType msgtype)163 int module_speak(gchar * data, size_t bytes, SPDMessageType msgtype)
164 {
165 	DBG("write()\n");
166 
167 	if (kali_speaking) {
168 		DBG("Speaking when requested to write");
169 		return 0;
170 	}
171 
172 	DBG("Requested data: |%s|\n", data);
173 
174 	if (*kali_message != NULL) {
175 		g_free(*kali_message);
176 		*kali_message = NULL;
177 	}
178 	*kali_message = module_strip_ssml(data);
179 	kali_message_type = SPD_MSGTYPE_TEXT;
180 
181 	/* Setting voice */
182 	UPDATE_PARAMETER(rate, kali_set_rate);
183 	UPDATE_PARAMETER(volume, kali_set_volume);
184 	UPDATE_PARAMETER(pitch, kali_set_pitch);
185 	UPDATE_PARAMETER(punctuation_mode, kali_set_punctuation_mode);
186 	UPDATE_STRING_PARAMETER(voice.name, kali_set_voice);
187 	kali_set_voice(msg_settings.voice.name);
188 
189 	/* Send semaphore signal to the speaking thread */
190 	kali_speaking = 1;
191 	sem_post(&kali_semaphore);
192 
193 	DBG("Kali: leaving write() normally\n\r");
194 	return bytes;
195 }
196 
module_stop(void)197 int module_stop(void)
198 {
199 	int ret;
200 	DBG("kali: stop()\n");
201 
202 	kali_stop = 1;
203 	if (module_audio_id) {
204 		DBG("Stopping audio");
205 		ret = spd_audio_stop(module_audio_id);
206 		if (ret != 0)
207 			DBG("WARNING: Non 0 value from spd_audio_stop: %d",
208 			    ret);
209 	}
210 
211 	return 0;
212 }
213 
module_pause(void)214 size_t module_pause(void)
215 {
216 	DBG("pause requested\n");
217 	if (kali_speaking) {
218 		DBG("Kali doesn't support pause, stopping\n");
219 
220 		module_stop();
221 
222 		return -1;
223 	} else {
224 		return 0;
225 	}
226 }
227 
module_close(void)228 int module_close(void)
229 {
230 
231 	DBG("kali: close()\n");
232 
233 	DBG("Stopping speech");
234 	if (kali_speaking) {
235 		module_stop();
236 	}
237 
238 	quitteAnalyse();
239 	quitteTrans();
240 	quitteParle();
241 	quitteGlobal();
242 
243 	DBG("Terminating threads");
244 	if (module_terminate_thread(kali_speak_thread) != 0)
245 		return -1;
246 
247 	sem_destroy(&kali_semaphore);
248 
249 	return 0;
250 }
251 
252 /* Internal functions */
253 
_kali_speak(void * nothing)254 void *_kali_speak(void *nothing)
255 {
256 	AudioTrack track;
257 #if defined(BYTE_ORDER) && (BYTE_ORDER == BIG_ENDIAN)
258 	AudioFormat format = SPD_AUDIO_BE;
259 #else
260 	AudioFormat format = SPD_AUDIO_LE;
261 #endif
262 	const AudioTrackKali *wav;
263 	unsigned int pos;
264 	char *buf;
265 	int bytes;
266 	int ret;
267 
268 	DBG("kali: speaking thread starting.......\n");
269 
270 	set_speaking_thread_parameters();
271 
272 	while (1) {
273 		sem_wait(&kali_semaphore);
274 		DBG("Semaphore on\n");
275 
276 		kali_stop = 0;
277 		kali_speaking = 1;
278 
279 		/* TODO: free(buf) */
280 		buf = (char *)g_malloc((KaliMaxChunkLength + 1) * sizeof(char));
281 		pos = 0;
282 		module_report_event_begin();
283 		while (1) {
284 			if (kali_stop) {
285 				DBG("Stop in child, terminating");
286 				kali_speaking = 0;
287 				module_report_event_stop();
288 				break;
289 			}
290 			bytes =
291 			    module_get_message_part(*kali_message, buf, &pos,
292 						    KaliMaxChunkLength,
293 						    KaliDelimiters);
294 
295 			if (bytes < 0) {
296 				DBG("End of message");
297 				kali_speaking = 0;
298 				module_report_event_end();
299 				break;
300 			}
301 
302 			buf[bytes] = 0;
303 			DBG("Returned %d bytes from get_part\n", bytes);
304 			DBG("Text to synthesize is '%s'\n", buf);
305 
306 			if (kali_pause_requested && (current_index_mark != -1)) {
307 				DBG("Pause requested in parent, position %d\n",
308 				    current_index_mark);
309 				kali_pause_requested = 0;
310 				kali_position = current_index_mark;
311 				break;
312 			}
313 
314 			if (bytes > 0) {
315 				DBG("Speaking in child...");
316 
317 				DBG("Trying to synthesize text");
318 				MessageKali((unsigned char *)buf);
319 				while (QueryIndexKali() > 0)
320 					;
321 				wav =
322 				    (const AudioTrackKali *)
323 				    GetBufMultiKaliStd(0);
324 
325 				if (wav == NULL) {
326 					DBG("Stop in child, terminating");
327 					kali_speaking = 0;
328 					module_report_event_stop();
329 					break;
330 				}
331 
332 				track.num_samples = wav->num_samples;
333 				track.num_channels = wav->num_channels;
334 				track.sample_rate = wav->sample_rate;
335 				track.bits = wav->bits;
336 				track.samples = (signed short *)wav->samples;
337 
338 				DBG("Got %d samples", track.num_samples);
339 				if (track.samples != NULL) {
340 					if (kali_stop) {
341 						DBG("Stop in child, terminating");
342 						kali_speaking = 0;
343 						module_report_event_stop();
344 						break;
345 					}
346 					DBG("Playing part of the message");
347 					ret = module_tts_output(track, format);
348 					if (ret < 0)
349 						DBG("ERROR: failed to play the track");
350 					if (kali_stop) {
351 						DBG("Stop in child, terminating (s)");
352 						kali_speaking = 0;
353 						module_report_event_stop();
354 						break;
355 					}
356 				}
357 			} else if (bytes == -1) {
358 				DBG("End of data in speaking thread");
359 				kali_speaking = 0;
360 				module_report_event_end();
361 				break;
362 			} else {
363 				kali_speaking = 0;
364 				module_report_event_end();
365 				break;
366 			}
367 
368 			if (kali_stop) {
369 				DBG("Stop in child, terminating");
370 				kali_speaking = 0;
371 				module_report_event_stop();
372 				break;
373 			}
374 		}
375 		kali_stop = 0;
376 		g_free(buf);
377 	}
378 
379 	kali_speaking = 0;
380 
381 	DBG("kali: speaking thread ended.......\n");
382 
383 	pthread_exit(NULL);
384 }
385 
kali_set_rate(signed int rate)386 static void kali_set_rate(signed int rate)
387 {
388 	short speed;
389 
390 	assert(rate >= -100 && rate <= +100);
391 	if (rate < 0)
392 	  speed = GetDebitDefautKaliStd() - rate * (GetDebitMinKaliStd() - GetDebitDefautKaliStd()) / 100;
393 	else
394 	  speed = GetDebitDefautKaliStd() + rate * (GetDebitMaxKaliStd() - GetDebitDefautKaliStd()) / 100;
395 	SetDebitKali(speed);
396 }
397 
kali_set_volume(signed int volume)398 static void kali_set_volume(signed int volume)
399 {
400 	short vol;
401 
402 	assert(volume >= -100 && volume <= +100);
403 	if (volume < 0)
404 	  vol = GetVolumeDefautKaliStd() - volume * (GetVolumeMinKaliStd() - GetVolumeDefautKaliStd()) / 100;
405 	else
406 	  vol = GetVolumeDefautKaliStd() + volume * (GetVolumeMaxKaliStd() - GetVolumeDefautKaliStd()) / 100;
407 	SetVolumeKali(vol);
408 }
409 
kali_set_pitch(signed int pitch)410 static void kali_set_pitch(signed int pitch)
411 {
412 	short ptch;
413 
414 	assert(pitch >= -100 && pitch <= +100);
415 	if (pitch < 0)
416 	  ptch = GetHauteurDefautKaliStd() - pitch * (GetHauteurMinKaliStd() - GetHauteurDefautKaliStd()) / 100;
417 	else
418 	  ptch = GetHauteurDefautKaliStd() + pitch * (GetHauteurMaxKaliStd() - GetHauteurDefautKaliStd()) / 100;
419 	SetHauteurKali(ptch);
420 }
421 
kali_set_punctuation_mode(SPDPunctuation punct)422 void kali_set_punctuation_mode(SPDPunctuation punct)
423 {
424 	switch (punct) {
425 	case SPD_PUNCT_NONE:
426 		if (KaliExpandAbbreviations)
427 			SetModeLectureKali(0);
428 		else
429 			SetModeLectureKali(1);
430 		break;
431 	case SPD_PUNCT_SOME:
432 		SetModeLectureKali(2);
433 		break;
434 	case SPD_PUNCT_MOST:
435 		/* XXX approximation */
436 		SetModeLectureKali(2);
437 		break;
438 	case SPD_PUNCT_ALL:
439 		SetModeLectureKali(3);
440 		break;
441 	default:
442 		break;
443 	}
444 }
445 
kali_set_voice(char * voice)446 static void kali_set_voice(char *voice)
447 {
448 	short i, nlang;
449 	char *v = voice;
450 
451 	if (v == NULL)
452 		v = KaliVoiceParameters;
453 
454 	for (i = 0; kali_voice_list[i] != NULL; i++) {
455 		if (strcasecmp(kali_voice_list[i]->name, v) == 0) {
456 			nlang = GetNLangueVoixKaliStd(i + 1);
457 			SetLangueKali(nlang);
458 			SetVoixKali(i + 1);
459 			break;
460 		}
461 	}
462 }
463 
kali_get_voices()464 static SPDVoice **kali_get_voices()
465 {
466 	short i;
467 	SPDVoice **result = NULL;
468 	short num_voices;
469 	char *voice;
470 	short nlang;
471 	char *language;
472 
473 	num_voices = GetNbVoixKali();
474 	DBG("Kali: %d voices total.", num_voices);
475 	voice = (char *)g_malloc(12);
476 	language = (char *)g_malloc(9);
477 	result = g_new0(SPDVoice *, num_voices);
478 
479 	for (i = 0; i < num_voices; i++) {
480 		result[i] = g_new0(SPDVoice, 1);
481 		GetNomVoixKali(i + 1, voice);
482 		result[i]->name = (char *)g_strdup(voice);
483 		nlang = GetNLangueVoixKaliStd(i + 1);
484 		GetNomLangueKali(nlang, language);
485 		result[i]->language = (char *)g_strdup(language);
486 		result[i]->variant = NULL;
487 	}
488 	result[i] = NULL;
489 
490 	g_free(voice);
491 	g_free(language);
492 
493 	return result;
494 }
495