Clean up speech synthesis properties, and implement everything for WinRT.

I'd previously attempted to normalize everything to `u8`, but this had some drawbacks:

 * It failed to account for some synthesis drivers defining normal as mid-range, while most define it very low.
 * It didn't track the normal value for a given synthesizer.
 * There was no clean way to map a curve between the minimum, normal, and maximum rates.

Here we track the minimum, normal, and maximum values of rate, pitch, and volume. Sanity checks are done on set.

Also, as a further proof-of-concept, all properties are now implemented for the WinRT driver.
This commit is contained in:
Nolan Darilek 2020-05-18 18:12:59 -05:00
parent 3198a537f0
commit 7b4fb8dae4
6 changed files with 315 additions and 96 deletions

View File

@ -1,5 +1,4 @@
use std::io; use std::io;
use std::u8;
use tts::*; use tts::*;
@ -11,28 +10,34 @@ fn main() -> Result<(), Error> {
if rate { if rate {
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(tts.max_rate())?;
tts.speak("This is very fast.", false)?; 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.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)?; tts.set_rate(original_rate)?;
} }
let Features { pitch, .. } = tts.supported_features(); let Features { pitch, .. } = tts.supported_features();
if pitch { if pitch {
let original_pitch = tts.get_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.speak("This is high-pitch.", false)?;
tts.set_pitch(0)?; tts.set_pitch(tts.min_pitch())?;
tts.speak("This is low pitch.", false)?; 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)?; tts.set_pitch(original_pitch)?;
} }
let Features { volume, .. } = tts.supported_features(); let Features { volume, .. } = tts.supported_features();
if volume { if volume {
let original_volume = tts.get_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.speak("This is loud!", false)?;
tts.set_volume(0)?; tts.set_volume(tts.min_volume())?;
tts.speak("This is quiet.", false)?; 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.set_volume(original_volume)?;
} }
tts.speak("Goodbye.", false)?; tts.speak("Goodbye.", false)?;

View File

@ -1,6 +1,4 @@
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
use std::u8;
use log::{info, trace}; use log::{info, trace};
use speech_dispatcher::*; 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 { impl Backend for SpeechDispatcher {
fn supported_features(&self) -> Features { fn supported_features(&self) -> Features {
Features { Features {
@ -60,30 +46,66 @@ impl Backend for SpeechDispatcher {
Ok(()) Ok(())
} }
fn get_rate(&self) -> Result<u8, Error> { fn min_rate(&self) -> f32 {
Ok(i32_to_u8(self.0.get_voice_rate())) -100.
} }
fn set_rate(&mut self, rate: u8) -> Result<(), Error> { fn max_rate(&self) -> f32 {
self.0.set_voice_rate(u8_to_i32(rate)); 100.
}
fn normal_rate(&self) -> f32 {
0.
}
fn get_rate(&self) -> Result<f32, Error> {
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(()) Ok(())
} }
fn get_pitch(&self) -> Result<u8, Error> { fn min_pitch(&self) -> f32 {
Ok(i32_to_u8(self.0.get_voice_pitch())) -100.
} }
fn set_pitch(&mut self, pitch: u8) -> Result<(), Error> { fn max_pitch(&self) -> f32 {
self.0.set_voice_pitch(u8_to_i32(pitch)); 100.
}
fn normal_pitch(&self) -> f32 {
0.
}
fn get_pitch(&self) -> Result<f32, Error> {
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(()) Ok(())
} }
fn get_volume(&self) -> Result<u8, Error> { fn min_volume(&self) -> f32 {
Ok(i32_to_u8(self.0.get_volume())) -100.
} }
fn set_volume(&mut self, volume: u8) -> Result<(), Error> { fn max_volume(&self) -> f32 {
self.0.set_volume(u8_to_i32(volume)); 100.
}
fn normal_volume(&self) -> f32 {
0.
}
fn get_volume(&self) -> Result<f32, Error> {
Ok(self.0.get_volume() as f32)
}
fn set_volume(&mut self, volume: f32) -> Result<(), Error> {
self.0.set_volume(volume as i32);
Ok(()) Ok(())
} }
} }

View File

@ -37,27 +37,63 @@ impl Backend for Tolk {
Ok(()) Ok(())
} }
fn get_rate(&self) -> Result<u8, Error> { fn min_rate(&self) -> f32 {
unimplemented!()
}
fn max_rate(&self) -> f32 {
unimplemented!()
}
fn normal_rate(&self) -> f32 {
unimplemented!()
}
fn get_rate(&self) -> Result<f32, Error> {
unimplemented!(); unimplemented!();
} }
fn set_rate(&mut self, _rate: u8) -> Result<(), Error> { fn set_rate(&mut self, _rate: f32) -> Result<(), Error> {
unimplemented!(); unimplemented!();
} }
fn get_pitch(&self) -> Result<u8, Error> { fn min_pitch(&self) -> f32 {
unimplemented!()
}
fn max_pitch(&self) -> f32 {
unimplemented!()
}
fn normal_pitch(&self) -> f32 {
unimplemented!()
}
fn get_pitch(&self) -> Result<f32, Error> {
unimplemented!(); unimplemented!();
} }
fn set_pitch(&mut self, _pitch: u8) -> Result<(), Error> { fn set_pitch(&mut self, _pitch: f32) -> Result<(), Error> {
unimplemented!(); unimplemented!();
} }
fn get_volume(&self) -> Result<u8, Error> { fn min_volume(&self) -> f32 {
unimplemented!()
}
fn max_volume(&self) -> f32 {
unimplemented!()
}
fn normal_volume(&self) -> f32 {
unimplemented!()
}
fn get_volume(&self) -> Result<f32, Error> {
unimplemented!(); unimplemented!();
} }
fn set_volume(&mut self, _volume: u8) -> Result<(), Error> { fn set_volume(&mut self, _volume: f32) -> Result<(), Error> {
unimplemented!(); unimplemented!();
} }
} }

View File

@ -1,24 +1,22 @@
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
use std::u8;
use log::{info, trace}; use log::{info, trace};
use web_sys::SpeechSynthesisUtterance; use web_sys::SpeechSynthesisUtterance;
use crate::{Backend, Error}; use crate::{Backend, Error, Features};
pub struct Web { pub struct Web {
rate: u8, rate: f32,
pitch: u8, pitch: f32,
volume: u8, volume: f32,
} }
impl Web { impl Web {
pub fn new() -> Result<Self, Error> { pub fn new() -> Result<Self, Error> {
info!("Initializing Web backend"); info!("Initializing Web backend");
Ok(Web { Ok(Web {
rate: 25, rate: 1.,
pitch: 127, pitch: 1.,
volume: u8::MAX, volume: 1.,
}) })
} }
} }
@ -36,15 +34,9 @@ impl Backend for Web {
fn speak(&self, text: &str, interrupt: bool) -> Result<(), Error> { fn speak(&self, text: &str, interrupt: bool) -> Result<(), Error> {
trace!("speak({}, {})", text, interrupt); trace!("speak({}, {})", text, interrupt);
let utterance = SpeechSynthesisUtterance::new_with_text(text).unwrap(); let utterance = SpeechSynthesisUtterance::new_with_text(text).unwrap();
let mut rate: f32 = self.rate as f32; utterance.set_rate(self.rate);
rate = rate / u8::MAX as f32 * 10.; utterance.set_pitch(self.pitch);
utterance.set_rate(rate); utterance.set_volume(self.volume);
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 { if interrupt {
self.stop()?; self.stop()?;
} }
@ -64,29 +56,65 @@ impl Backend for Web {
Ok(()) Ok(())
} }
fn get_rate(&self) -> Result<u8, Error> { fn min_rate(&self) -> f32 {
0.1
}
fn max_rate(&self) -> f32 {
10.
}
fn normal_rate(&self) -> f32 {
1.
}
fn get_rate(&self) -> Result<f32, Error> {
Ok(self.rate) Ok(self.rate)
} }
fn set_rate(&mut self, rate: u8) -> Result<(), Error> { fn set_rate(&mut self, rate: f32) -> Result<(), Error> {
self.rate = rate; self.rate = rate;
Ok(()) Ok(())
} }
fn get_pitch(&self) -> Result<u8, Error> { fn min_pitch(&self) -> f32 {
0.
}
fn max_pitch(&self) -> f32 {
2.
}
fn normal_pitch(&self) -> f32 {
1.
}
fn get_pitch(&self) -> Result<f32, Error> {
Ok(self.pitch) Ok(self.pitch)
} }
fn set_pitch(&mut self, pitch: u8) -> Result<(), Error> { fn set_pitch(&mut self, pitch: f32) -> Result<(), Error> {
self.pitch = pitch; self.pitch = pitch;
Ok(()) Ok(())
} }
fn get_volume(&self) -> Result<u8, Error> { fn min_volume(&self) -> f32 {
0.
}
fn max_volume(&self) -> f32 {
1.
}
fn normal_volume(&self) -> f32 {
1.
}
fn get_volume(&self) -> Result<f32, Error> {
Ok(self.volume) Ok(self.volume)
} }
fn set_volume(&mut self, volume: u8) -> Result<(), Error> { fn set_volume(&mut self, volume: f32) -> Result<(), Error> {
self.volume = volume; self.volume = volume;
Ok(()) Ok(())
} }

View File

@ -48,9 +48,9 @@ impl Backend for WinRT {
fn supported_features(&self) -> Features { fn supported_features(&self) -> Features {
Features { Features {
stop: true, stop: true,
rate: false, rate: true,
pitch: false, pitch: true,
volume: false, volume: true,
} }
} }
@ -71,27 +71,69 @@ impl Backend for WinRT {
Ok(()) Ok(())
} }
fn get_rate(&self) -> std::result::Result<u8, Error> { fn min_rate(&self) -> f32 {
unimplemented!(); 0.5
} }
fn set_rate(&mut self, _rate: u8) -> std::result::Result<(), Error> { fn max_rate(&self) -> f32 {
unimplemented!(); 6.0
} }
fn get_pitch(&self) -> std::result::Result<u8, Error> { fn normal_rate(&self) -> f32 {
unimplemented!(); 1.
} }
fn set_pitch(&mut self, _pitch: u8) -> std::result::Result<(), Error> { fn get_rate(&self) -> std::result::Result<f32, Error> {
unimplemented!(); let rate = self.synth.options()?.speaking_rate()?;
Ok(rate as f32)
} }
fn get_volume(&self) -> std::result::Result<u8, Error> { fn set_rate(&mut self, rate: f32) -> std::result::Result<(), Error> {
unimplemented!(); self.synth.options()?.set_speaking_rate(rate.into())?;
Ok(())
} }
fn set_volume(&mut self, _volume: u8) -> std::result::Result<(), Error> { fn min_pitch(&self) -> f32 {
unimplemented!(); 0.
}
fn max_pitch(&self) -> f32 {
2.
}
fn normal_pitch(&self) -> f32 {
1.
}
fn get_pitch(&self) -> std::result::Result<f32, Error> {
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<f32, Error> {
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(())
} }
} }

View File

@ -39,18 +39,29 @@ pub enum Error {
WinRT(winrt::Error), WinRT(winrt::Error),
#[error("Unsupported feature")] #[error("Unsupported feature")]
UnsupportedFeature, UnsupportedFeature,
#[error("Out of range")]
OutOfRange,
} }
pub trait Backend { pub trait Backend {
fn supported_features(&self) -> Features; fn supported_features(&self) -> Features;
fn speak(&self, text: &str, interrupt: bool) -> Result<(), Error>; fn speak(&self, text: &str, interrupt: bool) -> Result<(), Error>;
fn stop(&self) -> Result<(), Error>; fn stop(&self) -> Result<(), Error>;
fn get_rate(&self) -> Result<u8, Error>; fn min_rate(&self) -> f32;
fn set_rate(&mut self, rate: u8) -> Result<(), Error>; fn max_rate(&self) -> f32;
fn get_pitch(&self) -> Result<u8, Error>; fn normal_rate(&self) -> f32;
fn set_pitch(&mut self, pitch: u8) -> Result<(), Error>; fn get_rate(&self) -> Result<f32, Error>;
fn get_volume(&self) -> Result<u8, Error>; fn set_rate(&mut self, rate: f32) -> Result<(), Error>;
fn set_volume(&mut self, volume: u8) -> Result<(), Error>; fn min_pitch(&self) -> f32;
fn max_pitch(&self) -> f32;
fn normal_pitch(&self) -> f32;
fn get_pitch(&self) -> Result<f32, Error>;
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<f32, Error>;
fn set_volume(&mut self, volume: f32) -> Result<(), Error>;
} }
pub struct TTS(Box<dyn Backend>); pub struct TTS(Box<dyn Backend>);
@ -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. * Gets the current speech rate.
*/ */
pub fn get_rate(&self) -> Result<u8, Error> { pub fn get_rate(&self) -> Result<f32, Error> {
let Features { rate, .. } = self.supported_features(); let Features { rate, .. } = self.supported_features();
if rate { if rate {
self.0.get_rate() self.0.get_rate()
@ -145,22 +177,47 @@ impl TTS {
/** /**
* Sets the desired speech rate. * 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 { let Features {
rate: rate_feature, .. rate: rate_feature, ..
} = self.supported_features(); } = self.supported_features();
if rate_feature { if rate_feature {
self.0.set_rate(rate)?; if rate < self.0.min_rate() || rate > self.0.max_rate() {
Ok(self) Err(Error::OutOfRange)
} else {
self.0.set_rate(rate)?;
Ok(self)
}
} else { } else {
Err(Error::UnsupportedFeature) 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. * Gets the current speech pitch.
*/ */
pub fn get_pitch(&self) -> Result<u8, Error> { pub fn get_pitch(&self) -> Result<f32, Error> {
let Features { pitch, .. } = self.supported_features(); let Features { pitch, .. } = self.supported_features();
if pitch { if pitch {
self.0.get_pitch() self.0.get_pitch()
@ -172,23 +229,48 @@ impl TTS {
/** /**
* Sets the desired speech pitch. * 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 { let Features {
pitch: pitch_feature, pitch: pitch_feature,
.. ..
} = self.supported_features(); } = self.supported_features();
if pitch_feature { if pitch_feature {
self.0.set_pitch(pitch)?; if pitch < self.0.min_pitch() || pitch > self.0.max_pitch() {
Ok(self) Err(Error::OutOfRange)
} else {
self.0.set_pitch(pitch)?;
Ok(self)
}
} else { } else {
Err(Error::UnsupportedFeature) 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. * Gets the current speech volume.
*/ */
pub fn get_volume(&self) -> Result<u8, Error> { pub fn get_volume(&self) -> Result<f32, Error> {
let Features { volume, .. } = self.supported_features(); let Features { volume, .. } = self.supported_features();
if volume { if volume {
self.0.get_volume() self.0.get_volume()
@ -200,14 +282,18 @@ impl TTS {
/** /**
* Sets the desired speech volume. * 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 { let Features {
volume: volume_feature, volume: volume_feature,
.. ..
} = self.supported_features(); } = self.supported_features();
if volume_feature { if volume_feature {
self.0.set_volume(volume)?; if volume < self.0.min_volume() || volume > self.0.max_volume() {
Ok(self) Err(Error::OutOfRange)
} else {
self.0.set_volume(volume)?;
Ok(self)
}
} else { } else {
Err(Error::UnsupportedFeature) Err(Error::UnsupportedFeature)
} }