use std::ffi::{CStr, CString, c_char};
use std::path::Path;
use crate::codec::NeuCodecDecoder;
use crate::phonemize;
pub struct NeuTtsHandle {
codec: NeuCodecDecoder,
}
unsafe fn cstr_to_string(ptr: *const c_char) -> Option<String> {
if ptr.is_null() { return None; }
Some(unsafe { CStr::from_ptr(ptr) }.to_string_lossy().into_owned())
}
fn to_c_str(s: &str) -> *const c_char {
match CString::new(s) {
Ok(cs) => cs.into_raw(),
Err(_) => std::ptr::null(),
}
}
#[no_mangle]
pub unsafe extern "C" fn neutts_set_espeak_data_path(path: *const c_char) {
if let Some(s) = unsafe { cstr_to_string(path) } {
phonemize::set_data_path(Path::new(&s));
}
}
#[no_mangle]
pub unsafe extern "C" fn neutts_model_load(weights_path: *const c_char) -> *mut NeuTtsHandle {
let path_opt = unsafe { cstr_to_string(weights_path) }
.filter(|s| !s.is_empty());
let result = match path_opt {
Some(ref p) => NeuCodecDecoder::load(Path::new(p)),
None => NeuCodecDecoder::new(),
};
match result {
Ok(codec) => Box::into_raw(Box::new(NeuTtsHandle { codec })),
Err(e) => {
eprintln!("[neutts] load error: {e:#}");
std::ptr::null_mut()
}
}
}
#[no_mangle]
pub unsafe extern "C" fn neutts_decode_tokens(
model: *const NeuTtsHandle,
codes: *const i32,
num_codes: usize,
out_len: *mut usize,
) -> *mut f32 {
if model.is_null() || codes.is_null() || out_len.is_null() {
return std::ptr::null_mut();
}
let h = unsafe { &*model };
let codes_slice = unsafe { std::slice::from_raw_parts(codes, num_codes) };
match h.codec.decode(codes_slice) {
Ok(audio) => {
let len = audio.len();
let mut boxed = audio.into_boxed_slice();
let ptr = boxed.as_mut_ptr();
std::mem::forget(boxed);
unsafe { *out_len = len; }
ptr
}
Err(e) => {
eprintln!("[neutts] decode error: {e:#}");
std::ptr::null_mut()
}
}
}
#[no_mangle]
pub unsafe extern "C" fn neutts_write_wav(
samples: *const f32,
num_samples: usize,
output_path: *const c_char,
) -> *const c_char {
if samples.is_null() || output_path.is_null() {
return to_c_str("null argument");
}
let path_str = match unsafe { cstr_to_string(output_path) } {
Some(s) => s,
None => return to_c_str("invalid output_path"),
};
let audio = unsafe { std::slice::from_raw_parts(samples, num_samples) };
let spec = hound::WavSpec {
channels: 1,
sample_rate: crate::codec::SAMPLE_RATE,
bits_per_sample: 16,
sample_format: hound::SampleFormat::Int,
};
let mut writer = match hound::WavWriter::create(&path_str, spec) {
Ok(w) => w,
Err(e) => return to_c_str(&format!("Cannot create WAV: {e}")),
};
for &s in audio {
let s16 = (s * i16::MAX as f32).clamp(i16::MIN as f32, i16::MAX as f32) as i16;
if let Err(e) = writer.write_sample(s16) {
return to_c_str(&format!("WAV write error: {e}"));
}
}
if let Err(e) = writer.finalize() {
return to_c_str(&format!("WAV finalise error: {e}"));
}
std::ptr::null()
}
#[no_mangle]
pub unsafe extern "C" fn neutts_free_audio(ptr: *mut f32, num_samples: usize) {
if !ptr.is_null() {
let slice = unsafe { std::slice::from_raw_parts_mut(ptr, num_samples) };
drop(unsafe { Box::from_raw(slice as *mut [f32]) });
}
}
#[no_mangle]
pub unsafe extern "C" fn neutts_free_error(s: *const c_char) {
if !s.is_null() {
drop(unsafe { CString::from_raw(s as *mut c_char) });
}
}
#[no_mangle]
pub unsafe extern "C" fn neutts_model_free(model: *mut NeuTtsHandle) {
if !model.is_null() {
drop(unsafe { Box::from_raw(model) });
}
}