1 /***************************************************************************
2 * Copyright (C) 2005 to 2014 by Jonathan Duddington *
3 * email: jonsd@users.sourceforge.net *
4 * *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 3 of the License, or *
8 * (at your option) any later version. *
9 * *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
14 * *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, write see: *
17 * <http://www.gnu.org/licenses/>. *
18 ***************************************************************************/
19
20 #include "StdAfx.h"
21
22 #include <stdio.h>
23 #include <ctype.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <wctype.h>
27 #include <wchar.h>
28 #include <math.h>
29
30 #include "speak_lib.h"
31 #include "speech.h"
32 #include "phoneme.h"
33 #include "synthesize.h"
34 #include "voice.h"
35 #include "translate.h"
36
37 #ifdef PLATFORM_POSIX
38 #include <unistd.h>
39 #endif
40
41 #include <locale.h>
42 #define N_XML_BUF 256
43
44
45 static const char *xmlbase = ""; // base URL from <speak>
46
47 static int namedata_ix=0;
48 static int n_namedata = 0;
49 char *namedata = NULL;
50
51
52 static FILE *f_input = NULL;
53 static int ungot_char2 = 0;
54 unsigned char *p_textinput;
55 wchar_t *p_wchar_input;
56 static int ungot_char;
57 static const char *ungot_word = NULL;
58 static int end_of_input;
59
60 static int ignore_text=0; // set during <sub> ... </sub> to ignore text which has been replaced by an alias
61 static int audio_text=0; // set during <audio> ... </audio>
62 static int clear_skipping_text = 0; // next clause should clear the skipping_text flag
63 int count_characters = 0;
64 static int sayas_mode;
65 static int sayas_start;
66 static int ssml_ignore_l_angle = 0;
67
68 // alter tone for announce punctuation or capitals
69 //static const char *tone_punct_on = "\0016T"; // add reverberation, lower pitch
70 //static const char *tone_punct_off = "\001T\001P";
71
72 // punctuations symbols that can end a clause
73 static const unsigned short punct_chars[] = {',','.','?','!',':',';',
74 0x00a1, // inverted exclamation
75 0x00bf, // inverted question
76 0x2013, // en-dash
77 0x2014, // em-dash
78 0x2026, // elipsis
79
80 0x037e, // Greek question mark (looks like semicolon)
81 0x0387, // Greek semicolon, ano teleia
82 0x0964, // Devanagari Danda (fullstop)
83
84 0x0589, // Armenian period
85 0x055d, // Armenian comma
86 0x055c, // Armenian exclamation
87 0x055e, // Armenian question
88 0x055b, // Armenian emphasis mark
89
90 0x060c, // Arabic ,
91 0x061b, // Arabic ;
92 0x061f, // Arabic ?
93 0x06d4, // Arabic .
94
95 0x0df4, // Singhalese Kunddaliya
96 0x0f0d, // Tibet Shad
97 0x0f0e,
98
99 0x1362, // Ethiopic period
100 0x1363,
101 0x1364,
102 0x1365,
103 0x1366,
104 0x1367,
105 0x1368,
106 0x10fb, // Georgian paragraph
107
108 0x3001, // ideograph comma
109 0x3002, // ideograph period
110
111 0xff01, // fullwidth exclamation
112 0xff0c, // fullwidth comma
113 0xff0e, // fullwidth period
114 0xff1a, // fullwidth colon
115 0xff1b, // fullwidth semicolon
116 0xff1f, // fullwidth question mark
117
118 0};
119
120
121 // indexed by (entry num. in punct_chars) + 1
122 // bits 0-7 pause x 10mS, bits 12-14 intonation type, bit 15 don't need following space or bracket
123 static const unsigned int punct_attributes [] = { 0,
124 CLAUSE_COMMA, CLAUSE_PERIOD, CLAUSE_QUESTION, CLAUSE_EXCLAMATION, CLAUSE_COLON, CLAUSE_SEMICOLON,
125 CLAUSE_SEMICOLON | 0x8000, // inverted exclamation
126 CLAUSE_SEMICOLON | 0x8000, // inverted question
127 CLAUSE_SEMICOLON, // en-dash
128 CLAUSE_SEMICOLON, // em-dash
129 CLAUSE_SEMICOLON | PUNCT_SAY_NAME | 0x8000, // elipsis
130
131 CLAUSE_QUESTION, // Greek question mark
132 CLAUSE_SEMICOLON, // Greek semicolon
133 CLAUSE_PERIOD | 0x8000, // Devanagari Danda (fullstop)
134
135 CLAUSE_PERIOD | 0x8000, // Armenian period
136 CLAUSE_COMMA, // Armenian comma
137 CLAUSE_EXCLAMATION | PUNCT_IN_WORD, // Armenian exclamation
138 CLAUSE_QUESTION | PUNCT_IN_WORD, // Armenian question
139 CLAUSE_PERIOD | PUNCT_IN_WORD, // Armenian emphasis mark
140
141 CLAUSE_COMMA, // Arabic ,
142 CLAUSE_SEMICOLON, // Arabic ;
143 CLAUSE_QUESTION, // Arabic question mark
144 CLAUSE_PERIOD, // Arabic full stop
145
146 CLAUSE_PERIOD+0x8000, // Singhalese period
147 CLAUSE_PERIOD+0x8000, // Tibet period
148 CLAUSE_PARAGRAPH,
149
150 CLAUSE_PERIOD, // Ethiopic period
151 CLAUSE_COMMA, // Ethiopic comma
152 CLAUSE_SEMICOLON, // Ethiopic semicolon
153 CLAUSE_COLON, // Ethiopic colon
154 CLAUSE_COLON, // Ethiopic preface colon
155 CLAUSE_QUESTION, // Ethiopic question mark
156 CLAUSE_PARAGRAPH, // Ethiopic paragraph
157 CLAUSE_PARAGRAPH, // Georgian paragraph
158
159 CLAUSE_COMMA+0x8000, // ideograph comma
160 CLAUSE_PERIOD+0x8000, // ideograph period
161
162 CLAUSE_EXCLAMATION+0x8000, // fullwidth
163 CLAUSE_COMMA+0x8000,
164 CLAUSE_PERIOD+0x8000,
165 CLAUSE_COLON+0x8000,
166 CLAUSE_SEMICOLON+0x8000,
167 CLAUSE_QUESTION+0x8000,
168
169 CLAUSE_SEMICOLON, // spare
170 0 };
171
172
173 // stack for language and voice properties
174 // frame 0 is for the defaults, before any ssml tags.
175 typedef struct {
176 int tag_type;
177 int voice_variant_number;
178 int voice_gender;
179 int voice_age;
180 char voice_name[40];
181 char language[20];
182 } SSML_STACK;
183
184 #define N_SSML_STACK 20
185 static int n_ssml_stack;
186 static SSML_STACK ssml_stack[N_SSML_STACK];
187
188 static espeak_VOICE base_voice;
189 static char base_voice_variant_name[40] = {0};
190 static char current_voice_id[40] = {0};
191
192
193 #define N_PARAM_STACK 20
194 static int n_param_stack;
195 PARAM_STACK param_stack[N_PARAM_STACK];
196
197 static int speech_parameters[N_SPEECH_PARAM]; // current values, from param_stack
198 int saved_parameters[N_SPEECH_PARAM]; //Parameters saved on synthesis start
199
200 const int param_defaults[N_SPEECH_PARAM] = {
201 0, // silence (internal use)
202 175, // rate wpm
203 100, // volume
204 50, // pitch
205 50, // range
206 0, // punctuation
207 0, // capital letters
208 0, // wordgap
209 0, // options
210 0, // intonation
211 0,
212 0,
213 0, // emphasis
214 0, // line length
215 0, // voice type
216 };
217
218
219 // additional Latin characters beyond the ascii character set
220 #define MAX_WALPHA 0x24f
221 // indexed by character - 0x80
222 // 0=not alphabetic, 0xff=lower case, 0xfe=no case, 0xfd=use wchar_tolower
223 // other=value to add to upper case to convert to lower case
224 static unsigned char walpha_tab[MAX_WALPHA-0x7f] = {
225 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 080
226 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 090
227 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xfe, 0, 0, 0, 0, 0, // 0a0
228 0, 0, 0, 0, 0, 0xff, 0, 0, 0, 0, 0xfe, 0, 0, 0, 0, 0, // 0b0
229 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, // 0c0
230 32, 32, 32, 32, 32, 32, 32, 0, 32, 32, 32, 32, 32, 32, 32, 0xff, // 0d0
231 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // 0e0
232 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // 0f0
233 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, // 100
234 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, // 110
235 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, // 120
236 0xfd, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, 0xfe, 1, 0xff, 1, 0xff, 1, 0xff, 1, // 130
237 0xff, 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, 0xfe, 1, 0xff, 1, 0xff, 1, 0xff, // 140
238 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, // 150
239 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, // 160
240 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, 0xfd, 1, 0xff, 1, 0xff, 1, 0xff, 0xff, // 170
241 0xff, 210, 1, 0xff, 1, 0xff, 206, 1, 0xff, 205, 205, 1, 0xff, 0xfe, 79, 202, // 180
242 203, 1, 0xff, 205, 207, 0xff, 211, 209, 1, 0xff, 0xff, 0xfe, 211, 213, 0xff, 214, // 190
243 1, 0xff, 1, 0xff, 1, 0xff, 218, 1, 0xff, 218, 0xfe, 0xfe, 1, 0xff, 218, 1, // 1a0
244 0xff, 217, 217, 1, 0xff, 1, 0xff, 219, 1, 0xff, 0xfe, 0xfe, 1, 0xff, 0xfe, 0xff, // 1b0
245 0xfe, 0xfe, 0xfe, 0xfe, 2, 0xff, 0xff, 2, 0xff, 0xff, 2, 0xff, 0xff, 1, 0xff, 1, // 1c0
246 0xff, 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, 0xff, 1, 0xff, // 1d0
247 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, // 1e0
248 0xfe, 2, 0xff, 0xff, 1, 0xff, 0xfd, 0xfd, 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, // 1f0
249 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, // 200
250 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, // 210
251 0xfd, 0xfe, 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, // 220
252 1, 0xff, 1, 0xff, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfd, 1, 0xff, 0xfd, 0xfd, 0xfe, // 230
253 0xfe, 1, 0xff, 0xfd, 69, 71, 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff, 1, 0xff}; // 240
254
255 static const short wchar_tolower[] = {
256 0x130, 0x069,
257 0x178, 0x0ff,
258 0x1f6, 0x195,
259 0x1f7, 0x1bf,
260 0x220, 0x19e,
261 0x23a, 0x2c65,
262 0x23d, 0x19a,
263 0x23e, 0x2c66,
264 0x243, 0x180,
265 0,0 };
266
267 static const short wchar_toupper[] = {
268 0x0b5, 0x39c,
269 0x0df, 0x0df,
270 0x0ff, 0x178,
271 0x131, 0x049,
272 0x17f, 0x053,
273 0x180, 0x243,
274 0x195, 0x1f6,
275 0x19a, 0x23d,
276 0x19e, 0x220,
277 0x1bf, 0x1f7,
278 0x1c6, 0x1c4,
279 0x1c9, 0x1c7,
280 0x1cc, 0x1ca,
281 0x1dd, 0x18e,
282 0x1f3, 0x1f1,
283 0,0 };
284
285
286 #ifdef NEED_WCHAR_FUNCTIONS
287
288 // use ctype.h functions for Latin1 (character < 0x100)
iswalpha(int c)289 int iswalpha(int c)
290 {
291 if(c < 0x80)
292 return(isalpha(c));
293 if((c > 0x3040) && (c <= 0xa700))
294 return(1); // japanese, chinese characters
295 if(c > MAX_WALPHA)
296 return(0);
297 return(walpha_tab[c-0x80]);
298 }
299
iswdigit(int c)300 int iswdigit(int c)
301 {
302 if(c < 0x80)
303 return(isdigit(c));
304 return(0);
305 }
306
iswalnum(int c)307 int iswalnum(int c)
308 {
309 if(iswdigit(c))
310 return(1);
311 return(iswalpha(c));
312 }
313
towlower(int c)314 int towlower(int c)
315 {
316 int x;
317 int ix;
318
319 if(c < 0x80)
320 return(tolower(c));
321
322 if((c > MAX_WALPHA) || ((x = walpha_tab[c-0x80]) >= 0xfe))
323 return(c);
324
325 if(x == 0xfd)
326 {
327 // special cases, lookup translation table
328 for(ix=0; wchar_tolower[ix] != 0; ix+=2)
329 {
330 if(wchar_tolower[ix] == c)
331 return(wchar_tolower[ix+1]);
332 }
333 }
334 return(c + x); // convert to lower case
335 }
336
towupper(int c)337 int towupper(int c)
338 {
339 int ix;
340 // check whether a previous character code is the upper-case equivalent of this character
341 if(towlower(c-32) == c)
342 return(c-32); // yes, use it
343 if(towlower(c-1) == c)
344 return(c-1);
345 for(ix=0; wchar_toupper[ix] != 0; ix+=2)
346 {
347 if(wchar_toupper[ix] == c)
348 return(wchar_toupper[ix+1]);
349 }
350 return(c); // no
351 }
352
iswupper(int c)353 int iswupper(int c)
354 {
355 int x;
356 if(c < 0x80)
357 return(isupper(c));
358 if(((c > MAX_WALPHA) || (x = walpha_tab[c-0x80])==0) || (x == 0xff))
359 return(0);
360 return(1);
361 }
362
iswlower(int c)363 int iswlower(int c)
364 {
365 if(c < 0x80)
366 return(islower(c));
367 if((c > MAX_WALPHA) || (walpha_tab[c-0x80] != 0xff))
368 return(0);
369 return(1);
370 }
371
iswspace(int c)372 int iswspace(int c)
373 {
374 if(c < 0x80)
375 return(isspace(c));
376 if(c == 0xa0)
377 return(1);
378 return(0);
379 }
380
iswpunct(int c)381 int iswpunct(int c)
382 {
383 if(c < 0x100)
384 return(ispunct(c));
385 return(0);
386 }
387
wcschr(const wchar_t * str,int c)388 const wchar_t *wcschr(const wchar_t *str, int c)
389 {
390 while(*str != 0)
391 {
392 if(*str == c)
393 return(str);
394 str++;
395 }
396 return(NULL);
397 }
398
399 #ifndef WINCE
400 // wcslen() is provided by WINCE, but not the other wchar functions
wcslen(const wchar_t * str)401 const int wcslen(const wchar_t *str)
402 {
403 int ix=0;
404
405 while(*str != 0)
406 {
407 ix++;
408 }
409 return(ix);
410 }
411 #endif
412
wcstod(const wchar_t * str,wchar_t ** tailptr)413 float wcstod(const wchar_t *str, wchar_t **tailptr)
414 {
415 int ix;
416 char buf[80];
417 while(isspace(*str)) str++;
418 for(ix=0; ix<80; ix++)
419 {
420 buf[ix] = str[ix];
421 if(isspace(buf[ix]))
422 break;
423 }
424 *tailptr = (wchar_t *)&str[ix];
425 return(atof(buf));
426 }
427 #endif
428
429
430 // use internal data for iswalpha up to U+024F
431 // iswalpha() on Windows is unreliable (U+AA, U+BA).
iswalpha2(int c)432 int iswalpha2(int c)
433 {
434 if(c < 0x80)
435 return(isalpha(c));
436 if((c > 0x3040) && (c <= 0xa700))
437 return(1); // japanese, chinese characters
438 if(c > MAX_WALPHA)
439 return(iswalpha(c));
440 return(walpha_tab[c-0x80]);
441 }
442
iswlower2(int c)443 int iswlower2(int c)
444 {
445 if(c < 0x80)
446 return(islower(c));
447 if(c > MAX_WALPHA)
448 return(iswlower(c));
449 if(walpha_tab[c-0x80] == 0xff)
450 return(1);
451 return(0);
452 }
453
iswupper2(int c)454 int iswupper2(int c)
455 {
456 int x;
457 if(c < 0x80)
458 return(isupper(c));
459 if(c > MAX_WALPHA)
460 return(iswupper(c));
461 if(((x = walpha_tab[c-0x80]) > 0) && (x < 0xfe))
462 return(1);
463 return(0);
464 }
465
towlower2(unsigned int c)466 int towlower2(unsigned int c)
467 {
468 int x;
469 int ix;
470
471 // check for non-standard upper to lower case conversions
472 if(c == 'I')
473 {
474 if(translator->langopts.dotless_i)
475 {
476 c = 0x131; // I -> ı
477 }
478 }
479
480 if(c < 0x80)
481 return(tolower(c));
482
483 if(c > MAX_WALPHA)
484 return(towlower(c));
485
486 if((x = walpha_tab[c-0x80]) >= 0xfe)
487 return(c); // this is not an upper case letter
488
489 if(x == 0xfd)
490 {
491 // special cases, lookup translation table
492 for(ix=0; wchar_tolower[ix] != 0; ix+=2)
493 {
494 if(wchar_tolower[ix] == (int)c)
495 return(wchar_tolower[ix+1]);
496 }
497 }
498 return(c + x); // convert to lower case
499 }
500
towupper2(unsigned int c)501 int towupper2(unsigned int c)
502 {
503 int ix;
504 if(c > MAX_WALPHA)
505 return(towupper(c));
506
507 // check whether a previous character code is the upper-case equivalent of this character
508 if(towlower2(c-32) == (int)c)
509 return(c-32); // yes, use it
510 if(towlower2(c-1) == (int)c)
511 return(c-1);
512 for(ix=0; wchar_toupper[ix] != 0; ix+=2)
513 {
514 if(wchar_toupper[ix] == (int)c)
515 return(wchar_toupper[ix+1]);
516 }
517 return(c); // no
518 }
519
IsRomanU(unsigned int c)520 static int IsRomanU(unsigned int c)
521 {//================================
522 if((c=='I') || (c=='V') || (c=='X') || (c=='L'))
523 return(1);
524 return(0);
525 }
526
527
GetC_unget(int c)528 static void GetC_unget(int c)
529 {//==========================
530 // This is only called with UTF8 input, not wchar input
531 if(f_input != NULL)
532 ungetc(c,f_input);
533 else
534 {
535 p_textinput--;
536 *p_textinput = c;
537 end_of_input = 0;
538 }
539 }
540
Eof(void)541 int Eof(void)
542 {//==========
543 if(ungot_char != 0)
544 return(0);
545
546 if(f_input != 0)
547 return(feof(f_input));
548
549 return(end_of_input);
550 }
551
552
GetC_get(void)553 static int GetC_get(void)
554 {//======================
555 unsigned int c;
556 unsigned int c2;
557
558 if(f_input != NULL)
559 {
560 c = fgetc(f_input);
561 if(feof(f_input)) c = ' ';
562
563 if(option_multibyte == espeakCHARS_16BIT)
564 {
565 c2 = fgetc(f_input);
566 if(feof(f_input)) c2 = 0;
567 c = c + (c2 << 8);
568 }
569 return(c);
570 }
571
572 if(option_multibyte == espeakCHARS_WCHAR)
573 {
574 if(*p_wchar_input == 0)
575 {
576 end_of_input = 1;
577 return(0);
578 }
579
580 if(!end_of_input)
581 return(*p_wchar_input++);
582 }
583 else
584 {
585 if(*p_textinput == 0)
586 {
587 end_of_input = 1;
588 return(0);
589 }
590
591 if(!end_of_input)
592 {
593 if(option_multibyte == espeakCHARS_16BIT)
594 {
595 c = p_textinput[0] + (p_textinput[1] << 8);
596 p_textinput += 2;
597 return(c);
598 }
599 return(*p_textinput++ & 0xff);
600 }
601 }
602 return(0);
603 }
604
605
GetC(void)606 static int GetC(void)
607 {//==================
608 // Returns a unicode wide character
609 // Performs UTF8 checking and conversion
610
611 int c;
612 int c1;
613 int c2;
614 int cbuf[4];
615 int ix;
616 int n_bytes;
617 static int ungot2 = 0;
618 static const unsigned char mask[4] = {0xff,0x1f,0x0f,0x07};
619
620 if((c1 = ungot_char) != 0)
621 {
622 ungot_char = 0;
623 return(c1);
624 }
625
626 if(ungot2 != 0)
627 {
628 c1 = ungot2;
629 ungot2 = 0;
630 }
631 else
632 {
633 c1 = GetC_get();
634 }
635
636 if((option_multibyte == espeakCHARS_WCHAR) || (option_multibyte == espeakCHARS_16BIT))
637 {
638 count_characters++;
639 return(c1); // wchar_t text
640 }
641
642 if((option_multibyte < 2) && (c1 & 0x80))
643 {
644 // multi-byte utf8 encoding, convert to unicode
645 n_bytes = 0;
646
647 if(((c1 & 0xe0) == 0xc0) && ((c1 & 0x1e) != 0))
648 n_bytes = 1;
649 else
650 if((c1 & 0xf0) == 0xe0)
651 n_bytes = 2;
652 else
653 if(((c1 & 0xf8) == 0xf0) && ((c1 & 0x0f) <= 4))
654 n_bytes = 3;
655
656 if((ix = n_bytes) > 0)
657 {
658 c = c1 & mask[ix];
659 while(ix > 0)
660 {
661 if((c2 = cbuf[ix] = GetC_get()) == 0)
662 {
663 if(option_multibyte==espeakCHARS_AUTO)
664 option_multibyte=espeakCHARS_8BIT; // change "auto" option to "no"
665 GetC_unget(' ');
666 break;
667 }
668
669 if((c2 & 0xc0) != 0x80)
670 {
671 // This is not UTF8. Change to 8-bit characterset.
672 if((n_bytes == 2) && (ix == 1))
673 ungot2 = cbuf[2];
674 GetC_unget(c2);
675 break;
676 }
677 c = (c << 6) + (c2 & 0x3f);
678 ix--;
679 }
680 if(ix == 0)
681 {
682 count_characters++;
683 return(c);
684 }
685 }
686 // top-bit-set character is not utf8, drop through to 8bit charset case
687 if((option_multibyte==espeakCHARS_AUTO) && !Eof())
688 option_multibyte=espeakCHARS_8BIT; // change "auto" option to "no"
689 }
690
691 // 8 bit character set, convert to unicode if
692 count_characters++;
693 if(c1 >= 0xa0)
694 return(translator->charset_a0[c1-0xa0]);
695 return(c1);
696 } // end of GetC
697
698
UngetC(int c)699 static void UngetC(int c)
700 {//======================
701 ungot_char = c;
702 }
703
704
WordToString2(unsigned int word)705 const char *WordToString2(unsigned int word)
706 {//============================================
707 // Convert a language mnemonic word into a string
708 int ix;
709 static char buf[5];
710 char *p;
711
712 p = buf;
713 for(ix=3; ix>=0; ix--)
714 {
715 if((*p = word >> (ix*8)) != 0)
716 p++;
717 }
718 *p = 0;
719 return(buf);
720 }
721
722
LookupSpecial(Translator * tr,const char * string,char * text_out)723 static const char *LookupSpecial(Translator *tr, const char *string, char* text_out)
724 {//=================================================================================
725 unsigned int flags[2];
726 char phonemes[55];
727 char phonemes2[55];
728 char *string1 = (char *)string;
729
730 flags[0] = flags[1] = 0;
731 if(LookupDictList(tr,&string1,phonemes,flags,0,NULL))
732 {
733 SetWordStress(tr, phonemes, flags, -1, 0);
734 DecodePhonemes(phonemes,phonemes2);
735 sprintf(text_out,"[\002%s]]",phonemes2);
736 return(text_out);
737 }
738 return(NULL);
739 }
740
741
LookupCharName(Translator * tr,int c,int only)742 static const char *LookupCharName(Translator *tr, int c, int only)
743 {//===============================================================
744 // Find the phoneme string (in ascii) to speak the name of character c
745 // Used for punctuation characters and symbols
746
747 int ix;
748 unsigned int flags[2];
749 char single_letter[24];
750 char phonemes[60];
751 char phonemes2[60];
752 const char *lang_name = NULL;
753 char *string;
754 static char buf[60];
755
756 buf[0] = 0;
757 flags[0] = 0;
758 flags[1] = 0;
759 single_letter[0] = 0;
760 single_letter[1] = '_';
761 ix = utf8_out(c,&single_letter[2]);
762 single_letter[2+ix]=0;
763
764 if(only)
765 {
766 string = &single_letter[2];
767 LookupDictList(tr, &string, phonemes, flags, 0, NULL);
768 }
769 else
770 {
771 string = &single_letter[1];
772 if(LookupDictList(tr, &string, phonemes, flags, 0, NULL) == 0)
773 {
774 // try _* then *
775 string = &single_letter[2];
776 if(LookupDictList(tr, &string, phonemes, flags, 0, NULL) == 0)
777 {
778 // now try the rules
779 single_letter[1] = ' ';
780 TranslateRules(tr, &single_letter[2], phonemes, sizeof(phonemes), NULL,0,NULL);
781 }
782 }
783 }
784
785 if((only==0) && ((phonemes[0] == 0) || (phonemes[0] == phonSWITCH)) && (tr->translator_name != L('e','n')))
786 {
787 // not found, try English
788 SetTranslator2("en");
789 string = &single_letter[1];
790 single_letter[1] = '_';
791 if(LookupDictList(translator2, &string, phonemes, flags, 0, NULL) == 0)
792 {
793 string = &single_letter[2];
794 LookupDictList(translator2, &string, phonemes, flags, 0, NULL);
795 }
796 if(phonemes[0])
797 {
798 lang_name = "en";
799 }
800 else
801 {
802 SelectPhonemeTable(voice->phoneme_tab_ix); // revert to original phoneme table
803 }
804 }
805
806 if(phonemes[0])
807 {
808 if(lang_name)
809 {
810 SetWordStress(translator2, phonemes, flags, -1, 0);
811 DecodePhonemes(phonemes,phonemes2);
812 sprintf(buf,"[\002_^_%s %s _^_%s]]","en",phonemes2,WordToString2(tr->translator_name));
813 SelectPhonemeTable(voice->phoneme_tab_ix); // revert to original phoneme table
814 }
815 else
816 {
817 SetWordStress(tr, phonemes, flags, -1, 0);
818 DecodePhonemes(phonemes,phonemes2);
819 sprintf(buf,"[\002%s]] ",phonemes2);
820 }
821 }
822 else
823 if(only == 0)
824 {
825 strcpy(buf,"[\002(X1)(X1)(X1)]]");
826 }
827
828 return(buf);
829 }
830
Read4Bytes(FILE * f)831 int Read4Bytes(FILE *f)
832 {//====================
833 // Read 4 bytes (least significant first) into a word
834 int ix;
835 unsigned char c;
836 int acc=0;
837
838 for(ix=0; ix<4; ix++)
839 {
840 c = fgetc(f) & 0xff;
841 acc += (c << (ix*8));
842 }
843 return(acc);
844 }
845
846
LoadSoundFile(const char * fname,int index)847 static int LoadSoundFile(const char *fname, int index)
848 {//===================================================
849 FILE *f;
850 char *p;
851 int *ip;
852 int length;
853 char fname_temp[100];
854 char fname2[sizeof(path_home)+13+40];
855
856 if(fname == NULL)
857 {
858 // filename is already in the table
859 fname = soundicon_tab[index].filename;
860 }
861
862 if(fname==NULL)
863 return(1);
864
865 if(fname[0] != '/')
866 {
867 // a relative path, look in espeak-data/soundicons
868 sprintf(fname2,"%s%csoundicons%c%s",path_home,PATHSEP,PATHSEP,fname);
869 fname = fname2;
870 }
871
872 f = NULL;
873 #ifdef PLATFORM_POSIX
874 if((f = fopen(fname,"rb")) != NULL)
875 {
876 int ix;
877 int fd_temp;
878 int header[3];
879 char command[sizeof(fname2)+sizeof(fname2)+40];
880
881 fseek(f,20,SEEK_SET);
882 for(ix=0; ix<3; ix++)
883 header[ix] = Read4Bytes(f);
884
885 // if the sound file is not mono, 16 bit signed, at the correct sample rate, then convert it
886 if((header[0] != 0x10001) || (header[1] != samplerate) || (header[2] != samplerate*2))
887 {
888 fclose(f);
889 f = NULL;
890
891 strcpy(fname_temp,"/tmp/espeakXXXXXX");
892 if((fd_temp = mkstemp(fname_temp)) >= 0)
893 {
894 close(fd_temp);
895 sprintf(command,"sox \"%s\" -r %d -c1 -t wav %s\n", fname, samplerate, fname_temp);
896 if(system(command) == 0)
897 {
898 fname = fname_temp;
899 }
900 }
901 }
902 }
903 #endif
904
905 if(f == NULL)
906 {
907 f = fopen(fname,"rb");
908 if(f == NULL)
909 {
910 // fprintf(stderr,"Can't read temp file: %s\n",fname);
911 return(3);
912 }
913 }
914
915 length = GetFileLength(fname);
916 fseek(f,0,SEEK_SET);
917 if((p = (char *)realloc(soundicon_tab[index].data, length)) == NULL)
918 {
919 fclose(f);
920 return(4);
921 }
922 length = fread(p,1,length,f);
923 fclose(f);
924 remove(fname_temp);
925
926 ip = (int *)(&p[40]);
927 soundicon_tab[index].length = (*ip) / 2; // length in samples
928 soundicon_tab[index].data = p;
929 return(0);
930 } // end of LoadSoundFile
931
932
LookupSoundicon(int c)933 static int LookupSoundicon(int c)
934 {//==============================
935 // Find the sound icon number for a punctuation chatacter
936 int ix;
937
938 for(ix=N_SOUNDICON_SLOTS; ix<n_soundicon_tab; ix++)
939 {
940 if(soundicon_tab[ix].name == c)
941 {
942 if(soundicon_tab[ix].length == 0)
943 {
944 if(LoadSoundFile(NULL,ix)!=0)
945 return(-1); // sound file is not available
946 }
947 return(ix);
948 }
949 }
950 return(-1);
951 }
952
953
LoadSoundFile2(const char * fname)954 static int LoadSoundFile2(const char *fname)
955 {//=========================================
956 // Load a sound file into one of the reserved slots in the sound icon table
957 // (if it'snot already loaded)
958
959 int ix;
960 static int slot = -1;
961
962 for(ix=0; ix<n_soundicon_tab; ix++)
963 {
964 if(((soundicon_tab[ix].filename != NULL) && strcmp(fname, soundicon_tab[ix].filename) == 0))
965 return(ix); // already loaded
966 }
967
968 // load the file into the next slot
969 slot++;
970 if(slot >= N_SOUNDICON_SLOTS)
971 slot = 0;
972
973 if(LoadSoundFile(fname, slot) != 0)
974 return(-1);
975
976 soundicon_tab[slot].filename = (char *)realloc(soundicon_tab[ix].filename, strlen(fname)+1);
977 strcpy(soundicon_tab[slot].filename, fname);
978 return(slot);
979 }
980
981
982
AnnouncePunctuation(Translator * tr,int c1,int * c2_ptr,char * output,int * bufix,int end_clause)983 static int AnnouncePunctuation(Translator *tr, int c1, int *c2_ptr, char *output, int *bufix, int end_clause)
984 {//=============================================================================================================
985 // announce punctuation names
986 // c1: the punctuation character
987 // c2: the following character
988
989 int punct_count;
990 const char *punctname = NULL;
991 int soundicon;
992 int attributes;
993 int short_pause;
994 int c2;
995 int len;
996 int bufix1;
997 char buf[200];
998 char buf2[80];
999 char ph_buf[30];
1000
1001 c2 = *c2_ptr;
1002 buf[0] = 0;
1003
1004 if((soundicon = LookupSoundicon(c1)) >= 0)
1005 {
1006 // add an embedded command to play the soundicon
1007 sprintf(buf,"\001%dI ",soundicon);
1008 UngetC(c2);
1009 }
1010 else
1011 {
1012 if((c1 == '.') && (end_clause) && (c2 != '.'))
1013 {
1014 if(LookupSpecial(tr, "_.p", ph_buf))
1015 {
1016 punctname = ph_buf; // use word for 'period' instead of 'dot'
1017 }
1018 }
1019 if(punctname == NULL)
1020 {
1021 punctname = LookupCharName(tr, c1, 0);
1022 }
1023
1024 if(punctname == NULL)
1025 return(-1);
1026
1027 if((*bufix==0) || (end_clause ==0) || (tr->langopts.param[LOPT_ANNOUNCE_PUNCT] & 2))
1028 {
1029 punct_count=1;
1030 while((c2 == c1) && (c1 != '<')) // don't eat extra '<', it can miss XML tags
1031 {
1032 punct_count++;
1033 c2 = GetC();
1034 }
1035 *c2_ptr = c2;
1036 if(end_clause)
1037 {
1038 UngetC(c2);
1039 }
1040
1041 if(punct_count==1)
1042 {
1043 // sprintf(buf,"%s %s %s",tone_punct_on,punctname,tone_punct_off);
1044 sprintf(buf," %s",punctname); // we need the space before punctname, to ensure it doesn't merge with the previous word (eg. "2.-a")
1045 }
1046 else
1047 if(punct_count < 4)
1048 {
1049 buf[0] = 0;
1050 if(embedded_value[EMBED_S] < 300)
1051 sprintf(buf,"\001+10S"); // Speak punctuation name faster, unless we are already speaking fast. It would upset Sonic SpeedUp
1052
1053 while(punct_count-- > 0)
1054 {
1055 sprintf(buf2," %s",punctname);
1056 strcat(buf, buf2);
1057 }
1058
1059 if(embedded_value[EMBED_S] < 300)
1060 {
1061 sprintf(buf2," \001-10S");
1062 strcat(buf, buf2);
1063 }
1064 }
1065 else
1066 {
1067 sprintf(buf," %s %d %s",
1068 punctname,punct_count,punctname);
1069 }
1070 }
1071 else
1072 {
1073 // end the clause now and pick up the punctuation next time
1074 UngetC(c2);
1075 if(option_ssml)
1076 {
1077 if((c1 == '<') || (c1 == '&'))
1078 ssml_ignore_l_angle = c1; // this was < which was converted to <, don't pick it up again as <
1079 }
1080 ungot_char2 = c1;
1081 buf[0] = ' ';
1082 buf[1] = 0;
1083 }
1084 }
1085
1086 bufix1 = *bufix;
1087 len = strlen(buf);
1088 strcpy(&output[*bufix],buf);
1089 *bufix += len;
1090
1091 if(end_clause==0)
1092 return(-1);
1093
1094 if(c1 == '-')
1095 return(CLAUSE_NONE); // no pause
1096
1097 attributes = punct_attributes[lookupwchar(punct_chars,c1)];
1098
1099 short_pause = CLAUSE_SHORTFALL;
1100 if((attributes & CLAUSE_BITS_INTONATION) == 0x1000)
1101 short_pause = CLAUSE_SHORTCOMMA;
1102
1103 if((bufix1 > 0) && !(tr->langopts.param[LOPT_ANNOUNCE_PUNCT] & 2))
1104 {
1105 if((attributes & ~0x8000) == CLAUSE_SEMICOLON)
1106 return(CLAUSE_SHORTFALL);
1107 return(short_pause);
1108 }
1109
1110 if(attributes & CLAUSE_BIT_SENTENCE)
1111 return(attributes);
1112
1113 return(short_pause);
1114 } // end of AnnouncePunctuation
1115
1116 #define SSML_SPEAK 1
1117 #define SSML_VOICE 2
1118 #define SSML_PROSODY 3
1119 #define SSML_SAYAS 4
1120 #define SSML_MARK 5
1121 #define SSML_SENTENCE 6
1122 #define SSML_PARAGRAPH 7
1123 #define SSML_PHONEME 8
1124 #define SSML_SUB 9
1125 #define SSML_STYLE 10
1126 #define SSML_AUDIO 11
1127 #define SSML_EMPHASIS 12
1128 #define SSML_BREAK 13
1129 #define SSML_IGNORE_TEXT 14
1130 #define HTML_BREAK 15
1131 #define HTML_NOSPACE 16 // don't insert a space for this element, so it doesn't break a word
1132 #define SSML_CLOSE 0x20 // for a closing tag, OR this with the tag type
1133
1134 // these tags have no effect if they are self-closing, eg. <voice />
1135 static char ignore_if_self_closing[] = {0,1,1,1,1,0,0,0,0,1,1,0,1,0,1,0,0,0,0};
1136
1137
1138 static MNEM_TAB ssmltags[] = {
1139 {"speak", SSML_SPEAK},
1140 {"voice", SSML_VOICE},
1141 {"prosody", SSML_PROSODY},
1142 {"say-as", SSML_SAYAS},
1143 {"mark", SSML_MARK},
1144 {"s", SSML_SENTENCE},
1145 {"p", SSML_PARAGRAPH},
1146 {"phoneme", SSML_PHONEME},
1147 {"sub", SSML_SUB},
1148 {"tts:style", SSML_STYLE},
1149 {"audio", SSML_AUDIO},
1150 {"emphasis", SSML_EMPHASIS},
1151 {"break", SSML_BREAK},
1152 {"metadata", SSML_IGNORE_TEXT},
1153
1154 {"br", HTML_BREAK},
1155 {"li", HTML_BREAK},
1156 {"dd", HTML_BREAK},
1157 {"img", HTML_BREAK},
1158 {"td", HTML_BREAK},
1159 {"h1", SSML_PARAGRAPH},
1160 {"h2", SSML_PARAGRAPH},
1161 {"h3", SSML_PARAGRAPH},
1162 {"h4", SSML_PARAGRAPH},
1163 {"hr", SSML_PARAGRAPH},
1164 {"script", SSML_IGNORE_TEXT},
1165 {"style", SSML_IGNORE_TEXT},
1166 {"font", HTML_NOSPACE},
1167 {"b", HTML_NOSPACE},
1168 {"i", HTML_NOSPACE},
1169 {"strong", HTML_NOSPACE},
1170 {"em", HTML_NOSPACE},
1171 {"code", HTML_NOSPACE},
1172 {NULL,0}};
1173
1174
1175
1176
VoiceFromStack()1177 static const char *VoiceFromStack()
1178 {//================================
1179 // Use the voice properties from the SSML stack to choose a voice, and switch
1180 // to that voice if it's not the current voice
1181 int ix;
1182 const char *p;
1183 SSML_STACK *sp;
1184 const char *v_id;
1185 int voice_name_specified;
1186 int voice_found;
1187 espeak_VOICE voice_select;
1188 static char voice_name[40];
1189 char language[40];
1190 char buf[80];
1191
1192 strcpy(voice_name,ssml_stack[0].voice_name);
1193 strcpy(language,ssml_stack[0].language);
1194 voice_select.age = ssml_stack[0].voice_age;
1195 voice_select.gender = ssml_stack[0].voice_gender;
1196 voice_select.variant = ssml_stack[0].voice_variant_number;
1197 voice_select.identifier = NULL;
1198
1199 for(ix=0; ix<n_ssml_stack; ix++)
1200 {
1201 sp = &ssml_stack[ix];
1202 voice_name_specified = 0;
1203
1204 if((sp->voice_name[0] != 0) && (SelectVoiceByName(NULL,sp->voice_name) != NULL))
1205 {
1206 voice_name_specified = 1;
1207 strcpy(voice_name, sp->voice_name);
1208 language[0] = 0;
1209 voice_select.gender = 0;
1210 voice_select.age = 0;
1211 voice_select.variant = 0;
1212 }
1213 if(sp->language[0] != 0)
1214 {
1215 strcpy(language, sp->language);
1216
1217 // is this language provided by the base voice?
1218 p = base_voice.languages;
1219 while(*p++ != 0)
1220 {
1221 if(strcmp(p, language) == 0)
1222 {
1223 // yes, change the language to the main language of the base voice
1224 strcpy(language, &base_voice.languages[1]);
1225 break;
1226 }
1227 p += (strlen(p) + 1);
1228 }
1229
1230 if(voice_name_specified == 0)
1231 voice_name[0] = 0; // forget a previous voice name if a language is specified
1232 }
1233 if(sp->voice_gender != 0)
1234 {
1235 voice_select.gender = sp->voice_gender;
1236 }
1237
1238 if(sp->voice_age != 0)
1239 voice_select.age = sp->voice_age;
1240 if(sp->voice_variant_number != 0)
1241 voice_select.variant = sp->voice_variant_number;
1242 }
1243
1244 voice_select.name = voice_name;
1245 voice_select.languages = language;
1246 v_id = SelectVoice(&voice_select, &voice_found);
1247 if(v_id == NULL)
1248 return("default");
1249
1250 if((strchr(v_id, '+') == NULL) && ((voice_select.gender == 0) || (voice_select.gender == base_voice.gender)) && (base_voice_variant_name[0] != 0))
1251 {
1252 // a voice variant has not been selected, use the original voice variant
1253 sprintf(buf, "%s+%s", v_id, base_voice_variant_name);
1254 strncpy0(voice_name, buf, sizeof(voice_name));
1255 return(voice_name);
1256 }
1257 return(v_id);
1258 } // end of VoiceFromStack
1259
1260
1261
ProcessParamStack(char * outbuf,int * outix)1262 static void ProcessParamStack(char *outbuf, int *outix)
1263 {//====================================================
1264 // Set the speech parameters from the parameter stack
1265 int param;
1266 int ix;
1267 int value;
1268 char buf[20];
1269 int new_parameters[N_SPEECH_PARAM];
1270 static char cmd_letter[N_SPEECH_PARAM] = {0, 'S','A','P','R', 0, 'C', 0, 0, 0, 0, 0, 'F'}; // embedded command letters
1271
1272
1273 for(param=0; param<N_SPEECH_PARAM; param++)
1274 new_parameters[param] = -1;
1275
1276 for(ix=0; ix<n_param_stack; ix++)
1277 {
1278 for(param=0; param<N_SPEECH_PARAM; param++)
1279 {
1280 if(param_stack[ix].parameter[param] >= 0)
1281 new_parameters[param] = param_stack[ix].parameter[param];
1282 }
1283 }
1284
1285 for(param=0; param<N_SPEECH_PARAM; param++)
1286 {
1287 if((value = new_parameters[param]) != speech_parameters[param])
1288 {
1289 buf[0] = 0;
1290
1291 switch(param)
1292 {
1293 case espeakPUNCTUATION:
1294 option_punctuation = value-1;
1295 break;
1296
1297 case espeakCAPITALS:
1298 option_capitals = value;
1299 break;
1300
1301 case espeakRATE:
1302 case espeakVOLUME:
1303 case espeakPITCH:
1304 case espeakRANGE:
1305 case espeakEMPHASIS:
1306 sprintf(buf,"%c%d%c",CTRL_EMBEDDED,value,cmd_letter[param]);
1307 break;
1308 }
1309
1310 speech_parameters[param] = new_parameters[param];
1311 strcpy(&outbuf[*outix],buf);
1312 *outix += strlen(buf);
1313 }
1314 }
1315 } // end of ProcessParamStack
1316
1317
PushParamStack(int tag_type)1318 static PARAM_STACK *PushParamStack(int tag_type)
1319 {//=============================================
1320 int ix;
1321 PARAM_STACK *sp;
1322
1323 sp = ¶m_stack[n_param_stack];
1324 if(n_param_stack < (N_PARAM_STACK-1))
1325 n_param_stack++;
1326
1327 sp->type = tag_type;
1328 for(ix=0; ix<N_SPEECH_PARAM; ix++)
1329 {
1330 sp->parameter[ix] = -1;
1331 }
1332 return(sp);
1333 } // end of PushParamStack
1334
1335
PopParamStack(int tag_type,char * outbuf,int * outix)1336 static void PopParamStack(int tag_type, char *outbuf, int *outix)
1337 {//==============================================================
1338 // unwind the stack up to and including the previous tag of this type
1339 int ix;
1340 int top = 0;
1341
1342 if(tag_type >= SSML_CLOSE)
1343 tag_type -= SSML_CLOSE;
1344
1345 for(ix=0; ix<n_param_stack; ix++)
1346 {
1347 if(param_stack[ix].type == tag_type)
1348 {
1349 top = ix;
1350 }
1351 }
1352 if(top > 0)
1353 {
1354 n_param_stack = top;
1355 }
1356 ProcessParamStack(outbuf, outix);
1357 } // end of PopParamStack
1358
1359
1360
GetSsmlAttribute(wchar_t * pw,const char * name)1361 static wchar_t *GetSsmlAttribute(wchar_t *pw, const char *name)
1362 {//============================================================
1363 // Gets the value string for an attribute.
1364 // Returns NULL if the attribute is not present
1365 int ix;
1366 static wchar_t empty[1] = {0};
1367
1368 while(*pw != 0)
1369 {
1370 if(iswspace(pw[-1]))
1371 {
1372 ix = 0;
1373 while(*pw == name[ix])
1374 {
1375 pw++;
1376 ix++;
1377 }
1378 if(name[ix]==0)
1379 {
1380 // found the attribute, now get the value
1381 while(iswspace(*pw)) pw++;
1382 if(*pw == '=') pw++;
1383 while(iswspace(*pw)) pw++;
1384 if((*pw == '"') || (*pw == '\'')) // allow single-quotes ?
1385 return(pw+1);
1386 else
1387 return(empty);
1388 }
1389 }
1390 pw++;
1391 }
1392 return(NULL);
1393 } // end of GetSsmlAttribute
1394
1395
attrcmp(const wchar_t * string1,const char * string2)1396 static int attrcmp(const wchar_t *string1, const char *string2)
1397 {//============================================================
1398 int ix;
1399
1400 if(string1 == NULL)
1401 return(1);
1402
1403 for(ix=0; (string1[ix] == string2[ix]) && (string1[ix] != 0); ix++)
1404 {
1405 }
1406 if(((string1[ix]=='"') || (string1[ix]=='\'')) && (string2[ix]==0))
1407 return(0);
1408 return(1);
1409 }
1410
1411
attrlookup(const wchar_t * string1,const MNEM_TAB * mtab)1412 static int attrlookup(const wchar_t *string1, const MNEM_TAB *mtab)
1413 {//================================================================
1414 int ix;
1415
1416 for(ix=0; mtab[ix].mnem != NULL; ix++)
1417 {
1418 if(attrcmp(string1,mtab[ix].mnem) == 0)
1419 return(mtab[ix].value);
1420 }
1421 return(mtab[ix].value);
1422 }
1423
1424
attrnumber(const wchar_t * pw,int default_value,int type)1425 static int attrnumber(const wchar_t *pw, int default_value, int type)
1426 {//==================================================================
1427 int value = 0;
1428
1429 if((pw == NULL) || !IsDigit09(*pw))
1430 return(default_value);
1431
1432 while(IsDigit09(*pw))
1433 {
1434 value = value*10 + *pw++ - '0';
1435 }
1436 if((type==1) && (towlower(*pw)=='s'))
1437 {
1438 // time: seconds rather than ms
1439 value *= 1000;
1440 }
1441 return(value);
1442 } // end of attrnumber
1443
1444
1445
attrcopy_utf8(char * buf,const wchar_t * pw,int len)1446 static int attrcopy_utf8(char *buf, const wchar_t *pw, int len)
1447 {//============================================================
1448 // Convert attribute string into utf8, write to buf, and return its utf8 length
1449 unsigned int c;
1450 int ix = 0;
1451 int n;
1452 int prev_c = 0;
1453
1454 if(pw != NULL)
1455 {
1456 while((ix < (len-4)) && ((c = *pw++) != 0))
1457 {
1458 if((c=='"') && (prev_c != '\\'))
1459 break; // " indicates end of attribute, unless preceded by backstroke
1460 n = utf8_out(c,&buf[ix]);
1461 ix += n;
1462 prev_c = c;
1463 }
1464 }
1465 buf[ix] = 0;
1466 return(ix);
1467 } // end of attrcopy_utf8
1468
1469
1470
attr_prosody_value(int param_type,const wchar_t * pw,int * value_out)1471 static int attr_prosody_value(int param_type, const wchar_t *pw, int *value_out)
1472 {//=============================================================================
1473 int sign = 0;
1474 wchar_t *tail;
1475 double value;
1476
1477 while(iswspace(*pw)) pw++;
1478 if(*pw == '+')
1479 {
1480 pw++;
1481 sign = 1;
1482 }
1483 if(*pw == '-')
1484 {
1485 pw++;
1486 sign = -1;
1487 }
1488 value = (double)wcstod(pw,&tail);
1489 if(tail == pw)
1490 {
1491 // failed to find a number, return 100%
1492 *value_out = 100;
1493 return(2);
1494 }
1495
1496 if(*tail == '%')
1497 {
1498 if(sign != 0)
1499 value = 100 + (sign * value);
1500 *value_out = (int)value;
1501 return(2); // percentage
1502 }
1503
1504 if((tail[0]=='s') && (tail[1]=='t'))
1505 {
1506 #ifdef PLATFORM_RISCOS
1507 *value_out = 100;
1508 #else
1509 double x;
1510 // convert from semitones to a frequency percentage
1511 x = pow((double)2.0,(double)((value*sign)/12)) * 100;
1512 *value_out = (int)x;
1513 #endif
1514 return(2); // percentage
1515 }
1516
1517 if(param_type == espeakRATE)
1518 {
1519 if(sign == 0)
1520 *value_out = (int)(value * 100);
1521 else
1522 *value_out = 100 + (int)(sign * value * 100);
1523 return(2); // percentage
1524 }
1525
1526 *value_out = (int)value;
1527 return(sign); // -1, 0, or 1
1528 } // end of attr_prosody_value
1529
1530
AddNameData(const char * name,int wide)1531 int AddNameData(const char *name, int wide)
1532 {//========================================
1533 // Add the name to the namedata and return its position
1534 // (Used by the Windows SAPI wrapper)
1535 int ix;
1536 int len;
1537 void *vp;
1538
1539 if(wide)
1540 {
1541 len = (wcslen((const wchar_t *)name)+1)*sizeof(wchar_t);
1542 n_namedata = (n_namedata + sizeof(wchar_t) - 1) % sizeof(wchar_t); // round to wchar_t boundary
1543 }
1544 else
1545 {
1546 len = strlen(name)+1;
1547 }
1548
1549 if(namedata_ix+len >= n_namedata)
1550 {
1551 // allocate more space for marker names
1552 if((vp = realloc(namedata, namedata_ix+len + 1000)) == NULL)
1553 return(-1); // failed to allocate, original data is unchanged but ignore this new name
1554 // !!! Bug?? If the allocated data shifts position, then pointers given to user application will be invalid
1555
1556 namedata = (char *)vp;
1557 n_namedata = namedata_ix+len + 1000;
1558 }
1559 memcpy(&namedata[ix = namedata_ix],name,len);
1560 namedata_ix += len;
1561 return(ix);
1562 } // end of AddNameData
1563
1564
SetVoiceStack(espeak_VOICE * v,const char * variant_name)1565 void SetVoiceStack(espeak_VOICE *v, const char *variant_name)
1566 {//==========================================================
1567 SSML_STACK *sp;
1568 sp = &ssml_stack[0];
1569
1570 if(v == NULL)
1571 {
1572 memset(sp,0,sizeof(ssml_stack[0]));
1573 return;
1574 }
1575 if(v->languages != NULL)
1576 strcpy(sp->language,v->languages);
1577 if(v->name != NULL)
1578 strncpy0(sp->voice_name, v->name, sizeof(sp->voice_name));
1579 sp->voice_variant_number = v->variant;
1580 sp->voice_age = v->age;
1581 sp->voice_gender = v->gender;
1582
1583 if(memcmp(variant_name, "!v", 2) == 0)
1584 variant_name += 3;// strip variant directory name, !v plus PATHSEP
1585 strncpy0(base_voice_variant_name, variant_name, sizeof(base_voice_variant_name));
1586 memcpy(&base_voice, ¤t_voice_selected, sizeof(base_voice));
1587 }
1588
1589
GetVoiceAttributes(wchar_t * pw,int tag_type)1590 static int GetVoiceAttributes(wchar_t *pw, int tag_type)
1591 {//=====================================================
1592 // Determines whether voice attribute are specified in this tag, and if so, whether this means
1593 // a voice change.
1594 // If it's a closing tag, delete the top frame of the stack and determine whether this implies
1595 // a voice change.
1596 // Returns CLAUSE_BIT_VOICE if there is a voice change
1597
1598 wchar_t *lang;
1599 wchar_t *gender;
1600 wchar_t *name;
1601 wchar_t *age;
1602 wchar_t *variant;
1603 int value;
1604 const char *new_voice_id;
1605 SSML_STACK *ssml_sp;
1606
1607 static const MNEM_TAB mnem_gender[] = {
1608 {"male", 1},
1609 {"female", 2},
1610 {"neutral", 3},
1611 {NULL, 0}};
1612
1613 if(tag_type & SSML_CLOSE)
1614 {
1615 // delete a stack frame
1616 if(n_ssml_stack > 1)
1617 {
1618 n_ssml_stack--;
1619 }
1620 }
1621 else
1622 {
1623 // add a stack frame if any voice details are specified
1624 lang = GetSsmlAttribute(pw,"xml:lang");
1625
1626 if(tag_type != SSML_VOICE)
1627 {
1628 // only expect an xml:lang attribute
1629 name = NULL;
1630 variant = NULL;
1631 age = NULL;
1632 gender = NULL;
1633 }
1634 else
1635 {
1636 name = GetSsmlAttribute(pw,"name");
1637 variant = GetSsmlAttribute(pw,"variant");
1638 age = GetSsmlAttribute(pw,"age");
1639 gender = GetSsmlAttribute(pw,"gender");
1640 }
1641
1642 if((tag_type != SSML_VOICE) && (lang==NULL))
1643 return(0); // <s> or <p> without language spec, nothing to do
1644
1645 ssml_sp = &ssml_stack[n_ssml_stack++];
1646
1647 attrcopy_utf8(ssml_sp->language,lang,sizeof(ssml_sp->language));
1648 attrcopy_utf8(ssml_sp->voice_name,name,sizeof(ssml_sp->voice_name));
1649 if((value = attrnumber(variant,1,0)) > 0)
1650 value--; // variant='0' and variant='1' the same
1651 ssml_sp->voice_variant_number = value;
1652 ssml_sp->voice_age = attrnumber(age,0,0);
1653 ssml_sp->voice_gender = attrlookup(gender,mnem_gender);
1654 ssml_sp->tag_type = tag_type;
1655 }
1656
1657 new_voice_id = VoiceFromStack();
1658 if(strcmp(new_voice_id,current_voice_id) != 0)
1659 {
1660 // add an embedded command to change the voice
1661 strcpy(current_voice_id,new_voice_id);
1662 return(CLAUSE_BIT_VOICE); // change of voice
1663 }
1664
1665 return(0);
1666 } // end of GetVoiceAttributes
1667
1668
SetProsodyParameter(int param_type,wchar_t * attr1,PARAM_STACK * sp)1669 static void SetProsodyParameter(int param_type, wchar_t *attr1, PARAM_STACK *sp)
1670 {//=============================================================================
1671 int value;
1672 int sign;
1673
1674 static const MNEM_TAB mnem_volume[] = {
1675 {"default",100},
1676 {"silent",0},
1677 {"x-soft",30},
1678 {"soft",65},
1679 {"medium",100},
1680 {"loud",150},
1681 {"x-loud",230},
1682 {NULL, -1}};
1683
1684 static const MNEM_TAB mnem_rate[] = {
1685 {"default",100},
1686 {"x-slow",60},
1687 {"slow",80},
1688 {"medium",100},
1689 {"fast",125},
1690 {"x-fast",160},
1691 {NULL, -1}};
1692
1693 static const MNEM_TAB mnem_pitch[] = {
1694 {"default",100},
1695 {"x-low",70},
1696 {"low",85},
1697 {"medium",100},
1698 {"high",110},
1699 {"x-high",120},
1700 {NULL, -1}};
1701
1702 static const MNEM_TAB mnem_range[] = {
1703 {"default",100},
1704 {"x-low",20},
1705 {"low",50},
1706 {"medium",100},
1707 {"high",140},
1708 {"x-high",180},
1709 {NULL, -1}};
1710
1711 static const MNEM_TAB *mnem_tabs[5] = {
1712 NULL, mnem_rate, mnem_volume, mnem_pitch, mnem_range };
1713
1714
1715 if((value = attrlookup(attr1,mnem_tabs[param_type])) >= 0)
1716 {
1717 // mnemonic specifies a value as a percentage of the base pitch/range/rate/volume
1718 sp->parameter[param_type] = (param_stack[0].parameter[param_type] * value)/100;
1719 }
1720 else
1721 {
1722 sign = attr_prosody_value(param_type,attr1,&value);
1723
1724 if(sign == 0)
1725 sp->parameter[param_type] = value; // absolute value in Hz
1726 else
1727 if(sign == 2)
1728 {
1729 // change specified as percentage or in semitones
1730 sp->parameter[param_type] = (speech_parameters[param_type] * value)/100;
1731 }
1732 else
1733 {
1734 // change specified as plus or minus Hz
1735 sp->parameter[param_type] = speech_parameters[param_type] + (value*sign);
1736 }
1737 }
1738 } // end of SetProsodyParemeter
1739
1740
ReplaceKeyName(char * outbuf,int index,int * outix)1741 static int ReplaceKeyName(char *outbuf, int index, int *outix)
1742 {//===========================================================
1743 // Replace some key-names by single characters, so they can be pronounced in different languages
1744 static MNEM_TAB keynames[] = {
1745 {"space ",0xe020},
1746 {"tab ", 0xe009},
1747 {"underscore ", 0xe05f},
1748 {"double-quote ", '"'},
1749 {NULL, 0}};
1750
1751 int ix;
1752 int letter;
1753 char *p;
1754
1755 p = &outbuf[index];
1756
1757 if((letter = LookupMnem(keynames, p)) != 0)
1758 {
1759 ix = utf8_out(letter, p);
1760 *outix = index + ix;
1761 return(letter);
1762 }
1763 return(0);
1764 }
1765
1766
ProcessSsmlTag(wchar_t * xml_buf,char * outbuf,int * outix,int n_outbuf,int self_closing)1767 static int ProcessSsmlTag(wchar_t *xml_buf, char *outbuf, int *outix, int n_outbuf, int self_closing)
1768 {//==================================================================================================
1769 // xml_buf is the tag and attributes with a zero terminator in place of the original '>'
1770 // returns a clause terminator value.
1771
1772 unsigned int ix;
1773 int index;
1774 int c;
1775 int tag_type;
1776 int value;
1777 int value2;
1778 int value3;
1779 int voice_change_flag;
1780 wchar_t *px;
1781 wchar_t *attr1;
1782 wchar_t *attr2;
1783 wchar_t *attr3;
1784 int terminator;
1785 char *uri;
1786 int param_type;
1787 char tag_name[40];
1788 char buf[80];
1789 PARAM_STACK *sp;
1790 SSML_STACK *ssml_sp;
1791
1792 static const MNEM_TAB mnem_punct[] = {
1793 {"none", 1},
1794 {"all", 2},
1795 {"some", 3},
1796 {NULL, -1}};
1797
1798 static const MNEM_TAB mnem_capitals[] = {
1799 {"no", 0},
1800 {"spelling", 2},
1801 {"icon", 1},
1802 {"pitch", 20}, // this is the amount by which to raise the pitch
1803 {NULL, -1}};
1804
1805 static const MNEM_TAB mnem_interpret_as[] = {
1806 {"characters",SAYAS_CHARS},
1807 {"tts:char",SAYAS_SINGLE_CHARS},
1808 {"tts:key",SAYAS_KEY},
1809 {"tts:digits",SAYAS_DIGITS},
1810 {"telephone",SAYAS_DIGITS1},
1811 {NULL, -1}};
1812
1813 static const MNEM_TAB mnem_sayas_format[] = {
1814 {"glyphs",1},
1815 {NULL, -1}};
1816
1817 static const MNEM_TAB mnem_break[] = {
1818 {"none",0},
1819 {"x-weak",1},
1820 {"weak",2},
1821 {"medium",3},
1822 {"strong",4},
1823 {"x-strong",5},
1824 {NULL,-1}};
1825
1826 static const MNEM_TAB mnem_emphasis[] = {
1827 {"none",1},
1828 {"reduced",2},
1829 {"moderate",3},
1830 {"strong",4},
1831 {"x-strong",5},
1832 {NULL,-1}};
1833
1834 static const char *prosody_attr[5] = {
1835 NULL, "rate", "volume", "pitch", "range" };
1836
1837 for(ix=0; ix<(sizeof(tag_name)-1); ix++)
1838 {
1839 if(((c = xml_buf[ix]) == 0) || iswspace(c))
1840 break;
1841 tag_name[ix] = tolower((char)c);
1842 }
1843 tag_name[ix] = 0;
1844
1845 px = &xml_buf[ix]; // the tag's attributes
1846
1847 if(tag_name[0] == '/')
1848 {
1849 // closing tag
1850 if((tag_type = LookupMnem(ssmltags,&tag_name[1])) != HTML_NOSPACE)
1851 {
1852 outbuf[(*outix)++] = ' ';
1853 }
1854 tag_type += SSML_CLOSE;
1855 }
1856 else
1857 {
1858 if((tag_type = LookupMnem(ssmltags,tag_name)) != HTML_NOSPACE)
1859 {
1860 // separate SSML tags from the previous word (but not HMTL tags such as <b> <font> which can occur inside a word)
1861 outbuf[(*outix)++] = ' ';
1862 }
1863
1864 if(self_closing && ignore_if_self_closing[tag_type])
1865 return(0);
1866 }
1867
1868
1869 voice_change_flag = 0;
1870 terminator = CLAUSE_NONE;
1871 ssml_sp = &ssml_stack[n_ssml_stack-1];
1872
1873 switch(tag_type)
1874 {
1875 case SSML_STYLE:
1876 sp = PushParamStack(tag_type);
1877 attr1 = GetSsmlAttribute(px,"field");
1878 attr2 = GetSsmlAttribute(px,"mode");
1879
1880
1881 if(attrcmp(attr1,"punctuation")==0)
1882 {
1883 value = attrlookup(attr2,mnem_punct);
1884 sp->parameter[espeakPUNCTUATION] = value;
1885 }
1886 else
1887 if(attrcmp(attr1,"capital_letters")==0)
1888 {
1889 value = attrlookup(attr2,mnem_capitals);
1890 sp->parameter[espeakCAPITALS] = value;
1891 }
1892 ProcessParamStack(outbuf, outix);
1893 break;
1894
1895 case SSML_PROSODY:
1896 sp = PushParamStack(tag_type);
1897
1898 // look for attributes: rate, volume, pitch, range
1899 for(param_type=espeakRATE; param_type <= espeakRANGE; param_type++)
1900 {
1901 if((attr1 = GetSsmlAttribute(px,prosody_attr[param_type])) != NULL)
1902 {
1903 SetProsodyParameter(param_type, attr1, sp);
1904 }
1905 }
1906
1907 ProcessParamStack(outbuf, outix);
1908 break;
1909
1910 case SSML_EMPHASIS:
1911 sp = PushParamStack(tag_type);
1912 value = 3; // default is "moderate"
1913 if((attr1 = GetSsmlAttribute(px,"level")) != NULL)
1914 {
1915 value = attrlookup(attr1,mnem_emphasis);
1916 }
1917
1918 if(translator->langopts.tone_language == 1)
1919 {
1920 static unsigned char emphasis_to_pitch_range[] = {50,50,40,70,90,100};
1921 static unsigned char emphasis_to_volume[] = {100,100,70,110,135,150};
1922 // tone language (eg.Chinese) do emphasis by increasing the pitch range.
1923 sp->parameter[espeakRANGE] = emphasis_to_pitch_range[value];
1924 sp->parameter[espeakVOLUME] = emphasis_to_volume[value];
1925 }
1926 else
1927 {
1928 static unsigned char emphasis_to_volume2[] = {100,100,75,100,120,150};
1929 sp->parameter[espeakVOLUME] = emphasis_to_volume2[value];
1930 sp->parameter[espeakEMPHASIS] = value;
1931 }
1932 ProcessParamStack(outbuf, outix);
1933 break;
1934
1935 case SSML_STYLE + SSML_CLOSE:
1936 case SSML_PROSODY + SSML_CLOSE:
1937 case SSML_EMPHASIS + SSML_CLOSE:
1938 PopParamStack(tag_type, outbuf, outix);
1939 break;
1940
1941 case SSML_SAYAS:
1942 attr1 = GetSsmlAttribute(px,"interpret-as");
1943 attr2 = GetSsmlAttribute(px,"format");
1944 attr3 = GetSsmlAttribute(px,"detail");
1945 value = attrlookup(attr1,mnem_interpret_as);
1946 value2 = attrlookup(attr2,mnem_sayas_format);
1947 if(value2 == 1)
1948 value = SAYAS_GLYPHS;
1949
1950 value3 = attrnumber(attr3,0,0);
1951
1952 if(value == SAYAS_DIGITS)
1953 {
1954 if(value3 <= 1)
1955 value = SAYAS_DIGITS1;
1956 else
1957 value = SAYAS_DIGITS + value3;
1958 }
1959
1960 sprintf(buf,"%c%dY",CTRL_EMBEDDED,value);
1961 strcpy(&outbuf[*outix],buf);
1962 *outix += strlen(buf);
1963
1964 sayas_start = *outix;
1965 sayas_mode = value; // punctuation doesn't end clause during SAY-AS
1966 break;
1967
1968 case SSML_SAYAS + SSML_CLOSE:
1969 if(sayas_mode == SAYAS_KEY)
1970 {
1971 outbuf[*outix] = 0;
1972 ReplaceKeyName(outbuf, sayas_start, outix);
1973 }
1974
1975 outbuf[(*outix)++] = CTRL_EMBEDDED;
1976 outbuf[(*outix)++] = 'Y';
1977 sayas_mode = 0;
1978 break;
1979
1980 case SSML_SUB:
1981 if((attr1 = GetSsmlAttribute(px,"alias")) != NULL)
1982 {
1983 // use the alias rather than the text
1984 ignore_text = 1;
1985 *outix += attrcopy_utf8(&outbuf[*outix],attr1,n_outbuf-*outix);
1986 }
1987 break;
1988
1989 case SSML_IGNORE_TEXT:
1990 ignore_text = 1;
1991 break;
1992
1993 case SSML_SUB + SSML_CLOSE:
1994 case SSML_IGNORE_TEXT + SSML_CLOSE:
1995 ignore_text = 0;
1996 break;
1997
1998 case SSML_MARK:
1999 if((attr1 = GetSsmlAttribute(px,"name")) != NULL)
2000 {
2001 // add name to circular buffer of marker names
2002 attrcopy_utf8(buf,attr1,sizeof(buf));
2003
2004 if(strcmp(skip_marker,buf)==0)
2005 {
2006 // This is the marker we are waiting for before starting to speak
2007 clear_skipping_text = 1;
2008 skip_marker[0] = 0;
2009 return(CLAUSE_NONE);
2010 }
2011
2012 if((index = AddNameData(buf,0)) >= 0)
2013 {
2014 sprintf(buf,"%c%dM",CTRL_EMBEDDED,index);
2015 strcpy(&outbuf[*outix],buf);
2016 *outix += strlen(buf);
2017 }
2018 }
2019 break;
2020
2021 case SSML_AUDIO:
2022 sp = PushParamStack(tag_type);
2023
2024 if((attr1 = GetSsmlAttribute(px,"src")) != NULL)
2025 {
2026 char fname[256];
2027 attrcopy_utf8(buf,attr1,sizeof(buf));
2028
2029 if(uri_callback == NULL)
2030 {
2031 if((xmlbase != NULL) && (buf[0] != '/'))
2032 {
2033 sprintf(fname,"%s/%s",xmlbase,buf);
2034 index = LoadSoundFile2(fname);
2035 }
2036 else
2037 {
2038 index = LoadSoundFile2(buf);
2039 }
2040 if(index >= 0)
2041 {
2042 sprintf(buf,"%c%dI",CTRL_EMBEDDED,index);
2043 strcpy(&outbuf[*outix],buf);
2044 *outix += strlen(buf);
2045 sp->parameter[espeakSILENCE] = 1;
2046 }
2047 }
2048 else
2049 {
2050 if((index = AddNameData(buf,0)) >= 0)
2051 {
2052 uri = &namedata[index];
2053 if(uri_callback(1,uri,xmlbase) == 0)
2054 {
2055 sprintf(buf,"%c%dU",CTRL_EMBEDDED,index);
2056 strcpy(&outbuf[*outix],buf);
2057 *outix += strlen(buf);
2058 sp->parameter[espeakSILENCE] = 1;
2059 }
2060 }
2061 }
2062 }
2063 ProcessParamStack(outbuf, outix);
2064
2065 if(self_closing)
2066 PopParamStack(tag_type, outbuf, outix);
2067 else
2068 audio_text = 1;
2069 return(CLAUSE_NONE);
2070
2071 case SSML_AUDIO + SSML_CLOSE:
2072 PopParamStack(tag_type, outbuf, outix);
2073 audio_text = 0;
2074 return(CLAUSE_NONE);
2075
2076 case SSML_BREAK:
2077 value = 21;
2078 terminator = CLAUSE_NONE;
2079
2080 if((attr1 = GetSsmlAttribute(px,"strength")) != NULL)
2081 {
2082 static int break_value[6] = {0,7,14,21,40,80}; // *10mS
2083 value = attrlookup(attr1,mnem_break);
2084 if(value < 3)
2085 {
2086 // adjust prepause on the following word
2087 sprintf(&outbuf[*outix],"%c%dB",CTRL_EMBEDDED,value);
2088 *outix += 3;
2089 terminator = 0;
2090 }
2091 value = break_value[value];
2092 }
2093 if((attr2 = GetSsmlAttribute(px,"time")) != NULL)
2094 {
2095 value2 = attrnumber(attr2,0,1); // pause in mS
2096
2097 // compensate for speaking speed to keep constant pause length, see function PauseLength()
2098 // 'value' here is x 10mS
2099 value = (value2 * 256) / (speed.clause_pause_factor * 10);
2100 if(value < 200)
2101 value = (value2 * 256) / (speed.pause_factor * 10);
2102
2103 if(terminator == 0)
2104 terminator = CLAUSE_NONE;
2105 }
2106 if(terminator)
2107 {
2108 if(value > 0xfff)
2109 {
2110 // scale down the value and set a scaling indicator bit
2111 value = value / 32;
2112 if(value > 0xfff)
2113 value = 0xfff;
2114 terminator |= CLAUSE_PAUSE_LONG;
2115 }
2116 return(terminator + value);
2117 }
2118 break;
2119
2120 case SSML_SPEAK:
2121 if((attr1 = GetSsmlAttribute(px,"xml:base")) != NULL)
2122 {
2123 attrcopy_utf8(buf,attr1,sizeof(buf));
2124 if((index = AddNameData(buf,0)) >= 0)
2125 {
2126 xmlbase = &namedata[index];
2127 }
2128 }
2129 if(GetVoiceAttributes(px, tag_type) == 0)
2130 return(0); // no voice change
2131 return(CLAUSE_VOICE);
2132
2133 case SSML_VOICE:
2134 if(GetVoiceAttributes(px, tag_type) == 0)
2135 return(0); // no voice change
2136 return(CLAUSE_VOICE);
2137
2138 case SSML_SPEAK + SSML_CLOSE:
2139 // unwind stack until the previous <voice> or <speak> tag
2140 while((n_ssml_stack > 1) && (ssml_stack[n_ssml_stack-1].tag_type != SSML_SPEAK))
2141 {
2142 n_ssml_stack--;
2143 }
2144 return(CLAUSE_PERIOD + GetVoiceAttributes(px, tag_type));
2145
2146 case SSML_VOICE + SSML_CLOSE:
2147 // unwind stack until the previous <voice> or <speak> tag
2148 while((n_ssml_stack > 1) && (ssml_stack[n_ssml_stack-1].tag_type != SSML_VOICE))
2149 {
2150 n_ssml_stack--;
2151 }
2152
2153 terminator=0; // ?? Sentence intonation, but no pause ??
2154 return(terminator + GetVoiceAttributes(px, tag_type));
2155
2156 case HTML_BREAK:
2157 case HTML_BREAK + SSML_CLOSE:
2158 return(CLAUSE_COLON);
2159
2160 case SSML_SENTENCE:
2161 if(ssml_sp->tag_type == SSML_SENTENCE)
2162 {
2163 // new sentence implies end-of-sentence
2164 voice_change_flag = GetVoiceAttributes(px, SSML_SENTENCE+SSML_CLOSE);
2165 }
2166 voice_change_flag |= GetVoiceAttributes(px, tag_type);
2167 return(CLAUSE_PARAGRAPH + voice_change_flag);
2168
2169
2170 case SSML_PARAGRAPH:
2171 if(ssml_sp->tag_type == SSML_SENTENCE)
2172 {
2173 // new paragraph implies end-of-sentence or end-of-paragraph
2174 voice_change_flag = GetVoiceAttributes(px, SSML_SENTENCE+SSML_CLOSE);
2175 }
2176 if(ssml_sp->tag_type == SSML_PARAGRAPH)
2177 {
2178 // new paragraph implies end-of-sentence or end-of-paragraph
2179 voice_change_flag |= GetVoiceAttributes(px, SSML_PARAGRAPH+SSML_CLOSE);
2180 }
2181 voice_change_flag |= GetVoiceAttributes(px, tag_type);
2182 return(CLAUSE_PARAGRAPH + voice_change_flag);
2183
2184
2185 case SSML_SENTENCE + SSML_CLOSE:
2186 if(ssml_sp->tag_type == SSML_SENTENCE)
2187 {
2188 // end of a sentence which specified a language
2189 voice_change_flag = GetVoiceAttributes(px, tag_type);
2190 }
2191 return(CLAUSE_PERIOD + voice_change_flag);
2192
2193
2194 case SSML_PARAGRAPH + SSML_CLOSE:
2195 if((ssml_sp->tag_type == SSML_SENTENCE) || (ssml_sp->tag_type == SSML_PARAGRAPH))
2196 {
2197 // End of a paragraph which specified a language.
2198 // (End-of-paragraph also implies end-of-sentence)
2199 return(GetVoiceAttributes(px, tag_type) + CLAUSE_PARAGRAPH);
2200 }
2201 return(CLAUSE_PARAGRAPH);
2202 }
2203 return(0);
2204 } // end of ProcessSsmlTag
2205
2206
RemoveChar(char * p)2207 static void RemoveChar(char *p)
2208 {//=======================
2209 // Replace a UTF-8 character by spaces
2210 int c;
2211
2212 memset(p, ' ', utf8_in(&c, p));
2213 } // end of RemoveChar
2214
2215
2216 static MNEM_TAB xml_char_mnemonics[] = {
2217 {"gt",'>'},
2218 {"lt", 0xe000 + '<'}, // private usage area, to avoid confusion with XML tag
2219 {"amp", '&'},
2220 {"quot", '"'},
2221 {"nbsp", ' '},
2222 {"apos", '\''},
2223 {NULL,-1}};
2224
2225
ReadClause(Translator * tr,FILE * f_in,char * buf,short * charix,int * charix_top,int n_buf,int * tone_type,char * voice_change)2226 int ReadClause(Translator *tr, FILE *f_in, char *buf, short *charix, int *charix_top, int n_buf, int *tone_type, char *voice_change)
2227 {//=================================================================================================================================
2228 /* Find the end of the current clause.
2229 Write the clause into buf
2230
2231 returns: clause type (bits 0-7: pause x10mS, bits 8-11 intonation type)
2232
2233 Also checks for blank line (paragraph) as end-of-clause indicator.
2234
2235 Does not end clause for:
2236 punctuation immediately followed by alphanumeric eg. 1.23 !Speak :path
2237 repeated punctuation, eg. ... !!!
2238 */
2239 int c1=' '; // current character
2240 int c2; // next character
2241 int cprev=' '; // previous character
2242 int cprev2=' ';
2243 int c_next;
2244 int parag;
2245 int ix = 0;
2246 int j;
2247 int nl_count;
2248 int linelength = 0;
2249 int phoneme_mode = 0;
2250 int n_xml_buf;
2251 int terminator;
2252 int punct;
2253 int found;
2254 int any_alnum = 0;
2255 int self_closing;
2256 int punct_data = 0;
2257 int is_end_clause;
2258 int announced_punctuation = 0;
2259 int stressed_word = 0;
2260 int end_clause_after_tag = 0;
2261 int end_clause_index = 0;
2262 wchar_t xml_buf[N_XML_BUF+1];
2263
2264 #define N_XML_BUF2 20
2265 char xml_buf2[N_XML_BUF2+2]; // for &<name> and &<number> sequences
2266 static char ungot_string[N_XML_BUF2+4];
2267 static int ungot_string_ix = -1;
2268
2269 if(clear_skipping_text)
2270 {
2271 skipping_text = 0;
2272 clear_skipping_text = 0;
2273 }
2274
2275 tr->phonemes_repeat_count = 0;
2276 tr->clause_upper_count = 0;
2277 tr->clause_lower_count = 0;
2278 end_of_input = 0;
2279 *tone_type = 0;
2280 *voice_change = 0;
2281
2282 f_input = f_in; // for GetC etc
2283
2284 if(ungot_word != NULL)
2285 {
2286 strcpy(buf,ungot_word);
2287 ix += strlen(ungot_word);
2288 ungot_word = NULL;
2289 }
2290
2291 if(ungot_char2 != 0)
2292 {
2293 c2 = ungot_char2;
2294 }
2295 else
2296 {
2297 c2 = GetC();
2298 }
2299
2300 while(!Eof() || (ungot_char != 0) || (ungot_char2 != 0) || (ungot_string_ix >= 0))
2301 {
2302 if(!iswalnum(c1))
2303 {
2304 if((end_character_position > 0) && (count_characters > end_character_position))
2305 {
2306 end_of_input = 1;
2307 return(CLAUSE_EOF);
2308 }
2309
2310 if((skip_characters > 0) && (count_characters >= skip_characters))
2311 {
2312 // reached the specified start position
2313 // don't break a word
2314 clear_skipping_text = 1;
2315 skip_characters = 0;
2316 UngetC(c2);
2317 return(CLAUSE_NONE);
2318 }
2319 }
2320
2321 cprev2 = cprev;
2322 cprev = c1;
2323 c1 = c2;
2324
2325 if(ungot_string_ix >= 0)
2326 {
2327 if(ungot_string[ungot_string_ix] == 0)
2328 ungot_string_ix = -1;
2329 }
2330
2331 if((ungot_string_ix == 0) && (ungot_char2 == 0))
2332 {
2333 c1 = ungot_string[ungot_string_ix++];
2334 }
2335 if(ungot_string_ix >= 0)
2336 {
2337 c2 = ungot_string[ungot_string_ix++];
2338 }
2339 else
2340 {
2341 c2 = GetC();
2342
2343 if(Eof())
2344 {
2345 c2 = ' ';
2346 }
2347 }
2348 ungot_char2 = 0;
2349
2350 if((option_ssml) && (phoneme_mode==0))
2351 {
2352 if((ssml_ignore_l_angle != '&') && (c1 == '&') && ((c2=='#') || ((c2 >= 'a') && (c2 <= 'z'))))
2353 {
2354 n_xml_buf = 0;
2355 c1 = c2;
2356 while(!Eof() && (iswalnum(c1) || (c1=='#')) && (n_xml_buf < N_XML_BUF2))
2357 {
2358 xml_buf2[n_xml_buf++] = c1;
2359 c1 = GetC();
2360 }
2361 xml_buf2[n_xml_buf] = 0;
2362 c2 = GetC();
2363 sprintf(ungot_string,"%s%c%c",&xml_buf2[0],c1,c2);
2364
2365 if(c1 == ';')
2366 {
2367 if(xml_buf2[0] == '#')
2368 {
2369 // character code number
2370 if(xml_buf2[1] == 'x')
2371 found = sscanf(&xml_buf2[2],"%x",(unsigned int *)(&c1));
2372 else
2373 found = sscanf(&xml_buf2[1],"%d",&c1);
2374 }
2375 else
2376 {
2377 if((found = LookupMnem(xml_char_mnemonics,xml_buf2)) != -1)
2378 {
2379 c1 = found;
2380 if(c2 == 0)
2381 c2 = ' ';
2382 }
2383 }
2384 }
2385 else
2386 {
2387 found = -1;
2388 }
2389
2390 if(found <= 0)
2391 {
2392 ungot_string_ix = 0;
2393 c1 = '&';
2394 c2 = ' ';
2395 }
2396
2397 if((c1 <= 0x20) && ((sayas_mode == SAYAS_SINGLE_CHARS) || (sayas_mode == SAYAS_KEY)))
2398 {
2399 c1 += 0xe000; // move into unicode private usage area
2400 }
2401 }
2402 else
2403 if((c1 == '<') && (ssml_ignore_l_angle != '<'))
2404 {
2405 if(c2 == '!')
2406 {
2407 // a comment, ignore until closing '<'
2408 while(!Eof() && (c1 != '>'))
2409 {
2410 c1 = GetC();
2411 }
2412 c2 = ' ';
2413 }
2414 else
2415 if((c2 == '/') || iswalpha2(c2))
2416 {
2417 // check for space in the output buffer for embedded commands produced by the SSML tag
2418 if(ix > (n_buf - 20))
2419 {
2420 // Perhaps not enough room, end the clause before the SSML tag
2421 UngetC(c2);
2422 ungot_char2 = c1;
2423 buf[ix] = ' ';
2424 buf[ix+1] = 0;
2425 return(CLAUSE_NONE);
2426 }
2427
2428 // SSML Tag
2429 n_xml_buf = 0;
2430 c1 = c2;
2431 while(!Eof() && (c1 != '>') && (n_xml_buf < N_XML_BUF))
2432 {
2433 xml_buf[n_xml_buf++] = c1;
2434 c1 = GetC();
2435 }
2436 xml_buf[n_xml_buf] = 0;
2437 c2 = ' ';
2438
2439 self_closing = 0;
2440 if(xml_buf[n_xml_buf-1] == '/')
2441 {
2442 // a self-closing tag
2443 xml_buf[n_xml_buf-1] = ' ';
2444 self_closing = 1;
2445 }
2446
2447 terminator = ProcessSsmlTag(xml_buf,buf,&ix,n_buf,self_closing);
2448
2449 if(terminator != 0)
2450 {
2451 if(end_clause_after_tag)
2452 ix = end_clause_index;
2453
2454 buf[ix] = ' ';
2455 buf[ix++] = 0;
2456
2457 if(terminator & CLAUSE_BIT_VOICE)
2458 {
2459 strcpy(voice_change, current_voice_id);
2460 }
2461 return(terminator);
2462 }
2463 c1 = ' ';
2464 c2 = GetC();
2465 continue;
2466 }
2467 }
2468 }
2469 ssml_ignore_l_angle=0;
2470
2471 if(ignore_text)
2472 continue;
2473
2474 if((c2=='\n') && (option_linelength == -1))
2475 {
2476 // single-line mode, return immediately on NL
2477 if((punct = lookupwchar(punct_chars,c1)) == 0)
2478 {
2479 charix[ix] = count_characters - clause_start_char;
2480 *charix_top = ix;
2481 ix += utf8_out(c1,&buf[ix]);
2482 terminator = CLAUSE_PERIOD; // line doesn't end in punctuation, assume period
2483 }
2484 else
2485 {
2486 terminator = punct_attributes[punct];
2487 }
2488 buf[ix] = ' ';
2489 buf[ix+1] = 0;
2490 return(terminator);
2491 }
2492
2493 if((c1 == CTRL_EMBEDDED) || (c1 == ctrl_embedded))
2494 {
2495 // an embedded command. If it's a voice change, end the clause
2496 if(c2 == 'V')
2497 {
2498 buf[ix++] = 0; // end the clause at this point
2499 while(!iswspace(c1 = GetC()) && !Eof() && (ix < (n_buf-1)))
2500 buf[ix++] = c1; // add voice name to end of buffer, after the text
2501 buf[ix++] = 0;
2502 return(CLAUSE_VOICE);
2503 }
2504 else
2505 if(c2 == 'B')
2506 {
2507 // set the punctuation option from an embedded command
2508 // B0 B1 B<punct list><space>
2509 strcpy(&buf[ix]," ");
2510 ix += 3;
2511
2512 if((c2 = GetC()) == '0')
2513 option_punctuation = 0;
2514 else
2515 {
2516 option_punctuation = 1;
2517 option_punctlist[0] = 0;
2518 if(c2 != '1')
2519 {
2520 // a list of punctuation characters to be spoken, terminated by space
2521 j = 0;
2522 while(!iswspace(c2) && !Eof())
2523 {
2524 option_punctlist[j++] = c2;
2525 c2 = GetC();
2526 buf[ix++] = ' ';
2527 }
2528 option_punctlist[j] = 0; // terminate punctuation list
2529 option_punctuation = 2;
2530 }
2531 }
2532 c2 = GetC();
2533 continue;
2534 }
2535 }
2536
2537 linelength++;
2538
2539 if((j = lookupwchar2(tr->chars_ignore,c1)) != 0)
2540 {
2541 if(j == 1)
2542 {
2543 // ignore this character (eg. zero-width-non-joiner U+200C)
2544 continue;
2545 }
2546 c1 = j; // replace the character
2547 }
2548
2549 if(iswalnum(c1))
2550 any_alnum = 1;
2551 else
2552 {
2553 if(stressed_word)
2554 {
2555 stressed_word = 0;
2556 c1 = CHAR_EMPHASIS; // indicate this word is stressed
2557 UngetC(c2);
2558 c2 = ' ';
2559 }
2560
2561 if(c1 == 0xf0b)
2562 c1 = ' '; // Tibet inter-syllabic mark, ?? replace by space ??
2563
2564 if(iswspace(c1))
2565 {
2566 char *p_word;
2567
2568 if(tr->translator_name == 0x6a626f)
2569 {
2570 // language jbo : lojban
2571 // treat "i" or ".i" as end-of-sentence
2572 p_word = &buf[ix-1];
2573 if(p_word[0] == 'i')
2574 {
2575 if(p_word[-1] == '.')
2576 p_word--;
2577 if(p_word[-1] == ' ')
2578 {
2579 ungot_word = "i ";
2580 UngetC(c2);
2581 p_word[0] = 0;
2582 return(CLAUSE_PERIOD);
2583 }
2584 }
2585 }
2586 }
2587
2588 if(c1 == 0xd4d)
2589 {
2590 // Malayalam virama, check if next character is Zero-width-joiner
2591 if(c2 == 0x200d)
2592 {
2593 c1 = 0xd4e; // use this unofficial code for chillu-virama
2594 }
2595 }
2596 }
2597
2598 if(iswupper2(c1))
2599 {
2600 tr->clause_upper_count++;
2601 if((option_capitals == 2) && (sayas_mode == 0) && !iswupper2(cprev))
2602 {
2603 char text_buf[40];
2604 char text_buf2[30];
2605 if(LookupSpecial(tr, "_cap", text_buf2) != NULL)
2606 {
2607 sprintf(text_buf,"%s",text_buf2);
2608 j = strlen(text_buf);
2609 if((ix + j) < n_buf)
2610 {
2611 strcpy(&buf[ix],text_buf);
2612 ix += j;
2613 }
2614 }
2615 }
2616 }
2617 else
2618 if(iswalpha2(c1))
2619 tr->clause_lower_count++;
2620
2621 if(option_phoneme_input)
2622 {
2623 if(phoneme_mode > 0)
2624 phoneme_mode--;
2625 else
2626 if((c1 == '[') && (c2 == '['))
2627 phoneme_mode = -1; // input is phoneme mnemonics, so don't look for punctuation
2628 else
2629 if((c1 == ']') && (c2 == ']'))
2630 phoneme_mode = 2; // set phoneme_mode to zero after the next two characters
2631 }
2632
2633 if(c1 == '\n')
2634 {
2635 parag = 0;
2636
2637 // count consecutive newlines, ignoring other spaces
2638 while(!Eof() && iswspace(c2))
2639 {
2640 if(c2 == '\n')
2641 parag++;
2642 c2 = GetC();
2643 }
2644 if(parag > 0)
2645 {
2646 // 2nd newline, assume paragraph
2647 UngetC(c2);
2648
2649 if(end_clause_after_tag)
2650 {
2651 RemoveChar(&buf[end_clause_index]); // delete clause-end punctiation
2652 }
2653 buf[ix] = ' ';
2654 buf[ix+1] = 0;
2655 if(parag > 3)
2656 parag = 3;
2657 if(option_ssml) parag=1;
2658 return((CLAUSE_PARAGRAPH-30) + 30*parag); // several blank lines, longer pause
2659 }
2660
2661 if(linelength <= option_linelength)
2662 {
2663 // treat lines shorter than a specified length as end-of-clause
2664 UngetC(c2);
2665 buf[ix] = ' ';
2666 buf[ix+1] = 0;
2667 return(CLAUSE_COLON);
2668 }
2669
2670 linelength = 0;
2671 }
2672
2673 announced_punctuation = 0;
2674
2675 if((phoneme_mode==0) && (sayas_mode==0))
2676 {
2677 is_end_clause = 0;
2678
2679 if(end_clause_after_tag)
2680 {
2681 // Because of an xml tag, we are waiting for the
2682 // next non-blank character to decide whether to end the clause
2683 // i.e. is dot followed by an upper-case letter?
2684
2685 if(!iswspace(c1))
2686 {
2687 if(!IsAlpha(c1) || !iswlower2(c1))
2688 // if(iswdigit(c1) || (IsAlpha(c1) && !iswlower2(c1)))
2689 {
2690 UngetC(c2);
2691 ungot_char2 = c1;
2692 buf[end_clause_index] = ' '; // delete the end-clause punctuation
2693 buf[end_clause_index+1] = 0;
2694 return(end_clause_after_tag);
2695 }
2696 end_clause_after_tag = 0;
2697 }
2698 }
2699
2700 if((c1 == '.') && (c2 == '.'))
2701 {
2702 while((c_next = GetC()) == '.')
2703 {
2704 // 3 or more dots, replace by elipsis
2705 c1 = 0x2026;
2706 c2 = ' ';
2707 }
2708 if(c1 == 0x2026)
2709 c2 = c_next;
2710 else
2711 UngetC(c_next);
2712 }
2713
2714 punct_data = 0;
2715 if((punct = lookupwchar(punct_chars,c1)) != 0)
2716 {
2717 punct_data = punct_attributes[punct];
2718
2719 if(punct_data & PUNCT_IN_WORD)
2720 {
2721 // Armenian punctuation inside a word
2722 stressed_word = 1;
2723 *tone_type = punct_data >> 12 & 0xf; // override the end-of-sentence type
2724 continue;
2725 }
2726
2727 if((iswspace(c2) || (punct_data & 0x8000) || IsBracket(c2) || (c2=='?') || Eof() || (c2 == ctrl_embedded))) // don't check for '-' because it prevents recognizing ':-)'
2728 // if((iswspace(c2) || (punct_data & 0x8000) || IsBracket(c2) || (c2=='?') || (c2=='-') || Eof()))
2729 {
2730 // note: (c2='?') is for when a smart-quote has been replaced by '?'
2731 is_end_clause = 1;
2732 }
2733 }
2734
2735 // don't announce punctuation for the alternative text inside inside <audio> ... </audio>
2736 if(c1 == 0xe000+'<') c1 = '<';
2737 if(option_punctuation && iswpunct(c1) && (audio_text == 0))
2738 {
2739 // option is set to explicitly speak punctuation characters
2740 // if a list of allowed punctuation has been set up, check whether the character is in it
2741 if((option_punctuation == 1) || (wcschr(option_punctlist,c1) != NULL))
2742 {
2743 tr->phonemes_repeat_count = 0;
2744 if((terminator = AnnouncePunctuation(tr, c1, &c2, buf, &ix, is_end_clause)) >= 0)
2745 return(terminator);
2746 announced_punctuation = c1;
2747 }
2748 }
2749
2750 if((punct_data & PUNCT_SAY_NAME) && (announced_punctuation == 0))
2751 {
2752 // used for elipsis (and 3 dots) if a pronunciation for elipsis is given in *_list
2753 char *p2;
2754
2755 p2 = &buf[ix];
2756 sprintf(p2,"%s",LookupCharName(tr, c1, 1));
2757 if(p2[0] != 0)
2758 {
2759 ix += strlen(p2);
2760 announced_punctuation = c1;
2761 punct_data = punct_data & ~CLAUSE_BITS_INTONATION; // change intonation type to 0 (full-stop)
2762 }
2763 }
2764
2765 if(is_end_clause)
2766 {
2767 nl_count = 0;
2768 c_next = c2;
2769
2770 if(iswspace(c_next))
2771 {
2772 while(!Eof() && iswspace(c_next))
2773 {
2774 if(c_next == '\n')
2775 nl_count++;
2776 c_next = GetC(); // skip past space(s)
2777 }
2778 }
2779
2780 if((c1 == '.') && (nl_count < 2))
2781 {
2782 punct_data |= CLAUSE_DOT;
2783 }
2784
2785 if(nl_count==0)
2786 {
2787 if((c1 == ',') && (cprev == '.') && (tr->translator_name == L('h','u')) && iswdigit(cprev2) && (iswdigit(c_next) || (iswlower2(c_next))))
2788 {
2789 // lang=hu, fix for ordinal numbers, eg: "december 2., szerda", ignore ',' after ordinal number
2790 c1 = CHAR_COMMA_BREAK;
2791 is_end_clause = 0;
2792 }
2793
2794 if(c1 == '.')
2795 {
2796 if((tr->langopts.numbers & NUM_ORDINAL_DOT) &&
2797 (iswdigit(cprev) || (IsRomanU(cprev) && (IsRomanU(cprev2) || iswspace(cprev2))))) // lang=hu
2798 {
2799 // dot after a number indicates an ordinal number
2800 if(!iswdigit(cprev))
2801 {
2802 is_end_clause = 0; // Roman number followed by dot
2803 }
2804 else
2805 {
2806 if (iswlower2(c_next) || (c_next=='-')) // hyphen is needed for lang-hu (eg. 2.-kal)
2807 is_end_clause = 0; // only if followed by lower-case, (or if there is a XML tag)
2808 }
2809 }
2810 else
2811 if(c_next == '\'')
2812 {
2813 is_end_clause = 0; // eg. u.s.a.'s
2814 }
2815 if(iswlower2(c_next))
2816 {
2817 // next word has no capital letter, this dot is probably from an abbreviation
2818 // c1 = ' ';
2819 is_end_clause = 0;
2820 }
2821 if(any_alnum==0)
2822 {
2823 // no letters or digits yet, so probably not a sentence terminator
2824 // Here, dot is followed by space or bracket
2825 c1 = ' ';
2826 is_end_clause = 0;
2827 }
2828 }
2829 else
2830 {
2831 if(any_alnum==0)
2832 {
2833 // no letters or digits yet, so probably not a sentence terminator
2834 is_end_clause = 0;
2835 }
2836 }
2837
2838 if(is_end_clause && (c1 == '.') && (c_next == '<') && option_ssml)
2839 {
2840 // wait until after the end of the xml tag, then look for upper-case letter
2841 is_end_clause = 0;
2842 end_clause_index = ix;
2843 end_clause_after_tag = punct_data;
2844 }
2845 }
2846
2847 if(is_end_clause)
2848 {
2849 UngetC(c_next);
2850 buf[ix] = ' ';
2851 buf[ix+1] = 0;
2852
2853 if(iswdigit(cprev) && !IsAlpha(c_next)) // ????
2854 {
2855 punct_data &= ~CLAUSE_DOT;
2856 }
2857 if(nl_count > 1)
2858 {
2859 if((punct_data == CLAUSE_QUESTION) || (punct_data == CLAUSE_EXCLAMATION))
2860 return(punct_data + 35); // with a longer pause
2861 return(CLAUSE_PARAGRAPH);
2862 }
2863 return(punct_data); // only recognise punctuation if followed by a blank or bracket/quote
2864 }
2865 else
2866 {
2867 if(!Eof())
2868 {
2869 if(iswspace(c2))
2870 UngetC(c_next);
2871 }
2872 }
2873 }
2874 }
2875
2876 if(speech_parameters[espeakSILENCE]==1)
2877 continue;
2878
2879 if(c1 == announced_punctuation)
2880 {
2881 // This character has already been announced, so delete it so that it isn't spoken a second time.
2882 // Unless it's a hyphen or apostrophe (which is used by TranslateClause() )
2883 if(IsBracket(c1))
2884 {
2885 c1 = 0xe000 + '('; // Unicode private useage area. So TranslateRules() knows the bracket name has been spoken
2886 }
2887 else
2888 if(c1 != '-')
2889 {
2890 c1 = ' ';
2891 }
2892 }
2893
2894 j = ix+1;
2895
2896 if(c1 == 0xe000 + '<') c1 = '<';
2897
2898 ix += utf8_out(c1,&buf[ix]); // buf[ix++] = c1;
2899 if(!iswspace(c1) && !IsBracket(c1))
2900 {
2901 charix[ix] = count_characters - clause_start_char;
2902 while(j < ix)
2903 charix[j++] = -1; // subsequent bytes of a multibyte character
2904 }
2905 *charix_top = ix;
2906
2907 if(((ix > (n_buf-75)) && !IsAlpha(c1) && !iswdigit(c1)) || (ix >= (n_buf-4)))
2908 {
2909 // clause too long, getting near end of buffer, so break here
2910 // try to break at a word boundary (unless we actually reach the end of buffer).
2911 // (n_buf-4) is to allow for 3 bytes of multibyte character plus terminator.
2912 buf[ix] = ' ';
2913 buf[ix+1] = 0;
2914 UngetC(c2);
2915 return(CLAUSE_NONE);
2916 }
2917 }
2918
2919 if(stressed_word)
2920 {
2921 ix += utf8_out(CHAR_EMPHASIS, &buf[ix]);
2922 }
2923 if(end_clause_after_tag)
2924 {
2925 RemoveChar(&buf[end_clause_index]); // delete clause-end punctiation
2926 }
2927 buf[ix] = ' ';
2928 buf[ix+1] = 0;
2929 return(CLAUSE_EOF); // end of file
2930 } // end of ReadClause
2931
2932
InitNamedata(void)2933 void InitNamedata(void)
2934 {//====================
2935 namedata_ix = 0;
2936 if(namedata != NULL)
2937 {
2938 free(namedata);
2939 namedata = NULL;
2940 n_namedata = 0;
2941 }
2942 }
2943
2944
InitText2(void)2945 void InitText2(void)
2946 {//=================
2947 int param;
2948
2949 ungot_char = 0;
2950 ungot_char2 = 0;
2951
2952 n_ssml_stack =1;
2953 n_param_stack = 1;
2954 ssml_stack[0].tag_type = 0;
2955
2956 for(param=0; param<N_SPEECH_PARAM; param++)
2957 speech_parameters[param] = param_stack[0].parameter[param]; // set all speech parameters to defaults
2958
2959 option_punctuation = speech_parameters[espeakPUNCTUATION];
2960 option_capitals = speech_parameters[espeakCAPITALS];
2961
2962 current_voice_id[0] = 0;
2963
2964 ignore_text = 0;
2965 audio_text = 0;
2966 clear_skipping_text = 0;
2967 count_characters = -1;
2968 sayas_mode = 0;
2969
2970 xmlbase = NULL;
2971 }
2972
2973