use std::num::NonZeroUsize;
use std::time::Duration;
use audio_samples::{
AudioDynamicRange, AudioIirFiltering, AudioParametricEq, AudioSamples,
operations::types::{CompressorConfig, EqBand, ParametricEq},
sample_rate, sine_wave,
};
fn sine_f32(freq: f64, ms: u64, amp: f64) -> AudioSamples<'static, f32> {
sine_wave::<f32>(freq, Duration::from_millis(ms), sample_rate!(44100), amp)
}
fn rms(audio: &AudioSamples<'_, f32>) -> f64 {
let slice = audio.as_slice().expect("contiguous buffer");
let sum_sq: f64 = slice.iter().map(|&x| f64::from(x) * f64::from(x)).sum();
(sum_sq / slice.len() as f64).sqrt()
}
fn peak_abs(audio: &AudioSamples<'_, f32>) -> f32 {
audio
.as_slice()
.expect("contiguous buffer")
.iter()
.map(|x| x.abs())
.fold(0.0_f32, f32::max)
}
#[test]
fn butterworth_lowpass_passes_below_cutoff() {
let mut audio = sine_f32(440.0, 500, 0.8);
let rms_before = rms(&audio);
audio
.butterworth_lowpass(NonZeroUsize::new(2).unwrap(), 2000.0)
.unwrap();
let rms_after = rms(&audio);
assert!(
rms_after > rms_before * 0.7,
"lowpass at 2 kHz attenuated 440 Hz too much: before={rms_before:.4}, after={rms_after:.4}"
);
}
#[test]
fn butterworth_lowpass_attenuates_above_cutoff() {
let mut audio = sine_f32(8000.0, 500, 0.8);
let rms_before = rms(&audio);
audio
.butterworth_lowpass(NonZeroUsize::new(4).unwrap(), 1000.0)
.unwrap();
let rms_after = rms(&audio);
assert!(
rms_after < rms_before * 0.3,
"lowpass at 1 kHz did not attenuate 8 kHz enough: \
before={rms_before:.4}, after={rms_after:.4} (ratio {:.2})",
rms_after / rms_before
);
}
#[test]
fn butterworth_lowpass_does_not_produce_nan_or_inf() {
let mut audio = sine_f32(1000.0, 200, 0.9);
audio
.butterworth_lowpass(NonZeroUsize::new(4).unwrap(), 500.0)
.unwrap();
let all_finite = audio.as_slice().unwrap().iter().all(|x| x.is_finite());
assert!(all_finite, "butterworth_lowpass produced NaN or Inf");
}
#[test]
fn compressor_modifies_signal() {
let original = sine_f32(440.0, 300, 0.9);
let rms_original = rms(&original);
let mut compressed = sine_f32(440.0, 300, 0.9);
compressed
.apply_compressor(&CompressorConfig::vocal())
.unwrap();
let rms_after = rms(&compressed);
assert!(
(rms_after - rms_original).abs() > 0.01,
"compressor (with make-up gain) did not modify RMS: \
original={rms_original:.4}, after={rms_after:.4}"
);
}
#[test]
fn compressor_output_is_finite() {
let mut audio = sine_f32(440.0, 200, 0.8);
audio.apply_compressor(&CompressorConfig::vocal()).unwrap();
let all_finite = audio.as_slice().unwrap().iter().all(|x| x.is_finite());
assert!(all_finite, "compressor output contains NaN or Inf");
}
#[test]
fn eq_peak_boost_increases_rms() {
let mut audio = sine_f32(1000.0, 300, 0.5);
let rms_before = rms(&audio);
let mut eq = ParametricEq::new();
eq.add_band(EqBand::peak(1000.0, 6.0, 2.0)); audio.apply_parametric_eq(&eq).unwrap();
let rms_after = rms(&audio);
assert!(
rms_after > rms_before,
"EQ +6 dB boost did not increase RMS: before={rms_before:.4}, after={rms_after:.4}"
);
}
#[test]
fn eq_output_is_finite() {
let mut audio = sine_f32(440.0, 200, 0.7);
let mut eq = ParametricEq::new();
eq.add_band(EqBand::peak(1000.0, 3.0, 2.0));
eq.add_band(EqBand::low_shelf(100.0, -2.0, 0.707));
audio.apply_parametric_eq(&eq).unwrap();
let all_finite = audio.as_slice().unwrap().iter().all(|x| x.is_finite());
assert!(all_finite, "parametric EQ output contains NaN or Inf");
}
#[test]
fn full_chain_lowpass_compress_eq_is_finite() {
let mut audio = sine_f32(440.0, 500, 0.8);
audio
.butterworth_lowpass(NonZeroUsize::new(2).unwrap(), 2000.0)
.unwrap();
audio.apply_compressor(&CompressorConfig::vocal()).unwrap();
let mut eq = ParametricEq::new();
eq.add_band(EqBand::peak(800.0, 2.0, 1.5));
audio.apply_parametric_eq(&eq).unwrap();
let all_finite = audio.as_slice().unwrap().iter().all(|x| x.is_finite());
assert!(all_finite, "full effects chain produced NaN or Inf");
let peak = peak_abs(&audio);
assert!(peak > 0.0, "full effects chain produced silence");
}