pub fn a_weighting(frequencies: &[f64], min_db: Option<f64>) -> Vec<f64> {
let c = [
12194.217_f64.powi(2),
20.598997_f64.powi(2),
107.65265_f64.powi(2),
737.86223_f64.powi(2),
];
frequencies
.iter()
.map(|&f| {
let f_sq = f * f;
let mut w = 2.0
+ 20.0
* (c[0].log10() + 2.0 * f_sq.log10()
- (f_sq + c[0]).log10()
- (f_sq + c[1]).log10()
- 0.5 * (f_sq + c[2]).log10()
- 0.5 * (f_sq + c[3]).log10());
if let Some(min) = min_db {
w = w.max(min);
}
w
})
.collect()
}
pub fn b_weighting(frequencies: &[f64], min_db: Option<f64>) -> Vec<f64> {
let c = [
12194.217_f64.powi(2),
20.598997_f64.powi(2),
158.48932_f64.powi(2),
];
frequencies
.iter()
.map(|&f| {
let f_sq = f * f;
let mut w = 0.17
+ 20.0
* (c[0].log10() + 1.5 * f_sq.log10()
- (f_sq + c[0]).log10()
- (f_sq + c[1]).log10()
- 0.5 * (f_sq + c[2]).log10());
if let Some(min) = min_db {
w = w.max(min);
}
w
})
.collect()
}
pub fn c_weighting(frequencies: &[f64], min_db: Option<f64>) -> Vec<f64> {
let c = [12194.217_f64.powi(2), 20.598997_f64.powi(2)];
frequencies
.iter()
.map(|&f| {
let f_sq = f * f;
let mut w = 0.062
+ 20.0
* (c[0].log10() + f_sq.log10() - (f_sq + c[0]).log10() - (f_sq + c[1]).log10());
if let Some(min) = min_db {
w = w.max(min);
}
w
})
.collect()
}
pub fn d_weighting(frequencies: &[f64], min_db: Option<f64>) -> Vec<f64> {
let c = [
8.3046305e-3_f64.powi(2),
1018.7_f64.powi(2),
1039.6_f64.powi(2),
3136.5_f64.powi(2),
3424.0_f64.powi(2),
282.7_f64.powi(2),
1160.0_f64.powi(2),
];
frequencies
.iter()
.map(|&f| {
let f_sq = f * f;
let mut w = 20.0
* (0.5 * f_sq.log10() - c[0].log10()
+ 0.5
* (((c[1] - f_sq).powi(2) + c[2] * f_sq).log10()
- ((c[3] - f_sq).powi(2) + c[4] * f_sq).log10()
- (c[5] + f_sq).log10()
- (c[6] + f_sq).log10()));
if let Some(min) = min_db {
w = w.max(min);
}
w
})
.collect()
}
pub fn z_weighting(frequencies: &[f64], min_db: Option<f64>) -> Vec<f64> {
let w = min_db.unwrap_or(0.0).min(0.0);
vec![w; frequencies.len()]
}
pub fn frequency_weighting(
frequencies: &[f64],
kind: &str,
min_db: Option<f64>,
) -> Result<Vec<f64>, String> {
match kind.to_uppercase().as_str() {
"A" => Ok(a_weighting(frequencies, min_db)),
"B" => Ok(b_weighting(frequencies, min_db)),
"C" => Ok(c_weighting(frequencies, min_db)),
"D" => Ok(d_weighting(frequencies, min_db)),
"Z" => Ok(z_weighting(frequencies, min_db)),
_ => Err(format!("Unknown weighting kind: {}", kind)),
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde::Deserialize;
#[derive(Deserialize)]
struct WeightingTestCase {
name: String,
function: String,
frequencies: Vec<f64>,
min_db: Option<f64>,
expected: Vec<f64>,
}
fn load_weighting_references() -> Vec<WeightingTestCase> {
let data = include_str!("../../tests/references/frequency_weighting.json");
serde_json::from_str(data).expect("Failed to parse frequency_weighting.json")
}
fn assert_f64_eq(actual: &[f64], expected: &[f64], tol: f64, name: &str) {
assert_eq!(actual.len(), expected.len(), "{name}: length mismatch");
for (i, (a, e)) in actual.iter().zip(expected.iter()).enumerate() {
let diff = (a - e).abs();
assert!(
diff < tol,
"{name}[{i}]: got {a}, expected {e}, diff {diff}"
);
}
}
#[test]
fn test_a_weighting_matches_librosa() {
for case in load_weighting_references()
.into_iter()
.filter(|c| c.function == "A_weighting")
{
let result = a_weighting(&case.frequencies, case.min_db);
assert_f64_eq(&result, &case.expected, 1e-10, &case.name);
}
}
#[test]
fn test_b_weighting_matches_librosa() {
for case in load_weighting_references()
.into_iter()
.filter(|c| c.function == "B_weighting")
{
let result = b_weighting(&case.frequencies, case.min_db);
assert_f64_eq(&result, &case.expected, 1e-10, &case.name);
}
}
#[test]
fn test_c_weighting_matches_librosa() {
for case in load_weighting_references()
.into_iter()
.filter(|c| c.function == "C_weighting")
{
let result = c_weighting(&case.frequencies, case.min_db);
assert_f64_eq(&result, &case.expected, 1e-10, &case.name);
}
}
#[test]
fn test_d_weighting_matches_librosa() {
for case in load_weighting_references()
.into_iter()
.filter(|c| c.function == "D_weighting")
{
let result = d_weighting(&case.frequencies, case.min_db);
assert_f64_eq(&result, &case.expected, 1e-10, &case.name);
}
}
#[test]
fn test_a_weighting_1khz_approx_zero() {
let w = a_weighting(&[1000.0], None);
assert!(
w[0].abs() < 1.0,
"A-weighting at 1kHz should be ~0 dB, got {}",
w[0]
);
}
#[test]
fn test_z_weighting_all_zeros() {
let w = z_weighting(&[100.0, 1000.0, 10000.0], None);
assert_eq!(w, vec![0.0; 3]);
}
#[test]
fn test_frequency_weighting_dispatcher() {
let freqs = [1000.0];
let a = frequency_weighting(&freqs, "A", None).unwrap();
assert_eq!(a, a_weighting(&freqs, None));
assert!(frequency_weighting(&freqs, "X", None).is_err());
}
}