1 /*************************************************************************/
2 /* */
3 /* Centre for Speech Technology Research */
4 /* University of Edinburgh, UK */
5 /* Copyright (c) 1998 */
6 /* All Rights Reserved. */
7 /* */
8 /* Permission is hereby granted, free of charge, to use and distribute */
9 /* this software and its documentation without restriction, including */
10 /* without limitation the rights to use, copy, modify, merge, publish, */
11 /* distribute, sublicense, and/or sell copies of this work, and to */
12 /* permit persons to whom this work is furnished to do so, subject to */
13 /* the following conditions: */
14 /* 1. The code must retain the above copyright notice, this list of */
15 /* conditions and the following disclaimer. */
16 /* 2. Any modifications must be clearly marked as such. */
17 /* 3. Original authors' names are not deleted. */
18 /* 4. The authors' names are not used to endorse or promote products */
19 /* derived from this software without specific prior written */
20 /* permission. */
21 /* */
22 /* THE UNIVERSITY OF EDINBURGH AND THE CONTRIBUTORS TO THIS WORK */
23 /* DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING */
24 /* ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT */
25 /* SHALL THE UNIVERSITY OF EDINBURGH NOR THE CONTRIBUTORS BE LIABLE */
26 /* FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES */
27 /* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN */
28 /* AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, */
29 /* ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF */
30 /* THIS SOFTWARE. */
31 /* */
32 /*************************************************************************/
33 /* Author : Alan W Black and Paul Taylor */
34 /* Date : February 1998 */
35 /*-----------------------------------------------------------------------*/
36 /* */
37 /* An implementation of Metrical Tree Phonology */
38 /* */
39 /*=======================================================================*/
40
41 #include <cmath>
42 #include "festival.h"
43 #include "lexicon.h"
44
45 extern EST_Features phone_def;
46
nucleus_count(EST_Relation & phone)47 static int nucleus_count(EST_Relation &phone)
48 {
49 int v = 0;
50 for (EST_Item *l = phone.head(); l; l = l->next())
51 if (l->S("df.syllabic") == "+")
52 ++v;
53
54 return v;
55 }
56
legal_c1(EST_Item * x)57 static bool legal_c1(EST_Item *x)
58 {
59 return (x->S("df.syllabic") == "-");
60 }
61
legal_c2(EST_Item * c1,EST_Item * x)62 static bool legal_c2(EST_Item *c1, EST_Item *x)
63 {
64 if (x->S("df.syllabic") == "+")
65 return false;
66
67 if ((x->name() == "s") && ((c1->name() == "l") || (c1->name() ==
68 "w") || (c1->name() == "p") || (c1->name() ==
69 "t") || (c1->name() == "k") || (c1->name() ==
70 "m") || (c1->name() == "n") || (c1->name() ==
71 "r") || (c1->name() == "f")))
72 return true;
73
74
75 if ((c1->S("df.manner") == "approximant") &&
76 (x->S("df.manner") == "stop") || (x->name() == "th")
77 || (x->name() == "f"))
78 // if (ph_is_semivowel(c1->name()) && (ph_is_stop(x->name())
79 // || (x->name() == "th")
80 // || (x->name() == "f")))
81 {
82 if (x->name() == "y")
83 return false;
84
85 if ((c1->name() == "l") && ((x->name() == "t") ||
86 (x->name() == "d") || (x->name() == "th")))
87 return false;
88
89 if ((c1->name() == "w") && ((x->name() == "p") ||
90 (x->name() == "b") || (x->name() == "f")))
91 return false;
92 return true;
93 }
94 // for "vroom"
95 if ((c1->name() == "r") && (x->name() == "v"))
96 return true;
97 return false;
98 }
legal_c3(EST_Item * c1,EST_Item * c2,EST_Item * pos)99 static bool legal_c3(EST_Item * c1, EST_Item * c2, EST_Item *pos)
100 {
101 (void) c1;
102 if (pos->name() != "s")
103 return false;
104 if ((c2->S("df.manner") == "stop") && (c2->S("df.voicing") == "-"))
105 return true;
106 return false;
107 }
108
make_onset(EST_Item * syl_struct_root,EST_Item * nucleus,int flat)109 EST_Item *make_onset(EST_Item *syl_struct_root, EST_Item *nucleus, int flat)
110 {
111 EST_Item *c1, *c2, *c3;
112 EST_Item *p, *onset;
113
114 // if first syllable in word, put all prevocalic segments in onset
115 if ((prev(syl_struct_root) == 0) && prev(nucleus))
116 {
117 if (flat)
118 onset = syl_struct_root;
119 else
120 {
121 onset = daughter1(syl_struct_root)->insert_before();
122 onset->set("sylval", "Onset");
123 }
124 // tmpeorary hack because of lack of prepend daughter fn.
125 EST_Item *s;
126 for (s = nucleus->prev(); prev(s); s = s->prev());
127 for (c1 = s; c1 != nucleus; c1 = next(c1))
128 onset->append_daughter(c1);
129 return 0;
130 }
131
132 c1 = prev(nucleus);
133 if (c1 == 0)
134 return c1;
135
136 // if (ph_is_vowel(c1->name()))
137 // return next(c1);
138 if (c1->S("df.syllabic") == "+")
139 return next(c1);
140
141 // if (c1->S("df.type") == "vowel")
142 // return next(c1);
143
144 if (flat)
145 onset = syl_struct_root;
146 else
147 {
148 onset = daughter1(syl_struct_root)->insert_before();
149 onset->set("sylval", "Onset");
150 }
151
152 // add first consonant
153 if (legal_c1(c1))
154 p = onset->append_daughter(c1);
155 else
156 return nucleus;
157
158 // add second consonant
159 c2 = prev(c1);
160 if (c2 == 0)
161 return 0;
162 if (legal_c2(c1, c2))
163 p = p->insert_before(c2);
164 else
165 return c1;
166
167 // add third consonant (s)
168 c3 = prev(c2);
169 if (c3 == 0)
170 return 0;
171
172 // add third consonant
173 // if (legal_c3(c1->name(), c2->name(), c3->name()))
174 // p = p->insert_before(c3);
175 if (legal_c3(c1, c2, c3))
176 p = p->insert_before(c3);
177 else
178 return c2;
179
180 return c3;
181 }
182
make_nucleus(EST_Item * syl_struct_root,EST_Item * nucleus,int flat)183 static void make_nucleus(EST_Item *syl_struct_root, EST_Item *nucleus,
184 int flat)
185 {
186 EST_Item *m;
187
188 if (flat)
189 m = syl_struct_root;
190 else
191 {
192 // add rhyme
193 m = syl_struct_root->append_daughter();
194 m->set("sylval", "Rhyme");
195
196 // add nucleus
197 m = m->append_daughter();
198 m->set("sylval", "Nucleus");
199 }
200
201 m->append_daughter(nucleus);
202 }
203
make_coda(EST_Item * first_coda,EST_Item * first_onset,EST_Item * syl_struct_root,int flat)204 static void make_coda(EST_Item *first_coda, EST_Item *first_onset,
205 EST_Item *syl_struct_root, int flat)
206 {
207 EST_Item *m=0;;
208
209 if ((first_coda != 0) && (first_coda != first_onset))
210 {
211 if (flat)
212 m = syl_struct_root;
213 else
214 {
215 m = daughter1(syl_struct_root);
216 if (m->f("sylval") != "Rhyme")
217 m = daughter2(syl_struct_root);
218
219 m = m->append_daughter();
220 m->set("sylval", "Coda");
221 }
222 }
223
224 for (; (first_coda != 0) && (first_coda != first_onset);
225 first_coda = first_coda->next())
226 m->append_daughter(first_coda);
227 }
228
229 // if "flat" is set to 1 a tree syllable structure is built, otherwise
230 // a flat list like structure is built.
231
syllabify_word(EST_Item * w,EST_Relation & phone,EST_Relation & sylstructure,EST_Relation & syllable,int flat)232 int syllabify_word(EST_Item *w, EST_Relation &phone,
233 EST_Relation &sylstructure,
234 EST_Relation &syllable,
235 int flat)
236 {
237 EST_Item *prev_syl, *this_syl, *l, *this_struct, *prev_struct;
238 EST_Item *first_onset, *first_coda = 0;
239 EST_String v;
240
241 // cout << "phones: " << phone << endl;
242
243 int count = nucleus_count(phone);
244
245 prev_struct =first_onset = 0;
246
247 if (count < 1)
248 return 0;
249
250 for (prev_syl = 0, l = phone.head(); l; l = l->next())
251 {
252 // cout << "type " << l->S("name") << ": " << l->S("df.type") << endl;
253 if (l->S("df.syllabic") == "+")
254 // if (ph_is_vowel(l->name()))
255 {
256
257 if (count == 1)
258 this_syl = syllable.append(w);
259 else
260 this_syl = syllable.append();
261 this_struct = sylstructure.append(this_syl);
262 this_syl->set("stress_num", l->I("stress_num"));
263
264 // note: it must be in this order
265 // cout << "this struct: " << *this_syl << endl;
266 // cout << "this struct: " << *this_struct << endl;
267 make_nucleus(this_struct, l, flat);
268
269 first_onset = make_onset(this_struct, l, flat);
270
271 make_coda(first_coda, first_onset, prev_struct, flat);
272
273 prev_syl = this_syl;
274 prev_struct = this_struct;
275 first_coda = l->next();
276 }
277 }
278
279 make_coda(first_coda, first_onset, prev_struct, flat);
280 return count;
281 }
282
fix_lex_string(LISP lpos,EST_StrList & s)283 void fix_lex_string(LISP lpos, EST_StrList &s)
284 {
285 LISP a, b, c;
286 EST_String p;
287
288 for (a = car(lpos); a != NIL; a = cdr(a))
289 {
290 // cout << "1:\n";
291 // lprint(a);
292 // for (b = car(a); b != NIL; b = cdr(b))
293 // {
294 b = car(a);
295 // cout << "0:\n";
296 // lprint(b);
297 for (c = car(b); c != NIL; c = cdr(c))
298 {
299 p = get_c_string(car(c));
300 if (ph_is_vowel(p))
301 p += "0";
302 s.append(p);
303 }
304 }
305 // cout << "def list: " << s << endl;
306 }
307
308 //Adds phoneme name to syllable as a string
add_syllable_name(EST_Item * syl,const EST_String & fname)309 void add_syllable_name(EST_Item *syl, const EST_String &fname)
310 {
311 EST_Item *s, *p;
312
313 s = syl->as_relation("SylStructure");
314
315 for (p = first_leaf_in_tree(s); p != next_leaf(last_leaf_in_tree(s));
316 p = next_leaf(p))
317 if (p == first_leaf_in_tree(s))
318 s->set(fname, p->S("name"));
319 else
320 s->set(fname, s->S(fname) + " " + p->S("name"));
321 }
322
lex_to_phones(const EST_String & name,const EST_String & pos,EST_Relation & phone)323 void lex_to_phones(const EST_String &name, const EST_String &pos,
324 EST_Relation &phone)
325 {
326 LISP entry, lpos;
327 EST_Item *p;
328 EST_StrList lex_def;
329 EST_String lex_phone;
330
331 if (pos != "0")
332 lpos = rintern(pos);
333 else
334 lpos = NIL;
335 entry = lex_lookup_word(name, lpos);
336
337 lpos = cdr(cdr(entry));
338
339 if (!siod_atomic_list(car(lpos)))
340 fix_lex_string(lpos, lex_def);
341 else
342 siod_list_to_strlist(car(lpos), lex_def);
343
344 for (EST_Litem *sl = lex_def.head(); sl; sl = sl->next())
345 {
346 p = phone.append();
347 lex_phone = lex_def(sl);
348 if (lex_phone.contains(RXint))
349 {
350 p->set("name", lex_phone.before(RXint));
351 p->set("stress_num", lex_phone.at(RXint));
352 }
353 else
354 p->set("name", lex_phone);
355
356 // df = "distinctive features"
357 if (phone_def.present(p->S("name")))
358 p->set("df", phone_def.A(p->S("name")));
359 else
360 EST_error("Word \"%s\" has illegal phoneme \"%s\" in lexicon\n",
361 (const char *)name, (const char *)p->S("name"));
362 }
363 }
364
365
366 /*static bool legal_c3(EST_String c1, EST_String c2, EST_String pos)
367 {
368 (void) c1;
369 if (pos != "s")
370 return false;
371 if (ph_is_stop(c2) && (!ph_is_voiced(c2)))
372 return true;
373 return false;
374 }
375 */
376
377
378
379 /*static int vowel_count(EST_StrList &full)
380 {
381 int v = 0;
382 EST_Litem *l;
383
384 for (l = full.head(); l; l = l->next())
385 if (ph_is_stress_vowel(full(l)))
386 ++v;
387 return v;
388 }
389
390
391 static bool ph_is_s(const EST_String &p)
392 {
393 return (p == "s") ? true : false;
394 }
395 */
396
397 /*void subword_phonology(EST_Utterance &word)
398 {
399 word.create_relation("MetricalTree");
400 word.create_relation("SylStructure");
401 word.create_relation("Syllable");
402
403 syllabify_word(word);
404 subword_metrical_tree(word);
405
406 if (siod_get_lval("mettree_debug", NULL) != NIL)
407 word.save("word.utt", "est");
408 }
409 */
410
411 /*bool vowel(EST_String p)
412 {
413 if (p.contains(RXint))
414 p = p.before(RXint);
415 return ph_is_vowel(p);
416 }
417 */
418
419 // Add phones, and sylstructure for a single syllable
420
421
422 /*bool met_node_is_leaf(EST_Item *met_node)
423 {
424 return met_node->in_relation("Syllable");
425 }
426 */
427
428
429 /*static int sonority(EST_String p)
430 {
431 if (p.contains(RXint))
432 p = p.before(RXint);
433 if (ph_is_vowel(p))
434 return 6;
435 if (ph_is_liquid(p) || ph_is_approximant(p))
436 return 5;
437 if (ph_is_nasal(p))
438 return 4;
439 if (ph_is_fricative(p) && (!ph_is_s(p)))
440 return 3;
441 if (ph_is_stop(p))
442 return 2;
443 return 1;
444 }
445
446 // Parse arbitrary phone string with numbered vowels into
447 // syllable, phone and sylstructure relations
448
449 static bool ph_is_semivowel(EST_String c1)
450 {
451 return (ph_is_liquid(c1) || (ph_is_approximant(c1)));
452 }
453
454 static bool ph_is_s(EST_String c1)
455 {
456 return (c1 == "s");
457 }
458 */
459 /*static bool ph_is_stress_vowel(EST_String p)
460 {
461 if (p.contains(RXint))
462 p = p.before(RXint);
463 // cout << "p = " << p << endl;
464 return (ph_is_vowel(p));
465 }
466
467
468 static int vowel_count(EST_Relation &phone)
469 {
470 int v = 0;
471 for (EST_Item *l = phone.head(); l; l = l->next())
472 if (ph_is_vowel(l->name()))
473 ++v;
474 return v;
475 }
476 */
477 /*static void syllabify_word(EST_Utterance &word)
478 {
479 EST_Item *prev_syl, *this_syl, *l;
480 EST_Item *first_onset, *first_coda = 0;
481 EST_String v;
482
483 first_onset = 0;
484
485 if (nucleus_count(*word.relation("Phone")) < 1)
486 {
487 cerr << "Error: Pronunciation for " <<
488 *(word.relation("Word")->head()) << " does not contain vowel\n";
489 festival_error();
490 }
491
492 for (prev_syl = 0, l = word.relation("Phone")->head(); l; l = l->next())
493 {
494 cout << "syl: " << l->S("name") << ": " << l->S("df.syllabic", 1)
495 << endl;
496 if (l->S("df.syllabic") == "+")
497 {
498 this_syl = word.relation("Syllable")->append();
499 word.relation("SylStructure")->append(this_syl);
500 this_syl->set("stress_num", l->I("stress_num"));
501
502 // note: it must be in this order
503 make_nucleus(this_syl->as_relation("SylStructure"), l);
504
505 first_onset = make_onset(this_syl->as_relation("SylStructure"),l);
506
507 make_coda(first_coda, first_onset,
508 prev_syl->as_relation("SylStructure"));
509
510 prev_syl = this_syl;
511 first_coda = l->next();
512 }
513 }
514
515 make_coda(first_coda, first_onset, prev_syl->as_relation("SylStructure"));
516 }
517 */
518
519
520 /*void convert_cmu_lex_to_utt(EST_Utterance &u, EST_Item *w)
521 {
522 LISP entry, lpos;
523 EST_String pos;
524 EST_Utterance word;
525 EST_Item *nw, *p;
526 EST_StrList lex_def;
527
528 pos = w->f("pos");
529 if (pos != "0")
530 lpos = rintern(pos);
531 else
532 lpos = NIL;
533 entry = lex_lookup_word(w->name(), lpos);
534
535 lprint(entry);
536
537 lpos = cdr(cdr(entry));
538
539 printf("lpos\n");
540 lprint(lpos);
541
542 word.create_relation("Word");
543 word.create_relation("MetricalTree");
544 word.create_relation("SylStructure");
545 word.create_relation("Syllable");
546 word.create_relation("Phone");
547
548 nw = word.relation("Word")->append();
549 nw->set("name", w->S("name"));
550
551 // parse_lex_string(word, car(lpos));
552 EST_String def, lex_phone;
553
554 // cout << "DEF\n";
555 lprint(lpos);
556
557
558 // make phone relation
559 siod_list_to_strlist(car(lpos), lex_def);
560
561 for (EST_Litem *sl = lex_def.head(); sl; sl = sl->next())
562 {
563 p = word.relation("Phone")->append();
564 lex_phone = lex_def(sl);
565 if (lex_phone.contains(RXint))
566 {
567 p->set("name", lex_phone.before(RXint));
568 p->set("stress_num", lex_phone.at(RXint));
569 }
570 else
571 p->set("name", lex_phone);
572 }
573
574 syllabify_word(word);
575
576 // cout << "before MT: " << *w << " F:" << w->f << endl;
577 // cout << "before MT: " << *(word.relation("Syllable")->head()->Info())
578 // << " F:" << word.relation("Syllable")->head()->Info()->f << endl;
579
580 subword_metrical_tree(word);
581
582 // temporary hack because single syllable words don't get a metrical
583 // node
584 // if ((word.relation("MetricalTree")->head() == 0) &&
585 // (word.relation("Syllable")->head() != 0))
586 // word.relation("MetricalTree")->append(word.relation("Syllable")->head()->Info());
587
588 if (siod_get_lval("mettree_debug", NULL) != NIL)
589 word.save("word.utt", "est");
590 // u.save("before.utt", "est");
591 // cout << "before merge: " << *w << " F:" << w->f << endl;
592 // cout << "before syl: " << *(word.relation("Syllable")->head()->Info())
593 // << " F:" << word.relation("Syllable")->head()->Info()->f << endl;
594
595 utterance_merge(u, word, w, "MetricalTree");
596 // cout << "after merge: " << *w << " F:" << w->f << endl;
597 // cout << "after syl: " << *(word.relation("Syllable")->head()->Info())
598 // << " F:" << word.relation("Syllable")->head()->Info()->f << endl;
599
600 // u.save("after.utt", "est");
601 }
602
603 */
604
605 /*static void subword_metrical_tree(EST_Utterance &word)
606 {
607 EST_Item *s;
608 EST_Item *new_leaf;
609
610 // cout << endl<< endl << *(word.relation("Word")->head()->Info()) << endl;
611
612 // cout << "head: " << word.relation("Syllable")->head() << endl;
613 // cout << "head: " << *(word.relation("Syllable")->head()->Info()) << endl;
614 // cout << "pre foot iteration:" << *(s->Info()) << " stress: " << s->Info()->f("stress_num") << endl << endl;
615
616 // absorb initial unstressed syllables
617 for (s = word.relation("Syllable")->head();
618 s && (s->f("stress_num") == 0); s = s->next())
619 {
620 // cout << "**1 syl:" << *s << endl;
621 new_leaf = word.relation("MetricalTree")->append(s);
622 new_leaf->set("MetricalValue", "w");
623 }
624
625 // cout << "utt to now 1c: " << word << endl;
626 // In a multi-syllable word
627
628 if (next(word.relation("Syllable")->head()))
629 {
630 //s = word.relation("Syllable")->head();
631 // cout << "**2 syl:" << *s << endl;
632 for (; s;)
633 {
634 cout << "**3 syl:" << *s << endl;
635 cout << "foot iteration\n" << *s << endl << endl;
636 new_leaf = word.relation("MetricalTree")->append(s);
637 new_leaf->set("MetricalValue", "s");
638 // s = make_foot(new_leaf, next(s));
639 }
640 }
641
642 else if (s) // For single syllable words
643 {
644 // cout << "adding single node\n" << *s << endl << endl;
645 new_leaf = word.relation("MetricalTree")->append(s);
646 // cout << "added node\n" << *s << endl << endl;
647 }
648
649 // cout << "utt to now 2: " << word << endl;
650
651 if (siod_get_lval("mettree_debug", NULL) != NIL)
652 word.save("sub_word.utt", "est");
653
654 s = word.relation("MetricalTree")->head();
655
656 // make_super_foot(s, next(s));
657 if (siod_get_lval("mettree_debug", NULL) != NIL)
658 word.save("super_foot.utt", "est");
659
660 // all_stress(word);
661 }
662 */
663
664
665
666
667