use std::time::Duration;
use rodio::{source::SeekError, Sample, Source};
use crate::util::{self, ToF32, ZERO_DB};
pub fn normalize<I>(
input: I,
ratio: f32,
threshold: f32,
knee_width: f32,
attack: Duration,
release: Duration,
) -> Normalize<I>
where
I: Source,
I::Item: Sample,
{
let sample_rate = input.sample_rate();
let channels = input.channels() as usize;
let attack = duration_to_coefficient(attack, sample_rate);
let release = duration_to_coefficient(release, sample_rate);
Normalize {
input,
ratio,
threshold,
knee_width,
attack,
release,
normalisation_integrators: vec![ZERO_DB; channels],
normalisation_peaks: vec![ZERO_DB; channels],
position: 0,
}
}
#[must_use]
fn duration_to_coefficient(duration: Duration, sample_rate: u32) -> f32 {
f32::exp(-1.0 / (duration.as_secs_f32() * sample_rate.to_f32_lossy()))
}
#[derive(Clone, Debug)]
pub struct Normalize<I>
where
I: Source,
I::Item: Sample,
{
input: I,
ratio: f32,
threshold: f32,
knee_width: f32,
attack: f32,
release: f32,
normalisation_integrators: Vec<f32>,
normalisation_peaks: Vec<f32>,
position: usize,
}
impl<I> Normalize<I>
where
I: Source,
I::Item: Sample,
{
#[inline]
pub fn inner(&self) -> &I {
&self.input
}
#[inline]
pub fn inner_mut(&mut self) -> &mut I {
&mut self.input
}
#[inline]
pub fn into_inner(self) -> I {
self.input
}
}
impl<I> Iterator for Normalize<I>
where
I: Source,
I::Item: Sample,
{
type Item = I::Item;
#[inline]
fn next(&mut self) -> Option<I::Item> {
let sample = self.input.next()?;
let channel = self.position % self.input.channels() as usize;
self.position = self.position.wrapping_add(1);
sample.amplify(self.ratio);
let threshold_db = self.threshold;
let knee_db = self.knee_width;
let attack_cf = self.attack;
let release_cf = self.release;
let sample_f32 = sample.to_f32();
let mut limiter_db = ZERO_DB;
if sample_f32.is_normal() {
let bias_db = util::ratio_to_db(sample_f32.abs()) - threshold_db;
let knee_boundary_db = bias_db * 2.0;
if knee_boundary_db < -knee_db {
limiter_db = ZERO_DB;
} else if knee_boundary_db.abs() <= knee_db {
limiter_db = (knee_boundary_db + knee_db).powi(2) / (8.0 * knee_db);
} else {
limiter_db = bias_db;
}
}
self.normalisation_integrators[channel] = f32::max(
limiter_db,
release_cf * self.normalisation_integrators[channel] - release_cf * limiter_db
+ limiter_db,
);
self.normalisation_peaks[channel] = attack_cf * self.normalisation_peaks[channel]
- attack_cf * self.normalisation_integrators[channel]
+ self.normalisation_integrators[channel];
let max_peak = self
.normalisation_peaks
.iter()
.copied()
.fold(ZERO_DB, f32::max);
sample.amplify(util::db_to_ratio(-max_peak));
Some(sample)
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
self.input.size_hint()
}
}
impl<I> Source for Normalize<I>
where
I: Source,
I::Item: Sample,
{
#[inline]
fn current_frame_len(&self) -> Option<usize> {
self.input.current_frame_len()
}
#[inline]
fn channels(&self) -> u16 {
self.input.channels()
}
#[inline]
fn sample_rate(&self) -> u32 {
self.input.sample_rate()
}
#[inline]
fn total_duration(&self) -> Option<Duration> {
self.input.total_duration()
}
#[inline]
fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> {
self.input.try_seek(pos)?;
self.normalisation_integrators = vec![ZERO_DB; self.channels() as usize];
self.normalisation_peaks = vec![ZERO_DB; self.channels() as usize];
self.position = 0;
Ok(())
}
}