use crate::loudness::{integrated_loudness, true_peak_dbtp};
#[derive(Debug, Clone)]
pub struct Stats {
pub sample_rate: u32,
pub channels: usize,
pub duration_sec: f32,
pub samples: usize,
pub peak_dbfs: f32,
pub rms_dbfs: f32,
pub crest_factor_db: f32,
pub integrated_lufs: f32,
pub true_peak_dbtp: f32,
pub dc_offset: f32,
pub channel_peaks: Vec<f32>,
pub channel_rms: Vec<f32>,
}
pub fn compute_stats(channels: &[Vec<f32>], sample_rate: u32) -> Option<Stats> {
if channels.is_empty() || channels[0].is_empty() {
return None;
}
let n = channels[0].len();
let samples = n;
let duration_sec = n as f32 / sample_rate as f32;
let mut peak_sq = 0.0f32;
let mut rms_acc = 0.0f64;
let mut dc_sum = 0.0f64;
let total = (n * channels.len()) as f64;
let mut channel_peaks = Vec::with_capacity(channels.len());
let mut channel_rms = Vec::with_capacity(channels.len());
for ch in channels {
let mut ch_peak = 0.0f32;
let mut ch_sum_sq = 0.0f64;
for &s in ch {
let abs = s.abs();
if abs > ch_peak {
ch_peak = abs;
}
ch_sum_sq += (s as f64) * (s as f64);
dc_sum += s as f64;
}
if ch_peak > peak_sq {
peak_sq = ch_peak;
}
rms_acc += ch_sum_sq;
channel_peaks.push(20.0 * ch_peak.max(1e-10).log10());
let ch_rms = (ch_sum_sq / n as f64).sqrt() as f32;
channel_rms.push(20.0 * ch_rms.max(1e-10).log10());
}
let rms = (rms_acc / total).sqrt() as f32;
let peak_dbfs = 20.0 * peak_sq.max(1e-10).log10();
let rms_dbfs = 20.0 * rms.max(1e-10).log10();
let crest_factor_db = peak_dbfs - rms_dbfs;
let dc_offset = (dc_sum / total) as f32;
let integrated_lufs = integrated_loudness(channels, sample_rate);
let true_peak_dbtp = true_peak_dbtp(channels, sample_rate);
Some(Stats {
sample_rate,
channels: channels.len(),
duration_sec,
samples,
peak_dbfs,
rms_dbfs,
crest_factor_db,
integrated_lufs,
true_peak_dbtp,
dc_offset,
channel_peaks,
channel_rms,
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::generate_wave;
#[test]
fn stats_returns_none_for_empty() {
assert!(compute_stats(&[], 44100).is_none());
assert!(compute_stats(&[vec![]], 44100).is_none());
}
#[test]
fn stats_sine_values() {
let audio = generate_wave(48_000, 1000.0, 1.0, 0.0);
let s = compute_stats(&audio.channels, audio.sample_rate).unwrap();
assert_eq!(s.sample_rate, 48_000);
assert_eq!(s.channels, 1);
assert!(s.duration_sec > 0.99 && s.duration_sec < 1.01);
assert!((s.peak_dbfs - (-6.02)).abs() < 0.1, "peak: {}", s.peak_dbfs);
assert!((s.rms_dbfs - (-9.03)).abs() < 0.2, "rms: {}", s.rms_dbfs);
}
#[test]
fn stats_mono_peak() {
let audio = generate_wave(44_100, 440.0, 2.0, 0.0);
let s = compute_stats(&audio.channels, audio.sample_rate).unwrap();
assert!((s.channel_peaks[0] - (-6.02)).abs() < 0.1);
}
}