eSpeak NG is an open source speech synthesizer that supports more than hundred languages and accents.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

phonemelist.c 18KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593
  1. /*
  2. * Copyright (C) 2005 to 2014 by Jonathan Duddington
  3. * email: [email protected]
  4. * Copyright (C) 2015-2016 Reece H. Dunn
  5. *
  6. * This program is free software; you can redistribute it and/or modify
  7. * it under the terms of the GNU General Public License as published by
  8. * the Free Software Foundation; either version 3 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License
  17. * along with this program; if not, see: <http://www.gnu.org/licenses/>.
  18. */
  19. #include "config.h"
  20. #include <stdbool.h>
  21. #include <stdint.h>
  22. #include <stdio.h>
  23. #include <stdlib.h>
  24. #include <string.h>
  25. #include <espeak-ng/espeak_ng.h>
  26. #include <espeak-ng/speak_lib.h>
  27. #include <espeak-ng/encoding.h>
  28. #include "phonemelist.h"
  29. #include "phoneme.h" // for PHONEME_TAB, phVOWEL, REPLACE_PHONEMES, phPAUSE
  30. #include "synthdata.h" // for InterpretPhoneme, SelectPhonemeTable
  31. #include "synthesize.h" // for PHONEME_LIST, PHONEME_LIST2, phoneme_tab
  32. #include "translate.h" // for Translator, LANGUAGE_OPTIONS, option_wordgap
  33. #include "phoneme.h"
  34. #include "synthesize.h"
  35. #include "translate.h"
  36. #include "speech.h"
  37. static void SetRegressiveVoicing(int regression, PHONEME_LIST2 *plist2, PHONEME_TAB *ph, Translator *tr);
  38. static void ReInterpretPhoneme(PHONEME_TAB *ph, PHONEME_TAB *ph2, PHONEME_LIST *plist3, Translator *tr, PHONEME_DATA *phdata, WORD_PH_DATA *worddata);
  39. static const unsigned char pause_phonemes[8] = {
  40. 0, phonPAUSE_VSHORT, phonPAUSE_SHORT, phonPAUSE, phonPAUSE_LONG, phonGLOTTALSTOP, phonPAUSE_LONG, phonPAUSE_LONG
  41. };
  42. extern int n_ph_list2;
  43. extern PHONEME_LIST2 ph_list2[N_PHONEME_LIST]; // first stage of text->phonemes
  44. static int SubstitutePhonemes(PHONEME_LIST *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. bool word_end;
  53. PHONEME_LIST2 *plist2;
  54. PHONEME_TAB *next = NULL;
  55. int deleted_sourceix = -1;
  56. for (ix = 0; (ix < n_ph_list2) && (n_plist_out < N_PHONEME_LIST); ix++) {
  57. plist2 = &ph_list2[ix];
  58. if (deleted_sourceix != -1) {
  59. plist2->sourceix = deleted_sourceix;
  60. deleted_sourceix = -1;
  61. }
  62. if (plist2->phcode == phonSWITCH)
  63. SelectPhonemeTable(plist2->tone_ph);
  64. // don't do any substitution if the language has been temporarily changed
  65. if (!(plist2->synthflags & SFLAG_SWITCHED_LANG)) {
  66. if (ix < (n_ph_list2 -1))
  67. next = phoneme_tab[ph_list2[ix+1].phcode];
  68. word_end = false;
  69. if (ix == n_ph_list2 -1 || (plist2+1)->sourceix || ((next != 0) && (next->type == phPAUSE)))
  70. word_end = true; // this phoneme is the end of a word
  71. // check whether a Voice has specified that we should replace this phoneme
  72. for (k = 0; k < n_replace_phonemes; k++) {
  73. if (plist2->phcode == replace_phonemes[k].old_ph) {
  74. replace_flags = replace_phonemes[k].type;
  75. if ((replace_flags & 1) && (word_end == false))
  76. continue; // this replacement only occurs at the end of a word
  77. if ((replace_flags & 2) && ((plist2->stresslevel & 0x7) > 3))
  78. continue; // this replacement doesn't occur in stressed syllables
  79. if ((replace_flags & 4) && (plist2->sourceix == 0))
  80. continue; // this replacement only occurs at the start of a word
  81. // substitute the replacement phoneme
  82. plist2->phcode = replace_phonemes[k].new_ph;
  83. if ((plist2->stresslevel > 1) && (phoneme_tab[plist2->phcode]->phflags & phUNSTRESSED))
  84. plist2->stresslevel = 0; // the replacement must be unstressed
  85. break;
  86. }
  87. }
  88. if (plist2->phcode == 0) {
  89. deleted_sourceix = plist2->sourceix;
  90. continue; // phoneme has been replaced by NULL, so don't copy it
  91. }
  92. }
  93. // copy phoneme into the output list
  94. memcpy(&plist_out[n_plist_out], plist2, sizeof(PHONEME_LIST2));
  95. plist_out[n_plist_out].ph = phoneme_tab[plist2->phcode];
  96. plist_out[n_plist_out].type = plist_out[n_plist_out].ph->type;
  97. n_plist_out++;
  98. }
  99. return n_plist_out;
  100. }
  101. void MakePhonemeList(Translator *tr, int post_pause, bool start_sentence)
  102. {
  103. int ix = 0;
  104. int j;
  105. int insert_ph = 0;
  106. PHONEME_LIST *phlist;
  107. PHONEME_TAB *ph = NULL;
  108. PHONEME_TAB *next;
  109. int unstress_count = 0;
  110. int word_stress = 0;
  111. int current_phoneme_tab;
  112. int max_stress;
  113. int end_sourceix;
  114. int alternative;
  115. int delete_count;
  116. int word_start;
  117. PHONEME_DATA phdata;
  118. bool start_of_clause = true;
  119. int n_ph_list3;
  120. PHONEME_LIST *plist3;
  121. PHONEME_LIST *plist3_inserted = NULL;
  122. PHONEME_LIST ph_list3[N_PHONEME_LIST];
  123. PHONEME_LIST2 *plist2;
  124. WORD_PH_DATA worddata;
  125. memset(&worddata, 0, sizeof(worddata));
  126. plist2 = ph_list2;
  127. phlist = phoneme_list;
  128. end_sourceix = plist2[n_ph_list2-1].sourceix;
  129. MAKE_MEM_UNDEFINED(&phoneme_list, sizeof(phoneme_list));
  130. // is the last word of the clause unstressed ?
  131. max_stress = 0;
  132. for (j = n_ph_list2-3; j >= 0; j--) {
  133. // start with the last phoneme (before the terminating pauses) and move backwards
  134. if ((plist2[j].stresslevel & 0x7f) > max_stress)
  135. max_stress = plist2[j].stresslevel & 0x7f;
  136. if (plist2[j].sourceix != 0)
  137. break;
  138. }
  139. if (max_stress < 4) {
  140. // the last word is unstressed, look for a previous word that can be stressed
  141. while (--j >= 0) {
  142. if (plist2[j].synthflags & SFLAG_PROMOTE_STRESS) { // dictionary flags indicated that this stress can be promoted
  143. plist2[j].stresslevel = 4; // promote to stressed
  144. break;
  145. }
  146. if (plist2[j].stresslevel >= 4) {
  147. // found a stressed syllable, so stop looking
  148. break;
  149. }
  150. }
  151. }
  152. // look for switch of phoneme tables
  153. delete_count = 0;
  154. current_phoneme_tab = tr->phoneme_tab_ix;
  155. int deleted_sourceix = -1;
  156. for (j = 0; j < n_ph_list2; j++) {
  157. if (current_phoneme_tab != tr->phoneme_tab_ix)
  158. plist2[j].synthflags |= SFLAG_SWITCHED_LANG;
  159. if (delete_count > 0) {
  160. memcpy(&plist2[j-delete_count], &plist2[j], sizeof(plist2[0]));
  161. if (deleted_sourceix != -1) {
  162. plist2[j-delete_count].sourceix = deleted_sourceix;
  163. deleted_sourceix = -1;
  164. }
  165. }
  166. if (plist2[j].phcode == phonSWITCH) {
  167. if ((!(plist2[j].synthflags & SFLAG_EMBEDDED)) && (
  168. (plist2[j].tone_ph == current_phoneme_tab) ||
  169. (plist2[j+1].phcode == phonSWITCH) ||
  170. ((plist2[j+1].phcode == phonPAUSE) && (plist2[j+2].phcode == phonSWITCH))
  171. )) {
  172. // delete this phonSWITCH if it's switching to the current phoneme table, or
  173. // delete this phonSWITCH if its followed by another phonSWITCH
  174. if (deleted_sourceix == -1 && plist2[j].sourceix != 0)
  175. deleted_sourceix = plist2[j].sourceix;
  176. delete_count++;
  177. } else
  178. current_phoneme_tab = plist2[j].tone_ph;
  179. }
  180. }
  181. n_ph_list2 -= delete_count;
  182. SelectPhonemeTable(current_phoneme_tab);
  183. int regression;
  184. if ((regression = tr->langopts.param[LOPT_REGRESSIVE_VOICING]) != 0) {
  185. SetRegressiveVoicing(regression, plist2, ph, tr);
  186. }
  187. SelectPhonemeTable(tr->phoneme_tab_ix);
  188. n_ph_list3 = SubstitutePhonemes(ph_list3) - 2;
  189. for (j = 0; (j < n_ph_list3) && (ix < N_PHONEME_LIST-3);) {
  190. if (ph_list3[j].sourceix) {
  191. // start of a word
  192. int k;
  193. int nextw;
  194. word_stress = 0;
  195. // find the highest stress level in this word
  196. for (nextw = j; nextw < n_ph_list3;) {
  197. if (ph_list3[nextw].stresslevel > word_stress)
  198. word_stress = ph_list3[nextw].stresslevel;
  199. nextw++;
  200. if (ph_list3[nextw].sourceix)
  201. break; // start of the next word
  202. }
  203. for (k = j; k < nextw; k++)
  204. ph_list3[k].wordstress = word_stress;
  205. j = nextw;
  206. } else
  207. j++;
  208. }
  209. // transfer all the phonemes of the clause into phoneme_list
  210. ph = phoneme_tab[phonPAUSE];
  211. ph_list3[0].ph = ph;
  212. word_start = 1;
  213. SelectPhonemeTable(tr->phoneme_tab_ix);
  214. for (j = 0; insert_ph || ((j < n_ph_list3) && (ix < N_PHONEME_LIST-3)); j++) {
  215. plist3 = &ph_list3[j];
  216. bool inserted = false;
  217. bool deleted = false;
  218. if (insert_ph != 0) {
  219. // we have a (linking) phoneme which we need to insert here
  220. next = phoneme_tab[plist3->phcode]; // this phoneme, i.e. after the insert
  221. // re-use the previous entry for the inserted phoneme.
  222. // That's OK because we don't look backwards from plist3 *** but CountVowelPosition() and isAfterStress does !!!
  223. j--;
  224. plist3 = plist3_inserted = &ph_list3[j];
  225. if (j > 0) {
  226. // move all previous phonemes in the word back one place
  227. int k;
  228. if (word_start > 0) {
  229. k = word_start;
  230. word_start--;
  231. } else
  232. k = 2; // No more space, don't loose the start of word mark at ph_list2[word_start]
  233. for (; k <= j; k++)
  234. memcpy(&ph_list3[k-1], &ph_list3[k], sizeof(*plist3));
  235. }
  236. memset(&plist3[0], 0, sizeof(*plist3));
  237. plist3->phcode = insert_ph;
  238. ph = phoneme_tab[insert_ph];
  239. plist3->ph = ph;
  240. insert_ph = 0;
  241. inserted = true; // don't insert the same phoneme repeatedly
  242. } else {
  243. // otherwise get the next phoneme from the list
  244. if (plist3->sourceix != 0)
  245. word_start = j;
  246. ph = phoneme_tab[plist3->phcode];
  247. plist3[0].ph = ph;
  248. if (plist3->phcode == phonSWITCH) {
  249. // change phoneme table
  250. SelectPhonemeTable(plist3->tone_ph);
  251. }
  252. next = phoneme_tab[plist3[1].phcode]; // the phoneme after this one
  253. plist3[1].ph = next;
  254. }
  255. if (ph == NULL) continue;
  256. InterpretPhoneme(tr, 0x100, plist3, &phdata, &worddata);
  257. if ((alternative = phdata.pd_param[pd_CHANGE_NEXTPHONEME]) > 0) {
  258. ph_list3[j+1].ph = phoneme_tab[alternative];
  259. ph_list3[j+1].phcode = alternative;
  260. ph_list3[j+1].type = phoneme_tab[alternative]->type;
  261. next = phoneme_tab[alternative];
  262. }
  263. if (((alternative = phdata.pd_param[pd_INSERTPHONEME]) > 0) && (inserted == false)) {
  264. // PROBLEM: if we insert a phoneme before a vowel then we loose the stress.
  265. PHONEME_TAB *ph2;
  266. ph2 = ph;
  267. insert_ph = plist3->phcode;
  268. ph = phoneme_tab[alternative];
  269. plist3->ph = ph;
  270. plist3->phcode = alternative;
  271. ReInterpretPhoneme(ph, ph2, plist3, tr, &phdata, &worddata);
  272. }
  273. if ((alternative = phdata.pd_param[pd_CHANGEPHONEME]) > 0) {
  274. PHONEME_TAB *ph2;
  275. ph2 = ph;
  276. ph = phoneme_tab[alternative];
  277. plist3->ph = ph;
  278. plist3->phcode = alternative;
  279. if (alternative == 1)
  280. deleted = true; // NULL phoneme, discard
  281. else {
  282. ReInterpretPhoneme(ph, ph2, plist3, tr, &phdata, &worddata);
  283. }
  284. }
  285. if ((ph->type == phVOWEL) && (deleted == false)) {
  286. // Check for consecutive unstressed syllables, even across word boundaries.
  287. // Do this after changing phonemes according to stress level.
  288. if (plist3->stresslevel <= 1) {
  289. // an unstressed vowel
  290. unstress_count++;
  291. if (tr->langopts.stress_flags & 0x08) {
  292. // change sequences of consecutive unstressed vowels in unstressed words to diminished stress (TEST)
  293. PHONEME_LIST *p;
  294. for (p = plist3+1; p->type != phPAUSE; p++) {
  295. if (p->type == phVOWEL) {
  296. if (p->stresslevel <= 1) {
  297. if (plist3->wordstress < 4)
  298. plist3->stresslevel = 0;
  299. if (p->wordstress < 4)
  300. p->stresslevel = 0;
  301. }
  302. break;
  303. }
  304. }
  305. } else {
  306. if ((unstress_count > 1) && ((unstress_count & 1) == 0)) {
  307. // in a sequence of unstressed syllables, reduce alternate syllables to 'diminished'
  308. // stress. But not for the last phoneme of a stressed word
  309. if ((tr->langopts.stress_flags & S_NO_DIM) || ((word_stress > 3) && ((plist3+1)->sourceix != 0))) {
  310. // An unstressed final vowel of a stressed word
  311. unstress_count = 1; // try again for next syllable
  312. } else
  313. plist3->stresslevel = 0; // change stress to 'diminished'
  314. }
  315. }
  316. } else
  317. unstress_count = 0;
  318. }
  319. if ((plist3+1)->synthflags & SFLAG_LENGTHEN) {
  320. static const char types_double[] = { phFRICATIVE, phVFRICATIVE, phNASAL, phLIQUID, 0 };
  321. if ((j > 0) && (strchr(types_double, next->type))) {
  322. // lengthen this consonant by doubling it
  323. // BUT, can't insert a phoneme at position plist3[0] because it crashes PrevPh()
  324. insert_ph = next->code;
  325. (plist3+1)->synthflags ^= SFLAG_LENGTHEN;
  326. }
  327. }
  328. if ((plist3+1)->sourceix != 0) {
  329. int x;
  330. if (tr->langopts.vowel_pause && (ph->type != phPAUSE)) {
  331. if ((ph->type != phVOWEL) && (tr->langopts.vowel_pause & 0x200)) {
  332. // add a pause after a word which ends in a consonant
  333. insert_ph = phonPAUSE_NOLINK;
  334. }
  335. if (next->type == phVOWEL) {
  336. if ((x = tr->langopts.vowel_pause & 0x0c) != 0) {
  337. // break before a word which starts with a vowel
  338. if (x == 0xc)
  339. insert_ph = phonPAUSE_NOLINK;
  340. else
  341. insert_ph = phonPAUSE_VSHORT;
  342. }
  343. if ((ph->type == phVOWEL) && ((x = tr->langopts.vowel_pause & 0x03) != 0)) {
  344. // adjacent vowels over a word boundary
  345. if (x == 2)
  346. insert_ph = phonPAUSE_SHORT;
  347. else
  348. insert_ph = phonPAUSE_VSHORT;
  349. }
  350. if (((plist3+1)->stresslevel >= 4) && (tr->langopts.vowel_pause & 0x100)) {
  351. // pause before a words which starts with a stressed vowel
  352. insert_ph = phonPAUSE_SHORT;
  353. }
  354. }
  355. }
  356. if ((plist3 != plist3_inserted) && (ix > 0)) {
  357. if ((x = (tr->langopts.word_gap & 0x7)) != 0) {
  358. if ((x > 1) || ((insert_ph != phonPAUSE_SHORT) && (insert_ph != phonPAUSE_NOLINK))) {
  359. // don't reduce the pause
  360. insert_ph = pause_phonemes[x];
  361. }
  362. }
  363. if (option_wordgap > 0)
  364. insert_ph = phonPAUSE_LONG;
  365. }
  366. }
  367. plist3[2].ph = phoneme_tab[plist3[2].phcode];
  368. if ((insert_ph == 0) && (phdata.pd_param[pd_APPENDPHONEME] != 0))
  369. insert_ph = phdata.pd_param[pd_APPENDPHONEME];
  370. if (deleted == false) {
  371. phlist[ix].ph = ph;
  372. phlist[ix].type = ph->type;
  373. phlist[ix].env = PITCHfall; // default, can be changed in the "intonation" module
  374. phlist[ix].synthflags = plist3->synthflags;
  375. phlist[ix].stresslevel = plist3->stresslevel & 0xf;
  376. phlist[ix].wordstress = plist3->wordstress;
  377. phlist[ix].tone_ph = plist3->tone_ph;
  378. phlist[ix].sourceix = 0;
  379. phlist[ix].phcode = ph->code;
  380. if (plist3->sourceix != 0) {
  381. phlist[ix].sourceix = plist3->sourceix;
  382. phlist[ix].newword = PHLIST_START_OF_WORD;
  383. if (start_sentence) {
  384. phlist[ix].newword |= PHLIST_START_OF_SENTENCE;
  385. start_sentence = false;
  386. }
  387. if (start_of_clause) {
  388. phlist[ix].newword |= PHLIST_START_OF_CLAUSE;
  389. start_of_clause = false;
  390. }
  391. } else
  392. phlist[ix].newword = 0;
  393. phlist[ix].length = phdata.pd_param[i_SET_LENGTH]*2;
  394. if ((ph->code == phonPAUSE_LONG) && (option_wordgap > 0) && (plist3[1].sourceix != 0)) {
  395. phlist[ix].ph = phoneme_tab[phonPAUSE_SHORT];
  396. phlist[ix].length = option_wordgap*14; // 10mS per unit at the default speed
  397. }
  398. if (ph->type == phVOWEL || ph->type == phLIQUID || ph->type == phNASAL || ph->type == phVSTOP || ph->type == phVFRICATIVE || (ph->phflags & phPREVOICE)) {
  399. phlist[ix].length = 128; // length_mod
  400. phlist[ix].env = PITCHfall;
  401. }
  402. phlist[ix].prepause = 0;
  403. phlist[ix].amp = 20; // default, will be changed later
  404. phlist[ix].pitch1 = 255;
  405. phlist[ix].pitch2 = 255;
  406. ix++;
  407. }
  408. }
  409. phlist[ix].newword = PHLIST_END_OF_CLAUSE;
  410. phlist[ix].phcode = phonPAUSE;
  411. phlist[ix].type = phPAUSE; // terminate with 2 Pause phonemes
  412. phlist[ix].length = post_pause; // length of the pause, depends on the punctuation
  413. phlist[ix].sourceix = end_sourceix;
  414. phlist[ix].synthflags = 0;
  415. phlist[ix].prepause = 0;
  416. phlist[ix++].ph = phoneme_tab[phonPAUSE];
  417. phlist[ix].newword = 0;
  418. phlist[ix].phcode = phonPAUSE;
  419. phlist[ix].type = phPAUSE;
  420. phlist[ix].length = 0;
  421. phlist[ix].sourceix = 0;
  422. phlist[ix].synthflags = 0;
  423. phlist[ix].prepause = 0;
  424. phlist[ix++].ph = phoneme_tab[phonPAUSE_SHORT];
  425. n_phoneme_list = ix;
  426. SelectPhonemeTable(tr->phoneme_tab_ix);
  427. }
  428. static void SetRegressiveVoicing(int regression, PHONEME_LIST2 *plist2, PHONEME_TAB *ph, Translator *tr) {
  429. // set consonant clusters to all voiced or all unvoiced
  430. // Regressive
  431. int type;
  432. bool stop_propagation = false;
  433. int voicing = 0;
  434. for (int j = n_ph_list2-1; j >= 0; j--) {
  435. if (plist2[j].phcode == phonSWITCH) {
  436. /* Find previous phonSWITCH to determine language we're switching back to */
  437. int k;
  438. for (k = j-1; k >= 0; k--)
  439. if (plist2[k].phcode == phonSWITCH)
  440. break;
  441. if (k >= 0)
  442. SelectPhonemeTable(plist2[k].tone_ph);
  443. else
  444. SelectPhonemeTable(tr->phoneme_tab_ix);
  445. }
  446. ph = phoneme_tab[plist2[j].phcode];
  447. if (ph == NULL)
  448. continue;
  449. if (plist2[j].synthflags & SFLAG_SWITCHED_LANG) {
  450. stop_propagation = false;
  451. voicing = 0;
  452. if (regression & 0x100)
  453. voicing = 1; // word-end devoicing
  454. continue;
  455. }
  456. type = ph->type;
  457. if (regression & 0x2) {
  458. // [v] amd [v;] don't cause regression, or [R^]
  459. if (((ph->mnemonic & 0xff) == 'v') || ((ph->mnemonic & 0xff) == 'R')) {
  460. stop_propagation = true;
  461. if (regression & 0x10)
  462. voicing = 0;
  463. }
  464. }
  465. if ((type == phSTOP) || type == (phFRICATIVE)) {
  466. if ((voicing == 0) && (regression & 0xf))
  467. voicing = 1;
  468. else if ((voicing == 2) && (ph->end_type != 0)) // use end_type field for voicing_switch for consonants
  469. plist2[j].phcode = ph->end_type; // change to voiced equivalent
  470. } else if ((type == phVSTOP) || type == (phVFRICATIVE)) {
  471. if ((voicing == 0) && (regression & 0xf))
  472. voicing = 2;
  473. else if ((voicing == 1) && (ph->end_type != 0))
  474. plist2[j].phcode = ph->end_type; // change to unvoiced equivalent
  475. } else {
  476. if (regression & 0x8) {
  477. // LANG=Polish, propagate through liquids and nasals
  478. if ((type == phPAUSE) || (type == phVOWEL))
  479. voicing = 0;
  480. } else
  481. voicing = 0;
  482. }
  483. if (stop_propagation) {
  484. voicing = 0;
  485. stop_propagation = false;
  486. }
  487. if (plist2[j].sourceix) {
  488. if (regression & 0x04) {
  489. // stop propagation at a word boundary
  490. voicing = 0;
  491. }
  492. if (regression & 0x100) {
  493. // devoice word-final consonants, unless propagating voiced
  494. if (voicing == 0)
  495. voicing = 1;
  496. }
  497. }
  498. }
  499. }
  500. static void ReInterpretPhoneme(PHONEME_TAB *ph, PHONEME_TAB *ph2, PHONEME_LIST *plist3, Translator *tr, PHONEME_DATA *phdata, WORD_PH_DATA *worddata) {
  501. if (ph->type == phVOWEL) {
  502. plist3->synthflags |= SFLAG_SYLLABLE;
  503. if (ph2->type != phVOWEL)
  504. plist3->stresslevel = 0; // change from non-vowel to vowel, make sure it's unstressed
  505. } else
  506. plist3->synthflags &= ~SFLAG_SYLLABLE;
  507. // re-interpret the changed phoneme
  508. // But it doesn't obey a second ChangePhoneme()
  509. InterpretPhoneme(tr, 0x100, plist3, phdata, worddata);
  510. }