use super::slope::{calculate_absolute_slope_in_range, calculate_standard_deviation_in_range};
use super::types::HeadphoneLossData;
use crate::Curve;
use crate::read;
pub fn headphone_loss(curve: &Curve) -> f64 {
let freq = &curve.freq;
let deviation = &curve.spl;
const FMIN: f64 = 50.0;
const FMAX: f64 = 10000.0;
let sd = calculate_standard_deviation_in_range(freq, deviation, FMIN, FMAX);
let as_value = calculate_absolute_slope_in_range(freq, deviation, FMIN, FMAX);
114.49 - (12.62 * sd) - (15.52 * as_value)
}
pub fn headphone_loss_with_target(
data: &HeadphoneLossData,
response: &Curve,
target: &Curve,
) -> f64 {
let freqs = read::create_log_frequency_grid(10 * 12, 20.0, 20000.0);
let input_curve = read::normalize_and_interpolate_response(&freqs, response);
let target_curve = read::normalize_and_interpolate_response(&freqs, target);
let deviation = Curve {
freq: freqs.clone(),
spl: &target_curve.spl - &input_curve.spl,
phase: None,
..Default::default()
};
let smooth_deviation = if data.smooth {
read::smooth_one_over_n_octave(&deviation, data.smooth_n)
} else {
deviation.clone()
};
headphone_loss(&smooth_deviation)
}
#[cfg(test)]
mod tests {
use super::*;
use ndarray::Array1;
#[test]
fn test_headphone_loss_perfect_harman_deviation() {
let freq = Array1::from(vec![50.0, 100.0, 1000.0, 5000.0, 10000.0]);
let deviation = Array1::zeros(5);
let curve = Curve {
freq: freq.clone(),
spl: deviation,
phase: None,
..Default::default()
};
let score = headphone_loss(&curve);
let expected_score = 114.49;
assert!(
(score - expected_score).abs() < 1e-12,
"Perfect Harman score incorrect: got {}, expected {}",
score,
expected_score
);
}
#[test]
fn test_headphone_loss_with_deviation() {
let freq = Array1::from(vec![50.0, 100.0, 1000.0, 5000.0, 10000.0]);
let deviation = Array1::from(vec![1.0, 1.0, 1.0, 1.0, 1.0]);
let curve = Curve {
freq: freq.clone(),
spl: deviation,
phase: None,
..Default::default()
};
let score = headphone_loss(&curve);
let expected_preference = 114.49;
let expected_score = expected_preference;
assert!(
(score - expected_score).abs() < 1e-10,
"Constant deviation score incorrect: got {}, expected {}",
score,
expected_score
);
}
#[test]
fn test_headphone_loss_with_slope() {
let freq = Array1::from(vec![
50.0, 100.0, 200.0, 400.0, 800.0, 1600.0, 3200.0, 6400.0, 10000.0,
]);
let deviation = freq.mapv(|f: f64| 1.0 * f.log2());
let curve = Curve {
freq: freq.clone(),
spl: deviation,
phase: None,
..Default::default()
};
let score = headphone_loss(&curve);
assert!(
score > 50.0,
"Sloped deviation should have lower preference: got {}",
score
);
}
#[test]
fn test_headphone_loss_with_target() {
let freq = Array1::logspace(10.0, 1.301, 4.301, 100);
let response = Array1::from_elem(100, 5.0); let target = Array1::from_elem(100, 5.0);
let response_curve = Curve {
freq: freq.clone(),
spl: response,
phase: None,
..Default::default()
};
let target_curve = Curve {
freq: freq.clone(),
spl: target,
phase: None,
..Default::default()
};
let data = HeadphoneLossData::new(false, 2);
let score = headphone_loss_with_target(&data, &response_curve, &target_curve);
let expected_perfect_score = 114.49;
assert!(
(score - expected_perfect_score).abs() < 1e-10,
"Perfect target match score incorrect: got {}, expected {}",
score,
expected_perfect_score
);
}
#[test]
fn test_headphone_loss_perfect_correction() {
let freq = Array1::logspace(10.0, 1.699, 4.0, 100); let zero_deviation = Array1::zeros(100);
let curve = Curve {
freq: freq.clone(),
spl: zero_deviation,
phase: None,
..Default::default()
};
let score = headphone_loss(&curve);
let expected_perfect = 114.49;
assert!(
(score - expected_perfect).abs() < 1e-10,
"Perfect correction score incorrect: got {}, expected {}",
score,
expected_perfect
);
}
#[test]
fn test_headphone_loss_sign_independence() {
let freq = Array1::logspace(10.0, 1.699, 4.0, 100);
let deviation_positive = freq.mapv(|f: f64| 0.5 * f.log2() + 2.0);
let deviation_negative = -&deviation_positive;
let curve_pos = Curve {
freq: freq.clone(),
spl: deviation_positive,
phase: None,
..Default::default()
};
let curve_neg = Curve {
freq: freq.clone(),
spl: deviation_negative,
phase: None,
..Default::default()
};
let score_pos = headphone_loss(&curve_pos);
let score_neg = headphone_loss(&curve_neg);
assert!(
(score_pos - score_neg).abs() < 1e-10,
"Sign independence violated: pos={}, neg={}",
score_pos,
score_neg
);
}
#[test]
fn test_headphone_loss_worse_than_perfect() {
let freq = Array1::logspace(10.0, 1.699, 4.0, 100);
let zero_deviation = Array1::zeros(100);
let nonzero_deviation = Array1::from_elem(100, 3.0);
let perfect_curve = Curve {
freq: freq.clone(),
spl: zero_deviation,
phase: None,
..Default::default()
};
let imperfect_curve = Curve {
freq: freq.clone(),
spl: nonzero_deviation,
phase: None,
..Default::default()
};
let perfect_score = headphone_loss(&perfect_curve);
let imperfect_score = headphone_loss(&imperfect_curve);
assert!(
imperfect_score < perfect_score,
"Imperfect correction should score lower: perfect={}, imperfect={}",
perfect_score,
imperfect_score
);
assert!(
(perfect_score - 114.49).abs() < 1e-10,
"Perfect score should be 114.49, got {}",
perfect_score
);
}
}