| /* |
| * festival.c - Speech Dispatcher backend for Festival |
| * |
| * Copyright (C) 2003, 2007 Brailcom, o.p.s. |
| * |
| * This is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2, or (at your option) |
| * any later version. |
| * |
| * This software is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program. If not, see <https://www.gnu.org/licenses/>. |
| * |
| * $Id: festival.c,v 1.82 2008-06-09 10:33:38 hanke Exp $ |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include <config.h> |
| #endif |
| |
| #include <stdio.h> |
| |
| #include <speechd_types.h> |
| #include "fdsetconv.h" |
| |
| #include "festival_client.h" |
| #include "module_utils.h" |
| |
| #define MODULE_NAME "festival" |
| #define MODULE_VERSION "0.6" |
| |
| DECLARE_DEBUG() |
| |
| /* Thread and process control */ |
| static int festival_speaking = 0; |
| static int festival_pause_requested = 0; |
| |
| static SPDMessageType festival_message_type; |
| signed int festival_volume = 0; |
| |
| int festival_stop_request = 0; |
| int festival_stop = 0; |
| |
| int festival_process_pid = 0; |
| |
| FT_Info *festival_info = NULL; |
| |
| SPDVoice **festival_voice_list = NULL; |
| |
| enum { |
| FCT_SOCKET = 0, |
| FCT_LOCAL = 1, |
| } FestivalComType; |
| |
| struct { |
| int pipe_in[2]; |
| int pipe_out[2]; |
| int pid; |
| } module_p; |
| |
| #define COM_SOCKET ((FestivalComType == FCT_SOCKET) ? 1 : 0) |
| #define COM_LOCAL ((FestivalComType == FCT_LOCAL) ? 1 : 0) |
| |
| /* --- SETTINGS COMMANDS --- */ |
| |
| #define FEST_SET_STR(name, fest_param) \ |
| int \ |
| name(FT_Info *info, char *param, char **resp) \ |
| { \ |
| char *r; \ |
| int ret; \ |
| char *f; \ |
| if (festival_check_info(info, #name)) return -1; \ |
| if (param == NULL){ \ |
| FEST_SEND_CMD("("fest_param" nil)"); \ |
| }else{ \ |
| f = g_ascii_strdown(param, -1); \ |
| FEST_SEND_CMDA("("fest_param" \"%s\")", f); \ |
| g_free(f); \ |
| } \ |
| ret = festival_read_response(info, &r); \ |
| if (ret != 0) return -1; \ |
| if (r != NULL){ \ |
| if (resp != NULL) \ |
| *resp = r; \ |
| else \ |
| g_free(r); \ |
| } \ |
| return ret; \ |
| } |
| |
| #define FEST_SET_SYMB(name, fest_param) \ |
| int \ |
| name(FT_Info *info, char *param) \ |
| { \ |
| char *f = NULL; \ |
| if (festival_check_info(info, #name)) return -1; \ |
| if (param == NULL) return -1; \ |
| FEST_SEND_CMDA("("fest_param" '%s)", f = g_ascii_strdown(param, -1)); \ |
| g_free(f); \ |
| return festival_read_response(info, NULL); \ |
| } |
| |
| #define FEST_SET_INT(name, fest_param) \ |
| int \ |
| name(FT_Info *info, int param) \ |
| { \ |
| if (festival_check_info(info, #name)) return -1; \ |
| FEST_SEND_CMDA("("fest_param" %d)", param); \ |
| return festival_read_response(info, NULL); \ |
| } |
| |
| FEST_SET_SYMB(FestivalSetMultiMode, "speechd-enable-multi-mode") |
| |
| FEST_SET_INT(FestivalSetRate, "speechd-set-rate") |
| FEST_SET_INT(FestivalSetPitch, "speechd-set-pitch") |
| FEST_SET_SYMB(FestivalSetPunctuationMode, "speechd-set-punctuation-mode") |
| FEST_SET_STR(FestivalSetCapLetRecogn, |
| "speechd-set-capital-character-recognition-mode") |
| FEST_SET_STR(FestivalSetLanguage, "speechd-set-language") |
| FEST_SET_STR(FestivalSetVoice, "speechd-set-voice") |
| FEST_SET_SYMB(FestivalSetSynthesisVoice, "speechd-set-festival-voice") |
| |
| /* Internal functions prototypes */ |
| static SPDVoice **festivalGetVoices(FT_Info * info); |
| |
| void festival_parent_clean(); |
| |
| void festival_set_rate(signed int rate); |
| void festival_set_pitch(signed int pitch); |
| void festival_set_voice(SPDVoiceType voice); |
| void festival_set_synthesis_voice(char *synthesis_voice); |
| void festival_set_language(char *language); |
| void festival_set_punctuation_mode(SPDPunctuation punct); |
| void festival_set_cap_let_recogn(SPDCapitalLetters recogn); |
| void festival_set_volume(signed int volume); |
| |
| int init_festival_standalone(); |
| int init_festival_socket(); |
| |
| int is_text(SPDMessageType msg_type); |
| |
| MOD_OPTION_1_INT(FestivalComunicationType) |
| |
| MOD_OPTION_1_INT(FestivalMaxChunkLength) |
| MOD_OPTION_1_STR(FestivalDelimiters) |
| MOD_OPTION_1_STR(FestivalServerHost) |
| MOD_OPTION_1_STR(FestivalStripPunctChars) |
| MOD_OPTION_1_INT(FestivalServerPort) |
| MOD_OPTION_1_INT(FestivalPitchDeviation) |
| MOD_OPTION_1_INT(FestivalDebugSaveOutput) |
| MOD_OPTION_1_STR(FestivalRecodeFallback) |
| |
| MOD_OPTION_1_INT(FestivalCacheOn) |
| MOD_OPTION_1_INT(FestivalCacheMaxKBytes) |
| MOD_OPTION_1_INT(FestivalCacheDistinguishVoices) |
| MOD_OPTION_1_INT(FestivalCacheDistinguishRate) |
| MOD_OPTION_1_INT(FestivalCacheDistinguishPitch) |
| |
| MOD_OPTION_1_INT(FestivalReopenSocket) |
| |
| typedef struct { |
| size_t size; |
| GHashTable *caches; |
| GList *cache_counter; |
| } TCache; |
| |
| typedef struct { |
| time_t start; |
| int count; |
| size_t size; |
| GHashTable *p_caches; |
| char *key; |
| } TCounterEntry; |
| |
| typedef struct { |
| TCounterEntry *p_counter_entry; |
| FT_Wave *fwave; |
| } TCacheEntry; |
| |
| TCache FestivalCache; |
| |
| int cache_init(); |
| int cache_reset(); |
| int cache_insert(char *key, SPDMessageType msgtype, FT_Wave * value); |
| FT_Wave *cache_lookup(const char *key, SPDMessageType msgtype, int add_counter); |
| |
| /* Public functions */ |
| |
| int module_load(void) |
| { |
| |
| INIT_SETTINGS_TABLES(); |
| |
| REGISTER_DEBUG(); |
| |
| MOD_OPTION_1_INT_REG(FestivalComunicationType, 0); |
| |
| MOD_OPTION_1_STR_REG(FestivalServerHost, "localhost"); |
| MOD_OPTION_1_INT_REG(FestivalServerPort, 1314); |
| |
| MOD_OPTION_1_INT_REG(FestivalDebugSaveOutput, 0); |
| |
| MOD_OPTION_1_STR_REG(FestivalRecodeFallback, "?"); |
| |
| MOD_OPTION_1_INT_REG(FestivalCacheOn, 1); |
| MOD_OPTION_1_INT_REG(FestivalCacheMaxKBytes, 5120); |
| MOD_OPTION_1_INT_REG(FestivalCacheDistinguishVoices, 0); |
| MOD_OPTION_1_INT_REG(FestivalCacheDistinguishRate, 0); |
| MOD_OPTION_1_INT_REG(FestivalCacheDistinguishPitch, 0); |
| |
| /* TODO: Maybe switch this option to 1 when the bug with the 40ms delay |
| in Festival is fixed */ |
| MOD_OPTION_1_INT_REG(FestivalReopenSocket, 0); |
| |
| return 0; |
| } |
| |
| #define ABORT(msg) g_string_append(info, msg); \ |
| *status_info = g_string_free(info, 0); \ |
| return -1; |
| |
| int module_init(char **status_info) |
| { |
| int ret; |
| |
| GString *info; |
| |
| info = g_string_new(""); |
| |
| DBG("module_init()"); |
| |
| module_audio_set_server(); |
| |
| /* Initialize appropriate communication mechanism */ |
| FestivalComType = FestivalComunicationType; |
| if (COM_SOCKET) { |
| g_string_append(info, |
| "Communicating with Festival through a socket. "); |
| ret = init_festival_socket(); |
| if (ret == -1) { |
| ABORT |
| ("Can't connect to Festival server. Check your configuration " |
| "in etc/speech-dispatcher/modules/festival.conf for the specified host and port " |
| "and check if Festival is really running there, e.g. with telnet. " |
| "Please see documentation for more info."); |
| } else if (ret == -2) { |
| ABORT("Connect to the Festival server was successful, " |
| "but I got disconnected immediately. This is most likely " |
| "because of authorization problems. Check the variable " |
| "server_access_list in etc/festival.scm and consult documentation " |
| "for more information."); |
| } |
| } |
| if (COM_LOCAL) { |
| g_string_append(info, |
| "Communicating with Festival through a local child process."); |
| if (init_festival_standalone()) { |
| ABORT |
| ("Local connect to Festival failed for unknown reasons."); |
| } |
| } |
| |
| /* Get festival voice list */ |
| festival_voice_list = festivalGetVoices(festival_info); |
| if (!festival_voice_list) { |
| ABORT("No voice list"); |
| } |
| |
| cache_init(); |
| |
| festival_speaking = 0; |
| |
| *status_info = g_string_free(info, 0); |
| |
| return 0; |
| } |
| |
| #undef ABORT |
| |
| SPDVoice **module_list_voices(void) |
| { |
| g_free(festival_voice_list); |
| festival_voice_list = festivalGetVoices(festival_info); |
| return festival_voice_list; |
| } |
| |
| int module_stop(void) |
| { |
| DBG("stop()\n"); |
| |
| if (festival_speaking) { |
| /* if(COM_SOCKET) */ |
| if (0) { |
| if (festival_info != 0) |
| if ((festival_info->server_fd != -1) |
| && FestivalReopenSocket) { |
| /* TODO: Maybe use shutdown here? */ |
| close(festival_info->server_fd); |
| festival_info->server_fd = -1; |
| festival_connection_crashed = 1; |
| DBG("festival socket closed by module_stop()"); |
| } |
| } |
| if (COM_LOCAL) { |
| DBG("festival local stopped by sending SIGINT"); |
| /* TODO: Write this function for local communication */ |
| // festival_stop_local(); |
| } |
| |
| if (!festival_stop) |
| festival_stop = 1; |
| } |
| |
| return 0; |
| } |
| |
| size_t module_pause(void) |
| { |
| DBG("pause requested\n"); |
| if (festival_speaking) { |
| DBG("Sending request for pause to child\n"); |
| festival_pause_requested = 1; |
| DBG("Signalled to pause"); |
| return 0; |
| } else { |
| return -1; |
| } |
| } |
| |
| int module_close(void) |
| { |
| |
| DBG("festival: close()\n"); |
| |
| DBG("Stopping the module"); |
| |
| // DBG("festivalClose()"); |
| // festivalClose(festival_info); |
| |
| if (festival_info) { |
| delete_FT_Info(festival_info); |
| festival_info = NULL; |
| } |
| |
| /* TODO: Solve this */ |
| // DBG("Removing junk files in tmp/"); |
| // system("rm -f /tmp/est* 2> /dev/null"); |
| return 0; |
| } |
| |
| /* Internal functions */ |
| |
| #define CLEAN_UP(code, im) \ |
| { \ |
| if(!wave_cached) if (fwave) delete_FT_Wave(fwave); \ |
| festival_stop = 0; \ |
| festival_speaking = 0; \ |
| im(); \ |
| return; \ |
| } |
| |
| #define CLP(code, im) \ |
| { \ |
| festival_stop = 0; \ |
| festival_speaking = 0; \ |
| im(); \ |
| return; \ |
| } |
| |
| static SPDVoice **festivalGetVoices(FT_Info * info) |
| { |
| char *reply; |
| char **voices; |
| char *lang; |
| char *region; |
| int i, j; |
| int num_voices = 0; |
| SPDVoice **result; |
| |
| FEST_SEND_CMD("(apply append (voice-list-language-codes))"); |
| if (festival_read_response(info, &reply)) { |
| DBG("ERROR: Invalid reply for voice-list"); |
| return NULL; |
| } |
| /* Remove trailing newline */ |
| reply[strlen(reply) - 1] = 0; |
| DBG("Voice list reply: |%s|", reply); |
| voices = lisp_list_get_vect(reply); |
| g_free(reply); |
| if (voices == NULL) { |
| DBG("ERROR: Can't parse voice listing reply into vector"); |
| return NULL; |
| } |
| |
| /* Compute number of voices */ |
| for (i = 0;; i++, num_voices++) |
| if (voices[i] == NULL) |
| break; |
| num_voices /= 3; |
| |
| result = g_malloc((num_voices + 1) * sizeof(SPDVoice *)); |
| |
| for (i = 0, j = 0;; j++) { |
| if (voices[i] == NULL) |
| break; |
| else if (strlen(voices[i]) == 0) |
| continue; |
| else { |
| result[j] = g_malloc(sizeof(SPDVoice)); |
| result[j]->name = strdup(voices[i]); |
| lang = voices[i + 1]; |
| if (lang && !strcmp(lang, "nil")) |
| lang = NULL; |
| region = voices[i + 2]; |
| if (region && !strcmp(region, "nil")) |
| region = NULL; |
| if (lang && region) |
| result[j]->language = g_strdup_printf("%s-%s", lang, region); |
| else if (lang) |
| result[j]->language = g_strdup(lang); |
| else if (region) |
| result[j]->language = g_strdup(region); |
| else |
| result[j]->language = NULL; |
| result[j]->variant = NULL; |
| i += 3; |
| } |
| } |
| result[j] = NULL; |
| g_strfreev(voices); |
| return result; |
| } |
| |
| int festival_send_to_audio(FT_Wave * fwave, int first) |
| { |
| AudioTrack track; |
| #if defined(BYTE_ORDER) && (BYTE_ORDER == BIG_ENDIAN) |
| AudioFormat format = SPD_AUDIO_BE; |
| #else |
| AudioFormat format = SPD_AUDIO_LE; |
| #endif |
| |
| if (fwave->samples == NULL) |
| return 0; |
| |
| track.num_samples = fwave->num_samples; |
| track.num_channels = 1; |
| track.sample_rate = fwave->sample_rate; |
| track.bits = 16; |
| track.samples = fwave->samples; |
| |
| if (first) |
| module_strip_head_silence(&track); |
| |
| DBG("Sending to audio"); |
| |
| module_tts_output_server(&track, format); |
| DBG("Sent to audio."); |
| |
| return 0; |
| } |
| |
| void module_speak_sync(const char *festival_message, size_t bytes, SPDMessageType msgtype) |
| { |
| int ret; |
| int wave_cached; |
| FT_Wave *fwave; |
| int debug_count = 0; |
| int r; |
| int terminate = 0; |
| int first; |
| |
| char *callback = NULL; |
| |
| DBG("module_speak()\n"); |
| |
| if (festival_message == NULL) { |
| module_speak_error(); |
| return; |
| } |
| |
| if (festival_speaking) { |
| module_speak_error(); |
| DBG("Speaking when requested to write\n"); |
| return; |
| } |
| |
| festival_stop_request = 0; |
| |
| festival_message_type = msgtype; |
| if ((msgtype == SPD_MSGTYPE_TEXT) |
| && (msg_settings.spelling_mode == SPD_SPELL_ON)) |
| festival_message_type = SPD_MSGTYPE_SPELL; |
| |
| /* If the connection crashed or language or voice |
| change, we will need to set all the parameters again */ |
| if (COM_SOCKET) { |
| if (festival_connection_crashed) { |
| DBG("Recovering after a connection loss"); |
| CLEAN_OLD_SETTINGS_TABLE(); |
| festival_info = festivalOpen(festival_info); |
| if (festival_info) |
| festival_connection_crashed = 0; |
| else { |
| DBG("Can't recover. Not possible to open connection to Festival."); |
| module_speak_error(); |
| return; |
| } |
| ret = FestivalSetMultiMode(festival_info, "t"); |
| if (ret != 0) { |
| module_speak_error(); |
| return; |
| } |
| } |
| } |
| |
| /* If the voice was changed, re-set all the parameters */ |
| // TODO: Handle synthesis_voice change too |
| if ((msg_settings.voice_type != msg_settings_old.voice_type) |
| || ((msg_settings.voice.language != NULL) |
| && (msg_settings_old.voice.language != NULL) |
| && |
| (strcmp |
| (msg_settings.voice.language, |
| msg_settings_old.voice.language)))) { |
| DBG("Cleaning old settings table"); |
| CLEAN_OLD_SETTINGS_TABLE(); |
| } |
| |
| /* Setting voice parameters */ |
| DBG("Updating parameters"); |
| UPDATE_STRING_PARAMETER(voice.language, festival_set_language); |
| UPDATE_PARAMETER(voice_type, festival_set_voice); |
| UPDATE_STRING_PARAMETER(voice.name, festival_set_synthesis_voice); |
| UPDATE_PARAMETER(rate, festival_set_rate); |
| UPDATE_PARAMETER(pitch, festival_set_pitch); |
| UPDATE_PARAMETER(volume, festival_set_volume); |
| UPDATE_PARAMETER(punctuation_mode, festival_set_punctuation_mode); |
| UPDATE_PARAMETER(cap_let_recogn, festival_set_cap_let_recogn); |
| |
| if (festival_connection_crashed) { |
| module_speak_error(); |
| DBG("ERROR: Festival connection not working!"); |
| return; |
| } |
| |
| DBG("Requested data: |%s| \n", festival_message); |
| |
| festival_stop = 0; |
| festival_pause_requested = 0; |
| |
| festival_speaking = 1; |
| wave_cached = 0; |
| fwave = NULL; |
| |
| terminate = 0; |
| |
| module_speak_ok(); |
| |
| module_report_event_begin(); |
| |
| DBG("Going to synthesize: |%s|", festival_message); |
| if (bytes > 0) { |
| if (!is_text(festival_message_type)) { /* it is a raw text */ |
| DBG("Cache mechanisms..."); |
| fwave = |
| cache_lookup(festival_message, |
| festival_message_type, 1); |
| if (fwave != NULL) { |
| wave_cached = 1; |
| if (fwave->num_samples != 0) { |
| if (FestivalDebugSaveOutput) { |
| char filename_debug |
| [256]; |
| sprintf(filename_debug, |
| "/tmp/debug-festival-%d.snd", |
| debug_count++); |
| save_FT_Wave_snd(fwave, |
| filename_debug); |
| } |
| |
| festival_send_to_audio(fwave, 1); |
| |
| if (!festival_stop) { |
| CLEAN_UP(0, |
| module_report_event_end); |
| } else { |
| CLEAN_UP(0, |
| module_report_event_stop); |
| } |
| |
| } else { |
| CLEAN_UP(0, |
| module_report_event_end); |
| } |
| } |
| } |
| |
| /* Set multi-mode for appropriate kind of events */ |
| if (is_text(festival_message_type)) { /* it is a raw text */ |
| ret = FestivalSetMultiMode(festival_info, "t"); |
| if (ret != 0) |
| CLP(0, module_report_event_stop); |
| } else { /* it is some kind of event */ |
| ret = |
| FestivalSetMultiMode(festival_info, "nil"); |
| if (ret != 0) |
| CLP(0, module_report_event_stop); |
| } |
| |
| switch (festival_message_type) { |
| case SPD_MSGTYPE_TEXT: |
| r = festivalStringToWaveRequest(festival_info, |
| festival_message); |
| break; |
| case SPD_MSGTYPE_SOUND_ICON: |
| r = festivalSoundIcon(festival_info, |
| festival_message); |
| break; |
| case SPD_MSGTYPE_CHAR: |
| r = festivalCharacter(festival_info, |
| festival_message); |
| break; |
| case SPD_MSGTYPE_KEY: |
| /* TODO: make sure all SSIP cases are supported */ |
| r = festivalKey(festival_info, |
| festival_message); |
| break; |
| case SPD_MSGTYPE_SPELL: |
| r = festivalSpell(festival_info, |
| festival_message); |
| break; |
| default: |
| r = -1; |
| } |
| if (r < 0) { |
| DBG("Couldn't process the request to say the object."); |
| CLP(0, module_report_event_stop); |
| } |
| } |
| |
| first = 1; |
| while (1) { |
| /* Process server events in case we were told to stop in between */ |
| module_process(STDIN_FILENO, 0); |
| |
| wave_cached = 0; |
| DBG("Retrieving data\n"); |
| |
| /* (speechd-next) */ |
| if (is_text(festival_message_type)) { |
| |
| if (festival_stop) { |
| DBG("Module stopped 1"); |
| CLEAN_UP(0, module_report_event_stop); |
| } |
| |
| DBG("Getting data in multi mode"); |
| fwave = |
| festivalGetDataMulti(festival_info, |
| &callback, |
| &festival_stop_request, |
| FestivalReopenSocket); |
| |
| if (callback != NULL) { |
| DBG("Reporting mark %s", callback); |
| module_report_index_mark (callback); |
| if (festival_pause_requested && |
| !strncmp(callback, |
| INDEX_MARK_BODY, |
| INDEX_MARK_BODY_LEN)) { |
| DBG("Pause requested, pausing."); |
| g_free(callback); |
| CLEAN_UP(0, module_report_event_pause); |
| } |
| g_free(callback); |
| callback = NULL; |
| continue; |
| } |
| } else { /* is event */ |
| DBG("Getting data in single mode"); |
| fwave = |
| festivalStringToWaveGetData(festival_info); |
| terminate = 1; |
| callback = NULL; |
| } |
| |
| if (fwave == NULL) { |
| DBG("End of sound samples, terminating this message..."); |
| CLEAN_UP(0, module_report_event_end); |
| } |
| |
| if (festival_message_type == SPD_MSGTYPE_CHAR |
| || festival_message_type == SPD_MSGTYPE_KEY |
| || festival_message_type == |
| SPD_MSGTYPE_SOUND_ICON) { |
| DBG("Storing record for %s in cache\n", |
| festival_message); |
| /* cache_insert takes care of not inserting the same |
| message again */ |
| cache_insert(g_strdup(festival_message), |
| festival_message_type, fwave); |
| wave_cached = 1; |
| } |
| |
| if (festival_stop) { |
| DBG("Module stopped 2"); |
| CLEAN_UP(0, module_report_event_stop); |
| } |
| |
| if (fwave->num_samples != 0) { |
| DBG("Sending message to audio: %lu bytes\n", |
| (long unsigned)((fwave->num_samples) * |
| sizeof(short))); |
| |
| if (FestivalDebugSaveOutput) { |
| char filename_debug[256]; |
| sprintf(filename_debug, |
| "/tmp/debug-festival-%d.snd", |
| debug_count++); |
| save_FT_Wave_snd(fwave, filename_debug); |
| } |
| |
| DBG("Playing sound samples"); |
| festival_send_to_audio(fwave, first); |
| first = 0; |
| |
| if (!wave_cached) |
| delete_FT_Wave(fwave); |
| DBG("End of playing sound samples"); |
| } |
| |
| if (terminate) { |
| DBG("Ok, end of samples, returning"); |
| CLP(0, module_report_event_end); |
| } |
| |
| if (festival_stop) { |
| DBG("Module stopped 3"); |
| CLP(0, module_report_event_stop); |
| } |
| } |
| DBG("Festival: leaving module_speak_sync() normally\n\r"); |
| festival_speaking = 0; |
| return; |
| } |
| |
| int is_text(SPDMessageType msg_type) |
| { |
| if (msg_type == SPD_MSGTYPE_TEXT || msg_type == SPD_MSGTYPE_SPELL) |
| return 1; |
| else |
| return 0; |
| } |
| |
| void festival_set_language(char *language) |
| { |
| FestivalSetLanguage(festival_info, language, NULL); |
| g_free(festival_voice_list); |
| festival_voice_list = festivalGetVoices(festival_info); |
| } |
| |
| void festival_set_voice(SPDVoiceType voice) |
| { |
| char *voice_name; |
| |
| voice_name = EVoice2str(voice); |
| FestivalSetVoice(festival_info, voice_name, NULL); |
| g_free(voice_name); |
| } |
| |
| void festival_set_synthesis_voice(char *voice_name) |
| { |
| |
| FestivalSetSynthesisVoice(festival_info, voice_name); |
| } |
| |
| void festival_set_rate(signed int rate) |
| { |
| FestivalSetRate(festival_info, rate); |
| } |
| |
| void festival_set_pitch(signed int pitch) |
| { |
| FestivalSetPitch(festival_info, pitch); |
| } |
| |
| void festival_set_volume(signed int volume) |
| { |
| festival_volume = volume; |
| } |
| |
| void festival_set_punctuation_mode(SPDPunctuation punct) |
| { |
| char *punct_mode; |
| punct_mode = EPunctMode2str(punct); |
| FestivalSetPunctuationMode(festival_info, punct_mode); |
| g_free(punct_mode); |
| } |
| |
| void festival_set_cap_let_recogn(SPDCapitalLetters recogn) |
| { |
| char *recogn_mode; |
| |
| if (recogn == SPD_CAP_NONE) |
| recogn_mode = NULL; |
| else |
| recogn_mode = ECapLetRecogn2str(recogn); |
| FestivalSetCapLetRecogn(festival_info, recogn_mode, NULL); |
| g_free(recogn_mode); |
| } |
| |
| /* --- Cache related functions --- */ |
| |
| void cache_destroy_entry(gpointer data) |
| { |
| TCacheEntry *entry = data; |
| g_free(entry->fwave); |
| g_free(entry); |
| } |
| |
| void cache_destroy_table_entry(gpointer data) |
| { |
| g_hash_table_destroy(data); |
| } |
| |
| void cache_free_counter_entry(gpointer data, gpointer user_data) |
| { |
| g_free(((TCounterEntry *) data)->key); |
| g_free(data); |
| } |
| |
| int cache_init() |
| { |
| |
| if (FestivalCacheOn == 0) |
| return 0; |
| |
| FestivalCache.size = 0; |
| FestivalCache.caches = |
| g_hash_table_new_full(g_str_hash, g_str_equal, g_free, |
| cache_destroy_table_entry); |
| FestivalCache.cache_counter = NULL; |
| DBG("Cache: initialized"); |
| return 0; |
| } |
| |
| int cache_destroy() |
| { |
| g_hash_table_destroy(FestivalCache.caches); |
| g_list_foreach(FestivalCache.cache_counter, cache_free_counter_entry, |
| NULL); |
| g_list_free(FestivalCache.cache_counter); |
| return 0; |
| } |
| |
| int cache_reset() |
| { |
| /* TODO: it could free everything in the cache and go from start, |
| but currently it isn't called by anybody */ |
| return 0; |
| } |
| |
| /* Compare two cache entries according to their score (how many |
| times the entry was requested divided by the time it exists |
| in the database) */ |
| gint cache_counter_comp(gconstpointer a, gconstpointer b) |
| { |
| const TCounterEntry *A = a; |
| const TCounterEntry *B = b; |
| time_t t; |
| float ret; |
| |
| t = time(NULL); |
| ret = (((float)A->count / (float)(t - A->start)) |
| - ((float)B->count / (float)(t - B->start))); |
| |
| if (ret > 0) |
| return -1; |
| if (ret == 0) |
| return 0; |
| if (ret < 0) |
| return 1; |
| |
| return 0; |
| } |
| |
| /* List scores of all entries in the cache*/ |
| void cache_debug_foreach_list_score(gpointer a, gpointer user) |
| { |
| const TCounterEntry *A = a; |
| |
| DBG("key: %s -> score %f (count: %d, dtime: %d)", A->key, |
| ((float)A->count / (float)(time(NULL) - A->start)), (int)A->count, |
| (int)(time(NULL) - A->start)); |
| } |
| |
| /* Remove 1/3 of the least used (according to cache_counter_comp) entries |
| (measured by size) */ |
| int cache_clean(size_t new_element_size) |
| { |
| size_t req_size; |
| GList *gl; |
| TCounterEntry *centry; |
| |
| DBG("Cache: cleaning, cache size %lu kbytes (>max %d).", |
| (unsigned long)(FestivalCache.size / 1024), FestivalCacheMaxKBytes); |
| |
| req_size = 2 * FestivalCache.size / 3; |
| |
| FestivalCache.cache_counter = |
| g_list_sort(FestivalCache.cache_counter, cache_counter_comp); |
| g_list_foreach(FestivalCache.cache_counter, |
| cache_debug_foreach_list_score, NULL); |
| |
| while ((FestivalCache.size + new_element_size) > req_size) { |
| gl = g_list_last(FestivalCache.cache_counter); |
| if (gl == NULL) |
| break; |
| if (gl->data == NULL) { |
| DBG("Error: Cache: gl->data in cache_clean is NULL, but shouldn't be."); |
| return -1; |
| } |
| centry = gl->data; |
| DBG("Cache: Removing element with key '%s'", centry->key); |
| if (FestivalCache.size < centry->size) { |
| DBG("Error: Cache: FestivalCache.size < centry->size, this shouldn't be."); |
| return -1; |
| } |
| FestivalCache.size -= centry->size; |
| /* Remove the data itself from the hash table */ |
| g_hash_table_remove(centry->p_caches, centry->key); |
| /* Remove the associated entry in the counter list */ |
| cache_free_counter_entry(centry, NULL); |
| FestivalCache.cache_counter = |
| g_list_delete_link(FestivalCache.cache_counter, gl); |
| } |
| |
| return 0; |
| } |
| |
| /* Generate a key for searching between the different hash tables */ |
| char *cache_gen_key(SPDMessageType type) |
| { |
| char *key; |
| char ktype; |
| int kpitch = 0, krate = 0, kvoice = 0; |
| |
| if (msg_settings.voice.language == NULL) |
| return NULL; |
| |
| DBG("v, p, r = %d %d %d", FestivalCacheDistinguishVoices, |
| FestivalCacheDistinguishPitch, FestivalCacheDistinguishRate); |
| |
| if (FestivalCacheDistinguishVoices) |
| kvoice = msg_settings.voice_type; |
| if (FestivalCacheDistinguishPitch) |
| kpitch = msg_settings.pitch; |
| if (FestivalCacheDistinguishRate) |
| krate = msg_settings.rate; |
| |
| if (type == SPD_MSGTYPE_CHAR) |
| ktype = 'c'; |
| else if (type == SPD_MSGTYPE_KEY) |
| ktype = 'k'; |
| else if (type == SPD_MSGTYPE_SOUND_ICON) |
| ktype = 's'; |
| else { |
| DBG("Invalid message type for cache_gen_key()"); |
| return NULL; |
| } |
| |
| key = |
| g_strdup_printf("%c_%s_%d_%d_%d", ktype, |
| msg_settings.voice.language, kvoice, krate, kpitch); |
| |
| return key; |
| } |
| |
| /* Insert one entry into the cache */ |
| int cache_insert(char *key, SPDMessageType msgtype, FT_Wave * fwave) |
| { |
| GHashTable *cache; |
| TCacheEntry *entry; |
| TCounterEntry *centry; |
| char *key_table; |
| |
| if (FestivalCacheOn == 0) |
| return 0; |
| |
| if (key == NULL) |
| return -1; |
| if (fwave == NULL) |
| return -1; |
| |
| /* Check if the entry isn't present already */ |
| if (cache_lookup(key, msgtype, 0) != NULL) |
| return 0; |
| |
| /* Clean less used cache entries if the size would exceed max. size */ |
| if ((FestivalCache.size + fwave->num_samples * sizeof(short)) |
| > (FestivalCacheMaxKBytes * 1024)) |
| if (cache_clean(fwave->num_samples * sizeof(short)) != 0) |
| return -1; |
| |
| key_table = cache_gen_key(msgtype); |
| |
| DBG("Cache: Inserting wave with key:'%s' into table '%s'", key, |
| key_table); |
| |
| /* Select the right table according to language, voice, etc. or create a new one */ |
| cache = g_hash_table_lookup(FestivalCache.caches, key_table); |
| if (cache == NULL) { |
| cache = g_hash_table_new(g_str_hash, g_str_equal); |
| g_hash_table_insert(FestivalCache.caches, key_table, cache); |
| } else { |
| g_free(key_table); |
| } |
| |
| /* Fill the CounterEntry structure that will later allow us to remove |
| the less used entries from cache */ |
| centry = (TCounterEntry *) g_malloc(sizeof(TCounterEntry)); |
| centry->start = time(NULL); |
| centry->count = 1; |
| centry->size = fwave->num_samples * sizeof(short); |
| centry->p_caches = cache; |
| centry->key = g_strdup(key); |
| FestivalCache.cache_counter = |
| g_list_append(FestivalCache.cache_counter, centry); |
| |
| entry = (TCacheEntry *) g_malloc(sizeof(TCacheEntry)); |
| entry->p_counter_entry = centry; |
| entry->fwave = fwave; |
| |
| FestivalCache.size += centry->size; |
| g_hash_table_insert(cache, g_strdup(key), entry); |
| |
| return 0; |
| } |
| |
| /* Retrieve wave from the cache */ |
| FT_Wave *cache_lookup(const char *key, SPDMessageType msgtype, int add_counter) |
| { |
| GHashTable *cache; |
| TCacheEntry *entry; |
| char *key_table; |
| |
| if (FestivalCacheOn == 0) |
| return NULL; |
| if (key == NULL) |
| return NULL; |
| |
| key_table = cache_gen_key(msgtype); |
| |
| if (add_counter) |
| DBG("Cache: looking up a wave with key '%s' in '%s'", key, |
| key_table); |
| |
| if (key_table == NULL) |
| return NULL; |
| cache = g_hash_table_lookup(FestivalCache.caches, key_table); |
| g_free(key_table); |
| if (cache == NULL) |
| return NULL; |
| |
| entry = g_hash_table_lookup(cache, key); |
| if (entry == NULL) |
| return NULL; |
| entry->p_counter_entry->count++; |
| |
| DBG("Cache: corresponding wave found: %s", key); |
| |
| return entry->fwave; |
| } |
| |
| int init_festival_standalone() |
| { |
| int ret; |
| int fr; |
| |
| if ((pipe(module_p.pipe_in) != 0) |
| || (pipe(module_p.pipe_out) != 0)) { |
| DBG("Can't open pipe! Module not loaded."); |
| return -1; |
| } |
| |
| DBG("Starting Festival as a child process"); |
| |
| fr = fork(); |
| switch (fr) { |
| case -1: |
| DBG("ERROR: Can't fork! Module not loaded."); |
| return -1; |
| case 0: |
| ret = dup2(module_p.pipe_in[0], 0); |
| close(module_p.pipe_in[0]); |
| close(module_p.pipe_in[1]); |
| |
| ret = dup2(module_p.pipe_out[1], 1); |
| close(module_p.pipe_out[1]); |
| close(module_p.pipe_out[0]); |
| |
| /* TODO: fix festival hardcoded path */ |
| if (execlp("festival", "", (char *)0) == -1) |
| exit(1); |
| |
| default: |
| festival_process_pid = fr; |
| close(module_p.pipe_in[0]); |
| close(module_p.pipe_out[1]); |
| |
| usleep(100); /* So that the other child has at least time to fail |
| with the execlp */ |
| ret = waitpid(module_p.pid, NULL, WNOHANG); |
| if (ret != 0) { |
| DBG("Can't execute festival. Bad filename in configuration?"); |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| assert(0); |
| } |
| |
| int init_festival_socket() |
| { |
| int r; |
| |
| /* Init festival and register a new voice */ |
| festival_info = festivalDefaultInfo(); |
| festival_info->server_host = FestivalServerHost; |
| festival_info->server_port = FestivalServerPort; |
| |
| festival_info = festivalOpen(festival_info); |
| if (festival_info == NULL) |
| return -1; |
| r = FestivalSetMultiMode(festival_info, "t"); |
| if (r != 0) |
| return -2; |
| |
| DBG("FestivalServerHost = %s\n", FestivalServerHost); |
| DBG("FestivalServerPort = %d\n", FestivalServerPort); |
| |
| return 0; |
| } |
| |
| int stop_festival_local() |
| { |
| if (festival_process_pid > 0) |
| kill(festival_process_pid, SIGINT); |
| return 0; |
| } |