From 5b0d1b662137f24119eb873bc0a16c7e34982175 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Caddet?= Date: Thu, 3 Sep 2020 16:50:11 +0200 Subject: [PATCH 01/13] Add voices feature Implemented for AVFoundation backend but set_voice has no effect for now Warning: does not build on Linux or windows for now --- Cargo.toml | 1 + src/backends/appkit.rs | 12 +++++++++ src/backends/av_foundation.rs | 18 +++++++++++++ src/backends/av_foundation/voices.rs | 31 ++++++++++++++++++++++ src/lib.rs | 39 ++++++++++++++++++++++++++++ 5 files changed, 101 insertions(+) create mode 100644 src/backends/av_foundation/voices.rs diff --git a/Cargo.toml b/Cargo.toml index af088f3..7afb5e7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,7 @@ speech-dispatcher = "0.6" [target.'cfg(any(target_os = "macos", target_os = "ios"))'.dependencies] cocoa-foundation = "0.1" +core-foundation = "0.9" libc = "0.2" objc = "0.2" diff --git a/src/backends/appkit.rs b/src/backends/appkit.rs index 8584188..ec32729 100644 --- a/src/backends/appkit.rs +++ b/src/backends/appkit.rs @@ -195,6 +195,18 @@ impl Backend for AppKit { let is_speaking: i8 = unsafe { msg_send![self.0, isSpeaking] }; Ok(is_speaking == YES) } + + fn voice(&self) -> Result { + unimplemented!() + } + + fn list_voices(&self) -> Vec { + unimplemented!() + } + + fn set_voice(&self, voice: String) -> Result<(),Error> { + unimplemented!() + } } impl Drop for AppKit { diff --git a/src/backends/av_foundation.rs b/src/backends/av_foundation.rs index a04eaea..70e3f33 100644 --- a/src/backends/av_foundation.rs +++ b/src/backends/av_foundation.rs @@ -8,11 +8,15 @@ use objc::*; use crate::{Backend, Error, Features}; +mod voices; +use voices::AVSpeechSynthesisVoice; + pub struct AvFoundation { synth: *mut Object, rate: f32, volume: f32, pitch: f32, + voice: AVSpeechSynthesisVoice, } impl AvFoundation { @@ -25,6 +29,7 @@ impl AvFoundation { rate: 0.5, volume: 1., pitch: 1., + voice: AVSpeechSynthesisVoice::new(), } } } @@ -38,6 +43,7 @@ impl Backend for AvFoundation { pitch: true, volume: true, is_speaking: true, + voices: true, } } @@ -134,6 +140,18 @@ impl Backend for AvFoundation { let is_speaking: i8 = unsafe { msg_send![self.synth, isSpeaking] }; Ok(is_speaking == 1) } + + fn voice(&self) -> Result { + Ok(self.voice.identifier()) + } + + fn list_voices(&self) -> Vec { + AVSpeechSynthesisVoice::list().iter().map(|v| {v.identifier()}).collect() + } + + fn set_voice(&self, voice: String) -> Result<(),Error> { + Ok(()) + } } impl Drop for AvFoundation { diff --git a/src/backends/av_foundation/voices.rs b/src/backends/av_foundation/voices.rs new file mode 100644 index 0000000..98c6036 --- /dev/null +++ b/src/backends/av_foundation/voices.rs @@ -0,0 +1,31 @@ + +use objc::runtime::*; +use objc::*; +use core_foundation::array::CFArray; +use core_foundation::string::CFString; + +#[derive(Copy,Clone)] +pub struct AVSpeechSynthesisVoice(*const Object); + +impl AVSpeechSynthesisVoice { + pub fn new() -> Self { + Self::list()[0] + } + + pub fn list() -> Vec { + let voices: CFArray = unsafe{msg_send![class!(AVSpeechSynthesisVoice), speechVoices]}; + voices.iter().map(|v| { + AVSpeechSynthesisVoice{0: *v as *mut Object} + }).collect() + } + + pub fn name(self) -> String { + let name: CFString = unsafe{msg_send![self.0, name]}; + name.to_string() + } + + pub fn identifier(self) -> String { + let identifier: CFString = unsafe{msg_send![self.0, identifier]}; + identifier.to_string() + } +} diff --git a/src/lib.rs b/src/lib.rs index 56b9f32..af6db3f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -46,6 +46,7 @@ pub struct Features { pub pitch: bool, pub volume: bool, pub is_speaking: bool, + pub voices: bool, } impl Default for Features { @@ -56,6 +57,7 @@ impl Default for Features { pitch: false, volume: false, is_speaking: false, + voices: false, } } } @@ -98,6 +100,9 @@ pub trait Backend { fn get_volume(&self) -> Result; fn set_volume(&mut self, volume: f32) -> Result<(), Error>; fn is_speaking(&self) -> Result; + fn voice(&self) -> Result; + fn list_voices(&self) -> Vec; + fn set_voice(&self, voice: String) -> Result<(),Error>; } pub struct TTS(Box); @@ -371,4 +376,38 @@ impl TTS { Err(Error::UnsupportedFeature) } } + + /** + * Returns list of available voices. + */ + pub fn list_voices(&self) -> Vec { + self.0.list_voices() + } + + /** + * Return the current speaking voice. + */ + pub fn voice(&self) -> Result { + let Features { voices, .. } = self.supported_features(); + if voices { + self.0.voice() + } else { + Err(Error::UnsupportedFeature) + } + } + + /** + * Set speaking voice. + */ + pub fn set_voice(&self, voice: String) -> Result<(),Error> { + let Features { + voices: voices_feature, + .. + } = self.0.supported_features(); + if voices_feature { + self.0.set_voice(voice) + } else { + Err(Error::UnsupportedFeature) + } + } } From 6ed94686f3acccc095a1157b230693212a8e7e37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Caddet?= Date: Thu, 3 Sep 2020 18:40:32 +0200 Subject: [PATCH 02/13] implement set_voice for AVFoundation backend - TODO: test the implementation - fixed: set_voice mutability of self parameter --- src/backends/appkit.rs | 2 +- src/backends/av_foundation.rs | 6 ++++-- src/backends/av_foundation/voices.rs | 10 ++++++++-- src/lib.rs | 4 ++-- 4 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/backends/appkit.rs b/src/backends/appkit.rs index ec32729..164cf53 100644 --- a/src/backends/appkit.rs +++ b/src/backends/appkit.rs @@ -204,7 +204,7 @@ impl Backend for AppKit { unimplemented!() } - fn set_voice(&self, voice: String) -> Result<(),Error> { + fn set_voice(&mut self, voice: String) -> Result<(),Error> { unimplemented!() } } diff --git a/src/backends/av_foundation.rs b/src/backends/av_foundation.rs index 70e3f33..c51988a 100644 --- a/src/backends/av_foundation.rs +++ b/src/backends/av_foundation.rs @@ -29,7 +29,7 @@ impl AvFoundation { rate: 0.5, volume: 1., pitch: 1., - voice: AVSpeechSynthesisVoice::new(), + voice: AVSpeechSynthesisVoice::default(), } } } @@ -59,6 +59,7 @@ impl Backend for AvFoundation { let _: () = msg_send![utterance, setRate: self.rate]; let _: () = msg_send![utterance, setVolume: self.volume]; let _: () = msg_send![utterance, setPitchMultiplier: self.pitch]; + let _: () = msg_send![utterance, setVoice: self.voice]; let _: () = msg_send![self.synth, speakUtterance: utterance]; } Ok(()) @@ -149,7 +150,8 @@ impl Backend for AvFoundation { AVSpeechSynthesisVoice::list().iter().map(|v| {v.identifier()}).collect() } - fn set_voice(&self, voice: String) -> Result<(),Error> { + fn set_voice(&mut self, voice: String) -> Result<(),Error> { + self.voice = AVSpeechSynthesisVoice::new(voice); Ok(()) } } diff --git a/src/backends/av_foundation/voices.rs b/src/backends/av_foundation/voices.rs index 98c6036..a23c14a 100644 --- a/src/backends/av_foundation/voices.rs +++ b/src/backends/av_foundation/voices.rs @@ -8,8 +8,14 @@ use core_foundation::string::CFString; pub struct AVSpeechSynthesisVoice(*const Object); impl AVSpeechSynthesisVoice { - pub fn new() -> Self { - Self::list()[0] + pub fn new(identifier: String) -> Self { + let i: CFString = CFString::from(identifier.as_str()); + let voice: *const Object = unsafe{msg_send![class!(AVSpeechSynthesisVoice).metaclass(), voiceWithIdentifier: i]}; + AVSpeechSynthesisVoice{0: voice} + } + + pub fn default() -> Self { + AVSpeechSynthesisVoice::list()[0] } pub fn list() -> Vec { diff --git a/src/lib.rs b/src/lib.rs index af6db3f..61a47ae 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -102,7 +102,7 @@ pub trait Backend { fn is_speaking(&self) -> Result; fn voice(&self) -> Result; fn list_voices(&self) -> Vec; - fn set_voice(&self, voice: String) -> Result<(),Error>; + fn set_voice(&mut self, voice: String) -> Result<(),Error>; } pub struct TTS(Box); @@ -399,7 +399,7 @@ impl TTS { /** * Set speaking voice. */ - pub fn set_voice(&self, voice: String) -> Result<(),Error> { + pub fn set_voice(&mut self, voice: String) -> Result<(),Error> { let Features { voices: voices_feature, .. From 0fb6c62d838ee2b07c8c1df64fa5e4f2f4161b7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Caddet?= Date: Fri, 4 Sep 2020 15:48:56 +0200 Subject: [PATCH 03/13] fix some parameters types and implement set_voice We have an ilegal hardware instruction in backend::av_foundation::voices::AVSpeechSynthesisVoice::new(identifier) when sending voiceWithIdentifier. Is it because the runLoop is not runing when it's called? --- examples/hello_world.rs | 14 ++++++++++++++ src/backends/appkit.rs | 2 +- src/backends/av_foundation.rs | 2 +- src/backends/av_foundation/voices.rs | 11 +++++++---- src/lib.rs | 6 +++--- 5 files changed, 26 insertions(+), 9 deletions(-) diff --git a/examples/hello_world.rs b/examples/hello_world.rs index e95c53c..f3e054e 100644 --- a/examples/hello_world.rs +++ b/examples/hello_world.rs @@ -47,6 +47,20 @@ fn main() -> Result<(), Error> { tts.speak("This is normal volume.", false)?; tts.set_volume(original_volume)?; } + let Features { voices, .. } = tts.supported_features(); + if voices { + let original_voice = tts.voice()?; + let voices_list = tts.list_voices(); + println!("Available voices:\n==="); + for v in voices_list.iter() { + println!("{}",v); + tts.set_voice(v)?; + println!("voice set"); + println!("{}", tts.voice()?); + tts.speak(v,false)?; + } + tts.set_voice(original_voice)?; + } tts.speak("Goodbye.", false)?; let mut _input = String::new(); #[cfg(target_os = "macos")] diff --git a/src/backends/appkit.rs b/src/backends/appkit.rs index 164cf53..dd8caa3 100644 --- a/src/backends/appkit.rs +++ b/src/backends/appkit.rs @@ -204,7 +204,7 @@ impl Backend for AppKit { unimplemented!() } - fn set_voice(&mut self, voice: String) -> Result<(),Error> { + fn set_voice(&mut self, voice: &str) -> Result<(),Error> { unimplemented!() } } diff --git a/src/backends/av_foundation.rs b/src/backends/av_foundation.rs index c51988a..b5bfd32 100644 --- a/src/backends/av_foundation.rs +++ b/src/backends/av_foundation.rs @@ -150,7 +150,7 @@ impl Backend for AvFoundation { AVSpeechSynthesisVoice::list().iter().map(|v| {v.identifier()}).collect() } - fn set_voice(&mut self, voice: String) -> Result<(),Error> { + fn set_voice(&mut self, voice: &str) -> Result<(),Error> { self.voice = AVSpeechSynthesisVoice::new(voice); Ok(()) } diff --git a/src/backends/av_foundation/voices.rs b/src/backends/av_foundation/voices.rs index a23c14a..1f5b1d5 100644 --- a/src/backends/av_foundation/voices.rs +++ b/src/backends/av_foundation/voices.rs @@ -2,16 +2,19 @@ use objc::runtime::*; use objc::*; use core_foundation::array::CFArray; +use cocoa_foundation::foundation::NSString; +use cocoa_foundation::base::{nil,id}; use core_foundation::string::CFString; #[derive(Copy,Clone)] pub struct AVSpeechSynthesisVoice(*const Object); impl AVSpeechSynthesisVoice { - pub fn new(identifier: String) -> Self { - let i: CFString = CFString::from(identifier.as_str()); - let voice: *const Object = unsafe{msg_send![class!(AVSpeechSynthesisVoice).metaclass(), voiceWithIdentifier: i]}; - AVSpeechSynthesisVoice{0: voice} + pub fn new(identifier: &str) -> Self { + unsafe{ + let i: id = NSString::alloc(nil).init_str(identifier); + msg_send![class!(AVSpeechSynthesisVoice), voiceWithIdentifier:i] + } } pub fn default() -> Self { diff --git a/src/lib.rs b/src/lib.rs index 61a47ae..daebd23 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -102,7 +102,7 @@ pub trait Backend { fn is_speaking(&self) -> Result; fn voice(&self) -> Result; fn list_voices(&self) -> Vec; - fn set_voice(&mut self, voice: String) -> Result<(),Error>; + fn set_voice(&mut self, voice: &str) -> Result<(),Error>; } pub struct TTS(Box); @@ -399,13 +399,13 @@ impl TTS { /** * Set speaking voice. */ - pub fn set_voice(&mut self, voice: String) -> Result<(),Error> { + pub fn set_voice>(&mut self, voice: S) -> Result<(),Error> { let Features { voices: voices_feature, .. } = self.0.supported_features(); if voices_feature { - self.0.set_voice(voice) + self.0.set_voice(voice.into().as_str()) } else { Err(Error::UnsupportedFeature) } From 1b8809aaeb7061ca0e08c7bc1d35ef13fae8984c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Caddet?= Date: Sat, 5 Sep 2020 10:55:23 +0200 Subject: [PATCH 04/13] remove the example changing voice. the default() voice working properly for av_foundation --- examples/hello_world.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/hello_world.rs b/examples/hello_world.rs index f3e054e..a34f5f8 100644 --- a/examples/hello_world.rs +++ b/examples/hello_world.rs @@ -47,7 +47,7 @@ fn main() -> Result<(), Error> { tts.speak("This is normal volume.", false)?; tts.set_volume(original_volume)?; } - let Features { voices, .. } = tts.supported_features(); +/* let Features { voices, .. } = tts.supported_features(); if voices { let original_voice = tts.voice()?; let voices_list = tts.list_voices(); @@ -60,7 +60,7 @@ fn main() -> Result<(), Error> { tts.speak(v,false)?; } tts.set_voice(original_voice)?; - } + }*/ tts.speak("Goodbye.", false)?; let mut _input = String::new(); #[cfg(target_os = "macos")] From b238c8c9382877c9e8756d021be5f2f6de69c3cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Caddet?= Date: Sat, 5 Sep 2020 11:30:11 +0200 Subject: [PATCH 05/13] fix return type of AVSpeechSynthesisVoice:new --- src/backends/av_foundation/voices.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/backends/av_foundation/voices.rs b/src/backends/av_foundation/voices.rs index 1f5b1d5..14e7974 100644 --- a/src/backends/av_foundation/voices.rs +++ b/src/backends/av_foundation/voices.rs @@ -11,10 +11,12 @@ pub struct AVSpeechSynthesisVoice(*const Object); impl AVSpeechSynthesisVoice { pub fn new(identifier: &str) -> Self { + let voice: *const Object; unsafe{ let i: id = NSString::alloc(nil).init_str(identifier); - msg_send![class!(AVSpeechSynthesisVoice), voiceWithIdentifier:i] - } + voice = msg_send![class!(AVSpeechSynthesisVoice), voiceWithIdentifier:i]; + }; + AVSpeechSynthesisVoice{0:voice} } pub fn default() -> Self { @@ -24,7 +26,7 @@ impl AVSpeechSynthesisVoice { pub fn list() -> Vec { let voices: CFArray = unsafe{msg_send![class!(AVSpeechSynthesisVoice), speechVoices]}; voices.iter().map(|v| { - AVSpeechSynthesisVoice{0: *v as *mut Object} + AVSpeechSynthesisVoice{0: *v as *const Object} }).collect() } From 335ac710a61680897fcfcc30794097972a5857da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Caddet?= Date: Sat, 5 Sep 2020 12:07:51 +0200 Subject: [PATCH 06/13] add unimplemented functions forvoices feature on every backends --- src/backends/speech_dispatcher.rs | 12 ++++++++++++ src/backends/tolk.rs | 12 ++++++++++++ src/backends/web.rs | 12 ++++++++++++ src/backends/winrt.rs | 12 ++++++++++++ 4 files changed, 48 insertions(+) diff --git a/src/backends/speech_dispatcher.rs b/src/backends/speech_dispatcher.rs index 009a8fb..89ab24c 100644 --- a/src/backends/speech_dispatcher.rs +++ b/src/backends/speech_dispatcher.rs @@ -149,6 +149,18 @@ impl Backend for SpeechDispatcher { let is_speaking = speaking.get(&self.0.client_id()).unwrap(); Ok(*is_speaking) } + + fn voice(&self) -> Result { + unimplemented!() + } + + fn list_voices(&self) -> Vec { + unimplemented!() + } + + fn set_voice(&mut self, voice: &str) -> Result<(),Error> { + unimplemented!() + } } impl Drop for SpeechDispatcher { diff --git a/src/backends/tolk.rs b/src/backends/tolk.rs index 370da65..e6d0f07 100644 --- a/src/backends/tolk.rs +++ b/src/backends/tolk.rs @@ -101,4 +101,16 @@ impl Backend for Tolk { fn is_speaking(&self) -> Result { unimplemented!() } + + fn voice(&self) -> Result { + unimplemented!() + } + + fn list_voices(&self) -> Vec { + unimplemented!() + } + + fn set_voice(&mut self, voice: &str) -> Result<(),Error> { + unimplemented!() + } } diff --git a/src/backends/web.rs b/src/backends/web.rs index 664793c..a8be3c2 100644 --- a/src/backends/web.rs +++ b/src/backends/web.rs @@ -131,4 +131,16 @@ impl Backend for Web { Err(Error::NoneError) } } + + fn voice(&self) -> Result { + unimplemented!() + } + + fn list_voices(&self) -> Vec { + unimplemented!() + } + + fn set_voice(&mut self, voice: &str) -> Result<(),Error> { + unimplemented!() + } } diff --git a/src/backends/winrt.rs b/src/backends/winrt.rs index 7464f58..2e8af98 100644 --- a/src/backends/winrt.rs +++ b/src/backends/winrt.rs @@ -156,4 +156,16 @@ impl Backend for WinRT { let playing = state == MediaPlaybackState::Opening || state == MediaPlaybackState::Playing; Ok(playing) } + + fn voice(&self) -> Result { + unimplemented!() + } + + fn list_voices(&self) -> Vec { + unimplemented!() + } + + fn set_voice(&mut self, voice: &str) -> Result<(),Error> { + unimplemented!() + } } From 8c8dc0ae9f01f58bb0301519046746c9ff5732a6 Mon Sep 17 00:00:00 2001 From: Francois Caddet Date: Sat, 26 Sep 2020 23:03:56 +0200 Subject: [PATCH 07/13] add voices value returned by the backends --- src/backends/speech_dispatcher.rs | 1 + src/backends/web.rs | 1 + src/backends/winrt.rs | 1 + 3 files changed, 3 insertions(+) diff --git a/src/backends/speech_dispatcher.rs b/src/backends/speech_dispatcher.rs index afbbba3..7fa1fbe 100644 --- a/src/backends/speech_dispatcher.rs +++ b/src/backends/speech_dispatcher.rs @@ -75,6 +75,7 @@ impl Backend for SpeechDispatcher { pitch: true, volume: true, is_speaking: true, + voices: false, utterance_callbacks: true, } } diff --git a/src/backends/web.rs b/src/backends/web.rs index a43b2c8..c2d6ddd 100644 --- a/src/backends/web.rs +++ b/src/backends/web.rs @@ -47,6 +47,7 @@ impl Backend for Web { pitch: true, volume: true, is_speaking: true, + voices: true, utterance_callbacks: true, } } diff --git a/src/backends/winrt.rs b/src/backends/winrt.rs index 98826e6..d22d6a3 100644 --- a/src/backends/winrt.rs +++ b/src/backends/winrt.rs @@ -140,6 +140,7 @@ impl Backend for WinRT { pitch: true, volume: true, is_speaking: true, + voices: true, utterance_callbacks: true, } } From 008662c940c74d32dc288b8df27ef44fddd33fff Mon Sep 17 00:00:00 2001 From: Francois Caddet Date: Sat, 26 Sep 2020 23:16:10 +0200 Subject: [PATCH 08/13] temporary fix to a build issue with the crate speech-dispatcher --- Cargo.toml | 9 +++++++++ speech-dispatcher.patch | 22 ++++++++++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 speech-dispatcher.patch diff --git a/Cargo.toml b/Cargo.toml index 549745f..8d3757f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,15 @@ license = "MIT" exclude = ["*.cfg", "*.yml"] edition = "2018" +[package.metadata.patch.speech-dispatcher] +version = "0.7.0" +patches = [ + "speech-dispatcher.patch" +] + +[patch.crates-io] +speech-dispatcher = { path = './target/patch/speech-dispatcher-0.7.0'} + [lib] crate-type = ["lib", "cdylib", "staticlib"] diff --git a/speech-dispatcher.patch b/speech-dispatcher.patch new file mode 100644 index 0000000..65ebeab --- /dev/null +++ b/speech-dispatcher.patch @@ -0,0 +1,22 @@ +diff --git src/lib.rs src/lib.rs +index 26ba271..180513e 100644 +--- src/lib.rs ++++ src/lib.rs +@@ -127,7 +127,7 @@ unsafe extern "C" fn cb(msg_id: u64, client_id: u64, state: u32) { + } + } + +-unsafe extern "C" fn cb_im(msg_id: u64, client_id: u64, state: u32, index_mark: *mut i8) { ++unsafe extern "C" fn cb_im(msg_id: u64, client_id: u64, state: u32, index_mark: *mut u8) { + let index_mark = CStr::from_ptr(index_mark); + let index_mark = index_mark.to_string_lossy().to_string(); + let state = match state { +@@ -325,7 +325,7 @@ impl Connection { + i32_to_bool(v) + } + +- pub fn wchar(&self, priority: Priority, wchar: i32) -> bool { ++ pub fn wchar(&self, priority: Priority, wchar: u32) -> bool { + let v = unsafe { spd_wchar(self.0, priority as u32, wchar) }; + i32_to_bool(v) + } From f78aed211fa2ff2a2684c5ad20939851efddbe62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Caddet?= Date: Sat, 26 Sep 2020 23:36:15 +0200 Subject: [PATCH 09/13] fix conflicts --- src/backends/av_foundation.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/backends/av_foundation.rs b/src/backends/av_foundation.rs index c1f1c7f..6940e07 100644 --- a/src/backends/av_foundation.rs +++ b/src/backends/av_foundation.rs @@ -120,11 +120,8 @@ impl Backend for AvFoundation { pitch: true, volume: true, is_speaking: true, -<<<<<<< HEAD voices: true, -======= utterance_callbacks: true, ->>>>>>> develop } } From f7297e18fd77b99157b0dfc4782e8593ac9f4a47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Caddet?= Date: Sat, 26 Sep 2020 23:39:30 +0200 Subject: [PATCH 10/13] add condition for macOS 11 and greater for default backend --- src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 67dea00..bc29f4b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -230,7 +230,8 @@ impl TTS { let version = version[1]; let version_parts: Vec<&str> = version.split(".").collect(); let minor_version: i8 = version_parts[1].parse().unwrap(); - if minor_version >= 14 { + let major_version: i8 = version_parts[0].parse().unwrap(); + if minor_version >= 14 || major_version >= 11 { TTS::new(Backends::AvFoundation) } else { TTS::new(Backends::AppKit) From e19eb56169e7491deb0bcf35d2eaabc15ae3dbc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Caddet?= Date: Sun, 27 Sep 2020 20:04:12 +0200 Subject: [PATCH 11/13] first implementation of a voice trait for macOS WARN: not tested --- Cargo.toml | 1 + examples/hello_world.rs | 4 ++-- src/backends/av_foundation/voices.rs | 30 ++++++++++++++++++++++------ src/lib.rs | 1 + 4 files changed, 28 insertions(+), 8 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8d3757f..cc236ba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ crate-type = ["lib", "cdylib", "staticlib"] lazy_static = "1" log = "0.4" thiserror = "1" +unic-langid = "0.9.0" [dev-dependencies] env_logger = "0.7" diff --git a/examples/hello_world.rs b/examples/hello_world.rs index 255cae2..264d9b8 100644 --- a/examples/hello_world.rs +++ b/examples/hello_world.rs @@ -59,7 +59,7 @@ fn main() -> Result<(), Error> { tts.speak("This is normal volume.", false)?; tts.set_volume(original_volume)?; } -/* let Features { voices, .. } = tts.supported_features(); + let Features { voices, .. } = tts.supported_features(); if voices { let original_voice = tts.voice()?; let voices_list = tts.list_voices(); @@ -72,7 +72,7 @@ fn main() -> Result<(), Error> { tts.speak(v,false)?; } tts.set_voice(original_voice)?; - }*/ + } tts.speak("Goodbye.", false)?; let mut _input = String::new(); // The below is only needed to make the example run on MacOS because there is no NSRunLoop in this context. diff --git a/src/backends/av_foundation/voices.rs b/src/backends/av_foundation/voices.rs index 14e7974..b8ee960 100644 --- a/src/backends/av_foundation/voices.rs +++ b/src/backends/av_foundation/voices.rs @@ -6,6 +6,10 @@ use cocoa_foundation::foundation::NSString; use cocoa_foundation::base::{nil,id}; use core_foundation::string::CFString; +use crate::backends::AvFoundation; +use crate::voices; +use crate::voices::Gender; + #[derive(Copy,Clone)] pub struct AVSpeechSynthesisVoice(*const Object); @@ -18,25 +22,39 @@ impl AVSpeechSynthesisVoice { }; AVSpeechSynthesisVoice{0:voice} } +} - pub fn default() -> Self { - AVSpeechSynthesisVoice::list()[0] - } +impl voices::Backend for AVSpeechSynthesisVoice { + type Backend = AvFoundation; - pub fn list() -> Vec { + fn list() -> Vec { let voices: CFArray = unsafe{msg_send![class!(AVSpeechSynthesisVoice), speechVoices]}; voices.iter().map(|v| { AVSpeechSynthesisVoice{0: *v as *const Object} }).collect() } - pub fn name(self) -> String { + fn name(self) -> String { let name: CFString = unsafe{msg_send![self.0, name]}; name.to_string() } - pub fn identifier(self) -> String { + fn gender(self) -> Gender { + let gender: i64 = unsafe{ msg_send![self.0, gender] }; + match gender { + 1 => Gender::Male, + 2 => Gender::Female, + _ => Gender::Other, + } + } + + fn id(self) -> String { let identifier: CFString = unsafe{msg_send![self.0, identifier]}; identifier.to_string() } + + fn language(self) -> voices::LanguageIdentifier { + let lang: CFString = unsafe{msg_send![self.0, language]}; + lang.to_string().parse().unwrap() + } } diff --git a/src/lib.rs b/src/lib.rs index bc29f4b..91f13c7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -32,6 +32,7 @@ use web_sys::SpeechSynthesisUtterance; use tts_winrt_bindings::windows::media::playback::MediaPlaybackItem; mod backends; +mod voices; pub enum Backends { #[cfg(target_os = "linux")] From 3294a82485d94e22138b444e84d0a791d094c20f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Caddet?= Date: Sun, 27 Sep 2020 20:35:40 +0200 Subject: [PATCH 12/13] some fixes now build on macOS --- src/backends/av_foundation.rs | 9 +++++---- src/backends/av_foundation/voices.rs | 2 +- src/voices.rs | 17 +++++++++++++++++ 3 files changed, 23 insertions(+), 5 deletions(-) create mode 100644 src/voices.rs diff --git a/src/backends/av_foundation.rs b/src/backends/av_foundation.rs index 6940e07..445b3c9 100644 --- a/src/backends/av_foundation.rs +++ b/src/backends/av_foundation.rs @@ -10,9 +10,10 @@ use objc::runtime::{Object, Sel}; use objc::{class, declare::ClassDecl, msg_send, sel, sel_impl}; use crate::{Backend, BackendId, Error, Features, UtteranceId, CALLBACKS}; +use crate::voices::Backend as VoiceBackend; mod voices; -use voices::AVSpeechSynthesisVoice; +use voices::*; pub(crate) struct AvFoundation { id: BackendId, @@ -100,7 +101,7 @@ impl AvFoundation { rate: 0.5, volume: 1., pitch: 1., - voice: AVSpeechSynthesisVoice::default(), + voice: AVSpeechSynthesisVoice::new(""), } }; *backend_id += 1; @@ -222,11 +223,11 @@ impl Backend for AvFoundation { } fn voice(&self) -> Result { - Ok(self.voice.identifier()) + Ok(self.voice.id()) } fn list_voices(&self) -> Vec { - AVSpeechSynthesisVoice::list().iter().map(|v| {v.identifier()}).collect() + AVSpeechSynthesisVoice::list().iter().map(|v| {v.id()}).collect() } fn set_voice(&mut self, voice: &str) -> Result<(),Error> { diff --git a/src/backends/av_foundation/voices.rs b/src/backends/av_foundation/voices.rs index b8ee960..32ae9a2 100644 --- a/src/backends/av_foundation/voices.rs +++ b/src/backends/av_foundation/voices.rs @@ -11,7 +11,7 @@ use crate::voices; use crate::voices::Gender; #[derive(Copy,Clone)] -pub struct AVSpeechSynthesisVoice(*const Object); +pub(crate) struct AVSpeechSynthesisVoice(*const Object); impl AVSpeechSynthesisVoice { pub fn new(identifier: &str) -> Self { diff --git a/src/voices.rs b/src/voices.rs new file mode 100644 index 0000000..031fcdd --- /dev/null +++ b/src/voices.rs @@ -0,0 +1,17 @@ + +pub use unic_langid::LanguageIdentifier; + +pub enum Gender { + Other, + Male, + Female, +} + +pub trait Backend: Sized { + type Backend: crate::Backend; + fn list() -> Vec; + fn name(self) -> String; + fn gender(self) -> Gender; + fn id(self) -> String; + fn language(self) -> LanguageIdentifier; +} From d2c42d97f5313f4c2ca5972b02f6f309374a635b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Caddet?= Date: Mon, 28 Sep 2020 11:18:54 +0200 Subject: [PATCH 13/13] the voices::Backend trait is almost stable --- src/backends/av_foundation.rs | 4 ++-- src/backends/av_foundation/voices.rs | 13 ++++++++++--- src/voices.rs | 4 ++++ 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/backends/av_foundation.rs b/src/backends/av_foundation.rs index 445b3c9..7e3590f 100644 --- a/src/backends/av_foundation.rs +++ b/src/backends/av_foundation.rs @@ -101,7 +101,7 @@ impl AvFoundation { rate: 0.5, volume: 1., pitch: 1., - voice: AVSpeechSynthesisVoice::new(""), + voice: AVSpeechSynthesisVoice::new(), } }; *backend_id += 1; @@ -231,7 +231,7 @@ impl Backend for AvFoundation { } fn set_voice(&mut self, voice: &str) -> Result<(),Error> { - self.voice = AVSpeechSynthesisVoice::new(voice); + self.voice = AVSpeechSynthesisVoice::new(); Ok(()) } } diff --git a/src/backends/av_foundation/voices.rs b/src/backends/av_foundation/voices.rs index 32ae9a2..71bf8c1 100644 --- a/src/backends/av_foundation/voices.rs +++ b/src/backends/av_foundation/voices.rs @@ -14,11 +14,10 @@ use crate::voices::Gender; pub(crate) struct AVSpeechSynthesisVoice(*const Object); impl AVSpeechSynthesisVoice { - pub fn new(identifier: &str) -> Self { + pub fn new() -> Self { let voice: *const Object; unsafe{ - let i: id = NSString::alloc(nil).init_str(identifier); - voice = msg_send![class!(AVSpeechSynthesisVoice), voiceWithIdentifier:i]; + voice = msg_send![class!(AVSpeechSynthesisVoice), new]; }; AVSpeechSynthesisVoice{0:voice} } @@ -27,6 +26,14 @@ impl AVSpeechSynthesisVoice { impl voices::Backend for AVSpeechSynthesisVoice { type Backend = AvFoundation; + fn from_id(id: String) -> Self { + unimplemented!() + } + + fn from_language(lang: voices::LanguageIdentifier) -> Self { + unimplemented!() + } + fn list() -> Vec { let voices: CFArray = unsafe{msg_send![class!(AVSpeechSynthesisVoice), speechVoices]}; voices.iter().map(|v| { diff --git a/src/voices.rs b/src/voices.rs index 031fcdd..8ca927d 100644 --- a/src/voices.rs +++ b/src/voices.rs @@ -9,9 +9,13 @@ pub enum Gender { pub trait Backend: Sized { type Backend: crate::Backend; + fn from_id(id: String) -> Self; + fn from_language(lang: LanguageIdentifier) -> Self; fn list() -> Vec; fn name(self) -> String; fn gender(self) -> Gender; fn id(self) -> String; fn language(self) -> LanguageIdentifier; } + +pub struct Voice(Box);