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::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)?;

View File

@ -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<u8, Error> {
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<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(())
}
fn get_pitch(&self) -> Result<u8, Error> {
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<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(())
}
fn get_volume(&self) -> Result<u8, Error> {
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<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(())
}
}

View File

@ -37,27 +37,63 @@ impl Backend for Tolk {
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!();
}
fn set_rate(&mut self, _rate: u8) -> Result<(), Error> {
fn set_rate(&mut self, _rate: f32) -> Result<(), Error> {
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!();
}
fn set_pitch(&mut self, _pitch: u8) -> Result<(), Error> {
fn set_pitch(&mut self, _pitch: f32) -> Result<(), Error> {
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!();
}
fn set_volume(&mut self, _volume: u8) -> Result<(), Error> {
fn set_volume(&mut self, _volume: f32) -> Result<(), Error> {
unimplemented!();
}
}

View File

@ -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<Self, Error> {
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<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)
}
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<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)
}
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<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)
}
fn set_volume(&mut self, volume: u8) -> Result<(), Error> {
fn set_volume(&mut self, volume: f32) -> Result<(), Error> {
self.volume = volume;
Ok(())
}

View File

@ -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<u8, Error> {
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<u8, Error> {
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<f32, Error> {
let rate = self.synth.options()?.speaking_rate()?;
Ok(rate as f32)
}
fn get_volume(&self) -> std::result::Result<u8, Error> {
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<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),
#[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<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>;
fn min_rate(&self) -> f32;
fn max_rate(&self) -> f32;
fn normal_rate(&self) -> f32;
fn get_rate(&self) -> Result<f32, Error>;
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<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>);
@ -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<u8, Error> {
pub fn get_rate(&self) -> Result<f32, Error> {
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<u8, Error> {
pub fn get_pitch(&self) -> Result<f32, Error> {
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<u8, Error> {
pub fn get_volume(&self) -> Result<f32, Error> {
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)
}