#![allow(dead_code)]
#![allow(clippy::cast_precision_loss)]
const FLOOR: f32 = 1e-10;
#[must_use]
pub fn compute_peak_db(samples: &[f32]) -> f32 {
if samples.is_empty() {
return f32::NEG_INFINITY;
}
let peak = samples.iter().map(|s| s.abs()).fold(0.0f32, f32::max);
20.0 * (peak + FLOOR).log10()
}
#[must_use]
pub fn compute_rms_db(samples: &[f32]) -> f32 {
if samples.is_empty() {
return f32::NEG_INFINITY;
}
let mean_sq = samples.iter().map(|s| s * s).sum::<f32>() / samples.len() as f32;
20.0 * (mean_sq.sqrt() + FLOOR).log10()
}
#[must_use]
pub fn compute_dr(samples: &[f32]) -> f32 {
if samples.is_empty() {
return 0.0;
}
compute_peak_db(samples) - compute_rms_db(samples)
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct DynamicRangeMeter {
pub peak_db: f32,
pub rms_db: f32,
pub crest_factor_db: f32,
pub dynamic_range_db: f32,
}
impl DynamicRangeMeter {
#[must_use]
pub fn from_samples(samples: &[f32]) -> Self {
let peak_db = compute_peak_db(samples);
let rms_db = compute_rms_db(samples);
let crest_factor_db = if peak_db.is_finite() && rms_db.is_finite() {
peak_db - rms_db
} else {
0.0
};
let dynamic_range_db = crest_factor_db;
Self {
peak_db,
rms_db,
crest_factor_db,
dynamic_range_db,
}
}
#[must_use]
pub fn is_highly_compressed(&self) -> bool {
self.dynamic_range_db < 8.0
}
}
#[derive(Debug, Clone)]
pub struct DrMeasurement {
pub track_id: String,
pub dr_db: f32,
pub peak_db: f32,
pub rms_db: f32,
}
impl DrMeasurement {
#[must_use]
pub fn from_samples(track_id: impl Into<String>, samples: &[f32]) -> Self {
let meter = DynamicRangeMeter::from_samples(samples);
Self {
track_id: track_id.into(),
dr_db: meter.dynamic_range_db,
peak_db: meter.peak_db,
rms_db: meter.rms_db,
}
}
#[must_use]
pub fn dr_score(&self) -> &str {
if self.dr_db >= 20.0 {
"Excellent"
} else if self.dr_db >= 13.0 {
"Good"
} else if self.dr_db >= 8.0 {
"Fair"
} else {
"Poor"
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn constant_signal(amp: f32, n: usize) -> Vec<f32> {
vec![amp; n]
}
fn sine_wave(freq_hz: f32, sample_rate: f32, n: usize) -> Vec<f32> {
(0..n)
.map(|i| (2.0 * std::f32::consts::PI * freq_hz * i as f32 / sample_rate).sin())
.collect()
}
#[test]
fn test_peak_db_full_scale() {
let s = constant_signal(1.0, 100);
let peak = compute_peak_db(&s);
assert!(peak > -0.01 && peak < 0.01, "peak={peak}");
}
#[test]
fn test_peak_db_half_scale() {
let s = constant_signal(0.5, 100);
let peak = compute_peak_db(&s);
assert!(peak < -5.0 && peak > -7.0, "peak={peak}");
}
#[test]
fn test_peak_db_empty_is_neg_inf() {
assert_eq!(compute_peak_db(&[]), f32::NEG_INFINITY);
}
#[test]
fn test_rms_db_constant_signal() {
let s = constant_signal(1.0, 1000);
let rms = compute_rms_db(&s);
assert!(rms > -0.01 && rms < 0.01, "rms={rms}");
}
#[test]
fn test_rms_db_empty_is_neg_inf() {
assert_eq!(compute_rms_db(&[]), f32::NEG_INFINITY);
}
#[test]
fn test_rms_db_sine_approx_minus_3db() {
let s = sine_wave(1_000.0, 44_100.0, 44_100);
let rms = compute_rms_db(&s);
assert!(rms > -4.0 && rms < -2.0, "rms={rms}");
}
#[test]
fn test_dr_empty_is_zero() {
assert!((compute_dr(&[]) - 0.0).abs() < f32::EPSILON);
}
#[test]
fn test_dr_constant_signal_near_zero() {
let s = constant_signal(0.8, 1000);
let dr = compute_dr(&s);
assert!(dr.abs() < 0.5, "dr={dr}");
}
#[test]
fn test_meter_from_samples_fields() {
let s = sine_wave(440.0, 44_100.0, 44_100);
let m = DynamicRangeMeter::from_samples(&s);
assert!(m.peak_db.is_finite());
assert!(m.rms_db.is_finite());
assert!(m.crest_factor_db >= 0.0, "crest={}", m.crest_factor_db);
}
#[test]
fn test_is_highly_compressed_true() {
let m = DynamicRangeMeter {
peak_db: -0.1,
rms_db: -3.0,
crest_factor_db: 5.0,
dynamic_range_db: 5.0,
};
assert!(m.is_highly_compressed());
}
#[test]
fn test_is_highly_compressed_false() {
let m = DynamicRangeMeter {
peak_db: -0.1,
rms_db: -20.0,
crest_factor_db: 19.9,
dynamic_range_db: 19.9,
};
assert!(!m.is_highly_compressed());
}
#[test]
fn test_dr_score_excellent() {
let m = DrMeasurement {
track_id: "t".into(),
dr_db: 22.0,
peak_db: -0.1,
rms_db: -22.1,
};
assert_eq!(m.dr_score(), "Excellent");
}
#[test]
fn test_dr_score_good() {
let m = DrMeasurement {
track_id: "t".into(),
dr_db: 15.0,
peak_db: -0.1,
rms_db: -15.1,
};
assert_eq!(m.dr_score(), "Good");
}
#[test]
fn test_dr_score_fair() {
let m = DrMeasurement {
track_id: "t".into(),
dr_db: 9.0,
peak_db: -0.1,
rms_db: -9.1,
};
assert_eq!(m.dr_score(), "Fair");
}
#[test]
fn test_dr_score_poor() {
let m = DrMeasurement {
track_id: "t".into(),
dr_db: 5.0,
peak_db: -0.1,
rms_db: -5.1,
};
assert_eq!(m.dr_score(), "Poor");
}
#[test]
fn test_dr_measurement_from_samples() {
let s = sine_wave(440.0, 44_100.0, 44_100);
let m = DrMeasurement::from_samples("track-1", &s);
assert_eq!(m.track_id, "track-1");
assert!(m.dr_db >= 0.0);
}
}