use super::*;
#[test]
fn test_esr_identical_is_zero() {
let sig: Vec<f32> = (0..1024).map(|i| (i as f32 * 0.001).sin()).collect();
let esr = compute_esr(&sig, &sig);
assert!(
esr < 1e-15,
"ESR of identical signals should be near zero, got {esr}"
);
}
#[test]
fn test_esr_all_zero_test() {
let sig: Vec<f32> = (0..1024).map(|i| (i as f32 * 0.001).sin()).collect();
let zeros = vec![0.0f32; sig.len()];
let esr = compute_esr(&sig, &zeros);
assert!(
(esr - 1.0).abs() < 1e-10,
"ESR of signal vs zeros should be ~1.0, got {esr}"
);
}
#[test]
fn test_compute_esr_blockwise() {
let sig: Vec<f32> = (0..100).map(|i| i as f32).collect();
let mut perturbed = sig.clone();
perturbed[5] += 1.0; perturbed[55] += 2.0;
let block_size = 50;
let esr_blocks = compute_esr_blockwise(&sig, &perturbed, block_size);
assert_eq!(esr_blocks.len(), 2);
let expected_esr_0 = compute_esr(&sig[0..50], &perturbed[0..50]);
assert!((esr_blocks[0] - expected_esr_0).abs() < 1e-15);
let expected_esr_1 = compute_esr(&sig[50..100], &perturbed[50..100]);
assert!((esr_blocks[1] - expected_esr_1).abs() < 1e-15);
}
#[test]
fn test_compute_lufs_sine() {
let sr = 48000;
let n = sr as usize;
let amplitude = 10.0f32.powf(-20.0 / 20.0);
let sig: Vec<f32> = (0..n)
.map(|i| amplitude * (2.0 * std::f32::consts::PI * 1000.0 * i as f32 / sr as f32).sin())
.collect();
let lufs = compute_lufs(&sig, sr);
assert!(
(lufs - (-23.0)).abs() < 0.5,
"Expected ~-23.0 LUFS for 1kHz sine at -20 dBFS, got {lufs}"
);
}
#[test]
fn test_compute_lufs_empty() {
assert!(compute_lufs(&[], 48000).is_infinite());
}
#[test]
fn test_compute_lufs_silence() {
let sig = vec![0.0f32; 48000];
assert!(compute_integrated_lufs(&sig, 48000).is_infinite());
}
#[test]
fn test_integrated_lufs_full_scale_sine() {
let sr = 48000;
let n = sr as usize; let sig: Vec<f32> = (0..n)
.map(|i| (2.0 * std::f32::consts::PI * 1000.0 * i as f32 / sr as f32).sin())
.collect();
let lufs = compute_integrated_lufs(&sig, sr);
assert!(
(lufs - (-3.0)).abs() < 0.2,
"Expected ~-3.0 LUFS for full-scale 1 kHz sine, got {lufs}"
);
}
#[test]
fn test_integrated_lufs_empty() {
assert!(compute_integrated_lufs(&[], 48000).is_infinite());
}
#[test]
fn test_integrated_lufs_too_short() {
let sr = 48000;
let n = (sr as f64 * 0.3) as usize; let sig: Vec<f32> = vec![0.5f32; n];
assert!(compute_integrated_lufs(&sig, sr).is_infinite());
}
#[test]
fn test_lufs_2pass_absolute_gate() {
let sr = 48000;
let block_samples = (sr as usize * 400) / 1000; let _hop = block_samples / 4;
let total_samples = block_samples + _hop * 4; let mut sig = vec![0.0f32; total_samples];
let ampl_loud = 10.0f32.powf(-20.0 / 20.0);
for (i, s) in sig.iter_mut().enumerate().take(block_samples) {
*s = ampl_loud * (2.0 * std::f32::consts::PI * 1000.0 * i as f32 / sr as f32).sin();
}
let lufs = compute_integrated_lufs(&sig, sr);
assert!(
lufs > -30.0 && lufs < -20.0,
"Gated LUFS should discard silence, got {lufs}"
);
}
#[test]
fn test_lufs_2pass_relative_gate() {
let sr = 48000;
let loud_len = sr as usize * 2; let quiet_len = sr as usize * 3; let total_samples = loud_len + quiet_len;
let mut sig = vec![0.0f32; total_samples];
let ampl_loud = 10.0f32.powf(-20.0 / 20.0);
let ampl_quiet = 10.0f32.powf(-46.0 / 20.0);
for (i, s) in sig.iter_mut().enumerate().take(loud_len) {
*s = ampl_loud * (2.0 * std::f32::consts::PI * 1000.0 * i as f32 / sr as f32).sin();
}
for (i, s) in sig.iter_mut().enumerate().skip(loud_len) {
*s = ampl_quiet * (2.0 * std::f32::consts::PI * 1000.0 * i as f32 / sr as f32).sin();
}
let lufs = compute_integrated_lufs(&sig, sr);
let ungated = {
let kw = apply_k_weighting(&sig);
let mean_sq: f64 = kw.iter().map(|&x| (x as f64).powi(2)).sum::<f64>() / kw.len() as f64;
-0.691 + 10.0 * mean_sq.log10()
};
assert!(
lufs > ungated,
"Gated LUFS ({lufs}) should be higher than ungated ({ungated}) — quiet blocks were removed"
);
assert!(lufs < -22.0, "Gated LUFS should not exceed -22, got {lufs}");
}
#[test]
fn test_lufs_2pass_steady_signal_consistent() {
let sr = 48000;
let n = sr as usize * 2; let ampl = 10.0f32.powf(-12.0 / 20.0);
let sig: Vec<f32> = (0..n)
.map(|i| ampl * (2.0 * std::f32::consts::PI * 1000.0 * i as f32 / sr as f32).sin())
.collect();
let lufs = compute_integrated_lufs(&sig, sr);
assert!(
(lufs - (-15.0)).abs() < 0.3,
"Steady sine at -12 dBFS should be ~ -15.0 LUFS, got {lufs}"
);
}
#[test]
fn test_k_weighting_preserves_1khz() {
let sr = 48000;
let n = 48000;
let sig: Vec<f32> = (0..n)
.map(|i| (2.0 * std::f32::consts::PI * 1000.0 * i as f32 / sr as f32).sin())
.collect();
let kw = apply_k_weighting(&sig);
let rms_in: f64 = (sig.iter().map(|&x| (x as f64).powi(2)).sum::<f64>() / n as f64).sqrt();
let rms_out: f64 = (kw.iter().map(|&x| (x as f64).powi(2)).sum::<f64>() / n as f64).sqrt();
let gain_db = 20.0 * (rms_out / rms_in).log10();
assert!(
gain_db.abs() < 1.5,
"K-weighting at 1 kHz should have near-unity gain, got {gain_db:.2} dB"
);
}
#[test]
fn test_k_weighting_boosts_high_frequencies() {
let sr = 48000;
let n = 48000;
let sig: Vec<f32> = (0..n)
.map(|i| (2.0 * std::f32::consts::PI * 10000.0 * i as f32 / sr as f32).sin())
.collect();
let kw = apply_k_weighting(&sig);
let rms_in: f64 = (sig.iter().map(|&x| (x as f64).powi(2)).sum::<f64>() / n as f64).sqrt();
let rms_out: f64 = (kw.iter().map(|&x| (x as f64).powi(2)).sum::<f64>() / n as f64).sqrt();
let gain_db = 20.0 * (rms_out / rms_in).log10();
assert!(
gain_db > 2.5 && gain_db < 5.5,
"K-weighting at 10 kHz should have ~+4 dB gain, got {gain_db:.2} dB"
);
}
#[test]
fn test_lra_empty() {
assert_eq!(compute_lra(&[], 48000), 0.0);
}
#[test]
fn test_lra_too_short() {
let sr = 48000;
let n = sr as usize * 2; let sig = vec![0.1f32; n];
assert_eq!(compute_lra(&sig, sr), 0.0);
}
#[test]
fn test_lra_steady_sine_zero() {
let sr = 48000;
let n = sr as usize * 6; let ampl = 10.0f32.powf(-12.0 / 20.0);
let sig: Vec<f32> = (0..n)
.map(|i| ampl * (2.0 * std::f32::consts::PI * 1000.0 * i as f32 / sr as f32).sin())
.collect();
let lra = compute_lra(&sig, sr);
assert!(lra < 0.5, "Steady sine LRA should be ~0, got {lra}");
}
#[test]
fn test_lra_dynamic_signal() {
let sr = 48000;
let block_len = sr as usize * 3; let n = block_len * 2; let mut sig = vec![0.0f32; n];
let ampl_loud = 10.0f32.powf(-12.0 / 20.0);
let ampl_quiet = 10.0f32.powf(-30.0 / 20.0);
for (i, s) in sig.iter_mut().enumerate().take(block_len) {
*s = ampl_loud * (2.0 * std::f32::consts::PI * 1000.0 * i as f32 / sr as f32).sin();
}
for (i, s) in sig.iter_mut().enumerate().skip(block_len) {
*s = ampl_quiet * (2.0 * std::f32::consts::PI * 1000.0 * i as f32 / sr as f32).sin();
}
let lra = compute_lra(&sig, sr);
assert!(
lra > 12.0 && lra < 18.0,
"Dynamic signal LRA should be ~15.3 LU, got {lra}"
);
}
#[test]
fn test_lra_absolute_gate_removes_silence() {
let sr = 48000;
let block_len = sr as usize * 3; let n = block_len * 2; let mut sig = vec![0.0f32; n];
let ampl = 10.0f32.powf(-12.0 / 20.0);
for (i, s) in sig.iter_mut().enumerate().take(block_len) {
*s = ampl * (2.0 * std::f32::consts::PI * 1000.0 * i as f32 / sr as f32).sin();
}
let lra = compute_lra(&sig, sr);
assert!(
lra < 0.5,
"Silence block should be gated, LRA should be ~0, got {lra}"
);
}
#[test]
fn test_lra_with_one_block() {
let sr = 48000;
let n = sr as usize * 3;
let ampl = 10.0f32.powf(-12.0 / 20.0);
let sig: Vec<f32> = (0..n)
.map(|i| ampl * (2.0 * std::f32::consts::PI * 1000.0 * i as f32 / sr as f32).sin())
.collect();
assert_eq!(compute_lra(&sig, sr), 0.0);
}
#[test]
fn test_short_term_loudness_returns_values() {
let sr = 48000;
let n = sr as usize * 6; let ampl = 10.0f32.powf(-12.0 / 20.0);
let sig: Vec<f32> = (0..n)
.map(|i| ampl * (2.0 * std::f32::consts::PI * 1000.0 * i as f32 / sr as f32).sin())
.collect();
let st = short_term_loudness(&sig, sr);
assert_eq!(
st.len(),
2,
"6 s should produce 2 short-term blocks, got {}",
st.len()
);
assert!(
(st[0] - st[1]).abs() < 0.1,
"Steady sine short-term blocks should match, got {:?}",
st
);
}
#[test]
fn test_short_term_loudness_empty() {
assert!(short_term_loudness(&[], 48000).is_empty());
}
#[test]
fn test_short_term_loudness_too_short() {
let sig = vec![0.1f32; 48000]; assert!(short_term_loudness(&sig, 48000).is_empty());
}
#[test]
fn test_measure_loudness_combined() {
let sr = 48000;
let n = sr as usize * 6; let ampl = 10.0f32.powf(-20.0 / 20.0);
let sig: Vec<f32> = (0..n)
.map(|i| ampl * (2.0 * std::f32::consts::PI * 1000.0 * i as f32 / sr as f32).sin())
.collect();
let result = measure_loudness(&sig, sr);
assert!(
(result.integrated_lufs - (-23.0)).abs() < 0.5,
"Combined integrated LUFS mismatch: {}",
result.integrated_lufs
);
assert!(
result.lra < 0.5,
"Combined LRA should be ~0: {}",
result.lra
);
assert!(
(result.true_peak_db - (-20.0)).abs() < 1.0,
"Combined dBTP mismatch: {}",
result.true_peak_db
);
assert_eq!(result.short_term.len(), 2);
}
#[test]
fn test_measure_loudness_empty() {
let result = measure_loudness(&[], 48000);
assert!(result.integrated_lufs.is_infinite());
assert_eq!(result.lra, 0.0);
assert!(result.true_peak_db.is_infinite());
assert!(result.short_term.is_empty());
}
#[test]
fn test_measure_loudness_dynamic() {
let sr = 48000;
let block_len = sr as usize * 3;
let n = block_len * 2;
let mut sig = vec![0.0f32; n];
let ampl_loud = 10.0f32.powf(-12.0 / 20.0);
let ampl_quiet = 10.0f32.powf(-30.0 / 20.0);
for (i, s) in sig.iter_mut().enumerate().take(block_len) {
*s = ampl_loud * (2.0 * std::f32::consts::PI * 1000.0 * i as f32 / sr as f32).sin();
}
for (i, s) in sig.iter_mut().enumerate().skip(block_len) {
*s = ampl_quiet * (2.0 * std::f32::consts::PI * 1000.0 * i as f32 / sr as f32).sin();
}
let result = measure_loudness(&sig, sr);
assert!(
result.lra > 5.0,
"Dynamic signal should have LRA > 5 LU, got {}",
result.lra
);
}
#[test]
fn test_mr_stft_identical_is_zero() {
let sig: Vec<f32> = (0..4096)
.map(|i| (2.0 * std::f32::consts::PI * 440.0 * i as f32 / 48000.0).sin())
.collect();
let loss = compute_mr_stft(&sig, &sig);
assert!(
loss < 1e-12,
"MR-STFT of identical signals should be near zero, got {loss}"
);
}
#[test]
fn test_mr_stft_empty() {
assert_eq!(compute_mr_stft(&[], &[]), 0.0);
}
#[test]
fn test_mr_stft_different_signals() {
let sig_a: Vec<f32> = (0..4096)
.map(|i| (2.0 * std::f32::consts::PI * 440.0 * i as f32 / 48000.0).sin())
.collect();
let sig_b: Vec<f32> = (0..4096)
.map(|i| (2.0 * std::f32::consts::PI * 880.0 * i as f32 / 48000.0).sin())
.collect();
let loss = compute_mr_stft(&sig_a, &sig_b);
assert!(
loss > 0.0,
"MR-STFT of different signals should be positive, got {loss}"
);
}
#[test]
fn test_esr_invariant_to_sample_rate() {
let sig: Vec<f32> = (0..2048).map(|i| (i as f32 * 0.001).sin()).collect();
let mut perturbed = sig.clone();
perturbed[100] += 1e-5;
let esr1 = compute_esr(&sig, &perturbed);
let esr2 = compute_esr(&sig, &perturbed);
assert!((esr1 - esr2).abs() < 1e-15, "ESR should be deterministic");
}
#[test]
fn test_true_peak_empty() {
assert!(compute_true_peak_db(&[]).is_infinite());
}
#[test]
fn test_true_peak_silence() {
let sig = vec![0.0f32; 1024];
assert!(compute_true_peak_db(&sig).is_infinite());
}
#[test]
fn test_true_peak_sine_minus_6_db() {
let sr = 48000.0f64;
let n = 2048;
let ampl = 0.5f32;
let sig: Vec<f32> = (0..n)
.map(|i| ampl * (2.0 * std::f64::consts::PI * 1000.0 * i as f64 / sr).sin() as f32)
.collect();
let tp = compute_true_peak_db(&sig);
assert!(
(tp - (-6.02)).abs() < 0.3,
"1kHz -6 dBFS sine should be ~-6 dBTP, got {tp}"
);
}
#[test]
fn test_true_peak_full_scale_sine() {
let sr = 48000.0f64;
let n = 2048;
let sig: Vec<f32> = (0..n)
.map(|i| (2.0 * std::f64::consts::PI * 1000.0 * i as f64 / sr).sin() as f32)
.collect();
let tp = compute_true_peak_db(&sig);
assert!(
tp.abs() < 0.15,
"1kHz 0 dBFS sine should be ~0 dBTP, got {tp}"
);
}
#[test]
fn test_oversample_4x_length() {
let sig = vec![0.5f32; 128];
let up = oversample_4x(&sig);
assert_eq!(up.len(), 128 * 4, "4× oversampled length must be 4× input");
}
#[test]
fn test_bs1770_fir_dc_gain() {
for (p, phase) in BS1770_PHASES.iter().enumerate() {
let sum: f64 = phase.iter().sum();
assert!(
(sum - 1.0).abs() < 3e-2,
"Phase {p} DC gain should be ~1.0, got {sum}"
);
}
}
#[test]
fn test_true_peak_dc_minus_6_db() {
let sig = vec![0.5f32; 1024];
let tp = compute_true_peak_db(&sig);
assert!(
tp > -6.5 && tp < -4.5,
"DC 0.5 dBTP={tp} should be between -6.5 and -4.5 (transient overshoot)"
);
let up = oversample_4x(&sig);
let steady_peak = up[48..].iter().fold(0.0f64, |m, &x| m.max(x.abs()));
let steady_tp = 20.0 * steady_peak.log10();
assert!(
(steady_tp - (-6.02)).abs() < 0.1,
"Steady-state DC 0.5 should be ~-6 dBTP, got {steady_tp}"
);
}
#[test]
fn test_true_peak_detects_gibbs_overshoot() {
let n = 512;
let mut sig = vec![0.0f32; n];
let step_at = n / 2;
for (i, s) in sig.iter_mut().enumerate() {
*s = if i < step_at { 0.99f32 } else { -0.99f32 };
}
let sample_peak = sig.iter().fold(0.0f32, |m, &x| m.max(x.abs()));
assert!(
sample_peak < 1.0,
"Signal samples must all be < 1.0; sample_peak={sample_peak}"
);
let overs = find_true_peak_overs(&sig);
assert!(
!overs.is_empty(),
"Step discontinuity must produce inter-sample overs"
);
let pos = overs[0].position;
let near_step = pos >= step_at.saturating_sub(24) && pos <= step_at + 24;
assert!(
pos < 24 || near_step,
"Over position {pos} must be near step at {step_at} or at initial transient (< 24)"
);
assert!(
overs[0].dbtp > 0.0,
"Inter-sample over must have dBTP > 0, got {}",
overs[0].dbtp
);
}
#[test]
fn test_bs1770_fir_symmetry() {
for k in 0..12 {
assert!(
(BS1770_PHASES[3][k] - BS1770_PHASES[0][11 - k]).abs() < 1e-12,
"Phase symmetry p3[{k}] != p0[{}]: {} != {}",
11 - k,
BS1770_PHASES[3][k],
BS1770_PHASES[0][11 - k]
);
assert!(
(BS1770_PHASES[2][k] - BS1770_PHASES[1][11 - k]).abs() < 1e-12,
"Phase symmetry p2[{k}] != p1[{}]: {} != {}",
11 - k,
BS1770_PHASES[2][k],
BS1770_PHASES[1][11 - k]
);
}
}
#[test]
fn test_true_peak_no_overs_quiet_signal() {
let sig = vec![0.3f32; 1024];
let overs = find_true_peak_overs(&sig);
assert!(overs.is_empty(), "Quiet signal should have no overs");
}
#[test]
fn test_find_true_peak_overs_empty() {
let overs = find_true_peak_overs(&[]);
assert!(overs.is_empty());
}
#[test]
fn test_true_peak_detects_hf_sine_overs() {
let sr = 48000.0f64;
let ampl = 0.999f64;
let freq = 21000.0;
let n = 4800; let sig: Vec<f32> = (0..n)
.map(|i| (ampl * (2.0 * std::f64::consts::PI * freq * i as f64 / sr).sin()) as f32)
.collect();
let sample_peak = sig.iter().fold(0.0f32, |m, &x| m.max(x.abs()));
assert!(
sample_peak < 1.0,
"Sample peak {sample_peak} must be < 1.0 for sample-peak not to fire"
);
let overs = find_true_peak_overs(&sig);
for o in &overs {
assert!(o.dbtp > 0.0, "Detected over must have dBTP > 0");
}
}
#[test]
fn test_true_peak_detects_near_full_scale_overs() {
let sr = 48000.0f64;
let ampl = 0.999999f64;
let freq = 1000.0;
let n = 4800;
let sig: Vec<f32> = (0..n)
.map(|i| (ampl * (2.0 * std::f64::consts::PI * freq * i as f64 / sr).sin()) as f32)
.collect();
let tp = compute_true_peak_db(&sig);
assert!(
tp.abs() < 2.0,
"Near-full-scale 1kHz sine dBTP={tp} should be close to 0"
);
}
#[test]
fn test_oversample_4x_deterministic() {
let sig: Vec<f32> = vec![1.0, -0.5, 0.25, 0.0, -0.125];
let up1 = oversample_4x(&sig);
let up2 = oversample_4x(&sig);
assert_eq!(up1.len(), up2.len());
for (i, (&a, &b)) in up1.iter().zip(up2.iter()).enumerate() {
assert!(
(a - b).abs() < 1e-15,
"oversample_4x must be deterministic at index {i}"
);
}
}
#[test]
fn test_oversample_4x_first_sample() {
let sig = vec![0.5f32];
let up = oversample_4x(&sig);
for p in 0..4 {
let expected = 0.5f64 * BS1770_PHASES[p][0];
let got = up[p];
assert!(
(got - expected).abs() < 1e-12,
"Phase {p}: expected {expected}, got {got}"
);
}
}