From a3a871716fa4cc5aab95f37010dba0a69a2d2d01 Mon Sep 17 00:00:00 2001 From: mcb2003 Date: Sat, 12 Dec 2020 00:23:36 +0000 Subject: [PATCH 01/17] FFI: Create ffi Cargo feature and include ffi module. --- Cargo.toml | 2 ++ src/ffi.rs | 0 src/lib.rs | 2 ++ 3 files changed, 4 insertions(+) create mode 100644 src/ffi.rs diff --git a/Cargo.toml b/Cargo.toml index fd7424c..c5f70d3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,10 +13,12 @@ crate-type = ["lib", "cdylib", "staticlib"] [features] use_tolk = ["tolk"] +ffi = ["libc"] [dependencies] dyn-clonable = "0.9" lazy_static = "1" +libc = {version = "0.2", optional = true} log = "0.4" thiserror = "1" diff --git a/src/ffi.rs b/src/ffi.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/lib.rs b/src/lib.rs index 820ccd3..9256a53 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,6 +28,8 @@ use objc::{class, msg_send, sel, sel_impl}; use thiserror::Error; mod backends; +#[cfg(feature = "ffi")] +pub mod ffi; #[derive(Clone, Copy, Debug)] pub enum Backends { From 9471a2086a143a31b65ede9e29c63974cf09cde1 Mon Sep 17 00:00:00 2001 From: mcb2003 Date: Sat, 12 Dec 2020 01:13:40 +0000 Subject: [PATCH 02/17] FFI: Create error handling code and LAST_ERROR static Any errors reported will cause the C API functions to return an error value (NULL or -1). The caller can then use: * const char* tts_get_error() to get a pointer to a string describing the error * void tts_clear_error() to deallocate any currently stored error message. --- src/ffi.rs | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/src/ffi.rs b/src/ffi.rs index e69de29..5a2e286 100644 --- a/src/ffi.rs +++ b/src/ffi.rs @@ -0,0 +1,37 @@ +use libc::c_char; +use std::{ + cell::RefCell, + ffi::{CString, NulError}, + ptr, +}; + +thread_local! { + /// Stores the last reported error, so it can be retrieved at will from C + static LAST_ERROR: RefCell> = RefCell::new(None); +} + +fn set_last_error>>(err: E) -> Result<(), NulError> { + LAST_ERROR.with(|last| { + *last.borrow_mut() = Some(CString::new(err)?); + Ok(()) + }) +} + +/// Get the last reported error as a const C string (const char*) +/// This string will be valid until at least the next call to `tts_get_error`. +/// It is never called internally by the library. +#[no_mangle] +pub extern "C" fn tts_get_error() -> *const c_char { + LAST_ERROR.with(|err| match &*err.borrow() { + Some(e) => e.as_ptr(), + None => ptr::null(), + }) +} + +/// Deallocate the last reported error (if any). +#[no_mangle] +pub extern "C" fn tts_clear_error() { + LAST_ERROR.with(|err| { + *err.borrow_mut() = None; + }); +} From ab558cbbd2da413d9343b18b3e9d6bae3914e46c Mon Sep 17 00:00:00 2001 From: mcb2003 Date: Sat, 12 Dec 2020 01:40:13 +0000 Subject: [PATCH 03/17] FFI: Implement tts_default() constructor and tts_free() destructor * tts_default() allocates a new TTS struct via it's default() constructor, returning a pointer to it or NULL on error. * tts_free(tts) destroys the TTS pointed to by tts. If tts is NULL, this function does nothing. --- src/ffi.rs | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/ffi.rs b/src/ffi.rs index 5a2e286..71f14d3 100644 --- a/src/ffi.rs +++ b/src/ffi.rs @@ -5,6 +5,8 @@ use std::{ ptr, }; +use crate::TTS; + thread_local! { /// Stores the last reported error, so it can be retrieved at will from C static LAST_ERROR: RefCell> = RefCell::new(None); @@ -35,3 +37,29 @@ pub extern "C" fn tts_clear_error() { *err.borrow_mut() = None; }); } + +/// Creates a new TTS object with the default backend and returns a pointer to it. +/// If an error occured, a null pointer is returned, +/// Call `tts_get_error()` for more information about the specific error. +#[no_mangle] +pub extern "C" fn tts_default() -> *mut TTS { + match TTS::default() { + Ok(tts) => Box::into_raw(Box::new(tts)), + Err(e) => { + set_last_error(e.to_string()).unwrap(); + ptr::null_mut() + } + } +} + +/// Frees the memory associated with a TTS object. +/// If `tts` is a null pointer, this function does nothing. +#[no_mangle] +pub extern "C" fn tts_free(tts: *mut TTS) { + if tts.is_null() { + return; + } + unsafe { + Box::from_raw(tts); // Goes out of scope and is dropped + } +} From 56a2d554fa251bbda88d0eda452ade3b2cf3cf64 Mon Sep 17 00:00:00 2001 From: mcb2003 Date: Sat, 12 Dec 2020 16:32:53 +0000 Subject: [PATCH 04/17] FFI: Implement tts_new(backend) This required giving the Backends enum and Features struct a C representation. --- src/ffi.rs | 16 +++++++++++++++- src/lib.rs | 2 ++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/ffi.rs b/src/ffi.rs index 71f14d3..16cac9e 100644 --- a/src/ffi.rs +++ b/src/ffi.rs @@ -5,7 +5,7 @@ use std::{ ptr, }; -use crate::TTS; +use crate::{Backends, TTS}; thread_local! { /// Stores the last reported error, so it can be retrieved at will from C @@ -38,6 +38,20 @@ pub extern "C" fn tts_clear_error() { }); } +/// Creates a new TTS object with the specified backend and returns a pointer to it. +/// If an error occured, a null pointer is returned, +/// Call `tts_get_error()` for more information about the specific error. +#[no_mangle] +pub extern "C" fn tts_new(backend: Backends) -> *mut TTS { + match TTS::new(backend) { + Ok(tts) => Box::into_raw(Box::new(tts)), + Err(e) => { + set_last_error(e.to_string()).unwrap(); + ptr::null_mut() + } + } +} + /// Creates a new TTS object with the default backend and returns a pointer to it. /// If an error occured, a null pointer is returned, /// Call `tts_get_error()` for more information about the specific error. diff --git a/src/lib.rs b/src/lib.rs index 9256a53..76aa3a0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -31,6 +31,7 @@ mod backends; #[cfg(feature = "ffi")] pub mod ffi; +#[repr(C)] #[derive(Clone, Copy, Debug)] pub enum Backends { #[cfg(target_os = "linux")] @@ -75,6 +76,7 @@ unsafe impl Send for UtteranceId {} unsafe impl Sync for UtteranceId {} +#[repr(C)] pub struct Features { pub stop: bool, pub rate: bool, From 7c5ceb2bd3587cadb99127dee205920139de0075 Mon Sep 17 00:00:00 2001 From: mcb2003 Date: Sat, 12 Dec 2020 16:35:08 +0000 Subject: [PATCH 05/17] FFI: Add cbindgen config for generation of C/C++ headers. --- cbindgen.toml | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 cbindgen.toml diff --git a/cbindgen.toml b/cbindgen.toml new file mode 100644 index 0000000..7b647ff --- /dev/null +++ b/cbindgen.toml @@ -0,0 +1,8 @@ +include_guard = "TTS_RS_H" +autogen_warning = "/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */" + +[defines] +"target_os = linux" = "__linux__" +"target_os = macos" = "__APPLE__" +"target_arch = wasm32" = "__EMSCRIPTEN__" +windows = "__WIN32__" From 195ccbeb23a98b69fe2fb2f2430d93339457f955 Mon Sep 17 00:00:00 2001 From: mcb2003 Date: Sat, 12 Dec 2020 16:59:07 +0000 Subject: [PATCH 06/17] FFI: Allow determining TTS supported Features from C/C++ --- src/ffi.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/ffi.rs b/src/ffi.rs index 16cac9e..693d9f3 100644 --- a/src/ffi.rs +++ b/src/ffi.rs @@ -5,7 +5,7 @@ use std::{ ptr, }; -use crate::{Backends, TTS}; +use crate::{Backends, Features, TTS}; thread_local! { /// Stores the last reported error, so it can be retrieved at will from C @@ -77,3 +77,10 @@ pub extern "C" fn tts_free(tts: *mut TTS) { Box::from_raw(tts); // Goes out of scope and is dropped } } + +/// Returns the features supported by this TTS engine. +/// tts must be a valid pointer to a TTS object. +#[no_mangle] +pub extern "C" fn tts_supported_features(tts: *mut TTS) -> Features { + unsafe { tts.as_ref().unwrap().supported_features() } +} From c0f92500997ccd0912bb79e48a1a5ff21129a8d6 Mon Sep 17 00:00:00 2001 From: mcb2003 Date: Sat, 12 Dec 2020 17:06:58 +0000 Subject: [PATCH 07/17] FFI: Update documentation comments to match their Rust equivilents. --- src/ffi.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/ffi.rs b/src/ffi.rs index 693d9f3..bb71589 100644 --- a/src/ffi.rs +++ b/src/ffi.rs @@ -38,8 +38,8 @@ pub extern "C" fn tts_clear_error() { }); } -/// Creates a new TTS object with the specified backend and returns a pointer to it. -/// If an error occured, a null pointer is returned, +/// Create a new `TTS` instance with the specified backend. +/// If an error occures, returns a null pointer, /// Call `tts_get_error()` for more information about the specific error. #[no_mangle] pub extern "C" fn tts_new(backend: Backends) -> *mut TTS { @@ -52,8 +52,8 @@ pub extern "C" fn tts_new(backend: Backends) -> *mut TTS { } } -/// Creates a new TTS object with the default backend and returns a pointer to it. -/// If an error occured, a null pointer is returned, +/// Create a new TTS object with the default backend. +/// If an error occures, returns a null pointer, /// Call `tts_get_error()` for more information about the specific error. #[no_mangle] pub extern "C" fn tts_default() -> *mut TTS { @@ -66,7 +66,7 @@ pub extern "C" fn tts_default() -> *mut TTS { } } -/// Frees the memory associated with a TTS object. +/// Free the memory associated with a TTS object. /// If `tts` is a null pointer, this function does nothing. #[no_mangle] pub extern "C" fn tts_free(tts: *mut TTS) { @@ -79,7 +79,7 @@ pub extern "C" fn tts_free(tts: *mut TTS) { } /// Returns the features supported by this TTS engine. -/// tts must be a valid pointer to a TTS object. +/// `tts` must be a valid pointer to a TTS object. #[no_mangle] pub extern "C" fn tts_supported_features(tts: *mut TTS) -> Features { unsafe { tts.as_ref().unwrap().supported_features() } From 98bfc10d41107429ddb53deb725e1211ffc00445 Mon Sep 17 00:00:00 2001 From: mcb2003 Date: Sat, 12 Dec 2020 19:11:05 +0000 Subject: [PATCH 08/17] FFI: Implement tts_speak() The tts_speak() function has an additional parameter that, if not NULL, will be filled with a pointer to an UtteranceId. If this is specified, the caller must also call tts_free_utterance() to deallocate the UtteranceId when they're done with it. --- src/ffi.rs | 52 +++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 49 insertions(+), 3 deletions(-) diff --git a/src/ffi.rs b/src/ffi.rs index bb71589..ec349e7 100644 --- a/src/ffi.rs +++ b/src/ffi.rs @@ -1,11 +1,11 @@ use libc::c_char; use std::{ cell::RefCell, - ffi::{CString, NulError}, + ffi::{CStr, CString, NulError}, ptr, }; -use crate::{Backends, Features, TTS}; +use crate::{Backends, Features, UtteranceId, TTS}; thread_local! { /// Stores the last reported error, so it can be retrieved at will from C @@ -19,7 +19,7 @@ fn set_last_error>>(err: E) -> Result<(), NulError> { }) } -/// Get the last reported error as a const C string (const char*) +/// Get the last reported error as a const C string. /// This string will be valid until at least the next call to `tts_get_error`. /// It is never called internally by the library. #[no_mangle] @@ -84,3 +84,49 @@ pub extern "C" fn tts_free(tts: *mut TTS) { pub extern "C" fn tts_supported_features(tts: *mut TTS) -> Features { unsafe { tts.as_ref().unwrap().supported_features() } } + +/// Speaks the specified text, optionally interrupting current speech. +/// If `utterance` is not NULL, , fills it with a pointer to the returned UtteranceId (or NULL if +/// the backend doesn't provide one). +/// Returns true on success, false on error or if `tts` is NULL. +#[no_mangle] +pub extern "C" fn tts_speak( + tts: *mut TTS, + text: *const c_char, + interrupt: bool, + utterance: *mut *mut UtteranceId, +) -> bool { + if tts.is_null() { + return true; + } + unsafe { + let text = CStr::from_ptr(text).to_string_lossy().into_owned(); + match tts.as_mut().unwrap().speak(text, interrupt) { + Ok(u) => { + if !utterance.is_null() { + *utterance = match u { + Some(u) => Box::into_raw(Box::new(u)), + None => ptr::null_mut(), + }; + } + return true; + } + Err(e) => { + set_last_error(e.to_string()).unwrap(); + return false; + } + } + } +} + +/// Free the memory associated with an `UtteranceId`. +/// Does nothing if `utterance` is NULL. +#[no_mangle] +pub extern "C" fn tts_free_utterance(utterance: *mut UtteranceId) { + if utterance.is_null() { + return; + } + unsafe { + Box::from_raw(utterance); + } +} From ae6e64d973c8ef4e1df8eee01fbee560b1bac4ac Mon Sep 17 00:00:00 2001 From: mcb2003 Date: Sat, 12 Dec 2020 19:25:36 +0000 Subject: [PATCH 09/17] FFI: Implement tts_stop() function. --- src/ffi.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/ffi.rs b/src/ffi.rs index ec349e7..6323ab6 100644 --- a/src/ffi.rs +++ b/src/ffi.rs @@ -130,3 +130,18 @@ pub extern "C" fn tts_free_utterance(utterance: *mut UtteranceId) { Box::from_raw(utterance); } } +/// Stops current speech. +/// Returns true on success, false on error or if `tts` is NULL. +#[no_mangle] +pub extern "C" fn tts_stop(tts: *mut TTS) -> bool { + if tts.is_null() { + return false; + } + match unsafe { tts.as_mut().unwrap().stop() } { + Ok(_) => true, + Err(e) => { + set_last_error(e.to_string()).unwrap(); + false + } + } +} From f71529d9b67115277c0daa1c112c8ad25534e230 Mon Sep 17 00:00:00 2001 From: mcb2003 Date: Sat, 12 Dec 2020 19:37:44 +0000 Subject: [PATCH 10/17] FFI: Make all functions with unsafe blocks completely unsafe. This is better than using unsafe blocks inside the functions, as that tells the compiler that the unsafeness won't leak out of the block, which isn't true in this case as we're dealing with another unsafe language. --- src/ffi.rs | 50 ++++++++++++++++++++++---------------------------- 1 file changed, 22 insertions(+), 28 deletions(-) diff --git a/src/ffi.rs b/src/ffi.rs index 6323ab6..bf44363 100644 --- a/src/ffi.rs +++ b/src/ffi.rs @@ -69,20 +69,18 @@ pub extern "C" fn tts_default() -> *mut TTS { /// Free the memory associated with a TTS object. /// If `tts` is a null pointer, this function does nothing. #[no_mangle] -pub extern "C" fn tts_free(tts: *mut TTS) { +pub unsafe extern "C" fn tts_free(tts: *mut TTS) { if tts.is_null() { return; } - unsafe { - Box::from_raw(tts); // Goes out of scope and is dropped - } + Box::from_raw(tts); // Goes out of scope and is dropped } /// Returns the features supported by this TTS engine. /// `tts` must be a valid pointer to a TTS object. #[no_mangle] -pub extern "C" fn tts_supported_features(tts: *mut TTS) -> Features { - unsafe { tts.as_ref().unwrap().supported_features() } +pub unsafe extern "C" fn tts_supported_features(tts: *mut TTS) -> Features { + tts.as_ref().unwrap().supported_features() } /// Speaks the specified text, optionally interrupting current speech. @@ -90,7 +88,7 @@ pub extern "C" fn tts_supported_features(tts: *mut TTS) -> Features { /// the backend doesn't provide one). /// Returns true on success, false on error or if `tts` is NULL. #[no_mangle] -pub extern "C" fn tts_speak( +pub unsafe extern "C" fn tts_speak( tts: *mut TTS, text: *const c_char, interrupt: bool, @@ -99,22 +97,20 @@ pub extern "C" fn tts_speak( if tts.is_null() { return true; } - unsafe { - let text = CStr::from_ptr(text).to_string_lossy().into_owned(); - match tts.as_mut().unwrap().speak(text, interrupt) { - Ok(u) => { - if !utterance.is_null() { - *utterance = match u { - Some(u) => Box::into_raw(Box::new(u)), - None => ptr::null_mut(), - }; - } - return true; - } - Err(e) => { - set_last_error(e.to_string()).unwrap(); - return false; + let text = CStr::from_ptr(text).to_string_lossy().into_owned(); + match tts.as_mut().unwrap().speak(text, interrupt) { + Ok(u) => { + if !utterance.is_null() { + *utterance = match u { + Some(u) => Box::into_raw(Box::new(u)), + None => ptr::null_mut(), + }; } + return true; + } + Err(e) => { + set_last_error(e.to_string()).unwrap(); + return false; } } } @@ -122,22 +118,20 @@ pub extern "C" fn tts_speak( /// Free the memory associated with an `UtteranceId`. /// Does nothing if `utterance` is NULL. #[no_mangle] -pub extern "C" fn tts_free_utterance(utterance: *mut UtteranceId) { +pub unsafe extern "C" fn tts_free_utterance(utterance: *mut UtteranceId) { if utterance.is_null() { return; } - unsafe { - Box::from_raw(utterance); - } + Box::from_raw(utterance); } /// Stops current speech. /// Returns true on success, false on error or if `tts` is NULL. #[no_mangle] -pub extern "C" fn tts_stop(tts: *mut TTS) -> bool { +pub unsafe extern "C" fn tts_stop(tts: *mut TTS) -> bool { if tts.is_null() { return false; } - match unsafe { tts.as_mut().unwrap().stop() } { + match tts.as_mut().unwrap().stop() { Ok(_) => true, Err(e) => { set_last_error(e.to_string()).unwrap(); From f4c7adfe5a1a061548fd830227057a316b3774d5 Mon Sep 17 00:00:00 2001 From: mcb2003 Date: Sat, 12 Dec 2020 20:04:48 +0000 Subject: [PATCH 11/17] FFI: Allow getting and setting rate, and getting min, normal and max rate. --- src/ffi.rs | 63 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 62 insertions(+), 1 deletion(-) diff --git a/src/ffi.rs b/src/ffi.rs index bf44363..542c7e0 100644 --- a/src/ffi.rs +++ b/src/ffi.rs @@ -1,4 +1,4 @@ -use libc::c_char; +use libc::{c_char, c_float}; use std::{ cell::RefCell, ffi::{CStr, CString, NulError}, @@ -139,3 +139,64 @@ pub unsafe extern "C" fn tts_stop(tts: *mut TTS) -> bool { } } } + +/// Returns the minimum rate for this speech synthesizer. +/// `tts` must be a valid pointer to a TTS object. +#[no_mangle] +pub unsafe extern "C" fn tts_min_rate(tts: *mut TTS) -> c_float { + tts.as_ref().unwrap().min_rate() +} + +/// Returns the maximum rate for this speech synthesizer. +/// `tts` must be a valid pointer to a TTS object. +#[no_mangle] +pub unsafe extern "C" fn tts_max_rate(tts: *mut TTS) -> c_float { + tts.as_ref().unwrap().max_rate() +} + +/// Returns the normal rate for this speech synthesizer. +/// `tts` must be a valid pointer to a TTS object. +#[no_mangle] +pub unsafe extern "C" fn tts_normal_rate(tts: *mut TTS) -> c_float { + tts.as_ref().unwrap().normal_rate() +} + +/// Gets the current speech rate. +/// Returns true on success, false on error (likely that the backend doesn't support rate changes) +/// or if `tts` is NULL. +/// Does nothing if `rate` is NULL. +#[no_mangle] +pub unsafe extern "C" fn tts_get_rate(tts: *mut TTS, rate: *mut c_float) -> bool { + if tts.is_null() { + return false; + } + match tts.as_ref().unwrap().get_rate() { + Ok(r) => { + if !rate.is_null() { + *rate = r; + } + true + } + Err(e) => { + set_last_error(e.to_string()).unwrap(); + false + } + } +} + +/// Sets the desired speech rate. +/// Returns true on success, false on error (likely that the backend doesn't support rate changes) +/// or if `tts` is NULL. +#[no_mangle] +pub unsafe extern "C" fn tts_set_rate(tts: *mut TTS, rate: c_float) -> bool { + if tts.is_null() { + return false; + } + match tts.as_mut().unwrap().set_rate(rate) { + Ok(_) => true, + Err(e) => { + set_last_error(e.to_string()).unwrap(); + false + } + } +} From aa47b12622cef3798a50040b8424d7f359633708 Mon Sep 17 00:00:00 2001 From: mcb2003 Date: Sat, 12 Dec 2020 20:20:22 +0000 Subject: [PATCH 12/17] FFI: Generate C bindings by default, not C++ More portible, and the FFI is much more C-like. --- cbindgen.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/cbindgen.toml b/cbindgen.toml index 7b647ff..533cc46 100644 --- a/cbindgen.toml +++ b/cbindgen.toml @@ -1,3 +1,4 @@ +language = "c" include_guard = "TTS_RS_H" autogen_warning = "/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */" From 73210d657264cb25a5106822168407c984c37ed8 Mon Sep 17 00:00:00 2001 From: mcb2003 Date: Sat, 12 Dec 2020 20:29:34 +0000 Subject: [PATCH 13/17] FFI: Allow getting and setting pitch, and getting min, normal and max pitch. --- src/ffi.rs | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/src/ffi.rs b/src/ffi.rs index 542c7e0..ec16519 100644 --- a/src/ffi.rs +++ b/src/ffi.rs @@ -200,3 +200,64 @@ pub unsafe extern "C" fn tts_set_rate(tts: *mut TTS, rate: c_float) -> bool { } } } + +/// Returns the minimum pitch for this speech synthesizer. +/// `tts` must be a valid pointer to a TTS object. +#[no_mangle] +pub unsafe extern "C" fn tts_min_pitch(tts: *mut TTS) -> c_float { + tts.as_ref().unwrap().min_pitch() +} + +/// Returns the maximum pitch for this speech synthesizer. +/// `tts` must be a valid pointer to a TTS object. +#[no_mangle] +pub unsafe extern "C" fn tts_max_pitch(tts: *mut TTS) -> c_float { + tts.as_ref().unwrap().max_pitch() +} + +/// Returns the normal pitch for this speech synthesizer. +/// `tts` must be a valid pointer to a TTS object. +#[no_mangle] +pub unsafe extern "C" fn tts_normal_pitch(tts: *mut TTS) -> c_float { + tts.as_ref().unwrap().normal_pitch() +} + +/// Gets the current speech pitch. +/// Returns true on success, false on error (likely that the backend doesn't support pitch changes) +/// or if `tts` is NULL. +/// Does nothing if `pitch` is NULL. +#[no_mangle] +pub unsafe extern "C" fn tts_get_pitch(tts: *mut TTS, pitch: *mut c_float) -> bool { + if tts.is_null() { + return false; + } + match tts.as_ref().unwrap().get_pitch() { + Ok(r) => { + if !pitch.is_null() { + *pitch = r; + } + true + } + Err(e) => { + set_last_error(e.to_string()).unwrap(); + false + } + } +} + +/// Sets the desired speech pitch. +/// Returns true on success, false on error (likely that the backend doesn't support pitch changes) +/// or if `tts` is NULL. +#[no_mangle] +pub unsafe extern "C" fn tts_set_pitch(tts: *mut TTS, pitch: c_float) -> bool { + if tts.is_null() { + return false; + } + match tts.as_mut().unwrap().set_pitch(pitch) { + Ok(_) => true, + Err(e) => { + set_last_error(e.to_string()).unwrap(); + false + } + } +} From a2c13dd21f18d627b4337042daf340c14015aa98 Mon Sep 17 00:00:00 2001 From: mcb2003 Date: Sat, 12 Dec 2020 20:31:30 +0000 Subject: [PATCH 14/17] FFI: Allow getting and setting volume, and getting min, normal and max volume. --- src/ffi.rs | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/src/ffi.rs b/src/ffi.rs index ec16519..b0fcd29 100644 --- a/src/ffi.rs +++ b/src/ffi.rs @@ -261,3 +261,64 @@ pub unsafe extern "C" fn tts_set_pitch(tts: *mut TTS, pitch: c_float) -> bool { } } } + +/// Returns the minimum volume for this speech synthesizer. +/// `tts` must be a valid pointer to a TTS object. +#[no_mangle] +pub unsafe extern "C" fn tts_min_volume(tts: *mut TTS) -> c_float { + tts.as_ref().unwrap().min_volume() +} + +/// Returns the maximum volume for this speech synthesizer. +/// `tts` must be a valid pointer to a TTS object. +#[no_mangle] +pub unsafe extern "C" fn tts_max_volume(tts: *mut TTS) -> c_float { + tts.as_ref().unwrap().max_volume() +} + +/// Returns the normal volume for this speech synthesizer. +/// `tts` must be a valid pointer to a TTS object. +#[no_mangle] +pub unsafe extern "C" fn tts_normal_volume(tts: *mut TTS) -> c_float { + tts.as_ref().unwrap().normal_volume() +} + +/// Gets the current speech volume. +/// Returns true on success, false on error (likely that the backend doesn't support volume changes) +/// or if `tts` is NULL. +/// Does nothing if `volume` is NULL. +#[no_mangle] +pub unsafe extern "C" fn tts_get_volume(tts: *mut TTS, volume: *mut c_float) -> bool { + if tts.is_null() { + return false; + } + match tts.as_ref().unwrap().get_volume() { + Ok(r) => { + if !volume.is_null() { + *volume = r; + } + true + } + Err(e) => { + set_last_error(e.to_string()).unwrap(); + false + } + } +} + +/// Sets the desired speech volume. +/// Returns true on success, false on error (likely that the backend doesn't support volume changes) +/// or if `tts` is NULL. +#[no_mangle] +pub unsafe extern "C" fn tts_set_volume(tts: *mut TTS, volume: c_float) -> bool { + if tts.is_null() { + return false; + } + match tts.as_mut().unwrap().set_volume(volume) { + Ok(_) => true, + Err(e) => { + set_last_error(e.to_string()).unwrap(); + false + } + } +} From f421dd9362f24fbf5bffe9803b206e9b38b9c422 Mon Sep 17 00:00:00 2001 From: mcb2003 Date: Sat, 12 Dec 2020 20:44:44 +0000 Subject: [PATCH 15/17] FFI: Implement tts_is_speaking() function. --- src/ffi.rs | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/ffi.rs b/src/ffi.rs index b0fcd29..f5dbb48 100644 --- a/src/ffi.rs +++ b/src/ffi.rs @@ -322,3 +322,28 @@ pub unsafe extern "C" fn tts_set_volume(tts: *mut TTS, volume: c_float) -> bool } } } + +/// fills `speaking` with a bool indicating whether this speech synthesizer is speaking. +/// Returns true on success, false on error (likely that the backend doesn't support speaking +/// status) or if `tts` is NULL. +/// If `speaking` is NULL, returns this value instead, meaning you can't tell the difference +/// between an error occuring and the synth not speaking. +#[no_mangle] +pub unsafe extern "C" fn tts_is_speaking(tts: *mut TTS, speaking: *mut bool) -> bool { + if tts.is_null() { + return false; + } + match tts.as_ref().unwrap().is_speaking() { + Ok(s) => { + if speaking.is_null() { + return s; + } + *speaking = s; + true + } + Err(e) => { + set_last_error(e.to_string()).unwrap(); + false + } + } +} From d6504a33e95dc8fb2979a0953201f71495310db9 Mon Sep 17 00:00:00 2001 From: mcb2003 Date: Sat, 12 Dec 2020 21:16:55 +0000 Subject: [PATCH 16/17] FFI: Make cbindgen generate extern C block for C++, use qualified screaming snake case for enum variants. --- cbindgen.toml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cbindgen.toml b/cbindgen.toml index 533cc46..ddb27e5 100644 --- a/cbindgen.toml +++ b/cbindgen.toml @@ -1,7 +1,12 @@ language = "c" +cpp_compat = true include_guard = "TTS_RS_H" +header = "/* SPDXLicenseIdentifier: MIT */" autogen_warning = "/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */" +[enum] +rename_variants = "QualifiedScreamingSnakeCase" + [defines] "target_os = linux" = "__linux__" "target_os = macos" = "__APPLE__" From 496d183fa4a8433522a44c5869a55029d677955a Mon Sep 17 00:00:00 2001 From: mcb2003 Date: Sat, 12 Dec 2020 21:37:24 +0000 Subject: [PATCH 17/17] FFI: Add module level doc comment. --- src/ffi.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ffi.rs b/src/ffi.rs index f5dbb48..6f14190 100644 --- a/src/ffi.rs +++ b/src/ffi.rs @@ -1,3 +1,5 @@ +//! Bindings to this library to allow it to be called from C/C++. + use libc::{c_char, c_float}; use std::{ cell::RefCell,