diff --git a/TODO.md b/TODO.md index 1091769b19..d0ef07cb3f 100644 --- a/TODO.md +++ b/TODO.md @@ -16,6 +16,13 @@ Audio Samples - Real versions of ['AO0', 'AW0', 'AW2', 'AY0', 'EY0', 'OY0', 'UH0'], which were backfilled with fakes. +Scaling + 1. Load test + 2. Read wave files into memory + 3. Multi-server, with nginx as load balancer + 4. Prevent others from calling it + 5. Caching strategy + Tokenizing - Plurals - Times (9 p.m. ET) @@ -42,13 +49,6 @@ Cleanup - Remove old query parameters - Set default volume -Scaling - 1. Load test - 2. Read wave files into memory - 3. Multi-server, with nginx as load balancer - 4. Prevent others from calling it - 5. Caching strategy - Server 1. Favicon, .ico and apple icon 2. robots.txt diff --git a/config.toml b/config.toml index fcf88da1b4..e7470d4bc3 100644 --- a/config.toml +++ b/config.toml @@ -12,6 +12,7 @@ # Logging verbosity log_handler = false log_parsing = true + log_syllable_decomposition = false log_synthesis = false log_timing = true @@ -22,5 +23,7 @@ # Logging verbosity log_handler = false log_parsing = false + log_syllable_decomposition = true log_synthesis = true + log_timing = false diff --git a/src/config.rs b/src/config.rs index 2102c7c7d5..35511ef88e 100644 --- a/src/config.rs +++ b/src/config.rs @@ -28,6 +28,7 @@ pub struct Config { /// What events to log. pub log_handler: Option, pub log_parsing: Option, + pub log_syllable_decomposition: Option, pub log_synthesis: Option, pub log_timing: Option, } @@ -115,6 +116,9 @@ impl Config { log_parsing: self.log_parsing .clone() .or(other.log_parsing.clone()), + log_syllable_decomposition: self.log_syllable_decomposition + .clone() + .or(other.log_syllable_decomposition.clone()), log_synthesis: self.log_synthesis .clone() .or(other.log_synthesis.clone()), diff --git a/src/lang/syllables.rs b/src/lang/syllables.rs index d381915062..0fe5cede8c 100644 --- a/src/lang/syllables.rs +++ b/src/lang/syllables.rs @@ -18,13 +18,23 @@ Arpabet has 54 different units: - 3 Semivowels (Y, W, Q) */ -pub fn arpabet_to_syllables(input: &Vec) -> Option>> { +pub type Polysyllable = Vec>; + +/// Traits on syllables, in case I decide to change the underlying +/// representation. +pub trait Polysyllabic { + /// Number of monophones in a polysyllable (each syllable has n-many + /// monophones). + fn monophone_count(&self) -> usize; +} + +/// Break an arpabet string into syllables comprised of arpabet monophones. +pub fn arpabet_to_syllables(input: &Vec) -> Option { let mut phones = Vec::new(); for atom in input { match Phoneme::from_str(atom) { None => { - println!("NONE: {}", atom); // TODO REMOVE return None; }, Some(phone) => { phones.push(phone); }, @@ -39,9 +49,6 @@ pub fn arpabet_to_syllables(input: &Vec) -> Option>> { let mut consonants_after_vowel = 0; // Count consonants after vowel for phone in phones { - println!("syllable_has_vowel = {}", syllable_has_vowel); - println!("Phone: {:?}, {}", phone, phone.is_vowel()); - if !building_syllable { // Starting the very first syllable of the word. building_syllable = true; @@ -59,150 +66,39 @@ pub fn arpabet_to_syllables(input: &Vec) -> Option>> { // Find out where to allocate any run of consonants from the // previous (current) syllable. - let mut leading_consonant = None; - if consonants_after_vowel == 2 { - // There are two consonants between vowels. - // Split consonants - one per vowel. - leading_consonant = syllable.pop(); + if consonants_after_vowel > 0 { + let movement = consonant_allocation(&syllable, + consonants_after_vowel); - // But let's check some heuristics... - // Advance means put the consonants on the NEXT syllable - let mut advance = false; - // Reattach means put the consonants on the PREVIOUS syllable - let mut reattach = false; - { - // >>> 2-CONSONANT SEPARATION HEURISTICS <<< - let first_consonant = syllable.last(); - if first_consonant.is_some() && leading_consonant.is_some() { - // Sequence is: - // Vowel, Consonant, Consonant, (Vowel - the current "phone") - let first_consonant = first_consonant.unwrap(); - let second_consonant = leading_consonant.as_ref().unwrap(); - - advance = match *first_consonant { - Phoneme::K { .. } => { - match *second_consonant { - Phoneme::Y => true, // K Y .. - _ => false, - } - }, - Phoneme::T { .. } => { - match *second_consonant { - Phoneme::R => true, // T R .. - _ => false, - } - }, - _ => false, - }; - - if !advance { - reattach = match *first_consonant { - Phoneme::S { .. } => { - match *second_consonant { - Phoneme::T => true, // .. S T - _ => false, - } - }, - _ => false, - }; - } - } - } - - if advance && leading_consonant.is_some() { - // Let's actually move both consonants forward to the next syllable. - let first_consonant = syllable.pop(); - if first_consonant.is_some() { + match movement { + ConsonantAlloc::MoveNone => { syllables.push(syllable); syllable = Vec::new(); - syllable.push(first_consonant.unwrap()); // 1st - syllable.push(leading_consonant.unwrap()); // 2nd - } - - syllable.push(phone); - syllable_has_vowel = true; - consonants_after_vowel = 0; - continue; + }, + ConsonantAlloc::MoveOne => { + let one = syllable.pop(); + syllables.push(syllable); + syllable = Vec::new(); + syllable.push(one.unwrap()); + }, + ConsonantAlloc::MoveTwo => { + let one = syllable.pop(); + let two = syllable.pop(); + syllables.push(syllable); + syllable = Vec::new(); + syllable.push(two.unwrap()); + syllable.push(one.unwrap()); + }, } - if reattach && leading_consonant.is_some() { - // Keep both consonants on the last syllable. - syllable.push(leading_consonant.unwrap()); - syllables.push(syllable); - syllable = Vec::new(); - syllable.push(phone); - syllable_has_vowel = true; - consonants_after_vowel = 0; - continue; - } - - } else if consonants_after_vowel == 1 { - // There's just one consonant between vowels. - // Usually we want to assign the consonant to the next vowel. - leading_consonant = syllable.pop(); - - // But let's check some heuristics... - let mut reattach = false; - { - // >>> 1-CONSONANT SEPARATION HEURISTICS <<< - let previous_vowel = syllable.last(); - if previous_vowel.is_some() && leading_consonant.is_some() { - // Sequence is: Vowel, Consonant, (Vowel - the current "phone") - let previous_vowel = previous_vowel.unwrap(); - let previous_consonant = leading_consonant.as_ref().unwrap(); - - reattach = match *previous_vowel { - Phoneme::Aa { .. } => { - match *previous_consonant { - Phoneme::L => true, // AA L .. ("All") - _ => false, - } - } - Phoneme::Ae { .. } => { - match *previous_consonant { - Phoneme::S => true, // AE S .. ("Ass") - _ => false, - } - }, - Phoneme::Ah { stress : Stress::Secondary } => { - match *previous_consonant { - Phoneme::L => true, // AH2 L .. ("A(u)l", eg (n)ull-ify) - _ => false, - } - }, - Phoneme::Ao { .. } => { - match *previous_consonant { - Phoneme::R => true, // AO R .. ("Orr") - _ => false, - } - }, - Phoneme::Ih { .. } => { - match *previous_consonant { - //Phoneme::K => true, // IH K .. ("Ick") - Phoneme::N => true, // IH N .. ("Inn") - Phoneme::S => true, // IH S .. ("Iss") - _ => false, - } - }, - _ => false, - }; - } - } - - if reattach && leading_consonant.is_some() { - // Nope, let's put the consonant back with the previous vowel. - syllable.push(leading_consonant.unwrap()); - leading_consonant = None; - } + syllable.push(phone); + syllable_has_vowel = true; + consonants_after_vowel = 0; + continue; } syllables.push(syllable); - syllable = Vec::new(); - if leading_consonant.is_some() { - syllable.push(leading_consonant.unwrap()); - } - syllable.push(phone); syllable_has_vowel = true; consonants_after_vowel = 0; @@ -226,10 +122,8 @@ pub fn arpabet_to_syllables(input: &Vec) -> Option>> { syllables.push(syllable); } - println!("Syllables: {} - {:?}\n\n", syllables.len(), syllables); - // Convert back to string. - // FIXME: So inefficient! + // FIXME: So inefficient! Linked lists would probably be best here. let mut output_syllables = Vec::new(); for syllable in syllables { let mut output_syllable = Vec::new(); @@ -239,17 +133,192 @@ pub fn arpabet_to_syllables(input: &Vec) -> Option>> { output_syllables.push(output_syllable); } + info!(target: "syllable", "Syllables: {:?}", output_syllables); + Some(output_syllables) } +// How many consonants to keep for a leading vowel. +enum ConsonantAlloc { + MoveNone, + MoveOne, + MoveTwo, +} + +// Heuristics for how to handle the consonants between vowels. +// Determine which vowel they get allocated to. +#[inline] +fn consonant_allocation(syllable: &Vec, + consonants_after_vowel: usize) + -> ConsonantAlloc { + + match consonants_after_vowel { + 4 => { + // There are four consonants between vowels. + // Of the four consonants, how many does the last vowel retain? + /*let mut movement = ConsonantAlloc::KeepTwo; + + let first_consonant = syllable.get(syllable.len() - 4); + let second_consonant = syllable.get(syllable.len() - 3); + let third_consonant = syllable.get(syllable.len() - 2); + let fourth_consonant = syllable.get(syllable.len() - 1); + + if first_consonant.is_some() + && second_consonant.is_some() + && third_consonant.is_some() + && fourth_consonant.is_some() { + let first_consonant = first_consonant.unwrap(); + let second_consonant = second_consonant.unwrap(); + let third_consonant = third_consonant.unwrap(); + let fourth_consonant = fourth_consonant.unwrap(); + + movement = match *first_consonant { + _ => ConsonantAlloc::KeepTwo, + }; + } + + movement*/ + ConsonantAlloc::MoveTwo + }, + 3 => { + let first_consonant = syllable.get(syllable.len() - 3); + let second_consonant = syllable.get(syllable.len() - 2); + let third_consonant = syllable.get(syllable.len() - 1); + + if first_consonant.is_some() + && second_consonant.is_some() + && third_consonant.is_some() { + let first_consonant = first_consonant.unwrap(); + let second_consonant = second_consonant.unwrap(); + let third_consonant = third_consonant.unwrap(); + + return match *first_consonant { + Phoneme::M => { + match *second_consonant { + Phoneme::P => { + match *third_consonant { + Phoneme::L => ConsonantAlloc::MoveTwo, // M | P L ... + Phoneme::R => ConsonantAlloc::MoveTwo, // M | P R ... + Phoneme::T => ConsonantAlloc::MoveOne, // M P | T ... + _ => ConsonantAlloc::MoveNone, + } + }, + _ => ConsonantAlloc::MoveNone, + } + }, + Phoneme::Z => ConsonantAlloc::MoveTwo, // Z _ _ ... + _ => ConsonantAlloc::MoveNone, + }; + } + + ConsonantAlloc::MoveNone + }, + 2 => { + // Usually we allocate one consonant per vowel, which seems to work most + // of the time. + let first_consonant = syllable.get(syllable.len() - 2); + let second_consonant = syllable.get(syllable.len() - 1); + + if first_consonant.is_some() + && second_consonant.is_some() { + let first_consonant = first_consonant.unwrap(); + let second_consonant = second_consonant.unwrap(); + + return match *first_consonant { + Phoneme::K => { + match *second_consonant { + Phoneme::Y => ConsonantAlloc::MoveTwo, // K Y .. + _ => ConsonantAlloc::MoveOne, + } + }, + Phoneme::S => { + match *second_consonant { + Phoneme::T => ConsonantAlloc::MoveNone, // .. S T + _ => ConsonantAlloc::MoveOne, + } + } + Phoneme::T => { + match *second_consonant { + Phoneme::R => ConsonantAlloc::MoveTwo, // T R .. + _ => ConsonantAlloc::MoveOne, + } + }, + _ => ConsonantAlloc::MoveOne, + }; + } + + ConsonantAlloc::MoveOne + }, + 1 => { + // Usually we want to assign the single consonant to the next vowel. + let previous_vowel = syllable.get(syllable.len() - 2); + let previous_consonant = syllable.get(syllable.len() - 1); + + if previous_vowel.is_some() && previous_consonant.is_some() { + let previous_vowel = previous_vowel.unwrap(); + let previous_consonant = previous_consonant.unwrap(); + + return match *previous_vowel { + Phoneme::Aa { .. } => { + match *previous_consonant { + Phoneme::L => ConsonantAlloc::MoveNone, // AA L .. ("All") + _ => ConsonantAlloc::MoveOne, + } + } + Phoneme::Ae { .. } => { + match *previous_consonant { + Phoneme::S => ConsonantAlloc::MoveNone, // AE S .. ("Ass") + _ => ConsonantAlloc::MoveOne, + } + }, + Phoneme::Ah { stress: Stress::Secondary } => { + match *previous_consonant { + // AH2 L .. ("A(u)l", eg (n)ull-ify) + Phoneme::L => ConsonantAlloc::MoveNone, + _ => ConsonantAlloc::MoveOne, + } + }, + Phoneme::Ao { .. } => { + match *previous_consonant { + Phoneme::R => ConsonantAlloc::MoveNone, // AO R .. ("Orr") + _ => ConsonantAlloc::MoveOne, + } + }, + Phoneme::Ih { .. } => { + match *previous_consonant { + //Phoneme::K => ConsonantAlloc::MoveNone, // IH K .. ("Ick") + Phoneme::N => ConsonantAlloc::MoveNone, // IH N .. ("Inn") + Phoneme::S => ConsonantAlloc::MoveNone, // IH S .. ("Iss") + _ => ConsonantAlloc::MoveOne, + } + }, + _ => ConsonantAlloc::MoveOne, + }; + } + + ConsonantAlloc::MoveOne + }, + _ => ConsonantAlloc::MoveNone, + } +} + +impl Polysyllabic for Polysyllable { + fn monophone_count(&self) -> usize { + let mut count = 0; + for syllable in self { + count += syllable.len(); + } + count + } +} #[cfg(test)] mod tests { - use super::arpabet_to_syllables; + use super::*; #[test] fn test_easy_cases() { - // Test a bunch of random words from ARPABET. + // Test a bunch of random easy words from ARPABET. // Reduction assert_eq!(syl("R IH0|D AH1 K|SH AH0 N"), conv("R IH0 D AH1 K SH AH0 N")); @@ -334,31 +403,47 @@ mod tests { /* assert_eq!(syl(""), conv("")); - assert_eq!(syl(""), conv("")); - assert_eq!(syl(""), conv("")); - assert_eq!(syl(""), conv("")); */ } - // TODO ENABLE - //#[test] - fn test_lots_of_consonants() { - // Wisecracks - assert_eq!(syl("W AY1 Z|K R AE2 K S"), conv("W AY1 Z K R AE2 K S")); + #[test] + fn test_three_consonants() { + // Test words with three consonants between vowels + + // Employable (vs. empty) + assert_eq!(syl("EH0 M|P L OY1|AH0|B AH0 L"), + conv("EH0 M P L OY1 AH0 B AH0 L")); + + // Empty (vs. employable) + assert_eq!(syl("EH1 M P|T IY0"), conv("EH1 M P T IY0")); // Temperamental assert_eq!(syl("T EH2 M|P R AH0|M EH1 N|T AH0 L"), conv("T EH2 M P R AH0 M EH1 N T AH0 L")); + // Wisecracks + assert_eq!(syl("W AY1 Z|K R AE2 K S"), conv("W AY1 Z K R AE2 K S")); + + /* + assert_eq!(syl(""), conv("")); + */ + } + + #[test] + fn test_four_consonants() { + // TODO ENABLE - These don't work yet. + + // Explain + assert_eq!(syl("IH0 K S|P L EY1 N"), conv("IH0 K S P L EY1 N")); + // Landscape assert_eq!(syl("L AE1 N D|S K EY2 P"), conv("L AE1 N D S K EY2 P")); } - // TODO ENABLE //#[test] fn test_difficult() { - // Explain - assert_eq!(syl("IH0 K|S P L EY1 N"), conv("IH0 K S P L EY1 N")); + // TODO ENABLE - These don't work yet. + // Talking assert_eq!(syl("T AO1 K|IH0 NG"), conv("T AO1 K IH0 NG")); @@ -367,6 +452,31 @@ mod tests { conv("AA2 R K AH0 P EH1 L AH0 G OW2")); } + #[test] + fn polysyllablic_monophone_count() { + fn syllable(input: Vec>) -> Polysyllable { + let mut out = Vec::new(); + for syllable in input { + let new_syllable = syllable.iter().map(|s| s.to_string()).collect(); + out.push(new_syllable); + } + out + } + + let polyphone = syllable(vec![vec![]]); + assert_eq!(0, polyphone.monophone_count()); + + let polyphone = syllable(vec![vec!["A"]]); + assert_eq!(1, polyphone.monophone_count()); + + let polyphone = syllable(vec![vec!["A", "B", "C"], vec!["D"]]); + assert_eq!(4, polyphone.monophone_count()); + + let polyphone = syllable(vec![vec!["A", "B", "C"], vec!["D", "E"], + vec!["F", "G"]]); + assert_eq!(7, polyphone.monophone_count()); + } + // Shorter for tests fn conv(input: &str) -> Option>> { arpabet_to_syllables(&pho(input)) diff --git a/src/logger.rs b/src/logger.rs index a5bd91040d..28fcff315d 100644 --- a/src/logger.rs +++ b/src/logger.rs @@ -35,6 +35,7 @@ impl log::Log for SimpleLogger { match metadata.target() { "handler" => self.config.log_handler.unwrap_or(false), "parsing" => self.config.log_parsing.unwrap_or(false), + "syllable" => self.config.log_syllable_decomposition.unwrap_or(false), "synthesis" => self.config.log_synthesis.unwrap_or(false), "timing" => self.config.log_timing.unwrap_or(false), _ => metadata.level() <= LogLevel::Info, diff --git a/src/synthesis/word_synthesizer.rs b/src/synthesis/word_synthesizer.rs index 953693cc15..6fa9dc6b47 100644 --- a/src/synthesis/word_synthesizer.rs +++ b/src/synthesis/word_synthesizer.rs @@ -1,6 +1,8 @@ // Copyright (c) 2016 Brandon Thomas use lang::arpabet::Arpabet; +use lang::syllables::Polysyllabic; +use lang::syllables::Polysyllable; use lang::syllables::arpabet_to_syllables; use speaker::Speaker; use synthesis::audio::SampleBytes; @@ -73,10 +75,19 @@ impl WordSynthesizer { buffer.extend(pause); } - for syllable_phones in syllables_of_phones { - let samples = try!(self.get_syllabic_n_phone_samples(speaker, - syllable_phones, false, false)); + let samples = try!(self.get_polysyllable_sample(speaker, + syllables_of_phones, false)); + if debug_padding_between_phones.is_some() { + let padding = generate_pause(debug_padding_between_phones.unwrap()); + let last = samples.len() - 1; + for (i, sample) in samples.into_iter().enumerate() { + buffer.extend(sample); + if i < last { + buffer.extend(padding.clone()); + } + } + } else { for sample in samples { buffer.extend(sample); } @@ -164,6 +175,283 @@ impl WordSynthesizer { Ok(()) } + /// TODO DOC + /// TODO DOC + fn get_polysyllable_sample(&self, + speaker: &Speaker, + syllables: Polysyllable, + use_ends: bool) + -> Result, SynthesisError> { + + let mut capacity = syllables.monophone_count(); + + let mut monophones_fulfilled: Vec = Vec::with_capacity(capacity); + let mut chunks: Vec> = Vec::with_capacity(capacity); + + // Use this to debug the synthesis. + let mut debug: Vec> = Vec::with_capacity(capacity); + + for _ in 0..capacity { + monophones_fulfilled.push(false); + chunks.push(None); + debug.push(None); + } + + let mut syllables_fulfilled: Vec = Vec::with_capacity(syllables.len()); + + for _ in 0..syllables.len() { + syllables_fulfilled.push(false); + } + + // Part 1: Fulfill entire syllables at once (mono- or poly-syllables). + + // TODO/FIXME: This is a horrendous implementation that *must* change. + // NB: Outer two loops look O(n^2), but it cannot be. Inner iteration + // eagerly breaks when there is no match. + // FIXME: Inner code, however, is terribly inefficient. + + let mut start = 0; // Index into output vectors. + + for i in 0..syllables.len() { + for j in i..syllables.len() { + + let mut polyphone: Vec = Vec::new(); + + for k in i..j { + let ref syllable = syllables[k]; + for phone in syllable { + polyphone.push(phone.clone()); + } + } + + // println!("n-phone: {:?}", candidate_n_phone); + let phone = self.audiobank.get_n_phone( + speaker.as_str(), + &polyphone, + SamplePreference::Middle, // TODO + false // TODO + ); + + if phone.is_none() { + break; + } + + let phone = phone.unwrap(); + let end = start + polyphone.len(); + + chunks[start] = Some(phone); + debug[start] = Some(polyphone.join("_")); + + for k in start..end { + monophones_fulfilled[k] = true; + } + } + + start += syllables[i].len(); // Monophone count of single syllable + } + + // Part 2: Fix up any holes left unfulfilled. + + let mut monophones = Vec::new(); + + for syllable in syllables { + for phone in syllable { + monophones.push(phone.clone()); + } + } + + // 5-phone + if monophones.len() >= 5 { + let range = monophones.len() - 4; + for i in 0..range { + if monophones_fulfilled[i] + || monophones_fulfilled[i+1] + || monophones_fulfilled[i+2] + || monophones_fulfilled[i+3] + || monophones_fulfilled[i+4] { + continue; + } + + let candidate_n_phone = &monophones[i..i+5]; + + let sample_preference = match i { + 0 => { SamplePreference::Begin }, + _ if i == range - 1 => { SamplePreference::End }, + _ => { SamplePreference::Middle }, + }; + + //println!("n-phone: {:?}", candidate_n_phone); + let phone = self.audiobank.get_n_phone(speaker.as_str(), + candidate_n_phone, + sample_preference, + use_ends); + + if phone.is_some() { + chunks[i] = phone; + debug[i] = Some(monophones[i..i+5].join("_")); + + for j in i..i+5 { + monophones_fulfilled[j] = true; + } + } + } + } + + info!(target: "synthesis", "5-fulfilled: {:?}", monophones_fulfilled); + + // 4-phone + if monophones.len() >= 4 { + let range = monophones.len() - 3; + for i in 0..range { + if monophones_fulfilled[i] + || monophones_fulfilled[i+1] + || monophones_fulfilled[i+2] + || monophones_fulfilled[i+3] { + continue; + } + + let candidate_n_phone = &monophones[i..i+4]; + + let sample_preference = match i { + 0 => { SamplePreference::Begin }, + _ if i == range - 1 => { SamplePreference::End }, + _ => { SamplePreference::Middle }, + }; + + //println!("n-phone: {:?}", candidate_n_phone); + let phone = self.audiobank.get_n_phone(speaker.as_str(), + candidate_n_phone, + sample_preference, + use_ends); + + if phone.is_some() { + chunks[i] = phone; + debug[i] = Some(monophones[i..i+4].join("_")); + + for j in i..i+4 { + monophones_fulfilled[j] = true; + } + } + } + } + + info!(target: "synthesis", "4-fulfilled: {:?}", monophones_fulfilled); + + // 3-phone + if monophones.len() >= 3 { + let range = monophones.len() - 2; + for i in 0..range { + if monophones_fulfilled[i] + || monophones_fulfilled[i+1] + || monophones_fulfilled[i+2] { + continue; + } + + let candidate_n_phone = &monophones[i..i+3]; + + let sample_preference = match i { + 0 => { SamplePreference::Begin }, + _ if i == range - 1 => { SamplePreference::End }, + _ => { SamplePreference::Middle }, + }; + + let phone = self.audiobank.get_n_phone(speaker.as_str(), + candidate_n_phone, + sample_preference, + use_ends); + + if phone.is_some() { + chunks[i] = phone; + debug[i] = Some(monophones[i..i+3].join("_")); + for j in i..i+3 { + monophones_fulfilled[j] = true; + } + } + } + } + + info!(target: "synthesis", "3-fulfilled: {:?}", monophones_fulfilled); + + // 2-phone + if monophones.len() >= 2 { + let range = monophones.len() - 1; + for i in 0..range { + if monophones_fulfilled[i] || monophones_fulfilled[i+1] { + continue; + } + + let candidate_n_phone = &monophones[i..i+2]; + + let sample_preference = match i { + 0 => { SamplePreference::Begin }, + _ if i == range - 1 => { SamplePreference::End }, + _ => { SamplePreference::Middle }, + }; + + let phone = self.audiobank.get_n_phone(speaker.as_str(), + candidate_n_phone, + sample_preference, + use_ends); + + if phone.is_some() { + chunks[i] = phone; + debug[i] = Some(monophones[i..i+2].join("_")); + for j in i..i+2 { + monophones_fulfilled[j] = true; + } + } + } + } + + info!(target: "synthesis", "2-fulfilled: {:?}", monophones_fulfilled); + + // 1-phone + for i in 0..monophones.len() { + if monophones_fulfilled[i] { + continue; + } + + let sample_preference = match i { + 0 => { SamplePreference::Begin }, + _ if i == monophones.len() - 1 => { SamplePreference::End }, + _ => { SamplePreference::Middle }, + }; + + let phone = self.audiobank.get_n_phone(speaker.as_str(), + &monophones[i..i+1], + sample_preference, + use_ends); + + if phone.is_some() { + chunks[i] = phone; + debug[i] = Some(monophones[i].to_string()); + monophones_fulfilled[i] = true; + } + } + + info!(target: "synthesis", "1-fulfilled: {:?}", monophones_fulfilled); + + for x in monophones_fulfilled { + if !x { return Err(SynthesisError::MonophoneDne); } + } + + let debug_str : Vec = debug.into_iter() + .map(|x| if x.is_some() { x.unwrap() } else { "None".to_string() }) + .collect(); + + info!(target: "synthesis", "Comprised of: {:?}", debug_str); + + /*info!(target: "synthesis", "Results Length: {} of {} phones", + results.len(), + syllable.len());*/ + + let results: Vec = chunks.into_iter() + .filter_map(|x| x) // Woo, already Option! + .collect(); + + Ok(results) // TODO + } + /// TODO DOC /// TODO DOC fn get_syllabic_n_phone_samples(&self, @@ -363,15 +651,15 @@ impl WordSynthesizer { .map(|x| if x.is_some() { x.unwrap() } else { "None".to_string() }) .collect(); - //info!(target: "synthesis", "Comprised of: {:?}", debug_str); + info!(target: "synthesis", "Comprised of: {:?}", debug_str); let results : Vec = chunks.into_iter() .filter_map(|x| x) // Woo, already Option! .collect(); - info!(target: "synthesis", "Results Length: {} of {} phones", + /*info!(target: "synthesis", "Results Length: {} of {} phones", results.len(), - syllable.len()); + syllable.len());*/ Ok(results) }