#![allow(missing_docs)]
#![warn(clippy::all)]
#![allow(clippy::module_name_repetitions)]
#![allow(clippy::unused_async)] #![allow(clippy::cast_precision_loss)] #![allow(clippy::cast_possible_truncation)] #![allow(clippy::cast_sign_loss)] #![allow(clippy::cast_lossless)] #![allow(clippy::unused_self)] #![allow(clippy::must_use_candidate)] #![allow(clippy::missing_errors_doc)] #![allow(clippy::missing_panics_doc)] #![allow(clippy::uninlined_format_args)] #![allow(clippy::similar_names)] #![allow(clippy::unnecessary_wraps)] #![allow(clippy::format_push_string)] #![allow(clippy::manual_clamp)] #![allow(clippy::doc_markdown)] #![allow(clippy::return_self_not_must_use)] #![allow(clippy::if_not_else)] #![allow(clippy::redundant_closure_for_method_calls)] #![allow(clippy::match_same_arms)] #![allow(clippy::inefficient_to_string)] #![allow(clippy::needless_pass_by_value)] #![allow(clippy::too_many_lines)] #![allow(clippy::struct_excessive_bools)] #![allow(clippy::needless_range_loop)] #![allow(clippy::wildcard_imports)] #![allow(clippy::single_char_add_str)] #![allow(clippy::map_unwrap_or)] #![allow(clippy::excessive_precision)] #![allow(clippy::cast_possible_wrap)] #![allow(clippy::cloned_instead_of_copied)] #![allow(clippy::useless_vec)] #![allow(clippy::ptr_as_ptr)] #![allow(clippy::manual_let_else)] #![allow(clippy::unnecessary_cast)] #![allow(clippy::trivially_copy_pass_by_ref)] #![allow(clippy::items_after_statements)] #![allow(clippy::too_many_arguments)] #![allow(clippy::new_without_default)] #![allow(clippy::needless_borrow)] #![allow(clippy::derivable_impls)] #![allow(clippy::clone_on_copy)] #![allow(clippy::useless_format)] #![allow(clippy::unwrap_or_default)] #![allow(clippy::single_match_else)] #![allow(clippy::vec_init_then_push)] #![allow(clippy::unnecessary_mut_passed)] #![allow(clippy::manual_range_contains)] #![allow(clippy::len_zero)] #![allow(clippy::float_cmp)] #![allow(clippy::range_plus_one)] #![allow(clippy::manual_string_new)] #![allow(clippy::should_implement_trait)] #![allow(clippy::let_and_return)] #![allow(clippy::type_complexity)] #![allow(clippy::collapsible_else_if)] #![allow(clippy::collapsible_if)] #![allow(clippy::collapsible_match)] #![allow(clippy::single_char_pattern)] #![allow(clippy::needless_borrows_for_generic_args)] #![allow(clippy::default_trait_access)] #![allow(clippy::empty_line_after_doc_comments)] #![allow(clippy::bool_to_int_with_if)] #![allow(clippy::manual_ok_err)] #![allow(clippy::match_like_matches_macro)] #![allow(clippy::needless_continue)] #![allow(clippy::explicit_iter_loop)] #![allow(clippy::semicolon_if_nothing_returned)] #![allow(clippy::unnecessary_map_or)] #![allow(clippy::ref_option)] #![allow(clippy::used_underscore_binding)] #![allow(clippy::ip_constant)] #![allow(clippy::for_kv_map)] #![allow(clippy::assigning_clones)] #![allow(clippy::manual_map)] #![allow(clippy::manual_flatten)] #![allow(clippy::await_holding_lock)] #![allow(clippy::borrowed_box)] #![allow(clippy::unnecessary_literal_bound)] #![allow(clippy::borrow_as_ptr)] #![allow(clippy::case_sensitive_file_extension_comparisons)] #![allow(clippy::comparison_chain)] #![allow(clippy::format_collect)] #![allow(clippy::if_same_then_else)] #![allow(clippy::implicit_saturating_sub)] #![allow(clippy::iter_kv_map)] #![allow(clippy::match_result_ok)] #![allow(clippy::match_wildcard_for_single_variants)] #![allow(clippy::missing_const_for_thread_local)] #![allow(clippy::mixed_attributes_style)] #![allow(clippy::stable_sort_primitive)] #![allow(clippy::struct_field_names)] #![allow(clippy::unnecessary_debug_formatting)] #![allow(clippy::useless_asref)] #![allow(clippy::useless_conversion)]
pub use voirs_recognizer::traits::{PhonemeAlignment, Transcript};
pub use voirs_sdk::{AudioBuffer, LanguageCode, Phoneme, VoirsError};
pub mod accuracy_benchmarks;
pub mod advanced_preprocessing;
pub mod audio;
pub mod audit;
pub mod automated_benchmarks;
pub mod backends;
pub mod benchmark_export;
pub mod benchmark_runner;
pub mod benchmarks;
pub mod caching;
pub mod commercial_tool_comparison;
pub mod comparison;
pub mod compliance;
pub mod compliance_testing;
pub mod context_aware;
pub mod conversational;
pub mod cpp_bindings;
pub mod cross_language_validation;
pub mod csf_validation;
pub mod data_quality_validation;
pub mod data_versioning;
pub mod dataset_management;
pub mod deep_learning_metrics;
pub mod distributed;
pub mod doc_generation;
pub mod enterprise_security;
pub mod error_enhancement;
pub mod fairness;
pub mod federated;
pub mod fuzzing;
pub mod graphql;
pub mod ground_truth_dataset;
pub mod integration;
pub mod kubernetes;
pub mod logging;
pub mod matlab_bindings;
pub mod metric_reliability_testing;
pub mod metrics_comparison;
pub mod metrics_explainability;
pub mod multi_turn_dialogue;
pub mod multiregion;
pub mod nodejs_bindings;
pub mod observability;
pub mod perceptual;
pub mod performance;
pub mod performance_enhancements;
pub mod performance_monitor;
pub mod platform;
pub mod plugins;
pub mod precision;
pub mod privacy;
pub mod pronunciation;
pub mod protocol_documentation;
pub mod quality;
pub mod quality_gates;
#[cfg(feature = "r-integration")]
pub mod r_integration;
#[cfg(feature = "r-integration")]
pub mod r_package_foundation;
pub mod rbac;
pub mod regression_detector;
pub mod regression_testing;
pub mod reproducibility;
pub mod rest_api;
pub mod semantic_similarity;
pub mod standards;
pub mod statistical;
pub mod statistical_enhancements;
pub mod task_oriented;
pub mod traits;
pub mod user_experience;
pub mod validation;
pub mod validation_certificates;
pub mod websocket;
pub mod workflows;
#[cfg(feature = "python")]
pub mod python;
pub use performance::{multi_gpu, LRUCache, PersistentCache, SlidingWindowProcessor};
pub use traits::*;
#[cfg(feature = "r-integration")]
pub use r_integration::*;
#[cfg(feature = "python")]
pub use python::*;
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
pub mod prelude {
pub use crate::traits::{
ComparativeEvaluator, ComparisonMetric, ComparisonResult, EvaluationResult,
PronunciationEvaluator, PronunciationMetric, PronunciationScore,
QualityEvaluator as QualityEvaluatorTrait, QualityMetric, QualityScore,
SelfEvaluationResult, SelfEvaluator,
};
pub use crate::audio::{
AudioFormat, AudioLoader, LoadOptions, StreamingConfig, StreamingEvaluator,
};
pub use crate::comparison::ComparativeEvaluatorImpl;
pub use crate::compliance::{
ComplianceChecker, ComplianceConfig, ComplianceResult, ComplianceStatus,
};
pub use crate::integration::{EcosystemConfig, EcosystemEvaluator, EcosystemResults};
pub use crate::perceptual::{
EnhancedMultiListenerSimulator, IntelligibilityMonitor, MultiListenerConfig,
};
pub use crate::performance_enhancements::{CacheStats, OptimizedQualityEvaluator};
pub use crate::platform::{DeploymentConfig, PlatformCompatibility, PlatformInfo};
pub use crate::plugins::{
EvaluationContext, ExampleMetricPlugin, MetricPlugin, MetricResult, PluginConfig,
PluginError, PluginInfo, PluginManager,
};
pub use crate::pronunciation::PronunciationEvaluatorImpl;
pub use crate::quality::{
AdvancedSpectralAnalysis, AgeGroup, ChildrenEvaluationConfig, ChildrenEvaluationResult,
ChildrenSpeechEvaluator, CochlearImplantStrategy, CulturalRegion, ElderlyAgeGroup,
ElderlyPathologicalConfig, ElderlyPathologicalEvaluator, ElderlyPathologicalResult,
EmotionType, EmotionalEvaluationConfig, EmotionalSpeechEvaluationResult,
EmotionalSpeechEvaluator, ExpressionStyle, HearingAidType, ModelArchitecture, NeuralConfig,
NeuralEvaluator, NeuralQualityAssessment, PathologicalCondition, PersonalityTrait,
PsychoacousticAnalysis, PsychoacousticConfig, PsychoacousticEvaluator, QualityEvaluator,
SeverityLevel, SingingEvaluationConfig, SingingEvaluationResult, SingingEvaluator,
SpectralAnalysisConfig, SpectralAnalyzer,
};
pub use crate::validation::{ValidationConfig, ValidationFramework, ValidationResult};
pub use crate::websocket::{
RealtimeAnalysis, SessionConfig, WebSocketConfig, WebSocketError, WebSocketMessage,
WebSocketSessionManager,
};
#[cfg(feature = "r-integration")]
pub use crate::r_integration::{
RAnovaResult, RArimaModel, RDataFrame, RGamModel, RKmeansResult, RLinearModel,
RLogisticModel, RPcaResult, RRandomForestModel, RSession, RSurvivalModel, RTestResult,
RTimeSeriesResult, RValue,
};
pub use voirs_recognizer::traits::{PhonemeAlignment, Transcript};
pub use voirs_sdk::{AudioBuffer, LanguageCode, Phoneme, VoirsError};
pub use async_trait::async_trait;
}
#[derive(Debug, thiserror::Error)]
pub enum EvaluationError {
#[error("Quality evaluation failed: {message}")]
QualityEvaluationError {
message: String,
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
#[error("Pronunciation evaluation failed: {message}")]
PronunciationEvaluationError {
message: String,
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
#[error("Comparison evaluation failed: {message}")]
ComparisonError {
message: String,
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
#[error("Metric calculation failed: {metric} - {message}")]
MetricCalculationError {
metric: String,
message: String,
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
#[error("Audio processing error: {message}")]
AudioProcessingError {
message: String,
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
#[error("Processing error: {message}")]
ProcessingError {
message: String,
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
#[error("Configuration error: {message}")]
ConfigurationError {
message: String,
},
#[error("Model error: {message}")]
ModelError {
message: String,
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
#[error("Invalid input: {message}")]
InvalidInput {
message: String,
},
#[error("Feature not supported: {feature}")]
FeatureNotSupported {
feature: String,
},
#[error("I/O error: {0}")]
Io(String),
#[error("Error: {0}")]
Other(String),
}
impl From<EvaluationError> for VoirsError {
fn from(err: EvaluationError) -> Self {
match err {
EvaluationError::QualityEvaluationError { message, source } => {
VoirsError::ModelError {
model_type: voirs_sdk::error::ModelType::Vocoder, message,
source,
}
}
EvaluationError::PronunciationEvaluationError { message, source } => {
VoirsError::ModelError {
model_type: voirs_sdk::error::ModelType::ASR,
message,
source,
}
}
EvaluationError::ComparisonError { message, source: _ } => VoirsError::AudioError {
message,
buffer_info: None,
},
EvaluationError::MetricCalculationError {
metric,
message,
source: _,
} => VoirsError::AudioError {
message: format!("Metric calculation failed: {metric} - {message}"),
buffer_info: None,
},
EvaluationError::AudioProcessingError { message, source: _ } => {
VoirsError::AudioError {
message,
buffer_info: None,
}
}
EvaluationError::ConfigurationError { message } => VoirsError::ConfigError {
field: "evaluation".to_string(),
message,
},
EvaluationError::ModelError { message, source } => VoirsError::ModelError {
model_type: voirs_sdk::error::ModelType::Vocoder,
message,
source,
},
EvaluationError::InvalidInput { message } => VoirsError::ConfigError {
field: "input".to_string(),
message: format!("Invalid input: {message}"),
},
EvaluationError::FeatureNotSupported { feature } => VoirsError::ModelError {
model_type: voirs_sdk::error::ModelType::Vocoder,
message: format!("Feature not supported: {feature}"),
source: None,
},
EvaluationError::ProcessingError { message, source: _ } => VoirsError::AudioError {
message,
buffer_info: None,
},
EvaluationError::Io(msg) => VoirsError::IoError {
path: std::path::PathBuf::from("unknown"),
operation: voirs_sdk::error::IoOperation::Read,
source: std::io::Error::other(msg),
},
EvaluationError::Other(msg) => VoirsError::InternalError {
component: "evaluation".to_string(),
message: msg,
},
}
}
}
impl From<VoirsError> for EvaluationError {
fn from(err: VoirsError) -> Self {
match err {
VoirsError::ModelError {
model_type: _,
message,
source,
} => EvaluationError::ModelError { message, source },
VoirsError::AudioError {
message,
buffer_info: _,
} => EvaluationError::AudioProcessingError {
message,
source: None,
},
VoirsError::ConfigError { field: _, message } => {
EvaluationError::ConfigurationError { message }
}
VoirsError::G2pError { message, .. } => EvaluationError::ModelError {
message,
source: None,
},
VoirsError::NetworkError { message, .. } => EvaluationError::ModelError {
message: format!("Network error: {message}"),
source: None,
},
VoirsError::IoError {
path, operation, ..
} => EvaluationError::ModelError {
message: format!("IO error: {} on {}", operation, path.display()),
source: None,
},
VoirsError::DataValidationFailed { data_type, reason } => {
EvaluationError::InvalidInput {
message: format!("Validation failed for {data_type}: {reason}"),
}
}
VoirsError::TextPreprocessingError { message, .. } => {
EvaluationError::AudioProcessingError {
message: format!("Text preprocessing error: {message}"),
source: None,
}
}
VoirsError::NotImplemented { feature } => {
EvaluationError::FeatureNotSupported { feature }
}
VoirsError::ResourceExhausted { resource, details } => EvaluationError::ModelError {
message: format!("Resource exhausted: {resource}: {details}"),
source: None,
},
VoirsError::InternalError { component, message } => EvaluationError::ModelError {
message: format!("Internal error in {component}: {message}"),
source: None,
},
_ => EvaluationError::ModelError {
message: format!("Unknown error: {err}"),
source: None,
},
}
}
}
impl From<scirs2_fft::error::FFTError> for EvaluationError {
fn from(err: scirs2_fft::error::FFTError) -> Self {
EvaluationError::AudioProcessingError {
message: format!("FFT computation error: {err}"),
source: Some(Box::new(err)),
}
}
}
#[must_use]
pub fn default_quality_config() -> QualityEvaluationConfig {
QualityEvaluationConfig::default()
}
#[must_use]
pub fn default_pronunciation_config() -> PronunciationEvaluationConfig {
PronunciationEvaluationConfig::default()
}
#[must_use]
pub fn default_comparison_config() -> ComparisonConfig {
ComparisonConfig::default()
}
pub fn validate_audio_compatibility(
audio1: &AudioBuffer,
audio2: &AudioBuffer,
) -> Result<(), EvaluationError> {
if audio1.sample_rate() != audio2.sample_rate() {
return Err(EvaluationError::InvalidInput {
message: format!(
"Sample rate mismatch: {} vs {}",
audio1.sample_rate(),
audio2.sample_rate()
),
});
}
if audio1.channels() != audio2.channels() {
return Err(EvaluationError::InvalidInput {
message: format!(
"Channel count mismatch: {} vs {}",
audio1.channels(),
audio2.channels()
),
});
}
Ok(())
}
#[must_use]
pub fn calculate_correlation(scores1: &[f32], scores2: &[f32]) -> f32 {
if scores1.len() != scores2.len() || scores1.is_empty() {
return 0.0;
}
let n = scores1.len() as f32;
let mean1 = scores1.iter().sum::<f32>() / n;
let mean2 = scores2.iter().sum::<f32>() / n;
let mut numerator = 0.0;
let mut sum_sq1 = 0.0;
let mut sum_sq2 = 0.0;
for (&s1, &s2) in scores1.iter().zip(scores2.iter()) {
let diff1 = s1 - mean1;
let diff2 = s2 - mean2;
numerator += diff1 * diff2;
sum_sq1 += diff1 * diff1;
sum_sq2 += diff2 * diff2;
}
let denominator = (sum_sq1 * sum_sq2).sqrt();
if denominator > 0.0 {
numerator / denominator
} else {
0.0
}
}
#[must_use]
pub fn quality_score_to_label(score: f32) -> &'static str {
match score {
s if s >= 0.9 => "Excellent",
s if s >= 0.8 => "Good",
s if s >= 0.7 => "Fair",
s if s >= 0.6 => "Poor",
_ => "Very Poor",
}
}
#[must_use]
pub fn pronunciation_score_to_label(score: f32) -> &'static str {
match score {
s if s >= 0.95 => "Native-like",
s if s >= 0.85 => "Very Good",
s if s >= 0.75 => "Good",
s if s >= 0.65 => "Acceptable",
s if s >= 0.5 => "Needs Improvement",
_ => "Poor",
}
}
pub fn normalize_scores(scores: &mut [f32]) {
if scores.is_empty() {
return;
}
let min_score = scores.iter().fold(f32::INFINITY, |a, &b| a.min(b));
let max_score = scores.iter().fold(f32::NEG_INFINITY, |a, &b| a.max(b));
if max_score > min_score {
let range = max_score - min_score;
for score in scores {
*score = (*score - min_score) / range;
}
}
}
#[must_use]
pub fn weighted_average(scores: &[f32], weights: &[f32]) -> f32 {
if scores.len() != weights.len() || scores.is_empty() {
return 0.0;
}
let weighted_sum: f32 = scores.iter().zip(weights.iter()).map(|(s, w)| s * w).sum();
let weight_sum: f32 = weights.iter().sum();
if weight_sum > 0.0 {
weighted_sum / weight_sum
} else {
0.0
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_version() {
assert!(!VERSION.is_empty());
}
#[test]
fn test_audio_compatibility_validation() {
let audio1 = AudioBuffer::new(vec![0.1, 0.2, 0.3], 16000, 1);
let audio2 = AudioBuffer::new(vec![0.4, 0.5, 0.6], 16000, 1);
assert!(validate_audio_compatibility(&audio1, &audio2).is_ok());
let audio3 = AudioBuffer::new(vec![0.1, 0.2, 0.3], 22050, 1);
assert!(validate_audio_compatibility(&audio1, &audio3).is_err());
let audio4 = AudioBuffer::new(vec![0.1, 0.2, 0.3, 0.4], 16000, 2);
assert!(validate_audio_compatibility(&audio1, &audio4).is_err());
}
#[test]
fn test_correlation_calculation() {
let scores1 = vec![1.0, 2.0, 3.0, 4.0, 5.0];
let scores2 = vec![2.0, 4.0, 6.0, 8.0, 10.0];
let correlation = calculate_correlation(&scores1, &scores2);
assert!((correlation - 1.0).abs() < 0.001);
let scores3 = vec![5.0, 4.0, 3.0, 2.0, 1.0];
let correlation_neg = calculate_correlation(&scores1, &scores3);
assert!((correlation_neg + 1.0).abs() < 0.001); }
#[test]
fn test_quality_score_labels() {
assert_eq!(quality_score_to_label(0.95), "Excellent");
assert_eq!(quality_score_to_label(0.85), "Good");
assert_eq!(quality_score_to_label(0.75), "Fair");
assert_eq!(quality_score_to_label(0.65), "Poor");
assert_eq!(quality_score_to_label(0.45), "Very Poor");
}
#[test]
fn test_pronunciation_score_labels() {
assert_eq!(pronunciation_score_to_label(0.97), "Native-like");
assert_eq!(pronunciation_score_to_label(0.87), "Very Good");
assert_eq!(pronunciation_score_to_label(0.77), "Good");
assert_eq!(pronunciation_score_to_label(0.67), "Acceptable");
assert_eq!(pronunciation_score_to_label(0.57), "Needs Improvement");
assert_eq!(pronunciation_score_to_label(0.37), "Poor");
}
#[test]
fn test_score_normalization() {
let mut scores = vec![10.0, 20.0, 30.0, 40.0, 50.0];
normalize_scores(&mut scores);
assert!((scores[0] - 0.0).abs() < 0.001);
assert!((scores[4] - 1.0).abs() < 0.001);
assert!(scores.iter().all(|&s| (0.0..=1.0).contains(&s)));
}
#[test]
fn test_weighted_average() {
let scores = vec![0.8, 0.6, 0.9];
let weights = vec![0.5, 0.3, 0.2];
let avg = weighted_average(&scores, &weights);
let expected = (0.8 * 0.5 + 0.6 * 0.3 + 0.9 * 0.2) / (0.5 + 0.3 + 0.2);
assert!((avg - expected).abs() < 0.001);
}
#[test]
fn test_default_configs() {
let quality_config = default_quality_config();
assert!(quality_config.objective_metrics);
let pronunciation_config = default_pronunciation_config();
assert!(pronunciation_config.phoneme_level_scoring);
let comparison_config = default_comparison_config();
assert!(comparison_config.enable_statistical_analysis);
}
}