From 65eeddc1adf2ec86eacfcd9d87f7678952c9875c Mon Sep 17 00:00:00 2001 From: Nolan Darilek Date: Tue, 18 Aug 2020 14:24:00 -0500 Subject: [PATCH 01/15] Remove missing targets. --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 00245b3..ed74a63 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -35,5 +35,5 @@ jobs: - run: | cargo build --release cargo install cargo-lipo - rustup target add aarch64-apple-ios armv7-apple-ios armv7s-apple-ios + rustup target add aarch64-apple-ios cargo lipo --release From 3b3be830c6fc2aeb1c460683ce4b10fe1896f59a Mon Sep 17 00:00:00 2001 From: Nolan Darilek Date: Tue, 18 Aug 2020 14:59:48 -0500 Subject: [PATCH 02/15] Update iOS build targets. --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ed74a63..7d7b211 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -35,5 +35,5 @@ jobs: - run: | cargo build --release cargo install cargo-lipo - rustup target add aarch64-apple-ios + rustup target add aarch64-apple-ios x86_64-apple-ios cargo lipo --release From 2f85c3b2bf38d730338653245ffc431fc89ea23f Mon Sep 17 00:00:00 2001 From: Nolan Darilek Date: Tue, 18 Aug 2020 15:16:30 -0500 Subject: [PATCH 03/15] Add iOS build. --- Cargo.toml | 5 ++--- src/backends/av_foundation.rs | 4 ++-- src/backends/mod.rs | 4 ++-- src/lib.rs | 9 ++++++--- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index fed932d..4a2f44e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,8 +9,7 @@ exclude = ["*.cfg", "*.yml"] edition = "2018" [lib] -name = "tts" -crate-type = ["staticlib", "cdylib"] +crate-type = ["lib", "staticlib"] [dependencies] log = "0.4" @@ -27,7 +26,7 @@ tts_winrt_bindings = { version = "0.1", path="winrt_bindings" } [target.'cfg(target_os = "linux")'.dependencies] speech-dispatcher = "0.4" -[target.'cfg(target_os = "macos")'.dependencies] +[target.'cfg(any(target_os = "macos", target_os = "ios"))'.dependencies] cocoa-foundation = "0.1" libc = "0.2" objc = "0.2" diff --git a/src/backends/av_foundation.rs b/src/backends/av_foundation.rs index 13ea05c..a04eaea 100644 --- a/src/backends/av_foundation.rs +++ b/src/backends/av_foundation.rs @@ -1,4 +1,4 @@ -#[cfg(target_os = "macos")] +#[cfg(any(target_os = "macos", target_os = "ios"))] #[link(name = "AVFoundation", kind = "framework")] use cocoa_foundation::base::{id, nil}; use cocoa_foundation::foundation::NSString; @@ -132,7 +132,7 @@ impl Backend for AvFoundation { fn is_speaking(&self) -> Result { let is_speaking: i8 = unsafe { msg_send![self.synth, isSpeaking] }; - Ok(is_speaking == YES) + Ok(is_speaking == 1) } } diff --git a/src/backends/mod.rs b/src/backends/mod.rs index 7971db1..c999faf 100644 --- a/src/backends/mod.rs +++ b/src/backends/mod.rs @@ -13,7 +13,7 @@ mod web; #[cfg(target_os = "macos")] mod appkit; -#[cfg(target_os = "macos")] +#[cfg(any(target_os = "macos", target_os = "ios"))] mod av_foundation; #[cfg(target_os = "linux")] @@ -28,5 +28,5 @@ pub use self::web::*; #[cfg(target_os = "macos")] pub use self::appkit::*; -#[cfg(target_os = "macos")] +#[cfg(any(target_os = "macos", target_os = "ios"))] pub use self::av_foundation::*; diff --git a/src/lib.rs b/src/lib.rs index d5d8210..d1dda48 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,8 +11,9 @@ * * WebAssembly */ +use std::boxed::Box; #[cfg(target_os = "macos")] -use std::{boxed::Box, ffi::CStr}; +use std::ffi::CStr; #[cfg(target_os = "macos")] use cocoa_foundation::base::id; @@ -35,7 +36,7 @@ pub enum Backends { WinRT, #[cfg(target_os = "macos")] AppKit, - #[cfg(target_os = "macos")] + #[cfg(any(target_os = "macos", target_os = "ios"))] AvFoundation, } @@ -122,7 +123,7 @@ impl TTS { } #[cfg(target_os = "macos")] Backends::AppKit => Ok(TTS(Box::new(backends::AppKit::new()))), - #[cfg(target_os = "macos")] + #[cfg(any(target_os = "macos", target_os = "ios"))] Backends::AvFoundation => Ok(TTS(Box::new(backends::AvFoundation::new()))), } } @@ -156,6 +157,8 @@ impl TTS { TTS::new(Backends::AppKit) } }; + #[cfg(target_os = "ios")] + let tts = TTS::new(Backends::AvFoundation); tts } From a1e4215ea7587e16c3d7d3b7e5ec5891839e0747 Mon Sep 17 00:00:00 2001 From: Nolan Darilek Date: Tue, 18 Aug 2020 15:19:34 -0500 Subject: [PATCH 04/15] Normal volume of speech-dispatcher is 100, not 0. --- src/backends/speech_dispatcher.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backends/speech_dispatcher.rs b/src/backends/speech_dispatcher.rs index 6a36d6c..0239016 100644 --- a/src/backends/speech_dispatcher.rs +++ b/src/backends/speech_dispatcher.rs @@ -98,7 +98,7 @@ impl Backend for SpeechDispatcher { } fn normal_volume(&self) -> f32 { - 0. + 100. } fn get_volume(&self) -> Result { From 7bcbda15b303771b7ba2291baae442326d7b82ff Mon Sep 17 00:00:00 2001 From: Nolan Darilek Date: Tue, 18 Aug 2020 15:22:12 -0500 Subject: [PATCH 05/15] Update supported platforms. --- README.md | 4 ++-- src/lib.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 921b598..c76fe23 100644 --- a/README.md +++ b/README.md @@ -8,5 +8,5 @@ This library provides a high-level Text-To-Speech (TTS) interface supporting var * Linux via [Speech Dispatcher](https://freebsoft.org/speechd) * MacOS * AppKit on MacOS 10.13 and below - * AVFoundation on MacOS 10.14 and, eventually, iDevices -* WebAssembly \ No newline at end of file + * AVFoundation on MacOS 10.14 and above, and iOS +* WebAssembly diff --git a/src/lib.rs b/src/lib.rs index d1dda48..b64c86b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,7 +7,7 @@ * * Linux via [Speech Dispatcher](https://freebsoft.org/speechd) * * MacOS * * AppKit on MacOS 10.13 and below - * * AVFoundation on MacOS 10.14 and, eventually, iDevices + * * AVFoundation on MacOS 10.14 and above, and iOS * * WebAssembly */ From bdace524b973c913aabe5096c18a8c5b4d20b5fe Mon Sep 17 00:00:00 2001 From: Nolan Darilek Date: Tue, 18 Aug 2020 15:22:46 -0500 Subject: [PATCH 06/15] Bump version. --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 4a2f44e..aee2d9a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tts" -version = "0.5.0" +version = "0.6.0" authors = ["Nolan Darilek "] repository = "https://github.com/ndarilek/tts-rs" description = "High-level Text-To-Speech (TTS) interface" From 907b8283154c9a8a8f0b36bccc17236fda5eff78 Mon Sep 17 00:00:00 2001 From: Nolan Darilek Date: Tue, 18 Aug 2020 15:26:23 -0500 Subject: [PATCH 07/15] Add iOS test to release builds. --- .github/workflows/release.yml | 3 +++ .github/workflows/test.yml | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ef0f86b..448c9ab 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -35,6 +35,9 @@ jobs: - uses: actions/checkout@v2 - run: | cargo build --release + rustup target add aarch64-apple-ios x86_64-apple-ios + cargo install cargo-lipo + cargo lipo --release publish_winrt_bindings: name: Publish winrt_bindings diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7d7b211..9404d8a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -34,6 +34,6 @@ jobs: - uses: actions/checkout@v2 - run: | cargo build --release - cargo install cargo-lipo rustup target add aarch64-apple-ios x86_64-apple-ios + cargo install cargo-lipo cargo lipo --release From 045b80c9215d5106fa8760b197afa91a2f5717aa Mon Sep 17 00:00:00 2001 From: Nolan Darilek Date: Tue, 18 Aug 2020 15:27:07 -0500 Subject: [PATCH 08/15] Don't scream if winrt_bindings fails to build. --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 448c9ab..f53a0d6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -52,7 +52,7 @@ jobs: cargo login $CARGO_TOKEN cd winrt_bindings cargo package - cargo publish + cargo publish || true publish: name: Publish From 951e31b284a5b50a4a2c5313000adb6d35c9ae84 Mon Sep 17 00:00:00 2001 From: Nolan Darilek Date: Wed, 19 Aug 2020 21:28:30 -0500 Subject: [PATCH 09/15] Implement `is_speaking` For Speech-dispatcher. --- Cargo.toml | 5 ++-- src/backends/speech_dispatcher.rs | 49 +++++++++++++++++++++++++++++-- 2 files changed, 49 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index aee2d9a..17afd2a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tts" -version = "0.6.0" +version = "0.6.1" authors = ["Nolan Darilek "] repository = "https://github.com/ndarilek/tts-rs" description = "High-level Text-To-Speech (TTS) interface" @@ -12,6 +12,7 @@ edition = "2018" crate-type = ["lib", "staticlib"] [dependencies] +lazy_static = "1" log = "0.4" thiserror = "1" @@ -24,7 +25,7 @@ winrt = "0.7" tts_winrt_bindings = { version = "0.1", path="winrt_bindings" } [target.'cfg(target_os = "linux")'.dependencies] -speech-dispatcher = "0.4" +speech-dispatcher = "0.6" [target.'cfg(any(target_os = "macos", target_os = "ios"))'.dependencies] cocoa-foundation = "0.1" diff --git a/src/backends/speech_dispatcher.rs b/src/backends/speech_dispatcher.rs index 0239016..b12f51a 100644 --- a/src/backends/speech_dispatcher.rs +++ b/src/backends/speech_dispatcher.rs @@ -1,4 +1,8 @@ #[cfg(target_os = "linux")] +use std::collections::HashMap; +use std::sync::Mutex; + +use lazy_static::*; use log::{info, trace}; use speech_dispatcher::*; @@ -6,11 +10,41 @@ use crate::{Backend, Error, Features}; pub struct SpeechDispatcher(Connection); +lazy_static! { + static ref SPEAKING: Mutex> = { + let m: HashMap = HashMap::new(); + Mutex::new(m) + }; +} + impl SpeechDispatcher { pub fn new() -> Self { info!("Initializing SpeechDispatcher backend"); let connection = speech_dispatcher::Connection::open("tts", "tts", "tts", Mode::Single); - SpeechDispatcher(connection) + let sd = SpeechDispatcher(connection); + let mut speaking = SPEAKING.lock().unwrap(); + speaking.insert(sd.0.client_id(), false); + sd.0.on_begin(Some(|_msg_id, client_id| { + let mut speaking = SPEAKING.lock().unwrap(); + speaking.insert(client_id, true); + })); + sd.0.on_end(Some(|_msg_id, client_id| { + let mut speaking = SPEAKING.lock().unwrap(); + speaking.insert(client_id, false); + })); + sd.0.on_cancel(Some(|_msg_id, client_id| { + let mut speaking = SPEAKING.lock().unwrap(); + speaking.insert(client_id, false); + })); + sd.0.on_pause(Some(|_msg_id, client_id| { + let mut speaking = SPEAKING.lock().unwrap(); + speaking.insert(client_id, false); + })); + sd.0.on_resume(Some(|_msg_id, client_id| { + let mut speaking = SPEAKING.lock().unwrap(); + speaking.insert(client_id, true); + })); + sd } } @@ -21,7 +55,7 @@ impl Backend for SpeechDispatcher { rate: true, pitch: true, volume: true, - is_speaking: false, + is_speaking: true, } } @@ -111,6 +145,15 @@ impl Backend for SpeechDispatcher { } fn is_speaking(&self) -> Result { - unimplemented!() + let speaking = SPEAKING.lock().unwrap(); + let is_speaking = speaking.get(&self.0.client_id()).unwrap(); + Ok(*is_speaking) + } +} + +impl Drop for SpeechDispatcher { + fn drop(&mut self) { + let mut speaking = SPEAKING.lock().unwrap(); + speaking.remove(&self.0.client_id()); } } From 1507527175beca9486ae3b7536bf3872dd7b4555 Mon Sep 17 00:00:00 2001 From: Nolan Darilek Date: Mon, 24 Aug 2020 16:44:00 -0500 Subject: [PATCH 10/15] Add `Default` implementation for `Features` so backends need only specify features they actually support. --- src/backends/appkit.rs | 2 +- src/backends/tolk.rs | 5 +---- src/lib.rs | 12 ++++++++++++ 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/backends/appkit.rs b/src/backends/appkit.rs index 6bfbf06..dcc68d9 100644 --- a/src/backends/appkit.rs +++ b/src/backends/appkit.rs @@ -95,9 +95,9 @@ impl Backend for AppKit { Features { stop: true, rate: true, - pitch: false, volume: true, is_speaking: true, + ..Default::default(), } } diff --git a/src/backends/tolk.rs b/src/backends/tolk.rs index 176fdb7..b56a9b0 100644 --- a/src/backends/tolk.rs +++ b/src/backends/tolk.rs @@ -22,10 +22,7 @@ impl Backend for Tolk { fn supported_features(&self) -> Features { Features { stop: true, - rate: false, - pitch: false, - volume: false, - is_speaking: false, + ..Default::default(), } } diff --git a/src/lib.rs b/src/lib.rs index b64c86b..56b9f32 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -48,6 +48,18 @@ pub struct Features { pub is_speaking: bool, } +impl Default for Features { + fn default() -> Self { + Self { + stop: false, + rate: false, + pitch: false, + volume: false, + is_speaking: false, + } + } +} + #[derive(Debug, Error)] pub enum Error { #[error("IO error: {0}")] From d3ffd5078f21ca23ccd75a2098a6bb05f7b04550 Mon Sep 17 00:00:00 2001 From: Nolan Darilek Date: Mon, 24 Aug 2020 16:46:57 -0500 Subject: [PATCH 11/15] cargo fmt --- src/backends/appkit.rs | 2 +- src/backends/tolk.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/backends/appkit.rs b/src/backends/appkit.rs index dcc68d9..8584188 100644 --- a/src/backends/appkit.rs +++ b/src/backends/appkit.rs @@ -97,7 +97,7 @@ impl Backend for AppKit { rate: true, volume: true, is_speaking: true, - ..Default::default(), + ..Default::default() } } diff --git a/src/backends/tolk.rs b/src/backends/tolk.rs index b56a9b0..370da65 100644 --- a/src/backends/tolk.rs +++ b/src/backends/tolk.rs @@ -22,7 +22,7 @@ impl Backend for Tolk { fn supported_features(&self) -> Features { Features { stop: true, - ..Default::default(), + ..Default::default() } } From 6c091f3284c360c6d58d4beff10bc417f49071aa Mon Sep 17 00:00:00 2001 From: Nolan Darilek Date: Tue, 25 Aug 2020 11:50:25 -0500 Subject: [PATCH 12/15] Switch Speech-dispatcher initialization to threaded mode so callbacks work and `is_speaking` is correct. --- Cargo.toml | 2 +- src/backends/speech_dispatcher.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 17afd2a..af088f3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tts" -version = "0.6.1" +version = "0.6.2" authors = ["Nolan Darilek "] repository = "https://github.com/ndarilek/tts-rs" description = "High-level Text-To-Speech (TTS) interface" diff --git a/src/backends/speech_dispatcher.rs b/src/backends/speech_dispatcher.rs index b12f51a..009a8fb 100644 --- a/src/backends/speech_dispatcher.rs +++ b/src/backends/speech_dispatcher.rs @@ -20,7 +20,7 @@ lazy_static! { impl SpeechDispatcher { pub fn new() -> Self { info!("Initializing SpeechDispatcher backend"); - let connection = speech_dispatcher::Connection::open("tts", "tts", "tts", Mode::Single); + let connection = speech_dispatcher::Connection::open("tts", "tts", "tts", Mode::Threaded); let sd = SpeechDispatcher(connection); let mut speaking = SPEAKING.lock().unwrap(); speaking.insert(sd.0.client_id(), false); From 665013fdff5105d54018bb2a268c369a12779f99 Mon Sep 17 00:00:00 2001 From: Nolan Darilek Date: Wed, 2 Sep 2020 11:40:08 -0500 Subject: [PATCH 13/15] Split text sent to Tolk backend to account for some sort of length limit. Tolk seems to fail on strings larger than 325 characters in length. Here we: * Send any strings with 300 or fewer characters through directly. * For larger strings, split on whitespace boundaries, then create and send buffers of 300 or fewer characters. This may not handle internationalized text, and may not handle someone bombarding TTS with a giant word. PRs for either welcome. --- src/backends/tolk.rs | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/backends/tolk.rs b/src/backends/tolk.rs index 370da65..c71cdfa 100644 --- a/src/backends/tolk.rs +++ b/src/backends/tolk.rs @@ -28,7 +28,25 @@ impl Backend for Tolk { fn speak(&mut self, text: &str, interrupt: bool) -> Result<(), Error> { trace!("speak({}, {})", text, interrupt); - self.0.speak(text, interrupt); + const BUFFER_LENGTH: usize = 300; + if text.len() <= BUFFER_LENGTH { + self.0.speak(text, interrupt); + } else { + if interrupt { + self.stop()?; + } + let tokens = text.split_whitespace(); + let mut buffer = String::new(); + for token in tokens { + if buffer.len() + token.len() > BUFFER_LENGTH { + self.0.speak(buffer, false); + buffer = String::new(); + } else { + buffer.push_str(token); + buffer.push(' '); + } + } + } Ok(()) } From 81b23330e965a88fb52896051b9a0e04e585756f Mon Sep 17 00:00:00 2001 From: Nolan Darilek Date: Wed, 2 Sep 2020 15:37:34 -0500 Subject: [PATCH 14/15] Move iOS build into separate CI run to see if this odd bug is triggered. --- .github/workflows/test.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9404d8a..49bc04b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -5,7 +5,6 @@ on: pull_request: jobs: - build_linux: name: Build Linux runs-on: ubuntu-latest @@ -34,6 +33,13 @@ jobs: - uses: actions/checkout@v2 - run: | cargo build --release + + build_ios: + name: Build iOS + runs-on: macos-latest + steps: + - uses: actions/checkout@v2 + - run: | rustup target add aarch64-apple-ios x86_64-apple-ios cargo install cargo-lipo cargo lipo --release From d3ca27c7074cac36738f20052d178aff28e910e5 Mon Sep 17 00:00:00 2001 From: Nolan Darilek Date: Wed, 2 Sep 2020 15:52:11 -0500 Subject: [PATCH 15/15] Force Rust toolchain update, and separate out iOS build. --- .github/workflows/release.yml | 14 +++++++++++++- .github/workflows/test.yml | 4 ++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f53a0d6..973b9bf 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -6,7 +6,6 @@ on: - "v*" jobs: - build_linux: name: Build Linux runs-on: ubuntu-latest @@ -15,6 +14,7 @@ jobs: - run: | sudo apt-get update sudo apt-get install -y libspeechd-dev + rustup update cargo build --release rustup target add wasm32-unknown-unknown cargo build --release --target wasm32-unknown-unknown @@ -26,6 +26,7 @@ jobs: - uses: actions/checkout@v2 - run: | choco install -y llvm + rustup update cargo build --release build_macos: @@ -34,7 +35,16 @@ jobs: steps: - uses: actions/checkout@v2 - run: | + rustup update cargo build --release + + build_ios: + name: Build iOS + runs-on: macos-latest + steps: + - uses: actions/checkout@v2 + - run: | + rustup update rustup target add aarch64-apple-ios x86_64-apple-ios cargo install cargo-lipo cargo lipo --release @@ -49,6 +59,7 @@ jobs: - uses: actions/checkout@v2 - run: | choco install -y llvm + rustup update cargo login $CARGO_TOKEN cd winrt_bindings cargo package @@ -65,5 +76,6 @@ jobs: - run: | sudo apt-get update sudo apt-get install -y libspeechd-dev + rustup update cargo login $CARGO_TOKEN cargo publish diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 49bc04b..d49d480 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -13,6 +13,7 @@ jobs: - run: | sudo apt-get update sudo apt-get install -y libspeechd-dev + rustup update cargo build --release rustup target add wasm32-unknown-unknown cargo build --release --target wasm32-unknown-unknown @@ -24,6 +25,7 @@ jobs: - uses: actions/checkout@v2 - run: | choco install -y llvm + rustup update cargo build --release build_macos: @@ -32,6 +34,7 @@ jobs: steps: - uses: actions/checkout@v2 - run: | + rustup update cargo build --release build_ios: @@ -40,6 +43,7 @@ jobs: steps: - uses: actions/checkout@v2 - run: | + rustup update rustup target add aarch64-apple-ios x86_64-apple-ios cargo install cargo-lipo cargo lipo --release