use crate::analyse::db_to_linear;
const ATTACK_MS: f32 = 5.0;
const RELEASE_MS: f32 = 100.0;
const LOOKAHEAD_MS: u32 = 5;
pub fn limit(samples: &mut [i16], sample_rate: u32, ceiling_db: f32) {
let ceiling_linear = db_to_linear(ceiling_db) * i16::MAX as f32;
let attack_coeff = attack_coeff(sample_rate);
let release_coeff = release_coeff(sample_rate);
let lookahead = ((sample_rate as f32 * LOOKAHEAD_MS as f32) / 1000.0) as usize;
let mut gain: f32 = 1.0;
for i in 0..samples.len() {
let lookahead_end = (i + lookahead).min(samples.len());
let peak_ahead = samples[i..lookahead_end]
.iter()
.map(|&s| (s as f32).abs())
.fold(0.0f32, f32::max);
let target_gain = if peak_ahead > ceiling_linear {
ceiling_linear / peak_ahead
} else {
1.0
};
if target_gain < gain {
gain = gain * attack_coeff + target_gain * (1.0 - attack_coeff);
} else {
gain = gain * release_coeff + target_gain * (1.0 - release_coeff);
}
let limited = (samples[i] as f32 * gain)
.round()
.clamp(-ceiling_linear, ceiling_linear);
samples[i] = limited as i16;
}
}
fn attack_coeff(sample_rate: u32) -> f32 {
let attack_samples = sample_rate as f32 * ATTACK_MS / 1000.0;
(-1.0f32 / attack_samples).exp()
}
fn release_coeff(sample_rate: u32) -> f32 {
let release_samples = sample_rate as f32 * RELEASE_MS / 1000.0;
(-1.0f32 / release_samples).exp()
}