static void Write4Bytes(FILE *f, int value) | static void Write4Bytes(FILE *f, int value) | ||||
{ | { | ||||
// Write 4 bytes to a file, least significant first | |||||
// Write 4 bytes to a file, least significant first | |||||
int ix; | int ix; | ||||
for (ix = 0; ix < 4; ix++) { | for (ix = 0; ix < 4; ix++) { | ||||
fclose(f_wavfile); | fclose(f_wavfile); | ||||
f_wavfile = NULL; | f_wavfile = NULL; | ||||
} | } | ||||
static int SynthCallback(short *wav, int numsamples, espeak_EVENT *events) | static int SynthCallback(short *wav, int numsamples, espeak_EVENT *events) | ||||
{ | { | ||||
char fname[210]; | char fname[210]; | ||||
if (quiet) return 0; // -q quiet mode | |||||
if (quiet) return 0; // -q quiet mode | |||||
if (wav == NULL) { | if (wav == NULL) { | ||||
CloseWavFile(); | CloseWavFile(); | ||||
sprintf(fname, "%s_%.2d%s", wavefile, wavefile_count+1, filetype); | sprintf(fname, "%s_%.2d%s", wavefile, wavefile_count+1, filetype); | ||||
if (OpenWavFile(fname, samplerate) != 0) | if (OpenWavFile(fname, samplerate) != 0) | ||||
return 1; | return 1; | ||||
} else { | |||||
if (OpenWavFile(wavefile, samplerate) != 0) | |||||
return 1; | |||||
} | |||||
} else if (OpenWavFile(wavefile, samplerate) != 0) | |||||
return 1; | |||||
} | } | ||||
if (numsamples > 0) { | if (numsamples > 0) { | ||||
}; | }; | ||||
int optind; | int optind; | ||||
static int optional_argument; | static int optional_argument; | ||||
static const char *arg_opts = "abfgklpsvw"; // which options have arguments | |||||
static const char *arg_opts = "abfgklpsvw"; // which options have arguments | |||||
static char *opt_string = ""; | static char *opt_string = ""; | ||||
#define no_argument 0 | #define no_argument 0 | ||||
#define required_argument 1 | #define required_argument 1 | ||||
FILE *f_text = NULL; | FILE *f_text = NULL; | ||||
char *p_text = NULL; | char *p_text = NULL; | ||||
FILE *f_phonemes_out = stdout; | FILE *f_phonemes_out = stdout; | ||||
char *data_path = NULL; // use default path for espeak-data | |||||
char *data_path = NULL; // use default path for espeak-data | |||||
int option_index = 0; | int option_index = 0; | ||||
int c; | int c; | ||||
espeak_VOICE voice_select; | espeak_VOICE voice_select; | ||||
char filename[200]; | char filename[200]; | ||||
char voicename[40]; | char voicename[40]; | ||||
#define N_PUNCTLIST 100 | |||||
#define N_PUNCTLIST 100 | |||||
wchar_t option_punctlist[N_PUNCTLIST]; | wchar_t option_punctlist[N_PUNCTLIST]; | ||||
voicename[0] = 0; | voicename[0] = 0; | ||||
if (c == '-') { | if (c == '-') { | ||||
if (p[0] == 0) | if (p[0] == 0) | ||||
break; // -- means don't interpret further - as commands | |||||
break; // -- means don't interpret further - as commands | |||||
opt_string = ""; | opt_string = ""; | ||||
for (ix = 0;; ix++) { | for (ix = 0;; ix++) { | ||||
c = getopt_long(argc, argv, "a:b:f:g:hk:l:mp:qs:v:w:xXz", | c = getopt_long(argc, argv, "a:b:f:g:hk:l:mp:qs:v:w:xXz", | ||||
long_options, &option_index); | long_options, &option_index); | ||||
/* Detect the end of the options. */ | |||||
// Detect the end of the options. | |||||
if (c == -1) | if (c == -1) | ||||
break; | break; | ||||
optarg2 = optarg; | optarg2 = optarg; | ||||
option_waveout = 1; | option_waveout = 1; | ||||
strncpy0(wavefile, optarg2, sizeof(filename)); | strncpy0(wavefile, optarg2, sizeof(filename)); | ||||
break; | break; | ||||
case 'z': // remove pause from the end of a sentence | |||||
case 'z': // remove pause from the end of a sentence | |||||
synth_flags &= ~espeakENDPAUSE; | synth_flags &= ~espeakENDPAUSE; | ||||
break; | break; | ||||
case 0x100: // --stdin | |||||
case 0x100: // --stdin | |||||
flag_stdin = 1; | flag_stdin = 1; | ||||
break; | break; | ||||
case 0x105: // --stdout | |||||
case 0x105: // --stdout | |||||
option_waveout = 1; | option_waveout = 1; | ||||
strcpy(wavefile, "stdout"); | strcpy(wavefile, "stdout"); | ||||
break; | break; | ||||
case 0x101: // --compile-debug | |||||
case 0x102: // --compile | |||||
case 0x101: // --compile-debug | |||||
case 0x102: // --compile | |||||
strncpy0(voicename, optarg2, sizeof(voicename)); | strncpy0(voicename, optarg2, sizeof(voicename)); | ||||
flag_compile = c; | flag_compile = c; | ||||
quiet = 1; | quiet = 1; | ||||
break; | break; | ||||
case 0x103: // --punct | |||||
case 0x103: // --punct | |||||
option_punctuation = 1; | option_punctuation = 1; | ||||
if (optarg2 != NULL) { | if (optarg2 != NULL) { | ||||
ix = 0; | ix = 0; | ||||
option_punctuation = 2; | option_punctuation = 2; | ||||
} | } | ||||
break; | break; | ||||
case 0x104: // --voices | |||||
case 0x104: // --voices | |||||
espeak_Initialize(AUDIO_OUTPUT_SYNCHRONOUS, 0, data_path, 0); | espeak_Initialize(AUDIO_OUTPUT_SYNCHRONOUS, 0, data_path, 0); | ||||
DisplayVoices(stdout, optarg2); | DisplayVoices(stdout, optarg2); | ||||
exit(0); | exit(0); | ||||
case 0x106: // -- split | |||||
case 0x106: // -- split | |||||
if (optarg2 == NULL) | if (optarg2 == NULL) | ||||
samples_split_seconds = 30 * 60; // default 30 minutes | |||||
samples_split_seconds = 30 * 60; // default 30 minutes | |||||
else | else | ||||
samples_split_seconds = atoi(optarg2) * 60; | samples_split_seconds = atoi(optarg2) * 60; | ||||
break; | break; | ||||
case 0x107: // --path | |||||
case 0x107: // --path | |||||
data_path = optarg2; | data_path = optarg2; | ||||
break; | break; | ||||
case 0x108: // --phonout | |||||
case 0x108: // --phonout | |||||
if ((f_phonemes_out = fopen(optarg2, "w")) == NULL) | if ((f_phonemes_out = fopen(optarg2, "w")) == NULL) | ||||
fprintf(stderr, "Can't write to: %s\n", optarg2); | fprintf(stderr, "Can't write to: %s\n", optarg2); | ||||
break; | break; | ||||
case 0x109: // --pho | |||||
case 0x109: // --pho | |||||
phoneme_options |= espeakPHONEMES_MBROLA; | phoneme_options |= espeakPHONEMES_MBROLA; | ||||
break; | break; | ||||
case 0x10a: // --ipa | |||||
case 0x10a: // --ipa | |||||
phoneme_options |= espeakPHONEMES_IPA; | phoneme_options |= espeakPHONEMES_IPA; | ||||
if (optarg2 != NULL) { | if (optarg2 != NULL) { | ||||
// deprecated and obsolete | // deprecated and obsolete | ||||
phoneme_options |= espeakPHONEMES_TIE; | phoneme_options |= espeakPHONEMES_TIE; | ||||
break; | break; | ||||
case 3: | case 3: | ||||
phonemes_separator = 0x200d; // ZWJ | |||||
phonemes_separator = 0x200d; // ZWJ | |||||
phoneme_options |= espeakPHONEMES_TIE; | phoneme_options |= espeakPHONEMES_TIE; | ||||
break; | break; | ||||
} | } | ||||
} | } | ||||
break; | break; | ||||
case 0x10b: // --version | |||||
case 0x10b: // --version | |||||
PrintVersion(); | PrintVersion(); | ||||
exit(0); | exit(0); | ||||
case 0x10c: // --sep | |||||
case 0x10c: // --sep | |||||
phoneme_options |= espeakPHONEMES_SHOW; | phoneme_options |= espeakPHONEMES_SHOW; | ||||
if (optarg2 == 0) | if (optarg2 == 0) | ||||
phonemes_separator = ' '; | phonemes_separator = ' '; | ||||
else | else | ||||
utf8_in(&phonemes_separator, optarg2); | utf8_in(&phonemes_separator, optarg2); | ||||
if (phonemes_separator == 'z') | if (phonemes_separator == 'z') | ||||
phonemes_separator = 0x200c; // ZWNJ | |||||
phonemes_separator = 0x200c; // ZWNJ | |||||
break; | break; | ||||
case 0x10d: // --tie | |||||
case 0x10d: // --tie | |||||
phoneme_options |= (espeakPHONEMES_SHOW | espeakPHONEMES_TIE); | phoneme_options |= (espeakPHONEMES_SHOW | espeakPHONEMES_TIE); | ||||
if (optarg2 == 0) | if (optarg2 == 0) | ||||
phonemes_separator = 0x0361; // default: combining-double-inverted-breve | |||||
phonemes_separator = 0x0361; // default: combining-double-inverted-breve | |||||
else | else | ||||
utf8_in(&phonemes_separator, optarg2); | utf8_in(&phonemes_separator, optarg2); | ||||
if (phonemes_separator == 'z') | if (phonemes_separator == 'z') | ||||
phonemes_separator = 0x200d; // ZWJ | |||||
phonemes_separator = 0x200d; // ZWJ | |||||
break; | break; | ||||
case 0x10e: // --compile-mbrola | |||||
case 0x10e: // --compile-mbrola | |||||
samplerate = espeak_Initialize(AUDIO_OUTPUT_PLAYBACK, 0, data_path, 0); | samplerate = espeak_Initialize(AUDIO_OUTPUT_PLAYBACK, 0, data_path, 0); | ||||
espeak_ng_CompileMbrolaVoice(optarg2, stdout); | espeak_ng_CompileMbrolaVoice(optarg2, stdout); | ||||
exit(0); | exit(0); | ||||
case 0x10f: // --compile-intonations | |||||
case 0x10f: // --compile-intonations | |||||
samplerate = espeak_Initialize(AUDIO_OUTPUT_PLAYBACK, 0, data_path, espeakINITIALIZE_PATH_ONLY); | samplerate = espeak_Initialize(AUDIO_OUTPUT_PLAYBACK, 0, data_path, espeakINITIALIZE_PATH_ONLY); | ||||
espeak_ng_CompileIntonation(stdout); | espeak_ng_CompileIntonation(stdout); | ||||
exit(0); | exit(0); | ||||
case 0x110: // --compile-phonemes | |||||
case 0x110: // --compile-phonemes | |||||
samplerate = espeak_Initialize(AUDIO_OUTPUT_PLAYBACK, 0, data_path, espeakINITIALIZE_PATH_ONLY); | samplerate = espeak_Initialize(AUDIO_OUTPUT_PLAYBACK, 0, data_path, espeakINITIALIZE_PATH_ONLY); | ||||
espeak_ng_CompilePhonemeData(22050, stdout); | espeak_ng_CompilePhonemeData(22050, stdout); | ||||
exit(0); | exit(0); | ||||
} | } | ||||
if (f_phonemes_out != stdout) | if (f_phonemes_out != stdout) | ||||
fclose(f_phonemes_out); // needed for WinCE | |||||
fclose(f_phonemes_out); | |||||
return 0; | return 0; | ||||
} | } |
static char letterGroupsDefined[N_LETTER_GROUPS]; | static char letterGroupsDefined[N_LETTER_GROUPS]; | ||||
MNEM_TAB mnem_rules[] = { | MNEM_TAB mnem_rules[] = { | ||||
{ "unpr", DOLLAR_UNPR }, | |||||
{ "unpr", DOLLAR_UNPR }, | |||||
{ "noprefix", DOLLAR_NOPREFIX }, // rule fails if a prefix has been removed | { "noprefix", DOLLAR_NOPREFIX }, // rule fails if a prefix has been removed | ||||
{ "list", DOLLAR_LIST }, // a pronunciation is given in the *_list file | |||||
{ "list", DOLLAR_LIST }, // a pronunciation is given in the *_list file | |||||
{ "w_alt1", 0x11 }, | { "w_alt1", 0x11 }, | ||||
{ "w_alt2", 0x12 }, | { "w_alt2", 0x12 }, | ||||
{ "w_alt4", 0x14 }, | { "w_alt4", 0x14 }, | ||||
{ "w_alt5", 0x15 }, | { "w_alt5", 0x15 }, | ||||
{ "w_alt6", 0x16 }, | { "w_alt6", 0x16 }, | ||||
{ "w_alt", 0x11 }, // note: put longer names before their sub-strings | |||||
{ "w_alt", 0x11 }, // note: put longer names before their sub-strings | |||||
{ "p_alt1", 0x21 }, | { "p_alt1", 0x21 }, | ||||
{ "p_alt2", 0x22 }, | { "p_alt2", 0x22 }, | ||||
{ "p_alt4", 0x24 }, | { "p_alt4", 0x24 }, | ||||
{ "p_alt5", 0x25 }, | { "p_alt5", 0x25 }, | ||||
{ "p_alt6", 0x26 }, | { "p_alt6", 0x26 }, | ||||
{ "p_alt", 0x21 }, | |||||
{ "p_alt", 0x21 }, | |||||
{ NULL, -1 } | { NULL, -1 } | ||||
}; | }; | ||||
MNEM_TAB mnem_flags[] = { | MNEM_TAB mnem_flags[] = { | ||||
// these in the first group put a value in bits0-3 of dictionary_flags | // these in the first group put a value in bits0-3 of dictionary_flags | ||||
{ "$1", 0x41 }, // stress on 1st syllable | |||||
{ "$2", 0x42 }, // stress on 2nd syllable | |||||
{ "$3", 0x43 }, | |||||
{ "$4", 0x44 }, | |||||
{ "$5", 0x45 }, | |||||
{ "$6", 0x46 }, | |||||
{ "$7", 0x47 }, | |||||
{ "$u", 0x48 }, // reduce to unstressed | |||||
{ "$u1", 0x49 }, | |||||
{ "$u2", 0x4a }, | |||||
{ "$u3", 0x4b }, | |||||
{ "$u+", 0x4c }, // reduce to unstressed, but stress at end of clause | |||||
{ "$1", 0x41 }, // stress on 1st syllable | |||||
{ "$2", 0x42 }, // stress on 2nd syllable | |||||
{ "$3", 0x43 }, | |||||
{ "$4", 0x44 }, | |||||
{ "$5", 0x45 }, | |||||
{ "$6", 0x46 }, | |||||
{ "$7", 0x47 }, | |||||
{ "$u", 0x48 }, // reduce to unstressed | |||||
{ "$u1", 0x49 }, | |||||
{ "$u2", 0x4a }, | |||||
{ "$u3", 0x4b }, | |||||
{ "$u+", 0x4c }, // reduce to unstressed, but stress at end of clause | |||||
{ "$u1+", 0x4d }, | { "$u1+", 0x4d }, | ||||
{ "$u2+", 0x4e }, | { "$u2+", 0x4e }, | ||||
{ "$u3+", 0x4f }, | { "$u3+", 0x4f }, | ||||
// these set the corresponding numbered bit if dictionary_flags | // these set the corresponding numbered bit if dictionary_flags | ||||
{ "$pause", 8 }, // ensure pause before this word | |||||
{ "$strend", 9 }, // full stress if at end of clause | |||||
{ "$strend2", 10 }, // full stress if at end of clause, or only followed by unstressed | |||||
{ "$unstressend", 11 }, // reduce stress at end of clause | |||||
{ "$pause", 8 }, // ensure pause before this word | |||||
{ "$strend", 9 }, // full stress if at end of clause | |||||
{ "$strend2", 10 }, // full stress if at end of clause, or only followed by unstressed | |||||
{ "$unstressend", 11 }, // reduce stress at end of clause | |||||
{ "$accent_before", 12 }, // used with accent names, say this accent name before the letter name | { "$accent_before", 12 }, // used with accent names, say this accent name before the letter name | ||||
{ "$abbrev", 13 }, // use this pronuciation rather than split into letters | |||||
// language specific | |||||
{ "$double", 14 }, // IT double the initial consonant of next word | |||||
{ "$alt", 15 }, // use alternative pronunciation | |||||
{ "$alt1", 15 }, // synonym for $alt | |||||
{ "$alt2", 16 }, | |||||
{ "$alt3", 17 }, | |||||
{ "$alt4", 18 }, | |||||
{ "$alt5", 19 }, | |||||
{ "$alt6", 20 }, | |||||
{ "$alt7", 21 }, | |||||
{ "$combine", 23 }, // Combine with the next word | |||||
{ "$dot", 24 }, // ignore '.' after this word (abbreviation) | |||||
{ "$hasdot", 25 }, // use this pronunciation if there is a dot after the word | |||||
{ "$max3", 27 }, // limit to 3 repetitions | |||||
{ "$brk", 28 }, // a shorter $pause | |||||
{ "$text", 29 }, // word translates to replcement text, not phonemes | |||||
// flags in dictionary word 2 | |||||
{ "$verbf", 0x20 }, // verb follows | |||||
{ "$verbsf", 0x21 }, // verb follows, allow -s suffix | |||||
{ "$nounf", 0x22 }, // noun follows | |||||
{ "$pastf", 0x23 }, // past tense follows | |||||
{ "$verb", 0x24 }, // use this pronunciation when its a verb | |||||
{ "$noun", 0x25 }, // use this pronunciation when its a noun | |||||
{ "$past", 0x26 }, // use this pronunciation when its past tense | |||||
{ "$abbrev", 13 }, // use this pronuciation rather than split into letters | |||||
// language specific | |||||
{ "$double", 14 }, // IT double the initial consonant of next word | |||||
{ "$alt", 15 }, // use alternative pronunciation | |||||
{ "$alt1", 15 }, // synonym for $alt | |||||
{ "$alt2", 16 }, | |||||
{ "$alt3", 17 }, | |||||
{ "$alt4", 18 }, | |||||
{ "$alt5", 19 }, | |||||
{ "$alt6", 20 }, | |||||
{ "$alt7", 21 }, | |||||
{ "$combine", 23 }, // Combine with the next word | |||||
{ "$dot", 24 }, // ignore '.' after this word (abbreviation) | |||||
{ "$hasdot", 25 }, // use this pronunciation if there is a dot after the word | |||||
{ "$max3", 27 }, // limit to 3 repetitions | |||||
{ "$brk", 28 }, // a shorter $pause | |||||
{ "$text", 29 }, // word translates to replcement text, not phonemes | |||||
// flags in dictionary word 2 | |||||
{ "$verbf", 0x20 }, // verb follows | |||||
{ "$verbsf", 0x21 }, // verb follows, allow -s suffix | |||||
{ "$nounf", 0x22 }, // noun follows | |||||
{ "$pastf", 0x23 }, // past tense follows | |||||
{ "$verb", 0x24 }, // use this pronunciation when its a verb | |||||
{ "$noun", 0x25 }, // use this pronunciation when its a noun | |||||
{ "$past", 0x26 }, // use this pronunciation when its past tense | |||||
{ "$verbextend", 0x28 }, // extend influence of 'verb follows' | { "$verbextend", 0x28 }, // extend influence of 'verb follows' | ||||
{ "$capital", 0x29 }, // use this pronunciation if initial letter is upper case | |||||
{ "$allcaps", 0x2a }, // use this pronunciation if initial letter is upper case | |||||
{ "$accent", 0x2b }, // character name is base-character name + accent name | |||||
{ "$sentence", 0x2d }, // only if this clause is a sentence (i.e. terminator is {. ? !} not {, ; :} | |||||
{ "$only", 0x2e }, // only match on this word without suffix | |||||
{ "$onlys", 0x2f }, // only match with none, or with 's' suffix | |||||
{ "$stem", 0x30 }, // must have a suffix | |||||
{ "$atend", 0x31 }, // use this pronunciation if at end of clause | |||||
{ "$atstart", 0x32 }, // use this pronunciation at start of clause | |||||
{ "$native", 0x33 }, // not if we've switched translators | |||||
{ "$capital", 0x29 }, // use this pronunciation if initial letter is upper case | |||||
{ "$allcaps", 0x2a }, // use this pronunciation if initial letter is upper case | |||||
{ "$accent", 0x2b }, // character name is base-character name + accent name | |||||
{ "$sentence", 0x2d }, // only if this clause is a sentence (i.e. terminator is {. ? !} not {, ; :} | |||||
{ "$only", 0x2e }, // only match on this word without suffix | |||||
{ "$onlys", 0x2f }, // only match with none, or with 's' suffix | |||||
{ "$stem", 0x30 }, // must have a suffix | |||||
{ "$atend", 0x31 }, // use this pronunciation if at end of clause | |||||
{ "$atstart", 0x32 }, // use this pronunciation at start of clause | |||||
{ "$native", 0x33 }, // not if we've switched translators | |||||
// doesn't set dictionary_flags | // doesn't set dictionary_flags | ||||
{ "$?", 100 }, // conditional rule, followed by byte giving the condition number | |||||
{ "$?", 100 }, // conditional rule, followed by byte giving the condition number | |||||
{ "$textmode", 200 }, | |||||
{ "$textmode", 200 }, | |||||
{ "$phonememode", 201 }, | { "$phonememode", 201 }, | ||||
{ NULL, -1 } | |||||
{ NULL, -1 } | |||||
}; | }; | ||||
#define LEN_GROUP_NAME 12 | #define LEN_GROUP_NAME 12 | ||||
int isspace2(unsigned int c) | int isspace2(unsigned int c) | ||||
{ | { | ||||
// can't use isspace() because on Windows, isspace(0xe1) gives TRUE ! | |||||
// can't use isspace() because on Windows, isspace(0xe1) gives TRUE ! | |||||
int c2; | int c2; | ||||
if (((c2 = (c & 0xff)) == 0) || (c > ' ')) | if (((c2 = (c & 0xff)) == 0) || (c > ' ')) | ||||
static FILE *fopen_log(const char *fname, const char *access) | static FILE *fopen_log(const char *fname, const char *access) | ||||
{ | { | ||||
// performs fopen, but produces error message to f_log if it fails | |||||
// performs fopen, but produces error message to f_log if it fails | |||||
FILE *f; | FILE *f; | ||||
if ((f = fopen(fname, access)) == NULL) { | if ((f = fopen(fname, access)) == NULL) { | ||||
return f; | return f; | ||||
} | } | ||||
/* Lookup a mnemonic string in a table, return its name */ | |||||
// Lookup a mnemonic string in a table, return its name | |||||
const char *LookupMnemName(MNEM_TAB *table, const int value) | const char *LookupMnemName(MNEM_TAB *table, const int value) | ||||
{ | { | ||||
while (table->mnem != NULL) { | while (table->mnem != NULL) { | ||||
return table->mnem; | return table->mnem; | ||||
table++; | table++; | ||||
} | } | ||||
return ""; /* not found */ | |||||
return ""; // not found | |||||
} | } | ||||
void print_dictionary_flags(unsigned int *flags, char *buf, int buf_len) | void print_dictionary_flags(unsigned int *flags, char *buf, int buf_len) | ||||
char *DecodeRule(const char *group_chars, int group_length, char *rule, int control) | char *DecodeRule(const char *group_chars, int group_length, char *rule, int control) | ||||
{ | { | ||||
/* Convert compiled match template to ascii */ | |||||
// Convert compiled match template to ascii | |||||
unsigned char rb; | unsigned char rb; | ||||
unsigned char c; | unsigned char c; | ||||
char suffix[20]; | char suffix[20]; | ||||
static char output[80]; | static char output[80]; | ||||
static char symbols[] = | |||||
{ ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', | |||||
'&', '%', '+', '#', 'S', 'D', 'Z', 'A', 'L', '!', ' ', '@', '?', 'J', 'N', 'K', 'V', '?', 'T', 'X', '?', 'W' }; | |||||
static char symbols[] = { | |||||
' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', | |||||
'&', '%', '+', '#', 'S', 'D', 'Z', 'A', 'L', '!', | |||||
' ', '@', '?', 'J', 'N', 'K', 'V', '?', 'T', 'X', | |||||
'?', 'W' | |||||
}; | |||||
static char symbols_lg[] = { 'A', 'B', 'C', 'H', 'F', 'G', 'Y' }; | static char symbols_lg[] = { 'A', 'B', 'C', 'H', 'F', 'G', 'Y' }; | ||||
finished = 1; | finished = 1; | ||||
break; | break; | ||||
case RULE_PRE_ATSTART: | case RULE_PRE_ATSTART: | ||||
at_start = 1; // drop through to next case | |||||
at_start = 1; | |||||
// fallthrough: | |||||
case RULE_PRE: | case RULE_PRE: | ||||
match_type = RULE_PRE; | match_type = RULE_PRE; | ||||
*p = 0; | *p = 0; | ||||
case RULE_PH_COMMON: | case RULE_PH_COMMON: | ||||
break; | break; | ||||
case RULE_CONDITION: | case RULE_CONDITION: | ||||
/* conditional rule, next byte gives condition number */ | |||||
// conditional rule, next byte gives condition number | |||||
condition_num = *rule++; | condition_num = *rule++; | ||||
break; | break; | ||||
case RULE_LINENUM: | case RULE_LINENUM: | ||||
} | } | ||||
*p = 0; | *p = 0; | ||||
buf[p_end - p] = 0; // prevent overflow in output[] | |||||
buf[p_end - p] = 0; // prevent overflow in output[] | |||||
strcat(p, buf); | strcat(p, buf); | ||||
ix = strlen(output); | ix = strlen(output); | ||||
while (ix < 8) | while (ix < 8) | ||||
static int compile_line(char *linebuf, char *dict_line, int *hash) | static int compile_line(char *linebuf, char *dict_line, int *hash) | ||||
{ | { | ||||
// Compile a line in the language_list file | |||||
// Compile a line in the language_list file | |||||
unsigned char c; | unsigned char c; | ||||
char *p; | char *p; | ||||
char *word; | char *word; | ||||
int len_word; | int len_word; | ||||
int len_phonetic; | int len_phonetic; | ||||
int text_not_phonemes; // this word specifies replacement text, not phonemes | |||||
int text_not_phonemes; // this word specifies replacement text, not phonemes | |||||
unsigned int wc; | unsigned int wc; | ||||
int all_upper_case; | int all_upper_case; | ||||
} | } | ||||
if ((c == '$') && isalnum(p[1])) { | if ((c == '$') && isalnum(p[1])) { | ||||
/* read keyword parameter */ | |||||
// read keyword parameter | |||||
mnemptr = p; | mnemptr = p; | ||||
while (!isspace2(c = *p)) p++; | while (!isspace2(c = *p)) p++; | ||||
*p = 0; | *p = 0; | ||||
} | } | ||||
} | } | ||||
if ((c == '/') && (p[1] == '/') && (multiple_words == 0)) { | |||||
c = '\n'; /* "//" treat comment as end of line */ | |||||
} | |||||
if ((c == '/') && (p[1] == '/') && (multiple_words == 0)) | |||||
c = '\n'; // "//" treat comment as end of line | |||||
switch (step) | switch (step) | ||||
{ | { | ||||
c = ' '; | c = ' '; | ||||
} | } | ||||
if (isspace2(c)) { | if (isspace2(c)) { | ||||
p[0] = 0; /* terminate english word */ | |||||
p[0] = 0; // terminate english word | |||||
if (multiple_words) { | if (multiple_words) { | ||||
multiple_string = multiple_string_end = p+1; | multiple_string = multiple_string_end = p+1; | ||||
if (isspace2(c)) | if (isspace2(c)) | ||||
multiple_words++; | multiple_words++; | ||||
else if (c == ')') { | else if (c == ')') { | ||||
p[0] = ' '; // terminate extra string | |||||
p[0] = ' '; // terminate extra string | |||||
multiple_string_end = p+1; | multiple_string_end = p+1; | ||||
step = 3; | step = 3; | ||||
} | } | ||||
break; | break; | ||||
case 4: | case 4: | ||||
if (isspace2(c)) { | if (isspace2(c)) { | ||||
p[0] = 0; /* terminate phonetic */ | |||||
p[0] = 0; // terminate phonetic | |||||
step = 5; | step = 5; | ||||
} | } | ||||
break; | break; | ||||
} | } | ||||
if (word[0] == 0) | if (word[0] == 0) | ||||
return 0; /* blank line */ | |||||
return 0; // blank line | |||||
if (text_mode) | if (text_mode) | ||||
text_not_phonemes = 1; | text_not_phonemes = 1; | ||||
if (text_not_phonemes) { | if (text_not_phonemes) { | ||||
if (word[0] == '_') { | if (word[0] == '_') { | ||||
// This is a special word, used by eSpeak. Translate this into phonemes now | // This is a special word, used by eSpeak. Translate this into phonemes now | ||||
strcat(phonetic, " "); // need a space to indicate word-boundary | |||||
strcat(phonetic, " "); // need a space to indicate word-boundary | |||||
// PROBLEM vowel reductions are not applied to the translated phonemes | // PROBLEM vowel reductions are not applied to the translated phonemes | ||||
// condition rules are not applied | // condition rules are not applied | ||||
if (text_not_phonemes != translator->langopts.textmode) | if (text_not_phonemes != translator->langopts.textmode) | ||||
flag_codes[n_flag_codes++] = BITNUM_FLAG_TEXTMODE; | flag_codes[n_flag_codes++] = BITNUM_FLAG_TEXTMODE; | ||||
if (sscanf(word, "U+%x", &wc) == 1) { | if (sscanf(word, "U+%x", &wc) == 1) { | ||||
// Character code | // Character code | ||||
ix = utf8_out(wc, word); | ix = utf8_out(wc, word); | ||||
*hash = HashDictionary(word); | *hash = HashDictionary(word); | ||||
len_phonetic = strlen(encoded_ph); | len_phonetic = strlen(encoded_ph); | ||||
dict_line[1] = len_word; // bit 6 indicates whether the word has been compressed | |||||
dict_line[1] = len_word; // bit 6 indicates whether the word has been compressed | |||||
len_word &= 0x3f; | len_word &= 0x3f; | ||||
memcpy(&dict_line[2], word, len_word); | memcpy(&dict_line[2], word, len_word); | ||||
static void compile_dictlist_start(void) | static void compile_dictlist_start(void) | ||||
{ | { | ||||
// initialise dictionary list | |||||
// initialise dictionary list | |||||
int ix; | int ix; | ||||
char *p; | char *p; | ||||
char *p2; | char *p2; | ||||
static void compile_dictlist_end(FILE *f_out) | static void compile_dictlist_end(FILE *f_out) | ||||
{ | { | ||||
// Write out the compiled dictionary list | |||||
// Write out the compiled dictionary list | |||||
int hash; | int hash; | ||||
int length; | int length; | ||||
char *p; | char *p; | ||||
linenum++; | linenum++; | ||||
length = compile_line(buf, dict_line, &hash); | length = compile_line(buf, dict_line, &hash); | ||||
if (length == 0) continue; /* blank line */ | |||||
if (length == 0) continue; // blank line | |||||
hash_counts[hash]++; | hash_counts[hash]++; | ||||
static char group_name[LEN_GROUP_NAME+1]; | static char group_name[LEN_GROUP_NAME+1]; | ||||
static int group3_ix; | static int group3_ix; | ||||
#define N_RULES 3000 // max rules for each group | |||||
#define N_RULES 3000 // max rules for each group | |||||
int isHexDigit(int c) | int isHexDigit(int c) | ||||
{ | { | ||||
static void copy_rule_string(char *string, int *state_out) | static void copy_rule_string(char *string, int *state_out) | ||||
{ | { | ||||
// state 0: conditional, 1=pre, 2=match, 3=post, 4=phonemes | |||||
// state 0: conditional, 1=pre, 2=match, 3=post, 4=phonemes | |||||
static char *outbuf[5] = { rule_cond, rule_pre, rule_match, rule_post, rule_phonemes }; | static char *outbuf[5] = { rule_cond, rule_pre, rule_match, rule_post, rule_phonemes }; | ||||
static int next_state[5] = { 2, 2, 4, 4, 4 }; | static int next_state[5] = { 2, 2, 4, 4, 4 }; | ||||
char *output; | char *output; | ||||
rule_phonemes[len++] = ' '; | rule_phonemes[len++] = ' '; | ||||
output = &rule_phonemes[len]; | output = &rule_phonemes[len]; | ||||
} | } | ||||
sxflags = 0x808000; // to ensure non-zero bytes | |||||
sxflags = 0x808000; // to ensure non-zero bytes | |||||
for (p = string, ix = 0;;) { | for (p = string, ix = 0;;) { | ||||
literal = 0; | literal = 0; | ||||
p += 2; | p += 2; | ||||
} | } | ||||
if (c == '\\') { | if (c == '\\') { | ||||
c = *p++; // treat next character literally | |||||
c = *p++; // treat next character literally | |||||
if ((c >= '0') && (c <= '3') && (p[0] >= '0') && (p[0] <= '7') && (p[1] >= '0') && (p[1] <= '7')) { | if ((c >= '0') && (c <= '3') && (p[0] >= '0') && (p[0] <= '7') && (p[1] >= '0') && (p[1] <= '7')) { | ||||
// character code given by 3 digit octal value; | // character code given by 3 digit octal value; | ||||
c = (c-'0')*64 + (p[0]-'0')*8 + (p[1]-'0'); | c = (c-'0')*64 + (p[0]-'0')*8 + (p[1]-'0'); | ||||
break; | break; | ||||
case 'Y': | case 'Y': | ||||
c = 'I'; // drop through to next case | |||||
case 'A': // vowel | |||||
c = 'I'; | |||||
// fallthrough: | |||||
case 'A': // vowel | |||||
case 'B': | case 'B': | ||||
case 'C': | case 'C': | ||||
case 'H': | case 'H': | ||||
error_count++; | error_count++; | ||||
} | } | ||||
break; | break; | ||||
case 'P': | |||||
sxflags |= SUFX_P; // Prefix, now drop through to Suffix | |||||
case 'P': // Prefix | |||||
sxflags |= SUFX_P; | |||||
// fallthrough | // fallthrough | ||||
case 'S': | |||||
case 'S': // Suffix | |||||
output[ix++] = RULE_ENDING; | output[ix++] = RULE_ENDING; | ||||
value = 0; | value = 0; | ||||
while (!isspace2(c = *p++) && (c != 0)) { | while (!isspace2(c = *p++) && (c != 0)) { | ||||
case 'i': | case 'i': | ||||
sxflags |= SUFX_I; | sxflags |= SUFX_I; | ||||
break; | break; | ||||
case 'p': // obsolete, replaced by 'P' above | |||||
case 'p': // obsolete, replaced by 'P' above | |||||
sxflags |= SUFX_P; | sxflags |= SUFX_P; | ||||
break; | break; | ||||
case 'v': | case 'v': | ||||
switch (c = input[ix]) | switch (c = input[ix]) | ||||
{ | { | ||||
case ')': // end of prefix section | |||||
case ')': // end of prefix section | |||||
*p = 0; | *p = 0; | ||||
state = 1; | state = 1; | ||||
copy_rule_string(buf, &state); | copy_rule_string(buf, &state); | ||||
p = buf; | p = buf; | ||||
break; | break; | ||||
case '(': // start of suffix section | |||||
case '(': // start of suffix section | |||||
*p = 0; | *p = 0; | ||||
state = 2; | state = 2; | ||||
copy_rule_string(buf, &state); | copy_rule_string(buf, &state); | ||||
error_count++; | error_count++; | ||||
} | } | ||||
break; | break; | ||||
case '\n': // end of line | |||||
case '\n': // end of line | |||||
case '\r': | case '\r': | ||||
case 0: // end of line | |||||
case 0: // end of line | |||||
*p = 0; | *p = 0; | ||||
copy_rule_string(buf, &state); | copy_rule_string(buf, &state); | ||||
finish = 1; | finish = 1; | ||||
break; | break; | ||||
case '\t': // end of section section | |||||
case '\t': // end of section section | |||||
case ' ': | case ' ': | ||||
*p = 0; | *p = 0; | ||||
copy_rule_string(buf, &state); | copy_rule_string(buf, &state); | ||||
static int __cdecl rgroup_sorter(RGROUP *a, RGROUP *b) | static int __cdecl rgroup_sorter(RGROUP *a, RGROUP *b) | ||||
{ | { | ||||
// Sort long names before short names | |||||
// Sort long names before short names | |||||
int ix; | int ix; | ||||
ix = strlen(b->name) - strlen(a->name); | ix = strlen(b->name) - strlen(a->name); | ||||
if (ix != 0) return ix; | if (ix != 0) return ix; | ||||
qsort((void *)rules, n_rules, sizeof(char *), (int(__cdecl *)(const void *, const void *))string_sorter); | qsort((void *)rules, n_rules, sizeof(char *), (int(__cdecl *)(const void *, const void *))string_sorter); | ||||
if (strcmp(name, "9") == 0) | if (strcmp(name, "9") == 0) | ||||
len_name = 0; // don't remove characters from numeric match strings | |||||
len_name = 0; // don't remove characters from numeric match strings | |||||
for (ix = 0; ix < n_rules; ix++) { | for (ix = 0; ix < n_rules; ix++) { | ||||
p = rules[ix]; | p = rules[ix]; | ||||
len1 = strlen(p) + 1; // phoneme string | |||||
len1 = strlen(p) + 1; // phoneme string | |||||
p3 = &p[len1]; | p3 = &p[len1]; | ||||
p2 = p3 + len_name; // remove group name from start of match string | |||||
p2 = p3 + len_name; // remove group name from start of match string | |||||
len2 = strlen(p2); | len2 = strlen(p2); | ||||
nextchar_count[(unsigned char)(p2[0])]++; // the next byte after the group name | |||||
nextchar_count[(unsigned char)(p2[0])]++; // the next byte after the group name | |||||
if ((common[0] != 0) && (strcmp(p, common) == 0)) { | if ((common[0] != 0) && (strcmp(p, common) == 0)) { | ||||
fwrite(p2, len2, 1, f_out); | fwrite(p2, len2, 1, f_out); | ||||
fputc(0, f_out); // no phoneme string, it's the same as previous rule | |||||
fputc(0, f_out); // no phoneme string, it's the same as previous rule | |||||
} else { | } else { | ||||
if ((ix < n_rules-1) && (strcmp(p, rules[ix+1]) == 0)) { | if ((ix < n_rules-1) && (strcmp(p, rules[ix+1]) == 0)) { | ||||
common = rules[ix]; // phoneme string is same as next, set as common | |||||
common = rules[ix]; // phoneme string is same as next, set as common | |||||
fputc(RULE_PH_COMMON, f_out); | fputc(RULE_PH_COMMON, f_out); | ||||
} | } | ||||
int length; | int length; | ||||
int max_length = 0; | int max_length = 0; | ||||
#define N_LETTERGP_ITEMS 200 | |||||
#define N_LETTERGP_ITEMS 200 | |||||
char *items[N_LETTERGP_ITEMS]; | char *items[N_LETTERGP_ITEMS]; | ||||
char item_length[N_LETTERGP_ITEMS]; | char item_length[N_LETTERGP_ITEMS]; | ||||
items[n_items] = p_start = p; | items[n_items] = p_start = p; | ||||
while ((*p & 0xff) > ' ') { | while ((*p & 0xff) > ' ') { | ||||
if (*p == '_') *p = ' '; // allow '_' for word break | |||||
if (*p == '_') *p = ' '; // allow '_' for word break | |||||
p++; | p++; | ||||
} | } | ||||
*p++ = 0; | *p++ = 0; | ||||
// write out the items, longest first | // write out the items, longest first | ||||
while (max_length > 1) { | while (max_length > 1) { | ||||
for (ix = 0; ix < n_items; ix++) { | for (ix = 0; ix < n_items; ix++) { | ||||
if (item_length[ix] == max_length) { | |||||
if (item_length[ix] == max_length) | |||||
fwrite(items[ix], 1, max_length, f_out); | fwrite(items[ix], 1, max_length, f_out); | ||||
} | |||||
} | } | ||||
max_length--; | max_length--; | ||||
} | } | ||||
if ((p = (unsigned char *)strstr(buf, "//")) != NULL) | if ((p = (unsigned char *)strstr(buf, "//")) != NULL) | ||||
*p = 0; | *p = 0; | ||||
if (buf[0] == '\r') buf++; // ignore extra \r in \r\n | |||||
if (buf[0] == '\r') buf++; // ignore extra \r in \r\n | |||||
} | } | ||||
if ((buf == NULL) || (buf[0] == '.')) { | if ((buf == NULL) || (buf[0] == '.')) { | ||||
if (compile_mode == 2) { | if (compile_mode == 2) { | ||||
// end of the character replacements section | // end of the character replacements section | ||||
fwrite(&n_rules, 1, 4, f_out); // write a zero word to terminate the replacemenmt list | |||||
fwrite(&n_rules, 1, 4, f_out); // write a zero word to terminate the replacemenmt list | |||||
compile_mode = 0; | compile_mode = 0; | ||||
} | } | ||||
if (buf == NULL) break; // end of file | |||||
if (buf == NULL) break; // end of file | |||||
if (memcmp(buf, ".L", 2) == 0) { | if (memcmp(buf, ".L", 2) == 0) { | ||||
compile_lettergroup(&buf[2], f_out); | compile_lettergroup(&buf[2], f_out); | ||||
compile_mode = 1; | compile_mode = 1; | ||||
p = (unsigned char *)&buf[6]; | p = (unsigned char *)&buf[6]; | ||||
while ((p[0] == ' ') || (p[0] == '\t')) p++; // Note: Windows isspace(0xe1) gives TRUE ! | |||||
while ((p[0] == ' ') || (p[0] == '\t')) p++; // Note: Windows isspace(0xe1) gives TRUE ! | |||||
ix = 0; | ix = 0; | ||||
while ((*p > ' ') && (ix < LEN_GROUP_NAME)) | while ((*p > ' ') && (ix < LEN_GROUP_NAME)) | ||||
group_name[ix++] = *p++; | group_name[ix++] = *p++; | ||||
if (translator->letter_bits_offset > 0) { | if (translator->letter_bits_offset > 0) { | ||||
utf8_in(&wc, group_name); | utf8_in(&wc, group_name); | ||||
if (((ix = (wc - translator->letter_bits_offset)) >= 0) && (ix < 128)) | if (((ix = (wc - translator->letter_bits_offset)) >= 0) && (ix < 128)) | ||||
group3_ix = ix+1; // not zero | |||||
group3_ix = ix+1; // not zero | |||||
} | } | ||||
} | } | ||||
switch (compile_mode) | switch (compile_mode) | ||||
{ | { | ||||
case 1: // .group | |||||
case 1: // .group | |||||
prule = compile_rule(buf); | prule = compile_rule(buf); | ||||
if (prule != NULL) { | if (prule != NULL) { | ||||
if (n_rules < N_RULES) | if (n_rules < N_RULES) | ||||
} | } | ||||
break; | break; | ||||
case 2: // .replace | |||||
case 2: // .replace | |||||
{ | { | ||||
int replace1; | int replace1; | ||||
int replace2; | int replace2; | ||||
ix += 16; | ix += 16; | ||||
} | } | ||||
if (replace1 != 0) { | if (replace1 != 0) { | ||||
Write4Bytes(f_out, replace1); // write as little-endian | |||||
Write4Bytes(f_out, replace2); // if big-endian, reverse the bytes in LoadDictionary() | |||||
Write4Bytes(f_out, replace1); // write as little-endian | |||||
Write4Bytes(f_out, replace2); // if big-endian, reverse the bytes in LoadDictionary() | |||||
} | } | ||||
} | } | ||||
break; | |||||
break; | |||||
} | } | ||||
} | } | ||||
fclose(f_temp); | fclose(f_temp); | ||||
int CompileDictionary(const char *dsource, const char *dict_name, FILE *log, char *fname_err, int flags) | int CompileDictionary(const char *dsource, const char *dict_name, FILE *log, char *fname_err, int flags) | ||||
{ | { | ||||
// fname: space to write the filename in case of error | |||||
// flags: bit 0: include source line number information, for debug purposes. | |||||
// fname: space to write the filename in case of error | |||||
// flags: bit 0: include source line number information, for debug purposes. | |||||
FILE *f_in; | FILE *f_in; | ||||
FILE *f_out; | FILE *f_out; |
char name2[40]; | char name2[40]; | ||||
char mbrola_voice[40]; | char mbrola_voice[40]; | ||||
char buf[sizeof(path_home)+30]; | char buf[sizeof(path_home)+30]; | ||||
int mbrola_ctrl = 20; // volume in 1/16 ths | |||||
int mbrola_ctrl = 20; // volume in 1/16 ths | |||||
MBROLA_TAB data[N_PHONEME_TAB]; | MBROLA_TAB data[N_PHONEME_TAB]; | ||||
strcpy(buf, filepath); | strcpy(buf, filepath); | ||||
buf[sizeof(phoneme)-1] = 0; | buf[sizeof(phoneme)-1] = 0; | ||||
if ((p = strstr(buf, "//")) != NULL) | if ((p = strstr(buf, "//")) != NULL) | ||||
*p = 0; // truncate line at comment | |||||
*p = 0; // truncate line at comment | |||||
if (memcmp(buf, "volume", 6) == 0) { | if (memcmp(buf, "volume", 6) == 0) { | ||||
mbrola_ctrl = atoi(&buf[6]); | mbrola_ctrl = atoi(&buf[6]); | ||||
return ENE_WRITE_ERROR; | return ENE_WRITE_ERROR; | ||||
} | } | ||||
data[count].name = 0; // list terminator | |||||
data[count].name = 0; // list terminator | |||||
Write4Bytes(f_out, mbrola_ctrl); | Write4Bytes(f_out, mbrola_ctrl); | ||||
pw_end = (int *)(&data[count+1]); | pw_end = (int *)(&data[count+1]); |
typedef enum { | typedef enum { | ||||
CS_UNDEFINED, // The command has just been created | CS_UNDEFINED, // The command has just been created | ||||
CS_PENDING, // stored in the fifo | |||||
CS_PROCESSED // processed | |||||
CS_PENDING, // stored in the fifo | |||||
CS_PROCESSED // processed | |||||
} t_command_state; | } t_command_state; | ||||
typedef struct { | typedef struct { |
static t_espeak_callback *my_callback = NULL; | static t_espeak_callback *my_callback = NULL; | ||||
static int my_event_is_running = 0; | static int my_event_is_running = 0; | ||||
enum { MIN_TIMEOUT_IN_MS = 10, | |||||
ACTIVITY_TIMEOUT = 50, // in ms, check that the stream is active | |||||
MAX_ACTIVITY_CHECK = 6 }; | |||||
enum { | |||||
MIN_TIMEOUT_IN_MS = 10, | |||||
ACTIVITY_TIMEOUT = 50, // in ms, check that the stream is active | |||||
MAX_ACTIVITY_CHECK = 6 | |||||
}; | |||||
typedef struct t_node { | typedef struct t_node { | ||||
void *data; | void *data; | ||||
for (i = 0; i < MAX_ACTIVITY_CHECK && (*stop_is_required == 0); i++) { | for (i = 0; i < MAX_ACTIVITY_CHECK && (*stop_is_required == 0); i++) { | ||||
err = wave_get_remaining_time(sample, time_in_ms); | err = wave_get_remaining_time(sample, time_in_ms); | ||||
if (err || wave_is_busy(NULL) || (*time_in_ms == 0)) { // if err, stream not available: quit | |||||
// if wave is busy, time_in_ms is known: quit | |||||
// if wave is not busy but remaining time == 0, event is reached: quit | |||||
if (err || // if err, stream not available: quit | |||||
wave_is_busy(NULL) || // if wave is busy, time_in_ms is known: quit | |||||
(*time_in_ms == 0)) { // if wave is not busy but remaining time == 0, event is reached: quit | |||||
break; | break; | ||||
} | } | ||||
SHOW_TIME("polling_thread > unlocked\n"); | SHOW_TIME("polling_thread > unlocked\n"); | ||||
a_stop_is_required = 0; | a_stop_is_required = 0; | ||||
a_status = sem_getvalue(&my_sem_stop_is_required, &a_stop_is_required); // NOTE: may set a_stop_is_required to -1 | |||||
a_status = sem_getvalue(&my_sem_stop_is_required, &a_stop_is_required); // NOTE: may set a_stop_is_required to -1 | |||||
if ((a_status == 0) && (a_stop_is_required > 0)) { | if ((a_status == 0) && (a_stop_is_required > 0)) { | ||||
SHOW("polling_thread > stop required (%d)\n", __LINE__); | SHOW("polling_thread > stop required (%d)\n", __LINE__); | ||||
while (0 == sem_trywait(&my_sem_stop_is_required)) { | |||||
} | |||||
; | |||||
while (0 == sem_trywait(&my_sem_stop_is_required)) | |||||
; | |||||
} else | } else | ||||
a_stop_is_required = 0; | a_stop_is_required = 0; | ||||
// In this loop, my_event_is_running = 1 | // In this loop, my_event_is_running = 1 | ||||
while (head && (a_stop_is_required <= 0)) { | while (head && (a_stop_is_required <= 0)) { | ||||
SHOW_TIME("polling_thread > check head\n"); | SHOW_TIME("polling_thread > check head\n"); | ||||
while (0 == sem_trywait(&my_sem_start_is_required)) { | |||||
} | |||||
while (0 == sem_trywait(&my_sem_start_is_required)) | |||||
; | |||||
espeak_EVENT *event = (espeak_EVENT *)(head->data); | espeak_EVENT *event = (espeak_EVENT *)(head->data); | ||||
assert(event); | assert(event); | ||||
if ((a_status == 0) && (a_stop_is_required > 0)) { | if ((a_status == 0) && (a_stop_is_required > 0)) { | ||||
SHOW("polling_thread > stop required (%d)\n", __LINE__); | SHOW("polling_thread > stop required (%d)\n", __LINE__); | ||||
while (0 == sem_trywait(&my_sem_stop_is_required)) { | |||||
} | |||||
while (0 == sem_trywait(&my_sem_stop_is_required)) | |||||
; | |||||
} else | } else | ||||
a_stop_is_required = 0; | a_stop_is_required = 0; | ||||
} else // The event will be notified soon: sleep until timeout or stop request | } else // The event will be notified soon: sleep until timeout or stop request | ||||
a_status = sem_getvalue(&my_sem_stop_is_required, &a_stop_is_required); | a_status = sem_getvalue(&my_sem_stop_is_required, &a_stop_is_required); | ||||
if ((a_status == 0) && (a_stop_is_required > 0)) { | if ((a_status == 0) && (a_stop_is_required > 0)) { | ||||
SHOW("polling_thread > stop required (%d)\n", __LINE__); | SHOW("polling_thread > stop required (%d)\n", __LINE__); | ||||
while (0 == sem_trywait(&my_sem_stop_is_required)) { | |||||
} | |||||
while (0 == sem_trywait(&my_sem_stop_is_required)) | |||||
; | |||||
} else | } else | ||||
a_stop_is_required = 0; | a_stop_is_required = 0; | ||||
} | } | ||||
{ | { | ||||
ENTER("event > init"); | ENTER("event > init"); | ||||
while (event_delete((espeak_EVENT *)pop())) { | |||||
} | |||||
while (event_delete((espeak_EVENT *)pop())) | |||||
; | |||||
node_counter = 0; | node_counter = 0; | ||||
} | } |
static pthread_mutex_t my_mutex; | static pthread_mutex_t my_mutex; | ||||
static int my_command_is_running = 0; | static int my_command_is_running = 0; | ||||
static int my_stop_is_required = 0; | static int my_stop_is_required = 0; | ||||
// + fifo | |||||
// | |||||
// my_thread: reads commands from the fifo, and runs them. | // my_thread: reads commands from the fifo, and runs them. | ||||
static pthread_t my_thread; | static pthread_t my_thread; | ||||
static t_espeak_command *pop(); | static t_espeak_command *pop(); | ||||
static void init(int process_parameters); | static void init(int process_parameters); | ||||
static int node_counter = 0; | static int node_counter = 0; | ||||
enum { MAX_NODE_COUNTER = 400, | |||||
INACTIVITY_TIMEOUT = 50, // in ms, check that the stream is inactive | |||||
MAX_INACTIVITY_CHECK = 2 }; | |||||
enum { | |||||
MAX_NODE_COUNTER = 400, | |||||
INACTIVITY_TIMEOUT = 50, // in ms, check that the stream is inactive | |||||
MAX_INACTIVITY_CHECK = 2 | |||||
}; | |||||
void fifo_init() | void fifo_init() | ||||
{ | { | ||||
display_espeak_command(a_command); | display_espeak_command(a_command); | ||||
// purge start semaphore | // purge start semaphore | ||||
SHOW_TIME("say_thread > purge my_sem_start_is_required\n"); | SHOW_TIME("say_thread > purge my_sem_start_is_required\n"); | ||||
while (0 == sem_trywait(&my_sem_start_is_required)) { | |||||
} | |||||
while (0 == sem_trywait(&my_sem_start_is_required)) | |||||
; | |||||
if (my_stop_is_required) { | if (my_stop_is_required) { | ||||
SHOW_TIME("say_thread > my_command_is_running = 0\n"); | SHOW_TIME("say_thread > my_command_is_running = 0\n"); | ||||
// purge start semaphore | // purge start semaphore | ||||
SHOW_TIME("say_thread > purge my_sem_start_is_required\n"); | SHOW_TIME("say_thread > purge my_sem_start_is_required\n"); | ||||
while (0 == sem_trywait(&my_sem_start_is_required)) { | |||||
} | |||||
while (0 == sem_trywait(&my_sem_start_is_required)) | |||||
; | |||||
// acknowledge the stop request | // acknowledge the stop request | ||||
SHOW_TIME("say_thread > post my_sem_stop_is_acknowledged\n"); | SHOW_TIME("say_thread > post my_sem_stop_is_acknowledged\n"); |
typedef struct { | typedef struct { | ||||
char stress; | char stress; | ||||
char env; | char env; | ||||
char flags; // bit 0=pitch rising, bit1=emnphasized, bit2=end of clause | |||||
char flags; // bit 0=pitch rising, bit1=emnphasized, bit2=end of clause | |||||
char nextph_type; | char nextph_type; | ||||
unsigned char pitch1; | unsigned char pitch1; | ||||
unsigned char pitch2; | unsigned char pitch2; | ||||
static SYLLABLE *syllable_tab; | static SYLLABLE *syllable_tab; | ||||
static int tone_pitch_env; /* used to return pitch envelope */ | |||||
static int tone_pitch_env; // used to return pitch envelope | |||||
/* Pitch data for tone types */ | /* Pitch data for tone types */ | ||||
/*****************************/ | /*****************************/ | ||||
#define PITCHfall 0 | |||||
#define PITCHrise 2 | |||||
#define PITCHfrise 4 // and 3 must be for the variant preceded by 'r' | |||||
#define PITCHfrise2 6 // and 5 must be the 'r' variant | |||||
#define PITCHrisefall 8 | |||||
#define PITCHfall 0 | |||||
#define PITCHrise 2 | |||||
#define PITCHfrise 4 // and 3 must be for the variant preceded by 'r' | |||||
#define PITCHfrise2 6 // and 5 must be the 'r' variant | |||||
#define PITCHrisefall 8 | |||||
/* 0 fall */ | |||||
unsigned char env_fall[128] = { | unsigned char env_fall[128] = { | ||||
0xff, 0xfd, 0xfa, 0xf8, 0xf6, 0xf4, 0xf2, 0xf0, 0xee, 0xec, 0xea, 0xe8, 0xe6, 0xe4, 0xe2, 0xe0, | 0xff, 0xfd, 0xfa, 0xf8, 0xf6, 0xf4, 0xf2, 0xf0, 0xee, 0xec, 0xea, 0xe8, 0xe6, 0xe4, 0xe2, 0xe0, | ||||
0xde, 0xdc, 0xda, 0xd8, 0xd6, 0xd4, 0xd2, 0xd0, 0xce, 0xcc, 0xca, 0xc8, 0xc6, 0xc4, 0xc2, 0xc0, | 0xde, 0xdc, 0xda, 0xd8, 0xd6, 0xd4, 0xd2, 0xd0, 0xce, 0xcc, 0xca, 0xc8, 0xc6, 0xc4, 0xc2, 0xc0, | ||||
0x1e, 0x1c, 0x1a, 0x18, 0x16, 0x14, 0x12, 0x10, 0x0e, 0x0c, 0x0a, 0x08, 0x06, 0x04, 0x02, 0x00 | 0x1e, 0x1c, 0x1a, 0x18, 0x16, 0x14, 0x12, 0x10, 0x0e, 0x0c, 0x0a, 0x08, 0x06, 0x04, 0x02, 0x00 | ||||
}; | }; | ||||
/* 1 rise */ | |||||
unsigned char env_rise[128] = { | unsigned char env_rise[128] = { | ||||
0x00, 0x02, 0x04, 0x06, 0x08, 0x0a, 0x0c, 0x0e, 0x10, 0x12, 0x14, 0x16, 0x18, 0x1a, 0x1c, 0x1e, | 0x00, 0x02, 0x04, 0x06, 0x08, 0x0a, 0x0c, 0x0e, 0x10, 0x12, 0x14, 0x16, 0x18, 0x1a, 0x1c, 0x1e, | ||||
0x20, 0x22, 0x24, 0x26, 0x28, 0x2a, 0x2c, 0x2e, 0x30, 0x32, 0x34, 0x36, 0x38, 0x3a, 0x3c, 0x3e, | 0x20, 0x22, 0x24, 0x26, 0x28, 0x2a, 0x2c, 0x2e, 0x30, 0x32, 0x34, 0x36, 0x38, 0x3a, 0x3c, 0x3e, | ||||
env_risefallrise, env_risefallrise | env_risefallrise, env_risefallrise | ||||
}; | }; | ||||
/* indexed by stress */ | |||||
// indexed by stress | |||||
static int min_drop[] = { 6, 7, 9, 9, 20, 20, 20, 25 }; | static int min_drop[] = { 6, 7, 9, 9, 20, 20, 20, 25 }; | ||||
// pitch change during the main part of the clause | // pitch change during the main part of the clause | ||||
static signed char oflow_emf[] = { 10, 52, 32, 20, 10 }; | static signed char oflow_emf[] = { 10, 52, 32, 20, 10 }; | ||||
static signed char oflow_less[] = { 6, 38, 24, 14, 4 }; | static signed char oflow_less[] = { 6, 38, 24, 14, 4 }; | ||||
#define N_TONE_HEAD_TABLE 13 | |||||
#define N_TONE_NUCLEUS_TABLE 13 | |||||
#define N_TONE_HEAD_TABLE 13 | |||||
#define N_TONE_NUCLEUS_TABLE 13 | |||||
typedef struct { | typedef struct { | ||||
unsigned char pre_start; | unsigned char pre_start; | ||||
} TONE_HEAD; | } TONE_HEAD; | ||||
typedef struct { | typedef struct { | ||||
unsigned char pitch_env0; /* pitch envelope, tonic syllable at end */ | |||||
unsigned char pitch_env0; // pitch envelope, tonic syllable at end | |||||
unsigned char tonic_max0; | unsigned char tonic_max0; | ||||
unsigned char tonic_min0; | unsigned char tonic_min0; | ||||
unsigned char pitch_env1; /* followed by unstressed */ | |||||
unsigned char pitch_env1; // followed by unstressed | |||||
unsigned char tonic_max1; | unsigned char tonic_max1; | ||||
unsigned char tonic_min1; | unsigned char tonic_min1; | ||||
#define T_EMPH 1 | #define T_EMPH 1 | ||||
static TONE_HEAD tone_head_table[N_TONE_HEAD_TABLE] = { | static TONE_HEAD tone_head_table[N_TONE_HEAD_TABLE] = { | ||||
{ 46, 57, 78, 50, drops_0, 3, 7, 5, oflow }, // 0 statement | |||||
{ 46, 57, 78, 46, drops_0, 3, 7, 5, oflow }, // 1 comma | |||||
{ 46, 57, 78, 46, drops_0, 3, 7, 5, oflow }, // 2 question | |||||
{ 46, 57, 90, 50, drops_0, 3, 9, 5, oflow_emf }, // 3 exclamation | |||||
{ 46, 57, 78, 50, drops_0, 3, 7, 5, oflow }, // 4 statement, emphatic | |||||
{ 46, 57, 74, 55, drops_0, 4, 7, 5, oflow_less },// 5 statement, less intonation | |||||
{ 46, 57, 74, 55, drops_0, 4, 7, 5, oflow_less },// 6 comma, less intonation | |||||
{ 46, 57, 74, 55, drops_0, 4, 7, 5, oflow_less },// 7 comma, less intonation, less rise | |||||
{ 46, 57, 78, 50, drops_0, 3, 7, 5, oflow }, // 8 pitch raises at end of sentence | |||||
{ 46, 57, 78, 46, drops_0, 3, 7, 5, oflow }, // 9 comma | |||||
{ 46, 57, 78, 50, drops_0, 3, 7, 5, oflow }, // 10 question | |||||
{ 34, 41, 41, 32, drops_0, 3, 7, 5, oflow_less }, // 11 test | |||||
{ 46, 57, 55, 50, drops_0, 3, 7, 5, oflow_less }, // 12 test | |||||
{ 46, 57, 78, 50, drops_0, 3, 7, 5, oflow }, // 0 statement | |||||
{ 46, 57, 78, 46, drops_0, 3, 7, 5, oflow }, // 1 comma | |||||
{ 46, 57, 78, 46, drops_0, 3, 7, 5, oflow }, // 2 question | |||||
{ 46, 57, 90, 50, drops_0, 3, 9, 5, oflow_emf }, // 3 exclamation | |||||
{ 46, 57, 78, 50, drops_0, 3, 7, 5, oflow }, // 4 statement, emphatic | |||||
{ 46, 57, 74, 55, drops_0, 4, 7, 5, oflow_less }, // 5 statement, less intonation | |||||
{ 46, 57, 74, 55, drops_0, 4, 7, 5, oflow_less }, // 6 comma, less intonation | |||||
{ 46, 57, 74, 55, drops_0, 4, 7, 5, oflow_less }, // 7 comma, less intonation, less rise | |||||
{ 46, 57, 78, 50, drops_0, 3, 7, 5, oflow }, // 8 pitch raises at end of sentence | |||||
{ 46, 57, 78, 46, drops_0, 3, 7, 5, oflow }, // 9 comma | |||||
{ 46, 57, 78, 50, drops_0, 3, 7, 5, oflow }, // 10 question | |||||
{ 34, 41, 41, 32, drops_0, 3, 7, 5, oflow_less }, // 11 test | |||||
{ 46, 57, 55, 50, drops_0, 3, 7, 5, oflow_less }, // 12 test | |||||
}; | }; | ||||
static TONE_NUCLEUS tone_nucleus_table[N_TONE_NUCLEUS_TABLE] = { | static TONE_NUCLEUS tone_nucleus_table[N_TONE_NUCLEUS_TABLE] = { | ||||
{ PITCHfall, 64, 8, PITCHfall, 70, 18, NULL, 24, 12, 0 }, // 0 statement | |||||
{ PITCHfrise, 80, 18, PITCHfrise2, 78, 22, NULL, 34, 52, 0 }, // 1 comma | |||||
{ PITCHfrise, 88, 22, PITCHfrise2, 82, 22, NULL, 34, 64, 0 }, // 2 question | |||||
{ PITCHfall, 92, 8, PITCHfall, 92, 80, NULL, 76, 8, T_EMPH }, // 3 exclamation | |||||
{ PITCHfall, 86, 4, PITCHfall, 94, 66, NULL, 34, 10, 0 }, // 4 statement, emphatic | |||||
{ PITCHfall, 62, 10, PITCHfall, 62, 20, NULL, 28, 16, 0 }, // 5 statement, less intonation | |||||
{ PITCHfrise, 68, 18, PITCHfrise2, 68, 22, NULL, 30, 44, 0 }, // 6 comma, less intonation | |||||
{ PITCHfrise2, 64, 16, PITCHfall, 66, 32, NULL, 32, 18, 0 }, // 7 comma, less intonation, less rise | |||||
{ PITCHrise, 68, 46, PITCHfall, 42, 32, NULL, 46, 58, 0 }, // 8 pitch raises at end of sentence | |||||
{ PITCHfrise, 78, 24, PITCHfrise2, 72, 22, NULL, 42, 52, 0 }, // 9 comma | |||||
{ PITCHfrise, 88, 34, PITCHfall, 64, 32, NULL, 46, 82, 0 }, // 10 question | |||||
{ PITCHfall, 56, 12, PITCHfall, 56, 20, NULL, 24, 12, 0 }, // 11 test | |||||
{ PITCHfall, 70, 18, PITCHfall, 70, 24, NULL, 32, 20, 0 }, // 12 test | |||||
{ PITCHfall, 64, 8, PITCHfall, 70, 18, NULL, 24, 12, 0 }, // 0 statement | |||||
{ PITCHfrise, 80, 18, PITCHfrise2, 78, 22, NULL, 34, 52, 0 }, // 1 comma | |||||
{ PITCHfrise, 88, 22, PITCHfrise2, 82, 22, NULL, 34, 64, 0 }, // 2 question | |||||
{ PITCHfall, 92, 8, PITCHfall, 92, 80, NULL, 76, 8, T_EMPH }, // 3 exclamation | |||||
{ PITCHfall, 86, 4, PITCHfall, 94, 66, NULL, 34, 10, 0 }, // 4 statement, emphatic | |||||
{ PITCHfall, 62, 10, PITCHfall, 62, 20, NULL, 28, 16, 0 }, // 5 statement, less intonation | |||||
{ PITCHfrise, 68, 18, PITCHfrise2, 68, 22, NULL, 30, 44, 0 }, // 6 comma, less intonation | |||||
{ PITCHfrise2, 64, 16, PITCHfall, 66, 32, NULL, 32, 18, 0 }, // 7 comma, less intonation, less rise | |||||
{ PITCHrise, 68, 46, PITCHfall, 42, 32, NULL, 46, 58, 0 }, // 8 pitch raises at end of sentence | |||||
{ PITCHfrise, 78, 24, PITCHfrise2, 72, 22, NULL, 42, 52, 0 }, // 9 comma | |||||
{ PITCHfrise, 88, 34, PITCHfall, 64, 32, NULL, 46, 82, 0 }, // 10 question | |||||
{ PITCHfall, 56, 12, PITCHfall, 56, 20, NULL, 24, 12, 0 }, // 11 test | |||||
{ PITCHfall, 70, 18, PITCHfall, 70, 24, NULL, 32, 20, 0 }, // 12 test | |||||
}; | }; | ||||
/* index by 0=. 1=, 2=?, 3=! 4=none, 5=emphasized */ | |||||
// index by 0=. 1=, 2=?, 3=! 4=none, 5=emphasized | |||||
unsigned char punctuation_to_tone[INTONATION_TYPES][PUNCT_INTONATIONS] = { | unsigned char punctuation_to_tone[INTONATION_TYPES][PUNCT_INTONATIONS] = { | ||||
{ 0, 1, 2, 3, 0, 4 }, | |||||
{ 0, 1, 2, 3, 0, 4 }, | |||||
{ 5, 6, 2, 3, 0, 4 }, | |||||
{ 5, 7, 1, 3, 0, 4 }, | |||||
{ 8, 9, 10, 3, 0, 0 }, | |||||
{ 8, 8, 10, 3, 0, 0 }, | |||||
{ 11, 11, 11, 11, 0, 0 }, // 6 test | |||||
{ 0, 1, 2, 3, 0, 4 }, | |||||
{ 0, 1, 2, 3, 0, 4 }, | |||||
{ 5, 6, 2, 3, 0, 4 }, | |||||
{ 5, 7, 1, 3, 0, 4 }, | |||||
{ 8, 9, 10, 3, 0, 0 }, | |||||
{ 8, 8, 10, 3, 0, 0 }, | |||||
{ 11, 11, 11, 11, 0, 0 }, // 6 test | |||||
{ 12, 12, 12, 12, 0, 0 } | { 12, 12, 12, 12, 0, 0 } | ||||
}; | }; | ||||
int n_tunes = 0; | int n_tunes = 0; | ||||
TUNE *tunes = NULL; | TUNE *tunes = NULL; | ||||
#define SECONDARY 3 | |||||
#define PRIMARY 4 | |||||
#define SECONDARY 3 | |||||
#define PRIMARY 4 | |||||
#define PRIMARY_STRESSED 6 | #define PRIMARY_STRESSED 6 | ||||
#define PRIMARY_LAST 7 | |||||
#define PRIMARY_LAST 7 | |||||
static int number_pre; | static int number_pre; | ||||
static int number_body; | static int number_body; | ||||
int ix; | int ix; | ||||
int stress; | int stress; | ||||
int max_stress = 0; | int max_stress = 0; | ||||
int max_stress_posn = 0; // last syllable ot the highest stress | |||||
int max_stress_posn2 = 0; // penuntimate syllable of the highest stress | |||||
int max_stress_posn = 0; // last syllable ot the highest stress | |||||
int max_stress_posn2 = 0; // penuntimate syllable of the highest stress | |||||
number_pre = -1; /* number of vowels before 1st primary stress */ | |||||
number_pre = -1; // number of vowels before 1st primary stress | |||||
number_body = 0; | number_body = 0; | ||||
number_tail = 0; /* number between tonic syllable and next primary */ | |||||
number_tail = 0; // number between tonic syllable and next primary | |||||
last_primary = -1; | last_primary = -1; | ||||
for (ix = start; ix < end; ix++) { | for (ix = start; ix < end; ix++) { | ||||
stress = syllable_tab[ix].stress; /* marked stress level */ | |||||
stress = syllable_tab[ix].stress; // marked stress level | |||||
if (stress >= max_stress) { | if (stress >= max_stress) { | ||||
if (stress > max_stress) | if (stress > max_stress) | ||||
tone_posn = max_stress_posn; | tone_posn = max_stress_posn; | ||||
tone_posn2 = max_stress_posn2; | tone_posn2 = max_stress_posn2; | ||||
if (no_tonic) { | |||||
tone_posn = tone_posn2 = end; // next position after the end of the truncated clause | |||||
} else if (last_primary >= 0) { | |||||
if (no_tonic) | |||||
tone_posn = tone_posn2 = end; // next position after the end of the truncated clause | |||||
else if (last_primary >= 0) { | |||||
if (end == clause_end) | if (end == clause_end) | ||||
syllable_tab[last_primary].stress = PRIMARY_LAST; | syllable_tab[last_primary].stress = PRIMARY_LAST; | ||||
} else { | } else { | ||||
} | } | ||||
} | } | ||||
/* Count number of primary stresses up to tonic syllable or body_reset */ | |||||
// Count number of primary stresses up to tonic syllable or body_reset | |||||
static int count_increments(int ix, int end_ix, int min_stress) | static int count_increments(int ix, int end_ix, int min_stress) | ||||
{ | { | ||||
int count = 0; | int count = 0; | ||||
int pitch = 0; | int pitch = 0; | ||||
int increment = 0; | int increment = 0; | ||||
int n_steps = 0; | int n_steps = 0; | ||||
int stage; // onset, head, last | |||||
int stage; // onset, head, last | |||||
int initial; | int initial; | ||||
int overflow_ix = 0; | int overflow_ix = 0; | ||||
int pitch_range; | int pitch_range; | ||||
int unstressed_inc; | int unstressed_inc; | ||||
int used_onset = 0; | int used_onset = 0; | ||||
int head_final = end_ix; | int head_final = end_ix; | ||||
int secondary = 2; // 2 | |||||
int secondary = 2; | |||||
pitch_range = (tune->head_end - tune->head_start) << 8; | pitch_range = (tune->head_end - tune->head_start) << 8; | ||||
pitch_range_abs = abs(pitch_range); | pitch_range_abs = abs(pitch_range); | ||||
drops = drops_0; // this should be controled by tune->head_drops | |||||
drops = drops_0; // this should be controled by tune->head_drops | |||||
initial = 1; | initial = 1; | ||||
stage = 0; | stage = 0; | ||||
if (tune->onset == 255) | if (tune->onset == 255) | ||||
stage = 1; // no onset specified | |||||
stage = 1; // no onset specified | |||||
if (tune->head_last != 255) { | if (tune->head_last != 255) { | ||||
// find the last primary stress in the body | // find the last primary stress in the body | ||||
} else if (stress >= SECONDARY) | } else if (stress >= SECONDARY) | ||||
set_pitch(syl, (pitch >> 8), drops[stress]); | set_pitch(syl, (pitch >> 8), drops[stress]); | ||||
else { | else { | ||||
/* unstressed, drop pitch if preceded by PRIMARY */ | |||||
// unstressed, drop pitch if preceded by PRIMARY | |||||
if ((syllable_tab[ix-1].stress & 0x3f) >= SECONDARY) | if ((syllable_tab[ix-1].stress & 0x3f) >= SECONDARY) | ||||
set_pitch(syl, (pitch >> 8) - th->body_lower_u, drops[stress]); | set_pitch(syl, (pitch >> 8) - th->body_lower_u, drops[stress]); | ||||
else | else | ||||
static void SetPitchGradient(int start_ix, int end_ix, int start_pitch, int end_pitch) | static void SetPitchGradient(int start_ix, int end_ix, int start_pitch, int end_pitch) | ||||
{ | { | ||||
// Set a linear pitch change over a number of syllables. | |||||
// Used for pre-head, unstressed syllables in the body, and the tail | |||||
// Set a linear pitch change over a number of syllables. | |||||
// Used for pre-head, unstressed syllables in the body, and the tail | |||||
int ix; | int ix; | ||||
int stress; | int stress; | ||||
tune = &tunes[tune_number]; | tune = &tunes[tune_number]; | ||||
ix = start; | ix = start; | ||||
/* vowels before the first primary stress */ | |||||
/******************************************/ | |||||
// vowels before the first primary stress | |||||
SetPitchGradient(ix, ix+number_pre, tune->prehead_start, tune->prehead_end); | SetPitchGradient(ix, ix+number_pre, tune->prehead_start, tune->prehead_end); | ||||
ix += number_pre; | ix += number_pre; | ||||
/* body of tonic segment */ | |||||
/*************************/ | |||||
// body of tonic segment | |||||
if (option_tone_flags & OPTION_EMPHASIZE_PENULTIMATE) | if (option_tone_flags & OPTION_EMPHASIZE_PENULTIMATE) | ||||
tone_posn = tone_posn2; // put tone on the penultimate stressed word | |||||
tone_posn = tone_posn2; // put tone on the penultimate stressed word | |||||
ix = SetHeadIntonation(tune, ix, tone_posn, 0); | ix = SetHeadIntonation(tune, ix, tone_posn, 0); | ||||
if (no_tonic) | if (no_tonic) | ||||
return 0; | return 0; | ||||
/* tonic syllable */ | |||||
/******************/ | |||||
// tonic syllable | |||||
if (number_tail == 0) { | if (number_tail == 0) { | ||||
tone_pitch_env = tune->nucleus0_env; | tone_pitch_env = tune->nucleus0_env; | ||||
if (syllable_tab[tone_posn].stress == PRIMARY) | if (syllable_tab[tone_posn].stress == PRIMARY) | ||||
syllable_tab[tone_posn].stress = PRIMARY_STRESSED; | syllable_tab[tone_posn].stress = PRIMARY_STRESSED; | ||||
/* tail, after the tonic syllable */ | |||||
/**********************************/ | |||||
// tail, after the tonic syllable | |||||
SetPitchGradient(ix, end, tune->tail_start, tune->tail_end); | SetPitchGradient(ix, end, tune->tail_start, tune->tail_end); | ||||
tn = &tone_nucleus_table[tune_number]; | tn = &tone_nucleus_table[tune_number]; | ||||
ix = start; | ix = start; | ||||
/* vowels before the first primary stress */ | |||||
/******************************************/ | |||||
// vowels before the first primary stress | |||||
SetPitchGradient(ix, ix+number_pre, th->pre_start, th->pre_end); | SetPitchGradient(ix, ix+number_pre, th->pre_start, th->pre_end); | ||||
ix += number_pre; | ix += number_pre; | ||||
/* body of tonic segment */ | |||||
/*************************/ | |||||
// body of tonic segment | |||||
if (option_tone_flags & OPTION_EMPHASIZE_PENULTIMATE) | if (option_tone_flags & OPTION_EMPHASIZE_PENULTIMATE) | ||||
tone_posn = tone_posn2; // put tone on the penultimate stressed word | |||||
tone_posn = tone_posn2; // put tone on the penultimate stressed word | |||||
ix = calc_pitch_segment(ix, tone_posn, th, tn, PRIMARY, continuing); | ix = calc_pitch_segment(ix, tone_posn, th, tn, PRIMARY, continuing); | ||||
if (no_tonic) | if (no_tonic) | ||||
return 0; | return 0; | ||||
/* tonic syllable */ | |||||
/******************/ | |||||
// tonic syllable | |||||
if (tn->flags & T_EMPH) { | |||||
if (tn->flags & T_EMPH) | |||||
syllable_tab[ix].flags |= SYL_EMPHASIS; | syllable_tab[ix].flags |= SYL_EMPHASIS; | ||||
} | |||||
if (number_tail == 0) { | if (number_tail == 0) { | ||||
tone_pitch_env = tn->pitch_env0; | tone_pitch_env = tn->pitch_env0; | ||||
if (syllable_tab[tone_posn].stress == PRIMARY) | if (syllable_tab[tone_posn].stress == PRIMARY) | ||||
syllable_tab[tone_posn].stress = PRIMARY_STRESSED; | syllable_tab[tone_posn].stress = PRIMARY_STRESSED; | ||||
/* tail, after the tonic syllable */ | |||||
/**********************************/ | |||||
// tail, after the tonic syllable | |||||
SetPitchGradient(ix, end, tn->tail_start, tn->tail_end); | SetPitchGradient(ix, end, tn->tail_start, tn->tail_end); | ||||
static void CalcPitches_Tone(Translator *tr, int clause_tone) | static void CalcPitches_Tone(Translator *tr, int clause_tone) | ||||
{ | { | ||||
// clause_tone: 0=. 1=, 2=?, 3=! 4=none | |||||
// clause_tone: 0=. 1=, 2=?, 3=! 4=none | |||||
PHONEME_LIST *p; | PHONEME_LIST *p; | ||||
int ix; | int ix; | ||||
int count_stressed = 0; | int count_stressed = 0; | ||||
int pause; | int pause; | ||||
int tone_promoted; | int tone_promoted; | ||||
PHONEME_TAB *tph; | PHONEME_TAB *tph; | ||||
PHONEME_TAB *prev_tph; // forget across word boundary | |||||
PHONEME_TAB *prevw_tph; // remember across word boundary | |||||
PHONEME_TAB *prev_tph; // forget across word boundary | |||||
PHONEME_TAB *prevw_tph; // remember across word boundary | |||||
PHONEME_LIST *prev_p; | PHONEME_LIST *prev_p; | ||||
int pitch_adjust = 0; // pitch gradient through the clause - inital value | |||||
int pitch_decrement = 0; // decrease by this for each stressed syllable | |||||
int pitch_low = 0; // until it drops to this | |||||
int pitch_high = 0; // then reset to this | |||||
int pitch_adjust = 0; // pitch gradient through the clause - inital value | |||||
int pitch_decrement = 0; // decrease by this for each stressed syllable | |||||
int pitch_low = 0; // until it drops to this | |||||
int pitch_high = 0; // then reset to this | |||||
// count number of stressed syllables | // count number of stressed syllables | ||||
p = &phoneme_list[0]; | p = &phoneme_list[0]; | ||||
// LANG=vi | // LANG=vi | ||||
p = &phoneme_list[final_stressed]; | p = &phoneme_list[final_stressed]; | ||||
if (p->tone_ph == 0) | if (p->tone_ph == 0) | ||||
p->tone_ph = PhonemeCode('7'); // change default tone (tone 1) to falling tone at end of clause | |||||
p->tone_ph = PhonemeCode('7'); // change default tone (tone 1) to falling tone at end of clause | |||||
} | } | ||||
pause = 1; | pause = 1; | ||||
// perform tone sandhi | // perform tone sandhi | ||||
for (ix = 0; ix < n_phoneme_list; ix++, p++) { | for (ix = 0; ix < n_phoneme_list; ix++, p++) { | ||||
if ((p->type == phPAUSE) && (p->ph->std_length > 50)) { | if ((p->type == phPAUSE) && (p->ph->std_length > 50)) { | ||||
pause = 1; // there is a pause since the previous vowel | |||||
prevw_tph = phoneme_tab[phonPAUSE]; // forget previous tone | |||||
pause = 1; // there is a pause since the previous vowel | |||||
prevw_tph = phoneme_tab[phonPAUSE]; // forget previous tone | |||||
} | } | ||||
if (p->newword) | if (p->newword) | ||||
prev_tph = phoneme_tab[phonPAUSE]; // forget across word boundaries | |||||
prev_tph = phoneme_tab[phonPAUSE]; // forget across word boundaries | |||||
if (p->synthflags & SFLAG_SYLLABLE) { | if (p->synthflags & SFLAG_SYLLABLE) { | ||||
tone_ph = p->tone_ph; | tone_ph = p->tone_ph; | ||||
if (tr->translator_name == L('z', 'h')) { | if (tr->translator_name == L('z', 'h')) { | ||||
if (tone_ph == 0) { | if (tone_ph == 0) { | ||||
if (pause || tone_promoted) { | if (pause || tone_promoted) { | ||||
tone_ph = PhonemeCode2('5', '5'); // no previous vowel, use tone 1 | |||||
tone_ph = PhonemeCode2('5', '5'); // no previous vowel, use tone 1 | |||||
tone_promoted = 1; | tone_promoted = 1; | ||||
} else | } else | ||||
tone_ph = PhonemeCode2('1', '1'); // default tone 5 | |||||
tone_ph = PhonemeCode2('1', '1'); // default tone 5 | |||||
p->tone_ph = tone_ph; | p->tone_ph = tone_ph; | ||||
tph = phoneme_tab[tone_ph]; | tph = phoneme_tab[tone_ph]; | ||||
} | } | ||||
if (prevw_tph->mnemonic == 0x343132) { // [214] | if (prevw_tph->mnemonic == 0x343132) { // [214] | ||||
if (tph->mnemonic == 0x343132) // [214] | |||||
if (tph->mnemonic == 0x343132) // [214] | |||||
prev_p->tone_ph = PhonemeCode2('3', '5'); | prev_p->tone_ph = PhonemeCode2('3', '5'); | ||||
else | else | ||||
prev_p->tone_ph = PhonemeCode2('2', '1'); | prev_p->tone_ph = PhonemeCode2('2', '1'); | ||||
} | } | ||||
if ((prev_tph->mnemonic == 0x3135) && (tph->mnemonic == 0x3135)) // [51] + [51] | |||||
if ((prev_tph->mnemonic == 0x3135) && (tph->mnemonic == 0x3135)) // [51] + [51] | |||||
prev_p->tone_ph = PhonemeCode2('5', '3'); | prev_p->tone_ph = PhonemeCode2('5', '3'); | ||||
if (tph->mnemonic == 0x3131) { // [11] Tone 5 | if (tph->mnemonic == 0x3131) { // [11] Tone 5 | ||||
p->tone_ph = PhonemeCode2('4', '4'); | p->tone_ph = PhonemeCode2('4', '4'); | ||||
// tone 5 is unstressed (shorter) | // tone 5 is unstressed (shorter) | ||||
p->stresslevel = 0; // diminished stress | |||||
p->stresslevel = 0; // diminished stress | |||||
} | } | ||||
} | } | ||||
} | } | ||||
if (tone_ph == 0) { | if (tone_ph == 0) { | ||||
tone_ph = phonDEFAULTTONE; // no tone specified, use default tone 1 | |||||
tone_ph = phonDEFAULTTONE; // no tone specified, use default tone 1 | |||||
p->tone_ph = tone_ph; | p->tone_ph = tone_ph; | ||||
} | } | ||||
p->pitch1 = pitch_adjust + phoneme_tab[tone_ph]->start_type; | p->pitch1 = pitch_adjust + phoneme_tab[tone_ph]->start_type; | ||||
void CalcPitches(Translator *tr, int clause_type) | void CalcPitches(Translator *tr, int clause_type) | ||||
{ | { | ||||
// clause_type: 0=. 1=, 2=?, 3=! 4=none | |||||
// clause_type: 0=. 1=, 2=?, 3=! 4=none | |||||
PHONEME_LIST *p; | PHONEME_LIST *p; | ||||
SYLLABLE *syl; | SYLLABLE *syl; | ||||
int ix; | int ix; | ||||
SYLLABLE syllable_tab2[N_PHONEME_LIST]; | SYLLABLE syllable_tab2[N_PHONEME_LIST]; | ||||
syllable_tab = syllable_tab2; // don't use permanent storage. it's only needed during the call of CalcPitches() | |||||
syllable_tab = syllable_tab2; // don't use permanent storage. it's only needed during the call of CalcPitches() | |||||
n_st = 0; | n_st = 0; | ||||
n_primary = 0; | n_primary = 0; | ||||
for (ix = 0; ix < (n_phoneme_list-1); ix++) { | for (ix = 0; ix < (n_phoneme_list-1); ix++) { | ||||
} else if ((p->ph->code == phonPAUSE_CLAUSE) && (n_st > 0)) | } else if ((p->ph->code == phonPAUSE_CLAUSE) && (n_st > 0)) | ||||
syllable_tab[n_st-1].flags |= SYL_END_CLAUSE; | syllable_tab[n_st-1].flags |= SYL_END_CLAUSE; | ||||
} | } | ||||
syllable_tab[n_st].stress = 0; // extra 0 entry at the end | |||||
syllable_tab[n_st].stress = 0; // extra 0 entry at the end | |||||
if (n_st == 0) | if (n_st == 0) | ||||
return; // nothing to do | |||||
return; // nothing to do | |||||
if (tr->langopts.tone_language == 1) { | if (tr->langopts.tone_language == 1) { | ||||
CalcPitches_Tone(tr, clause_type); | CalcPitches_Tone(tr, clause_type); | ||||
group_tone_comma = tr->langopts.tunes[1]; | group_tone_comma = tr->langopts.tunes[1]; | ||||
} else { | } else { | ||||
group_tone = tr->punct_to_tone[option][clause_type]; | group_tone = tr->punct_to_tone[option][clause_type]; | ||||
group_tone_comma = tr->punct_to_tone[option][1]; // emphatic form of statement | |||||
group_tone_comma = tr->punct_to_tone[option][1]; // emphatic form of statement | |||||
} | } | ||||
if (clause_type == 4) | if (clause_type == 4) | ||||
no_tonic = 1; /* incomplete clause, used for abbreviations such as Mr. Dr. Mrs. */ | |||||
no_tonic = 1; // incomplete clause, used for abbreviations such as Mr. Dr. Mrs. | |||||
else | else | ||||
no_tonic = 0; | no_tonic = 0; | ||||
count_pitch_vowels(st_start, ix, n_st); | count_pitch_vowels(st_start, ix, n_st); | ||||
if ((ix < n_st) || (clause_type == 0)) { | if ((ix < n_st) || (clause_type == 0)) { | ||||
calc_pitches(option, st_start, ix, group_tone); // split into > 1 tone groups | |||||
calc_pitches(option, st_start, ix, group_tone); // split into > 1 tone groups | |||||
if ((clause_type == 1) || (clause_type == 2)) | if ((clause_type == 1) || (clause_type == 2)) | ||||
group_tone = tr->langopts.tunes[1]; // , or ? remainder has comma-tone | |||||
group_tone = tr->langopts.tunes[1]; // , or ? remainder has comma-tone | |||||
else | else | ||||
group_tone = tr->langopts.tunes[0]; // . or ! remainder has statement tone | |||||
group_tone = tr->langopts.tunes[0]; // . or ! remainder has statement tone | |||||
} else | } else | ||||
calc_pitches(option, st_start, ix, group_tone); | calc_pitches(option, st_start, ix, group_tone); | ||||
} | } | ||||
if (syl->flags & SYL_EMPHASIS) | if (syl->flags & SYL_EMPHASIS) | ||||
p->stresslevel |= 8; // emphasized | |||||
p->stresslevel |= 8; // emphasized | |||||
st_ix++; | st_ix++; | ||||
} | } |
#define getrandom(min, max) ((rand()%(long)(((max)+1)-(min)))+(min)) | #define getrandom(min, max) ((rand()%(long)(((max)+1)-(min)))+(min)) | ||||
#endif | #endif | ||||
/* function prototypes for functions private to this file */ | |||||
// function prototypes for functions private to this file | |||||
static void flutter(klatt_frame_ptr); | static void flutter(klatt_frame_ptr); | ||||
static double sampled_source(int); | static double sampled_source(int); | ||||
#define NUMBER_OF_SAMPLES 100 | #define NUMBER_OF_SAMPLES 100 | ||||
static int scale_wav_tab[] = { 45, 38, 45, 45, 55 }; // scale output from different voicing sources | |||||
static int scale_wav_tab[] = { 45, 38, 45, 45, 55 }; // scale output from different voicing sources | |||||
// For testing, this can be overwritten in KlattInit() | // For testing, this can be overwritten in KlattInit() | ||||
static short natural_samples2[256] = { | static short natural_samples2[256] = { | ||||
2583, 2516, 2450, 2384, 2319, 2254, 2191, 2127, | |||||
2067, 2005, 1946, 1890, 1832, 1779, 1726, 1675, | |||||
1626, 1579, 1533, 1491, 1449, 1409, 1372, 1336, | |||||
1302, 1271, 1239, 1211, 1184, 1158, 1134, 1111, | |||||
1089, 1069, 1049, 1031, 1013, 996, 980, 965, | |||||
950, 936, 921, 909, 895, 881, 869, 855, | |||||
843, 830, 818, 804, 792, 779, 766, 754, | |||||
740, 728, 715, 702, 689, 676, 663, 651, | |||||
637, 626, 612, 601, 588, 576, 564, 552, | |||||
540, 530, 517, 507, 496, 485, 475, 464, | |||||
454, 443, 434, 424, 414, 404, 394, 385, | |||||
375, 366, 355, 347, 336, 328, 317, 308, | |||||
299, 288, 280, 269, 260, 250, 240, 231, | |||||
220, 212, 200, 192, 181, 172, 161, 152, | |||||
142, 133, 123, 113, 105, 94, 86, 76, | |||||
67, 57, 49, 39, 30, 22, 11, 4, | |||||
-5, -14, -23, -32, -41, -50, -60, -69, | |||||
-78, -87, -96, -107, -115, -126, -134, -144, | |||||
-154, -164, -174, -183, -193, -203, -213, -222, | |||||
-233, -242, -252, -262, -271, -281, -291, -301, | |||||
-310, -320, -330, -339, -349, -357, -368, -377, | |||||
-387, -397, -406, -417, -426, -436, -446, -456, | |||||
-467, -477, -487, -499, -509, -521, -532, -543, | |||||
-555, -567, -579, -591, -603, -616, -628, -641, | |||||
-653, -666, -679, -692, -705, -717, -732, -743, | |||||
-758, -769, -783, -795, -808, -820, -834, -845, | |||||
-860, -872, -885, -898, -911, -926, -939, -955, | |||||
-968, -986, -999, -1018, -1034, -1054, -1072, -1094, | |||||
2583, 2516, 2450, 2384, 2319, 2254, 2191, 2127, | |||||
2067, 2005, 1946, 1890, 1832, 1779, 1726, 1675, | |||||
1626, 1579, 1533, 1491, 1449, 1409, 1372, 1336, | |||||
1302, 1271, 1239, 1211, 1184, 1158, 1134, 1111, | |||||
1089, 1069, 1049, 1031, 1013, 996, 980, 965, | |||||
950, 936, 921, 909, 895, 881, 869, 855, | |||||
843, 830, 818, 804, 792, 779, 766, 754, | |||||
740, 728, 715, 702, 689, 676, 663, 651, | |||||
637, 626, 612, 601, 588, 576, 564, 552, | |||||
540, 530, 517, 507, 496, 485, 475, 464, | |||||
454, 443, 434, 424, 414, 404, 394, 385, | |||||
375, 366, 355, 347, 336, 328, 317, 308, | |||||
299, 288, 280, 269, 260, 250, 240, 231, | |||||
220, 212, 200, 192, 181, 172, 161, 152, | |||||
142, 133, 123, 113, 105, 94, 86, 76, | |||||
67, 57, 49, 39, 30, 22, 11, 4, | |||||
-5, -14, -23, -32, -41, -50, -60, -69, | |||||
-78, -87, -96, -107, -115, -126, -134, -144, | |||||
-154, -164, -174, -183, -193, -203, -213, -222, | |||||
-233, -242, -252, -262, -271, -281, -291, -301, | |||||
-310, -320, -330, -339, -349, -357, -368, -377, | |||||
-387, -397, -406, -417, -426, -436, -446, -456, | |||||
-467, -477, -487, -499, -509, -521, -532, -543, | |||||
-555, -567, -579, -591, -603, -616, -628, -641, | |||||
-653, -666, -679, -692, -705, -717, -732, -743, | |||||
-758, -769, -783, -795, -808, -820, -834, -845, | |||||
-860, -872, -885, -898, -911, -926, -939, -955, | |||||
-968, -986, -999, -1018, -1034, -1054, -1072, -1094, | |||||
-1115, -1138, -1162, -1188, -1215, -1244, -1274, -1307, | -1115, -1138, -1162, -1188, -1215, -1244, -1274, -1307, | ||||
-1340, -1377, -1415, -1453, -1496, -1538, -1584, -1631, | -1340, -1377, -1415, -1453, -1496, -1538, -1584, -1631, | ||||
-1680, -1732, -1783, -1839, -1894, -1952, -2010, -2072, | -1680, -1732, -1783, -1839, -1894, -1952, -2010, -2072, | ||||
-2133, -2196, -2260, -2325, -2390, -2456, -2522, -2589, | -2133, -2196, -2260, -2325, -2390, -2456, -2522, -2589, | ||||
}; | }; | ||||
static short natural_samples[100] = { | static short natural_samples[100] = { | ||||
-310, -400, 530, 356, 224, 89, 23, -10, -58, -16, 461, 599, 536, 701, 770, | |||||
605, 497, 461, 560, 404, 110, 224, 131, 104, -97, 155, 278, -154, -1165, | |||||
-598, 737, 125, -592, 41, 11, -247, -10, 65, 92, 80, -304, 71, 167, -1, 122, | |||||
233, 161, -43, 278, 479, 485, 407, 266, 650, 134, 80, 236, 68, 260, 269, 179, | |||||
53, 140, 275, 293, 296, 104, 257, 152, 311, 182, 263, 245, 125, 314, 140, 44, | |||||
203, 230, -235, -286, 23, 107, 92, -91, 38, 464, 443, 176, 98, -784, -2449, | |||||
-310, -400, 530, 356, 224, 89, 23, -10, -58, -16, 461, 599, 536, 701, 770, | |||||
605, 497, 461, 560, 404, 110, 224, 131, 104, -97, 155, 278, -154, -1165, | |||||
-598, 737, 125, -592, 41, 11, -247, -10, 65, 92, 80, -304, 71, 167, -1, 122, | |||||
233, 161, -43, 278, 479, 485, 407, 266, 650, 134, 80, 236, 68, 260, 269, 179, | |||||
53, 140, 275, 293, 296, 104, 257, 152, 311, 182, 263, 245, 125, 314, 140, 44, | |||||
203, 230, -235, -286, 23, 107, 92, -91, 38, 464, 443, 176, 98, -784, -2449, | |||||
-1891, -1045, -1600, -1462, -1384, -1261, -949, -730 | -1891, -1045, -1600, -1462, -1384, -1261, -949, -730 | ||||
}; | }; | ||||
fla = (double)kt_globals.f0_flutter / 50; | fla = (double)kt_globals.f0_flutter / 50; | ||||
flb = (double)kt_globals.original_f0 / 100; | flb = (double)kt_globals.original_f0 / 100; | ||||
flc = sin(PI*12.7*time_count); // because we are calling flutter() more frequently, every 2.9mS | |||||
flc = sin(PI*12.7*time_count); // because we are calling flutter() more frequently, every 2.9mS | |||||
fld = sin(PI*7.1*time_count); | fld = sin(PI*7.1*time_count); | ||||
fle = sin(PI*4.7*time_count); | fle = sin(PI*4.7*time_count); | ||||
delta_f0 = fla * flb * (flc + fld + fle) * 10; | delta_f0 = fla * flb * (flc + fld + fle) * 10; | ||||
static double sourc; | static double sourc; | ||||
int ix; | int ix; | ||||
flutter(frame); /* add f0 flutter */ | |||||
flutter(frame); // add f0 flutter | |||||
/* MAIN LOOP, for each output sample of current frame: */ | |||||
// MAIN LOOP, for each output sample of current frame: | |||||
for (kt_globals.ns = 0; kt_globals.ns < kt_globals.nspfr; kt_globals.ns++) { | for (kt_globals.ns = 0; kt_globals.ns < kt_globals.nspfr; kt_globals.ns++) { | ||||
/* Get low-passed random number for aspiration and frication noise */ | |||||
// Get low-passed random number for aspiration and frication noise | |||||
noise = gen_noise(noise); | noise = gen_noise(noise); | ||||
/* | |||||
Amplitude modulate noise (reduce noise amplitude during | |||||
second half of glottal period) if voicing simultaneously present. | |||||
*/ | |||||
// Amplitude modulate noise (reduce noise amplitude during | |||||
// second half of glottal period) if voicing simultaneously present. | |||||
if (kt_globals.nper > kt_globals.nmod) | if (kt_globals.nper > kt_globals.nmod) | ||||
noise *= (double)0.5; | noise *= (double)0.5; | ||||
/* Compute frication noise */ | |||||
// Compute frication noise | |||||
frics = kt_globals.amp_frica * noise; | frics = kt_globals.amp_frica * noise; | ||||
/* | |||||
Compute voicing waveform. Run glottal source simulation at 4 | |||||
times normal sample rate to minimize quantization noise in | |||||
period of female voice. | |||||
*/ | |||||
// Compute voicing waveform. Run glottal source simulation at 4 | |||||
// times normal sample rate to minimize quantization noise in | |||||
// period of female voice. | |||||
for (n4 = 0; n4 < 4; n4++) { | for (n4 = 0; n4 < 4; n4++) { | ||||
switch (kt_globals.glsource) | switch (kt_globals.glsource) | ||||
break; | break; | ||||
} | } | ||||
/* Reset period when counter 'nper' reaches T0 */ | |||||
// Reset period when counter 'nper' reaches T0 | |||||
if (kt_globals.nper >= kt_globals.T0) { | if (kt_globals.nper >= kt_globals.T0) { | ||||
kt_globals.nper = 0; | kt_globals.nper = 0; | ||||
pitch_synch_par_reset(frame); | pitch_synch_par_reset(frame); | ||||
} | } | ||||
/* | |||||
Low-pass filter voicing waveform before downsampling from 4*samrate | |||||
to samrate samples/sec. Resonator f=.09*samrate, bw=.06*samrate | |||||
*/ | |||||
// Low-pass filter voicing waveform before downsampling from 4*samrate | |||||
// to samrate samples/sec. Resonator f=.09*samrate, bw=.06*samrate | |||||
voice = resonator(&(kt_globals.rsn[RLP]), voice); | voice = resonator(&(kt_globals.rsn[RLP]), voice); | ||||
/* Increment counter that keeps track of 4*samrate samples per sec */ | |||||
// Increment counter that keeps track of 4*samrate samples per sec | |||||
kt_globals.nper++; | kt_globals.nper++; | ||||
} | } | ||||
/* | |||||
Tilt spectrum of voicing source down by soft low-pass filtering, amount | |||||
of tilt determined by TLTdb | |||||
*/ | |||||
// Tilt spectrum of voicing source down by soft low-pass filtering, amount | |||||
// of tilt determined by TLTdb | |||||
voice = (voice * kt_globals.onemd) + (vlast * kt_globals.decay); | voice = (voice * kt_globals.onemd) + (vlast * kt_globals.decay); | ||||
vlast = voice; | vlast = voice; | ||||
/* | |||||
Add breathiness during glottal open phase. Amount of breathiness | |||||
determined by parameter Aturb Use nrand rather than noise because | |||||
noise is low-passed. | |||||
*/ | |||||
// Add breathiness during glottal open phase. Amount of breathiness | |||||
// determined by parameter Aturb Use nrand rather than noise because | |||||
// noise is low-passed. | |||||
if (kt_globals.nper < kt_globals.nopen) | if (kt_globals.nper < kt_globals.nopen) | ||||
voice += kt_globals.amp_breth * kt_globals.nrand; | voice += kt_globals.amp_breth * kt_globals.nrand; | ||||
/* Set amplitude of voicing */ | |||||
// Set amplitude of voicing | |||||
glotout = kt_globals.amp_voice * voice; | glotout = kt_globals.amp_voice * voice; | ||||
par_glotout = kt_globals.par_amp_voice * voice; | par_glotout = kt_globals.par_amp_voice * voice; | ||||
/* Compute aspiration amplitude and add to voicing source */ | |||||
// Compute aspiration amplitude and add to voicing source | |||||
aspiration = kt_globals.amp_aspir * noise; | aspiration = kt_globals.amp_aspir * noise; | ||||
glotout += aspiration; | glotout += aspiration; | ||||
par_glotout += aspiration; | par_glotout += aspiration; | ||||
/* | |||||
Cascade vocal tract, excited by laryngeal sources. | |||||
Nasal antiresonator, then formants FNP, F5, F4, F3, F2, F1 | |||||
*/ | |||||
// Cascade vocal tract, excited by laryngeal sources. | |||||
// Nasal antiresonator, then formants FNP, F5, F4, F3, F2, F1 | |||||
out = 0; | out = 0; | ||||
if (kt_globals.synthesis_model != ALL_PARALLEL) { | if (kt_globals.synthesis_model != ALL_PARALLEL) { | ||||
out = resonator2(&(kt_globals.rsn[R1c]), casc_next_in); | out = resonator2(&(kt_globals.rsn[R1c]), casc_next_in); | ||||
} | } | ||||
/* Excite parallel F1 and FNP by voicing waveform */ | |||||
sourc = par_glotout; /* Source is voicing plus aspiration */ | |||||
// Excite parallel F1 and FNP by voicing waveform | |||||
sourc = par_glotout; // Source is voicing plus aspiration | |||||
/* | |||||
Standard parallel vocal tract Formants F6,F5,F4,F3,F2, | |||||
outputs added with alternating sign. Sound source for other | |||||
parallel resonators is frication plus first difference of | |||||
voicing waveform. | |||||
*/ | |||||
// Standard parallel vocal tract Formants F6,F5,F4,F3,F2, | |||||
// outputs added with alternating sign. Sound source for other | |||||
// parallel resonators is frication plus first difference of | |||||
// voicing waveform. | |||||
out += resonator(&(kt_globals.rsn[R1p]), sourc); | out += resonator(&(kt_globals.rsn[R1p]), sourc); | ||||
out += resonator(&(kt_globals.rsn[Rnpp]), sourc); | out += resonator(&(kt_globals.rsn[Rnpp]), sourc); | ||||
out = outbypas - out; | out = outbypas - out; | ||||
out = resonator(&(kt_globals.rsn[Rout]), out); | out = resonator(&(kt_globals.rsn[Rout]), out); | ||||
temp = (int)(out * wdata.amplitude * kt_globals.amp_gain0); /* Convert back to integer */ | |||||
temp = (int)(out * wdata.amplitude * kt_globals.amp_gain0); // Convert back to integer | |||||
// mix with a recorded WAV if required for this phoneme | // mix with a recorded WAV if required for this phoneme | ||||
{ | |||||
int z2; | |||||
signed char c; | |||||
int sample; | |||||
z2 = 0; | |||||
if (wdata.mix_wavefile_ix < wdata.n_mix_wavefile) { | |||||
if (wdata.mix_wave_scale == 0) { | |||||
// a 16 bit sample | |||||
c = wdata.mix_wavefile[wdata.mix_wavefile_ix+1]; | |||||
sample = wdata.mix_wavefile[wdata.mix_wavefile_ix] + (c * 256); | |||||
wdata.mix_wavefile_ix += 2; | |||||
} else { | |||||
// a 8 bit sample, scaled | |||||
sample = (signed char)wdata.mix_wavefile[wdata.mix_wavefile_ix++] * wdata.mix_wave_scale; | |||||
} | |||||
z2 = sample * wdata.amplitude_v / 1024; | |||||
z2 = (z2 * wdata.mix_wave_amp)/40; | |||||
temp += z2; | |||||
int z2; | |||||
signed char c; | |||||
int sample; | |||||
z2 = 0; | |||||
if (wdata.mix_wavefile_ix < wdata.n_mix_wavefile) { | |||||
if (wdata.mix_wave_scale == 0) { | |||||
// a 16 bit sample | |||||
c = wdata.mix_wavefile[wdata.mix_wavefile_ix+1]; | |||||
sample = wdata.mix_wavefile[wdata.mix_wavefile_ix] + (c * 256); | |||||
wdata.mix_wavefile_ix += 2; | |||||
} else { | |||||
// a 8 bit sample, scaled | |||||
sample = (signed char)wdata.mix_wavefile[wdata.mix_wavefile_ix++] * wdata.mix_wave_scale; | |||||
} | } | ||||
z2 = sample * wdata.amplitude_v / 1024; | |||||
z2 = (z2 * wdata.mix_wave_amp)/40; | |||||
temp += z2; | |||||
} | } | ||||
// if fadeout is set, fade to zero over 64 samples, to avoid clicks at end of synthesis | // if fadeout is set, fade to zero over 64 samples, to avoid clicks at end of synthesis | ||||
kt_globals.minus_pi_t = -PI / kt_globals.samrate; | kt_globals.minus_pi_t = -PI / kt_globals.samrate; | ||||
kt_globals.two_pi_t = -2.0 * kt_globals.minus_pi_t; | kt_globals.two_pi_t = -2.0 * kt_globals.minus_pi_t; | ||||
setabc(kt_globals.FLPhz, kt_globals.BLPhz, &(kt_globals.rsn[RLP])); | setabc(kt_globals.FLPhz, kt_globals.BLPhz, &(kt_globals.rsn[RLP])); | ||||
} | } | ||||
if (control > 0) { | if (control > 0) { | ||||
Gain0_tmp = 57; | Gain0_tmp = 57; | ||||
kt_globals.amp_gain0 = DBtoLIN(Gain0_tmp) / kt_globals.scale_wav; | kt_globals.amp_gain0 = DBtoLIN(Gain0_tmp) / kt_globals.scale_wav; | ||||
/* Set coefficients of variable cascade resonators */ | |||||
// Set coefficients of variable cascade resonators | |||||
for (ix = 1; ix <= 9; ix++) { | for (ix = 1; ix <= 9; ix++) { | ||||
// formants 1 to 8, plus nasal pole | // formants 1 to 8, plus nasal pole | ||||
setabc(frame->Fhz[ix], frame->Bhz[ix], &(kt_globals.rsn[ix])); | setabc(frame->Fhz[ix], frame->Bhz[ix], &(kt_globals.rsn[ix])); | ||||
kt_globals.rsn[F_NZ].b_inc = (kt_globals.rsn_next[F_NZ].b - kt_globals.rsn[F_NZ].b) / 64.0; | kt_globals.rsn[F_NZ].b_inc = (kt_globals.rsn_next[F_NZ].b - kt_globals.rsn[F_NZ].b) / 64.0; | ||||
kt_globals.rsn[F_NZ].c_inc = (kt_globals.rsn_next[F_NZ].c - kt_globals.rsn[F_NZ].c) / 64.0; | kt_globals.rsn[F_NZ].c_inc = (kt_globals.rsn_next[F_NZ].c - kt_globals.rsn[F_NZ].c) / 64.0; | ||||
/* Set coefficients of parallel resonators, and amplitude of outputs */ | |||||
// Set coefficients of parallel resonators, and amplitude of outputs | |||||
for (ix = 0; ix <= 6; ix++) { | for (ix = 0; ix <= 6; ix++) { | ||||
setabc(frame->Fhz[ix], frame->Bphz[ix], &(kt_globals.rsn[Rparallel+ix])); | setabc(frame->Fhz[ix], frame->Bphz[ix], &(kt_globals.rsn[Rparallel+ix])); | ||||
kt_globals.rsn[Rparallel+ix].a *= amp_par[ix]; | kt_globals.rsn[Rparallel+ix].a *= amp_par[ix]; | ||||
} | } | ||||
/* output low-pass filter */ | |||||
// output low-pass filter | |||||
setabc((long)0.0, (long)(kt_globals.samrate/2), &(kt_globals.rsn[Rout])); | setabc((long)0.0, (long)(kt_globals.samrate/2), &(kt_globals.rsn[Rout])); | ||||
} | } | ||||
static long skew; | static long skew; | ||||
static short B0[224] = { | static short B0[224] = { | ||||
1200, 1142, 1088, 1038, 991, 948, 907, 869, 833, 799, 768, 738, 710, 683, 658, | 1200, 1142, 1088, 1038, 991, 948, 907, 869, 833, 799, 768, 738, 710, 683, 658, | ||||
634, 612, 590, 570, 551, 533, 515, 499, 483, 468, 454, 440, 427, 415, 403, | |||||
391, 380, 370, 360, 350, 341, 332, 323, 315, 307, 300, 292, 285, 278, 272, | |||||
265, 259, 253, 247, 242, 237, 231, 226, 221, 217, 212, 208, 204, 199, 195, | |||||
192, 188, 184, 180, 177, 174, 170, 167, 164, 161, 158, 155, 153, 150, 147, | |||||
145, 142, 140, 137, 135, 133, 131, 128, 126, 124, 122, 120, 119, 117, 115, | |||||
113, 111, 110, 108, 106, 105, 103, 102, 100, 99, 97, 96, 95, 93, 92, 91, 90, | |||||
88, 87, 86, 85, 84, 83, 82, 80, 79, 78, 77, 76, 75, 75, 74, 73, 72, 71, | |||||
70, 69, 68, 68, 67, 66, 65, 64, 64, 63, 62, 61, 61, 60, 59, 59, 58, 57, | |||||
57, 56, 56, 55, 55, 54, 54, 53, 53, 52, 52, 51, 51, 50, 50, 49, 49, 48, 48, | |||||
47, 47, 46, 46, 45, 45, 44, 44, 43, 43, 42, 42, 41, 41, 41, 41, 40, 40, | |||||
39, 39, 38, 38, 38, 38, 37, 37, 36, 36, 36, 36, 35, 35, 35, 35, 34, 34, 33, | |||||
33, 33, 33, 32, 32, 32, 32, 31, 31, 31, 31, 30, 30, 30, 30, 29, 29, 29, 29, | |||||
28, 28, 28, 28, 27, 27 | |||||
634, 612, 590, 570, 551, 533, 515, 499, 483, 468, 454, 440, 427, 415, 403, | |||||
391, 380, 370, 360, 350, 341, 332, 323, 315, 307, 300, 292, 285, 278, 272, | |||||
265, 259, 253, 247, 242, 237, 231, 226, 221, 217, 212, 208, 204, 199, 195, | |||||
192, 188, 184, 180, 177, 174, 170, 167, 164, 161, 158, 155, 153, 150, 147, | |||||
145, 142, 140, 137, 135, 133, 131, 128, 126, 124, 122, 120, 119, 117, 115, | |||||
113, 111, 110, 108, 106, 105, 103, 102, 100, 99, 97, 96, 95, 93, 92, 91, 90, | |||||
88, 87, 86, 85, 84, 83, 82, 80, 79, 78, 77, 76, 75, 75, 74, 73, 72, 71, | |||||
70, 69, 68, 68, 67, 66, 65, 64, 64, 63, 62, 61, 61, 60, 59, 59, 58, 57, | |||||
57, 56, 56, 55, 55, 54, 54, 53, 53, 52, 52, 51, 51, 50, 50, 49, 49, 48, 48, | |||||
47, 47, 46, 46, 45, 45, 44, 44, 43, 43, 42, 42, 41, 41, 41, 41, 40, 40, | |||||
39, 39, 38, 38, 38, 38, 37, 37, 36, 36, 36, 36, 35, 35, 35, 35, 34, 34, 33, | |||||
33, 33, 33, 32, 32, 32, 32, 31, 31, 31, 31, 30, 30, 30, 30, 29, 29, 29, 29, | |||||
28, 28, 28, 28, 27, 27 | |||||
}; | }; | ||||
if (frame->F0hz10 > 0) { | if (frame->F0hz10 > 0) { | ||||
/* T0 is 4* the number of samples in one pitch period */ | |||||
// T0 is 4* the number of samples in one pitch period | |||||
kt_globals.T0 = (40 * kt_globals.samrate) / frame->F0hz10; | kt_globals.T0 = (40 * kt_globals.samrate) / frame->F0hz10; | ||||
kt_globals.amp_voice = DBtoLIN(frame->AVdb_tmp); | kt_globals.amp_voice = DBtoLIN(frame->AVdb_tmp); | ||||
/* Duration of period before amplitude modulation */ | |||||
// Duration of period before amplitude modulation | |||||
kt_globals.nmod = kt_globals.T0; | kt_globals.nmod = kt_globals.T0; | ||||
if (frame->AVdb_tmp > 0) | if (frame->AVdb_tmp > 0) | ||||
kt_globals.nmod >>= 1; | kt_globals.nmod >>= 1; | ||||
/* Breathiness of voicing waveform */ | |||||
// Breathiness of voicing waveform | |||||
kt_globals.amp_breth = DBtoLIN(frame->Aturb) * 0.1; | kt_globals.amp_breth = DBtoLIN(frame->Aturb) * 0.1; | ||||
/* Set open phase of glottal period where 40 <= open phase <= 263 */ | |||||
// Set open phase of glottal period where 40 <= open phase <= 263 | |||||
kt_globals.nopen = 4 * frame->Kopen; | kt_globals.nopen = 4 * frame->Kopen; | ||||
kt_globals.nopen = kt_globals.T0 - 2; | kt_globals.nopen = kt_globals.T0 - 2; | ||||
if (kt_globals.nopen < 40) { | if (kt_globals.nopen < 40) { | ||||
/* F0 max = 1000 Hz */ | |||||
// F0 max = 1000 Hz | |||||
kt_globals.nopen = 40; | kt_globals.nopen = 40; | ||||
} | } | ||||
/* Reset a & b, which determine shape of "natural" glottal waveform */ | |||||
// Reset a & b, which determine shape of "natural" glottal waveform | |||||
kt_globals.pulse_shape_b = B0[kt_globals.nopen-40]; | kt_globals.pulse_shape_b = B0[kt_globals.nopen-40]; | ||||
kt_globals.pulse_shape_a = (kt_globals.pulse_shape_b * kt_globals.nopen) * 0.333; | kt_globals.pulse_shape_a = (kt_globals.pulse_shape_b * kt_globals.nopen) * 0.333; | ||||
/* Reset width of "impulsive" glottal pulse */ | |||||
// Reset width of "impulsive" glottal pulse | |||||
temp = kt_globals.samrate / kt_globals.nopen; | temp = kt_globals.samrate / kt_globals.nopen; | ||||
setabc((long)0, temp, &(kt_globals.rsn[RGL])); | setabc((long)0, temp, &(kt_globals.rsn[RGL])); | ||||
/* Make gain at F1 about constant */ | |||||
// Make gain at F1 about constant | |||||
temp1 = kt_globals.nopen *.00833; | temp1 = kt_globals.nopen *.00833; | ||||
kt_globals.rsn[RGL].a *= temp1 * temp1; | kt_globals.rsn[RGL].a *= temp1 * temp1; | ||||
/* | |||||
Truncate skewness so as not to exceed duration of closed phase | |||||
of glottal period. | |||||
*/ | |||||
// Truncate skewness so as not to exceed duration of closed phase | |||||
// of glottal period. | |||||
temp = kt_globals.T0 - kt_globals.nopen; | temp = kt_globals.T0 - kt_globals.nopen; | ||||
if (frame->Kskew > temp) | if (frame->Kskew > temp) | ||||
else | else | ||||
skew = -frame->Kskew; | skew = -frame->Kskew; | ||||
/* Add skewness to closed portion of voicing period */ | |||||
// Add skewness to closed portion of voicing period | |||||
kt_globals.T0 = kt_globals.T0 + skew; | kt_globals.T0 = kt_globals.T0 + skew; | ||||
skew = -skew; | skew = -skew; | ||||
} else { | } else { | ||||
kt_globals.T0 = 4; /* Default for f0 undefined */ | |||||
kt_globals.T0 = 4; // Default for f0 undefined | |||||
kt_globals.amp_voice = 0.0; | kt_globals.amp_voice = 0.0; | ||||
kt_globals.nmod = kt_globals.T0; | kt_globals.nmod = kt_globals.T0; | ||||
kt_globals.amp_breth = 0.0; | kt_globals.amp_breth = 0.0; | ||||
kt_globals.pulse_shape_b = 0.0; | kt_globals.pulse_shape_b = 0.0; | ||||
} | } | ||||
/* Reset these pars pitch synchronously or at update rate if f0=0 */ | |||||
// Reset these pars pitch synchronously or at update rate if f0=0 | |||||
if ((kt_globals.T0 != 4) || (kt_globals.ns == 0)) { | if ((kt_globals.T0 != 4) || (kt_globals.ns == 0)) { | ||||
/* Set one-pole low-pass filter that tilts glottal source */ | |||||
// Set one-pole low-pass filter that tilts glottal source | |||||
kt_globals.decay = (0.033 * frame->TLTdb); | kt_globals.decay = (0.033 * frame->TLTdb); | ||||
double r; | double r; | ||||
double arg; | double arg; | ||||
/* Let r = exp(-pi bw t) */ | |||||
// Let r = exp(-pi bw t) | |||||
arg = kt_globals.minus_pi_t * bw; | arg = kt_globals.minus_pi_t * bw; | ||||
r = exp(arg); | r = exp(arg); | ||||
/* Let c = -r**2 */ | |||||
// Let c = -r**2 | |||||
rp->c = -(r * r); | rp->c = -(r * r); | ||||
/* Let b = r * 2*cos(2 pi f t) */ | |||||
// Let b = r * 2*cos(2 pi f t) | |||||
arg = kt_globals.two_pi_t * f; | arg = kt_globals.two_pi_t * f; | ||||
rp->b = r * cos(arg) * 2.0; | rp->b = r * cos(arg) * 2.0; | ||||
/* Let a = 1.0 - b - c */ | |||||
// Let a = 1.0 - b - c | |||||
rp->a = 1.0 - rp->b - rp->c; | rp->a = 1.0 - rp->b - rp->c; | ||||
} | } | ||||
f = -f; | f = -f; | ||||
/* First compute ordinary resonator coefficients */ | |||||
/* Let r = exp(-pi bw t) */ | |||||
// First compute ordinary resonator coefficients | |||||
// Let r = exp(-pi bw t) | |||||
arg = kt_globals.minus_pi_t * bw; | arg = kt_globals.minus_pi_t * bw; | ||||
r = exp(arg); | r = exp(arg); | ||||
/* Let c = -r**2 */ | |||||
// Let c = -r**2 | |||||
rp->c = -(r * r); | rp->c = -(r * r); | ||||
/* Let b = r * 2*cos(2 pi f t) */ | |||||
// Let b = r * 2*cos(2 pi f t) | |||||
arg = kt_globals.two_pi_t * f; | arg = kt_globals.two_pi_t * f; | ||||
rp->b = r * cos(arg) * 2.; | rp->b = r * cos(arg) * 2.; | ||||
/* Let a = 1.0 - b - c */ | |||||
// Let a = 1.0 - b - c | |||||
rp->a = 1.0 - rp->b - rp->c; | rp->a = 1.0 - rp->b - rp->c; | ||||
/* Now convert to antiresonator coefficients (a'=1/a, b'=b/a, c'=c/a) */ | |||||
/* If f == 0 then rp->a gets set to 0 which makes a'=1/a set a', b' and c' to | |||||
* INF, causing an audible sound spike when triggered (e.g. apiration with the | |||||
* nasal register set to f=0, bw=0). | |||||
*/ | |||||
// Now convert to antiresonator coefficients (a'=1/a, b'=b/a, c'=c/a) | |||||
// If f == 0 then rp->a gets set to 0 which makes a'=1/a set a', b' and c' to | |||||
// INF, causing an audible sound spike when triggered (e.g. apiration with the | |||||
// nasal register set to f=0, bw=0). | |||||
if (rp->a != 0) { | if (rp->a != 0) { | ||||
/* Now convert to antiresonator coefficients (a'=1/a, b'=b/a, c'=c/a) */ | |||||
// Now convert to antiresonator coefficients (a'=1/a, b'=b/a, c'=c/a) | |||||
rp->a = 1.0 / rp->a; | rp->a = 1.0 / rp->a; | ||||
rp->c *= -rp->a; | rp->c *= -rp->a; | ||||
rp->b *= -rp->a; | rp->b *= -rp->a; | ||||
static double DBtoLIN(long dB) | static double DBtoLIN(long dB) | ||||
{ | { | ||||
static short amptable[88] = { | static short amptable[88] = { | ||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 7, | |||||
8, 9, 10, 11, 13, 14, 16, 18, 20, 22, 25, 28, 32, | |||||
35, 40, 45, 51, 57, 64, 71, 80, 90, 101, 114, 128, | |||||
142, 159, 179, 202, 227, 256, 284, 318, 359, 405, | |||||
455, 512, 568, 638, 719, 881, 911, 1024, 1137, 1276, | |||||
1438, 1622, 1823, 2048, 2273, 2552, 2875, 3244, 3645, | |||||
4096, 4547, 5104, 5751, 6488, 7291, 8192, 9093, 10207, | |||||
11502, 12976, 14582, 16384, 18350, 20644, 23429, | |||||
26214, 29491, 32767 | |||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 7, | |||||
8, 9, 10, 11, 13, 14, 16, 18, 20, 22, 25, 28, 32, | |||||
35, 40, 45, 51, 57, 64, 71, 80, 90, 101, 114, 128, | |||||
142, 159, 179, 202, 227, 256, 284, 318, 359, 405, | |||||
455, 512, 568, 638, 719, 881, 911, 1024, 1137, 1276, | |||||
1438, 1622, 1823, 2048, 2273, 2552, 2875, 3244, 3645, | |||||
4096, 4547, 5104, 5751, 6488, 7291, 8192, 9093, 10207, | |||||
11502, 12976, 14582, 16384, 18350, 20644, 23429, | |||||
26214, 29491, 32767 | |||||
}; | }; | ||||
if ((dB < 0) || (dB > 87)) | if ((dB < 0) || (dB > 87)) | ||||
if (kt_globals.nspfr > STEPSIZE) | if (kt_globals.nspfr > STEPSIZE) | ||||
kt_globals.nspfr = STEPSIZE; | kt_globals.nspfr = STEPSIZE; | ||||
frame_init(&kt_frame); /* get parameters for next frame of speech */ | |||||
frame_init(&kt_frame); // get parameters for next frame of speech | |||||
if (parwave(&kt_frame) == 1) | if (parwave(&kt_frame) == 1) | ||||
return 1; // output buffer is full | |||||
return 1; // output buffer is full | |||||
} | } | ||||
if (end_wave > 0) { | if (end_wave > 0) { | ||||
fade = 64; // not followd by formant synthesis | |||||
fade = 64; // not followed by formant synthesis | |||||
// fade out to avoid a click | // fade out to avoid a click | ||||
kt_globals.fadeout = fade; | kt_globals.fadeout = fade; | ||||
sample_count -= fade; | sample_count -= fade; | ||||
kt_globals.nspfr = fade; | kt_globals.nspfr = fade; | ||||
if (parwave(&kt_frame) == 1) | if (parwave(&kt_frame) == 1) | ||||
return 1; // output buffer is full | |||||
return 1; // output buffer is full | |||||
} | } | ||||
return 0; | return 0; | ||||
end_wave = 0; | end_wave = 0; | ||||
if (control & 2) | if (control & 2) | ||||
end_wave = 1; // fadeout at the end | |||||
end_wave = 1; // fadeout at the end | |||||
if (control & 1) { | if (control & 1) { | ||||
end_wave = 1; | end_wave = 1; | ||||
for (qix = wcmdq_head+1;; qix++) { | for (qix = wcmdq_head+1;; qix++) { | ||||
cmd = wcmdq[qix][0]; | cmd = wcmdq[qix][0]; | ||||
if (cmd == WCMD_KLATT) { | if (cmd == WCMD_KLATT) { | ||||
end_wave = 0; // next wave generation is from another spectrum | |||||
end_wave = 0; // next wave generation is from another spectrum | |||||
fr3 = (frame_t *)wcmdq[qix][2]; | fr3 = (frame_t *)wcmdq[qix][2]; | ||||
for (ix = 1; ix < 6; ix++) { | for (ix = 1; ix < 6; ix++) { | ||||
break; | break; | ||||
} | } | ||||
if ((cmd == WCMD_WAVE) || (cmd == WCMD_PAUSE)) | if ((cmd == WCMD_WAVE) || (cmd == WCMD_PAUSE)) | ||||
break; // next is not from spectrum, so continue until end of wave cycle | |||||
break; // next is not from spectrum, so continue until end of wave cycle | |||||
} | } | ||||
} | } | ||||
// nasal zero frequency | // nasal zero frequency | ||||
peaks[0].freq1 = fr1->klattp[KLATT_FNZ] * 2; | peaks[0].freq1 = fr1->klattp[KLATT_FNZ] * 2; | ||||
if (peaks[0].freq1 == 0) | if (peaks[0].freq1 == 0) | ||||
peaks[0].freq1 = kt_frame.Fhz[F_NP]; // if no nasal zero, set it to same freq as nasal pole | |||||
peaks[0].freq1 = kt_frame.Fhz[F_NP]; // if no nasal zero, set it to same freq as nasal pole | |||||
peaks[0].freq = (int)peaks[0].freq1; | peaks[0].freq = (int)peaks[0].freq1; | ||||
next = fr2->klattp[KLATT_FNZ] * 2; | next = fr2->klattp[KLATT_FNZ] * 2; | ||||
if (fr1->frflags & FRFLAG_KLATT) { | if (fr1->frflags & FRFLAG_KLATT) { | ||||
// the frame contains additional parameters for parallel resonators | // the frame contains additional parameters for parallel resonators | ||||
for (ix = 1; ix < 7; ix++) { | for (ix = 1; ix < 7; ix++) { | ||||
peaks[ix].bp1 = fr1->klatt_bp[ix] * 4; // parallel bandwidth | |||||
peaks[ix].bp1 = fr1->klatt_bp[ix] * 4; // parallel bandwidth | |||||
peaks[ix].bp = (int)peaks[ix].bp1; | peaks[ix].bp = (int)peaks[ix].bp1; | ||||
next = fr2->klatt_bp[ix] * 4; | next = fr2->klatt_bp[ix] * 4; | ||||
peaks[ix].bp_inc = ((next - peaks[ix].bp1) * STEPSIZE) / length; | peaks[ix].bp_inc = ((next - peaks[ix].bp1) * STEPSIZE) / length; | ||||
peaks[ix].ap1 = fr1->klatt_ap[ix]; // parallal amplitude | |||||
peaks[ix].ap1 = fr1->klatt_ap[ix]; // parallal amplitude | |||||
peaks[ix].ap = (int)peaks[ix].ap1; | peaks[ix].ap = (int)peaks[ix].ap1; | ||||
next = fr2->klatt_ap[ix]; | next = fr2->klatt_ap[ix]; | ||||
peaks[ix].ap_inc = ((next - peaks[ix].ap1) * STEPSIZE) / length; | peaks[ix].ap_inc = ((next - peaks[ix].ap1) * STEPSIZE) / length; | ||||
kt_globals.synthesis_model = CASCADE_PARALLEL; | kt_globals.synthesis_model = CASCADE_PARALLEL; | ||||
kt_globals.samrate = 22050; | kt_globals.samrate = 22050; | ||||
kt_globals.glsource = IMPULSIVE; // IMPULSIVE, NATURAL, SAMPLED | |||||
kt_globals.glsource = IMPULSIVE; | |||||
kt_globals.scale_wav = scale_wav_tab[kt_globals.glsource]; | kt_globals.scale_wav = scale_wav_tab[kt_globals.glsource]; | ||||
kt_globals.natural_samples = natural_samples; | kt_globals.natural_samples = natural_samples; | ||||
kt_globals.num_samples = NUMBER_OF_SAMPLES; | kt_globals.num_samples = NUMBER_OF_SAMPLES; | ||||
kt_frame.Bhz_next[F_NZ] = bandwidth[F_NZ]; | kt_frame.Bhz_next[F_NZ] = bandwidth[F_NZ]; | ||||
kt_frame.F0hz10 = 1000; | kt_frame.F0hz10 = 1000; | ||||
kt_frame.AVdb = 59; // 59 | |||||
kt_frame.AVdb = 59; | |||||
kt_frame.ASP = 0; | kt_frame.ASP = 0; | ||||
kt_frame.Kopen = 40; // 40 | |||||
kt_frame.Kopen = 40; | |||||
kt_frame.Aturb = 0; | kt_frame.Aturb = 0; | ||||
kt_frame.TLTdb = 0; | kt_frame.TLTdb = 0; | ||||
kt_frame.AF = 50; | kt_frame.AF = 50; | ||||
kt_frame.Kskew = 0; | kt_frame.Kskew = 0; | ||||
kt_frame.AB = 0; | kt_frame.AB = 0; | ||||
kt_frame.AVpdb = 0; | kt_frame.AVpdb = 0; | ||||
kt_frame.Gain0 = 62; // 60 | |||||
kt_frame.Gain0 = 62; | |||||
} | } |
struct datablock *next; | struct datablock *next; | ||||
int done; | int done; | ||||
int size; | int size; | ||||
char buffer[1]; /* 1 or more, dynamically allocated */ | |||||
char buffer[1]; // 1 or more, dynamically allocated | |||||
}; | }; | ||||
static struct datablock *mbr_pending_data_head, *mbr_pending_data_tail; | static struct datablock *mbr_pending_data_head, *mbr_pending_data_tail; | ||||
} | } | ||||
if (result == 0) { | if (result == 0) { | ||||
/* EOF on stderr, assume mbrola died. */ | |||||
// EOF on stderr, assume mbrola died. | |||||
return mbrola_died(); | return mbrola_died(); | ||||
} | } | ||||
buf_ptr[result] = 0; | buf_ptr[result] = 0; | ||||
for (; (lf = strchr(buf_ptr, '\n')); buf_ptr = lf + 1) { | for (; (lf = strchr(buf_ptr, '\n')); buf_ptr = lf + 1) { | ||||
/* inhibit the reset signal messages */ | |||||
// inhibit the reset signal messages | |||||
if (strncmp(buf_ptr, "Got a reset signal", 18) == 0 || | if (strncmp(buf_ptr, "Got a reset signal", 18) == 0 || | ||||
strncmp(buf_ptr, "Input Flush Signal", 18) == 0) | strncmp(buf_ptr, "Input Flush Signal", 18) == 0) | ||||
continue; | continue; | ||||
*lf = 0; | *lf = 0; | ||||
log("mbrola: %s", buf_ptr); | log("mbrola: %s", buf_ptr); | ||||
/* is this the last line? */ | |||||
// is this the last line? | |||||
if (lf == &buf_ptr[result - 1]) { | if (lf == &buf_ptr[result - 1]) { | ||||
snprintf(mbr_errorbuf, sizeof(mbr_errorbuf), | snprintf(mbr_errorbuf, sizeof(mbr_errorbuf), | ||||
"%s", buf_ptr); | "%s", buf_ptr); | ||||
/* don't consider this fatal at this point */ | |||||
// don't consider this fatal at this point | |||||
return 0; | return 0; | ||||
} | } | ||||
} | } | ||||
static int mbrola_is_idle(void) | static int mbrola_is_idle(void) | ||||
{ | { | ||||
char *p; | char *p; | ||||
char buffer[20]; /* looking for "12345 (mbrola) S" so 20 is plenty*/ | |||||
char buffer[20]; // looking for "12345 (mbrola) S" so 20 is plenty | |||||
/* look in /proc to determine if mbrola is still running or sleeping */ | |||||
// look in /proc to determine if mbrola is still running or sleeping | |||||
if (lseek(mbr_proc_stat, 0, SEEK_SET) != 0) | if (lseek(mbr_proc_stat, 0, SEEK_SET) != 0) | ||||
return 0; | return 0; | ||||
if (read(mbr_proc_stat, buffer, sizeof(buffer)) != sizeof(buffer)) | if (read(mbr_proc_stat, buffer, sizeof(buffer)) != sizeof(buffer)) | ||||
return -1; | return -1; | ||||
} | } | ||||
/* we should actually be getting only 44 bytes */ | |||||
// we should actually be getting only 44 bytes | |||||
result = receive_from_mbrola(wavhdr, 45); | result = receive_from_mbrola(wavhdr, 45); | ||||
if (result != 44) { | if (result != 44) { | ||||
if (result >= 0) | if (result >= 0) | ||||
return -1; | return -1; | ||||
} | } | ||||
/* parse wavhdr to get mbrola voice samplerate */ | |||||
// parse wavhdr to get mbrola voice samplerate | |||||
if (memcmp(wavhdr, "RIFF", 4) != 0 || | if (memcmp(wavhdr, "RIFF", 4) != 0 || | ||||
memcmp(wavhdr+8, "WAVEfmt ", 8) != 0) { | memcmp(wavhdr+8, "WAVEfmt ", 8) != 0) { | ||||
err("mbrola did not return a .wav header"); | err("mbrola did not return a .wav header"); | ||||
} | } | ||||
mbr_samplerate = wavhdr[24] + (wavhdr[25]<<8) + | mbr_samplerate = wavhdr[24] + (wavhdr[25]<<8) + | ||||
(wavhdr[26]<<16) + (wavhdr[27]<<24); | (wavhdr[26]<<16) + (wavhdr[27]<<24); | ||||
// log("mbrowrap: voice samplerate = %d", mbr_samplerate); | |||||
/* remember the voice path for setVolumeRatio_MBR() */ | |||||
// remember the voice path for setVolumeRatio_MBR() | |||||
if (mbr_voice_path != voice_path) { | if (mbr_voice_path != voice_path) { | ||||
free(mbr_voice_path); | free(mbr_voice_path); | ||||
mbr_voice_path = strdup(voice_path); | mbr_voice_path = strdup(voice_path); |
#define M_RETROFLEX 20 | #define M_RETROFLEX 20 | ||||
#define M_HOOK 21 | #define M_HOOK 21 | ||||
#define M_MIDDLE_DOT M_DOT_ABOVE // duplicate of M_DOT_ABOVE | |||||
#define M_MIDDLE_DOT M_DOT_ABOVE // duplicate of M_DOT_ABOVE | |||||
#define M_IMPLOSIVE M_HOOK | #define M_IMPLOSIVE M_HOOK | ||||
static int n_digit_lookup; | static int n_digit_lookup; | ||||
#define LETTER(ch, mod1, mod2) (ch-59)+(mod1 << 6)+(mod2 << 11) | #define LETTER(ch, mod1, mod2) (ch-59)+(mod1 << 6)+(mod2 << 11) | ||||
#define LIGATURE(ch1, ch2, mod1) (ch1-59)+((ch2-59) << 6)+(mod1 << 12)+M_LIGATURE | #define LIGATURE(ch1, ch2, mod1) (ch1-59)+((ch2-59) << 6)+(mod1 << 12)+M_LIGATURE | ||||
#define L_ALPHA 60 // U+3B1 | |||||
#define L_SCHWA 61 // U+259 | |||||
#define L_OPEN_E 62 // U+25B | |||||
#define L_GAMMA 63 // U+3B3 | |||||
#define L_IOTA 64 // U+3B9 | |||||
#define L_OE 65 // U+153 | |||||
#define L_OMEGA 66 // U+3C9 | |||||
#define L_ALPHA 60 // U+3B1 | |||||
#define L_SCHWA 61 // U+259 | |||||
#define L_OPEN_E 62 // U+25B | |||||
#define L_GAMMA 63 // U+3B3 | |||||
#define L_IOTA 64 // U+3B9 | |||||
#define L_OE 65 // U+153 | |||||
#define L_OMEGA 66 // U+3C9 | |||||
#define L_PHI 67 // U+3C6 | |||||
#define L_ESH 68 // U+283 | |||||
#define L_PHI 67 // U+3C6 | |||||
#define L_ESH 68 // U+283 | |||||
#define L_UPSILON 69 // U+3C5 | #define L_UPSILON 69 // U+3C5 | ||||
#define L_EZH 70 // U+292 | #define L_EZH 70 // U+292 | ||||
#define L_GLOTTAL 71 // U+294 | #define L_GLOTTAL 71 // U+294 | ||||
#define L_RLONG 73 // U+27C | #define L_RLONG 73 // U+27C | ||||
static const short non_ascii_tab[] = { | static const short non_ascii_tab[] = { | ||||
0, 0x3b1, 0x259, 0x25b, 0x3b3, 0x3b9, 0x153, 0x3c9, | |||||
0, | |||||
0x3b1, 0x259, 0x25b, 0x3b3, 0x3b9, 0x153, 0x3c9, | |||||
0x3c6, 0x283, 0x3c5, 0x292, 0x294, 0x27e, 0x27c | 0x3c6, 0x283, 0x3c5, 0x292, 0x294, 0x27e, 0x27c | ||||
}; | }; | ||||
// characters U+00e0 to U+017f | // characters U+00e0 to U+017f | ||||
static const unsigned short letter_accents_0e0[] = { | static const unsigned short letter_accents_0e0[] = { | ||||
LETTER('a', M_GRAVE, 0), // U+00e0 | |||||
LETTER('a', M_GRAVE, 0), // U+00e0 | |||||
LETTER('a', M_ACUTE, 0), | LETTER('a', M_ACUTE, 0), | ||||
LETTER('a', M_CIRCUMFLEX, 0), | LETTER('a', M_CIRCUMFLEX, 0), | ||||
LETTER('a', M_TILDE, 0), | LETTER('a', M_TILDE, 0), | ||||
LETTER('i', M_ACUTE, 0), | LETTER('i', M_ACUTE, 0), | ||||
LETTER('i', M_CIRCUMFLEX, 0), | LETTER('i', M_CIRCUMFLEX, 0), | ||||
LETTER('i', M_DIAERESIS, 0), | LETTER('i', M_DIAERESIS, 0), | ||||
LETTER('d', M_NAME, 0), // eth // U+00f0 | |||||
LETTER('d', M_NAME, 0), // eth U+00f0 | |||||
LETTER('n', M_TILDE, 0), | LETTER('n', M_TILDE, 0), | ||||
LETTER('o', M_GRAVE, 0), | LETTER('o', M_GRAVE, 0), | ||||
LETTER('o', M_ACUTE, 0), | LETTER('o', M_ACUTE, 0), | ||||
LETTER('o', M_CIRCUMFLEX, 0), | LETTER('o', M_CIRCUMFLEX, 0), | ||||
LETTER('o', M_TILDE, 0), | LETTER('o', M_TILDE, 0), | ||||
LETTER('o', M_DIAERESIS, 0), | LETTER('o', M_DIAERESIS, 0), | ||||
0, // division sign | |||||
0, // division sign | |||||
LETTER('o', M_STROKE, 0), | LETTER('o', M_STROKE, 0), | ||||
LETTER('u', M_GRAVE, 0), | LETTER('u', M_GRAVE, 0), | ||||
LETTER('u', M_ACUTE, 0), | LETTER('u', M_ACUTE, 0), | ||||
LETTER('u', M_CIRCUMFLEX, 0), | LETTER('u', M_CIRCUMFLEX, 0), | ||||
LETTER('u', M_DIAERESIS, 0), | LETTER('u', M_DIAERESIS, 0), | ||||
LETTER('y', M_ACUTE, 0), | LETTER('y', M_ACUTE, 0), | ||||
LETTER('t', M_NAME, 0), // thorn | |||||
LETTER('t', M_NAME, 0), // thorn | |||||
LETTER('y', M_DIAERESIS, 0), | LETTER('y', M_DIAERESIS, 0), | ||||
CAPITAL, // U+0100 | |||||
CAPITAL, // U+0100 | |||||
LETTER('a', M_MACRON, 0), | LETTER('a', M_MACRON, 0), | ||||
CAPITAL, | CAPITAL, | ||||
LETTER('a', M_BREVE, 0), | LETTER('a', M_BREVE, 0), | ||||
LETTER('c', M_CARON, 0), | LETTER('c', M_CARON, 0), | ||||
CAPITAL, | CAPITAL, | ||||
LETTER('d', M_CARON, 0), | LETTER('d', M_CARON, 0), | ||||
CAPITAL, // U+0110 | |||||
CAPITAL, // U+0110 | |||||
LETTER('d', M_STROKE, 0), | LETTER('d', M_STROKE, 0), | ||||
CAPITAL, | CAPITAL, | ||||
LETTER('e', M_MACRON, 0), | LETTER('e', M_MACRON, 0), | ||||
LETTER('g', M_CIRCUMFLEX, 0), | LETTER('g', M_CIRCUMFLEX, 0), | ||||
CAPITAL, | CAPITAL, | ||||
LETTER('g', M_BREVE, 0), | LETTER('g', M_BREVE, 0), | ||||
CAPITAL, // U+0120 | |||||
CAPITAL, // U+0120 | |||||
LETTER('g', M_DOT_ABOVE, 0), | LETTER('g', M_DOT_ABOVE, 0), | ||||
CAPITAL, | CAPITAL, | ||||
LETTER('g', M_CEDILLA, 0), | LETTER('g', M_CEDILLA, 0), | ||||
LETTER('i', M_BREVE, 0), | LETTER('i', M_BREVE, 0), | ||||
CAPITAL, | CAPITAL, | ||||
LETTER('i', M_OGONEK, 0), | LETTER('i', M_OGONEK, 0), | ||||
CAPITAL, // U+0130 | |||||
CAPITAL, // U+0130 | |||||
LETTER('i', M_NAME, 0), // dotless i | LETTER('i', M_NAME, 0), // dotless i | ||||
CAPITAL, | CAPITAL, | ||||
LIGATURE('i', 'j', 0), | LIGATURE('i', 'j', 0), | ||||
LETTER('j', M_CIRCUMFLEX, 0), | LETTER('j', M_CIRCUMFLEX, 0), | ||||
CAPITAL, | CAPITAL, | ||||
LETTER('k', M_CEDILLA, 0), | LETTER('k', M_CEDILLA, 0), | ||||
LETTER('k', M_NAME, 0), // kra | |||||
LETTER('k', M_NAME, 0), // kra | |||||
CAPITAL, | CAPITAL, | ||||
LETTER('l', M_ACUTE, 0), | LETTER('l', M_ACUTE, 0), | ||||
CAPITAL, | CAPITAL, | ||||
CAPITAL, | CAPITAL, | ||||
LETTER('l', M_CARON, 0), | LETTER('l', M_CARON, 0), | ||||
CAPITAL, | CAPITAL, | ||||
LETTER('l', M_MIDDLE_DOT, 0), // U+0140 | |||||
LETTER('l', M_MIDDLE_DOT, 0), // U+0140 | |||||
CAPITAL, | CAPITAL, | ||||
LETTER('l', M_STROKE, 0), | LETTER('l', M_STROKE, 0), | ||||
CAPITAL, | CAPITAL, | ||||
LETTER('n', M_CEDILLA, 0), | LETTER('n', M_CEDILLA, 0), | ||||
CAPITAL, | CAPITAL, | ||||
LETTER('n', M_CARON, 0), | LETTER('n', M_CARON, 0), | ||||
LETTER('n', M_NAME, 0), // apostrophe n | |||||
LETTER('n', M_NAME, 0), // apostrophe n | |||||
CAPITAL, | CAPITAL, | ||||
LETTER('n', M_NAME, 0), // eng | |||||
LETTER('n', M_NAME, 0), // eng | |||||
CAPITAL, | CAPITAL, | ||||
LETTER('o', M_MACRON, 0), | LETTER('o', M_MACRON, 0), | ||||
CAPITAL, | CAPITAL, | ||||
LETTER('o', M_BREVE, 0), | LETTER('o', M_BREVE, 0), | ||||
CAPITAL, // U+0150 | |||||
CAPITAL, // U+0150 | |||||
LETTER('o', M_DOUBLE_ACUTE, 0), | LETTER('o', M_DOUBLE_ACUTE, 0), | ||||
CAPITAL, | CAPITAL, | ||||
LIGATURE('o', 'e', 0), | LIGATURE('o', 'e', 0), | ||||
LETTER('s', M_CIRCUMFLEX, 0), | LETTER('s', M_CIRCUMFLEX, 0), | ||||
CAPITAL, | CAPITAL, | ||||
LETTER('s', M_CEDILLA, 0), | LETTER('s', M_CEDILLA, 0), | ||||
CAPITAL, // U+0160 | |||||
CAPITAL, // U+0160 | |||||
LETTER('s', M_CARON, 0), | LETTER('s', M_CARON, 0), | ||||
CAPITAL, | CAPITAL, | ||||
LETTER('t', M_CEDILLA, 0), | LETTER('t', M_CEDILLA, 0), | ||||
LETTER('u', M_BREVE, 0), | LETTER('u', M_BREVE, 0), | ||||
CAPITAL, | CAPITAL, | ||||
LETTER('u', M_RING, 0), | LETTER('u', M_RING, 0), | ||||
CAPITAL, // U+0170 | |||||
CAPITAL, // U+0170 | |||||
LETTER('u', M_DOUBLE_ACUTE, 0), | LETTER('u', M_DOUBLE_ACUTE, 0), | ||||
CAPITAL, | CAPITAL, | ||||
LETTER('u', M_OGONEK, 0), | LETTER('u', M_OGONEK, 0), | ||||
LETTER('w', M_CIRCUMFLEX, 0), | LETTER('w', M_CIRCUMFLEX, 0), | ||||
CAPITAL, | CAPITAL, | ||||
LETTER('y', M_CIRCUMFLEX, 0), | LETTER('y', M_CIRCUMFLEX, 0), | ||||
CAPITAL, // Y-DIAERESIS | |||||
CAPITAL, // Y-DIAERESIS | |||||
CAPITAL, | CAPITAL, | ||||
LETTER('z', M_ACUTE, 0), | LETTER('z', M_ACUTE, 0), | ||||
CAPITAL, | CAPITAL, | ||||
LETTER('z', M_DOT_ABOVE, 0), | LETTER('z', M_DOT_ABOVE, 0), | ||||
CAPITAL, | CAPITAL, | ||||
LETTER('z', M_CARON, 0), | LETTER('z', M_CARON, 0), | ||||
LETTER('s', M_NAME, 0), // long-s // U+17f | |||||
LETTER('s', M_NAME, 0), // long-s U+17f | |||||
}; | }; | ||||
// characters U+0250 to U+029F | // characters U+0250 to U+029F | ||||
static const unsigned short letter_accents_250[] = { | static const unsigned short letter_accents_250[] = { | ||||
LETTER('a', M_TURNED, 0), // U+250 | |||||
LETTER('a', M_TURNED, 0), // U+250 | |||||
LETTER(L_ALPHA, 0, 0), | LETTER(L_ALPHA, 0, 0), | ||||
LETTER(L_ALPHA, M_TURNED, 0), | LETTER(L_ALPHA, M_TURNED, 0), | ||||
LETTER('b', M_IMPLOSIVE, 0), | LETTER('b', M_IMPLOSIVE, 0), | ||||
0, // open-o | |||||
0, // open-o | |||||
LETTER('c', M_CURL, 0), | LETTER('c', M_CURL, 0), | ||||
LETTER('d', M_RETROFLEX, 0), | LETTER('d', M_RETROFLEX, 0), | ||||
LETTER('d', M_IMPLOSIVE, 0), | LETTER('d', M_IMPLOSIVE, 0), | ||||
LETTER('e', M_REVERSED, 0), // U+258 | |||||
0, // schwa | |||||
LETTER('e', M_REVERSED, 0), // U+258 | |||||
0, // schwa | |||||
LETTER(L_SCHWA, M_HOOK, 0), | LETTER(L_SCHWA, M_HOOK, 0), | ||||
0, // open-e | |||||
0, // open-e | |||||
LETTER(L_OPEN_E, M_REVERSED, 0), | LETTER(L_OPEN_E, M_REVERSED, 0), | ||||
LETTER(L_OPEN_E, M_HOOK, M_REVERSED), | LETTER(L_OPEN_E, M_HOOK, M_REVERSED), | ||||
0, | 0, | ||||
LETTER('j', M_BAR, 0), | LETTER('j', M_BAR, 0), | ||||
LETTER('g', M_IMPLOSIVE, 0), // U+260 | |||||
LETTER('g', M_IMPLOSIVE, 0), // U+260 | |||||
LETTER('g', 0, 0), | LETTER('g', 0, 0), | ||||
LETTER('g', M_SMALLCAP, 0), | LETTER('g', M_SMALLCAP, 0), | ||||
LETTER(L_GAMMA, 0, 0), | LETTER(L_GAMMA, 0, 0), | ||||
0, // ramshorn | |||||
0, // ramshorn | |||||
LETTER('h', M_TURNED, 0), | LETTER('h', M_TURNED, 0), | ||||
LETTER('h', M_HOOK, 0), | LETTER('h', M_HOOK, 0), | ||||
0, | 0, | ||||
LETTER('i', M_BAR, 0), // U+268 | |||||
LETTER('i', M_BAR, 0), // U+268 | |||||
LETTER(L_IOTA, 0, 0), | LETTER(L_IOTA, 0, 0), | ||||
LETTER('i', M_SMALLCAP, 0), | LETTER('i', M_SMALLCAP, 0), | ||||
LETTER('l', M_TILDE, 0), | LETTER('l', M_TILDE, 0), | ||||
LETTER('o', M_BAR, 0), | LETTER('o', M_BAR, 0), | ||||
LIGATURE('o', 'e', M_SMALLCAP), | LIGATURE('o', 'e', M_SMALLCAP), | ||||
0, | 0, | ||||
LETTER(L_PHI, 0, 0), // U+278 | |||||
LETTER(L_PHI, 0, 0), // U+278 | |||||
LETTER('r', M_TURNED, 0), | LETTER('r', M_TURNED, 0), | ||||
LETTER(L_RLONG, M_TURNED, 0), | LETTER(L_RLONG, M_TURNED, 0), | ||||
LETTER('r', M_RETROFLEX, M_TURNED), | LETTER('r', M_RETROFLEX, M_TURNED), | ||||
0, | 0, | ||||
LETTER('r', M_RETROFLEX, 0), | LETTER('r', M_RETROFLEX, 0), | ||||
0, // r-tap | |||||
0, // r-tap | |||||
LETTER(L_RTAP, M_REVERSED, 0), | LETTER(L_RTAP, M_REVERSED, 0), | ||||
LETTER('r', M_SMALLCAP, 0), // U+280 | |||||
LETTER('r', M_SMALLCAP, 0), // U+280 | |||||
LETTER('r', M_TURNED, M_SMALLCAP), | LETTER('r', M_TURNED, M_SMALLCAP), | ||||
LETTER('s', M_RETROFLEX, 0), | LETTER('s', M_RETROFLEX, 0), | ||||
0, // esh | |||||
0, // esh | |||||
LETTER('j', M_HOOK, 0), | LETTER('j', M_HOOK, 0), | ||||
LETTER(L_ESH, M_REVERSED, 0), | LETTER(L_ESH, M_REVERSED, 0), | ||||
LETTER(L_ESH, M_CURL, 0), | LETTER(L_ESH, M_CURL, 0), | ||||
LETTER('t', M_TURNED, 0), | LETTER('t', M_TURNED, 0), | ||||
LETTER('t', M_RETROFLEX, 0), // U+288 | |||||
LETTER('t', M_RETROFLEX, 0), // U+288 | |||||
LETTER('u', M_BAR, 0), | LETTER('u', M_BAR, 0), | ||||
LETTER(L_UPSILON, 0, 0), | LETTER(L_UPSILON, 0, 0), | ||||
LETTER('v', M_HOOK, 0), | LETTER('v', M_HOOK, 0), | ||||
LETTER('w', M_TURNED, 0), | LETTER('w', M_TURNED, 0), | ||||
LETTER('y', M_TURNED, 0), | LETTER('y', M_TURNED, 0), | ||||
LETTER('y', M_SMALLCAP, 0), | LETTER('y', M_SMALLCAP, 0), | ||||
LETTER('z', M_RETROFLEX, 0), // U+290 | |||||
LETTER('z', M_RETROFLEX, 0), // U+290 | |||||
LETTER('z', M_CURL, 0), | LETTER('z', M_CURL, 0), | ||||
0, // ezh | |||||
0, // ezh | |||||
LETTER(L_EZH, M_CURL, 0), | LETTER(L_EZH, M_CURL, 0), | ||||
0, // glottal stop | |||||
0, // glottal stop | |||||
LETTER(L_GLOTTAL, M_REVERSED, 0), | LETTER(L_GLOTTAL, M_REVERSED, 0), | ||||
LETTER(L_GLOTTAL, M_TURNED, 0), | LETTER(L_GLOTTAL, M_TURNED, 0), | ||||
0, | 0, | ||||
0, // bilabial click // U+298 | |||||
0, // bilabial click U+298 | |||||
LETTER('b', M_SMALLCAP, 0), | LETTER('b', M_SMALLCAP, 0), | ||||
0, | 0, | ||||
LETTER('g', M_IMPLOSIVE, M_SMALLCAP), | LETTER('g', M_IMPLOSIVE, M_SMALLCAP), | ||||
LETTER('j', M_CURL, 0), | LETTER('j', M_CURL, 0), | ||||
LETTER('k', M_TURNED, 0), | LETTER('k', M_TURNED, 0), | ||||
LETTER('l', M_SMALLCAP, 0), | LETTER('l', M_SMALLCAP, 0), | ||||
LETTER('q', M_HOOK, 0), // U+2a0 | |||||
LETTER('q', M_HOOK, 0), // U+2a0 | |||||
LETTER(L_GLOTTAL, M_STROKE, 0), | LETTER(L_GLOTTAL, M_STROKE, 0), | ||||
LETTER(L_GLOTTAL, M_STROKE, M_REVERSED), | LETTER(L_GLOTTAL, M_STROKE, M_REVERSED), | ||||
LIGATURE('d', 'z', 0), | LIGATURE('d', 'z', 0), | ||||
0, // dezh | |||||
0, // dezh | |||||
LIGATURE('d', 'z', M_CURL), | LIGATURE('d', 'z', M_CURL), | ||||
LIGATURE('t', 's', 0), | LIGATURE('t', 's', 0), | ||||
0, // tesh | |||||
0, // tesh | |||||
LIGATURE('t', 's', M_CURL), | LIGATURE('t', 's', M_CURL), | ||||
}; | }; | ||||
void LookupLetter(Translator *tr, unsigned int letter, int next_byte, char *ph_buf1, int control) | void LookupLetter(Translator *tr, unsigned int letter, int next_byte, char *ph_buf1, int control) | ||||
{ | { | ||||
// control, bit 0: not the first letter of a word | |||||
// control, bit 0: not the first letter of a word | |||||
int len; | int len; | ||||
static char single_letter[10] = { 0, 0 }; | static char single_letter[10] = { 0, 0 }; | ||||
single_letter[1] = '_'; | single_letter[1] = '_'; | ||||
if (Lookup(tr, &single_letter[1], ph_buf3) != 0) | if (Lookup(tr, &single_letter[1], ph_buf3) != 0) | ||||
return; // the character is specified as _* so ignore it when speaking normal text | |||||
return; // the character is specified as _* so ignore it when speaking normal text | |||||
// check whether this character is specified for English | // check whether this character is specified for English | ||||
if (tr->translator_name == L('e', 'n')) | if (tr->translator_name == L('e', 'n')) | ||||
return; // we are already using English | |||||
return; // we are already using English | |||||
SetTranslator2("en"); | SetTranslator2("en"); | ||||
if (Lookup(translator2, &single_letter[2], ph_buf3) != 0) { | if (Lookup(translator2, &single_letter[2], ph_buf3) != 0) { | ||||
// yes, switch to English and re-translate the word | // yes, switch to English and re-translate the word | ||||
sprintf(ph_buf1, "%c", phonSWITCH); | sprintf(ph_buf1, "%c", phonSWITCH); | ||||
} | } | ||||
SelectPhonemeTable(voice->phoneme_tab_ix); // revert to original phoneme table | |||||
SelectPhonemeTable(voice->phoneme_tab_ix); // revert to original phoneme table | |||||
return; | return; | ||||
} | } | ||||
if (next_byte != ' ') | if (next_byte != ' ') | ||||
next_byte = RULE_SPELLING; | next_byte = RULE_SPELLING; | ||||
single_letter[3+len] = next_byte; // follow by space-space if the end of the word, or space-31 | |||||
single_letter[3+len] = next_byte; // follow by space-space if the end of the word, or space-31 | |||||
single_letter[1] = '_'; | single_letter[1] = '_'; | ||||
SetWordStress(tr, ph_buf1, dict_flags, -1, control & 1); | SetWordStress(tr, ph_buf1, dict_flags, -1, control & 1); | ||||
} | } | ||||
// unicode ranges for non-ascii digits 0-9 | |||||
// unicode ranges for non-ascii digits 0-9 (these must be in ascending order) | |||||
static const int number_ranges[] = { | static const int number_ranges[] = { | ||||
0x660, 0x6f0, // arabic | |||||
0x966, 0x9e6, 0xa66, 0xae6, 0xb66, 0xbe6, 0xc66, 0xce6, 0xd66, // indic | |||||
0x660, 0x6f0, // arabic | |||||
0x966, 0x9e6, 0xa66, 0xae6, 0xb66, 0xbe6, 0xc66, 0xce6, 0xd66, // indic | |||||
0xe50, 0xed0, 0xf20, 0x1040, 0x1090, | 0xe50, 0xed0, 0xf20, 0x1040, 0x1090, | ||||
0 | 0 | ||||
}; // these must be in ascending order | |||||
}; | |||||
int NonAsciiNumber(int letter) | int NonAsciiNumber(int letter) | ||||
{ | { | ||||
// Change non-ascii digit into ascii digit '0' to '9', (or -1 if not) | |||||
// Change non-ascii digit into ascii digit '0' to '9', (or -1 if not) | |||||
const int *p; | const int *p; | ||||
int base; | int base; | ||||
for (p = number_ranges; (base = *p) != 0; p++) { | for (p = number_ranges; (base = *p) != 0; p++) { | ||||
if (letter < base) | if (letter < base) | ||||
break; // not found | |||||
break; // not found | |||||
if (letter < (base+10)) | if (letter < (base+10)) | ||||
return letter-base+'0'; | return letter-base+'0'; | ||||
} | } | ||||
return -1; | return -1; | ||||
} | } | ||||
#define L_SUB 0x4000 // subscript | |||||
#define L_SUP 0x8000 // superscript | |||||
#define L_SUB 0x4000 // subscript | |||||
#define L_SUP 0x8000 // superscript | |||||
static const char *modifiers[] = { NULL, "_sub", "_sup", NULL }; | static const char *modifiers[] = { NULL, "_sub", "_sup", NULL }; | ||||
0, 0 | 0, 0 | ||||
}; | }; | ||||
static const char *hex_letters[] = { "'e:j", "b'i:", "s'i:", "d'i:", "'i:", "'ef" }; // names, using phonemes available to all languages | |||||
// names, using phonemes available to all languages | |||||
static const char *hex_letters[] = { | |||||
"'e:j", | |||||
"b'i:", | |||||
"s'i:", | |||||
"d'i:", | |||||
"'i:", | |||||
"'ef" | |||||
}; | |||||
int IsSuperscript(int letter) | int IsSuperscript(int letter) | ||||
{ | { | ||||
// is this a subscript or superscript letter ? | |||||
// is this a subscript or superscript letter ? | |||||
int ix; | int ix; | ||||
int c; | int c; | ||||
int TranslateLetter(Translator *tr, char *word, char *phonemes, int control) | int TranslateLetter(Translator *tr, char *word, char *phonemes, int control) | ||||
{ | { | ||||
// get pronunciation for an isolated letter | |||||
// return number of bytes used by the letter | |||||
// control bit 0: a non-initial letter in a word | |||||
// bit 1: say 'capital' | |||||
// bit 2: say character code for unknown letters | |||||
// get pronunciation for an isolated letter | |||||
// return number of bytes used by the letter | |||||
// control bit 0: a non-initial letter in a word | |||||
// bit 1: say 'capital' | |||||
// bit 2: say character code for unknown letters | |||||
int n_bytes; | int n_bytes; | ||||
int letter; | int letter; | ||||
n_bytes = utf8_in(&letter, word); | n_bytes = utf8_in(&letter, word); | ||||
if ((letter & 0xfff00) == 0x0e000) | if ((letter & 0xfff00) == 0x0e000) | ||||
letter &= 0xff; // uncode private usage area | |||||
letter &= 0xff; // uncode private usage area | |||||
if (control & 2) { | if (control & 2) { | ||||
// include CAPITAL information | // include CAPITAL information | ||||
// don't say "superscript" during normal text reading | // don't say "superscript" during normal text reading | ||||
Lookup(tr, modifier, capital); | Lookup(tr, modifier, capital); | ||||
if (capital[0] == 0) { | if (capital[0] == 0) { | ||||
capital[2] = SetTranslator2("en"); // overwrites previous contents of translator2 | |||||
capital[2] = SetTranslator2("en"); // overwrites previous contents of translator2 | |||||
Lookup(translator2, modifier, &capital[3]); | Lookup(translator2, modifier, &capital[3]); | ||||
if (capital[3] != 0) { | if (capital[3] != 0) { | ||||
capital[0] = phonPAUSE; | capital[0] = phonPAUSE; | ||||
ph_buf2[0] = 0; | ph_buf2[0] = 0; | ||||
if (Lookup(translator, alphabet->name, ph_alphabet) == 0) { // the original language for the current voice | if (Lookup(translator, alphabet->name, ph_alphabet) == 0) { // the original language for the current voice | ||||
// Can't find the local name for this alphabet, use the English name | // Can't find the local name for this alphabet, use the English name | ||||
ph_alphabet[2] = SetTranslator2("en"); // overwrites previous contents of translator2 | |||||
ph_alphabet[2] = SetTranslator2("en"); // overwrites previous contents of translator2 | |||||
Lookup(translator2, alphabet->name, ph_buf2); | Lookup(translator2, alphabet->name, ph_buf2); | ||||
} else if (translator != tr) { | } else if (translator != tr) { | ||||
phontab_1 = tr->phoneme_tab_ix; | phontab_1 = tr->phoneme_tab_ix; | ||||
} | } | ||||
} | } | ||||
// caution: SetWordStress() etc don't expect phonSWITCH + phoneme table number | |||||
// caution: SetWordStress() etc don't expect phonSWITCH + phoneme table number | |||||
if (ph_buf[0] == 0) { | if (ph_buf[0] == 0) { | ||||
if ((al_offset != 0) && (al_offset == translator->langopts.alt_alphabet)) | if ((al_offset != 0) && (al_offset == translator->langopts.alt_alphabet)) | ||||
if ((initial = (code/28)/21) != 11) { | if ((initial = (code/28)/21) != 11) { | ||||
p3 += utf8_out(initial + 0x1100, p3); | p3 += utf8_out(initial + 0x1100, p3); | ||||
} | } | ||||
utf8_out(((code/28) % 21) + 0x1161, p3); // medial | |||||
utf8_out((code % 28) + 0x11a7, &p3[3]); // final | |||||
utf8_out(((code/28) % 21) + 0x1161, p3); // medial | |||||
utf8_out((code % 28) + 0x11a7, &p3[3]); // final | |||||
p3[6] = ' '; | p3[6] = ' '; | ||||
p3[7] = 0; | p3[7] = 0; | ||||
ph_buf[3] = 0; | ph_buf[3] = 0; | ||||
LookupLetter(translator2, letter, word[n_bytes], &ph_buf[3], control & 1); | LookupLetter(translator2, letter, word[n_bytes], &ph_buf[3], control & 1); | ||||
} | } | ||||
SelectPhonemeTable(voice->phoneme_tab_ix); // revert to original phoneme table | |||||
SelectPhonemeTable(voice->phoneme_tab_ix); // revert to original phoneme table | |||||
if (ph_buf[3] != 0) { | if (ph_buf[3] != 0) { | ||||
ph_buf[0] = phonPAUSE; | ph_buf[0] = phonPAUSE; | ||||
ph_buf[1] = phonSWITCH; | ph_buf[1] = phonSWITCH; | ||||
len = strlen(&ph_buf[3]) + 3; | len = strlen(&ph_buf[3]) + 3; | ||||
ph_buf[len] = phonSWITCH; // switch back | |||||
ph_buf[len] = phonSWITCH; // switch back | |||||
ph_buf[len+1] = tr->phoneme_tab_ix; | ph_buf[len+1] = tr->phoneme_tab_ix; | ||||
ph_buf[len+2] = 0; | ph_buf[len+2] = 0; | ||||
} | } | ||||
if (tr->langopts.accents & 2) // 'capital' before or after the word ? | if (tr->langopts.accents & 2) // 'capital' before or after the word ? | ||||
sprintf(ph_buf2, "%c%s%s%s", 0xff, ph_alphabet, ph_buf, capital); | sprintf(ph_buf2, "%c%s%s%s", 0xff, ph_alphabet, ph_buf, capital); | ||||
else | else | ||||
sprintf(ph_buf2, "%c%s%s%s", 0xff, ph_alphabet, capital, ph_buf); // the 0xff marker will be removed or replaced in SetSpellingStress() | |||||
if ((len + strlen(ph_buf2)) < N_WORD_PHONEMES) { | |||||
sprintf(ph_buf2, "%c%s%s%s", 0xff, ph_alphabet, capital, ph_buf); // the 0xff marker will be removed or replaced in SetSpellingStress() | |||||
if ((len + strlen(ph_buf2)) < N_WORD_PHONEMES) | |||||
strcpy(&phonemes[len], ph_buf2); | strcpy(&phonemes[len], ph_buf2); | ||||
} | |||||
return n_bytes; | return n_bytes; | ||||
} | } | ||||
void SetSpellingStress(Translator *tr, char *phonemes, int control, int n_chars) | void SetSpellingStress(Translator *tr, char *phonemes, int control, int n_chars) | ||||
{ | { | ||||
// Individual letter names, reduce the stress of some. | |||||
// Individual letter names, reduce the stress of some. | |||||
int ix; | int ix; | ||||
unsigned int c; | unsigned int c; | ||||
int n_stress = 0; | int n_stress = 0; | ||||
} else { | } else { | ||||
if (count != n_stress) { | if (count != n_stress) { | ||||
if (((count % 3) != 0) || (count == n_stress-1)) | if (((count % 3) != 0) || (count == n_stress-1)) | ||||
c = phonSTRESS_3; // reduce to secondary stress | |||||
c = phonSTRESS_3; // reduce to secondary stress | |||||
} | } | ||||
} | } | ||||
} else if (c == 0xff) { | } else if (c == 0xff) { | ||||
if ((control < 2) || (ix == 0)) | if ((control < 2) || (ix == 0)) | ||||
continue; // don't insert pauses | |||||
continue; // don't insert pauses | |||||
if (control == 4) | if (control == 4) | ||||
c = phonPAUSE; // pause after each character | |||||
c = phonPAUSE; // pause after each character | |||||
if (((count % 3) == 0) || (control > 2)) | if (((count % 3) == 0) || (control > 2)) | ||||
c = phonPAUSE_NOLINK; // pause following a primary stress | |||||
c = phonPAUSE_NOLINK; // pause following a primary stress | |||||
else | else | ||||
c = phonPAUSE_VSHORT; | c = phonPAUSE_VSHORT; | ||||
} | } | ||||
nextflags = TranslateWord(tr, &word_end[2], 0, NULL, NULL); | nextflags = TranslateWord(tr, &word_end[2], 0, NULL, NULL); | ||||
if ((tr->prev_dict_flags[0] & FLAG_ALT_TRANS) && ((c2 == 0) || (wtab[0].flags & FLAG_COMMA_AFTER) || iswdigit(c2))) | if ((tr->prev_dict_flags[0] & FLAG_ALT_TRANS) && ((c2 == 0) || (wtab[0].flags & FLAG_COMMA_AFTER) || iswdigit(c2))) | ||||
ordinal = 0; // TEST 09.02.10 | |||||
ordinal = 0; // TEST 09.02.10 | |||||
if (nextflags & FLAG_ALT_TRANS) | if (nextflags & FLAG_ALT_TRANS) | ||||
ordinal = 0; | ordinal = 0; | ||||
if (nextflags & FLAG_ALT3_TRANS) { | if (nextflags & FLAG_ALT3_TRANS) { | ||||
if (word[-2] == '-') | if (word[-2] == '-') | ||||
ordinal = 0; // eg. december 2-5. között | |||||
ordinal = 0; // e.g. december 2-5. között | |||||
if (tr->prev_dict_flags[0] & (FLAG_ALT_TRANS | FLAG_ALT3_TRANS)) | if (tr->prev_dict_flags[0] & (FLAG_ALT_TRANS | FLAG_ALT3_TRANS)) | ||||
ordinal = 0x22; | ordinal = 0x22; | ||||
static int hu_number_e(const char *word, int thousandplex, int value) | static int hu_number_e(const char *word, int thousandplex, int value) | ||||
{ | { | ||||
// lang-hu: variant form of numbers when followed by hyphen and a suffix starting with 'a' or 'e' (but not a, e, az, ez, azt, ezt, att. ett | |||||
// lang-hu: variant form of numbers when followed by hyphen and a suffix starting with 'a' or 'e' (but not a, e, az, ez, azt, ezt, att. ett | |||||
if ((word[0] == 'a') || (word[0] == 'e')) { | if ((word[0] == 'a') || (word[0] == 'e')) { | ||||
if ((word[1] == ' ') || (word[1] == 'z') || ((word[1] == 't') && (word[2] == 't'))) | if ((word[1] == ' ') || (word[1] == 'z') || ((word[1] == 't') && (word[2] == 't'))) | ||||
return 0; | return 0; | ||||
if (((thousandplex == 1) || ((value % 1000) == 0)) && (word[1] == 'l')) | if (((thousandplex == 1) || ((value % 1000) == 0)) && (word[1] == 'l')) | ||||
return 0; // 1000-el | |||||
return 0; // 1000-el | |||||
return 1; | return 1; | ||||
} | } | ||||
flags[1] = 0; | flags[1] = 0; | ||||
if (((tr->langopts.numbers & NUM_ROMAN_CAPITALS) && !(wtab[0].flags & FLAG_ALL_UPPER)) || IsDigit09(word[-2])) | if (((tr->langopts.numbers & NUM_ROMAN_CAPITALS) && !(wtab[0].flags & FLAG_ALL_UPPER)) || IsDigit09(word[-2])) | ||||
return 0; // not '2xx' | |||||
return 0; // not '2xx' | |||||
if (word[1] == ' ') { | if (word[1] == ' ') { | ||||
if ((tr->langopts.numbers & (NUM_ROMAN_CAPITALS | NUM_ROMAN_ORDINAL | NUM_ORDINAL_DOT)) && (wtab[0].flags & FLAG_HAS_DOT)) { | if ((tr->langopts.numbers & (NUM_ROMAN_CAPITALS | NUM_ROMAN_ORDINAL | NUM_ORDINAL_DOT)) && (wtab[0].flags & FLAG_HAS_DOT)) { | ||||
// allow single letter Roman ordinal followed by dot. | // allow single letter Roman ordinal followed by dot. | ||||
} else | } else | ||||
return 0; // only one letter, don't speak as a Roman Number | |||||
return 0; // only one letter, don't speak as a Roman Number | |||||
} | } | ||||
word_start = word; | word_start = word; | ||||
} | } | ||||
if (IsDigit09(word[0])) | if (IsDigit09(word[0])) | ||||
return 0; // eg. 'xx2' | |||||
return 0; // e.g. 'xx2' | |||||
acc += prev; | acc += prev; | ||||
if (acc < tr->langopts.min_roman) | if (acc < tr->langopts.min_roman) | ||||
if (acc > tr->langopts.max_roman) | if (acc > tr->langopts.max_roman) | ||||
return 0; | return 0; | ||||
Lookup(tr, "_roman", ph_roman); // precede by "roman" if _rom is defined in *_list | |||||
Lookup(tr, "_roman", ph_roman); // precede by "roman" if _rom is defined in *_list | |||||
p = &ph_out[0]; | p = &ph_out[0]; | ||||
if ((tr->langopts.numbers & NUM_ROMAN_AFTER) == 0) { | if ((tr->langopts.numbers & NUM_ROMAN_AFTER) == 0) { | ||||
switch ((translator->langopts.numbers2 >> 6) & 0x7) | switch ((translator->langopts.numbers2 >> 6) & 0x7) | ||||
{ | { | ||||
case 1: // lang=ru use singular for xx1 except for x11 | |||||
case 1: // lang=ru use singular for xx1 except for x11 | |||||
if ((teens == 0) && ((value % 10) == 1)) | if ((teens == 0) && ((value % 10) == 1)) | ||||
return "1M"; | return "1M"; | ||||
break; | break; | ||||
case 2: // lang=cs,sk | |||||
case 2: // lang=cs,sk | |||||
if ((value >= 2) && (value <= 4)) | if ((value >= 2) && (value <= 4)) | ||||
return "0MA"; | return "0MA"; | ||||
break; | break; | ||||
case 3: // lang=pl | |||||
case 3: // lang=pl | |||||
if ((teens == 0) && (((value % 10) >= 2) && ((value % 10) <= 4))) | if ((teens == 0) && (((value % 10) >= 2) && ((value % 10) <= 4))) | ||||
return "0MA"; | return "0MA"; | ||||
break; | break; | ||||
case 4: // lang=lt | |||||
case 4: // lang=lt | |||||
if ((teens == 1) || ((value % 10) == 0)) | if ((teens == 1) || ((value % 10) == 0)) | ||||
return "0MB"; | return "0MB"; | ||||
if ((value % 10) == 1) | if ((value % 10) == 1) | ||||
return "0MA"; | return "0MA"; | ||||
break; | break; | ||||
case 5: // lang=bs,hr,sr | |||||
case 5: // lang=bs,hr,sr | |||||
if (teens == 0) { | if (teens == 0) { | ||||
if ((value % 10) == 1) | if ((value % 10) == 1) | ||||
return "1M"; | return "1M"; | ||||
static int LookupThousands(Translator *tr, int value, int thousandplex, int thousands_exact, char *ph_out) | static int LookupThousands(Translator *tr, int value, int thousandplex, int thousands_exact, char *ph_out) | ||||
{ | { | ||||
// thousands_exact: bit 0 no hundreds,tens,or units, bit 1 ordinal numberr | |||||
// thousands_exact: bit 0 no hundreds,tens,or units, bit 1 ordinal numberr | |||||
int found; | int found; | ||||
int found_value = 0; | int found_value = 0; | ||||
char string[12]; | char string[12]; | ||||
static int LookupNum2(Translator *tr, int value, int thousandplex, const int control, char *ph_out) | static int LookupNum2(Translator *tr, int value, int thousandplex, const int control, char *ph_out) | ||||
{ | { | ||||
// Lookup a 2 digit number | |||||
// control bit 0: ordinal number | |||||
// control bit 1: final tens and units (not number of thousands) (use special form of '1', LANG=de "eins") | |||||
// control bit 2: tens and units only, no higher digits | |||||
// control bit 3: use feminine form of '2' (for thousands | |||||
// control bit 4: speak zero tens | |||||
// control bit 5: variant of ordinal number (lang=hu) | |||||
// bit 8 followed by decimal fraction | |||||
// bit 9: use #f form for both tens and units (lang=ml) | |||||
// Lookup a 2 digit number | |||||
// control bit 0: ordinal number | |||||
// control bit 1: final tens and units (not number of thousands) (use special form of '1', LANG=de "eins") | |||||
// control bit 2: tens and units only, no higher digits | |||||
// control bit 3: use feminine form of '2' (for thousands | |||||
// control bit 4: speak zero tens | |||||
// control bit 5: variant of ordinal number (lang=hu) | |||||
// bit 8 followed by decimal fraction | |||||
// bit 9: use #f form for both tens and units (lang=ml) | |||||
int found; | int found; | ||||
int ix; | int ix; | ||||
int found_ordinal = 0; | int found_ordinal = 0; | ||||
int next_phtype; | int next_phtype; | ||||
int ord_type = 'o'; | int ord_type = 'o'; | ||||
char string[12]; // for looking up entries in *_list | |||||
char string[12]; // for looking up entries in *_list | |||||
char ph_ordinal[20]; | char ph_ordinal[20]; | ||||
char ph_tens[50]; | char ph_tens[50]; | ||||
char ph_digits[50]; | char ph_digits[50]; | ||||
strcpy(ph_ordinal, ph_ordinal2); | strcpy(ph_ordinal, ph_ordinal2); | ||||
if (control & 4) { | if (control & 4) { | ||||
sprintf(string, "_%d%cx", value, ord_type); // LANG=hu, special word for 1. 2. when there are no higher digits | |||||
sprintf(string, "_%d%cx", value, ord_type); // LANG=hu, special word for 1. 2. when there are no higher digits | |||||
if ((found = Lookup(tr, string, ph_digits)) != 0) { | if ((found = Lookup(tr, string, ph_digits)) != 0) { | ||||
if (ph_ordinal2x[0] != 0) | if (ph_ordinal2x[0] != 0) | ||||
strcpy(ph_ordinal, ph_ordinal2x); // alternate pronunciation (lang=an) | |||||
strcpy(ph_ordinal, ph_ordinal2x); // alternate pronunciation (lang=an) | |||||
} | } | ||||
} | } | ||||
if (found == 0) { | if (found == 0) { | ||||
} else { | } else { | ||||
// followed by hundreds or thousands etc | // followed by hundreds or thousands etc | ||||
if ((tr->langopts.numbers2 & NUM2_ORDINAL_AND_THOUSANDS) && (thousandplex <= 1)) | if ((tr->langopts.numbers2 & NUM2_ORDINAL_AND_THOUSANDS) && (thousandplex <= 1)) | ||||
sprintf(string, "_%do", value); // LANG=TA | |||||
sprintf(string, "_%do", value); // LANG=TA | |||||
else | else | ||||
sprintf(string, "_%da", value); | sprintf(string, "_%da", value); | ||||
found = Lookup(tr, string, ph_digits); | found = Lookup(tr, string, ph_digits); | ||||
// speak leading zero | // speak leading zero | ||||
Lookup(tr, "_0", ph_tens); | Lookup(tr, "_0", ph_tens); | ||||
} else { | } else { | ||||
if (found) { | |||||
if (found) | |||||
ph_tens[0] = 0; | ph_tens[0] = 0; | ||||
} else { | |||||
else { | |||||
if (is_ordinal) { | if (is_ordinal) { | ||||
sprintf(string, "_%dX%c", tens, ord_type); | sprintf(string, "_%dX%c", tens, ord_type); | ||||
if (Lookup(tr, string, ph_tens) != 0) { | if (Lookup(tr, string, ph_tens) != 0) { | ||||
if ((is_ordinal) && ((tr->langopts.numbers & NUM_SWAP_TENS) == 0)) { | if ((is_ordinal) && ((tr->langopts.numbers & NUM_SWAP_TENS) == 0)) { | ||||
// ordinal | // ordinal | ||||
sprintf(string, "_%d%c", units, ord_type); | sprintf(string, "_%d%c", units, ord_type); | ||||
if ((found = Lookup(tr, string, ph_digits)) != 0) { | |||||
if ((found = Lookup(tr, string, ph_digits)) != 0) | |||||
found_ordinal = 1; | found_ordinal = 1; | ||||
} | |||||
} | } | ||||
if (found == 0) { | if (found == 0) { | ||||
if ((number_control & 1) && (control & 2)) { | if ((number_control & 1) && (control & 2)) { | ||||
static int LookupNum3(Translator *tr, int value, char *ph_out, int suppress_null, int thousandplex, int control) | static int LookupNum3(Translator *tr, int value, char *ph_out, int suppress_null, int thousandplex, int control) | ||||
{ | { | ||||
// Translate a 3 digit number | |||||
// control bit 0, previous thousands | |||||
// bit 1, ordinal number | |||||
// bit 5 variant form of ordinal number | |||||
// bit 8 followed by decimal fraction | |||||
// Translate a 3 digit number | |||||
// control bit 0, previous thousands | |||||
// bit 1, ordinal number | |||||
// bit 5 variant form of ordinal number | |||||
// bit 8 followed by decimal fraction | |||||
int found; | int found; | ||||
int hundreds; | int hundreds; | ||||
int tensunits; | int tensunits; | ||||
int tplex; | int tplex; | ||||
int say_zero_hundred = 0; | int say_zero_hundred = 0; | ||||
int say_one_hundred; | int say_one_hundred; | ||||
char string[12]; // for looking up entries in **_list | |||||
char string[12]; // for looking up entries in **_list | |||||
char buf1[100]; | char buf1[100]; | ||||
char buf2[100]; | char buf2[100]; | ||||
char ph_100[20]; | char ph_100[20]; | ||||
ph_thousands[0] = 0; | ph_thousands[0] = 0; | ||||
ph_thousand_and[0] = 0; | ph_thousand_and[0] = 0; | ||||
if ((tr->langopts.numbers & NUM_ZERO_HUNDRED) && ((control & 1) || (hundreds >= 10))) { | |||||
say_zero_hundred = 1; // lang=vi | |||||
} | |||||
if ((tr->langopts.numbers & NUM_ZERO_HUNDRED) && ((control & 1) || (hundreds >= 10))) | |||||
say_zero_hundred = 1; // lang=vi | |||||
if ((hundreds > 0) || say_zero_hundred) { | if ((hundreds > 0) || say_zero_hundred) { | ||||
found = 0; | found = 0; | ||||
if (LookupThousands(tr, hundreds / 10, tplex, exact | ordinal, ph_10T) == 0) { | if (LookupThousands(tr, hundreds / 10, tplex, exact | ordinal, ph_10T) == 0) { | ||||
x = 0; | x = 0; | ||||
if (tr->langopts.numbers2 & (1 << tplex)) | if (tr->langopts.numbers2 & (1 << tplex)) | ||||
x = 8; // use variant (feminine) for before thousands and millions | |||||
x = 8; // use variant (feminine) for before thousands and millions | |||||
if (tr->translator_name == L('m', 'l')) | if (tr->translator_name == L('m', 'l')) | ||||
x = 0x208; | x = 0x208; | ||||
LookupNum2(tr, hundreds/10, thousandplex, x, ph_digits); | LookupNum2(tr, hundreds/10, thousandplex, x, ph_digits); | ||||
} | } | ||||
if (tr->langopts.numbers2 & 0x200) | if (tr->langopts.numbers2 & 0x200) | ||||
sprintf(ph_thousands, "%s%c%s%c", ph_10T, phonEND_WORD, ph_digits, phonEND_WORD); // say "thousands" before its number, not after | |||||
sprintf(ph_thousands, "%s%c%s%c", ph_10T, phonEND_WORD, ph_digits, phonEND_WORD); // say "thousands" before its number, not after | |||||
else | else | ||||
sprintf(ph_thousands, "%s%c%s%c", ph_digits, phonEND_WORD, ph_10T, phonEND_WORD); | sprintf(ph_thousands, "%s%c%s%c", ph_digits, phonEND_WORD, ph_10T, phonEND_WORD); | ||||
if ((tensunits != 0) || (suppress_null == 0)) { | if ((tensunits != 0) || (suppress_null == 0)) { | ||||
x = 0; | x = 0; | ||||
if (thousandplex == 0) { | if (thousandplex == 0) { | ||||
x = 2; // allow "eins" for 1 rather than "ein" | |||||
x = 2; // allow "eins" for 1 rather than "ein" | |||||
if (ordinal) | if (ordinal) | ||||
x = 3; // ordinal number | |||||
x = 3; // ordinal number | |||||
if ((value < 100) && !(control & 1)) | if ((value < 100) && !(control & 1)) | ||||
x |= 4; // tens and units only, no higher digits | |||||
x |= 4; // tens and units only, no higher digits | |||||
if (ordinal & 0x20) | if (ordinal & 0x20) | ||||
x |= 0x20; // variant form of ordinal number | |||||
x |= 0x20; // variant form of ordinal number | |||||
} else if (tr->langopts.numbers2 & (1 << thousandplex)) | } else if (tr->langopts.numbers2 & (1 << thousandplex)) | ||||
x = 8; // use variant (feminine) for before thousands and millions | |||||
x = 8; // use variant (feminine) for before thousands and millions | |||||
if ((tr->translator_name == L('m', 'l')) && (thousandplex == 1)) | if ((tr->translator_name == L('m', 'l')) && (thousandplex == 1)) | ||||
x |= 0x208; // use #f form for both tens and units | |||||
x |= 0x208; // use #f form for both tens and units | |||||
if ((tr->langopts.numbers2 & NUM2_ZERO_TENS) && ((control & 1) || (hundreds > 0))) { | if ((tr->langopts.numbers2 & NUM2_ZERO_TENS) && ((control & 1) || (hundreds > 0))) { | ||||
// LANG=zh, | // LANG=zh, | ||||
if (LookupNum2(tr, tensunits, thousandplex, x | (control & 0x100), buf2) != 0) { | if (LookupNum2(tr, tensunits, thousandplex, x | (control & 0x100), buf2) != 0) { | ||||
if (tr->langopts.numbers & NUM_SINGLE_AND) | if (tr->langopts.numbers & NUM_SINGLE_AND) | ||||
ph_hundred_and[0] = 0; // don't put 'and' after 'hundred' if there's 'and' between tens and units | |||||
ph_hundred_and[0] = 0; // don't put 'and' after 'hundred' if there's 'and' between tens and units | |||||
} | } | ||||
} else { | } else { | ||||
if (ph_ordinal2[0] != 0) { | if (ph_ordinal2[0] != 0) { | ||||
ix = strlen(buf1); | ix = strlen(buf1); | ||||
if ((ix > 0) && (buf1[ix-1] == phonPAUSE_SHORT)) | if ((ix > 0) && (buf1[ix-1] == phonPAUSE_SHORT)) | ||||
buf1[ix-1] = 0; // remove pause before addding ordinal suffix | |||||
buf1[ix-1] = 0; // remove pause before addding ordinal suffix | |||||
strcpy(buf2, ph_ordinal2); | strcpy(buf2, ph_ordinal2); | ||||
} | } | ||||
} | } | ||||
bool CheckThousandsGroup(char *word, int group_len) | bool CheckThousandsGroup(char *word, int group_len) | ||||
{ | { | ||||
// Is this a group of 3 digits which looks like a thousands group? | |||||
// Is this a group of 3 digits which looks like a thousands group? | |||||
int ix; | int ix; | ||||
if (IsDigit09(word[group_len]) || IsDigit09(-1)) | if (IsDigit09(word[group_len]) || IsDigit09(-1)) | ||||
static int TranslateNumber_1(Translator *tr, char *word, char *ph_out, unsigned int *flags, WORD_TAB *wtab, int control) | static int TranslateNumber_1(Translator *tr, char *word, char *ph_out, unsigned int *flags, WORD_TAB *wtab, int control) | ||||
{ | { | ||||
// Number translation with various options | |||||
// the "word" may be up to 4 digits | |||||
// "words" of 3 digits may be preceded by another number "word" for thousands or millions | |||||
// Number translation with various options | |||||
// the "word" may be up to 4 digits | |||||
// "words" of 3 digits may be preceded by another number "word" for thousands or millions | |||||
int n_digits; | int n_digits; | ||||
int value; | int value; | ||||
int group_len; | int group_len; | ||||
int len; | int len; | ||||
char *p; | char *p; | ||||
char string[32]; // for looking up entries in **_list | |||||
char string[32]; // for looking up entries in **_list | |||||
char buf1[100]; | char buf1[100]; | ||||
char ph_append[50]; | char ph_append[50]; | ||||
char ph_buf[200]; | char ph_buf[200]; | ||||
char ph_buf2[50]; | char ph_buf2[50]; | ||||
char ph_zeros[50]; | char ph_zeros[50]; | ||||
char suffix[30]; // string[] must be long enough for sizeof(suffix)+2 | |||||
char suffix[30]; // string[] must be long enough for sizeof(suffix)+2 | |||||
char buf_digit_lookup[50]; | char buf_digit_lookup[50]; | ||||
static const char str_pause[2] = { phonPAUSE_NOLINK, 0 }; | static const char str_pause[2] = { phonPAUSE_NOLINK, 0 }; | ||||
group_len = 4; | group_len = 4; | ||||
// is there a previous thousands part (as a previous "word") ? | // is there a previous thousands part (as a previous "word") ? | ||||
if ((n_digits == group_len) && (word[-2] == tr->langopts.thousands_sep) && IsDigit09(word[-3])) { | |||||
if ((n_digits == group_len) && (word[-2] == tr->langopts.thousands_sep) && IsDigit09(word[-3])) | |||||
prev_thousands = 1; | prev_thousands = 1; | ||||
} else if ((tr->langopts.thousands_sep == ' ') || (tr->langopts.numbers & NUM_ALLOW_SPACE)) { | |||||
else if ((tr->langopts.thousands_sep == ' ') || (tr->langopts.numbers & NUM_ALLOW_SPACE)) { | |||||
// thousands groups can be separated by spaces | // thousands groups can be separated by spaces | ||||
if ((n_digits == 3) && !(wtab->flags & FLAG_MULTIPLE_SPACES) && IsDigit09(word[-2])) | if ((n_digits == 3) && !(wtab->flags & FLAG_MULTIPLE_SPACES) && IsDigit09(word[-2])) | ||||
prev_thousands = 1; | prev_thousands = 1; | ||||
} | } | ||||
if ((ordinal == 0) || (tr->translator_name == L('h', 'u'))) { | if ((ordinal == 0) || (tr->translator_name == L('h', 'u'))) { | ||||
// NOTE lang=hu, allow both dot and ordinal suffix, eg. "december 21.-én" | |||||
// NOTE lang=hu, allow both dot and ordinal suffix, eg. "december 21.-én" | |||||
// look for an ordinal number suffix after the number | // look for an ordinal number suffix after the number | ||||
ix++; | ix++; | ||||
p = suffix; | p = suffix; | ||||
if (suffix[0] != 0) { | if (suffix[0] != 0) { | ||||
if ((tr->langopts.ordinal_indicator != NULL) && (strcmp(suffix, tr->langopts.ordinal_indicator) == 0)) | if ((tr->langopts.ordinal_indicator != NULL) && (strcmp(suffix, tr->langopts.ordinal_indicator) == 0)) | ||||
ordinal = 2; | ordinal = 2; | ||||
else if (!IsDigit09(suffix[0])) { // not _#9 (tab) | |||||
else if (!IsDigit09(suffix[0])) { // not _#9 (tab) | |||||
sprintf(string, "_#%s", suffix); | sprintf(string, "_#%s", suffix); | ||||
if (Lookup(tr, string, ph_ordinal2)) { | if (Lookup(tr, string, ph_ordinal2)) { | ||||
// this is an ordinal suffix | // this is an ordinal suffix | ||||
flags[0] |= FLAG_SKIPWORDS; | flags[0] |= FLAG_SKIPWORDS; | ||||
skipwords = 1; | skipwords = 1; | ||||
sprintf(string, "_x#%s", suffix); | sprintf(string, "_x#%s", suffix); | ||||
Lookup(tr, string, ph_ordinal2x); // is there an alternate pronunciation? | |||||
Lookup(tr, string, ph_ordinal2x); // is there an alternate pronunciation? | |||||
} | } | ||||
} | } | ||||
} | } | ||||
} else { | } else { | ||||
if (n_digits > 3) { | if (n_digits > 3) { | ||||
flags[0] &= ~FLAG_SKIPWORDS; | flags[0] &= ~FLAG_SKIPWORDS; | ||||
return 0; // long number string with leading zero, speak as individual digits | |||||
return 0; // long number string with leading zero, speak as individual digits | |||||
} | } | ||||
// speak leading zeros | // speak leading zeros | ||||
if (tr->translator_name == L('h', 'u')) { | if (tr->translator_name == L('h', 'u')) { | ||||
// variant form of numbers when followed by hyphen and a suffix starting with 'a' or 'e' (but not a, e, az, ez, azt, ezt | // variant form of numbers when followed by hyphen and a suffix starting with 'a' or 'e' (but not a, e, az, ez, azt, ezt | ||||
if ((wtab[thousandplex].flags & FLAG_HYPHEN_AFTER) && (thousands_exact == 1) && hu_number_e(&word[suffix_ix], thousandplex, value)) | if ((wtab[thousandplex].flags & FLAG_HYPHEN_AFTER) && (thousands_exact == 1) && hu_number_e(&word[suffix_ix], thousandplex, value)) | ||||
number_control |= 1; // use _1e variant of number | |||||
number_control |= 1; // use _1e variant of number | |||||
} | } | ||||
if ((word[n_digits] == tr->langopts.decimal_sep) && IsDigit09(word[n_digits+1])) { | if ((word[n_digits] == tr->langopts.decimal_sep) && IsDigit09(word[n_digits+1])) { | ||||
char *p2; | char *p2; | ||||
// look for combinations of the number with the next word | // look for combinations of the number with the next word | ||||
p = word; | p = word; | ||||
while (IsDigit09(p[1])) p++; // just use the last digit | |||||
while (IsDigit09(p[1])) p++; // just use the last digit | |||||
if (IsDigit09(p[-1])) { | if (IsDigit09(p[-1])) { | ||||
p2 = p - 1; | p2 = p - 1; | ||||
if (LookupDictList(tr, &p2, buf_digit_lookup, flags, FLAG_SUFX, wtab)) // lookup 2 digits | if (LookupDictList(tr, &p2, buf_digit_lookup, flags, FLAG_SUFX, wtab)) // lookup 2 digits | ||||
LookupNum3(tr, value, ph_buf, suppress_null, thousandplex, prev_thousands | ordinal | decimal_point); | LookupNum3(tr, value, ph_buf, suppress_null, thousandplex, prev_thousands | ordinal | decimal_point); | ||||
if ((thousandplex > 0) && (tr->langopts.numbers2 & 0x200)) | if ((thousandplex > 0) && (tr->langopts.numbers2 & 0x200)) | ||||
sprintf(ph_out, "%s%s%c%s%s", ph_zeros, ph_append, phonEND_WORD, ph_buf2, ph_buf); // say "thousands" before its number | |||||
sprintf(ph_out, "%s%s%c%s%s", ph_zeros, ph_append, phonEND_WORD, ph_buf2, ph_buf); // say "thousands" before its number | |||||
else | else | ||||
sprintf(ph_out, "%s%s%s%c%s", ph_zeros, ph_buf2, ph_buf, phonEND_WORD, ph_append); | sprintf(ph_out, "%s%s%s%c%s", ph_zeros, ph_buf2, ph_buf, phonEND_WORD, ph_append); | ||||
{ | { | ||||
case NUM_DFRACTION_4: | case NUM_DFRACTION_4: | ||||
max_decimal_count = 5; | max_decimal_count = 5; | ||||
// fallthrough | |||||
// fallthrough: | |||||
case NUM_DFRACTION_2: | case NUM_DFRACTION_2: | ||||
// French/Polish decimal fraction | // French/Polish decimal fraction | ||||
while (word[n_digits] == '0') { | while (word[n_digits] == '0') { | ||||
n_digits += decimal_count; | n_digits += decimal_count; | ||||
} | } | ||||
break; | break; | ||||
case NUM_DFRACTION_1: // italian, say "hundredths" if leading zero | |||||
case NUM_DFRACTION_5: // hungarian, always say "tenths" etc. | |||||
case NUM_DFRACTION_6: // kazakh, always say "tenths" etc, before the decimal fraction | |||||
case NUM_DFRACTION_1: // italian, say "hundredths" if leading zero | |||||
case NUM_DFRACTION_5: // hungarian, always say "tenths" etc. | |||||
case NUM_DFRACTION_6: // kazakh, always say "tenths" etc, before the decimal fraction | |||||
LookupNum3(tr, atoi(&word[n_digits]), ph_buf, 0, 0, 0); | LookupNum3(tr, atoi(&word[n_digits]), ph_buf, 0, 0, 0); | ||||
if ((word[n_digits] == '0') || (decimal_mode != NUM_DFRACTION_1)) { | if ((word[n_digits] == '0') || (decimal_mode != NUM_DFRACTION_1)) { | ||||
// decimal part has leading zeros, so add a "hundredths" or "thousandths" suffix | // decimal part has leading zeros, so add a "hundredths" or "thousandths" suffix | ||||
sprintf(string, "_0Z%d", decimal_count); | sprintf(string, "_0Z%d", decimal_count); | ||||
if (Lookup(tr, string, buf1) == 0) | if (Lookup(tr, string, buf1) == 0) | ||||
break; // revert to speaking single digits | |||||
break; // revert to speaking single digits | |||||
if (decimal_mode == NUM_DFRACTION_6) | if (decimal_mode == NUM_DFRACTION_6) | ||||
strcat(ph_out, buf1); | strcat(ph_out, buf1); | ||||
utf8_in(&next_char, p); | utf8_in(&next_char, p); | ||||
if (!iswalpha2(next_char) && (thousands_exact == 0)) | if (!iswalpha2(next_char) && (thousands_exact == 0)) | ||||
strcat(ph_out, str_pause); // don't add pause for 100s, 6th, etc. | |||||
strcat(ph_out, str_pause); // don't add pause for 100s, 6th, etc. | |||||
} | } | ||||
*flags |= FLAG_FOUND; | *flags |= FLAG_FOUND; | ||||
int TranslateNumber(Translator *tr, char *word1, char *ph_out, unsigned int *flags, WORD_TAB *wtab, int control) | int TranslateNumber(Translator *tr, char *word1, char *ph_out, unsigned int *flags, WORD_TAB *wtab, int control) | ||||
{ | { | ||||
if ((option_sayas == SAYAS_DIGITS1) || (wtab[0].flags & FLAG_INDIVIDUAL_DIGITS)) | if ((option_sayas == SAYAS_DIGITS1) || (wtab[0].flags & FLAG_INDIVIDUAL_DIGITS)) | ||||
return 0; // speak digits individually | |||||
return 0; // speak digits individually | |||||
if (tr->langopts.numbers != 0) | if (tr->langopts.numbers != 0) | ||||
return TranslateNumber_1(tr, word1, ph_out, flags, wtab, control); | return TranslateNumber_1(tr, word1, ph_out, flags, wtab, control); |
{ | { | ||||
#endif | #endif | ||||
// phoneme types | // phoneme types | ||||
#define phPAUSE 0 | #define phPAUSE 0 | ||||
#define phSTRESS 1 | #define phSTRESS 1 | ||||
#define phDELETED 14 | #define phDELETED 14 | ||||
#define phINVALID 15 | #define phINVALID 15 | ||||
// phoneme properties | // phoneme properties | ||||
// bits 16-19 give place of articulation | // bits 16-19 give place of articulation | ||||
#define phARTICULATION 0xf0000 | #define phARTICULATION 0xf0000 | ||||
unsigned char end_type; | unsigned char end_type; | ||||
unsigned char std_length; // for vowels, in mS/2; for phSTRESS phonemes, this is the stress/tone type | unsigned char std_length; // for vowels, in mS/2; for phSTRESS phonemes, this is the stress/tone type | ||||
unsigned char length_mod; // a length_mod group number, used to access length_mod_tab | unsigned char length_mod; // a length_mod group number, used to access length_mod_tab | ||||
} PHONEME_TAB; | } PHONEME_TAB; | ||||
// Several phoneme tables may be loaded into memory. phoneme_tab points to | // Several phoneme tables may be loaded into memory. phoneme_tab points to | ||||
// one for the current voice | // one for the current voice | ||||
extern int n_phoneme_tab; | extern int n_phoneme_tab; | ||||
int equivalence_tables; // lists of equivalent phonemes to match other languages, byte index into phondata | int equivalence_tables; // lists of equivalent phonemes to match other languages, byte index into phondata | ||||
} PHONEME_TAB_LIST; | } PHONEME_TAB_LIST; | ||||
// table of phonemes to be replaced with different phonemes, for the current voice | // table of phonemes to be replaced with different phonemes, for the current voice | ||||
#define N_REPLACE_PHONEMES 60 | #define N_REPLACE_PHONEMES 60 | ||||
typedef struct { | typedef struct { | ||||
extern int n_replace_phonemes; | extern int n_replace_phonemes; | ||||
extern REPLACE_PHONEMES replace_phonemes[N_REPLACE_PHONEMES]; | extern REPLACE_PHONEMES replace_phonemes[N_REPLACE_PHONEMES]; | ||||
// Table of phoneme programs and lengths. Used by MakeVowelLists | // Table of phoneme programs and lengths. Used by MakeVowelLists | ||||
typedef struct { | typedef struct { | ||||
unsigned int addr; | unsigned int addr; | ||||
unsigned int length; | unsigned int length; | ||||
} PHONEME_PROG_LOG; | } PHONEME_PROG_LOG; | ||||
#define PH(c1, c2) (c2<<8)+c1 // combine two characters into an integer for phoneme name | #define PH(c1, c2) (c2<<8)+c1 // combine two characters into an integer for phoneme name | ||||
#define PH3(c1, c2, c3) (c3<<16)+(c2<<8)+c1 | #define PH3(c1, c2, c3) (c3<<16)+(c2<<8)+c1 | ||||
#define PhonemeCode2(c1, c2) PhonemeCode((c2<<8)+c1) | #define PhonemeCode2(c1, c2) PhonemeCode((c2<<8)+c1) |
#include "synthesize.h" | #include "synthesize.h" | ||||
#include "translate.h" | #include "translate.h" | ||||
const unsigned char pause_phonemes[8] = { 0, phonPAUSE_VSHORT, phonPAUSE_SHORT, phonPAUSE, phonPAUSE_LONG, phonGLOTTALSTOP, phonPAUSE_LONG, phonPAUSE_LONG }; | |||||
const unsigned char pause_phonemes[8] = { | |||||
0, phonPAUSE_VSHORT, phonPAUSE_SHORT, phonPAUSE, phonPAUSE_LONG, phonGLOTTALSTOP, phonPAUSE_LONG, phonPAUSE_LONG | |||||
}; | |||||
extern int n_ph_list2; | extern int n_ph_list2; | ||||
extern PHONEME_LIST2 ph_list2[N_PHONEME_LIST]; // first stage of text->phonemes | |||||
extern PHONEME_LIST2 ph_list2[N_PHONEME_LIST]; // first stage of text->phonemes | |||||
static int SubstitutePhonemes(Translator *tr, PHONEME_LIST *plist_out) | static int SubstitutePhonemes(Translator *tr, PHONEME_LIST *plist_out) | ||||
{ | { | ||||
// Copy the phonemes list and perform any substitutions that are required for the | |||||
// current voice | |||||
// Copy the phonemes list and perform any substitutions that are required for the | |||||
// current voice | |||||
int ix; | int ix; | ||||
int k; | int k; | ||||
int replace_flags; | int replace_flags; | ||||
replace_flags = replace_phonemes[k].type; | replace_flags = replace_phonemes[k].type; | ||||
if ((replace_flags & 1) && (word_end == 0)) | if ((replace_flags & 1) && (word_end == 0)) | ||||
continue; // this replacement only occurs at the end of a word | |||||
continue; // this replacement only occurs at the end of a word | |||||
if ((replace_flags & 2) && ((plist2->stresslevel & 0x7) > 3)) | if ((replace_flags & 2) && ((plist2->stresslevel & 0x7) > 3)) | ||||
continue; // this replacement doesn't occur in stressed syllables | |||||
continue; // this replacement doesn't occur in stressed syllables | |||||
if ((replace_flags & 4) && (plist2->sourceix == 0)) | if ((replace_flags & 4) && (plist2->sourceix == 0)) | ||||
continue; // this replacement only occurs at the start of a word | |||||
continue; // this replacement only occurs at the start of a word | |||||
// substitute the replacement phoneme | // substitute the replacement phoneme | ||||
plist2->phcode = replace_phonemes[k].new_ph; | plist2->phcode = replace_phonemes[k].new_ph; | ||||
if ((plist2->stresslevel > 1) && (phoneme_tab[plist2->phcode]->phflags & phUNSTRESSED)) | if ((plist2->stresslevel > 1) && (phoneme_tab[plist2->phcode]->phflags & phUNSTRESSED)) | ||||
plist2->stresslevel = 0; // the replacement must be unstressed | |||||
plist2->stresslevel = 0; // the replacement must be unstressed | |||||
break; | break; | ||||
} | } | ||||
} | } | ||||
if (plist2->phcode == 0) | if (plist2->phcode == 0) | ||||
continue; // phoneme has been replaced by NULL, so don't copy it | |||||
continue; // phoneme has been replaced by NULL, so don't copy it | |||||
} | } | ||||
// copy phoneme into the output list | // copy phoneme into the output list | ||||
// the last word is unstressed, look for a previous word that can be stressed | // the last word is unstressed, look for a previous word that can be stressed | ||||
while (--j >= 0) { | while (--j >= 0) { | ||||
if (plist2[j].synthflags & SFLAG_PROMOTE_STRESS) { // dictionary flags indicated that this stress can be promoted | if (plist2[j].synthflags & SFLAG_PROMOTE_STRESS) { // dictionary flags indicated that this stress can be promoted | ||||
plist2[j].stresslevel = 4; // promote to stressed | |||||
plist2[j].stresslevel = 4; // promote to stressed | |||||
break; | break; | ||||
} | } | ||||
if (plist2[j].stresslevel >= 4) { | if (plist2[j].stresslevel >= 4) { | ||||
stop_propagation = 0; | stop_propagation = 0; | ||||
voicing = 0; | voicing = 0; | ||||
if (regression & 0x100) | if (regression & 0x100) | ||||
voicing = 1; // word-end devoicing | |||||
voicing = 1; // word-end devoicing | |||||
continue; | continue; | ||||
} | } | ||||
if ((voicing == 0) && (regression & 0xf)) | if ((voicing == 0) && (regression & 0xf)) | ||||
voicing = 1; | voicing = 1; | ||||
else if ((voicing == 2) && (ph->end_type != 0)) // use end_type field for voicing_switch for consonants | else if ((voicing == 2) && (ph->end_type != 0)) // use end_type field for voicing_switch for consonants | ||||
plist2[j].phcode = ph->end_type; // change to voiced equivalent | |||||
plist2[j].phcode = ph->end_type; // change to voiced equivalent | |||||
} else if ((type == phVSTOP) || type == (phVFRICATIVE)) { | } else if ((type == phVSTOP) || type == (phVFRICATIVE)) { | ||||
if ((voicing == 0) && (regression & 0xf)) | if ((voicing == 0) && (regression & 0xf)) | ||||
voicing = 2; | voicing = 2; | ||||
else if ((voicing == 1) && (ph->end_type != 0)) | else if ((voicing == 1) && (ph->end_type != 0)) | ||||
plist2[j].phcode = ph->end_type; // change to unvoiced equivalent | |||||
plist2[j].phcode = ph->end_type; // change to unvoiced equivalent | |||||
} else { | } else { | ||||
if (regression & 0x8) { | if (regression & 0x8) { | ||||
// LANG=Polish, propagate through liquids and nasals | // LANG=Polish, propagate through liquids and nasals | ||||
nextw++; | nextw++; | ||||
if (ph_list3[nextw].sourceix) | if (ph_list3[nextw].sourceix) | ||||
break; // start of the next word | |||||
break; // start of the next word | |||||
} | } | ||||
for (k = j; k < nextw; k++) | for (k = j; k < nextw; k++) | ||||
ph_list3[k].wordstress = word_stress; | ph_list3[k].wordstress = word_stress; | ||||
ph = phoneme_tab[insert_ph]; | ph = phoneme_tab[insert_ph]; | ||||
plist3->ph = ph; | plist3->ph = ph; | ||||
insert_ph = 0; | insert_ph = 0; | ||||
inserted = 1; // don't insert the same phoneme repeatedly | |||||
inserted = 1; // don't insert the same phoneme repeatedly | |||||
} else { | } else { | ||||
// otherwise get the next phoneme from the list | // otherwise get the next phoneme from the list | ||||
if (plist3->sourceix != 0) | if (plist3->sourceix != 0) | ||||
// change phoneme table | // change phoneme table | ||||
SelectPhonemeTable(plist3->tone_ph); | SelectPhonemeTable(plist3->tone_ph); | ||||
} | } | ||||
next = phoneme_tab[plist3[1].phcode]; // the phoneme after this one | |||||
next = phoneme_tab[plist3[1].phcode]; // the phoneme after this one | |||||
plist3[1].ph = next; | plist3[1].ph = next; | ||||
} | } | ||||
if (ph->type == phVOWEL) { | if (ph->type == phVOWEL) { | ||||
plist3->synthflags |= SFLAG_SYLLABLE; | plist3->synthflags |= SFLAG_SYLLABLE; | ||||
if (ph2->type != phVOWEL) | if (ph2->type != phVOWEL) | ||||
plist3->stresslevel = 0; // change from non-vowel to vowel, make sure it's unstressed | |||||
plist3->stresslevel = 0; // change from non-vowel to vowel, make sure it's unstressed | |||||
} else | } else | ||||
plist3->synthflags &= ~SFLAG_SYLLABLE; | plist3->synthflags &= ~SFLAG_SYLLABLE; | ||||
plist3->phcode = alternative; | plist3->phcode = alternative; | ||||
if (alternative == 1) | if (alternative == 1) | ||||
deleted = 1; // NULL phoneme, discard | |||||
deleted = 1; // NULL phoneme, discard | |||||
else { | else { | ||||
if (ph->type == phVOWEL) { | if (ph->type == phVOWEL) { | ||||
plist3->synthflags |= SFLAG_SYLLABLE; | plist3->synthflags |= SFLAG_SYLLABLE; | ||||
if (ph2->type != phVOWEL) | if (ph2->type != phVOWEL) | ||||
plist3->stresslevel = 0; // change from non-vowel to vowel, make sure it's unstressed | |||||
plist3->stresslevel = 0; // change from non-vowel to vowel, make sure it's unstressed | |||||
} else | } else | ||||
plist3->synthflags &= ~SFLAG_SYLLABLE; | plist3->synthflags &= ~SFLAG_SYLLABLE; | ||||
// stress. But not for the last phoneme of a stressed word | // stress. But not for the last phoneme of a stressed word | ||||
if ((tr->langopts.stress_flags & S_NO_DIM) || ((word_stress > 3) && ((plist3+1)->sourceix != 0))) { | if ((tr->langopts.stress_flags & S_NO_DIM) || ((word_stress > 3) && ((plist3+1)->sourceix != 0))) { | ||||
// An unstressed final vowel of a stressed word | // An unstressed final vowel of a stressed word | ||||
unstress_count = 1; // try again for next syllable | |||||
unstress_count = 1; // try again for next syllable | |||||
} else | } else | ||||
plist3->stresslevel = 0; // change stress to 'diminished' | |||||
plist3->stresslevel = 0; // change stress to 'diminished' | |||||
} | } | ||||
} | } | ||||
} else { | |||||
} else | |||||
unstress_count = 0; | unstress_count = 0; | ||||
} | |||||
} | } | ||||
if ((plist3+1)->synthflags & SFLAG_LENGTHEN) { | if ((plist3+1)->synthflags & SFLAG_LENGTHEN) { | ||||
if (deleted == 0) { | if (deleted == 0) { | ||||
phlist[ix].ph = ph; | phlist[ix].ph = ph; | ||||
phlist[ix].type = ph->type; | phlist[ix].type = ph->type; | ||||
phlist[ix].env = PITCHfall; // default, can be changed in the "intonation" module | |||||
phlist[ix].env = PITCHfall; // default, can be changed in the "intonation" module | |||||
phlist[ix].synthflags = plist3->synthflags; | phlist[ix].synthflags = plist3->synthflags; | ||||
phlist[ix].stresslevel = plist3->stresslevel & 0xf; | phlist[ix].stresslevel = plist3->stresslevel & 0xf; | ||||
phlist[ix].wordstress = plist3->wordstress; | phlist[ix].wordstress = plist3->wordstress; | ||||
if (plist3->sourceix != 0) { | if (plist3->sourceix != 0) { | ||||
phlist[ix].sourceix = plist3->sourceix; | phlist[ix].sourceix = plist3->sourceix; | ||||
phlist[ix].newword = 1; // this phoneme is the start of a word | |||||
phlist[ix].newword = 1; // this phoneme is the start of a word | |||||
if (start_sentence) { | if (start_sentence) { | ||||
phlist[ix].newword = 5; // start of sentence + start of word | |||||
phlist[ix].newword = 5; // start of sentence + start of word | |||||
start_sentence = 0; | start_sentence = 0; | ||||
} | } | ||||
} else | } else | ||||
phlist[ix].length = phdata.pd_param[i_SET_LENGTH]*2; | phlist[ix].length = phdata.pd_param[i_SET_LENGTH]*2; | ||||
if ((ph->code == phonPAUSE_LONG) && (option_wordgap > 0) && (plist3[1].sourceix != 0)) { | if ((ph->code == phonPAUSE_LONG) && (option_wordgap > 0) && (plist3[1].sourceix != 0)) { | ||||
phlist[ix].ph = phoneme_tab[phonPAUSE_SHORT]; | phlist[ix].ph = phoneme_tab[phonPAUSE_SHORT]; | ||||
phlist[ix].length = option_wordgap*14; // 10mS per unit at the default speed | |||||
phlist[ix].length = option_wordgap*14; // 10mS per unit at the default speed | |||||
} | } | ||||
if (ph->type == phVOWEL || ph->type == phLIQUID || ph->type == phNASAL || ph->type == phVSTOP || ph->type == phVFRICATIVE || (ph->phflags & phPREVOICE)) { | if (ph->type == phVOWEL || ph->type == phLIQUID || ph->type == phNASAL || ph->type == phVSTOP || ph->type == phVFRICATIVE || (ph->phflags & phPREVOICE)) { | ||||
phlist[ix].length = 128; // length_mod | |||||
phlist[ix].length = 128; // length_mod | |||||
phlist[ix].env = PITCHfall; | phlist[ix].env = PITCHfall; | ||||
} | } | ||||
phlist[ix].prepause = 0; | phlist[ix].prepause = 0; | ||||
phlist[ix].amp = 20; // default, will be changed later | |||||
phlist[ix].amp = 20; // default, will be changed later | |||||
phlist[ix].pitch1 = 255; | phlist[ix].pitch1 = 255; | ||||
phlist[ix].pitch2 = 255; | phlist[ix].pitch2 = 255; | ||||
ix++; | ix++; | ||||
} | } | ||||
} | } | ||||
phlist[ix].newword = 2; // end of clause | |||||
phlist[ix].newword = 2; // end of clause | |||||
phlist[ix].phcode = phonPAUSE; | phlist[ix].phcode = phonPAUSE; | ||||
phlist[ix].type = phPAUSE; // terminate with 2 Pause phonemes | |||||
phlist[ix].length = post_pause; // length of the pause, depends on the punctuation | |||||
phlist[ix].type = phPAUSE; // terminate with 2 Pause phonemes | |||||
phlist[ix].length = post_pause; // length of the pause, depends on the punctuation | |||||
phlist[ix].sourceix = end_sourceix; | phlist[ix].sourceix = end_sourceix; | ||||
phlist[ix].synthflags = 0; | phlist[ix].synthflags = 0; | ||||
phlist[ix++].ph = phoneme_tab[phonPAUSE]; | phlist[ix++].ph = phoneme_tab[phonPAUSE]; |
// convert from words-per-minute to internal speed factor | // convert from words-per-minute to internal speed factor | ||||
// Use this to calibrate speed for wpm 80-350 | // Use this to calibrate speed for wpm 80-350 | ||||
static unsigned char speed_lookup[] = { | static unsigned char speed_lookup[] = { | ||||
255, 255, 255, 255, 255, // 80 | |||||
253, 249, 245, 242, 238, // 85 | |||||
235, 232, 228, 225, 222, // 90 | |||||
218, 216, 213, 210, 207, // 95 | |||||
255, 255, 255, 255, 255, // 80 | |||||
253, 249, 245, 242, 238, // 85 | |||||
235, 232, 228, 225, 222, // 90 | |||||
218, 216, 213, 210, 207, // 95 | |||||
204, 201, 198, 196, 193, // 100 | 204, 201, 198, 196, 193, // 100 | ||||
191, 188, 186, 183, 181, // 105 | 191, 188, 186, 183, 181, // 105 | ||||
179, 176, 174, 172, 169, // 110 | 179, 176, 174, 172, 169, // 110 | ||||
118, 117, 115, 114, 113, // 145 | 118, 117, 115, 114, 113, // 145 | ||||
112, 111, 110, 109, 107, // 150 | 112, 111, 110, 109, 107, // 150 | ||||
106, 105, 104, 103, 102, // 155 | 106, 105, 104, 103, 102, // 155 | ||||
101, 100, 99, 98, 97,// 160 | |||||
96, 95, 94, 93, 92, // 165 | |||||
91, 90, 89, 89, 88, // 170 | |||||
87, 86, 85, 84, 83, // 175 | |||||
82, 82, 81, 80, 80, // 180 | |||||
79, 78, 77, 76, 76, // 185 | |||||
75, 75, 74, 73, 72, // 190 | |||||
71, 71, 70, 69, 69, // 195 | |||||
68, 67, 67, 66, 66, // 200 | |||||
65, 64, 64, 63, 62, // 205 | |||||
62, 61, 61, 60, 59, // 210 | |||||
59, 58, 58, 57, 57, // 215 | |||||
56, 56, 55, 54, 54, // 220 | |||||
53, 53, 52, 52, 52, // 225 | |||||
51, 50, 50, 49, 49, // 230 | |||||
48, 48, 47, 47, 46, // 235 | |||||
46, 46, 45, 45, 44, // 240 | |||||
44, 44, 43, 43, 42, // 245 | |||||
41, 40, 40, 40, 39, // 250 | |||||
39, 39, 38, 38, 38, // 255 | |||||
37, 37, 37, 36, 36, // 260 | |||||
35, 35, 35, 35, 34, // 265 | |||||
34, 34, 33, 33, 33, // 270 | |||||
32, 32, 31, 31, 31, // 275 | |||||
30, 30, 30, 29, 29, // 280 | |||||
29, 29, 28, 28, 27, // 285 | |||||
27, 27, 27, 26, 26, // 290 | |||||
26, 26, 25, 25, 25, // 295 | |||||
24, 24, 24, 24, 23, // 300 | |||||
23, 23, 23, 22, 22, // 305 | |||||
22, 21, 21, 21, 21, // 310 | |||||
20, 20, 20, 20, 19, // 315 | |||||
19, 19, 18, 18, 17, // 320 | |||||
17, 17, 16, 16, 16, // 325 | |||||
16, 16, 16, 15, 15, // 330 | |||||
15, 15, 14, 14, 14, // 335 | |||||
13, 13, 13, 12, 12, // 340 | |||||
12, 12, 11, 11, 11, // 345 | |||||
11, 10, 10, 10, 9, // 350 | |||||
9, 9, 8, 8, 8, // 355 | |||||
101, 100, 99, 98, 97, // 160 | |||||
96, 95, 94, 93, 92, // 165 | |||||
91, 90, 89, 89, 88, // 170 | |||||
87, 86, 85, 84, 83, // 175 | |||||
82, 82, 81, 80, 80, // 180 | |||||
79, 78, 77, 76, 76, // 185 | |||||
75, 75, 74, 73, 72, // 190 | |||||
71, 71, 70, 69, 69, // 195 | |||||
68, 67, 67, 66, 66, // 200 | |||||
65, 64, 64, 63, 62, // 205 | |||||
62, 61, 61, 60, 59, // 210 | |||||
59, 58, 58, 57, 57, // 215 | |||||
56, 56, 55, 54, 54, // 220 | |||||
53, 53, 52, 52, 52, // 225 | |||||
51, 50, 50, 49, 49, // 230 | |||||
48, 48, 47, 47, 46, // 235 | |||||
46, 46, 45, 45, 44, // 240 | |||||
44, 44, 43, 43, 42, // 245 | |||||
41, 40, 40, 40, 39, // 250 | |||||
39, 39, 38, 38, 38, // 255 | |||||
37, 37, 37, 36, 36, // 260 | |||||
35, 35, 35, 35, 34, // 265 | |||||
34, 34, 33, 33, 33, // 270 | |||||
32, 32, 31, 31, 31, // 275 | |||||
30, 30, 30, 29, 29, // 280 | |||||
29, 29, 28, 28, 27, // 285 | |||||
27, 27, 27, 26, 26, // 290 | |||||
26, 26, 25, 25, 25, // 295 | |||||
24, 24, 24, 24, 23, // 300 | |||||
23, 23, 23, 22, 22, // 305 | |||||
22, 21, 21, 21, 21, // 310 | |||||
20, 20, 20, 20, 19, // 315 | |||||
19, 19, 18, 18, 17, // 320 | |||||
17, 17, 16, 16, 16, // 325 | |||||
16, 16, 16, 15, 15, // 330 | |||||
15, 15, 14, 14, 14, // 335 | |||||
13, 13, 13, 12, 12, // 340 | |||||
12, 12, 11, 11, 11, // 345 | |||||
11, 10, 10, 10, 9, // 350 | |||||
9, 9, 8, 8, 8, // 355 | |||||
}; | }; | ||||
// speed_factor1 adjustments for speeds 350 to 374: pauses | // speed_factor1 adjustments for speeds 350 to 374: pauses | ||||
static unsigned char pause_factor_350[] = { | static unsigned char pause_factor_350[] = { | ||||
22, 22, 22, 22, 22, 22, 22, 21, 21, 21, // 350 | 22, 22, 22, 22, 22, 22, 22, 21, 21, 21, // 350 | ||||
21, 20, 20, 19, 19, 18, 17, 16, 15, 15, // 360 | 21, 20, 20, 19, 19, 18, 17, 16, 15, 15, // 360 | ||||
15, 15, 15, 15, 15 | |||||
}; // 370 | |||||
15, 15, 15, 15, 15 // 370 | |||||
}; | |||||
// wav_factor adjustments for speeds 350 to 450 | // wav_factor adjustments for speeds 350 to 450 | ||||
// Use this to calibrate speed for wpm 350-450 | // Use this to calibrate speed for wpm 350-450 | ||||
111, 111, 110, 109, 108, // 365 | 111, 111, 110, 109, 108, // 365 | ||||
107, 106, 106, 104, 103, // 370 | 107, 106, 106, 104, 103, // 370 | ||||
103, 102, 102, 102, 101, // 375 | 103, 102, 102, 102, 101, // 375 | ||||
101, 99, 98, 98, 97,// 380 | |||||
96, 96, 95, 94, 93, // 385 | |||||
91, 90, 91, 90, 89, // 390 | |||||
88, 86, 85, 86, 85, // 395 | |||||
85, 84, 82, 81, 80, // 400 | |||||
79, 77, 78, 78, 76, // 405 | |||||
77, 75, 75, 74, 73, // 410 | |||||
71, 72, 70, 69, 69, // 415 | |||||
69, 67, 65, 64, 63, // 420 | |||||
63, 63, 61, 61, 59, // 425 | |||||
59, 59, 58, 56, 57, // 430 | |||||
58, 56, 54, 53, 52, // 435 | |||||
52, 53, 52, 52, 50, // 440 | |||||
48, 47, 47, 45, 46, // 445 | |||||
45 | |||||
}; // 450 | |||||
101, 99, 98, 98, 97, // 380 | |||||
96, 96, 95, 94, 93, // 385 | |||||
91, 90, 91, 90, 89, // 390 | |||||
88, 86, 85, 86, 85, // 395 | |||||
85, 84, 82, 81, 80, // 400 | |||||
79, 77, 78, 78, 76, // 405 | |||||
77, 75, 75, 74, 73, // 410 | |||||
71, 72, 70, 69, 69, // 415 | |||||
69, 67, 65, 64, 63, // 420 | |||||
63, 63, 61, 61, 59, // 425 | |||||
59, 59, 58, 56, 57, // 430 | |||||
58, 56, 54, 53, 52, // 435 | |||||
52, 53, 52, 52, 50, // 440 | |||||
48, 47, 47, 45, 46, // 445 | |||||
45 // 450 | |||||
}; | |||||
static int speed1 = 130; | static int speed1 = 130; | ||||
static int speed2 = 121; | static int speed2 = 121; | ||||
speed.loud_consonants = 0; | speed.loud_consonants = 0; | ||||
speed.min_sample_len = 450; | speed.min_sample_len = 450; | ||||
speed.lenmod_factor = 110; // controls the effect of FRFLAG_LEN_MOD reduce length change | |||||
speed.lenmod_factor = 110; // controls the effect of FRFLAG_LEN_MOD reduce length change | |||||
speed.lenmod2_factor = 100; | speed.lenmod2_factor = 100; | ||||
speed.min_pause = 5; | speed.min_pause = 5; | ||||
s1 = (x * voice->speedf1)/256; | s1 = (x * voice->speedf1)/256; | ||||
if (wpm >= 170) | if (wpm >= 170) | ||||
speed.wav_factor = 110 + (150*s1)/128; // reduced speed adjustment, used for playing recorded sounds | |||||
speed.wav_factor = 110 + (150*s1)/128; // reduced speed adjustment, used for playing recorded sounds | |||||
else | else | ||||
speed.wav_factor = 128 + (128*s1)/130; // = 215 at 170 wpm | |||||
speed.wav_factor = 128 + (128*s1)/130; // = 215 at 170 wpm | |||||
if (wpm >= 350) | if (wpm >= 350) | ||||
speed.wav_factor = wav_factor_350[wpm-350]; | speed.wav_factor = wav_factor_350[wpm-350]; | ||||
speed.min_sample_len = 420 - (wpm - 440); | speed.min_sample_len = 420 - (wpm - 440); | ||||
} | } | ||||
// adjust for different sample rates | |||||
// adjust for different sample rates | |||||
speed.min_sample_len = (speed.min_sample_len * samplerate_native) / 22050; | speed.min_sample_len = (speed.min_sample_len * samplerate_native) / 22050; | ||||
speed.pause_factor = (256 * s1)/115; // full speed adjustment, used for pause length | |||||
speed.pause_factor = (256 * s1)/115; // full speed adjustment, used for pause length | |||||
speed.clause_pause_factor = 0; | speed.clause_pause_factor = 0; | ||||
if (wpm > 430) | if (wpm > 430) | ||||
} | } | ||||
} | } | ||||
#else // not using sonic speed-up | |||||
#else | |||||
void SetSpeed(int control) | void SetSpeed(int control) | ||||
{ | { | ||||
// This is the earlier version of SetSpeed() before sonic speed-up was added | |||||
// This is the earlier version of SetSpeed() before sonic speed-up was added | |||||
int x; | int x; | ||||
int s1; | int s1; | ||||
int wpm; | int wpm; | ||||
speed.loud_consonants = 0; | speed.loud_consonants = 0; | ||||
speed.min_sample_len = 450; | speed.min_sample_len = 450; | ||||
speed.lenmod_factor = 110; // controls the effect of FRFLAG_LEN_MOD reduce length change | |||||
speed.lenmod_factor = 110; // controls the effect of FRFLAG_LEN_MOD reduce length change | |||||
speed.lenmod2_factor = 100; | speed.lenmod2_factor = 100; | ||||
wpm = embedded_value[EMBED_S]; | wpm = embedded_value[EMBED_S]; | ||||
s1 = (x * voice->speedf1)/256; | s1 = (x * voice->speedf1)/256; | ||||
if (wpm >= 170) | if (wpm >= 170) | ||||
speed.wav_factor = 110 + (150*s1)/128; // reduced speed adjustment, used for playing recorded sounds | |||||
speed.wav_factor = 110 + (150*s1)/128; // reduced speed adjustment, used for playing recorded sounds | |||||
else | else | ||||
speed.wav_factor = 128 + (128*s1)/130; // = 215 at 170 wpm | |||||
speed.wav_factor = 128 + (128*s1)/130; // = 215 at 170 wpm | |||||
if (wpm >= 350) | if (wpm >= 350) | ||||
speed.wav_factor = wav_factor_350[wpm-350]; | speed.wav_factor = wav_factor_350[wpm-350]; | ||||
speed.min_sample_len = 420 - (wpm - 440); | speed.min_sample_len = 420 - (wpm - 440); | ||||
} | } | ||||
speed.pause_factor = (256 * s1)/115; // full speed adjustment, used for pause length | |||||
speed.pause_factor = (256 * s1)/115; // full speed adjustment, used for pause length | |||||
speed.clause_pause_factor = 0; | speed.clause_pause_factor = 0; | ||||
if (wpm > 430) | if (wpm > 430) | ||||
#endif | #endif | ||||
void SetParameter(int parameter, int value, int relative) | void SetParameter(int parameter, int value, int relative) | ||||
{ | { | ||||
// parameter: reset-all, amp, pitch, speed, linelength, expression, capitals, number grouping | |||||
// relative 0=absolute 1=relative | |||||
// parameter: reset-all, amp, pitch, speed, linelength, expression, capitals, number grouping | |||||
// relative 0=absolute 1=relative | |||||
int new_value = value; | int new_value = value; | ||||
int default_value; | int default_value; | ||||
if ((word & 0x1f) == EMBED_S) { | if ((word & 0x1f) == EMBED_S) { | ||||
// speed | // speed | ||||
SetEmbedded(word & 0x7f, word >> 8); // adjusts embedded_value[EMBED_S] | |||||
SetEmbedded(word & 0x7f, word >> 8); // adjusts embedded_value[EMBED_S] | |||||
SetSpeed(1); | SetSpeed(1); | ||||
} | } | ||||
} while ((word & 0x80) == 0); | } while ((word & 0x80) == 0); | ||||
if ((p->ph->phflags & phSIBILANT) && next->type == phSTOP && !next->newword) { | if ((p->ph->phflags & phSIBILANT) && next->type == phSTOP && !next->newword) { | ||||
if (prev->type == phVOWEL) | if (prev->type == phVOWEL) | ||||
p->length = 200; // ?? should do this if it's from a prefix | |||||
p->length = 200; // ?? should do this if it's from a prefix | |||||
else | else | ||||
p->length = 150; | p->length = 150; | ||||
} else | } else | ||||
p->prepause = 40; | p->prepause = 40; | ||||
if (prev->type == phVOWEL) { | if (prev->type == phVOWEL) { | ||||
p->prepause = 0; // use murmur instead to link from the preceding vowel | |||||
p->prepause = 0; // use murmur instead to link from the preceding vowel | |||||
} else if (prev->type == phPAUSE) { | } else if (prev->type == phPAUSE) { | ||||
// reduce by the length of the preceding pause | // reduce by the length of the preceding pause | ||||
if (prev->length < p->prepause) | if (prev->length < p->prepause) | ||||
break; | break; | ||||
case phLIQUID: | case phLIQUID: | ||||
case phNASAL: | case phNASAL: | ||||
p->amp = tr->stress_amps[0]; // unless changed later | |||||
p->length = 256; // TEMPORARY | |||||
p->amp = tr->stress_amps[0]; // unless changed later | |||||
p->length = 256; // TEMPORARY | |||||
min_drop = 0; | min_drop = 0; | ||||
if (p->newword) { | if (p->newword) { | ||||
length_mod = length_mod / 128; | length_mod = length_mod / 128; | ||||
if (length_mod < 8) | if (length_mod < 8) | ||||
length_mod = 8; // restrict how much lengths can be reduced | |||||
length_mod = 8; // restrict how much lengths can be reduced | |||||
if (stress >= 7) { | if (stress >= 7) { | ||||
// tonic syllable, include a constant component so it doesn't decrease directly with speed | // tonic syllable, include a constant component so it doesn't decrease directly with speed | ||||
// this is the last syllable in the clause, lengthen it - more for short vowels | // this is the last syllable in the clause, lengthen it - more for short vowels | ||||
len = (p->ph->std_length * 2); | len = (p->ph->std_length * 2); | ||||
if (tr->langopts.stress_flags & S_EO_CLAUSE1) | if (tr->langopts.stress_flags & S_EO_CLAUSE1) | ||||
len = 200; // don't lengthen short vowels more than long vowels at end-of-clause | |||||
len = 200; // don't lengthen short vowels more than long vowels at end-of-clause | |||||
length_mod = length_mod * (256 + (280 - len)/3)/256; | length_mod = length_mod * (256 + (280 - len)/3)/256; | ||||
} | } | ||||
// pre-vocalic part | // pre-vocalic part | ||||
// set last-pitch | // set last-pitch | ||||
env2 = p->env + 1; // version for use with preceding semi-vowel | |||||
env2 = p->env + 1; // version for use with preceding semi-vowel | |||||
if (p->tone_ph != 0) { | if (p->tone_ph != 0) { | ||||
InterpretPhoneme2(p->tone_ph, &phdata_tone); | InterpretPhoneme2(p->tone_ph, &phdata_tone); | ||||
if (pre_sonorant || pre_voiced) { | if (pre_sonorant || pre_voiced) { | ||||
// set pitch for pre-vocalic part | // set pitch for pre-vocalic part | ||||
if (pitch_start == 255) | if (pitch_start == 255) | ||||
last_pitch = pitch_start; // pitch is not set | |||||
last_pitch = pitch_start; // pitch is not set | |||||
if (pitch_start - last_pitch > 16) | if (pitch_start - last_pitch > 16) | ||||
last_pitch = pitch_start - 16; | last_pitch = pitch_start - 16; |
int (*uri_callback)(int, const char *, const char *) = NULL; | int (*uri_callback)(int, const char *, const char *) = NULL; | ||||
int (*phoneme_callback)(const char *) = NULL; | int (*phoneme_callback)(const char *) = NULL; | ||||
char path_home[N_PATH_HOME]; // this is the espeak-data directory | |||||
char path_home[N_PATH_HOME]; // this is the espeak-data directory | |||||
extern int saved_parameters[N_SPEECH_PARAM]; // Parameters saved on synthesis start | extern int saved_parameters[N_SPEECH_PARAM]; // Parameters saved on synthesis start | ||||
void WVoiceChanged(voice_t *wvoice) | void WVoiceChanged(voice_t *wvoice) | ||||
{ | { | ||||
// Voice change in wavegen | |||||
// Voice change in wavegen | |||||
voice_samplerate = wvoice->samplerate; | voice_samplerate = wvoice->samplerate; | ||||
} | } | ||||
my_mode = output_type; | my_mode = output_type; | ||||
my_audio = NULL; | my_audio = NULL; | ||||
synchronous_mode = 1; | synchronous_mode = 1; | ||||
option_waveout = 1; // inhibit portaudio callback from wavegen.cpp | |||||
option_waveout = 1; // inhibit portaudio callback from wavegen.cpp | |||||
out_samplerate = 0; | out_samplerate = 0; | ||||
switch (my_mode) | switch (my_mode) | ||||
return 0; | return 0; | ||||
if (S_ISDIR(statbuf.st_mode)) | if (S_ISDIR(statbuf.st_mode)) | ||||
return -2; // a directory | |||||
return -2; // a directory | |||||
return statbuf.st_size; | return statbuf.st_size; | ||||
} | } | ||||
{ | { | ||||
char *p; | char *p; | ||||
if ((p = (char *)malloc(size)) == NULL) | if ((p = (char *)malloc(size)) == NULL) | ||||
fprintf(stderr, "Can't allocate memory\n"); // I was told that size+1 fixes a crash on 64-bit systems | |||||
fprintf(stderr, "Can't allocate memory\n"); // I was told that size+1 fixes a crash on 64-bit systems | |||||
return p; | return p; | ||||
} | } | ||||
if ((env = getenv("ESPEAK_DATA_PATH")) != NULL) { | if ((env = getenv("ESPEAK_DATA_PATH")) != NULL) { | ||||
sprintf(path_home, "%s/espeak-data", env); | sprintf(path_home, "%s/espeak-data", env); | ||||
if (GetFileLength(path_home) == -2) | if (GetFileLength(path_home) == -2) | ||||
return; // an espeak-data directory exists | |||||
return; // an espeak-data directory exists | |||||
} | } | ||||
buf[0] = 0; | buf[0] = 0; | ||||
if ((env = getenv("ESPEAK_DATA_PATH")) != NULL) { | if ((env = getenv("ESPEAK_DATA_PATH")) != NULL) { | ||||
snprintf(path_home, sizeof(path_home), "%s/espeak-data", env); | snprintf(path_home, sizeof(path_home), "%s/espeak-data", env); | ||||
if (GetFileLength(path_home) == -2) | if (GetFileLength(path_home) == -2) | ||||
return; // an espeak-data directory exists | |||||
return; // an espeak-data directory exists | |||||
} | } | ||||
snprintf(path_home, sizeof(path_home), "%s/espeak-data", getenv("HOME")); | snprintf(path_home, sizeof(path_home), "%s/espeak-data", getenv("HOME")); | ||||
{ | { | ||||
int param; | int param; | ||||
int result; | int result; | ||||
int srate = 22050; // default sample rate 22050 Hz | |||||
int srate = 22050; // default sample rate 22050 Hz | |||||
err = EE_OK; | err = EE_OK; | ||||
LoadConfig(); | LoadConfig(); | ||||
#endif | #endif | ||||
if ((outbuf == NULL) || (event_list == NULL)) | if ((outbuf == NULL) || (event_list == NULL)) | ||||
return EE_INTERNAL_ERROR; // espeak_Initialize() has not been called | |||||
return EE_INTERNAL_ERROR; // espeak_Initialize() has not been called | |||||
option_multibyte = flags & 7; | option_multibyte = flags & 7; | ||||
option_ssml = flags & espeakSSML; | option_ssml = flags & espeakSSML; | ||||
if (my_mode == AUDIO_OUTPUT_SYNCH_PLAYBACK) { | if (my_mode == AUDIO_OUTPUT_SYNCH_PLAYBACK) { | ||||
for (;;) { | for (;;) { | ||||
#ifdef PLATFORM_WINDOWS | #ifdef PLATFORM_WINDOWS | ||||
Sleep(300); // 0.3s | |||||
Sleep(300); // 0.3s | |||||
#else | #else | ||||
#ifdef USE_NANOSLEEP | #ifdef USE_NANOSLEEP | ||||
struct timespec period; | struct timespec period; | ||||
struct timespec remaining; | struct timespec remaining; | ||||
period.tv_sec = 0; | period.tv_sec = 0; | ||||
period.tv_nsec = 300000000; // 0.3 sec | |||||
period.tv_nsec = 300000000; // 0.3 sec | |||||
nanosleep(&period, &remaining); | nanosleep(&period, &remaining); | ||||
#else | #else | ||||
sleep(1); | sleep(1); | ||||
} else | } else | ||||
finished = synth_callback((short *)outbuf, length, event_list); | finished = synth_callback((short *)outbuf, length, event_list); | ||||
if (finished) { | if (finished) { | ||||
SpeakNextClause(NULL, 0, 2); // stop | |||||
SpeakNextClause(NULL, 0, 2); // stop | |||||
break; | break; | ||||
} | } | ||||
if (dispatch_audio(NULL, 0, NULL) < 0) // TBD: test case | if (dispatch_audio(NULL, 0, NULL) < 0) // TBD: test case | ||||
return err = EE_INTERNAL_ERROR; | return err = EE_INTERNAL_ERROR; | ||||
} else | } else | ||||
synth_callback(NULL, 0, event_list); // NULL buffer ptr indicates end of data | |||||
synth_callback(NULL, 0, event_list); // NULL buffer ptr indicates end of data | |||||
#else | #else | ||||
synth_callback(NULL, 0, event_list); // NULL buffer ptr indicates end of data | |||||
synth_callback(NULL, 0, event_list); // NULL buffer ptr indicates end of data | |||||
#endif | #endif | ||||
break; | break; | ||||
} | } | ||||
my_unique_identifier = 0; | my_unique_identifier = 0; | ||||
my_user_data = NULL; | my_user_data = NULL; | ||||
Synthesize(0, key, 0); // speak key as a text string | |||||
Synthesize(0, key, 0); // speak key as a text string | |||||
} | } | ||||
void sync_espeak_Char(wchar_t character) | void sync_espeak_Char(wchar_t character) | ||||
ESPEAK_API void espeak_SetPhonemeTrace(int phonememode, FILE *stream) | ESPEAK_API void espeak_SetPhonemeTrace(int phonememode, FILE *stream) | ||||
{ | { | ||||
ENTER("espeak_SetPhonemes"); | ENTER("espeak_SetPhonemes"); | ||||
/* phonememode: Controls the output of phoneme symbols for the text | |||||
bits 0-2: | |||||
value=0 No phoneme output (default) | |||||
value=1 Output the translated phoneme symbols for the text | |||||
value=2 as (1), but produces IPA phoneme names rather than ascii | |||||
bit 3: output a trace of how the translation was done (showing the matching rules and list entries) | |||||
bit 4: produce pho data for mbrola | |||||
bit 7: use (bits 8-23) as a tie within multi-letter phonemes names | |||||
bits 8-23: separator character, between phoneme names | |||||
stream output stream for the phoneme symbols (and trace). If stream=NULL then it uses stdout. | |||||
*/ | |||||
/* phonememode: Controls the output of phoneme symbols for the text | |||||
bits 0-2: | |||||
value=0 No phoneme output (default) | |||||
value=1 Output the translated phoneme symbols for the text | |||||
value=2 as (1), but produces IPA phoneme names rather than ascii | |||||
bit 3: output a trace of how the translation was done (showing the matching rules and list entries) | |||||
bit 4: produce pho data for mbrola | |||||
bit 7: use (bits 8-23) as a tie within multi-letter phonemes names | |||||
bits 8-23: separator character, between phoneme names | |||||
stream output stream for the phoneme symbols (and trace). If stream=NULL then it uses stdout. | |||||
*/ | |||||
option_phonemes = phonememode; | option_phonemes = phonememode; | ||||
f_trans = stream; | f_trans = stream; | ||||
if (stream == NULL) | if (stream == NULL) | ||||
wave_close(my_audio); | wave_close(my_audio); | ||||
SHOW_TIME("espeak_Cancel > LEAVE"); | SHOW_TIME("espeak_Cancel > LEAVE"); | ||||
#endif | #endif | ||||
embedded_value[EMBED_T] = 0; // reset echo for pronunciation announcements | |||||
embedded_value[EMBED_T] = 0; // reset echo for pronunciation announcements | |||||
for (int i = 0; i < N_SPEECH_PARAM; i++) | for (int i = 0; i < N_SPEECH_PARAM; i++) | ||||
SetParameter(i, saved_parameters[i], 0); | SetParameter(i, saved_parameters[i], 0); |
float polint(float xa[], float ya[], int n, float x) | float polint(float xa[], float ya[], int n, float x) | ||||
{ | { | ||||
// General polinomial interpolation routine, xa[1...n] ya[1...n] | |||||
// General polinomial interpolation routine, xa[1...n] ya[1...n] | |||||
int i, m, ns = 1; | int i, m, ns = 1; | ||||
float den, dif, dift, ho, hp, w; | float den, dif, dift, ho, hp, w; | ||||
float y; // result | |||||
float y; // result | |||||
float c[9], d[9]; | float c[9], d[9]; | ||||
dif = fabs(x-xa[1]); | dif = fabs(x-xa[1]); | ||||
hp = xa[i+m]-x; | hp = xa[i+m]-x; | ||||
w = c[i+1]-d[i]; | w = c[i+1]-d[i]; | ||||
if ((den = ho-hp) == 0.0) | if ((den = ho-hp) == 0.0) | ||||
return ya[2]; // two input xa are identical | |||||
return ya[2]; // two input xa are identical | |||||
den = w/den; | den = w/den; | ||||
d[i] = hp*den; | d[i] = hp*den; | ||||
c[i] = ho*den; | c[i] = ho*den; | ||||
if (frame >= spect->numframes-1) return 0; | if (frame >= spect->numframes-1) return 0; | ||||
for (ix = frame+1; ix < spect->numframes-1; ix++) { | for (ix = frame+1; ix < spect->numframes-1; ix++) { | ||||
if (spect->frames[ix]->keyframe) break; // reached next keyframe | |||||
if (spect->frames[ix]->keyframe) | |||||
break; // reached next keyframe | |||||
adjust += spect->frames[ix]->length_adjust; | adjust += spect->frames[ix]->length_adjust; | ||||
} | } | ||||
return (spect->frames[ix]->time - spect->frames[frame]->time) * 1000.0 + adjust; | return (spect->frames[ix]->time - spect->frames[frame]->time) * 1000.0 + adjust; | ||||
fread(&id2, sizeof(uint32_t), 1, stream); | fread(&id2, sizeof(uint32_t), 1, stream); | ||||
if ((id1 == FILEID1_SPECTSEQ) && (id2 == FILEID2_SPECTSEQ)) | if ((id1 == FILEID1_SPECTSEQ) && (id2 == FILEID2_SPECTSEQ)) | ||||
spect->file_format = 0; // eSpeak formants | |||||
spect->file_format = 0; // eSpeak formants | |||||
else if ((id1 == FILEID1_SPECTSEQ) && (id2 == FILEID2_SPECTSEK)) | else if ((id1 == FILEID1_SPECTSEQ) && (id2 == FILEID2_SPECTSEK)) | ||||
spect->file_format = 1; // formants for Klatt synthesizer | |||||
spect->file_format = 1; // formants for Klatt synthesizer | |||||
else if ((id1 == FILEID1_SPECTSEQ) && (id2 == FILEID2_SPECTSQ2)) | else if ((id1 == FILEID1_SPECTSEQ) && (id2 == FILEID2_SPECTSQ2)) | ||||
spect->file_format = 2; // formants for Klatt synthesizer | |||||
spect->file_format = 2; // formants for Klatt synthesizer | |||||
else { | else { | ||||
fprintf(stderr, "Unsupported spectral file format.\n"); | fprintf(stderr, "Unsupported spectral file format.\n"); | ||||
fclose(stream); | fclose(stream); |
#define MAX_DISPLAY_FREQ 9500 | #define MAX_DISPLAY_FREQ 9500 | ||||
#define FRAME_HEIGHT 240 | #define FRAME_HEIGHT 240 | ||||
#define T_ZOOMOUT 301 | #define T_ZOOMOUT 301 | ||||
#define T_ZOOMIN 302 | #define T_ZOOMIN 302 | ||||
#define T_USEPITCHENV 303 | #define T_USEPITCHENV 303 | ||||
#define T_KOPEN 320 | #define T_KOPEN 320 | ||||
#define T_FNZ 321 | #define T_FNZ 321 | ||||
#define FILEID1_SPECTSEQ 0x43455053 | #define FILEID1_SPECTSEQ 0x43455053 | ||||
#define FILEID2_SPECTSEQ 0x51455354 // for eSpeak sequence | #define FILEID2_SPECTSEQ 0x51455354 // for eSpeak sequence | ||||
#define FILEID2_SPECTSEK 0x4b455354 // for Klatt sequence | #define FILEID2_SPECTSEK 0x4b455354 // for Klatt sequence | ||||
#define FILEID1_PRAATSEQ 0x41415250 | #define FILEID1_PRAATSEQ 0x41415250 | ||||
#define FILEID2_PRAATSEQ 0x51455354 | #define FILEID2_PRAATSEQ 0x51455354 | ||||
typedef struct { | typedef struct { | ||||
unsigned short pitch1; | unsigned short pitch1; | ||||
unsigned short pitch2; | unsigned short pitch2; | ||||
typedef struct { | typedef struct { | ||||
short freq; | short freq; | ||||
short bandw; | short bandw; | ||||
} formant_t; | |||||
} formant_t; | |||||
typedef struct { | typedef struct { | ||||
short pkfreq; | short pkfreq; | ||||
short klt_bw; | short klt_bw; | ||||
short klt_ap; | short klt_ap; | ||||
short klt_bp; | short klt_bp; | ||||
} peak_t; | |||||
} peak_t; | |||||
typedef struct { | typedef struct { | ||||
int keyframe; | int keyframe; | ||||
unsigned short nx; | unsigned short nx; | ||||
short markers; | short markers; | ||||
int max_y; | int max_y; | ||||
USHORT *spect; // sqrt of harmonic amplitudes, 1-nx at 'pitch' | |||||
USHORT *spect; // sqrt of harmonic amplitudes, 1-nx at 'pitch' | |||||
short klatt_param[N_KLATTP2]; | short klatt_param[N_KLATTP2]; | ||||
formant_t formants[N_PEAKS]; // this is just the estimate given by Praat | |||||
formant_t formants[N_PEAKS]; // this is just the estimate given by Praat | |||||
peak_t peaks[N_PEAKS]; | peak_t peaks[N_PEAKS]; | ||||
} SpectFrame; | } SpectFrame; | ||||
#endif | #endif | ||||
#endif | #endif | ||||
typedef struct { | typedef struct { | ||||
const char *mnem; | const char *mnem; | ||||
int value; | int value; |
espeak_ERROR LoadMbrolaTable(const char *mbrola_voice, const char *phtrans, int *srate) | espeak_ERROR LoadMbrolaTable(const char *mbrola_voice, const char *phtrans, int *srate) | ||||
{ | { | ||||
// Load a phoneme name translation table from espeak-data/mbrola | |||||
// Load a phoneme name translation table from espeak-data/mbrola | |||||
int size; | int size; | ||||
int ix; | int ix; | ||||
close_MBR(); | close_MBR(); | ||||
#endif | #endif | ||||
#ifdef PLATFORM_WINDOWS | #ifdef PLATFORM_WINDOWS | ||||
if (load_MBR() == FALSE) { // load mbrola.dll | |||||
if (load_MBR() == FALSE) { // load mbrola.dll | |||||
fprintf(stderr, "Can't load mbrola.dll\n"); | fprintf(stderr, "Can't load mbrola.dll\n"); | ||||
return EE_INTERNAL_ERROR; | return EE_INTERNAL_ERROR; | ||||
} | } | ||||
#endif | #endif | ||||
if (init_MBR(path) != 0) // initialise the required mbrola voice | |||||
if (init_MBR(path) != 0) // initialise the required mbrola voice | |||||
return EE_NOT_FOUND; | return EE_NOT_FOUND; | ||||
setNoError_MBR(1); // don't stop on phoneme errors | |||||
setNoError_MBR(1); // don't stop on phoneme errors | |||||
// read eSpeak's mbrola phoneme translation data, eg. en1_phtrans | // read eSpeak's mbrola phoneme translation data, eg. en1_phtrans | ||||
sprintf(path, "%s/mbrola_ph/%s", path_home, phtrans); | sprintf(path, "%s/mbrola_ph/%s", path_home, phtrans); | ||||
else | else | ||||
SetParameter(espeakVOICETYPE, 1, 0); | SetParameter(espeakVOICETYPE, 1, 0); | ||||
strcpy(mbrola_name, mbrola_voice); | strcpy(mbrola_name, mbrola_voice); | ||||
mbrola_delay = 1000; // improve synchronization of events | |||||
mbrola_delay = 1000; // improve synchronization of events | |||||
return EE_OK; | return EE_OK; | ||||
} | } | ||||
static int GetMbrName(PHONEME_LIST *plist, PHONEME_TAB *ph, PHONEME_TAB *ph_prev, PHONEME_TAB *ph_next, int *name2, int *split, int *control) | 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 | |||||
// Look up a phoneme in the mbrola phoneme name translation table | |||||
// It may give none, 1, or 2 mbrola phonemes | |||||
MBROLA_TAB *pr; | MBROLA_TAB *pr; | ||||
PHONEME_TAB *other_ph; | PHONEME_TAB *other_ph; | ||||
int found = 0; | int found = 0; | ||||
if (pr->control & 2) | if (pr->control & 2) | ||||
other_ph = ph_prev; | other_ph = ph_prev; | ||||
else if ((pr->control & 8) && ((plist+1)->newword)) | else if ((pr->control & 8) && ((plist+1)->newword)) | ||||
other_ph = phoneme_tab[phPAUSE]; // don't match the next phoneme over a word boundary | |||||
other_ph = phoneme_tab[phPAUSE]; // don't match the next phoneme over a word boundary | |||||
else | else | ||||
other_ph = ph_next; | other_ph = ph_next; | ||||
found = 1; | found = 1; | ||||
} | } | ||||
if ((pr->control & 4) && (plist->newword == 0)) // only at start of word | |||||
if ((pr->control & 4) && (plist->newword == 0)) // only at start of word | |||||
found = 0; | found = 0; | ||||
if ((pr->control & 0x40) && (plist[1].newword == 0)) // only at the end of a word | |||||
if ((pr->control & 0x40) && (plist[1].newword == 0)) // only at the end of a word | |||||
found = 0; | found = 0; | ||||
if ((pr->control & 0x20) && (plist->stresslevel < plist->wordstress)) | if ((pr->control & 0x20) && (plist->stresslevel < plist->wordstress)) | ||||
found = 0; // only in stressed syllables | |||||
found = 0; // only in stressed syllables | |||||
if (found) { | if (found) { | ||||
*name2 = pr->mbr_name2; | *name2 = pr->mbr_name2; | ||||
static char *WritePitch(int env, int pitch1, int pitch2, int split, int final) | static char *WritePitch(int env, int pitch1, int pitch2, int split, int final) | ||||
{ | { | ||||
// final=1: only give the final pitch value. | |||||
// final=1: only give the final pitch value. | |||||
int x; | int x; | ||||
int ix; | int ix; | ||||
int pitch_base; | int pitch_base; | ||||
int min = 999; | int min = 999; | ||||
int y_max = 0; | int y_max = 0; | ||||
int y_min = 0; | int y_min = 0; | ||||
int env100 = 80; // apply the pitch change only over this proportion of the mbrola phoneme(s) | |||||
int env100 = 80; // apply the pitch change only over this proportion of the mbrola phoneme(s) | |||||
int y2; | int y2; | ||||
int y[4]; | int y[4]; | ||||
int env_split; | int env_split; | ||||
y[3] = y[2] + (127 - y[2])/2; | y[3] = y[2] + (127 - y[2])/2; | ||||
// set initial pitch | // set initial pitch | ||||
p1 = ((pitch_env[0]*pitch_range)>>8) + pitch_base; // Hz << 12 | |||||
p1 = ((pitch_env[0]*pitch_range)>>8) + pitch_base; // Hz << 12 | |||||
p_end = ((pitch_env[127]*pitch_range)>>8) + pitch_base; | p_end = ((pitch_env[127]*pitch_range)>>8) + pitch_base; | ||||
if (split >= 0) { | if (split >= 0) { | ||||
int MbrolaTranslate(PHONEME_LIST *plist, int n_phonemes, int resume, FILE *f_mbrola) | int MbrolaTranslate(PHONEME_LIST *plist, int n_phonemes, int resume, FILE *f_mbrola) | ||||
{ | { | ||||
// Generate a mbrola pho file | |||||
// Generate a mbrola pho file | |||||
unsigned int name; | unsigned int name; | ||||
int len; | int len; | ||||
int len1; | int len1; | ||||
if (name == 0) { | if (name == 0) { | ||||
phix++; | phix++; | ||||
continue; // ignore this phoneme | |||||
continue; // ignore this phoneme | |||||
} | } | ||||
if ((ph->type == phPAUSE) && (name == ph->mnemonic)) { | if ((ph->type == phPAUSE) && (name == ph->mnemonic)) { | ||||
case phVOWEL: | case phVOWEL: | ||||
len = ph->std_length; | len = ph->std_length; | ||||
if (p->synthflags & SFLAG_LENGTHEN) | if (p->synthflags & SFLAG_LENGTHEN) | ||||
len += phoneme_tab[phonLENGTHEN]->std_length; // phoneme was followed by an extra : symbol | |||||
len += phoneme_tab[phonLENGTHEN]->std_length; // phoneme was followed by an extra : symbol | |||||
if (ph_next->type == phPAUSE) | if (ph_next->type == phPAUSE) | ||||
len += 50; // lengthen vowels before a pause | |||||
len += 50; // lengthen vowels before a pause | |||||
len = (len * p->length)/256; | len = (len * p->length)/256; | ||||
if (name2 == 0) { | if (name2 == 0) { | ||||
InterpretPhoneme(NULL, 0, p, &phdata, NULL); | InterpretPhoneme(NULL, 0, p, &phdata, NULL); | ||||
len = DoSample3(&phdata, 0, -1); | len = DoSample3(&phdata, 0, -1); | ||||
len = (len * 1000)/samplerate; // convert to mS | |||||
len = (len * 1000)/samplerate; // convert to mS | |||||
len += PauseLength(p->prepause, 1); | len += PauseLength(p->prepause, 1); | ||||
break; | break; | ||||
case phVSTOP: | case phVSTOP: | ||||
len = 0; | len = 0; | ||||
InterpretPhoneme(NULL, 0, p, &phdata, NULL); | InterpretPhoneme(NULL, 0, p, &phdata, NULL); | ||||
if (p->synthflags & SFLAG_LENGTHEN) | if (p->synthflags & SFLAG_LENGTHEN) | ||||
len = DoSample3(&phdata, p->length, -1); // play it twice for [s:] etc. | |||||
len = DoSample3(&phdata, p->length, -1); // play it twice for [s:] etc. | |||||
len += DoSample3(&phdata, p->length, -1); | len += DoSample3(&phdata, p->length, -1); | ||||
len = (len * 1000)/samplerate; // convert to mS | |||||
len = (len * 1000)/samplerate; // convert to mS | |||||
break; | break; | ||||
case phNASAL: | case phNASAL: | ||||
if (next->type != phVOWEL) { | if (next->type != phVOWEL) { | ||||
} | } | ||||
if (f_mbrola) | if (f_mbrola) | ||||
fwrite(mbr_buf, 1, (ptr-mbr_buf), f_mbrola); // write .pho to a file | |||||
fwrite(mbr_buf, 1, (ptr-mbr_buf), f_mbrola); // write .pho to a file | |||||
else { | else { | ||||
int res = write_MBR(mbr_buf); | int res = write_MBR(mbr_buf); | ||||
if (res < 0) | if (res < 0) | ||||
return 0; /* don't get stuck on error */ | |||||
return 0; // don't get stuck on error | |||||
if (res == 0) | if (res == 0) | ||||
return 1; | return 1; | ||||
wcmdq[wcmdq_tail][0] = WCMD_MBROLA_DATA; | wcmdq[wcmdq_tail][0] = WCMD_MBROLA_DATA; | ||||
int MbrolaFill(int length, int resume, int amplitude) | int MbrolaFill(int length, int resume, int amplitude) | ||||
{ | { | ||||
// Read audio data from Mbrola (length is in millisecs) | |||||
// Read audio data from Mbrola (length is in millisecs) | |||||
static int n_samples; | static int n_samples; | ||||
int req_samples, result; | int req_samples, result; | ||||
for (ix = 0; ix < result; ix++) { | for (ix = 0; ix < result; ix++) { | ||||
value16 = out_ptr[0] + (out_ptr[1] << 8); | value16 = out_ptr[0] + (out_ptr[1] << 8); | ||||
value = value16 * amplitude; | value = value16 * amplitude; | ||||
value = value / 40; // adjust this constant to give a suitable amplitude for mbrola voices | |||||
value = value / 40; // adjust this constant to give a suitable amplitude for mbrola voices | |||||
if (value > 0x7fff) | if (value > 0x7fff) | ||||
value = 0x7fff; | value = 0x7fff; | ||||
if (value < -0x8000) | if (value < -0x8000) | ||||
void MbrolaReset(void) | void MbrolaReset(void) | ||||
{ | { | ||||
// Reset the Mbrola engine and flush the pending audio | |||||
// Reset the Mbrola engine and flush the pending audio | |||||
reset_MBR(); | reset_MBR(); | ||||
} | } | ||||
#else // INCLUDE_MBROLA | |||||
#else | |||||
// mbrola interface is not compiled, provide dummy functions. | // mbrola interface is not compiled, provide dummy functions. | ||||
{ | { | ||||
} | } | ||||
#endif // INCLUDE_MBROLA | |||||
#endif |
PHONEME_TAB_LIST phoneme_tab_list[N_PHONEME_TABS]; | PHONEME_TAB_LIST phoneme_tab_list[N_PHONEME_TABS]; | ||||
int phoneme_tab_number = 0; | int phoneme_tab_number = 0; | ||||
int wavefile_ix; // a wavefile to play along with the synthesis | |||||
int wavefile_ix; // a wavefile to play along with the synthesis | |||||
int wavefile_amp; | int wavefile_amp; | ||||
int wavefile_ix2; | int wavefile_ix2; | ||||
int wavefile_amp2; | int wavefile_amp2; | ||||
n_tunes = length / sizeof(TUNE); | n_tunes = length / sizeof(TUNE); | ||||
// read the version number and sample rate from the first 8 bytes of phondata | // read the version number and sample rate from the first 8 bytes of phondata | ||||
version = 0; // bytes 0-3, version number | |||||
rate = 0; // bytes 4-7, sample rate | |||||
version = 0; // bytes 0-3, version number | |||||
rate = 0; // bytes 4-7, sample rate | |||||
for (ix = 0; ix < 4; ix++) { | for (ix = 0; ix < 4; ix++) { | ||||
version += (wavefile_data[ix] << (ix*8)); | version += (wavefile_data[ix] << (ix*8)); | ||||
rate += (wavefile_data[ix+4] << (ix*8)); | rate += (wavefile_data[ix+4] << (ix*8)); | ||||
if (which == 1) | if (which == 1) | ||||
nf = seq_break + 1; | nf = seq_break + 1; | ||||
else { | else { | ||||
frames = &frames_buf[seq_break]; // body of vowel, skip past initial frames | |||||
frames = &frames_buf[seq_break]; // body of vowel, skip past initial frames | |||||
nf -= seq_break; | nf -= seq_break; | ||||
} | } | ||||
} | } | ||||
if (length1 > 0) { | if (length1 > 0) { | ||||
if (which == 2) { | if (which == 2) { | ||||
// adjust the length of the main part to match the standard length specified for the vowel | // adjust the length of the main part to match the standard length specified for the vowel | ||||
// less the front part of the vowel and any added suffix | |||||
// less the front part of the vowel and any added suffix | |||||
length_std = fmt_params->std_length + seq_len_adjust - 45; | length_std = fmt_params->std_length + seq_len_adjust - 45; | ||||
if (length_std < 10) | if (length_std < 10) | ||||
length_std = 10; | length_std = 10; | ||||
if (plist->synthflags & SFLAG_LENGTHEN) | if (plist->synthflags & SFLAG_LENGTHEN) | ||||
length_std += (phoneme_tab[phonLENGTHEN]->std_length * 2); // phoneme was followed by an extra : symbol | |||||
// can adjust vowel length for stressed syllables here | |||||
length_std += (phoneme_tab[phonLENGTHEN]->std_length * 2); // phoneme was followed by an extra : symbol | |||||
// can adjust vowel length for stressed syllables here | |||||
length_factor = (length_std * 256)/ length1; | length_factor = (length_std * 256)/ length1; | ||||
{ | { | ||||
if (index == 0) { | if (index == 0) { | ||||
fprintf(stderr, "espeak: No envelope\n"); | fprintf(stderr, "espeak: No envelope\n"); | ||||
return envelope_data[0]; // not found, use a default envelope | |||||
return envelope_data[0]; // not found, use a default envelope | |||||
} | } | ||||
return (unsigned char *)&phondata_ptr[index]; | return (unsigned char *)&phondata_ptr[index]; | ||||
} | } | ||||
n_phoneme_tab = ph_code; | n_phoneme_tab = ph_code; | ||||
if (recursing == 0) | if (recursing == 0) | ||||
phoneme_tab_flags[ph_code] |= 1; // not inherited | |||||
phoneme_tab_flags[ph_code] |= 1; // not inherited | |||||
} | } | ||||
} | } | ||||
void SelectPhonemeTable(int number) | void SelectPhonemeTable(int number) | ||||
{ | { | ||||
n_phoneme_tab = 0; | n_phoneme_tab = 0; | ||||
SetUpPhonemeTable(number, 0); // recursively for included phoneme tables | |||||
SetUpPhonemeTable(number, 0); // recursively for included phoneme tables | |||||
n_phoneme_tab++; | n_phoneme_tab++; | ||||
current_phoneme_table = number; | current_phoneme_table = number; | ||||
} | } | ||||
int SelectPhonemeTableName(const char *name) | int SelectPhonemeTableName(const char *name) | ||||
{ | { | ||||
// Look up a phoneme set by name, and select it if it exists | |||||
// Returns the phoneme table number | |||||
// Look up a phoneme set by name, and select it if it exists | |||||
// Returns the phoneme table number | |||||
int ix; | int ix; | ||||
if ((ix = LookupPhonemeTable(name)) == -1) | if ((ix = LookupPhonemeTable(name)) == -1) | ||||
void LoadConfig(void) | void LoadConfig(void) | ||||
{ | { | ||||
// Load configuration file, if one exists | |||||
// Load configuration file, if one exists | |||||
char buf[sizeof(path_home)+10]; | char buf[sizeof(path_home)+10]; | ||||
FILE *f; | FILE *f; | ||||
int ix; | int ix; | ||||
static bool StressCondition(Translator *tr, PHONEME_LIST *plist, int condition, int control) | static bool StressCondition(Translator *tr, PHONEME_LIST *plist, int condition, int control) | ||||
{ | { | ||||
// condition: | |||||
// 0 if diminished, 1 if unstressed, 2 if not stressed, 3 if stressed, 4 if max stress | |||||
// condition: | |||||
// 0 if diminished, 1 if unstressed, 2 if not stressed, 3 if stressed, 4 if max stress | |||||
int stress_level; | int stress_level; | ||||
PHONEME_LIST *pl; | PHONEME_LIST *pl; | ||||
if (phoneme_tab[plist[1].phcode]->type == phVOWEL) | if (phoneme_tab[plist[1].phcode]->type == phVOWEL) | ||||
pl = &plist[1]; | pl = &plist[1]; | ||||
else | else | ||||
return false; // no stress elevel for this consonant | |||||
return false; // no stress elevel for this consonant | |||||
} | } | ||||
stress_level = pl->stresslevel & 0xf; | stress_level = pl->stresslevel & 0xf; | ||||
switch (which) | switch (which) | ||||
{ | { | ||||
case 0: // prevPh | |||||
case 5: // prevPhW | |||||
case 0: // prevPh | |||||
case 5: // prevPhW | |||||
plist--; | plist--; | ||||
check_endtype = 1; | check_endtype = 1; | ||||
break; | break; | ||||
case 1: // thisPh | |||||
case 1: // thisPh | |||||
break; | break; | ||||
case 2: // nextPh | |||||
case 4: // nextPhW | |||||
case 2: // nextPh | |||||
case 4: // nextPhW | |||||
plist++; | plist++; | ||||
break; | break; | ||||
case 3: // next2Ph | |||||
case 6: // next2PhW | |||||
case 3: // next2Ph | |||||
case 6: // next2PhW | |||||
plist += 2; | plist += 2; | ||||
break; | break; | ||||
case 7: | case 7: | ||||
} | } | ||||
} | } | ||||
break; | break; | ||||
case 8: // prevVowel in this word | |||||
case 8: // prevVowel in this word | |||||
if ((worddata == NULL) || (worddata->prev_vowel.ph == NULL)) | if ((worddata == NULL) || (worddata->prev_vowel.ph == NULL)) | ||||
return false; // no previous vowel | |||||
return false; // no previous vowel | |||||
plist = &(worddata->prev_vowel); | plist = &(worddata->prev_vowel); | ||||
check_endtype = 1; | check_endtype = 1; | ||||
break; | break; | ||||
case 9: // next3PhW | |||||
case 9: // next3PhW | |||||
for (ix = 1; ix <= 3; ix++) { | for (ix = 1; ix <= 3; ix++) { | ||||
if (plist[ix].sourceix) | if (plist[ix].sourceix) | ||||
return false; | return false; | ||||
// not an exact match, check for a vowel type (eg. #i ) | // not an exact match, check for a vowel type (eg. #i ) | ||||
if ((check_endtype) && (ph->type == phVOWEL)) | if ((check_endtype) && (ph->type == phVOWEL)) | ||||
return data == ph->end_type; // prevPh() match on end_type | |||||
return data == ph->start_type; // thisPh() or nextPh(), match on start_type | |||||
return data == ph->end_type; // prevPh() match on end_type | |||||
return data == ph->start_type; // thisPh() or nextPh(), match on start_type | |||||
} | } | ||||
data = instn & 0x1f; | data = instn & 0x1f; | ||||
case 3: | case 3: | ||||
case 4: | case 4: | ||||
return StressCondition(tr, plist, data, 0); | return StressCondition(tr, plist, data, 0); | ||||
case 5: // isBreak, Either pause phoneme, or (stop/vstop/vfric not followed by vowel or (liquid in same word)) | |||||
case 5: // isBreak, Either pause phoneme, or (stop/vstop/vfric not followed by vowel or (liquid in same word)) | |||||
return (ph->type == phPAUSE) || (plist_this->synthflags & SFLAG_NEXT_PAUSE); | return (ph->type == phPAUSE) || (plist_this->synthflags & SFLAG_NEXT_PAUSE); | ||||
case 6: // isWordStart | |||||
case 6: // isWordStart | |||||
return plist->sourceix != 0; | return plist->sourceix != 0; | ||||
case 7: // notWordStart | |||||
case 7: // notWordStart | |||||
return plist->sourceix == 0; | return plist->sourceix == 0; | ||||
case 8: // isWordEnd | |||||
case 8: // isWordEnd | |||||
return plist[1].sourceix || (plist[1].ph->type == phPAUSE); | return plist[1].sourceix || (plist[1].ph->type == phPAUSE); | ||||
break; | break; | ||||
case 9: // isAfterStress | |||||
case 9: // isAfterStress | |||||
if (plist->sourceix != 0) | if (plist->sourceix != 0) | ||||
return false; | return false; | ||||
do { | do { | ||||
} while (plist->sourceix == 0); | } while (plist->sourceix == 0); | ||||
break; | break; | ||||
case 10: // isNotVowel | |||||
case 10: // isNotVowel | |||||
return ph->type != phVOWEL; | return ph->type != phVOWEL; | ||||
case 11: // isFinalVowel | |||||
case 11: // isFinalVowel | |||||
for (;;) { | for (;;) { | ||||
plist++; | plist++; | ||||
// plist->ph = phoneme_tab[plist->phcode]; // Why was this line here?? It corrupts plist if we have language switching if phoneme_tab is wrong language | |||||
if (plist->sourceix != 0) | if (plist->sourceix != 0) | ||||
return true; // start of next word, without finding another vowel | |||||
return true; // start of next word, without finding another vowel | |||||
if (plist->ph->type == phVOWEL) | if (plist->ph->type == phVOWEL) | ||||
return false; | return false; | ||||
} | } | ||||
break; | break; | ||||
case 12: // isVoiced | |||||
case 12: // isVoiced | |||||
return (ph->type == phVOWEL) || (ph->type == phLIQUID) || (ph->phflags & phVOICED); | return (ph->type == phVOWEL) || (ph->type == phLIQUID) || (ph->phflags & phVOICED); | ||||
case 13: // isFirstVowel | |||||
case 13: // isFirstVowel | |||||
return CountVowelPosition(plist) == 1; | return CountVowelPosition(plist) == 1; | ||||
case 14: // isSecondVowel | |||||
case 14: // isSecondVowel | |||||
return CountVowelPosition(plist) == 2; | return CountVowelPosition(plist) == 2; | ||||
case 15: // isSeqFlag1 | |||||
case 15: // isSeqFlag1 | |||||
// is this preceded by a sequence if 1 or more vowels which have 'flag1' ? (lang=hi) | // is this preceded by a sequence if 1 or more vowels which have 'flag1' ? (lang=hi) | ||||
if (plist->sourceix != 0) | if (plist->sourceix != 0) | ||||
return false; // this is the first phoneme in the word, so no. | |||||
return false; // this is the first phoneme in the word, so no. | |||||
count = 0; | count = 0; | ||||
for (;;) { | for (;;) { | ||||
if (plist->ph->phflags & phFLAG1) | if (plist->ph->phflags & phFLAG1) | ||||
count++; | count++; | ||||
else | else | ||||
break; // stop when we find a vowel without flag1 | |||||
break; // stop when we find a vowel without flag1 | |||||
} | } | ||||
if (plist->sourceix != 0) | if (plist->sourceix != 0) | ||||
break; | break; | ||||
} | } | ||||
return count > 0; | return count > 0; | ||||
case 0x10: // isTranslationGiven | |||||
case 0x10: // isTranslationGiven | |||||
return (plist->synthflags & SFLAG_DICTIONARY) != 0; | return (plist->synthflags & SFLAG_DICTIONARY) != 0; | ||||
} | } | ||||
break; | break; | ||||
// Other conditions | // Other conditions | ||||
switch (data) | switch (data) | ||||
{ | { | ||||
case 1: // PreVoicing | |||||
case 1: // PreVoicing | |||||
return control & 1; | return control & 1; | ||||
case 2: // KlattSynth | |||||
case 2: // KlattSynth | |||||
return voice->klattv[0] != 0; | return voice->klattv[0] != 0; | ||||
case 3: // MbrolaSynth | |||||
case 3: // MbrolaSynth | |||||
return mbrola_name[0] != 0; | return mbrola_name[0] != 0; | ||||
} | } | ||||
} | } | ||||
if (instn_type == 2) { | if (instn_type == 2) { | ||||
phdata->pd_control |= pd_FORNEXTPH; | phdata->pd_control |= pd_FORNEXTPH; | ||||
voweltype = plist[1].ph->start_type; // SwitchNextVowelType | |||||
voweltype = plist[1].ph->start_type; // SwitchNextVowelType | |||||
} else | } else | ||||
voweltype = plist[-1].ph->end_type; // SwitchPrevVowelType | |||||
voweltype = plist[-1].ph->end_type; // SwitchPrevVowelType | |||||
voweltype -= phonVOWELTYPES; | voweltype -= phonVOWELTYPES; | ||||
if ((voweltype >= 0) && (voweltype < 6)) { | if ((voweltype >= 0) && (voweltype < 6)) { | ||||
prog = *p_prog + voweltype*2; | prog = *p_prog + voweltype*2; | ||||
phdata->sound_addr[instn_type] = (((prog[1] & 0xf) << 16) + prog[2]) * 4; | phdata->sound_addr[instn_type] = (((prog[1] & 0xf) << 16) + prog[2]) * 4; | ||||
x = (prog[1] >> 4) & 0xff; | x = (prog[1] >> 4) & 0xff; | ||||
phdata->sound_param[instn_type] = x; // sign extend | |||||
phdata->sound_param[instn_type] = x; // sign extend | |||||
} | } | ||||
*p_prog += 12; | *p_prog += 12; | ||||
case 6: | case 6: | ||||
type2 = (instn & 0xf00) >> 9; | type2 = (instn & 0xf00) >> 9; | ||||
if ((type2 == 5) || (type2 == 6)) | if ((type2 == 5) || (type2 == 6)) | ||||
return 12; // switch on vowel type | |||||
return 12; // switch on vowel type | |||||
return 1; | return 1; | ||||
case 2: | case 2: | ||||
case 3: | case 3: | ||||
void InterpretPhoneme(Translator *tr, int control, PHONEME_LIST *plist, PHONEME_DATA *phdata, WORD_PH_DATA *worddata) | void InterpretPhoneme(Translator *tr, int control, PHONEME_LIST *plist, PHONEME_DATA *phdata, WORD_PH_DATA *worddata) | ||||
{ | { | ||||
// control: | |||||
// bit 0: PreVoicing | |||||
// bit 8: change phonemes | |||||
// control: | |||||
// bit 0: PreVoicing | |||||
// bit 8: change phonemes | |||||
PHONEME_TAB *ph; | PHONEME_TAB *ph; | ||||
USHORT *prog; | USHORT *prog; | ||||
USHORT instn; | USHORT instn; | ||||
#define N_RETURN 10 | #define N_RETURN 10 | ||||
int n_return = 0; | int n_return = 0; | ||||
USHORT *return_addr[N_RETURN]; // return address stack | |||||
USHORT *return_addr[N_RETURN]; // return address stack | |||||
ph = plist->ph; | ph = plist->ph; | ||||
switch (instn >> 12) | switch (instn >> 12) | ||||
{ | { | ||||
case 0: // 0xxx | |||||
case 0: // 0xxx | |||||
data = instn & 0xff; | data = instn & 0xff; | ||||
if (instn2 == 0) { | if (instn2 == 0) { | ||||
case i_RETURN: | case i_RETURN: | ||||
end_flag = 1; | end_flag = 1; | ||||
break; | break; | ||||
case i_CONTINUE: | case i_CONTINUE: | ||||
break; | break; | ||||
default: | default: | ||||
InvalidInstn(ph, instn); | InvalidInstn(ph, instn); | ||||
break; | break; | ||||
phdata->ipa_string[ix] = 0; | phdata->ipa_string[ix] = 0; | ||||
} else if (instn2 < N_PHONEME_DATA_PARAM) { | } else if (instn2 < N_PHONEME_DATA_PARAM) { | ||||
if (instn2 == i_CHANGE_PHONEME2) | if (instn2 == i_CHANGE_PHONEME2) | ||||
phdata->pd_param[i_CHANGE_PHONEME] = data; // also set ChangePhoneme | |||||
phdata->pd_param[i_CHANGE_PHONEME] = data; // also set ChangePhoneme | |||||
phdata->pd_param[instn2] = data; | phdata->pd_param[instn2] = data; | ||||
if ((instn2 == i_CHANGE_PHONEME) && (control & 0x100)) { | if ((instn2 == i_CHANGE_PHONEME) && (control & 0x100)) { | ||||
// found ChangePhoneme() in PhonemeList mode, exit | // found ChangePhoneme() in PhonemeList mode, exit | ||||
break; | break; | ||||
case 1: | case 1: | ||||
if (tr == NULL) | if (tr == NULL) | ||||
break; // ignore if in synthesis stage | |||||
break; // ignore if in synthesis stage | |||||
if (instn2 < 8) { | if (instn2 < 8) { | ||||
// ChangeIf | // ChangeIf | ||||
if (StressCondition(tr, plist, instn2 & 7, 1) == true) { | if (StressCondition(tr, plist, instn2 & 7, 1) == true) { | ||||
phdata->pd_param[i_CHANGE_PHONEME] = instn & 0xff; | phdata->pd_param[i_CHANGE_PHONEME] = instn & 0xff; | ||||
end_flag = 1; // change phoneme, exit | |||||
end_flag = 1; // change phoneme, exit | |||||
} | } | ||||
} | } | ||||
break; | break; | ||||
// instruction after a condition is not JUMP_FALSE, so skip the instruction. | // instruction after a condition is not JUMP_FALSE, so skip the instruction. | ||||
prog += NumInstnWords(prog); | prog += NumInstnWords(prog); | ||||
if ((prog[0] & 0xfe00) == 0x6000) | if ((prog[0] & 0xfe00) == 0x6000) | ||||
prog++; // and skip ELSE jump | |||||
prog++; // and skip ELSE jump | |||||
} | } | ||||
} | } | ||||
prog--; | prog--; | ||||
case 0: | case 0: | ||||
prog += (instn & 0xff) - 1; | prog += (instn & 0xff) - 1; | ||||
break; | break; | ||||
case 4: | case 4: | ||||
// conditional jumps should have been processed in the Condition section | // conditional jumps should have been processed in the Condition section | ||||
break; | break; | ||||
case 5: // NexttVowelStarts | |||||
case 5: // NexttVowelStarts | |||||
SwitchOnVowelType(plist, phdata, &prog, 2); | SwitchOnVowelType(plist, phdata, &prog, 2); | ||||
break; | break; | ||||
case 6: // PrevVowelTypeEndings | |||||
case 6: // PrevVowelTypeEndings | |||||
SwitchOnVowelType(plist, phdata, &prog, 3); | SwitchOnVowelType(plist, phdata, &prog, 3); | ||||
break; | break; | ||||
} | } | ||||
break; | break; | ||||
} | } | ||||
break; | break; | ||||
case 10: // Vowelin, Vowelout | |||||
case 10: // Vowelin, Vowelout | |||||
if (instn2 == 1) | if (instn2 == 1) | ||||
ix = 0; | ix = 0; | ||||
else | else | ||||
phdata->vowel_transition[ix+1] = (prog[2] << 16) + prog[3]; | phdata->vowel_transition[ix+1] = (prog[2] << 16) + prog[3]; | ||||
prog += 3; | prog += 3; | ||||
break; | break; | ||||
case 11: // FMT | |||||
case 12: // WAV | |||||
case 13: // VowelStart | |||||
case 14: // VowelEnd | |||||
case 15: // addWav | |||||
case 11: // FMT | |||||
case 12: // WAV | |||||
case 13: // VowelStart | |||||
case 14: // VowelEnd | |||||
case 15: // addWav | |||||
instn2 = (instn >> 12) - 11; | instn2 = (instn >> 12) - 11; | ||||
phdata->sound_addr[instn2] = ((instn & 0xf) << 18) + (prog[1] << 2); | phdata->sound_addr[instn2] = ((instn & 0xf) << 18) + (prog[1] << 2); | ||||
param_sc = phdata->sound_param[instn2] = (instn >> 4) & 0xff; | param_sc = phdata->sound_param[instn2] = (instn >> 4) & 0xff; | ||||
plist->std_length = phdata->pd_param[i_SET_LENGTH]; | plist->std_length = phdata->pd_param[i_SET_LENGTH]; | ||||
if (phdata->sound_addr[0] != 0) { | if (phdata->sound_addr[0] != 0) { | ||||
plist->phontab_addr = phdata->sound_addr[0]; // FMT address | |||||
plist->phontab_addr = phdata->sound_addr[0]; // FMT address | |||||
plist->sound_param = phdata->sound_param[0]; | plist->sound_param = phdata->sound_param[0]; | ||||
} else { | } else { | ||||
plist->phontab_addr = phdata->sound_addr[1]; // WAV address | |||||
plist->phontab_addr = phdata->sound_addr[1]; // WAV address | |||||
plist->sound_param = phdata->sound_param[1]; | plist->sound_param = phdata->sound_param[1]; | ||||
} | } | ||||
} | } | ||||
void InterpretPhoneme2(int phcode, PHONEME_DATA *phdata) | void InterpretPhoneme2(int phcode, PHONEME_DATA *phdata) | ||||
{ | { | ||||
// Examine the program of a single isolated phoneme | |||||
// Examine the program of a single isolated phoneme | |||||
int ix; | int ix; | ||||
PHONEME_LIST plist[4]; | PHONEME_LIST plist[4]; | ||||
memset(plist, 0, sizeof(plist)); | memset(plist, 0, sizeof(plist)); |
const char *WordToString(unsigned int word) | const char *WordToString(unsigned int word) | ||||
{ | { | ||||
// Convert a phoneme mnemonic word into a string | |||||
// Convert a phoneme mnemonic word into a string | |||||
int ix; | int ix; | ||||
static char buf[5]; | static char buf[5]; | ||||
intptr_t *q; | intptr_t *q; | ||||
last_amp_cmd = wcmdq_tail; | last_amp_cmd = wcmdq_tail; | ||||
amp_length = 0; // total length of vowel with this amplitude envelope | |||||
amp_length = 0; // total length of vowel with this amplitude envelope | |||||
q = wcmdq[wcmdq_tail]; | q = wcmdq[wcmdq_tail]; | ||||
q[0] = WCMD_AMPLITUDE; | q[0] = WCMD_AMPLITUDE; | ||||
q[1] = 0; // fill in later from amp_length | |||||
q[1] = 0; // fill in later from amp_length | |||||
q[2] = (intptr_t)amp_env; | q[2] = (intptr_t)amp_env; | ||||
q[3] = amp; | q[3] = amp; | ||||
WcmdqInc(); | WcmdqInc(); | ||||
env = envelope_data[PITCHfall]; | env = envelope_data[PITCHfall]; | ||||
} | } | ||||
last_pitch_cmd = wcmdq_tail; | last_pitch_cmd = wcmdq_tail; | ||||
pitch_length = 0; // total length of spect with this pitch envelope | |||||
pitch_length = 0; // total length of spect with this pitch envelope | |||||
if (pitch2 < 0) | if (pitch2 < 0) | ||||
pitch2 = 0; | pitch2 = 0; | ||||
q = wcmdq[wcmdq_tail]; | q = wcmdq[wcmdq_tail]; | ||||
q[0] = WCMD_PITCH; | q[0] = WCMD_PITCH; | ||||
q[1] = 0; // length, fill in later from pitch_length | |||||
q[1] = 0; // length, fill in later from pitch_length | |||||
q[2] = (intptr_t)env; | q[2] = (intptr_t)env; | ||||
q[3] = (pitch1 << 16) + pitch2; | q[3] = (pitch1 << 16) + pitch2; | ||||
WcmdqInc(); | WcmdqInc(); | ||||
} else | } else | ||||
len = (pause * speed.wav_factor)/256; | len = (pause * speed.wav_factor)/256; | ||||
if (len < speed.min_pause) { | |||||
len = speed.min_pause; // mS, limit the amount to which pauses can be shortened | |||||
} | |||||
if (len < speed.min_pause) | |||||
len = speed.min_pause; // mS, limit the amount to which pauses can be shortened | |||||
return len; | return len; | ||||
} | } | ||||
static void DoPause(int length, int control) | static void DoPause(int length, int control) | ||||
{ | { | ||||
// length in nominal mS | |||||
// control = 1, less shortening at fast speeds | |||||
// length in nominal mS | |||||
// control = 1, less shortening at fast speeds | |||||
unsigned int len; | unsigned int len; | ||||
int srate2; | int srate2; | ||||
len = PauseLength(length, control); | len = PauseLength(length, control); | ||||
if (len < 90000) | if (len < 90000) | ||||
len = (len * samplerate) / 1000; // convert from mS to number of samples | |||||
len = (len * samplerate) / 1000; // convert from mS to number of samples | |||||
else { | else { | ||||
srate2 = samplerate / 25; // avoid overflow | |||||
srate2 = samplerate / 25; // avoid overflow | |||||
len = (len * srate2) / 40; | len = (len * srate2) / 40; | ||||
} | } | ||||
} | } | ||||
} | } | ||||
} | } | ||||
extern int seq_len_adjust; // temporary fix to advance the start point for playing the wav sample | |||||
extern int seq_len_adjust; // temporary fix to advance the start point for playing the wav sample | |||||
static int DoSample2(int index, int which, int std_length, int control, int length_mod, int amp) | static int DoSample2(int index, int which, int std_length, int control, int length_mod, int amp) | ||||
{ | { | ||||
p = &wavefile_data[index]; | p = &wavefile_data[index]; | ||||
wav_scale = p[2]; | wav_scale = p[2]; | ||||
wav_length = (p[1] * 256); | wav_length = (p[1] * 256); | ||||
wav_length += p[0]; // length in bytes | |||||
wav_length += p[0]; // length in bytes | |||||
if (wav_length == 0) | if (wav_length == 0) | ||||
return 0; | return 0; | ||||
min_length = speed.min_sample_len; | min_length = speed.min_sample_len; | ||||
if (wav_scale == 0) | if (wav_scale == 0) | ||||
min_length *= 2; // 16 bit samples | |||||
min_length *= 2; // 16 bit samples | |||||
if (std_length > 0) { | if (std_length > 0) { | ||||
std_length = (std_length * samplerate)/1000; | std_length = (std_length * samplerate)/1000; | ||||
last_wcmdq = wcmdq_tail; | last_wcmdq = wcmdq_tail; | ||||
q = wcmdq[wcmdq_tail]; | q = wcmdq[wcmdq_tail]; | ||||
q[0] = WCMD_WAVE2; | q[0] = WCMD_WAVE2; | ||||
q[1] = length | (wav_length << 16); // length in samples | |||||
q[1] = length | (wav_length << 16); // length in samples | |||||
q[2] = (intptr_t)(&wavefile_data[index]); | q[2] = (intptr_t)(&wavefile_data[index]); | ||||
q[3] = wav_scale + (amp << 8); | q[3] = wav_scale + (amp << 8); | ||||
WcmdqInc(); | WcmdqInc(); | ||||
last_wcmdq = wcmdq_tail; | last_wcmdq = wcmdq_tail; | ||||
q = wcmdq[wcmdq_tail]; | q = wcmdq[wcmdq_tail]; | ||||
q[0] = WCMD_WAVE; | q[0] = WCMD_WAVE; | ||||
q[1] = x; // length in samples | |||||
q[1] = x; // length in samples | |||||
q[2] = (intptr_t)(&wavefile_data[index]); | q[2] = (intptr_t)(&wavefile_data[index]); | ||||
q[3] = wav_scale + (amp << 8); | q[3] = wav_scale + (amp << 8); | ||||
WcmdqInc(); | WcmdqInc(); | ||||
last_wcmdq = wcmdq_tail; | last_wcmdq = wcmdq_tail; | ||||
q = wcmdq[wcmdq_tail]; | q = wcmdq[wcmdq_tail]; | ||||
q[0] = WCMD_WAVE; | q[0] = WCMD_WAVE; | ||||
q[1] = len4*2; // length in samples | |||||
q[1] = len4*2; // length in samples | |||||
q[2] = (intptr_t)(&wavefile_data[index+x]); | q[2] = (intptr_t)(&wavefile_data[index+x]); | ||||
q[3] = wav_scale + (amp << 8); | q[3] = wav_scale + (amp << 8); | ||||
WcmdqInc(); | WcmdqInc(); | ||||
last_wcmdq = wcmdq_tail; | last_wcmdq = wcmdq_tail; | ||||
q = wcmdq[wcmdq_tail]; | q = wcmdq[wcmdq_tail]; | ||||
q[0] = WCMD_WAVE; | q[0] = WCMD_WAVE; | ||||
q[1] = length; // length in samples | |||||
q[1] = length; // length in samples | |||||
q[2] = (intptr_t)(&wavefile_data[index+x]); | q[2] = (intptr_t)(&wavefile_data[index+x]); | ||||
q[3] = wav_scale + (amp << 8); | q[3] = wav_scale + (amp << 8); | ||||
WcmdqInc(); | WcmdqInc(); | ||||
// enough to use a round-robin without checks. | // enough to use a round-robin without checks. | ||||
// Only needed for modifying spectra for blending to consonants | // Only needed for modifying spectra for blending to consonants | ||||
#define N_FRAME_POOL N_WCMDQ | |||||
#define N_FRAME_POOL N_WCMDQ | |||||
static int ix = 0; | static int ix = 0; | ||||
static frame_t frame_pool[N_FRAME_POOL]; | static frame_t frame_pool[N_FRAME_POOL]; | ||||
static void set_frame_rms(frame_t *fr, int new_rms) | static void set_frame_rms(frame_t *fr, int new_rms) | ||||
{ | { | ||||
// Each frame includes its RMS amplitude value, so to set a new | |||||
// RMS just adjust the formant amplitudes by the appropriate ratio | |||||
// Each frame includes its RMS amplitude value, so to set a new | |||||
// RMS just adjust the formant amplitudes by the appropriate ratio | |||||
int x; | int x; | ||||
int h; | int h; | ||||
int ix; | int ix; | ||||
static const short sqrt_tab[200] = { | static const short sqrt_tab[200] = { | ||||
0, 64, 90, 110, 128, 143, 156, 169, 181, 192, 202, 212, 221, 230, 239, 247, | |||||
0, 64, 90, 110, 128, 143, 156, 169, 181, 192, 202, 212, 221, 230, 239, 247, | |||||
256, 263, 271, 278, 286, 293, 300, 306, 313, 320, 326, 332, 338, 344, 350, 356, | 256, 263, 271, 278, 286, 293, 300, 306, 313, 320, 326, 332, 338, 344, 350, 356, | ||||
362, 367, 373, 378, 384, 389, 394, 399, 404, 409, 414, 419, 424, 429, 434, 438, | 362, 367, 373, 378, 384, 389, 394, 399, 404, 409, 414, 419, 424, 429, 434, 438, | ||||
443, 448, 452, 457, 461, 465, 470, 474, 478, 483, 487, 491, 495, 499, 503, 507, | 443, 448, 452, 457, 461, 465, 470, 474, 478, 483, 487, 491, 495, 499, 503, 507, | ||||
return; | return; | ||||
} | } | ||||
if (fr->rms == 0) return; // check for divide by zero | |||||
if (fr->rms == 0) return; // check for divide by zero | |||||
x = (new_rms * 64)/fr->rms; | x = (new_rms * 64)/fr->rms; | ||||
if (x >= 200) x = 199; | if (x >= 200) x = 199; | ||||
x = sqrt_tab[x]; // sqrt(new_rms/fr->rms)*0x200; | |||||
x = sqrt_tab[x]; // sqrt(new_rms/fr->rms)*0x200; | |||||
for (ix = 0; ix < 8; ix++) { | for (ix = 0; ix < 8; ix++) { | ||||
h = fr->fheight[ix] * x; | h = fr->fheight[ix] * x; | ||||
static void formants_reduce_hf(frame_t *fr, int level) | static void formants_reduce_hf(frame_t *fr, int level) | ||||
{ | { | ||||
// change height of peaks 2 to 8, percentage | |||||
// change height of peaks 2 to 8, percentage | |||||
int ix; | int ix; | ||||
int x; | int x; | ||||
static frame_t *CopyFrame(frame_t *frame1, int copy) | static frame_t *CopyFrame(frame_t *frame1, int copy) | ||||
{ | { | ||||
// create a copy of the specified frame in temporary buffer | |||||
// create a copy of the specified frame in temporary buffer | |||||
frame_t *frame2; | frame_t *frame2; | ||||
if ((copy == 0) && (frame1->frflags & FRFLAG_COPIED)) { | if ((copy == 0) && (frame1->frflags & FRFLAG_COPIED)) { | ||||
fr->ffreq[3] += f3_adj; | fr->ffreq[3] += f3_adj; | ||||
if (flags & 0x20) | if (flags & 0x20) | ||||
f3_adj = -f3_adj; // . reverse direction for f4,f5 change | |||||
f3_adj = -f3_adj; // reverse direction for f4,f5 change | |||||
fr->ffreq[4] += f3_adj; | fr->ffreq[4] += f3_adj; | ||||
fr->ffreq[5] += f3_adj; | fr->ffreq[5] += f3_adj; | ||||
static int VowelCloseness(frame_t *fr) | static int VowelCloseness(frame_t *fr) | ||||
{ | { | ||||
// return a value 0-3 depending on the vowel's f1 | |||||
// return a value 0-3 depending on the vowel's f1 | |||||
int f1; | int f1; | ||||
if ((f1 = fr->ffreq[1]) < 300) | if ((f1 = fr->ffreq[1]) < 300) | ||||
int flags; | int flags; | ||||
int vcolour; | int vcolour; | ||||
#define N_VCOLOUR 2 | |||||
// percentage change for each formant in 256ths | |||||
#define N_VCOLOUR 2 | |||||
// percentage change for each formant in 256ths | |||||
static short vcolouring[N_VCOLOUR][5] = { | static short vcolouring[N_VCOLOUR][5] = { | ||||
{ 243, 272, 256, 256, 256 }, // palatal consonant follows | |||||
{ 256, 256, 240, 240, 240 }, // retroflex | |||||
{ 243, 272, 256, 256, 256 }, // palatal consonant follows | |||||
{ 256, 256, 240, 240, 240 }, // retroflex | |||||
}; | }; | ||||
frame_t *fr = NULL; | frame_t *fr = NULL; | ||||
flags |= 8; | flags |= 8; | ||||
if (which == 1) { | if (which == 1) { | ||||
/* entry to vowel */ | |||||
// entry to vowel | |||||
fr = CopyFrame(seq[0].frame, 0); | fr = CopyFrame(seq[0].frame, 0); | ||||
seq[0].frame = fr; | seq[0].frame = fr; | ||||
seq[0].length = VOWEL_FRONT_LENGTH; | seq[0].length = VOWEL_FRONT_LENGTH; | ||||
if (len > 0) | if (len > 0) | ||||
seq[0].length = len; | seq[0].length = len; | ||||
seq[0].frflags |= FRFLAG_LEN_MOD2; // reduce length modification | |||||
seq[0].frflags |= FRFLAG_LEN_MOD2; // reduce length modification | |||||
fr->frflags |= FRFLAG_LEN_MOD2; | fr->frflags |= FRFLAG_LEN_MOD2; | ||||
next_rms = seq[1].frame->rms; | next_rms = seq[1].frame->rms; | ||||
if (flags & 4) | if (flags & 4) | ||||
fr->frflags |= FRFLAG_FORMANT_RATE; | fr->frflags |= FRFLAG_FORMANT_RATE; | ||||
if (flags & 2) | if (flags & 2) | ||||
fr->frflags |= FRFLAG_BREAK; // don't merge with next frame | |||||
fr->frflags |= FRFLAG_BREAK; // don't merge with next frame | |||||
} | } | ||||
if (flags & 0x40) | if (flags & 0x40) | ||||
DoPause(20, 0); // add a short pause after the consonant | |||||
DoPause(20, 0); // add a short pause after the consonant | |||||
if (flags & 16) | if (flags & 16) | ||||
return len; | return len; | ||||
q[3] = (intptr_t)frame2; | q[3] = (intptr_t)frame2; | ||||
frame1 = frame2; | frame1 = frame2; | ||||
} else | } else | ||||
break; // doesn't follow on from previous frame | |||||
break; // doesn't follow on from previous frame | |||||
frame = frame2 = (frame_t *)q[2]; | frame = frame2 = (frame_t *)q[2]; | ||||
modified = 0; | modified = 0; | ||||
break; | break; | ||||
if (frame->frflags & FRFLAG_FORMANT_RATE) | if (frame->frflags & FRFLAG_FORMANT_RATE) | ||||
len = (len * 12)/10; // allow slightly greater rate of change for this frame (was 12/10) | |||||
len = (len * 12)/10; // allow slightly greater rate of change for this frame (was 12/10) | |||||
for (pk = 0; pk < 6; pk++) { | for (pk = 0; pk < 6; pk++) { | ||||
int f1, f2; | int f1, f2; | ||||
q[2] = (intptr_t)frame2; | q[2] = (intptr_t)frame2; | ||||
frame1 = frame2; | frame1 = frame2; | ||||
} else | } else | ||||
break; // doesn't follow on from previous frame | |||||
break; // doesn't follow on from previous frame | |||||
} | } | ||||
frame = frame2 = (frame_t *)q[3]; | frame = frame2 = (frame_t *)q[3]; | ||||
break; | break; | ||||
if (frame1->frflags & FRFLAG_FORMANT_RATE) | if (frame1->frflags & FRFLAG_FORMANT_RATE) | ||||
len = (len *6)/5; // allow slightly greater rate of change for this frame | |||||
len = (len *6)/5; // allow slightly greater rate of change for this frame | |||||
for (pk = 0; pk < 6; pk++) { | for (pk = 0; pk < 6; pk++) { | ||||
int f1, f2; | int f1, f2; | ||||
length_mod = plist->length; | length_mod = plist->length; | ||||
if (length_mod == 0) length_mod = 256; | if (length_mod == 0) length_mod = 256; | ||||
length_min = (samplerate/70); // greater than one cycle at low pitch (Hz) | |||||
length_min = (samplerate/70); // greater than one cycle at low pitch (Hz) | |||||
if (which == 2) { | if (which == 2) { | ||||
if ((translator->langopts.param[LOPT_LONG_VOWEL_THRESHOLD] > 0) && ((this_ph->std_length >= translator->langopts.param[LOPT_LONG_VOWEL_THRESHOLD]) || (plist->synthflags & SFLAG_LENGTHEN) || (this_ph->phflags & phLONG))) | if ((translator->langopts.param[LOPT_LONG_VOWEL_THRESHOLD] > 0) && ((this_ph->std_length >= translator->langopts.param[LOPT_LONG_VOWEL_THRESHOLD]) || (plist->synthflags & SFLAG_LENGTHEN) || (this_ph->phflags & phLONG))) | ||||
length_min *= 2; // ensure long vowels are longer | |||||
length_min *= 2; // ensure long vowels are longer | |||||
} | } | ||||
if (which == 1) { | if (which == 1) { | ||||
modn_flags = 0; | modn_flags = 0; | ||||
frames = LookupSpect(this_ph, which, fmt_params, &n_frames, plist); | frames = LookupSpect(this_ph, which, fmt_params, &n_frames, plist); | ||||
if (frames == NULL) | if (frames == NULL) | ||||
return 0; // not found | |||||
return 0; // not found | |||||
if (fmt_params->fmt_amp != fmt_amplitude) { | if (fmt_params->fmt_amp != fmt_amplitude) { | ||||
// an amplitude adjustment is specified for this sequence | // an amplitude adjustment is specified for this sequence | ||||
} | } | ||||
if ((this_ph->type == phVOWEL) && (which == 2)) { | if ((this_ph->type == phVOWEL) && (which == 2)) { | ||||
SmoothSpect(); // process previous syllable | |||||
SmoothSpect(); // process previous syllable | |||||
// remember the point in the output queue of the centre of the vowel | // remember the point in the output queue of the centre of the vowel | ||||
syllable_centre = wcmdq_tail; | syllable_centre = wcmdq_tail; | ||||
length_sum = 0; | length_sum = 0; | ||||
for (frameix = 1; frameix < n_frames; frameix++) { | for (frameix = 1; frameix < n_frames; frameix++) { | ||||
length_factor = length_mod; | length_factor = length_mod; | ||||
if (frames[frameix-1].frflags & FRFLAG_LEN_MOD) { // reduce effect of length mod | |||||
if (frames[frameix-1].frflags & FRFLAG_LEN_MOD) // reduce effect of length mod | |||||
length_factor = (length_mod*(256-speed.lenmod_factor) + 256*speed.lenmod_factor)/256; | length_factor = (length_mod*(256-speed.lenmod_factor) + 256*speed.lenmod_factor)/256; | ||||
} else if (frames[frameix-1].frflags & FRFLAG_LEN_MOD2) { // reduce effect of length mod, used for the start of a vowel | |||||
else if (frames[frameix-1].frflags & FRFLAG_LEN_MOD2) // reduce effect of length mod, used for the start of a vowel | |||||
length_factor = (length_mod*(256-speed.lenmod2_factor) + 256*speed.lenmod2_factor)/256; | length_factor = (length_mod*(256-speed.lenmod2_factor) + 256*speed.lenmod2_factor)/256; | ||||
} | |||||
frame_length = frames[frameix-1].length; | frame_length = frames[frameix-1].length; | ||||
len = (frame_length * samplerate)/1000; | len = (frame_length * samplerate)/1000; | ||||
if (frame1->frflags & FRFLAG_MODULATE) | if (frame1->frflags & FRFLAG_MODULATE) | ||||
modulation = 6; | modulation = 6; | ||||
if ((frameix == n_frames-1) && (modn_flags & 0xf00)) | if ((frameix == n_frames-1) && (modn_flags & 0xf00)) | ||||
modulation |= modn_flags; // before or after a glottal stop | |||||
modulation |= modn_flags; // before or after a glottal stop | |||||
} | } | ||||
len = frame_lengths[frameix]; | len = frame_lengths[frameix]; | ||||
void DoMarker(int type, int char_posn, int length, int value) | void DoMarker(int type, int char_posn, int length, int value) | ||||
{ | { | ||||
// This could be used to return an index to the word currently being spoken | |||||
// Type 1=word, 2=sentence, 3=named marker, 4=play audio, 5=end | |||||
// This could be used to return an index to the word currently being spoken | |||||
// Type 1=word, 2=sentence, 3=named marker, 4=play audio, 5=end | |||||
if (WcmdqFree() > 5) { | if (WcmdqFree() > 5) { | ||||
wcmdq[wcmdq_tail][0] = WCMD_MARKER + (type << 8); | wcmdq[wcmdq_tail][0] = WCMD_MARKER + (type << 8); | ||||
wcmdq[wcmdq_tail][1] = (char_posn & 0xffffff) | (length << 24); | wcmdq[wcmdq_tail][1] = (char_posn & 0xffffff) | (length << 24); | ||||
void DoPhonemeMarker(int type, int char_posn, int length, char *name) | void DoPhonemeMarker(int type, int char_posn, int length, char *name) | ||||
{ | { | ||||
// This could be used to return an index to the word currently being spoken | |||||
// Type 7=phoneme | |||||
// This could be used to return an index to the word currently being spoken | |||||
// Type 7=phoneme | |||||
int *p; | int *p; | ||||
if (WcmdqFree() > 5) { | if (WcmdqFree() > 5) { | ||||
wcmdq[wcmdq_tail][0] = WCMD_MARKER + (type << 8); | wcmdq[wcmdq_tail][0] = WCMD_MARKER + (type << 8); | ||||
wcmdq[wcmdq_tail][1] = (char_posn & 0xffffff) | (length << 24); | wcmdq[wcmdq_tail][1] = (char_posn & 0xffffff) | (length << 24); | ||||
p = (int *)name; | p = (int *)name; | ||||
wcmdq[wcmdq_tail][2] = p[0]; // up to 8 bytes of UTF8 characters | |||||
wcmdq[wcmdq_tail][2] = p[0]; // up to 8 bytes of UTF8 characters | |||||
wcmdq[wcmdq_tail][3] = p[1]; | wcmdq[wcmdq_tail][3] = p[1]; | ||||
WcmdqInc(); | WcmdqInc(); | ||||
} | } | ||||
#if HAVE_SONIC_H | #if HAVE_SONIC_H | ||||
void DoSonicSpeed(int value) | void DoSonicSpeed(int value) | ||||
{ | { | ||||
// value, multiplier * 1024 | |||||
// value, multiplier * 1024 | |||||
wcmdq[wcmdq_tail][0] = WCMD_SONIC_SPEED; | wcmdq[wcmdq_tail][0] = WCMD_SONIC_SPEED; | ||||
wcmdq[wcmdq_tail][1] = value; | wcmdq[wcmdq_tail][1] = value; | ||||
WcmdqInc(); | WcmdqInc(); | ||||
void DoVoiceChange(voice_t *v) | void DoVoiceChange(voice_t *v) | ||||
{ | { | ||||
// allocate memory for a copy of the voice data, and free it in wavegenfill() | |||||
// allocate memory for a copy of the voice data, and free it in wavegenfill() | |||||
voice_t *v2; | voice_t *v2; | ||||
v2 = (voice_t *)malloc(sizeof(voice_t)); | v2 = (voice_t *)malloc(sizeof(voice_t)); | ||||
void DoEmbedded(int *embix, int sourceix) | void DoEmbedded(int *embix, int sourceix) | ||||
{ | { | ||||
// There were embedded commands in the text at this point | // There were embedded commands in the text at this point | ||||
unsigned int word; // bit 7=last command for this word, bits 5,6 sign, bits 0-4 command | |||||
unsigned int word; // bit 7=last command for this word, bits 5,6 sign, bits 0-4 command | |||||
unsigned int value; | unsigned int value; | ||||
int command; | int command; | ||||
command = word & 0x7f; | command = word & 0x7f; | ||||
if (command == 0) | if (command == 0) | ||||
return; // error | |||||
return; // error | |||||
(*embix)++; | (*embix)++; | ||||
switch (command & 0x1f) | switch (command & 0x1f) | ||||
{ | { | ||||
case EMBED_S: // speed | |||||
SetEmbedded((command & 0x60) + EMBED_S2, value); // adjusts embedded_value[EMBED_S2] | |||||
case EMBED_S: // speed | |||||
SetEmbedded((command & 0x60) + EMBED_S2, value); // adjusts embedded_value[EMBED_S2] | |||||
SetSpeed(2); | SetSpeed(2); | ||||
break; | break; | ||||
case EMBED_I: // play dynamically loaded wav data (sound icon) | |||||
case EMBED_I: // play dynamically loaded wav data (sound icon) | |||||
if ((int)value < n_soundicon_tab) { | if ((int)value < n_soundicon_tab) { | ||||
if (soundicon_tab[value].length != 0) { | if (soundicon_tab[value].length != 0) { | ||||
DoPause(10, 0); // ensure a break in the speech | |||||
DoPause(10, 0); // ensure a break in the speech | |||||
wcmdq[wcmdq_tail][0] = WCMD_WAVE; | wcmdq[wcmdq_tail][0] = WCMD_WAVE; | ||||
wcmdq[wcmdq_tail][1] = soundicon_tab[value].length; | wcmdq[wcmdq_tail][1] = soundicon_tab[value].length; | ||||
wcmdq[wcmdq_tail][2] = (intptr_t)soundicon_tab[value].data + 44; // skip WAV header | |||||
wcmdq[wcmdq_tail][3] = 0x1500; // 16 bit data, amp=21 | |||||
wcmdq[wcmdq_tail][2] = (intptr_t)soundicon_tab[value].data + 44; // skip WAV header | |||||
wcmdq[wcmdq_tail][3] = 0x1500; // 16 bit data, amp=21 | |||||
WcmdqInc(); | WcmdqInc(); | ||||
} | } | ||||
} | } | ||||
break; | break; | ||||
case EMBED_M: // named marker | |||||
case EMBED_M: // named marker | |||||
DoMarker(espeakEVENT_MARK, (sourceix & 0x7ff) + clause_start_char, 0, value); | DoMarker(espeakEVENT_MARK, (sourceix & 0x7ff) + clause_start_char, 0, value); | ||||
break; | break; | ||||
case EMBED_U: // play sound | |||||
DoMarker(espeakEVENT_PLAY, count_characters+1, 0, value); // always occurs at end of clause | |||||
case EMBED_U: // play sound | |||||
DoMarker(espeakEVENT_PLAY, count_characters+1, 0, value); // always occurs at end of clause | |||||
break; | break; | ||||
default: | default: | ||||
DoPause(10, 0); // ensure a break in the speech | |||||
DoPause(10, 0); // ensure a break in the speech | |||||
wcmdq[wcmdq_tail][0] = WCMD_EMBEDDED; | wcmdq[wcmdq_tail][0] = WCMD_EMBEDDED; | ||||
wcmdq[wcmdq_tail][1] = command; | wcmdq[wcmdq_tail][1] = command; | ||||
wcmdq[wcmdq_tail][2] = value; | wcmdq[wcmdq_tail][2] = value; | ||||
last_pitch_cmd = -1; | last_pitch_cmd = -1; | ||||
memset(vowel_transition, 0, sizeof(vowel_transition)); | memset(vowel_transition, 0, sizeof(vowel_transition)); | ||||
memset(&worddata, 0, sizeof(worddata)); | memset(&worddata, 0, sizeof(worddata)); | ||||
DoPause(0, 0); // isolate from the previous clause | |||||
DoPause(0, 0); // isolate from the previous clause | |||||
} | } | ||||
while ((ix < (*n_ph)) && (ix < N_PHONEME_LIST-2)) { | while ((ix < (*n_ph)) && (ix < N_PHONEME_LIST-2)) { | ||||
if (p->type == phPAUSE) | if (p->type == phPAUSE) | ||||
free_min = 10; | free_min = 10; | ||||
else if (p->type != phVOWEL) | else if (p->type != phVOWEL) | ||||
free_min = 15; // we need less Q space for non-vowels, and we need to generate phonemes after a vowel so that the pitch_length is filled in | |||||
free_min = 15; // we need less Q space for non-vowels, and we need to generate phonemes after a vowel so that the pitch_length is filled in | |||||
else | else | ||||
free_min = MIN_WCMDQ; // 25 | |||||
free_min = MIN_WCMDQ; | |||||
if (WcmdqFree() <= free_min) | if (WcmdqFree() <= free_min) | ||||
return 1; // wait | |||||
return 1; // wait | |||||
prev = &phoneme_list[ix-1]; | prev = &phoneme_list[ix-1]; | ||||
next = &phoneme_list[ix+1]; | next = &phoneme_list[ix+1]; | ||||
sourceix = (p->sourceix & 0x7ff) + clause_start_char; | sourceix = (p->sourceix & 0x7ff) + clause_start_char; | ||||
if (p->newword & 4) | if (p->newword & 4) | ||||
DoMarker(espeakEVENT_SENTENCE, sourceix, 0, count_sentences); // start of sentence | |||||
DoMarker(espeakEVENT_SENTENCE, sourceix, 0, count_sentences); // start of sentence | |||||
if (p->newword & 1) | if (p->newword & 1) | ||||
DoMarker(espeakEVENT_WORD, sourceix, p->sourceix >> 11, clause_start_word + word_count++); // NOTE, this count doesn't include multiple-word pronunciations in *_list. eg (of a) | |||||
DoMarker(espeakEVENT_WORD, sourceix, p->sourceix >> 11, clause_start_word + word_count++); // NOTE, this count doesn't include multiple-word pronunciations in *_list. eg (of a) | |||||
} | } | ||||
EndAmplitude(); | EndAmplitude(); | ||||
InterpretPhoneme(NULL, 0, p, &phdata, &worddata); | InterpretPhoneme(NULL, 0, p, &phdata, &worddata); | ||||
if (p->synthflags & SFLAG_LENGTHEN) | if (p->synthflags & SFLAG_LENGTHEN) | ||||
DoSample3(&phdata, p->length, 0); // play it twice for [s:] etc. | |||||
DoSample3(&phdata, p->length, 0); // play it twice for [s:] etc. | |||||
DoSample3(&phdata, p->length, 0); | DoSample3(&phdata, p->length, 0); | ||||
break; | break; | ||||
case phVSTOP: | case phVSTOP: | ||||
} else if (prev->type == phVOWEL && (p->synthflags & SFLAG_SEQCONTINUE)) | } else if (prev->type == phVOWEL && (p->synthflags & SFLAG_SEQCONTINUE)) | ||||
DoSpect2(p->ph, 0, &fmtp, p, 0); | DoSpect2(p->ph, 0, &fmtp, p, 0); | ||||
else { | else { | ||||
last_frame = NULL; // only for nasal ? | |||||
last_frame = NULL; // only for nasal ? | |||||
DoSpect2(p->ph, 0, &fmtp, p, 0); | DoSpect2(p->ph, 0, &fmtp, p, 0); | ||||
last_frame = NULL; | last_frame = NULL; | ||||
} | } | ||||
modulation = 2; | modulation = 2; | ||||
if (stress <= 1) | if (stress <= 1) | ||||
modulation = 1; // 16ths | |||||
modulation = 1; // 16ths | |||||
else if (stress >= 7) | else if (stress >= 7) | ||||
modulation = 3; | modulation = 3; | ||||
if (prev->type == phVSTOP || prev->type == phVFRICATIVE) { | if (prev->type == phVSTOP || prev->type == phVFRICATIVE) { | ||||
DoAmplitude(p->amp, amp_env); | DoAmplitude(p->amp, amp_env); | ||||
DoPitch(pitch_env, p->pitch1, p->pitch2); // don't use prevocalic rising tone | |||||
DoPitch(pitch_env, p->pitch1, p->pitch2); // don't use prevocalic rising tone | |||||
DoSpect2(ph, 1, &fmtp, p, modulation); | DoSpect2(ph, 1, &fmtp, p, modulation); | ||||
} else if (prev->type == phLIQUID || prev->type == phNASAL) { | } else if (prev->type == phLIQUID || prev->type == phNASAL) { | ||||
DoAmplitude(p->amp, amp_env); | DoAmplitude(p->amp, amp_env); | ||||
DoSpect2(ph, 1, &fmtp, p, modulation); // continue with pre-vocalic rising tone | |||||
DoSpect2(ph, 1, &fmtp, p, modulation); // continue with pre-vocalic rising tone | |||||
DoPitch(pitch_env, p->pitch1, p->pitch2); | DoPitch(pitch_env, p->pitch1, p->pitch2); | ||||
} else if (vowelstart_prev) { | } else if (vowelstart_prev) { | ||||
// VowelStart from the previous phoneme, but not phLIQUID or phNASAL | // VowelStart from the previous phoneme, but not phLIQUID or phNASAL | ||||
DoPitch(envelope_data[PITCHrise], p->pitch2 - 15, p->pitch2); | DoPitch(envelope_data[PITCHrise], p->pitch2 - 15, p->pitch2); | ||||
DoAmplitude(p->amp-1, amp_env); | DoAmplitude(p->amp-1, amp_env); | ||||
DoSpect2(ph, 1, &fmtp, p, modulation); // continue with pre-vocalic rising tone | |||||
DoSpect2(ph, 1, &fmtp, p, modulation); // continue with pre-vocalic rising tone | |||||
DoPitch(pitch_env, p->pitch1, p->pitch2); | DoPitch(pitch_env, p->pitch1, p->pitch2); | ||||
} else { | } else { | ||||
if (!(p->synthflags & SFLAG_SEQCONTINUE)) { | if (!(p->synthflags & SFLAG_SEQCONTINUE)) { | ||||
InterpretPhoneme(NULL, 0, next, &phdata_next, NULL); | InterpretPhoneme(NULL, 0, next, &phdata_next, NULL); | ||||
fmtp.use_vowelin = 1; | fmtp.use_vowelin = 1; | ||||
fmtp.transition0 = phdata_next.vowel_transition[2]; // always do vowel_transition, even if ph_VWLEND ?? consider [N] | |||||
fmtp.transition0 = phdata_next.vowel_transition[2]; // always do vowel_transition, even if ph_VWLEND ?? consider [N] | |||||
fmtp.transition1 = phdata_next.vowel_transition[3]; | fmtp.transition1 = phdata_next.vowel_transition[3]; | ||||
if ((fmtp.fmt2_addr = phdata_next.sound_addr[pd_VWLEND]) != 0) { | |||||
if ((fmtp.fmt2_addr = phdata_next.sound_addr[pd_VWLEND]) != 0) | |||||
fmtp.fmt2_lenadj = phdata_next.sound_param[pd_VWLEND]; | fmtp.fmt2_lenadj = phdata_next.sound_param[pd_VWLEND]; | ||||
} | |||||
} | } | ||||
DoSpect2(ph, 2, &fmtp, p, modulation); | DoSpect2(ph, 2, &fmtp, p, modulation); | ||||
} | } | ||||
EndPitch(1); | EndPitch(1); | ||||
if (*n_ph > 0) { | if (*n_ph > 0) { | ||||
DoMarker(espeakEVENT_END, count_characters, 0, count_sentences); // end of clause | |||||
DoMarker(espeakEVENT_END, count_characters, 0, count_sentences); // end of clause | |||||
*n_ph = 0; | *n_ph = 0; | ||||
} | } | ||||
return 0; // finished the phoneme list | |||||
return 0; // finished the phoneme list | |||||
} | } | ||||
static int timer_on = 0; | static int timer_on = 0; | ||||
int SpeakNextClause(FILE *f_in, const void *text_in, int control) | int SpeakNextClause(FILE *f_in, const void *text_in, int control) | ||||
{ | { | ||||
// Speak text from file (f_in) or memory (text_in) | |||||
// control 0: start | |||||
// either f_in or text_in is set, the other must be NULL | |||||
// Speak text from file (f_in) or memory (text_in) | |||||
// control 0: start | |||||
// either f_in or text_in is set, the other must be NULL | |||||
// The other calls have f_in and text_in = NULL | |||||
// control 1: speak next text | |||||
// 2: stop | |||||
// 3: pause (toggle) | |||||
// 4: is file being read (0=no, 1=yes) | |||||
// 5: interrupt and flush current text. | |||||
// The other calls have f_in and text_in = NULL | |||||
// control 1: speak next text | |||||
// 2: stop | |||||
// 3: pause (toggle) | |||||
// 4: is file being read (0=no, 1=yes) | |||||
// 5: interrupt and flush current text. | |||||
int clause_tone; | int clause_tone; | ||||
char *voice_change; | char *voice_change; | ||||
WavegenOpenSound(); | WavegenOpenSound(); | ||||
timer_on = 1; | timer_on = 1; | ||||
paused = 0; | paused = 0; | ||||
Generate(phoneme_list, &n_phoneme_list, 0); // re-start from beginning of clause | |||||
Generate(phoneme_list, &n_phoneme_list, 0); // re-start from beginning of clause | |||||
} | } | ||||
return 0; | return 0; | ||||
} | } | ||||
phoneme_callback(phon_out); | phoneme_callback(phon_out); | ||||
} | } | ||||
if (skipping_text) { | if (skipping_text) { | ||||
n_phoneme_list = 0; | n_phoneme_list = 0; | ||||
return 1; | return 1; |
{ | { | ||||
#endif | #endif | ||||
#define espeakINITIALIZE_PHONEME_IPA 0x0002 // move this to speak_lib.h, after eSpeak version 1.46.02 | |||||
#define espeakINITIALIZE_PHONEME_IPA 0x0002 // move this to speak_lib.h, after eSpeak version 1.46.02 | |||||
#define N_PHONEME_LIST 1000 // enough for source[N_TR_SOURCE] full of text, else it will truncate | |||||
#define N_PHONEME_LIST 1000 // enough for source[N_TR_SOURCE] full of text, else it will truncate | |||||
#define MAX_HARMONIC 400 // 400 * 50Hz = 20 kHz, more than enough | |||||
#define N_SEQ_FRAMES 25 // max frames in a spectrum sequence (real max is ablut 8) | |||||
#define STEPSIZE 64 // 2.9mS at 22 kHz sample rate | |||||
#define MAX_HARMONIC 400 // 400 * 50Hz = 20 kHz, more than enough | |||||
#define N_SEQ_FRAMES 25 // max frames in a spectrum sequence (real max is ablut 8) | |||||
#define STEPSIZE 64 // 2.9mS at 22 kHz sample rate | |||||
// flags set for frames within a spectrum sequence | // flags set for frames within a spectrum sequence | ||||
#define FRFLAG_KLATT 0x01 // this frame includes extra data for Klatt synthesizer | |||||
#define FRFLAG_VOWEL_CENTRE 0x02 // centre point of vowel | |||||
#define FRFLAG_LEN_MOD 0x04 // reduce effect of length adjustment | |||||
#define FRFLAG_BREAK_LF 0x08 // but keep f3 upwards | |||||
#define FRFLAG_BREAK 0x10 // don't merge with next frame | |||||
#define FRFLAG_BREAK_2 0x18 // FRFLAG_BREAK_LF or FRFLAG_BREAK | |||||
#define FRFLAG_FORMANT_RATE 0x20 // Flag5 allow increased rate of change of formant freq | |||||
#define FRFLAG_MODULATE 0x40 // Flag6 modulate amplitude of some cycles to give trill | |||||
#define FRFLAG_DEFER_WAV 0x80 // Flag7 defer mixing WAV until the next frame | |||||
#define FRFLAG_LEN_MOD2 0x4000 // reduce effect of length adjustment, used for the start of a vowel | |||||
#define FRFLAG_COPIED 0x8000 // This frame has been copied into temporary rw memory | |||||
#define SFLAG_SEQCONTINUE 0x01 // a liquid or nasal after a vowel, but not followed by a vowel | |||||
#define SFLAG_EMBEDDED 0x02 // there are embedded commands before this phoneme | |||||
#define SFLAG_SYLLABLE 0x04 // vowel or syllabic consonant | |||||
#define SFLAG_LENGTHEN 0x08 // lengthen symbol : included after this phoneme | |||||
#define SFLAG_DICTIONARY 0x10 // the pronunciation of this word was listed in the xx_list dictionary | |||||
#define SFLAG_SWITCHED_LANG 0x20 // this word uses phonemes from a different language | |||||
#define SFLAG_PROMOTE_STRESS 0x40 // this unstressed word can be promoted to stressed | |||||
#define SFLAG_PREV_PAUSE 0x1000 // consider previous phoneme as pause | |||||
#define SFLAG_NEXT_PAUSE 0x2000 // consider next phoneme as pause | |||||
#define FRFLAG_KLATT 0x01 // this frame includes extra data for Klatt synthesizer | |||||
#define FRFLAG_VOWEL_CENTRE 0x02 // centre point of vowel | |||||
#define FRFLAG_LEN_MOD 0x04 // reduce effect of length adjustment | |||||
#define FRFLAG_BREAK_LF 0x08 // but keep f3 upwards | |||||
#define FRFLAG_BREAK 0x10 // don't merge with next frame | |||||
#define FRFLAG_BREAK_2 0x18 // FRFLAG_BREAK_LF or FRFLAG_BREAK | |||||
#define FRFLAG_FORMANT_RATE 0x20 // Flag5 allow increased rate of change of formant freq | |||||
#define FRFLAG_MODULATE 0x40 // Flag6 modulate amplitude of some cycles to give trill | |||||
#define FRFLAG_DEFER_WAV 0x80 // Flag7 defer mixing WAV until the next frame | |||||
#define FRFLAG_LEN_MOD2 0x4000 // reduce effect of length adjustment, used for the start of a vowel | |||||
#define FRFLAG_COPIED 0x8000 // This frame has been copied into temporary rw memory | |||||
#define SFLAG_SEQCONTINUE 0x01 // a liquid or nasal after a vowel, but not followed by a vowel | |||||
#define SFLAG_EMBEDDED 0x02 // there are embedded commands before this phoneme | |||||
#define SFLAG_SYLLABLE 0x04 // vowel or syllabic consonant | |||||
#define SFLAG_LENGTHEN 0x08 // lengthen symbol : included after this phoneme | |||||
#define SFLAG_DICTIONARY 0x10 // the pronunciation of this word was listed in the xx_list dictionary | |||||
#define SFLAG_SWITCHED_LANG 0x20 // this word uses phonemes from a different language | |||||
#define SFLAG_PROMOTE_STRESS 0x40 // this unstressed word can be promoted to stressed | |||||
#define SFLAG_PREV_PAUSE 0x1000 // consider previous phoneme as pause | |||||
#define SFLAG_NEXT_PAUSE 0x2000 // consider next phoneme as pause | |||||
// embedded command numbers | // embedded command numbers | ||||
#define EMBED_P 1 // pitch | |||||
#define EMBED_S 2 // speed (used in setlengths) | |||||
#define EMBED_A 3 // amplitude/volume | |||||
#define EMBED_R 4 // pitch range/expression | |||||
#define EMBED_H 5 // echo/reverberation | |||||
#define EMBED_T 6 // different tone for announcing punctuation (not used) | |||||
#define EMBED_I 7 // sound icon | |||||
#define EMBED_S2 8 // speed (used in synthesize) | |||||
#define EMBED_Y 9 // say-as commands | |||||
#define EMBED_M 10 // mark name | |||||
#define EMBED_U 11 // audio uri | |||||
#define EMBED_B 12 // break | |||||
#define EMBED_F 13 // emphasis | |||||
#define EMBED_C 14 // capital letter indication | |||||
#define EMBED_P 1 // pitch | |||||
#define EMBED_S 2 // speed (used in setlengths) | |||||
#define EMBED_A 3 // amplitude/volume | |||||
#define EMBED_R 4 // pitch range/expression | |||||
#define EMBED_H 5 // echo/reverberation | |||||
#define EMBED_T 6 // different tone for announcing punctuation (not used) | |||||
#define EMBED_I 7 // sound icon | |||||
#define EMBED_S2 8 // speed (used in synthesize) | |||||
#define EMBED_Y 9 // say-as commands | |||||
#define EMBED_M 10 // mark name | |||||
#define EMBED_U 11 // audio uri | |||||
#define EMBED_B 12 // break | |||||
#define EMBED_F 13 // emphasis | |||||
#define EMBED_C 14 // capital letter indication | |||||
#define N_EMBEDDED_VALUES 15 | #define N_EMBEDDED_VALUES 15 | ||||
extern int embedded_value[N_EMBEDDED_VALUES]; | extern int embedded_value[N_EMBEDDED_VALUES]; | ||||
extern int embedded_default[N_EMBEDDED_VALUES]; | extern int embedded_default[N_EMBEDDED_VALUES]; | ||||
#define N_PEAKS 9 | #define N_PEAKS 9 | ||||
#define N_PEAKS2 9 // plus Notch and Fill (not yet implemented) | |||||
#define N_PEAKS2 9 // plus Notch and Fill (not yet implemented) | |||||
#define N_MARKERS 8 | #define N_MARKERS 8 | ||||
#define N_KLATTP 10 // this affects the phoneme data file format | |||||
#define N_KLATTP2 14 // used in vowel files, with extra parameters for future extensions | |||||
#define N_KLATTP 10 // this affects the phoneme data file format | |||||
#define N_KLATTP2 14 // used in vowel files, with extra parameters for future extensions | |||||
#define KLATT_AV 0 | #define KLATT_AV 0 | ||||
#define KLATT_FNZ 1 // nasal zero freq | |||||
#define KLATT_FNZ 1 // nasal zero freq | |||||
#define KLATT_Tilt 2 | #define KLATT_Tilt 2 | ||||
#define KLATT_Aspr 3 | #define KLATT_Aspr 3 | ||||
#define KLATT_Skew 4 | #define KLATT_Skew 4 | ||||
#define KLATT_FricBP 8 | #define KLATT_FricBP 8 | ||||
#define KLATT_Turb 9 | #define KLATT_Turb 9 | ||||
typedef struct { // 64 bytes | |||||
typedef struct { // 64 bytes | |||||
short frflags; | short frflags; | ||||
short ffreq[7]; | short ffreq[7]; | ||||
unsigned char length; | unsigned char length; | ||||
unsigned char rms; | unsigned char rms; | ||||
unsigned char fheight[8]; | unsigned char fheight[8]; | ||||
unsigned char fwidth[6]; // width/4 f0-5 | |||||
unsigned char fright[3]; // width/4 f0-2 | |||||
unsigned char bw[4]; // Klatt bandwidth BNZ /2, f1,f2,f3 | |||||
unsigned char klattp[5]; // AV, FNZ, Tilt, Aspr, Skew | |||||
unsigned char klattp2[5]; // continuation of klattp[], Avp, Fric, FricBP, Turb | |||||
unsigned char klatt_ap[7]; // Klatt parallel amplitude | |||||
unsigned char klatt_bp[7]; // Klatt parallel bandwidth /2 | |||||
unsigned char spare; // pad to multiple of 4 bytes | |||||
} frame_t; // with extra Klatt parameters for parallel resonators | |||||
typedef struct { // 44 bytes | |||||
unsigned char fwidth[6]; // width/4 f0-5 | |||||
unsigned char fright[3]; // width/4 f0-2 | |||||
unsigned char bw[4]; // Klatt bandwidth BNZ /2, f1,f2,f3 | |||||
unsigned char klattp[5]; // AV, FNZ, Tilt, Aspr, Skew | |||||
unsigned char klattp2[5]; // continuation of klattp[], Avp, Fric, FricBP, Turb | |||||
unsigned char klatt_ap[7]; // Klatt parallel amplitude | |||||
unsigned char klatt_bp[7]; // Klatt parallel bandwidth /2 | |||||
unsigned char spare; // pad to multiple of 4 bytes | |||||
} frame_t; // with extra Klatt parameters for parallel resonators | |||||
typedef struct { // 44 bytes | |||||
short frflags; | short frflags; | ||||
short ffreq[7]; | short ffreq[7]; | ||||
unsigned char length; | unsigned char length; | ||||
unsigned char rms; | unsigned char rms; | ||||
unsigned char fheight[8]; | unsigned char fheight[8]; | ||||
unsigned char fwidth[6]; // width/4 f0-5 | |||||
unsigned char fright[3]; // width/4 f0-2 | |||||
unsigned char bw[4]; // Klatt bandwidth BNZ /2, f1,f2,f3 | |||||
unsigned char klattp[5]; // AV, FNZ, Tilt, Aspr, Skew | |||||
} frame_t2; // without the extra Klatt parameters | |||||
unsigned char fwidth[6]; // width/4 f0-5 | |||||
unsigned char fright[3]; // width/4 f0-2 | |||||
unsigned char bw[4]; // Klatt bandwidth BNZ /2, f1,f2,f3 | |||||
unsigned char klattp[5]; // AV, FNZ, Tilt, Aspr, Skew | |||||
} frame_t2; // without the extra Klatt parameters | |||||
// formant data used by wavegen | // formant data used by wavegen | ||||
typedef struct { | typedef struct { | ||||
DOUBLEX height1; | DOUBLEX height1; | ||||
DOUBLEX left1; | DOUBLEX left1; | ||||
DOUBLEX right1; | DOUBLEX right1; | ||||
DOUBLEX freq_inc; // increment by this every 64 samples | |||||
DOUBLEX freq_inc; // increment by this every 64 samples | |||||
DOUBLEX height_inc; | DOUBLEX height_inc; | ||||
DOUBLEX left_inc; | DOUBLEX left_inc; | ||||
DOUBLEX right_inc; | DOUBLEX right_inc; | ||||
} wavegen_peaks_t; | |||||
} wavegen_peaks_t; | |||||
typedef struct { | typedef struct { | ||||
unsigned char *pitch_env; | unsigned char *pitch_env; | ||||
int pitch_range; // Hz*256 range of envelope | int pitch_range; // Hz*256 range of envelope | ||||
unsigned char *mix_wavefile; // wave file to be added to synthesis | unsigned char *mix_wavefile; // wave file to be added to synthesis | ||||
int n_mix_wavefile; // length in bytes | |||||
int mix_wave_scale; // 0=2 byte samples | |||||
int n_mix_wavefile; // length in bytes | |||||
int mix_wave_scale; // 0=2 byte samples | |||||
int mix_wave_amp; | int mix_wave_amp; | ||||
int mix_wavefile_ix; | int mix_wavefile_ix; | ||||
int mix_wavefile_max; // length of available WAV data (in bytes) | int mix_wavefile_max; // length of available WAV data (in bytes) | ||||
int amplitude_fmt; // percentage amplitude adjustment for formant synthesis | int amplitude_fmt; // percentage amplitude adjustment for formant synthesis | ||||
} WGEN_DATA; | } WGEN_DATA; | ||||
typedef struct { | typedef struct { | ||||
double a; | double a; | ||||
double b; | double b; | ||||
double c; | double c; | ||||
double x1; | double x1; | ||||
double x2; | double x2; | ||||
} RESONATOR; | |||||
} RESONATOR; | |||||
typedef struct { | typedef struct { | ||||
short length_total; // not used | short length_total; // not used | ||||
unsigned char n_frames; | unsigned char n_frames; | ||||
unsigned char sqflags; | unsigned char sqflags; | ||||
frame_t2 frame[N_SEQ_FRAMES]; // max. frames in a spectrum sequence | |||||
} SPECT_SEQ; // sequence of espeak formant frames | |||||
frame_t2 frame[N_SEQ_FRAMES]; // max. frames in a spectrum sequence | |||||
} SPECT_SEQ; // sequence of espeak formant frames | |||||
typedef struct { | typedef struct { | ||||
short length_total; // not used | short length_total; // not used | ||||
unsigned char n_frames; | unsigned char n_frames; | ||||
unsigned char sqflags; | unsigned char sqflags; | ||||
frame_t frame[N_SEQ_FRAMES]; // max. frames in a spectrum sequence | |||||
} SPECT_SEQK; // sequence of klatt formants frames | |||||
frame_t frame[N_SEQ_FRAMES]; // max. frames in a spectrum sequence | |||||
} SPECT_SEQK; // sequence of klatt formants frames | |||||
typedef struct { | typedef struct { | ||||
short length; | short length; | ||||
// a clause translated into phoneme codes (first stage) | // a clause translated into phoneme codes (first stage) | ||||
typedef struct { | typedef struct { | ||||
unsigned short synthflags; // NOTE Put shorts on 32bit boundaries, because of RISC OS compiler bug? | |||||
unsigned short synthflags; // NOTE Put shorts on 32bit boundaries, because of RISC OS compiler bug? | |||||
unsigned char phcode; | unsigned char phcode; | ||||
unsigned char stresslevel; | unsigned char stresslevel; | ||||
unsigned short sourceix; // ix into the original source text string, only set at the start of a word | unsigned short sourceix; // ix into the original source text string, only set at the start of a word | ||||
unsigned char wordstress; // the highest level stress in this word | |||||
unsigned char wordstress; // the highest level stress in this word | |||||
unsigned char tone_ph; // tone phoneme to use with this vowel | unsigned char tone_ph; // tone phoneme to use with this vowel | ||||
} PHONEME_LIST2; | } PHONEME_LIST2; | ||||
typedef struct { | typedef struct { | ||||
// The first section is a copy of PHONEME_LIST2 | |||||
// The first section is a copy of PHONEME_LIST2 | |||||
unsigned short synthflags; | unsigned short synthflags; | ||||
unsigned char phcode; | unsigned char phcode; | ||||
unsigned char stresslevel; | unsigned char stresslevel; | ||||
unsigned short sourceix; // ix into the original source text string, only set at the start of a word | unsigned short sourceix; // ix into the original source text string, only set at the start of a word | ||||
unsigned char wordstress; // the highest level stress in this word | |||||
unsigned char wordstress; // the highest level stress in this word | |||||
unsigned char tone_ph; // tone phoneme to use with this vowel | unsigned char tone_ph; // tone phoneme to use with this vowel | ||||
PHONEME_TAB *ph; | PHONEME_TAB *ph; | ||||
int sound_param; | int sound_param; | ||||
} PHONEME_LIST; | } PHONEME_LIST; | ||||
#define pd_FMT 0 | #define pd_FMT 0 | ||||
#define pd_WAV 1 | #define pd_WAV 1 | ||||
#define pd_VWLSTART 2 | #define pd_VWLSTART 2 | ||||
char ipa_string[18]; | char ipa_string[18]; | ||||
} PHONEME_DATA; | } PHONEME_DATA; | ||||
typedef struct { | typedef struct { | ||||
int fmt_control; | int fmt_control; | ||||
int use_vowelin; | int use_vowelin; | ||||
#define i_ADD_LENGTH 0x0c | #define i_ADD_LENGTH 0x0c | ||||
// conditions and jumps | // conditions and jumps | ||||
#define i_CONDITION 0x2000 | #define i_CONDITION 0x2000 | ||||
#define i_OR 0x1000 // added to i_CONDITION | #define i_OR 0x1000 // added to i_CONDITION | ||||
#define i_isSeqFlag1 0x8f | #define i_isSeqFlag1 0x8f | ||||
#define i_IsTranslationGiven 0x90 | #define i_IsTranslationGiven 0x90 | ||||
// place of articulation | // place of articulation | ||||
#define i_isVel 0x28 | #define i_isVel 0x28 | ||||
// phflags | // phflags | ||||
#define i_isSibilant 0x45 // bit 5 in phflags | |||||
#define i_isPalatal 0x49 // bit 9 in phflags | |||||
#define i_isLong 0x55 // bit 21 in phflags | |||||
#define i_isRhotic 0x57 // bit 23 in phflags | |||||
#define i_isSibilant 0x45 // bit 5 in phflags | |||||
#define i_isPalatal 0x49 // bit 9 in phflags | |||||
#define i_isLong 0x55 // bit 21 in phflags | |||||
#define i_isRhotic 0x57 // bit 23 in phflags | |||||
#define i_isFlag1 0x5c | #define i_isFlag1 0x5c | ||||
#define i_isFlag2 0x5d | #define i_isFlag2 0x5d | ||||
#define i_isFlag3 0x5e | #define i_isFlag3 0x5e | ||||
#define i_StressLevel 0x800 | #define i_StressLevel 0x800 | ||||
typedef struct { | typedef struct { | ||||
int name; | int name; | ||||
int length; | int length; | ||||
unsigned int next_phoneme; | unsigned int next_phoneme; | ||||
int mbr_name; | int mbr_name; | ||||
int mbr_name2; | int mbr_name2; | ||||
int percent; // percentage length of first component | |||||
int percent; // percentage length of first component | |||||
int control; | int control; | ||||
} MBROLA_TAB; | } MBROLA_TAB; | ||||
int fast_settings[8]; | int fast_settings[8]; | ||||
} SPEED_FACTORS; | } SPEED_FACTORS; | ||||
typedef struct { | typedef struct { | ||||
char name[12]; | char name[12]; | ||||
unsigned char flags[4]; | unsigned char flags[4]; | ||||
unsigned char head_max_steps; | unsigned char head_max_steps; | ||||
unsigned char n_head_extend; | unsigned char n_head_extend; | ||||
signed char unstr_start[3]; // for: onset, head, last | |||||
signed char unstr_start[3]; // for: onset, head, last | |||||
signed char unstr_end[3]; | signed char unstr_end[3]; | ||||
unsigned char nucleus0_env; // pitch envelope, tonic syllable is at end, no tail | |||||
unsigned char nucleus0_env; // pitch envelope, tonic syllable is at end, no tail | |||||
unsigned char nucleus0_max; | unsigned char nucleus0_max; | ||||
unsigned char nucleus0_min; | unsigned char nucleus0_min; | ||||
unsigned char nucleus1_env; // when followed by a tail | |||||
unsigned char nucleus1_env; // when followed by a tail | |||||
unsigned char nucleus1_max; | unsigned char nucleus1_max; | ||||
unsigned char nucleus1_min; | unsigned char nucleus1_min; | ||||
unsigned char tail_start; | unsigned char tail_start; | ||||
unsigned char split_tune; | unsigned char split_tune; | ||||
unsigned char spare[8]; | unsigned char spare[8]; | ||||
int spare2; // the struct length should be a multiple of 4 bytes | |||||
int spare2; // the struct length should be a multiple of 4 bytes | |||||
} TUNE; | } TUNE; | ||||
extern int n_tunes; | extern int n_tunes; | ||||
#define WCMD_FMT_AMPLITUDE 14 | #define WCMD_FMT_AMPLITUDE 14 | ||||
#define WCMD_SONIC_SPEED 15 | #define WCMD_SONIC_SPEED 15 | ||||
#define N_WCMDQ 170 | #define N_WCMDQ 170 | ||||
#define MIN_WCMDQ 25 // need this many free entries before adding new phoneme | #define MIN_WCMDQ 25 // need this many free entries before adding new phoneme | ||||
int WavegenFill(int fill_zeros); | int WavegenFill(int fill_zeros); | ||||
void MarkerEvent(int type, unsigned int char_position, int value, int value2, unsigned char *out_ptr); | void MarkerEvent(int type, unsigned int char_position, int value, int value2, unsigned char *out_ptr); | ||||
extern unsigned char *wavefile_data; | extern unsigned char *wavefile_data; | ||||
extern int samplerate; | extern int samplerate; | ||||
extern int samplerate_native; | extern int samplerate_native; | ||||
int Reverse4Bytes(int word); | int Reverse4Bytes(int word); | ||||
int CompileDictionary(const char *dsource, const char *dict_name, FILE *log, char *err_name, int flags); | int CompileDictionary(const char *dsource, const char *dict_name, FILE *log, char *err_name, int flags); | ||||
#define ENV_LEN 128 // length of pitch envelopes | #define ENV_LEN 128 // length of pitch envelopes | ||||
#define PITCHfall 0 // standard pitch envelopes | |||||
#define PITCHrise 2 | |||||
#define PITCHfall 0 // standard pitch envelopes | |||||
#define PITCHrise 2 | |||||
#define N_ENVELOPE_DATA 20 | #define N_ENVELOPE_DATA 20 | ||||
extern unsigned char *envelope_data[N_ENVELOPE_DATA]; | extern unsigned char *envelope_data[N_ENVELOPE_DATA]; | ||||
{ | { | ||||
#endif | #endif | ||||
#define L(c1, c2) (c1<<8)+c2 // combine two characters into an integer for translator name | |||||
#define L(c1, c2) (c1<<8)+c2 // combine two characters into an integer for translator name | |||||
#define CTRL_EMBEDDED 0x01 // control character at the start of an embedded command | |||||
#define REPLACED_E 'E' // 'e' replaced by silent e | |||||
#define CTRL_EMBEDDED 0x01 // control character at the start of an embedded command | |||||
#define REPLACED_E 'E' // 'e' replaced by silent e | |||||
#define N_WORD_PHONEMES 200 // max phonemes in a word | |||||
#define N_WORD_BYTES 160 // max bytes for the UTF8 characters in a word | |||||
#define N_CLAUSE_WORDS 300 // max words in a clause | |||||
#define N_TR_SOURCE 800 // the source text of a single clause (UTF8 bytes) | |||||
#define N_WORD_PHONEMES 200 // max phonemes in a word | |||||
#define N_WORD_BYTES 160 // max bytes for the UTF8 characters in a word | |||||
#define N_CLAUSE_WORDS 300 // max words in a clause | |||||
#define N_TR_SOURCE 800 // the source text of a single clause (UTF8 bytes) | |||||
#define N_RULE_GROUP2 120 // max num of two-letter rule chains | |||||
#define N_RULE_GROUP2 120 // max num of two-letter rule chains | |||||
#define N_HASH_DICT 1024 | #define N_HASH_DICT 1024 | ||||
#define N_CHARSETS 20 | #define N_CHARSETS 20 | ||||
#define N_LETTER_GROUPS 95 // maximum is 127-32 | |||||
#define N_LETTER_GROUPS 95 // maximum is 127-32 | |||||
/* dictionary flags, word 1 */ | |||||
// dictionary flags, word 1 | |||||
// bits 0-3 stressed syllable, bit 6=unstressed | // bits 0-3 stressed syllable, bit 6=unstressed | ||||
#define FLAG_SKIPWORDS 0x80 | #define FLAG_SKIPWORDS 0x80 | ||||
#define FLAG_PREPAUSE 0x100 | #define FLAG_PREPAUSE 0x100 | ||||
#define FLAG_STRESS_END 0x200 // full stress if at end of clause | |||||
#define FLAG_STRESS_END2 0x400 // full stress if at end of clause, or only followed by unstressed | |||||
#define FLAG_UNSTRESS_END 0x800 // reduce stress at end of clause | |||||
#define FLAG_SPELLWORD 0x1000 // re-translate the word as individual letters, separated by spaces | |||||
#define FLAG_ACCENT_BEFORE 0x1000 // say this accent name before the letter name | |||||
#define FLAG_ABBREV 0x2000 // spell as letters, even with a vowel, OR use specified pronunciation rather than split into letters | |||||
#define FLAG_DOUBLING 0x4000 // doubles the following consonant | |||||
#define BITNUM_FLAG_ALT 14 // bit number of FLAG_ALT_TRANS - 1 | |||||
#define FLAG_ALT_TRANS 0x8000 // language specific | |||||
#define FLAG_ALT2_TRANS 0x10000 // language specific | |||||
#define FLAG_ALT3_TRANS 0x20000 // language specific | |||||
#define FLAG_ALT4_TRANS 0x40000 // language specific | |||||
#define FLAG_ALT5_TRANS 0x80000 // language specific | |||||
#define FLAG_ALT6_TRANS 0x100000 // language specific | |||||
#define FLAG_ALT7_TRANS 0x200000 // language specific | |||||
#define FLAG_COMBINE 0x800000 // combine with the next word | |||||
#define FLAG_ALLOW_DOT 0x01000000 // ignore '.' after word (abbreviation) | |||||
#define FLAG_NEEDS_DOT 0x02000000 // only if the word is followed by a dot | |||||
#define FLAG_STRESS_END 0x200 // full stress if at end of clause | |||||
#define FLAG_STRESS_END2 0x400 // full stress if at end of clause, or only followed by unstressed | |||||
#define FLAG_UNSTRESS_END 0x800 // reduce stress at end of clause | |||||
#define FLAG_SPELLWORD 0x1000 // re-translate the word as individual letters, separated by spaces | |||||
#define FLAG_ACCENT_BEFORE 0x1000 // say this accent name before the letter name | |||||
#define FLAG_ABBREV 0x2000 // spell as letters, even with a vowel, OR use specified pronunciation rather than split into letters | |||||
#define FLAG_DOUBLING 0x4000 // doubles the following consonant | |||||
#define BITNUM_FLAG_ALT 14 // bit number of FLAG_ALT_TRANS - 1 | |||||
#define FLAG_ALT_TRANS 0x8000 // language specific | |||||
#define FLAG_ALT2_TRANS 0x10000 // language specific | |||||
#define FLAG_ALT3_TRANS 0x20000 // language specific | |||||
#define FLAG_ALT4_TRANS 0x40000 // language specific | |||||
#define FLAG_ALT5_TRANS 0x80000 // language specific | |||||
#define FLAG_ALT6_TRANS 0x100000 // language specific | |||||
#define FLAG_ALT7_TRANS 0x200000 // language specific | |||||
#define FLAG_COMBINE 0x800000 // combine with the next word | |||||
#define FLAG_ALLOW_DOT 0x01000000 // ignore '.' after word (abbreviation) | |||||
#define FLAG_NEEDS_DOT 0x02000000 // only if the word is followed by a dot | |||||
#define FLAG_WAS_UNPRONOUNCABLE 0x04000000 // the unpronounceable routine was used | #define FLAG_WAS_UNPRONOUNCABLE 0x04000000 // the unpronounceable routine was used | ||||
#define FLAG_MAX3 0x08000000 // limit to 3 repeats | |||||
#define FLAG_PAUSE1 0x10000000 // shorter prepause | |||||
#define FLAG_TEXTMODE 0x20000000 // word translates to replacement text, not phonemes | |||||
#define FLAG_MAX3 0x08000000 // limit to 3 repeats | |||||
#define FLAG_PAUSE1 0x10000000 // shorter prepause | |||||
#define FLAG_TEXTMODE 0x20000000 // word translates to replacement text, not phonemes | |||||
#define BITNUM_FLAG_TEXTMODE 29 | #define BITNUM_FLAG_TEXTMODE 29 | ||||
#define FLAG_FOUND_ATTRIBUTES 0x40000000 // word was found in the dictionary list (has attributes) | |||||
#define FLAG_FOUND 0x80000000 // pronunciation was found in the dictionary list | |||||
#define FLAG_FOUND_ATTRIBUTES 0x40000000 // word was found in the dictionary list (has attributes) | |||||
#define FLAG_FOUND 0x80000000 // pronunciation was found in the dictionary list | |||||
// dictionary flags, word 2 | // dictionary flags, word 2 | ||||
#define FLAG_VERBF 0x1 /* verb follows */ | |||||
#define FLAG_VERBSF 0x2 /* verb follows, may have -s suffix */ | |||||
#define FLAG_NOUNF 0x4 /* noun follows */ | |||||
#define FLAG_PASTF 0x8 /* past tense follows */ | |||||
#define FLAG_VERB 0x10 /* pronunciation for verb */ | |||||
#define FLAG_NOUN 0x20 /* pronunciation for noun */ | |||||
#define FLAG_PAST 0x40 /* pronunciation for past tense */ | |||||
#define FLAG_VERB_EXT 0x100 /* extend the 'verb follows' */ | |||||
#define FLAG_CAPITAL 0x200 /* pronunciation if initial letter is upper case */ | |||||
#define FLAG_ALLCAPS 0x400 // only if the word is all capitals | |||||
#define FLAG_ACCENT 0x800 // character name is base-character name + accent name | |||||
#define FLAG_HYPHENATED 0x1000 // multiple-words, but needs hyphen between parts 1 and 2 | |||||
#define FLAG_SENTENCE 0x2000 // only if the clause is a sentence | |||||
#define FLAG_VERBF 0x1 // verb follows | |||||
#define FLAG_VERBSF 0x2 // verb follows, may have -s suffix | |||||
#define FLAG_NOUNF 0x4 // noun follows | |||||
#define FLAG_PASTF 0x8 // past tense follows | |||||
#define FLAG_VERB 0x10 // pronunciation for verb | |||||
#define FLAG_NOUN 0x20 // pronunciation for noun | |||||
#define FLAG_PAST 0x40 // pronunciation for past tense | |||||
#define FLAG_VERB_EXT 0x100 // extend the 'verb follows' | |||||
#define FLAG_CAPITAL 0x200 // pronunciation if initial letter is upper case | |||||
#define FLAG_ALLCAPS 0x400 // only if the word is all capitals | |||||
#define FLAG_ACCENT 0x800 // character name is base-character name + accent name | |||||
#define FLAG_HYPHENATED 0x1000 // multiple-words, but needs hyphen between parts 1 and 2 | |||||
#define FLAG_SENTENCE 0x2000 // only if the clause is a sentence | |||||
#define FLAG_ONLY 0x4000 | #define FLAG_ONLY 0x4000 | ||||
#define FLAG_ONLY_S 0x8000 | #define FLAG_ONLY_S 0x8000 | ||||
#define FLAG_STEM 0x10000 // must have a suffix | |||||
#define FLAG_ATEND 0x20000 // use this pronunciation if at end of clause | |||||
#define FLAG_ATSTART 0x40000 // use this pronunciation if at start of clause | |||||
#define FLAG_NATIVE 0x80000 // not if we've switched translators | |||||
#define FLAG_LOOKUP_SYMBOL 0x40000000 // to indicate called from Lookup() | |||||
#define BITNUM_FLAG_ALLCAPS 0x2a | |||||
#define BITNUM_FLAG_HYPHENATED 0x2c | |||||
#define BITNUM_FLAG_ONLY 0x2e | |||||
#define BITNUM_FLAG_ONLY_S 0x2f | |||||
#define FLAG_STEM 0x10000 // must have a suffix | |||||
#define FLAG_ATEND 0x20000 // use this pronunciation if at end of clause | |||||
#define FLAG_ATSTART 0x40000 // use this pronunciation if at start of clause | |||||
#define FLAG_NATIVE 0x80000 // not if we've switched translators | |||||
#define FLAG_LOOKUP_SYMBOL 0x40000000 // to indicate called from Lookup() | |||||
#define BITNUM_FLAG_ALLCAPS 0x2a | |||||
#define BITNUM_FLAG_HYPHENATED 0x2c | |||||
#define BITNUM_FLAG_ONLY 0x2e | |||||
#define BITNUM_FLAG_ONLY_S 0x2f | |||||
// wordflags, flags in source word | // wordflags, flags in source word | ||||
#define FLAG_ALL_UPPER 0x1 /* no lower case letters in the word */ | |||||
#define FLAG_FIRST_UPPER 0x2 /* first letter is upper case */ | |||||
#define FLAG_UPPERS 0x3 // FLAG_ALL_UPPER | FLAG_FIRST_UPPER | |||||
#define FLAG_HAS_PLURAL 0x4 /* upper-case word with s or 's lower-case ending */ | |||||
#define FLAG_PHONEMES 0x8 /* word is phonemes */ | |||||
#define FLAG_LAST_WORD 0x10 /* last word in clause */ | |||||
#define FLAG_EMBEDDED 0x40 /* word is preceded by embedded commands */ | |||||
#define FLAG_ALL_UPPER 0x1 // no lower case letters in the word | |||||
#define FLAG_FIRST_UPPER 0x2 // first letter is upper case | |||||
#define FLAG_UPPERS 0x3 // FLAG_ALL_UPPER | FLAG_FIRST_UPPER | |||||
#define FLAG_HAS_PLURAL 0x4 // upper-case word with s or 's lower-case ending | |||||
#define FLAG_PHONEMES 0x8 // word is phonemes | |||||
#define FLAG_LAST_WORD 0x10 // last word in clause | |||||
#define FLAG_EMBEDDED 0x40 // word is preceded by embedded commands | |||||
#define FLAG_HYPHEN 0x80 | #define FLAG_HYPHEN 0x80 | ||||
#define FLAG_NOSPACE 0x100 // word is not seperated from previous word by a space | |||||
#define FLAG_FIRST_WORD 0x200 // first word in clause | |||||
#define FLAG_FOCUS 0x400 // the focus word of a clause | |||||
#define FLAG_NOSPACE 0x100 // word is not seperated from previous word by a space | |||||
#define FLAG_FIRST_WORD 0x200 // first word in clause | |||||
#define FLAG_FOCUS 0x400 // the focus word of a clause | |||||
#define FLAG_EMPHASIZED 0x800 | #define FLAG_EMPHASIZED 0x800 | ||||
#define FLAG_EMPHASIZED2 0xc00 // FLAG_FOCUS | FLAG_EMPHASIZED | |||||
#define FLAG_EMPHASIZED2 0xc00 // FLAG_FOCUS | FLAG_EMPHASIZED | |||||
#define FLAG_DONT_SWITCH_TRANSLATOR 0x1000 | #define FLAG_DONT_SWITCH_TRANSLATOR 0x1000 | ||||
#define FLAG_SUFFIX_REMOVED 0x2000 | #define FLAG_SUFFIX_REMOVED 0x2000 | ||||
#define FLAG_HYPHEN_AFTER 0x4000 | #define FLAG_HYPHEN_AFTER 0x4000 | ||||
#define FLAG_TRANSLATOR2 0x400000 // retranslating using a different language | #define FLAG_TRANSLATOR2 0x400000 // retranslating using a different language | ||||
#define FLAG_PREFIX_REMOVED 0x800000 // a prefix has been removed from this word | #define FLAG_PREFIX_REMOVED 0x800000 // a prefix has been removed from this word | ||||
#define FLAG_SUFFIX_VOWEL 0x08000000 // remember an initial vowel from the suffix | |||||
#define FLAG_NO_TRACE 0x10000000 // passed to TranslateRules() to suppress dictionary lookup printout | |||||
#define FLAG_SUFFIX_VOWEL 0x08000000 // remember an initial vowel from the suffix | |||||
#define FLAG_NO_TRACE 0x10000000 // passed to TranslateRules() to suppress dictionary lookup printout | |||||
#define FLAG_NO_PREFIX 0x20000000 | #define FLAG_NO_PREFIX 0x20000000 | ||||
#define FLAG_UNPRON_TEST 0x80000000 // do unpronounability test on the beginning of the word | |||||
#define FLAG_UNPRON_TEST 0x80000000 // do unpronounability test on the beginning of the word | |||||
// prefix/suffix flags (bits 8 to 14, bits 16 to 22) don't use 0x8000, 0x800000 | // prefix/suffix flags (bits 8 to 14, bits 16 to 22) don't use 0x8000, 0x800000 | ||||
#define SUFX_E 0x0100 // e may have been added | #define SUFX_E 0x0100 // e may have been added | ||||
#define SUFX_UNPRON 0x8000 // used to return $unpron flag from *_rules | #define SUFX_UNPRON 0x8000 // used to return $unpron flag from *_rules | ||||
#define FLAG_ALLOW_TEXTMODE 0x02 // allow dictionary to translate to text rather than phonemes | #define FLAG_ALLOW_TEXTMODE 0x02 // allow dictionary to translate to text rather than phonemes | ||||
#define FLAG_SUFX 0x04 | #define FLAG_SUFX 0x04 | ||||
#define FLAG_SUFX_S 0x08 | #define FLAG_SUFX_S 0x08 | ||||
#define FLAG_SUFX_E_ADDED 0x10 | #define FLAG_SUFX_E_ADDED 0x10 | ||||
// codes in dictionary rules | // codes in dictionary rules | ||||
#define RULE_PRE 1 | |||||
#define RULE_POST 2 | |||||
#define RULE_PHONEMES 3 | |||||
#define RULE_PH_COMMON 4 // At start of rule. Its phoneme string is used by subsequent rules | |||||
#define RULE_CONDITION 5 // followed by condition number (byte) | |||||
#define RULE_PRE 1 | |||||
#define RULE_POST 2 | |||||
#define RULE_PHONEMES 3 | |||||
#define RULE_PH_COMMON 4 // At start of rule. Its phoneme string is used by subsequent rules | |||||
#define RULE_CONDITION 5 // followed by condition number (byte) | |||||
#define RULE_GROUP_START 6 | #define RULE_GROUP_START 6 | ||||
#define RULE_GROUP_END 7 | |||||
#define RULE_PRE_ATSTART 8 // as RULE_PRE but also match with 'start of word' | |||||
#define RULE_LINENUM 9 // next 2 bytes give a line number, for debugging purposes | |||||
#define RULE_SPACE 32 // ascii space | |||||
#define RULE_SYLLABLE 21 // @ | |||||
#define RULE_STRESSED 10 // & | |||||
#define RULE_DOUBLE 11 // % | |||||
#define RULE_INC_SCORE 12 // + | |||||
#define RULE_DEL_FWD 13 // # | |||||
#define RULE_ENDING 14 // S | |||||
#define RULE_DIGIT 15 // D digit | |||||
#define RULE_NONALPHA 16 // Z non-alpha | |||||
#define RULE_LETTERGP 17 // A B C H F G Y letter group number | |||||
#define RULE_LETTERGP2 18 // L + letter group number | |||||
#define RULE_CAPITAL 19 // ! word starts with a capital letter | |||||
#define RULE_REPLACEMENTS 20 // section for character replacements | |||||
#define RULE_SKIPCHARS 23 // J | |||||
#define RULE_NO_SUFFIX 24 // N | |||||
#define RULE_NOTVOWEL 25 // K | |||||
#define RULE_IFVERB 26 // V | |||||
#define RULE_DOLLAR 28 // $ commands | |||||
#define RULE_NOVOWELS 29 // X no vowels up to word boundary | |||||
#define RULE_SPELLING 31 // W while spelling letter-by-letter | |||||
#define RULE_LAST_RULE 31 | |||||
#define RULE_GROUP_END 7 | |||||
#define RULE_PRE_ATSTART 8 // as RULE_PRE but also match with 'start of word' | |||||
#define RULE_LINENUM 9 // next 2 bytes give a line number, for debugging purposes | |||||
#define RULE_SPACE 32 // ascii space | |||||
#define RULE_SYLLABLE 21 // @ | |||||
#define RULE_STRESSED 10 // & | |||||
#define RULE_DOUBLE 11 // % | |||||
#define RULE_INC_SCORE 12 // + | |||||
#define RULE_DEL_FWD 13 // # | |||||
#define RULE_ENDING 14 // S | |||||
#define RULE_DIGIT 15 // D digit | |||||
#define RULE_NONALPHA 16 // Z non-alpha | |||||
#define RULE_LETTERGP 17 // A B C H F G Y letter group number | |||||
#define RULE_LETTERGP2 18 // L + letter group number | |||||
#define RULE_CAPITAL 19 // ! word starts with a capital letter | |||||
#define RULE_REPLACEMENTS 20 // section for character replacements | |||||
#define RULE_SKIPCHARS 23 // J | |||||
#define RULE_NO_SUFFIX 24 // N | |||||
#define RULE_NOTVOWEL 25 // K | |||||
#define RULE_IFVERB 26 // V | |||||
#define RULE_DOLLAR 28 // $ commands | |||||
#define RULE_NOVOWELS 29 // X no vowels up to word boundary | |||||
#define RULE_SPELLING 31 // W while spelling letter-by-letter | |||||
#define RULE_LAST_RULE 31 | |||||
#define DOLLAR_UNPR 0x01 | #define DOLLAR_UNPR 0x01 | ||||
#define DOLLAR_NOPREFIX 0x02 | #define DOLLAR_NOPREFIX 0x02 | ||||
#define DOLLAR_LIST 0x03 | #define DOLLAR_LIST 0x03 | ||||
#define LETTERGP_A 0 | |||||
#define LETTERGP_B 1 | |||||
#define LETTERGP_C 2 | |||||
#define LETTERGP_H 3 | |||||
#define LETTERGP_F 4 | |||||
#define LETTERGP_G 5 | |||||
#define LETTERGP_Y 6 | |||||
#define LETTERGP_VOWEL2 7 | |||||
#define LETTERGP_A 0 | |||||
#define LETTERGP_B 1 | |||||
#define LETTERGP_C 2 | |||||
#define LETTERGP_H 3 | |||||
#define LETTERGP_F 4 | |||||
#define LETTERGP_G 5 | |||||
#define LETTERGP_Y 6 | |||||
#define LETTERGP_VOWEL2 7 | |||||
// Punctuation types returned by ReadClause() | // Punctuation types returned by ReadClause() | ||||
// bits 0-11 pause x 10mS | // bits 0-11 pause x 10mS | ||||
// bit 22= dot after the last word | // bit 22= dot after the last word | ||||
// bit 23= pause is x 320mS (not x 10mS) | // bit 23= pause is x 320mS (not x 10mS) | ||||
#define CLAUSE_BIT_SENTENCE 0x80000 | |||||
#define CLAUSE_BIT_CLAUSE 0x40000 | |||||
#define CLAUSE_BIT_VOICE 0x20000 | |||||
#define CLAUSE_BITS_INTONATION 0x7000 | |||||
#define PUNCT_IN_WORD 0x100000 | |||||
#define PUNCT_SAY_NAME 0x200000 | |||||
#define CLAUSE_DOT 0x400000 | |||||
#define CLAUSE_PAUSE_LONG 0x800000 | |||||
#define CLAUSE_BIT_SENTENCE 0x80000 | |||||
#define CLAUSE_BIT_CLAUSE 0x40000 | |||||
#define CLAUSE_BIT_VOICE 0x20000 | |||||
#define CLAUSE_BITS_INTONATION 0x7000 | |||||
#define PUNCT_IN_WORD 0x100000 | |||||
#define PUNCT_SAY_NAME 0x200000 | |||||
#define CLAUSE_DOT 0x400000 | |||||
#define CLAUSE_PAUSE_LONG 0x800000 | |||||
#define CLAUSE_NONE ( 0 + 0x04000) | #define CLAUSE_NONE ( 0 + 0x04000) | ||||
#define CLAUSE_PARAGRAPH (70 + 0x80000) | #define CLAUSE_PARAGRAPH (70 + 0x80000) | ||||
#define CLAUSE_COLON (30 + 0x40000) | #define CLAUSE_COLON (30 + 0x40000) | ||||
#define CLAUSE_SEMICOLON (30 + 0x41000) | #define CLAUSE_SEMICOLON (30 + 0x41000) | ||||
#define SAYAS_CHARS 0x12 | |||||
#define SAYAS_GLYPHS 0x13 | |||||
#define SAYAS_CHARS 0x12 | |||||
#define SAYAS_GLYPHS 0x13 | |||||
#define SAYAS_SINGLE_CHARS 0x14 | #define SAYAS_SINGLE_CHARS 0x14 | ||||
#define SAYAS_KEY 0x24 | |||||
#define SAYAS_DIGITS 0x40 // + number of digits | |||||
#define SAYAS_DIGITS1 0xc1 | |||||
#define SAYAS_KEY 0x24 | |||||
#define SAYAS_DIGITS 0x40 // + number of digits | |||||
#define SAYAS_DIGITS1 0xc1 | |||||
#define CHAR_EMPHASIS 0x0530 // this is an unused character code | |||||
#define CHAR_COMMA_BREAK 0x0557 // unused character code | |||||
#define CHAR_EMPHASIS 0x0530 // this is an unused character code | |||||
#define CHAR_COMMA_BREAK 0x0557 // unused character code | |||||
// Rule: | // Rule: | ||||
// [4] [match] [1 pre] [2 post] [3 phonemes] 0 | // [4] [match] [1 pre] [2 post] [3 phonemes] 0 | ||||
char *del_fwd; | char *del_fwd; | ||||
} MatchRecord; | } MatchRecord; | ||||
// used to mark words with the source[] buffer | // used to mark words with the source[] buffer | ||||
typedef struct { | typedef struct { | ||||
unsigned int flags; | unsigned int flags; | ||||
unsigned char length; | unsigned char length; | ||||
} WORD_TAB; | } WORD_TAB; | ||||
typedef struct { | typedef struct { | ||||
int type; | int type; | ||||
int parameter[N_SPEECH_PARAM]; | int parameter[N_SPEECH_PARAM]; | ||||
extern PARAM_STACK param_stack[]; | extern PARAM_STACK param_stack[]; | ||||
extern const int param_defaults[N_SPEECH_PARAM]; | extern const int param_defaults[N_SPEECH_PARAM]; | ||||
typedef struct { | typedef struct { | ||||
const char *name; | const char *name; | ||||
int offset; | int offset; | ||||
extern ALPHABET alphabets[]; | extern ALPHABET alphabets[]; | ||||
extern ALPHABET *current_alphabet; | extern ALPHABET *current_alphabet; | ||||
// alphabet flags | // alphabet flags | ||||
#define AL_DONT_NAME 0x01 // don't speak the alphabet name | |||||
#define AL_NOT_LETTERS 0x02 // don't use the language for speaking letters | |||||
#define AL_WORDS 0x04 // use the language to speak words | |||||
#define AL_NOT_CODE 0x08 // don't speak the character code | |||||
#define AL_NO_SYMBOL 0x10 // don't repeat "symbol" or "character" | |||||
#define N_LOPTS 21 | |||||
#define LOPT_DIERESES 1 | |||||
#define AL_DONT_NAME 0x01 // don't speak the alphabet name | |||||
#define AL_NOT_LETTERS 0x02 // don't use the language for speaking letters | |||||
#define AL_WORDS 0x04 // use the language to speak words | |||||
#define AL_NOT_CODE 0x08 // don't speak the character code | |||||
#define AL_NO_SYMBOL 0x10 // don't repeat "symbol" or "character" | |||||
#define N_LOPTS 21 | |||||
#define LOPT_DIERESES 1 | |||||
// 1=remove [:] from unstressed syllables, 2= remove from unstressed or non-penultimate syllables | // 1=remove [:] from unstressed syllables, 2= remove from unstressed or non-penultimate syllables | ||||
// bit 4=0, if stress < 4, bit 4=1, if not the highest stress in the word | // bit 4=0, if stress < 4, bit 4=1, if not the highest stress in the word | ||||
#define LOPT_IT_LENGTHEN 2 | |||||
#define LOPT_IT_LENGTHEN 2 | |||||
// 1=german | // 1=german | ||||
#define LOPT_PREFIXES 3 | |||||
#define LOPT_PREFIXES 3 | |||||
// non-zero, change voiced/unoiced to match last consonant in a cluster | // non-zero, change voiced/unoiced to match last consonant in a cluster | ||||
// bit 0=use regressive voicing | // bit 0=use regressive voicing | ||||
// bit 3=LANG=pl, propagate over liquids and nasals | // bit 3=LANG=pl, propagate over liquids and nasals | ||||
// bit 4=LANG=cz,sk don't progagate to [v] | // bit 4=LANG=cz,sk don't progagate to [v] | ||||
// bit 8=devoice word-final consonants | // bit 8=devoice word-final consonants | ||||
#define LOPT_REGRESSIVE_VOICING 4 | |||||
#define LOPT_REGRESSIVE_VOICING 4 | |||||
// 0=default, 1=no check, other allow this character as an extra initial letter (default is 's') | // 0=default, 1=no check, other allow this character as an extra initial letter (default is 's') | ||||
#define LOPT_UNPRONOUNCABLE 5 | |||||
#define LOPT_UNPRONOUNCABLE 5 | |||||
// select length_mods tables, (length_mod_tab) + (length_mod_tab0 * 100) | // select length_mods tables, (length_mod_tab) + (length_mod_tab0 * 100) | ||||
#define LOPT_LENGTH_MODS 6 | |||||
#define LOPT_LENGTH_MODS 6 | |||||
// increase this to prevent sonorants being shortened before shortened (eg. unstressed) vowels | // increase this to prevent sonorants being shortened before shortened (eg. unstressed) vowels | ||||
#define LOPT_SONORANT_MIN 7 | |||||
#define LOPT_SONORANT_MIN 7 | |||||
// bit 0: don't break vowels at word boundary | // bit 0: don't break vowels at word boundary | ||||
#define LOPT_WORD_MERGE 8 | |||||
#define LOPT_WORD_MERGE 8 | |||||
// max. amplitude for vowel at the end of a clause | // max. amplitude for vowel at the end of a clause | ||||
#define LOPT_MAXAMP_EOC 9 | |||||
#define LOPT_MAXAMP_EOC 9 | |||||
// bit 0=reduce even if phonemes are specified in the **_list file | // bit 0=reduce even if phonemes are specified in the **_list file | ||||
// bit 1=don't reduce the strongest vowel in a word which is marked 'unstressed' | // bit 1=don't reduce the strongest vowel in a word which is marked 'unstressed' | ||||
#define LOPT_REDUCE 10 | |||||
#define LOPT_REDUCE 10 | |||||
// LANG=cs,sk combine some prepositions with the following word, if the combination has N or fewer syllables | // LANG=cs,sk combine some prepositions with the following word, if the combination has N or fewer syllables | ||||
// bits 0-3 N syllables | // bits 0-3 N syllables | ||||
// 1 = allow capitals inside a word | // 1 = allow capitals inside a word | ||||
// 2 = stressed syllable is indicated by capitals | // 2 = stressed syllable is indicated by capitals | ||||
#define LOPT_CAPS_IN_WORD 13 | |||||
#define LOPT_CAPS_IN_WORD 13 | |||||
// bit 0=Italian "syntactic doubling" of consoants in the word after a word marked with $double attribute | // bit 0=Italian "syntactic doubling" of consoants in the word after a word marked with $double attribute | ||||
// bit 1=also after a word which ends with a stressed vowel | // bit 1=also after a word which ends with a stressed vowel | ||||
#define LOPT_IT_DOUBLING 14 | |||||
#define LOPT_IT_DOUBLING 14 | |||||
// Call ApplySpecialAttributes() if $alt or $alt2 is set for a word | // Call ApplySpecialAttributes() if $alt or $alt2 is set for a word | ||||
// bit 1: stressed syllable: $alt change [e],[o] to [E],[O], $alt2 change [E],[O] to [e],[o] | // bit 1: stressed syllable: $alt change [e],[o] to [E],[O], $alt2 change [E],[O] to [e],[o] | ||||
#define LOPT_ALT 15 | |||||
#define LOPT_ALT 15 | |||||
// pause for bracket (default=4), pause when annoucing bracket names (default=2) | // pause for bracket (default=4), pause when annoucing bracket names (default=2) | ||||
#define LOPT_BRACKET_PAUSE 16 | #define LOPT_BRACKET_PAUSE 16 | ||||
#define LOPT_LONG_VOWEL_THRESHOLD 18 | #define LOPT_LONG_VOWEL_THRESHOLD 18 | ||||
// bit 0: Don't allow suffices if there is no previous syllable | // bit 0: Don't allow suffices if there is no previous syllable | ||||
#define LOPT_SUFFIX 19 | |||||
#define LOPT_SUFFIX 19 | |||||
// bit 0 Apostrophe at start of word is part of the word | // bit 0 Apostrophe at start of word is part of the word | ||||
// bit 1 Apostrophe at end of word is part of the word | // bit 1 Apostrophe at end of word is part of the word | ||||
#define LOPT_APOSTROPHE 20 | |||||
#define LOPT_APOSTROPHE 20 | |||||
// stress_rule | // stress_rule | ||||
#define STRESSPOSN_1L 0 // 1st syllable | |||||
#define STRESSPOSN_2L 1 // 2nd syllable | |||||
#define STRESSPOSN_2R 2 // penultimate | |||||
#define STRESSPOSN_1R 3 // final syllable | |||||
#define STRESSPOSN_3R 4 // antipenultimate | |||||
#define STRESSPOSN_1L 0 // 1st syllable | |||||
#define STRESSPOSN_2L 1 // 2nd syllable | |||||
#define STRESSPOSN_2R 2 // penultimate | |||||
#define STRESSPOSN_1R 3 // final syllable | |||||
#define STRESSPOSN_3R 4 // antipenultimate | |||||
typedef struct { | typedef struct { | ||||
// bits0-2 separate words with (1=pause_vshort, 2=pause_short, 3=pause, 4=pause_long 5=[?] phonemme) | // bits0-2 separate words with (1=pause_vshort, 2=pause_short, 3=pause, 4=pause_long 5=[?] phonemme) | ||||
// bit15= Give stress to the first unstressed syllable | // bit15= Give stress to the first unstressed syllable | ||||
int stress_flags; | int stress_flags; | ||||
int unstressed_wd1; // stress for $u word of 1 syllable | int unstressed_wd1; // stress for $u word of 1 syllable | ||||
int unstressed_wd2; // stress for $u word of >1 syllable | int unstressed_wd2; // stress for $u word of >1 syllable | ||||
#define NUM2_PERCENT_BEFORE 0x10000 | #define NUM2_PERCENT_BEFORE 0x10000 | ||||
#define NUM2_OMIT_1_HUNDRED_ONLY 0x20000 | #define NUM2_OMIT_1_HUNDRED_ONLY 0x20000 | ||||
#define NUM2_ORDINAL_AND_THOUSANDS 0x40000 | #define NUM2_ORDINAL_AND_THOUSANDS 0x40000 | ||||
#define NUM2_ORDINAL_DROP_VOWEL 0x80000 // currently only for tens and units | |||||
#define NUM2_ORDINAL_DROP_VOWEL 0x80000 // currently only for tens and units | |||||
#define NUM2_ZERO_TENS 0x100000 | #define NUM2_ZERO_TENS 0x100000 | ||||
// bits 1-4 use variant form of numbers before thousands,millions,etc. | // bits 1-4 use variant form of numbers before thousands,millions,etc. | ||||
// bits 6-8 use different forms of thousand, million, etc (M MA MB) | // bits 6-8 use different forms of thousand, million, etc (M MA MB) | ||||
int dict_dialect; // bitmap, use a dialect for foreign words | int dict_dialect; // bitmap, use a dialect for foreign words | ||||
} LANGUAGE_OPTIONS; | } LANGUAGE_OPTIONS; | ||||
// a parameter of ChangePhonemes() | // a parameter of ChangePhonemes() | ||||
typedef struct { | typedef struct { | ||||
int flags; | int flags; | ||||
unsigned char vowel_stressed; // syllable number of the highest stressed vowel | unsigned char vowel_stressed; // syllable number of the highest stressed vowel | ||||
} CHANGEPH; | } CHANGEPH; | ||||
typedef struct { | typedef struct { | ||||
LANGUAGE_OPTIONS langopts; | LANGUAGE_OPTIONS langopts; | ||||
int translator_name; | int translator_name; | ||||
int transpose_max; | int transpose_max; | ||||
const wchar_t *letter_groups[8]; | const wchar_t *letter_groups[8]; | ||||
/* index1=option, index2 by 0=. 1=, 2=?, 3=! 4=none */ | /* index1=option, index2 by 0=. 1=, 2=?, 3=! 4=none */ | ||||
#define INTONATION_TYPES 8 | |||||
#define PUNCT_INTONATIONS 6 | |||||
#define INTONATION_TYPES 8 | |||||
#define PUNCT_INTONATIONS 6 | |||||
unsigned char punct_to_tone[INTONATION_TYPES][PUNCT_INTONATIONS]; | unsigned char punct_to_tone[INTONATION_TYPES][PUNCT_INTONATIONS]; | ||||
char *data_dictrules; // language_1 translation rules file | char *data_dictrules; // language_1 translation rules file | ||||
int clause_terminator; | int clause_terminator; | ||||
} Translator; | } Translator; | ||||
extern int option_tone2; | extern int option_tone2; | ||||
#define OPTION_EMPHASIZE_ALLCAPS 0x100 | #define OPTION_EMPHASIZE_ALLCAPS 0x100 | ||||
#define OPTION_EMPHASIZE_PENULTIMATE 0x200 | #define OPTION_EMPHASIZE_PENULTIMATE 0x200 | ||||
extern char *namedata; | extern char *namedata; | ||||
extern int pre_pause; | extern int pre_pause; | ||||
#define N_MARKER_LENGTH 50 // max.length of a mark name | #define N_MARKER_LENGTH 50 // max.length of a mark name | ||||
extern char skip_marker[N_MARKER_LENGTH]; | extern char skip_marker[N_MARKER_LENGTH]; | ||||
void InterpretPhoneme2(int phcode, PHONEME_DATA *phdata); | void InterpretPhoneme2(int phcode, PHONEME_DATA *phdata); | ||||
char *WritePhMnemonic(char *phon_out, PHONEME_TAB *ph, PHONEME_LIST *plist, int use_ipa, int *flags); | char *WritePhMnemonic(char *phon_out, PHONEME_TAB *ph, PHONEME_LIST *plist, int use_ipa, int *flags); | ||||
extern FILE *f_trans; // for logging | |||||
extern FILE *f_trans; // for logging | |||||
extern FILE *f_logespeak; | extern FILE *f_logespeak; | ||||
extern int logging_type; // from config file | |||||
extern int logging_type; // from config file | |||||
#ifdef __cplusplus | #ifdef __cplusplus | ||||
} | } |
{ | { | ||||
#endif | #endif | ||||
typedef struct { | typedef struct { | ||||
char v_name[40]; | char v_name[40]; | ||||
char language_name[20]; | char language_name[20]; | ||||
int phoneme_tab_ix; // phoneme table number | |||||
int pitch_base; // Hz<<12 | |||||
int pitch_range; // standard = 0x1000 | |||||
int phoneme_tab_ix; // phoneme table number | |||||
int pitch_base; // Hz<<12 | |||||
int pitch_range; // standard = 0x1000 | |||||
int speedf1; | int speedf1; | ||||
int speedf2; | int speedf2; | ||||
int n_harmonic_peaks; // highest formant which is formed from adding harmonics | int n_harmonic_peaks; // highest formant which is formed from adding harmonics | ||||
int peak_shape; // alternative shape for formant peaks (0=standard 1=squarer) | int peak_shape; // alternative shape for formant peaks (0=standard 1=squarer) | ||||
int voicing; // 100% = 64, level of formant-synthesized sound | int voicing; // 100% = 64, level of formant-synthesized sound | ||||
int formant_factor; // adjust nominal formant frequencies by this because of the voice's pitch (256ths) | |||||
int formant_factor; // adjust nominal formant frequencies by this because of the voice's pitch (256ths) | |||||
int consonant_amp; // amplitude of unvoiced consonants | int consonant_amp; // amplitude of unvoiced consonants | ||||
int consonant_ampv; // amplitude of the noise component of voiced consonants | int consonant_ampv; // amplitude of the noise component of voiced consonants | ||||
int samplerate; | int samplerate; | ||||
short freq[N_PEAKS]; // 100% = 256 | short freq[N_PEAKS]; // 100% = 256 | ||||
short height[N_PEAKS]; // 100% = 256 | short height[N_PEAKS]; // 100% = 256 | ||||
short width[N_PEAKS]; // 100% = 256 | short width[N_PEAKS]; // 100% = 256 | ||||
short freqadd[N_PEAKS]; // Hz | |||||
short freqadd[N_PEAKS]; // Hz | |||||
// copies without temporary adjustments from embedded commands | // copies without temporary adjustments from embedded commands | ||||
short freq2[N_PEAKS]; // 100% = 256 | short freq2[N_PEAKS]; // 100% = 256 | ||||
short width2[N_PEAKS]; // 100% = 256 | short width2[N_PEAKS]; // 100% = 256 | ||||
int breath[N_PEAKS]; // amount of breath for each formant. breath[0] indicates whether any are set. | int breath[N_PEAKS]; // amount of breath for each formant. breath[0] indicates whether any are set. | ||||
int breathw[N_PEAKS]; // width of each breath formant | |||||
int breathw[N_PEAKS]; // width of each breath formant | |||||
// This table provides the opportunity for tone control. | // This table provides the opportunity for tone control. | ||||
// Adjustment of harmonic amplitudes, steps of 8Hz | // Adjustment of harmonic amplitudes, steps of 8Hz |
int tone_points[12] = { 600, 170, 1200, 135, 2000, 110, 3000, 110, -1, 0 }; | int tone_points[12] = { 600, 170, 1200, 135, 2000, 110, 3000, 110, -1, 0 }; | ||||
// limit the rate of change for each formant number | // limit the rate of change for each formant number | ||||
static int formant_rate_22050[9] = { 240, 170, 170, 170, 170, 170, 170, 170, 170 }; // values for 22kHz sample rate | |||||
int formant_rate[9]; // values adjusted for actual sample rate | |||||
static int formant_rate_22050[9] = { 240, 170, 170, 170, 170, 170, 170, 170, 170 }; // values for 22kHz sample rate | |||||
int formant_rate[9]; // values adjusted for actual sample rate | |||||
#define DEFAULT_LANGUAGE_PRIORITY 5 | #define DEFAULT_LANGUAGE_PRIORITY 5 | ||||
#define N_VOICES_LIST 250 | #define N_VOICES_LIST 250 | ||||
V_PHONEMES, | V_PHONEMES, | ||||
V_DICTIONARY, | V_DICTIONARY, | ||||
// these affect voice quality, are independent of language | |||||
// these affect voice quality, are independent of language | |||||
V_FORMANT, | V_FORMANT, | ||||
V_PITCH, | V_PITCH, | ||||
V_ECHO, | V_ECHO, | ||||
V_BREATH, | V_BREATH, | ||||
V_BREATHW, | V_BREATHW, | ||||
// these override defaults set by the translator | |||||
// these override defaults set by the translator | |||||
V_WORDGAP, | V_WORDGAP, | ||||
V_INTONATION, | V_INTONATION, | ||||
V_TUNES, | V_TUNES, | ||||
V_ALPHABET2, | V_ALPHABET2, | ||||
V_DICTDIALECT, | V_DICTDIALECT, | ||||
// these need a phoneme table to have been specified | |||||
// these need a phoneme table to have been specified | |||||
V_REPLACE, | V_REPLACE, | ||||
V_CONSONANTS | V_CONSONANTS | ||||
}; | }; | ||||
static MNEM_TAB options_tab[] = { | static MNEM_TAB options_tab[] = { | ||||
{ "reduce_t", LOPT_REDUCE_T }, | |||||
{ "bracket", LOPT_BRACKET_PAUSE }, | |||||
{ "reduce_t", LOPT_REDUCE_T }, | |||||
{ "bracket", LOPT_BRACKET_PAUSE }, | |||||
{ NULL, -1 } | { NULL, -1 } | ||||
}; | }; | ||||
static MNEM_TAB keyword_tab[] = { | static MNEM_TAB keyword_tab[] = { | ||||
{ "name", V_NAME }, | |||||
{ "language", V_LANGUAGE }, | |||||
{ "gender", V_GENDER }, | |||||
{ "formant", V_FORMANT }, | |||||
{ "pitch", V_PITCH }, | |||||
{ "phonemes", V_PHONEMES }, | |||||
{ "translator", V_TRANSLATOR }, | |||||
{ "dictionary", V_DICTIONARY }, | |||||
{ "name", V_NAME }, | |||||
{ "language", V_LANGUAGE }, | |||||
{ "gender", V_GENDER }, | |||||
{ "formant", V_FORMANT }, | |||||
{ "pitch", V_PITCH }, | |||||
{ "phonemes", V_PHONEMES }, | |||||
{ "translator", V_TRANSLATOR }, | |||||
{ "dictionary", V_DICTIONARY }, | |||||
{ "stressLength", V_STRESSLENGTH }, | { "stressLength", V_STRESSLENGTH }, | ||||
{ "stressAmp", V_STRESSAMP }, | |||||
{ "stressAdd", V_STRESSADD }, | |||||
{ "intonation", V_INTONATION }, | |||||
{ "tunes", V_TUNES }, | |||||
{ "dictrules", V_DICTRULES }, | |||||
{ "stressrule", V_STRESSRULE }, | |||||
{ "stressopt", V_STRESSOPT }, | |||||
{ "charset", V_CHARSET }, | |||||
{ "replace", V_REPLACE }, | |||||
{ "words", V_WORDGAP }, | |||||
{ "echo", V_ECHO }, | |||||
{ "flutter", V_FLUTTER }, | |||||
{ "roughness", V_ROUGHNESS }, | |||||
{ "clarity", V_CLARITY }, | |||||
{ "tone", V_TONE }, | |||||
{ "voicing", V_VOICING }, | |||||
{ "breath", V_BREATH }, | |||||
{ "breathw", V_BREATHW }, | |||||
{ "numbers", V_NUMBERS }, | |||||
{ "option", V_OPTION }, | |||||
{ "mbrola", V_MBROLA }, | |||||
{ "consonants", V_CONSONANTS }, | |||||
{ "klatt", V_KLATT }, | |||||
{ "fast_test2", V_FAST }, | |||||
{ "speed", V_SPEED }, | |||||
{ "dict_min", V_DICTMIN }, | |||||
{ "alphabet2", V_ALPHABET2 }, | |||||
{ "dictdialect", V_DICTDIALECT }, | |||||
{ "stressAmp", V_STRESSAMP }, | |||||
{ "stressAdd", V_STRESSADD }, | |||||
{ "intonation", V_INTONATION }, | |||||
{ "tunes", V_TUNES }, | |||||
{ "dictrules", V_DICTRULES }, | |||||
{ "stressrule", V_STRESSRULE }, | |||||
{ "stressopt", V_STRESSOPT }, | |||||
{ "charset", V_CHARSET }, | |||||
{ "replace", V_REPLACE }, | |||||
{ "words", V_WORDGAP }, | |||||
{ "echo", V_ECHO }, | |||||
{ "flutter", V_FLUTTER }, | |||||
{ "roughness", V_ROUGHNESS }, | |||||
{ "clarity", V_CLARITY }, | |||||
{ "tone", V_TONE }, | |||||
{ "voicing", V_VOICING }, | |||||
{ "breath", V_BREATH }, | |||||
{ "breathw", V_BREATHW }, | |||||
{ "numbers", V_NUMBERS }, | |||||
{ "option", V_OPTION }, | |||||
{ "mbrola", V_MBROLA }, | |||||
{ "consonants", V_CONSONANTS }, | |||||
{ "klatt", V_KLATT }, | |||||
{ "fast_test2", V_FAST }, | |||||
{ "speed", V_SPEED }, | |||||
{ "dict_min", V_DICTMIN }, | |||||
{ "alphabet2", V_ALPHABET2 }, | |||||
{ "dictdialect", V_DICTDIALECT }, | |||||
// these just set a value in langopts.param[] | // these just set a value in langopts.param[] | ||||
{ "l_dieresis", 0x100+LOPT_DIERESES }, | |||||
{ "l_prefix", 0x100+LOPT_PREFIXES }, | |||||
{ "l_regressive_v", 0x100+LOPT_REGRESSIVE_VOICING }, | |||||
{ "l_dieresis", 0x100+LOPT_DIERESES }, | |||||
{ "l_prefix", 0x100+LOPT_PREFIXES }, | |||||
{ "l_regressive_v", 0x100+LOPT_REGRESSIVE_VOICING }, | |||||
{ "l_unpronouncable", 0x100+LOPT_UNPRONOUNCABLE }, | { "l_unpronouncable", 0x100+LOPT_UNPRONOUNCABLE }, | ||||
{ "l_sonorant_min", 0x100+LOPT_SONORANT_MIN }, | |||||
{ "l_length_mods", 0x100+LOPT_LENGTH_MODS }, | |||||
{ "apostrophe", 0x100+LOPT_APOSTROPHE }, | |||||
{ NULL, 0 } | |||||
{ "l_sonorant_min", 0x100+LOPT_SONORANT_MIN }, | |||||
{ "l_length_mods", 0x100+LOPT_LENGTH_MODS }, | |||||
{ "apostrophe", 0x100+LOPT_APOSTROPHE }, | |||||
{ NULL, 0 } | |||||
}; | }; | ||||
static MNEM_TAB dict_dialects[] = { | static MNEM_TAB dict_dialects[] = { | ||||
{ "en-us", DICTDIALECT_EN_US }, | |||||
{ "es-la", DICTDIALECT_ES_LA }, | |||||
{ NULL, 0 } | |||||
{ "en-us", DICTDIALECT_EN_US }, | |||||
{ "es-la", DICTDIALECT_ES_LA }, | |||||
{ NULL, 0 } | |||||
}; | }; | ||||
#define N_VOICE_VARIANTS 12 | #define N_VOICE_VARIANTS 12 | ||||
static char *fgets_strip(char *buf, int size, FILE *f_in) | static char *fgets_strip(char *buf, int size, FILE *f_in) | ||||
{ | { | ||||
// strip trailing spaces, and truncate lines at // comment | |||||
// strip trailing spaces, and truncate lines at // comment | |||||
int len; | int len; | ||||
char *p; | char *p; | ||||
if (pt > 0) | if (pt > 0) | ||||
tone_pts[pt+1] = tone_pts[pt-1]; | tone_pts[pt+1] = tone_pts[pt-1]; | ||||
} | } | ||||
freq2 = tone_pts[pt] / 8; // 8Hz steps | |||||
freq2 = tone_pts[pt] / 8; // 8Hz steps | |||||
height2 = tone_pts[pt+1]; | height2 = tone_pts[pt+1]; | ||||
if ((freq2 - freq1) > 0) { | if ((freq2 - freq1) > 0) { | ||||
rate = (double)(height2-height1)/(freq2-freq1); | rate = (double)(height2-height1)/(freq2-freq1); | ||||
void ReadTonePoints(char *string, int *tone_pts) | void ReadTonePoints(char *string, int *tone_pts) | ||||
{ | { | ||||
// tone_pts[] is int[12] | |||||
// tone_pts[] is int[12] | |||||
int ix; | int ix; | ||||
for (ix = 0; ix < 12; ix++) | for (ix = 0; ix < 12; ix++) | ||||
static espeak_VOICE *ReadVoiceFile(FILE *f_in, const char *fname, const char *leafname) | static espeak_VOICE *ReadVoiceFile(FILE *f_in, const char *fname, const char *leafname) | ||||
{ | { | ||||
// Read a Voice file, allocate a VOICE_DATA and set data from the | |||||
// file's language, gender, name lines | |||||
// Read a Voice file, allocate a VOICE_DATA and set data from the | |||||
// file's language, gender, name lines | |||||
char linebuf[120]; | char linebuf[120]; | ||||
char vname[80]; | char vname[80]; | ||||
char vgender[80]; | char vgender[80]; | ||||
char vlanguage[80]; | char vlanguage[80]; | ||||
char languages[300]; // allow space for several alternate language names and priorities | |||||
char languages[300]; // allow space for several alternate language names and priorities | |||||
unsigned int len; | unsigned int len; | ||||
int langix = 0; | int langix = 0; | ||||
espeak_VOICE *voice_data; | espeak_VOICE *voice_data; | ||||
int priority; | int priority; | ||||
int age; | int age; | ||||
int n_variants = 4; // default, number of variants of this voice before using another voice | |||||
int n_variants = 4; // default, number of variants of this voice before using another voice | |||||
int gender; | int gender; | ||||
#ifdef PLATFORM_WINDOWS | #ifdef PLATFORM_WINDOWS | ||||
gender = LookupMnem(genders, vgender); | gender = LookupMnem(genders, vgender); | ||||
if (n_languages == 0) | if (n_languages == 0) | ||||
return NULL; // no language lines in the voice file | |||||
return NULL; // no language lines in the voice file | |||||
p = (char *)calloc(sizeof(espeak_VOICE) + langix + strlen(fname) + strlen(vname) + 3, 1); | p = (char *)calloc(sizeof(espeak_VOICE) + langix + strlen(fname) + strlen(vname) + 3, 1); | ||||
voice_data = (espeak_VOICE *)p; | voice_data = (espeak_VOICE *)p; | ||||
void VoiceReset(int tone_only) | void VoiceReset(int tone_only) | ||||
{ | { | ||||
// Set voice to the default values | |||||
// Set voice to the default values | |||||
int pk; | int pk; | ||||
static unsigned char default_heights[N_PEAKS] = { 130, 128, 120, 116, 100, 100, 128, 128, 128 }; // changed for v.1.47 | |||||
static unsigned char default_heights[N_PEAKS] = { 130, 128, 120, 116, 100, 100, 128, 128, 128 }; // changed for v.1.47 | |||||
static unsigned char default_widths[N_PEAKS] = { 140, 128, 128, 160, 171, 171, 128, 128, 128 }; | static unsigned char default_widths[N_PEAKS] = { 140, 128, 128, 160, 171, 171, 128, 128, 128 }; | ||||
static int breath_widths[N_PEAKS] = { 0, 200, 200, 400, 400, 400, 600, 600, 600 }; | static int breath_widths[N_PEAKS] = { 0, 200, 200, 400, 400, 400, 600, 600, 600 }; | ||||
voice->n_harmonic_peaks = 5; | voice->n_harmonic_peaks = 5; | ||||
voice->peak_shape = 0; | voice->peak_shape = 0; | ||||
voice->voicing = 64; | voice->voicing = 64; | ||||
voice->consonant_amp = 90; // change from 100 to 90 for v.1.47 | |||||
voice->consonant_amp = 90; // change from 100 to 90 for v.1.47 | |||||
voice->consonant_ampv = 100; | voice->consonant_ampv = 100; | ||||
voice->samplerate = samplerate_native; | voice->samplerate = samplerate_native; | ||||
memset(voice->klattv, 0, sizeof(voice->klattv)); | memset(voice->klattv, 0, sizeof(voice->klattv)); | ||||
voice->height[pk] = default_heights[pk]*2; | voice->height[pk] = default_heights[pk]*2; | ||||
voice->width[pk] = default_widths[pk]*2; | voice->width[pk] = default_widths[pk]*2; | ||||
voice->breath[pk] = 0; | voice->breath[pk] = 0; | ||||
voice->breathw[pk] = breath_widths[pk]; // default breath formant woidths | |||||
voice->breathw[pk] = breath_widths[pk]; // default breath formant woidths | |||||
voice->freqadd[pk] = 0; | voice->freqadd[pk] = 0; | ||||
// adjust formant smoothing depending on sample rate | // adjust formant smoothing depending on sample rate | ||||
return; | return; | ||||
if ((phon = LookupPhonemeString(phon_string1)) == 0) | if ((phon = LookupPhonemeString(phon_string1)) == 0) | ||||
return; // not recognised | |||||
return; // not recognised | |||||
replace_phonemes[n_replace_phonemes].old_ph = phon; | replace_phonemes[n_replace_phonemes].old_ph = phon; | ||||
replace_phonemes[n_replace_phonemes].new_ph = LookupPhonemeString(phon_string2); | replace_phonemes[n_replace_phonemes].new_ph = LookupPhonemeString(phon_string2); | ||||
static int Read8Numbers(char *data_in, int *data) | static int Read8Numbers(char *data_in, int *data) | ||||
{ | { | ||||
// Read 8 integer numbers | |||||
// Read 8 integer numbers | |||||
memset(data, 0, 8+sizeof(int)); | memset(data, 0, 8+sizeof(int)); | ||||
return sscanf(data_in, "%d %d %d %d %d %d %d %d", | return sscanf(data_in, "%d %d %d %d %d %d %d %d", | ||||
&data[0], &data[1], &data[2], &data[3], &data[4], &data[5], &data[6], &data[7]); | &data[0], &data[1], &data[2], &data[3], &data[4], &data[5], &data[6], &data[7]); | ||||
static unsigned int StringToWord2(const char *string) | static unsigned int StringToWord2(const char *string) | ||||
{ | { | ||||
// Convert a language name string to a word such as L('e','n') | |||||
// Convert a language name string to a word such as L('e','n') | |||||
int ix; | int ix; | ||||
int c; | int c; | ||||
unsigned int value = 0; | unsigned int value = 0; | ||||
voice_t *LoadVoice(const char *vname, int control) | voice_t *LoadVoice(const char *vname, int control) | ||||
{ | { | ||||
// control, bit 0 1= no_default | |||||
// bit 1 1 = change tone only, not language | |||||
// bit 2 1 = don't report error on LoadDictionary | |||||
// bit 4 1 = vname = full path | |||||
// control, bit 0 1= no_default | |||||
// bit 1 1 = change tone only, not language | |||||
// bit 2 1 = don't report error on LoadDictionary | |||||
// bit 4 1 = vname = full path | |||||
FILE *f_voice = NULL; | FILE *f_voice = NULL; | ||||
char *p; | char *p; | ||||
int pitch1; | int pitch1; | ||||
int pitch2; | int pitch2; | ||||
static char voice_identifier[40]; // file name for current_voice_selected | |||||
static char voice_name[40]; // voice name for current_voice_selected | |||||
static char voice_languages[100]; // list of languages and priorities for current_voice_selected | |||||
static char voice_identifier[40]; // file name for current_voice_selected | |||||
static char voice_name[40]; // voice name for current_voice_selected | |||||
static char voice_languages[100]; // list of languages and priorities for current_voice_selected | |||||
// which directory to look for a named voice. List of voice names, must end in a space. | // which directory to look for a named voice. List of voice names, must end in a space. | ||||
static const char *voices_asia = | static const char *voices_asia = | ||||
strcpy(voicename, "default"); | strcpy(voicename, "default"); | ||||
sprintf(path_voices, "%s%cvoices%c", path_home, PATHSEP, PATHSEP); | sprintf(path_voices, "%s%cvoices%c", path_home, PATHSEP, PATHSEP); | ||||
sprintf(buf, "%s%s", path_voices, voicename); // first, look in the main voices directory | |||||
sprintf(buf, "%s%s", path_voices, voicename); // first, look in the main voices directory | |||||
if (GetFileLength(buf) <= 0) { | if (GetFileLength(buf) <= 0) { | ||||
// then look in the appropriate subdirectory | // then look in the appropriate subdirectory | ||||
if ((voicename[0] == 'm') && (voicename[1] == 'b')) | if ((voicename[0] == 'm') && (voicename[1] == 'b')) | ||||
voice_dir = "mb"; // mbrola voices | |||||
voice_dir = "mb"; // mbrola voices | |||||
else { | else { | ||||
sprintf(name2, "%s ", voicename); | sprintf(name2, "%s ", voicename); | ||||
if (strstr(voices_europe, voicename) != NULL) | if (strstr(voices_europe, voicename) != NULL) | ||||
f_voice = fopen(buf, "r"); | f_voice = fopen(buf, "r"); | ||||
language_type = "en"; // default | |||||
language_type = "en"; // default | |||||
if (f_voice == NULL) { | if (f_voice == NULL) { | ||||
if (control & 3) | if (control & 3) | ||||
return NULL; // can't open file | |||||
return NULL; // can't open file | |||||
if (SelectPhonemeTableName(voicename) >= 0) | if (SelectPhonemeTableName(voicename) >= 0) | ||||
language_type = voicename; | language_type = voicename; | ||||
VoiceReset(tone_only); | VoiceReset(tone_only); | ||||
if (!tone_only) | if (!tone_only) | ||||
SelectPhonemeTableName(phonemes_name); // set up phoneme_tab | |||||
SelectPhonemeTableName(phonemes_name); // set up phoneme_tab | |||||
while ((f_voice != NULL) && (fgets_strip(buf, sizeof(buf), f_voice) != NULL)) { | while ((f_voice != NULL) && (fgets_strip(buf, sizeof(buf), f_voice) != NULL)) { | ||||
// isolate the attribute name | // isolate the attribute name | ||||
new_translator = SelectTranslator(translator_name); | new_translator = SelectTranslator(translator_name); | ||||
langopts = &new_translator->langopts; | langopts = &new_translator->langopts; | ||||
break; | break; | ||||
case V_DICTIONARY: // dictionary | |||||
case V_DICTIONARY: // dictionary | |||||
sscanf(p, "%s", new_dictionary); | sscanf(p, "%s", new_dictionary); | ||||
break; | break; | ||||
case V_PHONEMES: // phoneme table | |||||
case V_PHONEMES: // phoneme table | |||||
sscanf(p, "%s", phonemes_name); | sscanf(p, "%s", phonemes_name); | ||||
break; | break; | ||||
case V_FORMANT: | case V_FORMANT: | ||||
voice->pitch_base = (pitch1 - 9) << 12; | voice->pitch_base = (pitch1 - 9) << 12; | ||||
voice->pitch_range = (pitch2 - pitch1) * 108; | voice->pitch_range = (pitch2 - pitch1) * 108; | ||||
factor = (double)(pitch1 - 82)/82; | factor = (double)(pitch1 - 82)/82; | ||||
voice->formant_factor = (int)((1+factor/4) * 256); // nominal formant shift for a different voice pitch | |||||
voice->formant_factor = (int)((1+factor/4) * 256); // nominal formant shift for a different voice pitch | |||||
} | } | ||||
break; | break; | ||||
case V_STRESSLENGTH: // stressLength | |||||
case V_STRESSLENGTH: // stressLength | |||||
stress_lengths_set = Read8Numbers(p, stress_lengths); | stress_lengths_set = Read8Numbers(p, stress_lengths); | ||||
break; | break; | ||||
case V_STRESSAMP: // stressAmp | |||||
case V_STRESSAMP: // stressAmp | |||||
stress_amps_set = Read8Numbers(p, stress_amps); | stress_amps_set = Read8Numbers(p, stress_amps); | ||||
break; | break; | ||||
case V_STRESSADD: // stressAdd | |||||
case V_STRESSADD: // stressAdd | |||||
stress_add_set = Read8Numbers(p, stress_add); | stress_add_set = Read8Numbers(p, stress_add); | ||||
break; | break; | ||||
case V_INTONATION: // intonation | |||||
case V_INTONATION: // intonation | |||||
sscanf(p, "%d %d", &option_tone_flags, &option_tone2); | sscanf(p, "%d %d", &option_tone_flags, &option_tone2); | ||||
if ((option_tone_flags & 0xff) != 0) | if ((option_tone_flags & 0xff) != 0) | ||||
langopts->intonation_group = option_tone_flags & 0xff; | langopts->intonation_group = option_tone_flags & 0xff; | ||||
langopts->tunes[ix] = value; | langopts->tunes[ix] = value; | ||||
} | } | ||||
break; | break; | ||||
case V_DICTRULES: // conditional dictionary rules and list entries | |||||
case V_DICTRULES: // conditional dictionary rules and list entries | |||||
case V_NUMBERS: | case V_NUMBERS: | ||||
case V_STRESSOPT: | case V_STRESSOPT: | ||||
// expect a list of numbers | // expect a list of numbers | ||||
} | } | ||||
PhonemeReplacement(key, p); | PhonemeReplacement(key, p); | ||||
break; | break; | ||||
case V_WORDGAP: // words | |||||
case V_WORDGAP: // words | |||||
sscanf(p, "%d %d", &langopts->word_gap, &langopts->vowel_pause); | sscanf(p, "%d %d", &langopts->word_gap, &langopts->vowel_pause); | ||||
break; | break; | ||||
case V_STRESSRULE: | case V_STRESSRULE: | ||||
voice->echo_amp = 0; | voice->echo_amp = 0; | ||||
sscanf(p, "%d %d", &voice->echo_delay, &voice->echo_amp); | sscanf(p, "%d %d", &voice->echo_delay, &voice->echo_amp); | ||||
break; | break; | ||||
case V_FLUTTER: // flutter | |||||
case V_FLUTTER: // flutter | |||||
if (sscanf(p, "%d", &value) == 1) | if (sscanf(p, "%d", &value) == 1) | ||||
voice->flutter = value * 32; | voice->flutter = value * 32; | ||||
break; | break; | ||||
case V_ROUGHNESS: // roughness | |||||
case V_ROUGHNESS: // roughness | |||||
if (sscanf(p, "%d", &value) == 1) | if (sscanf(p, "%d", &value) == 1) | ||||
voice->roughness = value; | voice->roughness = value; | ||||
break; | break; | ||||
case V_CLARITY: // formantshape | |||||
case V_CLARITY: // formantshape | |||||
if (sscanf(p, "%d", &value) == 1) { | if (sscanf(p, "%d", &value) == 1) { | ||||
if (value > 4) { | if (value > 4) { | ||||
voice->peak_shape = 1; // squarer formant peaks | |||||
voice->peak_shape = 1; // squarer formant peaks | |||||
value = 4; | value = 4; | ||||
} | } | ||||
voice->n_harmonic_peaks = 1+value; | voice->n_harmonic_peaks = 1+value; | ||||
name2[0] = 0; | name2[0] = 0; | ||||
sscanf(p, "%s %s %d", name1, name2, &srate); | sscanf(p, "%s %s %d", name1, name2, &srate); | ||||
if (LoadMbrolaTable(name1, name2, &srate) != EE_OK) { | |||||
if (LoadMbrolaTable(name1, name2, &srate) != EE_OK) | |||||
fprintf(stderr, "mbrola voice not found\n"); | fprintf(stderr, "mbrola voice not found\n"); | ||||
} | |||||
voice->samplerate = srate; | voice->samplerate = srate; | ||||
} | } | ||||
break; | break; | ||||
case V_KLATT: | case V_KLATT: | ||||
voice->klattv[0] = 1; // default source: IMPULSIVE | |||||
voice->klattv[0] = 1; // default source: IMPULSIVE | |||||
Read8Numbers(p, voice->klattv); | Read8Numbers(p, voice->klattv); | ||||
voice->klattv[KLATT_Kopen] -= 40; | voice->klattv[KLATT_Kopen] -= 40; | ||||
break; | break; | ||||
new_translator = SelectTranslator(translator_name); | new_translator = SelectTranslator(translator_name); | ||||
} | } | ||||
SetSpeed(3); // for speed_percent | |||||
SetSpeed(3); // for speed_percent | |||||
for (ix = 0; ix < N_PEAKS; ix++) { | for (ix = 0; ix < N_PEAKS; ix++) { | ||||
voice->freq2[ix] = voice->freq[ix]; | voice->freq2[ix] = voice->freq[ix]; | ||||
LoadDictionary(new_translator, new_dictionary, control & 4); | LoadDictionary(new_translator, new_dictionary, control & 4); | ||||
if (dictionary_name[0] == 0) { | if (dictionary_name[0] == 0) { | ||||
DeleteTranslator(new_translator); | DeleteTranslator(new_translator); | ||||
return NULL; // no dictionary loaded | |||||
return NULL; // no dictionary loaded | |||||
} | } | ||||
new_translator->dict_condition = conditional_rules; | new_translator->dict_condition = conditional_rules; | ||||
langopts = &new_translator->langopts; | langopts = &new_translator->langopts; | ||||
if ((value = langopts->param[LOPT_LENGTH_MODS]) != 0) | if ((value = langopts->param[LOPT_LENGTH_MODS]) != 0) | ||||
SetLengthMods(new_translator, value); | SetLengthMods(new_translator, value); | ||||
if (!tone_only) | if (!tone_only) | ||||
translator = new_translator; | translator = new_translator; | ||||
// relative lengths of different stress syllables | // relative lengths of different stress syllables | ||||
for (ix = 0; ix < stress_lengths_set; ix++) | for (ix = 0; ix < stress_lengths_set; ix++) | ||||
translator->stress_lengths[ix] = stress_lengths[ix]; | translator->stress_lengths[ix] = stress_lengths[ix]; | ||||
static char *ExtractVoiceVariantName(char *vname, int variant_num, int add_dir) | static char *ExtractVoiceVariantName(char *vname, int variant_num, int add_dir) | ||||
{ | { | ||||
// Remove any voice variant suffix (name or number) from a voice name | |||||
// Returns the voice variant name | |||||
// Remove any voice variant suffix (name or number) from a voice name | |||||
// Returns the voice variant name | |||||
char *p; | char *p; | ||||
static char variant_name[40]; | static char variant_name[40]; | ||||
if ((p = strchr(vname, '+')) != NULL) { | if ((p = strchr(vname, '+')) != NULL) { | ||||
// The voice name has a +variant suffix | // The voice name has a +variant suffix | ||||
variant_num = 0; | variant_num = 0; | ||||
*p++ = 0; // delete the suffix from the voice name | |||||
*p++ = 0; // delete the suffix from the voice name | |||||
if (IsDigit09(*p)) | if (IsDigit09(*p)) | ||||
variant_num = atoi(p); // variant number | |||||
variant_num = atoi(p); // variant number | |||||
else { | else { | ||||
// voice variant name, not number | // voice variant name, not number | ||||
sprintf(variant_name, "%s%s", variant_prefix, p); | sprintf(variant_name, "%s%s", variant_prefix, p); | ||||
if (variant_num > 0) { | if (variant_num > 0) { | ||||
if (variant_num < 10) | if (variant_num < 10) | ||||
sprintf(variant_name, "%sm%d", variant_prefix, variant_num); // male | |||||
sprintf(variant_name, "%sm%d", variant_prefix, variant_num); // male | |||||
else | else | ||||
sprintf(variant_name, "%sf%d", variant_prefix, variant_num-10); // female | |||||
sprintf(variant_name, "%sf%d", variant_prefix, variant_num-10); // female | |||||
} | } | ||||
return variant_name; | return variant_name; | ||||
voice_t *LoadVoiceVariant(const char *vname, int variant_num) | voice_t *LoadVoiceVariant(const char *vname, int variant_num) | ||||
{ | { | ||||
// Load a voice file. | |||||
// Also apply a voice variant if specified by "variant", or by "+number" or "+name" in the "vname" | |||||
// Load a voice file. | |||||
// Also apply a voice variant if specified by "variant", or by "+number" or "+name" in the "vname" | |||||
voice_t *v; | voice_t *v; | ||||
char *variant_name; | char *variant_name; | ||||
espeak_VOICE *v1 = *(espeak_VOICE **)p1; | espeak_VOICE *v1 = *(espeak_VOICE **)p1; | ||||
espeak_VOICE *v2 = *(espeak_VOICE **)p2; | espeak_VOICE *v2 = *(espeak_VOICE **)p2; | ||||
if ((ix = strcmp(&v1->languages[1], &v2->languages[1])) != 0) // primary language name | |||||
if ((ix = strcmp(&v1->languages[1], &v2->languages[1])) != 0) // primary language name | |||||
return ix; | return ix; | ||||
if ((ix = v1->languages[0] - v2->languages[0]) != 0) // priority number | |||||
if ((ix = v1->languages[0] - v2->languages[0]) != 0) // priority number | |||||
return ix; | return ix; | ||||
return strcmp(v1->name, v2->name); | return strcmp(v1->name, v2->name); | ||||
} | } | ||||
int required_age; | int required_age; | ||||
int diff; | int diff; | ||||
p = voice->languages; // list of languages+dialects for which this voice is suitable | |||||
p = voice->languages; // list of languages+dialects for which this voice is suitable | |||||
if (spec_n_parts < 0) { | if (spec_n_parts < 0) { | ||||
// match on the subdirectory | // match on the subdirectory | ||||
break; | break; | ||||
} | } | ||||
p += (ix+1); | p += (ix+1); | ||||
matching_parts += matching; // number of parts which match | |||||
matching_parts += matching; // number of parts which match | |||||
if (matching_parts == 0) | if (matching_parts == 0) | ||||
continue; // no matching parts for this language | |||||
continue; // no matching parts for this language | |||||
x = 5; | x = 5; | ||||
// reduce the score if not all parts of the required language match | // reduce the score if not all parts of the required language match | ||||
} | } | ||||
if ((voice_spec->age <= 12) && (voice->gender == 2) && (voice->age > 12)) | if ((voice_spec->age <= 12) && (voice->gender == 2) && (voice->age > 12)) | ||||
score += 5; // give some preference for non-child female voice if a child is requested | |||||
score += 5; // give some preference for non-child female voice if a child is requested | |||||
if (voice->age != 0) { | if (voice->age != 0) { | ||||
if (voice_spec->age == 0) | if (voice_spec->age == 0) | ||||
ratio = (required_age*100)/voice->age; | ratio = (required_age*100)/voice->age; | ||||
if (ratio < 100) | if (ratio < 100) | ||||
ratio = 10000/ratio; | ratio = 10000/ratio; | ||||
ratio = (ratio - 100)/10; // 0=exact match, 10=out by factor of 2 | |||||
ratio = (ratio - 100)/10; // 0=exact match, 10=out by factor of 2 | |||||
x = 5 - ratio; | x = 5 - ratio; | ||||
if (x > 0) x = 0; | if (x > 0) x = 0; | ||||
score = score + x; | score = score + x; | ||||
if (voice_spec->age > 0) | if (voice_spec->age > 0) | ||||
score += 10; // required age specified, favour voices with a specified age (near it) | |||||
score += 10; // required age specified, favour voices with a specified age (near it) | |||||
} | } | ||||
if (score < 1) | if (score < 1) | ||||
score = 1; | score = 1; | ||||
static int SetVoiceScores(espeak_VOICE *voice_select, espeak_VOICE **voices, int control) | static int SetVoiceScores(espeak_VOICE *voice_select, espeak_VOICE **voices, int control) | ||||
{ | { | ||||
// control: bit0=1 include mbrola voices | |||||
// control: bit0=1 include mbrola voices | |||||
int ix; | int ix; | ||||
int score; | int score; | ||||
int nv; // number of candidates | |||||
int nv; // number of candidates | |||||
int n_parts = 0; | int n_parts = 0; | ||||
int lang_len = 0; | int lang_len = 0; | ||||
espeak_VOICE *vp; | espeak_VOICE *vp; | ||||
if ((n_parts == 1) && (control & 1)) { | if ((n_parts == 1) && (control & 1)) { | ||||
if (strcmp(language, "mbrola") == 0) { | if (strcmp(language, "mbrola") == 0) { | ||||
language[2] = 0; // truncate to "mb" | |||||
language[2] = 0; // truncate to "mb" | |||||
lang_len = 2; | lang_len = 2; | ||||
} | } | ||||
vp->score = score; | vp->score = score; | ||||
} | } | ||||
} | } | ||||
voices[nv] = NULL; // list terminator | |||||
voices[nv] = NULL; // list terminator | |||||
if (nv == 0) | if (nv == 0) | ||||
return 0; | return 0; | ||||
int match_fname = -1; | int match_fname = -1; | ||||
int match_fname2 = -1; | int match_fname2 = -1; | ||||
int match_name = -1; | int match_name = -1; | ||||
const char *id; // this is the filename within espeak-data/voices | |||||
const char *id; // this is the filename within espeak-data/voices | |||||
char *variant_name; | char *variant_name; | ||||
int last_part_len; | int last_part_len; | ||||
char last_part[41]; | char last_part[41]; | ||||
if (voices == NULL) { | if (voices == NULL) { | ||||
if (n_voices_list == 0) | if (n_voices_list == 0) | ||||
espeak_ListVoices(NULL); // create the voices list | |||||
espeak_ListVoices(NULL); // create the voices list | |||||
voices = voices_list; | voices = voices_list; | ||||
} | } | ||||
for (ix = 0; voices[ix] != NULL; ix++) { | for (ix = 0; voices[ix] != NULL; ix++) { | ||||
if (strcmp(name, voices[ix]->name) == 0) { | if (strcmp(name, voices[ix]->name) == 0) { | ||||
match_name = ix; // found matching voice name | |||||
match_name = ix; // found matching voice name | |||||
break; | break; | ||||
} else { | } else { | ||||
id = voices[ix]->identifier; | id = voices[ix]->identifier; | ||||
if (strcmp(name, id) == 0) | if (strcmp(name, id) == 0) | ||||
match_fname = ix; // matching identifier, use this if no matching name | |||||
match_fname = ix; // matching identifier, use this if no matching name | |||||
else if (strcmp(last_part, &id[strlen(id)-last_part_len]) == 0) | else if (strcmp(last_part, &id[strlen(id)-last_part_len]) == 0) | ||||
match_fname2 = ix; | match_fname2 = ix; | ||||
} | } | ||||
} | } | ||||
if (match_name < 0) { | if (match_name < 0) { | ||||
match_name = match_fname; // no matching name, try matching filename | |||||
match_name = match_fname; // no matching name, try matching filename | |||||
if (match_name < 0) | if (match_name < 0) | ||||
match_name = match_fname2; // try matching just the last part of the filename | |||||
match_name = match_fname2; // try matching just the last part of the filename | |||||
} | } | ||||
if (match_name < 0) | if (match_name < 0) | ||||
char const *SelectVoice(espeak_VOICE *voice_select, int *found) | char const *SelectVoice(espeak_VOICE *voice_select, int *found) | ||||
{ | { | ||||
// Returns a path within espeak-voices, with a possible +variant suffix | |||||
// variant is an output-only parameter | |||||
int nv; // number of candidates | |||||
// Returns a path within espeak-voices, with a possible +variant suffix | |||||
// variant is an output-only parameter | |||||
int nv; // number of candidates | |||||
int ix, ix2; | int ix, ix2; | ||||
int j; | int j; | ||||
int n_variants; | int n_variants; | ||||
memcpy(&voice_select2, voice_select, sizeof(voice_select2)); | memcpy(&voice_select2, voice_select, sizeof(voice_select2)); | ||||
if (n_voices_list == 0) | if (n_voices_list == 0) | ||||
espeak_ListVoices(NULL); // create the voices list | |||||
espeak_ListVoices(NULL); // create the voices list | |||||
if ((voice_select2.languages == NULL) || (voice_select2.languages[0] == 0)) { | if ((voice_select2.languages == NULL) || (voice_select2.languages[0] == 0)) { | ||||
// no language is specified. Get language from the named voice | // no language is specified. Get language from the named voice | ||||
else if (voice_select2.gender == 1) | else if (voice_select2.gender == 1) | ||||
gender = 1; | gender = 1; | ||||
#define AGE_OLD 60 | |||||
#define AGE_OLD 60 | |||||
if (voice_select2.age < AGE_OLD) | if (voice_select2.age < AGE_OLD) | ||||
aged = 0; | aged = 0; | ||||
p = p_start = variant_lists[gender]; | p = p_start = variant_lists[gender]; | ||||
if (aged == 0) | if (aged == 0) | ||||
p++; // the first voice in the variants list is older | |||||
p++; // the first voice in the variants list is older | |||||
// add variants for the top voices | // add variants for the top voices | ||||
n_variants = 0; | n_variants = 0; | ||||
continue; | continue; | ||||
} | } | ||||
vp2 = &voice_variants[n_variants++]; // allocate space for voice variant | |||||
memcpy(vp2, vp, sizeof(espeak_VOICE)); // copy from the original voice | |||||
vp2 = &voice_variants[n_variants++]; // allocate space for voice variant | |||||
memcpy(vp2, vp, sizeof(espeak_VOICE)); // copy from the original voice | |||||
vp2->variant = variant_number; | vp2->variant = variant_number; | ||||
voices2[ix2++] = vp2; | voices2[ix2++] = vp2; | ||||
p++; | p++; | ||||
} | } | ||||
// add any more variants to the end of the list | // add any more variants to the end of the list | ||||
while ((vp != NULL) && ((variant_number = *p++) != 0) && (n_variants < N_VOICE_VARIANTS)) { | while ((vp != NULL) && ((variant_number = *p++) != 0) && (n_variants < N_VOICE_VARIANTS)) { | ||||
vp2 = &voice_variants[n_variants++]; // allocate space for voice variant | |||||
memcpy(vp2, vp, sizeof(espeak_VOICE)); // copy from the original voice | |||||
vp2 = &voice_variants[n_variants++]; // allocate space for voice variant | |||||
memcpy(vp2, vp, sizeof(espeak_VOICE)); // copy from the original voice | |||||
vp2->variant = variant_number; | vp2->variant = variant_number; | ||||
voices2[ix2++] = vp2; | voices2[ix2++] = vp2; | ||||
} | } | ||||
regs.r[6] = 0; | regs.r[6] = 0; | ||||
while (regs.r[3] > 0) { | while (regs.r[3] > 0) { | ||||
error = _kernel_swi(0x0c+0x20000, ®s, ®s); /* OS_GBPB 10, read directory entries */ | |||||
error = _kernel_swi(0x0c+0x20000, ®s, ®s); // OS_GBPB 10, read directory entries | |||||
if ((error != NULL) || (regs.r[3] == 0)) | if ((error != NULL) || (regs.r[3] == 0)) | ||||
break; | break; | ||||
type = (int *)(&buf[16]); | type = (int *)(&buf[16]); | ||||
WIN32_FIND_DATAA FindFileData; | WIN32_FIND_DATAA FindFileData; | ||||
HANDLE hFind = INVALID_HANDLE_VALUE; | HANDLE hFind = INVALID_HANDLE_VALUE; | ||||
#undef UNICODE // we need FindFirstFileA() which takes an 8-bit c-string | |||||
#undef UNICODE // we need FindFirstFileA() which takes an 8-bit c-string | |||||
sprintf(fname, "%s\\*", path); | sprintf(fname, "%s\\*", path); | ||||
hFind = FindFirstFileA(fname, &FindFileData); | hFind = FindFirstFileA(fname, &FindFileData); | ||||
if (hFind == INVALID_HANDLE_VALUE) | if (hFind == INVALID_HANDLE_VALUE) | ||||
do { | do { | ||||
if (n_voices_list >= (N_VOICES_LIST-2)) | if (n_voices_list >= (N_VOICES_LIST-2)) | ||||
break; // voices list is full | |||||
break; // voices list is full | |||||
if (FindFileData.cFileName[0] != '.') { | if (FindFileData.cFileName[0] != '.') { | ||||
sprintf(fname, "%s%c%s", path, PATHSEP, FindFileData.cFileName); | sprintf(fname, "%s%c%s", path, PATHSEP, FindFileData.cFileName); | ||||
DIR *dir; | DIR *dir; | ||||
struct dirent *ent; | struct dirent *ent; | ||||
if ((dir = opendir((char *)path)) == NULL) // note: (char *) is needed for WINCE | |||||
if ((dir = opendir((char *)path)) == NULL) // note: (char *) is needed for WINCE | |||||
return; | return; | ||||
while ((ent = readdir(dir)) != NULL) { | while ((ent = readdir(dir)) != NULL) { | ||||
if (n_voices_list >= (N_VOICES_LIST-2)) | if (n_voices_list >= (N_VOICES_LIST-2)) | ||||
break; // voices list is full | |||||
break; // voices list is full | |||||
if (ent->d_name[0] == '.') | if (ent->d_name[0] == '.') | ||||
continue; | continue; | ||||
} | } | ||||
memset(&voice_selector, 0, sizeof(voice_selector)); | memset(&voice_selector, 0, sizeof(voice_selector)); | ||||
voice_selector.name = (char *)name; // include variant name in voice stack ?? | |||||
voice_selector.name = (char *)name; // include variant name in voice stack ?? | |||||
// first check for a voice with this filename | // first check for a voice with this filename | ||||
// This may avoid the need to call espeak_ListVoices(). | // This may avoid the need to call espeak_ListVoices(). | ||||
} | } | ||||
if (n_voices_list == 0) | if (n_voices_list == 0) | ||||
espeak_ListVoices(NULL); // create the voices list | |||||
espeak_ListVoices(NULL); // create the voices list | |||||
if ((v = SelectVoiceByName(voices_list, buf)) != NULL) { | if ((v = SelectVoiceByName(voices_list, buf)) != NULL) { | ||||
if (LoadVoice(v->identifier, 0) != NULL) { | if (LoadVoice(v->identifier, 0) != NULL) { | ||||
return EE_OK; | return EE_OK; | ||||
} | } | ||||
} | } | ||||
return EE_INTERNAL_ERROR; // voice name not found | |||||
return EE_INTERNAL_ERROR; // voice name not found | |||||
} | } | ||||
espeak_ERROR SetVoiceByProperties(espeak_VOICE *voice_selector) | espeak_ERROR SetVoiceByProperties(espeak_VOICE *voice_selector) | ||||
sprintf(path_voices, "%s%cvoices", path_home, PATHSEP); | sprintf(path_voices, "%s%cvoices", path_home, PATHSEP); | ||||
len_path_voices = strlen(path_voices)+1; | len_path_voices = strlen(path_voices)+1; | ||||
GetVoices(path_voices); | GetVoices(path_voices); | ||||
voices_list[n_voices_list] = NULL; // voices list terminator | |||||
voices_list[n_voices_list] = NULL; // voices list terminator | |||||
} | } | ||||
return (const espeak_VOICE **)voices_list; | return (const espeak_VOICE **)voices_list; | ||||
#else | #else | ||||
len_path_voices = strlen(path_voices)+1; | len_path_voices = strlen(path_voices)+1; | ||||
GetVoices(path_voices); | GetVoices(path_voices); | ||||
voices_list[n_voices_list] = NULL; // voices list terminator | |||||
voices_list[n_voices_list] = NULL; // voices list terminator | |||||
voices = (espeak_VOICE **)realloc(voices, sizeof(espeak_VOICE *)*(n_voices_list+1)); | voices = (espeak_VOICE **)realloc(voices, sizeof(espeak_VOICE *)*(n_voices_list+1)); | ||||
// sort the voices list | // sort the voices list |
long tv_sec; | long tv_sec; | ||||
long tv_nsec; | long tv_nsec; | ||||
}; | }; | ||||
#endif /* HAVE_STRUCT_TIMESPEC */ | |||||
#endif | |||||
enum { ONE_BILLION = 1000000000 }; | enum { ONE_BILLION = 1000000000 }; | ||||
int wave_pulse_get_remaining_time(uint32_t sample, uint32_t *time); | int wave_pulse_get_remaining_time(uint32_t sample, uint32_t *time); | ||||
// wrappers | // wrappers | ||||
int wave_init(int srate) { | |||||
int wave_init(int srate) | |||||
{ | |||||
pulse_running = is_pulse_running(); | pulse_running = is_pulse_running(); | ||||
if (pulse_running) | if (pulse_running) | ||||
return wave_port_init(srate); | return wave_port_init(srate); | ||||
} | } | ||||
void *wave_open(const char *the_api) { | |||||
void *wave_open(const char *the_api) | |||||
{ | |||||
if (pulse_running) | if (pulse_running) | ||||
return wave_pulse_open(the_api); | return wave_pulse_open(the_api); | ||||
else | else | ||||
return wave_port_open(the_api); | return wave_port_open(the_api); | ||||
} | } | ||||
size_t wave_write(void *theHandler, char *theMono16BitsWaveBuffer, size_t theSize) { | |||||
size_t wave_write(void *theHandler, char *theMono16BitsWaveBuffer, size_t theSize) | |||||
{ | |||||
if (pulse_running) | if (pulse_running) | ||||
return wave_pulse_write(theHandler, theMono16BitsWaveBuffer, theSize); | return wave_pulse_write(theHandler, theMono16BitsWaveBuffer, theSize); | ||||
else | else | ||||
return wave_port_write(theHandler, theMono16BitsWaveBuffer, theSize); | return wave_port_write(theHandler, theMono16BitsWaveBuffer, theSize); | ||||
} | } | ||||
int wave_close(void *theHandler) { | |||||
int wave_close(void *theHandler) | |||||
{ | |||||
if (pulse_running) | if (pulse_running) | ||||
return wave_pulse_close(theHandler); | return wave_pulse_close(theHandler); | ||||
else | else | ||||
return wave_port_close(theHandler); | return wave_port_close(theHandler); | ||||
} | } | ||||
int wave_is_busy(void *theHandler) { | |||||
int wave_is_busy(void *theHandler) | |||||
{ | |||||
if (pulse_running) | if (pulse_running) | ||||
return wave_pulse_is_busy(theHandler); | return wave_pulse_is_busy(theHandler); | ||||
else | else | ||||
return wave_port_is_busy(theHandler); | return wave_port_is_busy(theHandler); | ||||
} | } | ||||
void wave_terminate() { | |||||
void wave_terminate() | |||||
{ | |||||
if (pulse_running) | if (pulse_running) | ||||
wave_pulse_terminate(); | wave_pulse_terminate(); | ||||
else | else | ||||
wave_port_terminate(); | wave_port_terminate(); | ||||
} | } | ||||
uint32_t wave_get_read_position(void *theHandler) { | |||||
uint32_t wave_get_read_position(void *theHandler) | |||||
{ | |||||
if (pulse_running) | if (pulse_running) | ||||
return wave_pulse_get_read_position(theHandler); | return wave_pulse_get_read_position(theHandler); | ||||
else | else | ||||
return wave_port_get_read_position(theHandler); | return wave_port_get_read_position(theHandler); | ||||
} | } | ||||
uint32_t wave_get_write_position(void *theHandler) { | |||||
uint32_t wave_get_write_position(void *theHandler) | |||||
{ | |||||
if (pulse_running) | if (pulse_running) | ||||
return wave_pulse_get_write_position(theHandler); | return wave_pulse_get_write_position(theHandler); | ||||
else | else | ||||
return wave_port_get_write_position(theHandler); | return wave_port_get_write_position(theHandler); | ||||
} | } | ||||
void wave_flush(void *theHandler) { | |||||
void wave_flush(void *theHandler) | |||||
{ | |||||
if (pulse_running) | if (pulse_running) | ||||
wave_pulse_flush(theHandler); | wave_pulse_flush(theHandler); | ||||
else | else | ||||
wave_port_flush(theHandler); | wave_port_flush(theHandler); | ||||
} | } | ||||
void wave_set_callback_is_output_enabled(t_wave_callback *cb) { | |||||
void wave_set_callback_is_output_enabled(t_wave_callback *cb) | |||||
{ | |||||
if (pulse_running) | if (pulse_running) | ||||
wave_pulse_set_callback_is_output_enabled(cb); | wave_pulse_set_callback_is_output_enabled(cb); | ||||
else | else | ||||
wave_port_set_callback_is_output_enabled(cb); | wave_port_set_callback_is_output_enabled(cb); | ||||
} | } | ||||
void *wave_test_get_write_buffer() { | |||||
void *wave_test_get_write_buffer() | |||||
{ | |||||
if (pulse_running) | if (pulse_running) | ||||
return wave_pulse_test_get_write_buffer(); | return wave_pulse_test_get_write_buffer(); | ||||
else | else | ||||
#define wave_test_get_write_buffer wave_port_test_get_write_buffer | #define wave_test_get_write_buffer wave_port_test_get_write_buffer | ||||
#define wave_get_remaining_time wave_port_get_remaining_time | #define wave_get_remaining_time wave_port_get_remaining_time | ||||
#endif // USE_PULSEAUDIO | |||||
#endif | |||||
static t_wave_callback *my_callback_is_output_enabled = NULL; | static t_wave_callback *my_callback_is_output_enabled = NULL; | ||||
mInCallbackFinishedState = true; | mInCallbackFinishedState = true; | ||||
size_t aUsedMem = 0; | size_t aUsedMem = 0; | ||||
aUsedMem = (size_t)(aWrite - myRead); | aUsedMem = (size_t)(aWrite - myRead); | ||||
if (aUsedMem) { | |||||
if (aUsedMem) | |||||
memcpy(outputBuffer, myRead, aUsedMem); | memcpy(outputBuffer, myRead, aUsedMem); | ||||
} | |||||
char *p = (char *)outputBuffer + aUsedMem; | char *p = (char *)outputBuffer + aUsedMem; | ||||
memset(p, 0, n - aUsedMem); | memset(p, 0, n - aUsedMem); | ||||
myRead = aWrite; | myRead = aWrite; | ||||
PaDeviceID playbackDevice = Pa_GetDefaultOutputDeviceID(); | PaDeviceID playbackDevice = Pa_GetDefaultOutputDeviceID(); | ||||
PaError err = Pa_OpenStream(&pa_stream, | PaError err = Pa_OpenStream(&pa_stream, | ||||
/* capture parameters */ | |||||
// capture parameters | |||||
paNoDevice, | paNoDevice, | ||||
0, | 0, | ||||
paInt16, | paInt16, | ||||
NULL, | NULL, | ||||
/* playback parameters */ | |||||
// playback parameters | |||||
playbackDevice, | playbackDevice, | ||||
out_channels, | out_channels, | ||||
paInt16, | paInt16, | ||||
NULL, | NULL, | ||||
/* general parameters */ | |||||
// general parameters | |||||
wave_samplerate, FRAMES_PER_BUFFER, 0, | wave_samplerate, FRAMES_PER_BUFFER, 0, | ||||
paNoFlag, | paNoFlag, | ||||
pa_callback, (void *)userdata); | pa_callback, (void *)userdata); | ||||
// failed to open with mono, try stereo | // failed to open with mono, try stereo | ||||
out_channels = 2; | out_channels = 2; | ||||
PaError err = Pa_OpenStream(&pa_stream, | PaError err = Pa_OpenStream(&pa_stream, | ||||
/* capture parameters */ | |||||
// capture parameters | |||||
paNoDevice, | paNoDevice, | ||||
0, | 0, | ||||
paInt16, | paInt16, | ||||
NULL, | NULL, | ||||
/* playback parameters */ | |||||
// playback parameters | |||||
playbackDevice, | playbackDevice, | ||||
out_channels, | out_channels, | ||||
paInt16, | paInt16, | ||||
NULL, | NULL, | ||||
/* general parameters */ | |||||
// general parameters | |||||
wave_samplerate, FRAMES_PER_BUFFER, 0, | wave_samplerate, FRAMES_PER_BUFFER, 0, | ||||
paNoFlag, | paNoFlag, | ||||
pa_callback, (void *)userdata); | pa_callback, (void *)userdata); | ||||
#else | #else | ||||
myOutputParameters.channelCount = out_channels; | myOutputParameters.channelCount = out_channels; | ||||
unsigned long framesPerBuffer = paFramesPerBufferUnspecified; | unsigned long framesPerBuffer = paFramesPerBufferUnspecified; | ||||
err = Pa_OpenStream( | |||||
&pa_stream, | |||||
NULL, /* no input */ | |||||
&myOutputParameters, | |||||
wave_samplerate, | |||||
framesPerBuffer, | |||||
paNoFlag, | |||||
pa_callback, | |||||
(void *)userdata); | |||||
err = Pa_OpenStream(&pa_stream, | |||||
NULL, // no input | |||||
&myOutputParameters, | |||||
wave_samplerate, | |||||
framesPerBuffer, | |||||
paNoFlag, | |||||
pa_callback, | |||||
(void *)userdata); | |||||
if ((err != paNoError) | if ((err != paNoError) | ||||
&& (err != paInvalidChannelCount)) { // err==paUnanticipatedHostError | |||||
&& (err != paInvalidChannelCount)) { | |||||
fprintf(stderr, "wave_open_sound > Pa_OpenStream : err=%d (%s)\n", err, Pa_GetErrorText(err)); | fprintf(stderr, "wave_open_sound > Pa_OpenStream : err=%d (%s)\n", err, Pa_GetErrorText(err)); | ||||
framesPerBuffer = FRAMES_PER_BUFFER; | framesPerBuffer = FRAMES_PER_BUFFER; | ||||
err = Pa_OpenStream( | |||||
&pa_stream, | |||||
NULL, /* no input */ | |||||
&myOutputParameters, | |||||
wave_samplerate, | |||||
framesPerBuffer, | |||||
paNoFlag, | |||||
// paClipOff | paDitherOff, | |||||
pa_callback, | |||||
(void *)userdata); | |||||
err = Pa_OpenStream(&pa_stream, | |||||
NULL, // no input | |||||
&myOutputParameters, | |||||
wave_samplerate, | |||||
framesPerBuffer, | |||||
paNoFlag, | |||||
pa_callback, | |||||
(void *)userdata); | |||||
} | } | ||||
if (err == paInvalidChannelCount) { | if (err == paInvalidChannelCount) { | ||||
SHOW_TIME("wave_open_sound > try stereo"); | SHOW_TIME("wave_open_sound > try stereo"); | ||||
// failed to open with mono, try stereo | // failed to open with mono, try stereo | ||||
out_channels = 2; | out_channels = 2; | ||||
myOutputParameters.channelCount = out_channels; | myOutputParameters.channelCount = out_channels; | ||||
err = Pa_OpenStream( | |||||
&pa_stream, | |||||
NULL, /* no input */ | |||||
&myOutputParameters, | |||||
wave_samplerate, | |||||
framesPerBuffer, | |||||
paNoFlag, | |||||
// paClipOff | paDitherOff, | |||||
pa_callback, | |||||
(void *)userdata); | |||||
err = Pa_OpenStream(&pa_stream, | |||||
NULL, // no input | |||||
&myOutputParameters, | |||||
wave_samplerate, | |||||
framesPerBuffer, | |||||
paNoFlag, | |||||
pa_callback, | |||||
(void *)userdata); | |||||
} | } | ||||
mInCallbackFinishedState = false; | mInCallbackFinishedState = false; | ||||
#endif | #endif | ||||
myWrite = myBuffer; | myWrite = myBuffer; | ||||
myWrite += copyBuffer(myWrite, theMono16BitsWaveBuffer+aFreeMem/2, theSize - aFreeMem/2); | myWrite += copyBuffer(myWrite, theMono16BitsWaveBuffer+aFreeMem/2, theSize - aFreeMem/2); | ||||
} else { // 1 channel (mono) | } else { // 1 channel (mono) | ||||
// copy with wrap around at the end of ringbuffer | |||||
// copy with wrap around at the end of ringbuffer | |||||
copyBuffer(myWrite, theMono16BitsWaveBuffer, aFreeMem); | copyBuffer(myWrite, theMono16BitsWaveBuffer, aFreeMem); | ||||
myWrite = myBuffer; | myWrite = myBuffer; | ||||
myWrite += copyBuffer(myWrite, theMono16BitsWaveBuffer+aFreeMem, theSize - aFreeMem); | myWrite += copyBuffer(myWrite, theMono16BitsWaveBuffer+aFreeMem, theSize - aFreeMem); | ||||
// DMM: This doesn't seem to be true; it seems to be necessary to | // DMM: This doesn't seem to be true; it seems to be necessary to | ||||
// call StopStream if the callback brought us here, and AbortStream | // call StopStream if the callback brought us here, and AbortStream | ||||
// if the user brought us here. | // if the user brought us here. | ||||
// | |||||
#if (USE_PORTAUDIO == 19) | #if (USE_PORTAUDIO == 19) | ||||
if (pa_stream) { | if (pa_stream) { | ||||
#else | #else | ||||
int wave_init(int srate) { | |||||
int wave_init(int srate) | |||||
{ | |||||
return 1; | return 1; | ||||
} | } | ||||
void *wave_open(const char *the_api) { | |||||
void *wave_open(const char *the_api) | |||||
{ | |||||
return (void *)1; | return (void *)1; | ||||
} | } | ||||
size_t wave_write(void *theHandler, char *theMono16BitsWaveBuffer, size_t theSize) { | |||||
size_t wave_write(void *theHandler, char *theMono16BitsWaveBuffer, size_t theSize) | |||||
{ | |||||
return theSize; | return theSize; | ||||
} | } | ||||
int wave_close(void *theHandler) { | |||||
int wave_close(void *theHandler) | |||||
{ | |||||
return 0; | return 0; | ||||
} | } | ||||
int wave_is_busy(void *theHandler) { | |||||
int wave_is_busy(void *theHandler) | |||||
{ | |||||
return 0; | return 0; | ||||
} | } | ||||
void wave_terminate() { | |||||
void wave_terminate() | |||||
{ | |||||
} | } | ||||
uint32_t wave_get_read_position(void *theHandler) { | |||||
uint32_t wave_get_read_position(void *theHandler) | |||||
{ | |||||
return 0; | return 0; | ||||
} | } | ||||
uint32_t wave_get_write_position(void *theHandler) { | |||||
uint32_t wave_get_write_position(void *theHandler) | |||||
{ | |||||
return 0; | return 0; | ||||
} | } | ||||
void wave_flush(void *theHandler) { | |||||
void wave_flush(void *theHandler) | |||||
{ | |||||
} | } | ||||
typedef int (t_wave_callback)(void); | typedef int (t_wave_callback)(void); | ||||
void wave_set_callback_is_output_enabled(t_wave_callback *cb) { | |||||
void wave_set_callback_is_output_enabled(t_wave_callback *cb) | |||||
{ | |||||
} | } | ||||
extern void *wave_test_get_write_buffer() { | |||||
extern void *wave_test_get_write_buffer() | |||||
{ | |||||
return NULL; | return NULL; | ||||
} | } | ||||
return 0; | return 0; | ||||
} | } | ||||
#endif // of USE_PORTAUDIO | |||||
#endif | |||||
void clock_gettime2(struct timespec *ts) | void clock_gettime2(struct timespec *ts) | ||||
{ | { | ||||
ts->tv_nsec = (long int)t_ns; | ts->tv_nsec = (long int)t_ns; | ||||
} | } | ||||
#endif // USE_ASYNC | |||||
#endif |
typedef int (t_wave_callback)(void); | typedef int (t_wave_callback)(void); | ||||
extern void wave_set_callback_is_output_enabled(t_wave_callback *cb); | extern void wave_set_callback_is_output_enabled(t_wave_callback *cb); | ||||
// general functions | // general functions | ||||
extern void clock_gettime2(struct timespec *ts); | extern void clock_gettime2(struct timespec *ts); | ||||
extern void add_time_in_ms(struct timespec *ts, int time_in_ms); | extern void add_time_in_ms(struct timespec *ts, int time_in_ms); |
} else | } else | ||||
return 0; | return 0; | ||||
} | } | ||||
#endif // USE_PORTAUDIO | |||||
#endif | |||||
static pthread_mutex_t pulse_mutex; | static pthread_mutex_t pulse_mutex; | ||||
if (!connected) { SHOW("CHECK_CONNECTED_NO_RETVAL: !pulse_connected\n", ""); return; } \ | if (!connected) { SHOW("CHECK_CONNECTED_NO_RETVAL: !pulse_connected\n", ""); return; } \ | ||||
} while (0); | } while (0); | ||||
static void subscribe_cb(struct pa_context *c, enum pa_subscription_event_type t, uint32_t index, void *userdata) { | |||||
static void subscribe_cb(struct pa_context *c, enum pa_subscription_event_type t, uint32_t index, void *userdata) | |||||
{ | |||||
ENTER(__FUNCTION__); | ENTER(__FUNCTION__); | ||||
assert(c); | assert(c); | ||||
return; | return; | ||||
} | } | ||||
static void context_state_cb(pa_context *c, void *userdata) { | |||||
static void context_state_cb(pa_context *c, void *userdata) | |||||
{ | |||||
ENTER(__FUNCTION__); | ENTER(__FUNCTION__); | ||||
assert(c); | assert(c); | ||||
} | } | ||||
} | } | ||||
static void stream_state_cb(pa_stream *s, void *userdata) { | |||||
static void stream_state_cb(pa_stream *s, void *userdata) | |||||
{ | |||||
ENTER(__FUNCTION__); | ENTER(__FUNCTION__); | ||||
assert(s); | assert(s); | ||||
} | } | ||||
} | } | ||||
static void stream_success_cb(pa_stream *s, int success, void *userdata) { | |||||
static void stream_success_cb(pa_stream *s, int success, void *userdata) | |||||
{ | |||||
ENTER(__FUNCTION__); | ENTER(__FUNCTION__); | ||||
assert(s); | assert(s); | ||||
pa_threaded_mainloop_signal(mainloop, 0); | pa_threaded_mainloop_signal(mainloop, 0); | ||||
} | } | ||||
static void context_success_cb(pa_context *c, int success, void *userdata) { | |||||
static void context_success_cb(pa_context *c, int success, void *userdata) | |||||
{ | |||||
ENTER(__FUNCTION__); | ENTER(__FUNCTION__); | ||||
assert(c); | assert(c); | ||||
pa_threaded_mainloop_signal(mainloop, 0); | pa_threaded_mainloop_signal(mainloop, 0); | ||||
} | } | ||||
static void stream_request_cb(pa_stream *s, size_t length, void *userdata) { | |||||
static void stream_request_cb(pa_stream *s, size_t length, void *userdata) | |||||
{ | |||||
ENTER(__FUNCTION__); | ENTER(__FUNCTION__); | ||||
assert(s); | assert(s); | ||||
pa_threaded_mainloop_signal(mainloop, 0); | pa_threaded_mainloop_signal(mainloop, 0); | ||||
} | } | ||||
static void stream_latency_update_cb(pa_stream *s, void *userdata) { | |||||
static void stream_latency_update_cb(pa_stream *s, void *userdata) | |||||
{ | |||||
assert(s); | assert(s); | ||||
pa_threaded_mainloop_signal(mainloop, 0); | pa_threaded_mainloop_signal(mainloop, 0); | ||||
} | } | ||||
static int pulse_free(void) { | |||||
static int pulse_free(void) | |||||
{ | |||||
ENTER(__FUNCTION__); | ENTER(__FUNCTION__); | ||||
size_t l = 0; | size_t l = 0; | ||||
pa_operation *o = NULL; | pa_operation *o = NULL; | ||||
return (int)l; | return (int)l; | ||||
} | } | ||||
static int pulse_playing(const pa_timing_info *the_timing_info) { | |||||
static int pulse_playing(const pa_timing_info *the_timing_info) | |||||
{ | |||||
ENTER(__FUNCTION__); | ENTER(__FUNCTION__); | ||||
int r = 0; | int r = 0; | ||||
const pa_timing_info *i; | const pa_timing_info *i; | ||||
return r; | return r; | ||||
} | } | ||||
static void pulse_write(void *ptr, int length) { | |||||
static void pulse_write(void *ptr, int length) | |||||
{ | |||||
ENTER(__FUNCTION__); | ENTER(__FUNCTION__); | ||||
SHOW("pulse_write > length=%d\n", length); | SHOW("pulse_write > length=%d\n", length); | ||||
pa_threaded_mainloop_unlock(mainloop); | pa_threaded_mainloop_unlock(mainloop); | ||||
} | } | ||||
static int drain(void) { | |||||
static int drain(void) | |||||
{ | |||||
pa_operation *o = NULL; | pa_operation *o = NULL; | ||||
int success = 0; | int success = 0; | ||||
int ret = PULSE_ERROR; | int ret = PULSE_ERROR; | ||||
return ret; | return ret; | ||||
} | } | ||||
static void pulse_close(void) { | |||||
static void pulse_close(void) | |||||
{ | |||||
ENTER(__FUNCTION__); | ENTER(__FUNCTION__); | ||||
drain(); | drain(); | ||||
goto unlock_and_fail; | goto unlock_and_fail; | ||||
} | } | ||||
/* Wait until the context is ready */ | |||||
// Wait until the context is ready | |||||
SHOW_TIME("pa_threaded_mainloop_wait"); | SHOW_TIME("pa_threaded_mainloop_wait"); | ||||
pa_threaded_mainloop_wait(mainloop); | pa_threaded_mainloop_wait(mainloop); | ||||
goto unlock_and_fail; | goto unlock_and_fail; | ||||
} | } | ||||
/* Wait until the stream is ready */ | |||||
// Wait until the stream is ready | |||||
SHOW_TIME("pa_threaded_mainloop_wait"); | SHOW_TIME("pa_threaded_mainloop_wait"); | ||||
pa_threaded_mainloop_wait(mainloop); | pa_threaded_mainloop_wait(mainloop); | ||||
goto unlock_and_fail; | goto unlock_and_fail; | ||||
} | } | ||||
/* Now subscribe to events */ | |||||
// Now subscribe to events | |||||
SHOW_TIME("pa_context_subscribe"); | SHOW_TIME("pa_context_subscribe"); | ||||
if (!(o = pa_context_subscribe(context, PA_SUBSCRIPTION_MASK_SINK_INPUT, context_success_cb, &success))) { | if (!(o = pa_context_subscribe(context, PA_SUBSCRIPTION_MASK_SINK_INPUT, context_success_cb, &success))) { | ||||
SHOW("pa_context_subscribe() failed: %s", pa_strerror(pa_context_errno(context))); | SHOW("pa_context_subscribe() failed: %s", pa_strerror(pa_context_errno(context))); | ||||
// TBD: take in account time suplied by portaudio V18 API | // TBD: take in account time suplied by portaudio V18 API | ||||
a_time = sample - a_timing_info.read_index; | a_time = sample - a_timing_info.read_index; | ||||
a_time = 0.5 + (a_time * 1000.0) / wave_samplerate; | a_time = 0.5 + (a_time * 1000.0) / wave_samplerate; | ||||
} else { | |||||
} else | |||||
a_time = 0; | a_time = 0; | ||||
} | |||||
SHOW("wave_get_remaining_time > sample=%d, time=%d\n", sample, (uint32_t)a_time); | SHOW("wave_get_remaining_time > sample=%d, time=%d\n", sample, (uint32_t)a_time); | ||||
#else | #else | ||||
int wave_init(return 1; ) { | |||||
int wave_init(int srate) | |||||
{ | |||||
return 1; | |||||
} | } | ||||
void *wave_open(const char *the_api) { | |||||
void *wave_open(const char *the_api) | |||||
{ | |||||
return (void *)1; | return (void *)1; | ||||
} | } | ||||
size_t wave_write(void *theHandler, char *theMono16BitsWaveBuffer, size_t theSize) { | |||||
size_t wave_write(void *theHandler, char *theMono16BitsWaveBuffer, size_t theSize) | |||||
{ | |||||
return theSize; | return theSize; | ||||
} | } | ||||
int wave_close(void *theHandler) { | |||||
int wave_close(void *theHandler) | |||||
{ | |||||
return 0; | return 0; | ||||
} | } | ||||
int wave_is_busy(void *theHandler) { | |||||
int wave_is_busy(void *theHandler) | |||||
{ | |||||
return 0; | return 0; | ||||
} | } | ||||
void wave_terminate() { | |||||
void wave_terminate() | |||||
{ | |||||
} | } | ||||
uint32_t wave_get_read_position(void *theHandler) { | |||||
uint32_t wave_get_read_position(void *theHandler) | |||||
{ | |||||
return 0; | return 0; | ||||
} | } | ||||
uint32_t wave_get_write_position(void *theHandler) { | |||||
uint32_t wave_get_write_position(void *theHandler) | |||||
{ | |||||
return 0; | return 0; | ||||
} | } | ||||
void wave_flush(void *theHandler) { | |||||
void wave_flush(void *theHandler) | |||||
{ | |||||
} | } | ||||
typedef int (t_wave_callback)(void); | typedef int (t_wave_callback)(void); | ||||
void wave_set_callback_is_output_enabled(t_wave_callback *cb) { | |||||
void wave_set_callback_is_output_enabled(t_wave_callback *cb) | |||||
{ | |||||
} | } | ||||
extern void *wave_test_get_write_buffer() { | |||||
extern void *wave_test_get_write_buffer() | |||||
{ | |||||
return NULL; | return NULL; | ||||
} | } | ||||
return 0; | return 0; | ||||
} | } | ||||
#endif // of USE_PULSEAUDIO | |||||
#endif | |||||
#ifndef USE_PORTAUDIO | #ifndef USE_PORTAUDIO | ||||
} | } | ||||
ts->tv_nsec = (long int)t_ns; | ts->tv_nsec = (long int)t_ns; | ||||
} | } | ||||
#endif // ifndef USE_PORTAUDIO | |||||
#endif // USE_ASYNC | |||||
#endif | |||||
#endif |
// sun_audio_fd: modified to hold the file descriptor of the opened | // sun_audio_fd: modified to hold the file descriptor of the opened | ||||
// audio device. | // audio device. | ||||
// | // | ||||
int wave_init(int srate) { | |||||
int wave_init(int srate) | |||||
{ | |||||
ENTER("wave_init"); | ENTER("wave_init"); | ||||
audio_info_t ainfo; | audio_info_t ainfo; | ||||
// | // | ||||
actual_index = sample - total_samples_skipped; | actual_index = sample - total_samples_skipped; | ||||
if ((sample < total_samples_skipped) || | if ((sample < total_samples_skipped) || | ||||
(actual_index <= ainfo.play.samples)) { | |||||
(actual_index <= ainfo.play.samples)) | |||||
*time = 0; | *time = 0; | ||||
} else { | |||||
else { | |||||
a_time = ((actual_index - ainfo.play.samples) * 1000) / wave_samplerate; | a_time = ((actual_index - ainfo.play.samples) * 1000) / wave_samplerate; | ||||
*time = (uint32_t)a_time; | *time = (uint32_t)a_time; | ||||
} | } | ||||
#else | #else | ||||
init wave_init() { | |||||
int wave_init(int srate) | |||||
{ | |||||
return 1; | return 1; | ||||
} | } | ||||
void *wave_open(const char *the_api) { | |||||
void *wave_open(const char *the_api) | |||||
{ | |||||
return (void *)1; | return (void *)1; | ||||
} | } | ||||
size_t wave_write(void *theHandler, char *theMono16BitsWaveBuffer, size_t theSize) { | |||||
size_t wave_write(void *theHandler, char *theMono16BitsWaveBuffer, size_t theSize) | |||||
{ | |||||
return theSize; | return theSize; | ||||
} | } | ||||
int wave_close(void *theHandler) { | |||||
int wave_close(void *theHandler) | |||||
{ | |||||
return 0; | return 0; | ||||
} | } | ||||
int wave_is_busy(void *theHandler) { | |||||
int wave_is_busy(void *theHandler) | |||||
{ | |||||
return 0; | return 0; | ||||
} | } | ||||
void wave_terminate() { | |||||
void wave_terminate() | |||||
{ | |||||
} | } | ||||
uint32_t wave_get_read_position(void *theHandler) { | |||||
uint32_t wave_get_read_position(void *theHandler) | |||||
{ | |||||
return 0; | return 0; | ||||
} | } | ||||
uint32_t wave_get_write_position(void *theHandler) { | |||||
uint32_t wave_get_write_position(void *theHandler) | |||||
{ | |||||
return 0; | return 0; | ||||
} | } | ||||
void wave_flush(void *theHandler) { | |||||
void wave_flush(void *theHandler) | |||||
{ | |||||
} | } | ||||
typedef int (t_wave_callback)(void); | typedef int (t_wave_callback)(void); | ||||
void wave_set_callback_is_output_enabled(t_wave_callback *cb) { | |||||
void wave_set_callback_is_output_enabled(t_wave_callback *cb) | |||||
{ | |||||
} | } | ||||
extern void *wave_test_get_write_buffer() { | |||||
extern void *wave_test_get_write_buffer() | |||||
{ | |||||
return NULL; | return NULL; | ||||
} | } | ||||
return 0; | return 0; | ||||
} | } | ||||
#endif // of USE_PORTAUDIO | |||||
#endif | |||||
void clock_gettime2(struct timespec *ts) | void clock_gettime2(struct timespec *ts) | ||||
{ | { | ||||
ts->tv_nsec = (long int)t_ns; | ts->tv_nsec = (long int)t_ns; | ||||
} | } | ||||
#endif // USE_ASYNC | |||||
#endif |
FILE *f_log = NULL; | FILE *f_log = NULL; | ||||
int option_waveout = 0; | int option_waveout = 0; | ||||
static int option_harmonic1 = 10; // 10 | |||||
static int option_harmonic1 = 10; | |||||
static int flutter_amp = 64; | static int flutter_amp = 64; | ||||
static int general_amplitude = 60; | static int general_amplitude = 60; | ||||
static int consonant_amp = 26; // 24 | |||||
static int consonant_amp = 26; | |||||
int embedded_value[N_EMBEDDED_VALUES]; | int embedded_value[N_EMBEDDED_VALUES]; | ||||
static int PHASE_INC_FACTOR; | static int PHASE_INC_FACTOR; | ||||
int samplerate = 0; // this is set by Wavegeninit() | |||||
int samplerate = 0; // this is set by Wavegeninit() | |||||
int samplerate_native = 0; | int samplerate_native = 0; | ||||
extern int option_device_number; | extern int option_device_number; | ||||
extern int option_quiet; | extern int option_quiet; | ||||
int echo_tail; | int echo_tail; | ||||
int echo_amp = 0; | int echo_amp = 0; | ||||
short echo_buf[N_ECHO_BUF]; | short echo_buf[N_ECHO_BUF]; | ||||
static int echo_length = 0; // period (in sample\) to ensure completion of echo at the end of speech, set in WavegenSetEcho() | |||||
static int echo_length = 0; // period (in sample\) to ensure completion of echo at the end of speech, set in WavegenSetEcho() | |||||
static int voicing; | static int voicing; | ||||
static RESONATOR rbreath[N_PEAKS]; | static RESONATOR rbreath[N_PEAKS]; | ||||
static int harm_sqrt_n = 0; | static int harm_sqrt_n = 0; | ||||
#define N_LOWHARM 30 | #define N_LOWHARM 30 | ||||
static int harm_inc[N_LOWHARM]; // only for these harmonics do we interpolate amplitude between steps | |||||
static int harm_inc[N_LOWHARM]; // only for these harmonics do we interpolate amplitude between steps | |||||
static int *harmspect; | static int *harmspect; | ||||
static int hswitch = 0; | static int hswitch = 0; | ||||
static int hspect[2][MAX_HARMONIC]; // 2 copies, we interpolate between then | |||||
static int hspect[2][MAX_HARMONIC]; // 2 copies, we interpolate between then | |||||
static int max_hval = 0; | static int max_hval = 0; | ||||
static int nsamples = 0; // number to do | |||||
static int nsamples = 0; // number to do | |||||
static int modulation_type = 0; | static int modulation_type = 0; | ||||
static int glottal_flag = 0; | static int glottal_flag = 0; | ||||
static int glottal_reduce = 0; | static int glottal_reduce = 0; | ||||
static int amp_inc; | static int amp_inc; | ||||
static unsigned char *amplitude_env = NULL; | static unsigned char *amplitude_env = NULL; | ||||
static int samplecount = 0; // number done | |||||
static int samplecount_start = 0; // count at start of this segment | |||||
static int end_wave = 0; // continue to end of wave cycle | |||||
static int samplecount = 0; // number done | |||||
static int samplecount_start = 0; // count at start of this segment | |||||
static int end_wave = 0; // continue to end of wave cycle | |||||
static int wavephase; | static int wavephase; | ||||
static int phaseinc; | static int phaseinc; | ||||
static int cycle_samples; // number of samples in a cycle at current pitch | |||||
static int cycle_samples; // number of samples in a cycle at current pitch | |||||
static int cbytes; | static int cbytes; | ||||
static int hf_factor; | static int hf_factor; | ||||
int wcmdq_tail = 0; | int wcmdq_tail = 0; | ||||
// pitch,speed, | // pitch,speed, | ||||
int embedded_default[N_EMBEDDED_VALUES] = { 0, 50, 175, 100, 50, 0, 0, 0, 175, 0, 0, 0, 0, 0, 0 }; | |||||
static int embedded_max[N_EMBEDDED_VALUES] = { 0, 0x7fff, 750, 300, 99, 99, 99, 0, 750, 0, 0, 0, 0, 4, 0 }; | |||||
int embedded_default[N_EMBEDDED_VALUES] = { 0, 50, 175, 100, 50, 0, 0, 0, 175, 0, 0, 0, 0, 0, 0 }; | |||||
static int embedded_max[N_EMBEDDED_VALUES] = { 0, 0x7fff, 750, 300, 99, 99, 99, 0, 750, 0, 0, 0, 0, 4, 0 }; | |||||
#define N_CALLBACK_IX N_WAV_BUF-2 // adjust this delay to match display with the currently spoken word | |||||
#define N_CALLBACK_IX N_WAV_BUF-2 // adjust this delay to match display with the currently spoken word | |||||
int current_source_index = 0; | int current_source_index = 0; | ||||
extern FILE *f_wave; | extern FILE *f_wave; | ||||
// the presets are for 22050 Hz sample rate. | // the presets are for 22050 Hz sample rate. | ||||
// A different rate will need to recalculate the presets in WavegenInit() | // A different rate will need to recalculate the presets in WavegenInit() | ||||
static unsigned char wavemult[N_WAVEMULT] = { | static unsigned char wavemult[N_WAVEMULT] = { | ||||
0, 0, 0, 2, 3, 5, 8, 11, 14, 18, 22, 27, 32, 37, 43, 49, | |||||
55, 62, 69, 76, 83, 90, 98, 105, 113, 121, 128, 136, 144, 152, 159, 166, | |||||
0, 0, 0, 2, 3, 5, 8, 11, 14, 18, 22, 27, 32, 37, 43, 49, | |||||
55, 62, 69, 76, 83, 90, 98, 105, 113, 121, 128, 136, 144, 152, 159, 166, | |||||
174, 181, 188, 194, 201, 207, 213, 218, 224, 228, 233, 237, 240, 244, 246, 249, | 174, 181, 188, 194, 201, 207, 213, 218, 224, 228, 233, 237, 240, 244, 246, 249, | ||||
251, 252, 253, 253, 253, 253, 252, 251, 249, 246, 244, 240, 237, 233, 228, 224, | 251, 252, 253, 253, 253, 253, 252, 251, 249, 246, 244, 240, 237, 233, 228, 224, | ||||
218, 213, 207, 201, 194, 188, 181, 174, 166, 159, 152, 144, 136, 128, 121, 113, | 218, 213, 207, 201, 194, 188, 181, 174, 166, 159, 152, 144, 136, 128, 121, 113, | ||||
105, 98, 90, 83, 76, 69, 62, 55, 49, 43, 37, 32, 27, 22, 18, 14, | |||||
11, 8, 5, 3, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | |||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 | |||||
105, 98, 90, 83, 76, 69, 62, 55, 49, 43, 37, 32, 27, 22, 18, 14, | |||||
11, 8, 5, 3, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | |||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 | |||||
}; | }; | ||||
// set from y = pow(2,x) * 128, x=-1 to 1 | // set from y = pow(2,x) * 128, x=-1 to 1 | ||||
unsigned char pitch_adjust_tab[MAX_PITCH_VALUE+1] = { | unsigned char pitch_adjust_tab[MAX_PITCH_VALUE+1] = { | ||||
64, 65, 66, 67, 68, 69, 70, 71, | |||||
72, 73, 74, 75, 76, 77, 78, 79, | |||||
80, 81, 82, 83, 84, 86, 87, 88, | |||||
89, 91, 92, 93, 94, 96, 97, 98, | |||||
64, 65, 66, 67, 68, 69, 70, 71, | |||||
72, 73, 74, 75, 76, 77, 78, 79, | |||||
80, 81, 82, 83, 84, 86, 87, 88, | |||||
89, 91, 92, 93, 94, 96, 97, 98, | |||||
100, 101, 103, 104, 105, 107, 108, 110, | 100, 101, 103, 104, 105, 107, 108, 110, | ||||
111, 113, 115, 116, 118, 119, 121, 123, | 111, 113, 115, 116, 118, 119, 121, 123, | ||||
124, 126, 128, 130, 132, 133, 135, 137, | 124, 126, 128, 130, 132, 133, 135, 137, | ||||
245, 244, 242, 241, 239, 238, 236, 234, 233, 231, 229, 227, 225, 223, 220, 218, | 245, 244, 242, 241, 239, 238, 236, 234, 233, 231, 229, 227, 225, 223, 220, 218, | ||||
216, 213, 211, 209, 207, 205, 203, 201, 199, 197, 195, 193, 191, 189, 187, 185, | 216, 213, 211, 209, 207, 205, 203, 201, 199, 197, 195, 193, 191, 189, 187, 185, | ||||
183, 180, 178, 176, 173, 171, 169, 166, 164, 161, 159, 156, 154, 151, 148, 146, | 183, 180, 178, 176, 173, 171, 169, 166, 164, 161, 159, 156, 154, 151, 148, 146, | ||||
143, 140, 138, 135, 132, 129, 126, 123, 120, 118, 115, 112, 108, 105, 102, 99, | |||||
96, 95, 93, 91, 90, 88, 86, 85, 83, 82, 80, 79, 77, 76, 74, 73, | |||||
72, 70, 69, 68, 67, 66, 64, 63, 62, 61, 60, 59, 58, 57, 56, 55, | |||||
55, 54, 53, 52, 52, 51, 50, 50, 49, 48, 48, 47, 47, 46, 46, 46, | |||||
45, 45, 45, 44, 44, 44, 44, 44, 44, 44, 43, 43, 43, 43, 44, 43, | |||||
42, 42, 41, 40, 40, 39, 38, 38, 37, 36, 36, 35, 35, 34, 33, 33, | |||||
32, 32, 31, 30, 30, 29, 29, 28, 28, 27, 26, 26, 25, 25, 24, 24, | |||||
23, 23, 22, 22, 21, 21, 20, 20, 19, 19, 18, 18, 18, 17, 17, 16, | |||||
16, 15, 15, 15, 14, 14, 13, 13, 13, 12, 12, 11, 11, 11, 10, 10, | |||||
10, 9, 9, 9, 8, 8, 8, 7, 7, 7, 7, 6, 6, 6, 5, 5, | |||||
5, 5, 4, 4, 4, 4, 4, 3, 3, 3, 3, 2, 2, 2, 2, 2, | |||||
2, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, | |||||
0 | |||||
143, 140, 138, 135, 132, 129, 126, 123, 120, 118, 115, 112, 108, 105, 102, 99, | |||||
96, 95, 93, 91, 90, 88, 86, 85, 83, 82, 80, 79, 77, 76, 74, 73, | |||||
72, 70, 69, 68, 67, 66, 64, 63, 62, 61, 60, 59, 58, 57, 56, 55, | |||||
55, 54, 53, 52, 52, 51, 50, 50, 49, 48, 48, 47, 47, 46, 46, 46, | |||||
45, 45, 45, 44, 44, 44, 44, 44, 44, 44, 43, 43, 43, 43, 44, 43, | |||||
42, 42, 41, 40, 40, 39, 38, 38, 37, 36, 36, 35, 35, 34, 33, 33, | |||||
32, 32, 31, 30, 30, 29, 29, 28, 28, 27, 26, 26, 25, 25, 24, 24, | |||||
23, 23, 22, 22, 21, 21, 20, 20, 19, 19, 18, 18, 18, 17, 17, 16, | |||||
16, 15, 15, 15, 14, 14, 13, 13, 13, 12, 12, 11, 11, 11, 10, 10, | |||||
10, 9, 9, 9, 8, 8, 8, 7, 7, 7, 7, 6, 6, 6, 5, 5, | |||||
5, 5, 4, 4, 4, 4, 4, 3, 3, 3, 3, 2, 2, 2, 2, 2, | |||||
2, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, | |||||
0 | |||||
}; | }; | ||||
static unsigned char pk_shape2[PEAKSHAPEW+1] = { | static unsigned char pk_shape2[PEAKSHAPEW+1] = { | ||||
243, 243, 242, 241, 239, 237, 235, 233, 231, 229, 227, 225, 223, 221, 218, 216, | 243, 243, 242, 241, 239, 237, 235, 233, 231, 229, 227, 225, 223, 221, 218, 216, | ||||
213, 211, 208, 205, 203, 200, 197, 194, 191, 187, 184, 181, 178, 174, 171, 167, | 213, 211, 208, 205, 203, 200, 197, 194, 191, 187, 184, 181, 178, 174, 171, 167, | ||||
163, 160, 156, 152, 148, 144, 140, 136, 132, 127, 123, 119, 114, 110, 105, 100, | 163, 160, 156, 152, 148, 144, 140, 136, 132, 127, 123, 119, 114, 110, 105, 100, | ||||
96, 94, 91, 88, 86, 83, 81, 78, 76, 74, 71, 69, 66, 64, 62, 60, | |||||
57, 55, 53, 51, 49, 47, 44, 42, 40, 38, 36, 34, 32, 30, 29, 27, | |||||
25, 23, 21, 19, 18, 16, 14, 12, 11, 9, 7, 6, 4, 3, 1, 0, | |||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | |||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | |||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | |||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | |||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | |||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | |||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | |||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | |||||
0 | |||||
96, 94, 91, 88, 86, 83, 81, 78, 76, 74, 71, 69, 66, 64, 62, 60, | |||||
57, 55, 53, 51, 49, 47, 44, 42, 40, 38, 36, 34, 32, 30, 29, 27, | |||||
25, 23, 21, 19, 18, 16, 14, 12, 11, 9, 7, 6, 4, 3, 1, 0, | |||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | |||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | |||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | |||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | |||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | |||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | |||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | |||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | |||||
0 | |||||
}; | }; | ||||
static unsigned char *pk_shape; | static unsigned char *pk_shape; | ||||
out_ptr = out_start = outbuffer; | out_ptr = out_start = outbuffer; | ||||
out_end = out_start + outbuffer_size; | out_end = out_start + outbuffer_size; | ||||
} | } | ||||
out_end2 = &outbuffer[pa_size]; // top of data needed for the portaudio buffer | |||||
out_end2 = &outbuffer[pa_size]; // top of data needed for the portaudio buffer | |||||
#ifdef LIBRARY | #ifdef LIBRARY | ||||
event_list_ix = 0; | event_list_ix = 0; | ||||
result = WavegenFill(1); | result = WavegenFill(1); | ||||
// copy from the outbut buffer into the portaudio buffer | // copy from the outbut buffer into the portaudio buffer | ||||
if (result && (out_ptr > out_end2)) { | |||||
result = 0; // don't end yet, there is more data in the buffer than can fit in portaudio | |||||
} | |||||
if (result && (out_ptr > out_end2)) | |||||
result = 0; // don't end yet, there is more data in the buffer than can fit in portaudio | |||||
while (out_ptr < out_end2) | while (out_ptr < out_end2) | ||||
*out_ptr++ = 0; // fill with zeros up to the size of the portaudio buffer | |||||
*out_ptr++ = 0; // fill with zeros up to the size of the portaudio buffer | |||||
memcpy(outputBuffer, outbuffer, pa_size); | memcpy(outputBuffer, outbuffer, pa_size); | ||||
// move the remaining contents of the start of the output buffer | // move the remaining contents of the start of the output buffer | ||||
for (p = out_end2; p < out_end; p++) { | |||||
for (p = out_end2; p < out_end; p++) | |||||
p[-pa_size] = p[0]; | p[-pa_size] = p[0]; | ||||
} | |||||
out_ptr -= pa_size; | out_ptr -= pa_size; | ||||
#ifdef LIBRARY | #ifdef LIBRARY | ||||
event_list[event_list_ix].user_data = 0; | event_list[event_list_ix].user_data = 0; | ||||
if (synth_callback(NULL, 0, event_list) == 1) { | if (synth_callback(NULL, 0, event_list) == 1) { | ||||
SpeakNextClause(NULL, NULL, 2); // stop speaking | |||||
SpeakNextClause(NULL, NULL, 2); // stop speaking | |||||
result = 1; | result = 1; | ||||
} | } | ||||
} | } | ||||
#endif | #endif | ||||
#ifdef ARCH_BIG | #ifdef ARCH_BIG | ||||
{ | |||||
// swap the order of bytes in each sound sample in the portaudio buffer | |||||
int c; | |||||
unsigned char *buf_end; | |||||
out_buf = (unsigned char *)outputBuffer; | |||||
buf_end = out_buf + framesPerBuffer*2; | |||||
while (out_buf < buf_end) { | |||||
c = out_buf[0]; | |||||
out_buf[0] = out_buf[1]; | |||||
out_buf[1] = c; | |||||
out_buf += 2; | |||||
} | |||||
// swap the order of bytes in each sound sample in the portaudio buffer | |||||
int c; | |||||
unsigned char *buf_end; | |||||
out_buf = (unsigned char *)outputBuffer; | |||||
buf_end = out_buf + framesPerBuffer*2; | |||||
while (out_buf < buf_end) { | |||||
c = out_buf[0]; | |||||
out_buf[0] = out_buf[1]; | |||||
out_buf[1] = c; | |||||
out_buf += 2; | |||||
} | } | ||||
#endif | #endif | ||||
return 1; | return 1; | ||||
} | } | ||||
} else | } else | ||||
WavegenOpenSound(); // still items in the queue, shouldn't be closed | |||||
WavegenOpenSound(); // still items in the queue, shouldn't be closed | |||||
} | } | ||||
return 0; | return 0; | ||||
} | } | ||||
} | } | ||||
return 0; | return 0; | ||||
} | } | ||||
#else | #else | ||||
int WavegenOpenSound() | int WavegenOpenSound() | ||||
{ | { | ||||
return 0; | return 0; | ||||
} | } | ||||
int WavegenCloseSound() | int WavegenCloseSound() | ||||
{ | { | ||||
return 0; | return 0; | ||||
} | } | ||||
int WavegenInitSound() | int WavegenInitSound() | ||||
{ | { | ||||
return 0; | return 0; | ||||
} | } | ||||
#endif | #endif | ||||
void WavegenInit(int rate, int wavemult_fact) | void WavegenInit(int rate, int wavemult_fact) | ||||
double x; | double x; | ||||
if (wavemult_fact == 0) | if (wavemult_fact == 0) | ||||
wavemult_fact = 60; // default | |||||
wavemult_fact = 60; // default | |||||
wvoice = NULL; | wvoice = NULL; | ||||
samplerate = samplerate_native = rate; | samplerate = samplerate_native = rate; | ||||
PHASE_INC_FACTOR = 0x8000000 / samplerate; // assumes pitch is Hz*32 | |||||
PHASE_INC_FACTOR = 0x8000000 / samplerate; // assumes pitch is Hz*32 | |||||
Flutter_inc = (64 * samplerate)/rate; | Flutter_inc = (64 * samplerate)/rate; | ||||
samplecount = 0; | samplecount = 0; | ||||
nsamples = 0; | nsamples = 0; | ||||
} | } | ||||
} | } | ||||
pk_shape = pk_shape2; // pk_shape2 | |||||
pk_shape = pk_shape2; | |||||
#ifdef INCLUDE_KLATT | #ifdef INCLUDE_KLATT | ||||
KlattInit(); | KlattInit(); | ||||
amp = 0; | amp = 0; | ||||
echo_head = (delay * samplerate)/1000; | echo_head = (delay * samplerate)/1000; | ||||
echo_length = echo_head; // ensure completion of echo at the end of speech. Use 1 delay period? | |||||
echo_length = echo_head; // ensure completion of echo at the end of speech. Use 1 delay period? | |||||
if (amp == 0) | if (amp == 0) | ||||
echo_length = 0; | echo_length = 0; | ||||
if (amp > 20) | if (amp > 20) | ||||
echo_length = echo_head * 2; // perhaps allow 2 echo periods if the echo is loud. | |||||
echo_length = echo_head * 2; // perhaps allow 2 echo periods if the echo is loud. | |||||
// echo_amp units are 1/256ths of the amplitude of the original sound. | // echo_amp units are 1/256ths of the amplitude of the original sound. | ||||
echo_amp = amp; | echo_amp = amp; | ||||
int PeaksToHarmspect(wavegen_peaks_t *peaks, int pitch, int *htab, int control) | int PeaksToHarmspect(wavegen_peaks_t *peaks, int pitch, int *htab, int control) | ||||
{ | { | ||||
// Calculate the amplitude of each harmonics from the formants | |||||
// Only for formants 0 to 5 | |||||
// Calculate the amplitude of each harmonics from the formants | |||||
// Only for formants 0 to 5 | |||||
// control 0=initial call, 1=every 64 cycles | |||||
// control 0=initial call, 1=every 64 cycles | |||||
// pitch and freqs are Hz<<16 | // pitch and freqs are Hz<<16 | ||||
int f; | int f; | ||||
wavegen_peaks_t *p; | wavegen_peaks_t *p; | ||||
int fp; // centre freq of peak | |||||
int fhi; // high freq of peak | |||||
int h; // harmonic number | |||||
int fp; // centre freq of peak | |||||
int fhi; // high freq of peak | |||||
int h; // harmonic number | |||||
int pk; | int pk; | ||||
int hmax; | int hmax; | ||||
int hmax_samplerate; // highest harmonic allowed for the samplerate | |||||
int hmax_samplerate; // highest harmonic allowed for the samplerate | |||||
int x; | int x; | ||||
int ix; | int ix; | ||||
int h1; | int h1; | ||||
hmax = MAX_HARMONIC-1; | hmax = MAX_HARMONIC-1; | ||||
// restrict highest harmonic to half the samplerate | // restrict highest harmonic to half the samplerate | ||||
hmax_samplerate = (((samplerate * 19)/40) << 16)/pitch; // only 95% of Nyquist freq | |||||
hmax_samplerate = (((samplerate * 19)/40) << 16)/pitch; // only 95% of Nyquist freq | |||||
if (hmax > hmax_samplerate) | if (hmax > hmax_samplerate) | ||||
hmax = hmax_samplerate; | hmax = hmax_samplerate; | ||||
int h2; | int h2; | ||||
// increase bass | // increase bass | ||||
y = peaks[1].height * 10; // addition as a multiple of 1/256s | y = peaks[1].height * 10; // addition as a multiple of 1/256s | ||||
h2 = (1000<<16)/pitch; // decrease until 1000Hz | |||||
h2 = (1000<<16)/pitch; // decrease until 1000Hz | |||||
if (h2 > 0) { | if (h2 > 0) { | ||||
x = y/h2; | x = y/h2; | ||||
h = 1; | h = 1; | ||||
htab[h] = (x * x) >> 8; | htab[h] = (x * x) >> 8; | ||||
if ((ix = (f >> 19)) < N_TONE_ADJUST) | if ((ix = (f >> 19)) < N_TONE_ADJUST) | ||||
htab[h] = (htab[h] * wvoice->tone_adjust[ix]) >> 13; // index tone_adjust with Hz/8 | |||||
htab[h] = (htab[h] * wvoice->tone_adjust[ix]) >> 13; // index tone_adjust with Hz/8 | |||||
} | } | ||||
// adjust the amplitude of the first harmonic, affects tonal quality | // adjust the amplitude of the first harmonic, affects tonal quality | ||||
h1 = htab[1] * option_harmonic1; | h1 = htab[1] * option_harmonic1; | ||||
htab[1] = h1/8; | htab[1] = h1/8; | ||||
// calc intermediate increments of LF harmonics | // calc intermediate increments of LF harmonics | ||||
if (control & 1) { | if (control & 1) { | ||||
for (h = 1; h < N_LOWHARM; h++) | for (h = 1; h < N_LOWHARM; h++) | ||||
harm_inc[h] = (htab[h] - harmspect[h]) >> 3; | harm_inc[h] = (htab[h] - harmspect[h]) >> 3; | ||||
} | } | ||||
return hmax; // highest harmonic number | |||||
return hmax; // highest harmonic number | |||||
} | } | ||||
static void AdvanceParameters() | static void AdvanceParameters() | ||||
{ | { | ||||
// Called every 64 samples to increment the formant freq, height, and widths | |||||
// Called every 64 samples to increment the formant freq, height, and widths | |||||
int x; | int x; | ||||
int ix; | int ix; | ||||
Flutter_ix += Flutter_inc; | Flutter_ix += Flutter_inc; | ||||
wdata.pitch += x; | wdata.pitch += x; | ||||
if (wdata.pitch < 102400) | if (wdata.pitch < 102400) | ||||
wdata.pitch = 102400; // min pitch, 25 Hz (25 << 12) | |||||
wdata.pitch = 102400; // min pitch, 25 Hz (25 << 12) | |||||
if (samplecount == samplecount_start) | if (samplecount == samplecount_start) | ||||
return; | return; | ||||
static void setresonator(RESONATOR *rp, int freq, int bwidth, int init) | static void setresonator(RESONATOR *rp, int freq, int bwidth, int init) | ||||
{ | { | ||||
// freq Frequency of resonator in Hz | |||||
// bwidth Bandwidth of resonator in Hz | |||||
// init Initialize internal data | |||||
// freq Frequency of resonator in Hz | |||||
// bwidth Bandwidth of resonator in Hz | |||||
// init Initialize internal data | |||||
double x; | double x; | ||||
double arg; | double arg; | ||||
static int agc = 256; | static int agc = 256; | ||||
static int h_switch_sign = 0; | static int h_switch_sign = 0; | ||||
static int cycle_count = 0; | static int cycle_count = 0; | ||||
static int amplitude2 = 0; // adjusted for pitch | |||||
static int amplitude2 = 0; // adjusted for pitch | |||||
// continue until the output buffer is full, or | // continue until the output buffer is full, or | ||||
// the required number of samples have been produced | // the required number of samples have been produced | ||||
// pitch is Hz<<12 | // pitch is Hz<<12 | ||||
phaseinc = (wdata.pitch>>7) * PHASE_INC_FACTOR; | phaseinc = (wdata.pitch>>7) * PHASE_INC_FACTOR; | ||||
cycle_samples = samplerate/(wdata.pitch >> 12); // sr/(pitch*2) | |||||
cycle_samples = samplerate/(wdata.pitch >> 12); // sr/(pitch*2) | |||||
hf_factor = wdata.pitch >> 11; | hf_factor = wdata.pitch >> 11; | ||||
maxh = maxh2; | maxh = maxh2; | ||||
// 8 bit data, shift by the specified scale factor | // 8 bit data, shift by the specified scale factor | ||||
value = (signed char)data[ix++] * scale; | value = (signed char)data[ix++] * scale; | ||||
} | } | ||||
value *= (consonant_amp * general_amplitude); // reduce strength of consonant | |||||
value *= (consonant_amp * general_amplitude); // reduce strength of consonant | |||||
value = value >> 10; | value = value >> 10; | ||||
value = (value * amp)/32; | value = (value * amp)/32; | ||||
switch (command) | switch (command) | ||||
{ | { | ||||
case EMBED_T: | case EMBED_T: | ||||
WavegenSetEcho(); // and drop through to case P | |||||
WavegenSetEcho(); // and drop through to case P | |||||
case EMBED_P: | case EMBED_P: | ||||
SetPitchFormants(); | SetPitchFormants(); | ||||
break; | break; | ||||
case EMBED_A: // amplitude | |||||
case EMBED_A: // amplitude | |||||
general_amplitude = GetAmplitude(); | general_amplitude = GetAmplitude(); | ||||
break; | break; | ||||
case EMBED_F: // emphasis | |||||
case EMBED_F: // emphasis | |||||
general_amplitude = GetAmplitude(); | general_amplitude = GetAmplitude(); | ||||
break; | break; | ||||
case EMBED_H: | case EMBED_H: | ||||
consonant_amp = (v->consonant_amp * 26) /100; | consonant_amp = (v->consonant_amp * 26) /100; | ||||
if (samplerate <= 11000) { | if (samplerate <= 11000) { | ||||
consonant_amp = consonant_amp*2; // emphasize consonants at low sample rates | |||||
consonant_amp = consonant_amp*2; // emphasize consonants at low sample rates | |||||
option_harmonic1 = 6; | option_harmonic1 = 6; | ||||
} | } | ||||
WavegenSetEcho(); | WavegenSetEcho(); | ||||
amp_inc = (256 * ENV_LEN * STEPSIZE)/length; | amp_inc = (256 * ENV_LEN * STEPSIZE)/length; | ||||
wdata.amplitude = (value * general_amplitude)/16; | wdata.amplitude = (value * general_amplitude)/16; | ||||
wdata.amplitude_v = (wdata.amplitude * wvoice->consonant_ampv * 15)/100; // for wave mixed with voiced sounds | |||||
wdata.amplitude_v = (wdata.amplitude * wvoice->consonant_ampv * 15)/100; // for wave mixed with voiced sounds | |||||
amplitude_env = amp_env; | amplitude_env = amp_env; | ||||
} | } | ||||
int pitch_value; | int pitch_value; | ||||
if (pitch1 > pitch2) { | if (pitch1 > pitch2) { | ||||
x = pitch1; // swap values | |||||
x = pitch1; // swap values | |||||
pitch1 = pitch2; | pitch1 = pitch2; | ||||
pitch2 = x; | pitch2 = x; | ||||
} | } | ||||
if ((pitch_value = embedded_value[EMBED_P]) > MAX_PITCH_VALUE) | if ((pitch_value = embedded_value[EMBED_P]) > MAX_PITCH_VALUE) | ||||
pitch_value = MAX_PITCH_VALUE; | pitch_value = MAX_PITCH_VALUE; | ||||
pitch_value -= embedded_value[EMBED_T]; // adjust tone for announcing punctuation | |||||
pitch_value -= embedded_value[EMBED_T]; // adjust tone for announcing punctuation | |||||
if (pitch_value < 0) | if (pitch_value < 0) | ||||
pitch_value = 0; | pitch_value = 0; | ||||
void SetPitch(int length, unsigned char *env, int pitch1, int pitch2) | void SetPitch(int length, unsigned char *env, int pitch1, int pitch2) | ||||
{ | { | ||||
// length in samples | |||||
// length in samples | |||||
if ((wdata.pitch_env = env) == NULL) | if ((wdata.pitch_env = env) == NULL) | ||||
wdata.pitch_env = env_fall; // default | |||||
wdata.pitch_env = env_fall; // default | |||||
wdata.pitch_ix = 0; | wdata.pitch_ix = 0; | ||||
if (length == 0) | if (length == 0) | ||||
SetPitch2(wvoice, pitch1, pitch2, &wdata.pitch_base, &wdata.pitch_range); | SetPitch2(wvoice, pitch1, pitch2, &wdata.pitch_base, &wdata.pitch_range); | ||||
// set initial pitch | // set initial pitch | ||||
wdata.pitch = ((wdata.pitch_env[0] * wdata.pitch_range) >>8) + wdata.pitch_base; // Hz << 12 | |||||
wdata.pitch = ((wdata.pitch_env[0] * wdata.pitch_range) >>8) + wdata.pitch_base; // Hz << 12 | |||||
flutter_amp = wvoice->flutter; | flutter_amp = wvoice->flutter; | ||||
} | } | ||||
int length4; | int length4; | ||||
int qix; | int qix; | ||||
int cmd; | int cmd; | ||||
static int glottal_reduce_tab1[4] = { 0x30, 0x30, 0x40, 0x50 }; // vowel before [?], amp * 1/256 | |||||
static int glottal_reduce_tab2[4] = { 0x90, 0xa0, 0xb0, 0xc0 }; // vowel after [?], amp * 1/256 | |||||
static int glottal_reduce_tab1[4] = { 0x30, 0x30, 0x40, 0x50 }; // vowel before [?], amp * 1/256 | |||||
static int glottal_reduce_tab2[4] = { 0x90, 0xa0, 0xb0, 0xc0 }; // vowel after [?], amp * 1/256 | |||||
harm_sqrt_n = 0; | harm_sqrt_n = 0; | ||||
end_wave = 1; | end_wave = 1; | ||||
glottal_flag = 0; | glottal_flag = 0; | ||||
if (modn & 0x400) { | if (modn & 0x400) { | ||||
glottal_flag = 3; // before a glottal stop | |||||
glottal_flag = 3; // before a glottal stop | |||||
glottal_reduce = glottal_reduce_tab1[(modn >> 8) & 3]; | glottal_reduce = glottal_reduce_tab1[(modn >> 8) & 3]; | ||||
} | } | ||||
if (modn & 0x800) { | if (modn & 0x800) { | ||||
glottal_flag = 4; // after a glottal stop | |||||
glottal_flag = 4; // after a glottal stop | |||||
glottal_reduce = glottal_reduce_tab2[(modn >> 8) & 3]; | glottal_reduce = glottal_reduce_tab2[(modn >> 8) & 3]; | ||||
} | } | ||||
cmd = wcmdq[qix][0]; | cmd = wcmdq[qix][0]; | ||||
if (cmd == WCMD_SPECT) { | if (cmd == WCMD_SPECT) { | ||||
end_wave = 0; // next wave generation is from another spectrum | |||||
end_wave = 0; // next wave generation is from another spectrum | |||||
break; | break; | ||||
} | } | ||||
if ((cmd == WCMD_WAVE) || (cmd == WCMD_PAUSE)) | if ((cmd == WCMD_WAVE) || (cmd == WCMD_PAUSE)) | ||||
break; // next is not from spectrum, so continue until end of wave cycle | |||||
break; // next is not from spectrum, so continue until end of wave cycle | |||||
} | } | ||||
// round the length to a multiple of the stepsize | // round the length to a multiple of the stepsize | ||||
peaks[ix].freq1 = (fr1->ffreq[ix] * v->freq[ix] + v->freqadd[ix]*256) << 8; | peaks[ix].freq1 = (fr1->ffreq[ix] * v->freq[ix] + v->freqadd[ix]*256) << 8; | ||||
peaks[ix].freq = (int)peaks[ix].freq1; | peaks[ix].freq = (int)peaks[ix].freq1; | ||||
next = (fr2->ffreq[ix] * v->freq[ix] + v->freqadd[ix]*256) << 8; | next = (fr2->ffreq[ix] * v->freq[ix] + v->freqadd[ix]*256) << 8; | ||||
peaks[ix].freq_inc = ((next - peaks[ix].freq1) * (STEPSIZE/4)) / length4; // lower headroom for fixed point math | |||||
peaks[ix].freq_inc = ((next - peaks[ix].freq1) * (STEPSIZE/4)) / length4; // lower headroom for fixed point math | |||||
} | } | ||||
peaks[ix].height1 = (fr1->fheight[ix] * v->height[ix]) << 6; | peaks[ix].height1 = (fr1->fheight[ix] * v->height[ix]) << 6; | ||||
peaks[ix].right = (int)peaks[ix].right1; | peaks[ix].right = (int)peaks[ix].right1; | ||||
next = (fr2->fright[ix] * v->width[ix]) << 10; | next = (fr2->fright[ix] * v->width[ix]) << 10; | ||||
peaks[ix].right_inc = ((next - peaks[ix].right1) * STEPSIZE) / length2; | peaks[ix].right_inc = ((next - peaks[ix].right1) * STEPSIZE) / length2; | ||||
} else { | |||||
} else | |||||
peaks[ix].right = peaks[ix].left; | peaks[ix].right = peaks[ix].left; | ||||
} | |||||
} | } | ||||
} | } | ||||
} | } | ||||
void Write4Bytes(FILE *f, int value) | void Write4Bytes(FILE *f, int value) | ||||
{ | { | ||||
// Write 4 bytes to a file, least significant first | |||||
// Write 4 bytes to a file, least significant first | |||||
int ix; | int ix; | ||||
for (ix = 0; ix < 4; ix++) { | for (ix = 0; ix < 4; ix++) { | ||||
int WavegenFill2(int fill_zeros) | int WavegenFill2(int fill_zeros) | ||||
{ | { | ||||
// Pick up next wavegen commands from the queue | |||||
// return: 0 output buffer has been filled | |||||
// return: 1 input command queue is now empty | |||||
// Pick up next wavegen commands from the queue | |||||
// return: 0 output buffer has been filled | |||||
// return: 1 input command queue is now empty | |||||
intptr_t *q; | intptr_t *q; | ||||
int length; | int length; | ||||
// continue to play silence until echo is completed | // continue to play silence until echo is completed | ||||
resume = PlaySilence(echo_complete, resume); | resume = PlaySilence(echo_complete, resume); | ||||
if (resume == 1) | if (resume == 1) | ||||
return 0; // not yet finished | |||||
return 0; // not yet finished | |||||
} | } | ||||
if (fill_zeros) { | if (fill_zeros) { | ||||
while (out_ptr < out_end) | while (out_ptr < out_end) | ||||
*out_ptr++ = 0; | *out_ptr++ = 0; | ||||
} | } | ||||
return 1; // queue empty, close sound channel | |||||
return 1; // queue empty, close sound channel | |||||
} | } | ||||
result = 0; | result = 0; | ||||
wdata.mix_wavefile_offset = 0; | wdata.mix_wavefile_offset = 0; | ||||
wdata.mix_wavefile = (unsigned char *)q[2]; | wdata.mix_wavefile = (unsigned char *)q[2]; | ||||
break; | break; | ||||
case WCMD_SPECT2: // as WCMD_SPECT but stop any concurrent wave file | |||||
wdata.n_mix_wavefile = 0; // ... and drop through to WCMD_SPECT case | |||||
case WCMD_SPECT2: // as WCMD_SPECT but stop any concurrent wave file | |||||
wdata.n_mix_wavefile = 0; // ... and drop through to WCMD_SPECT case | |||||
case WCMD_SPECT: | case WCMD_SPECT: | ||||
echo_complete = echo_length; | echo_complete = echo_length; | ||||
result = Wavegen2(length & 0xffff, q[1] >> 16, resume, (frame_t *)q[2], (frame_t *)q[3]); | result = Wavegen2(length & 0xffff, q[1] >> 16, resume, (frame_t *)q[2], (frame_t *)q[3]); | ||||
break; | break; | ||||
#ifdef INCLUDE_KLATT | #ifdef INCLUDE_KLATT | ||||
case WCMD_KLATT2: // as WCMD_SPECT but stop any concurrent wave file | |||||
wdata.n_mix_wavefile = 0; // ... and drop through to WCMD_SPECT case | |||||
case WCMD_KLATT2: // as WCMD_SPECT but stop any concurrent wave file | |||||
wdata.n_mix_wavefile = 0; // ... and drop through to WCMD_SPECT case | |||||
case WCMD_KLATT: | case WCMD_KLATT: | ||||
echo_complete = echo_length; | echo_complete = echo_length; | ||||
result = Wavegen_Klatt2(length & 0xffff, q[1] >> 16, resume, (frame_t *)q[2], (frame_t *)q[3]); | result = Wavegen_Klatt2(length & 0xffff, q[1] >> 16, resume, (frame_t *)q[2], (frame_t *)q[3]); | ||||
break; | break; | ||||
case WCMD_FMT_AMPLITUDE: | case WCMD_FMT_AMPLITUDE: | ||||
if ((wdata.amplitude_fmt = q[1]) == 0) | if ((wdata.amplitude_fmt = q[1]) == 0) | ||||
wdata.amplitude_fmt = 100; // percentage, but value=0 means 100% | |||||
wdata.amplitude_fmt = 100; // percentage, but value=0 means 100% | |||||
break; | break; | ||||
#if HAVE_SONIC_H | #if HAVE_SONIC_H | ||||
case WCMD_SONIC_SPEED: | case WCMD_SONIC_SPEED: | ||||
} | } | ||||
#if HAVE_SONIC_H | #if HAVE_SONIC_H | ||||
/* Speed up the audio samples with libsonic. */ | |||||
// Speed up the audio samples with libsonic. | |||||
static int SpeedUp(short *outbuf, int length_in, int length_out, int end_of_text) | static int SpeedUp(short *outbuf, int length_in, int length_out, int end_of_text) | ||||
{ | { | ||||
if (length_in > 0) { | if (length_in > 0) { | ||||
} | } | ||||
#endif | #endif | ||||
/* Call WavegenFill2, and then speed up the output samples. */ | |||||
// Call WavegenFill2, and then speed up the output samples. | |||||
int WavegenFill(int fill_zeros) | int WavegenFill(int fill_zeros) | ||||
{ | { | ||||
int finished; | int finished; | ||||
out_ptr = p_start + length; | out_ptr = p_start + length; | ||||
if (length >= max_length) | if (length >= max_length) | ||||
finished = 0; // there may be more data to flush | |||||
finished = 0; // there may be more data to flush | |||||
} | } | ||||
#endif | #endif | ||||
return finished; | return finished; |
#include "translate.h" | #include "translate.h" | ||||
extern void Write4Bytes(FILE *f, int value); | extern void Write4Bytes(FILE *f, int value); | ||||
char path_home[N_PATH_HOME]; // this is the espeak-data directory | |||||
char path_home[N_PATH_HOME]; // this is the espeak-data directory | |||||
char filetype[5]; | char filetype[5]; | ||||
char wavefile[200]; | char wavefile[200]; | ||||
if (path[0] != 0) { | if (path[0] != 0) { | ||||
if (strcmp(path, "stdout") == 0) { | if (strcmp(path, "stdout") == 0) { | ||||
#ifdef PLATFORM_WINDOWS | #ifdef PLATFORM_WINDOWS | ||||
// prevent Windows adding 0x0d before 0x0a bytes | |||||
// prevent Windows adding 0x0d before 0x0a bytes | |||||
_setmode(_fileno(stdout), _O_BINARY); | _setmode(_fileno(stdout), _O_BINARY); | ||||
#endif | #endif | ||||
f_wave = stdout; | f_wave = stdout; | ||||
if (((env = getenv("ESPEAK_DATA_PATH")) != NULL) && ((strlen(env)+12) < sizeof(path_home))) { | if (((env = getenv("ESPEAK_DATA_PATH")) != NULL) && ((strlen(env)+12) < sizeof(path_home))) { | ||||
sprintf(path_home, "%s\\espeak-data", env); | sprintf(path_home, "%s\\espeak-data", env); | ||||
if (GetFileLength(path_home) == -2) | if (GetFileLength(path_home) == -2) | ||||
return; // an espeak-data directory exists in the directory specified by environment variable | |||||
return; // an espeak-data directory exists in the directory specified by environment variable | |||||
} | } | ||||
strcpy(path_home, argv0); | strcpy(path_home, argv0); | ||||
if ((p = strrchr(path_home, '\\')) != NULL) { | if ((p = strrchr(path_home, '\\')) != NULL) { | ||||
strcpy(&p[1], "espeak-data"); | strcpy(&p[1], "espeak-data"); | ||||
if (GetFileLength(path_home) == -2) | if (GetFileLength(path_home) == -2) | ||||
return; // an espeak-data directory exists in the same directory as the espeak program | |||||
return; // an espeak-data directory exists in the same directory as the espeak program | |||||
} | } | ||||
// otherwise, look in the Windows Registry | // otherwise, look in the Windows Registry | ||||
if ((env = getenv("ESPEAK_DATA_PATH")) != NULL) { | if ((env = getenv("ESPEAK_DATA_PATH")) != NULL) { | ||||
snprintf(path_home, sizeof(path_home), "%s/espeak-data", env); | snprintf(path_home, sizeof(path_home), "%s/espeak-data", env); | ||||
if (GetFileLength(path_home) == -2) | if (GetFileLength(path_home) == -2) | ||||
return; // an espeak-data directory exists | |||||
return; // an espeak-data directory exists | |||||
} | } | ||||
snprintf(path_home, sizeof(path_home), "%s/espeak-data", getenv("HOME")); | snprintf(path_home, sizeof(path_home), "%s/espeak-data", getenv("HOME")); | ||||
{ | { | ||||
int param; | int param; | ||||
int result; | int result; | ||||
int srate = 22050; // default sample rate | |||||
int srate = 22050; // default sample rate | |||||
// It seems that the wctype functions don't work until the locale has been set | // It seems that the wctype functions don't work until the locale has been set | ||||
// to something other than the default "C". Then, not only Latin1 but also the | // to something other than the default "C". Then, not only Latin1 but also the | ||||
FILE *f_text = NULL; | FILE *f_text = NULL; | ||||
const char *p_text = NULL; | const char *p_text = NULL; | ||||
char *data_path = NULL; // use default path for espeak-data | |||||
char *data_path = NULL; // use default path for espeak-data | |||||
int option_index = 0; | int option_index = 0; | ||||
int c; | int c; | ||||
int speed = 175; | int speed = 175; | ||||
int ix; | int ix; | ||||
char *optarg2; | char *optarg2; | ||||
int amp = 100; // default | |||||
int amp = 100; // default | |||||
int wordgap = 0; | int wordgap = 0; | ||||
int flag_stdin = 0; | int flag_stdin = 0; | ||||
int flag_compile = 0; | int flag_compile = 0; | ||||
option_wordgap = 0; | option_wordgap = 0; | ||||
option_endpause = 1; | option_endpause = 1; | ||||
option_phoneme_input = 1; | option_phoneme_input = 1; | ||||
option_multibyte = espeakCHARS_AUTO; // auto | |||||
option_multibyte = espeakCHARS_AUTO; | |||||
f_trans = stdout; | f_trans = stdout; | ||||
#ifdef NEED_GETOPT | #ifdef NEED_GETOPT | ||||
if (c == '-') { | if (c == '-') { | ||||
if (p[0] == 0) | if (p[0] == 0) | ||||
break; // -- means don't interpret further - as commands | |||||
break; // -- means don't interpret further - as commands | |||||
opt_string = ""; | opt_string = ""; | ||||
for (ix = 0;; ix++) { | for (ix = 0;; ix++) { | ||||
} | } | ||||
#else | #else | ||||
while (true) { | while (true) { | ||||
c = getopt_long(argc, argv, "a:b:f:g:hk:l:p:qs:v:w:xXmz", // NOTE: also change arg_opts to indicate which commands have a numeric value | |||||
c = getopt_long(argc, argv, "a:b:f:g:hk:l:p:qs:v:w:xXmz", // NOTE: also change arg_opts to indicate which commands have a numeric value | |||||
long_options, &option_index); | long_options, &option_index); | ||||
/* Detect the end of the options. */ | |||||
// Detect the end of the options. | |||||
if (c == -1) | if (c == -1) | ||||
break; | break; | ||||
optarg2 = optarg; | optarg2 = optarg; | ||||
case 'z': | case 'z': | ||||
option_endpause = 0; | option_endpause = 0; | ||||
break; | break; | ||||
case 0x100: // --stdin | |||||
case 0x100: // --stdin | |||||
flag_stdin = 1; | flag_stdin = 1; | ||||
break; | break; | ||||
case 0x105: // --stdout | |||||
case 0x105: // --stdout | |||||
option_waveout = 1; | option_waveout = 1; | ||||
strcpy(wavefile, "stdout"); | strcpy(wavefile, "stdout"); | ||||
break; | break; | ||||
case 0x101: // --compile-debug | |||||
case 0x102: // --compile | |||||
case 0x101: // --compile-debug | |||||
case 0x102: // --compile | |||||
if (optarg2 != NULL) | if (optarg2 != NULL) | ||||
strncpy0(voicename, optarg2, sizeof(voicename)); | strncpy0(voicename, optarg2, sizeof(voicename)); | ||||
flag_compile = c; | flag_compile = c; | ||||
break; | break; | ||||
case 0x103: // --punct | |||||
case 0x103: // --punct | |||||
option_punctuation = 1; | option_punctuation = 1; | ||||
if (optarg2 != NULL) { | if (optarg2 != NULL) { | ||||
ix = 0; | ix = 0; | ||||
option_punctuation = 2; | option_punctuation = 2; | ||||
} | } | ||||
break; | break; | ||||
case 0x104: // --voices | |||||
case 0x104: // --voices | |||||
init_path(argv[0], data_path); | init_path(argv[0], data_path); | ||||
DisplayVoices(stdout, optarg2); | DisplayVoices(stdout, optarg2); | ||||
exit(0); | exit(0); | ||||
case 0x106: // -- split | |||||
case 0x106: // -- split | |||||
if (optarg2 == NULL) | if (optarg2 == NULL) | ||||
samples_split = 30; // default 30 minutes | |||||
samples_split = 30; // default 30 minutes | |||||
else | else | ||||
samples_split = atoi(optarg2); | samples_split = atoi(optarg2); | ||||
break; | break; | ||||
case 0x107: // --path | |||||
case 0x107: // --path | |||||
data_path = optarg2; | data_path = optarg2; | ||||
break; | break; | ||||
case 0x108: // --phonout | |||||
case 0x108: // --phonout | |||||
if ((f_trans = fopen(optarg2, "w")) == NULL) { | if ((f_trans = fopen(optarg2, "w")) == NULL) { | ||||
fprintf(stderr, "Can't write to: %s\n", optarg2); | fprintf(stderr, "Can't write to: %s\n", optarg2); | ||||
f_trans = stderr; | f_trans = stderr; | ||||
} | } | ||||
break; | break; | ||||
case 0x109: // --pho | |||||
case 0x109: // --pho | |||||
phoneme_options |= espeakPHONEMES_MBROLA; | phoneme_options |= espeakPHONEMES_MBROLA; | ||||
break; | break; | ||||
case 0x10a: // --ipa | |||||
case 0x10a: // --ipa | |||||
phoneme_options |= espeakPHONEMES_IPA; | phoneme_options |= espeakPHONEMES_IPA; | ||||
if (optarg2 != NULL) { | if (optarg2 != NULL) { | ||||
// deprecated and obsolete | // deprecated and obsolete | ||||
phoneme_options |= espeakPHONEMES_TIE; | phoneme_options |= espeakPHONEMES_TIE; | ||||
break; | break; | ||||
case 3: | case 3: | ||||
phonemes_separator = 0x200d; // ZWJ | |||||
phonemes_separator = 0x200d; // ZWJ | |||||
phoneme_options |= espeakPHONEMES_TIE; | phoneme_options |= espeakPHONEMES_TIE; | ||||
break; | break; | ||||
} | } | ||||
} | } | ||||
break; | break; | ||||
case 0x10b: // --version | |||||
case 0x10b: // --version | |||||
init_path(argv[0], data_path); | init_path(argv[0], data_path); | ||||
printf("speak text-to-speech: %s Data at: %s\n", version_string, path_home); | printf("speak text-to-speech: %s Data at: %s\n", version_string, path_home); | ||||
exit(0); | exit(0); | ||||
case 0x10c: // --sep | |||||
case 0x10c: // --sep | |||||
phoneme_options |= espeakPHONEMES_SHOW; | phoneme_options |= espeakPHONEMES_SHOW; | ||||
if (optarg2 == 0) | if (optarg2 == 0) | ||||
phonemes_separator = ' '; | phonemes_separator = ' '; | ||||
else | else | ||||
utf8_in(&phonemes_separator, optarg2); | utf8_in(&phonemes_separator, optarg2); | ||||
if (phonemes_separator == 'z') | if (phonemes_separator == 'z') | ||||
phonemes_separator = 0x200c; // ZWNJ | |||||
phonemes_separator = 0x200c; // ZWNJ | |||||
break; | break; | ||||
case 0x10d: // --tie | |||||
case 0x10d: // --tie | |||||
phoneme_options |= (espeakPHONEMES_SHOW | espeakPHONEMES_TIE); | phoneme_options |= (espeakPHONEMES_SHOW | espeakPHONEMES_TIE); | ||||
if (optarg2 == 0) | if (optarg2 == 0) | ||||
phonemes_separator = 0x0361; // default: combining-double-inverted-breve | |||||
phonemes_separator = 0x0361; // default: combining-double-inverted-breve | |||||
else | else | ||||
utf8_in(&phonemes_separator, optarg2); | utf8_in(&phonemes_separator, optarg2); | ||||
if (phonemes_separator == 'z') | if (phonemes_separator == 'z') | ||||
phonemes_separator = 0x200d; // ZWJ | |||||
phonemes_separator = 0x200d; // ZWJ | |||||
break; | break; | ||||
default: | default: | ||||
exit(0); | exit(0); | ||||
#ifdef PLATFORM_DOS | #ifdef PLATFORM_DOS | ||||
char path_dsource[sizeof(path_home)+20]; | char path_dsource[sizeof(path_home)+20]; | ||||
strcpy(path_dsource, path_home); | strcpy(path_dsource, path_home); | ||||
path_dsource[strlen(path_home)-11] = 0; // remove "espeak-data" from the end | |||||
path_dsource[strlen(path_home)-11] = 0; // remove "espeak-data" from the end | |||||
strcat(path_dsource, "dictsource\\"); | strcat(path_dsource, "dictsource\\"); | ||||
CompileDictionary(path_dsource, dictionary_name, NULL, NULL, flag_compile & 0x1); | CompileDictionary(path_dsource, dictionary_name, NULL, NULL, flag_compile & 0x1); | ||||
#else | #else | ||||
#ifdef PLATFORM_WINDOWS | #ifdef PLATFORM_WINDOWS | ||||
char path_dsource[sizeof(path_home)+20]; | char path_dsource[sizeof(path_home)+20]; | ||||
strcpy(path_dsource, path_home); | strcpy(path_dsource, path_home); | ||||
path_dsource[strlen(path_home)-11] = 0; // remove "espeak-data" from the end | |||||
path_dsource[strlen(path_home)-11] = 0; // remove "espeak-data" from the end | |||||
strcat(path_dsource, "dictsource\\"); | strcat(path_dsource, "dictsource\\"); | ||||
CompileDictionary(path_dsource, dictionary_name, NULL, NULL, flag_compile & 0x1); | CompileDictionary(path_dsource, dictionary_name, NULL, NULL, flag_compile & 0x1); | ||||
#else | #else | ||||
option_phonemes = phoneme_options | (phonemes_separator << 8); | option_phonemes = phoneme_options | (phonemes_separator << 8); | ||||
if (pitch_adjustment != 50) { | |||||
if (pitch_adjustment != 50) | |||||
SetParameter(espeakPITCH, pitch_adjustment, 0); | SetParameter(espeakPITCH, pitch_adjustment, 0); | ||||
} | |||||
DoVoiceChange(voice); | DoVoiceChange(voice); | ||||
if (filename[0] == 0) { | if (filename[0] == 0) { | ||||
} else { | } else { | ||||
f_text = stdin; | f_text = stdin; | ||||
if (flag_stdin == 0) | if (flag_stdin == 0) | ||||
option_linelength = -1; // single input lines on stdin | |||||
option_linelength = -1; // single input lines on stdin | |||||
} | } | ||||
} else | } else | ||||
f_text = fopen(filename, "r"); | f_text = fopen(filename, "r"); | ||||
for (;;) { | for (;;) { | ||||
if (WavegenFile() != 0) { | if (WavegenFile() != 0) { | ||||
if (ix == 0) | if (ix == 0) | ||||
break; // finished, wavegen command queue is empty | |||||
break; // finished, wavegen command queue is empty | |||||
} | } | ||||
if (Generate(phoneme_list, &n_phoneme_list, 1) == 0) | if (Generate(phoneme_list, &n_phoneme_list, 1) == 0) | ||||
// NOTE: if nanosleep() isn't recognised on your system, try replacing | // NOTE: if nanosleep() isn't recognised on your system, try replacing | ||||
// this by sleep(1); | // this by sleep(1); | ||||
#ifdef PLATFORM_WINDOWS | #ifdef PLATFORM_WINDOWS | ||||
Sleep(300); // 0.3s | |||||
Sleep(300); // 0.3s | |||||
#else | #else | ||||
#ifdef USE_NANOSLEEP | #ifdef USE_NANOSLEEP | ||||
struct timespec period; | struct timespec period; | ||||
struct timespec remaining; | struct timespec remaining; | ||||
period.tv_sec = 0; | period.tv_sec = 0; | ||||
period.tv_nsec = 300000000; // 0.3 sec | |||||
period.tv_nsec = 300000000; // 0.3 sec | |||||
nanosleep(&period, &remaining); | nanosleep(&period, &remaining); | ||||
#else | #else | ||||
sleep(1); | sleep(1); | ||||
} | } | ||||
if ((f_trans != stdout) && (f_trans != stderr)) | if ((f_trans != stdout) && (f_trans != stderr)) | ||||
fclose(f_trans); // needed for WinCe | |||||
fclose(f_trans); | |||||
return 0; | return 0; | ||||
} | } |