use std::f32::consts::SQRT_2;
use biquad::{Biquad, Coefficients, DirectForm1, Q_BUTTERWORTH_F32, ToHertz, Type};
use rodio::SampleRate;
const FREQUENCIES: &[f32] = &[
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, 1000.0, 1250.0, 1600.0, 2000.0, 2500.0, 3150.0, 4000.0, 5000.0, 6300.0,
8000.0, 10000.0, 12500.0,
];
const ALPHA_F: &[f32] = &[
0.532, 0.506, 0.480, 0.455, 0.432, 0.409, 0.387, 0.367, 0.349, 0.330, 0.315, 0.301, 0.288,
0.276, 0.267, 0.259, 0.253, 0.250, 0.246, 0.244, 0.243, 0.243, 0.243, 0.242, 0.242, 0.245,
0.254, 0.271, 0.301,
];
const L_U: &[f32] = &[
-31.6, -27.2, -23.0, -19.1, -15.9, -13.0, -10.3, -8.1, -6.2, -4.5, -3.1, -2.0, -1.1, -0.4, 0.0,
0.3, 0.5, 0.0, -2.7, -4.1, -1.0, 1.7, 2.5, 1.2, -2.1, -7.1, -11.2, -10.7, -3.1,
];
const T_F: &[f32] = &[
78.5, 68.7, 59.5, 51.1, 44.0, 37.5, 31.5, 26.5, 22.1, 17.9, 14.4, 11.4, 8.6, 6.2, 4.4, 3.0,
2.2, 2.4, 3.5, 1.7, -1.3, -4.2, -6.0, -5.4, -1.5, 6.0, 12.6, 13.9, 12.3,
];
const REF_SPL: f32 = 94.0;
const LOUDNESS_SCALE: f32 = 4.47e-3;
pub const REFERENCE_SPL: f32 = 83.0;
const NUM_BANDS: usize = 7;
const BAND_FREQUENCIES: [f32; NUM_BANDS] = [
31.5, 80.0, 250.0, 500.0, 2000.0, 6300.0, 12500.0, ];
const BAND_Q: [f32; NUM_BANDS] = [
Q_BUTTERWORTH_F32, 1.0, 1.2, SQRT_2, 1.2, 1.5, Q_BUTTERWORTH_F32, ];
fn calculate_target_spl(frequency: f32, phon: f32) -> f32 {
let idx = FREQUENCIES
.iter()
.position(|&f| f >= frequency)
.unwrap_or(FREQUENCIES.len() - 1);
let idx_low = if idx == 0 { 0 } else { idx - 1 };
let f1 = FREQUENCIES[idx_low];
let f2 = FREQUENCIES[idx];
let t = if 2.0 * (f1 - f2).abs() <= f32::EPSILON * (f1.abs() + f2.abs()) {
0.0
} else {
(frequency - f1) / (f2 - f1)
};
let alpha_f = ALPHA_F[idx_low] + t * (ALPHA_F[idx] - ALPHA_F[idx_low]);
let lu_f = L_U[idx_low] + t * (L_U[idx] - L_U[idx_low]);
let tf_f = T_F[idx_low] + t * (T_F[idx] - T_F[idx_low]);
let a_f = LOUDNESS_SCALE * (10.0_f32.powf(0.025 * phon) - 1.15)
+ (0.4 * 10.0_f32.powf((tf_f + lu_f) / 10.0 - 9.0)).powf(alpha_f);
(10.0 / alpha_f) * f32::log10(a_f) - lu_f + REF_SPL
}
#[derive(Debug, Clone)]
pub struct EqualLoudnessFilter {
filters: [DirectForm1<f32>; NUM_BANDS],
volume: f32,
sample_rate: SampleRate,
lufs_target: f32,
}
impl EqualLoudnessFilter {
#[must_use]
pub fn new(sample_rate: SampleRate, lufs_target: f32, volume: f32) -> Self {
let mut filter = Self {
filters: [(); NUM_BANDS].map(|()| {
DirectForm1::<f32>::new(
Coefficients::<f32>::from_params(
Type::PeakingEQ(0.0),
sample_rate.hz(),
1000.0.hz(),
1.0,
)
.expect("failed to create filter coefficients"),
)
}),
sample_rate,
lufs_target,
volume,
};
let phon = Self::calculate_phon(volume, lufs_target);
for band in 0..NUM_BANDS {
let coeffs = filter.calculate_coefficients_for_phon(band, phon);
filter.filters[band].update_coefficients(coeffs);
}
filter
}
fn calculate_phon(volume: f32, lufs_target: f32) -> f32 {
let listening_level = REFERENCE_SPL + lufs_target;
(listening_level * volume).clamp(0.0, 100.0)
}
pub fn update_volume(&mut self, volume: f32) {
if 2.0 * (volume - self.volume).abs() > f32::EPSILON * (volume.abs() + self.volume.abs()) {
let phon = Self::calculate_phon(volume, self.lufs_target);
for band in 0..NUM_BANDS {
let new_coeffs = self.calculate_coefficients_for_phon(band, phon);
self.filters[band].update_coefficients(new_coeffs);
}
self.volume = volume;
}
}
#[inline]
pub fn process(&mut self, input: f32) -> f32 {
let mut output = input;
for filter in &mut self.filters {
output = filter.run(output);
}
output
}
fn calculate_coefficients_for_phon(&self, band: usize, phon: f32) -> Coefficients<f32> {
let freq = BAND_FREQUENCIES[band];
let q = BAND_Q[band];
let target_response = calculate_target_spl(freq, phon);
let reference_response = calculate_target_spl(freq, REFERENCE_SPL + self.lufs_target);
let shape_difference =
(target_response - reference_response) - (phon - (REFERENCE_SPL + self.lufs_target));
let max_boost_db = 20.0 * (1.0 / self.volume).log10();
let safe_gain = shape_difference.min(max_boost_db);
let filter_type = if band == 0 {
Type::LowShelf(safe_gain)
} else if band == NUM_BANDS - 1 {
Type::HighShelf(safe_gain)
} else {
Type::PeakingEQ(safe_gain)
};
Coefficients::<f32>::from_params(filter_type, self.sample_rate.hz(), freq.hz(), q)
.expect("failed to create filter coefficients")
}
pub fn reset(&mut self) {
for filter in &mut self.filters {
filter.reset_state();
}
}
}