rigela_utils/
ibmeci.rs

1/*
2 * Copyright (c) 2024. The RigelA open source project team and
3 * its contributors reserve all rights.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
9 * Unless required by applicable law or agreed to in writing, software distributed under the
10 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 * See the License for the specific language governing permissions and limitations under the License.
12 */
13
14use 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
154// Voice params
155pub 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;
160//noinspection SpellCheckingInspection
161pub const VP_BREATHINESS: u32 = 5;
162pub const VP_SPEED: u32 = 6;
163pub const VP_VOLUME: u32 = 7;
164
165//noinspection SpellCheckingInspection
166static 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//noinspection SpellCheckingInspection
196#[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    //noinspection SpellCheckingInspection
208    /**
209    获取一个实例。
210    */
211    pub async fn get() -> Result<&'static Self, String> {
212        // 单例模式
213        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    /**
276    合成语音。
277    `text` 要合成的文字。
278    */
279    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            // 如果有不能被编码成gbk的字符,我们需要过滤他们
284            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    /**
335    设置语音参数。
336    `vp` 参数key。
337    `value` 参数值。
338    */
339    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    /**
344    获取语音参数。
345    `vp` 参数key。
346    */
347    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    /**
352    获取发音人列表。
353    */
354    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    /**
371    设置当前发音人。
372    `voice_id` 声音id。
373    */
374    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}