#![allow(dead_code)]
#![allow(clippy::cast_precision_loss)]
#[derive(Debug, Clone, PartialEq)]
pub struct OctaveBand {
pub center_hz: f32,
pub low_hz: f32,
pub high_hz: f32,
pub spl_db: f32,
}
impl OctaveBand {
#[must_use]
pub fn new(center_hz: f32, low_hz: f32, high_hz: f32) -> Self {
Self {
center_hz,
low_hz,
high_hz,
spl_db: 0.0,
}
}
#[must_use]
pub fn bandwidth_hz(&self) -> f32 {
self.high_hz - self.low_hz
}
}
pub struct OctaveAnalyzer;
impl OctaveAnalyzer {
#[must_use]
pub fn third_octave_bands() -> Vec<OctaveBand> {
let factor: f32 = 2.0f32.powf(1.0 / 3.0);
let centres: [f32; 31] = [
20.0, 25.0, 31.5, 40.0, 50.0, 63.0, 80.0, 100.0, 125.0, 160.0, 200.0, 250.0, 315.0,
400.0, 500.0, 630.0, 800.0, 1_000.0, 1_250.0, 1_600.0, 2_000.0, 2_500.0, 3_150.0,
4_000.0, 5_000.0, 6_300.0, 8_000.0, 10_000.0, 12_500.0, 16_000.0, 20_000.0,
];
centres
.iter()
.map(|&c| OctaveBand::new(c, c / factor.sqrt(), c * factor.sqrt()))
.collect()
}
#[must_use]
pub fn full_octave_bands() -> Vec<OctaveBand> {
let factor: f32 = 2.0f32.sqrt(); let centres: [f32; 10] = [
31.5, 63.0, 125.0, 250.0, 500.0, 1_000.0, 2_000.0, 4_000.0, 8_000.0, 16_000.0,
];
centres
.iter()
.map(|&c| OctaveBand::new(c, c / factor, c * factor))
.collect()
}
}
pub fn fill_spl(bands: &mut [OctaveBand], magnitudes: &[f32], sample_rate: u32) {
let n_bins = magnitudes.len();
if n_bins == 0 || sample_rate == 0 {
return;
}
let nyquist = sample_rate as f32 / 2.0;
let bin_width = nyquist / n_bins as f32;
for band in bands.iter_mut() {
let mut power: f32 = 0.0;
for (bin, &mag) in magnitudes.iter().enumerate() {
let bin_hz = bin as f32 * bin_width;
if bin_hz >= band.low_hz && bin_hz < band.high_hz {
power += mag * mag;
}
}
band.spl_db = if power > 0.0 {
10.0 * power.log10()
} else {
f32::NEG_INFINITY
};
}
}
pub struct SpectrumEquality;
impl SpectrumEquality {
#[must_use]
pub fn compare(measured: &[OctaveBand], target: &[OctaveBand]) -> f32 {
let len = measured.len().min(target.len());
if len == 0 {
return 0.0;
}
let sum: f32 = measured
.iter()
.zip(target.iter())
.take(len)
.map(|(m, t)| (m.spl_db - t.spl_db).abs())
.sum();
sum / len as f32
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_octave_band_bandwidth() {
let b = OctaveBand::new(1_000.0, 707.0, 1_414.0);
assert!((b.bandwidth_hz() - 707.0).abs() < 1.0);
}
#[test]
fn test_octave_band_default_spl_zero() {
let b = OctaveBand::new(500.0, 354.0, 707.0);
assert_eq!(b.spl_db, 0.0);
}
#[test]
fn test_third_octave_band_count() {
let bands = OctaveAnalyzer::third_octave_bands();
assert_eq!(bands.len(), 31);
}
#[test]
fn test_third_octave_first_band_center() {
let bands = OctaveAnalyzer::third_octave_bands();
assert!((bands[0].center_hz - 20.0).abs() < 0.1);
}
#[test]
fn test_third_octave_last_band_center() {
let bands = OctaveAnalyzer::third_octave_bands();
assert!((bands[30].center_hz - 20_000.0).abs() < 1.0);
}
#[test]
fn test_third_octave_all_spl_zero_initially() {
let bands = OctaveAnalyzer::third_octave_bands();
for b in &bands {
assert_eq!(b.spl_db, 0.0);
}
}
#[test]
fn test_third_octave_low_below_center() {
let bands = OctaveAnalyzer::third_octave_bands();
for b in &bands {
assert!(b.low_hz < b.center_hz, "{} not < {}", b.low_hz, b.center_hz);
}
}
#[test]
fn test_full_octave_band_count() {
let bands = OctaveAnalyzer::full_octave_bands();
assert_eq!(bands.len(), 10);
}
#[test]
fn test_full_octave_first_center() {
let bands = OctaveAnalyzer::full_octave_bands();
assert!((bands[0].center_hz - 31.5).abs() < 0.5);
}
#[test]
fn test_full_octave_last_center() {
let bands = OctaveAnalyzer::full_octave_bands();
assert!((bands[9].center_hz - 16_000.0).abs() < 1.0);
}
#[test]
fn test_fill_spl_nonzero_magnitudes() {
let mut bands = OctaveAnalyzer::full_octave_bands();
let mags = vec![1.0f32; 1024];
fill_spl(&mut bands, &mags, 48_000);
let finite_count = bands.iter().filter(|b| b.spl_db.is_finite()).count();
assert!(finite_count > 0);
}
#[test]
fn test_fill_spl_empty_magnitudes_no_change() {
let mut bands = OctaveAnalyzer::third_octave_bands();
fill_spl(&mut bands, &[], 48_000);
for b in &bands {
assert_eq!(b.spl_db, 0.0);
}
}
#[test]
fn test_fill_spl_zero_magnitudes_neg_inf() {
let mut bands = OctaveAnalyzer::full_octave_bands();
let mags = vec![0.0f32; 1024];
fill_spl(&mut bands, &mags, 48_000);
for b in &bands {
assert!(b.spl_db.is_infinite() || b.spl_db == 0.0);
}
}
#[test]
fn test_spectrum_equality_identical_bands() {
let bands = OctaveAnalyzer::full_octave_bands();
let mad = SpectrumEquality::compare(&bands, &bands);
assert!((mad - 0.0).abs() < f32::EPSILON);
}
#[test]
fn test_spectrum_equality_empty() {
let mad = SpectrumEquality::compare(&[], &[]);
assert!((mad - 0.0).abs() < f32::EPSILON);
}
#[test]
fn test_spectrum_equality_known_deviation() {
let mut a = vec![OctaveBand::new(1_000.0, 700.0, 1_400.0)];
let mut b = vec![OctaveBand::new(1_000.0, 700.0, 1_400.0)];
a[0].spl_db = 10.0;
b[0].spl_db = 5.0;
let mad = SpectrumEquality::compare(&a, &b);
assert!((mad - 5.0).abs() < 1e-5);
}
}