History

first commit

Alan W Black committed 9 years ago

Commit e7e4868No parent (first commit)510 files · +746275 −-5Browse files

Showing the changes to sapi/FliteTTSEngineObj/FliteTTSEngineObj.cpp (1 of 510 files). Show all

Unified · Split

sapi/FliteTTSEngineObj/FliteTTSEngineObj.cpp+719−0
@@ -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
44extern "C" {
45#include "flite.h"
46#include "cst_tokenstream.h"
47};
48
49static cst_val *sapi_tokentowords(cst_item *i);
50
51STDMETHODIMP
52CFliteTTSEngineObj::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
81STDMETHODIMP
82CFliteTTSEngineObj::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
113int
114CFliteTTSEngineObj::phoneme_len(cst_item *s)
115{
116 return (short) ((item_feat_float(s, "end")
117 - ffeature_float(s, "p.end")) * 1000);
118}
119
120int
121CFliteTTSEngineObj::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
140int
141CFliteTTSEngineObj::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
158void
159CFliteTTSEngineObj::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
259void
260CFliteTTSEngineObj::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
275void
276CFliteTTSEngineObj::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
305void
306CFliteTTSEngineObj::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 */
313static int
314utt_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
348static cst_val *
349sapi_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
365static cst_tokenstream *
366my_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
381static cst_item *
382append_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)) */
397static 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};
420static const double *sapi_ratetab = sapi_ratetab_foo + 10;
421
422/* Each step is prev * (2**(1/24)) */
423static 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};
446static const double *sapi_pitchtab = sapi_pitchtab_foo + 10;
447
448static double
449convert_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
458static double
459convert_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
474static void
475set_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
492void
493CFliteTTSEngineObj::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 }
542pod_bay_doors: /* Open the POD bay doors, KAL... */
543 ts_close(ts);
544}
545
546void
547CFliteTTSEngineObj::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
570void
571CFliteTTSEngineObj::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
587void
588CFliteTTSEngineObj::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
609void
610CFliteTTSEngineObj::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
628void
629CFliteTTSEngineObj::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
664STDMETHODIMP
665CFliteTTSEngineObj::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}

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t