1#![allow(missing_docs)]
40#![warn(clippy::all)]
41#![allow(clippy::module_name_repetitions)]
42#![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};
139pub use voirs_sdk::{AudioBuffer, LanguageCode, Phoneme, VoirsError};
140
141pub mod accuracy_benchmarks;
143pub mod advanced_preprocessing;
144pub mod audio;
145pub mod audit;
147pub mod automated_benchmarks;
148pub mod benchmark_export;
149pub mod benchmark_runner;
150pub mod benchmarks;
151pub mod caching;
154pub mod commercial_tool_comparison;
155pub mod comparison;
156pub mod compliance;
157pub mod compliance_testing;
159pub mod context_aware;
161pub mod conversational;
163pub mod cpp_bindings;
165pub mod cross_language_validation;
167pub mod csf_validation;
169pub mod data_quality_validation;
171pub mod data_versioning;
173pub mod dataset_management;
174pub mod deep_learning_metrics;
176pub mod distributed;
177pub mod doc_generation;
179pub mod enterprise_security;
181pub mod error_enhancement;
183pub mod fairness;
185pub mod federated;
187pub mod fuzzing;
188pub mod graphql;
190pub mod ground_truth_dataset;
192pub mod integration;
193pub mod kubernetes;
195pub mod logging;
197pub mod matlab_bindings;
199pub mod metric_reliability_testing;
201pub mod metrics_comparison;
203pub mod metrics_explainability;
205pub mod multi_turn_dialogue;
207pub mod multiregion;
209pub mod nodejs_bindings;
211pub mod observability;
213pub mod perceptual;
214pub mod performance;
215pub mod performance_enhancements;
217pub mod performance_monitor;
218pub mod platform;
219pub mod plugins;
221pub mod precision;
223pub mod privacy;
225pub mod pronunciation;
226pub mod protocol_documentation;
228pub mod quality;
229pub mod quality_gates;
231#[cfg(feature = "r-integration")]
233pub mod r_integration;
234#[cfg(feature = "r-integration")]
236pub mod r_package_foundation;
237pub mod rbac;
239pub mod regression_detector;
240pub mod regression_testing;
241pub mod reproducibility;
243pub mod rest_api;
245pub mod semantic_similarity;
247pub mod standards;
249pub mod statistical;
250pub mod statistical_enhancements;
252pub mod task_oriented;
254pub mod traits;
255pub mod user_experience;
257pub mod validation;
258pub mod validation_certificates;
260pub mod websocket;
262pub mod workflows;
264
265#[cfg(feature = "python")]
267pub mod python;
268
269pub use performance::{multi_gpu, LRUCache, PersistentCache, SlidingWindowProcessor};
271
272pub use traits::*;
274
275#[cfg(feature = "r-integration")]
281pub use r_integration::*;
282
283#[cfg(feature = "python")]
285pub use python::*;
286
287pub const VERSION: &str = env!("CARGO_PKG_VERSION");
289
290pub mod prelude {
292 pub use crate::traits::{
295 ComparativeEvaluator, ComparisonMetric, ComparisonResult, EvaluationResult,
296 PronunciationEvaluator, PronunciationMetric, PronunciationScore,
297 QualityEvaluator as QualityEvaluatorTrait, QualityMetric, QualityScore,
298 SelfEvaluationResult, SelfEvaluator,
299 };
300
301 pub use crate::audio::{
302 AudioFormat, AudioLoader, LoadOptions, StreamingConfig, StreamingEvaluator,
303 };
304 pub use crate::comparison::ComparativeEvaluatorImpl;
305 pub use crate::compliance::{
306 ComplianceChecker, ComplianceConfig, ComplianceResult, ComplianceStatus,
307 };
308 pub use crate::integration::{EcosystemConfig, EcosystemEvaluator, EcosystemResults};
309 pub use crate::perceptual::{
310 EnhancedMultiListenerSimulator, IntelligibilityMonitor, MultiListenerConfig,
311 };
312 pub use crate::performance_enhancements::{CacheStats, OptimizedQualityEvaluator};
313 pub use crate::platform::{DeploymentConfig, PlatformCompatibility, PlatformInfo};
314 pub use crate::plugins::{
315 EvaluationContext, ExampleMetricPlugin, MetricPlugin, MetricResult, PluginConfig,
316 PluginError, PluginInfo, PluginManager,
317 };
318 pub use crate::pronunciation::PronunciationEvaluatorImpl;
319 pub use crate::quality::{
320 AdvancedSpectralAnalysis, AgeGroup, ChildrenEvaluationConfig, ChildrenEvaluationResult,
321 ChildrenSpeechEvaluator, CochlearImplantStrategy, CulturalRegion, ElderlyAgeGroup,
322 ElderlyPathologicalConfig, ElderlyPathologicalEvaluator, ElderlyPathologicalResult,
323 EmotionType, EmotionalEvaluationConfig, EmotionalSpeechEvaluationResult,
324 EmotionalSpeechEvaluator, ExpressionStyle, HearingAidType, ModelArchitecture, NeuralConfig,
325 NeuralEvaluator, NeuralQualityAssessment, PathologicalCondition, PersonalityTrait,
326 PsychoacousticAnalysis, PsychoacousticConfig, PsychoacousticEvaluator, QualityEvaluator,
327 SeverityLevel, SingingEvaluationConfig, SingingEvaluationResult, SingingEvaluator,
328 SpectralAnalysisConfig, SpectralAnalyzer,
329 };
330 pub use crate::validation::{ValidationConfig, ValidationFramework, ValidationResult};
331 pub use crate::websocket::{
332 RealtimeAnalysis, SessionConfig, WebSocketConfig, WebSocketError, WebSocketMessage,
333 WebSocketSessionManager,
334 };
335
336 #[cfg(feature = "r-integration")]
338 pub use crate::r_integration::{
339 RAnovaResult, RArimaModel, RDataFrame, RGamModel, RKmeansResult, RLinearModel,
340 RLogisticModel, RPcaResult, RRandomForestModel, RSession, RSurvivalModel, RTestResult,
341 RTimeSeriesResult, RValue,
342 };
343
344 pub use voirs_recognizer::traits::{PhonemeAlignment, Transcript};
346 pub use voirs_sdk::{AudioBuffer, LanguageCode, Phoneme, VoirsError};
347
348 pub use async_trait::async_trait;
350}
351
352#[derive(Debug, thiserror::Error)]
358pub enum EvaluationError {
359 #[error("Quality evaluation failed: {message}")]
361 QualityEvaluationError {
362 message: String,
364 #[source]
366 source: Option<Box<dyn std::error::Error + Send + Sync>>,
367 },
368
369 #[error("Pronunciation evaluation failed: {message}")]
371 PronunciationEvaluationError {
372 message: String,
374 #[source]
376 source: Option<Box<dyn std::error::Error + Send + Sync>>,
377 },
378
379 #[error("Comparison evaluation failed: {message}")]
381 ComparisonError {
382 message: String,
384 #[source]
386 source: Option<Box<dyn std::error::Error + Send + Sync>>,
387 },
388
389 #[error("Metric calculation failed: {metric} - {message}")]
391 MetricCalculationError {
392 metric: String,
394 message: String,
396 #[source]
398 source: Option<Box<dyn std::error::Error + Send + Sync>>,
399 },
400
401 #[error("Audio processing error: {message}")]
403 AudioProcessingError {
404 message: String,
406 #[source]
408 source: Option<Box<dyn std::error::Error + Send + Sync>>,
409 },
410
411 #[error("Processing error: {message}")]
413 ProcessingError {
414 message: String,
416 #[source]
418 source: Option<Box<dyn std::error::Error + Send + Sync>>,
419 },
420
421 #[error("Configuration error: {message}")]
423 ConfigurationError {
424 message: String,
426 },
427
428 #[error("Model error: {message}")]
430 ModelError {
431 message: String,
433 #[source]
435 source: Option<Box<dyn std::error::Error + Send + Sync>>,
436 },
437
438 #[error("Invalid input: {message}")]
440 InvalidInput {
441 message: String,
443 },
444
445 #[error("Feature not supported: {feature}")]
447 FeatureNotSupported {
448 feature: String,
450 },
451
452 #[error("I/O error: {0}")]
454 Io(String),
455
456 #[error("Error: {0}")]
458 Other(String),
459}
460
461impl From<EvaluationError> for VoirsError {
462 fn from(err: EvaluationError) -> Self {
463 match err {
464 EvaluationError::QualityEvaluationError { message, source } => {
465 VoirsError::ModelError {
466 model_type: voirs_sdk::error::ModelType::Vocoder, message,
468 source,
469 }
470 }
471 EvaluationError::PronunciationEvaluationError { message, source } => {
472 VoirsError::ModelError {
473 model_type: voirs_sdk::error::ModelType::ASR,
474 message,
475 source,
476 }
477 }
478 EvaluationError::ComparisonError { message, source: _ } => VoirsError::AudioError {
479 message,
480 buffer_info: None,
481 },
482 EvaluationError::MetricCalculationError {
483 metric,
484 message,
485 source: _,
486 } => VoirsError::AudioError {
487 message: format!("Metric calculation failed: {metric} - {message}"),
488 buffer_info: None,
489 },
490 EvaluationError::AudioProcessingError { message, source: _ } => {
491 VoirsError::AudioError {
492 message,
493 buffer_info: None,
494 }
495 }
496 EvaluationError::ConfigurationError { message } => VoirsError::ConfigError {
497 field: "evaluation".to_string(),
498 message,
499 },
500 EvaluationError::ModelError { message, source } => VoirsError::ModelError {
501 model_type: voirs_sdk::error::ModelType::Vocoder,
502 message,
503 source,
504 },
505 EvaluationError::InvalidInput { message } => VoirsError::ConfigError {
506 field: "input".to_string(),
507 message: format!("Invalid input: {message}"),
508 },
509 EvaluationError::FeatureNotSupported { feature } => VoirsError::ModelError {
510 model_type: voirs_sdk::error::ModelType::Vocoder,
511 message: format!("Feature not supported: {feature}"),
512 source: None,
513 },
514 EvaluationError::ProcessingError { message, source: _ } => VoirsError::AudioError {
515 message,
516 buffer_info: None,
517 },
518 EvaluationError::Io(msg) => VoirsError::IoError {
519 path: std::path::PathBuf::from("unknown"),
520 operation: voirs_sdk::error::IoOperation::Read,
521 source: std::io::Error::new(std::io::ErrorKind::Other, msg),
522 },
523 EvaluationError::Other(msg) => VoirsError::InternalError {
524 component: "evaluation".to_string(),
525 message: msg,
526 },
527 }
528 }
529}
530
531impl From<VoirsError> for EvaluationError {
532 fn from(err: VoirsError) -> Self {
533 match err {
534 VoirsError::ModelError {
535 model_type: _,
536 message,
537 source,
538 } => EvaluationError::ModelError { message, source },
539 VoirsError::AudioError {
540 message,
541 buffer_info: _,
542 } => EvaluationError::AudioProcessingError {
543 message,
544 source: None,
545 },
546 VoirsError::ConfigError { field: _, message } => {
547 EvaluationError::ConfigurationError { message }
548 }
549 VoirsError::G2pError { message, .. } => EvaluationError::ModelError {
550 message,
551 source: None,
552 },
553 VoirsError::NetworkError { message, .. } => EvaluationError::ModelError {
554 message: format!("Network error: {message}"),
555 source: None,
556 },
557 VoirsError::IoError {
558 path, operation, ..
559 } => EvaluationError::ModelError {
560 message: format!("IO error: {} on {}", operation, path.display()),
561 source: None,
562 },
563 VoirsError::DataValidationFailed { data_type, reason } => {
564 EvaluationError::InvalidInput {
565 message: format!("Validation failed for {data_type}: {reason}"),
566 }
567 }
568 VoirsError::TextPreprocessingError { message, .. } => {
569 EvaluationError::AudioProcessingError {
570 message: format!("Text preprocessing error: {message}"),
571 source: None,
572 }
573 }
574 VoirsError::NotImplemented { feature } => {
575 EvaluationError::FeatureNotSupported { feature }
576 }
577 VoirsError::ResourceExhausted { resource, details } => EvaluationError::ModelError {
578 message: format!("Resource exhausted: {resource}: {details}"),
579 source: None,
580 },
581 VoirsError::InternalError { component, message } => EvaluationError::ModelError {
582 message: format!("Internal error in {component}: {message}"),
583 source: None,
584 },
585 _ => EvaluationError::ModelError {
586 message: format!("Unknown error: {err}"),
587 source: None,
588 },
589 }
590 }
591}
592
593impl From<scirs2_fft::error::FFTError> for EvaluationError {
594 fn from(err: scirs2_fft::error::FFTError) -> Self {
595 EvaluationError::AudioProcessingError {
596 message: format!("FFT computation error: {err}"),
597 source: Some(Box::new(err)),
598 }
599 }
600}
601
602#[must_use]
608pub fn default_quality_config() -> QualityEvaluationConfig {
609 QualityEvaluationConfig::default()
610}
611
612#[must_use]
614pub fn default_pronunciation_config() -> PronunciationEvaluationConfig {
615 PronunciationEvaluationConfig::default()
616}
617
618#[must_use]
620pub fn default_comparison_config() -> ComparisonConfig {
621 ComparisonConfig::default()
622}
623
624pub fn validate_audio_compatibility(
626 audio1: &AudioBuffer,
627 audio2: &AudioBuffer,
628) -> Result<(), EvaluationError> {
629 if audio1.sample_rate() != audio2.sample_rate() {
630 return Err(EvaluationError::InvalidInput {
631 message: format!(
632 "Sample rate mismatch: {} vs {}",
633 audio1.sample_rate(),
634 audio2.sample_rate()
635 ),
636 });
637 }
638
639 if audio1.channels() != audio2.channels() {
640 return Err(EvaluationError::InvalidInput {
641 message: format!(
642 "Channel count mismatch: {} vs {}",
643 audio1.channels(),
644 audio2.channels()
645 ),
646 });
647 }
648
649 Ok(())
650}
651
652#[must_use]
654pub fn calculate_correlation(scores1: &[f32], scores2: &[f32]) -> f32 {
655 if scores1.len() != scores2.len() || scores1.is_empty() {
656 return 0.0;
657 }
658
659 let n = scores1.len() as f32;
660 let mean1 = scores1.iter().sum::<f32>() / n;
661 let mean2 = scores2.iter().sum::<f32>() / n;
662
663 let mut numerator = 0.0;
664 let mut sum_sq1 = 0.0;
665 let mut sum_sq2 = 0.0;
666
667 for (&s1, &s2) in scores1.iter().zip(scores2.iter()) {
668 let diff1 = s1 - mean1;
669 let diff2 = s2 - mean2;
670 numerator += diff1 * diff2;
671 sum_sq1 += diff1 * diff1;
672 sum_sq2 += diff2 * diff2;
673 }
674
675 let denominator = (sum_sq1 * sum_sq2).sqrt();
676 if denominator > 0.0 {
677 numerator / denominator
678 } else {
679 0.0
680 }
681}
682
683#[must_use]
685pub fn quality_score_to_label(score: f32) -> &'static str {
686 match score {
687 s if s >= 0.9 => "Excellent",
688 s if s >= 0.8 => "Good",
689 s if s >= 0.7 => "Fair",
690 s if s >= 0.6 => "Poor",
691 _ => "Very Poor",
692 }
693}
694
695#[must_use]
697pub fn pronunciation_score_to_label(score: f32) -> &'static str {
698 match score {
699 s if s >= 0.95 => "Native-like",
700 s if s >= 0.85 => "Very Good",
701 s if s >= 0.75 => "Good",
702 s if s >= 0.65 => "Acceptable",
703 s if s >= 0.5 => "Needs Improvement",
704 _ => "Poor",
705 }
706}
707
708pub fn normalize_scores(scores: &mut [f32]) {
710 if scores.is_empty() {
711 return;
712 }
713
714 let min_score = scores.iter().fold(f32::INFINITY, |a, &b| a.min(b));
715 let max_score = scores.iter().fold(f32::NEG_INFINITY, |a, &b| a.max(b));
716
717 if max_score > min_score {
718 let range = max_score - min_score;
719 for score in scores {
720 *score = (*score - min_score) / range;
721 }
722 }
723}
724
725#[must_use]
727pub fn weighted_average(scores: &[f32], weights: &[f32]) -> f32 {
728 if scores.len() != weights.len() || scores.is_empty() {
729 return 0.0;
730 }
731
732 let weighted_sum: f32 = scores.iter().zip(weights.iter()).map(|(s, w)| s * w).sum();
733 let weight_sum: f32 = weights.iter().sum();
734
735 if weight_sum > 0.0 {
736 weighted_sum / weight_sum
737 } else {
738 0.0
739 }
740}
741
742#[cfg(test)]
743mod tests {
744 use super::*;
745
746 #[test]
747 fn test_version() {
748 assert!(!VERSION.is_empty());
750 }
751
752 #[test]
753 fn test_audio_compatibility_validation() {
754 let audio1 = AudioBuffer::new(vec![0.1, 0.2, 0.3], 16000, 1);
755 let audio2 = AudioBuffer::new(vec![0.4, 0.5, 0.6], 16000, 1);
756
757 assert!(validate_audio_compatibility(&audio1, &audio2).is_ok());
759
760 let audio3 = AudioBuffer::new(vec![0.1, 0.2, 0.3], 22050, 1);
762 assert!(validate_audio_compatibility(&audio1, &audio3).is_err());
763
764 let audio4 = AudioBuffer::new(vec![0.1, 0.2, 0.3, 0.4], 16000, 2);
766 assert!(validate_audio_compatibility(&audio1, &audio4).is_err());
767 }
768
769 #[test]
770 fn test_correlation_calculation() {
771 let scores1 = vec![1.0, 2.0, 3.0, 4.0, 5.0];
772 let scores2 = vec![2.0, 4.0, 6.0, 8.0, 10.0];
773
774 let correlation = calculate_correlation(&scores1, &scores2);
775 assert!((correlation - 1.0).abs() < 0.001); let scores3 = vec![5.0, 4.0, 3.0, 2.0, 1.0];
778 let correlation_neg = calculate_correlation(&scores1, &scores3);
779 assert!((correlation_neg + 1.0).abs() < 0.001); }
781
782 #[test]
783 fn test_quality_score_labels() {
784 assert_eq!(quality_score_to_label(0.95), "Excellent");
785 assert_eq!(quality_score_to_label(0.85), "Good");
786 assert_eq!(quality_score_to_label(0.75), "Fair");
787 assert_eq!(quality_score_to_label(0.65), "Poor");
788 assert_eq!(quality_score_to_label(0.45), "Very Poor");
789 }
790
791 #[test]
792 fn test_pronunciation_score_labels() {
793 assert_eq!(pronunciation_score_to_label(0.97), "Native-like");
794 assert_eq!(pronunciation_score_to_label(0.87), "Very Good");
795 assert_eq!(pronunciation_score_to_label(0.77), "Good");
796 assert_eq!(pronunciation_score_to_label(0.67), "Acceptable");
797 assert_eq!(pronunciation_score_to_label(0.57), "Needs Improvement");
798 assert_eq!(pronunciation_score_to_label(0.37), "Poor");
799 }
800
801 #[test]
802 fn test_score_normalization() {
803 let mut scores = vec![10.0, 20.0, 30.0, 40.0, 50.0];
804 normalize_scores(&mut scores);
805
806 assert!((scores[0] - 0.0).abs() < 0.001);
807 assert!((scores[4] - 1.0).abs() < 0.001);
808 assert!(scores.iter().all(|&s| (0.0..=1.0).contains(&s)));
809 }
810
811 #[test]
812 fn test_weighted_average() {
813 let scores = vec![0.8, 0.6, 0.9];
814 let weights = vec![0.5, 0.3, 0.2];
815
816 let avg = weighted_average(&scores, &weights);
817 let expected = (0.8 * 0.5 + 0.6 * 0.3 + 0.9 * 0.2) / (0.5 + 0.3 + 0.2);
818 assert!((avg - expected).abs() < 0.001);
819 }
820
821 #[test]
822 fn test_default_configs() {
823 let quality_config = default_quality_config();
824 assert!(quality_config.objective_metrics);
825
826 let pronunciation_config = default_pronunciation_config();
827 assert!(pronunciation_config.phoneme_level_scoring);
828
829 let comparison_config = default_comparison_config();
830 assert!(comparison_config.enable_statistical_analysis);
831 }
832}