use super::lufs;
use super::lufs::apply_k_weighting;
const LRA_BLOCK_MS: u64 = 3000;
const LRA_REL_GATE: f64 = -20.0;
pub fn compute_lra(samples: &[f32], sample_rate: u32) -> f64 {
if samples.is_empty() {
return 0.0;
}
let k_weighted = apply_k_weighting(samples);
let block_samples = (sample_rate as usize * LRA_BLOCK_MS as usize) / 1000;
if block_samples > k_weighted.len() {
return 0.0;
}
let num_blocks = (k_weighted.len() - block_samples) / block_samples + 1;
let hop = block_samples;
let mut short_term_lk: Vec<f64> = Vec::with_capacity(num_blocks);
for b in 0..num_blocks {
let start = b * hop;
let sum_sq: f64 = k_weighted[start..start + block_samples]
.iter()
.map(|&x| (x as f64).powi(2))
.sum();
let power = sum_sq / block_samples as f64;
short_term_lk.push(lufs::power_to_lkfs(power));
}
let abs_gated: Vec<f64> = short_term_lk
.iter()
.filter(|&&lk| lk > lufs::LUFS_ABS_GATE && lk.is_finite())
.copied()
.collect();
if abs_gated.len() < 2 {
return 0.0;
}
let l_asg = abs_gated.iter().sum::<f64>() / abs_gated.len() as f64;
let rel_threshold = l_asg + LRA_REL_GATE;
let mut gated: Vec<f64> = abs_gated
.into_iter()
.filter(|&lk| lk > rel_threshold)
.collect();
if gated.len() < 2 {
return 0.0;
}
gated.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
let p10 = percentile_by_interpolation(&gated, 10.0);
let p95 = percentile_by_interpolation(&gated, 95.0);
p95 - p10
}
fn percentile_by_interpolation(sorted: &[f64], pct: f64) -> f64 {
let n = sorted.len();
if n == 0 {
return 0.0;
}
if n == 1 {
return sorted[0];
}
let rank = (pct / 100.0) * (n - 1) as f64;
let lo = rank.floor() as usize;
let hi = rank.ceil() as usize;
if hi >= n {
return sorted[n - 1];
}
let frac = rank - lo as f64;
sorted[lo] + frac * (sorted[hi] - sorted[lo])
}
pub fn short_term_loudness(samples: &[f32], sample_rate: u32) -> Vec<f64> {
if samples.is_empty() {
return Vec::new();
}
let k_weighted = apply_k_weighting(samples);
let block_samples = (sample_rate as usize * LRA_BLOCK_MS as usize) / 1000;
if block_samples > k_weighted.len() {
return Vec::new();
}
let num_blocks = (k_weighted.len() - block_samples) / block_samples + 1;
let mut values = Vec::with_capacity(num_blocks);
for b in 0..num_blocks {
let start = b * block_samples;
let sum_sq: f64 = k_weighted[start..start + block_samples]
.iter()
.map(|&x| (x as f64).powi(2))
.sum();
values.push(lufs::power_to_lkfs(sum_sq / block_samples as f64));
}
values
}
#[derive(Debug, Clone, PartialEq)]
pub struct LoudnessResult {
pub integrated_lufs: f64,
pub lra: f64,
pub true_peak_db: f64,
pub short_term: Vec<f64>,
}
pub fn measure_loudness(samples: &[f32], sample_rate: u32) -> LoudnessResult {
let integrated_lufs = super::compute_integrated_lufs(samples, sample_rate);
let lra = compute_lra(samples, sample_rate);
let true_peak_db = super::compute_true_peak_db(samples);
let short_term = short_term_loudness(samples, sample_rate);
LoudnessResult {
integrated_lufs,
lra,
true_peak_db,
short_term,
}
}