use alloc::vec::Vec;
#[rustfmt::skip]
const FIR_COEFFS: [[f64; 12]; 4] = [
[
0.001_708_984_375_0,
0.010_986_328_125_0,
-0.019_653_320_312_5,
0.033_203_125_000_0,
-0.059_448_242_187_5,
0.137_329_101_562_5,
0.972_167_968_750_0,
-0.102_294_921_875_0,
0.047_607_421_875_0,
-0.026_611_328_125_0,
0.014_892_578_125_0,
-0.008_300_781_250_0,
],
[
-0.029_174_804_687_5,
0.029_296_875_000_0,
-0.051_757_812_500_0,
0.089_111_328_125_0,
-0.166_503_906_250_0,
0.465_087_890_625_0,
0.779_785_156_250_0,
-0.200_317_382_812_5,
0.101_562_500_000_0,
-0.058_227_539_062_5,
0.033_081_054_687_5,
-0.018_920_898_437_5,
],
[
-0.018_920_898_437_5,
0.033_081_054_687_5,
-0.058_227_539_062_5,
0.101_562_500_000_0,
-0.200_317_382_812_5,
0.779_785_156_250_0,
0.465_087_890_625_0,
-0.166_503_906_250_0,
0.089_111_328_125_0,
-0.051_757_812_500_0,
0.029_296_875_000_0,
-0.029_174_804_687_5,
],
[
-0.008_300_781_250_0,
0.014_892_578_125_0,
-0.026_611_328_125_0,
0.047_607_421_875_0,
-0.102_294_921_875_0,
0.972_167_968_750_0,
0.137_329_101_562_5,
-0.059_448_242_187_5,
0.033_203_125_000_0,
-0.019_653_320_312_5,
0.010_986_328_125_0,
0.001_708_984_375_0,
],
];
#[derive(Debug, Clone)]
pub struct TruePeakMeter {
buffer: Vec<f64>,
max_sample: f64,
}
impl TruePeakMeter {
#[must_use]
pub fn new() -> Self {
Self {
buffer: Vec::new(),
max_sample: 0.0,
}
}
pub fn reset(&mut self) {
self.buffer.clear();
self.max_sample = 0.0;
}
pub fn push_f32(&mut self, sample: f32) -> Result<(), crate::Error> {
self.push_f64(f64::from(sample))
}
pub fn push_f64(&mut self, sample: f64) -> Result<(), crate::Error> {
if !sample.is_finite() {
return Err(crate::Error::NonFiniteSample {
index: self.buffer.len(),
channel: 0,
value: sample,
});
}
self.buffer.push(sample);
Ok(())
}
pub fn push_f32_slice(&mut self, samples: &[f32]) -> Result<(), crate::Error> {
self.buffer.reserve(samples.len());
for (i, &s) in samples.iter().enumerate() {
let v = f64::from(s);
if !v.is_finite() {
return Err(crate::Error::NonFiniteSample {
index: self.buffer.len() + i,
channel: 0,
value: v,
});
}
self.buffer.push(v);
}
Ok(())
}
pub fn push_f64_slice(&mut self, samples: &[f64]) -> Result<(), crate::Error> {
for (i, &s) in samples.iter().enumerate() {
if !s.is_finite() {
return Err(crate::Error::NonFiniteSample {
index: self.buffer.len() + i,
channel: 0,
value: s,
});
}
}
self.buffer.extend_from_slice(samples);
Ok(())
}
pub fn finish(&mut self) -> f64 {
let oversampled = Self::oversample_4x(&self.buffer);
for &sample in &oversampled {
let abs = libm::fabs(sample);
if abs > self.max_sample {
self.max_sample = abs;
}
}
if self.max_sample <= 0.0 {
f64::NEG_INFINITY
} else {
20.0 * libm::log10(self.max_sample)
}
}
#[must_use]
pub fn current_level(&self) -> f64 {
let oversampled = Self::oversample_4x(&self.buffer);
let mut max_val = 0.0f64;
for &sample in &oversampled {
let abs = libm::fabs(sample);
if abs > max_val {
max_val = abs;
}
}
if max_val <= 0.0 {
f64::NEG_INFINITY
} else {
20.0 * libm::log10(max_val)
}
}
fn oversample_4x(input: &[f64]) -> Vec<f64> {
let n = input.len();
if n == 0 {
return Vec::new();
}
let mut output = Vec::with_capacity(n * 4);
for i in 0..n {
for (phase, taps) in FIR_COEFFS.iter().enumerate() {
let mut sum = 0.0f64;
for (t, &coeff) in taps.iter().enumerate() {
let input_idx = i as isize - t as isize;
let sample = if input_idx >= 0 {
input[input_idx as usize]
} else {
0.0
};
sum += coeff * sample;
}
let _ = phase; output.push(sum);
}
}
output
}
}
impl Default for TruePeakMeter {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_meter_returns_neg_infinity() {
let mut m = TruePeakMeter::new();
assert!(m.finish().is_infinite() && m.finish() < 0.0);
}
#[test]
fn zero_input_returns_neg_infinity() {
let mut m = TruePeakMeter::new();
m.push_f64(0.0).unwrap();
assert!(m.finish().is_infinite() && m.finish() < 0.0);
}
#[test]
fn full_scale_dc() {
let mut m = TruePeakMeter::new();
for _ in 0..192 {
m.push_f64(1.0).unwrap();
}
let level = m.finish();
assert!((level - 0.0).abs() < 1.1, "got {level}");
}
#[test]
fn half_scale_dc() {
let mut m = TruePeakMeter::new();
for _ in 0..192 {
m.push_f64(0.5).unwrap();
}
let level = m.finish();
assert!((level - (-6.02)).abs() < 1.1, "got {level}");
}
#[test]
fn half_scale_sine() {
let mut m = TruePeakMeter::new();
let fs = 48_000.0;
let freq = 1000.0;
let n = 19200;
for i in 0..n {
let t = i as f64 / fs;
let val = 0.5 * (2.0 * core::f64::consts::PI * freq * t).sin();
m.push_f64(val).unwrap();
}
let level = m.finish();
assert!((level - (-6.02)).abs() < 0.5, "got {level}");
}
#[test]
fn rejects_nan_f64() {
let mut m = TruePeakMeter::new();
let err = m.push_f64(f64::NAN).unwrap_err();
assert!(format!("{err}").contains("non-finite"));
}
#[test]
fn rejects_inf_f32() {
let mut m = TruePeakMeter::new();
let err = m.push_f32(f32::INFINITY).unwrap_err();
assert!(format!("{err}").contains("non-finite"));
}
#[test]
fn rejects_non_finite_in_slice() {
let mut m = TruePeakMeter::new();
let err = m
.push_f64_slice(&[0.5, f64::NEG_INFINITY, 0.5])
.unwrap_err();
let msg = format!("{err}");
assert!(msg.contains("non-finite"), "got: {msg}");
}
#[test]
fn meter_not_poisoned_after_nan_rejection() {
let mut m = TruePeakMeter::new();
m.push_f64(0.5).unwrap();
let _ = m.push_f64(f64::NAN);
for _ in 0..192 {
m.push_f64(0.5).unwrap();
}
let level = m.finish();
assert!(level.is_finite(), "meter was poisoned: got {level}");
assert!((level - (-6.02)).abs() < 1.1, "got {level}");
}
}