pub const BARK_CENTER_FREQUENCIES: [f64; 24] = [
50.0, 150.0, 250.0, 350.0, 450.0, 570.0, 700.0, 840.0, 1000.0, 1170.0, 1370.0, 1600.0,
1850.0, 2150.0, 2500.0, 2900.0, 3400.0, 4000.0, 4800.0, 5800.0, 7000.0, 8500.0, 10500.0,
13500.0,
];
pub const BARK_BAND_EDGES: [f64; 25] = [
0.0, 100.0, 200.0, 300.0, 400.0, 510.0, 630.0, 770.0, 920.0, 1080.0, 1270.0, 1480.0,
1720.0, 2000.0, 2320.0, 2700.0, 3150.0, 3700.0, 4400.0, 5300.0, 6400.0, 7700.0, 9500.0,
12000.0, 15500.0,
];
pub fn hz_to_bark(f: f64) -> f64 {
13.0 * (0.00076 * f).atan() + 3.5 * ((f / 7500.0).powi(2)).atan()
}
pub fn bark_to_hz(z: f64) -> f64 {
let mut f = 600.0 * z.sinh() / 7.0;
f = f.clamp(1.0, 25000.0);
for _ in 0..5 {
let z_est = hz_to_bark(f);
let err = z_est - z;
let delta = f * 1e-6 + 0.01;
let dz_df = (hz_to_bark(f + delta) - z_est) / delta;
if dz_df.abs() < 1e-15 {
break;
}
f -= err / dz_df;
f = f.clamp(1.0, 25000.0);
}
f
}
pub fn critical_bandwidth(f: f64) -> f64 {
25.0 + 75.0 * (1.0 + 1.4 * (f / 1000.0).powi(2)).powf(0.69)
}
pub fn bark_spectrum(freqs: &[f64], spl_db: &[f64]) -> [f64; 24] {
debug_assert_eq!(freqs.len(), spl_db.len());
let mut result = [-100.0_f64; 24];
for band in 0..24 {
let lo = BARK_BAND_EDGES[band];
let hi = BARK_BAND_EDGES[band + 1];
let mut energy_sum = 0.0_f64;
let mut count = 0u32;
for (i, &f) in freqs.iter().enumerate() {
if f >= lo && f < hi {
energy_sum += 10.0_f64.powf(spl_db[i] / 10.0);
count += 1;
}
}
if count > 0 {
result[band] = 10.0 * (energy_sum / count as f64).log10();
}
}
result
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_hz_to_bark_known() {
let z100 = hz_to_bark(100.0);
let z1000 = hz_to_bark(1000.0);
let z10000 = hz_to_bark(10000.0);
assert!((z100 - 1.0).abs() < 0.15, "100 Hz -> Bark = {z100}, expected ~1.0");
assert!((z1000 - 8.5).abs() < 0.3, "1000 Hz -> Bark = {z1000}, expected ~8.5");
assert!(
(z10000 - 22.4).abs() < 0.5,
"10000 Hz -> Bark = {z10000}, expected ~22.4"
);
}
#[test]
fn test_bark_to_hz_roundtrip() {
for &f in &[100.0, 500.0, 1000.0, 4000.0, 10000.0] {
let z = hz_to_bark(f);
let f_back = bark_to_hz(z);
let rel_err = (f_back - f).abs() / f;
assert!(
rel_err < 0.02,
"Roundtrip failed for {f} Hz: got {f_back} Hz (rel err {rel_err})"
);
}
}
#[test]
fn test_critical_bandwidth_increases() {
let cbw_500 = critical_bandwidth(500.0);
let cbw_5000 = critical_bandwidth(5000.0);
assert!(
cbw_5000 > cbw_500,
"CBW at 5kHz ({cbw_5000}) should be > CBW at 500Hz ({cbw_500})"
);
}
#[test]
fn test_bark_spectrum_flat_input() {
let n = 1000;
let freqs: Vec<f64> = (0..n).map(|i| 20.0 + (16000.0 - 20.0) * i as f64 / n as f64).collect();
let spl: Vec<f64> = vec![70.0; n];
let spectrum = bark_spectrum(&freqs, &spl);
for (i, &val) in spectrum.iter().enumerate() {
if val > -50.0 {
assert!(
(val - 70.0).abs() < 1.0,
"Band {i} = {val} dB, expected ~70 dB"
);
}
}
}
}