diff --git a/examples/hello_world.rs b/examples/hello_world.rs index 0399e1f..75a4eb1 100644 --- a/examples/hello_world.rs +++ b/examples/hello_world.rs @@ -1,5 +1,4 @@ use std::io; -use std::u8; use tts::*; @@ -11,28 +10,34 @@ fn main() -> Result<(), Error> { if rate { let original_rate = tts.get_rate()?; tts.speak(format!("Current rate: {}", original_rate), false)?; - tts.set_rate(u8::MAX)?; + tts.set_rate(tts.max_rate())?; tts.speak("This is very fast.", false)?; - tts.set_rate(0)?; + tts.set_rate(tts.min_rate())?; tts.speak("This is very slow.", false)?; + tts.set_rate(tts.normal_rate())?; + tts.speak("This is the normal rate.", false)?; tts.set_rate(original_rate)?; } let Features { pitch, .. } = tts.supported_features(); if pitch { let original_pitch = tts.get_pitch()?; - tts.set_pitch(u8::MAX)?; + tts.set_pitch(tts.max_pitch())?; tts.speak("This is high-pitch.", false)?; - tts.set_pitch(0)?; + tts.set_pitch(tts.min_pitch())?; tts.speak("This is low pitch.", false)?; + tts.set_pitch(tts.normal_pitch())?; + tts.speak("This is normal pitch.", false)?; tts.set_pitch(original_pitch)?; } let Features { volume, .. } = tts.supported_features(); if volume { let original_volume = tts.get_volume()?; - tts.set_volume(u8::MAX)?; + tts.set_volume(tts.max_volume())?; tts.speak("This is loud!", false)?; - tts.set_volume(0)?; + tts.set_volume(tts.min_volume())?; tts.speak("This is quiet.", false)?; + tts.set_volume(tts.normal_volume())?; + tts.speak("This is normal volume.", false)?; tts.set_volume(original_volume)?; } tts.speak("Goodbye.", false)?; diff --git a/src/backends/speech_dispatcher.rs b/src/backends/speech_dispatcher.rs index cf6a4bb..e9615d3 100644 --- a/src/backends/speech_dispatcher.rs +++ b/src/backends/speech_dispatcher.rs @@ -1,6 +1,4 @@ #[cfg(target_os = "linux")] -use std::u8; - use log::{info, trace}; use speech_dispatcher::*; @@ -16,18 +14,6 @@ impl SpeechDispatcher { } } -fn u8_to_i32(v: u8) -> i32 { - let ratio: f32 = v as f32 / u8::MAX as f32; - (ratio * 200. - 100.) as i32 -} - -fn i32_to_u8(v: i32) -> u8 { - let v = v as f32; - let ratio: f32 = (v + 100.) / 200.; - let v = ratio * u8::MAX as f32; - v as u8 -} - impl Backend for SpeechDispatcher { fn supported_features(&self) -> Features { Features { @@ -60,30 +46,66 @@ impl Backend for SpeechDispatcher { Ok(()) } - fn get_rate(&self) -> Result { - Ok(i32_to_u8(self.0.get_voice_rate())) + fn min_rate(&self) -> f32 { + -100. } - fn set_rate(&mut self, rate: u8) -> Result<(), Error> { - self.0.set_voice_rate(u8_to_i32(rate)); + fn max_rate(&self) -> f32 { + 100. + } + + fn normal_rate(&self) -> f32 { + 0. + } + + fn get_rate(&self) -> Result { + Ok(self.0.get_voice_rate() as f32) + } + + fn set_rate(&mut self, rate: f32) -> Result<(), Error> { + self.0.set_voice_rate(rate as i32); Ok(()) } - fn get_pitch(&self) -> Result { - Ok(i32_to_u8(self.0.get_voice_pitch())) + fn min_pitch(&self) -> f32 { + -100. } - fn set_pitch(&mut self, pitch: u8) -> Result<(), Error> { - self.0.set_voice_pitch(u8_to_i32(pitch)); + fn max_pitch(&self) -> f32 { + 100. + } + + fn normal_pitch(&self) -> f32 { + 0. + } + + fn get_pitch(&self) -> Result { + Ok(self.0.get_voice_pitch() as f32) + } + + fn set_pitch(&mut self, pitch: f32) -> Result<(), Error> { + self.0.set_voice_pitch(pitch as i32); Ok(()) } - fn get_volume(&self) -> Result { - Ok(i32_to_u8(self.0.get_volume())) + fn min_volume(&self) -> f32 { + -100. } - fn set_volume(&mut self, volume: u8) -> Result<(), Error> { - self.0.set_volume(u8_to_i32(volume)); + fn max_volume(&self) -> f32 { + 100. + } + + fn normal_volume(&self) -> f32 { + 0. + } + + fn get_volume(&self) -> Result { + Ok(self.0.get_volume() as f32) + } + + fn set_volume(&mut self, volume: f32) -> Result<(), Error> { + self.0.set_volume(volume as i32); Ok(()) } } diff --git a/src/backends/tolk.rs b/src/backends/tolk.rs index e97520e..4b9d747 100644 --- a/src/backends/tolk.rs +++ b/src/backends/tolk.rs @@ -37,27 +37,63 @@ impl Backend for Tolk { Ok(()) } - fn get_rate(&self) -> Result { + fn min_rate(&self) -> f32 { + unimplemented!() + } + + fn max_rate(&self) -> f32 { + unimplemented!() + } + + fn normal_rate(&self) -> f32 { + unimplemented!() + } + + fn get_rate(&self) -> Result { unimplemented!(); } - fn set_rate(&mut self, _rate: u8) -> Result<(), Error> { + fn set_rate(&mut self, _rate: f32) -> Result<(), Error> { unimplemented!(); } - fn get_pitch(&self) -> Result { + fn min_pitch(&self) -> f32 { + unimplemented!() + } + + fn max_pitch(&self) -> f32 { + unimplemented!() + } + + fn normal_pitch(&self) -> f32 { + unimplemented!() + } + + fn get_pitch(&self) -> Result { unimplemented!(); } - fn set_pitch(&mut self, _pitch: u8) -> Result<(), Error> { + fn set_pitch(&mut self, _pitch: f32) -> Result<(), Error> { unimplemented!(); } - fn get_volume(&self) -> Result { + fn min_volume(&self) -> f32 { + unimplemented!() + } + + fn max_volume(&self) -> f32 { + unimplemented!() + } + + fn normal_volume(&self) -> f32 { + unimplemented!() + } + + fn get_volume(&self) -> Result { unimplemented!(); } - fn set_volume(&mut self, _volume: u8) -> Result<(), Error> { + fn set_volume(&mut self, _volume: f32) -> Result<(), Error> { unimplemented!(); } } diff --git a/src/backends/web.rs b/src/backends/web.rs index 37eb29b..fe79d5a 100644 --- a/src/backends/web.rs +++ b/src/backends/web.rs @@ -1,24 +1,22 @@ #[cfg(target_arch = "wasm32")] -use std::u8; - use log::{info, trace}; use web_sys::SpeechSynthesisUtterance; -use crate::{Backend, Error}; +use crate::{Backend, Error, Features}; pub struct Web { - rate: u8, - pitch: u8, - volume: u8, + rate: f32, + pitch: f32, + volume: f32, } impl Web { pub fn new() -> Result { info!("Initializing Web backend"); Ok(Web { - rate: 25, - pitch: 127, - volume: u8::MAX, + rate: 1., + pitch: 1., + volume: 1., }) } } @@ -36,15 +34,9 @@ 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); + utterance.set_rate(self.rate); + utterance.set_pitch(self.pitch); + utterance.set_volume(self.volume); if interrupt { self.stop()?; } @@ -64,29 +56,65 @@ impl Backend for Web { Ok(()) } - fn get_rate(&self) -> Result { + fn min_rate(&self) -> f32 { + 0.1 + } + + fn max_rate(&self) -> f32 { + 10. + } + + fn normal_rate(&self) -> f32 { + 1. + } + + fn get_rate(&self) -> Result { Ok(self.rate) } - fn set_rate(&mut self, rate: u8) -> Result<(), Error> { + fn set_rate(&mut self, rate: f32) -> Result<(), Error> { self.rate = rate; Ok(()) } - fn get_pitch(&self) -> Result { + fn min_pitch(&self) -> f32 { + 0. + } + + fn max_pitch(&self) -> f32 { + 2. + } + + fn normal_pitch(&self) -> f32 { + 1. + } + + fn get_pitch(&self) -> Result { Ok(self.pitch) } - fn set_pitch(&mut self, pitch: u8) -> Result<(), Error> { + fn set_pitch(&mut self, pitch: f32) -> Result<(), Error> { self.pitch = pitch; Ok(()) } - fn get_volume(&self) -> Result { + fn min_volume(&self) -> f32 { + 0. + } + + fn max_volume(&self) -> f32 { + 1. + } + + fn normal_volume(&self) -> f32 { + 1. + } + + fn get_volume(&self) -> Result { Ok(self.volume) } - fn set_volume(&mut self, volume: u8) -> Result<(), Error> { + fn set_volume(&mut self, volume: f32) -> Result<(), Error> { self.volume = volume; Ok(()) } diff --git a/src/backends/winrt.rs b/src/backends/winrt.rs index 48f8cfe..d23480e 100644 --- a/src/backends/winrt.rs +++ b/src/backends/winrt.rs @@ -48,9 +48,9 @@ impl Backend for WinRT { fn supported_features(&self) -> Features { Features { stop: true, - rate: false, - pitch: false, - volume: false, + rate: true, + pitch: true, + volume: true, } } @@ -71,27 +71,69 @@ impl Backend for WinRT { Ok(()) } - fn get_rate(&self) -> std::result::Result { - unimplemented!(); + fn min_rate(&self) -> f32 { + 0.5 } - fn set_rate(&mut self, _rate: u8) -> std::result::Result<(), Error> { - unimplemented!(); + fn max_rate(&self) -> f32 { + 6.0 } - fn get_pitch(&self) -> std::result::Result { - unimplemented!(); + fn normal_rate(&self) -> f32 { + 1. } - fn set_pitch(&mut self, _pitch: u8) -> std::result::Result<(), Error> { - unimplemented!(); + fn get_rate(&self) -> std::result::Result { + let rate = self.synth.options()?.speaking_rate()?; + Ok(rate as f32) } - fn get_volume(&self) -> std::result::Result { - unimplemented!(); + fn set_rate(&mut self, rate: f32) -> std::result::Result<(), Error> { + self.synth.options()?.set_speaking_rate(rate.into())?; + Ok(()) } - fn set_volume(&mut self, _volume: u8) -> std::result::Result<(), Error> { - unimplemented!(); + fn min_pitch(&self) -> f32 { + 0. + } + + fn max_pitch(&self) -> f32 { + 2. + } + + fn normal_pitch(&self) -> f32 { + 1. + } + + fn get_pitch(&self) -> std::result::Result { + let pitch = self.synth.options()?.audio_pitch()?; + Ok(pitch as f32) + } + + fn set_pitch(&mut self, pitch: f32) -> std::result::Result<(), Error> { + self.synth.options()?.set_audio_pitch(pitch.into())?; + Ok(()) + } + + fn min_volume(&self) -> f32 { + 0. + } + + fn max_volume(&self) -> f32 { + 1. + } + + fn normal_volume(&self) -> f32 { + 1. + } + + fn get_volume(&self) -> std::result::Result { + let volume = self.synth.options()?.audio_volume()?; + Ok(volume as f32) + } + + fn set_volume(&mut self, volume: f32) -> std::result::Result<(), Error> { + self.synth.options()?.set_audio_volume(volume.into())?; + Ok(()) } } diff --git a/src/lib.rs b/src/lib.rs index 10648ff..d363a1d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -39,18 +39,29 @@ pub enum Error { WinRT(winrt::Error), #[error("Unsupported feature")] UnsupportedFeature, + #[error("Out of range")] + OutOfRange, } pub trait Backend { fn supported_features(&self) -> Features; fn speak(&self, text: &str, interrupt: bool) -> Result<(), Error>; fn stop(&self) -> Result<(), Error>; - fn get_rate(&self) -> Result; - fn set_rate(&mut self, rate: u8) -> Result<(), Error>; - fn get_pitch(&self) -> Result; - fn set_pitch(&mut self, pitch: u8) -> Result<(), Error>; - fn get_volume(&self) -> Result; - fn set_volume(&mut self, volume: u8) -> Result<(), Error>; + fn min_rate(&self) -> f32; + fn max_rate(&self) -> f32; + fn normal_rate(&self) -> f32; + fn get_rate(&self) -> Result; + fn set_rate(&mut self, rate: f32) -> Result<(), Error>; + fn min_pitch(&self) -> f32; + fn max_pitch(&self) -> f32; + fn normal_pitch(&self) -> f32; + fn get_pitch(&self) -> Result; + fn set_pitch(&mut self, pitch: f32) -> Result<(), Error>; + fn min_volume(&self) -> f32; + fn max_volume(&self) -> f32; + fn normal_volume(&self) -> f32; + fn get_volume(&self) -> Result; + fn set_volume(&mut self, volume: f32) -> Result<(), Error>; } pub struct TTS(Box); @@ -130,10 +141,31 @@ impl TTS { } } + /** + * Returns the minimum rate for this speech synthesizer. + */ + pub fn min_rate(&self) -> f32 { + self.0.min_rate() + } + + /** + * Returns the maximum rate for this speech synthesizer. + */ + pub fn max_rate(&self) -> f32 { + self.0.max_rate() + } + + /** + * Returns the normal rate for this speech synthesizer. + */ + pub fn normal_rate(&self) -> f32 { + self.0.normal_rate() + } + /** * Gets the current speech rate. */ - pub fn get_rate(&self) -> Result { + pub fn get_rate(&self) -> Result { let Features { rate, .. } = self.supported_features(); if rate { self.0.get_rate() @@ -145,22 +177,47 @@ impl TTS { /** * Sets the desired speech rate. */ - pub fn set_rate(&mut self, rate: u8) -> Result<&Self, Error> { + pub fn set_rate(&mut self, rate: f32) -> Result<&Self, Error> { let Features { rate: rate_feature, .. } = self.supported_features(); if rate_feature { - self.0.set_rate(rate)?; - Ok(self) + if rate < self.0.min_rate() || rate > self.0.max_rate() { + Err(Error::OutOfRange) + } else { + self.0.set_rate(rate)?; + Ok(self) + } } else { Err(Error::UnsupportedFeature) } } + /** + * Returns the minimum pitch for this speech synthesizer. + */ + pub fn min_pitch(&self) -> f32 { + self.0.min_pitch() + } + + /** + * Returns the maximum pitch for this speech synthesizer. + */ + pub fn max_pitch(&self) -> f32 { + self.0.max_pitch() + } + + /** + * Returns the normal pitch for this speech synthesizer. + */ + pub fn normal_pitch(&self) -> f32 { + self.0.normal_pitch() + } + /** * Gets the current speech pitch. */ - pub fn get_pitch(&self) -> Result { + pub fn get_pitch(&self) -> Result { let Features { pitch, .. } = self.supported_features(); if pitch { self.0.get_pitch() @@ -172,23 +229,48 @@ impl TTS { /** * Sets the desired speech pitch. */ - pub fn set_pitch(&mut self, pitch: u8) -> Result<&Self, Error> { + pub fn set_pitch(&mut self, pitch: f32) -> Result<&Self, Error> { let Features { pitch: pitch_feature, .. } = self.supported_features(); if pitch_feature { - self.0.set_pitch(pitch)?; - Ok(self) + if pitch < self.0.min_pitch() || pitch > self.0.max_pitch() { + Err(Error::OutOfRange) + } else { + self.0.set_pitch(pitch)?; + Ok(self) + } } else { Err(Error::UnsupportedFeature) } } + /** + * Returns the minimum volume for this speech synthesizer. + */ + pub fn min_volume(&self) -> f32 { + self.0.min_volume() + } + + /** + * Returns the maximum volume for this speech synthesizer. + */ + pub fn max_volume(&self) -> f32 { + self.0.max_volume() + } + + /** + * Returns the normal volume for this speech synthesizer. + */ + pub fn normal_volume(&self) -> f32 { + self.0.normal_volume() + } + /** * Gets the current speech volume. */ - pub fn get_volume(&self) -> Result { + pub fn get_volume(&self) -> Result { let Features { volume, .. } = self.supported_features(); if volume { self.0.get_volume() @@ -200,14 +282,18 @@ impl TTS { /** * Sets the desired speech volume. */ - pub fn set_volume(&mut self, volume: u8) -> Result<&Self, Error> { + pub fn set_volume(&mut self, volume: f32) -> Result<&Self, Error> { let Features { volume: volume_feature, .. } = self.supported_features(); if volume_feature { - self.0.set_volume(volume)?; - Ok(self) + if volume < self.0.min_volume() || volume > self.0.max_volume() { + Err(Error::OutOfRange) + } else { + self.0.set_volume(volume)?; + Ok(self) + } } else { Err(Error::UnsupportedFeature) }