1#![allow(dead_code)]
10
11use std::collections::HashMap;
12
13#[derive(Clone, Debug, PartialEq, Eq, Hash)]
19pub enum Phoneme {
20 Silence,
22 AA, AE, AH, AO, AW, AY, EH, ER, EY, IH, IY, OW, OY, UH, UW, B,
40 CH,
41 D,
42 DH,
43 F,
44 G,
45 HH,
46 JH,
47 K,
48 L,
49 M,
50 N,
51 NG,
52 P,
53 R,
54 S,
55 SH,
56 T,
57 TH,
58 V,
59 W,
60 Y,
61 Z,
62 ZH,
63}
64
65impl Phoneme {
66 pub fn all() -> &'static [Phoneme] {
68 &[
69 Phoneme::Silence,
70 Phoneme::AA,
71 Phoneme::AE,
72 Phoneme::AH,
73 Phoneme::AO,
74 Phoneme::AW,
75 Phoneme::AY,
76 Phoneme::EH,
77 Phoneme::ER,
78 Phoneme::EY,
79 Phoneme::IH,
80 Phoneme::IY,
81 Phoneme::OW,
82 Phoneme::OY,
83 Phoneme::UH,
84 Phoneme::UW,
85 Phoneme::B,
86 Phoneme::CH,
87 Phoneme::D,
88 Phoneme::DH,
89 Phoneme::F,
90 Phoneme::G,
91 Phoneme::HH,
92 Phoneme::JH,
93 Phoneme::K,
94 Phoneme::L,
95 Phoneme::M,
96 Phoneme::N,
97 Phoneme::NG,
98 Phoneme::P,
99 Phoneme::R,
100 Phoneme::S,
101 Phoneme::SH,
102 Phoneme::T,
103 Phoneme::TH,
104 Phoneme::V,
105 Phoneme::W,
106 Phoneme::Y,
107 Phoneme::Z,
108 Phoneme::ZH,
109 ]
110 }
111
112 pub fn name(&self) -> &'static str {
114 match self {
115 Phoneme::Silence => "Silence",
116 Phoneme::AA => "AA",
117 Phoneme::AE => "AE",
118 Phoneme::AH => "AH",
119 Phoneme::AO => "AO",
120 Phoneme::AW => "AW",
121 Phoneme::AY => "AY",
122 Phoneme::EH => "EH",
123 Phoneme::ER => "ER",
124 Phoneme::EY => "EY",
125 Phoneme::IH => "IH",
126 Phoneme::IY => "IY",
127 Phoneme::OW => "OW",
128 Phoneme::OY => "OY",
129 Phoneme::UH => "UH",
130 Phoneme::UW => "UW",
131 Phoneme::B => "B",
132 Phoneme::CH => "CH",
133 Phoneme::D => "D",
134 Phoneme::DH => "DH",
135 Phoneme::F => "F",
136 Phoneme::G => "G",
137 Phoneme::HH => "HH",
138 Phoneme::JH => "JH",
139 Phoneme::K => "K",
140 Phoneme::L => "L",
141 Phoneme::M => "M",
142 Phoneme::N => "N",
143 Phoneme::NG => "NG",
144 Phoneme::P => "P",
145 Phoneme::R => "R",
146 Phoneme::S => "S",
147 Phoneme::SH => "SH",
148 Phoneme::T => "T",
149 Phoneme::TH => "TH",
150 Phoneme::V => "V",
151 Phoneme::W => "W",
152 Phoneme::Y => "Y",
153 Phoneme::Z => "Z",
154 Phoneme::ZH => "ZH",
155 }
156 }
157
158 pub fn is_vowel(&self) -> bool {
160 matches!(
161 self,
162 Phoneme::AA
163 | Phoneme::AE
164 | Phoneme::AH
165 | Phoneme::AO
166 | Phoneme::AW
167 | Phoneme::AY
168 | Phoneme::EH
169 | Phoneme::ER
170 | Phoneme::EY
171 | Phoneme::IH
172 | Phoneme::IY
173 | Phoneme::OW
174 | Phoneme::OY
175 | Phoneme::UH
176 | Phoneme::UW
177 )
178 }
179
180 pub fn is_consonant(&self) -> bool {
182 matches!(
183 self,
184 Phoneme::B
185 | Phoneme::CH
186 | Phoneme::D
187 | Phoneme::DH
188 | Phoneme::F
189 | Phoneme::G
190 | Phoneme::HH
191 | Phoneme::JH
192 | Phoneme::K
193 | Phoneme::L
194 | Phoneme::M
195 | Phoneme::N
196 | Phoneme::NG
197 | Phoneme::P
198 | Phoneme::R
199 | Phoneme::S
200 | Phoneme::SH
201 | Phoneme::T
202 | Phoneme::TH
203 | Phoneme::V
204 | Phoneme::W
205 | Phoneme::Y
206 | Phoneme::Z
207 | Phoneme::ZH
208 )
209 }
210
211 pub fn from_arpabet(s: &str) -> Option<Phoneme> {
220 match s.to_uppercase().as_str() {
221 "SILENCE" | "SIL" | "SP" | "_" => Some(Phoneme::Silence),
222 "AA" => Some(Phoneme::AA),
223 "AE" => Some(Phoneme::AE),
224 "AH" => Some(Phoneme::AH),
225 "AO" => Some(Phoneme::AO),
226 "AW" => Some(Phoneme::AW),
227 "AY" => Some(Phoneme::AY),
228 "EH" => Some(Phoneme::EH),
229 "ER" => Some(Phoneme::ER),
230 "EY" => Some(Phoneme::EY),
231 "IH" => Some(Phoneme::IH),
232 "IY" => Some(Phoneme::IY),
233 "OW" => Some(Phoneme::OW),
234 "OY" => Some(Phoneme::OY),
235 "UH" => Some(Phoneme::UH),
236 "UW" => Some(Phoneme::UW),
237 "B" => Some(Phoneme::B),
238 "CH" => Some(Phoneme::CH),
239 "D" => Some(Phoneme::D),
240 "DH" => Some(Phoneme::DH),
241 "F" => Some(Phoneme::F),
242 "G" => Some(Phoneme::G),
243 "HH" => Some(Phoneme::HH),
244 "JH" => Some(Phoneme::JH),
245 "K" => Some(Phoneme::K),
246 "L" => Some(Phoneme::L),
247 "M" => Some(Phoneme::M),
248 "N" => Some(Phoneme::N),
249 "NG" => Some(Phoneme::NG),
250 "P" => Some(Phoneme::P),
251 "R" => Some(Phoneme::R),
252 "S" => Some(Phoneme::S),
253 "SH" => Some(Phoneme::SH),
254 "T" => Some(Phoneme::T),
255 "TH" => Some(Phoneme::TH),
256 "V" => Some(Phoneme::V),
257 "W" => Some(Phoneme::W),
258 "Y" => Some(Phoneme::Y),
259 "Z" => Some(Phoneme::Z),
260 "ZH" => Some(Phoneme::ZH),
261 _ => None,
262 }
263 }
264}
265
266#[derive(Clone, Debug, PartialEq, Eq, Hash)]
272pub enum Viseme {
273 Silence,
275 PP,
277 FF,
279 TH,
281 DD,
283 KK,
285 CH,
287 SS,
289 Aa,
291 E,
293 I,
295 O,
297 U,
299 RR,
301 Neutral,
303}
304
305impl Viseme {
306 pub fn all() -> &'static [Viseme] {
308 &[
309 Viseme::Silence,
310 Viseme::PP,
311 Viseme::FF,
312 Viseme::TH,
313 Viseme::DD,
314 Viseme::KK,
315 Viseme::CH,
316 Viseme::SS,
317 Viseme::Aa,
318 Viseme::E,
319 Viseme::I,
320 Viseme::O,
321 Viseme::U,
322 Viseme::RR,
323 Viseme::Neutral,
324 ]
325 }
326
327 pub fn name(&self) -> &'static str {
329 match self {
330 Viseme::Silence => "Silence",
331 Viseme::PP => "PP",
332 Viseme::FF => "FF",
333 Viseme::TH => "TH",
334 Viseme::DD => "DD",
335 Viseme::KK => "KK",
336 Viseme::CH => "CH",
337 Viseme::SS => "SS",
338 Viseme::Aa => "Aa",
339 Viseme::E => "E",
340 Viseme::I => "I",
341 Viseme::O => "O",
342 Viseme::U => "U",
343 Viseme::RR => "RR",
344 Viseme::Neutral => "Neutral",
345 }
346 }
347}
348
349pub fn phoneme_to_viseme(phoneme: &Phoneme) -> Viseme {
355 match phoneme {
356 Phoneme::Silence => Viseme::Silence,
358 Phoneme::B | Phoneme::M | Phoneme::P => Viseme::PP,
360 Phoneme::F | Phoneme::V => Viseme::FF,
362 Phoneme::TH | Phoneme::DH => Viseme::TH,
364 Phoneme::D | Phoneme::T | Phoneme::N | Phoneme::L => Viseme::DD,
366 Phoneme::K | Phoneme::G | Phoneme::NG => Viseme::KK,
368 Phoneme::CH | Phoneme::SH | Phoneme::ZH | Phoneme::JH => Viseme::CH,
370 Phoneme::S | Phoneme::Z => Viseme::SS,
372 Phoneme::AA | Phoneme::AE | Phoneme::AH => Viseme::Aa,
374 Phoneme::EH | Phoneme::AY | Phoneme::EY => Viseme::E,
376 Phoneme::ER => Viseme::RR,
378 Phoneme::IH | Phoneme::IY => Viseme::I,
380 Phoneme::AO | Phoneme::OW | Phoneme::OY => Viseme::O,
382 Phoneme::UH | Phoneme::UW | Phoneme::AW => Viseme::U,
384 Phoneme::R => Viseme::RR,
386 Phoneme::HH | Phoneme::W | Phoneme::Y => Viseme::Neutral,
388 }
389}
390
391pub type VisemeMorphWeights = HashMap<String, f32>;
397
398pub struct VisemeMapper {
400 mappings: HashMap<Viseme, VisemeMorphWeights>,
401}
402
403impl VisemeMapper {
404 pub fn new() -> Self {
406 Self {
407 mappings: HashMap::new(),
408 }
409 }
410
411 pub fn set_viseme(&mut self, viseme: Viseme, weights: VisemeMorphWeights) {
413 self.mappings.insert(viseme, weights);
414 }
415
416 pub fn get_weights(&self, viseme: &Viseme) -> VisemeMorphWeights {
418 self.mappings.get(viseme).cloned().unwrap_or_default()
419 }
420
421 pub fn evaluate_phoneme(&self, phoneme: &Phoneme) -> VisemeMorphWeights {
423 let viseme = phoneme_to_viseme(phoneme);
424 self.get_weights(&viseme)
425 }
426}
427
428impl Default for VisemeMapper {
429 fn default() -> Self {
430 Self::new()
431 }
432}
433
434pub fn default_viseme_mapper() -> VisemeMapper {
441 let mut mapper = VisemeMapper::new();
442
443 let weights = |pairs: &[(&str, f32)]| -> VisemeMorphWeights {
445 pairs.iter().map(|(k, v)| (k.to_string(), *v)).collect()
446 };
447
448 mapper.set_viseme(Viseme::Silence, weights(&[("lips_closed", 1.0)]));
450
451 mapper.set_viseme(
453 Viseme::PP,
454 weights(&[("lips_closed", 0.9), ("lips_press", 0.5)]),
455 );
456
457 mapper.set_viseme(
459 Viseme::FF,
460 weights(&[("lower_lip_up", 0.6), ("upper_teeth_show", 0.5)]),
461 );
462
463 mapper.set_viseme(
465 Viseme::TH,
466 weights(&[("lips_part", 0.4), ("tongue_tip_up", 0.7)]),
467 );
468
469 mapper.set_viseme(
471 Viseme::DD,
472 weights(&[("lips_part", 0.3), ("jaw_drop", 0.15)]),
473 );
474
475 mapper.set_viseme(
477 Viseme::KK,
478 weights(&[("lips_part", 0.25), ("jaw_drop", 0.2)]),
479 );
480
481 mapper.set_viseme(
483 Viseme::CH,
484 weights(&[("lips_round", 0.4), ("lips_part", 0.35), ("jaw_drop", 0.1)]),
485 );
486
487 mapper.set_viseme(
489 Viseme::SS,
490 weights(&[("lips_part", 0.2), ("teeth_show", 0.4)]),
491 );
492
493 mapper.set_viseme(
495 Viseme::Aa,
496 weights(&[("jaw_drop", 0.7), ("lips_open", 0.8)]),
497 );
498
499 mapper.set_viseme(
501 Viseme::E,
502 weights(&[("lips_wide", 0.5), ("jaw_drop", 0.35), ("lips_open", 0.4)]),
503 );
504
505 mapper.set_viseme(Viseme::I, weights(&[("lips_wide", 0.6), ("jaw_drop", 0.2)]));
507
508 mapper.set_viseme(
510 Viseme::O,
511 weights(&[("lips_round", 0.8), ("jaw_drop", 0.4)]),
512 );
513
514 mapper.set_viseme(
516 Viseme::U,
517 weights(&[("lips_round", 0.9), ("jaw_drop", 0.3), ("lips_pucker", 0.5)]),
518 );
519
520 mapper.set_viseme(
522 Viseme::RR,
523 weights(&[("lips_part", 0.35), ("jaw_drop", 0.25), ("lips_round", 0.3)]),
524 );
525
526 mapper.set_viseme(
528 Viseme::Neutral,
529 weights(&[("lips_part", 0.15), ("jaw_drop", 0.1)]),
530 );
531
532 mapper
533}
534
535pub struct PhonemeEvent {
541 pub start: f32,
543 pub end: f32,
545 pub phoneme: Phoneme,
547 pub intensity: f32,
549}
550
551pub struct LipSyncTrack {
553 pub events: Vec<PhonemeEvent>,
555 pub duration: f32,
557}
558
559impl LipSyncTrack {
560 pub fn new() -> Self {
562 Self {
563 events: Vec::new(),
564 duration: 0.0,
565 }
566 }
567
568 pub fn add_event(&mut self, event: PhonemeEvent) {
570 if event.end > self.duration {
571 self.duration = event.end;
572 }
573 self.events.push(event);
574 }
575
576 pub fn event_count(&self) -> usize {
578 self.events.len()
579 }
580
581 pub fn evaluate(&self, t: f32, mapper: &VisemeMapper) -> VisemeMorphWeights {
587 let maybe_idx = self
589 .events
590 .iter()
591 .enumerate()
592 .find(|(_, ev)| ev.start <= t && t < ev.end)
593 .map(|(i, _)| i);
594
595 let Some(idx) = maybe_idx else {
596 return mapper.get_weights(&Viseme::Silence);
598 };
599
600 let current = &self.events[idx];
601 let current_weights = mapper.evaluate_phoneme(¤t.phoneme);
602
603 let scale_weights = |w: &VisemeMorphWeights, scale: f32| -> VisemeMorphWeights {
605 w.iter().map(|(k, v)| (k.clone(), v * scale)).collect()
606 };
607
608 const BLEND_WINDOW: f32 = 0.05;
610 let time_left = current.end - t;
611
612 if time_left < BLEND_WINDOW {
613 if let Some(next) = self.events.get(idx + 1) {
614 let alpha = 1.0 - time_left / BLEND_WINDOW; let next_weights = mapper.evaluate_phoneme(&next.phoneme);
616
617 let mut blended: VisemeMorphWeights = HashMap::new();
619
620 let mut all_keys: std::collections::HashSet<&String> =
622 std::collections::HashSet::new();
623 for k in current_weights.keys() {
624 all_keys.insert(k);
625 }
626 for k in next_weights.keys() {
627 all_keys.insert(k);
628 }
629
630 for key in all_keys {
631 let cw = *current_weights.get(key).unwrap_or(&0.0);
632 let nw = *next_weights.get(key).unwrap_or(&0.0);
633 let lerped = cw * (1.0 - alpha) + nw * alpha;
634 blended.insert(key.clone(), lerped * current.intensity);
635 }
636 return blended;
637 }
638 }
639
640 scale_weights(¤t_weights, current.intensity)
641 }
642
643 pub fn from_string(s: &str) -> Self {
650 let mut track = LipSyncTrack::new();
651
652 let pairs: Vec<(f32, Phoneme)> = s
654 .split_whitespace()
655 .filter_map(|token| {
656 let (time_str, phon_str) = token.split_once(':')?;
657 let start: f32 = time_str.parse().ok()?;
658 let phoneme = Phoneme::from_arpabet(phon_str)?;
659 Some((start, phoneme))
660 })
661 .collect();
662
663 for (i, (start, phoneme)) in pairs.iter().enumerate() {
664 let end = pairs.get(i + 1).map(|(t, _)| *t).unwrap_or(start + 0.1);
665
666 track.add_event(PhonemeEvent {
667 start: *start,
668 end,
669 phoneme: phoneme.clone(),
670 intensity: 1.0,
671 });
672 }
673
674 track
675 }
676}
677
678impl Default for LipSyncTrack {
679 fn default() -> Self {
680 Self::new()
681 }
682}
683
684#[cfg(test)]
689mod tests {
690 use super::*;
691
692 #[test]
693 fn test_phoneme_all() {
694 let all = Phoneme::all();
695 assert_eq!(all.len(), 40);
697 assert!(all.contains(&Phoneme::Silence));
698 assert!(all.contains(&Phoneme::AA));
699 assert!(all.contains(&Phoneme::ZH));
700 }
701
702 #[test]
703 fn test_phoneme_is_vowel() {
704 assert!(Phoneme::AA.is_vowel());
705 assert!(Phoneme::IY.is_vowel());
706 assert!(Phoneme::UW.is_vowel());
707 assert!(Phoneme::ER.is_vowel());
708 assert!(!Phoneme::B.is_vowel());
709 assert!(!Phoneme::M.is_vowel());
710 assert!(!Phoneme::Silence.is_vowel());
711 }
712
713 #[test]
714 fn test_phoneme_is_consonant() {
715 assert!(Phoneme::B.is_consonant());
716 assert!(Phoneme::ZH.is_consonant());
717 assert!(Phoneme::NG.is_consonant());
718 assert!(!Phoneme::AA.is_consonant());
719 assert!(!Phoneme::Silence.is_consonant());
720 }
721
722 #[test]
723 fn test_phoneme_from_arpabet() {
724 assert_eq!(Phoneme::from_arpabet("AA"), Some(Phoneme::AA));
725 assert_eq!(Phoneme::from_arpabet("aa"), Some(Phoneme::AA));
726 assert_eq!(Phoneme::from_arpabet("sil"), Some(Phoneme::Silence));
727 assert_eq!(Phoneme::from_arpabet("SIL"), Some(Phoneme::Silence));
728 assert_eq!(Phoneme::from_arpabet("SP"), Some(Phoneme::Silence));
729 assert_eq!(Phoneme::from_arpabet("ZH"), Some(Phoneme::ZH));
730 assert_eq!(Phoneme::from_arpabet("NG"), Some(Phoneme::NG));
731 assert_eq!(Phoneme::from_arpabet("NOPE"), None);
732 }
733
734 #[test]
735 fn test_viseme_all() {
736 let all = Viseme::all();
737 assert_eq!(all.len(), 15);
738 assert!(all.contains(&Viseme::Silence));
739 assert!(all.contains(&Viseme::PP));
740 assert!(all.contains(&Viseme::RR));
741 assert!(all.contains(&Viseme::Neutral));
742 }
743
744 #[test]
745 fn test_phoneme_to_viseme_bilabial() {
746 assert_eq!(phoneme_to_viseme(&Phoneme::B), Viseme::PP);
747 assert_eq!(phoneme_to_viseme(&Phoneme::M), Viseme::PP);
748 assert_eq!(phoneme_to_viseme(&Phoneme::P), Viseme::PP);
749 }
750
751 #[test]
752 fn test_phoneme_to_viseme_vowel() {
753 assert_eq!(phoneme_to_viseme(&Phoneme::AA), Viseme::Aa);
754 assert_eq!(phoneme_to_viseme(&Phoneme::AH), Viseme::Aa);
755 assert_eq!(phoneme_to_viseme(&Phoneme::IH), Viseme::I);
756 assert_eq!(phoneme_to_viseme(&Phoneme::IY), Viseme::I);
757 assert_eq!(phoneme_to_viseme(&Phoneme::OW), Viseme::O);
758 assert_eq!(phoneme_to_viseme(&Phoneme::UW), Viseme::U);
759 assert_eq!(phoneme_to_viseme(&Phoneme::AW), Viseme::U);
760 assert_eq!(phoneme_to_viseme(&Phoneme::R), Viseme::RR);
761 assert_eq!(phoneme_to_viseme(&Phoneme::ER), Viseme::RR);
762 }
763
764 #[test]
765 fn test_phoneme_to_viseme_silence() {
766 assert_eq!(phoneme_to_viseme(&Phoneme::Silence), Viseme::Silence);
767 }
768
769 #[test]
770 fn test_viseme_mapper_default() {
771 let mapper = default_viseme_mapper();
772 let pp = mapper.get_weights(&Viseme::PP);
774 assert!(pp.contains_key("lips_closed"));
775 assert!(pp.contains_key("lips_press"));
776 assert!((pp["lips_closed"] - 0.9).abs() < 1e-5);
777
778 let sil = mapper.get_weights(&Viseme::Silence);
780 assert_eq!(sil["lips_closed"], 1.0);
781
782 let aa = mapper.get_weights(&Viseme::Aa);
784 assert!(aa.contains_key("jaw_drop"));
785 assert!((aa["jaw_drop"] - 0.7).abs() < 1e-5);
786 }
787
788 #[test]
789 fn test_viseme_mapper_evaluate_phoneme() {
790 let mapper = default_viseme_mapper();
791 let weights = mapper.evaluate_phoneme(&Phoneme::B);
792 assert!(weights.contains_key("lips_closed"));
794
795 let weights_i = mapper.evaluate_phoneme(&Phoneme::IY);
796 assert!(weights_i.contains_key("lips_wide"));
797
798 let weights_u = mapper.evaluate_phoneme(&Phoneme::UW);
799 assert!(weights_u.contains_key("lips_round"));
800 assert!(weights_u.contains_key("lips_pucker"));
801 }
802
803 #[test]
804 fn test_lip_sync_track_new() {
805 let track = LipSyncTrack::new();
806 assert_eq!(track.event_count(), 0);
807 assert_eq!(track.duration, 0.0);
808
809 let mut track2 = LipSyncTrack::default();
810 track2.add_event(PhonemeEvent {
811 start: 0.0,
812 end: 0.2,
813 phoneme: Phoneme::AA,
814 intensity: 1.0,
815 });
816 assert_eq!(track2.event_count(), 1);
817 assert!((track2.duration - 0.2).abs() < 1e-6);
818 }
819
820 #[test]
821 fn test_lip_sync_track_evaluate() {
822 let mapper = default_viseme_mapper();
823 let mut track = LipSyncTrack::new();
824
825 track.add_event(PhonemeEvent {
826 start: 0.0,
827 end: 0.3,
828 phoneme: Phoneme::AA,
829 intensity: 1.0,
830 });
831 track.add_event(PhonemeEvent {
832 start: 0.3,
833 end: 0.6,
834 phoneme: Phoneme::B,
835 intensity: 0.8,
836 });
837
838 let w = track.evaluate(0.1, &mapper);
840 assert!(w.contains_key("jaw_drop") || w.contains_key("lips_open"));
841
842 let w_before = track.evaluate(-0.1, &mapper);
844 assert!(w_before.contains_key("lips_closed") || w_before.is_empty());
845
846 let w_after = track.evaluate(0.7, &mapper);
848 assert!(w_after.contains_key("lips_closed") || w_after.is_empty());
849 }
850
851 #[test]
852 fn test_lip_sync_from_string() {
853 let track = LipSyncTrack::from_string("0.0:AA 0.2:B 0.4:IY");
854 assert_eq!(track.event_count(), 3);
855
856 assert_eq!(track.events[0].phoneme, Phoneme::AA);
858 assert!((track.events[0].start - 0.0).abs() < 1e-6);
859 assert!((track.events[0].end - 0.2).abs() < 1e-6);
860
861 assert_eq!(track.events[1].phoneme, Phoneme::B);
863 assert!((track.events[1].end - 0.4).abs() < 1e-6);
864
865 assert_eq!(track.events[2].phoneme, Phoneme::IY);
867 assert!((track.events[2].end - 0.5).abs() < 1e-6);
868
869 assert!((track.duration - 0.5).abs() < 1e-6);
871 }
872
873 #[test]
874 fn test_phoneme_name() {
875 assert_eq!(Phoneme::Silence.name(), "Silence");
876 assert_eq!(Phoneme::AA.name(), "AA");
877 assert_eq!(Phoneme::ZH.name(), "ZH");
878 assert_eq!(Phoneme::NG.name(), "NG");
879 assert_eq!(Phoneme::IY.name(), "IY");
880 assert_eq!(Phoneme::B.name(), "B");
881
882 assert_eq!(Viseme::Silence.name(), "Silence");
884 assert_eq!(Viseme::PP.name(), "PP");
885 assert_eq!(Viseme::Aa.name(), "Aa");
886 assert_eq!(Viseme::RR.name(), "RR");
887 }
888}