2022-06-13 15:35:32 +00:00
|
|
|
//! * a Text-To-Speech (TTS) library providing high-level interfaces to a variety of backends.
|
|
|
|
//! * Currently supported backends are:
|
|
|
|
//! * * Windows
|
|
|
|
//! * * Screen readers/SAPI via Tolk (requires `tolk` Cargo feature)
|
|
|
|
//! * * WinRT
|
|
|
|
//! * * Linux via [Speech Dispatcher](https://freebsoft.org/speechd)
|
|
|
|
//! * * MacOS/iOS
|
|
|
|
//! * * AppKit on MacOS 10.13 and below
|
|
|
|
//! * * AVFoundation on MacOS 10.14 and above, and iOS
|
|
|
|
//! * * Android
|
|
|
|
//! * * WebAssembly
|
2018-12-28 15:39:50 +00:00
|
|
|
|
2020-09-23 15:12:51 +00:00
|
|
|
use std::collections::HashMap;
|
2020-08-13 16:11:38 +00:00
|
|
|
#[cfg(target_os = "macos")]
|
2020-08-18 20:16:30 +00:00
|
|
|
use std::ffi::CStr;
|
2021-12-22 12:28:00 +00:00
|
|
|
use std::fmt;
|
2022-03-31 01:18:10 +00:00
|
|
|
#[cfg(windows)]
|
2022-03-31 01:13:27 +00:00
|
|
|
use std::string::FromUtf16Error;
|
2022-03-07 23:54:26 +00:00
|
|
|
use std::sync::{Arc, Mutex};
|
|
|
|
use std::{boxed::Box, sync::RwLock};
|
2020-08-13 16:08:00 +00:00
|
|
|
|
2020-09-22 19:29:45 +00:00
|
|
|
#[cfg(any(target_os = "macos", target_os = "ios"))]
|
2020-08-13 16:08:00 +00:00
|
|
|
use cocoa_foundation::base::id;
|
2020-11-03 03:27:13 +00:00
|
|
|
use dyn_clonable::*;
|
2020-09-23 15:12:51 +00:00
|
|
|
use lazy_static::lazy_static;
|
2020-08-13 16:08:00 +00:00
|
|
|
#[cfg(target_os = "macos")]
|
|
|
|
use libc::c_char;
|
|
|
|
#[cfg(target_os = "macos")]
|
|
|
|
use objc::{class, msg_send, sel, sel_impl};
|
2021-11-19 15:25:37 +00:00
|
|
|
#[cfg(target_os = "linux")]
|
2022-01-10 17:10:18 +00:00
|
|
|
use speech_dispatcher::Error as SpeechDispatcherError;
|
2020-05-18 20:01:28 +00:00
|
|
|
use thiserror::Error;
|
2021-05-20 18:59:02 +00:00
|
|
|
#[cfg(all(windows, feature = "tolk"))]
|
|
|
|
use tolk::Tolk;
|
2022-03-30 17:07:59 +00:00
|
|
|
pub use unic_langid::LanguageIdentifier;
|
2018-12-14 19:35:49 +00:00
|
|
|
|
|
|
|
mod backends;
|
|
|
|
|
2021-12-22 14:38:11 +00:00
|
|
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
|
|
|
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
2018-12-14 19:35:49 +00:00
|
|
|
pub enum Backends {
|
2021-12-22 14:38:11 +00:00
|
|
|
#[cfg(target_os = "android")]
|
|
|
|
Android,
|
|
|
|
#[cfg(target_os = "macos")]
|
|
|
|
AppKit,
|
|
|
|
#[cfg(any(target_os = "macos", target_os = "ios"))]
|
|
|
|
AvFoundation,
|
2018-12-14 19:35:49 +00:00
|
|
|
#[cfg(target_os = "linux")]
|
|
|
|
SpeechDispatcher,
|
2021-01-21 16:49:11 +00:00
|
|
|
#[cfg(all(windows, feature = "tolk"))]
|
2019-03-25 19:15:08 +00:00
|
|
|
Tolk,
|
2018-12-30 17:13:48 +00:00
|
|
|
#[cfg(target_arch = "wasm32")]
|
|
|
|
Web,
|
2019-03-25 19:15:08 +00:00
|
|
|
#[cfg(windows)]
|
2021-03-31 15:38:32 +00:00
|
|
|
WinRt,
|
2018-12-14 19:35:49 +00:00
|
|
|
}
|
|
|
|
|
2021-12-22 14:38:11 +00:00
|
|
|
impl fmt::Display for Backends {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
|
|
|
match self {
|
|
|
|
#[cfg(target_os = "android")]
|
|
|
|
Backends::Android => writeln!(f, "Android"),
|
|
|
|
#[cfg(target_os = "macos")]
|
|
|
|
Backends::AppKit => writeln!(f, "AppKit"),
|
|
|
|
#[cfg(any(target_os = "macos", target_os = "ios"))]
|
|
|
|
Backends::AvFoundation => writeln!(f, "AVFoundation"),
|
|
|
|
#[cfg(target_os = "linux")]
|
|
|
|
Backends::SpeechDispatcher => writeln!(f, "Speech Dispatcher"),
|
|
|
|
#[cfg(all(windows, feature = "tolk"))]
|
|
|
|
Backends::Tolk => writeln!(f, "Tolk"),
|
|
|
|
#[cfg(target_arch = "wasm32")]
|
|
|
|
Backends::Web => writeln!(f, "Web"),
|
|
|
|
#[cfg(windows)]
|
|
|
|
Backends::WinRt => writeln!(f, "Windows Runtime"),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
|
|
|
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
2020-09-23 15:12:51 +00:00
|
|
|
pub enum BackendId {
|
2021-12-22 14:38:11 +00:00
|
|
|
#[cfg(target_os = "android")]
|
|
|
|
Android(u64),
|
|
|
|
#[cfg(any(target_os = "macos", target_os = "ios"))]
|
|
|
|
AvFoundation(u64),
|
2020-09-22 17:40:03 +00:00
|
|
|
#[cfg(target_os = "linux")]
|
2022-10-19 15:28:18 +00:00
|
|
|
SpeechDispatcher(usize),
|
2020-09-22 17:40:03 +00:00
|
|
|
#[cfg(target_arch = "wasm32")]
|
|
|
|
Web(u64),
|
|
|
|
#[cfg(windows)]
|
2021-03-31 15:38:32 +00:00
|
|
|
WinRt(u64),
|
2020-09-22 17:40:03 +00:00
|
|
|
}
|
|
|
|
|
2021-12-22 14:38:11 +00:00
|
|
|
impl fmt::Display for BackendId {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
|
|
|
match self {
|
|
|
|
#[cfg(target_os = "android")]
|
|
|
|
BackendId::Android(id) => writeln!(f, "{}", id),
|
|
|
|
#[cfg(any(target_os = "macos", target_os = "ios"))]
|
|
|
|
BackendId::AvFoundation(id) => writeln!(f, "{}", id),
|
|
|
|
#[cfg(target_os = "linux")]
|
|
|
|
BackendId::SpeechDispatcher(id) => writeln!(f, "{}", id),
|
|
|
|
#[cfg(target_arch = "wasm32")]
|
|
|
|
BackendId::Web(id) => writeln!(f, "Web({})", id),
|
|
|
|
#[cfg(windows)]
|
|
|
|
BackendId::WinRt(id) => writeln!(f, "{}", id),
|
|
|
|
}
|
|
|
|
}
|
2020-09-22 17:40:03 +00:00
|
|
|
}
|
|
|
|
|
2022-01-04 12:43:32 +00:00
|
|
|
// # Note
|
|
|
|
//
|
|
|
|
// Most trait implementations are blocked by cocoa_foundation::base::id;
|
|
|
|
// which is a type alias for objc::runtime::Object, which only implements Debug.
|
|
|
|
#[derive(Debug)]
|
|
|
|
#[cfg_attr(
|
|
|
|
not(any(target_os = "macos", target_os = "ios")),
|
|
|
|
derive(Clone, Copy, Eq, Hash, PartialEq, PartialOrd, Ord)
|
|
|
|
)]
|
|
|
|
#[cfg_attr(
|
|
|
|
all(feature = "serde", not(any(target_os = "macos", target_os = "ios"))),
|
|
|
|
derive(serde::Serialize, serde::Deserialize)
|
|
|
|
)]
|
2020-09-22 17:40:03 +00:00
|
|
|
pub enum UtteranceId {
|
2021-12-22 14:38:11 +00:00
|
|
|
#[cfg(target_os = "android")]
|
|
|
|
Android(u64),
|
|
|
|
#[cfg(any(target_os = "macos", target_os = "ios"))]
|
|
|
|
AvFoundation(id),
|
2020-09-22 17:40:03 +00:00
|
|
|
#[cfg(target_os = "linux")]
|
2020-09-23 15:12:51 +00:00
|
|
|
SpeechDispatcher(u64),
|
2020-09-22 17:40:03 +00:00
|
|
|
#[cfg(target_arch = "wasm32")]
|
2020-10-08 12:16:10 +00:00
|
|
|
Web(u64),
|
2020-09-22 17:40:03 +00:00
|
|
|
#[cfg(windows)]
|
2021-03-31 15:38:32 +00:00
|
|
|
WinRt(u64),
|
2020-09-22 17:40:03 +00:00
|
|
|
}
|
|
|
|
|
2022-01-04 12:43:32 +00:00
|
|
|
// # Note
|
|
|
|
//
|
|
|
|
// Display is not implemented by cocoa_foundation::base::id;
|
|
|
|
// which is a type alias for objc::runtime::Object, which only implements Debug.
|
|
|
|
#[cfg(not(any(target_os = "macos", target_os = "ios")))]
|
2021-12-22 14:38:11 +00:00
|
|
|
impl fmt::Display for UtteranceId {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
|
|
|
match self {
|
|
|
|
#[cfg(target_os = "android")]
|
|
|
|
UtteranceId::Android(id) => writeln!(f, "{}", id),
|
|
|
|
#[cfg(target_os = "linux")]
|
|
|
|
UtteranceId::SpeechDispatcher(id) => writeln!(f, "{}", id),
|
|
|
|
#[cfg(target_arch = "wasm32")]
|
|
|
|
UtteranceId::Web(id) => writeln!(f, "Web({})", id),
|
|
|
|
#[cfg(windows)]
|
|
|
|
UtteranceId::WinRt(id) => writeln!(f, "{}", id),
|
|
|
|
}
|
|
|
|
}
|
2020-09-22 17:40:03 +00:00
|
|
|
}
|
|
|
|
|
2020-12-03 19:21:24 +00:00
|
|
|
unsafe impl Send for UtteranceId {}
|
|
|
|
|
|
|
|
unsafe impl Sync for UtteranceId {}
|
|
|
|
|
2022-03-10 20:08:13 +00:00
|
|
|
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq, PartialOrd, Ord)]
|
2021-12-22 12:28:00 +00:00
|
|
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
2019-03-24 21:30:45 +00:00
|
|
|
pub struct Features {
|
2020-06-02 19:53:14 +00:00
|
|
|
pub is_speaking: bool,
|
2021-12-22 12:28:00 +00:00
|
|
|
pub pitch: bool,
|
|
|
|
pub rate: bool,
|
|
|
|
pub stop: bool,
|
2020-09-23 15:12:51 +00:00
|
|
|
pub utterance_callbacks: bool,
|
2022-03-30 23:38:25 +00:00
|
|
|
pub voice: bool,
|
|
|
|
pub get_voice: bool,
|
2021-12-22 12:28:00 +00:00
|
|
|
pub volume: bool,
|
2019-03-24 21:30:45 +00:00
|
|
|
}
|
|
|
|
|
2021-12-22 12:28:00 +00:00
|
|
|
impl fmt::Display for Features {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
|
|
|
writeln!(f, "{:#?}", self)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Features {
|
|
|
|
pub fn new() -> Self {
|
|
|
|
Self::default()
|
2020-08-24 21:44:00 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-18 20:01:28 +00:00
|
|
|
#[derive(Debug, Error)]
|
|
|
|
pub enum Error {
|
|
|
|
#[error("IO error: {0}")]
|
2021-03-31 15:38:32 +00:00
|
|
|
Io(#[from] std::io::Error),
|
2020-06-02 19:53:14 +00:00
|
|
|
#[error("Value not received")]
|
|
|
|
NoneError,
|
2020-12-30 15:44:47 +00:00
|
|
|
#[error("Operation failed")]
|
|
|
|
OperationFailed,
|
2020-06-02 19:57:21 +00:00
|
|
|
#[cfg(target_arch = "wasm32")]
|
2021-11-19 15:22:05 +00:00
|
|
|
#[error("JavaScript error: [0]")]
|
2020-06-02 19:53:14 +00:00
|
|
|
JavaScriptError(wasm_bindgen::JsValue),
|
2021-11-19 15:22:05 +00:00
|
|
|
#[cfg(target_os = "linux")]
|
|
|
|
#[error("Speech Dispatcher error: {0}")]
|
|
|
|
SpeechDispatcher(#[from] SpeechDispatcherError),
|
2020-05-18 20:01:28 +00:00
|
|
|
#[cfg(windows)]
|
|
|
|
#[error("WinRT error")]
|
2021-11-16 17:13:31 +00:00
|
|
|
WinRt(windows::core::Error),
|
2022-03-31 01:13:27 +00:00
|
|
|
#[cfg(windows)]
|
|
|
|
#[error("UTF string conversion failed")]
|
|
|
|
UtfStringConversionFailed(#[from] FromUtf16Error),
|
2020-05-18 20:01:28 +00:00
|
|
|
#[error("Unsupported feature")]
|
|
|
|
UnsupportedFeature,
|
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.
2020-05-18 23:12:59 +00:00
|
|
|
#[error("Out of range")]
|
|
|
|
OutOfRange,
|
2020-12-29 17:15:24 +00:00
|
|
|
#[cfg(target_os = "android")]
|
|
|
|
#[error("JNI error: [0])]")]
|
|
|
|
JNI(#[from] jni::errors::Error),
|
2020-05-18 20:01:28 +00:00
|
|
|
}
|
|
|
|
|
2020-11-03 03:27:13 +00:00
|
|
|
#[clonable]
|
2022-03-30 23:07:08 +00:00
|
|
|
pub trait Backend: Clone {
|
2020-09-23 15:12:51 +00:00
|
|
|
fn id(&self) -> Option<BackendId>;
|
2019-03-24 21:30:45 +00:00
|
|
|
fn supported_features(&self) -> Features;
|
2020-09-22 17:40:03 +00:00
|
|
|
fn speak(&mut self, text: &str, interrupt: bool) -> Result<Option<UtteranceId>, Error>;
|
2020-07-06 17:52:18 +00:00
|
|
|
fn stop(&mut self) -> Result<(), Error>;
|
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.
2020-05-18 23:12:59 +00:00
|
|
|
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>;
|
2020-06-02 19:53:14 +00:00
|
|
|
fn is_speaking(&self) -> Result<bool, Error>;
|
2022-03-30 23:07:08 +00:00
|
|
|
fn voices(&self) -> Result<Vec<Voice>, Error>;
|
2022-03-31 15:39:39 +00:00
|
|
|
fn voice(&self) -> Result<Option<Voice>, Error>;
|
2022-03-30 23:38:25 +00:00
|
|
|
fn set_voice(&mut self, voice: &Voice) -> Result<(), Error>;
|
2018-12-14 19:35:49 +00:00
|
|
|
}
|
|
|
|
|
2020-09-23 15:12:51 +00:00
|
|
|
#[derive(Default)]
|
|
|
|
struct Callbacks {
|
2020-09-25 16:08:19 +00:00
|
|
|
utterance_begin: Option<Box<dyn FnMut(UtteranceId)>>,
|
|
|
|
utterance_end: Option<Box<dyn FnMut(UtteranceId)>>,
|
2020-11-03 17:03:55 +00:00
|
|
|
utterance_stop: Option<Box<dyn FnMut(UtteranceId)>>,
|
2020-09-23 15:12:51 +00:00
|
|
|
}
|
|
|
|
|
2020-09-25 16:08:19 +00:00
|
|
|
unsafe impl Send for Callbacks {}
|
|
|
|
|
|
|
|
unsafe impl Sync for Callbacks {}
|
|
|
|
|
2020-09-23 15:12:51 +00:00
|
|
|
lazy_static! {
|
|
|
|
static ref CALLBACKS: Mutex<HashMap<BackendId, Callbacks>> = {
|
|
|
|
let m: HashMap<BackendId, Callbacks> = HashMap::new();
|
|
|
|
Mutex::new(m)
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2020-11-03 03:27:13 +00:00
|
|
|
#[derive(Clone)]
|
2022-03-30 23:07:08 +00:00
|
|
|
pub struct Tts(Arc<RwLock<Box<dyn Backend>>>);
|
2018-12-14 19:35:49 +00:00
|
|
|
|
2022-03-30 23:07:08 +00:00
|
|
|
unsafe impl Send for Tts {}
|
2019-01-03 17:20:04 +00:00
|
|
|
|
2022-03-30 23:07:08 +00:00
|
|
|
unsafe impl Sync for Tts {}
|
2019-01-03 17:20:04 +00:00
|
|
|
|
2022-03-30 23:07:08 +00:00
|
|
|
impl Tts {
|
2022-06-13 15:35:32 +00:00
|
|
|
/// Create a new `TTS` instance with the specified backend.
|
2022-03-30 23:07:08 +00:00
|
|
|
pub fn new(backend: Backends) -> Result<Tts, Error> {
|
2020-09-23 15:12:51 +00:00
|
|
|
let backend = match backend {
|
2018-12-14 19:35:49 +00:00
|
|
|
#[cfg(target_os = "linux")]
|
2021-11-19 15:22:05 +00:00
|
|
|
Backends::SpeechDispatcher => {
|
|
|
|
let tts = backends::SpeechDispatcher::new()?;
|
2022-03-30 23:07:08 +00:00
|
|
|
Ok(Tts(Arc::new(RwLock::new(Box::new(tts)))))
|
2021-11-19 15:22:05 +00:00
|
|
|
}
|
2018-12-30 17:13:48 +00:00
|
|
|
#[cfg(target_arch = "wasm32")]
|
|
|
|
Backends::Web => {
|
|
|
|
let tts = backends::Web::new()?;
|
2022-03-08 00:31:25 +00:00
|
|
|
Ok(Tts(Arc::new(RwLock::new(Box::new(tts)))))
|
2019-01-03 16:16:54 +00:00
|
|
|
}
|
2021-01-21 16:49:11 +00:00
|
|
|
#[cfg(all(windows, feature = "tolk"))]
|
2019-03-25 19:15:08 +00:00
|
|
|
Backends::Tolk => {
|
|
|
|
let tts = backends::Tolk::new();
|
2020-06-11 18:00:24 +00:00
|
|
|
if let Some(tts) = tts {
|
2022-03-08 00:31:25 +00:00
|
|
|
Ok(Tts(Arc::new(RwLock::new(Box::new(tts)))))
|
2020-06-11 18:00:24 +00:00
|
|
|
} else {
|
|
|
|
Err(Error::NoneError)
|
|
|
|
}
|
2019-12-23 13:37:48 +00:00
|
|
|
}
|
2020-05-18 20:01:28 +00:00
|
|
|
#[cfg(windows)]
|
2021-03-31 15:38:32 +00:00
|
|
|
Backends::WinRt => {
|
|
|
|
let tts = backends::WinRt::new()?;
|
2022-03-07 23:54:26 +00:00
|
|
|
Ok(Tts(Arc::new(RwLock::new(Box::new(tts)))))
|
2020-05-18 20:01:28 +00:00
|
|
|
}
|
2020-08-11 17:11:19 +00:00
|
|
|
#[cfg(target_os = "macos")]
|
2022-06-14 18:09:50 +00:00
|
|
|
Backends::AppKit => Ok(Tts(Arc::new(RwLock::new(Box::new(
|
|
|
|
backends::AppKit::new()?
|
|
|
|
))))),
|
2020-08-18 20:16:30 +00:00
|
|
|
#[cfg(any(target_os = "macos", target_os = "ios"))]
|
2022-03-08 00:31:25 +00:00
|
|
|
Backends::AvFoundation => Ok(Tts(Arc::new(RwLock::new(Box::new(
|
2022-06-14 18:09:50 +00:00
|
|
|
backends::AvFoundation::new()?,
|
2022-03-08 00:31:25 +00:00
|
|
|
))))),
|
2020-12-27 15:41:11 +00:00
|
|
|
#[cfg(target_os = "android")]
|
2020-12-29 17:15:24 +00:00
|
|
|
Backends::Android => {
|
|
|
|
let tts = backends::Android::new()?;
|
2022-03-08 00:31:25 +00:00
|
|
|
Ok(Tts(Arc::new(RwLock::new(Box::new(tts)))))
|
2020-12-29 17:15:24 +00:00
|
|
|
}
|
2020-09-23 15:12:51 +00:00
|
|
|
};
|
2020-09-26 17:43:16 +00:00
|
|
|
if let Ok(backend) = backend {
|
2022-03-07 23:54:26 +00:00
|
|
|
if let Some(id) = backend.0.read().unwrap().id() {
|
2020-09-24 22:56:46 +00:00
|
|
|
let mut callbacks = CALLBACKS.lock().unwrap();
|
2020-09-23 15:12:51 +00:00
|
|
|
callbacks.insert(id, Callbacks::default());
|
|
|
|
}
|
|
|
|
Ok(backend)
|
|
|
|
} else {
|
|
|
|
backend
|
2018-12-14 19:35:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-30 23:07:08 +00:00
|
|
|
pub fn default() -> Result<Tts, Error> {
|
2018-12-30 17:13:48 +00:00
|
|
|
#[cfg(target_os = "linux")]
|
2021-03-31 15:40:42 +00:00
|
|
|
let tts = Tts::new(Backends::SpeechDispatcher);
|
2021-01-21 16:49:11 +00:00
|
|
|
#[cfg(all(windows, feature = "tolk"))]
|
2021-03-31 15:40:42 +00:00
|
|
|
let tts = if let Ok(tts) = Tts::new(Backends::Tolk) {
|
2020-06-11 18:00:24 +00:00
|
|
|
Ok(tts)
|
|
|
|
} else {
|
2021-03-31 15:40:42 +00:00
|
|
|
Tts::new(Backends::WinRt)
|
2020-05-18 20:01:28 +00:00
|
|
|
};
|
2021-01-21 16:49:11 +00:00
|
|
|
#[cfg(all(windows, not(feature = "tolk")))]
|
2021-03-31 15:40:42 +00:00
|
|
|
let tts = Tts::new(Backends::WinRt);
|
2018-12-30 17:13:48 +00:00
|
|
|
#[cfg(target_arch = "wasm32")]
|
2021-03-31 15:40:42 +00:00
|
|
|
let tts = Tts::new(Backends::Web);
|
2020-08-11 17:11:19 +00:00
|
|
|
#[cfg(target_os = "macos")]
|
2020-08-13 16:08:00 +00:00
|
|
|
let tts = unsafe {
|
|
|
|
// Needed because the Rust NSProcessInfo structs report bogus values, and I don't want to pull in a full bindgen stack.
|
|
|
|
let pi: id = msg_send![class!(NSProcessInfo), new];
|
|
|
|
let version: id = msg_send![pi, operatingSystemVersionString];
|
|
|
|
let str: *const c_char = msg_send![version, UTF8String];
|
|
|
|
let str = CStr::from_ptr(str);
|
|
|
|
let str = str.to_string_lossy();
|
2021-03-12 11:48:14 +00:00
|
|
|
let version: Vec<&str> = str.split(' ').collect();
|
2020-08-13 16:08:00 +00:00
|
|
|
let version = version[1];
|
2021-03-12 11:48:14 +00:00
|
|
|
let version_parts: Vec<&str> = version.split('.').collect();
|
2020-09-26 21:39:30 +00:00
|
|
|
let major_version: i8 = version_parts[0].parse().unwrap();
|
2020-08-13 16:08:00 +00:00
|
|
|
let minor_version: i8 = version_parts[1].parse().unwrap();
|
2020-11-25 16:13:17 +00:00
|
|
|
if major_version >= 11 || minor_version >= 14 {
|
2021-03-31 15:40:42 +00:00
|
|
|
Tts::new(Backends::AvFoundation)
|
2020-08-13 16:08:00 +00:00
|
|
|
} else {
|
2021-03-31 15:40:42 +00:00
|
|
|
Tts::new(Backends::AppKit)
|
2020-08-13 16:08:00 +00:00
|
|
|
}
|
|
|
|
};
|
2020-08-18 20:16:30 +00:00
|
|
|
#[cfg(target_os = "ios")]
|
2021-03-31 15:40:42 +00:00
|
|
|
let tts = Tts::new(Backends::AvFoundation);
|
2020-12-27 15:41:11 +00:00
|
|
|
#[cfg(target_os = "android")]
|
2021-03-31 15:40:42 +00:00
|
|
|
let tts = Tts::new(Backends::Android);
|
2018-12-30 17:13:48 +00:00
|
|
|
tts
|
|
|
|
}
|
|
|
|
|
2022-06-13 15:35:32 +00:00
|
|
|
/// Returns the features supported by this TTS engine
|
2019-03-24 21:30:45 +00:00
|
|
|
pub fn supported_features(&self) -> Features {
|
2022-03-07 23:54:26 +00:00
|
|
|
self.0.read().unwrap().supported_features()
|
2019-03-24 21:30:45 +00:00
|
|
|
}
|
|
|
|
|
2022-06-13 15:35:32 +00:00
|
|
|
/// Speaks the specified text, optionally interrupting current speech.
|
2020-09-22 17:40:03 +00:00
|
|
|
pub fn speak<S: Into<String>>(
|
|
|
|
&mut self,
|
|
|
|
text: S,
|
|
|
|
interrupt: bool,
|
|
|
|
) -> Result<Option<UtteranceId>, Error> {
|
2022-03-07 23:54:26 +00:00
|
|
|
self.0
|
|
|
|
.write()
|
|
|
|
.unwrap()
|
|
|
|
.speak(text.into().as_str(), interrupt)
|
2018-12-14 19:35:49 +00:00
|
|
|
}
|
|
|
|
|
2022-06-13 15:35:32 +00:00
|
|
|
/// Stops current speech.
|
2020-07-06 17:52:18 +00:00
|
|
|
pub fn stop(&mut self) -> Result<&Self, Error> {
|
2019-03-24 21:30:45 +00:00
|
|
|
let Features { stop, .. } = self.supported_features();
|
|
|
|
if stop {
|
2022-03-07 23:54:26 +00:00
|
|
|
self.0.write().unwrap().stop()?;
|
2019-03-24 21:30:45 +00:00
|
|
|
Ok(self)
|
|
|
|
} else {
|
2020-05-18 20:01:28 +00:00
|
|
|
Err(Error::UnsupportedFeature)
|
2019-03-24 21:30:45 +00:00
|
|
|
}
|
2018-12-28 14:49:02 +00:00
|
|
|
}
|
|
|
|
|
2022-06-13 15:35:32 +00:00
|
|
|
/// Returns the minimum rate for this speech synthesizer.
|
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.
2020-05-18 23:12:59 +00:00
|
|
|
pub fn min_rate(&self) -> f32 {
|
2022-03-07 23:54:26 +00:00
|
|
|
self.0.read().unwrap().min_rate()
|
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.
2020-05-18 23:12:59 +00:00
|
|
|
}
|
|
|
|
|
2022-06-13 15:35:32 +00:00
|
|
|
/// Returns the maximum rate for this speech synthesizer.
|
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.
2020-05-18 23:12:59 +00:00
|
|
|
pub fn max_rate(&self) -> f32 {
|
2022-03-07 23:54:26 +00:00
|
|
|
self.0.read().unwrap().max_rate()
|
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.
2020-05-18 23:12:59 +00:00
|
|
|
}
|
|
|
|
|
2022-06-13 15:35:32 +00:00
|
|
|
/// Returns the normal rate for this speech synthesizer.
|
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.
2020-05-18 23:12:59 +00:00
|
|
|
pub fn normal_rate(&self) -> f32 {
|
2022-03-07 23:54:26 +00:00
|
|
|
self.0.read().unwrap().normal_rate()
|
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.
2020-05-18 23:12:59 +00:00
|
|
|
}
|
|
|
|
|
2022-06-13 15:35:32 +00:00
|
|
|
/// Gets the current speech rate.
|
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.
2020-05-18 23:12:59 +00:00
|
|
|
pub fn get_rate(&self) -> Result<f32, Error> {
|
2019-03-24 21:30:45 +00:00
|
|
|
let Features { rate, .. } = self.supported_features();
|
|
|
|
if rate {
|
2022-03-07 23:54:26 +00:00
|
|
|
self.0.read().unwrap().get_rate()
|
2019-03-24 21:30:45 +00:00
|
|
|
} else {
|
2020-05-18 20:01:28 +00:00
|
|
|
Err(Error::UnsupportedFeature)
|
2019-03-24 21:30:45 +00:00
|
|
|
}
|
2018-12-14 19:35:49 +00:00
|
|
|
}
|
|
|
|
|
2022-06-13 15:35:32 +00:00
|
|
|
/// Sets the desired speech rate.
|
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.
2020-05-18 23:12:59 +00:00
|
|
|
pub fn set_rate(&mut self, rate: f32) -> Result<&Self, Error> {
|
2019-03-25 16:34:30 +00:00
|
|
|
let Features {
|
|
|
|
rate: rate_feature, ..
|
|
|
|
} = self.supported_features();
|
2019-03-24 21:30:45 +00:00
|
|
|
if rate_feature {
|
2022-03-07 23:54:26 +00:00
|
|
|
let mut backend = self.0.write().unwrap();
|
|
|
|
if rate < backend.min_rate() || rate > backend.max_rate() {
|
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.
2020-05-18 23:12:59 +00:00
|
|
|
Err(Error::OutOfRange)
|
|
|
|
} else {
|
2022-03-07 23:54:26 +00:00
|
|
|
backend.set_rate(rate)?;
|
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.
2020-05-18 23:12:59 +00:00
|
|
|
Ok(self)
|
|
|
|
}
|
2019-03-24 21:30:45 +00:00
|
|
|
} else {
|
2020-05-18 20:01:28 +00:00
|
|
|
Err(Error::UnsupportedFeature)
|
2019-03-24 21:30:45 +00:00
|
|
|
}
|
2018-12-14 19:35:49 +00:00
|
|
|
}
|
2018-12-15 15:56:13 +00:00
|
|
|
|
2022-06-13 15:35:32 +00:00
|
|
|
/// Returns the minimum pitch for this speech synthesizer.
|
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.
2020-05-18 23:12:59 +00:00
|
|
|
pub fn min_pitch(&self) -> f32 {
|
2022-03-07 23:54:26 +00:00
|
|
|
self.0.read().unwrap().min_pitch()
|
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.
2020-05-18 23:12:59 +00:00
|
|
|
}
|
|
|
|
|
2022-06-13 15:35:32 +00:00
|
|
|
/// Returns the maximum pitch for this speech synthesizer.
|
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.
2020-05-18 23:12:59 +00:00
|
|
|
pub fn max_pitch(&self) -> f32 {
|
2022-03-07 23:54:26 +00:00
|
|
|
self.0.read().unwrap().max_pitch()
|
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.
2020-05-18 23:12:59 +00:00
|
|
|
}
|
|
|
|
|
2022-06-13 15:35:32 +00:00
|
|
|
/// Returns the normal pitch for this speech synthesizer.
|
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.
2020-05-18 23:12:59 +00:00
|
|
|
pub fn normal_pitch(&self) -> f32 {
|
2022-03-07 23:54:26 +00:00
|
|
|
self.0.read().unwrap().normal_pitch()
|
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.
2020-05-18 23:12:59 +00:00
|
|
|
}
|
|
|
|
|
2022-06-13 15:35:32 +00:00
|
|
|
/// Gets the current speech pitch.
|
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.
2020-05-18 23:12:59 +00:00
|
|
|
pub fn get_pitch(&self) -> Result<f32, Error> {
|
2019-03-24 21:30:45 +00:00
|
|
|
let Features { pitch, .. } = self.supported_features();
|
|
|
|
if pitch {
|
2022-03-07 23:54:26 +00:00
|
|
|
self.0.read().unwrap().get_pitch()
|
2019-03-24 21:30:45 +00:00
|
|
|
} else {
|
2020-05-18 20:01:28 +00:00
|
|
|
Err(Error::UnsupportedFeature)
|
2019-03-24 21:30:45 +00:00
|
|
|
}
|
2018-12-15 15:56:13 +00:00
|
|
|
}
|
|
|
|
|
2022-06-13 15:35:32 +00:00
|
|
|
/// Sets the desired speech pitch.
|
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.
2020-05-18 23:12:59 +00:00
|
|
|
pub fn set_pitch(&mut self, pitch: f32) -> Result<&Self, Error> {
|
2019-03-25 16:34:30 +00:00
|
|
|
let Features {
|
|
|
|
pitch: pitch_feature,
|
|
|
|
..
|
|
|
|
} = self.supported_features();
|
2019-03-24 21:30:45 +00:00
|
|
|
if pitch_feature {
|
2022-03-07 23:54:26 +00:00
|
|
|
let mut backend = self.0.write().unwrap();
|
|
|
|
if pitch < backend.min_pitch() || pitch > backend.max_pitch() {
|
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.
2020-05-18 23:12:59 +00:00
|
|
|
Err(Error::OutOfRange)
|
|
|
|
} else {
|
2022-03-07 23:54:26 +00:00
|
|
|
backend.set_pitch(pitch)?;
|
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.
2020-05-18 23:12:59 +00:00
|
|
|
Ok(self)
|
|
|
|
}
|
2019-03-24 21:30:45 +00:00
|
|
|
} else {
|
2020-05-18 20:01:28 +00:00
|
|
|
Err(Error::UnsupportedFeature)
|
2019-03-24 21:30:45 +00:00
|
|
|
}
|
2018-12-15 15:56:13 +00:00
|
|
|
}
|
|
|
|
|
2022-06-13 15:35:32 +00:00
|
|
|
/// Returns the minimum volume for this speech synthesizer.
|
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.
2020-05-18 23:12:59 +00:00
|
|
|
pub fn min_volume(&self) -> f32 {
|
2022-03-07 23:54:26 +00:00
|
|
|
self.0.read().unwrap().min_volume()
|
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.
2020-05-18 23:12:59 +00:00
|
|
|
}
|
|
|
|
|
2022-06-13 15:35:32 +00:00
|
|
|
/// Returns the maximum volume for this speech synthesizer.
|
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.
2020-05-18 23:12:59 +00:00
|
|
|
pub fn max_volume(&self) -> f32 {
|
2022-03-07 23:54:26 +00:00
|
|
|
self.0.read().unwrap().max_volume()
|
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.
2020-05-18 23:12:59 +00:00
|
|
|
}
|
|
|
|
|
2022-06-13 15:35:32 +00:00
|
|
|
/// Returns the normal volume for this speech synthesizer.
|
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.
2020-05-18 23:12:59 +00:00
|
|
|
pub fn normal_volume(&self) -> f32 {
|
2022-03-07 23:54:26 +00:00
|
|
|
self.0.read().unwrap().normal_volume()
|
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.
2020-05-18 23:12:59 +00:00
|
|
|
}
|
|
|
|
|
2022-06-13 15:35:32 +00:00
|
|
|
/// Gets the current speech volume.
|
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.
2020-05-18 23:12:59 +00:00
|
|
|
pub fn get_volume(&self) -> Result<f32, Error> {
|
2019-03-24 21:30:45 +00:00
|
|
|
let Features { volume, .. } = self.supported_features();
|
|
|
|
if volume {
|
2022-03-07 23:54:26 +00:00
|
|
|
self.0.read().unwrap().get_volume()
|
2019-03-24 21:30:45 +00:00
|
|
|
} else {
|
2020-05-18 20:01:28 +00:00
|
|
|
Err(Error::UnsupportedFeature)
|
2019-03-24 21:30:45 +00:00
|
|
|
}
|
2018-12-15 15:56:13 +00:00
|
|
|
}
|
|
|
|
|
2022-06-13 15:35:32 +00:00
|
|
|
/// Sets the desired speech volume.
|
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.
2020-05-18 23:12:59 +00:00
|
|
|
pub fn set_volume(&mut self, volume: f32) -> Result<&Self, Error> {
|
2019-03-25 16:34:30 +00:00
|
|
|
let Features {
|
|
|
|
volume: volume_feature,
|
|
|
|
..
|
|
|
|
} = self.supported_features();
|
2019-03-24 21:30:45 +00:00
|
|
|
if volume_feature {
|
2022-03-07 23:54:26 +00:00
|
|
|
let mut backend = self.0.write().unwrap();
|
|
|
|
if volume < backend.min_volume() || volume > backend.max_volume() {
|
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.
2020-05-18 23:12:59 +00:00
|
|
|
Err(Error::OutOfRange)
|
|
|
|
} else {
|
2022-03-07 23:54:26 +00:00
|
|
|
backend.set_volume(volume)?;
|
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.
2020-05-18 23:12:59 +00:00
|
|
|
Ok(self)
|
|
|
|
}
|
2019-03-24 21:30:45 +00:00
|
|
|
} else {
|
2020-05-18 20:01:28 +00:00
|
|
|
Err(Error::UnsupportedFeature)
|
2019-03-24 21:30:45 +00:00
|
|
|
}
|
2018-12-14 19:35:49 +00:00
|
|
|
}
|
2020-06-02 19:53:14 +00:00
|
|
|
|
2022-06-13 15:35:32 +00:00
|
|
|
/// Returns whether this speech synthesizer is speaking.
|
2020-06-02 19:53:14 +00:00
|
|
|
pub fn is_speaking(&self) -> Result<bool, Error> {
|
|
|
|
let Features { is_speaking, .. } = self.supported_features();
|
|
|
|
if is_speaking {
|
2022-03-07 23:54:26 +00:00
|
|
|
self.0.read().unwrap().is_speaking()
|
2020-06-02 19:53:14 +00:00
|
|
|
} else {
|
|
|
|
Err(Error::UnsupportedFeature)
|
|
|
|
}
|
|
|
|
}
|
2020-09-03 14:50:11 +00:00
|
|
|
|
2022-06-13 15:35:32 +00:00
|
|
|
/// Returns list of available voices.
|
2022-03-30 23:07:08 +00:00
|
|
|
pub fn voices(&self) -> Result<Vec<Voice>, Error> {
|
2022-03-30 23:38:25 +00:00
|
|
|
let Features { voice, .. } = self.supported_features();
|
|
|
|
if voice {
|
|
|
|
self.0.read().unwrap().voices()
|
|
|
|
} else {
|
|
|
|
Err(Error::UnsupportedFeature)
|
|
|
|
}
|
2020-09-03 14:50:11 +00:00
|
|
|
}
|
|
|
|
|
2022-06-13 15:35:32 +00:00
|
|
|
/// Return the current speaking voice.
|
2022-03-31 15:39:39 +00:00
|
|
|
pub fn voice(&self) -> Result<Option<Voice>, Error> {
|
2022-03-30 23:38:25 +00:00
|
|
|
let Features { get_voice, .. } = self.supported_features();
|
|
|
|
if get_voice {
|
2022-03-20 12:02:37 +00:00
|
|
|
self.0.read().unwrap().voice()
|
2020-09-03 14:50:11 +00:00
|
|
|
} else {
|
|
|
|
Err(Error::UnsupportedFeature)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-13 15:35:32 +00:00
|
|
|
/// Set speaking voice.
|
2022-03-30 23:38:25 +00:00
|
|
|
pub fn set_voice(&mut self, voice: &Voice) -> Result<(), Error> {
|
2020-09-03 14:50:11 +00:00
|
|
|
let Features {
|
2022-03-30 23:38:25 +00:00
|
|
|
voice: voice_feature,
|
2020-09-03 14:50:11 +00:00
|
|
|
..
|
2022-03-20 12:02:37 +00:00
|
|
|
} = self.supported_features();
|
2022-03-30 23:38:25 +00:00
|
|
|
if voice_feature {
|
|
|
|
self.0.write().unwrap().set_voice(voice)
|
2020-09-03 14:50:11 +00:00
|
|
|
} else {
|
|
|
|
Err(Error::UnsupportedFeature)
|
|
|
|
}
|
|
|
|
}
|
2020-09-26 16:20:10 +00:00
|
|
|
|
2022-06-13 15:35:32 +00:00
|
|
|
/// Called when this speech synthesizer begins speaking an utterance.
|
2020-09-25 16:08:19 +00:00
|
|
|
pub fn on_utterance_begin(
|
|
|
|
&self,
|
|
|
|
callback: Option<Box<dyn FnMut(UtteranceId)>>,
|
|
|
|
) -> Result<(), Error> {
|
2020-09-23 15:12:51 +00:00
|
|
|
let Features {
|
|
|
|
utterance_callbacks,
|
|
|
|
..
|
|
|
|
} = self.supported_features();
|
|
|
|
if utterance_callbacks {
|
|
|
|
let mut callbacks = CALLBACKS.lock().unwrap();
|
2022-03-07 23:54:26 +00:00
|
|
|
let id = self.0.read().unwrap().id().unwrap();
|
2020-09-23 15:12:51 +00:00
|
|
|
let mut callbacks = callbacks.get_mut(&id).unwrap();
|
|
|
|
callbacks.utterance_begin = callback;
|
|
|
|
Ok(())
|
|
|
|
} else {
|
|
|
|
Err(Error::UnsupportedFeature)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-13 15:35:32 +00:00
|
|
|
/// Called when this speech synthesizer finishes speaking an utterance.
|
2020-09-25 16:08:19 +00:00
|
|
|
pub fn on_utterance_end(
|
|
|
|
&self,
|
|
|
|
callback: Option<Box<dyn FnMut(UtteranceId)>>,
|
|
|
|
) -> Result<(), Error> {
|
2020-09-23 15:12:51 +00:00
|
|
|
let Features {
|
|
|
|
utterance_callbacks,
|
|
|
|
..
|
|
|
|
} = self.supported_features();
|
|
|
|
if utterance_callbacks {
|
|
|
|
let mut callbacks = CALLBACKS.lock().unwrap();
|
2022-03-07 23:54:26 +00:00
|
|
|
let id = self.0.read().unwrap().id().unwrap();
|
2020-09-23 15:12:51 +00:00
|
|
|
let mut callbacks = callbacks.get_mut(&id).unwrap();
|
|
|
|
callbacks.utterance_end = callback;
|
|
|
|
Ok(())
|
|
|
|
} else {
|
|
|
|
Err(Error::UnsupportedFeature)
|
|
|
|
}
|
|
|
|
}
|
2020-10-14 08:51:08 +00:00
|
|
|
|
2022-06-13 15:35:32 +00:00
|
|
|
/// Called when this speech synthesizer is stopped and still has utterances in its queue.
|
2020-10-08 12:56:45 +00:00
|
|
|
pub fn on_utterance_stop(
|
|
|
|
&self,
|
2020-11-03 17:03:55 +00:00
|
|
|
callback: Option<Box<dyn FnMut(UtteranceId)>>,
|
2020-10-08 12:56:45 +00:00
|
|
|
) -> Result<(), Error> {
|
|
|
|
let Features {
|
|
|
|
utterance_callbacks,
|
|
|
|
..
|
|
|
|
} = self.supported_features();
|
|
|
|
if utterance_callbacks {
|
|
|
|
let mut callbacks = CALLBACKS.lock().unwrap();
|
2022-03-07 23:54:26 +00:00
|
|
|
let id = self.0.read().unwrap().id().unwrap();
|
2020-10-08 12:56:45 +00:00
|
|
|
let mut callbacks = callbacks.get_mut(&id).unwrap();
|
|
|
|
callbacks.utterance_stop = callback;
|
|
|
|
Ok(())
|
|
|
|
} else {
|
|
|
|
Err(Error::UnsupportedFeature)
|
|
|
|
}
|
|
|
|
}
|
2021-05-12 00:37:56 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Returns `true` if a screen reader is available to provide speech.
|
|
|
|
*/
|
2021-05-12 01:18:14 +00:00
|
|
|
#[allow(unreachable_code)]
|
2021-05-12 00:37:56 +00:00
|
|
|
pub fn screen_reader_available() -> bool {
|
2021-05-12 01:18:14 +00:00
|
|
|
#[cfg(target_os = "windows")]
|
|
|
|
{
|
2021-05-12 00:37:56 +00:00
|
|
|
#[cfg(feature = "tolk")]
|
|
|
|
{
|
2021-05-20 18:59:02 +00:00
|
|
|
let tolk = Tolk::new();
|
|
|
|
return tolk.detect_screen_reader().is_some();
|
2021-05-12 00:37:56 +00:00
|
|
|
}
|
|
|
|
#[cfg(not(feature = "tolk"))]
|
|
|
|
return false;
|
|
|
|
}
|
2021-05-12 01:18:14 +00:00
|
|
|
false
|
2021-05-12 00:37:56 +00:00
|
|
|
}
|
2020-09-23 15:12:51 +00:00
|
|
|
}
|
|
|
|
|
2022-03-30 23:07:08 +00:00
|
|
|
impl Drop for Tts {
|
2020-09-23 15:12:51 +00:00
|
|
|
fn drop(&mut self) {
|
2022-03-10 17:12:46 +00:00
|
|
|
if Arc::strong_count(&self.0) <= 1 {
|
|
|
|
if let Some(id) = self.0.read().unwrap().id() {
|
|
|
|
let mut callbacks = CALLBACKS.lock().unwrap();
|
|
|
|
callbacks.remove(&id);
|
|
|
|
}
|
2020-09-23 15:12:51 +00:00
|
|
|
}
|
|
|
|
}
|
2018-12-14 19:35:49 +00:00
|
|
|
}
|
2022-03-30 15:54:30 +00:00
|
|
|
|
2022-07-20 23:25:14 +00:00
|
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
2022-03-30 15:54:30 +00:00
|
|
|
pub enum Gender {
|
|
|
|
Male,
|
|
|
|
Female,
|
|
|
|
}
|
|
|
|
|
2022-07-20 23:25:14 +00:00
|
|
|
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
2022-03-30 23:07:08 +00:00
|
|
|
pub struct Voice {
|
2022-03-31 15:39:39 +00:00
|
|
|
pub(crate) id: String,
|
|
|
|
pub(crate) name: String,
|
2022-03-31 18:18:57 +00:00
|
|
|
pub(crate) gender: Option<Gender>,
|
2022-03-31 15:39:39 +00:00
|
|
|
pub(crate) language: LanguageIdentifier,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Voice {
|
|
|
|
pub fn id(&self) -> String {
|
|
|
|
self.id.clone()
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn name(&self) -> String {
|
|
|
|
self.name.clone()
|
|
|
|
}
|
|
|
|
|
2022-03-31 18:18:57 +00:00
|
|
|
pub fn gender(&self) -> Option<Gender> {
|
2022-03-31 15:39:39 +00:00
|
|
|
self.gender
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn language(&self) -> LanguageIdentifier {
|
|
|
|
self.language.clone()
|
|
|
|
}
|
2022-03-30 15:54:30 +00:00
|
|
|
}
|