extern crate alloc;
use alloc::vec;
use alloc::vec::Vec;
#[cfg(not(feature = "std"))]
use num_traits::Float;
const NSEG: usize = 10;
const NPCT: usize = 10; const NTERMS: usize = 5;
const POLY_OFFSET_DB: f32 = 0.65;
pub fn fit_baseline(
avg_spectrum_power: &[f32],
freq_min_bin: usize,
freq_max_bin: usize,
) -> Vec<f32> {
let ia = freq_min_bin.min(avg_spectrum_power.len().saturating_sub(1));
let ib = freq_max_bin.min(avg_spectrum_power.len().saturating_sub(1));
if ib <= ia {
return Vec::new();
}
let n = ib - ia + 1;
let s_db: Vec<f32> = avg_spectrum_power[ia..=ib]
.iter()
.map(|&p| 10.0 * p.max(1e-30).log10())
.collect();
let nlen = n / NSEG;
if nlen == 0 {
return s_db;
}
let i0 = (n / 2) as i32;
let mut xs: Vec<f64> = Vec::with_capacity(n);
let mut ys: Vec<f64> = Vec::with_capacity(n);
for seg in 0..NSEG {
let ja = seg * nlen;
let jb = (ja + nlen).min(n);
if jb <= ja {
continue;
}
let mut sorted: Vec<f32> = s_db[ja..jb].to_vec();
sorted.sort_by(|a, b| a.partial_cmp(b).unwrap());
let pct_idx = (NPCT * sorted.len()) / 100;
let base = sorted[pct_idx.min(sorted.len() - 1)];
for j in ja..jb {
if s_db[j] <= base {
xs.push(j as f64 - i0 as f64);
ys.push(s_db[j] as f64);
}
}
}
if xs.len() < NTERMS {
let mut flat = s_db.clone();
flat.sort_by(|a, b| a.partial_cmp(b).unwrap());
let med = flat[flat.len() / 2];
return vec![med + POLY_OFFSET_DB; n];
}
let coeffs = polyfit_5term(&xs, &ys);
(0..n)
.map(|i| {
let t = i as f64 - i0 as f64;
let mut p = coeffs[NTERMS - 1];
for k in (0..NTERMS - 1).rev() {
p = p * t + coeffs[k];
}
p as f32 + POLY_OFFSET_DB
})
.collect()
}
fn polyfit_5term(xs: &[f64], ys: &[f64]) -> [f64; NTERMS] {
debug_assert_eq!(xs.len(), ys.len());
debug_assert!(xs.len() >= NTERMS);
let mut mom = [0.0f64; 2 * NTERMS - 1];
let mut rhs = [0.0f64; NTERMS];
for (i, &x) in xs.iter().enumerate() {
let mut xp = 1.0f64;
for k in 0..NTERMS {
mom[k] += xp;
rhs[k] += xp * ys[i];
xp *= x;
}
for k in NTERMS..2 * NTERMS - 1 {
mom[k] += xp;
xp *= x;
}
}
let mut aug = [[0.0f64; NTERMS + 1]; NTERMS];
for (i, row) in aug.iter_mut().enumerate() {
row[..NTERMS].copy_from_slice(&mom[i..i + NTERMS]);
row[NTERMS] = rhs[i];
}
for i in 0..NTERMS {
let mut max_row = i;
let mut max_abs = aug[i][i].abs();
for r in (i + 1)..NTERMS {
if aug[r][i].abs() > max_abs {
max_abs = aug[r][i].abs();
max_row = r;
}
}
if max_row != i {
aug.swap(i, max_row);
}
if aug[i][i].abs() < 1e-30 {
return [0.0; NTERMS];
}
for r in (i + 1)..NTERMS {
let factor = aug[r][i] / aug[i][i];
for c in i..=NTERMS {
aug[r][c] -= factor * aug[i][c];
}
}
}
let mut a = [0.0f64; NTERMS];
for i in (0..NTERMS).rev() {
let mut s = aug[i][NTERMS];
for j in (i + 1)..NTERMS {
s -= aug[i][j] * a[j];
}
a[i] = s / aug[i][i];
}
a
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn flat_spectrum_gives_flat_baseline() {
let n = 1000;
let avg = vec![100.0f32; n]; let base = fit_baseline(&avg, 0, n - 1);
assert_eq!(base.len(), n);
for &b in base.iter().take(50).chain(base.iter().skip(950)) {
assert!(
(b - 20.65).abs() < 0.5,
"expected ~20.65 dB for flat power, got {b:.2}"
);
}
}
#[test]
fn baseline_below_signal_peak() {
let n = 1000;
let mut avg = vec![100.0f32; n];
avg[500] = 10000.0;
let base = fit_baseline(&avg, 0, n - 1);
assert!(
base[500] < 30.0,
"baseline at signal bin {:.2} dB; expected near 20 dB noise floor",
base[500]
);
}
}