pub mod aligner;
pub mod classifier;
pub mod cmudict;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Phoneme {
pub base: String,
pub stress: Option<u8>,
}
impl Phoneme {
pub fn is_vowel(&self) -> bool {
self.stress.is_some()
}
}
pub fn parse_phoneme(tok: &str) -> Phoneme {
if let Some(d @ b'0'..=b'2') = tok.as_bytes().last().copied() {
return Phoneme {
base: tok[..tok.len() - 1].to_string(),
stress: Some(d - b'0'),
};
}
let base = tok.to_string();
Phoneme { base, stress: None }
}
pub trait PronunciationProvider: Send + Sync {
fn pronunciations(&self, word: &str) -> Vec<Vec<Phoneme>>;
}
#[cfg(test)]
pub struct NoPronunciationProvider;
#[cfg(test)]
impl PronunciationProvider for NoPronunciationProvider {
fn pronunciations(&self, _word: &str) -> Vec<Vec<Phoneme>> {
Vec::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[rstest::rstest]
#[case::vowel_primary("AH1", "AH", Some(1))]
#[case::vowel_unstressed("IH0", "IH", Some(0))]
#[case::vowel_secondary("EH2", "EH", Some(2))]
#[case::digit_not_stress("AH3", "AH3", None)]
#[case::consonant_b("B", "B", None)]
#[case::consonant_ng("NG", "NG", None)]
fn parses_phoneme_base_and_stress(
#[case] tok: &str,
#[case] base: &str,
#[case] stress: Option<u8>,
) {
let ph = parse_phoneme(tok);
assert_eq!(ph.base, base);
assert_eq!(ph.stress, stress);
assert_eq!(ph.is_vowel(), stress.is_some());
}
#[test]
fn no_provider_yields_no_pronunciations() {
assert!(NoPronunciationProvider.pronunciations("become").is_empty());
}
#[test]
fn parses_runtime_consonant_token_without_stress() {
let token = std::hint::black_box("NG");
let ph = parse_phoneme(token);
assert_eq!(ph.base, "NG");
assert_eq!(ph.stress, None);
assert!(!ph.is_vowel());
}
}