use crate::engine::{RAYLEIGH_LOG_BIAS_DB, RAYLEIGH_STD_DB};
use crate::profile::NoiseProfile;
use crate::stft::{fft_size_for, hop_for, Fft};
use ferraus_core::AudioBuffer;
use realfft::num_complex::Complex32;
pub const BIRDIE_THRESHOLD: f64 = 0.0003;
const BIRDIE_JUMP_DB: f32 = 10.0;
pub fn birdie_score(x: &[f32], sample_rate: u32) -> f64 {
let n = (fft_size_for(sample_rate) / 2).max(256);
let hop = n / 4;
if x.len() < n + hop {
return 0.0;
}
let mut fft = Fft::new(n);
let bins = fft.bins();
let bin_hz = sample_rate as f64 / n as f64;
let lo = ((100.0 / bin_hz).ceil() as usize).max(1);
let hi = ((16_000.0f64.min(sample_rate as f64 / 2.0) / bin_hz) as usize).min(bins - 1);
let mut spec = vec![Complex32::new(0.0, 0.0); bins];
let mut levels: Vec<Vec<f32>> = Vec::new();
let mut s = 0;
while s + n <= x.len() {
fft.forward(&x[s..s + n], &mut spec);
levels.push(spec[lo..=hi].iter().map(|z| 10.0 * z.norm_sqr().max(1e-24).log10()).collect());
s += hop;
}
let width = hi - lo + 1;
let mut medians = vec![0f32; width];
let mut col = Vec::with_capacity(levels.len());
for (k, m) in medians.iter_mut().enumerate() {
col.clear();
col.extend(levels.iter().map(|f| f[k]));
col.sort_unstable_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
*m = col[col.len() / 2];
}
let mut count = 0usize;
for t in 1..levels.len() {
for k in 0..width {
let l = levels[t][k];
if l - levels[t - 1][k] > BIRDIE_JUMP_DB && l - medians[k] > BIRDIE_JUMP_DB {
count += 1;
}
}
}
count as f64 / ((levels.len() - 1) * width) as f64
}
pub fn hard_gate(buf: &AudioBuffer, profile: &NoiseProfile, reduction_db: f32, sensitivity: f32) -> AudioBuffer {
let n = fft_size_for(buf.sample_rate);
let hop = hop_for(n);
let mut fft = Fft::new(n);
let bins = fft.bins();
let gmin = 10f32.powf(-reduction_db.abs() / 20.0);
let (mean, std) = (&profile.linked.mean_db, &profile.linked.std_db);
let thr: Vec<f32> = (0..bins.min(mean.len()))
.map(|k| mean[k] + RAYLEIGH_LOG_BIAS_DB + sensitivity * std[k] / RAYLEIGH_STD_DB)
.collect();
let scale2 = fft.mag_scale * fft.mag_scale;
let total = buf.frames();
let mut spec = vec![Complex32::new(0.0, 0.0); bins];
let data = buf
.data
.iter()
.map(|ch| {
let pad = n - hop;
let mut x = vec![0f32; pad];
x.extend_from_slice(ch);
x.resize(pad + total + n, 0.0);
let mut y = vec![0f32; x.len() + n];
let mut s = 0;
while s + n <= x.len() {
fft.forward(&x[s..s + n], &mut spec);
for (k, z) in spec.iter_mut().enumerate() {
let p = z.norm_sqr() * scale2;
let db = 10.0 * p.max(1e-16).log10();
if k >= thr.len() || db <= thr[k] {
*z *= gmin;
}
}
let t = fft.inverse_windowed(&mut spec);
for (o, v) in y[s..s + n].iter_mut().zip(t) {
*o += v;
}
s += hop;
}
y[pad..pad + total].to_vec()
})
.collect();
AudioBuffer::from_planar(buf.sample_rate, data)
}