pub(crate) use super::*;
#[test]
fn test_mel_config_whisper() {
let config = MelConfig::whisper();
assert_eq!(config.n_mels, 80);
assert_eq!(config.n_fft, 400);
assert_eq!(config.hop_length, 160);
assert_eq!(config.sample_rate, 16000);
}
#[test]
fn test_mel_config_tts() {
let config = MelConfig::tts();
assert_eq!(config.n_mels, 80);
assert_eq!(config.n_fft, 1024);
assert_eq!(config.hop_length, 256);
assert_eq!(config.sample_rate, 22050);
}
#[test]
fn test_mel_config_n_freqs() {
let config = MelConfig::whisper();
assert_eq!(config.n_freqs(), 201); }
#[test]
fn test_hz_to_mel_zero() {
let mel = MelFilterbank::hz_to_mel(0.0);
assert!((mel - 0.0).abs() < 1e-5, "0 Hz should map to 0 mel");
}
#[test]
fn test_hz_to_mel_1000hz() {
let mel = MelFilterbank::hz_to_mel(1000.0);
assert!(
(mel - 1000.0).abs() < 50.0,
"1000 Hz should be close to 1000 mel, got {mel}"
);
}
#[test]
fn test_mel_to_hz_roundtrip() {
let frequencies = [0.0, 100.0, 500.0, 1000.0, 4000.0, 8000.0];
for &hz in &frequencies {
let mel = MelFilterbank::hz_to_mel(hz);
let recovered = MelFilterbank::mel_to_hz(mel);
assert!(
(hz - recovered).abs() < 0.1,
"Roundtrip failed for {hz} Hz: got {recovered}"
);
}
}
#[test]
fn test_mel_scale_monotonic() {
let mut prev_mel = -1.0_f32;
for hz in (0..8000).step_by(100) {
let mel = MelFilterbank::hz_to_mel(hz as f32);
assert!(
mel > prev_mel,
"Mel scale should be monotonically increasing"
);
prev_mel = mel;
}
}
#[test]
fn test_mel_filterbank_new() {
let config = MelConfig::whisper();
let mel = MelFilterbank::new(&config);
assert_eq!(mel.n_mels(), 80);
assert_eq!(mel.n_fft(), 400);
assert_eq!(mel.sample_rate(), 16000);
assert_eq!(mel.n_freqs(), 201);
}
#[test]
fn test_mel_filterbank_filters_shape() {
let config = MelConfig::whisper();
let mel = MelFilterbank::new(&config);
assert_eq!(mel.filters.len(), 80 * 201);
}
#[test]
fn test_mel_filterbank_filters_nonnegative() {
let config = MelConfig::whisper();
let mel = MelFilterbank::new(&config);
for &f in &mel.filters {
assert!(f >= 0.0, "Filter values should be non-negative");
}
}
#[test]
fn test_mel_filterbank_slaney_normalization() {
let config = MelConfig::whisper();
let mel = MelFilterbank::new(&config);
let max_filter_val = mel.filters.iter().cloned().fold(0.0_f32, f32::max);
assert!(
(max_filter_val - 1.0).abs() > 0.001,
"Slaney normalization should NOT produce peak=1.0, got max={:.6}",
max_filter_val
);
for &f in &mel.filters {
assert!(f >= 0.0, "Filter values should be non-negative");
assert!(f.is_finite(), "Filter values should be finite");
}
}
#[test]
fn test_mel_filterbank_slaney_max_below_threshold() {
let config = MelConfig::whisper();
let mel = MelFilterbank::new(&config);
let max_filter_val = mel.filters.iter().cloned().fold(0.0_f32, f32::max);
assert!(
max_filter_val < 0.1,
"Slaney-normalized filterbank max should be < 0.1, got {:.6}",
max_filter_val
);
}
#[test]
fn test_hann_window_endpoints() {
let window = MelFilterbank::hann_window(100);
assert!(window[0] < 0.01, "Hann window should start near 0");
assert!(window[99] < 0.01, "Hann window should end near 0");
}
#[test]
fn test_hann_window_peak() {
let window = MelFilterbank::hann_window(100);
let mid = window[50];
assert!(mid > 0.9, "Hann window should peak near 1.0 in middle");
}
#[test]
fn test_mel_compute_empty() {
let config = MelConfig::whisper();
let mel = MelFilterbank::new(&config);
let result = mel.compute(&[]);
assert!(result.is_ok());
assert!(result.map_or(false, |v| v.is_empty()));
}
#[test]
fn test_mel_compute_short_audio() {
let config = MelConfig::whisper();
let mel = MelFilterbank::new(&config);
let audio = vec![0.0; 100]; let result = mel.compute(&audio);
assert!(result.is_ok());
assert!(result.map_or(false, |v| v.is_empty()));
}
#[test]
fn test_mel_compute_exact_one_frame() {
let config = MelConfig { center_pad: false, ..MelConfig::whisper() };
let mel = MelFilterbank::new(&config);
let audio = vec![0.0; 400]; let result = mel.compute(&audio).expect("compute should succeed");
assert_eq!(result.len(), 80 * 1);
}
#[test]
fn test_mel_compute_multiple_frames() {
let config = MelConfig { center_pad: false, ..MelConfig::whisper() };
let mel = MelFilterbank::new(&config);
let audio = vec![0.0; 16000];
let result = mel.compute(&audio).expect("compute should succeed");
let n_frames = result.len() / 80;
assert_eq!(n_frames, 98);
}
#[test]
fn test_mel_compute_center_padded_frames() {
let config = MelConfig::whisper(); let mel = MelFilterbank::new(&config);
let audio = vec![0.0; 16000];
let result = mel.compute(&audio).expect("compute should succeed");
let n_frames = result.len() / 80;
assert_eq!(n_frames, 100);
}
#[test]
fn test_mel_compute_sine_wave() {
let config = MelConfig::whisper();
let mel = MelFilterbank::new(&config);
let sample_rate = 16000.0;
let freq = 440.0;
let audio: Vec<f32> = (0..16000)
.map(|i| (2.0 * PI * freq * i as f32 / sample_rate).sin())
.collect();
let result = mel.compute(&audio).expect("compute should succeed");
let max_val = result.iter().cloned().fold(f32::NEG_INFINITY, f32::max);
let min_val = result.iter().cloned().fold(f32::INFINITY, f32::min);
assert!(max_val.is_finite(), "Max should be finite");
assert!(min_val.is_finite(), "Min should be finite");
assert!(max_val > min_val, "Should have variation in output");
}
#[test]
fn test_num_frames() {
let config = MelConfig { center_pad: false, ..MelConfig::whisper() };
let mel = MelFilterbank::new(&config);
assert_eq!(mel.num_frames(0), 0);
assert_eq!(mel.num_frames(100), 0);
assert_eq!(mel.num_frames(400), 1);
assert_eq!(mel.num_frames(560), 2);
assert_eq!(mel.num_frames(16000), 98);
}
#[test]
fn test_num_frames_center_padded() {
let config = MelConfig::whisper(); let mel = MelFilterbank::new(&config);
assert_eq!(mel.num_frames(0), 0);
assert_eq!(mel.num_frames(100), 0);
assert_eq!(mel.num_frames(160), 1);
assert_eq!(mel.num_frames(400), 2);
assert_eq!(mel.num_frames(16000), 100);
}
#[test]
fn test_normalize_global_empty() {
let config = MelConfig::whisper();
let mel = MelFilterbank::new(&config);
let mut data: Vec<f32> = vec![];
mel.normalize_global(&mut data);
assert!(data.is_empty());
}
#[test]
fn test_normalize_global_mean_zero() {
let config = MelConfig::whisper();
let mel = MelFilterbank::new(&config);
let mut data = vec![1.0, 2.0, 3.0, 4.0, 5.0];
mel.normalize_global(&mut data);
let mean: f32 = data.iter().sum::<f32>() / data.len() as f32;
assert!(mean.abs() < 1e-5, "Mean after normalization should be ~0");
}
#[test]
fn test_normalize_global_std_one() {
let config = MelConfig::whisper();
let mel = MelFilterbank::new(&config);
let mut data = vec![1.0, 2.0, 3.0, 4.0, 5.0];
mel.normalize_global(&mut data);
let variance: f32 = data.iter().map(|&x| x.powi(2)).sum::<f32>() / data.len() as f32;
let std = variance.sqrt();
assert!(
(std - 1.0).abs() < 1e-5,
"Std after normalization should be ~1, got {std}"
);
}
#[test]
fn test_apply_filterbank_shape() {
let config = MelConfig::whisper();
let mel = MelFilterbank::new(&config);
let power_spec = vec![1.0; mel.n_freqs()];
let result = mel.apply_filterbank(&power_spec);
assert_eq!(result.len(), 80);
}
#[test]
fn test_apply_filterbank_zeros() {
let config = MelConfig::whisper();
let mel = MelFilterbank::new(&config);
let power_spec = vec![0.0; mel.n_freqs()];
let result = mel.apply_filterbank(&power_spec);
for &val in &result {
assert!(
(val - 0.0).abs() < 1e-10,
"Zero input should give zero output"
);
}
}
#[test]
fn test_detect_clipping_no_clipping() {
let samples = vec![0.0, 0.5, -0.5, 0.99, -0.99];
let report = detect_clipping(&samples);
assert!(!report.has_clipping);
assert_eq!(report.positive_clipped, 0);
assert_eq!(report.negative_clipped, 0);
assert!((report.max_value - 0.99).abs() < 1e-6);
assert!((report.min_value - (-0.99)).abs() < 1e-6);
assert_eq!(report.total_samples, 5);
}
#[test]
fn test_detect_clipping_positive() {
let samples = vec![0.5, 1.5, 0.8, 2.0, 0.9];
let report = detect_clipping(&samples);
assert!(report.has_clipping);
assert_eq!(report.positive_clipped, 2);
assert_eq!(report.negative_clipped, 0);
assert!((report.max_value - 2.0).abs() < 1e-6);
}
#[test]
fn test_detect_clipping_negative() {
let samples = vec![-0.5, -1.5, -0.8, -2.0, -0.9];
let report = detect_clipping(&samples);
assert!(report.has_clipping);
assert_eq!(report.positive_clipped, 0);
assert_eq!(report.negative_clipped, 2);
assert!((report.min_value - (-2.0)).abs() < 1e-6);
}
#[test]
fn test_detect_clipping_both() {
let samples = vec![1.5, -1.5, 0.5, 2.0, -2.0];
let report = detect_clipping(&samples);
assert!(report.has_clipping);
assert_eq!(report.positive_clipped, 2);
assert_eq!(report.negative_clipped, 2);
}
#[test]
fn test_detect_clipping_empty() {
let samples: Vec<f32> = vec![];
let report = detect_clipping(&samples);
assert!(!report.has_clipping);
assert_eq!(report.total_samples, 0);
assert!((report.clipping_percentage() - 0.0).abs() < 1e-6);
}
#[test]
fn test_detect_clipping_exactly_one() {
let samples = vec![1.0, -1.0, 0.5];
let report = detect_clipping(&samples);
assert!(!report.has_clipping);
assert_eq!(report.positive_clipped, 0);
assert_eq!(report.negative_clipped, 0);
}
#[test]
fn test_clipping_percentage() {
let samples = vec![1.5, -1.5, 0.5, 0.3, 0.2];
let report = detect_clipping(&samples);
assert!((report.clipping_percentage() - 40.0).abs() < 1e-6);
}
#[test]
fn test_has_nan_false() {
let samples = vec![0.0, 0.5, -0.5, 1.0, -1.0];
assert!(!has_nan(&samples));
}
#[test]
fn test_has_nan_true() {
let samples = vec![0.0, 0.5, f32::NAN, 1.0];
assert!(has_nan(&samples));
}
#[test]
fn test_has_nan_empty() {
let samples: Vec<f32> = vec![];
assert!(!has_nan(&samples));
}
#[test]
fn test_validate_audio_valid() {
let samples = vec![0.0, 0.5, -0.5, 0.99, -0.99];
assert!(validate_audio(&samples).is_ok());
}
#[test]
fn test_validate_audio_empty() {
let samples: Vec<f32> = vec![];
let result = validate_audio(&samples);
assert!(result.is_err());
let msg = result.err().map(|e| e.to_string()).unwrap_or_default();
assert!(msg.contains("empty"), "Error should mention empty: {}", msg);
}
#[test]
fn test_validate_audio_nan() {
let samples = vec![0.0, f32::NAN, 0.5];
let result = validate_audio(&samples);
assert!(result.is_err());
let msg = result.err().map(|e| e.to_string()).unwrap_or_default();
assert!(msg.contains("NaN"), "Error should mention NaN: {}", msg);
}
#[test]
fn test_validate_audio_clipping() {
let samples = vec![0.0, 1.5, -0.5];
let result = validate_audio(&samples);
assert!(result.is_err());
let msg = result.err().map(|e| e.to_string()).unwrap_or_default();
assert!(
msg.contains("clipping") || msg.contains("Clipping"),
"Error should mention clipping: {}",
msg
);
}
#[test]
fn test_has_inf_false() {
let samples = vec![0.0, 0.5, -0.5, 1.0, -1.0, f32::MAX, f32::MIN];
assert!(!has_inf(&samples));
}
#[test]
fn test_has_inf_positive() {
let samples = vec![0.0, f32::INFINITY, 0.5];
assert!(has_inf(&samples));
}
#[path = "tests_validation.rs"]
mod tests_validation;