| #define N_XML_BUF 500 | #define N_XML_BUF 500 | ||||
| static void DecodeWithPhonemeMode(char *buf, char *phonemes, Translator *tr, Translator *tr2, unsigned int flags[]); | |||||
| static void TerminateBufWithSpaceAndZero(char *buf, int index, int *ungetc); | |||||
| static const char *xmlbase = ""; // base URL from <speak> | static const char *xmlbase = ""; // base URL from <speak> | ||||
| static int namedata_ix = 0; | static int namedata_ix = 0; | ||||
| flags[0] = flags[1] = 0; | flags[0] = flags[1] = 0; | ||||
| if (LookupDictList(tr, &string1, phonemes, flags, 0, NULL)) { | if (LookupDictList(tr, &string1, phonemes, flags, 0, NULL)) { | ||||
| char phonemes2[55]; | |||||
| SetWordStress(tr, phonemes, flags, -1, 0); | |||||
| DecodePhonemes(phonemes, phonemes2); | |||||
| sprintf(text_out, "[\002%s]]", phonemes2); | |||||
| DecodeWithPhonemeMode(text_out, phonemes, tr, NULL, flags); | |||||
| return text_out; | return text_out; | ||||
| } | } | ||||
| return NULL; | return NULL; | ||||
| } | } | ||||
| static const char *LookupCharName(Translator *tr, int c, int only) | |||||
| static const char *LookupCharName(Translator *tr, int c, bool only) | |||||
| { | { | ||||
| // Find the phoneme string (in ascii) to speak the name of character c | // Find the phoneme string (in ascii) to speak the name of character c | ||||
| // Used for punctuation characters and symbols | // Used for punctuation characters and symbols | ||||
| ix = utf8_out(c, &single_letter[2]); | ix = utf8_out(c, &single_letter[2]); | ||||
| single_letter[2+ix] = 0; | single_letter[2+ix] = 0; | ||||
| if (only) { | |||||
| if (only == true) { | |||||
| string = &single_letter[2]; | string = &single_letter[2]; | ||||
| LookupDictList(tr, &string, phonemes, flags, 0, NULL); | LookupDictList(tr, &string, phonemes, flags, 0, NULL); | ||||
| } else { | |||||
| } | |||||
| if (only == false) { | |||||
| string = &single_letter[1]; | string = &single_letter[1]; | ||||
| if (LookupDictList(tr, &string, phonemes, flags, 0, NULL) == 0) { | if (LookupDictList(tr, &string, phonemes, flags, 0, NULL) == 0) { | ||||
| // try _* then * | // try _* then * | ||||
| TranslateRules(tr, &single_letter[2], phonemes, sizeof(phonemes), NULL, 0, NULL); | TranslateRules(tr, &single_letter[2], phonemes, sizeof(phonemes), NULL, 0, NULL); | ||||
| } | } | ||||
| } | } | ||||
| } | |||||
| if ((only == 0) && ((phonemes[0] == 0) || (phonemes[0] == phonSWITCH)) && (tr->translator_name != L('e', 'n'))) { | |||||
| // not found, try English | |||||
| SetTranslator2(ESPEAKNG_DEFAULT_VOICE); | |||||
| string = &single_letter[1]; | |||||
| single_letter[1] = '_'; | |||||
| if (LookupDictList(translator2, &string, phonemes, flags, 0, NULL) == 0) { | |||||
| string = &single_letter[2]; | |||||
| LookupDictList(translator2, &string, phonemes, flags, 0, NULL); | |||||
| } | |||||
| if (phonemes[0]) | |||||
| lang_name = ESPEAKNG_DEFAULT_VOICE; | |||||
| else | |||||
| SelectPhonemeTable(voice->phoneme_tab_ix); // revert to original phoneme table | |||||
| if (((phonemes[0] == 0) || (phonemes[0] == phonSWITCH)) && (tr->translator_name != L('e', 'n'))) { | |||||
| // not found, try English | |||||
| SetTranslator2(ESPEAKNG_DEFAULT_VOICE); | |||||
| string = &single_letter[1]; | |||||
| single_letter[1] = '_'; | |||||
| if (LookupDictList(translator2, &string, phonemes, flags, 0, NULL) == 0) { | |||||
| string = &single_letter[2]; | |||||
| LookupDictList(translator2, &string, phonemes, flags, 0, NULL); | |||||
| } | |||||
| if (phonemes[0]) | |||||
| lang_name = ESPEAKNG_DEFAULT_VOICE; | |||||
| else | |||||
| SelectPhonemeTable(voice->phoneme_tab_ix); // revert to original phoneme table | |||||
| } | |||||
| } | } | ||||
| if (phonemes[0]) { | if (phonemes[0]) { | ||||
| char phonemes2[60]; | |||||
| if (lang_name) { | if (lang_name) { | ||||
| SetWordStress(translator2, phonemes, flags, -1, 0); | |||||
| DecodePhonemes(phonemes, phonemes2); | |||||
| sprintf(buf, "[\002_^_%s %s _^_%s]]", ESPEAKNG_DEFAULT_VOICE, phonemes2, WordToString2(tr->translator_name)); | |||||
| DecodeWithPhonemeMode(buf, phonemes, tr, translator2, flags); | |||||
| SelectPhonemeTable(voice->phoneme_tab_ix); // revert to original phoneme table | SelectPhonemeTable(voice->phoneme_tab_ix); // revert to original phoneme table | ||||
| } else { | } else { | ||||
| SetWordStress(tr, phonemes, flags, -1, 0); | |||||
| DecodePhonemes(phonemes, phonemes2); | |||||
| sprintf(buf, "[\002%s]] ", phonemes2); | |||||
| DecodeWithPhonemeMode(buf, phonemes, tr, NULL, flags); | |||||
| } | } | ||||
| } else if (only == 0) | |||||
| } else if (only == false) | |||||
| strcpy(buf, "[\002(X1)(X1)(X1)]]"); | strcpy(buf, "[\002(X1)(X1)(X1)]]"); | ||||
| return buf; | return buf; | ||||
| punctname = ph_buf; // use word for 'period' instead of 'dot' | punctname = ph_buf; // use word for 'period' instead of 'dot' | ||||
| } | } | ||||
| if (punctname == NULL) | if (punctname == NULL) | ||||
| punctname = LookupCharName(tr, c1, 0); | |||||
| punctname = LookupCharName(tr, c1, false); | |||||
| if (punctname == NULL) | if (punctname == NULL) | ||||
| return -1; | return -1; | ||||
| punctname, punct_count, punctname); | punctname, punct_count, punctname); | ||||
| } else { | } else { | ||||
| // end the clause now and pick up the punctuation next time | // end the clause now and pick up the punctuation next time | ||||
| UngetC(c2); | |||||
| ungot_char2 = c1; | ungot_char2 = c1; | ||||
| buf[0] = ' '; | |||||
| buf[1] = 0; | |||||
| TerminateBufWithSpaceAndZero(buf, 0, &c2); | |||||
| } | } | ||||
| } | } | ||||
| // check for space in the output buffer for embedded commands produced by the SSML tag | // check for space in the output buffer for embedded commands produced by the SSML tag | ||||
| if (ix > (n_buf - 20)) { | if (ix > (n_buf - 20)) { | ||||
| // Perhaps not enough room, end the clause before the SSML tag | // Perhaps not enough room, end the clause before the SSML tag | ||||
| UngetC(c2); | |||||
| ungot_char2 = c1; | ungot_char2 = c1; | ||||
| buf[ix] = ' '; | |||||
| buf[ix+1] = 0; | |||||
| TerminateBufWithSpaceAndZero(buf, ix, &c2); | |||||
| return CLAUSE_NONE; | return CLAUSE_NONE; | ||||
| } | } | ||||
| terminator = ProcessSsmlTag(xml_buf, buf, &ix, n_buf, xmlbase, &audio_text, current_voice_id, &base_voice, base_voice_variant_name, &ignore_text, &clear_skipping_text, &sayas_mode, &sayas_start, ssml_stack, &n_ssml_stack, &n_param_stack, (int *)speech_parameters); | terminator = ProcessSsmlTag(xml_buf, buf, &ix, n_buf, xmlbase, &audio_text, current_voice_id, &base_voice, base_voice_variant_name, &ignore_text, &clear_skipping_text, &sayas_mode, &sayas_start, ssml_stack, &n_ssml_stack, &n_param_stack, (int *)speech_parameters); | ||||
| if (terminator != 0) { | if (terminator != 0) { | ||||
| buf[ix] = ' '; | |||||
| buf[ix++] = 0; | |||||
| TerminateBufWithSpaceAndZero(buf, ix, NULL); | |||||
| if (terminator & CLAUSE_TYPE_VOICE_CHANGE) | if (terminator & CLAUSE_TYPE_VOICE_CHANGE) | ||||
| strcpy(voice_change, current_voice_id); | strcpy(voice_change, current_voice_id); | ||||
| ix += utf8_out(c1, &buf[ix]); | ix += utf8_out(c1, &buf[ix]); | ||||
| terminator = CLAUSE_PERIOD; // line doesn't end in punctuation, assume period | terminator = CLAUSE_PERIOD; // line doesn't end in punctuation, assume period | ||||
| } | } | ||||
| buf[ix] = ' '; | |||||
| buf[ix+1] = 0; | |||||
| TerminateBufWithSpaceAndZero(buf, ix, NULL); | |||||
| return terminator; | return terminator; | ||||
| } | } | ||||
| } | } | ||||
| if (parag > 0) { | if (parag > 0) { | ||||
| // 2nd newline, assume paragraph | // 2nd newline, assume paragraph | ||||
| UngetC(c2); | |||||
| if (end_clause_after_tag) | if (end_clause_after_tag) | ||||
| RemoveChar(&buf[end_clause_index]); // delete clause-end punctiation | RemoveChar(&buf[end_clause_index]); // delete clause-end punctiation | ||||
| buf[ix] = ' '; | |||||
| buf[ix+1] = 0; | |||||
| TerminateBufWithSpaceAndZero(buf, ix, &c2); | |||||
| if (parag > 3) | if (parag > 3) | ||||
| parag = 3; | parag = 3; | ||||
| if (option_ssml) parag = 1; | if (option_ssml) parag = 1; | ||||
| if (linelength <= option_linelength) { | if (linelength <= option_linelength) { | ||||
| // treat lines shorter than a specified length as end-of-clause | // treat lines shorter than a specified length as end-of-clause | ||||
| UngetC(c2); | |||||
| buf[ix] = ' '; | |||||
| buf[ix+1] = 0; | |||||
| TerminateBufWithSpaceAndZero(buf, ix, &c2); | |||||
| return CLAUSE_COLON; | return CLAUSE_COLON; | ||||
| } | } | ||||
| if (!iswspace(c1)) { | if (!iswspace(c1)) { | ||||
| if (!IsAlpha(c1) || !iswlower(c1)) { | if (!IsAlpha(c1) || !iswlower(c1)) { | ||||
| UngetC(c2); | |||||
| ungot_char2 = c1; | ungot_char2 = c1; | ||||
| buf[end_clause_index] = ' '; // delete the end-clause punctuation | |||||
| buf[end_clause_index+1] = 0; | |||||
| TerminateBufWithSpaceAndZero(buf, end_clause_index, &c2); | |||||
| return end_clause_after_tag; | return end_clause_after_tag; | ||||
| } | } | ||||
| end_clause_after_tag = 0; | end_clause_after_tag = 0; | ||||
| char *p2; | char *p2; | ||||
| p2 = &buf[ix]; | p2 = &buf[ix]; | ||||
| sprintf(p2, "%s", LookupCharName(tr, c1, 1)); | |||||
| sprintf(p2, "%s", LookupCharName(tr, c1, true)); | |||||
| if (p2[0] != 0) { | if (p2[0] != 0) { | ||||
| ix += strlen(p2); | ix += strlen(p2); | ||||
| announced_punctuation = c1; | announced_punctuation = c1; | ||||
| } | } | ||||
| if (is_end_clause) { | if (is_end_clause) { | ||||
| UngetC(c_next); | |||||
| buf[ix] = ' '; | |||||
| buf[ix+1] = 0; | |||||
| TerminateBufWithSpaceAndZero(buf, ix, &c_next); | |||||
| if (iswdigit(cprev) && !IsAlpha(c_next)) // ???? | if (iswdigit(cprev) && !IsAlpha(c_next)) // ???? | ||||
| punct_data &= ~CLAUSE_DOT_AFTER_LAST_WORD; | punct_data &= ~CLAUSE_DOT_AFTER_LAST_WORD; | ||||
| // clause too long, getting near end of buffer, so break here | // 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). | // 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. | // (n_buf-4) is to allow for 3 bytes of multibyte character plus terminator. | ||||
| buf[ix] = ' '; | |||||
| buf[ix+1] = 0; | |||||
| UngetC(c2); | |||||
| TerminateBufWithSpaceAndZero(buf, ix, &c2); | |||||
| return CLAUSE_NONE; | return CLAUSE_NONE; | ||||
| } | } | ||||
| } | } | ||||
| ix += utf8_out(CHAR_EMPHASIS, &buf[ix]); | ix += utf8_out(CHAR_EMPHASIS, &buf[ix]); | ||||
| if (end_clause_after_tag) | if (end_clause_after_tag) | ||||
| RemoveChar(&buf[end_clause_index]); // delete clause-end punctiation | RemoveChar(&buf[end_clause_index]); // delete clause-end punctiation | ||||
| buf[ix] = ' '; | |||||
| buf[ix+1] = 0; | |||||
| TerminateBufWithSpaceAndZero(buf, ix, NULL); | |||||
| return CLAUSE_EOF; // end of file | return CLAUSE_EOF; // end of file | ||||
| } | } | ||||
| xmlbase = NULL; | xmlbase = NULL; | ||||
| } | } | ||||
| static void TerminateBufWithSpaceAndZero(char *buf, int index, int *ungetc) { | |||||
| buf[index] = ' '; | |||||
| buf[index+1] = 0; | |||||
| if (ungetc != NULL) { | |||||
| UngetC(*ungetc); | |||||
| } | |||||
| } | |||||
| static void DecodeWithPhonemeMode(char *buf, char *phonemes, Translator *tr, Translator *tr2, unsigned int flags[]) { | |||||
| char phonemes2[55]; | |||||
| if (tr2 == NULL) { | |||||
| SetWordStress(tr, phonemes, flags, -1, 0); | |||||
| DecodePhonemes(phonemes, phonemes2); | |||||
| sprintf(buf, "[\002%s]]", phonemes2); | |||||
| } else { | |||||
| SetWordStress(tr2, phonemes, flags, -1, 0); | |||||
| DecodePhonemes(phonemes, phonemes2); | |||||
| sprintf(buf, "[\002_^_%s %s _^_%s]]", ESPEAKNG_DEFAULT_VOICE, phonemes2, WordToString2(tr->translator_name)); | |||||
| } | |||||
| } |