PageRenderTime 64ms CodeModel.GetById 15ms app.highlight 43ms RepoModel.GetById 1ms app.codeStats 0ms

/native/external/espeak/src/phonemelist.cpp

http://eyes-free.googlecode.com/
C++ | 664 lines | 502 code | 96 blank | 66 comment | 218 complexity | 2a91be9ac15d9f2dd5bb6d97b662b3ba MD5 | raw file
  1/***************************************************************************
  2 *   Copyright (C) 2005 to 2007 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, see:                                 *
 17 *               <http://www.gnu.org/licenses/>.                           *
 18 ***************************************************************************/
 19
 20#include "StdAfx.h"
 21
 22#include <stdio.h>
 23#include <stdlib.h>
 24#include <string.h>
 25
 26#include "speak_lib.h"
 27#include "speech.h"
 28#include "phoneme.h"
 29#include "synthesize.h"
 30#include "translate.h"
 31
 32
 33const unsigned char pause_phonemes[8] = {0, phonPAUSE_VSHORT, phonPAUSE_SHORT, phonPAUSE, phonPAUSE_LONG, phonGLOTTALSTOP, phonPAUSE_LONG, phonPAUSE_LONG};
 34
 35
 36int Translator::ChangePhonemes(PHONEME_LIST2 *phlist, int n_ph, int index, PHONEME_TAB *ph, CHANGEPH *ch)
 37{//======================================================================================================
 38// Called for each phoneme in the phoneme list, to allow a language to make changes
 39// ph     The current phoneme
 40	return(0);
 41}
 42
 43
 44int Translator::SubstitutePhonemes(PHONEME_LIST2 *plist_out)
 45{//=========================================================
 46// Copy the phonemes list and perform any substitutions that are required for the
 47// current voice
 48	int ix;
 49	int k;
 50	int replace_flags;
 51	int n_plist_out = 0;
 52	int word_end;
 53	int max_stress = -1;
 54	int switched_language = 0;
 55	int max_stress_posn=0;
 56	int n_syllables = 0;
 57	int syllable = 0;
 58	int syllable_stressed = 0;
 59	PHONEME_LIST2 *plist2;
 60	PHONEME_LIST2 *pl;
 61	PHONEME_TAB *next=NULL;
 62
 63	for(ix=0; (ix < n_ph_list2) && (n_plist_out < N_PHONEME_LIST); ix++)
 64	{
 65		plist2 = &ph_list2[ix];
 66
 67		if(plist2->phcode == phonSWITCH)
 68			switched_language ^= 1;
 69
 70		// don't do any substitution if the language has been temporarily changed
 71		if(switched_language == 0)
 72		{
 73			if(ix < (n_ph_list2 -1))
 74				next = phoneme_tab[ph_list2[ix+1].phcode];
 75	
 76			word_end = 0;
 77			if((plist2+1)->sourceix || ((next != 0) && (next->type == phPAUSE)))
 78				word_end = 1;        // this phoneme is the end of a word
 79	
 80			if(langopts.phoneme_change != 0)
 81			{
 82				// this language does changes to phonemes after translation
 83
 84				if(plist2->sourceix)
 85				{
 86					// start of a word, find the stressed vowel
 87					syllable = 0;
 88					syllable_stressed = 0;
 89					n_syllables = 0;
 90
 91					max_stress = -1;
 92					max_stress_posn = ix;
 93					for(k=ix; k < n_ph_list2; k++)
 94					{
 95						if(((pl = &ph_list2[k])->sourceix != 0) && (k > ix))
 96							break;
 97		
 98						pl->stress &= 0xf;
 99		
100						if(phoneme_tab[pl->phcode]->type == phVOWEL)
101						{
102							n_syllables++;
103
104							if(pl->stress  > max_stress)
105							{
106								syllable_stressed = n_syllables;
107								max_stress = pl->stress;
108								max_stress_posn = k;
109							}
110						}
111					}
112				}
113
114				if(phoneme_tab[plist2->phcode]->type == phVOWEL)
115				{
116					syllable++;
117				}
118	
119				// make any language specific changes
120				int flags;
121				CHANGEPH ch;
122				flags = 0;
123				if(ix == max_stress_posn)
124					flags |= 2;
125				if(ix > max_stress_posn)
126					flags |= 4;
127				if(ph_list2[ix].synthflags & SFLAG_DICTIONARY)
128					flags |= 8;
129				ch.flags = flags | word_end;
130
131				ch.stress = plist2->stress;
132				ch.stress_highest = max_stress;
133				ch.n_vowels = n_syllables;
134				ch.vowel_this = syllable;
135				ch.vowel_stressed = syllable_stressed;
136
137				ChangePhonemes(ph_list2, n_ph_list2, ix, phoneme_tab[ph_list2[ix].phcode], &ch);
138			}
139	
140			// check whether a Voice has specified that we should replace this phoneme
141			for(k=0; k<n_replace_phonemes; k++)
142			{
143				if(plist2->phcode == replace_phonemes[k].old_ph)
144				{
145					replace_flags = replace_phonemes[k].type;
146	
147					if((replace_flags & 1) && (word_end == 0))
148						continue;     // this replacement only occurs at the end of a word
149	
150					if((replace_flags & 2) && ((plist2->stress & 0x7) > 3))
151						continue;     // this replacement doesn't occur in stressed syllables
152	
153					// substitute the replacement phoneme
154					plist2->phcode = replace_phonemes[k].new_ph;
155					break;
156				}
157			}
158	
159			if(plist2->phcode == 0)
160			{
161				continue;   // phoneme has been replaced by NULL, so don't copy it
162			}
163		}
164
165		// copy phoneme into the output list
166		memcpy(&plist_out[n_plist_out++],plist2,sizeof(PHONEME_LIST2));
167	}
168	return(n_plist_out);
169}  //  end of SubstitutePhonemes
170
171
172
173void Translator::MakePhonemeList(int post_pause, int start_sentence)
174{//============================================================================================
175
176	int  ix=0;
177	int  j;
178	int  insert_ph = 0;
179	PHONEME_LIST *phlist;
180	PHONEME_TAB *ph;
181	PHONEME_TAB *prev, *next, *next2;
182	int unstress_count = 0;
183	int word_stress = 0;
184	int switched_language = 0;
185	int max_stress;
186	int voicing;
187	int regression;
188	int end_sourceix;
189	int alternative;
190	int first_vowel=0;   // first vowel in a word
191	PHONEME_LIST2 ph_list3[N_PHONEME_LIST];
192
193	static PHONEME_LIST2 ph_list2_null = {0,0,0,0,0};
194	PHONEME_LIST2 *plist2 = &ph_list2_null;
195	PHONEME_LIST2 *plist2_inserted = NULL;
196
197	phlist = phoneme_list;
198	end_sourceix = ph_list2[n_ph_list2-1].sourceix;
199
200	// is the last word of the clause unstressed ?
201	max_stress = 0;
202	for(j=n_ph_list2-3; j>=0; j--)
203	{
204		// start with the last phoneme (before the terminating pauses) and move forwards
205		if((ph_list2[j].stress & 0x7f) > max_stress)
206			max_stress = ph_list2[j].stress & 0x7f;
207		if(ph_list2[j].sourceix != 0)
208			break;
209	}
210	if(max_stress < 4)
211	{
212		// the last word is unstressed, look for a previous word that can be stressed
213		while(--j >= 0)
214		{
215			if(ph_list2[j].synthflags & SFLAG_PROMOTE_STRESS)  // dictionary flags indicated that this stress can be promoted
216			{
217				ph_list2[j].stress = 4;   // promote to stressed
218				break;
219			}
220			if(ph_list2[j].stress >= 4)
221			{
222				// found a stressed syllable, so stop looking
223				break;
224			}
225		}
226	}
227
228	if((regression = langopts.param[LOPT_REGRESSIVE_VOICING]) != 0)
229	{
230		// set consonant clusters to all voiced or all unvoiced
231		// Regressive
232		int type;
233		voicing = 0;
234
235		for(j=n_ph_list2-1; j>=0; j--)
236		{
237			ph = phoneme_tab[ph_list2[j].phcode];
238			if(ph == NULL)
239				continue;
240
241			if(ph->code == phonSWITCH)
242				switched_language ^= 1;
243			if(switched_language)
244				continue;
245
246			type = ph->type;
247
248			if(regression & 0x2)
249			{
250				// LANG=Russian, [v] amd [v;] don't cause regression, or [R^]
251				if((ph->mnemonic == 'v') || (ph->mnemonic == ((';'<<8)+'v')) || ((ph->mnemonic & 0xff)== 'R'))
252					type = phLIQUID;
253			}
254
255			if((type==phSTOP) || type==(phFRICATIVE))
256			{
257				if(voicing==0)
258				{
259					voicing = 1;
260				}
261				else
262				if((voicing==2) && ((ph->phflags & phALTERNATIVE)==phSWITCHVOICING))
263				{
264					ph_list2[j].phcode = ph->alternative_ph;  // change to voiced equivalent
265				}
266			}
267			else
268			if((type==phVSTOP) || type==(phVFRICATIVE))
269			{
270				if(voicing==0)
271				{
272					voicing = 2;
273				}
274				else
275				if((voicing==1) && ((ph->phflags & phALTERNATIVE)==phSWITCHVOICING))
276				{
277					ph_list2[j].phcode = ph->alternative_ph;  // change to unvoiced equivalent
278				}
279			}
280			else
281			{
282				if(regression & 0x8)
283				{
284					// LANG=Polish, propagate through liquids and nasals
285					if((type == phPAUSE) || (type == phVOWEL))
286						voicing = 0;
287				}
288				else
289				{
290					voicing = 0;
291				}
292			}
293			if((regression & 0x4) && (ph_list2[j].sourceix))
294			{
295				// stop propagation at a word boundary
296				voicing = 0;
297			}
298		}
299	}
300
301	n_ph_list2 = SubstitutePhonemes(ph_list3) - 2;
302
303	// transfer all the phonemes of the clause into phoneme_list
304	ph = phoneme_tab[phonPAUSE];
305	switched_language = 0;
306
307	for(j=0; insert_ph || ((j<n_ph_list2) && (ix < N_PHONEME_LIST-3)); j++)
308	{
309		prev = ph;
310
311		plist2 = &ph_list3[j];
312
313		if(insert_ph != 0)
314		{
315			// we have a (linking) phoneme which we need to insert here
316			next = phoneme_tab[plist2->phcode];      // this phoneme, i.e. after the insert
317
318			// re-use the previous entry for the inserted phoneme.
319			// That's OK because we don't look backwards from plist2
320			j--;
321			plist2 = plist2_inserted = &ph_list3[j];
322			memset(plist2, 0, sizeof(*plist2));
323			plist2->phcode = insert_ph;
324			ph = phoneme_tab[insert_ph];
325			insert_ph = 0;
326		}
327		else
328		{
329			// otherwise get the next phoneme from the list
330			ph = phoneme_tab[plist2->phcode];
331
332			if(plist2->phcode == phonSWITCH)
333			{
334				// change phoneme table
335				SelectPhonemeTable(plist2->tone_number);
336				switched_language ^= SFLAG_SWITCHED_LANG;
337			}
338			next = phoneme_tab[(plist2+1)->phcode];      // the phoneme after this one
339		}
340
341		if(plist2->sourceix)
342		{
343			// start of a word
344			int k;
345			word_stress = 0;
346			first_vowel = 1;
347
348			// find the highest stress level in this word
349			for(k=j+1; k < n_ph_list2; k++)
350			{
351				if(ph_list3[k].sourceix)
352					break;   // start of the next word
353
354				if(ph_list3[k].stress > word_stress)
355					word_stress = ph_list3[k].stress;
356			}
357		}
358
359		if(ph == NULL) continue;
360
361		if(ph->type == phVOWEL)
362		{
363			// check for consecutive unstressed syllables
364			if(plist2->stress == 0)
365			{
366				// an unstressed vowel
367				unstress_count++;
368				if((unstress_count > 1) && ((unstress_count & 1)==0))
369				{
370					// in a sequence of unstressed syllables, reduce alternate syllables to 'diminished'
371               // stress.  But not for the last phoneme of a stressed word
372					if((langopts.stress_flags & 0x2) || ((word_stress > 3) && ((plist2+1)->sourceix!=0)))
373					{
374						// An unstressed final vowel of a stressed word
375						unstress_count=1;    // try again for next syllable
376					}
377					else
378					{
379						plist2->stress = 1;    // change stress to 'diminished'
380					}
381				}
382			}
383			else
384			{
385				unstress_count = 0;
386			}
387		}
388
389		alternative = 0;
390
391		if(ph->alternative_ph > 0)
392		{
393			switch(ph->phflags & phALTERNATIVE)
394			{
395			// This phoneme changes if vowel follows, or doesn't follow, depending on its phNOTFOLLOWS flag
396			case phBEFORENOTVOWEL:
397				if(next->type != phVOWEL)
398					alternative = ph->alternative_ph;
399				break;
400
401			case phBEFORENOTVOWEL2:    // LANG=tr
402				if(((plist2+1)->sourceix != 0) ||
403               ((next->type != phVOWEL) && ((phoneme_tab[(plist2+2)->phcode]->type != phVOWEL) || ((plist2+2)->sourceix != 0))))
404				{
405					alternative = ph->alternative_ph;
406				}
407				break;
408
409			case phBEFOREVOWELPAUSE:
410				if((next->type == phVOWEL) || (next->type == phPAUSE))
411					alternative = ph->alternative_ph;
412				break;
413
414			case phBEFOREVOWEL:
415				if(next->type == phVOWEL)
416					alternative = ph->alternative_ph;
417				break;
418			}
419		}
420		if(ph->phflags & phBEFOREPAUSE)
421		{
422			if(next->type == phPAUSE)
423				alternative = ph->link_out;   // replace with the link_out phoneme
424		}
425
426		if(alternative == 1)
427			continue;    // NULL phoneme, discard
428
429		if(alternative > 1)
430		{
431			PHONEME_TAB *ph2;
432			ph2 = ph;
433			ph = phoneme_tab[alternative];
434
435			if(ph->type == phVOWEL)
436			{
437				plist2->synthflags |= SFLAG_SYLLABLE;
438				if(ph2->type != phVOWEL)
439					plist2->stress = 0;   // change from non-vowel to vowel, make sure it's unstressed
440			}
441			else
442				plist2->synthflags &= ~SFLAG_SYLLABLE;
443		}
444
445		if(langopts.param[LOPT_REDUCE_T])
446		{
447			if((ph->mnemonic == 't') && (plist2->sourceix == 0) && ((prev->type == phVOWEL) || (prev->mnemonic == 'n')))
448			{
449				if(((plist2+1)->sourceix == 0) && ((plist2+1)->stress < 3) && (next->type == phVOWEL))
450				{
451					ph = phoneme_tab[phonT_REDUCED];
452				}
453			}
454		}
455
456
457		while((ph->reduce_to != 0) && (!(plist2->synthflags & SFLAG_DICTIONARY)  || (langopts.param[LOPT_REDUCE] & 1)))
458		{
459			int reduce_level;
460			int stress_level;
461			int reduce = 0;
462
463			reduce_level = (ph->phflags >> 28) & 7;
464
465			if(ph->type == phVOWEL)
466			{
467				stress_level = plist2->stress;
468			}
469			else
470			{
471				// consonant, get stress from the following vowel
472				if(next->type == phVOWEL)
473					stress_level = (plist2+1)->stress;
474				else
475					break;
476			}
477
478			if((stress_level == 1) && (first_vowel))
479				stress_level = 0;   // ignore 'dimished' stress on first syllable
480
481			if(stress_level == 1)
482				reduce = 1;    // stress = 'reduced'
483
484			if(stress_level < reduce_level)
485				reduce =1;
486
487			if((word_stress < 4) && (langopts.param[LOPT_REDUCE] & 0x2) && (stress_level >= word_stress))
488			{
489				// don't reduce the most stressed syllable in an unstressed word
490				reduce = 0;
491			}
492
493			if(reduce)
494				ph = phoneme_tab[ph->reduce_to];
495			else
496				break;
497		}
498
499		if(ph->type == phVOWEL)
500			first_vowel = 0;
501
502		if((plist2+1)->synthflags & SFLAG_LENGTHEN)
503		{
504			static char types_double[] = {phFRICATIVE,phVFRICATIVE,phNASAL,phLIQUID,0};
505			if(strchr(types_double,next->type))
506			{
507				// lengthen this consonant by doubling it
508				insert_ph = next->code;
509				(plist2+1)->synthflags ^= SFLAG_LENGTHEN;
510			}
511		}
512
513		if((plist2+1)->sourceix != 0)
514		{
515			int x;
516
517			if(langopts.vowel_pause && (ph->type != phPAUSE))
518			{
519
520				if((ph->type != phVOWEL) && (langopts.vowel_pause & 0x200))
521				{
522					// add a pause after a word which ends in a consonant
523					insert_ph = phonPAUSE_NOLINK;
524				}
525
526				if(next->type == phVOWEL)
527				{
528					if((x = langopts.vowel_pause & 0x0c) != 0)
529					{
530						// break before a word which starts with a vowel
531						if(x == 0xc)
532							insert_ph = phonPAUSE_NOLINK;
533						else
534							insert_ph = phonPAUSE_VSHORT;
535					}
536	
537					if((ph->type == phVOWEL) && ((x = langopts.vowel_pause & 0x03) != 0))
538					{
539						// adjacent vowels over a word boundary
540						if(x == 2)
541							insert_ph = phonPAUSE_SHORT;
542						else
543							insert_ph = phonPAUSE_VSHORT;
544					}
545	
546					if(((plist2+1)->stress >= 4) && (langopts.vowel_pause & 0x100))
547					{
548						// pause before a words which starts with a stressed vowel
549						insert_ph = phonPAUSE_SHORT;
550					}
551				}
552			}
553
554			if(plist2 != plist2_inserted)
555			{
556				if((x = (langopts.word_gap & 0x7)) != 0)
557				{
558					insert_ph = pause_phonemes[x];
559				}
560				if(option_wordgap > 0)
561				{
562					insert_ph = phonPAUSE_LONG;
563				}
564			}
565		}
566
567		next2 = phoneme_tab[(plist2+2)->phcode];
568
569		if((insert_ph == 0) && (ph->link_out != 0) && !(ph->phflags & phBEFOREPAUSE) && (((plist2+1)->synthflags & SFLAG_EMBEDDED)==0))
570		{
571			if(ph->phflags & phAPPENDPH)
572			{
573				// always append the specified phoneme, unless it already is the next phoneme
574				if((ph->link_out != (plist2+1)->phcode) && (next->type == phVOWEL))
575//				if(ph->link_out != (plist2+1)->phcode)
576				{
577					insert_ph = ph->link_out;
578				}
579			}
580			else
581			if(((langopts.word_gap & 8)==0) || ((plist2+1)->sourceix == 0))
582			{
583				// This phoneme can be linked to a following vowel by inserting a linking phoneme
584				if(next->type == phVOWEL)
585					insert_ph = ph->link_out;
586				else
587				if(next->code == phonPAUSE_SHORT)
588				{
589					// Pause followed by Vowel, replace the Short Pause with the linking phoneme,
590					if(next2->type == phVOWEL)
591						(plist2+1)->phcode = ph->link_out;  // replace pause by linking phoneme
592				}
593			}
594		}
595
596		if(ph->phflags & phVOICED)
597		{
598			// check that a voiced consonant is preceded or followed by a vowel or liquid
599			// and if not, add a short schwa
600
601			// not yet implemented
602		}
603
604		phlist[ix].ph = ph;
605		phlist[ix].type = ph->type;
606		phlist[ix].env = PITCHfall;          // default, can be changed in the "intonation" module
607		phlist[ix].synthflags = plist2->synthflags | switched_language;
608		phlist[ix].tone = plist2->stress & 0xf;
609		phlist[ix].tone_ph = plist2->tone_number;
610		phlist[ix].sourceix = 0;
611
612		if(plist2->sourceix != 0)
613		{
614			phlist[ix].sourceix = plist2->sourceix;
615			phlist[ix].newword = 1;     // this phoneme is the start of a word
616
617			if(start_sentence)
618			{
619				phlist[ix].newword = 5;  // start of sentence + start of word
620				start_sentence = 0;
621			}
622		}
623		else
624		{
625			phlist[ix].newword = 0;
626		}
627
628		phlist[ix].length = ph->std_length;
629		if((ph->code == phonPAUSE_LONG) && (option_wordgap > 0))
630		{
631			phlist[ix].ph = phoneme_tab[phonPAUSE_SHORT];
632			phlist[ix].length = option_wordgap*14;   // 10mS per unit at the default speed
633		}
634
635		if(ph->type==phVOWEL || ph->type==phLIQUID || ph->type==phNASAL || ph->type==phVSTOP || ph->type==phVFRICATIVE)
636		{
637			phlist[ix].length = 128;  // length_mod
638			phlist[ix].env = PITCHfall;
639		}
640
641		phlist[ix].prepause = 0;
642		phlist[ix].amp = 20;          // default, will be changed later
643		phlist[ix].pitch1 = 0x400;
644		phlist[ix].pitch2 = 0x400;
645		ix++;
646	}
647	phlist[ix].newword = 2;     // end of clause
648
649   phlist[ix].type = phPAUSE;  // terminate with 2 Pause phonemes
650	phlist[ix].length = post_pause;  // length of the pause, depends on the punctuation
651	phlist[ix].sourceix = end_sourceix;
652	phlist[ix].synthflags = 0;
653
654   phlist[ix++].ph = phoneme_tab[phonPAUSE];
655   phlist[ix].type = phPAUSE;
656	phlist[ix].length = 0;
657	phlist[ix].sourceix=0;
658	phlist[ix].synthflags = 0;
659   phlist[ix++].ph = phoneme_tab[phonPAUSE_SHORT];
660
661	n_phoneme_list = ix;
662}  // end of MakePhonemeList
663
664