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