mirror of
https://github.com/rhasspy/espeak-ng.git
synced 2026-11-01 13:54:20 +00:00
Fix: Speak punctuation option also caused text after [[ to be spoken as phoneme codes even if espeakPHONEMES flag is not set. Speak sequences of letters and dots as individual letters, and don't speak 'dot', eg. "u.s.a." Don't announce punctuation in the alternative text inside <audio ... </audio> Vowelcharts, show the positions of multiple FMT() in vowel phonemes definitions. Phoneme definitions: New attributes: flag1, flag2, flag3 New attribute: nopause. Prevents the insertion of a short pause when this phoneme starts a word which follows a vowel. New conditions: isFlag1, isFlag2, isFlag3, isSibilant. New statement: InsertPhoneme() Phonemes: improve syllablic [m-] [n-] [N-] *_rules: add attributes $p_alt .. $p_alt3, $w_alt .. $w_alt3 *_list: add attributes: $sentence, $atstart klatt synthesizer: implement echo. espeakedit, Prosody display: Indicate stressed and secondary-stress syllables. espeakedit: remember window size and position. Intonation: New file, 'phsource/intonation' to define 'tunes' which can be used from voice files. espeakedit: add Compile -> Intonationm data Speed: Increase range to 80 to 450, with default=175. Improve speaking at high speeds. lang=en: Reduce consecutive unstressed syllables to 'diminished' stress, only in unstressed words. lang=de: Change 'r' phoneme. git-svn-id: https://espeak.svn.sourceforge.net/svnroot/espeak/trunk@244 d46cf337-b52f-0410-862d-fd96e6ae7743
502 lines
12 KiB
C++
Executable File
502 lines
12 KiB
C++
Executable File
/***************************************************************************
|
|
* 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, see: *
|
|
* <http://www.gnu.org/licenses/>. *
|
|
***************************************************************************/
|
|
|
|
|
|
|
|
#include "wx/wx.h"
|
|
|
|
|
|
#include "speak_lib.h"
|
|
#include "speech.h"
|
|
#include "phoneme.h"
|
|
#include "main.h"
|
|
#include "synthesize.h"
|
|
#include "voice.h"
|
|
#include "translate.h"
|
|
#include "prosodydisplay.h"
|
|
|
|
#ifdef PLATFORM_POSIX
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
|
|
#include "wx/font.h"
|
|
#include "wx/textctrl.h"
|
|
#include "wx/button.h"
|
|
#include "wx/checkbox.h"
|
|
#include "wx/filename.h"
|
|
#include "wx/sound.h"
|
|
|
|
#define T_SOURCE 501
|
|
#define T_PHONETIC 502
|
|
#define T_TRANSLATE 503
|
|
#define T_PROCESS 504
|
|
#define T_RULES 505
|
|
|
|
TranslDlg *transldlg = NULL;
|
|
extern char *speech_to_phonemes(char *data, char *phout);
|
|
extern ProsodyDisplay *prosodycanvas;
|
|
extern void Write4Bytes(FILE *f, int value);
|
|
|
|
BEGIN_EVENT_TABLE(TranslDlg, wxPanel)
|
|
EVT_BUTTON(T_TRANSLATE,TranslDlg::OnCommand)
|
|
EVT_BUTTON(T_PROCESS,TranslDlg::OnCommand)
|
|
EVT_BUTTON(T_RULES,TranslDlg::OnCommand)
|
|
END_EVENT_TABLE()
|
|
|
|
|
|
FILE *f_wave = NULL;
|
|
|
|
|
|
class IPATextCtrl : public wxTextCtrl
|
|
{
|
|
public:
|
|
void OnKey(wxKeyEvent& event);
|
|
IPATextCtrl(wxWindow *parent,wxWindowID id,const wxPoint& pos,const wxSize& size);
|
|
|
|
DECLARE_EVENT_TABLE()
|
|
};
|
|
|
|
BEGIN_EVENT_TABLE(IPATextCtrl, wxTextCtrl)
|
|
EVT_CHAR(IPATextCtrl::OnKey)
|
|
END_EVENT_TABLE()
|
|
|
|
|
|
// using Kirschenbaum scheme to enter IPA from the keyboard
|
|
wchar_t ipa1[256] = {
|
|
0,0x250,32,32,32,32,32,32,32,32,0xa,0xb,0xc,0x26f,0x272,0x275,
|
|
32,32,0x25a,32,32,32,32,0x28d,32,32,32,32,32,32,32,32,
|
|
0x20,0x21,0x22,0x23,0x24,0x25,0x0e6,0x27,0x28,0x27e,0x2a,0x2b,0x2c,0x2d,0x2e,0x2f,
|
|
0x252,0x31,0x32,0x25c,0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x3b,0x3c,0x3d,0x3e,0x294,
|
|
0x259,0x251,0x3b2,0xe7,0xf0,0x25b,0x46,0x262,0x127,0x26a,0x25f,0x4b,0x26c,0x271,0x14b,0x254,
|
|
0x3a6,0x263,0x280,0x283,0x3b8,0x28a,0x28c,0x153,0x3c7,0xf8,0x292,0x32a,0x5c,0x5d,0x5e,0x5f,
|
|
0x60,0x61,0x62,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x6b,0x6c,0x6d,0x6e,0x6f,
|
|
0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x7b,0x7c,0x7d,0x303,0x7f,
|
|
0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8a,0x8b,0x8c,0x8d,0x8e,0x8f,
|
|
0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0x9b,0x9c,0x9d,0x9e,0x9f,
|
|
0xa0,0xa1,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xab,0xac,0xad,0xae,0xaf,
|
|
0xb0,0xb1,0xb2,0xb3,0xb4,0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xbb,0xbc,0xbd,0xbe,0xbf,
|
|
0xc0,0xc1,0xc2,0xc3,0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xcb,0xcc,0xcd,0xce,0xcf,
|
|
0xd0,0xd1,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xdb,0xdc,0xdd,0xde,0xdf,
|
|
0xe0,0xe1,0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xeb,0xec,0xed,0xee,0xef,
|
|
0xf0,0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,0xf9,0xfa,0xfb,0xfc,0xfd,0xfe,0xff
|
|
};
|
|
|
|
|
|
IPATextCtrl::IPATextCtrl(wxWindow *parent,wxWindowID id,const wxPoint& pos,const wxSize& size) :
|
|
wxTextCtrl(parent,id,_T(""),pos,size,wxTE_MULTILINE)
|
|
{
|
|
wxTextAttr attr;
|
|
wxFont font = wxFont(12,wxFONTFAMILY_ROMAN,wxFONTSTYLE_NORMAL,wxFONTWEIGHT_LIGHT);
|
|
|
|
attr.SetFont(font);
|
|
SetDefaultStyle(attr);
|
|
}
|
|
|
|
void IPATextCtrl::OnKey(wxKeyEvent& event)
|
|
{
|
|
long key;
|
|
wchar_t wbuf[3];
|
|
|
|
key = event.m_keyCode;
|
|
key = event.GetKeyCode();
|
|
|
|
if(event.ControlDown())
|
|
key = key & 0x1f;
|
|
|
|
if(key==0xd && !event.ControlDown())
|
|
event.Skip(); // ENTER
|
|
else
|
|
if(key != 8 && key < 0x7f)
|
|
{
|
|
wbuf[0] = ipa1[key];
|
|
wbuf[1] = 0;
|
|
WriteText(wxString(wbuf));
|
|
}
|
|
else
|
|
event.Skip();
|
|
}
|
|
|
|
|
|
|
|
|
|
void PlayWavFile(const char *fname)
|
|
{//================================
|
|
char command[120];
|
|
|
|
#ifdef PLATFORM_WINDOWS
|
|
wxSound(wxString(fname,wxConvLocal)).Play(wxSOUND_SYNC);
|
|
#else
|
|
sprintf(command,"play %s",fname);
|
|
if(system(command) == -1)
|
|
{
|
|
wxLogError(_T("Failed to run system command:\n\n"+wxString(command,wxConvLocal)));
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
char *WavFileName(void)
|
|
{//====================
|
|
static char f_speech[120];
|
|
if(!wxDirExists(wxPathOnly(path_speech)))
|
|
{
|
|
path_speech = wxFileSelector(_T("Speech output file"),
|
|
path_phsource,_T("speech.wav"),_T("*"),_T("*"),wxSAVE);
|
|
}
|
|
strcpy(f_speech,path_speech.mb_str(wxConvLocal));
|
|
return(f_speech);
|
|
}
|
|
|
|
|
|
|
|
int OpenWaveFile2(const char *path)
|
|
/*********************************/
|
|
{
|
|
// Set the length of 0x7fffffff for --stdout
|
|
// This will be changed to the correct length for -w (write to file)
|
|
static unsigned char wave_hdr[44] = {
|
|
'R','I','F','F',0,0,0,0,'W','A','V','E','f','m','t',' ',
|
|
0x10,0,0,0,1,0,1,0, 9,0x3d,0,0,0x12,0x7a,0,0,
|
|
2,0,0x10,0,'d','a','t','a', 0xff,0xff,0xff,0x7f};
|
|
|
|
if(path == NULL)
|
|
return(2);
|
|
|
|
if(strcmp(path,"stdout")==0)
|
|
f_wave = stdout;
|
|
else
|
|
f_wave = fopen(path,"wb");
|
|
|
|
if(f_wave != NULL)
|
|
{
|
|
fwrite(wave_hdr,1,24,f_wave);
|
|
Write4Bytes(f_wave,samplerate);
|
|
Write4Bytes(f_wave,samplerate * 2);
|
|
fwrite(&wave_hdr[32],1,12,f_wave);
|
|
return(0);
|
|
}
|
|
wxLogStatus(_T("Can't write to WAV file: '"+wxString(path,wxConvLocal))+_T("'"));
|
|
return(1);
|
|
} // end of OpenWaveFile
|
|
|
|
|
|
|
|
|
|
void CloseWaveFile2()
|
|
/******************/
|
|
{
|
|
unsigned int pos;
|
|
|
|
if((f_wave == NULL) || (f_wave == stdout))
|
|
return;
|
|
|
|
fflush(f_wave);
|
|
pos = ftell(f_wave);
|
|
|
|
fseek(f_wave,4,SEEK_SET);
|
|
Write4Bytes(f_wave,pos - 8);
|
|
|
|
fseek(f_wave,40,SEEK_SET);
|
|
Write4Bytes(f_wave,pos - 44);
|
|
|
|
|
|
fclose(f_wave);
|
|
f_wave = NULL;
|
|
|
|
} // end of CloseWaveFile
|
|
|
|
|
|
|
|
|
|
int WavegenFile2(void)
|
|
{//==================
|
|
int finished;
|
|
unsigned char wav_outbuf[1024];
|
|
|
|
out_ptr = out_start = wav_outbuf;
|
|
out_end = wav_outbuf + sizeof(wav_outbuf);
|
|
|
|
finished = WavegenFill(0);
|
|
|
|
if(f_wave != NULL)
|
|
{
|
|
fwrite(wav_outbuf, 1, out_ptr-wav_outbuf, f_wave);
|
|
}
|
|
return(finished);
|
|
} // end of WavegenFile
|
|
|
|
|
|
void MakeWave2(PHONEME_LIST *p, int n_phonemes)
|
|
{//============================================
|
|
int result;
|
|
char *fname_speech;
|
|
int n_ph;
|
|
|
|
#ifdef KLATT_TEST
|
|
KlattInit();
|
|
#endif
|
|
n_ph = n_phonemes;
|
|
fname_speech = WavFileName();
|
|
OpenWaveFile2(fname_speech);
|
|
|
|
Generate(p,&n_ph,0);
|
|
|
|
for(;;)
|
|
{
|
|
result = WavegenFile2();
|
|
if(result != 0)
|
|
break;
|
|
Generate(p,&n_ph,1);
|
|
}
|
|
|
|
CloseWaveFile2();
|
|
PlayWavFile(fname_speech);
|
|
|
|
} // end of MakeWave2
|
|
|
|
|
|
|
|
|
|
TranslDlg::TranslDlg(wxWindow *parent) : wxPanel(parent)
|
|
{//=====================================================
|
|
|
|
int height;
|
|
int width;
|
|
int x,y;
|
|
int height_ph = 350;
|
|
|
|
wxTextAttr attr;
|
|
wxFont font = wxFont(12,wxFONTFAMILY_ROMAN,wxFONTSTYLE_NORMAL,wxFONTWEIGHT_LIGHT,false,_T(""),wxFONTENCODING_SYSTEM);
|
|
|
|
attr.SetFont(font);
|
|
|
|
wxClientDisplayRect(&x,&y,&width, &height);
|
|
#ifdef PLATFORM_WINDOWS
|
|
if(height <= 768)
|
|
height_ph = height - 416;
|
|
#else
|
|
if(height <= 800)
|
|
height_ph = 280;
|
|
#endif
|
|
|
|
|
|
t_source = new wxTextCtrl(this,T_SOURCE,_T(""),wxPoint(0,4),
|
|
wxSize(298,250),wxTE_MULTILINE,wxDefaultValidator,_T("Text input window"));
|
|
t_source->SetDefaultStyle(attr);
|
|
|
|
|
|
t_phonetic = new wxTextCtrl(this,T_PHONETIC,_T(""),wxPoint(0,262),
|
|
wxSize(298,height_ph),wxTE_MULTILINE | wxTE_READONLY,wxDefaultValidator,_T("Phoneme translation window"));
|
|
|
|
y = height_ph + 270;
|
|
t_translate = new wxButton(this,T_TRANSLATE,_T("Translate"),wxPoint(4,y));
|
|
t_translate = new wxButton(this,T_RULES,_T("Show Rules"),wxPoint(4,y+32));
|
|
t_process = new wxButton(this,T_PROCESS,_T("Speak"),wxPoint(100,y));
|
|
|
|
t_source->SetFocus();
|
|
} // end of TransDlg::TransDlg
|
|
|
|
|
|
|
|
void TranslDlg::SpeakFile(void)
|
|
{//============================
|
|
wxString file;
|
|
wxFileName fname;
|
|
FILE *f_text;
|
|
char buf[200];
|
|
|
|
fname = wxFileName(path_speaktext);
|
|
|
|
file = wxFileSelector(_T("Text file to speak"),fname.GetPath(),fname.GetName(),_T(""),_T("*"),wxOPEN);
|
|
if(file == wxEmptyString) return;
|
|
|
|
strcpy(buf,file.mb_str(wxConvLocal));
|
|
f_text = fopen(buf,"r");
|
|
if(f_text == NULL)
|
|
{
|
|
wxLogError(_T("Failed to read: ")+file);
|
|
return;
|
|
}
|
|
|
|
path_speaktext = file;
|
|
|
|
InitText(0);
|
|
SpeakNextClause(f_text,NULL,0);
|
|
return;
|
|
} // end of SpeakFile
|
|
|
|
|
|
|
|
void TranslDlg::ReadVoice(int variant)
|
|
{//===================================
|
|
wxString path;
|
|
wxString filename;
|
|
char *p;
|
|
char vname[40];
|
|
char fname[sizeof(path_home)+10];
|
|
|
|
if(variant)
|
|
{
|
|
// remove variant from the previous voice name
|
|
if((p = strchr(voice_name2,'+')) != NULL)
|
|
*p = 0;
|
|
|
|
sprintf(fname,"%s/voices/!v",path_home);
|
|
path = wxFileSelector(_T("Load voice variant"),wxString(fname,wxConvLocal),_T(""),_T(""),_T("*"),wxOPEN);
|
|
if(path.IsEmpty())
|
|
{
|
|
strcpy(fname,voice_name2);
|
|
}
|
|
else
|
|
{
|
|
filename = path.Mid(strlen(fname)+1);
|
|
strcpy(vname,filename.mb_str(wxConvLocal));
|
|
sprintf(fname,"%s+%s",voice_name2,vname);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
sprintf(fname,"%s/voices",path_home);
|
|
path = wxFileSelector(_T("Load voice"),wxString(fname,wxConvLocal),_T(""),_T(""),_T("*"),wxOPEN);
|
|
if(path.IsEmpty())
|
|
return;
|
|
|
|
filename = path.Mid(strlen(fname)+1);
|
|
strcpy(fname,filename.mb_str(wxConvLocal));
|
|
}
|
|
|
|
if(SetVoiceByName(fname) != EE_OK)
|
|
{
|
|
wxLogError(_T("Failed to load voice data"));
|
|
}
|
|
else
|
|
{
|
|
strcpy(voice_name2,fname);
|
|
}
|
|
WavegenSetVoice(voice);
|
|
}
|
|
|
|
|
|
void TranslDlg::OnCommand(wxCommandEvent& event)
|
|
{//=============================================
|
|
#define N_PH_LIST N_PHONEME_LIST
|
|
|
|
void *vp;
|
|
char buf[1000];
|
|
char phon_out[N_PH_LIST*2];
|
|
int clause_tone;
|
|
int clause_count;
|
|
FILE *f;
|
|
int fd_temp;
|
|
char fname_temp[100];
|
|
static int n_ph_list;
|
|
static PHONEME_LIST ph_list[N_PH_LIST+1];
|
|
|
|
option_phonemes = 0;
|
|
|
|
switch(event.GetId())
|
|
{
|
|
case T_RULES:
|
|
case MENU_SPEAK_RULES:
|
|
option_phonemes = 2;
|
|
|
|
#ifdef PLATFORM_POSIX
|
|
strcpy(fname_temp,"/tmp/espeakXXXXXX");
|
|
if((fd_temp = mkstemp(fname_temp)) >= 0)
|
|
{
|
|
close(fd_temp);
|
|
|
|
if((f = fopen(fname_temp,"w+")) != NULL)
|
|
{
|
|
f_trans = f; // write translation rule trace to a temp file
|
|
}
|
|
}
|
|
#else
|
|
strcpy(fname_temp,tmpnam(NULL));
|
|
if((f = fopen(fname_temp,"w+")) != NULL)
|
|
{
|
|
f_trans = f; // write translation rule trace to a temp file
|
|
}
|
|
#endif
|
|
case T_TRANSLATE:
|
|
case MENU_SPEAK_TRANSLATE:
|
|
option_multibyte = espeakCHARS_AUTO;
|
|
SpeakNextClause(NULL,NULL,2); // stop speaking file
|
|
|
|
strncpy0(buf,t_source->GetValue().mb_str(wxConvUTF8),sizeof(buf));
|
|
phon_out[0] = 0;
|
|
n_ph_list = 0;
|
|
clause_count = 0;
|
|
|
|
vp = buf;
|
|
InitText(0);
|
|
while((vp != NULL) && (n_ph_list < N_PH_LIST))
|
|
{
|
|
vp = TranslateClause(translator,NULL,vp,&clause_tone,NULL);
|
|
CalcPitches(translator,clause_tone);
|
|
CalcLengths(translator);
|
|
|
|
GetTranslatedPhonemeString(translator->phon_out,sizeof(translator->phon_out));
|
|
if(clause_count++ > 0)
|
|
strcat(phon_out," ||");
|
|
strcat(phon_out,translator->phon_out);
|
|
t_phonetic->SetValue(wxString(translator->phon_out,wxConvLocal));
|
|
|
|
if((n_ph_list + n_phoneme_list) >= N_PH_LIST)
|
|
{
|
|
n_phoneme_list = N_PH_LIST - n_ph_list - n_phoneme_list;
|
|
}
|
|
|
|
memcpy(&ph_list[n_ph_list],phoneme_list,sizeof(PHONEME_LIST)*n_phoneme_list);
|
|
n_ph_list += n_phoneme_list;
|
|
}
|
|
|
|
t_phonetic->Clear();
|
|
if(option_phonemes == 2)
|
|
{
|
|
option_phonemes=0;
|
|
rewind(f_trans);
|
|
while(fgets(buf,sizeof(buf),f_trans) != NULL)
|
|
{
|
|
t_phonetic->AppendText(wxString(buf,wxConvUTF8));
|
|
}
|
|
t_phonetic->AppendText(_T("---\n"));
|
|
if(f_trans != NULL)
|
|
fclose(f_trans);
|
|
remove(fname_temp);
|
|
}
|
|
t_phonetic->AppendText(wxString(phon_out,wxConvLocal));
|
|
break;
|
|
|
|
case T_PROCESS:
|
|
case MENU_SPEAK_TEXT:
|
|
if(prosodycanvas == NULL)
|
|
{
|
|
myframe->OnProsody(event);
|
|
}
|
|
prosodycanvas->LayoutData(ph_list,n_ph_list);
|
|
option_phoneme_events = 1;
|
|
option_log_frames = 1;
|
|
MakeWave2(ph_list,n_ph_list);
|
|
option_log_frames = 0;
|
|
break;
|
|
}
|
|
} // end of TranslDlg::OnCommand
|