const FLOOR_DB: f64 = -160.0;
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Peak {
pub hz: f64,
pub db: f64,
}
pub fn db(amplitude: f64) -> f64 {
if amplitude <= 0.0 {
FLOOR_DB
} else {
(20.0 * amplitude.log10()).max(FLOOR_DB)
}
}
pub fn peaks(mags: &[f64], bin_hz: f64, max_peaks: usize) -> Vec<Peak> {
let mut found: Vec<Peak> = located(mags, bin_hz).into_iter().map(|(_, p)| p).collect();
found.truncate(max_peaks);
found
}
fn located(mags: &[f64], bin_hz: f64) -> Vec<(usize, Peak)> {
let mut found: Vec<(f64, usize, Peak)> = Vec::new();
for k in 1..mags.len().saturating_sub(1) {
if mags[k] <= mags[k - 1] || mags[k] < mags[k + 1] || mags[k] <= 0.0 {
continue;
}
let (a, b, c) = (mags[k - 1], mags[k], mags[k + 1]);
let denom = a - 2.0 * b + c;
let delta = if denom.abs() > f64::EPSILON {
(0.5 * (a - c) / denom).clamp(-0.5, 0.5)
} else {
0.0
};
let amplitude = b - 0.25 * (a - c) * delta;
found.push((
amplitude,
k,
Peak {
hz: (k as f64 + delta) * bin_hz,
db: db(amplitude),
},
));
}
found.sort_by(|x, y| y.0.total_cmp(&x.0));
found.into_iter().map(|(_, k, peak)| (k, peak)).collect()
}
pub fn resolved(mags: &[f64], bin_hz: f64, spanned: usize, max_peaks: usize) -> Vec<Peak> {
let r = spanned as f64 / (2 * (mags.len() - 1)) as f64;
let half = r / 2.0;
let dc = match mags {
[zero, one, ..] if zero >= one => *zero,
_ => 0.0,
};
let mut kept: Vec<(usize, Peak)> = Vec::new();
for (k, peak) in located(mags, bin_hz) {
if kept.len() == max_peaks {
break;
}
let leaked: f64 = kept
.iter()
.map(|&(q, _)| {
let near = (k.abs_diff(q) as f64 * r - half).max(0.0);
let image = (k + q) as f64 * r - half;
mags[q] / kernel(half) * (leakage(near) + leakage(image))
})
.sum::<f64>()
+ dc / kernel(half) * leakage(k as f64 * r - half);
if mags[k] > leaked {
kept.push((k, peak));
}
}
kept.into_iter().map(|(_, peak)| peak).collect()
}
pub(crate) fn leakage(m: f64) -> f64 {
match m > 1.0 {
true => (1.0 / (std::f64::consts::PI * m * (m * m - 1.0))).min(1.0),
false => 1.0,
}
}
pub(crate) fn kernel(d: f64) -> f64 {
let d = d.abs();
match d {
0.0 => 1.0,
_ if d >= 1.0 => 0.5,
_ => {
let x = std::f64::consts::PI * d;
x.sin() / (x * (1.0 - d * d))
}
}
}