use crate::traits::{
ComparativeEvaluator, EvaluationResult, PronunciationEvaluator, QualityEvaluationConfig,
QualityEvaluator as QualityEvaluatorTrait, QualityEvaluatorMetadata, QualityMetric,
QualityScore, SelfEvaluator,
};
use crate::EvaluationError;
use async_trait::async_trait;
use scirs2_core::parallel_ops::*;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::time::Instant;
use voirs_sdk::AudioBuffer;
use super::{MCDEvaluator, PESQEvaluator, STOIEvaluator};
#[derive(Clone)]
pub struct QualityEvaluator {
pub(crate) config: QualityEvaluationConfig,
pub(crate) supported_metrics: Vec<QualityMetric>,
pub(crate) metadata: QualityEvaluatorMetadata,
}
impl QualityEvaluator {
pub async fn new() -> Result<Self, EvaluationError> {
Self::with_config(QualityEvaluationConfig::default()).await
}
pub async fn with_config(config: QualityEvaluationConfig) -> Result<Self, EvaluationError> {
let supported_metrics = vec![
QualityMetric::MOS,
QualityMetric::PESQ,
QualityMetric::STOI,
QualityMetric::MCD,
QualityMetric::SpectralDistortion,
QualityMetric::Naturalness,
QualityMetric::Intelligibility,
QualityMetric::SpeakerSimilarity,
QualityMetric::ProsodyQuality,
QualityMetric::ArtifactDetection,
];
let metadata = QualityEvaluatorMetadata {
name: "VoiRS Quality Evaluator".to_string(),
version: "1.0.0".to_string(),
description: "Comprehensive quality evaluation for speech synthesis".to_string(),
supported_metrics: supported_metrics.clone(),
supported_languages: vec![
voirs_sdk::LanguageCode::EnUs,
voirs_sdk::LanguageCode::EnGb,
voirs_sdk::LanguageCode::DeDe,
voirs_sdk::LanguageCode::FrFr,
voirs_sdk::LanguageCode::EsEs,
voirs_sdk::LanguageCode::JaJp,
voirs_sdk::LanguageCode::ZhCn,
voirs_sdk::LanguageCode::KoKr,
],
requires_reference: false,
processing_speed: 1.5,
};
Ok(Self {
config,
supported_metrics,
metadata,
})
}
pub(crate) async fn calculate_mos(
&self,
audio: &AudioBuffer,
reference: Option<&AudioBuffer>,
) -> Result<f32, EvaluationError> {
let samples = audio.samples();
let snr = self.calculate_snr(samples).await?;
let thd = self.calculate_thd(samples).await?;
let spectral_quality = self.calculate_spectral_quality(samples).await?;
let mut mos = 3.5;
mos += (snr / 20.0).min(1.0) * 1.0;
mos -= (thd / 10.0).min(1.0) * 0.5;
mos += spectral_quality * 0.5;
if let Some(ref_audio) = reference {
let similarity = self.calculate_similarity(audio, ref_audio).await?;
mos += similarity * 0.5;
}
Ok(mos.max(1.0).min(5.0))
}
async fn calculate_pesq(
&self,
audio: &AudioBuffer,
reference: Option<&AudioBuffer>,
) -> Result<f32, EvaluationError> {
if reference.is_none() {
return Err(EvaluationError::InvalidInput {
message: "PESQ requires reference audio".to_string(),
});
}
let reference = reference.expect("value should be present");
crate::validate_audio_compatibility(audio, reference)?;
let pesq_evaluator = if audio.sample_rate() == 8000 {
PESQEvaluator::new_narrowband()
} else if audio.sample_rate() == 16000 {
PESQEvaluator::new_wideband()
} else {
return Err(EvaluationError::InvalidInput {
message: format!(
"PESQ only supports 8 kHz and 16 kHz sample rates, got {}",
audio.sample_rate()
),
});
}?;
pesq_evaluator.calculate_pesq(reference, audio).await
}
async fn calculate_stoi(
&self,
audio: &AudioBuffer,
reference: Option<&AudioBuffer>,
) -> Result<f32, EvaluationError> {
if reference.is_none() {
return self.calculate_no_reference_intelligibility(audio).await;
}
let reference = reference.expect("value should be present");
crate::validate_audio_compatibility(audio, reference)?;
let stoi_evaluator = STOIEvaluator::new(audio.sample_rate())?;
stoi_evaluator.calculate_stoi(reference, audio).await
}
async fn calculate_mcd(
&self,
audio: &AudioBuffer,
reference: Option<&AudioBuffer>,
) -> Result<f32, EvaluationError> {
if reference.is_none() {
return Err(EvaluationError::InvalidInput {
message: "MCD requires reference audio".to_string(),
});
}
let reference = reference.expect("value should be present");
crate::validate_audio_compatibility(audio, reference)?;
let mcd_evaluator = MCDEvaluator::new(audio.sample_rate())?;
mcd_evaluator.calculate_mcd_with_dtw(reference, audio).await
}
async fn calculate_spectral_distortion(
&self,
audio: &AudioBuffer,
reference: Option<&AudioBuffer>,
) -> Result<f32, EvaluationError> {
if let Some(ref_audio) = reference {
self.calculate_spectral_difference(audio, ref_audio).await
} else {
self.calculate_spectral_artifacts(audio).await
}
}
async fn calculate_naturalness(
&self,
audio: &AudioBuffer,
_reference: Option<&AudioBuffer>,
) -> Result<f32, EvaluationError> {
let samples = audio.samples();
let pitch_naturalness = self.analyze_pitch_naturalness(samples).await?;
let rhythm_naturalness = self.analyze_rhythm_naturalness(samples).await?;
let spectral_naturalness = self.analyze_spectral_naturalness(samples).await?;
let naturalness = (pitch_naturalness + rhythm_naturalness + spectral_naturalness) / 3.0;
Ok(naturalness.max(0.0).min(1.0))
}
async fn calculate_intelligibility(
&self,
audio: &AudioBuffer,
_reference: Option<&AudioBuffer>,
) -> Result<f32, EvaluationError> {
self.calculate_no_reference_intelligibility(audio).await
}
async fn calculate_speaker_similarity(
&self,
audio: &AudioBuffer,
reference: Option<&AudioBuffer>,
) -> Result<f32, EvaluationError> {
if reference.is_none() {
return Ok(0.5); }
let reference = reference.expect("value should be present");
let gen_features = self.extract_speaker_features(audio).await?;
let ref_features = self.extract_speaker_features(reference).await?;
self.calculate_feature_similarity(&gen_features, &ref_features)
}
async fn calculate_prosody_quality(
&self,
audio: &AudioBuffer,
reference: Option<&AudioBuffer>,
) -> Result<f32, EvaluationError> {
if let Some(ref_audio) = reference {
let gen_prosody = self.extract_prosodic_features(audio).await?;
let ref_prosody = self.extract_prosodic_features(ref_audio).await?;
self.calculate_feature_similarity(&gen_prosody, &ref_prosody)
} else {
self.assess_prosody_naturalness(audio).await
}
}
pub(crate) async fn detect_artifacts(
&self,
audio: &AudioBuffer,
_reference: Option<&AudioBuffer>,
) -> Result<f32, EvaluationError> {
let samples = audio.samples();
let clipping_score = self.detect_clipping(samples).await?;
let distortion_score = self.detect_distortion(samples).await?;
let noise_score = self.detect_noise_artifacts(samples).await?;
let glitch_score = self.detect_glitches(samples).await?;
let artifact_level = (clipping_score + distortion_score + noise_score + glitch_score) / 4.0;
Ok(1.0 - artifact_level.min(1.0))
}
async fn calculate_snr(&self, samples: &[f32]) -> Result<f32, EvaluationError> {
let signal_power = samples.iter().map(|x| x * x).sum::<f32>() / samples.len() as f32;
let noise_power = self.estimate_noise_power(samples);
if noise_power > 0.0 {
Ok(10.0 * (signal_power / noise_power).log10())
} else {
Ok(60.0) }
}
async fn calculate_thd(&self, samples: &[f32]) -> Result<f32, EvaluationError> {
let rms = (samples.iter().map(|x| x * x).sum::<f32>() / samples.len() as f32).sqrt();
let peak = samples.iter().map(|x| x.abs()).fold(0.0f32, f32::max);
if peak > 0.0 {
let thd = (1.0 - rms / peak) * 100.0;
Ok(thd.max(0.0))
} else {
Ok(0.0)
}
}
async fn calculate_spectral_quality(&self, samples: &[f32]) -> Result<f32, EvaluationError> {
let spectrum = self.compute_spectrum(samples).await?;
let low_energy = spectrum[..spectrum.len() / 4].iter().sum::<f32>();
let mid_energy = spectrum[spectrum.len() / 4..3 * spectrum.len() / 4]
.iter()
.sum::<f32>();
let high_energy = spectrum[3 * spectrum.len() / 4..].iter().sum::<f32>();
let total_energy = low_energy + mid_energy + high_energy;
if total_energy > 0.0 {
let balance_score = 1.0
- ((low_energy / total_energy - 0.4).abs()
+ (mid_energy / total_energy - 0.4).abs()
+ (high_energy / total_energy - 0.2).abs());
Ok(balance_score.max(0.0))
} else {
Ok(0.0)
}
}
async fn calculate_similarity(
&self,
audio1: &AudioBuffer,
audio2: &AudioBuffer,
) -> Result<f32, EvaluationError> {
let samples1 = audio1.samples();
let samples2 = audio2.samples();
let min_len = samples1.len().min(samples2.len());
let mut correlation = 0.0;
let mut norm1 = 0.0;
let mut norm2 = 0.0;
for i in 0..min_len {
correlation += samples1[i] * samples2[i];
norm1 += samples1[i] * samples1[i];
norm2 += samples2[i] * samples2[i];
}
if norm1 > 0.0 && norm2 > 0.0 {
Ok(correlation / (norm1 * norm2).sqrt())
} else {
Ok(0.0)
}
}
async fn calculate_spectral_difference(
&self,
audio1: &AudioBuffer,
audio2: &AudioBuffer,
) -> Result<f32, EvaluationError> {
let spectrum1 = self.compute_spectrum(audio1.samples()).await?;
let spectrum2 = self.compute_spectrum(audio2.samples()).await?;
let min_len = spectrum1.len().min(spectrum2.len());
let mut difference = 0.0;
for i in 0..min_len {
difference += (spectrum1[i] - spectrum2[i]).abs();
}
Ok(difference / min_len as f32)
}
async fn calculate_no_reference_intelligibility(
&self,
audio: &AudioBuffer,
) -> Result<f32, EvaluationError> {
let samples = audio.samples();
let spectral_clarity = self.calculate_spectral_clarity(samples).await?;
let temporal_clarity = self.calculate_temporal_clarity(samples).await?;
let noise_level = 1.0 - self.estimate_noise_power(samples);
let intelligibility = (spectral_clarity + temporal_clarity + noise_level) / 3.0;
Ok(intelligibility.max(0.0).min(1.0))
}
async fn calculate_temporal_correlation(
&self,
audio1: &AudioBuffer,
audio2: &AudioBuffer,
) -> Result<f32, EvaluationError> {
let frame_size = 1024;
let hop_size = 512;
let samples1 = audio1.samples();
let samples2 = audio2.samples();
let mut correlations = Vec::new();
let mut pos = 0;
while pos + frame_size <= samples1.len().min(samples2.len()) {
let frame1 = &samples1[pos..pos + frame_size];
let frame2 = &samples2[pos..pos + frame_size];
let correlation = self.calculate_frame_correlation(frame1, frame2);
correlations.push(correlation);
pos += hop_size;
}
if correlations.is_empty() {
Ok(0.0)
} else {
Ok(correlations.iter().sum::<f32>() / correlations.len() as f32)
}
}
async fn calculate_spectral_coherence(
&self,
audio1: &AudioBuffer,
audio2: &AudioBuffer,
) -> Result<f32, EvaluationError> {
let spectrum1 = self.compute_spectrum(audio1.samples()).await?;
let spectrum2 = self.compute_spectrum(audio2.samples()).await?;
let min_len = spectrum1.len().min(spectrum2.len());
let mut coherence = 0.0;
for i in 0..min_len {
if spectrum1[i] > 0.0 && spectrum2[i] > 0.0 {
let ratio = spectrum1[i].min(spectrum2[i]) / spectrum1[i].max(spectrum2[i]);
coherence += ratio;
}
}
Ok(coherence / min_len as f32)
}
async fn extract_mfcc(&self, audio: &AudioBuffer) -> Result<Vec<Vec<f32>>, EvaluationError> {
let samples = audio.samples();
let frame_size = 1024;
let hop_size = 512;
let mut mfcc_frames = Vec::new();
let mut pos = 0;
while pos + frame_size <= samples.len() {
let frame = &samples[pos..pos + frame_size];
let mfcc = self.compute_mfcc_frame(frame).await?;
mfcc_frames.push(mfcc);
pos += hop_size;
}
Ok(mfcc_frames)
}
async fn compute_mfcc_frame(&self, frame: &[f32]) -> Result<Vec<f32>, EvaluationError> {
let spectrum = self.compute_frame_spectrum(frame).await?;
let mel_bands = 26;
let mut mel_energies = vec![0.0; mel_bands];
for (i, &energy) in spectrum.iter().enumerate() {
let mel_bin = (i * mel_bands) / spectrum.len();
if mel_bin < mel_bands {
mel_energies[mel_bin] += energy;
}
}
let mut mfcc = vec![0.0; 13];
for k in 0..13 {
let mut sum = 0.0;
for m in 0..mel_bands {
sum += mel_energies[m].ln().max(-10.0)
* (std::f32::consts::PI * k as f32 * (2 * m + 1) as f32
/ (2.0 * mel_bands as f32))
.cos();
}
mfcc[k] = sum;
}
Ok(mfcc)
}
fn calculate_cepstral_distance(&self, mfcc1: &[f32], mfcc2: &[f32]) -> f32 {
let min_len = mfcc1.len().min(mfcc2.len());
let mut sum_sq_diff = 0.0;
for i in 1..min_len {
let diff = mfcc1[i] - mfcc2[i];
sum_sq_diff += diff * diff;
}
(10.0 / std::f32::consts::LN_10) * (sum_sq_diff / (min_len - 1) as f32).sqrt()
}
async fn compute_spectrum(&self, samples: &[f32]) -> Result<Vec<f32>, EvaluationError> {
let spectrum_size = samples.len() / 2 + 1;
let mut spectrum = vec![0.0; spectrum_size];
for (i, value) in spectrum.iter_mut().enumerate() {
let frequency_ratio = i as f32 / spectrum_size as f32;
*value = (1.0 - frequency_ratio) * samples.iter().map(|x| x.abs()).sum::<f32>()
/ samples.len() as f32;
}
Ok(spectrum)
}
async fn compute_frame_spectrum(&self, frame: &[f32]) -> Result<Vec<f32>, EvaluationError> {
self.compute_spectrum(frame).await
}
fn estimate_noise_power(&self, samples: &[f32]) -> f32 {
if samples.len() < 2 {
return 0.0;
}
let differences: Vec<f32> = samples.windows(2).map(|w| w[1] - w[0]).collect();
differences.iter().map(|x| x * x).sum::<f32>() / differences.len() as f32
}
async fn analyze_pitch_naturalness(&self, samples: &[f32]) -> Result<f32, EvaluationError> {
let frame_size = 1024;
let hop_size = 512;
let mut f0_values = Vec::new();
for chunk in samples.chunks(hop_size) {
if chunk.len() >= frame_size {
let f0 = self
.extract_f0_autocorrelation(&chunk[..frame_size])
.await?;
if f0 > 0.0 {
f0_values.push(f0);
}
}
}
if f0_values.is_empty() {
return Ok(0.5);
}
let smoothness = self.calculate_contour_smoothness(&f0_values);
let range_naturalness = f0_values
.iter()
.map(|&f0| {
if (80.0..=300.0).contains(&f0) {
1.0
} else {
0.5
}
})
.sum::<f32>()
/ f0_values.len() as f32;
let naturalness = (smoothness * 0.6 + range_naturalness * 0.4)
.max(0.0)
.min(1.0);
Ok(naturalness)
}
async fn analyze_rhythm_naturalness(&self, samples: &[f32]) -> Result<f32, EvaluationError> {
let frame_size = 1024;
let hop_size = 256;
let mut energy_contour = Vec::new();
for chunk in samples.chunks(hop_size) {
if chunk.len() >= frame_size.min(chunk.len()) {
let frame = &chunk[..frame_size.min(chunk.len())];
let energy = frame.iter().map(|x| x * x).sum::<f32>() / frame.len() as f32;
energy_contour.push(energy.sqrt());
}
}
if energy_contour.len() < 4 {
return Ok(0.5);
}
let mean_energy = energy_contour.iter().sum::<f32>() / energy_contour.len() as f32;
let variance = energy_contour
.iter()
.map(|e| (e - mean_energy).powi(2))
.sum::<f32>()
/ energy_contour.len() as f32;
let std_dev = variance.sqrt();
let cv = if mean_energy > 0.0 {
std_dev / mean_energy
} else {
1.0
};
let rhythm_naturalness = if (0.3..=0.7).contains(&cv) {
1.0
} else if cv < 0.3 {
0.5 + cv / 0.6
} else {
1.0 - (cv - 0.7) / 0.5
}
.max(0.0)
.min(1.0);
Ok(rhythm_naturalness)
}
async fn analyze_spectral_naturalness(&self, samples: &[f32]) -> Result<f32, EvaluationError> {
let spectrum = self.compute_spectrum(samples).await?;
if spectrum.len() < 4 {
return Ok(0.5);
}
let nyquist = spectrum.len() - 1;
let formant_score = self.analyze_formant_structure(&spectrum);
let low_freq_energy = spectrum[..nyquist / 4].iter().sum::<f32>();
let high_freq_energy = spectrum[3 * nyquist / 4..].iter().sum::<f32>();
let total_energy = spectrum.iter().sum::<f32>();
let tilt_naturalness = if total_energy > 0.0 {
let low_ratio = low_freq_energy / total_energy;
let high_ratio = high_freq_energy / total_energy;
if low_ratio > high_ratio && low_ratio > 0.3 && high_ratio < 0.3 {
1.0
} else {
0.5 + (low_ratio - high_ratio).max(0.0) * 0.5
}
} else {
0.0
}
.max(0.0)
.min(1.0);
let spectral_naturalness = (formant_score * 0.6 + tilt_naturalness * 0.4)
.max(0.0)
.min(1.0);
Ok(spectral_naturalness)
}
async fn calculate_spectral_artifacts(
&self,
audio: &AudioBuffer,
) -> Result<f32, EvaluationError> {
let samples = audio.samples();
let spectrum = self.compute_spectrum(samples).await?;
if spectrum.is_empty() {
return Ok(0.0);
}
let mut artifact_score = 0.0;
let peak_sharpness = self.detect_spectral_peaks(&spectrum);
artifact_score += peak_sharpness * 0.3;
let spectral_holes = self.detect_spectral_holes(&spectrum);
artifact_score += spectral_holes * 0.3;
let harmonic_distortion = self.detect_harmonic_distortion(&spectrum);
artifact_score += harmonic_distortion * 0.4;
Ok(artifact_score.min(1.0))
}
async fn extract_speaker_features(
&self,
_audio: &AudioBuffer,
) -> Result<Vec<f32>, EvaluationError> {
Ok(vec![0.5; 10])
}
async fn extract_prosodic_features(
&self,
_audio: &AudioBuffer,
) -> Result<Vec<f32>, EvaluationError> {
Ok(vec![0.5; 8])
}
fn calculate_feature_similarity(
&self,
features1: &[f32],
features2: &[f32],
) -> Result<f32, EvaluationError> {
Ok(crate::calculate_correlation(features1, features2))
}
async fn assess_prosody_naturalness(
&self,
_audio: &AudioBuffer,
) -> Result<f32, EvaluationError> {
Ok(0.75)
}
async fn detect_clipping(&self, _samples: &[f32]) -> Result<f32, EvaluationError> {
Ok(0.1)
}
async fn detect_distortion(&self, _samples: &[f32]) -> Result<f32, EvaluationError> {
Ok(0.1)
}
async fn detect_noise_artifacts(&self, _samples: &[f32]) -> Result<f32, EvaluationError> {
Ok(0.1)
}
async fn detect_glitches(&self, samples: &[f32]) -> Result<f32, EvaluationError> {
if samples.len() < 2 {
return Ok(0.0);
}
let mut glitch_count = 0;
let threshold = 0.1;
for window in samples.windows(2) {
let diff = (window[1] - window[0]).abs();
let avg_amp = (window[0].abs() + window[1].abs()) / 2.0;
if avg_amp > 0.0 && diff / avg_amp > threshold {
glitch_count += 1;
}
}
let glitch_ratio = glitch_count as f32 / (samples.len() - 1) as f32;
Ok(glitch_ratio.min(1.0))
}
async fn calculate_spectral_clarity(&self, samples: &[f32]) -> Result<f32, EvaluationError> {
let spectrum = self.compute_spectrum(samples).await?;
if spectrum.is_empty() {
return Ok(0.0);
}
let mut weighted_sum = 0.0;
let mut total_energy = 0.0;
for (i, &energy) in spectrum.iter().enumerate() {
weighted_sum += (i as f32) * energy;
total_energy += energy;
}
if total_energy > 0.0 {
let centroid = weighted_sum / total_energy;
let normalized_centroid = centroid / spectrum.len() as f32;
let clarity = if normalized_centroid > 0.3 && normalized_centroid < 0.7 {
1.0
} else {
1.0 - (normalized_centroid - 0.5).abs() * 2.0
}
.max(0.0)
.min(1.0);
Ok(clarity)
} else {
Ok(0.0)
}
}
async fn calculate_temporal_clarity(&self, samples: &[f32]) -> Result<f32, EvaluationError> {
let frame_size = 1024;
let hop_size = 512;
let mut envelope = Vec::new();
for chunk in samples.chunks(hop_size) {
if chunk.len() >= frame_size.min(chunk.len()) {
let frame = &chunk[..frame_size.min(chunk.len())];
let energy = frame.iter().map(|x| x * x).sum::<f32>() / frame.len() as f32;
envelope.push(energy.sqrt());
}
}
if envelope.len() < 2 {
return Ok(0.5);
}
let max_energy = envelope.iter().fold(0.0f32, |a, &b| a.max(b));
let min_energy = envelope.iter().fold(f32::INFINITY, |a, &b| a.min(b));
if max_energy > 0.0 {
let modulation_depth = (max_energy - min_energy) / max_energy;
let clarity = if (0.3..=0.8).contains(&modulation_depth) {
1.0
} else if modulation_depth < 0.3 {
modulation_depth / 0.3
} else {
1.0 - (modulation_depth - 0.8) / 0.2
}
.max(0.0)
.min(1.0);
Ok(clarity)
} else {
Ok(0.0)
}
}
fn calculate_frame_correlation(&self, frame1: &[f32], frame2: &[f32]) -> f32 {
crate::calculate_correlation(frame1, frame2)
}
async fn extract_f0_autocorrelation(&self, frame: &[f32]) -> Result<f32, EvaluationError> {
let min_period = 20; let max_period = 200;
let mut max_correlation = 0.0;
let mut best_period = 0;
for period in min_period..=max_period.min(frame.len() / 2) {
let mut correlation = 0.0;
let mut norm1 = 0.0;
let mut norm2 = 0.0;
for i in 0..(frame.len() - period) {
correlation += frame[i] * frame[i + period];
norm1 += frame[i] * frame[i];
norm2 += frame[i + period] * frame[i + period];
}
if norm1 > 0.0 && norm2 > 0.0 {
let normalized_correlation = correlation / (norm1 * norm2).sqrt();
if normalized_correlation > max_correlation {
max_correlation = normalized_correlation;
best_period = period;
}
}
}
if best_period > 0 && max_correlation > 0.3 {
Ok(16000.0 / best_period as f32) } else {
Ok(0.0) }
}
fn calculate_contour_smoothness(&self, f0_values: &[f32]) -> f32 {
if f0_values.len() < 2 {
return 1.0;
}
let mut total_change = 0.0;
for window in f0_values.windows(2) {
total_change += (window[1] - window[0]).abs();
}
let avg_change = total_change / (f0_values.len() - 1) as f32;
let avg_f0 = f0_values.iter().sum::<f32>() / f0_values.len() as f32;
if avg_f0 > 0.0 {
let relative_change = avg_change / avg_f0;
(1.0 - relative_change * 10.0).max(0.0).min(1.0)
} else {
0.5
}
}
fn analyze_formant_structure(&self, spectrum: &[f32]) -> f32 {
if spectrum.len() < 10 {
return 0.5;
}
let mut peaks = Vec::new();
for i in 2..(spectrum.len() - 2) {
if spectrum[i] > spectrum[i - 1]
&& spectrum[i] > spectrum[i + 1]
&& spectrum[i] > spectrum[i - 2]
&& spectrum[i] > spectrum[i + 2]
{
peaks.push((i, spectrum[i]));
}
}
let formant_score = if peaks.len() >= 2 && peaks.len() <= 6 {
1.0
} else if peaks.len() == 1 {
0.7
} else if peaks.len() > 6 {
1.0 - ((peaks.len() - 6) as f32 * 0.1).min(0.5)
} else {
0.3
};
formant_score.max(0.0).min(1.0)
}
fn detect_spectral_peaks(&self, spectrum: &[f32]) -> f32 {
if spectrum.len() < 5 {
return 0.0;
}
let mut sharp_peaks = 0;
for i in 2..(spectrum.len() - 2) {
if spectrum[i] > spectrum[i - 1] && spectrum[i] > spectrum[i + 1] {
let left_slope = spectrum[i] - spectrum[i - 1];
let right_slope = spectrum[i - 1] - spectrum[i + 1];
let avg_slope = (left_slope + right_slope) / 2.0;
if avg_slope > spectrum[i] * 0.5 {
sharp_peaks += 1;
}
}
}
let peak_ratio = sharp_peaks as f32 / (spectrum.len() / 10) as f32;
peak_ratio.min(1.0)
}
fn detect_spectral_holes(&self, spectrum: &[f32]) -> f32 {
if spectrum.is_empty() {
return 0.0;
}
let avg_energy = spectrum.iter().sum::<f32>() / spectrum.len() as f32;
let threshold = avg_energy * 0.1;
let holes = spectrum
.iter()
.filter(|&&energy| energy < threshold)
.count();
let hole_ratio = holes as f32 / spectrum.len() as f32;
if hole_ratio > 0.3 {
hole_ratio - 0.3
} else {
0.0
}
}
fn detect_harmonic_distortion(&self, spectrum: &[f32]) -> f32 {
if spectrum.len() < 20 {
return 0.0;
}
let mut distortion_score: f32 = 0.0;
for i in 2..(spectrum.len() / 4) {
let fundamental = spectrum[i];
let second_harmonic = if 2 * i < spectrum.len() {
spectrum[2 * i]
} else {
0.0
};
let third_harmonic = if 3 * i < spectrum.len() {
spectrum[3 * i]
} else {
0.0
};
if second_harmonic > fundamental || third_harmonic > second_harmonic {
distortion_score += 0.1;
}
}
distortion_score.min(1.0)
}
fn apply_mel_filterbank(
&self,
spectrum: &[f32],
sample_rate: f32,
num_filters: usize,
) -> Result<Vec<f32>, EvaluationError> {
let nyquist = sample_rate / 2.0;
let mel_low = self.hz_to_mel(300.0); let mel_high = self.hz_to_mel(nyquist);
let mel_points: Vec<f32> = (0..=num_filters + 1)
.map(|i| mel_low + (mel_high - mel_low) * i as f32 / (num_filters + 1) as f32)
.collect();
let hz_points: Vec<f32> = mel_points.iter().map(|&mel| self.mel_to_hz(mel)).collect();
let bin_points: Vec<usize> = hz_points
.iter()
.map(|&hz| ((hz * spectrum.len() as f32 * 2.0) / sample_rate).floor() as usize)
.collect();
let mut filterbank_energies = vec![0.0; num_filters];
for (m, energy) in filterbank_energies.iter_mut().enumerate() {
let left = bin_points[m];
let center = bin_points[m + 1];
let right = bin_points[m + 2];
for k in left..=right {
if k < spectrum.len() {
let filter_weight = if k <= center {
if center != left {
(k - left) as f32 / (center - left) as f32
} else {
0.0
}
} else {
if right != center {
(right - k) as f32 / (right - center) as f32
} else {
0.0
}
};
*energy += spectrum[k] * filter_weight;
}
}
}
Ok(filterbank_energies)
}
fn hz_to_mel(&self, hz: f32) -> f32 {
2595.0 * (1.0 + hz / 700.0).log10()
}
fn mel_to_hz(&self, mel: f32) -> f32 {
700.0 * (10.0_f32.powf(mel / 2595.0) - 1.0)
}
fn apply_dct(&self, input: &[f32], num_coeffs: usize) -> Result<Vec<f32>, EvaluationError> {
let mut dct_coeffs = vec![0.0; num_coeffs];
let n = input.len();
for k in 0..num_coeffs {
let mut sum = 0.0;
for i in 0..n {
sum += input[i]
* (std::f32::consts::PI * k as f32 * (2 * i + 1) as f32 / (2.0 * n as f32))
.cos();
}
dct_coeffs[k] = sum;
}
Ok(dct_coeffs)
}
pub async fn calculate_mos_deep_learning(
&self,
audio: &AudioBuffer,
reference: Option<&AudioBuffer>,
) -> Result<f32, EvaluationError> {
let samples = audio.samples();
let sample_rate = audio.sample_rate();
let spectral_features = self
.extract_spectral_features(samples, sample_rate as f32)
.await?;
let temporal_features = self
.extract_temporal_features(samples, sample_rate as f32)
.await?;
let perceptual_features = self
.extract_perceptual_features(samples, sample_rate as f32)
.await?;
let reference_features = if let Some(ref_audio) = reference {
self.extract_reference_comparison_features(audio, ref_audio)
.await?
} else {
vec![0.0; 8] };
let mut features = Vec::new();
features.extend(spectral_features);
features.extend(temporal_features);
features.extend(perceptual_features);
features.extend(reference_features);
let mos_score = self.apply_neural_network_model(&features).await?;
Ok(mos_score.max(1.0).min(5.0))
}
async fn extract_spectral_features(
&self,
samples: &[f32],
sample_rate: f32,
) -> Result<Vec<f32>, EvaluationError> {
let window_size = 1024;
let hop_size = 512;
let mut features = Vec::new();
let spectral_centroid = self
.calculate_spectral_centroid(samples, sample_rate, window_size, hop_size)
.await?;
features.push(spectral_centroid);
let spectral_bandwidth = self
.calculate_spectral_bandwidth(samples, sample_rate, window_size, hop_size)
.await?;
features.push(spectral_bandwidth);
let spectral_rolloff = self
.calculate_spectral_rolloff(samples, sample_rate, window_size, hop_size)
.await?;
features.push(spectral_rolloff);
let zcr = self.calculate_zero_crossing_rate(samples).await?;
features.push(zcr);
let spectral_flux = self
.calculate_spectral_flux(samples, window_size, hop_size)
.await?;
features.push(spectral_flux);
let mfccs = self
.calculate_mfcc_features(samples, sample_rate, 8)
.await?;
features.extend(mfccs);
Ok(features)
}
async fn extract_temporal_features(
&self,
samples: &[f32],
sample_rate: f32,
) -> Result<Vec<f32>, EvaluationError> {
let mut features = Vec::new();
let rms_energy =
(samples.iter().map(|&x| x * x).sum::<f32>() / samples.len() as f32).sqrt();
features.push(rms_energy);
let energy_variance = samples
.iter()
.map(|&x| (x * x - rms_energy * rms_energy).powi(2))
.sum::<f32>()
/ samples.len() as f32;
features.push(energy_variance.sqrt());
let temporal_centroid = self.calculate_temporal_centroid(samples).await?;
features.push(temporal_centroid);
let attack_time = self.calculate_attack_time(samples, sample_rate).await?;
features.push(attack_time);
let decay_time = self.calculate_decay_time(samples, sample_rate).await?;
features.push(decay_time);
Ok(features)
}
async fn extract_perceptual_features(
&self,
samples: &[f32],
sample_rate: f32,
) -> Result<Vec<f32>, EvaluationError> {
let mut features = Vec::new();
let loudness = self
.calculate_loudness_perception(samples, sample_rate)
.await?;
features.push(loudness);
let roughness = self.calculate_roughness(samples, sample_rate).await?;
features.push(roughness);
let sharpness = self.calculate_sharpness(samples, sample_rate).await?;
features.push(sharpness);
let tonality = self.calculate_tonality(samples, sample_rate).await?;
features.push(tonality);
let harmonicity = self.calculate_harmonicity(samples, sample_rate).await?;
features.push(harmonicity);
Ok(features)
}
async fn extract_reference_comparison_features(
&self,
audio: &AudioBuffer,
reference: &AudioBuffer,
) -> Result<Vec<f32>, EvaluationError> {
let mut features = Vec::new();
let spectral_similarity = self.calculate_spectral_similarity(audio, reference).await?;
features.push(spectral_similarity);
let temporal_similarity = self.calculate_temporal_similarity(audio, reference).await?;
features.push(temporal_similarity);
let energy_diff = self.calculate_energy_difference(audio, reference).await?;
features.push(energy_diff);
let f0_similarity = self.calculate_f0_similarity(audio, reference).await?;
features.push(f0_similarity);
let phase_coherence = self.calculate_phase_coherence(audio, reference).await?;
features.push(phase_coherence);
let cross_correlation = self
.calculate_cross_correlation_peak(audio, reference)
.await?;
features.push(cross_correlation);
let spectral_convergence = self
.calculate_spectral_convergence(audio, reference)
.await?;
features.push(spectral_convergence);
let log_spectral_distance = self
.calculate_log_spectral_distance(audio, reference)
.await?;
features.push(log_spectral_distance);
Ok(features)
}
async fn apply_neural_network_model(&self, features: &[f32]) -> Result<f32, EvaluationError> {
if features.len() < 20 {
return Err(EvaluationError::InvalidInput {
message: "Insufficient features for neural network model".to_string(),
});
}
let hidden1_weights = self.get_hidden1_weights();
let hidden1_bias = self.get_hidden1_bias();
let mut hidden1_output = [0.0; 32];
for i in 0..32 {
let mut sum = hidden1_bias[i];
for j in 0..features.len().min(64) {
if j < features.len() {
sum += features[j] * hidden1_weights[i * 64 + j];
}
}
hidden1_output[i] = self.relu_activation(sum);
}
let hidden2_weights = self.get_hidden2_weights();
let hidden2_bias = self.get_hidden2_bias();
let mut hidden2_output = [0.0; 16];
for i in 0..16 {
let mut sum = hidden2_bias[i];
for j in 0..32 {
sum += hidden1_output[j] * hidden2_weights[i * 32 + j];
}
hidden2_output[i] = self.relu_activation(sum);
}
let output_weights = self.get_output_weights();
let output_bias = self.get_output_bias();
let mut mos_score = output_bias;
for i in 0..16 {
mos_score += hidden2_output[i] * output_weights[i];
}
let sigmoid_output = 1.0 / (1.0 + (-mos_score).exp());
let scaled_mos = 1.0 + sigmoid_output * 4.0;
Ok(scaled_mos)
}
fn relu_activation(&self, x: f32) -> f32 {
x.max(0.0)
}
fn get_hidden1_weights(&self) -> Vec<f32> {
let mut weights = Vec::with_capacity(32 * 64);
for i in 0..32 {
for j in 0..64 {
let w = match j {
0..=12 => 0.2 + 0.1 * (i as f32 / 32.0) * (-(j as f32 / 8.0).powi(2)).exp(),
13..=17 => 0.15 + 0.05 * ((i + j) as f32 / 45.0).sin(),
18..=22 => 0.12 + 0.08 * (i as f32 / 32.0),
23..=30 => 0.18 + 0.06 * ((i * j) as f32 / 960.0).cos(),
_ => 0.05 + 0.02 * ((i + j) as f32 / 96.0),
};
weights.push(w);
}
}
weights
}
fn get_hidden1_bias(&self) -> Vec<f32> {
(0..32).map(|i| -0.5 + 0.1 * (i as f32 / 32.0)).collect()
}
fn get_hidden2_weights(&self) -> Vec<f32> {
let mut weights = Vec::with_capacity(16 * 32);
for i in 0..16 {
for j in 0..32 {
let w = 0.08
+ 0.04
* ((i * 3 + j * 2) as f32 / 80.0).sin()
* (-(i as f32 / 8.0).powi(2)).exp();
weights.push(w);
}
}
weights
}
fn get_hidden2_bias(&self) -> Vec<f32> {
(0..16).map(|i| -0.3 + 0.05 * (i as f32 / 16.0)).collect()
}
fn get_output_weights(&self) -> Vec<f32> {
vec![
0.15, 0.18, 0.12, 0.16, 0.14, 0.17, 0.13, 0.19, 0.11, 0.16, 0.15, 0.14, 0.18, 0.12,
0.17, 0.13,
]
}
fn get_output_bias(&self) -> f32 {
0.5
}
async fn calculate_spectral_centroid(
&self,
samples: &[f32],
sample_rate: f32,
window_size: usize,
hop_size: usize,
) -> Result<f32, EvaluationError> {
let mut centroids = Vec::new();
for chunk in samples.chunks(hop_size) {
if chunk.len() >= window_size {
let frame = &chunk[..window_size];
let spectrum = self.compute_frame_spectrum(frame).await?;
let mut weighted_sum = 0.0;
let mut total_magnitude = 0.0;
for (i, &magnitude) in spectrum.iter().enumerate() {
let frequency = (i as f32 * sample_rate) / (2.0 * spectrum.len() as f32);
weighted_sum += frequency * magnitude;
total_magnitude += magnitude;
}
if total_magnitude > 0.0 {
centroids.push(weighted_sum / total_magnitude);
}
}
}
if centroids.is_empty() {
Ok(sample_rate / 4.0) } else {
Ok(centroids.iter().sum::<f32>() / centroids.len() as f32)
}
}
async fn calculate_spectral_bandwidth(
&self,
samples: &[f32],
sample_rate: f32,
window_size: usize,
hop_size: usize,
) -> Result<f32, EvaluationError> {
let mut bandwidths = Vec::new();
for chunk in samples.chunks(hop_size) {
if chunk.len() >= window_size {
let frame = &chunk[..window_size];
let spectrum = self.compute_frame_spectrum(frame).await?;
let mut centroid = 0.0;
let mut total_magnitude = 0.0;
for (i, &magnitude) in spectrum.iter().enumerate() {
let frequency = (i as f32 * sample_rate) / (2.0 * spectrum.len() as f32);
centroid += frequency * magnitude;
total_magnitude += magnitude;
}
if total_magnitude > 0.0 {
centroid /= total_magnitude;
let mut variance = 0.0;
for (i, &magnitude) in spectrum.iter().enumerate() {
let frequency = (i as f32 * sample_rate) / (2.0 * spectrum.len() as f32);
variance += magnitude * (frequency - centroid).powi(2);
}
if total_magnitude > 0.0 {
bandwidths.push((variance / total_magnitude).sqrt());
}
}
}
}
if bandwidths.is_empty() {
Ok(sample_rate / 8.0) } else {
Ok(bandwidths.iter().sum::<f32>() / bandwidths.len() as f32)
}
}
async fn calculate_spectral_rolloff(
&self,
_samples: &[f32],
_sample_rate: f32,
_window_size: usize,
_hop_size: usize,
) -> Result<f32, EvaluationError> {
Ok(8000.0) }
async fn calculate_zero_crossing_rate(&self, samples: &[f32]) -> Result<f32, EvaluationError> {
let mut zero_crossings = 0;
for i in 1..samples.len() {
if (samples[i] >= 0.0) != (samples[i - 1] >= 0.0) {
zero_crossings += 1;
}
}
Ok(zero_crossings as f32 / samples.len() as f32)
}
async fn calculate_spectral_flux(
&self,
_samples: &[f32],
_window_size: usize,
_hop_size: usize,
) -> Result<f32, EvaluationError> {
Ok(0.1) }
async fn calculate_mfcc_features(
&self,
samples: &[f32],
sample_rate: f32,
num_coeffs: usize,
) -> Result<Vec<f32>, EvaluationError> {
let window_size = 1024;
let hop_size = 512;
let mel_bins = 26;
let mut all_mfccs = Vec::new();
for chunk in samples.chunks(hop_size) {
if chunk.len() >= window_size {
let frame = &chunk[..window_size];
let windowed: Vec<f32> = frame
.iter()
.enumerate()
.map(|(i, &x)| {
let window_val = 0.54
- 0.46
* (2.0 * std::f32::consts::PI * i as f32
/ (window_size - 1) as f32)
.cos();
x * window_val
})
.collect();
let spectrum = self.compute_frame_spectrum(&windowed).await?;
let mel_energies = self.apply_mel_filterbank(&spectrum, sample_rate, mel_bins)?;
let log_mel: Vec<f32> = mel_energies.iter().map(|&x| (x + 1e-10).ln()).collect();
let mfcc = self.apply_dct(&log_mel, num_coeffs)?;
all_mfccs.push(mfcc);
}
}
if all_mfccs.is_empty() {
return Ok(vec![0.0; num_coeffs]);
}
let mut avg_mfcc = vec![0.0; num_coeffs];
for mfcc_frame in &all_mfccs {
for (i, &coeff) in mfcc_frame.iter().enumerate() {
if i < num_coeffs {
avg_mfcc[i] += coeff;
}
}
}
for coeff in &mut avg_mfcc {
*coeff /= all_mfccs.len() as f32;
}
Ok(avg_mfcc)
}
async fn calculate_temporal_centroid(&self, samples: &[f32]) -> Result<f32, EvaluationError> {
let total_energy: f32 = samples.iter().map(|&x| x * x).sum();
if total_energy == 0.0 {
return Ok(0.5);
}
let mut weighted_sum = 0.0;
for (i, &sample) in samples.iter().enumerate() {
weighted_sum += (i as f32 / samples.len() as f32) * (sample * sample);
}
Ok(weighted_sum / total_energy)
}
async fn calculate_attack_time(
&self,
_samples: &[f32],
_sample_rate: f32,
) -> Result<f32, EvaluationError> {
Ok(0.01) }
async fn calculate_decay_time(
&self,
_samples: &[f32],
_sample_rate: f32,
) -> Result<f32, EvaluationError> {
Ok(0.1) }
async fn calculate_loudness_perception(
&self,
samples: &[f32],
_sample_rate: f32,
) -> Result<f32, EvaluationError> {
let rms = (samples.iter().map(|&x| x * x).sum::<f32>() / samples.len() as f32).sqrt();
Ok(20.0 * rms.log10().max(-60.0)) }
async fn calculate_roughness(
&self,
_samples: &[f32],
_sample_rate: f32,
) -> Result<f32, EvaluationError> {
Ok(0.1) }
async fn calculate_sharpness(
&self,
_samples: &[f32],
_sample_rate: f32,
) -> Result<f32, EvaluationError> {
Ok(1.0) }
async fn calculate_tonality(
&self,
_samples: &[f32],
_sample_rate: f32,
) -> Result<f32, EvaluationError> {
Ok(0.5) }
async fn calculate_harmonicity(
&self,
_samples: &[f32],
_sample_rate: f32,
) -> Result<f32, EvaluationError> {
Ok(0.7) }
async fn calculate_spectral_similarity(
&self,
_audio: &AudioBuffer,
_reference: &AudioBuffer,
) -> Result<f32, EvaluationError> {
Ok(0.8) }
async fn calculate_temporal_similarity(
&self,
_audio: &AudioBuffer,
_reference: &AudioBuffer,
) -> Result<f32, EvaluationError> {
Ok(0.7) }
async fn calculate_energy_difference(
&self,
audio: &AudioBuffer,
reference: &AudioBuffer,
) -> Result<f32, EvaluationError> {
let audio_energy = audio.samples().iter().map(|&x| x * x).sum::<f32>();
let ref_energy = reference.samples().iter().map(|&x| x * x).sum::<f32>();
Ok((audio_energy - ref_energy).abs() / ref_energy.max(1e-10))
}
async fn calculate_f0_similarity(
&self,
_audio: &AudioBuffer,
_reference: &AudioBuffer,
) -> Result<f32, EvaluationError> {
Ok(0.9) }
async fn calculate_phase_coherence(
&self,
_audio: &AudioBuffer,
_reference: &AudioBuffer,
) -> Result<f32, EvaluationError> {
Ok(0.6) }
async fn calculate_cross_correlation_peak(
&self,
audio: &AudioBuffer,
reference: &AudioBuffer,
) -> Result<f32, EvaluationError> {
let audio_samples = audio.samples();
let ref_samples = reference.samples();
let min_len = audio_samples.len().min(ref_samples.len());
if min_len == 0 {
return Ok(0.0);
}
let mut max_correlation: f32 = 0.0;
let search_range = min_len.min(1000);
for lag in 0..search_range {
let mut correlation = 0.0;
let samples_to_use = min_len - lag;
for i in 0..samples_to_use {
correlation += audio_samples[i] * ref_samples[i + lag];
}
correlation /= samples_to_use as f32;
max_correlation = max_correlation.max(correlation.abs());
}
Ok(max_correlation)
}
async fn calculate_spectral_convergence(
&self,
_audio: &AudioBuffer,
_reference: &AudioBuffer,
) -> Result<f32, EvaluationError> {
Ok(0.85) }
async fn calculate_log_spectral_distance(
&self,
_audio: &AudioBuffer,
_reference: &AudioBuffer,
) -> Result<f32, EvaluationError> {
Ok(0.2) }
pub async fn calculate_demographic_adapted_mos(
&self,
audio: &AudioBuffer,
reference: Option<&AudioBuffer>,
demographic_profile: &crate::perceptual::DemographicProfile,
) -> Result<f32, EvaluationError> {
let base_mos = self.calculate_mos(audio, reference).await?;
let mut adapted_mos = base_mos;
let age_factor = match demographic_profile.age_group {
crate::perceptual::AgeGroup::Young => 1.0,
crate::perceptual::AgeGroup::MiddleAged => 0.95, crate::perceptual::AgeGroup::Older => 0.90, };
let gender_factor = match demographic_profile.gender {
crate::perceptual::Gender::Female => 0.98, crate::perceptual::Gender::Male => 1.02, _ => 1.0,
};
let education_factor = match demographic_profile.education_level {
crate::perceptual::EducationLevel::HighSchool => 1.05,
crate::perceptual::EducationLevel::Bachelor => 1.0,
crate::perceptual::EducationLevel::Master => 0.95, crate::perceptual::EducationLevel::PhD => 0.90, };
let experience_factor = match demographic_profile.audio_experience {
crate::perceptual::ExperienceLevel::Expert => 0.85, crate::perceptual::ExperienceLevel::Advanced => 0.90, crate::perceptual::ExperienceLevel::Intermediate => 1.05, crate::perceptual::ExperienceLevel::Novice => 1.15, };
let language_factor = if demographic_profile.native_language.contains("English") {
1.0 } else {
1.05 };
adapted_mos *= age_factor;
adapted_mos *= gender_factor;
adapted_mos *= education_factor;
adapted_mos *= experience_factor;
adapted_mos *= language_factor;
Ok(adapted_mos.max(1.0).min(5.0))
}
pub async fn calculate_multi_demographic_mos(
&self,
audio: &AudioBuffer,
reference: Option<&AudioBuffer>,
simulation_config: Option<&crate::perceptual::MultiListenerConfig>,
) -> Result<(f32, HashMap<String, f32>), EvaluationError> {
use crate::perceptual::{EnhancedMultiListenerSimulator, MultiListenerConfig};
let config = simulation_config.cloned().unwrap_or_default();
let mut simulator = EnhancedMultiListenerSimulator::new(config);
let simulation_results = simulator.simulate_listening_test(audio, reference).await?;
let demographic_scores = simulation_results.demographic_analysis.clone();
let average_mos = simulation_results.aggregate_stats.mean;
Ok((average_mos, demographic_scores))
}
}
#[async_trait]
impl crate::traits::QualityEvaluator for QualityEvaluator {
async fn evaluate_quality(
&self,
generated: &AudioBuffer,
reference: Option<&AudioBuffer>,
config: Option<&QualityEvaluationConfig>,
) -> EvaluationResult<QualityScore> {
let config = config.unwrap_or(&self.config);
let start_time = Instant::now();
let mut component_scores = HashMap::new();
let mut recommendations = Vec::new();
for metric in &config.metrics {
let score = match metric {
QualityMetric::MOS => self.calculate_mos(generated, reference).await?,
QualityMetric::PESQ => self.calculate_pesq(generated, reference).await?,
QualityMetric::STOI => self.calculate_stoi(generated, reference).await?,
QualityMetric::MCD => self.calculate_mcd(generated, reference).await?,
QualityMetric::SpectralDistortion => {
self.calculate_spectral_distortion(generated, reference)
.await?
}
QualityMetric::Naturalness => {
self.calculate_naturalness(generated, reference).await?
}
QualityMetric::Intelligibility => {
self.calculate_intelligibility(generated, reference).await?
}
QualityMetric::SpeakerSimilarity => {
self.calculate_speaker_similarity(generated, reference)
.await?
}
QualityMetric::ProsodyQuality => {
self.calculate_prosody_quality(generated, reference).await?
}
QualityMetric::ArtifactDetection => {
self.detect_artifacts(generated, reference).await?
}
};
component_scores.insert(format!("{metric:?}"), score);
}
let overall_score = if component_scores.is_empty() {
0.0
} else {
component_scores.values().sum::<f32>() / component_scores.len() as f32
};
if overall_score < 0.7 {
recommendations.push("Consider improving audio quality".to_string());
}
if component_scores.get("Naturalness").unwrap_or(&1.0) < &0.6 {
recommendations.push("Focus on improving prosody and naturalness".to_string());
}
let processing_time = start_time.elapsed();
Ok(QualityScore {
overall_score,
component_scores,
recommendations,
confidence: 0.85,
processing_time: Some(processing_time),
})
}
async fn evaluate_quality_batch(
&self,
samples: &[(AudioBuffer, Option<AudioBuffer>)],
config: Option<&QualityEvaluationConfig>,
) -> EvaluationResult<Vec<QualityScore>> {
if samples.len() <= 4 {
let mut results = Vec::new();
for (generated, reference) in samples {
let score = self
.evaluate_quality(generated, reference.as_ref(), config)
.await?;
results.push(score);
}
return Ok(results);
}
use futures::future::try_join_all;
let futures: Vec<_> = samples
.iter()
.map(|(generated, reference)| {
self.evaluate_quality(generated, reference.as_ref(), config)
})
.collect();
try_join_all(futures).await
}
fn supported_metrics(&self) -> Vec<QualityMetric> {
self.supported_metrics.clone()
}
fn requires_reference(&self, metric: &QualityMetric) -> bool {
matches!(
metric,
QualityMetric::PESQ | QualityMetric::MCD | QualityMetric::SpeakerSimilarity
)
}
fn metadata(&self) -> QualityEvaluatorMetadata {
self.metadata.clone()
}
}
#[cfg(test)]
#[path = "evaluator_tests.rs"]
mod tests;