use super::{norm_sqr, Cf64, Stage};
use crate::portable_math::PortableFloat;
use crate::spectrum::fft_in_place;
use std::collections::VecDeque;
#[derive(Clone, Debug)]
pub struct FreqExcision {
n: usize,
window: Vec<f64>,
threshold: f64,
hist: VecDeque<Cf64>,
since: usize,
ola: Vec<Cf64>,
out: VecDeque<Cf64>,
buf: Vec<Cf64>,
mags: Vec<f64>,
frames: u64,
excised: u64,
}
impl FreqExcision {
pub fn new(n: usize, pfa: f64) -> Self {
assert!(
n >= 4 && n.is_power_of_two(),
"frame length must be a power of two ≥ 4"
);
assert!(0.0 < pfa && pfa < 1.0, "pfa must be in (0, 1)");
let window = (0..n)
.map(|k| (std::f64::consts::PI * k as f64 / n as f64).psin())
.collect();
let h = n / 2;
Self {
n,
window,
threshold: -pfa.pln(),
hist: std::iter::repeat_n(Cf64::default(), n).collect(),
since: 0,
ola: vec![Cf64::default(); n],
out: std::iter::repeat_n(Cf64::default(), h - 1).collect(),
buf: vec![Cf64::default(); n],
mags: vec![0.0; n],
frames: 0,
excised: 0,
}
}
pub fn delay(&self) -> usize {
self.n - 1
}
pub fn threshold_factor(&self) -> f64 {
self.threshold
}
pub fn frames(&self) -> u64 {
self.frames
}
pub fn bins_excised(&self) -> u64 {
self.excised
}
fn run_frame(&mut self) {
let n = self.n;
for (k, (b, &x)) in self.buf.iter_mut().zip(self.hist.iter()).enumerate() {
*b = x * self.window[k];
}
fft_in_place(&mut self.buf, false);
for (m, b) in self.mags.iter_mut().zip(&self.buf) {
*m = norm_sqr(*b);
}
let mut sorted = self.mags.clone();
sorted.sort_by(f64::total_cmp);
let median = 0.5 * (sorted[n / 2 - 1] + sorted[n / 2]);
let limit = self.threshold * median / std::f64::consts::LN_2;
for (b, &m) in self.buf.iter_mut().zip(&self.mags) {
if m > limit {
*b = Cf64::default();
self.excised += 1;
}
}
fft_in_place(&mut self.buf, true);
let scale = 1.0 / n as f64;
for (k, o) in self.ola.iter_mut().enumerate() {
*o = *o + self.buf[k] * (self.window[k] * scale);
}
let h = n / 2;
if self.frames == 0 {
self.ola.drain(..h);
self.out.extend(std::iter::repeat_n(Cf64::default(), h));
} else {
self.out.extend(self.ola.drain(..h));
}
self.ola.resize(n, Cf64::default());
self.frames += 1;
}
}
impl Stage for FreqExcision {
fn process(&mut self, block: &mut [Cf64]) {
let h = self.n / 2;
for x in block.iter_mut() {
self.hist.pop_front();
self.hist.push_back(*x);
self.since += 1;
if self.since == h {
self.since = 0;
self.run_frame();
}
*x = self.out.pop_front().unwrap_or_default();
}
}
}