use crate::{amplitude_to_db, AnalysisConfig, Result};
pub struct DynamicsAnalyzer {
config: AnalysisConfig,
}
impl DynamicsAnalyzer {
#[must_use]
pub fn new(config: AnalysisConfig) -> Self {
Self { config }
}
pub fn analyze(&self, samples: &[f32], _sample_rate: f32) -> Result<DynamicsResult> {
if samples.is_empty() {
return Ok(DynamicsResult::default());
}
let rms_values = super::rms::rms_over_time(samples, self.config.hop_size);
let peak = samples.iter().map(|&x| x.abs()).fold(0.0_f32, f32::max);
let rms = super::rms::rms_level(samples);
let crest = super::crest::crest_factor(samples);
let max_rms = rms_values.iter().copied().fold(0.0_f32, f32::max);
let min_rms = rms_values
.iter()
.copied()
.filter(|&x| x > 1e-6)
.fold(f32::INFINITY, f32::min);
let dynamic_range_db = if min_rms > 0.0 && min_rms.is_finite() {
amplitude_to_db(max_rms) - amplitude_to_db(min_rms)
} else {
0.0
};
let mean_rms = rms_values.iter().sum::<f32>() / rms_values.len() as f32;
let variance = rms_values
.iter()
.map(|&x| (x - mean_rms).powi(2))
.sum::<f32>()
/ rms_values.len() as f32;
let loudness_variation = variance.sqrt();
Ok(DynamicsResult {
peak,
rms,
crest,
dynamic_range_db,
loudness_variation,
rms_over_time: rms_values,
})
}
}
#[derive(Debug, Clone)]
pub struct DynamicsResult {
pub peak: f32,
pub rms: f32,
pub crest: f32,
pub dynamic_range_db: f32,
pub loudness_variation: f32,
pub rms_over_time: Vec<f32>,
}
impl Default for DynamicsResult {
fn default() -> Self {
Self {
peak: 0.0,
rms: 0.0,
crest: 0.0,
dynamic_range_db: 0.0,
loudness_variation: 0.0,
rms_over_time: Vec::new(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_dynamics_analyzer() {
let config = AnalysisConfig::default();
let analyzer = DynamicsAnalyzer::new(config);
let mut samples = Vec::new();
for i in 0..44100 {
let amp = (i as f32 / 10000.0).sin().abs() * 0.5 + 0.1;
samples.push(amp * (2.0 * std::f32::consts::PI * 440.0 * i as f32 / 44100.0).sin());
}
let result = analyzer.analyze(&samples, 44100.0).unwrap();
assert!(result.peak > 0.0);
assert!(result.rms > 0.0);
assert!(result.crest > 1.0);
assert!(result.dynamic_range_db > 0.0);
}
}