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) + } + } }