1use crate::{
15 call_proc,
16 common::SafeModuleHandle,
17 library::{get_rigela_library_path, setup_library},
18};
19use encoding_rs::GBK;
20use flume::{bounded, Sender};
21use log::{error, info};
22use std::{
23 alloc::{alloc_zeroed, dealloc, Layout},
24 ffi::{c_char, CString},
25 sync::OnceLock,
26 thread,
27};
28use tokio::sync::oneshot;
29use win_wrap::{
30 common::{free_library, get_proc_address, load_library, FARPROC, LPARAM, WPARAM},
31 message::{message_loop, post_thread_message},
32 threading::get_current_thread_id,
33};
34
35macro_rules! eci {
36 ($module:expr,new) => {
37 call_proc!($module, eciNew, extern "system" fn() -> i32,)
38 };
39 ($module:expr,delete,$handle:expr) => {
40 call_proc!($module, eciDelete, extern "system" fn(i32) -> i32, $handle)
41 };
42 ($module:expr,speaking,$handle:expr) => {
43 call_proc!(
44 $module,
45 eciSpeaking,
46 extern "system" fn(i32) -> bool,
47 $handle
48 )
49 };
50 ($module:expr,stop,$handle:expr) => {
51 call_proc!($module, eciStop, extern "system" fn(i32) -> bool, $handle)
52 };
53 ($module:expr,register_callback,$handle:expr,$cb:expr,$data:expr) => {
54 call_proc!(
55 $module,
56 eciRegisterCallback,
57 extern "system" fn(i32, extern "system" fn(u32, u32, u32, u32) -> u32, u32),
58 $handle,
59 $cb,
60 $data
61 )
62 };
63 ($module:expr,set_output_buffer,$handle:expr,$samples:expr,$buffer:expr) => {
64 call_proc!(
65 $module,
66 eciSetOutputBuffer,
67 extern "system" fn(i32, u32, *mut u8),
68 $handle,
69 $samples,
70 $buffer
71 )
72 };
73 ($module:expr,add_text,$handle:expr,$text:expr) => {{
74 if let Ok(p) = CString::new($text) {
75 call_proc!(
76 $module,
77 eciAddText,
78 extern "system" fn(i32, *const c_char) -> bool,
79 $handle,
80 p.as_ptr()
81 )
82 } else {
83 None
84 }
85 }};
86 ($module:expr,speak_text,$text:expr) => {{
87 if let Ok(p) = CString::new($text) {
88 call_proc!(
89 $module,
90 eciSpeakText,
91 extern "system" fn(*mut c_char),
92 p.as_ptr()
93 )
94 } else {
95 None
96 }
97 }};
98 ($module:expr,synthesize,$handle:expr) => {
99 call_proc!($module, eciSynthesize, extern "system" fn(i32), $handle)
100 };
101 ($module:expr,synchronize,$handle:expr) => {
102 call_proc!($module, eciSynchronize, extern "system" fn(i32), $handle)
103 };
104 ($module:expr,set_voice_param,$handle:expr,$voice:expr,$key:expr,$value:expr) => {
105 call_proc!(
106 $module,
107 eciSetVoiceParam,
108 extern "system" fn(i32, i32, u32, i32),
109 $handle,
110 $voice,
111 $key,
112 $value
113 )
114 };
115 ($module:expr,get_voice_param,$handle:expr,$voice:expr,$key:expr) => {
116 call_proc!(
117 $module,
118 eciGetVoiceParam,
119 extern "system" fn(i32, i32, u32) -> i32,
120 $handle,
121 $voice,
122 $key
123 )
124 };
125 ($module:expr,copy_voice,$handle:expr,$copy_from:expr,$copy_to:expr) => {
126 call_proc!(
127 $module,
128 eciCopyVoice,
129 extern "system" fn(i32, u32, u32),
130 $handle,
131 $copy_from,
132 $copy_to
133 )
134 };
135}
136
137#[allow(unused)]
138const MSG_WAVEFORM_BUFFER: u32 = 0;
139#[allow(unused)]
140const MSG_PHONEME_BUFFER: u32 = 1;
141#[allow(unused)]
142const MSG_INDEX_REPLY: u32 = 2;
143#[allow(unused)]
144const MSG_PHONEME_INDEX_REPLY: u32 = 3;
145#[allow(unused)]
146const MSG_WORD_INDEX_REPLY: u32 = 4;
147#[allow(unused)]
148const RETURN_DATA_NOT_PROCESSED: u32 = 0;
149#[allow(unused)]
150const RETURN_DATA_PROCESSED: u32 = 1;
151#[allow(unused)]
152const RETURN_DATA_ABORT: u32 = 2;
153
154pub const VP_GENDER: u32 = 0;
156pub const VP_HEAD_SIZE: u32 = 1;
157pub const VP_PITCH_BASELINE: u32 = 2;
158pub const VP_PITCH_FLUCTUATION: u32 = 3;
159pub const VP_ROUGHNESS: u32 = 4;
160pub const VP_BREATHINESS: u32 = 5;
162pub const VP_SPEED: u32 = 6;
163pub const VP_VOLUME: u32 = 7;
164
165static IBMECI: OnceLock<Ibmeci> = OnceLock::new();
167
168extern "system" fn _callback_internal(
169 #[allow(unused_variables)] h_eci: u32,
170 msg: u32,
171 param: u32,
172 #[allow(unused_variables)] data: u32,
173) -> u32 {
174 if msg != MSG_WAVEFORM_BUFFER {
175 return RETURN_DATA_PROCESSED;
176 }
177 unsafe {
178 let eci = IBMECI.get();
179 if eci.is_none() {
180 return RETURN_DATA_PROCESSED;
181 }
182
183 let eci = eci.unwrap();
184 let mut vec = vec![];
185 for i in 0..(param * 2) {
186 vec.push(*eci.buffer_ptr.wrapping_add(i as usize));
187 }
188 let _ = eci.data_tx.send(vec);
189 }
190 RETURN_DATA_PROCESSED
191}
192
193const BUFFER_LAYOUT: Layout = Layout::new::<[u8; 8192]>();
194
195#[derive(Debug)]
197pub struct Ibmeci {
198 buffer_ptr: *mut u8,
199 request_tx: Sender<(Vec<u8>, Sender<Vec<u8>>)>,
200 data_tx: Sender<Vec<u8>>,
201 h_module: SafeModuleHandle,
202 h_eci: i32,
203 thread: u32,
204}
205
206impl Ibmeci {
207 pub async fn get() -> Result<&'static Self, String> {
212 if let Some(self_) = IBMECI.get() {
214 return Ok(self_);
215 }
216 const LIB_NAME: &str = "ibmeci.dll";
217 let eci_path = get_rigela_library_path().join(LIB_NAME);
218 setup_library(&eci_path, include_bytes!("../lib/ibmeci.dll"));
219
220 let h_module = match load_library(eci_path.to_str().unwrap()) {
221 Ok(h) => SafeModuleHandle::new(h),
222 Err(e) => {
223 return Err(format!(
224 "Can't open the library ({}). {}",
225 eci_path.display(),
226 e
227 ))
228 }
229 };
230 info!("{} loaded.", eci_path.display());
231 let (tx, rx) = oneshot::channel();
232 thread::spawn(move || {
233 let h_eci = eci!(*h_module, new).unwrap_or(0);
234 let buffer_ptr = unsafe { alloc_zeroed(BUFFER_LAYOUT) };
235
236 let (tx_data, rx_data) = bounded(BUFFER_LAYOUT.size());
237 let (tx_request, rx_request) = bounded(2);
238 let self_ = Self {
239 buffer_ptr,
240 request_tx: tx_request,
241 data_tx: tx_data,
242 h_module: h_module.clone(),
243 h_eci,
244 thread: get_current_thread_id(),
245 };
246
247 eci!(*h_module, register_callback, h_eci, _callback_internal, 0);
248 eci!(
249 *h_module,
250 set_output_buffer,
251 h_eci,
252 (BUFFER_LAYOUT.size() / 2) as u32,
253 buffer_ptr
254 );
255 info!("Module handle: {:?}, eci handle: {}", h_module.0, h_eci);
256 IBMECI.set(self_).unwrap();
257 tx.send(IBMECI.get().unwrap()).unwrap();
258 message_loop(|_| {
259 if let Ok((text, tx_status)) = rx_request.try_recv() {
260 eci!(*h_module, add_text, h_eci, text);
261 eci!(*h_module, synthesize, h_eci);
262 eci!(*h_module, synchronize, h_eci);
263 while let Ok(data) = rx_data.try_recv() {
264 let _ = tx_status.send(data);
265 }
266 }
267 });
268 });
269 match rx.await {
270 Err(e) => Err(format!("Can't get the instance. ({})", e)),
271 Ok(x) => Ok(x),
272 }
273 }
274
275 pub async fn synth(&self, text: &str) -> Vec<u8> {
280 eci!(*self.h_module, stop, self.h_eci);
281 let (text, _, unmapped) = GBK.encode(text);
282 let text = if unmapped {
283 let mut v = vec![];
285 let mut u = vec![];
286 let mut has_html_char = false;
287 let mut last_char = 0u8;
288 for i in text.iter() {
289 let i = i.clone();
290 if last_char == 38 {
291 has_html_char = i == 35u8;
292 if has_html_char {
293 u.clear();
294 u.push(last_char);
295 u.push(i);
296 } else {
297 v.push(last_char);
298 v.push(i);
299 }
300 } else {
301 if has_html_char {
302 u.push(i);
303 if i == 59u8 {
304 has_html_char = false;
305 } else if !(i >= 48u8 && i <= 57u8) {
306 v.extend(&u);
307 has_html_char = false;
308 }
309 } else if i != 38 {
310 v.push(i);
311 }
312 }
313 last_char = i;
314 }
315 v.into()
316 } else {
317 text
318 };
319 let Some(eci) = IBMECI.get() else {
320 return vec![];
321 };
322 let (tx, rx) = bounded(2);
323 if let Err(e) = eci.request_tx.send_async((text.to_vec(), tx)).await {
324 error!("Can't synth the data. ({})", e);
325 }
326 post_thread_message(eci.thread, 0, WPARAM::default(), LPARAM::default());
327 let mut buf = vec![];
328 while let Ok(data) = rx.recv_async().await {
329 buf.extend(data);
330 }
331 buf
332 }
333
334 pub fn set_voice_param(&self, vp: u32, value: i32) {
340 eci!(*self.h_module, set_voice_param, self.h_eci, 0, vp, value);
341 }
342
343 pub fn get_voice_param(&self, vp: u32) -> i32 {
348 eci!(*self.h_module, get_voice_param, self.h_eci, 0, vp).unwrap_or(0)
349 }
350
351 pub fn get_voices(&self) -> Vec<(u32, String)> {
355 vec![
356 (1, "Adult Male 1"),
357 (2, "Adult Female 1"),
358 (3, "Child 1"),
359 (4, "Adult Male 2"),
360 (5, "Adult Male 3"),
361 (6, "Adult Female 2"),
362 (7, "Elderly Female 1"),
363 (8, "Elderly Male 1"),
364 ]
365 .iter()
366 .map(|i| (i.0, i.1.to_string()))
367 .collect()
368 }
369
370 pub fn set_voice(&self, voice_id: u32) {
375 eci!(*self.h_module, copy_voice, self.h_eci, voice_id, 0);
376 }
377}
378
379impl Drop for Ibmeci {
380 fn drop(&mut self) {
381 if !self.h_module.is_invalid() {
382 eci!(*self.h_module, delete, self.h_eci);
383 free_library(*self.h_module);
384 }
385 unsafe {
386 dealloc(self.buffer_ptr, BUFFER_LAYOUT);
387 }
388 }
389}
390
391unsafe impl Sync for Ibmeci {}
392
393unsafe impl Send for Ibmeci {}
394
395#[cfg(all(test, target_arch = "x86"))]
396mod test_eci {
397 use super::super::logger::init_logger;
398 use super::Ibmeci;
399
400 #[tokio::test]
401 async fn main() {
402 init_logger(Some("test.log"));
403 let eci = Ibmeci::get().await.unwrap();
404 for _ in 0..1000 {
405 let data = eci.synth("abc").await;
406 assert_eq!(data.len(), 21978);
407 }
408 }
409}