use rust_physics_engine::dsp::fir::{fir_apply, fir_lowpass};
use rust_physics_engine::dsp::windows::WindowKind;
use rust_physics_engine::monte_carlo::Rng;
use rust_physics_engine::transforms::fft::rfft;
use rust_physics_engine::transforms::spectral::welch;
fn main() {
let fs = 8_000.0; let n = 4_096;
let mut rng = Rng::new(0x5EED_1234);
let signal: Vec<f64> = (0..n)
.map(|i| {
let t = i as f64 / fs;
let wanted = (2.0 * std::f64::consts::PI * 440.0 * t).sin();
let interference = 0.8 * (2.0 * std::f64::consts::PI * 2_600.0 * t).sin();
let noise = 1.5 * (rng.next_f64() - 0.5);
wanted + interference + noise
})
.collect();
let spectrum = rfft(&signal);
let peak = |from: f64, to: f64| -> (f64, f64) {
let lo = (from * n as f64 / fs) as usize;
let hi = (to * n as f64 / fs) as usize;
let mut best = (0.0, 0.0);
for (k, c) in spectrum.iter().enumerate().take(hi + 1).skip(lo) {
let mag = (c.re * c.re + c.im * c.im).sqrt();
if mag > best.1 {
best = (k as f64 * fs / n as f64, mag);
}
}
best
};
let (f_wanted, m_wanted) = peak(300.0, 600.0);
let (f_interf, m_interf) = peak(2_400.0, 2_800.0);
println!("before filtering");
println!(" tone found at {f_wanted:.0} Hz (magnitude {m_wanted:.0})");
println!(" interference at {f_interf:.0} Hz (magnitude {m_interf:.0})");
assert!((f_wanted - 440.0).abs() < 5.0);
assert!((f_interf - 2_600.0).abs() < 5.0);
let taps = fir_lowpass(101, 1_000.0 / fs, WindowKind::Hamming);
let filtered = fir_apply(&taps, &signal);
let spectrum = rfft(&filtered[..n]);
let peak2 = |from: f64, to: f64| -> f64 {
let lo = (from * n as f64 / fs) as usize;
let hi = (to * n as f64 / fs) as usize;
spectrum[lo..=hi]
.iter()
.map(|c| (c.re * c.re + c.im * c.im).sqrt())
.fold(0.0, f64::max)
};
let after_wanted = peak2(300.0, 600.0);
let after_interf = peak2(2_400.0, 2_800.0);
println!();
println!("after a 1 kHz low-pass");
println!(" 440 Hz kept magnitude {after_wanted:.0}");
println!(" 2.6 kHz rejected magnitude {after_interf:.0}");
println!(
" rejection {:.0} dB",
20.0 * (m_interf / after_interf.max(1e-12)).log10()
);
assert!(after_wanted > 0.5 * m_wanted, "the passband tone was attenuated");
assert!(after_interf < 0.05 * m_interf, "the stopband tone survived");
let (freqs, psd) = welch(&signal, fs, 512, 256, WindowKind::Hann);
let loudest = psd
.iter()
.enumerate()
.max_by(|a, b| a.1.partial_cmp(b.1).unwrap())
.map(|(i, _)| freqs[i])
.unwrap();
println!();
println!("Welch PSD over {} segments peaks at {loudest:.0} Hz", n / 256 - 1);
assert!((loudest - 440.0).abs() < 20.0);
}