Files
espeak-ng/src/synth_mbrola.cpp
T
jonsd fc0bd7fe79 [1.44.42]
Phoneme definitions.
New keyword "prevoice", always add murmer before a voiced stop, not only after a vowel.
Fixed bug in nextVowel() condition.
Fixed bug is condition "isRhotic".


Command line options.
On Windows, --  means don't treat following arguments as options.
--help output now should the espeak-data path.

Tools->Lexicon.  Redesign Lexicon processing for Russian and Bulgarian.

Numbers.  Fixes for Hungarian ordinal numbers.

Fixed bug where double .. was ignored.

Fixed bug in reporting word event lengths in callbacks.
Fixed bug in reporting marker event positions in callbacks, when non-ascii text.

SSML.
Allow single as well as double quotes around strings.
Fix bugs in SSML "prosody rate".

Speed.
Only use Sonic speedups greater than x2.
Use custom eSpeak speed settings when Sonic speedup is used.

Mbrola.  Look for Mbrola voice also at /usr/share/mbrola/xx/xx

espeakedit.  Try "aplay" and then "play" to play sound.

Languages:
Kannada (kn) now in official release.
Esperanto (eo) change prosody.


git-svn-id: https://espeak.svn.sourceforge.net/svnroot/espeak/trunk@257 d46cf337-b52f-0410-862d-fd96e6ae7743
2011-03-13 13:30:04 +00:00

682 lines
16 KiB
C++

/***************************************************************************
* 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: *
* <http://www.gnu.org/licenses/>. *
***************************************************************************/
#include "StdAfx.h"
#include <stdio.h>
#include <ctype.h>
#include <wctype.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include "speak_lib.h"
#include "speech.h"
#include "phoneme.h"
#include "synthesize.h"
#include "translate.h"
#include "voice.h"
int option_mbrola_phonemes;
#ifdef INCLUDE_MBROLA
extern int Read4Bytes(FILE *f);
extern void SetPitch2(voice_t *voice, int pitch1, int pitch2, int *pitch_base, int *pitch_range);
extern unsigned char *outbuf;
#ifndef PLATFORM_WINDOWS
#include "mbrowrap.h"
#else
#include <windows.h>
typedef void (WINAPI *PROCVV)(void);
typedef void (WINAPI *PROCVI)(int);
typedef void (WINAPI *PROCVF)(float);
typedef int (WINAPI *PROCIV)();
typedef int (WINAPI *PROCIC) (char *);
typedef int (WINAPI *PROCISI)(short *,int);
typedef char* (WINAPI *PROCVCI)(char *,int);
PROCIC init_MBR;
PROCIC write_MBR;
PROCIV flush_MBR;
PROCISI read_MBR;
PROCVV close_MBR;
PROCVV reset_MBR;
PROCIV lastError_MBR;
PROCVCI lastErrorStr_MBR;
PROCVI setNoError_MBR;
PROCIV getFreq_MBR;
PROCVF setVolumeRatio_MBR;
HINSTANCE hinstDllMBR = NULL;
BOOL load_MBR()
{
if(hinstDllMBR != NULL)
return TRUE; // already loaded
if ((hinstDllMBR=LoadLibraryA("mbrola.dll")) == 0)
return FALSE;
init_MBR =(PROCIC) GetProcAddress(hinstDllMBR,"init_MBR");
write_MBR =(PROCIC) GetProcAddress(hinstDllMBR,"write_MBR");
flush_MBR =(PROCIV) GetProcAddress(hinstDllMBR,"flush_MBR");
read_MBR =(PROCISI) GetProcAddress(hinstDllMBR,"read_MBR");
close_MBR =(PROCVV) GetProcAddress(hinstDllMBR,"close_MBR");
reset_MBR =(PROCVV) GetProcAddress(hinstDllMBR,"reset_MBR");
lastError_MBR =(PROCIV) GetProcAddress(hinstDllMBR,"lastError_MBR");
lastErrorStr_MBR =(PROCVCI) GetProcAddress(hinstDllMBR,"lastErrorStr_MBR");
setNoError_MBR =(PROCVI) GetProcAddress(hinstDllMBR,"setNoError_MBR");
setVolumeRatio_MBR =(PROCVF) GetProcAddress(hinstDllMBR,"setVolumeRatio_MBR");
return TRUE;
}
void unload_MBR()
{
if (hinstDllMBR)
{
FreeLibrary (hinstDllMBR);
hinstDllMBR=NULL;
}
}
#endif // windows
static MBROLA_TAB *mbrola_tab = NULL;
static int mbrola_control = 0;
espeak_ERROR LoadMbrolaTable(const char *mbrola_voice, const char *phtrans, int srate)
{//===================================================================================
// Load a phoneme name translation table from espeak-data/mbrola
int size;
int ix;
int *pw;
FILE *f_in;
char path[sizeof(path_home)+15];
mbrola_name[0] = 0;
mbrola_delay = 0;
if(mbrola_voice == NULL)
{
samplerate = samplerate_native;
SetParameter(espeakVOICETYPE,0,0);
return(EE_OK);
}
sprintf(path,"%s/mbrola/%s",path_home,mbrola_voice);
#ifdef PLATFORM_POSIX
// if not found, then also look in
// usr/share/mbrola/xx, /usr/share/mbrola/xx/xx, /usr/share/mbrola/voices/xx
if(GetFileLength(path) <= 0)
{
sprintf(path,"/usr/share/mbrola/%s",mbrola_voice);
}
if(GetFileLength(path) <= 0)
{
sprintf(path,"/usr/share/mbrola/%s/%s",mbrola_voice,mbrola_voice);
}
if(GetFileLength(path) <= 0)
{
sprintf(path,"/usr/share/mbrola/voices/%s",mbrola_voice);
}
#endif
#ifdef PLATFORM_WINDOWS
if(load_MBR() == FALSE) // load mbrola.dll
return(EE_INTERNAL_ERROR);
#endif
if(init_MBR(path) != 0) // initialise the required mbrola voice
return(EE_NOT_FOUND);
setNoError_MBR(1); // don't stop on phoneme errors
// read eSpeak's mbrola phoneme translation data, eg. en1_phtrans
sprintf(path,"%s/mbrola_ph/%s",path_home,phtrans);
size = GetFileLength(path);
if((f_in = fopen(path,"rb")) == NULL) {
close_MBR();
return(EE_NOT_FOUND);
}
if((mbrola_tab = (MBROLA_TAB *)realloc(mbrola_tab,size)) == NULL)
{
fclose(f_in);
close_MBR();
return(EE_INTERNAL_ERROR);
}
mbrola_control = Read4Bytes(f_in);
pw = (int *)mbrola_tab;
for(ix=4; ix<size; ix+=4)
{
*pw++ = Read4Bytes(f_in);
}
size = fread(mbrola_tab,1,size,f_in);
fclose(f_in);
setVolumeRatio_MBR((float)(mbrola_control & 0xff) /16.0f);
// srate = getFreq_MBR();
samplerate = srate;
if(srate == 22050)
SetParameter(espeakVOICETYPE,0,0);
else
SetParameter(espeakVOICETYPE,1,0);
strcpy(mbrola_name,mbrola_voice);
// mbrola_delay = 3800; // improve synchronization of events
mbrola_delay = 1000; // improve synchronization of events
return(EE_OK);
} // end of LoadMbrolaTable
static int GetMbrName(PHONEME_LIST *plist, PHONEME_TAB *ph, PHONEME_TAB *ph_prev, PHONEME_TAB *ph_next, int *name2, int *split, int *control)
{//==========================================================================================================================================
// Look up a phoneme in the mbrola phoneme name translation table
// It may give none, 1, or 2 mbrola phonemes
int mnem = ph->mnemonic;
MBROLA_TAB *pr;
PHONEME_TAB *other_ph;
int found = 0;
// control
// bit 0 skip the next phoneme
// bit 1 match this and Previous phoneme
// bit 2 only at the start of a word
// bit 3 don't match two phonemes across a word boundary
pr = mbrola_tab;
while(pr->name != 0)
{
if(mnem == pr->name)
{
if(pr->next_phoneme == 0)
found = 1;
else
if((pr->next_phoneme == ':') && (plist->synthflags & SFLAG_LENGTHEN))
{
found = 1;
}
else
{
if(pr->control & 2)
other_ph = ph_prev;
else
if((pr->control & 8) && ((plist+1)->newword))
other_ph = phoneme_tab[phPAUSE]; // don't match the next phoneme over a word boundary
else
other_ph = ph_next;
if((pr->next_phoneme == other_ph->mnemonic) ||
((pr->next_phoneme == 2) && (other_ph->type == phVOWEL)) ||
((pr->next_phoneme == '_') && (other_ph->type == phPAUSE)))
{
found = 1;
}
}
if((pr->control & 4) && (plist->newword == 0)) // only at start of word
found = 0;
if(found)
{
*name2 = pr->mbr_name2;
*split = pr->percent;
*control = pr->control;
return(pr->mbr_name);
}
}
pr++;
}
*name2=0;
*split=0;
*control=0;
return(mnem);
}
static char *WritePitch(int env, int pitch1, int pitch2, int split, int final)
{//===========================================================================
// final=1: only give the final pitch value.
int x;
int ix;
int pitch_base;
int pitch_range;
int p1,p2,p_end;
unsigned char *pitch_env;
int max = -1;
int min = 999;
int y_max=0;
int y_min=0;
int env100 = 80; // apply the pitch change only over this proportion of the mbrola phoneme(s)
int y2;
int y[4];
int env_split;
char buf[50];
static char output[50];
output[0] = 0;
pitch_env = envelope_data[env];
SetPitch2(voice, pitch1, pitch2, &pitch_base, &pitch_range);
env_split = (split * 128)/100;
if(env_split < 0)
env_split = 0-env_split;
// find max and min in the pitch envelope
for(x=0; x<128; x++)
{
if(pitch_env[x] > max)
{
max = pitch_env[x];
y_max = x;
}
if(pitch_env[x] < min)
{
min = pitch_env[x];
y_min = x;
}
}
// set an additional pitch point half way through the phoneme.
// but look for a maximum or a minimum and use that instead
y[2] = 64;
if((y_max > 0) && (y_max < 127))
{
y[2] = y_max;
}
if((y_min > 0) && (y_min < 127))
{
y[2] = y_min;
}
y[1] = y[2] / 2;
y[3] = y[2] + (127 - y[2])/2;
// set initial pitch
p1 = ((pitch_env[0]*pitch_range)>>8) + pitch_base; // Hz << 12
p_end = ((pitch_env[127]*pitch_range)>>8) + pitch_base;
if(split >= 0)
{
sprintf(buf," 0 %d",p1/4096);
strcat(output,buf);
}
// don't use intermediate pitch points for linear rise and fall
if(env > 1)
{
for(ix=1; ix<4; ix++)
{
p2 = ((pitch_env[y[ix]]*pitch_range)>>8) + pitch_base;
if(split > 0)
{
y2 = (y[ix] * env100)/env_split;
}
else
if(split < 0)
{
y2 = ((y[ix]-env_split) * env100)/env_split;
}
else
{
y2 = (y[ix] * env100)/128;
}
if((y2 > 0) && (y2 <= env100))
{
sprintf(buf," %d %d",y2,p2/4096);
strcat(output,buf);
}
}
}
p_end = p_end/4096;
if(split <= 0)
{
sprintf(buf," %d %d",env100,p_end);
strcat(output,buf);
}
if(env100 < 100)
{
sprintf(buf," %d %d",100,p_end);
strcat(output,buf);
}
strcat(output,"\n");
if(final)
sprintf(output,"\t100 %d\n",p_end);
return(output);
} // end of WritePitch
int MbrolaTranslate(PHONEME_LIST *plist, int n_phonemes, int resume, FILE *f_mbrola)
{//=================================================================================
// Generate a mbrola pho file
unsigned int name;
int len;
int len1;
PHONEME_TAB *ph;
PHONEME_TAB *ph_next;
PHONEME_TAB *ph_prev;
PHONEME_LIST *p;
PHONEME_LIST *next;
PHONEME_LIST *prev;
PHONEME_DATA phdata;
FMT_PARAMS fmtp;
int pause = 0;
int released;
int name2;
int control;
int done;
int len_percent;
const char *final_pitch;
char *ptr;
char mbr_buf[120];
static int phix;
static int embedded_ix;
static int word_count;
if (!resume) {
phix = 1;
embedded_ix = 0;
word_count = 0;
}
while (phix < n_phonemes)
{
if (WcmdqFree() < MIN_WCMDQ)
return 1;
ptr = mbr_buf;
p = &plist[phix];
next = &plist[phix+1];
prev = &plist[phix-1];
ph = p->ph;
ph_prev = plist[phix-1].ph;
ph_next = plist[phix+1].ph;
if(p->synthflags & SFLAG_EMBEDDED)
{
DoEmbedded(&embedded_ix, p->sourceix);
}
if(p->newword & 4)
DoMarker(espeakEVENT_SENTENCE, (p->sourceix & 0x7ff) + clause_start_char, 0, count_sentences);
if(p->newword & 1)
DoMarker(espeakEVENT_WORD, (p->sourceix & 0x7ff) + clause_start_char, p->sourceix >> 11, clause_start_word + word_count++);
name = GetMbrName(p,ph,ph_prev,ph_next,&name2,&len_percent,&control);
if(control & 1)
phix++;
if(name == 0) {
phix++;
continue; // ignore this phoneme
}
if((ph->type == phPAUSE) && (name == ph->mnemonic))
{
// a pause phoneme, which has not been changed by the translation
name = '_';
len = (p->length * speed.pause_factor)/256;
// if(len == 0) continue;
if(len == 0)
len = 1;
}
else
len = (80 * speed.wav_factor)/256;
DoMarker(espeakEVENT_PHONEME, (p->sourceix & 0x7ff) + clause_start_char, 0, ph->mnemonic);
ptr += sprintf(ptr,"%s\t",WordToString(name));
if(name2 == '_')
{
// add a pause after this phoneme
pause = len_percent;
name2 = 0;
}
done = 0;
final_pitch = "";
switch(ph->type)
{
case phVOWEL:
len = ph->std_length;
if(p->synthflags & SFLAG_LENGTHEN)
len += phoneme_tab[phonLENGTHEN]->std_length; // phoneme was followed by an extra : symbol
if(ph_next->type == phPAUSE)
len += 50; // lengthen vowels before a pause
len = (len * p->length)/256;
if(name2 == 0)
{
char *pitch = WritePitch(p->env,p->pitch1,p->pitch2,0,0);
ptr += sprintf(ptr,"%d\t%s", len, pitch);
}
else
{
char *pitch;
pitch = WritePitch(p->env,p->pitch1,p->pitch2,len_percent,0);
len1 = (len * len_percent)/100;
ptr += sprintf(ptr,"%d\t%s", len1, pitch);
pitch = WritePitch(p->env,p->pitch1,p->pitch2,-len_percent,0);
ptr += sprintf(ptr,"%s\t%d\t%s", WordToString(name2), len-len1, pitch);
}
done = 1;
break;
case phSTOP:
released = 0;
if(next->type==phVOWEL) released = 1;
if(next->type==phLIQUID && !next->newword) released = 1;
if(released == 0)
p->synthflags |= SFLAG_NEXT_PAUSE;
InterpretPhoneme(NULL, 0, p, &phdata);
len = DoSample3(&phdata, 0, -1);
len = (len * 1000)/samplerate; // convert to mS
len += PauseLength(p->prepause,1);
break;
case phVSTOP:
len = (80 * speed.wav_factor)/256;
break;
case phFRICATIVE:
len = 0;
InterpretPhoneme(NULL, 0, p, &phdata);
if(p->synthflags & SFLAG_LENGTHEN)
len = DoSample3(&phdata, p->length, -1); // play it twice for [s:] etc.
len += DoSample3(&phdata, p->length, -1);
len = (len * 1000)/samplerate; // convert to mS
break;
case phNASAL:
if(next->type != phVOWEL)
{
memset(&fmtp, 0, sizeof(fmtp));
InterpretPhoneme(NULL, 0, p, &phdata);
fmtp.fmt_addr = phdata.sound_addr[pd_FMT];
len = DoSpect2(p->ph, 0, &fmtp, p, -1);
// len = DoSpect(p->ph,prev->ph,phoneme_tab[phonPAUSE],2,p,-1);
len = (len * 1000)/samplerate;
if(next->type == phPAUSE)
len += 50;
final_pitch = WritePitch(p->env,p->pitch1,p->pitch2,0,1);
}
break;
case phLIQUID:
if(next->type == phPAUSE)
{
len += 50;
final_pitch = WritePitch(p->env,p->pitch1,p->pitch2,0,1);
}
break;
}
if(!done)
{
if(name2 != 0)
{
len1 = (len * len_percent)/100;
ptr += sprintf(ptr,"%d\n%s\t",len1,WordToString(name2));
len -= len1;
}
ptr += sprintf(ptr,"%d%s\n",len,final_pitch);
}
if(pause)
{
len += PauseLength(pause,0);
ptr += sprintf(ptr,"_ \t%d\n",PauseLength(pause,0));
pause = 0;
}
if(f_mbrola)
{
fwrite(mbr_buf,1,(ptr-mbr_buf),f_mbrola); // write .pho to a file
}
else
{
int res = write_MBR(mbr_buf);
if (res < 0)
return 0; /* don't get stuck on error */
if (res == 0)
return 1;
wcmdq[wcmdq_tail][0] = WCMD_MBROLA_DATA;
wcmdq[wcmdq_tail][1] = len;
WcmdqInc();
}
phix++;
}
if(!f_mbrola)
{
flush_MBR();
// flush the mbrola output buffer
wcmdq[wcmdq_tail][0] = WCMD_MBROLA_DATA;
wcmdq[wcmdq_tail][1] = 500;
WcmdqInc();
}
return 0;
} // end of MbrolaTranslate
int MbrolaGenerate(PHONEME_LIST *phoneme_list, int *n_ph, int resume)
{//==================================================================
FILE *f_mbrola = NULL;
if(*n_ph == 0)
return(0);
if(option_mbrola_phonemes)
{
// send mbrola data to a file, not to the mbrola library
f_mbrola = f_trans;
}
int again = MbrolaTranslate(phoneme_list, *n_ph, resume, f_mbrola);
if (!again)
*n_ph = 0;
return again;
}
int MbrolaFill(int length, int resume)
{//===================================
// Read audio data from Mbrola (length is in milisecs)
static int n_samples;
int req_samples, result;
if (!resume)
n_samples = samplerate * length / 1000;
req_samples = (out_end - out_ptr)/2;
if (req_samples > n_samples)
req_samples = n_samples;
result = read_MBR((short *)out_ptr, req_samples);
if (result <= 0)
return 0;
out_ptr += result*2;
n_samples -= result;
return n_samples ? 1 : 0;
}
void MbrolaReset(void)
{//===================
// Reset the Mbrola engine and flush the pending audio
reset_MBR();
}
#else // INCLUDE_MBROLA
// mbrola interface is not compiled, provide dummy functions.
espeak_ERROR LoadMbrolaTable(const char *mbrola_voice, const char *phtrans, int srate)
{
return(EE_INTERNAL_ERROR);
}
int MbrolaGenerate(PHONEME_LIST *phoneme_list, int *n_ph, int resume)
{
return(0);
}
int MbrolaFill(int length, int resume)
{
return(0);
}
void MbrolaReset(void)
{
}
#endif // INCLUDE_MBROLA