diff --git a/Cargo.lock b/Cargo.lock index 10a3ec49bc..252812aec5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6,6 +6,7 @@ dependencies = [ "hound 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "hyper 0.9.10 (registry+https://github.com/rust-lang/crates.io-index)", "iron 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "params 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "regex 0.1.73 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/Cargo.toml b/Cargo.toml index b74c31a420..a43cc93f4f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ hound = "1.1.0" # WAV codec. hyper = "0.9.*" iron = "0.4.*" + lazy_static = "0.2.*" log = "0.3.*" params = "0.3.*" regex = "0.1.*" diff --git a/src/handlers/audio_synth_handler.rs b/src/handlers/audio_synth_handler.rs index d80fc5f98f..63a383c84c 100644 --- a/src/handlers/audio_synth_handler.rs +++ b/src/handlers/audio_synth_handler.rs @@ -1,7 +1,6 @@ // Copyright (c) 2015 Brandon Thomas // TODO: This looks really bad now. Needs cleanup. -use std::collections::HashMap; use config::Config; use crypto::digest::Digest; use crypto::sha1::Sha1; @@ -11,8 +10,11 @@ use iron::headers::{ETag, EntityTag, Headers, IfNoneMatch}; use iron::mime::Mime; use iron::prelude::*; use iron::status; +use old_words::split_sentence; use router::Router; use rustc_serialize::json; +use speaker::Speaker; +use std::collections::HashMap; use std::error::Error; use std::fmt::{self, Debug}; use std::fs::File; @@ -30,7 +32,6 @@ use std::sync::RwLock; use super::error_filter::build_error; use synthesizer::Synthesizer; use urlencoded::UrlEncodedQuery; -use words::split_sentence; const SENTENCE_PARAM : &'static str = "s"; const SPEAKER_PARAM : &'static str = "v"; @@ -56,7 +57,7 @@ struct SpeakRequest { pub sentence: String, /** The voice to use to render the audio. */ - pub speaker: String, + pub speaker: Speaker, /** An optional volume multiplier. */ pub volume: Option, @@ -143,7 +144,7 @@ impl SpeakRequest { Some(list) => { match list.get(0) { None => { return speaker_error; }, - Some(s) => { s.to_string() }, + Some(s) => { Speaker::new(s.to_string()) }, } }, }; @@ -386,7 +387,7 @@ impl AudioSynthHandler { fn sha_digest(&self, request: &SpeakRequest) -> String { let mut hasher = Sha1::new(); - hasher.input_str(&request.speaker); + hasher.input_str(&request.speaker.to_string()); hasher.input_str(&request.sentence); if request.volume.is_some() { diff --git a/src/handlers/vocab_list_handler.rs b/src/handlers/vocab_list_handler.rs index 57c3b6e326..cef52475c8 100644 --- a/src/handlers/vocab_list_handler.rs +++ b/src/handlers/vocab_list_handler.rs @@ -1,10 +1,10 @@ // Copyright (c) 2015 Brandon Thomas -use dictionary::VocabularyLibrary; use iron::Handler; use iron::mime::Mime; use iron::prelude::*; use iron::status; +use old_dictionary::VocabularyLibrary; use rustc_serialize::json; use std::path::Path; use std::path::PathBuf; diff --git a/src/arpabet.rs b/src/lang/arpabet.rs similarity index 77% rename from src/arpabet.rs rename to src/lang/arpabet.rs index b2cec792be..61442b490c 100644 --- a/src/arpabet.rs +++ b/src/lang/arpabet.rs @@ -1,27 +1,29 @@ // Copyright (c) 2016 Brandon Thomas +use super::dictionary::Dictionary; use error::SynthError; -use std::collections::HashMap; use regex::Regex; +use std::collections::HashMap; +use std::collections::HashSet; use std::fs::File; -use std::io::BufReader; use std::io::BufRead; +use std::io::BufReader; use std::io::Read; pub type Word = String; pub type Phoneme = String; pub type Polyphone = Vec; -pub struct ArpabetDictionary { +pub struct Arpabet { /// A map of lowercase words to polyphone breakdown (phones are uppercase). /// eg. 'jungle' -> [JH, AH1, NG, G, AH0, L] dictionary: HashMap } -impl ArpabetDictionary { +impl Arpabet { /// Read CMU's Arpabet Dictionary. /// See http://www.speech.cs.cmu.edu/cgi-bin/cmudict - pub fn load_from_file(filename: &str) -> Result { + pub fn load_from_file(filename: &str) -> Result { let mut f = try!(File::open(filename)); let mut reader = BufReader::new(f); @@ -54,7 +56,7 @@ impl ArpabetDictionary { if map.is_empty() { Err(SynthError::EmptyFile) } else { - Ok(ArpabetDictionary { dictionary: map }) + Ok(Arpabet { dictionary: map }) } } @@ -66,15 +68,25 @@ impl ArpabetDictionary { } /// Combine two dictionaries into a new one. - pub fn combine(&self, other: &ArpabetDictionary) -> ArpabetDictionary { + pub fn combine(&self, other: &Arpabet) -> Arpabet { let mut combined = self.dictionary.clone(); for (k, v) in other.dictionary.iter() { combined.insert(k.clone(), v.clone()); } - ArpabetDictionary { + Arpabet { dictionary: combined } } + + /// Extract and return the internal word dictionary (a list of all + /// the words contained in the arpabet). + pub fn to_dictionary(&self) -> Dictionary { + let mut words = HashSet::new(); + for word in self.dictionary.keys() { + words.insert(word.to_string()); + } + Dictionary::new(words) + } } diff --git a/src/lang/dictionary.rs b/src/lang/dictionary.rs new file mode 100644 index 0000000000..1572449535 --- /dev/null +++ b/src/lang/dictionary.rs @@ -0,0 +1,126 @@ +// Copyright (c) 2016 Brandon Thomas + +use speaker::Speaker; +use std::collections::HashSet; +use std::collections::HashMap; + +pub type Word = String; + +pub struct Dictionary { + /// Words that exist in this dictionary. + words: HashSet, +} + +pub struct UniversalDictionary { + /// There is a single arpabet dictionary that works for all speakers. + arpabet_dictionary: Dictionary, + /// There are speaker-specific word dictionaries. + speaker_dictionaries: HashMap, +} + +impl Dictionary { + pub fn new(words: HashSet) -> Dictionary { + Dictionary { words: words } + } + + pub fn empty() -> Dictionary { + Dictionary { words: HashSet::new() } + } + + /// Returns true if the dictionary contains the word. + pub fn contains(&self, word: &str) -> bool { + self.words.contains(word) + } + + fn insert(mut self, word: &str) -> Self { + self.words.insert(word.to_string()); + self + } +} + +impl UniversalDictionary { + pub fn new() -> UniversalDictionary { + UniversalDictionary { + arpabet_dictionary: Dictionary::empty(), + speaker_dictionaries: HashMap::new(), + } + } + + pub fn set_arpabet_dictionary(&mut self, arpabet_dictionary: Dictionary) + -> &mut Self { + self.arpabet_dictionary = arpabet_dictionary; + self + } + + pub fn set_speaker_dictionary(&mut self, + speaker: Speaker, + dictionary: Dictionary) + -> &mut Self { + self.speaker_dictionaries.insert(speaker, dictionary); + self + } + + /// Returns true if anyone can say the word. + pub fn contains(&self, word: &str) -> bool { + self.arpabet_dictionary.contains(word) + } + + /// Returns true if the speaker can say the word. + pub fn contains_for(&self, speaker: &Speaker, word: &str) -> bool { + self.arpabet_dictionary.contains(word) || + self.speaker_dictionaries.get(speaker) + .map(|dict| { dict.contains(word) }) + .unwrap_or(false) + } +} + +#[cfg(test)] +mod tests { + use speaker::Speaker; + use super::*; + + #[test] + fn universal_dictionary_contains_for_speaker() { + let alice = Speaker::new("alice".to_string()); + let bob = Speaker::new("bob".to_string()); + + let arpa = Dictionary::empty() + .insert("foo") + .insert("bar"); + + let alice_dict = Dictionary::empty() + .insert("111") + .insert("222"); + + let bob_dict = Dictionary::empty() + .insert("aaa") + .insert("bbb"); + + let mut uni = UniversalDictionary::new(); + uni.set_arpabet_dictionary(arpa) + .set_speaker_dictionary(alice.clone(), alice_dict) + .set_speaker_dictionary(bob.clone(), bob_dict); + + + assert_eq!(true, uni.contains_for(&alice, "foo")); + assert_eq!(true, uni.contains_for(&alice, "bar")); + assert_eq!(false, uni.contains_for(&alice, "baz")); + assert_eq!(true, uni.contains_for(&alice, "111")); + assert_eq!(true, uni.contains_for(&alice, "222")); + assert_eq!(false, uni.contains_for(&alice, "aaa")); + assert_eq!(false, uni.contains_for(&alice, "bbb")); + + assert_eq!(true, uni.contains_for(&bob, "foo")); + assert_eq!(false, uni.contains_for(&bob, "111")); + assert_eq!(true, uni.contains_for(&bob, "aaa")); + assert_eq!(false, uni.contains_for(&bob, "absent")); + + let nobody = Speaker::new("nobody".to_string()); + + assert_eq!(true, uni.contains_for(&nobody, "foo")); + assert_eq!(false, uni.contains_for(&nobody, "111")); + assert_eq!(false, uni.contains_for(&nobody, "aaa")); + assert_eq!(false, uni.contains_for(&nobody, "absent")); + } +} + diff --git a/src/lang/mod.rs b/src/lang/mod.rs new file mode 100644 index 0000000000..001dd2ff76 --- /dev/null +++ b/src/lang/mod.rs @@ -0,0 +1,6 @@ +// Copyright (c) 2016 Brandon Thomas + +pub mod arpabet; +pub mod dictionary; +pub mod tokenizer; + diff --git a/src/lang/tokenizer.rs b/src/lang/tokenizer.rs new file mode 100644 index 0000000000..80537e5746 --- /dev/null +++ b/src/lang/tokenizer.rs @@ -0,0 +1,154 @@ +// Copyright (c) 2016 Brandon Thomas + +// TODO: This doesn't land the tokenization I want, but it'll be iterative. + +use regex::Regex; +use speaker::Speaker; +use std::sync::Arc; +//use super::dictionary::UniversalDictionary; + +lazy_static! { + // Note: Rust regexes do not support lookaround. + // Designed not to match times (5:00) or URLs (http://) + pub static ref RE_ALPHA_COLON: Regex = Regex::new(r"([A-Za-z]):([^/])").unwrap(); + pub static ref RE_BEGIN_SINGLE_QUOTE: Regex = Regex::new("^'").unwrap(); + pub static ref RE_PERIOD_END: Regex = Regex::new("\\.$").unwrap(); + pub static ref RE_PERIOD_SPACE: Regex = Regex::new("\\.\\s").unwrap(); + pub static ref RE_SINGLE_QUOTE_END: Regex = Regex::new("'$").unwrap(); + pub static ref RE_SINGLE_QUOTE_SPACE: Regex = Regex::new("'\\s").unwrap(); + pub static ref RE_SPACE_SINGLE_QUOTE: Regex = Regex::new("\\s'").unwrap(); + pub static ref RE_URL: Regex = Regex::new(r"https?://[\w\.-]+/?(\w+)?").unwrap(); +} + +//pub type TokenizedSentence = String; + +pub struct Tokenizer { + // TODO: Use dictionary. + ///// The dictionary used to check if a word exists. + //dictionary: Arc, +} + +impl Tokenizer { + /*// TODO + pub fn tokenize_sentence(&self, + speaker: &Speaker, + raw_sentence: &str) -> TokenizedSentence */ + + // TODO: Make only for testing. + /// Only for testing. + pub fn empty() -> Tokenizer { + //Tokenizer { dictionary: Arc::new(UniversalDictionary::new()) } + Tokenizer {} + } + + pub fn convert(&self, speaker: &Speaker, raw_sentence: &str) -> String { + let mut sentence = raw_sentence.to_string(); + + sentence = RE_ALPHA_COLON.replace_all(&sentence, "$1 "); + sentence = RE_BEGIN_SINGLE_QUOTE.replace_all(&sentence, ""); + sentence = RE_PERIOD_END.replace_all(&sentence, ""); + sentence = RE_PERIOD_SPACE.replace_all(&sentence, " "); + sentence = RE_SINGLE_QUOTE_END.replace_all(&sentence, ""); + sentence = RE_SINGLE_QUOTE_SPACE.replace_all(&sentence, " "); + sentence = RE_SPACE_SINGLE_QUOTE.replace_all(&sentence, " "); + + // Replace &, @, <, >, etc. + sentence = sentence.replace("&", " and ") + .replace("@", " at ") + .replace("<", " less than ") + .replace(">", " greater than "); + + // Remove URLs + sentence = RE_URL.replace_all(&sentence, " "); + + // Remove characters. + sentence = sentence.replace("\"", "") + .replace(",", "") + .replace("!", "") + .replace("?", "") + .replace("--", " ") + .replace(" - ", " ") + .replace("...", " ") + .replace("…", " "); + + // Remove extra spaces + // Lowercase + let split = split_sentence(&sentence); + sentence = split.join(" "); + + info!("Final sentence: {}", sentence); + + sentence + } +} + +/// Split a sentence into words. Remove extra padding, etc. +fn split_sentence(sentence: &str) -> Vec { + let mut words = Vec::new(); + let split = sentence.split(char::is_whitespace); + for s in split { + let trim = s.trim(); + if trim.len() == 0 { continue; } + words.push(trim.to_lowercase()); + } + words +} + +#[cfg(test)] +mod tests { + use speaker::Speaker; + use super::*; + use super::split_sentence; + + #[test] + fn test_prepare() { + let t = Tokenizer::empty(); + let s = Speaker::new("speaker".to_string()); + + // Normalize spaces + assert_eq!("foo bar baz", &t.convert(&s, " FOO BAR BAZ ")); + + // Convert symbolic words + assert_eq!("fox and hound", &t.convert(&s, "Fox & Hound")); + assert_eq!("thing at place", &t.convert(&s, "thing @ place")); + assert_eq!("me greater than you", &t.convert(&s, "me > you")); + assert_eq!("this less than that", &t.convert(&s, "this < that")); + + // Drop unimportant characters + assert_eq!("it's a quote", &t.convert(&s, "'It's a quote'")); + assert_eq!("quote quote", &t.convert(&s, "\"Quote quote\"")); + + // Drop URLs + assert_eq!("testing link", &t.convert(&s, "Testing https://t.co/1A2b3cdEfG link")); + assert_eq!("visit", &t.convert(&s, "Visit https://t.co/A1A1b0b0b0…")); + + // Complex examples taken from real tweets. + assert_eq!("will be in novi michigan this friday at 5:00pm join the movement tickets available at", + &t.convert(&s, r#"Will be in Novi, Michigan this Friday at 5:00pm. Join the MOVEMENT! + Tickets available at: https://t.co/Q6APf0ZFYA… https://t.co/6WAyO9eQHN"#)); + + // TODO + // Handle numerics + //assert_eq!("u.s. murders increased ten point eight percent in twenty fifteen", + // &t.convert(&s, "U.S. Murders Increased 10.8% in 2015")); + + // TODO: WAAAY MORE TESTS. + } + + #[test] + fn test_split_sentence() { + assert_eq!(sen(&["foo"]), split_sentence(" foo ")); + assert_eq!(sen(&["foo", "bar", "baz"]), split_sentence("foo bar baz")); + assert_eq!(sen(&[".", "..", "..."]), split_sentence(" .\n..\t\t...\n ")); + + let empty : Vec = Vec::new(); + assert_eq!(empty, split_sentence(" \n\t ")); + } + + fn sen(list: &[&str]) -> Vec { + let mut out = Vec::new(); + for it in list { out.push(it.to_string()); } + out + } +} + diff --git a/src/main.rs b/src/main.rs index b5d4adced6..564a7ac237 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,11 +1,12 @@ // Copyright (c) 2015-2016 Brandon Thomas +#[macro_use] extern crate lazy_static; +#[macro_use] extern crate log; + extern crate clap; extern crate crypto; extern crate hound; extern crate iron; -#[macro_use] -extern crate log; extern crate regex; extern crate resolve; extern crate router; @@ -14,28 +15,30 @@ extern crate time; extern crate toml; extern crate urlencoded; -pub mod arpabet; pub mod audiobank; pub mod config; -pub mod dictionary; pub mod effects; pub mod error; pub mod handlers; +pub mod lang; pub mod logger; +pub mod old_dictionary; +pub mod old_words; +pub mod speaker; pub mod synthesizer; -pub mod words; -use arpabet::ArpabetDictionary; use audiobank::Audiobank; use clap::{App, Arg, ArgMatches}; use config::Config; -use dictionary::VocabularyLibrary; use handlers::audio_synth_handler::AudioSynthHandler; use handlers::error_filter::ErrorFilter; use handlers::file_server_handler::FileServerHandler; use handlers::vocab_list_handler::VocabListHandler; use iron::prelude::*; +use lang::arpabet::Arpabet; +use lang::tokenizer::Tokenizer; use logger::SimpleLogger; +use old_dictionary::VocabularyLibrary; use resolve::hostname; use router::Router; use std::path::Path; @@ -117,15 +120,15 @@ fn get_hostname() { fn create_synthesizer(config: &Config) -> Synthesizer { info!("Reading Arpabet Dictionary..."); - let arpabet_dictionary = ArpabetDictionary::load_from_file( + let arpabet_dictionary = Arpabet::load_from_file( &config.phoneme_dictionary_file_development).unwrap(); info!("Reading Extra Dictionary..."); - let extra_dictionary = ArpabetDictionary::load_from_file( + let extra_dictionary = Arpabet::load_from_file( &config.extra_dictionary_file_development).unwrap(); info!("Reading Square Dictionary..."); - let square_dictionary = ArpabetDictionary::load_from_file( + let square_dictionary = Arpabet::load_from_file( &config.square_dictionary_file_development).unwrap(); let dictionary = arpabet_dictionary @@ -134,7 +137,9 @@ fn create_synthesizer(config: &Config) -> Synthesizer { let audiobank = Audiobank::new(&config.get_sound_path()); + let tokenizer = Tokenizer::empty(); + info!("Building Synthesizer..."); - Synthesizer::new(dictionary, audiobank) + Synthesizer::new(dictionary, audiobank, tokenizer) } diff --git a/src/dictionary.rs b/src/old_dictionary.rs similarity index 100% rename from src/dictionary.rs rename to src/old_dictionary.rs diff --git a/src/words.rs b/src/old_words.rs similarity index 100% rename from src/words.rs rename to src/old_words.rs diff --git a/src/speaker.rs b/src/speaker.rs new file mode 100644 index 0000000000..81a36b918e --- /dev/null +++ b/src/speaker.rs @@ -0,0 +1,54 @@ +// Copyright (c) 2016 Brandon Thomas + +use std::fmt; + +/// Simply for type safety. +/// Don't do stupid things with this. +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +pub struct Speaker { + pub speaker: String, +} + +impl Speaker { + pub fn new(speaker: String) -> Speaker { + Speaker { speaker: speaker } + } + + pub fn as_str(&self) -> &str { + &self.speaker + } + + /// Don't overuse. + pub fn to_string(&self) -> String { + self.speaker.clone() + } +} + +impl fmt::Display for Speaker { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.speaker) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_speaker() { + let alice = Speaker::new("alice".to_string()); + let bob = Speaker::new("bob".to_string()); + + assert_eq!(alice, Speaker::new("alice".to_string())); + assert_eq!(&alice, &Speaker::new("alice".to_string())); + + assert!(alice == alice); + assert!(alice == Speaker::new("alice".to_string())); + assert!(&alice == &alice); + assert!(&alice == &Speaker::new("alice".to_string())); + + assert!(alice != bob); + assert!(&alice != &bob); + } +} + diff --git a/src/synthesizer.rs b/src/synthesizer.rs index fb59b64d72..e7153f94e4 100644 --- a/src/synthesizer.rs +++ b/src/synthesizer.rs @@ -1,6 +1,5 @@ // Copyright (c) 2016 Brandon Thomas -use arpabet::ArpabetDictionary; use audiobank::Audiobank; use audiobank::SampleBytes; use audiobank::SamplePreference; @@ -10,9 +9,12 @@ use effects::volume::change_volume; use error::SynthError; use hound::WavSpec; use hound::WavWriter; +use lang::arpabet::Arpabet; +use lang::tokenizer::Tokenizer; +use old_words::split_sentence; +use speaker::Speaker; use std::io::BufWriter; use std::io::Cursor; -use words::split_sentence; pub type WavBytes = Vec; @@ -25,20 +27,25 @@ pub struct Synthesizer { ///// Word dictionary //word_dictionary: Vocabulary, - /// Arpabet dictionary, (Word) -> (PhonemeList) - arpabet_dictionary: ArpabetDictionary, + /// Arpabet, (Word) -> (PhonemeList) + arpabet_dictionary: Arpabet, /// WAV Audiobank for sound generation. audiobank: Audiobank, + + /// Sentence tokenizer + tokenizer: Tokenizer, } impl Synthesizer { /// CTOR - pub fn new(arpabet_dictionary: ArpabetDictionary, audiobank: Audiobank) - -> Synthesizer { + pub fn new(arpabet_dictionary: Arpabet, + audiobank: Audiobank, + tokenizer: Tokenizer) -> Synthesizer { Synthesizer { arpabet_dictionary: arpabet_dictionary, audiobank: audiobank, + tokenizer: tokenizer, } } @@ -47,7 +54,7 @@ impl Synthesizer { */ pub fn generate(&self, sentence: &str, - speaker: &str, + speaker: &Speaker, use_words: bool, use_phonemes: bool, use_diphones: bool, @@ -62,7 +69,8 @@ impl Synthesizer { word_padding_end: Option) -> Result { - let mut words = split_sentence(sentence); + let prepared = self.tokenizer.convert(speaker, sentence); + let mut words = split_sentence(&prepared); if words.len() == 0 { return Err(SynthError::BadInput { @@ -137,11 +145,11 @@ impl Synthesizer { /// whether or not the word was successfully found and concatenated. fn concatenate_word(&self, concatenated_waveform: &mut Vec, - speaker: &str, + speaker: &Speaker, word: &str, word_padding_start: Option, word_padding_end: Option) -> bool { - match self.audiobank.get_word(speaker, word) { + match self.audiobank.get_word(speaker.as_str(), word) { None => { // The word does not exist in the database. false @@ -170,7 +178,7 @@ impl Synthesizer { /// successfully found and concatenated. fn concatenate_polyphone(&self, concatenated_waveform: &mut Vec, - speaker: &str, + speaker: &Speaker, word: &str, use_diphones: bool, monophone_padding_start: Option, @@ -217,7 +225,7 @@ impl Synthesizer { if use_diphones && i < end { let first = phoneme; let second = &polyphone[i+1]; - match self.audiobank.get_diphone(speaker, first, second) { + match self.audiobank.get_diphone(speaker.as_str(), first, second) { None => {}, Some(diphone_data) => { info!("Read diphone: {}, {}", first, second); @@ -230,16 +238,16 @@ impl Synthesizer { // Attempt to read a "begin" or "end" monophone. let mut read_results = if i == 0 { - self.audiobank.get_begin_phoneme(speaker, &phoneme) + self.audiobank.get_begin_phoneme(speaker.as_str(), &phoneme) } else if i == end { - self.audiobank.get_end_phoneme(speaker, &phoneme) + self.audiobank.get_end_phoneme(speaker.as_str(), &phoneme) } else { None }; // Read a regular monophone. if read_results.is_none() { - read_results = self.audiobank.get_phoneme(speaker, &phoneme); + read_results = self.audiobank.get_phoneme(speaker.as_str(), &phoneme); } match read_results { @@ -282,7 +290,7 @@ impl Synthesizer { // TODO: Doc and test. fn concatenate_n_phone(&self, concatenated_waveform: &mut Vec, - speaker: &str, + speaker: &Speaker, word: &str, polyphone_padding_end: Option, use_ends: bool) -> bool { @@ -314,7 +322,7 @@ impl Synthesizer { // TODO: Make this super easy to test (and write some tests). // TODO: Make this super efficient. fn get_n_phone_samples(&self, - speaker: &str, + speaker: &Speaker, word: &str, sample_preference: SamplePreference, use_ends: bool) @@ -361,7 +369,7 @@ impl Synthesizer { }; //println!("n-phone: {:?}", candidate_n_phone); - let phone = self.audiobank.get_n_phone(speaker, + let phone = self.audiobank.get_n_phone(speaker.as_str(), candidate_n_phone, sample_preference, use_ends); @@ -395,7 +403,7 @@ impl Synthesizer { _ => { SamplePreference::Middle }, }; - let phone = self.audiobank.get_n_phone(speaker, + let phone = self.audiobank.get_n_phone(speaker.as_str(), candidate_n_phone, sample_preference, use_ends); @@ -428,7 +436,7 @@ impl Synthesizer { _ => { SamplePreference::Middle }, }; - let phone = self.audiobank.get_n_phone(speaker, + let phone = self.audiobank.get_n_phone(speaker.as_str(), candidate_n_phone, sample_preference, use_ends); @@ -457,7 +465,7 @@ impl Synthesizer { _ => { SamplePreference::Middle }, }; - let phone = self.audiobank.get_n_phone(speaker, + let phone = self.audiobank.get_n_phone(speaker.as_str(), &polyphone[i..i+1], sample_preference, use_ends);