/*************************************************************************** * Copyright (C) 2005 to 2007 by Jonathan Duddington * * email: jonsd@users.sourceforge.net * * * * This program 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 3 of the License, or * * (at your option) any later version. * * * * This program 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, write see: * * . * ***************************************************************************/ #include "StdAfx.h" #include "stdio.h" #include "ctype.h" #include "string.h" #include "stdlib.h" #include "wchar.h" #include "locale.h" #include #include #include "speech.h" #include #ifdef PLATFORM_WINDOWS #include #else #include #endif #include "speak_lib.h" #include "phoneme.h" #include "synthesize.h" #include "voice.h" #include "translate.h" #include "debug.h" #include "fifo.h" #include "event.h" #include "wave.h" unsigned char *outbuf=NULL; extern espeak_VOICE voice_selected; espeak_EVENT *event_list=NULL; int event_list_ix=0; int n_event_list; long count_samples; void* my_audio=NULL; static unsigned int my_unique_identifier=0; static void* my_user_data=NULL; static espeak_AUDIO_OUTPUT my_mode=AUDIO_OUTPUT_SYNCHRONOUS; static int synchronous_mode = 1; t_espeak_callback* synth_callback = NULL; int (* uri_callback)(int, const char *, const char *) = NULL; int (* phoneme_callback)(const char *) = NULL; char path_home[N_PATH_HOME]; // this is the espeak-data directory #ifdef USE_ASYNC static int dispatch_audio(short* outbuf, int length, espeak_EVENT* event) {//====================================================================== ENTER("dispatch_audio"); int a_wave_can_be_played = fifo_is_command_enabled(); #ifdef DEBUG_ENABLED SHOW("*** dispatch_audio > uid=%d, [write=%p (%d bytes)], sample=%d, a_wave_can_be_played = %d\n", (event) ? event->unique_identifier : 0, wave_test_get_write_buffer(), 2*length, (event) ? event->sample : 0, a_wave_can_be_played); #endif switch(my_mode) { case AUDIO_OUTPUT_PLAYBACK: { if (outbuf && length && a_wave_can_be_played) { wave_write (my_audio, (char*)outbuf, 2*length); } while(a_wave_can_be_played) { // TBD: some event are filtered here but some insight might be given // TBD: in synthesise.cpp for avoiding to create WORDs with size=0. // TBD: For example sentence "or ALT)." returns three words // "or", "ALT" and "". // TBD: the last one has its size=0. if (event && (event->type == espeakEVENT_WORD) && (event->length==0)) { break; } espeak_ERROR a_error = event_declare(event); if (a_error != EE_BUFFER_FULL) { break; } SHOW_TIME("dispatch_audio > EE_BUFFER_FULL\n"); usleep(10000); a_wave_can_be_played = fifo_is_command_enabled(); } } break; case AUDIO_OUTPUT_RETRIEVAL: if (synth_callback) { synth_callback(outbuf, length, event); } break; case AUDIO_OUTPUT_SYNCHRONOUS: case AUDIO_OUTPUT_SYNCH_PLAYBACK: break; } if (!a_wave_can_be_played) { SHOW_TIME("dispatch_audio > synth must be stopped!\n"); } SHOW_TIME("LEAVE dispatch_audio\n"); return (a_wave_can_be_played==0); // 1 = stop synthesis } static int create_events(short* outbuf, int length, espeak_EVENT* event, uint32_t the_write_pos) {//===================================================================== int finished; int i=0; // The audio data are written to the output device. // The list of events in event_list (index: event_list_ix) is read: // Each event is declared to the "event" object which stores them internally. // The event object is responsible of calling the external callback // as soon as the relevant audio sample is played. do { // for each event espeak_EVENT* event; if (event_list_ix == 0) { event = NULL; } else { event = event_list + i; #ifdef DEBUG_ENABLED SHOW("Synthesize: event->sample(%d) + %d = %d\n", event->sample, the_write_pos, event->sample + the_write_pos); #endif event->sample += the_write_pos; } #ifdef DEBUG_ENABLED SHOW("*** Synthesize: i=%d (event_list_ix=%d), length=%d\n",i,event_list_ix,length); #endif finished = dispatch_audio((short *)outbuf, length, event); length = 0; // the wave data are played once. i++; } while((i < event_list_ix) && !finished); return finished; } int sync_espeak_terminated_msg( uint unique_identifier, void* user_data) {//===================================================================== ENTER("sync_espeak_terminated_msg"); int finished=0; memset(event_list, 0, 2*sizeof(espeak_EVENT)); event_list[0].type = espeakEVENT_MSG_TERMINATED; event_list[0].unique_identifier = unique_identifier; event_list[0].user_data = user_data; event_list[1].type = espeakEVENT_LIST_TERMINATED; event_list[1].unique_identifier = unique_identifier; event_list[1].user_data = user_data; if (my_mode==AUDIO_OUTPUT_PLAYBACK) { while(1) { espeak_ERROR a_error = event_declare(event_list); if (a_error != EE_BUFFER_FULL) { break; } SHOW_TIME("sync_espeak_terminated_msg > EE_BUFFER_FULL\n"); usleep(10000); } } else { if (synth_callback) { finished=synth_callback(NULL,0,event_list); } } return finished; } #endif static void select_output(espeak_AUDIO_OUTPUT output_type) {//======================================================= my_mode = output_type; my_audio = NULL; synchronous_mode = 1; option_waveout = 1; // inhibit portaudio callback from wavegen.cpp switch(my_mode) { case AUDIO_OUTPUT_PLAYBACK: synchronous_mode = 0; #ifdef USE_ASYNC wave_init(); wave_set_callback_is_output_enabled( fifo_is_command_enabled); my_audio = wave_open("alsa"); event_init(); #endif break; case AUDIO_OUTPUT_RETRIEVAL: synchronous_mode = 0; break; case AUDIO_OUTPUT_SYNCHRONOUS: break; case AUDIO_OUTPUT_SYNCH_PLAYBACK: option_waveout = 0; WavegenInitSound(); break; } } // end of select_output int GetFileLength(const char *filename) {//==================================== struct stat statbuf; if(stat(filename,&statbuf) != 0) return(0); if((statbuf.st_mode & S_IFMT) == S_IFDIR) // if(S_ISDIR(statbuf.st_mode)) return(-2); // a directory return(statbuf.st_size); } // end of GetFileLength char *Alloc(int size) {//================== char *p; if((p = (char *)malloc(size)) == NULL) fprintf(stderr,"Can't allocate memory\n"); // I was told that size+1 fixes a crash on 64-bit systems return(p); } void Free(void *ptr) {//================= if(ptr != NULL) free(ptr); } static void init_path(const char *path) {//==================================== #ifdef PLATFORM_WINDOWS HKEY RegKey; unsigned long size; unsigned long var_type; char *env; unsigned char buf[sizeof(path_home)-13]; if(path != NULL) { sprintf(path_home,"%s/espeak-data",path); return; } if((env = getenv("ESPEAK_DATA_PATH")) != NULL) { sprintf(path_home,"%s/espeak-data",env); if(GetFileLength(path_home) == -2) return; // an espeak-data directory exists } buf[0] = 0; RegOpenKeyExA(HKEY_LOCAL_MACHINE, "Software\\Microsoft\\Speech\\Voices\\Tokens\\eSpeak", 0, KEY_READ, &RegKey); size = sizeof(buf); var_type = REG_SZ; RegQueryValueExA(RegKey, "path", 0, &var_type, buf, &size); sprintf(path_home,"%s\\espeak-data",buf); #else char *env; if(path != NULL) { snprintf(path_home,sizeof(path_home),"%s/espeak-data",path); return; } // check for environment variable if((env = getenv("ESPEAK_DATA_PATH")) != NULL) { snprintf(path_home,sizeof(path_home),"%s/espeak-data",env); if(GetFileLength(path_home) == -2) return; // an espeak-data directory exists } snprintf(path_home,sizeof(path_home),"%s/espeak-data",getenv("HOME")); if(access(path_home,R_OK) != 0) { strcpy(path_home,PATH_ESPEAK_DATA); } #endif } static int initialise(void) {//======================== int param; int result; LoadConfig(); WavegenInit(22050,0); // 22050 if((result = LoadPhData()) != 1) { if(result == -1) { fprintf(stderr,"Failed to load espeak-data\n"); exit(1); } else fprintf(stderr,"Wrong version of espeak-data 0x%x (expects 0x%x) at %s\n",result,version_phdata,path_home); } memset(&voice_selected,0,sizeof(voice_selected)); SetVoiceStack(NULL); SynthesizeInit(); InitNamedata(); for(param=0; param uid=%d, flags=%d, >>>text=%s<<<\n", unique_identifier, flags, text); } #endif if((outbuf==NULL) || (event_list==NULL)) return(EE_INTERNAL_ERROR); // espeak_Initialize() has not been called option_multibyte = flags & 7; option_ssml = flags & espeakSSML; option_phoneme_input = flags & espeakPHONEMES; option_endpause = flags & espeakENDPAUSE; count_samples = 0; #ifdef USE_ASYNC if(my_mode == AUDIO_OUTPUT_PLAYBACK) { a_write_pos = wave_get_write_position(my_audio); } #endif if(translator == NULL) { SetVoiceByName("default"); } SpeakNextClause(NULL,text,0); if(my_mode == AUDIO_OUTPUT_SYNCH_PLAYBACK) { for(;;) { #ifdef PLATFORM_WINDOWS Sleep(300); // 0.3s #else #ifdef USE_NANOSLEEP struct timespec period; struct timespec remaining; period.tv_sec = 0; period.tv_nsec = 300000000; // 0.3 sec nanosleep(&period,&remaining); #else sleep(1); #endif #endif if(SynthOnTimer() != 0) break; } return(EE_OK); } for(;;) { #ifdef DEBUG_ENABLED SHOW("Synthesize > %s\n","for (next)"); #endif out_ptr = outbuf; out_end = &outbuf[outbuf_size]; event_list_ix = 0; WavegenFill(0); length = (out_ptr - outbuf)/2; count_samples += length; event_list[event_list_ix].type = espeakEVENT_LIST_TERMINATED; // indicates end of event list event_list[event_list_ix].unique_identifier = my_unique_identifier; event_list[event_list_ix].user_data = my_user_data; count_buffers++; if (my_mode==AUDIO_OUTPUT_PLAYBACK) { #ifdef USE_ASYNC finished = create_events((short *)outbuf, length, event_list, a_write_pos); length = 0; // the wave data are played once. #endif } else { finished = synth_callback((short *)outbuf, length, event_list); } if(finished) { SpeakNextClause(NULL,0,2); // stop break; } if(Generate(phoneme_list,&n_phoneme_list,1)==0) { if(WcmdqUsed() == 0) { // don't process the next clause until the previous clause has finished generating speech. // This ensures that