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"
[dependencies]
failure = "0.1"
log = "0.4"
[dev-dependencies]
@ -15,3 +16,7 @@ env_logger = "0.6"
[target.'cfg(target_os = "linux")'.dependencies]
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;
fn main() {
fn main() -> Result<(), std::io::Error> {
env_logger::init();
let tts: TTS = Default::default();
tts.speak("Hello, world.", false);
let original_rate = tts.get_rate();
tts.speak(format!("Current rate: {}", original_rate), false);
tts.set_rate(u8::MAX);
tts.speak("This is very fast.", false);
tts.set_rate(0);
tts.speak("This is very slow.", false);
tts.set_rate(original_rate);
let original_pitch = tts.get_pitch();
tts.set_pitch(u8::MAX);
tts.speak("This is high-pitch.", false);
tts.set_pitch(0);
tts.speak("This is low pitch.", false);
tts.set_pitch(original_pitch);
let original_volume = tts.get_volume();
tts.set_volume(u8::MAX);
tts.speak("This is loud!", false);
tts.set_volume(0);
tts.speak("This is quiet.", false);
tts.set_volume(original_volume);
tts.speak("Goodbye.", false);
let mut tts = TTS::default()?;
tts.speak("Hello, world.", false)?;
let original_rate = tts.get_rate()?;
tts.speak(format!("Current rate: {}", original_rate), false)?;
tts.set_rate(u8::MAX)?;
tts.speak("This is very fast.", false)?;
tts.set_rate(0)?;
tts.speak("This is very slow.", false)?;
tts.set_rate(original_rate)?;
let original_pitch = tts.get_pitch()?;
tts.set_pitch(u8::MAX)?;
tts.speak("This is high-pitch.", false)?;
tts.set_pitch(0)?;
tts.speak("This is low pitch.", false)?;
tts.set_pitch(original_pitch)?;
let original_volume = tts.get_volume()?;
tts.set_volume(u8::MAX)?;
tts.speak("This is loud!", false)?;
tts.set_volume(0)?;
tts.speak("This is quiet.", false)?;
tts.set_volume(original_volume)?;
tts.speak("Goodbye.", false)?;
Ok(())
}

View File

@ -1,5 +1,11 @@
#[cfg(target_os = "linux")]
mod speech_dispatcher;
#[cfg(target_arch = "wasm32")]
mod web;
#[cfg(target_os = "linux")]
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 speech_dispatcher::*;
use crate::Backend;
use crate::{Backend, Error};
pub struct SpeechDispatcher(Connection);
@ -30,40 +30,45 @@ fn i32_to_u8(v: i32) -> u8 {
}
impl Backend for SpeechDispatcher {
fn speak(&self, text: &str, interrupt: bool) {
fn speak(&self, text: &str, interrupt: bool) -> Result<(), Error> {
trace!("speak({}, {})", text, interrupt);
if interrupt {
self.0.cancel();
self.stop()?;
}
self.0.say(Priority::Important, text);
Ok(())
}
fn stop(&self) {
fn stop(&self) -> Result<(), Error> {
trace!("stop()");
self.0.cancel();
Ok(())
}
fn get_rate(&self) -> u8 {
i32_to_u8(self.0.get_voice_rate())
fn get_rate(&self) -> Result<u8, Error> {
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));
Ok(())
}
fn get_pitch(&self) -> u8 {
i32_to_u8(self.0.get_voice_pitch())
fn get_pitch(&self) -> Result<u8, Error> {
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));
Ok(())
}
fn get_volume(&self) -> u8 {
i32_to_u8(self.0.get_volume())
fn get_volume(&self) -> Result<u8, Error> {
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));
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::convert;
use std::fmt;
use std::io;
use failure::Fail;
mod backends;
pub enum Backends {
#[cfg(target_os = "linux")]
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 {
fn speak(&self, text: &str, interrupt: bool);
fn stop(&self);
fn get_rate(&self) -> u8;
fn set_rate(&self, rate: u8);
fn get_pitch(&self) -> u8;
fn set_pitch(&self, pitch: u8);
fn get_volume(&self) -> u8;
fn set_volume(&self, volume: u8);
fn speak(&self, text: &str, interrupt: bool) -> Result<(), Error>;
fn stop(&self) -> Result<(), Error>;
fn get_rate(&self) -> Result<u8, Error>;
fn set_rate(&mut self, rate: u8) -> Result<(), Error>;
fn get_pitch(&self) -> Result<u8, Error>;
fn set_pitch(&mut self, pitch: u8) -> Result<(), Error>;
fn get_volume(&self) -> Result<u8, Error>;
fn set_volume(&mut self, volume: u8) -> Result<(), Error>;
}
pub struct TTS(Box<Backend>);
@ -30,78 +53,84 @@ impl TTS {
/**
* 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 {
#[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.
*/
pub fn speak<S: Into<String>>(&self, text: S, interrupt: bool) -> &Self {
self.0.speak(text.into().as_str(), interrupt);
self
pub fn speak<S: Into<String>>(&self, text: S, interrupt: bool) -> Result<&Self, Error> {
self.0.speak(text.into().as_str(), interrupt)?;
Ok(self)
}
/**
* Stops current speech.
*/
pub fn stop(&self) -> &Self {
self.0.stop();
self
pub fn stop(&self) -> Result<&Self, Error> {
self.0.stop()?;
Ok(self)
}
/**
* Gets the current speech rate.
*/
pub fn get_rate(&self) -> u8 {
pub fn get_rate(&self) -> Result<u8, Error> {
self.0.get_rate()
}
/**
* Sets the desired speech rate.
*/
pub fn set_rate(&self, rate: u8) -> &Self {
self.0.set_rate(rate);
self
pub fn set_rate(&mut self, rate: u8) -> Result<&Self, Error> {
self.0.set_rate(rate)?;
Ok(self)
}
/**
* Gets the current speech pitch.
*/
pub fn get_pitch(&self) -> u8 {
pub fn get_pitch(&self) -> Result<u8, Error> {
self.0.get_pitch()
}
/**
* Sets the desired speech pitch.
*/
pub fn set_pitch(&self, pitch: u8) -> &Self {
self.0.set_pitch(pitch);
self
pub fn set_pitch(&mut self, pitch: u8) -> Result<&Self, Error> {
self.0.set_pitch(pitch)?;
Ok(self)
}
/**
* Gets the current speech volume.
*/
pub fn get_volume(&self) -> u8 {
pub fn get_volume(&self) -> Result<u8, Error> {
self.0.get_volume()
}
/**
* Sets the desired speech volume.
*/
pub fn set_volume(&self, volume: u8) -> &Self {
self.0.set_volume(volume);
self
}
}
impl Default for TTS {
fn default() -> TTS {
#[cfg(target_os = "linux")]
TTS::new(Backends::SpeechDispatcher)
pub fn set_volume(&mut self, volume: u8) -> Result<&Self, Error> {
self.0.set_volume(volume)?;
Ok(self)
}
}