From 2ede7e4e2f0cbc759b810e8e8e358811e76d8fd5 Mon Sep 17 00:00:00 2001 From: Guilherme Lima Date: Sat, 7 Feb 2026 20:53:56 +0000 Subject: [PATCH 01/14] build: compile espeak voices and expose the data dir as an env var --- espeak-sys/build.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/espeak-sys/build.rs b/espeak-sys/build.rs index 57f9cc7..373b265 100644 --- a/espeak-sys/build.rs +++ b/espeak-sys/build.rs @@ -7,6 +7,10 @@ fn main() { let espeak_src = manifest_dir.join("espeak-ng"); let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); + // espeak-ng-data/ will contain the Espeak voices + let data_dir = out_dir.join("share/espeak-ng-data"); + println!("cargo:data-dir={}", data_dir.display()); + // for faster builds unsafe { env::set_var( @@ -29,10 +33,6 @@ fn main() { .define("USE_LIBSONIC", "OFF") .define("USE_LIBPCAUDIO", "OFF") .define("USE_SPEECHPLAYER", "OFF") - .define("COMPILE_INTONATIONS", "OFF") - .define("COMPILE_PHONEMES", "OFF") - .define("COMPILE_DICTIONARIES", "OFF") - .always_configure(false) .very_verbose(env::var("CMAKE_VERBOSE").is_ok()) .build(); From 27f48492bdd5d44eac98cd0a0a81ab455dee7555 Mon Sep 17 00:00:00 2001 From: Guilherme Lima Date: Sat, 7 Feb 2026 21:41:00 +0000 Subject: [PATCH 02/14] feat: implement EspeakSynth constructors with espeak data dir --- build.rs | 6 +++++ espeak-sys/Cargo.toml | 2 ++ src/lib.rs | 61 +++++++++++++++++++++++++++++++++++++++---- 3 files changed, 64 insertions(+), 5 deletions(-) create mode 100644 build.rs diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..e2bde25 --- /dev/null +++ b/build.rs @@ -0,0 +1,6 @@ +fn main() { + let data_dir = + std::env::var("DEP_ESPEAK_SYS_DATA_DIR").expect("espeak-sys should export data-dir"); + + println!("cargo:rustc-env=ESPEAK_NG_DATA_DIR={}", data_dir); +} diff --git a/espeak-sys/Cargo.toml b/espeak-sys/Cargo.toml index d1d6523..aeb32ba 100644 --- a/espeak-sys/Cargo.toml +++ b/espeak-sys/Cargo.toml @@ -5,6 +5,8 @@ edition.workspace = true authors.workspace = true license.workspace = true +links = "espeak-sys" + [dependencies] [build-dependencies] diff --git a/src/lib.rs b/src/lib.rs index b93cf3f..c8cae36 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,49 @@ -pub fn add(left: u64, right: u64) -> u64 { - left + right +use std::ffi::CString; +use std::num::NonZeroU32; +use std::path::Path; + +use espeak_sys::*; + +pub struct EspeakSynth { + sample_rate: NonZeroU32, +} + +impl Default for EspeakSynth { + fn default() -> Self { + let data_dir = env!("ESPEAK_NG_DATA_DIR"); + Self::new(Path::new(data_dir)) + } +} + +impl EspeakSynth { + pub fn new(data_dir: &Path) -> Self { + if !data_dir.exists() { + panic!( + "espeak-ng-data directory does not exist: {}", + data_dir.display() + ) + } + + let data_dir = CString::new(data_dir.to_str().unwrap()).unwrap(); + + let sample_rate = unsafe { + espeak_Initialize( + espeak_AUDIO_OUTPUT_AUDIO_OUTPUT_SYNCHRONOUS, + 0, + data_dir.as_ptr(), + 0, + ) + }; + + assert!( + sample_rate > 0, + "Espeak initialization failed with EE_INTERNAL_ERROR" + ); + + Self { + sample_rate: NonZeroU32::new(sample_rate as u32).unwrap(), + } + } } #[cfg(test)] @@ -7,8 +51,15 @@ mod tests { use super::*; #[test] - fn it_works() { - let result = add(2, 2); - assert_eq!(result, 4); + fn default_initializes_espeak() { + let espeak = EspeakSynth::default(); + assert!(espeak.sample_rate.get() >= 22050); + } + + #[test] + #[should_panic = "espeak-ng-data directory does not exist: ./invalid"] + fn new_invalid_data_dir_panics() { + let invalid_dir = Path::new("./invalid"); + let _ = EspeakSynth::new(invalid_dir); } } From bf4394a31842b98fe34cb94f02b6ee0679fe9bfc Mon Sep 17 00:00:00 2001 From: Guilherme Lima Date: Sat, 7 Feb 2026 21:43:43 +0000 Subject: [PATCH 03/14] feat: implement EspeakSynth::sample_rate() --- src/lib.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index c8cae36..31dd42c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -44,6 +44,10 @@ impl EspeakSynth { sample_rate: NonZeroU32::new(sample_rate as u32).unwrap(), } } + + pub fn sample_rate(&self) -> NonZeroU32 { + self.sample_rate + } } #[cfg(test)] From 75f0cbb835a50f1511da537fe4533a8fed067dba Mon Sep 17 00:00:00 2001 From: Guilherme Lima Date: Sat, 7 Feb 2026 21:45:05 +0000 Subject: [PATCH 04/14] feat: implement Drop for EspeakSynth --- src/lib.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 31dd42c..4101602 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,6 +15,12 @@ impl Default for EspeakSynth { } } +impl Drop for EspeakSynth { + fn drop(&mut self) { + unsafe { espeak_Terminate() }; + } +} + impl EspeakSynth { pub fn new(data_dir: &Path) -> Self { if !data_dir.exists() { From b1c58203ee1e3df1f790a68f10efa2e775be91c7 Mon Sep 17 00:00:00 2001 From: Guilherme Lima Date: Sat, 7 Feb 2026 22:02:52 +0000 Subject: [PATCH 05/14] feat: implement EspeakSynth::available_voices() and introduce EspeakError enum --- Cargo.lock | 21 ++++++++++++++++++++ Cargo.toml | 1 + src/error.rs | 7 +++++++ src/lib.rs | 55 ++++++++++++++++++++++++++++++++++++++++++++++++---- 4 files changed, 80 insertions(+), 4 deletions(-) create mode 100644 src/error.rs diff --git a/Cargo.lock b/Cargo.lock index 8051905..38c2d98 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -93,6 +93,7 @@ name = "espeak-synth" version = "0.1.0" dependencies = [ "espeak-sys", + "thiserror", ] [[package]] @@ -248,6 +249,26 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "unicode-ident" version = "1.0.22" diff --git a/Cargo.toml b/Cargo.toml index 2d65a23..01c3bee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,3 +17,4 @@ license.workspace = true [dependencies] espeak-sys = { path = "espeak-sys" } +thiserror = "2.0.18" diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..df472a5 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,7 @@ +use thiserror::Error; + +#[derive(Clone, Debug, Error)] +pub enum EspeakError { + #[error("failed to list voices: {0}")] + ListVoices(String), +} diff --git a/src/lib.rs b/src/lib.rs index 4101602..0565299 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,9 +1,13 @@ -use std::ffi::CString; +use std::ffi::{CStr, CString}; use std::num::NonZeroU32; use std::path::Path; +use std::ptr; use espeak_sys::*; +mod error; +pub use error::*; + pub struct EspeakSynth { sample_rate: NonZeroU32, } @@ -54,6 +58,41 @@ impl EspeakSynth { pub fn sample_rate(&self) -> NonZeroU32 { self.sample_rate } + + pub fn available_voices(&self) -> Result, EspeakError> { + let voices_ptr = unsafe { espeak_ListVoices(ptr::null_mut()) }; + if voices_ptr.is_null() { + return Err(EspeakError::ListVoices( + "espeak_ListVoices returned a null pointer".to_owned(), + )); + } + + let mut voices: Vec = Vec::new(); + let mut i = 0; + + loop { + let voice = unsafe { *voices_ptr.add(i) }; + if voice.is_null() { + break; + } + + unsafe { + let voice = &*voice; + if !voice.name.is_null() { + let name = CStr::from_ptr(voice.name) + .to_str() + .map_err(|e| EspeakError::ListVoices(e.to_string()))? + .to_string(); + + voices.push(name); + } + } + + i += 1; + } + + Ok(voices) + } } #[cfg(test)] @@ -68,8 +107,16 @@ mod tests { #[test] #[should_panic = "espeak-ng-data directory does not exist: ./invalid"] - fn new_invalid_data_dir_panics() { - let invalid_dir = Path::new("./invalid"); - let _ = EspeakSynth::new(invalid_dir); + fn new_with_non_existent_data_dir_panics() { + let non_existent = Path::new("./invalid"); + let _ = EspeakSynth::new(non_existent); + } + + #[test] + fn available_voices_valid_data_dir_result_contains_expected_voices() { + let espeak = EspeakSynth::default(); + let voices = espeak.available_voices().unwrap(); + assert!(voices.contains(&"German".to_owned())); + assert!(voices.contains(&"English (Great Britain)".to_owned())); } } From b666560bf3b81535a6010063f1e4c712caf17261 Mon Sep 17 00:00:00 2001 From: Guilherme Lima Date: Sat, 7 Feb 2026 22:39:05 +0000 Subject: [PATCH 06/14] feat: add error variants --- src/error.rs | 58 +++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 53 insertions(+), 5 deletions(-) diff --git a/src/error.rs b/src/error.rs index df472a5..98428c6 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,7 +1,55 @@ -use thiserror::Error; +use std::{ffi::NulError, str::Utf8Error}; -#[derive(Clone, Debug, Error)] -pub enum EspeakError { - #[error("failed to list voices: {0}")] - ListVoices(String), +use espeak_sys::{ + espeak_ERROR, espeak_ERROR_EE_BUFFER_FULL, espeak_ERROR_EE_INTERNAL_ERROR, + espeak_ERROR_EE_NOT_FOUND, +}; + +#[derive(Clone, Debug, thiserror::Error)] +pub enum Error { + #[error("espeak operation failed: {}", espeak_error_msg(*.0))] + Espeak(espeak_ERROR), + + #[error("no voices available")] + NoVoicesAvailable, + + #[error(transparent)] + NullPointer(#[from] NulError), + + #[error(transparent)] + InvalidUtf8(#[from] Utf8Error), +} + +fn espeak_error_msg(code: espeak_ERROR) -> &'static str { + match code { + espeak_ERROR_EE_INTERNAL_ERROR => "internal error", + espeak_ERROR_EE_BUFFER_FULL => "buffer full", + espeak_ERROR_EE_NOT_FOUND => "not found", + _ => "unknown error", + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn espeak_variant_to_string_returns_expected() { + assert_eq!( + Error::Espeak(espeak_ERROR_EE_INTERNAL_ERROR).to_string(), + "espeak operation failed: internal error" + ); + assert_eq!( + Error::Espeak(espeak_ERROR_EE_BUFFER_FULL).to_string(), + "espeak operation failed: buffer full" + ); + assert_eq!( + Error::Espeak(espeak_ERROR_EE_NOT_FOUND).to_string(), + "espeak operation failed: not found" + ); + assert_eq!( + Error::Espeak(10).to_string(), + "espeak operation failed: unknowne" + ); + } } From 4da60b84192664c936aed33ece05091f66cc6e21 Mon Sep 17 00:00:00 2001 From: Guilherme Lima Date: Sat, 7 Feb 2026 22:42:55 +0000 Subject: [PATCH 07/14] feat: implement EspeakSynth::set_voice() --- src/error.rs | 2 +- src/lib.rs | 37 ++++++++++++++++++++++++++++--------- 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/src/error.rs b/src/error.rs index 98428c6..e3302e9 100644 --- a/src/error.rs +++ b/src/error.rs @@ -49,7 +49,7 @@ mod tests { ); assert_eq!( Error::Espeak(10).to_string(), - "espeak operation failed: unknowne" + "espeak operation failed: unknown error" ); } } diff --git a/src/lib.rs b/src/lib.rs index 0565299..c006deb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -59,12 +59,10 @@ impl EspeakSynth { self.sample_rate } - pub fn available_voices(&self) -> Result, EspeakError> { + pub fn available_voices(&self) -> Result, Error> { let voices_ptr = unsafe { espeak_ListVoices(ptr::null_mut()) }; if voices_ptr.is_null() { - return Err(EspeakError::ListVoices( - "espeak_ListVoices returned a null pointer".to_owned(), - )); + return Err(Error::NoVoicesAvailable); } let mut voices: Vec = Vec::new(); @@ -79,11 +77,7 @@ impl EspeakSynth { unsafe { let voice = &*voice; if !voice.name.is_null() { - let name = CStr::from_ptr(voice.name) - .to_str() - .map_err(|e| EspeakError::ListVoices(e.to_string()))? - .to_string(); - + let name = CStr::from_ptr(voice.name).to_str()?.to_string(); voices.push(name); } } @@ -93,6 +87,17 @@ impl EspeakSynth { Ok(voices) } + + fn set_voice(&self, voice: &str) -> Result<(), Error> { + let s = CString::new(voice)?; + + let result = unsafe { espeak_SetVoiceByName(s.as_ptr()) }; + if result != espeak_ERROR_EE_OK { + return Err(Error::Espeak(result)); + } + + Ok(()) + } } #[cfg(test)] @@ -119,4 +124,18 @@ mod tests { assert!(voices.contains(&"German".to_owned())); assert!(voices.contains(&"English (Great Britain)".to_owned())); } + + #[test] + fn set_voice_valid_returns_ok() { + let espeak = EspeakSynth::default(); + let result = espeak.set_voice("German"); + assert!(result.is_ok()); + } + + #[test] + fn set_voice_invalid_returns_ee_not_found_err() { + let espeak = EspeakSynth::default(); + let err = espeak.set_voice("Invalid").unwrap_err(); + assert!(matches!(err, Error::Espeak(code) if code == espeak_ERROR_EE_NOT_FOUND)); + } } From 930facef75c55d7e828f40070082450b8dd3c2e8 Mon Sep 17 00:00:00 2001 From: Guilherme Lima Date: Sat, 7 Feb 2026 23:23:30 +0000 Subject: [PATCH 08/14] feat: implement EspeakSynth::set_parameter and introduce Parameter type --- src/error.rs | 29 +++++++++++++++ src/lib.rs | 34 +++++++++++++++++- src/parameter.rs | 91 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 153 insertions(+), 1 deletion(-) create mode 100644 src/parameter.rs diff --git a/src/error.rs b/src/error.rs index e3302e9..dc2f017 100644 --- a/src/error.rs +++ b/src/error.rs @@ -5,6 +5,8 @@ use espeak_sys::{ espeak_ERROR_EE_NOT_FOUND, }; +use super::EspeakParam; + #[derive(Clone, Debug, thiserror::Error)] pub enum Error { #[error("espeak operation failed: {}", espeak_error_msg(*.0))] @@ -13,6 +15,9 @@ pub enum Error { #[error("no voices available")] NoVoicesAvailable, + #[error("invalid value for '{0:?}': {1}")] + InvalidParamValue(EspeakParam, u32), + #[error(transparent)] NullPointer(#[from] NulError), @@ -52,4 +57,28 @@ mod tests { "espeak operation failed: unknown error" ); } + + #[test] + fn invalid_param_value_variant_to_string_returns_expected() { + assert_eq!( + Error::InvalidParamValue(EspeakParam::Amplitude, 666).to_string(), + "invalid value for 'Amplitude': 666" + ); + assert_eq!( + Error::InvalidParamValue(EspeakParam::Pitch, 666).to_string(), + "invalid value for 'Pitch': 666" + ); + assert_eq!( + Error::InvalidParamValue(EspeakParam::PitchRange, 666).to_string(), + "invalid value for 'PitchRange': 666" + ); + assert_eq!( + Error::InvalidParamValue(EspeakParam::Speed, 666).to_string(), + "invalid value for 'Speed': 666" + ); + assert_eq!( + Error::InvalidParamValue(EspeakParam::WordGap, 666).to_string(), + "invalid value for 'WordGap': 666" + ); + } } diff --git a/src/lib.rs b/src/lib.rs index c006deb..868943d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,4 @@ -use std::ffi::{CStr, CString}; +use std::ffi::{CStr, CString, c_int}; use std::num::NonZeroU32; use std::path::Path; use std::ptr; @@ -6,7 +6,10 @@ use std::ptr; use espeak_sys::*; mod error; +mod parameter; + pub use error::*; +pub use parameter::*; pub struct EspeakSynth { sample_rate: NonZeroU32, @@ -98,6 +101,17 @@ impl EspeakSynth { Ok(()) } + + fn set_parameter(&self, param: EspeakParam, value: u32) -> Result<(), Error> { + validate_param_value(param, value)?; + + let result = unsafe { espeak_SetParameter(param as _, value as _, 0) }; + if result != espeak_ERROR_EE_OK { + return Err(Error::Espeak(result)); + } + + Ok(()) + } } #[cfg(test)] @@ -138,4 +152,22 @@ mod tests { let err = espeak.set_voice("Invalid").unwrap_err(); assert!(matches!(err, Error::Espeak(code) if code == espeak_ERROR_EE_NOT_FOUND)); } + + #[test] + fn set_parameter_valid_case_returns_ok() { + let espeak = EspeakSynth::default(); + let result = espeak.set_parameter(EspeakParam::Amplitude, 100); + assert!(result.is_ok()); + } + + #[test] + fn set_parameter_invalid_returns_err() { + let espeak = EspeakSynth::default(); + let err = espeak + .set_parameter(EspeakParam::Amplitude, 101) + .unwrap_err(); + assert!( + matches!(err, Error::InvalidParamValue(p, v) if p == EspeakParam::Amplitude && v == 101) + ); + } } diff --git a/src/parameter.rs b/src/parameter.rs new file mode 100644 index 0000000..34eeaa1 --- /dev/null +++ b/src/parameter.rs @@ -0,0 +1,91 @@ +use super::Error; + +pub const MAX_AMPLITUDE: u32 = 100; + +pub const MAX_PITCH: u32 = 100; + +pub const MAX_PITCH_RANGE: u32 = 100; + +pub const MAX_WORD_GAP: u32 = 100; + +pub const MIN_SPEED: u32 = 80; + +pub const MAX_SPEED: u32 = 450; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum EspeakParam { + Amplitude = 2, + Pitch = 3, + PitchRange = 4, + Speed = 1, + WordGap = 7, +} + +pub(crate) fn validate_param_value(param: EspeakParam, value: u32) -> Result<(), Error> { + let (min, max) = match param { + EspeakParam::Amplitude => (0, MAX_AMPLITUDE), + EspeakParam::Pitch => (0, MAX_PITCH), + EspeakParam::PitchRange => (0, MAX_PITCH_RANGE), + EspeakParam::Speed => (MIN_SPEED, MAX_SPEED), + EspeakParam::WordGap => (0, MAX_WORD_GAP), + }; + + if !(min..=max).contains(&value) { + return Err(Error::InvalidParamValue(param, value)); + } + + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn validate_param_value_valid_cases_return_ok() { + let test_cases = vec![ + (EspeakParam::Amplitude, 0), + (EspeakParam::Amplitude, 50), + (EspeakParam::Amplitude, 100), + (EspeakParam::Pitch, 0), + (EspeakParam::Pitch, 50), + (EspeakParam::Pitch, 100), + (EspeakParam::PitchRange, 0), + (EspeakParam::PitchRange, 50), + (EspeakParam::PitchRange, 100), + (EspeakParam::Speed, 80), + (EspeakParam::Speed, 200), + (EspeakParam::Speed, 450), + (EspeakParam::WordGap, 0), + (EspeakParam::WordGap, 50), + (EspeakParam::WordGap, 100), + ]; + + for (param, val) in test_cases { + assert!( + validate_param_value(param, val).is_ok(), + "({param:?}, {val}) returned err" + ); + } + } + + #[test] + fn validate_param_value_invalid_cases_return_err() { + let test_cases = vec![ + (EspeakParam::Amplitude, 101), + (EspeakParam::Pitch, 101), + (EspeakParam::PitchRange, 101), + (EspeakParam::Speed, 79), + (EspeakParam::Speed, 451), + (EspeakParam::WordGap, 101), + ]; + + for (param, val) in test_cases { + let result = validate_param_value(param, val); + assert!(result.is_err(), "({param:?}, {val}) returned err"); + assert!( + matches!(result.unwrap_err(), Error::InvalidParamValue(p, v) if p == param && v == val) + ); + } + } +} From 40fba99f57a1bb2122e9be3e9e01578a109be97c Mon Sep 17 00:00:00 2001 From: Guilherme Lima Date: Sat, 7 Feb 2026 23:25:05 +0000 Subject: [PATCH 09/14] fix: make wrongly private methods pubcli --- src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 868943d..eb04f69 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,4 @@ -use std::ffi::{CStr, CString, c_int}; +use std::ffi::{CStr, CString}; use std::num::NonZeroU32; use std::path::Path; use std::ptr; @@ -91,7 +91,7 @@ impl EspeakSynth { Ok(voices) } - fn set_voice(&self, voice: &str) -> Result<(), Error> { + pub fn set_voice(&self, voice: &str) -> Result<(), Error> { let s = CString::new(voice)?; let result = unsafe { espeak_SetVoiceByName(s.as_ptr()) }; @@ -102,7 +102,7 @@ impl EspeakSynth { Ok(()) } - fn set_parameter(&self, param: EspeakParam, value: u32) -> Result<(), Error> { + pub fn set_parameter(&self, param: EspeakParam, value: u32) -> Result<(), Error> { validate_param_value(param, value)?; let result = unsafe { espeak_SetParameter(param as _, value as _, 0) }; From a102ebc8e4e42915145d57691e5e6cfae6822e19 Mon Sep 17 00:00:00 2001 From: Guilherme Lima Date: Sat, 7 Feb 2026 23:27:11 +0000 Subject: [PATCH 10/14] fix: fix Clippy warning --- src/error.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/error.rs b/src/error.rs index dc2f017..50da08f 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,8 +1,8 @@ use std::{ffi::NulError, str::Utf8Error}; use espeak_sys::{ - espeak_ERROR, espeak_ERROR_EE_BUFFER_FULL, espeak_ERROR_EE_INTERNAL_ERROR, - espeak_ERROR_EE_NOT_FOUND, + espeak_ERROR, espeak_ERROR_EE_BUFFER_FULL as BUFFER_FULL, + espeak_ERROR_EE_INTERNAL_ERROR as INTERNAL_ERROR, espeak_ERROR_EE_NOT_FOUND as NOT_FOUND, }; use super::EspeakParam; @@ -27,9 +27,9 @@ pub enum Error { fn espeak_error_msg(code: espeak_ERROR) -> &'static str { match code { - espeak_ERROR_EE_INTERNAL_ERROR => "internal error", - espeak_ERROR_EE_BUFFER_FULL => "buffer full", - espeak_ERROR_EE_NOT_FOUND => "not found", + INTERNAL_ERROR => "internal error", + BUFFER_FULL => "buffer full", + NOT_FOUND => "not found", _ => "unknown error", } } @@ -41,15 +41,15 @@ mod tests { #[test] fn espeak_variant_to_string_returns_expected() { assert_eq!( - Error::Espeak(espeak_ERROR_EE_INTERNAL_ERROR).to_string(), + Error::Espeak(INTERNAL_ERROR).to_string(), "espeak operation failed: internal error" ); assert_eq!( - Error::Espeak(espeak_ERROR_EE_BUFFER_FULL).to_string(), + Error::Espeak(BUFFER_FULL).to_string(), "espeak operation failed: buffer full" ); assert_eq!( - Error::Espeak(espeak_ERROR_EE_NOT_FOUND).to_string(), + Error::Espeak(NOT_FOUND).to_string(), "espeak operation failed: not found" ); assert_eq!( From 0b6087c659013dd0d402ee1bf4410de6a8c0d547 Mon Sep 17 00:00:00 2001 From: Guilherme Lima Date: Sat, 7 Feb 2026 23:29:34 +0000 Subject: [PATCH 11/14] fix: fix Clippy warning --- espeak-sys/src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/espeak-sys/src/lib.rs b/espeak-sys/src/lib.rs index 15bf66a..4908bae 100644 --- a/espeak-sys/src/lib.rs +++ b/espeak-sys/src/lib.rs @@ -2,5 +2,7 @@ #![allow(non_camel_case_types)] #![allow(non_snake_case)] #![allow(unnecessary_transmutes)] +#![allow(clippy::ptr_offset_with_cast)] +#![allow(clippy::missing_safety_doc)] include!(concat!(env!("OUT_DIR"), "/bindings.rs")); From 2c1b0c0ab85d579a6204c06f77b08bd200a40628 Mon Sep 17 00:00:00 2001 From: Guilherme Lima Date: Sat, 7 Feb 2026 23:34:12 +0000 Subject: [PATCH 12/14] feat: implement synth callback and update our initialization logic to pass it to espeak --- src/callback.rs | 77 +++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 6 +++- 2 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 src/callback.rs diff --git a/src/callback.rs b/src/callback.rs new file mode 100644 index 0000000..109ce18 --- /dev/null +++ b/src/callback.rs @@ -0,0 +1,77 @@ +use espeak_sys::espeak_EVENT; +use std::ffi::{c_int, c_short}; + +pub(crate) unsafe extern "C" fn synth_callback( + wav: *mut c_short, + num_samples: c_int, + events: *mut espeak_EVENT, +) -> c_int { + if wav.is_null() || num_samples <= 0 || events.is_null() { + return 0; + } + + let user_data = unsafe { (*events).user_data }; + if user_data.is_null() { + return 0; + } + + let buffer = unsafe { &mut *(user_data as *mut Vec) }; + let slice = unsafe { std::slice::from_raw_parts(wav, num_samples as usize) }; + buffer.extend_from_slice(slice); + + 0 +} + +#[cfg(test)] +mod tests { + use super::*; + use std::ptr; + + fn create_mock_event(user_data: *mut std::ffi::c_void) -> espeak_EVENT { + espeak_EVENT { + type_: 0, + unique_identifier: 0, + text_position: 0, + length: 0, + audio_position: 0, + sample: 0, + user_data, + id: espeak_sys::espeak_EVENT__bindgen_ty_1 { number: 0 }, + } + } + + #[test] + fn appends_samples_to_buffer() { + let mut buffer: Vec = Vec::new(); + let mut wav_data: Vec = vec![1, 2, 3, 4, 5, 6]; + let mut event = create_mock_event(&mut buffer as *mut Vec as *mut _); + + let result = + unsafe { synth_callback(wav_data.as_mut_ptr(), wav_data.len() as c_int, &mut event) }; + + assert_eq!(result, 0); + assert_eq!(buffer, vec![1, 2, 3, 4, 5, 6]); + } + + #[test] + fn null_wav_returns_zero() { + let mut buffer: Vec = Vec::new(); + let mut event = create_mock_event(&mut buffer as *mut Vec as *mut _); + + let result = unsafe { synth_callback(ptr::null_mut(), 10, &mut event) }; + + assert_eq!(result, 0); + assert!(buffer.is_empty()); + } + + #[test] + fn null_user_data_returns_zero() { + let mut wav_data: Vec = vec![1, 2, 3]; + let mut event = create_mock_event(ptr::null_mut()); + + let result = + unsafe { synth_callback(wav_data.as_mut_ptr(), wav_data.len() as c_int, &mut event) }; + + assert_eq!(result, 0); + } +} diff --git a/src/lib.rs b/src/lib.rs index eb04f69..0b74c6d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,6 +5,7 @@ use std::ptr; use espeak_sys::*; +mod callback; mod error; mod parameter; @@ -38,7 +39,6 @@ impl EspeakSynth { } let data_dir = CString::new(data_dir.to_str().unwrap()).unwrap(); - let sample_rate = unsafe { espeak_Initialize( espeak_AUDIO_OUTPUT_AUDIO_OUTPUT_SYNCHRONOUS, @@ -53,6 +53,10 @@ impl EspeakSynth { "Espeak initialization failed with EE_INTERNAL_ERROR" ); + unsafe { + espeak_SetSynthCallback(Some(callback::synth_callback)); + }; + Self { sample_rate: NonZeroU32::new(sample_rate as u32).unwrap(), } From ed840102995e8e5aab3568270fe8d72928120552 Mon Sep 17 00:00:00 2001 From: Guilherme Lima Date: Sat, 7 Feb 2026 23:57:23 +0000 Subject: [PATCH 13/14] feat: implement EspeakSynth::synthesize --- Cargo.lock | 7 ++++ Cargo.toml | 3 ++ src/lib.rs | 63 ++++++++++++++++++++++++++++++++- testdata/dies_ist_ein_test.wav | Bin 0 -> 72354 bytes 4 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 testdata/dies_ist_ein_test.wav diff --git a/Cargo.lock b/Cargo.lock index 38c2d98..6bb65e6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -93,6 +93,7 @@ name = "espeak-synth" version = "0.1.0" dependencies = [ "espeak-sys", + "hound", "thiserror", ] @@ -116,6 +117,12 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" +[[package]] +name = "hound" +version = "3.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62adaabb884c94955b19907d60019f4e145d091c75345379e70d1ee696f7854f" + [[package]] name = "itertools" version = "0.13.0" diff --git a/Cargo.toml b/Cargo.toml index 01c3bee..75515b0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,3 +18,6 @@ license.workspace = true [dependencies] espeak-sys = { path = "espeak-sys" } thiserror = "2.0.18" + +[dev-dependencies] +hound = "3.5.1" diff --git a/src/lib.rs b/src/lib.rs index 0b74c6d..6c894f0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,4 @@ -use std::ffi::{CStr, CString}; +use std::ffi::{CStr, CString, c_void}; use std::num::NonZeroU32; use std::path::Path; use std::ptr; @@ -66,6 +66,28 @@ impl EspeakSynth { self.sample_rate } + pub fn synthesize(&self, text: &str, audio_buffer: &mut Vec) -> Result<(), Error> { + let text = CString::new(text)?; + let result = unsafe { + espeak_Synth( + text.as_ptr().cast(), + text.as_bytes_with_nul().len(), + 0, + espeak_POSITION_TYPE_POS_WORD, + 0, + 0, + ptr::null_mut(), + audio_buffer as *mut Vec as *mut c_void, + ) + }; + + if result != espeak_ERROR_EE_OK { + return Err(Error::Espeak(result)); + } + + Ok(()) + } + pub fn available_voices(&self) -> Result, Error> { let voices_ptr = unsafe { espeak_ListVoices(ptr::null_mut()) }; if voices_ptr.is_null() { @@ -121,6 +143,13 @@ impl EspeakSynth { #[cfg(test)] mod tests { use super::*; + use hound::WavReader; + + const REFERENCE_OUTPUT_WAV: &str = "testdata/dies_ist_ein_test.wav"; + const REFERENCE_TEXT: &str = "Dies ist ein Test"; + const REFERENCE_VOICE: &str = "German"; + const REFERENCE_PITCH: u32 = 40; + const REFERNECE_SPEED: u32 = 80; #[test] fn default_initializes_espeak() { @@ -174,4 +203,36 @@ mod tests { matches!(err, Error::InvalidParamValue(p, v) if p == EspeakParam::Amplitude && v == 101) ); } + + #[test] + fn synthesize_with_default_settings_works() { + let espeak = EspeakSynth::default(); + let mut buffer = Vec::new(); + + espeak.synthesize("test", &mut buffer).unwrap(); + assert!(!buffer.is_empty()); + } + + #[test] + fn synthesize_known_settings_result_matches_reference() { + let espeak = EspeakSynth::default(); + let mut buffer = Vec::new(); + + espeak.set_voice(REFERENCE_VOICE).unwrap(); + espeak + .set_parameter(EspeakParam::Pitch, REFERENCE_PITCH) + .unwrap(); + espeak + .set_parameter(EspeakParam::Speed, REFERNECE_SPEED) + .unwrap(); + + espeak.synthesize(REFERENCE_TEXT, &mut buffer).unwrap(); + assert!(!buffer.is_empty()); + + let reference_wav = WavReader::open(REFERENCE_OUTPUT_WAV).unwrap(); + let reference_samples: Vec = + reference_wav.into_samples().map(|s| s.unwrap()).collect(); + + assert_eq!(buffer, reference_samples); + } } diff --git a/testdata/dies_ist_ein_test.wav b/testdata/dies_ist_ein_test.wav new file mode 100644 index 0000000000000000000000000000000000000000..7c1b80c1edafa89c44427216653f81507379ab47 GIT binary patch literal 72354 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%0D7+JBD`;6$N?;z%BzO53Mki^C zjNf*6!ZpmSE|p5XqNYnyhy>0`OkqOFhe8Z^vyZz)ZIh%@i}(P4pBcg&T8Ci485v@#G`4}jJ^iHm{<*Cvs8X-jbg#@}enTF?{#T-sk$5|=nw!jtqJce;G910NIcB9b(cX@A#?UYBR|lP^cqx=?&MDIL|kS zbhaj|0p)ordV_2g?n0}r(9@%YoQo$OOD+>rg(A@X7Bn`qfXA@OJBb*Y`~RPgo* z@yIXdhA>rVSHMZ0m2aT;1om;F&058BzC?^X8M1T|ch(cjgs*`?{CqB-{Th%`q83fX ztEp6uu#RgCeoPfELpCnK&Lo5HU6~_7G1riP2(-*apsWY;_5m-gFO@4vB1^rUc^(#M z9M_z%OSdrXU?Z#09qEQxVRvP=Nt~E zWYBkL4`oFzVG4Ng9MO8q~dS%c0FqQ>g+0+9zPX#O~GGdL3wfH zNwA+8;#LbgH83oZzsWXeuC5_ebZql ztcvJMLou_{@VjGS9dF^Ob@3EYzOUi+D$xDipml3V%SAj>9GmzRijmiXUp|UYCqY)f zfR?2g8?*|NxeRg?PxOU`yo2=~1ude%KT-Ck!e@$}g1T#PC()bNf~3r& zl;CzPy!!>ueF*t$j(MA*XW}*D-w%;l6~J9T!Mk6^TtxX9i`XRWqu%m3GNr%#P>u!fot%+@mebV@X?}e`iagB;k|&QA&LPeuM-6)o$9zu(w@xx9C|YL^sU2Q3qFtpd z{eYmZvRFwwdMj}-u*&0bb@QvprLrzcz3drT2|HM^{zaZSHl49)$$_H5B{huwEnl3! z2gWj)%G<#z%}VvmpzSIxIwl#Yz3f=`K*t4pbEn?j(!+V$d2_sL{a*vg!arnn+C#r* zw9+(LKgAYhuu);m+0eSX;I=!d}mn=G)2#(`Ti}6<<^f)UAS_ zsN;g>D=JABQxEx{*!kX3?kHEf)8;(kit*I(?PL4!6giEaEMcWK*+_X$`MSeq`y#0K){2K#*@%M=WDwCNgT`H5v1=%dwXz2@_z5h*2=Enpw z{L|1!G|9WlTf_IOzeXSq)~zcQ&pePs$Z}*V`32b*^a~APYE!|)J1&#;`1X5mc_w%^ zc;daId=47)Soriw0D9(E5fPnew8cIe$INza@s60&8{eOw&W}1*}CZPjHvL~ z)Yy~Ju5g>?rJ|;65wnw6;$PwzZufeRP>HHK!HpCKsy*lNE%nrOye}VHP&?1~arGzP z*TS;vwoQQvviF)DAqPU5>u+gaX;RdE6f(;1Wo^GyWSJT3HG3;3>)h&^?!M`*5!fs2 zp?+s}N-jx9$@eP11Z@g#sJR+kS1nN{ORU03{~1?J`#MWAbDD9qv6uO(&F4AGUu7~B zDe7Of8}vgBJN5C}Awjva7gR3S-?zo}*51wb%qq3@un%yCd#3tT{5~?9St6UGxS(vO zs;ug%{2+fW?aur}PT)_mBYjIevF?GcSl1m_oTt9;G&_awMCzG~(ns=o$}Ht+WuYQj zK2&;{K28kd+W9Ye-n-5?J2;m>qRx4G_^w63D9 zt%-B5yN0)ouPytMpGY^72UKU(lY^&eTIyDWGz|Y7J|(n?x;|OpdR^fxuT-|ABRadsnp~!zz2~KK^^HQ#C}$H2 z{}Pwle!V;`FXv^Gr++;8>xJe+`+^S@3BrM3Yee7Z;;5#T2ZzrM+o@lp`d8RyA5vy3 zOe$(uMwqiIqOE1tbN0(_GEfb53;SdR;C6y~hIY1LO6a@L=OIsZr-SxLrf}O_eJ$6^ zLrdxub}raku(R6h<Xdxpi1%%bOc}7 zm*$MOWmX(A-!Q+dXlXm|l=(h#*Qu_uYO18*HQN2U>AGS~nEEG$QZk!Z!k%+av&UN+ zn=hM=n@?NJc7?~r{zTrAUR2%FT-FZ?ITW(NP*=BHeL>!op3FV;?6oIV#2NdRg_Nx> zA8MA`^z(T zafwLO)UWcj`i5E^YE7)sKJI$eBb74L8tR$*x;d%j#@C(&(yz%yS*7=lyUnQ;8P*gR z`b|h|>b-`?VTqN;Rh?J8W4+}KyCvAG-3or`ZCUmpe`-$F+gUH#JT^Zan{&YWMAfyr z<%ddLANPwH9OzfGi=sjG=spn#4MUY(S+4lurxN3@RG0AO)xtt4a=Rn5d{vP(@6Z$J zr3UG(j_b~#{q56f`5%0>Li^Pj*Q|f@)(vOXnOEav^x=^E)MMl0_ZeBwpZ@pCos;u9 zrYN~|kFm9V6U#Ekf)aHSL*LAYicslvMX`n)rF3qC7~nivP! z>-yuV6N(V6Cqy4!BVuZJWY}E&1$8aCo=RsAx&~Q?n2O4jWq*|&F*+;eIAp#EK_^+L z)N4-a<3j6)Ee~b&-e5+#o{A|#_h&hW}D@ZV}SQ>u0MTWzDu2?duX6T zO(9DR8@2s{Xk=^s#SV8Lv)wdTln*VNTGrNhq2j0`+jog5lwA$J7P2#^_m-YGgK8F{cC@nU9w!E^{cGmG}2#c(iW|kokvOFvjh-K()b z=)3TiiaSL;KF`ZFW>?Q@@hs#WQ$B;rsH{rp-^SmgO23L9&0S|S=@2`=a(-xZ@H}#S z1^>BgNi8-~XQ|phVy-m9UBkS#W@+29dB$#*vCgg{rn^)dbXVIgBq{7pSZb)mFhp}(IZCoh*z0TLJZza} zT3c=|`?GwV>Aq!%vzGr1u~b&1o^H5W=~Ct7Dt}iQ9r;7JRsT?xK$rNIS-mBzKhOL4 z(}(&wlk&zEG%o9Hf6DJvH?Ok4=81$o^^@xr*D8%$Tji|oH68B?Fa7J&^Y`rAo9`Fo zEh|)-_SsLNJEDTzEUO*-ui;i$v#=f^#|^_mf31AHT1b3)H6ctTo#%=&9x3vF8J#yW z`*hZwm#WX#T>fB3b)l)TbEm%P1Df}_-F`&fw5SQ;tnRq1zo%>A?fm+dU!=K}Gh%w^ z!-YxqCgqcgQgdaGH(zRYX4UEFe}B0<_H|_O0e(?f%UVMl=G2=}eNEM0BR5y-uDwEy zv$ZH}^vU(o8Rc+Oo zW|ws>yT1wTIV!UZY7|?W)OP(^UU*$1JzMuKzgfUBTN=AFkv-FPUPk=S}5x z%u7Wb?T*m*;W^=5LnC!h5&zCNJPRLCxn`I)$i$7cA2tP*YfCqLJ@E17+vgu$WqQ8Q zV65J_ao6_VZoRuV>F8-xrJ67NY{)Qm9M#&^@#_yIt$iixU#lcX43~HFrCZX<;)~{g zD15N{YSN{BS5ohtd-ddVPp3iIs!F?>F|{gUZbbA8YZSt1XDWURJh9X-9Z~$D>{m;i zy}IqRnJODlJiBDFv7T+SX9D*-{Z`r8V2SJz$JU%!yLZjj)p}KGstgWHwot{Md_!)> z4|m?x`tah@rqc1wbm5YGs3s|-LwKXe6;-ZAHi&2*+EKesIhKjzdwC~2=30iB>Xzr1 zzAqhHKHZ#U(|EoHZczzxLY=GK4y)%58DZ$8eW%(deM8h|b6i7h1?Ee}Fr(errQ$zZ zZFhZkCGoFhnQ}~UW!+}|8T}#MV$Cl>)8#kmv;1;jiBn@ss90b!8Lya370c|kJaYn7 zspGP%K{{Qh5LsAk*xwU`ks&F0i-yw>mmxE5| z`i3s7_Z5nZy&r~QA+>AP4OJCtuVDJZBu0ax})%Iaf|Zq6})3uV6beCL0;8S zeP_*xntkH`h1?d*~}5PRks@BSLc`Iz?^? z-)%_O6sv#Hb_p9*`9VZ~!$+y#H_Se|BFFf5$;?l|+2yZ8^R8EfOM)X-*V@tiV3(;q z&vtjTZImF1y%e>*(rk5Zpk?{rMZA56v`eKs5jB;IJii(beeIYZ^RDrO2bY&!R9qf% zcg5>P1y`L$<-V$C>ag{C$NyD%LC8W~WN>G>oO@?kSyH{|Udd7OavNuTY>p`Vsc2@A zw9H)5&Apeq%$yC14_z8Hruw;B>9s>@o{C8h3svm#e=%PwoS%OYZF!r2yqvr5ORw@# zuJJ@?MQaUdco|w$i4ETyz9}r;a4vX*B0wkd1>Vli?bZ|KRO7<(W#z0FRkx8(pmqlac|O`lSd6A;#=fTU74L1m+zr{*L_NuU#WHn- z_Gev^uA}zX;J2zl@(Q|@@X0UpaE?>9FzZxHu4S8Tm2^Gk^vlUxx2G=&SyQn$vS3rRBU)793zQ$3Vf zs3Y7OZ@%NY)D>V&m5WIkv zv8UW|&KTRXvT>jIH(lPS^5&VZ6N7Y#G0_cT+vIl|)9Gl-{q@(^o>8NB%oW35gxtEz zlF0@sr|Y#!#;q;7opUa`&uiC{sN4Ts8+PmE<8e7%%j^2qDd$#sS*vZm>NUni3R8s4i`Pfr=$%mrSWSM`j z?+;&!FW}4dw+OuBPGA?hE}h93CC8*i=%ad1vW_`Kr4d{BO#!3-xvz=O;4AmN_BRf^ z;rk|!w_eTV5US&W*3vCJ}hGSw10r+fLC+>t=%Kw@Bfpb}S|_wiCv3~`GlNu50RY_&TqUd(D2Gng-t6ep3cq`qx zpf<|ir5fO>dn_-_|Je!uQ9hPUwA3pIeEj{>j#9fTo~o%aMR#v>uWg^sj`kB<)@-q$ zdB=vK)shu8oi?kRJ)vl#UB?8BOWvJ-GX25R2l~6I*W+&Vd@$(kn9`A+9n{s}{L1%Y zCPf#A4wKJ7zSRO%&5#x0Jq;HX6Ufv4Y}Yp1;qvYUEj}K8D}6U5Z)?d+M|VC#GF{$6 zaaGIonVp3mUMm7! zxEow$zMQjxo-A>PTu%Lhis>JibM$(uDcJ~k7uiArx`|cAo^60QhfZ?_;to0n9_A^2 zJ2!$G#jWS|ahK5P7`+MjM(ACbB~&ARLx<9)=sQ=Rj6}7(jJ%EBrB{KPphV|04ju0b zc{5)RoeqnIy69wim54#zEk!j1-cBECCU&}~Ay4xlo|sMi0d%F4LIq!kKM3@>_2{Hq z4>@2Fd?vRDJ$5s>zWf_>S${9wB05nGnX1xDGMjw8;tz#Xz6{j^TYwd!^jQH3848kb80;2xC?Z5Sy7NAJUV`519Ow2hD{TW z*0^~)`=`aL@b1 zL*f>Zg<9OsB2~Hf|h|!$peK6d~u)>o9jE`tLnG;?4G&K z(Kd~3kMo{iPQGP^OLo$`NEYWTd6G8D4nUa-H=Hm`Fl^F~)UDIJSJjaDNk<^vcf-5T zf06G(m*7nDvsv>w^y7enPc|JnSAIkKhB`v8Rq3CSKD;;BQxXZTG|?`<8IVE zQuo&y`Jqh$WLZ^Xd#?hg%eFMqviYGLnSJ!I@>nrNTk zn-{S9YI&}+|H(tcn^rqtwT8Y0voJ6xaFn{Jh*KX7`YJz932aOE&-NGAL|YU4OZ#j4 zO#3E#N5`M&YTCrp%d^TYcX#r9@c%(*q=I~ayakW}dXVp^aOo5I6GaupTG@R`V}_>n zK(uK@egu*k$KOF`+RcGRfn0VT8^v1v!R#3J7F##4G4Ls%;Hsc<{TMfw9}66mAw+xN zo^&I&Ag{7M`s39iJE6nqMKXu6iqVznKCm_zs6K#RZzwz2=3x4pO6N8!AAInB|RsDU`~ z>BuC>PAGm-URTsm>{KMFMyj_4pHnYW=1I1b^Mp8J7IjQ=LHeGlM#T8K*%zA|nRshe ze-E7F)*x28Oy<{?kKS0ur8q3>NsnQA1nmuTMJ9y*r%6(cSNGBD!V*KrhJ1-IR_hu& zCoDwpl}d|^?sWBp$PB|Jwp(#nUPb=u(z~YVM!_htHgN87cCUC+ZE_D=c^A(vIBqkV}xv3oK|WWrS&OCFz(x1pP1fIf+ir6iR?s>!eD6M2uAMBElU{92ymbI_?cjF+MR>wa!F@XNYzO}Lia zMD9A!3VwvmZN%5)yQ6dI3tlS30(oKsaPU0edenxa|KVT6dx9qt$XVoLG8mQi2Fi$& zwzkv}^a9n=G+j(xr{=52B-xsyDIuh!&rB3alDtOPfggD&E?fr;2PFo~73Jy>Pn zK;RKFotFYX;Ri08+XHD_NxUZCQqAZDAh@winY6XsE1NBCE~${*lRH#H)YDZ0>cnz{ zT|_3`Qr1ABlc&=IxecBr&bls#_Yil1s6<-0?%q`Um5Qa75O<^y3{-_dWJ~`??*n0n z+@t=W+9)ZYUdn$9$&b2N^=8<3bw6cXP+o8)twGaQ?}>a??R}&}e#nwlz?-DZ^6)<* zm&nsB<)0hn|5H+7d1I|_4Y6lBe{;oHn-y>SyrOV~<&iHfP~@ozoTkG*C%Hp1hstF8 zc}KDTQq5F9Xwx;*RR<)Chy(mWf|7U@tAhHd8puA8HMrG&jsGq>tWP9gku`<({!&*9 zr`grrx0hYYZu5QcJo7Y#6(7w8v8Viz>`#HC+)2KrkSbI`AK=Htb9_%ACsSkTm-Ko% z3%>LPS&7<5!3v<7%tJON(}83*8J|5&tU~YCtH3K%39tC`u;=-xii<`^>T={mpWtsJ z^SeH9&b9#0@&US0dIc>}9bHoU!E0{>UV@*9Ll0yrc^92W*MI9s{h7$aS?v_yBpyNE zONBrSwSl0LAiO|dZ@Ol%1oc&#Bp)kTj8pJYOk?F2^|&CLjF${mdh}G~(UpG) zSrIf<(Og+2C`r9Cs7r84X#2|D!&b>pItP`tw;mu3dM-3rzSg;*v_Y}l^wxgLVY2se z>fFihx%MUHql*TX?5h~y?&7=Sw%Iq@$GE$&$G8cBQg2nykG{VAB&Lg^p>m__4b_4- z2g-%9k^{a{HPyel&l28$9jSZn0yxhCFC7V7DjQ?fE>7tRR=QI z!~V}cgFl9C58cQN{1%uLm>F0QNDZjCEUuJ)DKrFD#b#*cQuGhKM$9Aaq#l_$tH|bL zJiP7;vNqKN_S8pSLjThKWHV^sNb+}bGk(@4OMrNF6k5C*`7B+5ZaA5khwj3D;i#}u z*oJ)l^N@jYLNB3*&=OYt2GC09@tgTm{26`+EPH?S5I694XtA1~0L0S-^t1K=5oImD z8zMtM0+fVu@-JZNT_U{1M^X#?(&ea&oX50aUeiAUce4&`)o{tbk}_sJeSq3Z*{C_F zCz!`SplK)*lrcEWrvI*_GD*Y$5+Siu5NCP^Q3uJ$|Jb`_64qw?kb*q*9E869qa4MO`--!)-pvzIXl8<7qi~vmsIPOdD5*kgd;Ta_xeYCF?dqw!2VWk@-@nk=4aljyiGXeQS)k7f34pjtX4drTOaZvx@ z4eCun4b^gOBZJZKP}u}W;Lfyntyp3TuL!Z3?Ai8g+b3HC=Nor|H_LO@HQw>W{?yUX z(=~90*hY0G$8vA6ru~ckM~I^50mYR$SPDTCP+F!s*t1scgjv5W4bW^ zQabVq5lIe5X3GKkEY%owPg>ywI!4C}>4KDS3p0hQ(5;bN4OA8_=bmwX&dojHHo-?H za=n2aI{_7=)3{ySE98ho^5MLk_izCq3wPyz<@fVD};uy=6?Xy?i?)o zIq=#qH2Bs@dKfVS7X3WxL6QIW%{FuaF3tk-IGIm|Q0=L~_-aqpK~_`}wT=2h;gE%{ z_=Zrt7%R46z$PE?4O90gsx?5>o#$Qh8MAmS%f2hqfMVI@D68yYyyzGfYP1$-++ zbF0z!G}YJ3|1X<_Sac?v&o<_MMYh=A+*078hH_niy|z<0OuVPoNxlN3HIYt5&-(~^ zudH{FJy@z4sLoU_l9TdFijP4rHI%Ntwmi5rxQ;eVr`5))c|{+^17)+I(Lt}23l!0+ zUh4RusnT%H;O^(C;G4)6DeFtFvPS1s`*3F?_b!*wk>7>$s=8KtXzvL3C&xdw8&;QXkZY^2KDU|wk<+uAefNDc*g9MYzZ`3zB?Qg@ zL8DA`2){BTCvD8f1;J@*8 z7asHuaTaUnBw&jDBTNw*1Ai}6q&@H|X*3uJ&9k9y(2nU2^;O9vp6MCA9 znCvY2O236hHz$S>y?|cz8t-mHr|A-`wZ{|5z_8_n)k1$^7}noUp#4vUP3T8GM;Hs+ z5soh4r_ir{B0rvA$nS#;euOP~!Kd;g_;_F$ham#o!#nsN5a0g}&oCL@rYm|}&l67I z{t3XzyhAXE2%^bCU?{Q};WFWkkW3iJfmrd~fbF;})WzPxEdB<+8aZowAssaA%m?Bi z`Hq}{^tco*$IJF&qHy^93HRMOKGL=Auz-J#NexC2nSUQg@3`Jp;%ehKqvZkkeLCH zn&yHYYqI}yvdZ-3onG-B!)c;I}~*mks`1_D9!2G&^biT|)VUXERmG@^uPL2d*;%dzu#1RSnMDzJ(+U@hH^98b>0s`_Vm z2`{eL1WlbmECkA1D%RB}i9?95M-lZ23AmgKtu+f2c225dM~UNse}u(qL0reqQe~kHu)fD&ACw_85f|rU z^*cjY0t&f=Ho%O#ff%Sh*&S%{Qt~Ba@i>qny+nKRH}X2TeS=&Ki7$kA-3R&{1wFG6 z0lgruVATW^5n#-{5$?f$zXQF4z*8~0QXpC@5FQIxgsZR_AHL~zG^(WA5?zoD8;e|N zKVrKwM0765S{=OJ3DThINz9W2K%)KJFr#p zneIX@!2w(R7k?a>r&7fC8xiM)f`5}B)Afm0@DeO`Ynozg3y9a)FPH)h#3h0YkwsJ3 zh$QS7ZbLLFfoxnNc3=42gmj->KiFQ|fo?+v<*JP6zWrdKIB~&+H(EVLW4qbYdm{*4M+m z)Vb2Gv#G3|E5?{xR5Y##F^@HEF)Gk`fHS3<+L~&c3XRuIpDVo95J$A@x~r#44xZm} zb@Dv-(*9X2!Np*vTM;7>{82sy(NC`M8}=(>2_<=oyax-LhuM6^3|k6=;JHwJLAJ-1 z<4)Mr7l@1RV8^2g@Ln$v4(xS|CFekETazSk`|c61u#?rCTuz?B8ulQn`!(cs`0$C) z+EJj|Z0PeOteYDU6wt%d@YEU5wmHIDV8>rTbbTE^)A0X4g@f=bH?ixU56+5R2AUEb zaNRH9#|WY~qV(2Se<#Am&VfbQi1iw}ml9z_DekZzQn?C#c^mGs7dCkuxSPXo!Ro9d zc)p8&&j-UlwG!e48|?j`{7#@)m%}SffvqS*Y~PFck*G_&!)|~NyXsLuw;X|a-NMK@ zAfnPj3akYxh_ElX8SDRG*q=05bOJkc{W0HAY7FgRx=K@I5sG2TyUHiZnLyj^q?(~# zq&cEnW%y$F)3DMY=X1GA#%N@n|hd3CWX1FrLAqOV}R?X ztG6rK)fyIkuBXgf*}s*IMpeiGtX64W18v_4%Xte{avAoe^@wiIW5x0i9{B?hUt2<3 za)k|q8s`=k*gYMp_cK6O3sy&yA%kY3CSu}gvZJn<21%YV@Sx9~PJBDMxt zS&3gw;njl>N4uc?0`N!cKoWZ4yVf@u+J_ALUa(7Tgr2bTGZDj$MP$fhB{>-cB92K?@d2vRCM=Z`_=Z}Wa|a2aSI#}mdvu4)s{Vc{IGfYo4Y#=vr2fxNQ#3=1u9 zf%u@S5DmoJnM5TZV&5b7lWOV_;+@lUD2yzAbj{0<1%BasjIovbig#k)WtN(Jm2!0Ez|Lv>yvA# zYXJHHGV>^ zVR@$zWw7gyiJu8OJm-3>k8Yr^Ll^9g-A8FvbFvqrnjpk?>5%tK!UZ3`6iBgmpy`Vc z1(g&3AdcMuexHX#yoF`Y#7vUGCpGY-pCi8hh*(hr3nE(f5P}4r>~oChGOYV2!HJWS zny~9#VZR&WY|W1tE*F@xIgktqu4o7=98OeVL>q)(z~4HEpW6uiFrPWF>`CxUuc6_i zu|qqC-_3vGqY>wghdeY!<-`kc`yF(?JFb0z=X3&JXTf_h#6kFXQiy@YXbWzm>I8o7 zHfn3`^HUL<`}oI*=GGIP;bn$^GKYbbIuNVEO*kF=NOzEomF|~$}d9iC7Gy*h)~D-#yXgLpzssK8MZyytCj{0g{`hg}pkG`SsY zcsKAp8v8OFPRY!8JqVJ}1{&TCTvs9%Ig4&0Q^Cv8uuZG6mf8)f&BP1@#Oqu5Wti_J z-orP6)tUvH-5eJB4RFifW0l_x_WC+@0jm2g!o_2%A7u+e>qaGjJDwxHtnoXIa5M)OrwW+)CbQ(mpvBX?X z?l*gWb<16`PR8-Z_SV+TUTAA#J7rZ_n_3OFZnjeEP3uDIM(Z`(97j!8nOotl%!u9nVoWGzPOw#7^o^*mMVY6ou8MxT>84?=};< z`2pM<3XbG}KM!GX6j-s%gZ5gmK8+tnZ#PC(V`S+&KbRYbJ7#Adi$8$lgRK(foSo6=v41$D@;Q4iEvm6ol49LX> z*!y0HND6sZ5FgKC{y*Xl2g2rPmn#3P;_r%J!amASWE~!5%|Z~$D8jq zVXY-~9&|)FWzLBXi6hzG&K|H&azr?a?FVs266IL$47m1r%DpXo3SWWuvA2OQ((m)X zV@&}EcNX)M3y-1aZG^+nq*Tbf$k~kq3H0XDZ83Vf%)cM5!`z;_CKr@(g# ze5b&73Vf%)cM5!`z;_D#{}jl;PWfi6tf$~h%o~2i7JBVuFH~}56V|{KB8zN7XW*R< zWIA%$;_%sQ{67jiu|8ZSzPc7Wdr`>#&c@&3d*VCd9sl=AB7TeCQ}C0(cL%Z=*O|>A z5}15u0l$HrLgYvyWogtIwuv{4`jc5pZYEMFT5^raAf5t$xCx!lECy)a;M&cgSUQPaajqD%FThxkuS4Yt;x!pSaBjwF>0){r zmqHo1A@nxMaL;S0IO{PoafTwbo6u$4Y+(rI`X`f3G~;SYo(Alo+cIt(=rRyJm$ESS zoBR}_i*ybq*$=c|#vS6iNH1geY)Z#}}6r?0icm%{>Ej>f>wy> zjm!srtR*iC*%;AcIwBx){BKV?*)`^*I~!6qm)T4#;0;_0xf-7fqc*TlWr@UY-_P<_ zYylrB`@{cK7Dq^&`ry@|P8v0g+Xy-i!7c&8A#|Z)yA!gn zJHze-g$>|rJ~M@g0CM)|Kr3`$8bKv6gS|KTeF~|N%14pY{eLns(ue>ju?EVxUXnTj z2g$Pr#z7A4WHFuP&LFHZ16M~V^fuxbDn?K(Jz>-XxksAeI>R>5Je9p-7kN*B7OMkw zpc^-Fx|}Dh#W)j*7&b@pTDpvTiYJeNcEm`J<2lW^-`vaPX^`kbuP9Zsh1CHwWd>E& ziut18^Dt=dVtOqol_zYJW|3_Jn~8j8Aap4csOvZRI9@%)iR?8d3NmcR^)5CMdKpGNl}!Y1nsGBE8>Q*wU~eOZ zXz|uU)(arJqHO0gX37c+^rJG4SmYf?WaWJuH=(Q*a+W#|-@U$PSYBMfRSWKsb2AQ-tgAS0meAqSGpMp82lgs7DA)C{Y?b!spT>wil zk$ptvgKC=z7i_qAMK6h+Ji|6pBw!wCloWIo{mBSqkN*ku$P_ABnGK!}1ogy!b8+|0 z_`43|KLb`x4f`R!(+c+U6`TG|#!`7R6-M$P}QdA1-CR*AovX3wj zd`rj7q_~rr%8?wPU$NqIqe$`IqOBM0lIXd_yZi9HQ|RG0Q|aVDJaYs!gwBL4>=b%Q z2J#c}WYK1&P?@wSlgRqQRUM%9W#|x%ZZFV&A2A!&+e{Vk&A2k|GDelkcOVlOEqsO< zv=c2*6!PEGAuFQq%L4C29qEAXV#BxsJShvaTT5O;*8|c1C*sq{=%Ym6CTjCN$lO}Y zM@x?bj|-svkKhr2k`JvF{RsgnG-D2zAzPx|&cHoHiy?Z~C_HltDlYPH!u$vxN3>j` z@45%N+cBmEpqwbjQ{W#i!$zmV+hxF87T~v!(1Q0JzRkD}W+={GlY^c@bLG<#L$$yoPmR*xPW?D#6P@@(sTQ13> zDS9eBkU7uT=myj?p%%BsFZVWgmm(+SnQtl1H70T$*jR6zi*;Oawf7$-#!4du;FLj*WeMF>V|t^zlR$um4sdo)mJ(cdO*`dGTq2_Ohf*QNQ-o@BUj0TO}Xs{$$q9jJKcSW!f0YN|n zqzVi$Ff*sV_woJygzuO4x)QGp%FLX-_u6Z(w%58Bc}_3?V!Q3aJ+uk;Tb#O1k>p>| z4|S2e57j?{+)Y`4INVqKiq!p$1U~W$c+$iKZ-&p+_HJ#R&ywJbtbY8 zUr_&A`QscBlLFMI;JBlW(;~ery!r9H!?xWqIF**`IJr0lxNShie=DDNo;99+KC``jT&M6|Y~$*}tv{1-#u%fH{;95c z`G(@FC2nQgO74BSQtVTnTGgS(P`#vLWw~2rTJIL*&mkkJPOYb&1D~`3J+)_WVQfq0Fqwh+&_3v!eHZuZ_VjN! zIWLiya2q+o`_b(-Qr?P8^*UKaCb}yq9*qpaZ;*4k5BHU`kOgxO*p5eWLtlw{geJI| ze1|*xAlyGM!9C>w+{r!0jpT3W()xzZqgm|aL=puqLiGT;KXgJLQY-l$xQ$i#>|eOm zzK%@RB6+jiL3We9v929bjMz-LXYFV8wS|el%kkjK21>ivn}=+dP3mfDI&)OF+c?yr zt^FFiIl35KTf2vbZw%?Wt85+lnh}5)IFLWVzNU5Pcb9A4XS!L%n|hmTZL5^6(9yg# zu4Z{(j+`J3`7MVCGfEc&J5 z+q!f0=GytDlp0stC6;kYbbIf*z+o=Gg?6T9lac!EjFjcmd0?w~1p3iSs(lSKKpN>0j2^%3&O6?#38a1)j9)fTiXXSVytv2Yjzy93^fGrl|E`)WEw}POR?uExEU{ng_r}rXi5Ept}2sI z|1^_1#9UxbGh5-ewqtt2+wy0xuqEtjSch#%1#(Yok(c|u{6KP$_J}jY9iodgN;)bY zwS52{#Vm7pLyqmT{2P#RyPz_B1er{AS6r2CbZ7n7PQBcwyE;3sHvVKc#kj_Cq0@Hz zZaN*KM_=)8bT9Sky5sC~HAGlg8&`hzQ^cpA%k}27QXqYrI%f;4epY<3a8%hyYmVOJ z_QrF(>v)IOF8lo)Ta0QI5}fS2)cZ-m^-$72yZ!do`&)T+c+@dEbc4%*nsXnHmiDGQ z_=o|QjZZ3UZ&Okhy}bQ)RN9Kv1uvUr9{-q7(yX95<8@lYyVRmv)rB=%E22xdN=6}??iPHvly)p+~3kNyf_HN}r+&9DBYJAAtmKWNbEnO`aY!oWI-iVzWel@MC znqzuh_ts3;PN{rVKBTgR*&^DbKh9OPPTD9Q6yKp!(Pw%I|EHk_8vd5uX+9oxMxWU< zNbgFnEf>m$GF?e8;N|+N&*VAMRIx$mg}R(+r~scV)rnihq2efUCwkV_O7-$nrR$eG zLoei=CE{G%hTT?;*B`LgQ;^qw3VDuaQR`3*JrSeMg2w&@`7nPWOJy?ht)iiK^VL4c zg}+N(LjKQTN}`hJLu49L#k68WSSxde`I^x)c1$!=z=W~(>;h&piKVvz72r6~zlTVD z#f5^qFi-dnc&I&PU#Y(>8(67b&F+@(#1X26-onga7qcyx>(ohQ9O}TnHO9HbxZQJZ z;&{kVYOr@4>zv@&%Wg0;oLWtnu$OgT>3i}mNt~2tHdQqHlw7d6*jybZR8xluY_r-u zrICf7ihIHr9+ud)zElP=$Q63?ekh4YehOHbqWo= z@5ojDlGCm1Bm0;Cu%-j`X~mP?EP1~C)#dafZ=zBIU$4*Ni~LJBd}#PLB5hpuq>`~U z<4mQcw~A+1^;h&Rzx!SDYwUqkB&tX%qY~^sxOeq`+IV!M8kZS*3zaYKux_w;*kYyq zz(al_ux7XFlT~MHrkgj{1y<`Tf-Cl$E?dLYA@ojlmNZ@HB=i;qX`>p>B`&Aa%%0AQ@ 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 Date: Sun, 8 Feb 2026 00:07:57 +0000 Subject: [PATCH 14/14] build: link to advapi32 library if target is windows --- espeak-sys/build.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/espeak-sys/build.rs b/espeak-sys/build.rs index 373b265..47172ab 100644 --- a/espeak-sys/build.rs +++ b/espeak-sys/build.rs @@ -65,8 +65,9 @@ fn main() { println!("cargo:rustc-link-lib=c++"); } - if cfg!(all(debug_assertions, windows)) { - println!("cargo:rustc-link-lib=dylib=msvcrtd"); + if cfg!(target_os = "windows") { + // needed to find the data path + println!("cargo:rustc-link-lib=advapi32"); } // Generate bindings