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 &lt; 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 = &param_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, &current_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