1 /*************************************************************************/
2 /*                                                                       */
3 /*                Centre for Speech Technology Research                  */
4 /*                     University of Edinburgh, UK                       */
5 /*                       Copyright (c) 1996,1997                         */
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                            */
34 /*                     Date   :  April 1996                              */
35 /*-----------------------------------------------------------------------*/
36 /*                                                                       */
37 /* Phone Name sybsystem, allowing definitions of PhoneSets               */
38 /* by arbitrary features (and mapping)                                   */
39 /*                                                                       */
40 /*=======================================================================*/
41 #include <cstdio>
42 #include "EST_unix.h"
43 #include "festival.h"
44 #include "festivalP.h"
45 
46 static void check_phoneset(void);
47 static void ps_add_def(const EST_String &name, PhoneSet *ps);
48 static LISP lisp_select_phoneset(LISP phoneset);
49 
50 static LISP phone_set_list = NULL;
51 static PhoneSet *current_phoneset = NULL;
52 
53 static const EST_String f_cvox("cvox");
54 static const EST_String f_vc("vc");
55 static const EST_String f_ctype("ctype");
56 
VAL_REGISTER_CLASS(phone,Phone)57 VAL_REGISTER_CLASS(phone,Phone)
58 SIOD_REGISTER_CLASS(phone,Phone)
59 VAL_REGISTER_CLASS(phoneset,PhoneSet)
60 SIOD_REGISTER_CLASS(phoneset,PhoneSet)
61 
62 int Phone::match_features(Phone *foreign)
63 {
64     // Try to match the features of the foreign phone with this one
65     EST_Litem *f;
66 
67     for (f=features.list.head(); f != 0; f=f->next())
68     {
69 	if ( features.list(f).v != foreign->val(features.list(f).k))
70 	    return FALSE;
71     }
72 
73     return TRUE;
74 
75 }
76 
~PhoneSet()77 PhoneSet::~PhoneSet()
78 {
79     gc_unprotect(&silences);
80     gc_unprotect(&map);
81     gc_unprotect(&feature_defs);
82     gc_unprotect(&phones);
83 }
84 
member(const EST_String & ph) const85 Phone * PhoneSet::member(const EST_String &ph) const
86 {
87     LISP p = siod_assoc_str(ph,phones);
88     if (p != 0)
89 	return phone(car(cdr(p)));
90     else
91     {
92 	cerr << "Phone \"" << ph << "\" not member of PhoneSet \"" <<
93 	    psetname << "\"" << endl;
94 	return 0;
95     }
96 }
97 
phnum(const int n) const98 const char *PhoneSet::phnum(const int n) const
99 {
100     // return the nth phone name
101     int i;
102     LISP p;
103 
104     // Should use nth for this, but I want to controll the error case
105     for (i=0,p=phones; p != NIL; p=cdr(p),i++)
106     {
107 	if (i == n)
108 	    return get_c_string(car(car(p)));
109     }
110 
111     cerr << "Phone (phnum) " << n <<
112 	" too large, not that many members in PhoneSet \"" <<
113 	psetname << "\"" << endl;
114     festival_error();
115     return NULL;
116 }
117 
add_phone(Phone * phone)118 int PhoneSet::add_phone(Phone *phone)
119 {
120     // Add phone (deleting existing one with warning)
121     LISP lpair;
122 
123     lpair = siod_assoc_str(phone->phone_name(),phones);
124 
125     if (lpair == NIL)
126     {
127 	phones = cons(make_param_lisp(phone->phone_name(),
128 				      siod(phone)),
129 		      phones);
130 	return TRUE;
131     }
132     else
133 	return FALSE;
134 }
135 
set_feature(const EST_String & name,LISP vals)136 void PhoneSet::set_feature(const EST_String &name, LISP vals)
137 {
138     LISP lpair;
139 
140     lpair = siod_assoc_str(name, feature_defs);
141 
142     if (lpair == NIL)
143 	feature_defs = cons(make_param_lisp(name,vals),feature_defs);
144     else
145     {
146 	cerr << "PhoneSet: replacing feature definition of " <<
147 	    name << " PhoneSet " << psetname << endl;
148 	CAR(cdr(lpair)) = vals;
149     }
150 }
151 
phnum(const char * phone) const152 int PhoneSet::phnum(const char *phone) const
153 {
154     // Return a unique number for this phone, i.e. position in list
155     int i;
156     LISP p;
157 
158     for (i=0,p=phones; p != NIL; p=cdr(p),i++)
159     {
160 	if (streq(phone,get_c_string(car(car(p)))))
161 	    return i;
162     }
163 
164     cerr << "Phone \"" << phone << "\" not member of PhoneSet \"" <<
165 	psetname << "\"" << endl;
166     festival_error();
167 
168     return -1;
169 }
170 
find_matched_phone(Phone * foreign)171 Phone *PhoneSet::find_matched_phone(Phone *foreign)
172 {
173     // find a phone in the current set that matches the features
174     // in foreign (from a different phoneset)
175     LISP p;
176 
177     for (p=phones; p != NIL; p=cdr(p))
178     {
179 	if (phone(car(cdr(car(p))))->match_features(foreign))
180 	    return phone(car(cdr(car(p))));
181     }
182 
183     // could try harder
184 
185     cerr << "Cannot map phoneme " << *foreign << endl;
186     festival_error();
187 
188     return 0;
189 
190 }
191 
is_silence(const EST_String & ph) const192 int PhoneSet::is_silence(const EST_String &ph) const
193 {
194     // TRUE is ph is silence
195 
196     return (siod_member_str(ph,silences) != NIL);
197 
198 }
199 
set_silences(LISP sils)200 void PhoneSet::set_silences(LISP sils)
201 {
202     silences=sils;
203 }
204 
set_map(LISP m)205 void PhoneSet::set_map(LISP m)
206 {
207     map=m;
208 }
209 
operator =(const PhoneSet & a)210 PhoneSet &PhoneSet::operator = (const PhoneSet &a)
211 {
212     psetname = a.psetname;
213     silences = a.silences;
214     map = a.map;
215     feature_defs = a.feature_defs;
216     phones = a.phones;
217     return *this;
218 }
219 
make_phoneset(LISP args,LISP env)220 LISP make_phoneset(LISP args,LISP env)
221 {
222     // define a new phoneme set
223     (void)env;
224     PhoneSet *ps = new PhoneSet;
225     Phone *phone;
226     LISP f,p,pv;
227     LISP name, features, phones;
228     EST_String feat,val;
229     int num_feats;
230 
231     name = car(args);
232     features = car(cdr(args));
233     phones = car(cdr(cdr(args)));
234 
235     ps->set_phone_set_name(get_c_string(name));
236     // Define the phonetic features
237     num_feats = siod_llength(features);
238     for (f=features; f != NIL; f=cdr(f))
239 	ps->set_feature(get_c_string(car(car(f))),cdr(car(f)));
240 
241     // Define the phones
242     for (p=phones; p != NIL; p=cdr(p))
243     {
244 	if (siod_llength(cdr(car(p))) != num_feats)
245 	{
246 	    cerr << "Wrong number of phone features for "
247 		<< get_c_string(car(car(p))) << " in " <<
248 		    get_c_string(name) << endl;
249 	    festival_error();
250 	}
251 	phone = new Phone;
252 	phone->set_phone_name(get_c_string(car(car(p))));
253 	for (pv=cdr(car(p)),f=features; f != NIL; pv=cdr(pv),f=cdr(f))
254 	{
255 	    feat = get_c_string(car(car(f)));
256 	    val = get_c_string(car(pv));
257 	    if (ps->feat_val(feat,val))
258 		phone->add_feat(feat,val);
259 	    else
260 	    {
261 		cerr << "Phone " << phone->phone_name() <<
262 		    " has invalid value "
263 		    << get_c_string(car(pv)) << " for feature "
264 			<< feat << endl;
265 		festival_error();
266 	    }
267 	}
268 	if (ps->add_phone(phone) == FALSE)
269 	{
270 	    cerr << "Phone " << phone->phone_name() <<
271 		" multiply defined " << endl;
272 	    festival_error();
273 	}
274     }
275 
276     ps_add_def(ps->phone_set_name(),ps);
277     current_phoneset = ps;  // selects this one as current
278 
279     return NIL;
280 }
281 
lisp_set_silence(LISP silences)282 static LISP lisp_set_silence(LISP silences)
283 {
284     // Set list of names as silences for current phoneset
285 
286     check_phoneset();
287     current_phoneset->set_silences(silences);
288     return silences;
289 }
290 
phoneset_name_to_set(const EST_String & name)291 PhoneSet *phoneset_name_to_set(const EST_String &name)
292 {
293     LISP lpair = siod_assoc_str(name,phone_set_list);
294 
295     if (lpair == NIL)
296     {
297 	cerr << "Phoneset " << name << " not defined" << endl;
298 	festival_error();
299     }
300 
301     return phoneset(car(cdr(lpair)));
302 
303 }
304 
lisp_select_phoneset(LISP pset)305 static LISP lisp_select_phoneset(LISP pset)
306 {
307     // Select named phoneset and make it current
308     EST_String name = get_c_string(pset);
309     LISP lpair;
310 
311     lpair = siod_assoc_str(name,phone_set_list);
312 
313     if (lpair == NIL)
314     {
315 	cerr << "Phoneset " << name << " not defined" << endl;
316 	festival_error();
317     }
318     else
319 	current_phoneset = phoneset(car(cdr(lpair)));
320 
321     return pset;
322 }
323 
ps_add_def(const EST_String & name,PhoneSet * ps)324 static void ps_add_def(const EST_String &name, PhoneSet *ps)
325 {
326     //  Add phoneset to list of phonesets
327     LISP lpair;
328 
329     if (phone_set_list == NIL)
330 	gc_protect(&phone_set_list);
331 
332     lpair = siod_assoc_str(name,phone_set_list);
333 
334     if (lpair == NIL)
335     {
336 	phone_set_list = cons(cons(rintern(name),
337 				   cons(siod(ps),NIL)),
338 			      phone_set_list);
339     }
340     else
341     {
342 	cwarn << "Phoneset \"" << name << "\" redefined" << endl;
343 	setcar(cdr(lpair),siod(ps));
344     }
345 
346     return;
347 }
348 
check_phoneset(void)349 static void check_phoneset(void)
350 {
351     // check if there is a phoneset defined
352 
353     if (current_phoneset == NULL)
354     {
355 	cerr << "No phoneset currently selected";
356 	festival_error();
357     }
358 }
359 
ff_ph_feature(EST_Item * s,const EST_String & name)360 static EST_Val ff_ph_feature(EST_Item *s,const EST_String &name)
361 {
362     // This function is called for all phone features.
363     // It looks at the name to find out the
364     // the actual name used to call this feature and removed the
365     // ph_ prefix and uses the remainder as the phone feature name
366     Phone *phone_def;
367 
368     if (!name.contains("ph_",0))
369     {
370 	cerr << "Not a phone feature function " << name << endl;
371 	festival_error();
372     }
373 
374     check_phoneset();
375 
376     const EST_String &fname = name.after("ph_");
377     phone_def = current_phoneset->member(s->name());
378     if (phone_def == 0)
379     {
380 	cerr << "Phone " << s->name() << " not in PhoneSet \"" <<
381 	    current_phoneset->phone_set_name() << "\"" << endl;
382 	festival_error();
383     }
384 
385     const EST_String &rrr = phone_def->val(fname,EST_String::Empty);
386     if (rrr == EST_String::Empty)
387     {
388 	cerr << "Phone " << s->name() << " does not have feature " <<
389 	    fname << endl;
390 	festival_error();
391     }
392 
393     return EST_Val(rrr);
394 }
395 
find_phoneset(EST_String name)396 static PhoneSet *find_phoneset(EST_String name)
397 {
398     // get the phone set from the phone set list
399     LISP lpair;
400 
401     lpair = siod_assoc_str(name,phone_set_list);
402 
403     if (lpair == NIL)
404     {
405 	cerr << "Phoneset \"" << name << "\" not defined" << endl;
406 	festival_error();
407     }
408     return phoneset(car(cdr(lpair)));
409 
410 }
411 
map_phone(const EST_String & fromphonename,const EST_String & fromsetname,const EST_String & tosetname)412 const EST_String &map_phone(const EST_String &fromphonename, const EST_String &fromsetname,
413 			const EST_String &tosetname)
414 {
415     PhoneSet *fromset, *toset;
416     Phone *fromphone, *tophone;
417 
418     fromset = find_phoneset(fromsetname);
419     toset = find_phoneset(tosetname);
420 
421     // should check specific matches in fromset first
422     fromphone = fromset->member(fromphonename);
423     if (fromphone == 0)
424 	festival_error();
425     tophone = toset->find_matched_phone(fromphone);
426 
427     return tophone->phone_name();
428 }
429 
ph_is_silence(const EST_String & ph)430 int ph_is_silence(const EST_String &ph)
431 {
432     // TRUE if this phone is silence
433 
434     check_phoneset();
435     return current_phoneset->is_silence(ph);
436 
437 }
438 
ph_silence(void)439 EST_String ph_silence(void)
440 {
441     // return the first silence in the current_phoneset
442     EST_String s;
443 
444     check_phoneset();
445 
446     if (current_phoneset->get_silences() == NIL)
447     {
448 	cerr << "No silences set for PhoneSet\"" <<
449 	    current_phoneset->phone_set_name() << "\"" << endl;
450 	return "sil";
451     }
452     else
453 	return get_c_string(car(current_phoneset->get_silences()));
454 
455 }
456 
ph_is_vowel(const EST_String & ph)457 int ph_is_vowel(const EST_String &ph)
458 {
459     // TRUE if this phone is a vowel, assumes the feature vc is used
460 
461     return (ph_feat(ph,f_vc) == "+");
462 }
463 
ph_is_consonant(const EST_String & ph)464 int ph_is_consonant(const EST_String &ph)
465 {
466     // TRUE if this phone is a consonant, assumes the feature vc is used
467 
468     return ((ph_feat(ph,f_vc) == "-") &&
469 	    !(ph_is_silence(ph)));
470 }
471 
ph_is_liquid(const EST_String & ph)472 int ph_is_liquid(const EST_String &ph)
473 {
474     // TRUE if this phone is a liquid
475 
476     return (ph_feat(ph,f_ctype) == "l");
477 }
478 
ph_is_approximant(const EST_String & ph)479 int ph_is_approximant(const EST_String &ph)
480 {
481     // TRUE if this phone is an approximant
482 
483     return (ph_feat(ph,f_ctype) == "r");
484 }
485 
ph_is_stop(const EST_String & ph)486 int ph_is_stop(const EST_String &ph)
487 {
488     // TRUE if this phone is a stop
489 
490     return (ph_feat(ph,f_ctype) == "s");
491 }
492 
ph_is_fricative(const EST_String & ph)493 int ph_is_fricative(const EST_String &ph)
494 {
495     // TRUE if this phone is a stop
496 
497     return (ph_feat(ph,f_ctype) == "f");
498 }
499 
ph_is_nasal(const EST_String & ph)500 int ph_is_nasal(const EST_String &ph)
501 {
502     // TRUE if this phone is a nasal
503 
504     return (ph_feat(ph,f_ctype) == "n");
505 }
506 
ph_is_obstruent(const EST_String & ph)507 int ph_is_obstruent(const EST_String &ph)
508 {
509     // TRUE if this phone is a obstruent
510     EST_String v = ph_feat(ph,f_ctype);
511 
512     return ((v == "s") || (v == "f") || (v == "a"));
513 }
514 
ph_is_sonorant(const EST_String & ph)515 int ph_is_sonorant(const EST_String &ph)
516 {
517     // TRUE if this phone is a sonorant
518 
519     return !ph_is_obstruent(ph);
520 }
521 
ph_is_voiced(const EST_String & ph)522 int ph_is_voiced(const EST_String &ph)
523 {
524     // TRUE if this phone is a sonorant
525 
526     return (ph_feat(ph,f_cvox) == "+");
527 }
528 
ph_is_syllabic(const EST_String & ph)529 int ph_is_syllabic(const EST_String &ph)
530 {
531     // TRUE if this phone is a syllabic consonant (or vowel)
532     // Yes I know we just don't have this ...
533 
534     return (ph_feat(ph,f_vc) == "+");
535 }
536 
ph_feat(const EST_String & ph,const EST_String & feat)537 const EST_String &ph_feat(const EST_String &ph,const EST_String &feat)
538 {
539     // Values for this phone -- error is phone of feat doesn't exist
540     Phone *phone_def;
541     EST_String rrr;
542 
543     check_phoneset();
544     phone_def = current_phoneset->member(ph);
545     if (phone_def == 0)
546     {
547 	cerr << "Phone " << ph << " not in phone set " <<
548 	    current_phoneset->phone_set_name() << endl;
549 	festival_error();
550     }
551 
552     return phone_def->val(feat,EST_String::Empty);
553 
554 }
555 
ph_sonority(const EST_String & ph)556 int ph_sonority(const EST_String &ph)
557 {
558     // assumes standard phone features
559     Phone *p;
560 
561     check_phoneset();
562     p = current_phoneset->member(ph);
563     if (p == 0)
564 	return 1;
565 
566     if (p->val(f_vc) == "+")
567 	return 5;
568     else if (p->val(f_ctype) == "l") // || glide
569         return 4;
570     else if (p->val(f_ctype) == "n")
571         return 3;
572     else if (p->val(f_cvox) == "+") // voiced obstruents (stop fric affric)
573         return 2;
574     else
575         return 1;
576 
577 }
578 
l_phoneset(LISP options)579 LISP l_phoneset(LISP options)
580 {
581     //  Return Lisp form of current phone set
582     LISP description=NIL;
583 
584     check_phoneset();
585 
586     if ((options == NIL) ||
587 	(siod_member_str("silences",options)))
588     {
589 	description = cons(make_param_lisp("silences",
590 					   current_phoneset->get_silences()),
591 			   description);
592     }
593     if ((options == NIL) ||
594 	(siod_member_str("phones",options)))
595     {
596 	LISP phones = current_phoneset->get_phones();
597 	LISP features = current_phoneset->get_feature_defs();
598 	LISP p,f,p_desc=NIL,f_desc=NIL;
599 
600 	for (p=phones; p != NIL; p=cdr(p))
601 	{
602 	    f_desc = NIL;
603 	    for (f=reverse(features); f != NIL; f=cdr(f))
604 	    {
605 		f_desc = cons(rintern(ph_feat(get_c_string(car(car(p))),
606 						get_c_string(car(car(f))))),
607 			      f_desc);
608 	    }
609 	    p_desc = cons(cons(car(car(p)),f_desc),p_desc);
610 	}
611 	description = cons(make_param_lisp("phones",p_desc),description);
612     }
613     if ((options == NIL) ||
614 	(siod_member_str("features",options)))
615     {
616 	description = cons(make_param_lisp("features",
617 					   current_phoneset->get_feature_defs()),
618 			   description);
619     }
620     if ((options == NIL) ||
621 	(siod_member_str("name",options)))
622     {
623 	description = cons(make_param_str("name",
624 					  current_phoneset->phone_set_name()),
625 			   description);
626     }
627 
628     return description;
629 }
630 
l_phoneset_list()631 LISP l_phoneset_list()
632 {
633     LISP phonesets = NIL;
634     LISP l;
635 
636     for (l=phone_set_list; l != NIL; l=cdr(l))
637 	phonesets = cons(car(car(l)),phonesets);
638 
639     return phonesets;
640 }
641 
festival_Phone_init(void)642 void festival_Phone_init(void)
643 {
644     // define Lisp accessor functions
645 
646     init_fsubr("defPhoneSet",make_phoneset,
647  "(defPhoneSet PHONESETNAME FEATURES PHONEDEFS)\n\
648   Define a new phoneset named PHONESETNAME.  Each phone is described with a\n\
649   set of features as described in FEATURES.  Some of these FEATURES may\n\
650   be significant in various parts of the system.  Copying an existing\n\
651   description is a good start. [see Phonesets]");
652     init_subr_1("PhoneSet.select",lisp_select_phoneset,
653  "(PhoneSet.select PHONESETNAME)\n\
654   Select PHONESETNAME as current phoneset. [see Phonesets]");
655     init_subr_1("PhoneSet.silences",lisp_set_silence,
656  "(PhoneSet.silences LIST)\n\
657   Declare LIST of phones as silences.  The first in the list should be\n\
658   the \"most\" silent. [see Phonesets]");
659     init_subr_1("PhoneSet.description",l_phoneset,
660  "(Phoneset.description OPTIONS)\n\
661   Returns a lisp for of the current phoneme set.  Options is a list of\n\
662   parts of the definition you require.  OPTIONS may include, silences,\n\
663   phones, features and/or name.  If nil all are returned.");
664     init_subr_0("PhoneSet.list",l_phoneset_list,
665  "(Phoneset.list)\n\
666   List the names of all currently defined Phonesets.");
667     // All feature functions starting with "ph_"
668     festival_def_ff_pref("ph_","Segment",ff_ph_feature,
669     "Segment.ph_*\n\
670   Access phoneset features for a segment.  This definition covers multiple\n\
671   feature functions where ph_ may be extended with any features that\n\
672   are defined in the phoneset (e.g. vc, vlng, cplace etc.).");
673 
674 }
675 
676