Add Web backend and refactor API.

This commit is contained in:
Nolan Darilek 2018-12-30 17:13:48 +00:00
parent 7cab9aa3ac
commit 4fadad9d36
6 changed files with 202 additions and 71 deletions

View File

@ -8,6 +8,7 @@ license = "MIT"
edition = "2018" edition = "2018"
[dependencies] [dependencies]
failure = "0.1"
log = "0.4" log = "0.4"
[dev-dependencies] [dev-dependencies]
@ -15,3 +16,7 @@ env_logger = "0.6"
[target.'cfg(target_os = "linux")'.dependencies] [target.'cfg(target_os = "linux")'.dependencies]
speech-dispatcher = "0.2" speech-dispatcher = "0.2"
[target.wasm32-unknown-unknown.dependencies]
wasm-bindgen = "0.2"
web-sys = { version = "0.3", features = ["SpeechSynthesis", "SpeechSynthesisUtterance", "Window", ] }

View File

@ -2,28 +2,29 @@ use std::u8;
use tts::TTS; use tts::TTS;
fn main() { fn main() -> Result<(), std::io::Error> {
env_logger::init(); env_logger::init();
let tts: TTS = Default::default(); let mut tts = TTS::default()?;
tts.speak("Hello, world.", false); tts.speak("Hello, world.", false)?;
let original_rate = tts.get_rate(); let original_rate = tts.get_rate()?;
tts.speak(format!("Current rate: {}", original_rate), false); tts.speak(format!("Current rate: {}", original_rate), false)?;
tts.set_rate(u8::MAX); tts.set_rate(u8::MAX)?;
tts.speak("This is very fast.", false); tts.speak("This is very fast.", false)?;
tts.set_rate(0); tts.set_rate(0)?;
tts.speak("This is very slow.", false); tts.speak("This is very slow.", false)?;
tts.set_rate(original_rate); tts.set_rate(original_rate)?;
let original_pitch = tts.get_pitch(); let original_pitch = tts.get_pitch()?;
tts.set_pitch(u8::MAX); tts.set_pitch(u8::MAX)?;
tts.speak("This is high-pitch.", false); tts.speak("This is high-pitch.", false)?;
tts.set_pitch(0); tts.set_pitch(0)?;
tts.speak("This is low pitch.", false); tts.speak("This is low pitch.", false)?;
tts.set_pitch(original_pitch); tts.set_pitch(original_pitch)?;
let original_volume = tts.get_volume(); let original_volume = tts.get_volume()?;
tts.set_volume(u8::MAX); tts.set_volume(u8::MAX)?;
tts.speak("This is loud!", false); tts.speak("This is loud!", false)?;
tts.set_volume(0); tts.set_volume(0)?;
tts.speak("This is quiet.", false); tts.speak("This is quiet.", false)?;
tts.set_volume(original_volume); tts.set_volume(original_volume)?;
tts.speak("Goodbye.", false); tts.speak("Goodbye.", false)?;
Ok(())
} }

View File

@ -1,5 +1,11 @@
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
mod speech_dispatcher; mod speech_dispatcher;
#[cfg(target_arch = "wasm32")]
mod web;
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
pub use self::speech_dispatcher::*; pub use self::speech_dispatcher::*;
#[cfg(target_arch = "wasm32")]
pub use self::web::*;

View File

@ -5,7 +5,7 @@ use std::u8;
use log::{info, trace}; use log::{info, trace};
use speech_dispatcher::*; use speech_dispatcher::*;
use crate::Backend; use crate::{Backend, Error};
pub struct SpeechDispatcher(Connection); pub struct SpeechDispatcher(Connection);
@ -30,40 +30,45 @@ fn i32_to_u8(v: i32) -> u8 {
} }
impl Backend for SpeechDispatcher { impl Backend for SpeechDispatcher {
fn speak(&self, text: &str, interrupt: bool) { fn speak(&self, text: &str, interrupt: bool) -> Result<(), Error> {
trace!("speak({}, {})", text, interrupt); trace!("speak({}, {})", text, interrupt);
if interrupt { if interrupt {
self.0.cancel(); self.stop()?;
} }
self.0.say(Priority::Important, text); self.0.say(Priority::Important, text);
Ok(())
} }
fn stop(&self) { fn stop(&self) -> Result<(), Error> {
trace!("stop()"); trace!("stop()");
self.0.cancel(); self.0.cancel();
Ok(())
} }
fn get_rate(&self) -> u8 { fn get_rate(&self) -> Result<u8, Error> {
i32_to_u8(self.0.get_voice_rate()) Ok(i32_to_u8(self.0.get_voice_rate()))
} }
fn set_rate(&self, rate: u8) { fn set_rate(&mut self, rate: u8) -> Result<(), Error> {
self.0.set_voice_rate(u8_to_i32(rate)); self.0.set_voice_rate(u8_to_i32(rate));
Ok(())
} }
fn get_pitch(&self) -> u8 { fn get_pitch(&self) -> Result<u8, Error> {
i32_to_u8(self.0.get_voice_pitch()) Ok(i32_to_u8(self.0.get_voice_pitch()))
} }
fn set_pitch(&self, pitch: u8) { fn set_pitch(&mut self, pitch: u8) -> Result<(), Error> {
self.0.set_voice_pitch(u8_to_i32(pitch)); self.0.set_voice_pitch(u8_to_i32(pitch));
Ok(())
} }
fn get_volume(&self) -> u8 { fn get_volume(&self) -> Result<u8, Error> {
i32_to_u8(self.0.get_volume()) Ok(i32_to_u8(self.0.get_volume()))
} }
fn set_volume(&self, volume: u8) { fn set_volume(&mut self, volume: u8) -> Result<(), Error> {
self.0.set_volume(u8_to_i32(volume)); self.0.set_volume(u8_to_i32(volume));
Ok(())
} }
} }

85
src/backends/web.rs Normal file
View File

@ -0,0 +1,85 @@
#[cfg(target_arch = "wasm32")]
use std::u8;
use log::{info, trace};
use web_sys::SpeechSynthesisUtterance;
use crate::{Backend, Error};
pub struct Web {
rate: u8,
pitch: u8,
volume: u8,
}
impl Web {
pub fn new() -> Result<impl Backend, Error> {
info!("Initializing Web backend");
Ok(Web {
rate: 25,
pitch: 127,
volume: u8::MAX,
})
}
}
impl Backend for Web {
fn speak(&self, text: &str, interrupt: bool) -> Result<(), Error> {
trace!("speak({}, {})", text, interrupt);
let utterance = SpeechSynthesisUtterance::new_with_text(text).unwrap();
let mut rate: f32 = self.rate as f32;
rate = rate / u8::MAX as f32 * 10.;
utterance.set_rate(rate);
let mut pitch: f32 = self.pitch as f32;
pitch = pitch / u8::MAX as f32 * 2.;
utterance.set_pitch(pitch);
let mut volume: f32 = self.volume as f32;
volume = volume / u8::MAX as f32 * 1.;
utterance.set_volume(volume);
if interrupt {
self.stop()?;
}
if let Some(window) = web_sys::window() {
let speech_synthesis = window.speech_synthesis().unwrap();
speech_synthesis.speak(&utterance);
}
Ok(())
}
fn stop(&self) -> Result<(), Error> {
trace!("stop()");
if let Some(window) = web_sys::window() {
let speech_synthesis = window.speech_synthesis().unwrap();
speech_synthesis.cancel();
}
Ok(())
}
fn get_rate(&self) -> Result<u8, Error> {
Ok(self.rate)
}
fn set_rate(&mut self, rate: u8) -> Result<(), Error> {
self.rate = rate;
Ok(())
}
fn get_pitch(&self) -> Result<u8, Error> {
Ok(self.pitch)
}
fn set_pitch(&mut self, pitch: u8) -> Result<(), Error> {
self.pitch = pitch;
Ok(())
}
fn get_volume(&self) -> Result<u8, Error> {
Ok(self.volume)
}
fn set_volume(&mut self, volume: u8) -> Result<(), Error> {
self.volume = volume;
Ok(())
}
}

View File

@ -4,23 +4,46 @@
*/ */
use std::boxed::Box; use std::boxed::Box;
use std::convert;
use std::fmt;
use std::io;
use failure::Fail;
mod backends; mod backends;
pub enum Backends { pub enum Backends {
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
SpeechDispatcher, SpeechDispatcher,
#[cfg(target_arch = "wasm32")]
Web,
}
#[derive(Debug, Fail)]
pub struct Error(String);
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
write!(f, "{}", self.0)?;
Ok(())
}
}
impl convert::From<Error> for io::Error {
fn from(e: Error) -> io::Error {
io::Error::new(io::ErrorKind::Other, e.0)
}
} }
trait Backend { trait Backend {
fn speak(&self, text: &str, interrupt: bool); fn speak(&self, text: &str, interrupt: bool) -> Result<(), Error>;
fn stop(&self); fn stop(&self) -> Result<(), Error>;
fn get_rate(&self) -> u8; fn get_rate(&self) -> Result<u8, Error>;
fn set_rate(&self, rate: u8); fn set_rate(&mut self, rate: u8) -> Result<(), Error>;
fn get_pitch(&self) -> u8; fn get_pitch(&self) -> Result<u8, Error>;
fn set_pitch(&self, pitch: u8); fn set_pitch(&mut self, pitch: u8) -> Result<(), Error>;
fn get_volume(&self) -> u8; fn get_volume(&self) -> Result<u8, Error>;
fn set_volume(&self, volume: u8); fn set_volume(&mut self, volume: u8) -> Result<(), Error>;
} }
pub struct TTS(Box<Backend>); pub struct TTS(Box<Backend>);
@ -30,78 +53,84 @@ impl TTS {
/** /**
* Create a new `TTS` instance with the specified backend. * Create a new `TTS` instance with the specified backend.
*/ */
pub fn new(backend: Backends) -> TTS { pub fn new(backend: Backends) -> Result<TTS, Error> {
match backend { match backend {
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
Backends::SpeechDispatcher => TTS(Box::new(backends::SpeechDispatcher::new())), Backends::SpeechDispatcher => Ok(TTS(Box::new(backends::SpeechDispatcher::new()))),
#[cfg(target_arch = "wasm32")]
Backends::Web => {
let tts = backends::Web::new()?;
Ok(TTS(Box::new(tts)))
},
} }
} }
pub fn default() -> Result<TTS, Error> {
#[cfg(target_os = "linux")]
let tts = TTS::new(Backends::SpeechDispatcher);
#[cfg(target_arch = "wasm32")]
let tts = TTS::new(Backends::Web);
tts
}
/** /**
* Speaks the specified text, optionally interrupting current speech. * Speaks the specified text, optionally interrupting current speech.
*/ */
pub fn speak<S: Into<String>>(&self, text: S, interrupt: bool) -> &Self { pub fn speak<S: Into<String>>(&self, text: S, interrupt: bool) -> Result<&Self, Error> {
self.0.speak(text.into().as_str(), interrupt); self.0.speak(text.into().as_str(), interrupt)?;
self Ok(self)
} }
/** /**
* Stops current speech. * Stops current speech.
*/ */
pub fn stop(&self) -> &Self { pub fn stop(&self) -> Result<&Self, Error> {
self.0.stop(); self.0.stop()?;
self Ok(self)
} }
/** /**
* Gets the current speech rate. * Gets the current speech rate.
*/ */
pub fn get_rate(&self) -> u8 { pub fn get_rate(&self) -> Result<u8, Error> {
self.0.get_rate() self.0.get_rate()
} }
/** /**
* Sets the desired speech rate. * Sets the desired speech rate.
*/ */
pub fn set_rate(&self, rate: u8) -> &Self { pub fn set_rate(&mut self, rate: u8) -> Result<&Self, Error> {
self.0.set_rate(rate); self.0.set_rate(rate)?;
self Ok(self)
} }
/** /**
* Gets the current speech pitch. * Gets the current speech pitch.
*/ */
pub fn get_pitch(&self) -> u8 { pub fn get_pitch(&self) -> Result<u8, Error> {
self.0.get_pitch() self.0.get_pitch()
} }
/** /**
* Sets the desired speech pitch. * Sets the desired speech pitch.
*/ */
pub fn set_pitch(&self, pitch: u8) -> &Self { pub fn set_pitch(&mut self, pitch: u8) -> Result<&Self, Error> {
self.0.set_pitch(pitch); self.0.set_pitch(pitch)?;
self Ok(self)
} }
/** /**
* Gets the current speech volume. * Gets the current speech volume.
*/ */
pub fn get_volume(&self) -> u8 { pub fn get_volume(&self) -> Result<u8, Error> {
self.0.get_volume() self.0.get_volume()
} }
/** /**
* Sets the desired speech volume. * Sets the desired speech volume.
*/ */
pub fn set_volume(&self, volume: u8) -> &Self { pub fn set_volume(&mut self, volume: u8) -> Result<&Self, Error> {
self.0.set_volume(volume); self.0.set_volume(volume)?;
self Ok(self)
}
}
impl Default for TTS {
fn default() -> TTS {
#[cfg(target_os = "linux")]
TTS::new(Backends::SpeechDispatcher)
} }
} }