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