use crate::prelude::*;
use crate::{EvaluationError, QualityEvaluationConfig};
use std::ffi::{CStr, CString};
use std::os::raw::{c_char, c_float, c_int, c_uint, c_void};
use std::ptr;
use std::sync::Arc;
use tokio::runtime::Runtime;
use voirs_sdk::AudioBuffer;
#[repr(C)]
pub struct CppQualityEvaluatorHandle {
_private: [u8; 0],
}
#[repr(C)]
pub struct CppPronunciationEvaluatorHandle {
_private: [u8; 0],
}
#[repr(C)]
pub struct CppComparativeEvaluatorHandle {
_private: [u8; 0],
}
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct CppQualityResult {
pub overall_score: c_float,
pub pesq: c_float,
pub stoi: c_float,
pub mcd: c_float,
pub snr: c_float,
pub spectral_distortion: c_float,
pub processing_time_ms: f64,
pub error_code: c_int,
}
impl Default for CppQualityResult {
fn default() -> Self {
Self {
overall_score: 0.0,
pesq: -1.0,
stoi: -1.0,
mcd: -1.0,
snr: -1.0,
spectral_distortion: -1.0,
processing_time_ms: 0.0,
error_code: 0,
}
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct CppPronunciationResult {
pub overall_score: c_float,
pub phoneme_accuracy: c_float,
pub word_accuracy: c_float,
pub fluency: c_float,
pub prosody: c_float,
pub processing_time_ms: f64,
pub error_code: c_int,
}
impl Default for CppPronunciationResult {
fn default() -> Self {
Self {
overall_score: 0.0,
phoneme_accuracy: 0.0,
word_accuracy: 0.0,
fluency: 0.0,
prosody: 0.0,
processing_time_ms: 0.0,
error_code: 0,
}
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct CppComparisonResult {
pub system_a_score: c_float,
pub system_b_score: c_float,
pub winner: c_int,
pub p_value: f64,
pub effect_size: f64,
pub processing_time_ms: f64,
pub error_code: c_int,
}
impl Default for CppComparisonResult {
fn default() -> Self {
Self {
system_a_score: 0.0,
system_b_score: 0.0,
winner: 2, p_value: -1.0,
effect_size: -1.0,
processing_time_ms: 0.0,
error_code: 0,
}
}
}
struct CppQualityEvaluatorWrapper {
evaluator: Arc<QualityEvaluator>,
runtime: Runtime,
}
struct CppPronunciationEvaluatorWrapper {
evaluator: Arc<PronunciationEvaluatorImpl>,
runtime: Runtime,
}
struct CppComparativeEvaluatorWrapper {
evaluator: Arc<ComparativeEvaluatorImpl>,
runtime: Runtime,
}
#[no_mangle]
pub unsafe extern "C" fn cpp_quality_evaluator_new() -> *mut CppQualityEvaluatorHandle {
let runtime = match Runtime::new() {
Ok(rt) => rt,
Err(_) => return ptr::null_mut(),
};
let evaluator = match runtime.block_on(QualityEvaluator::new()) {
Ok(eval) => eval,
Err(_) => return ptr::null_mut(),
};
let wrapper = Box::new(CppQualityEvaluatorWrapper {
evaluator: Arc::new(evaluator),
runtime,
});
Box::into_raw(wrapper) as *mut CppQualityEvaluatorHandle
}
#[no_mangle]
pub unsafe extern "C" fn cpp_quality_evaluator_free(handle: *mut CppQualityEvaluatorHandle) {
if !handle.is_null() {
let _ = Box::from_raw(handle as *mut CppQualityEvaluatorWrapper);
}
}
#[no_mangle]
pub unsafe extern "C" fn cpp_quality_evaluator_evaluate(
handle: *mut CppQualityEvaluatorHandle,
generated: *const c_float,
generated_length: c_uint,
reference: *const c_float,
reference_length: c_uint,
sample_rate: c_uint,
channels: c_uint,
result: *mut CppQualityResult,
) -> c_int {
if handle.is_null() || generated.is_null() || result.is_null() {
return -1;
}
let wrapper = &*(handle as *const CppQualityEvaluatorWrapper);
let start_time = std::time::Instant::now();
let generated_samples = std::slice::from_raw_parts(generated, generated_length as usize);
let generated_buf = AudioBuffer::new(generated_samples.to_vec(), sample_rate, channels);
let reference_buf = if !reference.is_null() && reference_length > 0 {
let reference_samples = std::slice::from_raw_parts(reference, reference_length as usize);
Some(AudioBuffer::new(
reference_samples.to_vec(),
sample_rate,
channels,
))
} else {
None
};
let eval_result = wrapper.runtime.block_on(wrapper.evaluator.evaluate_quality(
&generated_buf,
reference_buf.as_ref(),
None,
));
let processing_time = start_time.elapsed().as_secs_f64() * 1000.0;
match eval_result {
Ok(quality) => {
*result = CppQualityResult {
overall_score: quality.overall_score,
pesq: quality
.component_scores
.get("pesq")
.copied()
.unwrap_or(-1.0),
stoi: quality
.component_scores
.get("stoi")
.copied()
.unwrap_or(-1.0),
mcd: quality.component_scores.get("mcd").copied().unwrap_or(-1.0),
snr: quality.component_scores.get("snr").copied().unwrap_or(-1.0),
spectral_distortion: quality
.component_scores
.get("spectral_distortion")
.copied()
.unwrap_or(-1.0),
processing_time_ms: processing_time,
error_code: 0,
};
0
}
Err(_) => {
*result = CppQualityResult {
error_code: -2,
processing_time_ms: processing_time,
..Default::default()
};
-2
}
}
}
#[no_mangle]
pub unsafe extern "C" fn cpp_pronunciation_evaluator_new() -> *mut CppPronunciationEvaluatorHandle {
let runtime = match Runtime::new() {
Ok(rt) => rt,
Err(_) => return ptr::null_mut(),
};
let evaluator = match runtime.block_on(PronunciationEvaluatorImpl::new()) {
Ok(eval) => eval,
Err(_) => return ptr::null_mut(),
};
let wrapper = Box::new(CppPronunciationEvaluatorWrapper {
evaluator: Arc::new(evaluator),
runtime,
});
Box::into_raw(wrapper) as *mut CppPronunciationEvaluatorHandle
}
#[no_mangle]
pub unsafe extern "C" fn cpp_pronunciation_evaluator_free(
handle: *mut CppPronunciationEvaluatorHandle,
) {
if !handle.is_null() {
let _ = Box::from_raw(handle as *mut CppPronunciationEvaluatorWrapper);
}
}
#[no_mangle]
pub unsafe extern "C" fn cpp_pronunciation_evaluator_evaluate(
handle: *mut CppPronunciationEvaluatorHandle,
audio: *const c_float,
audio_length: c_uint,
text: *const c_char,
sample_rate: c_uint,
channels: c_uint,
result: *mut CppPronunciationResult,
) -> c_int {
if handle.is_null() || audio.is_null() || text.is_null() || result.is_null() {
return -1;
}
let wrapper = &*(handle as *const CppPronunciationEvaluatorWrapper);
let start_time = std::time::Instant::now();
let audio_samples = std::slice::from_raw_parts(audio, audio_length as usize);
let audio_buf = AudioBuffer::new(audio_samples.to_vec(), sample_rate, channels);
let text_str = match CStr::from_ptr(text).to_str() {
Ok(s) => s,
Err(_) => return -1,
};
let eval_result = wrapper.runtime.block_on(
wrapper
.evaluator
.evaluate_pronunciation(&audio_buf, text_str, None),
);
let processing_time = start_time.elapsed().as_secs_f64() * 1000.0;
match eval_result {
Ok(pron) => {
let phoneme_accuracy = if !pron.phoneme_scores.is_empty() {
pron.phoneme_scores.iter().map(|p| p.accuracy).sum::<f32>()
/ pron.phoneme_scores.len() as f32
} else {
0.0
};
let word_accuracy = if !pron.word_scores.is_empty() {
pron.word_scores.iter().map(|w| w.accuracy).sum::<f32>()
/ pron.word_scores.len() as f32
} else {
0.0
};
*result = CppPronunciationResult {
overall_score: pron.overall_score,
phoneme_accuracy,
word_accuracy,
fluency: pron.fluency_score,
prosody: (pron.stress_accuracy + pron.intonation_accuracy) / 2.0,
processing_time_ms: processing_time,
error_code: 0,
};
0
}
Err(_) => {
*result = CppPronunciationResult {
error_code: -2,
processing_time_ms: processing_time,
..Default::default()
};
-2
}
}
}
#[no_mangle]
pub unsafe extern "C" fn cpp_comparative_evaluator_new() -> *mut CppComparativeEvaluatorHandle {
let runtime = match Runtime::new() {
Ok(rt) => rt,
Err(_) => return ptr::null_mut(),
};
let evaluator = match runtime.block_on(ComparativeEvaluatorImpl::new()) {
Ok(eval) => eval,
Err(_) => return ptr::null_mut(),
};
let wrapper = Box::new(CppComparativeEvaluatorWrapper {
evaluator: Arc::new(evaluator),
runtime,
});
Box::into_raw(wrapper) as *mut CppComparativeEvaluatorHandle
}
#[no_mangle]
pub unsafe extern "C" fn cpp_comparative_evaluator_free(
handle: *mut CppComparativeEvaluatorHandle,
) {
if !handle.is_null() {
let _ = Box::from_raw(handle as *mut CppComparativeEvaluatorWrapper);
}
}
#[no_mangle]
pub unsafe extern "C" fn cpp_comparative_evaluator_compare(
handle: *mut CppComparativeEvaluatorHandle,
system_a: *const c_float,
system_a_length: c_uint,
system_b: *const c_float,
system_b_length: c_uint,
reference: *const c_float,
reference_length: c_uint,
sample_rate: c_uint,
channels: c_uint,
result: *mut CppComparisonResult,
) -> c_int {
if handle.is_null() || system_a.is_null() || system_b.is_null() || result.is_null() {
return -1;
}
let wrapper = &*(handle as *const CppComparativeEvaluatorWrapper);
let start_time = std::time::Instant::now();
let system_a_samples = std::slice::from_raw_parts(system_a, system_a_length as usize);
let system_a_buf = AudioBuffer::new(system_a_samples.to_vec(), sample_rate, channels);
let system_b_samples = std::slice::from_raw_parts(system_b, system_b_length as usize);
let system_b_buf = AudioBuffer::new(system_b_samples.to_vec(), sample_rate, channels);
let reference_buf = if !reference.is_null() && reference_length > 0 {
let reference_samples = std::slice::from_raw_parts(reference, reference_length as usize);
Some(AudioBuffer::new(
reference_samples.to_vec(),
sample_rate,
channels,
))
} else {
None
};
use crate::traits::ComparativeEvaluator as _;
let eval_result = wrapper.runtime.block_on(wrapper.evaluator.compare_samples(
&system_a_buf,
&system_b_buf,
None,
));
let processing_time = start_time.elapsed().as_secs_f64() * 1000.0;
match eval_result {
Ok(comp) => {
let system_a_score = comp
.metric_comparisons
.values()
.map(|m| m.score_a)
.sum::<f32>()
/ comp.metric_comparisons.len().max(1) as f32;
let system_b_score = comp
.metric_comparisons
.values()
.map(|m| m.score_b)
.sum::<f32>()
/ comp.metric_comparisons.len().max(1) as f32;
let winner = if comp.preference_score < -0.1 {
0
} else if comp.preference_score > 0.1 {
1
} else {
2
};
let p_value = comp.statistical_significance.values().copied().sum::<f32>()
/ comp.statistical_significance.len().max(1) as f32;
*result = CppComparisonResult {
system_a_score,
system_b_score,
winner,
p_value: p_value as f64,
effect_size: comp.preference_score as f64,
processing_time_ms: processing_time,
error_code: 0,
};
0
}
Err(_) => {
*result = CppComparisonResult {
error_code: -2,
processing_time_ms: processing_time,
..Default::default()
};
-2
}
}
}
#[no_mangle]
pub unsafe extern "C" fn cpp_evaluation_version() -> *const c_char {
static VERSION_CSTRING: once_cell::sync::Lazy<CString> = once_cell::sync::Lazy::new(|| {
CString::new(crate::VERSION)
.unwrap_or_else(|_| CString::new("unknown").expect("value should be present"))
});
VERSION_CSTRING.as_ptr()
}
#[no_mangle]
pub unsafe extern "C" fn cpp_evaluation_last_error() -> *const c_char {
thread_local! {
static LAST_ERROR: std::cell::RefCell<Option<CString>> = std::cell::RefCell::new(None);
}
LAST_ERROR.with(|error| error.borrow().as_ref().map_or(ptr::null(), |s| s.as_ptr()))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cpp_quality_result_default() {
let result = CppQualityResult::default();
assert_eq!(result.overall_score, 0.0);
assert_eq!(result.pesq, -1.0);
assert_eq!(result.error_code, 0);
}
#[test]
fn test_cpp_pronunciation_result_default() {
let result = CppPronunciationResult::default();
assert_eq!(result.overall_score, 0.0);
assert_eq!(result.error_code, 0);
}
#[test]
fn test_cpp_comparison_result_default() {
let result = CppComparisonResult::default();
assert_eq!(result.winner, 2); assert_eq!(result.error_code, 0);
}
#[test]
fn test_version_string() {
unsafe {
let version = cpp_evaluation_version();
assert!(!version.is_null());
let version_str = CStr::from_ptr(version).to_str().unwrap();
assert!(!version_str.is_empty());
}
}
#[test]
fn test_quality_evaluator_lifecycle() {
unsafe {
let handle = cpp_quality_evaluator_new();
assert!(!handle.is_null());
cpp_quality_evaluator_free(handle);
}
}
#[test]
fn test_pronunciation_evaluator_lifecycle() {
unsafe {
let handle = cpp_pronunciation_evaluator_new();
assert!(!handle.is_null());
cpp_pronunciation_evaluator_free(handle);
}
}
#[test]
fn test_comparative_evaluator_lifecycle() {
unsafe {
let handle = cpp_comparative_evaluator_new();
assert!(!handle.is_null());
cpp_comparative_evaluator_free(handle);
}
}
}