diff options
Diffstat (limited to 'navit/support/espeak/readclause.c')
-rw-r--r-- | navit/support/espeak/readclause.c | 1033 |
1 files changed, 785 insertions, 248 deletions
diff --git a/navit/support/espeak/readclause.c b/navit/support/espeak/readclause.c index b8d302a27..172ebad15 100644 --- a/navit/support/espeak/readclause.c +++ b/navit/support/espeak/readclause.c @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (C) 2005 to 2007 by Jonathan Duddington * + * Copyright (C) 2005 to 2014 by Jonathan Duddington * * email: jonsd@users.sourceforge.net * * * * This program is free software; you can redistribute it and/or modify * @@ -41,7 +41,7 @@ #include <locale.h> #define N_XML_BUF 256 -#define double(x) ((double)(x)) + static const char *xmlbase = ""; // base URL from <speak> static int namedata_ix=0; @@ -58,20 +58,21 @@ static const char *ungot_word = NULL; static int end_of_input; static int ignore_text=0; // set during <sub> ... </sub> to ignore text which has been replaced by an alias +static int audio_text=0; // set during <audio> ... </audio> static int clear_skipping_text = 0; // next clause should clear the skipping_text flag int count_characters = 0; static int sayas_mode; +static int sayas_start; static int ssml_ignore_l_angle = 0; -static const char *punct_stop = ".:!?"; // pitch fall if followed by space -static const char *punct_close = ")]}>;'\""; // always pitch fall unless followed by alnum - // alter tone for announce punctuation or capitals -static const char *tone_punct_on = "\0016T"; // add reverberation, lower pitch -static const char *tone_punct_off = "\001T"; +//static const char *tone_punct_on = "\0016T"; // add reverberation, lower pitch +//static const char *tone_punct_off = "\001T\001P"; // punctuations symbols that can end a clause static const unsigned short punct_chars[] = {',','.','?','!',':',';', + 0x00a1, // inverted exclamation + 0x00bf, // inverted question 0x2013, // en-dash 0x2014, // em-dash 0x2026, // elipsis @@ -86,6 +87,15 @@ static const unsigned short punct_chars[] = {',','.','?','!',':',';', 0x055e, // Armenian question 0x055b, // Armenian emphasis mark + 0x060c, // Arabic , + 0x061b, // Arabic ; + 0x061f, // Arabic ? + 0x06d4, // Arabic . + + 0x0df4, // Singhalese Kunddaliya + 0x0f0d, // Tibet Shad + 0x0f0e, + 0x1362, // Ethiopic period 0x1363, 0x1364, @@ -93,6 +103,7 @@ static const unsigned short punct_chars[] = {',','.','?','!',':',';', 0x1366, 0x1367, 0x1368, + 0x10fb, // Georgian paragraph 0x3001, // ideograph comma 0x3002, // ideograph period @@ -103,7 +114,7 @@ static const unsigned short punct_chars[] = {',','.','?','!',':',';', 0xff1a, // fullwidth colon 0xff1b, // fullwidth semicolon 0xff1f, // fullwidth question mark - + 0}; @@ -111,19 +122,30 @@ static const unsigned short punct_chars[] = {',','.','?','!',':',';', // bits 0-7 pause x 10mS, bits 12-14 intonation type, bit 15 don't need following space or bracket static const unsigned int punct_attributes [] = { 0, CLAUSE_COMMA, CLAUSE_PERIOD, CLAUSE_QUESTION, CLAUSE_EXCLAMATION, CLAUSE_COLON, CLAUSE_SEMICOLON, + CLAUSE_SEMICOLON | 0x8000, // inverted exclamation + CLAUSE_SEMICOLON | 0x8000, // inverted question CLAUSE_SEMICOLON, // en-dash CLAUSE_SEMICOLON, // em-dash - CLAUSE_SEMICOLON, // elipsis + CLAUSE_SEMICOLON | PUNCT_SAY_NAME | 0x8000, // elipsis CLAUSE_QUESTION, // Greek question mark CLAUSE_SEMICOLON, // Greek semicolon - CLAUSE_PERIOD+0x8000, // Devanagari Danda (fullstop) + CLAUSE_PERIOD | 0x8000, // Devanagari Danda (fullstop) - CLAUSE_PERIOD+0x8000, // Armenian period + CLAUSE_PERIOD | 0x8000, // Armenian period CLAUSE_COMMA, // Armenian comma - CLAUSE_EXCLAMATION + PUNCT_IN_WORD, // Armenian exclamation - CLAUSE_QUESTION + PUNCT_IN_WORD, // Armenian question - CLAUSE_PERIOD + PUNCT_IN_WORD, // Armenian emphasis mark + CLAUSE_EXCLAMATION | PUNCT_IN_WORD, // Armenian exclamation + CLAUSE_QUESTION | PUNCT_IN_WORD, // Armenian question + CLAUSE_PERIOD | PUNCT_IN_WORD, // Armenian emphasis mark + + CLAUSE_COMMA, // Arabic , + CLAUSE_SEMICOLON, // Arabic ; + CLAUSE_QUESTION, // Arabic question mark + CLAUSE_PERIOD, // Arabic full stop + + CLAUSE_PERIOD+0x8000, // Singhalese period + CLAUSE_PERIOD+0x8000, // Tibet period + CLAUSE_PARAGRAPH, CLAUSE_PERIOD, // Ethiopic period CLAUSE_COMMA, // Ethiopic comma @@ -131,7 +153,8 @@ static const unsigned int punct_attributes [] = { 0, CLAUSE_COLON, // Ethiopic colon CLAUSE_COLON, // Ethiopic preface colon CLAUSE_QUESTION, // Ethiopic question mark - CLAUSE_PERIOD, // Ethiopic paragraph + CLAUSE_PARAGRAPH, // Ethiopic paragraph + CLAUSE_PARAGRAPH, // Georgian paragraph CLAUSE_COMMA+0x8000, // ideograph comma CLAUSE_PERIOD+0x8000, // ideograph period @@ -151,7 +174,7 @@ static const unsigned int punct_attributes [] = { 0, // frame 0 is for the defaults, before any ssml tags. typedef struct { int tag_type; - int voice_variant; + int voice_variant_number; int voice_gender; int voice_age; char voice_name[40]; @@ -162,6 +185,8 @@ typedef struct { static int n_ssml_stack; static SSML_STACK ssml_stack[N_SSML_STACK]; +static espeak_VOICE base_voice; +static char base_voice_variant_name[40] = {0}; static char current_voice_id[40] = {0}; @@ -170,10 +195,11 @@ static int n_param_stack; PARAM_STACK param_stack[N_PARAM_STACK]; static int speech_parameters[N_SPEECH_PARAM]; // current values, from param_stack +int saved_parameters[N_SPEECH_PARAM]; //Parameters saved on synthesis start const int param_defaults[N_SPEECH_PARAM] = { 0, // silence (internal use) - 170, // rate wpm + 175, // rate wpm 100, // volume 50, // pitch 50, // range @@ -190,49 +216,90 @@ const int param_defaults[N_SPEECH_PARAM] = { }; -#ifdef NEED_WCHAR_FUNCTIONS +// additional Latin characters beyond the ascii character set +#define MAX_WALPHA 0x24f +// indexed by character - 0x80 +// 0=not alphabetic, 0xff=lower case, 0xfe=no case, 0xfd=use wchar_tolower +// other=value to add to upper case to convert to lower case +static unsigned char walpha_tab[MAX_WALPHA-0x7f] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 080 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 090 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xfe, 0, 0, 0, 0, 0, // 0a0 + 0, 0, 0, 0, 0, 0xff, 0, 0, 0, 0, 0xfe, 0, 0, 0, 0, 0, // 0b0 + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, // 0c0 + 32, 32, 32, 32, 32, 32, 32, 0, 32, 32, 32, 32, 32, 32, 32, 0xff, // 0d0 + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // 0e0 + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // 0f0 + 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, // 100 + 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, // 110 + 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, // 120 + 0xfd, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, 0xfe, 1, 0xff, 1, 0xff, 1, 0xff, 1, // 130 + 0xff, 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, 0xfe, 1, 0xff, 1, 0xff, 1, 0xff, // 140 + 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, // 150 + 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, // 160 + 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, 0xfd, 1, 0xff, 1, 0xff, 1, 0xff, 0xff, // 170 + 0xff, 210, 1, 0xff, 1, 0xff, 206, 1, 0xff, 205, 205, 1, 0xff, 0xfe, 79, 202, // 180 + 203, 1, 0xff, 205, 207, 0xff, 211, 209, 1, 0xff, 0xff, 0xfe, 211, 213, 0xff, 214, // 190 + 1, 0xff, 1, 0xff, 1, 0xff, 218, 1, 0xff, 218, 0xfe, 0xfe, 1, 0xff, 218, 1, // 1a0 + 0xff, 217, 217, 1, 0xff, 1, 0xff, 219, 1, 0xff, 0xfe, 0xfe, 1, 0xff, 0xfe, 0xff, // 1b0 + 0xfe, 0xfe, 0xfe, 0xfe, 2, 0xff, 0xff, 2, 0xff, 0xff, 2, 0xff, 0xff, 1, 0xff, 1, // 1c0 + 0xff, 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, 0xff, 1, 0xff, // 1d0 + 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, // 1e0 + 0xfe, 2, 0xff, 0xff, 1, 0xff, 0xfd, 0xfd, 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, // 1f0 + 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, // 200 + 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, // 210 + 0xfd, 0xfe, 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, // 220 + 1, 0xff, 1, 0xff, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfd, 1, 0xff, 0xfd, 0xfd, 0xfe, // 230 + 0xfe, 1, 0xff, 0xfd, 69, 71, 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff}; // 240 + +static const short wchar_tolower[] = { + 0x130, 0x069, + 0x178, 0x0ff, + 0x1f6, 0x195, + 0x1f7, 0x1bf, + 0x220, 0x19e, + 0x23a, 0x2c65, + 0x23d, 0x19a, + 0x23e, 0x2c66, + 0x243, 0x180, + 0,0 }; + +static const short wchar_toupper[] = { + 0x0b5, 0x39c, + 0x0df, 0x0df, + 0x0ff, 0x178, + 0x131, 0x049, + 0x17f, 0x053, + 0x180, 0x243, + 0x195, 0x1f6, + 0x19a, 0x23d, + 0x19e, 0x220, + 0x1bf, 0x1f7, + 0x1c6, 0x1c4, + 0x1c9, 0x1c7, + 0x1cc, 0x1ca, + 0x1dd, 0x18e, + 0x1f3, 0x1f1, + 0,0 }; -// additional Latin characters beyond the Latin1 character set -#define MAX_WALPHA 0x233 -// indexed by character - 0x100 -// 0=not alphabetic, 0xff=lower case, other=value to add to upper case to convert to lower case -static unsigned char walpha_tab[MAX_WALPHA-0xff] = { - 1,0xff, 1,0xff, 1,0xff, 1,0xff, 1,0xff, 1,0xff, 1,0xff, 1,0xff, // 100 - 1,0xff, 1,0xff, 1,0xff, 1,0xff, 1,0xff, 1,0xff, 1,0xff, 1,0xff, // 110 - 1,0xff, 1,0xff, 1,0xff, 1,0xff, 1,0xff, 1,0xff, 1,0xff, 1,0xff, // 120 - 0xff,0xff, 1,0xff, 1,0xff, 1,0xff,0xff, 1,0xff, 1,0xff, 1,0xff, 1, // 130 - 0xff, 1,0xff, 1,0xff, 1,0xff, 1,0xff,0xff, 1,0xff, 1,0xff, 1,0xff, // 140 - 1,0xff, 1,0xff, 1,0xff, 1,0xff, 1,0xff, 1,0xff, 1,0xff, 1,0xff, // 150 - 1,0xff, 1,0xff, 1,0xff, 1,0xff, 1,0xff, 1,0xff, 1,0xff, 1,0xff, // 160 - 1,0xff, 1,0xff, 1,0xff, 1,0xff,0xff, 1,0xff, 1,0xff, 1,0xff,0xff, // 170 - 0xff, 210, 1,0xff, 1,0xff, 206, 1,0xff, 205, 205, 1,0xff,0xff, 79, 202, // 180 - 203, 1,0xff, 205, 207,0xff, 211, 209, 1,0xff,0xff,0xff, 211, 213,0xff, 214, // 190 - 1,0xff, 1,0xff, 1,0xff, 218, 1,0xff, 218,0xff,0xff, 1,0xff, 218, 1, // 1a0 - 0xff, 217, 217, 1,0xff, 1,0xff, 219, 1,0xff,0xff,0xff, 1,0xff,0xff,0xff, // 1b0 - 0xff,0xff,0xff,0xff, 2, 1,0xff, 2, 1,0xff, 2, 1,0xff, 1,0xff, 1, // 1c0 - 0xff, 1,0xff, 1,0xff, 1,0xff, 1,0xff, 1,0xff, 1,0xff,0xff, 1,0xff, // 1d0 - 1,0xff, 1,0xff, 1,0xff, 1,0xff, 1,0xff, 1,0xff, 1,0xff, 1,0xff, // 1e0 - 0xff, 2, 1,0xff, 1,0xff,0xff,0xff, 1,0xff, 1,0xff, 1,0xff, 1,0xff, // 1f0 - 1,0xff, 1,0xff, 1,0xff, 1,0xff, 1,0xff, 1,0xff, 1,0xff, 1,0xff, // 200 - 1,0xff, 1,0xff, 1,0xff, 1,0xff, 1,0xff, 1,0xff, 1,0xff, 1,0xff, // 210 - 0xff, 0, 1,0xff, 1,0xff, 1,0xff, 1,0xff, 1,0xff, 1,0xff, 1,0xff, // 220 - 1,0xff, 1,0xff }; // 230 + +#ifdef NEED_WCHAR_FUNCTIONS // use ctype.h functions for Latin1 (character < 0x100) int iswalpha(int c) { - if(c < 0x100) + if(c < 0x80) return(isalpha(c)); if((c > 0x3040) && (c <= 0xa700)) return(1); // japanese, chinese characters if(c > MAX_WALPHA) return(0); - return(walpha_tab[c-0x100]); + return(walpha_tab[c-0x80]); } int iswdigit(int c) { - if(c < 0x100) + if(c < 0x80) return(isdigit(c)); return(0); } @@ -247,44 +314,67 @@ int iswalnum(int c) int towlower(int c) { int x; - if(c < 0x100) + int ix; + + if(c < 0x80) return(tolower(c)); - if((c > MAX_WALPHA) || ((x = walpha_tab[c-0x100])==0xff)) - return(c); // already lower case + + if((c > MAX_WALPHA) || ((x = walpha_tab[c-0x80]) >= 0xfe)) + return(c); + + if(x == 0xfd) + { + // special cases, lookup translation table + for(ix=0; wchar_tolower[ix] != 0; ix+=2) + { + if(wchar_tolower[ix] == c) + return(wchar_tolower[ix+1]); + } + } return(c + x); // convert to lower case } int towupper(int c) { - // check whether the previous character code is the upper-case equivalent of this character - if(tolower(c-1) == c) - return(c-1); // yes, use it + int ix; + // check whether a previous character code is the upper-case equivalent of this character + if(towlower(c-32) == c) + return(c-32); // yes, use it + if(towlower(c-1) == c) + return(c-1); + for(ix=0; wchar_toupper[ix] != 0; ix+=2) + { + if(wchar_toupper[ix] == c) + return(wchar_toupper[ix+1]); + } return(c); // no } int iswupper(int c) { int x; - if(c < 0x100) + if(c < 0x80) return(isupper(c)); - if(((c > MAX_WALPHA) || (x = walpha_tab[c-0x100])==0) || (x == 0xff)) + if(((c > MAX_WALPHA) || (x = walpha_tab[c-0x80])==0) || (x == 0xff)) return(0); return(1); } int iswlower(int c) { - if(c < 0x100) + if(c < 0x80) return(islower(c)); - if((c > MAX_WALPHA) || (walpha_tab[c-0x100] != 0xff)) + if((c > MAX_WALPHA) || (walpha_tab[c-0x80] != 0xff)) return(0); return(1); } int iswspace(int c) { - if(c < 0x100) + if(c < 0x80) return(isspace(c)); + if(c == 0xa0) + return(1); return(0); } @@ -336,19 +426,105 @@ float wcstod(const wchar_t *str, wchar_t **tailptr) } #endif + +// use internal data for iswalpha up to U+024F +// iswalpha() on Windows is unreliable (U+AA, U+BA). +int iswalpha2(int c) +{ + if(c < 0x80) + return(isalpha(c)); + if((c > 0x3040) && (c <= 0xa700)) + return(1); // japanese, chinese characters + if(c > MAX_WALPHA) + return(iswalpha(c)); + return(walpha_tab[c-0x80]); +} + +int iswlower2(int c) +{ + if(c < 0x80) + return(islower(c)); + if(c > MAX_WALPHA) + return(iswlower(c)); + if(walpha_tab[c-0x80] == 0xff) + return(1); + return(0); +} + +int iswupper2(int c) +{ + int x; + if(c < 0x80) + return(isupper(c)); + if(c > MAX_WALPHA) + return(iswupper(c)); + if(((x = walpha_tab[c-0x80]) > 0) && (x < 0xfe)) + return(1); + return(0); +} + int towlower2(unsigned int c) { + int x; + int ix; + // check for non-standard upper to lower case conversions if(c == 'I') { - if(translator->translator_name == L('t','r')) + if(translator->langopts.dotless_i) { c = 0x131; // I -> ı } } - return(towlower(c)); + + if(c < 0x80) + return(tolower(c)); + + if(c > MAX_WALPHA) + return(towlower(c)); + + if((x = walpha_tab[c-0x80]) >= 0xfe) + return(c); // this is not an upper case letter + + if(x == 0xfd) + { + // special cases, lookup translation table + for(ix=0; wchar_tolower[ix] != 0; ix+=2) + { + if(wchar_tolower[ix] == (int)c) + return(wchar_tolower[ix+1]); + } + } + return(c + x); // convert to lower case +} + +int towupper2(unsigned int c) +{ + int ix; + if(c > MAX_WALPHA) + return(towupper(c)); + + // check whether a previous character code is the upper-case equivalent of this character + if(towlower2(c-32) == (int)c) + return(c-32); // yes, use it + if(towlower2(c-1) == (int)c) + return(c-1); + for(ix=0; wchar_toupper[ix] != 0; ix+=2) + { + if(wchar_toupper[ix] == (int)c) + return(wchar_toupper[ix+1]); + } + return(c); // no } +static int IsRomanU(unsigned int c) +{//================================ + if((c=='I') || (c=='V') || (c=='X') || (c=='L')) + return(1); + return(0); +} + + static void GetC_unget(int c) {//========================== // This is only called with UTF8 input, not wchar input @@ -411,7 +587,7 @@ static int GetC_get(void) end_of_input = 1; return(0); } - + if(!end_of_input) { if(option_multibyte == espeakCHARS_16BIT) @@ -438,10 +614,8 @@ static int GetC(void) int cbuf[4]; int ix; int n_bytes; - unsigned char m; static int ungot2 = 0; static const unsigned char mask[4] = {0xff,0x1f,0x0f,0x07}; - static const unsigned char mask2[4] = {0,0x80,0x20,0x30}; if((c1 = ungot_char) != 0) { @@ -482,7 +656,6 @@ static int GetC(void) if((ix = n_bytes) > 0) { c = c1 & mask[ix]; - m = mask2[ix]; while(ix > 0) { if((c2 = cbuf[ix] = GetC_get()) == 0) @@ -501,7 +674,6 @@ static int GetC(void) GetC_unget(c2); break; } - m = 0x80; c = (c << 6) + (c2 & 0x3f); ix--; } @@ -530,8 +702,8 @@ static void UngetC(int c) } -static const char *WordToString2(unsigned int word) -{//================================================ +const char *WordToString2(unsigned int word) +{//============================================ // Convert a language mnemonic word into a string int ix; static char buf[5]; @@ -555,20 +727,20 @@ static const char *LookupSpecial(Translator *tr, const char *string, char* text_ char phonemes2[55]; char *string1 = (char *)string; + flags[0] = flags[1] = 0; if(LookupDictList(tr,&string1,phonemes,flags,0,NULL)) { - SetWordStress(tr, phonemes, &flags[0], -1, 0); + SetWordStress(tr, phonemes, flags, -1, 0); DecodePhonemes(phonemes,phonemes2); - sprintf(text_out,"[[%s]]",phonemes2); - option_phoneme_input |= 2; + sprintf(text_out,"[\002%s]]",phonemes2); return(text_out); } return(NULL); } -static const char *LookupCharName(Translator *tr, int c) -{//===================================================== +static const char *LookupCharName(Translator *tr, int c, int only) +{//=============================================================== // Find the phoneme string (in ascii) to speak the name of character c // Used for punctuation characters and symbols @@ -589,20 +761,28 @@ static const char *LookupCharName(Translator *tr, int c) ix = utf8_out(c,&single_letter[2]); single_letter[2+ix]=0; - string = &single_letter[1]; - if(LookupDictList(tr, &string, phonemes, flags, 0, NULL) == 0) + if(only) { - // try _* then * string = &single_letter[2]; + LookupDictList(tr, &string, phonemes, flags, 0, NULL); + } + else + { + string = &single_letter[1]; if(LookupDictList(tr, &string, phonemes, flags, 0, NULL) == 0) { - // now try the rules - single_letter[1] = ' '; - TranslateRules(tr, &single_letter[2], phonemes, sizeof(phonemes), NULL,0,NULL); + // try _* then * + string = &single_letter[2]; + if(LookupDictList(tr, &string, phonemes, flags, 0, NULL) == 0) + { + // now try the rules + single_letter[1] = ' '; + TranslateRules(tr, &single_letter[2], phonemes, sizeof(phonemes), NULL,0,NULL); + } } } - if((phonemes[0] == 0) && (tr->translator_name != L('e','n'))) + if((only==0) && ((phonemes[0] == 0) || (phonemes[0] == phonSWITCH)) && (tr->translator_name != L('e','n'))) { // not found, try English SetTranslator2("en"); @@ -627,23 +807,22 @@ static const char *LookupCharName(Translator *tr, int c) { if(lang_name) { - SetWordStress(translator2, phonemes, &flags[0], -1, 0); + SetWordStress(translator2, phonemes, flags, -1, 0); DecodePhonemes(phonemes,phonemes2); - sprintf(buf,"[[_^_%s %s _^_%s]]","en",phonemes2,WordToString2(tr->translator_name)); + sprintf(buf,"[\002_^_%s %s _^_%s]]","en",phonemes2,WordToString2(tr->translator_name)); SelectPhonemeTable(voice->phoneme_tab_ix); // revert to original phoneme table } else { - SetWordStress(tr, phonemes, &flags[0], -1, 0); + SetWordStress(tr, phonemes, flags, -1, 0); DecodePhonemes(phonemes,phonemes2); - sprintf(buf,"[[%s]] ",phonemes2); + sprintf(buf,"[\002%s]] ",phonemes2); } - option_phoneme_input |= 2; } else + if(only == 0) { - strcpy(buf,"[[(X1)(X1)(X1)]]"); - option_phoneme_input |= 2; + strcpy(buf,"[\002(X1)(X1)(X1)]]"); } return(buf); @@ -691,12 +870,13 @@ static int LoadSoundFile(const char *fname, int index) } f = NULL; + +#ifndef _WIN32 #ifdef PLATFORM_POSIX if((f = fopen(fname,"rb")) != NULL) { int ix; int fd_temp; - const char *resample; int header[3]; char command[sizeof(fname2)+sizeof(fname2)+40]; @@ -710,17 +890,11 @@ static int LoadSoundFile(const char *fname, int index) fclose(f); f = NULL; - if(header[2] == samplerate) - resample = ""; - else - resample = "polyphase"; - strcpy(fname_temp,"/tmp/espeakXXXXXX"); if((fd_temp = mkstemp(fname_temp)) >= 0) { close(fd_temp); -// sprintf(fname_temp,"%s.wav",tmpnam(NULL)); - sprintf(command,"sox \"%s\" -r %d -w -s -c1 %s %s\n", fname, samplerate, fname_temp, resample); + sprintf(command,"sox \"%s\" -r %d -c1 -t wav %s\n", fname, samplerate, fname_temp); if(system(command) == 0) { fname = fname_temp; @@ -729,13 +903,14 @@ static int LoadSoundFile(const char *fname, int index) } } #endif +#endif if(f == NULL) { f = fopen(fname,"rb"); if(f == NULL) { - fprintf(stderr,"Can't read temp file: %s\n",fname); +// fprintf(stderr,"Can't read temp file: %s\n",fname); return(3); } } @@ -747,9 +922,9 @@ static int LoadSoundFile(const char *fname, int index) fclose(f); return(4); } - fread(p,length,1,f); + length = fread(p,1,length,f); fclose(f); -#if 0 +#ifndef _WIN32 remove(fname_temp); #endif @@ -810,57 +985,92 @@ static int LoadSoundFile2(const char *fname) -static int AnnouncePunctuation(Translator *tr, int c1, int c2, char *buf, int bufix) -{//================================================================================= +static int AnnouncePunctuation(Translator *tr, int c1, int *c2_ptr, char *output, int *bufix, int end_clause) +{//============================================================================================================= // announce punctuation names // c1: the punctuation character // c2: the following character int punct_count; - const char *punctname; - int found = 0; + const char *punctname = NULL; int soundicon; - char *p; + int attributes; + int short_pause; + int c2; + int len; + int bufix1; + char buf[200]; + char buf2[80]; + char ph_buf[30]; + + c2 = *c2_ptr; + buf[0] = 0; if((soundicon = LookupSoundicon(c1)) >= 0) { // add an embedded command to play the soundicon - sprintf(&buf[bufix],"\001%dI ",soundicon); + sprintf(buf,"\001%dI ",soundicon); UngetC(c2); - found = 1; } else - if((punctname = LookupCharName(tr, c1)) != NULL) { - found = 1; - if(bufix==0) + if((c1 == '.') && (end_clause) && (c2 != '.')) + { + if(LookupSpecial(tr, "_.p", ph_buf)) + { + punctname = ph_buf; // use word for 'period' instead of 'dot' + } + } + if(punctname == NULL) + { + punctname = LookupCharName(tr, c1, 0); + } + + if(punctname == NULL) + return(-1); + + if((*bufix==0) || (end_clause ==0) || (tr->langopts.param[LOPT_ANNOUNCE_PUNCT] & 2)) { punct_count=1; - while(c2 == c1) + while((c2 == c1) && (c1 != '<')) // don't eat extra '<', it can miss XML tags { punct_count++; c2 = GetC(); } - UngetC(c2); + *c2_ptr = c2; + if(end_clause) + { + UngetC(c2); + } - p = &buf[bufix]; if(punct_count==1) { - sprintf(p,"%s %s %s",tone_punct_on,punctname,tone_punct_off); +// sprintf(buf,"%s %s %s",tone_punct_on,punctname,tone_punct_off); + sprintf(buf," %s",punctname); // we need the space before punctname, to ensure it doesn't merge with the previous word (eg. "2.-a") } else if(punct_count < 4) { - sprintf(p,"\001+10S%s",tone_punct_on); + buf[0] = 0; + if(embedded_value[EMBED_S] < 300) + sprintf(buf,"\001+10S"); // Speak punctuation name faster, unless we are already speaking fast. It would upset Sonic SpeedUp + while(punct_count-- > 0) - sprintf(buf,"%s %s",buf,punctname); - sprintf(p,"%s %s\001-10S",buf,tone_punct_off); + { + sprintf(buf2," %s",punctname); + strcat(buf, buf2); + } + + if(embedded_value[EMBED_S] < 300) + { + sprintf(buf2," \001-10S"); + strcat(buf, buf2); + } } else { - sprintf(p,"%s %s %d %s %s", - tone_punct_on,punctname,punct_count,punctname,tone_punct_off); - return(CLAUSE_COMMA); + sprintf(buf," %s %d %s", + punctname,punct_count,punctname); } } else @@ -873,24 +1083,39 @@ static int AnnouncePunctuation(Translator *tr, int c1, int c2, char *buf, int bu ssml_ignore_l_angle = c1; // this was < which was converted to <, don't pick it up again as < } ungot_char2 = c1; - buf[bufix] = ' '; - buf[bufix+1] = 0; + buf[0] = ' '; + buf[1] = 0; } } - if(found == 0) + bufix1 = *bufix; + len = strlen(buf); + strcpy(&output[*bufix],buf); + *bufix += len; + + if(end_clause==0) return(-1); if(c1 == '-') return(CLAUSE_NONE); // no pause - if(bufix > 0) - return(CLAUSE_SHORTCOMMA); - if((strchr_w(punct_close,c1) != NULL) && !iswalnum(c2)) - return(CLAUSE_SHORTFALL+4); - if(iswspace(c2) && strchr_w(punct_stop,c1)!=NULL) - return(punct_attributes[lookupwchar(punct_chars,c1)]); - - return(CLAUSE_SHORTCOMMA); + + attributes = punct_attributes[lookupwchar(punct_chars,c1)]; + + short_pause = CLAUSE_SHORTFALL; + if((attributes & CLAUSE_BITS_INTONATION) == 0x1000) + short_pause = CLAUSE_SHORTCOMMA; + + if((bufix1 > 0) && !(tr->langopts.param[LOPT_ANNOUNCE_PUNCT] & 2)) + { + if((attributes & ~0x8000) == CLAUSE_SEMICOLON) + return(CLAUSE_SHORTFALL); + return(short_pause); + } + + if(attributes & CLAUSE_BIT_SENTENCE) + return(attributes); + + return(short_pause); } // end of AnnouncePunctuation #define SSML_SPEAK 1 @@ -908,10 +1133,11 @@ static int AnnouncePunctuation(Translator *tr, int c1, int c2, char *buf, int bu #define SSML_BREAK 13 #define SSML_IGNORE_TEXT 14 #define HTML_BREAK 15 -#define SSML_CLOSE 0x10 // for a closing tag, OR this with the tag type +#define HTML_NOSPACE 16 // don't insert a space for this element, so it doesn't break a word +#define SSML_CLOSE 0x20 // for a closing tag, OR this with the tag type // these tags have no effect if they are self-closing, eg. <voice /> -static char ignore_if_self_closing[] = {0,1,1,1,1,0,0,0,0,1,1,0,1,0,1,0,0}; +static char ignore_if_self_closing[] = {0,1,1,1,1,0,0,0,0,1,1,0,1,0,1,0,0,0,0}; static MNEM_TAB ssmltags[] = { @@ -932,6 +1158,7 @@ static MNEM_TAB ssmltags[] = { {"br", HTML_BREAK}, {"li", HTML_BREAK}, + {"dd", HTML_BREAK}, {"img", HTML_BREAK}, {"td", HTML_BREAK}, {"h1", SSML_PARAGRAPH}, @@ -941,29 +1168,37 @@ static MNEM_TAB ssmltags[] = { {"hr", SSML_PARAGRAPH}, {"script", SSML_IGNORE_TEXT}, {"style", SSML_IGNORE_TEXT}, + {"font", HTML_NOSPACE}, + {"b", HTML_NOSPACE}, + {"i", HTML_NOSPACE}, + {"strong", HTML_NOSPACE}, + {"em", HTML_NOSPACE}, + {"code", HTML_NOSPACE}, {NULL,0}}; -static const char *VoiceFromStack() +static const char *VoiceFromStack(void) {//================================ // Use the voice properties from the SSML stack to choose a voice, and switch // to that voice if it's not the current voice int ix; + const char *p; SSML_STACK *sp; const char *v_id; int voice_name_specified; int voice_found; espeak_VOICE voice_select; - char voice_name[40]; + static char voice_name[40]; char language[40]; + char buf[80]; strcpy(voice_name,ssml_stack[0].voice_name); strcpy(language,ssml_stack[0].language); voice_select.age = ssml_stack[0].voice_age; voice_select.gender = ssml_stack[0].voice_gender; - voice_select.variant = ssml_stack[0].voice_variant; + voice_select.variant = ssml_stack[0].voice_variant_number; voice_select.identifier = NULL; for(ix=0; ix<n_ssml_stack; ix++) @@ -983,15 +1218,32 @@ static const char *VoiceFromStack() if(sp->language[0] != 0) { strcpy(language, sp->language); + + // is this language provided by the base voice? + p = base_voice.languages; + while(*p++ != 0) + { + if(strcmp(p, language) == 0) + { + // yes, change the language to the main language of the base voice + strcpy(language, &base_voice.languages[1]); + break; + } + p += (strlen(p) + 1); + } + if(voice_name_specified == 0) voice_name[0] = 0; // forget a previous voice name if a language is specified } if(sp->voice_gender != 0) + { voice_select.gender = sp->voice_gender; + } + if(sp->voice_age != 0) voice_select.age = sp->voice_age; - if(sp->voice_variant != 0) - voice_select.variant = sp->voice_variant; + if(sp->voice_variant_number != 0) + voice_select.variant = sp->voice_variant_number; } voice_select.name = voice_name; @@ -999,6 +1251,14 @@ static const char *VoiceFromStack() v_id = SelectVoice(&voice_select, &voice_found); if(v_id == NULL) return("default"); + + if((strchr(v_id, '+') == NULL) && ((voice_select.gender == 0) || (voice_select.gender == base_voice.gender)) && (base_voice_variant_name[0] != 0)) + { + // a voice variant has not been selected, use the original voice variant + sprintf(buf, "%s+%s", v_id, base_voice_variant_name); + strncpy0(voice_name, buf, sizeof(voice_name)); + return(voice_name); + } return(v_id); } // end of VoiceFromStack @@ -1012,7 +1272,7 @@ static void ProcessParamStack(char *outbuf, int *outix) int value; char buf[20]; int new_parameters[N_SPEECH_PARAM]; - static char cmd_letter[N_SPEECH_PARAM] = {0, 'S','A','P','R', 0, 0, 0, 0, 0, 0, 0, 'F'}; // embedded command letters + static char cmd_letter[N_SPEECH_PARAM] = {0, 'S','A','P','R', 0, 'C', 0, 0, 0, 0, 0, 'F'}; // embedded command letters for(param=0; param<N_SPEECH_PARAM; param++) @@ -1054,7 +1314,7 @@ static void ProcessParamStack(char *outbuf, int *outix) speech_parameters[param] = new_parameters[param]; strcpy(&outbuf[*outix],buf); - (*outix) += strlen(buf); + *outix += strlen(buf); } } } // end of ProcessParamStack @@ -1126,7 +1386,7 @@ static wchar_t *GetSsmlAttribute(wchar_t *pw, const char *name) while(iswspace(*pw)) pw++; if(*pw == '=') pw++; while(iswspace(*pw)) pw++; - if(*pw == '"') + if((*pw == '"') || (*pw == '\'')) // allow single-quotes ? return(pw+1); else return(empty); @@ -1141,14 +1401,14 @@ static wchar_t *GetSsmlAttribute(wchar_t *pw, const char *name) static int attrcmp(const wchar_t *string1, const char *string2) {//============================================================ int ix; - + if(string1 == NULL) return(1); for(ix=0; (string1[ix] == string2[ix]) && (string1[ix] != 0); ix++) { } - if((string1[ix]=='"') && (string2[ix]==0)) + if(((string1[ix]=='"') || (string1[ix]=='\'')) && (string2[ix]==0)) return(0); return(1); } @@ -1171,10 +1431,10 @@ static int attrnumber(const wchar_t *pw, int default_value, int type) {//================================================================== int value = 0; - if((pw == NULL) || !isdigit(*pw)) + if((pw == NULL) || !IsDigit09(*pw)) return(default_value); - while(isdigit(*pw)) + while(IsDigit09(*pw)) { value = value*10 + *pw++ - '0'; } @@ -1217,7 +1477,7 @@ static int attr_prosody_value(int param_type, const wchar_t *pw, int *value_out) {//============================================================================= int sign = 0; wchar_t *tail; - float value; + double value; while(iswspace(*pw)) pw++; if(*pw == '+') @@ -1227,10 +1487,10 @@ static int attr_prosody_value(int param_type, const wchar_t *pw, int *value_out) } if(*pw == '-') { - pw++; + pw++; sign = -1; } - value = (float)wcstod(pw,&tail); + value = (double)wcstod(pw,&tail); if(tail == pw) { // failed to find a number, return 100% @@ -1248,16 +1508,23 @@ static int attr_prosody_value(int param_type, const wchar_t *pw, int *value_out) if((tail[0]=='s') && (tail[1]=='t')) { +#ifdef PLATFORM_RISCOS + *value_out = 100; +#else double x; // convert from semitones to a frequency percentage - x = pow(double(2.0),double((value*sign)/12)) * 100; + x = pow((double)2.0,(double)((value*sign)/12)) * 100; *value_out = (int)x; +#endif return(2); // percentage } if(param_type == espeakRATE) { - *value_out = (int)(value * 100); + if(sign == 0) + *value_out = (int)(value * 100); + else + *value_out = 100 + (int)(sign * value * 100); return(2); // percentage } @@ -1266,7 +1533,7 @@ static int attr_prosody_value(int param_type, const wchar_t *pw, int *value_out) } // end of attr_prosody_value -int AddNameData(const char *name, int wide) +static int AddNameData(const char *name, int wide) {//======================================== // Add the name to the namedata and return its position // (Used by the Windows SAPI wrapper) @@ -1287,11 +1554,12 @@ int AddNameData(const char *name, int wide) if(namedata_ix+len >= n_namedata) { // allocate more space for marker names - if((vp = realloc(namedata, namedata_ix+len + 300)) == NULL) + if((vp = realloc(namedata, namedata_ix+len + 1000)) == NULL) return(-1); // failed to allocate, original data is unchanged but ignore this new name +// !!! Bug?? If the allocated data shifts position, then pointers given to user application will be invalid namedata = (char *)vp; - n_namedata = namedata_ix+len + 300; + n_namedata = namedata_ix+len + 1000; } memcpy(&namedata[ix = namedata_ix],name,len); namedata_ix += len; @@ -1299,8 +1567,8 @@ int AddNameData(const char *name, int wide) } // end of AddNameData -void SetVoiceStack(espeak_VOICE *v) -{//================================ +void SetVoiceStack(espeak_VOICE *v, const char *variant_name) +{//========================================================== SSML_STACK *sp; sp = &ssml_stack[0]; @@ -1312,10 +1580,15 @@ void SetVoiceStack(espeak_VOICE *v) if(v->languages != NULL) strcpy(sp->language,v->languages); if(v->name != NULL) - strcpy(sp->voice_name,v->name); - sp->voice_variant = v->variant; + strncpy0(sp->voice_name, v->name, sizeof(sp->voice_name)); + sp->voice_variant_number = v->variant; sp->voice_age = v->age; sp->voice_gender = v->gender; + + if(memcmp(variant_name, "!v", 2) == 0) + variant_name += 3;// strip variant directory name, !v plus PATHSEP + strncpy0(base_voice_variant_name, variant_name, sizeof(base_voice_variant_name)); + memcpy(&base_voice, ¤t_voice_selected, sizeof(base_voice)); } @@ -1332,6 +1605,7 @@ static int GetVoiceAttributes(wchar_t *pw, int tag_type) wchar_t *name; wchar_t *age; wchar_t *variant; + int value; const char *new_voice_id; SSML_STACK *ssml_sp; @@ -1369,15 +1643,17 @@ static int GetVoiceAttributes(wchar_t *pw, int tag_type) age = GetSsmlAttribute(pw,"age"); gender = GetSsmlAttribute(pw,"gender"); } - + if((tag_type != SSML_VOICE) && (lang==NULL)) return(0); // <s> or <p> without language spec, nothing to do - + ssml_sp = &ssml_stack[n_ssml_stack++]; attrcopy_utf8(ssml_sp->language,lang,sizeof(ssml_sp->language)); attrcopy_utf8(ssml_sp->voice_name,name,sizeof(ssml_sp->voice_name)); - ssml_sp->voice_variant = attrnumber(variant,1,0)-1; + if((value = attrnumber(variant,1,0)) > 0) + value--; // variant='0' and variant='1' the same + ssml_sp->voice_variant_number = value; ssml_sp->voice_age = attrnumber(age,0,0); ssml_sp->voice_gender = attrlookup(gender,mnem_gender); ssml_sp->tag_type = tag_type; @@ -1415,8 +1691,8 @@ static void SetProsodyParameter(int param_type, wchar_t *attr1, PARAM_STACK *sp) {"x-slow",60}, {"slow",80}, {"medium",100}, - {"fast",120}, - {"x-fast",150}, + {"fast",125}, + {"x-fast",160}, {NULL, -1}}; static const MNEM_TAB mnem_pitch[] = { @@ -1467,6 +1743,31 @@ static void SetProsodyParameter(int param_type, wchar_t *attr1, PARAM_STACK *sp) } // end of SetProsodyParemeter +static int ReplaceKeyName(char *outbuf, int index, int *outix) +{//=========================================================== +// Replace some key-names by single characters, so they can be pronounced in different languages + static MNEM_TAB keynames[] = { + {"space ",0xe020}, + {"tab ", 0xe009}, + {"underscore ", 0xe05f}, + {"double-quote ", '"'}, + {NULL, 0}}; + + int ix; + int letter; + char *p; + + p = &outbuf[index]; + + if((letter = LookupMnem(keynames, p)) != 0) + { + ix = utf8_out(letter, p); + *outix = index + ix; + return(letter); + } + return(0); +} + static int ProcessSsmlTag(wchar_t *xml_buf, char *outbuf, int *outix, int n_outbuf, int self_closing) {//================================================================================================== @@ -1483,7 +1784,7 @@ static int ProcessSsmlTag(wchar_t *xml_buf, char *outbuf, int *outix, int n_outb int voice_change_flag; wchar_t *px; wchar_t *attr1; - wchar_t *attr2; + wchar_t *attr2; wchar_t *attr3; int terminator; char *uri; @@ -1532,6 +1833,7 @@ static int ProcessSsmlTag(wchar_t *xml_buf, char *outbuf, int *outix, int n_outb {"reduced",2}, {"moderate",3}, {"strong",4}, + {"x-strong",5}, {NULL,-1}}; static const char *prosody_attr[5] = { @@ -1546,19 +1848,29 @@ static int ProcessSsmlTag(wchar_t *xml_buf, char *outbuf, int *outix, int n_outb tag_name[ix] = 0; px = &xml_buf[ix]; // the tag's attributes - + if(tag_name[0] == '/') { - tag_type = LookupMnem(ssmltags,&tag_name[1]) + SSML_CLOSE; // closing tag + // closing tag + if((tag_type = LookupMnem(ssmltags,&tag_name[1])) != HTML_NOSPACE) + { + outbuf[(*outix)++] = ' '; + } + tag_type += SSML_CLOSE; } else { - tag_type = LookupMnem(ssmltags,tag_name); + if((tag_type = LookupMnem(ssmltags,tag_name)) != HTML_NOSPACE) + { + // separate SSML tags from the previous word (but not HMTL tags such as <b> <font> which can occur inside a word) + outbuf[(*outix)++] = ' '; + } if(self_closing && ignore_if_self_closing[tag_type]) return(0); } + voice_change_flag = 0; terminator = CLAUSE_NONE; ssml_sp = &ssml_stack[n_ssml_stack-1]; @@ -1610,14 +1922,16 @@ static int ProcessSsmlTag(wchar_t *xml_buf, char *outbuf, int *outix, int n_outb if(translator->langopts.tone_language == 1) { - static unsigned char emphasis_to_pitch_range[] = {50,50,40,70,90,90}; - static unsigned char emphasis_to_volume[] = {100,100,70,110,140,140}; + static unsigned char emphasis_to_pitch_range[] = {50,50,40,70,90,100}; + static unsigned char emphasis_to_volume[] = {100,100,70,110,135,150}; // tone language (eg.Chinese) do emphasis by increasing the pitch range. sp->parameter[espeakRANGE] = emphasis_to_pitch_range[value]; sp->parameter[espeakVOLUME] = emphasis_to_volume[value]; } else { + static unsigned char emphasis_to_volume2[] = {100,100,75,100,120,150}; + sp->parameter[espeakVOLUME] = emphasis_to_volume2[value]; sp->parameter[espeakEMPHASIS] = value; } ProcessParamStack(outbuf, outix); @@ -1650,12 +1964,19 @@ static int ProcessSsmlTag(wchar_t *xml_buf, char *outbuf, int *outix, int n_outb sprintf(buf,"%c%dY",CTRL_EMBEDDED,value); strcpy(&outbuf[*outix],buf); - (*outix) += strlen(buf); + *outix += strlen(buf); + sayas_start = *outix; sayas_mode = value; // punctuation doesn't end clause during SAY-AS break; case SSML_SAYAS + SSML_CLOSE: + if(sayas_mode == SAYAS_KEY) + { + outbuf[*outix] = 0; + ReplaceKeyName(outbuf, sayas_start, outix); + } + outbuf[(*outix)++] = CTRL_EMBEDDED; outbuf[(*outix)++] = 'Y'; sayas_mode = 0; @@ -1666,7 +1987,7 @@ static int ProcessSsmlTag(wchar_t *xml_buf, char *outbuf, int *outix, int n_outb { // use the alias rather than the text ignore_text = 1; - (*outix) += attrcopy_utf8(&outbuf[*outix],attr1,n_outbuf-*outix); + *outix += attrcopy_utf8(&outbuf[*outix],attr1,n_outbuf-*outix); } break; @@ -1697,7 +2018,7 @@ static int ProcessSsmlTag(wchar_t *xml_buf, char *outbuf, int *outix, int n_outb { sprintf(buf,"%c%dM",CTRL_EMBEDDED,index); strcpy(&outbuf[*outix],buf); - (*outix) += strlen(buf); + *outix += strlen(buf); } } break; @@ -1725,7 +2046,7 @@ static int ProcessSsmlTag(wchar_t *xml_buf, char *outbuf, int *outix, int n_outb { sprintf(buf,"%c%dI",CTRL_EMBEDDED,index); strcpy(&outbuf[*outix],buf); - (*outix) += strlen(buf); + *outix += strlen(buf); sp->parameter[espeakSILENCE] = 1; } } @@ -1738,7 +2059,7 @@ static int ProcessSsmlTag(wchar_t *xml_buf, char *outbuf, int *outix, int n_outb { sprintf(buf,"%c%dU",CTRL_EMBEDDED,index); strcpy(&outbuf[*outix],buf); - (*outix) += strlen(buf); + *outix += strlen(buf); sp->parameter[espeakSILENCE] = 1; } } @@ -1748,10 +2069,13 @@ static int ProcessSsmlTag(wchar_t *xml_buf, char *outbuf, int *outix, int n_outb if(self_closing) PopParamStack(tag_type, outbuf, outix); + else + audio_text = 1; return(CLAUSE_NONE); case SSML_AUDIO + SSML_CLOSE: PopParamStack(tag_type, outbuf, outix); + audio_text = 0; return(CLAUSE_NONE); case SSML_BREAK: @@ -1766,14 +2090,20 @@ static int ProcessSsmlTag(wchar_t *xml_buf, char *outbuf, int *outix, int n_outb { // adjust prepause on the following word sprintf(&outbuf[*outix],"%c%dB",CTRL_EMBEDDED,value); - (*outix) += 3; + *outix += 3; terminator = 0; } value = break_value[value]; } if((attr2 = GetSsmlAttribute(px,"time")) != NULL) { - value = (attrnumber(attr2,0,1) * 25) / speed.speed_factor1; // compensate for speaking speed to keep constant pause length + value2 = attrnumber(attr2,0,1); // pause in mS + + // compensate for speaking speed to keep constant pause length, see function PauseLength() + // 'value' here is x 10mS + value = (value2 * 256) / (speed.clause_pause_factor * 10); + if(value < 200) + value = (value2 * 256) / (speed.pause_factor * 10); if(terminator == 0) terminator = CLAUSE_NONE; @@ -1781,7 +2111,13 @@ static int ProcessSsmlTag(wchar_t *xml_buf, char *outbuf, int *outix, int n_outb if(terminator) { if(value > 0xfff) - value = 0xfff; + { + // scale down the value and set a scaling indicator bit + value = value / 32; + if(value > 0xfff) + value = 0xfff; + terminator |= CLAUSE_PAUSE_LONG; + } return(terminator + value); } break; @@ -1873,9 +2209,18 @@ terminator=0; // ?? Sentence intonation, but no pause ?? } // end of ProcessSsmlTag +static void RemoveChar(char *p) +{//======================= +// Replace a UTF-8 character by spaces + int c; + + memset(p, ' ', utf8_in(&c, p)); +} // end of RemoveChar + + static MNEM_TAB xml_char_mnemonics[] = { {"gt",'>'}, - {"lt",'<'}, + {"lt", 0xe000 + '<'}, // private usage area, to avoid confusion with XML tag {"amp", '&'}, {"quot", '"'}, {"nbsp", ' '}, @@ -1883,8 +2228,8 @@ static MNEM_TAB xml_char_mnemonics[] = { {NULL,-1}}; -int ReadClause(Translator *tr, FILE *f_in, char *buf, short *charix, int *charix_top, int n_buf, int *tone_type) -{//============================================================================================================= +int ReadClause(Translator *tr, FILE *f_in, char *buf, short *charix, int *charix_top, int n_buf, int *tone_type, char *voice_change) +{//================================================================================================================================= /* Find the end of the current clause. Write the clause into buf @@ -1899,6 +2244,8 @@ int ReadClause(Translator *tr, FILE *f_in, char *buf, short *charix, int *charix int c1=' '; // current character int c2; // next character int cprev=' '; // previous character + int cprev2=' '; + int c_next; int parag; int ix = 0; int j; @@ -1911,9 +2258,12 @@ int ReadClause(Translator *tr, FILE *f_in, char *buf, short *charix, int *charix int found; int any_alnum = 0; int self_closing; - int punct_data; + int punct_data = 0; + int is_end_clause; + int announced_punctuation = 0; int stressed_word = 0; - const char *p; + int end_clause_after_tag = 0; + int end_clause_index = 0; wchar_t xml_buf[N_XML_BUF+1]; #define N_XML_BUF2 20 @@ -1927,10 +2277,12 @@ int ReadClause(Translator *tr, FILE *f_in, char *buf, short *charix, int *charix clear_skipping_text = 0; } + tr->phonemes_repeat_count = 0; tr->clause_upper_count = 0; tr->clause_lower_count = 0; end_of_input = 0; *tone_type = 0; + *voice_change = 0; f_input = f_in; // for GetC etc @@ -1960,7 +2312,7 @@ f_input = f_in; // for GetC etc return(CLAUSE_EOF); } - if((skip_characters > 0) && (count_characters > skip_characters)) + if((skip_characters > 0) && (count_characters >= skip_characters)) { // reached the specified start position // don't break a word @@ -1971,6 +2323,7 @@ f_input = f_in; // for GetC etc } } + cprev2 = cprev; cprev = c1; c1 = c2; @@ -2064,8 +2417,19 @@ f_input = f_in; // for GetC etc c2 = ' '; } else - if((c2 == '/') || iswalpha(c2)) + if((c2 == '/') || iswalpha2(c2)) { + // check for space in the output buffer for embedded commands produced by the SSML tag + if(ix > (n_buf - 20)) + { + // Perhaps not enough room, end the clause before the SSML tag + UngetC(c2); + ungot_char2 = c1; + buf[ix] = ' '; + buf[ix+1] = 0; + return(CLAUSE_NONE); + } + // SSML Tag n_xml_buf = 0; c1 = c2; @@ -2076,9 +2440,7 @@ f_input = f_in; // for GetC etc } xml_buf[n_xml_buf] = 0; c2 = ' '; - - buf[ix++] = ' '; - + self_closing = 0; if(xml_buf[n_xml_buf-1] == '/') { @@ -2086,26 +2448,25 @@ f_input = f_in; // for GetC etc xml_buf[n_xml_buf-1] = ' '; self_closing = 1; } - - terminator = ProcessSsmlTag(xml_buf,buf,ix,n_buf,self_closing); - + + terminator = ProcessSsmlTag(xml_buf,buf,&ix,n_buf,self_closing); + if(terminator != 0) { + if(end_clause_after_tag) + ix = end_clause_index; + buf[ix] = ' '; buf[ix++] = 0; - + if(terminator & CLAUSE_BIT_VOICE) { - // a change in voice, write the new voice name to the end of the buf - p = current_voice_id; - while((*p != 0) && (ix < (n_buf-1))) - { - buf[ix++] = *p++; - } - buf[ix++] = 0; + strcpy(voice_change, current_voice_id); } return(terminator); } + c1 = ' '; + c2 = GetC(); continue; } } @@ -2180,6 +2541,16 @@ f_input = f_in; // for GetC etc linelength++; + if((j = lookupwchar2(tr->chars_ignore,c1)) != 0) + { + if(j == 1) + { + // ignore this character (eg. zero-width-non-joiner U+200C) + continue; + } + c1 = j; // replace the character + } + if(iswalnum(c1)) any_alnum = 1; else @@ -2192,10 +2563,13 @@ f_input = f_in; // for GetC etc c2 = ' '; } + if(c1 == 0xf0b) + c1 = ' '; // Tibet inter-syllabic mark, ?? replace by space ?? + if(iswspace(c1)) { char *p_word; - + if(tr->translator_name == 0x6a626f) { // language jbo : lojban @@ -2215,18 +2589,27 @@ f_input = f_in; // for GetC etc } } } + + if(c1 == 0xd4d) + { + // Malayalam virama, check if next character is Zero-width-joiner + if(c2 == 0x200d) + { + c1 = 0xd4e; // use this unofficial code for chillu-virama + } + } } - if(iswupper(c1)) + if(iswupper2(c1)) { tr->clause_upper_count++; - if((option_capitals == 2) && (sayas_mode == 0) && !iswupper(cprev)) + if((option_capitals == 2) && (sayas_mode == 0) && !iswupper2(cprev)) { char text_buf[40]; char text_buf2[30]; if(LookupSpecial(tr, "_cap", text_buf2) != NULL) { - sprintf(text_buf,"%s%s%s",tone_punct_on,text_buf2,tone_punct_off); + sprintf(text_buf,"%s",text_buf2); j = strlen(text_buf); if((ix + j) < n_buf) { @@ -2237,7 +2620,7 @@ f_input = f_in; // for GetC etc } } else - if(iswalpha(c1)) + if(iswalpha2(c1)) tr->clause_lower_count++; if(option_phoneme_input) @@ -2268,6 +2651,10 @@ f_input = f_in; // for GetC etc // 2nd newline, assume paragraph UngetC(c2); + if(end_clause_after_tag) + { + RemoveChar(&buf[end_clause_index]); // delete clause-end punctiation + } buf[ix] = ' '; buf[ix+1] = 0; if(parag > 3) @@ -2288,88 +2675,231 @@ if(option_ssml) parag=1; linelength = 0; } - if(option_punctuation && (phoneme_mode==0) && (sayas_mode==0) && iswpunct(c1)) + announced_punctuation = 0; + + if((phoneme_mode==0) && (sayas_mode==0)) { - // option is set to explicitly speak punctuation characters - // if a list of allowed punctuation has been set up, check whether the character is in it - if((option_punctuation == 1) || (wcschr(option_punctlist,c1) != NULL)) + is_end_clause = 0; + + if(end_clause_after_tag) { - if((terminator = AnnouncePunctuation(tr, c1, c2, buf, ix)) >= 0) - return(terminator); + // Because of an xml tag, we are waiting for the + // next non-blank character to decide whether to end the clause + // i.e. is dot followed by an upper-case letter? + + if(!iswspace(c1)) + { + if(!IsAlpha(c1) || !iswlower2(c1)) +// if(iswdigit(c1) || (IsAlpha(c1) && !iswlower2(c1))) + { + UngetC(c2); + ungot_char2 = c1; + buf[end_clause_index] = ' '; // delete the end-clause punctuation + buf[end_clause_index+1] = 0; + return(end_clause_after_tag); + } + end_clause_after_tag = 0; + } } - } - if((phoneme_mode==0) && (sayas_mode==0) && ((punct = lookupwchar(punct_chars,c1)) != 0)) - { - punct_data = punct_attributes[punct]; + if((c1 == '.') && (c2 == '.')) + { + while((c_next = GetC()) == '.') + { + // 3 or more dots, replace by elipsis + c1 = 0x2026; + c2 = ' '; + } + if(c1 == 0x2026) + c2 = c_next; + else + UngetC(c_next); + } - if(punct_data & PUNCT_IN_WORD) + punct_data = 0; + if((punct = lookupwchar(punct_chars,c1)) != 0) { - // Armenian punctuation inside a word - stressed_word = 1; - *tone_type = punct_data >> 12 & 0xf; // override the end-of-sentence type - continue; + punct_data = punct_attributes[punct]; + + if(punct_data & PUNCT_IN_WORD) + { + // Armenian punctuation inside a word + stressed_word = 1; + *tone_type = punct_data >> 12 & 0xf; // override the end-of-sentence type + continue; + } + + if((iswspace(c2) || (punct_data & 0x8000) || IsBracket(c2) || (c2=='?') || Eof() || (c2 == ctrl_embedded))) // don't check for '-' because it prevents recognizing ':-)' +// if((iswspace(c2) || (punct_data & 0x8000) || IsBracket(c2) || (c2=='?') || (c2=='-') || Eof())) + { + // note: (c2='?') is for when a smart-quote has been replaced by '?' + is_end_clause = 1; + } } - if((iswspace(c2) || (punct_data & 0x8000) || IsBracket(c2) || (c2=='?') || (c2=='-') || Eof())) + // don't announce punctuation for the alternative text inside inside <audio> ... </audio> + if(c1 == 0xe000+'<') c1 = '<'; + if(option_punctuation && iswpunct(c1) && (audio_text == 0)) { - // note: (c2='?') is for when a smart-quote has been replaced by '?' - buf[ix] = ' '; - buf[ix+1] = 0; - - if((c1 == '.') && (cprev == '.')) + // option is set to explicitly speak punctuation characters + // if a list of allowed punctuation has been set up, check whether the character is in it + if((option_punctuation == 1) || (wcschr(option_punctlist,c1) != NULL)) { - c1 = 0x2026; - punct = 9; // elipsis + tr->phonemes_repeat_count = 0; + if((terminator = AnnouncePunctuation(tr, c1, &c2, buf, &ix, is_end_clause)) >= 0) + return(terminator); + announced_punctuation = c1; } - + } + + if((punct_data & PUNCT_SAY_NAME) && (announced_punctuation == 0)) + { + // used for elipsis (and 3 dots) if a pronunciation for elipsis is given in *_list + char *p2; + + p2 = &buf[ix]; + sprintf(p2,"%s",LookupCharName(tr, c1, 1)); + if(p2[0] != 0) + { + ix += strlen(p2); + announced_punctuation = c1; + punct_data = punct_data & ~CLAUSE_BITS_INTONATION; // change intonation type to 0 (full-stop) + } + } + + if(is_end_clause) + { nl_count = 0; - while(!Eof() && iswspace(c2)) + c_next = c2; + + if(iswspace(c_next)) { - if(c2 == '\n') - nl_count++; - c2 = GetC(); // skip past space(s) + while(!Eof() && iswspace(c_next)) + { + if(c_next == '\n') + nl_count++; + c_next = GetC(); // skip past space(s) + } } - if(!Eof()) + + if((c1 == '.') && (nl_count < 2)) { - UngetC(c2); + punct_data |= CLAUSE_DOT; } - - if((nl_count==0) && (c1 == '.')) + + if(nl_count==0) { - if(iswdigit(cprev) && (tr->langopts.numbers & 0x10000) && islower(c2)) + if((c1 == ',') && (cprev == '.') && (tr->translator_name == L('h','u')) && iswdigit(cprev2) && (iswdigit(c_next) || (iswlower2(c_next)))) + { + // lang=hu, fix for ordinal numbers, eg: "december 2., szerda", ignore ',' after ordinal number + c1 = CHAR_COMMA_BREAK; + is_end_clause = 0; + } + + if(c1 == '.') + { + if((tr->langopts.numbers & NUM_ORDINAL_DOT) && + (iswdigit(cprev) || (IsRomanU(cprev) && (IsRomanU(cprev2) || iswspace(cprev2))))) // lang=hu + { + // dot after a number indicates an ordinal number + if(!iswdigit(cprev)) + { + is_end_clause = 0; // Roman number followed by dot + } + else + { + if (iswlower2(c_next) || (c_next=='-')) // hyphen is needed for lang-hu (eg. 2.-kal) + is_end_clause = 0; // only if followed by lower-case, (or if there is a XML tag) + } + } + else + if(c_next == '\'') + { + is_end_clause = 0; // eg. u.s.a.'s + } + if(iswlower2(c_next)) + { + // next word has no capital letter, this dot is probably from an abbreviation +// c1 = ' '; + is_end_clause = 0; + } + if(any_alnum==0) + { + // no letters or digits yet, so probably not a sentence terminator + // Here, dot is followed by space or bracket + c1 = ' '; + is_end_clause = 0; + } + } + else { - // dot after a number indicates an ordinal number - c2 = '.'; - continue; + if(any_alnum==0) + { + // no letters or digits yet, so probably not a sentence terminator + is_end_clause = 0; + } } - if(iswlower(c2)) + + if(is_end_clause && (c1 == '.') && (c_next == '<') && option_ssml) { - c2 = ' '; - continue; // next word has no capital letter, this dot is probably from an abbreviation + // wait until after the end of the xml tag, then look for upper-case letter + is_end_clause = 0; + end_clause_index = ix; + end_clause_after_tag = punct_data; } - if(any_alnum==0) + } + + if(is_end_clause) + { + UngetC(c_next); + buf[ix] = ' '; + buf[ix+1] = 0; + + if(iswdigit(cprev) && !IsAlpha(c_next)) // ???? { - c2 = ' '; // no letters or digits yet, so probably not a sentence terminator - continue; + punct_data &= ~CLAUSE_DOT; } + if(nl_count > 1) + { + if((punct_data == CLAUSE_QUESTION) || (punct_data == CLAUSE_EXCLAMATION)) + return(punct_data + 35); // with a longer pause + return(CLAUSE_PARAGRAPH); + } + return(punct_data); // only recognise punctuation if followed by a blank or bracket/quote } - - punct_data = punct_attributes[punct]; - if(nl_count > 1) + else { - if((punct_data == CLAUSE_QUESTION) || (punct_data == CLAUSE_EXCLAMATION)) - return(punct_data + 35); // with a longer pause - return(CLAUSE_PARAGRAPH); + if(!Eof()) + { + if(iswspace(c2)) + UngetC(c_next); + } } - return(punct_data); // only recognise punctuation if followed by a blank or bracket/quote } } if(speech_parameters[espeakSILENCE]==1) continue; + if(c1 == announced_punctuation) + { + // This character has already been announced, so delete it so that it isn't spoken a second time. + // Unless it's a hyphen or apostrophe (which is used by TranslateClause() ) + if(IsBracket(c1)) + { + c1 = 0xe000 + '('; // Unicode private useage area. So TranslateRules() knows the bracket name has been spoken + } + else + if(c1 != '-') + { + c1 = ' '; + } + } + j = ix+1; + + if(c1 == 0xe000 + '<') c1 = '<'; + ix += utf8_out(c1,&buf[ix]); // buf[ix++] = c1; if(!iswspace(c1) && !IsBracket(c1)) { @@ -2379,10 +2909,11 @@ if(option_ssml) parag=1; } *charix_top = ix; - if(((ix > (n_buf-20)) && !IsAlpha(c1) && !iswdigit(c1)) || (ix >= (n_buf-2))) + if(((ix > (n_buf-75)) && !IsAlpha(c1) && !iswdigit(c1)) || (ix >= (n_buf-4))) { // clause too long, getting near end of buffer, so break here // try to break at a word boundary (unless we actually reach the end of buffer). + // (n_buf-4) is to allow for 3 bytes of multibyte character plus terminator. buf[ix] = ' '; buf[ix+1] = 0; UngetC(c2); @@ -2394,6 +2925,10 @@ if(option_ssml) parag=1; { ix += utf8_out(CHAR_EMPHASIS, &buf[ix]); } + if(end_clause_after_tag) + { + RemoveChar(&buf[end_clause_index]); // delete clause-end punctiation + } buf[ix] = ' '; buf[ix+1] = 0; return(CLAUSE_EOF); // end of file @@ -2417,6 +2952,7 @@ void InitText2(void) int param; ungot_char = 0; + ungot_char2 = 0; n_ssml_stack =1; n_param_stack = 1; @@ -2431,6 +2967,7 @@ void InitText2(void) current_voice_id[0] = 0; ignore_text = 0; + audio_text = 0; clear_skipping_text = 0; count_characters = -1; sayas_mode = 0; |