use std::sync::atomic::Ordering;
use std::sync::Arc;
use crate::config::{LoudnessConfig, NormalizationMode};
use super::atomic_state::AtomicLoudnessState;
use super::info::LoudnessInfo;
use super::limiter::PeakLimiter;
use super::meter::LoudnessMeter;
pub struct LoudnessNormalizer {
meter: LoudnessMeter,
limiter: PeakLimiter,
config: LoudnessConfig,
atomic_state: Arc<AtomicLoudnessState>,
track_loudness: Option<f64>,
track_gain: Option<f64>,
channels: usize,
sample_rate: u32,
}
impl LoudnessNormalizer {
pub fn new(channels: usize, sample_rate: u32, config: LoudnessConfig) -> Self {
let atomic_state = Arc::new(AtomicLoudnessState::new(
config.smoothing_time_ms,
sample_rate,
));
Self {
meter: LoudnessMeter::new(channels, sample_rate),
limiter: PeakLimiter::new(
channels,
sample_rate,
config.true_peak_limit_db,
10.0, 100.0, ),
config,
atomic_state,
track_loudness: None,
track_gain: None,
channels,
sample_rate,
}
}
pub fn atomic_state(&self) -> Arc<AtomicLoudnessState> {
Arc::clone(&self.atomic_state)
}
pub fn set_enabled(&mut self, enabled: bool) {
self.atomic_state.set_enabled(enabled);
}
pub fn set_config(&mut self, config: LoudnessConfig) {
self.config = config.clone();
self.limiter.set_threshold_db(config.true_peak_limit_db);
self.atomic_state
.set_smoothing(config.smoothing_time_ms, self.sample_rate);
if let Some(loudness) = self.track_loudness {
let track_gain = self.config.target_lufs - loudness;
self.track_gain = Some(track_gain);
self.atomic_state.set_target_gain(track_gain);
}
}
pub fn set_target_lufs(&mut self, target_lufs: f64) {
self.config.target_lufs = target_lufs;
if let Some(loudness) = self.track_loudness {
let track_gain = target_lufs - loudness;
self.track_gain = Some(track_gain);
self.atomic_state.set_target_gain(track_gain);
}
}
pub fn set_album_gain(&self, gain_db: f64) {
self.atomic_state.set_album_gain(gain_db);
}
pub fn set_preamp_gain(&self, gain_db: f64) {
self.atomic_state.set_preamp_gain(gain_db);
}
pub fn set_mode(&self, mode: NormalizationMode) {
let mode_val = match mode {
NormalizationMode::Track => 0,
NormalizationMode::Album => 1,
NormalizationMode::Streaming => 2,
NormalizationMode::ReplayGainTrack => 3,
NormalizationMode::ReplayGainAlbum => 4,
};
self.atomic_state.set_mode(mode_val);
}
pub fn analyze_track(&mut self, samples: &[f64]) -> f64 {
self.meter.reset();
self.meter.process(samples);
let loudness = self.meter.integrated_loudness();
if loudness.is_finite() {
self.track_loudness = Some(loudness);
let gain_db = self.config.target_lufs - loudness;
self.track_gain = Some(gain_db);
self.atomic_state.set_target_gain(gain_db);
log::info!(
"Track analysis: Integrated loudness = {:.2} LUFS, Target gain = {:.2} dB",
loudness,
gain_db
);
} else {
self.track_loudness = None;
self.track_gain = Some(0.0);
self.atomic_state.set_target_gain(0.0);
log::warn!(
"Track analysis: Invalid loudness ({:.2}), using 0 dB gain (no normalization)",
loudness
);
}
loudness
}
pub fn calculate_gain(&mut self, samples: &[f64]) -> f64 {
self.meter.reset();
self.meter.process(samples);
let loudness = self.meter.integrated_loudness();
if loudness.is_finite() {
let gain_db = self.config.target_lufs - loudness;
log::info!(
"Gapless preload analysis: Integrated loudness = {:.2} LUFS, Pending gain = {:.2} dB",
loudness, gain_db
);
gain_db
} else {
log::warn!(
"Gapless preload analysis: Invalid loudness ({:.2}), using 0 dB gain",
loudness
);
0.0
}
}
pub fn calculate_gain_with_mode(
&mut self,
samples: &[f64],
mode: NormalizationMode,
metadata: &crate::decoder::TrackMetadata,
) -> f64 {
match mode {
NormalizationMode::ReplayGainTrack => {
if let Some(rg_gain) = metadata.rg_track_gain {
let gain_db =
rg_gain + (self.config.target_lufs - self.config.replaygain_reference_lufs);
log::info!(
"Gapless preload: Using ReplayGain track tag: {:.2} dB -> target gain: {:.2} dB",
rg_gain, gain_db
);
return gain_db;
}
log::warn!("Gapless preload: No ReplayGain track tag, falling back to EBU R128");
self.calculate_gain(samples)
}
NormalizationMode::ReplayGainAlbum => {
let rg_gain = metadata.rg_album_gain.or(metadata.rg_track_gain);
if let Some(gain) = rg_gain {
let gain_db =
gain + (self.config.target_lufs - self.config.replaygain_reference_lufs);
log::info!(
"Gapless preload: Using ReplayGain album tag: {:.2} dB -> target gain: {:.2} dB",
gain, gain_db
);
return gain_db;
}
log::warn!(
"Gapless preload: No ReplayGain album/track tag, falling back to EBU R128"
);
self.calculate_gain(samples)
}
_ => {
self.calculate_gain(samples)
}
}
}
pub fn reset(&mut self) {
self.meter.reset();
self.limiter.reset();
self.atomic_state
.target_gain_db
.store(0.0, Ordering::Relaxed);
self.atomic_state
.current_gain_db
.store(0.0, Ordering::Relaxed);
self.track_loudness = None;
self.track_gain = None;
}
pub fn process(&mut self, samples: &mut [f64]) {
if !self.atomic_state.enabled.load(Ordering::Relaxed) {
return;
}
let frames = samples.len() / self.channels;
if frames == 0 {
return;
}
if self.config.mode == NormalizationMode::Streaming {
self.meter.process(samples);
if self.meter.has_reliable_measurement() {
let current_loudness = self.meter.short_term_loudness();
if current_loudness > -70.0 {
let target_gain = self.config.target_lufs - current_loudness;
self.atomic_state
.set_target_gain(target_gain.clamp(-20.0, 20.0));
}
}
}
let linear_gain = self.atomic_state.process_gain(frames);
for sample in samples.iter_mut() {
*sample *= linear_gain;
}
self.limiter.process(samples);
}
pub fn get_loudness_info(&self) -> LoudnessInfo {
LoudnessInfo {
integrated_lufs: self.meter.integrated_loudness(),
short_term_lufs: self.meter.short_term_loudness(),
momentary_lufs: self.meter.momentary_loudness(),
loudness_range: self.meter.loudness_range(),
true_peak_dbtp: self.meter.true_peak(),
current_gain_db: self.atomic_state.current_gain_db.load(Ordering::Relaxed),
target_gain_db: self.atomic_state.target_gain_db.load(Ordering::Relaxed),
preamp_db: self.atomic_state.preamp_gain_db.load(Ordering::Relaxed),
}
}
pub fn track_loudness(&self) -> Option<f64> {
self.track_loudness
}
pub fn is_analyzed(&self) -> bool {
self.track_loudness.is_some()
}
}