Showing the changes to sapi/FliteTTSEngineObj/FliteTTSEngineObj.cpp (1 of 510 files). Show all
Unified · Split
| @@ -0,0 +1,719 @@ | |||
| 1 | /*************************************************************************/ | ||
| 2 | /* */ | ||
| 3 | /* Cepstral, LLC */ | ||
| 4 | /* Copyright (c) 2001 */ | ||
| 5 | /* All Rights Reserved. */ | ||
| 6 | /* */ | ||
| 7 | /* Permission is hereby granted, free of charge, to use and distribute */ | ||
| 8 | /* this software and its documentation without restriction, including */ | ||
| 9 | /* without limitation the rights to use, copy, modify, merge, publish, */ | ||
| 10 | /* distribute, sublicense, and/or sell copies of this work, and to */ | ||
| 11 | /* permit persons to whom this work is furnished to do so, subject to */ | ||
| 12 | /* the following conditions: */ | ||
| 13 | /* 1. The code must retain the above copyright notice, this list of */ | ||
| 14 | /* conditions and the following disclaimer. */ | ||
| 15 | /* 2. Any modifications must be clearly marked as such. */ | ||
| 16 | /* 3. Original authors' names are not deleted. */ | ||
| 17 | /* 4. The authors' names are not used to endorse or promote products */ | ||
| 18 | /* derived from this software without specific prior written */ | ||
| 19 | /* permission. */ | ||
| 20 | /* */ | ||
| 21 | /* CEPSTRAL, LLC AND THE CONTRIBUTORS TO THIS WORK DISCLAIM ALL */ | ||
| 22 | /* WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED */ | ||
| 23 | /* WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL */ | ||
| 24 | /* CEPSTRAL, LLC NOR THE CONTRIBUTORS BE LIABLE FOR ANY SPECIAL, */ | ||
| 25 | /* INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER */ | ||
| 26 | /* RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION */ | ||
| 27 | /* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR */ | ||
| 28 | /* IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ | ||
| 29 | /* */ | ||
| 30 | /*************************************************************************/ | ||
| 31 | /* Author: David Huggins-Daines (dhd@cepstral.com) */ | ||
| 32 | /* Date: December 2001 */ | ||
| 33 | /*************************************************************************/ | ||
| 34 | /* */ | ||
| 35 | /* FliteTTSEngineObj.cpp: implementation of SAPI interface to Flite */ | ||
| 36 | /* */ | ||
| 37 | /*************************************************************************/ | ||
| 38 | /* Updated December 2015: Alok Parlikar <alok@cobaltspeech.com> */ | ||
| 39 | /* to work with Flite-2.0.0. */ | ||
| 40 | /*************************************************************************/ | ||
| 41 | |||
| 42 | #include "FliteTTSEngineObj.h" | ||
| 43 | |||
| 44 | extern "C" { | ||
| 45 | #include "flite.h" | ||
| 46 | #include "cst_tokenstream.h" | ||
| 47 | }; | ||
| 48 | |||
| 49 | static cst_val *sapi_tokentowords(cst_item *i); | ||
| 50 | |||
| 51 | STDMETHODIMP | ||
| 52 | CFliteTTSEngineObj::SetObjectToken(ISpObjectToken* pToken) | ||
| 53 | { | ||
| 54 | USES_CONVERSION; | ||
| 55 | CSpDynamicString voxdir; | ||
| 56 | char *avoxdir; | ||
| 57 | HRESULT hr; | ||
| 58 | const cst_val *ttwv; | ||
| 59 | |||
| 60 | if (!SUCCEEDED(hr = SpGenericSetObjectToken(pToken, vox_token))) | ||
| 61 | return hr; | ||
| 62 | if (!SUCCEEDED(vox_token->GetStringValue(L"voxdir", &voxdir))) | ||
| 63 | avoxdir = NULL; /* It isn't always necessary */ | ||
| 64 | else | ||
| 65 | avoxdir = W2A(voxdir); | ||
| 66 | |||
| 67 | if (curr_vox) | ||
| 68 | (*unregfunc)(curr_vox); | ||
| 69 | if ((curr_vox = (*regfunc)(avoxdir)) == NULL) | ||
| 70 | return E_INVALIDARG; /* or something */ | ||
| 71 | |||
| 72 | if ((ttwv = feat_val(curr_vox->features, "tokentowords_func"))) { | ||
| 73 | feat_set(curr_vox->features, "old_tokentowords_func", ttwv); | ||
| 74 | feat_set(curr_vox->features, "tokentowords_func", | ||
| 75 | itemfunc_val(sapi_tokentowords)); | ||
| 76 | } | ||
| 77 | |||
| 78 | return hr; | ||
| 79 | } | ||
| 80 | |||
| 81 | STDMETHODIMP | ||
| 82 | CFliteTTSEngineObj::GetOutputFormat(const GUID * pTargetFormatId, | ||
| 83 | const WAVEFORMATEX * pTargetWaveFormatEx, | ||
| 84 | GUID * pDesiredFormatId, | ||
| 85 | WAVEFORMATEX ** ppCoMemDesiredWaveFormatEx) | ||
| 86 | { | ||
| 87 | WAVEFORMATEX *wfx; | ||
| 88 | |||
| 89 | if (curr_vox == NULL) | ||
| 90 | return SpConvertStreamFormatEnum(SPSF_16kHz16BitMono, | ||
| 91 | pDesiredFormatId, | ||
| 92 | ppCoMemDesiredWaveFormatEx); | ||
| 93 | |||
| 94 | /* Just return the default format for the voice, since SAPI | ||
| 95 | can do all the necessary conversion. (our resampling | ||
| 96 | algorithm is a lot better than SAPI's, though...) */ | ||
| 97 | if ((wfx = (WAVEFORMATEX *)CoTaskMemAlloc(sizeof(*wfx))) == NULL) | ||
| 98 | return E_OUTOFMEMORY; | ||
| 99 | memset(wfx, 0, sizeof(*wfx)); | ||
| 100 | wfx->nChannels = 1; | ||
| 101 | wfx->nSamplesPerSec = get_param_int(curr_vox->features, "sample_rate", 16000); | ||
| 102 | wfx->wFormatTag = WAVE_FORMAT_PCM; | ||
| 103 | wfx->wBitsPerSample = 16; | ||
| 104 | wfx->nBlockAlign = wfx->nChannels*wfx->wBitsPerSample/8; | ||
| 105 | wfx->nAvgBytesPerSec = wfx->nSamplesPerSec*wfx->nBlockAlign; | ||
| 106 | |||
| 107 | *pDesiredFormatId = SPDFID_WaveFormatEx; | ||
| 108 | *ppCoMemDesiredWaveFormatEx = wfx; | ||
| 109 | |||
| 110 | return S_OK; | ||
| 111 | } | ||
| 112 | |||
| 113 | int | ||
| 114 | CFliteTTSEngineObj::phoneme_len(cst_item *s) | ||
| 115 | { | ||
| 116 | return (short) ((item_feat_float(s, "end") | ||
| 117 | - ffeature_float(s, "p.end")) * 1000); | ||
| 118 | } | ||
| 119 | |||
| 120 | int | ||
| 121 | CFliteTTSEngineObj::viseme_len(cst_item *s) | ||
| 122 | { | ||
| 123 | int len, vis, v; | ||
| 124 | |||
| 125 | if (!visemefunc) | ||
| 126 | return 0; | ||
| 127 | |||
| 128 | vis = (*visemefunc)(s); | ||
| 129 | len = phoneme_len(s); | ||
| 130 | for (s = item_next(s); s; s = item_next(s)) { | ||
| 131 | v = (*visemefunc)(s); | ||
| 132 | if (v != vis) | ||
| 133 | break; | ||
| 134 | len += phoneme_len(s); | ||
| 135 | } | ||
| 136 | |||
| 137 | return len; | ||
| 138 | } | ||
| 139 | |||
| 140 | int | ||
| 141 | CFliteTTSEngineObj::next_viseme(cst_item *s) | ||
| 142 | { | ||
| 143 | int vis, v = 0; | ||
| 144 | |||
| 145 | if (!visemefunc) | ||
| 146 | return 0; | ||
| 147 | |||
| 148 | vis = (*visemefunc)(s); | ||
| 149 | for (s = item_next(s); s; s = item_next(s)) { | ||
| 150 | v = (*visemefunc)(s); | ||
| 151 | if (v != vis) | ||
| 152 | break; | ||
| 153 | } | ||
| 154 | |||
| 155 | return v; | ||
| 156 | } | ||
| 157 | |||
| 158 | void | ||
| 159 | CFliteTTSEngineObj::send_item_events() | ||
| 160 | { | ||
| 161 | USES_CONVERSION; | ||
| 162 | cst_item *i; | ||
| 163 | int sps; | ||
| 164 | |||
| 165 | sps = get_param_int(curr_vox->features, "sample_rate", 16000); | ||
| 166 | for (i = relation_head(utt_relation(curr_utt, "Segment")); | ||
| 167 | i; i = item_next(i)) | ||
| 168 | { | ||
| 169 | int offset; | ||
| 170 | SPEVENT evt; | ||
| 171 | cst_item *token; | ||
| 172 | const cst_val *bmark; | ||
| 173 | |||
| 174 | offset = (int) (ffeature_float(i, "p.end") * sps * sizeof(short)); | ||
| 175 | token = path_to_item(i, "R:SylStructure.parent.parent.R:Token.parent"); | ||
| 176 | |||
| 177 | /* Word boundaries */ | ||
| 178 | if (token | ||
| 179 | && item_parent(item_as(i, "SylStructure")) | ||
| 180 | && item_prev(item_as(i, "SylStructure")) == NULL | ||
| 181 | && item_prev(item_parent(item_as(i, "SylStructure"))) == NULL | ||
| 182 | && (token != | ||
| 183 | path_to_item(i, "R:SylStructure.parent.parent.p." | ||
| 184 | "R:Token.parent"))) | ||
| 185 | { | ||
| 186 | cst_item *prev; | ||
| 187 | |||
| 188 | if ((prev = item_prev(token)) | ||
| 189 | && (bmark = item_feat(prev, "bookmark"))) | ||
| 190 | { | ||
| 191 | SPEVENT fooevt; /* SpClearEvent will segfault... */ | ||
| 192 | fooevt.eEventId = SPEI_TTS_BOOKMARK; | ||
| 193 | fooevt.elParamType = SPET_LPARAM_IS_STRING; | ||
| 194 | fooevt.ullAudioStreamOffset = bcount + offset; | ||
| 195 | fooevt.wParam = atol(val_string(bmark)); | ||
| 196 | fooevt.lParam = (LPARAM) A2W(val_string(bmark)); | ||
| 197 | site->AddEvents(&fooevt, 1); | ||
| 198 | } | ||
| 199 | SpClearEvent(&evt); | ||
| 200 | evt.eEventId = SPEI_WORD_BOUNDARY; | ||
| 201 | evt.elParamType = SPET_LPARAM_IS_UNDEFINED; | ||
| 202 | evt.ullAudioStreamOffset = bcount + offset; | ||
| 203 | evt.wParam = item_feat_int(token, "token_length"); | ||
| 204 | evt.lParam = item_feat_int(token, "token_pos"); | ||
| 205 | site->AddEvents(&evt, 1); | ||
| 206 | } | ||
| 207 | |||
| 208 | /* Visemes */ | ||
| 209 | if (visemefunc | ||
| 210 | && ((*visemefunc)(i) | ||
| 211 | != (*visemefunc)(item_prev(i)))) { | ||
| 212 | SpClearEvent(&evt); | ||
| 213 | evt.eEventId = SPEI_VISEME; | ||
| 214 | evt.elParamType = SPET_LPARAM_IS_UNDEFINED; | ||
| 215 | evt.ullAudioStreamOffset = bcount + offset; | ||
| 216 | evt.wParam = MAKEWPARAM(next_viseme(i), viseme_len(i)); | ||
| 217 | evt.lParam = MAKELPARAM((*visemefunc)(i), (*featurefunc)(i)); | ||
| 218 | site->AddEvents(&evt, 1); | ||
| 219 | } | ||
| 220 | |||
| 221 | /* Bookmarks: | ||
| 222 | |||
| 223 | If the previous token is a bookmark and this is the | ||
| 224 | first segment in the first word of the current | ||
| 225 | token, then send an event. (we did that already) | ||
| 226 | |||
| 227 | If this is the last segment in the utterance | ||
| 228 | (probably a pause) and the last token in the | ||
| 229 | utterance is a bookmark, then send an event. */ | ||
| 230 | |||
| 231 | if (item_next(i) == NULL | ||
| 232 | && (bmark = item_feat(relation_tail(utt_relation(curr_utt, "Token")), | ||
| 233 | "bookmark"))) | ||
| 234 | { | ||
| 235 | SPEVENT fooevt; /* SpClearEvent will segfault... */ | ||
| 236 | fooevt.eEventId = SPEI_TTS_BOOKMARK; | ||
| 237 | fooevt.elParamType = SPET_LPARAM_IS_STRING; | ||
| 238 | fooevt.ullAudioStreamOffset = bcount + offset; | ||
| 239 | fooevt.wParam = atol(val_string(bmark)); | ||
| 240 | fooevt.lParam = (LPARAM) A2W(val_string(bmark)); | ||
| 241 | site->AddEvents(&fooevt, 1); | ||
| 242 | } | ||
| 243 | |||
| 244 | /* Phonemes */ | ||
| 245 | if (phonemefunc) { | ||
| 246 | SpClearEvent(&evt); | ||
| 247 | evt.eEventId = SPEI_PHONEME; | ||
| 248 | evt.elParamType = SPET_LPARAM_IS_UNDEFINED; | ||
| 249 | evt.ullAudioStreamOffset = bcount + offset; | ||
| 250 | evt.wParam = MAKEWPARAM((*phonemefunc)(item_next(i)), | ||
| 251 | phoneme_len(i)); | ||
| 252 | evt.lParam = MAKELPARAM((*phonemefunc)(i), | ||
| 253 | (*featurefunc)(i)); | ||
| 254 | site->AddEvents(&evt, 1); | ||
| 255 | } | ||
| 256 | } | ||
| 257 | } | ||
| 258 | |||
| 259 | void | ||
| 260 | CFliteTTSEngineObj::send_sentence_event(int fpos) | ||
| 261 | { | ||
| 262 | SPEVENT evt; | ||
| 263 | |||
| 264 | SpClearEvent(&evt); | ||
| 265 | evt.eEventId = SPEI_SENTENCE_BOUNDARY; | ||
| 266 | evt.elParamType = SPET_LPARAM_IS_UNDEFINED; | ||
| 267 | evt.ullAudioStreamOffset = bcount; | ||
| 268 | evt.lParam = sentence_start; | ||
| 269 | evt.wParam = fpos - sentence_start; | ||
| 270 | site->AddEvents(&evt, 1); | ||
| 271 | |||
| 272 | sentence_start = fpos; | ||
| 273 | } | ||
| 274 | |||
| 275 | void | ||
| 276 | CFliteTTSEngineObj::synth_one_utt() | ||
| 277 | { | ||
| 278 | cst_wave *wav; | ||
| 279 | ULONG b; | ||
| 280 | |||
| 281 | if (!(curr_utt && tok_rel && relation_head(tok_rel))) | ||
| 282 | return; | ||
| 283 | |||
| 284 | /* Link the vox features into the utterance features so the voice */ | ||
| 285 | /* features will be searched too (after the utt ones) */ | ||
| 286 | feat_link_into(curr_vox->features, curr_utt->features); | ||
| 287 | feat_link_into(curr_vox->ffunctions, curr_utt->ffunctions); | ||
| 288 | |||
| 289 | if (curr_vox->utt_init) | ||
| 290 | curr_vox->utt_init(curr_utt, curr_vox); | ||
| 291 | utt_synth_tokens(curr_utt); | ||
| 292 | wav = utt_wave(curr_utt); | ||
| 293 | send_item_events(); | ||
| 294 | if (volume != 100) | ||
| 295 | cst_wave_rescale(wav, volume * 65536 / 100); | ||
| 296 | site->Write(wav->samples, | ||
| 297 | wav->num_samples * sizeof(*wav->samples), | ||
| 298 | &b); | ||
| 299 | bcount += wav->num_samples * sizeof(*wav->samples); | ||
| 300 | delete_utterance(curr_utt); | ||
| 301 | curr_utt = NULL; | ||
| 302 | tok_rel = NULL; | ||
| 303 | } | ||
| 304 | |||
| 305 | void | ||
| 306 | CFliteTTSEngineObj::start_new_utt() | ||
| 307 | { | ||
| 308 | curr_utt = new_utterance(); | ||
| 309 | tok_rel = utt_relation_create(curr_utt, "Token"); | ||
| 310 | } | ||
| 311 | |||
| 312 | /* snarfed from flite.c */ | ||
| 313 | static int | ||
| 314 | utt_break(cst_tokenstream *ts, const char *token, cst_relation *tokens) | ||
| 315 | { | ||
| 316 | /* This is English (and some other latin based languages) */ | ||
| 317 | /* so it shouldn't be here */ | ||
| 318 | const char *postpunct; | ||
| 319 | const char *ltoken; | ||
| 320 | cst_item *tail = relation_tail(tokens); | ||
| 321 | |||
| 322 | if (!item_feat_present(tail, "punc")) | ||
| 323 | return FALSE; | ||
| 324 | |||
| 325 | ltoken = item_name(tail); | ||
| 326 | postpunct = item_feat_string(tail, "punc"); | ||
| 327 | |||
| 328 | if (strchr(ts->whitespace,'\n') != cst_strrchr(ts->whitespace,'\n')) | ||
| 329 | /* contains two new lines */ | ||
| 330 | return TRUE; | ||
| 331 | else if (strchr(postpunct,':') || | ||
| 332 | strchr(postpunct,'?') || | ||
| 333 | strchr(postpunct,'!')) | ||
| 334 | return TRUE; | ||
| 335 | else if (strchr(postpunct,'.') && | ||
| 336 | (strlen(ts->whitespace) > 1) && | ||
| 337 | strchr("ABCDEFGHIJKLMNOPQRSTUVWXYZ",token[0])) | ||
| 338 | return TRUE; | ||
| 339 | else if (strchr(postpunct,'.') && | ||
| 340 | /* next word starts with a capital */ | ||
| 341 | strchr("ABCDEFGHIJKLMNOPQRSTUVWXYZ",token[0]) && | ||
| 342 | !strchr("ABCDEFGHIJKLMNOPQRSTUVWXYZ",ltoken[strlen(ltoken)-1])) | ||
| 343 | return TRUE; | ||
| 344 | else | ||
| 345 | return FALSE; | ||
| 346 | } | ||
| 347 | |||
| 348 | static cst_val * | ||
| 349 | sapi_tokentowords(cst_item *i) | ||
| 350 | { | ||
| 351 | const cst_val *ttwv; | ||
| 352 | |||
| 353 | if (item_feat_present(i, "phones")) | ||
| 354 | return default_tokentowords(i); | ||
| 355 | if (item_feat_present(i, "bookmark")) | ||
| 356 | return NULL; | ||
| 357 | |||
| 358 | ttwv = utt_feat_val(item_utt(i), "old_tokentowords_func"); | ||
| 359 | if (ttwv) | ||
| 360 | return (*val_itemfunc(ttwv))(i); | ||
| 361 | else | ||
| 362 | return default_tokentowords(i); | ||
| 363 | } | ||
| 364 | |||
| 365 | static cst_tokenstream * | ||
| 366 | my_ts_open_string(cst_utterance *utt, const char *text) | ||
| 367 | { | ||
| 368 | cst_tokenstream *ts; | ||
| 369 | |||
| 370 | ts = ts_open_string(text, | ||
| 371 | get_param_string(utt->features,"text_whitespace", cst_ts_default_whitespacesymbols), | ||
| 372 | get_param_string(utt->features, "text_singlecharsymbols",cst_ts_default_singlecharsymbols), | ||
| 373 | get_param_string(utt->features, "text_prepunctuation",cst_ts_default_prepunctuationsymbols), | ||
| 374 | get_param_string(utt->features, "text_postpunctuation", cst_ts_default_postpunctuationsymbols) | ||
| 375 | ); | ||
| 376 | |||
| 377 | |||
| 378 | return ts; | ||
| 379 | } | ||
| 380 | |||
| 381 | static cst_item * | ||
| 382 | append_new_token(cst_tokenstream *ts, const char *token, cst_relation *rel) | ||
| 383 | { | ||
| 384 | cst_item *t; | ||
| 385 | |||
| 386 | t = relation_append(rel, NULL); | ||
| 387 | item_set_string(t, "name", token); | ||
| 388 | if (ts) { | ||
| 389 | item_set_string(t, "whitespace", ts->whitespace); | ||
| 390 | item_set_string(t, "prepunctuation", ts->prepunctuation); | ||
| 391 | item_set_string(t, "punc", ts->postpunctuation); | ||
| 392 | } | ||
| 393 | return t; | ||
| 394 | } | ||
| 395 | |||
| 396 | /* Each step is prev * (10**(1/10)) */ | ||
| 397 | static const double sapi_ratetab_foo[21] = { | ||
| 398 | 3, | ||
| 399 | 2.68787537952229, | ||
| 400 | 2.40822468528069, | ||
| 401 | 2.15766927997459, | ||
| 402 | 1.93318204493176, | ||
| 403 | 1.73205080756888, | ||
| 404 | 1.55184557391536, | ||
| 405 | 1.39038917031591, | ||
| 406 | 1.24573093961552, | ||
| 407 | 1.11612317403391, | ||
| 408 | 1, | ||
| 409 | 0.895958459840763, | ||
| 410 | 0.802741561760231, | ||
| 411 | 0.719223093324865, | ||
| 412 | 0.644394014977255, | ||
| 413 | 0.577350269189626, | ||
| 414 | 0.517281857971787, | ||
| 415 | 0.46346305677197, | ||
| 416 | 0.415243646538506, | ||
| 417 | 0.372041058011302, | ||
| 418 | 0.333333333333333 | ||
| 419 | }; | ||
| 420 | static const double *sapi_ratetab = sapi_ratetab_foo + 10; | ||
| 421 | |||
| 422 | /* Each step is prev * (2**(1/24)) */ | ||
| 423 | static const double sapi_pitchtab_foo[21] = { | ||
| 424 | 1.33333333333333, | ||
| 425 | 1.29537592153814, | ||
| 426 | 1.25849908357559, | ||
| 427 | 1.22267205760623, | ||
| 428 | 1.18786495752045, | ||
| 429 | 1.15404874800819, | ||
| 430 | 1.12119522033829, | ||
| 431 | 1.0892769688274, | ||
| 432 | 1.0582673679788, | ||
| 433 | 1.02814055027196, | ||
| 434 | 1.0, | ||
| 435 | 0.970435455228124, | ||
| 436 | 0.942809041582063, | ||
| 437 | 0.915969098305392, | ||
| 438 | 0.889893236113355, | ||
| 439 | 0.864559703100672, | ||
| 440 | 0.839947366596581, | ||
| 441 | 0.816035695536436, | ||
| 442 | 0.792804743335147, | ||
| 443 | 0.770235131248181, | ||
| 444 | 0.75 | ||
| 445 | }; | ||
| 446 | static const double *sapi_pitchtab = sapi_pitchtab_foo + 10; | ||
| 447 | |||
| 448 | static double | ||
| 449 | convert_sapi_rate(int r) | ||
| 450 | { | ||
| 451 | if (r < -10) | ||
| 452 | r = -10; | ||
| 453 | else if (r > 10) | ||
| 454 | r = 10; | ||
| 455 | return sapi_ratetab[r]; | ||
| 456 | } | ||
| 457 | |||
| 458 | static double | ||
| 459 | convert_sapi_pitch(int p) | ||
| 460 | { | ||
| 461 | if (p == -24) | ||
| 462 | return 0.5; | ||
| 463 | else if (p == 24) | ||
| 464 | return 2.0; | ||
| 465 | else { | ||
| 466 | if (p < -10) | ||
| 467 | p = -10; | ||
| 468 | else if (p > 10) | ||
| 469 | p = 10; | ||
| 470 | return sapi_pitchtab[p]; | ||
| 471 | } | ||
| 472 | } | ||
| 473 | |||
| 474 | static void | ||
| 475 | set_local_prosody(cst_item *t, const SPVSTATE *st) | ||
| 476 | { | ||
| 477 | int r, p; | ||
| 478 | |||
| 479 | if (st->EmphAdj) /* This doesn't really work, though */ | ||
| 480 | item_set_int(t, "EMPH", st->EmphAdj); | ||
| 481 | if ((r = st->RateAdj)) | ||
| 482 | item_set_float(t, "local_duration_stretch", | ||
| 483 | convert_sapi_rate(r)); | ||
| 484 | if ((p = st->PitchAdj.MiddleAdj)) | ||
| 485 | item_set_float(t, "local_f0_shift", | ||
| 486 | convert_sapi_pitch(p)); | ||
| 487 | /* We could do something with RangeAdj, but SAPI apparently | ||
| 488 | doesn't use it yet and the documentation doesn't say what | ||
| 489 | the value passed in it would mean, anyway. */ | ||
| 490 | } | ||
| 491 | |||
| 492 | void | ||
| 493 | CFliteTTSEngineObj::speak_frag() | ||
| 494 | { | ||
| 495 | cst_tokenstream *ts; | ||
| 496 | const char *token; | ||
| 497 | cst_item *t; | ||
| 498 | char *text; | ||
| 499 | size_t len; | ||
| 500 | |||
| 501 | // Sai Krishna + Tim 13 March 2017 | ||
| 502 | |||
| 503 | //len = wcstombs(NULL, curr_frag->pTextStart, curr_frag->ulTextLen); | ||
| 504 | //text = cst_alloc(char, len+1); | ||
| 505 | //wcstombs(text, curr_frag->pTextStart, curr_frag->ulTextLen); | ||
| 506 | |||
| 507 | len = WideCharToMultiByte(CP_UTF8, 0, curr_frag->pTextStart, curr_frag->ulTextLen, NULL, 0, NULL, NULL); | ||
| 508 | text = cst_alloc(char, len + 1); | ||
| 509 | // WideCharToMultiByte(CP_UTF8, 0, curr_frag->pTextStart, curr_frag->ulTextLen, text, len, NULL, NULL); | ||
| 510 | text[len] = 0x00; | ||
| 511 | |||
| 512 | ///////////////////////// | ||
| 513 | |||
| 514 | ts = my_ts_open_string(curr_utt, text); | ||
| 515 | cst_free(text); | ||
| 516 | |||
| 517 | while (!ts_eof(ts)) { | ||
| 518 | // Sai Krishna 14 June 2017. This needs to be modified. Earlier change to ts_getc() didnt help. | ||
| 519 | token = ts_get(ts); | ||
| 520 | if (relation_head(tok_rel) && utt_break(ts, token, tok_rel)) { | ||
| 521 | get_actions_and_do_them(); | ||
| 522 | if (aborted) | ||
| 523 | goto pod_bay_doors; | ||
| 524 | if (sentence_skip == 0) { | ||
| 525 | send_sentence_event(ts->token_pos | ||
| 526 | + curr_frag->ulTextSrcOffset); | ||
| 527 | synth_one_utt(); | ||
| 528 | start_new_utt(); | ||
| 529 | } else { | ||
| 530 | --sentence_skip; | ||
| 531 | site->CompleteSkip(1); | ||
| 532 | start_new_utt(); | ||
| 533 | } | ||
| 534 | } | ||
| 535 | |||
| 536 | t = append_new_token(ts, token, tok_rel); | ||
| 537 | item_set_int(t, "token_pos", | ||
| 538 | ts->token_pos + curr_frag->ulTextSrcOffset); | ||
| 539 | item_set_int(t, "token_length", strlen(token)); | ||
| 540 | set_local_prosody(t, &curr_frag->State); | ||
| 541 | } | ||
| 542 | pod_bay_doors: /* Open the POD bay doors, KAL... */ | ||
| 543 | ts_close(ts); | ||
| 544 | } | ||
| 545 | |||
| 546 | void | ||
| 547 | CFliteTTSEngineObj::spell_frag() | ||
| 548 | { | ||
| 549 | int i; | ||
| 550 | |||
| 551 | for (i = 0; i < curr_frag->ulTextLen; ++i) { | ||
| 552 | cst_item *t; | ||
| 553 | char *c; | ||
| 554 | int len; | ||
| 555 | |||
| 556 | if (!iswspace(curr_frag->pTextStart[i])) { | ||
| 557 | len = wcstombs(NULL, curr_frag->pTextStart + i, 1); | ||
| 558 | c = cst_alloc(char, len + 1); | ||
| 559 | wcstombs(c, curr_frag->pTextStart + i, 1); | ||
| 560 | |||
| 561 | t = append_new_token(NULL, c, tok_rel); | ||
| 562 | item_set_int(t, "token_pos", curr_frag->ulTextSrcOffset + i); | ||
| 563 | item_set_int(t, "token_length", 1); | ||
| 564 | set_local_prosody(t, &curr_frag->State); | ||
| 565 | cst_free(c); | ||
| 566 | } | ||
| 567 | } | ||
| 568 | } | ||
| 569 | |||
| 570 | void | ||
| 571 | CFliteTTSEngineObj::pronounce_frag() | ||
| 572 | { | ||
| 573 | cst_val *phones; | ||
| 574 | cst_item *t; | ||
| 575 | |||
| 576 | if (!pronouncefunc | ||
| 577 | || (phones = (*pronouncefunc)(curr_frag->State.pPhoneIds)) == NULL) | ||
| 578 | return; | ||
| 579 | |||
| 580 | t = append_new_token(NULL, "", tok_rel); | ||
| 581 | item_set_int(t, "token_pos", curr_frag->ulTextSrcOffset); | ||
| 582 | item_set_int(t, "token_length", curr_frag->ulTextLen); | ||
| 583 | set_local_prosody(t, &curr_frag->State); | ||
| 584 | item_set(t, "phones", phones); | ||
| 585 | } | ||
| 586 | |||
| 587 | void | ||
| 588 | CFliteTTSEngineObj::silence_frag() | ||
| 589 | { | ||
| 590 | int sps, silence; | ||
| 591 | short *buf; | ||
| 592 | ULONG b; | ||
| 593 | |||
| 594 | /* Force an utterance break */ | ||
| 595 | synth_one_utt(); | ||
| 596 | start_new_utt(); | ||
| 597 | |||
| 598 | sps = get_param_int(curr_vox->features, "sample_rate", 16000); | ||
| 599 | silence = sps * curr_frag->State.SilenceMSecs / 1000; | ||
| 600 | buf = cst_alloc(short, 512); | ||
| 601 | while (silence > 0) { | ||
| 602 | b = ((silence < 512) ? silence : 512) * sizeof(*buf); | ||
| 603 | site->Write(buf, b, &b); | ||
| 604 | bcount += b; | ||
| 605 | silence -= (b / sizeof(*buf)); | ||
| 606 | } | ||
| 607 | } | ||
| 608 | |||
| 609 | void | ||
| 610 | CFliteTTSEngineObj::set_bookmark() | ||
| 611 | { | ||
| 612 | size_t len; | ||
| 613 | char *bookmark; | ||
| 614 | cst_item *t; | ||
| 615 | |||
| 616 | len = wcstombs(NULL, curr_frag->pTextStart, curr_frag->ulTextLen); | ||
| 617 | bookmark = cst_alloc(char, len+1); | ||
| 618 | wcstombs(bookmark, curr_frag->pTextStart, curr_frag->ulTextLen); | ||
| 619 | |||
| 620 | t = relation_append(tok_rel, NULL); | ||
| 621 | item_set_string(t, "name", ""); | ||
| 622 | item_set_string(t, "bookmark", bookmark); | ||
| 623 | item_set_int(t, "token_pos", curr_frag->ulTextSrcOffset); | ||
| 624 | item_set_int(t, "token_length", curr_frag->ulTextLen); | ||
| 625 | cst_free(bookmark); | ||
| 626 | } | ||
| 627 | |||
| 628 | void | ||
| 629 | CFliteTTSEngineObj::get_actions_and_do_them() | ||
| 630 | { | ||
| 631 | DWORD actions; | ||
| 632 | |||
| 633 | actions = site->GetActions(); | ||
| 634 | if (actions & SPVES_ABORT) { | ||
| 635 | aborted = 1; | ||
| 636 | return; | ||
| 637 | } | ||
| 638 | if (actions & SPVES_SKIP) { | ||
| 639 | SPVSKIPTYPE stype; | ||
| 640 | long count; | ||
| 641 | |||
| 642 | site->GetSkipInfo(&stype, &count); | ||
| 643 | if (stype == SPVST_SENTENCE) { | ||
| 644 | sentence_skip += count; | ||
| 645 | } | ||
| 646 | } | ||
| 647 | if (actions & SPVES_RATE) { | ||
| 648 | long adj; | ||
| 649 | |||
| 650 | site->GetRate(&adj); | ||
| 651 | utt_set_feat_float(curr_utt, "duration_stretch", | ||
| 652 | convert_sapi_rate(adj)); | ||
| 653 | feat_set_float(curr_vox->features, "duration_stretch", | ||
| 654 | convert_sapi_rate(adj)); | ||
| 655 | } | ||
| 656 | if (actions & SPVES_VOLUME) { | ||
| 657 | USHORT adj; | ||
| 658 | |||
| 659 | site->GetVolume(&adj); | ||
| 660 | volume = adj; | ||
| 661 | } | ||
| 662 | } | ||
| 663 | |||
| 664 | STDMETHODIMP | ||
| 665 | CFliteTTSEngineObj::Speak(DWORD dwSpeakFlags, | ||
| 666 | REFGUID rguidFormatId, | ||
| 667 | const WAVEFORMATEX * pWaveFormatEx, | ||
| 668 | const SPVTEXTFRAG* pTextFragList, | ||
| 669 | ISpTTSEngineSite* pOutputSite) | ||
| 670 | { | ||
| 671 | if (curr_vox == NULL) | ||
| 672 | return E_POINTER; | ||
| 673 | |||
| 674 | start_new_utt(); | ||
| 675 | bcount = 0; | ||
| 676 | sentence_start = 0; | ||
| 677 | sentence_skip = 0; | ||
| 678 | aborted = 0; | ||
| 679 | volume = 100; | ||
| 680 | site = pOutputSite; | ||
| 681 | |||
| 682 | for (curr_frag = pTextFragList; curr_frag && !aborted; | ||
| 683 | curr_frag = curr_frag->pNext) { | ||
| 684 | get_actions_and_do_them(); | ||
| 685 | |||
| 686 | switch (curr_frag->State.eAction) { | ||
| 687 | case SPVA_Speak: | ||
| 688 | speak_frag(); | ||
| 689 | break; | ||
| 690 | case SPVA_SpellOut: | ||
| 691 | spell_frag(); | ||
| 692 | break; | ||
| 693 | case SPVA_Pronounce: | ||
| 694 | pronounce_frag(); | ||
| 695 | break; | ||
| 696 | case SPVA_Silence: | ||
| 697 | silence_frag(); | ||
| 698 | break; | ||
| 699 | case SPVA_Bookmark: | ||
| 700 | set_bookmark(); | ||
| 701 | break; | ||
| 702 | default: | ||
| 703 | break; | ||
| 704 | } | ||
| 705 | } | ||
| 706 | |||
| 707 | if (tok_rel && relation_tail(tok_rel)) | ||
| 708 | send_sentence_event(item_feat_int(relation_tail(tok_rel), "token_pos") | ||
| 709 | + item_feat_int(relation_tail(tok_rel), | ||
| 710 | "token_length")); | ||
| 711 | synth_one_utt(); | ||
| 712 | |||
| 713 | delete_utterance(curr_utt); | ||
| 714 | curr_utt = NULL; | ||
| 715 | tok_rel = NULL; | ||
| 716 | site = NULL; | ||
| 717 | |||
| 718 | return S_OK; | ||
| 719 | } | ||