/native/external/espeak/src/phonemelist.cpp
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