use crate::ballistics::{BallisticProcessor, BallisticType};
use crate::{MeteringError, MeteringResult};
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum PeakMeterType {
Sample,
Rms(f64),
Vu,
PpmEbu,
PpmBbc,
PpmDin,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum KSystemType {
K12,
K14,
K20,
}
impl KSystemType {
pub fn reference_dbfs(&self) -> f64 {
match self {
Self::K12 => -12.0,
Self::K14 => -14.0,
Self::K20 => -20.0,
}
}
pub fn headroom_db(&self) -> f64 {
match self {
Self::K12 => 12.0,
Self::K14 => 14.0,
Self::K20 => 20.0,
}
}
}
pub struct PeakMeter {
meter_type: PeakMeterType,
sample_rate: f64,
channels: usize,
ballistics: Option<Vec<BallisticProcessor>>,
rms_buffer: Vec<Vec<f64>>,
rms_buffer_size: usize,
rms_write_pos: usize,
peak_values: Vec<f64>,
rms_values: Vec<f64>,
peak_hold_time: f64,
}
impl PeakMeter {
pub fn new(
meter_type: PeakMeterType,
sample_rate: f64,
channels: usize,
peak_hold_time: f64,
) -> MeteringResult<Self> {
if sample_rate <= 0.0 {
return Err(MeteringError::InvalidConfig(
"Sample rate must be positive".to_string(),
));
}
if channels == 0 {
return Err(MeteringError::InvalidConfig(
"Must have at least one channel".to_string(),
));
}
let ballistics = match meter_type {
PeakMeterType::Vu => Some(
(0..channels)
.map(|_| {
BallisticProcessor::new(BallisticType::Vu, sample_rate, peak_hold_time)
})
.collect(),
),
PeakMeterType::PpmEbu => Some(
(0..channels)
.map(|_| {
BallisticProcessor::new(BallisticType::PpmEbu, sample_rate, peak_hold_time)
})
.collect(),
),
PeakMeterType::PpmBbc => Some(
(0..channels)
.map(|_| {
BallisticProcessor::new(BallisticType::PpmBbc, sample_rate, peak_hold_time)
})
.collect(),
),
PeakMeterType::PpmDin => Some(
(0..channels)
.map(|_| {
BallisticProcessor::new(BallisticType::PpmDin, sample_rate, peak_hold_time)
})
.collect(),
),
_ => None,
};
let rms_buffer_size = if let PeakMeterType::Rms(integration_time) = meter_type {
(integration_time * sample_rate) as usize
} else {
0
};
let rms_buffer = if rms_buffer_size > 0 {
vec![vec![0.0; rms_buffer_size]; channels]
} else {
vec![]
};
Ok(Self {
meter_type,
sample_rate,
channels,
ballistics,
rms_buffer,
rms_buffer_size,
rms_write_pos: 0,
peak_values: vec![0.0; channels],
rms_values: vec![0.0; channels],
peak_hold_time,
})
}
pub fn process_interleaved(&mut self, samples: &[f64]) {
let num_frames = samples.len() / self.channels;
for frame_idx in 0..num_frames {
for ch in 0..self.channels {
let sample_idx = frame_idx * self.channels + ch;
let sample = samples[sample_idx];
self.process_sample(ch, sample);
}
}
}
fn process_sample(&mut self, channel: usize, sample: f64) {
let abs_sample = sample.abs();
match self.meter_type {
PeakMeterType::Sample => {
if abs_sample > self.peak_values[channel] {
self.peak_values[channel] = abs_sample;
}
}
PeakMeterType::Rms(_) => {
let squared = sample * sample;
self.rms_buffer[channel][self.rms_write_pos] = squared;
let sum: f64 = self.rms_buffer[channel].iter().sum();
self.rms_values[channel] = (sum / self.rms_buffer_size as f64).sqrt();
}
PeakMeterType::Vu
| PeakMeterType::PpmEbu
| PeakMeterType::PpmBbc
| PeakMeterType::PpmDin => {
if let Some(ref mut ballistics) = self.ballistics {
let filtered = ballistics[channel].process(abs_sample);
self.peak_values[channel] = filtered;
}
}
}
}
pub fn peak_linear(&self) -> Vec<f64> {
match self.meter_type {
PeakMeterType::Rms(_) => self.rms_values.clone(),
_ => self.peak_values.clone(),
}
}
pub fn peak_dbfs(&self) -> Vec<f64> {
self.peak_linear()
.iter()
.map(|&peak| linear_to_dbfs(peak))
.collect()
}
pub fn peak_hold_linear(&self) -> Vec<f64> {
if let Some(ref ballistics) = self.ballistics {
ballistics
.iter()
.map(super::ballistics::BallisticProcessor::peak_hold_value)
.collect()
} else {
self.peak_values.clone()
}
}
pub fn peak_hold_dbfs(&self) -> Vec<f64> {
self.peak_hold_linear()
.iter()
.map(|&peak| linear_to_dbfs(peak))
.collect()
}
pub fn max_peak_linear(&self) -> f64 {
self.peak_linear().iter().copied().fold(0.0, f64::max)
}
pub fn max_peak_dbfs(&self) -> f64 {
linear_to_dbfs(self.max_peak_linear())
}
pub fn reset(&mut self) {
self.peak_values.fill(0.0);
self.rms_values.fill(0.0);
self.rms_write_pos = 0;
for buffer in &mut self.rms_buffer {
buffer.fill(0.0);
}
if let Some(ref mut ballistics) = self.ballistics {
for b in ballistics {
b.reset();
}
}
}
pub fn advance_rms_buffer(&mut self) {
if self.rms_buffer_size > 0 {
self.rms_write_pos = (self.rms_write_pos + 1) % self.rms_buffer_size;
}
}
}
pub struct KSystemMeter {
k_type: KSystemType,
rms_meter: PeakMeter,
peak_meter: PeakMeter,
}
impl KSystemMeter {
pub fn new(k_type: KSystemType, sample_rate: f64, channels: usize) -> MeteringResult<Self> {
let rms_meter = PeakMeter::new(
PeakMeterType::Rms(0.6),
sample_rate,
channels,
2.0, )?;
let peak_meter = PeakMeter::new(
PeakMeterType::Sample,
sample_rate,
channels,
2.0, )?;
Ok(Self {
k_type,
rms_meter,
peak_meter,
})
}
pub fn process_interleaved(&mut self, samples: &[f64]) {
self.rms_meter.process_interleaved(samples);
self.peak_meter.process_interleaved(samples);
self.rms_meter.advance_rms_buffer();
}
pub fn rms_relative_db(&self) -> Vec<f64> {
let reference = self.k_type.reference_dbfs();
self.rms_meter
.peak_dbfs()
.iter()
.map(|&dbfs| dbfs - reference)
.collect()
}
pub fn peak_relative_db(&self) -> Vec<f64> {
let reference = self.k_type.reference_dbfs();
self.peak_meter
.peak_dbfs()
.iter()
.map(|&dbfs| dbfs - reference)
.collect()
}
pub fn rms_dbfs(&self) -> Vec<f64> {
self.rms_meter.peak_dbfs()
}
pub fn peak_dbfs(&self) -> Vec<f64> {
self.peak_meter.peak_dbfs()
}
pub fn is_overload(&self) -> bool {
let headroom = self.k_type.headroom_db();
self.peak_relative_db()
.iter()
.any(|&level| level > headroom)
}
pub fn reset(&mut self) {
self.rms_meter.reset();
self.peak_meter.reset();
}
pub fn k_type(&self) -> KSystemType {
self.k_type
}
}
pub fn linear_to_dbfs(linear: f64) -> f64 {
if linear > 0.0 {
20.0 * linear.log10()
} else {
f64::NEG_INFINITY
}
}
pub fn dbfs_to_linear(dbfs: f64) -> f64 {
if dbfs.is_finite() {
10.0_f64.powf(dbfs / 20.0)
} else {
0.0
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sample_peak_meter() {
let mut meter = PeakMeter::new(PeakMeterType::Sample, 48000.0, 2, 0.0)
.expect("test expectation failed");
let samples = vec![0.5, 0.3, 0.8, 0.4, 0.6, 0.2];
meter.process_interleaved(&samples);
let peaks = meter.peak_linear();
assert_eq!(peaks[0], 0.8);
assert_eq!(peaks[1], 0.4);
}
#[test]
fn test_rms_meter() {
let mut meter = PeakMeter::new(PeakMeterType::Rms(0.1), 48000.0, 1, 0.0)
.expect("test expectation failed");
for i in 0..4800 {
let t = i as f64 / 48000.0;
let sample = (2.0 * std::f64::consts::PI * 1000.0 * t).sin();
meter.process_interleaved(&[sample]);
meter.advance_rms_buffer();
}
let rms = meter.peak_linear()[0];
assert!((rms - 0.707).abs() < 0.01);
}
#[test]
fn test_vu_meter() {
let mut meter =
PeakMeter::new(PeakMeterType::Vu, 48000.0, 1, 0.0).expect("test expectation failed");
for _ in 0..20000 {
meter.process_interleaved(&[1.0]);
}
let peak = meter.peak_linear()[0];
assert!(peak < 1.0, "Peak {:.3} should be < 1.0", peak);
assert!(
peak > 0.6,
"Peak {:.3} should be > 0.6 after VU integration",
peak
);
}
#[test]
fn test_k12_meter() {
let mut meter =
KSystemMeter::new(KSystemType::K12, 48000.0, 2).expect("test expectation failed");
let samples = vec![0.5, 0.5, 0.5, 0.5];
meter.process_interleaved(&samples);
assert_eq!(meter.k_type().reference_dbfs(), -12.0);
assert_eq!(meter.k_type().headroom_db(), 12.0);
}
#[test]
fn test_linear_dbfs_conversion() {
assert_eq!(linear_to_dbfs(1.0), 0.0);
assert_eq!(linear_to_dbfs(0.5), 20.0 * 0.5_f64.log10());
assert!(linear_to_dbfs(0.0).is_infinite());
assert_eq!(dbfs_to_linear(0.0), 1.0);
assert!((dbfs_to_linear(-6.0) - 0.501).abs() < 0.01);
}
#[test]
fn test_peak_meter_reset() {
let mut meter = PeakMeter::new(PeakMeterType::Sample, 48000.0, 2, 0.0)
.expect("test expectation failed");
meter.process_interleaved(&[0.8, 0.9]);
meter.reset();
let peaks = meter.peak_linear();
assert_eq!(peaks[0], 0.0);
assert_eq!(peaks[1], 0.0);
}
}
#[derive(Clone, Debug)]
pub struct PeakLevel {
pub linear: f64,
}
impl PeakLevel {
pub fn new(linear: f64) -> Self {
Self { linear }
}
pub fn to_dbfs(&self) -> f64 {
linear_to_dbfs(self.linear)
}
pub fn is_clipping(&self) -> bool {
self.linear >= 1.0
}
}
#[derive(Debug)]
pub struct SingleChannelPeakMeter {
current_peak: f64,
held_peak: f64,
sample_count: u64,
}
impl SingleChannelPeakMeter {
pub fn new() -> Self {
Self {
current_peak: 0.0,
held_peak: 0.0,
sample_count: 0,
}
}
pub fn push_sample(&mut self, sample: f64) {
let abs = sample.abs();
if abs > self.current_peak {
self.current_peak = abs;
}
if abs > self.held_peak {
self.held_peak = abs;
}
self.sample_count += 1;
}
pub fn push_slice(&mut self, samples: &[f64]) {
for &s in samples {
self.push_sample(s);
}
}
pub fn peak_dbfs(&self) -> f64 {
linear_to_dbfs(self.current_peak)
}
pub fn hold_peak(&self) -> f64 {
linear_to_dbfs(self.held_peak)
}
pub fn reset_hold(&mut self) {
self.held_peak = self.current_peak;
}
pub fn reset(&mut self) {
self.current_peak = 0.0;
self.held_peak = 0.0;
self.sample_count = 0;
}
pub fn sample_count(&self) -> u64 {
self.sample_count
}
pub fn level(&self) -> PeakLevel {
PeakLevel::new(self.current_peak)
}
}
impl Default for SingleChannelPeakMeter {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone, Debug)]
pub struct PeakEntry {
pub linear: f64,
pub sample_index: u64,
}
#[derive(Debug)]
pub struct PeakHistory {
entries: Vec<PeakEntry>,
capacity: usize,
}
impl PeakHistory {
pub fn with_capacity(capacity: usize) -> Self {
Self {
entries: Vec::with_capacity(capacity),
capacity: capacity.max(1),
}
}
pub fn record(&mut self, linear: f64, sample_index: u64) {
if self.entries.len() >= self.capacity {
self.entries.remove(0);
}
self.entries.push(PeakEntry {
linear,
sample_index,
});
}
pub fn max_peak(&self) -> f64 {
self.entries
.iter()
.map(|e| e.linear)
.fold(0.0_f64, f64::max)
}
pub fn max_peak_dbfs(&self) -> f64 {
linear_to_dbfs(self.max_peak())
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
pub fn entries(&self) -> &[PeakEntry] {
&self.entries
}
pub fn clear(&mut self) {
self.entries.clear();
}
}
#[cfg(test)]
mod peak_meter_tests {
use super::*;
#[test]
fn peak_level_zero_is_neg_inf_dbfs() {
let lvl = PeakLevel::new(0.0);
assert!(lvl.to_dbfs().is_infinite() && lvl.to_dbfs() < 0.0);
}
#[test]
fn peak_level_full_scale_is_zero_dbfs() {
let lvl = PeakLevel::new(1.0);
assert!((lvl.to_dbfs() - 0.0).abs() < 1e-9);
}
#[test]
fn peak_level_half_is_minus6dbfs() {
let lvl = PeakLevel::new(0.5);
assert!((lvl.to_dbfs() - (-6.020_599_913_279_624)).abs() < 1e-6);
}
#[test]
fn peak_level_clipping_at_or_above_one() {
assert!(PeakLevel::new(1.0).is_clipping());
assert!(PeakLevel::new(1.5).is_clipping());
assert!(!PeakLevel::new(0.99).is_clipping());
}
#[test]
fn sc_peak_meter_default_starts_empty() {
let m = SingleChannelPeakMeter::default();
assert_eq!(m.sample_count(), 0);
assert!(m.peak_dbfs().is_infinite() && m.peak_dbfs() < 0.0);
}
#[test]
fn sc_peak_meter_push_sample_tracks_peak() {
let mut m = SingleChannelPeakMeter::new();
m.push_sample(0.3);
m.push_sample(0.8);
m.push_sample(0.1);
assert!((m.peak_dbfs() - linear_to_dbfs(0.8)).abs() < 1e-9);
}
#[test]
fn sc_peak_meter_negative_sample_uses_absolute() {
let mut m = SingleChannelPeakMeter::new();
m.push_sample(-0.9);
assert!((m.peak_dbfs() - linear_to_dbfs(0.9)).abs() < 1e-9);
}
#[test]
fn sc_peak_meter_reset_clears_all() {
let mut m = SingleChannelPeakMeter::new();
m.push_sample(0.7);
m.reset();
assert_eq!(m.sample_count(), 0);
assert!(m.peak_dbfs().is_infinite() && m.peak_dbfs() < 0.0);
}
#[test]
fn sc_peak_meter_push_slice() {
let mut m = SingleChannelPeakMeter::new();
m.push_slice(&[0.1, 0.4, 0.2]);
assert_eq!(m.sample_count(), 3);
assert!((m.peak_dbfs() - linear_to_dbfs(0.4)).abs() < 1e-9);
}
#[test]
fn peak_history_empty_on_creation() {
let h = PeakHistory::with_capacity(10);
assert!(h.is_empty());
assert_eq!(h.len(), 0);
}
#[test]
fn peak_history_record_and_max() {
let mut h = PeakHistory::with_capacity(5);
h.record(0.3, 0);
h.record(0.9, 1);
h.record(0.5, 2);
assert!((h.max_peak() - 0.9).abs() < 1e-9);
}
#[test]
fn peak_history_evicts_oldest_when_full() {
let mut h = PeakHistory::with_capacity(3);
h.record(0.9, 0);
h.record(0.2, 1);
h.record(0.3, 2);
h.record(0.4, 3);
assert_eq!(h.len(), 3);
assert!((h.max_peak() - 0.4).abs() < 1e-9);
}
#[test]
fn peak_history_clear_resets() {
let mut h = PeakHistory::with_capacity(5);
h.record(0.5, 0);
h.clear();
assert!(h.is_empty());
}
}