Refactor of synthesis-related code

- refactor and modularize code into "sentence synth" and "word synth"
- change synth params in code and UI
- change error types, better error handling
This commit is contained in:
Brandon Thomas committed 2016-10-30 02:25:52 -04:00
1 parent 3344acf6db
commit 0ea02db27f
15 files changed
+663 -584

No files matched your search

+2
View File
@@ -2,7 +2,9 @@
<dictionary name="bt">
<words>
<w>arpabet</w>
<w>monophones</w>
<w>polyphone</w>
<w>polyphones</w>
</words>
</dictionary>
</component>
+10 -14
View File
@@ -50,18 +50,16 @@
/** Handle form submission. */
handleFormSubmit: function(ev) {
var $audio = $('#sound'),
sentence = $('#text').val(),
var sentence = $('#text').val(),
speaker = $('select').val(),
volume = $('input#vol').val(),
speed = $('input#speed').val(),
up = $('#use_phonemes').prop('checked'),
ud = $('#use_diphones').prop('checked'),
um = $('#use_monophones').prop('checked'),
un = $('#use_n_phones').prop('checked'),
uw = $('#use_words').prop('checked'),
ue = $('#use_ends').prop('checked'),
mps = $('input#monophone_padding_start').val(),
mpe = $('input#monophone_padding_end').val(),
pbp = $('input#padding_between_phones').val(),
pps = $('input#polyphone_padding_start').val(),
ppe = $('input#polyphone_padding_end').val(),
wps = $('input#word_padding_start').val(),
wpe = $('input#word_padding_end').val(),
@@ -70,13 +68,12 @@
+ '&s=' + query
+ '&vol=' + volume
+ '&spd=' + speed
+ '&mps=' + mps
+ '&mpe=' + mpe
+ '&pbp=' + pbp
+ '&pps=' + pps
+ '&ppe=' + ppe
+ '&wps=' + wps
+ '&wpe=' + wpe
+ '&up=' + up
+ '&ud=' + ud
+ '&um=' + um
+ '&un=' + un
+ '&uw=' + uw
+ '&ue=' + ue;
@@ -87,13 +84,12 @@
sentence,
volume,
speed,
up,
ud,
um,
un,
uw,
ue,
mps,
mpe,
pbp,
pps,
ppe,
wps,
wpe);
+9 -12
View File
@@ -9,7 +9,7 @@
$('input#text').on('keyup', function(ev) { return Events.handleTyping(ev); });
$('body').on('keydown', function(ev) { return Events.handleBodyTyping(ev); });
$('select').on('change', function(ev) { return Events.handleVoiceSelect(ev); });
}
};
/** Init the UI, possibly from a state passed in from the URL. */
var uiInitialize = function() {
@@ -17,20 +17,18 @@
speaker = Url.getSpeaker() || Library.getDefaultSpeaker(),
volume = Url.getVolume() || Volume.DEFAULT,
speed = Url.getSpeed() || 1.0,
usePhonemes = Url.getUsePhonemes(),
useDiphones = Url.getUseDiphones(),
useMonophones = Url.getUseMonophones(),
useNPhones = Url.getUseNPhones(),
useWords = Url.getUseWords(),
useEnds = Url.getUseEnds(),
mps = Url.getMonophonePaddingStart() || 0,
mpe = Url.getMonophonePaddingEnd() || 0,
pbp = Url.getPaddingBetweenPhones() || 0,
pps = Url.getPolyphonePaddingStart() || 600,
ppe = Url.getPolyphonePaddingEnd() || 600,
wps = Url.getWordPaddingStart() || 600,
wpe = Url.getWordPaddingEnd() || 600,
urlPreviousState = false;
if (usePhonemes === null) usePhonemes = true;
if (useDiphones === null) useDiphones = true;
if (useMonophones === null) useMonophones = true;
if (useNPhones === null) useNPhones = true;
if (useWords === null) useWords = true;
if (useEnds === null) useEnds = true;
@@ -48,14 +46,13 @@
$('input#speed').val(speed);
$('input#vol').val(volume);
$('#use_phonemes').prop('checked', usePhonemes);
$('#use_diphones').prop('checked', useDiphones);
$('#use_monophones').prop('checked', useMonophones);
$('#use_n_phones').prop('checked', useNPhones);
$('#use_words').prop('checked', useWords);
$('#use_ends').prop('checked', useEnds);
$('input#monophone_padding_start').val(mps);
$('input#monophone_padding_end').val(mpe);
$('input#padding_between_phones').val(pbp);
$('input#polyphone_padding_start').val(pps);
$('input#polyphone_padding_end').val(ppe);
$('input#word_padding_start').val(wps);
$('input#word_padding_end').val(wpe);
@@ -66,7 +63,7 @@
if (urlPreviousState) {
$('form').submit();
}
}
};
$(function() {
installEventHandlers();
+29 -46
View File
@@ -16,11 +16,8 @@
/** JSON key for the `speed` param. */
_SPEED_KEY: 'spd',
/** JSON key for the `use_phonemes` param. */
_USE_PHONEMES_KEY: 'up',
/** JSON key for the `use_diphones` param. */
_USE_DIPHONES_KEY: 'ud',
/** JSON key for the `use_monophones` param. */
_USE_MONOPHONES_KEY: 'um',
/** JSON key for the `use_n_phones` param. */
_USE_N_PHONES_KEY: 'un',
@@ -31,8 +28,8 @@
/** JSON key for the `use_ends` param. */
_USE_ENDS_KEY: 'ue',
_MONOPHONE_PADDING_START_KEY: 'mps',
_MONOPHONE_PADDING_END_KEY: 'mpe',
_PADDING_BETWEEN_PHONES: 'pbp',
_POLYPHONE_PADDING_START_KEY: 'pps',
_POLYPHONE_PADDING_END_KEY: 'ppe',
_WORD_PADDING_START_KEY: 'wps',
_WORD_PADDING_END_KEY: 'wpe',
@@ -79,23 +76,13 @@
}
},
/** Get the "use phonemes" from the `window.location`. */
getUsePhonemes: function() {
/** Get the "use monophones" from the `window.location`. */
getUseMonophones: function() {
var state = this.parseState(window.location);
if (!state || !(this._USE_PHONEMES_KEY in state)) {
if (!state || !(this._USE_MONOPHONES_KEY in state)) {
return null;
} else {
return state[this._USE_PHONEMES_KEY];
}
},
/** Get the "use diphones" from the `window.location`. */
getUseDiphones: function() {
var state = this.parseState(window.location);
if (!state || !(this._USE_DIPHONES_KEY in state)) {
return null;
} else {
return state[this._USE_DIPHONES_KEY];
return state[this._USE_MONOPHONES_KEY];
}
},
@@ -129,23 +116,23 @@
}
},
/** Get the MPS value from the `window.location`. */
getMonophonePaddingStart: function() {
/** Get the PBP value from the `window.location`. */
getPaddingBetweenPhones: function() {
var state = this.parseState(window.location);
if (!state || !(this._MONOPHONE_PADDING_START_KEY in state)) {
if (!state || !(this._PADDING_BETWEEN_PHONES in state)) {
return null;
} else {
return state[this._MONOPHONE_PADDING_START_KEY];
return state[this._PADDING_BETWEEN_PHONES];
}
},
/** Get the MPE value from the `window.location`. */
getMonophonePaddingEnd: function() {
/** Get the PPS value from the `window.location`. */
getPolyphonePaddingStart: function() {
var state = this.parseState(window.location);
if (!state || !(this._MONOPHONE_PADDING_END_KEY in state)) {
if (!state || !(this._POLYPHONE_PADDING_START_KEY in state)) {
return null;
} else {
return state[this._MONOPHONE_PADDING_END_KEY];
return state[this._POLYPHONE_PADDING_START_KEY];
}
},
@@ -184,13 +171,12 @@
rawSentence,
volume,
speed,
usePhonemes,
useDiphones,
useMonophones,
useNPhones,
useWords,
useEnds,
monophonePaddingStart,
monophonePaddingEnd,
paddingBetweenPhones,
polyphonePaddingStart,
polyphonePaddingEnd,
wordPaddingStart,
wordPaddingEnd) {
@@ -199,13 +185,12 @@
rawSentence,
volume,
speed,
usePhonemes,
useDiphones,
useMonophones,
useNPhones,
useWords,
useEnds,
monophonePaddingStart,
monophonePaddingEnd,
paddingBetweenPhones,
polyphonePaddingStart,
polyphonePaddingEnd,
wordPaddingStart,
wordPaddingEnd);
@@ -219,13 +204,12 @@
rawSentence,
volume,
speed,
usePhonemes,
useDiphones,
useMonophones,
useNPhones,
useWords,
useEnds,
monophonePaddingStart,
monophonePaddingEnd,
paddingBetweenPhones,
polyphonePaddingStart,
polyphonePaddingEnd,
wordPaddingStart,
wordPaddingEnd) {
@@ -234,20 +218,19 @@
cleanedSpeaker = speaker, // TODO: Check against speakers.
cleanedVolume = volume, // TODO: Filter invalid values.
state = {},
json = null,
json,
uriEncoded = null;
state[this._SPEAKER_KEY] = cleanedSpeaker;
state[this._SENTENCE_KEY] = cleanedSentence;
state[this._VOLUME_KEY] = cleanedVolume;
state[this._SPEED_KEY] = speed; // TODO: Filter invalid values.
state[this._USE_PHONEMES_KEY] = !!usePhonemes;
state[this._USE_DIPHONES_KEY] = !!useDiphones;
state[this._USE_MONOPHONES_KEY] = !!useMonophones;
state[this._USE_N_PHONES_KEY] = !!useNPhones;
state[this._USE_WORDS_KEY] = !!useWords;
state[this._USE_ENDS_KEY] = !!useEnds;
state[this._MONOPHONE_PADDING_START_KEY] = monophonePaddingStart;
state[this._MONOPHONE_PADDING_END_KEY] = monophonePaddingEnd;
state[this._PADDING_BETWEEN_PHONES] = paddingBetweenPhones;
state[this._POLYPHONE_PADDING_START_KEY] = polyphonePaddingStart;
state[this._POLYPHONE_PADDING_END_KEY] = polyphonePaddingEnd;
state[this._WORD_PADDING_START_KEY] = wordPaddingStart;
state[this._WORD_PADDING_END_KEY] = wordPaddingEnd;
-1
View File
@@ -41,7 +41,6 @@ impl Error for SynthError {
}*/
}
impl From<io::Error> for SynthError {
fn from(error: io::Error) -> SynthError {
SynthError::IoError { cause: error }
+40 -39
View File
@@ -3,7 +3,6 @@
use config::Config;
use crypto::digest::Digest;
use crypto::sha1::Sha1;
use error::SynthError;
use iron::Handler;
use iron::IronError;
use iron::Plugin;
@@ -23,6 +22,7 @@ use std::str::FromStr;
use std::sync::Arc;
use std::sync::RwLock;
use super::error_filter::build_error;
use synthesis::error::SynthesisError;
use synthesis::synthesizer::SynthesisParams;
use synthesis::synthesizer::Synthesizer;
use time::PreciseTime;
@@ -31,14 +31,14 @@ use urlencoded::UrlEncodedQuery;
const SENTENCE_PARAM : &'static str = "s";
const SPEAKER_PARAM : &'static str = "v";
const SPEED_PARAM : &'static str = "spd";
const USE_PHONEMES_PARAM: &'static str = "up";
const USE_MONOPHONES_PARAM: &'static str = "um";
const USE_SYLLABLES_PARAM: &'static str = "us";
const USE_N_PHONES_PARAM: &'static str = "un";
const USE_WORDS_PARAM: &'static str = "uw";
const USE_ENDS_PARAM: &'static str = "ue";
const VOLUME_PARAM : &'static str = "vol";
const MONOPHONE_PADDING_START_PARAM : &'static str = "mps";
const MONOPHONE_PADDING_END_PARAM : &'static str = "mpe";
const PADDING_BETWEEN_PHONES_PARAM: &'static str = "pbp";
const POLYPHONE_PADDING_START_PARAM : &'static str = "pps";
const POLYPHONE_PADDING_END_PARAM : &'static str = "ppe";
const WORD_PADDING_START_PARAM : &'static str = "wps";
const WORD_PADDING_END_PARAM : &'static str = "wpe";
@@ -60,26 +60,26 @@ struct SpeakRequest {
/** An optional speed multiplier. */
pub speed: Option<f32>,
/** Whether to use phonemes. */
pub use_phonemes: bool,
/** Whether to use words. */
pub use_words: bool,
/** Whether to use n-phones. */
pub use_n_phones: bool,
/** Whether to use bare monophones. */
pub use_bare_monophones: bool,
/** Whether to use syllable boundaries. */
pub use_syllables: bool,
/** Whether to use words. */
pub use_words: bool,
/** Whether to use "ends": start, end, etc. */
pub use_ends: bool,
/** Padding before a monophone. */
pub monophone_padding_start: Option<u16>,
/** Padding between monophones or n-phone clusters. */
pub padding_between_phones: Option<u16>,
/** Padding after a monophone. */
pub monophone_padding_end: Option<u16>,
/** Padding before a polyphone. */
pub polyphone_padding_start: Option<u16>,
/** Padding after a polyphone. */
pub polyphone_padding_end: Option<u16>,
@@ -148,14 +148,14 @@ impl SpeakRequest {
}
});
let use_phonemes = get_bool(params, USE_PHONEMES_PARAM).unwrap_or(true);
let use_monophones = get_bool(params, USE_MONOPHONES_PARAM).unwrap_or(true);
let use_n_phones = get_bool(params, USE_N_PHONES_PARAM).unwrap_or(true);
let use_syllables = get_bool(params, USE_SYLLABLES_PARAM).unwrap_or(true);
let use_words = get_bool(params, USE_WORDS_PARAM).unwrap_or(true);
let use_ends = get_bool(params, USE_ENDS_PARAM).unwrap_or(true);
let mps = get_u16(params, MONOPHONE_PADDING_START_PARAM);
let mpe = get_u16(params, MONOPHONE_PADDING_END_PARAM);
let pbp = get_u16(params, PADDING_BETWEEN_PHONES_PARAM).or(None);
let pps = get_u16(params, POLYPHONE_PADDING_START_PARAM).or(Some(600));
let ppe = get_u16(params, POLYPHONE_PADDING_END_PARAM).or(Some(600));
let wps = get_u16(params, WORD_PADDING_START_PARAM).or(Some(600));
let wpe = get_u16(params, WORD_PADDING_END_PARAM).or(Some(600));
@@ -165,13 +165,13 @@ impl SpeakRequest {
speaker: spk,
volume: volume,
speed: speed,
use_phonemes: use_phonemes,
use_bare_monophones: use_monophones,
use_n_phones: use_n_phones,
use_syllables: use_syllables,
use_words: use_words,
use_ends: use_ends,
monophone_padding_start: mps,
monophone_padding_end: mpe,
padding_between_phones: pbp,
polyphone_padding_start: pps,
polyphone_padding_end: ppe,
word_padding_start: wps,
word_padding_end: wpe,
@@ -200,7 +200,7 @@ impl SpeakRequest {
}
let mut use_byte = 0u8;
if self.use_phonemes { use_byte |= 1 << 1; }
if self.use_bare_monophones { use_byte |= 1 << 1; }
if self.use_n_phones { use_byte |= 1 << 2; }
if self.use_syllables { use_byte |= 1 << 3; }
if self.use_words { use_byte |= 1 << 4; }
@@ -275,22 +275,23 @@ impl AudioSynthHandler {
}
/// Create audio from the sentence.
fn create_audio(&self, request: SpeakRequest) -> Result<Vec<u8>, SynthError> {
fn create_audio(&self, request: SpeakRequest)
-> Result<Vec<u8>, SynthesisError> {
let synth = match self.synthesizer.read() {
Err(_) => { return Err(SynthError::LockError); },
Err(_) => { return Err(SynthesisError::LockError); },
Ok(synth) => synth,
};
let params = SynthesisParams {
use_words: request.use_words,
use_phonemes: request.use_phonemes,
use_bare_monophones: request.use_bare_monophones,
use_n_phones: request.use_n_phones,
use_syllables: request.use_syllables,
use_ends: request.use_ends,
volume: request.volume,
speed: request.speed,
monophone_padding_start: request.monophone_padding_start,
monophone_padding_end: request.monophone_padding_end,
padding_between_phones: request.padding_between_phones,
polyphone_padding_start: request.polyphone_padding_start,
polyphone_padding_end: request.polyphone_padding_end,
word_padding_start: request.word_padding_start,
word_padding_end: request.word_padding_end,
@@ -329,8 +330,8 @@ fn get_f32(params: &QueryParams, param_name: &str) -> Option<f32> {
.and_then(|res| res.ok())
}
impl From<SynthError> for IronError {
fn from(error: SynthError) -> IronError {
impl From<SynthesisError> for IronError {
fn from(error: SynthesisError) -> IronError {
let mime = "text/plain".parse::<Mime>().unwrap();
let response = (mime, status::InternalServerError, "Service Error");
IronError::new(error, response)
@@ -349,13 +350,13 @@ mod tests {
speaker: Speaker::new("speaker".to_string()),
volume: Some(2.0),
speed: Some(1.5),
use_phonemes: true,
use_bare_monophones: true,
use_n_phones: false,
use_syllables: true,
use_words: false,
use_ends: false,
monophone_padding_start: Some(300),
monophone_padding_end: None,
padding_between_phones: Some(300),
polyphone_padding_start: None,
polyphone_padding_end: None,
word_padding_start: None,
word_padding_end: None,
@@ -366,13 +367,13 @@ mod tests {
speaker: Speaker::new("speaker".to_string()),
volume: Some(2.0),
speed: Some(1.5),
use_phonemes: true,
use_bare_monophones: true,
use_n_phones: false,
use_syllables: true,
use_words: false,
use_ends: false,
monophone_padding_start: Some(300),
monophone_padding_end: None,
padding_between_phones: Some(300),
polyphone_padding_start: None,
polyphone_padding_end: None,
word_padding_start: None,
word_padding_end: None,
@@ -385,13 +386,13 @@ mod tests {
speaker: Speaker::new("speaker".to_string()),
volume: None,
speed: Some(1.5),
use_phonemes: false,
use_bare_monophones: false,
use_n_phones: true,
use_syllables: false,
use_words: true,
use_ends: false,
monophone_padding_start: Some(300),
monophone_padding_end: None,
padding_between_phones: Some(300),
polyphone_padding_start: None,
polyphone_padding_end: None,
word_padding_start: None,
word_padding_end: Some(400),
@@ -404,13 +405,13 @@ mod tests {
speaker: Speaker::new("speaker".to_string()),
volume: Some(2.0),
speed: Some(1.5),
use_phonemes: true,
use_bare_monophones: true,
use_n_phones: false,
use_syllables: true,
use_words: false,
use_ends: false,
monophone_padding_start: Some(300),
monophone_padding_end: None,
padding_between_phones: Some(300),
polyphone_padding_start: None,
polyphone_padding_end: None,
word_padding_start: None,
word_padding_end: None,
+1 -2
View File
@@ -18,7 +18,6 @@ extern crate time;
extern crate toml;
extern crate urlencoded;
pub mod audiobank;
pub mod config;
pub mod error;
pub mod handlers;
@@ -28,7 +27,6 @@ pub mod old_dictionary;
pub mod speaker;
pub mod synthesis;
use audiobank::Audiobank;
use clap::{App, Arg, ArgMatches};
use config::Config;
use handlers::audio_synth_handler::AudioSynthHandler;
@@ -50,6 +48,7 @@ use staticfile::Static;
use std::path::Path;
use std::sync::Arc;
use std::sync::RwLock;
use synthesis::audiobank::Audiobank;
use synthesis::synthesizer::Synthesizer;
fn main() {
+7
View File
@@ -0,0 +1,7 @@
// Copyright (c) 2016 Brandon Thomas <bt@brand.io, echelon@gmail.com>
/// Waveform data delivered to the client.
pub type WavBytes = Vec<u8>;
/// Waveform data as used internally.
pub type SampleBytes = Vec<i16>;
+11 -10
View File
@@ -1,12 +1,12 @@
// Copyright (c) 2016 Brandon Thomas <bt@brand.io, echelon@gmail.com>
use error::SynthError;
use hound::WavReader;
use hound::WavSpec;
use speaker::Speaker;
use std::path::Path;
use std::path::PathBuf;
pub type SampleBytes = Vec<i16>;
use synthesis::audio::SampleBytes;
use synthesis::error::SynthesisError;
/**
* Fetch wav audio files from the audio bank.
@@ -42,6 +42,7 @@ pub type SampleBytes = Vec<i16>;
* TODO: Caching of wav files in memory.
* TODO: Path generation is _insecure_.
*/
#[derive(Clone)]
pub struct Audiobank {
/// Root path to the files.
audio_path: PathBuf,
@@ -70,12 +71,12 @@ impl Audiobank {
/**
* Get the wav data for the (speaker,word), or None if it does not exist.
*/
pub fn get_word(&self, speaker: &str, word: &str) -> Option<SampleBytes> {
if check_path(speaker).is_err() || check_path(word).is_err() {
pub fn get_word(&self, speaker: &Speaker, word: &str) -> Option<SampleBytes> {
if check_path(speaker.as_str()).is_err() || check_path(word).is_err() {
return None;
}
let path = self.audio_path.join(format!("{}/", speaker))
let path = self.audio_path.join(format!("{}/", speaker.as_str()))
.join("words/")
.join(format!("{}.wav", word));
@@ -292,7 +293,7 @@ impl Audiobank {
// TODO: This should be removed. We should cache the wav headers.
pub fn get_spec(&self, speaker: &str, word: &str)
-> Result<WavSpec, SynthError> {
-> Result<WavSpec, SynthesisError> {
try!(check_path(speaker));
try!(check_path(word));
@@ -305,7 +306,7 @@ impl Audiobank {
}
// TODO: This should be removed. We should cache the wav headers.
pub fn get_misc_spec(&self, name: &str) -> Result<WavSpec, SynthError> {
pub fn get_misc_spec(&self, name: &str) -> Result<WavSpec, SynthesisError> {
try!(check_path(name));
let path = self.audio_path.join("misc/")
@@ -333,14 +334,14 @@ impl Audiobank {
}
}
fn check_path(path: &str) -> Result<(), SynthError> {
fn check_path(path: &str) -> Result<(), SynthesisError> {
// FIXME: Hack so I can release soon. Rewrite this whole logic plz.
if path.contains("..") ||
path.contains("/") ||
path.contains("$") ||
path.contains("~")
{
Err(SynthError::BadInput { description: "invalid path" })
Err(SynthesisError::BadInput { description: "invalid path" })
} else {
Ok(())
}
+3 -1
View File
@@ -1,7 +1,9 @@
// Copyright (c) 2016 Brandon Thomas <bt@brand.io, echelon@gmail.com>
use synthesis::audio::SampleBytes;
/// Create a waveform pause of the desired length.
pub fn generate_pause(length: u16) -> Vec<i16> {
pub fn generate_pause(length: u16) -> SampleBytes {
let mut sound = Vec::new();
let len = length * 2; // number of channels
+61
View File
@@ -0,0 +1,61 @@
// Copyright (c) 2016 Brandon Thomas <bt@brand.io, echelon@gmail.com>
use hound::Error as HoundError;
use std::convert::From;
use std::error::Error;
use std::fmt;
use std::io;
/// Error in synthesizing audio.
#[derive(Debug)]
pub enum SynthesisError {
ArpabetEntryDne,
/// Bad user input.
BadInput { description: &'static str },
/// Cannot load an expected audio file, eg. "blank.wav".
CannotLoadAudioFile,
CannotSynthesizeWord,
/// Wraps a Hound audio library error.
HoundError { cause: HoundError },
/// Wraps an IoError.
IoError { cause: io::Error },
/// Could not obtain a lock.
LockError,
MonophoneDne,
WordSampleDne,
}
impl Error for SynthesisError {
fn description(&self) -> &str {
"Synthesis Error" // TODO
}
/*fn cause(&self) -> Option<&Error> {
Some(&self)
}*/
}
impl fmt::Display for SynthesisError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Synthesis Error")
}
}
impl From<io::Error> for SynthesisError {
fn from(error: io::Error) -> SynthesisError {
SynthesisError::IoError { cause: error }
}
}
impl From<HoundError> for SynthesisError {
fn from(error: HoundError) -> SynthesisError {
SynthesisError::HoundError { cause: error }
}
}
+4
View File
@@ -1,5 +1,9 @@
// Copyright (c) 2016 Brandon Thomas <bt@brand.io, echelon@gmail.com>
pub mod audio;
pub mod audiobank;
pub mod effects;
pub mod error;
pub mod synthesizer;
pub mod tokens;
pub mod word_synthesizer;
+78 -440
View File
@@ -1,33 +1,31 @@
// Copyright (c) 2016 Brandon Thomas <bt@brand.io, echelon@gmail.com>
use audiobank::Audiobank;
use audiobank::SampleBytes;
use audiobank::SamplePreference;
use error::SynthError;
use hound::WavSpec;
use hound::WavWriter;
use lang::arpabet::Arpabet;
use speaker::Speaker;
use std::io::BufWriter;
use std::io::Cursor;
use synthesis::audio::WavBytes;
use synthesis::audiobank::Audiobank;
use synthesis::effects::pause::generate_pause;
use synthesis::effects::speed::change_speed;
use synthesis::effects::volume::change_volume;
use synthesis::error::SynthesisError;
use synthesis::tokens::*;
pub type WavBytes = Vec<u8>;
use synthesis::word_synthesizer::WordSynthesizer;
/// Parameters for synthesis unrelated to the body of text or the speaker.
pub struct SynthesisParams {
pub use_words: bool,
pub use_phonemes: bool,
pub use_n_phones: bool,
pub use_syllables: bool,
pub use_bare_monophones: bool,
pub use_ends: bool,
pub volume: Option<f32>,
pub speed: Option<f32>,
pub monophone_padding_start: Option<u16>,
pub monophone_padding_end: Option<u16>,
pub padding_between_phones: Option<u16>,
pub polyphone_padding_start: Option<u16>,
pub polyphone_padding_end: Option<u16>,
pub word_padding_start: Option<u16>,
pub word_padding_end: Option<u16>,
@@ -37,19 +35,20 @@ pub struct SynthesisParams {
* Generates wave files from text input.
*/
pub struct Synthesizer {
/// Arpabet, (Word) -> (PhonemeList)
arpabet_dictionary: Arpabet,
/// WAV Audiobank for sound generation.
audiobank: Audiobank,
word_synthesizer: WordSynthesizer,
}
impl Synthesizer {
/// CTOR
pub fn new(arpabet_dictionary: Arpabet, audiobank: Audiobank) -> Synthesizer {
pub fn new(arpabet_dictionary: Arpabet, audiobank: Audiobank) -> Self {
let word_synthesizer = WordSynthesizer::new(
arpabet_dictionary,
audiobank.clone() // FIXME
);
Synthesizer {
arpabet_dictionary: arpabet_dictionary,
audiobank: audiobank,
word_synthesizer: word_synthesizer,
}
}
@@ -60,30 +59,27 @@ impl Synthesizer {
tokens: Vec<SynthToken>,
speaker: &Speaker,
params: SynthesisParams)
-> Result<WavBytes, SynthError> {
-> Result<WavBytes, SynthesisError> {
if tokens.len() == 0 {
return Err(SynthError::BadInput {
return Err(SynthesisError::BadInput {
description: "There were no words to synthesize."
});
}
let mut concatenated_waveform : Vec<i16> = Vec::new();
if !self.concatenate_misc(&mut concatenated_waveform, "padding_ends") {
return Err(SynthError::BadInput {
description: "Cannot prefix with padding."
});
}
try!(self.pad_audio(&mut concatenated_waveform));
for token in tokens {
match token {
SynthToken::Filler { value: _v } => {
// TODO: Variable length filler content.
self.handle_word_token(&mut concatenated_waveform,
try!(self.synthesize_word(
&mut concatenated_waveform,
&speaker,
"um",
&params);
&params));
},
SynthToken::Pause { value: ref v } => {
let pause_duration = match *v {
@@ -95,17 +91,16 @@ impl Synthesizer {
concatenated_waveform.extend(pause);
},
SynthToken::Word { value: ref v } => {
self.handle_word_token(&mut concatenated_waveform, &speaker, &v.value,
&params);
try!(self.synthesize_word(
&mut concatenated_waveform,
&speaker,
&v.value,
&params));
}
}
}
if !self.concatenate_misc(&mut concatenated_waveform, "padding_ends") {
return Err(SynthError::BadInput {
description: "Cannot suffix with padding."
});
}
try!(self.pad_audio(&mut concatenated_waveform));
// FIXME: Super inefficient pieces.
if params.speed.is_some() {
@@ -118,435 +113,78 @@ impl Synthesizer {
params.volume.unwrap());
}
// This is the prototype we use for audio headers.
// TODO: Cache waveform headers.
// TODO: Add custom headers with "author" etc. metadata.
let spec = try!(self.audiobank.get_misc_spec("pause"));
Ok(self.write_buffer(&spec, concatenated_waveform))
}
fn handle_word_token(&self,
concatenated_waveform: &mut Vec<i16>,
speaker: &Speaker,
word: &str,
params: &SynthesisParams) {
let mut word_added = false;
/// Add a word to the output waveform.
fn synthesize_word(&self,
concatenated_waveform: &mut Vec<i16>,
speaker: &Speaker,
word: &str,
params: &SynthesisParams)
-> Result<(), SynthesisError> {
let mut added = false;
if params.use_words {
word_added = self.concatenate_word(concatenated_waveform,
// TODO: Prefix buffer and suffix buffer.
added = self.word_synthesizer.append_word_sample(
speaker,
word,
params.word_padding_start,
params.word_padding_end);
params.word_padding_end,
concatenated_waveform)
.is_ok(); // Dictionary lookup is allowed to fail.
}
if !word_added && params.use_n_phones {
// New phoneme algorithm
word_added = self.concatenate_n_phone(concatenated_waveform,
speaker,
word,
params.polyphone_padding_end,
params.use_ends);
if !added {
if params.use_n_phones {
// TODO: Prefix buffer and suffix buffer.
// TODO: Param for monophone padding.
added = try!(self.word_synthesizer.append_n_phones(
speaker,
word,
params.use_ends,
params.polyphone_padding_start,
params.polyphone_padding_end,
params.padding_between_phones,
concatenated_waveform)
.map(|_| true));
} else if params.use_bare_monophones {
// TODO: Prefix buffer and suffix buffer.
// TODO: Param for monophone padding.
added = try!(self.word_synthesizer.append_monophones(
speaker,
word,
params.polyphone_padding_start,
params.polyphone_padding_end,
params.padding_between_phones,
concatenated_waveform)
.map(|_| true));
}
}
if !word_added && params.use_phonemes {
// Old phoneme algorithm
self.concatenate_polyphone(concatenated_waveform,
speaker,
word,
params.use_syllables,
params.monophone_padding_start,
params.monophone_padding_end,
params.polyphone_padding_end);
if added {
Ok(())
} else {
Err(SynthesisError::CannotSynthesizeWord)
}
}
/// Concatenate a word to the waveform we're building. Returns
/// whether or not the word was successfully found and concatenated.
fn concatenate_word(&self,
concatenated_waveform: &mut Vec<i16>,
speaker: &Speaker,
word: &str,
word_padding_start: Option<u16>,
word_padding_end: Option<u16>) -> bool {
match self.audiobank.get_word(speaker.as_str(), word) {
None => {
// The word does not exist in the database.
false
},
Some(waveform_data) => {
// The word exists in the database.
if word_padding_start.is_some() {
let pause = generate_pause(word_padding_start.unwrap());
concatenated_waveform.extend(pause);
}
concatenated_waveform.extend(waveform_data);
if word_padding_end.is_some() {
let pause = generate_pause(word_padding_end.unwrap());
concatenated_waveform.extend(pause);
}
true
},
/// Add "silence" padding to the waveform.
/// This is necessary for smooth playback in the frontend UI.
fn pad_audio(&self, buffer: &mut Vec<i16>) -> Result<(), SynthesisError> {
if self.concatenate_misc(buffer, "padding_ends") {
Ok(())
} else {
Err(SynthesisError::CannotLoadAudioFile)
}
}
/// Concatenate a polyphone corresponding to the word to the
/// waveform we're building. Returns whether or not the word was
/// successfully found and concatenated.
fn concatenate_polyphone(&self,
concatenated_waveform: &mut Vec<i16>,
speaker: &Speaker,
word: &str,
_use_syllables: bool,
monophone_padding_start: Option<u16>,
monophone_padding_end: Option<u16>,
polyphone_padding_end: Option<u16>) -> bool {
let polyphone = match self.arpabet_dictionary.get_polyphone(word) {
Some(p) => { p },
None => {
info!(target: "synthesis",
"Word '{}' does not exist in polyphone database.", word);
// XXX: Adding static as a cue to denote that the given
// word->polyphone mapping doesn't exist in the database.
match self.audiobank.get_misc("record_static") {
None => {},
Some(waveform_data) => {
concatenated_waveform.extend(waveform_data);
},
}
return false;
},
};
info!(target: "synthesis",
"Word '{}' maps to polyphone '{:?}'", word, polyphone);
// Insert space before polyphone.
match self.audiobank.get_misc("pause") {
None => {},
Some(pause) => { concatenated_waveform.extend(pause); },
}
let end = polyphone.len() - 1;
let mut skip_next = false;
for i in 0..polyphone.len() {
if skip_next {
// We just read a diphone.
skip_next = false;
continue;
}
let phoneme = &polyphone[i];
// Attempt to read a "begin" or "end" monophone.
let mut read_results = if i == 0 {
self.audiobank.get_begin_phoneme(speaker.as_str(), &phoneme)
} else if i == end {
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.as_str(), &phoneme);
}
match read_results {
None => {
// XXX: Adding beep to denote that the given monophone
// doesn't exist in the database.
match self.audiobank.get_misc("beep") {
None => {},
Some(waveform_data) => {
concatenated_waveform.extend(waveform_data);
},
}
continue;
},
Some(waveform_data) => {
if monophone_padding_start.is_some() {
let pause = generate_pause(monophone_padding_start.unwrap());
concatenated_waveform.extend(pause);
}
concatenated_waveform.extend(waveform_data);
if monophone_padding_end.is_some() {
let pause = generate_pause(monophone_padding_end.unwrap());
concatenated_waveform.extend(pause);
}
},
}
}
// Insert space after polyphone.
if polyphone_padding_end.is_some() {
let pause = generate_pause(polyphone_padding_end.unwrap());
concatenated_waveform.extend(pause);
}
true
}
// TODO: Doc and test.
fn concatenate_n_phone(&self,
concatenated_waveform: &mut Vec<i16>,
speaker: &Speaker,
word: &str,
polyphone_padding_end: Option<u16>,
use_ends: bool) -> bool {
let pref = SamplePreference::Begin;
let samples = match self.get_n_phone_samples(speaker, word, pref, use_ends) {
Some(s) => s,
None => {
return false;
},
};
for sample in samples {
concatenated_waveform.extend(sample);
}
// Insert space after polyphone.
if polyphone_padding_end.is_some() {
let pause = generate_pause(polyphone_padding_end.unwrap());
concatenated_waveform.extend(pause);
}
true
}
// TODO: Make this super easy to test (and write some tests).
// TODO: Make this super efficient.
fn get_n_phone_samples(&self,
speaker: &Speaker,
word: &str,
_sample_preference: SamplePreference,
use_ends: bool)
-> Option<Vec<SampleBytes>> {
let polyphone = match self.arpabet_dictionary.get_polyphone(word) {
None => {
return None;
},
Some(p) => p,
};
let mut fulfilled : Vec<bool> = Vec::with_capacity(polyphone.len());
let mut chunks : Vec<Option<SampleBytes>> =
Vec::with_capacity(polyphone.len());
// Use this to debug the synthesis.
let mut debug : Vec<Option<String>> =
Vec::with_capacity(polyphone.len());
for _ in 0..polyphone.len() {
fulfilled.push(false);
chunks.push(None);
debug.push(None);
}
// 5-phone
if polyphone.len() >= 5 {
let range = polyphone.len() - 4;
for i in 0..range {
if fulfilled[i]
|| fulfilled[i+1]
|| fulfilled[i+2]
|| fulfilled[i+3]
|| fulfilled[i+4] {
continue;
}
let candidate_n_phone = &polyphone[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(polyphone[i..i+5].join("_"));
for j in i..i+5 {
fulfilled[j] = true;
}
}
}
}
info!(target: "synthesis", "5-fulfilled: {:?}", fulfilled);
// 4-phone
if polyphone.len() >= 4 {
let range = polyphone.len() - 3;
for i in 0..range {
if fulfilled[i]
|| fulfilled[i+1]
|| fulfilled[i+2]
|| fulfilled[i+3] {
continue;
}
let candidate_n_phone = &polyphone[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(polyphone[i..i+4].join("_"));
for j in i..i+4 {
fulfilled[j] = true;
}
}
}
}
info!(target: "synthesis", "4-fulfilled: {:?}", fulfilled);
// 3-phone
if polyphone.len() >= 3 {
let range = polyphone.len() - 2;
for i in 0..range {
if fulfilled[i] || fulfilled[i+1] || fulfilled[i+2] {
continue;
}
let candidate_n_phone = &polyphone[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(polyphone[i..i+3].join("_"));
for j in i..i+3 {
fulfilled[j] = true;
}
}
}
}
info!(target: "synthesis", "3-fulfilled: {:?}", fulfilled);
// 2-phone
if polyphone.len() >= 2 {
let range = polyphone.len() - 1;
for i in 0..range {
if fulfilled[i] || fulfilled[i+1] {
continue;
}
let candidate_n_phone = &polyphone[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(polyphone[i..i+2].join("_"));
for j in i..i+2 {
fulfilled[j] = true;
}
}
}
}
info!(target: "synthesis", "2-fulfilled: {:?}", fulfilled);
// 1-phone
for i in 0..polyphone.len() {
if fulfilled[i] {
continue;
}
let sample_preference = match i {
0 => { SamplePreference::Begin },
_ if i == polyphone.len() - 1 => { SamplePreference::End },
_ => { SamplePreference::Middle },
};
let phone = self.audiobank.get_n_phone(speaker.as_str(),
&polyphone[i..i+1],
sample_preference,
use_ends);
if phone.is_some() {
chunks[i] = phone;
debug[i] = Some(polyphone[i].to_string());
fulfilled[i] = true;
}
}
info!(target: "synthesis", "1-fulfilled: {:?}", fulfilled);
for x in fulfilled {
if !x {
return None;
}
}
let debug_str : Vec<String> = debug.into_iter()
.map(|x| if x.is_some() { x.unwrap() } else { "None".to_string() })
.collect();
info!(target: "synthesis", "Comprised of: {:?}", debug_str);
let results : Vec<SampleBytes> = chunks.into_iter()
.filter_map(|x| x) // Woo, already Option<T>!
.collect();
info!(target: "synthesis", "Results Length: {} of {} phones",
results.len(),
polyphone.len());
Some(results)
}
/// Concatenate a sound effect to the waveform we're building. Returns
/// whether or not the effect was successfully found and concatenated.
fn concatenate_misc(&self, concatenated_waveform: &mut Vec<i16>,
+394
View File
@@ -0,0 +1,394 @@
// Copyright (c) 2016 Brandon Thomas <bt@brand.io, echelon@gmail.com>
use lang::arpabet::Arpabet;
use speaker::Speaker;
use synthesis::audio::SampleBytes;
use synthesis::audiobank::Audiobank;
use synthesis::audiobank::SamplePreference;
use synthesis::effects::pause::generate_pause;
use synthesis::error::SynthesisError;
/// The word synthesizer is responsible for generating "word" waveforms.
/// It does not control the strategy for how a word is synthesized.
pub struct WordSynthesizer {
arpabet_dictionary: Arpabet,
audiobank: Audiobank,
}
impl WordSynthesizer {
/// CTOR
pub fn new(arpabet_dictionary: Arpabet, audiobank: Audiobank) -> Self {
WordSynthesizer {
arpabet_dictionary: arpabet_dictionary,
audiobank: audiobank,
}
}
/// Append a word sample pulled from the whole-word sample database.
pub fn append_word_sample(&self,
speaker: &Speaker,
word: &str,
padding_before_word: Option<u16>,
padding_after_word: Option<u16>,
buffer: &mut SampleBytes)
-> Result<(), SynthesisError> {
let bytes = try!(self.audiobank.get_word(speaker, word)
.ok_or(SynthesisError::WordSampleDne));
if padding_before_word.is_some() {
let pause = generate_pause(padding_before_word.unwrap());
buffer.extend(pause);
}
buffer.extend(bytes);
if padding_after_word.is_some() {
let pause = generate_pause(padding_after_word.unwrap());
buffer.extend(pause);
}
Ok(())
}
/// Append a word sample generated from Arpabet polyphones.
/// This uses the n-phone algorithm that builds samples from the
/// longest-contiguous n-phones that complete the polyphone.
pub fn append_n_phones(&self,
speaker: &Speaker,
word: &str,
use_ends: bool,
padding_before_polyphone: Option<u16>,
padding_after_polyphone: Option<u16>,
debug_padding_between_phones: Option<u16>,
buffer: &mut SampleBytes)
-> Result<(), SynthesisError> {
let samples = try!(self.get_n_phone_samples(speaker, word, use_ends));
if padding_before_polyphone.is_some() {
let pause = generate_pause(padding_before_polyphone.unwrap());
buffer.extend(pause);
}
if debug_padding_between_phones.is_some() {
let mut padding = None;
if debug_padding_between_phones.is_some() {
let pause = generate_pause(debug_padding_between_phones.unwrap());
padding = Some(pause)
}
let last = samples.len() - 1;
for (i, sample) in samples.into_iter().enumerate() {
buffer.extend(sample);
if i < last {
buffer.extend(padding.clone().unwrap());
}
}
} else {
for sample in samples {
buffer.extend(sample);
}
}
if padding_after_polyphone.is_some() {
let pause = generate_pause(padding_after_polyphone.unwrap());
buffer.extend(pause);
}
Ok(())
}
/// Similar to 'append_n_phones', but only uses single monophones.
pub fn append_monophones(&self,
speaker: &Speaker,
word: &str,
padding_before_polyphone: Option<u16>,
padding_after_polyphone: Option<u16>,
debug_padding_between_phones: Option<u16>,
buffer: &mut SampleBytes)
-> Result<(), SynthesisError> {
let samples = try!(self.get_monophone_samples(speaker, word,
debug_padding_between_phones));
if padding_before_polyphone.is_some() {
let pause = generate_pause(padding_before_polyphone.unwrap());
buffer.extend(pause);
}
buffer.extend(samples);
if padding_after_polyphone.is_some() {
let pause = generate_pause(padding_after_polyphone.unwrap());
buffer.extend(pause);
}
Ok(())
}
/// TODO DOC
/// TODO DOC
// TODO: Make this super easy to test (and write some tests).
// TODO: Make this super efficient.
fn get_n_phone_samples(&self,
speaker: &Speaker,
word: &str,
use_ends: bool)
-> Result<Vec<SampleBytes>, SynthesisError> {
let polyphone = try!(self.arpabet_dictionary.get_polyphone(word)
.ok_or(SynthesisError::ArpabetEntryDne));
let mut fulfilled : Vec<bool> = Vec::with_capacity(polyphone.len());
let mut chunks : Vec<Option<SampleBytes>> =
Vec::with_capacity(polyphone.len());
// Use this to debug the synthesis.
let mut debug : Vec<Option<String>> = Vec::with_capacity(polyphone.len());
for _ in 0..polyphone.len() {
fulfilled.push(false);
chunks.push(None);
debug.push(None);
}
// 5-phone
if polyphone.len() >= 5 {
let range = polyphone.len() - 4;
for i in 0..range {
if fulfilled[i]
|| fulfilled[i+1]
|| fulfilled[i+2]
|| fulfilled[i+3]
|| fulfilled[i+4] {
continue;
}
let candidate_n_phone = &polyphone[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(polyphone[i..i+5].join("_"));
for j in i..i+5 {
fulfilled[j] = true;
}
}
}
}
info!(target: "synthesis", "5-fulfilled: {:?}", fulfilled);
// 4-phone
if polyphone.len() >= 4 {
let range = polyphone.len() - 3;
for i in 0..range {
if fulfilled[i]
|| fulfilled[i+1]
|| fulfilled[i+2]
|| fulfilled[i+3] {
continue;
}
let candidate_n_phone = &polyphone[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(polyphone[i..i+4].join("_"));
for j in i..i+4 {
fulfilled[j] = true;
}
}
}
}
info!(target: "synthesis", "4-fulfilled: {:?}", fulfilled);
// 3-phone
if polyphone.len() >= 3 {
let range = polyphone.len() - 2;
for i in 0..range {
if fulfilled[i] || fulfilled[i+1] || fulfilled[i+2] {
continue;
}
let candidate_n_phone = &polyphone[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(polyphone[i..i+3].join("_"));
for j in i..i+3 {
fulfilled[j] = true;
}
}
}
}
info!(target: "synthesis", "3-fulfilled: {:?}", fulfilled);
// 2-phone
if polyphone.len() >= 2 {
let range = polyphone.len() - 1;
for i in 0..range {
if fulfilled[i] || fulfilled[i+1] {
continue;
}
let candidate_n_phone = &polyphone[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(polyphone[i..i+2].join("_"));
for j in i..i+2 {
fulfilled[j] = true;
}
}
}
}
info!(target: "synthesis", "2-fulfilled: {:?}", fulfilled);
// 1-phone
for i in 0..polyphone.len() {
if fulfilled[i] {
continue;
}
let sample_preference = match i {
0 => { SamplePreference::Begin },
_ if i == polyphone.len() - 1 => { SamplePreference::End },
_ => { SamplePreference::Middle },
};
let phone = self.audiobank.get_n_phone(speaker.as_str(),
&polyphone[i..i+1],
sample_preference,
use_ends);
if phone.is_some() {
chunks[i] = phone;
debug[i] = Some(polyphone[i].to_string());
fulfilled[i] = true;
}
}
info!(target: "synthesis", "1-fulfilled: {:?}", fulfilled);
for x in fulfilled {
if !x { return Err(SynthesisError::MonophoneDne); }
}
let debug_str : Vec<String> = debug.into_iter()
.map(|x| if x.is_some() { x.unwrap() } else { "None".to_string() })
.collect();
info!(target: "synthesis", "Comprised of: {:?}", debug_str);
let results : Vec<SampleBytes> = chunks.into_iter()
.filter_map(|x| x) // Woo, already Option<T>!
.collect();
info!(target: "synthesis", "Results Length: {} of {} phones",
results.len(),
polyphone.len());
Ok(results)
}
/// TODO DOC
/// TODO DOC
fn get_monophone_samples(&self,
speaker: &Speaker,
word: &str,
debug_padding_between_phones: Option<u16>)
-> Result<SampleBytes, SynthesisError> {
let polyphone = try!(self.arpabet_dictionary.get_polyphone(word)
.ok_or(SynthesisError::ArpabetEntryDne));
let mut padding = None;
if debug_padding_between_phones.is_some() {
let pause = generate_pause(debug_padding_between_phones.unwrap());
padding = Some(pause)
}
info!(target: "synthesis",
"Word '{}' maps to polyphone '{:?}'", word, polyphone);
let end = polyphone.len() - 1;
let mut output_waveform = Vec::new();
for i in 0..polyphone.len() {
let phoneme = &polyphone[i];
// Attempt to read a "begin" or "end" monophone.
let mut read_results = if i == 0 {
self.audiobank.get_begin_phoneme(speaker.as_str(), &phoneme)
} else if i == end {
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.as_str(), &phoneme);
}
let waveform = try!(read_results.ok_or(SynthesisError::MonophoneDne));
if padding.is_some() {
output_waveform.extend(padding.clone().unwrap());
}
output_waveform.extend(waveform);
}
Ok(output_waveform)
}
}
+14 -19
View File
@@ -31,28 +31,23 @@
<div>
<input type="checkbox" id="use_words" name="use_words"
checked="checked">
<label for="use_words">Use Words</label>
</div>
<div>
<input type="checkbox" id="use_phonemes" name="use_phonemes"
checked="checked">
<label for="use_phonemes">Use Phonemes (old algorithm)</label>
</div>
<div>
<input type="checkbox" id="use_diphones" name="use_diphones"
checked="checked">
<label for="use_diphones">Use Diphones</label>
<label for="use_words">Use whole word samples</label>
</div>
<div>
<input type="checkbox" id="use_n_phones" name="use_n_phones"
checked="checked">
<label for="use_n_phones">Use n-phones (new algorithm)</label>
<label for="use_n_phones">Use n-phones</label>
</div>
<div>
<input type="checkbox" id="use_monophones" name="use_monophones"
checked="checked">
<label for="use_monophones">Use bare monophones</label>
</div>
<div>
<input type="checkbox" id="use_ends" name="use_ends"
checked="checked">
<label for="use_ends">
Use ends of n-phones (new algorithm)</label>
Use _begin and _end n-phone samples</label>
</div>
</div>
@@ -62,14 +57,14 @@
<label for="speed">Speed</label>
</div>
<div>
<input id="monophone_padding_start" value="">
<label for="monophone_padding_start">
Monophone Padding Start</label>
<input id="padding_between_phones" value="">
<label for="padding_between_phones">
Padding Between Phones</label>
</div>
<div>
<input id="monophone_padding_end" value="">
<label for="monophone_padding_end">
Monophone Padding End</label>
<input id="polyphone_padding_start" value="">
<label for="polyphone_padding_start">
Polyphone Padding Start</label>
</div>
<div>
<input id="polyphone_padding_end" value="">