#![forbid(unsafe_code)]
#![allow(clippy::cast_lossless)]
use std::f64::consts::PI;
const OVERSAMPLE_FACTOR: usize = 4;
#[derive(Clone, Debug)]
pub struct TruePeakDetector {
sample_rate: f64,
channels: usize,
detectors: Vec<ChannelPeakDetector>,
}
impl TruePeakDetector {
#[must_use]
pub fn new(sample_rate: f64, channels: usize) -> Self {
let detectors = (0..channels)
.map(|_| ChannelPeakDetector::new(OVERSAMPLE_FACTOR))
.collect();
Self {
sample_rate,
channels,
detectors,
}
}
pub fn process_interleaved(&mut self, samples: &[f64]) -> f64 {
if samples.is_empty() || self.channels == 0 {
return 0.0;
}
let frames = samples.len() / self.channels;
let mut max_peak: f64 = 0.0;
for frame in 0..frames {
for ch in 0..self.channels {
let idx = frame * self.channels + ch;
let sample = samples.get(idx).copied().unwrap_or(0.0);
let peak = self.detectors[ch].process(sample);
max_peak = max_peak.max(peak);
}
}
max_peak
}
pub fn process_planar(&mut self, channels: &[&[f64]]) -> f64 {
let mut max_peak: f64 = 0.0;
for (ch_idx, samples) in channels.iter().enumerate() {
if ch_idx < self.detectors.len() {
for &sample in samples.iter() {
let peak = self.detectors[ch_idx].process(sample);
max_peak = max_peak.max(peak);
}
}
}
max_peak
}
#[must_use]
pub fn get_channel_peak(&self, channel: usize) -> f64 {
self.detectors.get(channel).map_or(0.0, |d| d.peak)
}
#[must_use]
pub fn get_all_peaks(&self) -> Vec<f64> {
self.detectors.iter().map(|d| d.peak).collect()
}
pub fn reset(&mut self) {
for detector in &mut self.detectors {
detector.reset();
}
}
#[must_use]
pub fn linear_to_dbtp(linear: f64) -> f64 {
if linear <= 0.0 {
f64::NEG_INFINITY
} else {
20.0 * linear.log10()
}
}
#[must_use]
pub fn dbtp_to_linear(dbtp: f64) -> f64 {
if dbtp.is_infinite() && dbtp.is_sign_negative() {
0.0
} else {
10.0_f64.powf(dbtp / 20.0)
}
}
#[must_use]
pub fn oversample_factor() -> usize {
OVERSAMPLE_FACTOR
}
}
#[derive(Clone, Debug)]
struct ChannelPeakDetector {
oversample: usize,
filters: Vec<OversampleFilter>,
peak: f64,
}
impl ChannelPeakDetector {
fn new(oversample: usize) -> Self {
let filters = (0..oversample)
.map(|phase| OversampleFilter::new(phase, oversample))
.collect();
Self {
oversample,
filters,
peak: 0.0,
}
}
fn process(&mut self, sample: f64) -> f64 {
for filter in &mut self.filters {
let upsampled = filter.process(sample);
let abs_val = upsampled.abs();
self.peak = self.peak.max(abs_val);
}
self.peak
}
fn reset(&mut self) {
self.peak = 0.0;
for filter in &mut self.filters {
filter.reset();
}
}
}
#[derive(Clone, Debug)]
struct OversampleFilter {
coeffs: Vec<f64>,
delay_line: Vec<f64>,
write_pos: usize,
}
impl OversampleFilter {
fn new(phase: usize, oversample: usize) -> Self {
let taps_per_phase = 12;
let coeffs = Self::design_filter(phase, oversample, taps_per_phase);
let delay_line = vec![0.0; taps_per_phase];
Self {
coeffs,
delay_line,
write_pos: 0,
}
}
fn design_filter(phase: usize, oversample: usize, length: usize) -> Vec<f64> {
let mut coeffs = Vec::with_capacity(length);
let center = (length - 1) as f64 / 2.0;
for i in 0..length {
let x = i as f64 - center + phase as f64 / oversample as f64;
let sinc = if x.abs() < 1e-10 {
1.0
} else {
let pi_x = PI * x;
pi_x.sin() / pi_x
};
let window = 0.54 - 0.46 * (2.0 * PI * i as f64 / (length - 1) as f64).cos();
coeffs.push(sinc * window);
}
let sum: f64 = coeffs.iter().sum();
if sum.abs() > 1e-10 {
for coeff in &mut coeffs {
*coeff /= sum;
}
}
coeffs
}
fn process(&mut self, sample: f64) -> f64 {
self.delay_line[self.write_pos] = sample;
self.write_pos = (self.write_pos + 1) % self.delay_line.len();
let mut output = 0.0;
let mut read_pos = self.write_pos;
for &coeff in &self.coeffs {
output += coeff * self.delay_line[read_pos];
read_pos = (read_pos + 1) % self.delay_line.len();
}
output
}
fn reset(&mut self) {
self.delay_line.fill(0.0);
self.write_pos = 0;
}
}
#[derive(Clone, Debug, Default)]
pub struct SamplePeakDetector {
peaks: Vec<f64>,
}
impl SamplePeakDetector {
#[must_use]
pub fn new(channels: usize) -> Self {
Self {
peaks: vec![0.0; channels],
}
}
pub fn process_interleaved(&mut self, samples: &[f64], channels: usize) {
if channels == 0 || channels != self.peaks.len() {
return;
}
let frames = samples.len() / channels;
for frame in 0..frames {
for ch in 0..channels {
let idx = frame * channels + ch;
if let Some(&sample) = samples.get(idx) {
self.peaks[ch] = self.peaks[ch].max(sample.abs());
}
}
}
}
pub fn process_planar(&mut self, channels: &[&[f64]]) {
for (ch_idx, samples) in channels.iter().enumerate() {
if ch_idx < self.peaks.len() {
for &sample in samples.iter() {
self.peaks[ch_idx] = self.peaks[ch_idx].max(sample.abs());
}
}
}
}
#[must_use]
pub fn get_peak(&self, channel: usize) -> f64 {
self.peaks.get(channel).copied().unwrap_or(0.0)
}
#[must_use]
pub fn max_peak(&self) -> f64 {
self.peaks.iter().copied().fold(0.0, f64::max)
}
#[must_use]
pub fn get_all_peaks(&self) -> &[f64] {
&self.peaks
}
pub fn reset(&mut self) {
self.peaks.fill(0.0);
}
}