#![allow(dead_code)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum MeterType {
Loudness,
TruePeak,
LoudnessRange,
Ppm,
Vu,
Rms,
Correlation,
KSystem,
}
impl MeterType {
pub fn name(self) -> &'static str {
match self {
Self::Loudness => "Loudness",
Self::TruePeak => "True Peak",
Self::LoudnessRange => "Loudness Range",
Self::Ppm => "PPM",
Self::Vu => "VU",
Self::Rms => "RMS",
Self::Correlation => "Correlation",
Self::KSystem => "K-System",
}
}
pub fn needs_k_weighting(self) -> bool {
matches!(self, Self::Loudness | Self::LoudnessRange | Self::KSystem)
}
pub fn needs_oversampling(self) -> bool {
matches!(self, Self::TruePeak)
}
pub fn all() -> &'static [MeterType] {
&[
Self::Loudness,
Self::TruePeak,
Self::LoudnessRange,
Self::Ppm,
Self::Vu,
Self::Rms,
Self::Correlation,
Self::KSystem,
]
}
}
impl std::fmt::Display for MeterType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.name())
}
}
#[derive(Debug, Clone)]
pub struct MeterTypeConfig {
pub meter_type: MeterType,
pub enabled: bool,
pub window_ms: f64,
pub attack_ms: f64,
pub release_ms: f64,
pub peak_hold_s: f64,
}
impl MeterTypeConfig {
pub fn default_for(meter_type: MeterType) -> Self {
let (window_ms, attack_ms, release_ms, peak_hold_s) = match meter_type {
MeterType::Loudness => (400.0, 0.0, 0.0, 0.0),
MeterType::TruePeak => (0.0, 0.0, 0.0, 2.0),
MeterType::LoudnessRange => (3_000.0, 0.0, 0.0, 0.0),
MeterType::Ppm => (0.0, 10.0, 1_500.0, 2.0),
MeterType::Vu => (300.0, 300.0, 300.0, 0.0),
MeterType::Rms => (300.0, 0.0, 0.0, 0.0),
MeterType::Correlation => (400.0, 0.0, 0.0, 0.0),
MeterType::KSystem => (300.0, 0.0, 0.0, 2.0),
};
Self {
meter_type,
enabled: true,
window_ms,
attack_ms,
release_ms,
peak_hold_s,
}
}
pub fn validate(&self) -> Result<(), String> {
if self.window_ms < 0.0 {
return Err(format!(
"{}: window_ms must be >= 0, got {}",
self.meter_type, self.window_ms
));
}
if self.attack_ms < 0.0 {
return Err(format!(
"{}: attack_ms must be >= 0, got {}",
self.meter_type, self.attack_ms
));
}
if self.release_ms < 0.0 {
return Err(format!(
"{}: release_ms must be >= 0, got {}",
self.meter_type, self.release_ms
));
}
if self.peak_hold_s < 0.0 {
return Err(format!(
"{}: peak_hold_s must be >= 0, got {}",
self.meter_type, self.peak_hold_s
));
}
Ok(())
}
}
#[derive(Debug, Clone, Default)]
pub struct MeterConfigSet {
entries: Vec<MeterTypeConfig>,
}
impl MeterConfigSet {
pub fn new() -> Self {
Self::default()
}
pub fn all_defaults() -> Self {
let entries = MeterType::all()
.iter()
.map(|&t| MeterTypeConfig::default_for(t))
.collect();
Self { entries }
}
pub fn insert(&mut self, config: MeterTypeConfig) {
if let Some(existing) = self
.entries
.iter_mut()
.find(|e| e.meter_type == config.meter_type)
{
*existing = config;
} else {
self.entries.push(config);
}
}
pub fn get(&self, meter_type: MeterType) -> Option<&MeterTypeConfig> {
self.entries.iter().find(|e| e.meter_type == meter_type)
}
pub fn is_enabled(&self, meter_type: MeterType) -> bool {
self.get(meter_type).is_some_and(|c| c.enabled)
}
pub fn disable(&mut self, meter_type: MeterType) {
if let Some(entry) = self.entries.iter_mut().find(|e| e.meter_type == meter_type) {
entry.enabled = false;
}
}
pub fn enable(&mut self, meter_type: MeterType) {
if let Some(entry) = self.entries.iter_mut().find(|e| e.meter_type == meter_type) {
entry.enabled = true;
}
}
pub fn validate_all(&self) -> Vec<String> {
self.entries
.iter()
.filter_map(|e| e.validate().err())
.collect()
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
pub fn iter(&self) -> impl Iterator<Item = &MeterTypeConfig> {
self.entries.iter()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn meter_type_name_loudness() {
assert_eq!(MeterType::Loudness.name(), "Loudness");
}
#[test]
fn meter_type_name_true_peak() {
assert_eq!(MeterType::TruePeak.name(), "True Peak");
}
#[test]
fn meter_type_needs_k_weighting() {
assert!(MeterType::Loudness.needs_k_weighting());
assert!(MeterType::KSystem.needs_k_weighting());
assert!(!MeterType::Vu.needs_k_weighting());
assert!(!MeterType::Ppm.needs_k_weighting());
}
#[test]
fn meter_type_needs_oversampling() {
assert!(MeterType::TruePeak.needs_oversampling());
assert!(!MeterType::Loudness.needs_oversampling());
}
#[test]
fn meter_type_all_has_eight_variants() {
assert_eq!(MeterType::all().len(), 8);
}
#[test]
fn meter_type_display() {
assert_eq!(format!("{}", MeterType::Rms), "RMS");
assert_eq!(format!("{}", MeterType::Correlation), "Correlation");
}
#[test]
fn config_default_vu_window() {
let cfg = MeterTypeConfig::default_for(MeterType::Vu);
assert_eq!(cfg.meter_type, MeterType::Vu);
assert!(cfg.window_ms > 0.0);
assert!(cfg.enabled);
}
#[test]
fn config_validate_ok() {
let cfg = MeterTypeConfig::default_for(MeterType::Ppm);
assert!(cfg.validate().is_ok());
}
#[test]
fn config_validate_negative_window() {
let mut cfg = MeterTypeConfig::default_for(MeterType::Rms);
cfg.window_ms = -1.0;
assert!(cfg.validate().is_err());
}
#[test]
fn config_validate_negative_attack() {
let mut cfg = MeterTypeConfig::default_for(MeterType::Vu);
cfg.attack_ms = -5.0;
assert!(cfg.validate().is_err());
}
#[test]
fn config_set_all_defaults_count() {
let set = MeterConfigSet::all_defaults();
assert_eq!(set.len(), MeterType::all().len());
}
#[test]
fn config_set_insert_and_get() {
let mut set = MeterConfigSet::new();
let cfg = MeterTypeConfig::default_for(MeterType::Loudness);
set.insert(cfg);
assert!(set.get(MeterType::Loudness).is_some());
assert!(set.get(MeterType::Vu).is_none());
}
#[test]
fn config_set_is_enabled() {
let set = MeterConfigSet::all_defaults();
assert!(set.is_enabled(MeterType::TruePeak));
}
#[test]
fn config_set_disable_enable_roundtrip() {
let mut set = MeterConfigSet::all_defaults();
set.disable(MeterType::LoudnessRange);
assert!(!set.is_enabled(MeterType::LoudnessRange));
set.enable(MeterType::LoudnessRange);
assert!(set.is_enabled(MeterType::LoudnessRange));
}
#[test]
fn config_set_validate_all_ok() {
let set = MeterConfigSet::all_defaults();
assert!(set.validate_all().is_empty());
}
#[test]
fn config_set_is_empty() {
let set = MeterConfigSet::new();
assert!(set.is_empty());
}
#[test]
fn config_set_replace_existing() {
let mut set = MeterConfigSet::all_defaults();
let initial_len = set.len();
let mut new_cfg = MeterTypeConfig::default_for(MeterType::Vu);
new_cfg.window_ms = 500.0;
set.insert(new_cfg);
assert_eq!(set.len(), initial_len);
assert_eq!(
set.get(MeterType::Vu)
.expect("get should succeed")
.window_ms,
500.0
);
}
}