use std::f64;
use ebur128::{EbuR128, Mode};
use serde::{Deserialize, Serialize};
use symphonia::core::audio::{Audio, GenericAudioBufferRef};
use thiserror::Error;
use crate::{
config::common_config,
media_container::MediaContainer,
symphonia_helpers::raw_decoder::{Decoder, DecodingError},
};
#[derive(Debug, Error)]
pub enum LoudnormAnalysisError {
#[error("{0}")]
Ebur128(#[from] ebur128::Error),
#[error("Decoding Error: {0}")]
Decoding(#[from] DecodingError),
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, PartialOrd, Copy)]
pub struct LoudnormAnalysis {
pub(crate) accurate_true_peak: bool,
pub(crate) i: f64,
pub(crate) tp: f64,
}
impl LoudnormAnalysis {
#[must_use]
pub fn accurate_true_peak(&self) -> bool {
self.accurate_true_peak
}
#[must_use]
pub fn measured_i(&self) -> f64 {
self.i
}
#[must_use]
pub fn measured_tp(&self) -> f64 {
self.tp
}
#[must_use]
pub fn calculated_gain_db(&self) -> f64 {
let config = common_config().loudnorm;
(config.target_i - self.i - config.target_offset).min(config.target_tp - self.tp)
}
#[must_use]
pub fn calculated_gain(&self) -> f64 {
10f64.powf(self.calculated_gain_db() / 20.0)
}
#[must_use]
pub fn calculated_replay_gain_peak(&self) -> f64 {
10.0_f64.powf(self.tp / 20.0)
}
pub fn from_container(
container: &MediaContainer,
accurate_true_peak: bool,
) -> Result<Self, LoudnormAnalysisError> {
let peak_mode = if accurate_true_peak {
Mode::TRUE_PEAK
} else {
Mode::SAMPLE_PEAK
};
let mut analyzer = EbuR128::new(
container.stream().codec_params.channels as u32,
container.stream().codec_params.sample_rate,
Mode::I | peak_mode,
)?;
let mut raw_decoder = Decoder::from_container(container, 512 * 1024)?;
while let Some(packet) = raw_decoder.decode_next_packet()? {
match packet {
GenericAudioBufferRef::S16(buf) => {
let mut frames = vec![0; buf.samples_interleaved()];
buf.copy_to_slice_interleaved(&mut frames);
analyzer.add_frames_i16(&frames)?;
}
GenericAudioBufferRef::S32(buf) => {
let mut frames = vec![0; buf.samples_interleaved()];
buf.copy_to_slice_interleaved(&mut frames);
analyzer.add_frames_i32(&frames)?;
}
GenericAudioBufferRef::F64(buf) => {
let mut frames = vec![0.0; buf.samples_interleaved()];
buf.copy_to_slice_interleaved(&mut frames);
analyzer.add_frames_f64(&frames)?;
}
buf => {
let mut frames = vec![0.0; buf.samples_interleaved()];
buf.copy_to_slice_interleaved(&mut frames);
analyzer.add_frames_f32(&frames)?;
}
}
}
let i = analyzer.loudness_global()?;
let tp = (0..container.stream().codec_params.channels as u32).try_fold(
f64::NEG_INFINITY,
|max, c| {
let peak = if accurate_true_peak {
analyzer.true_peak(c)
} else {
analyzer.sample_peak(c)
};
peak.map(|tp| f64::max(max, tp))
},
)?;
Ok(LoudnormAnalysis {
accurate_true_peak,
i,
tp,
})
}
}