use std::collections::VecDeque;
#[derive(Clone, Debug)]
pub struct BallisticsConfig {
pub integration_time: f64,
pub attack_time: f64,
pub release_time: f64,
pub peak_hold_time: f64,
pub return_time: f64,
pub sample_rate: f64,
}
impl BallisticsConfig {
#[must_use]
pub fn vu_meter(sample_rate: f64) -> Self {
Self {
integration_time: 0.300,
attack_time: 0.300,
release_time: 0.300,
peak_hold_time: 0.0,
return_time: 1.0,
sample_rate,
}
}
#[must_use]
pub fn bbc_ppm(sample_rate: f64) -> Self {
Self {
integration_time: 0.010,
attack_time: 0.010,
release_time: 2.8,
peak_hold_time: 1.0,
return_time: 0.0,
sample_rate,
}
}
#[must_use]
pub fn ebu_ppm(sample_rate: f64) -> Self {
Self {
integration_time: 0.010,
attack_time: 0.005,
release_time: 1.7,
peak_hold_time: 0.0,
return_time: 0.0,
sample_rate,
}
}
#[must_use]
pub fn nordic_ppm(sample_rate: f64) -> Self {
Self {
integration_time: 0.005,
attack_time: 0.005,
release_time: 1.5,
peak_hold_time: 0.0,
return_time: 0.0,
sample_rate,
}
}
#[must_use]
pub fn din_ppm(sample_rate: f64) -> Self {
Self {
integration_time: 0.010,
attack_time: 0.010,
release_time: 1.5,
peak_hold_time: 0.0,
return_time: 0.0,
sample_rate,
}
}
#[must_use]
pub fn digital_peak(sample_rate: f64, peak_hold_seconds: f64) -> Self {
Self {
integration_time: 0.0,
attack_time: 0.0,
release_time: 0.0,
peak_hold_time: peak_hold_seconds,
return_time: 0.0,
sample_rate,
}
}
#[must_use]
pub fn rms(sample_rate: f64, window_seconds: f64) -> Self {
Self {
integration_time: window_seconds,
attack_time: window_seconds,
release_time: window_seconds,
peak_hold_time: 0.0,
return_time: 0.0,
sample_rate,
}
}
#[must_use]
pub fn integration_coefficient(&self) -> f64 {
if self.integration_time <= 0.0 {
1.0
} else {
(-1.0 / (self.integration_time * self.sample_rate)).exp()
}
}
#[must_use]
pub fn attack_coefficient(&self) -> f64 {
if self.attack_time <= 0.0 {
1.0
} else {
(-1.0 / (self.attack_time * self.sample_rate)).exp()
}
}
#[must_use]
pub fn release_coefficient(&self) -> f64 {
if self.release_time <= 0.0 {
1.0
} else {
(-1.0 / (self.release_time * self.sample_rate)).exp()
}
}
#[must_use]
pub fn peak_hold_samples(&self) -> usize {
(self.peak_hold_time * self.sample_rate) as usize
}
#[must_use]
pub fn return_coefficient(&self) -> f64 {
if self.return_time <= 0.0 {
0.0
} else {
(-1.0 / (self.return_time * self.sample_rate)).exp()
}
}
}
pub struct BallisticsProcessor {
config: BallisticsConfig,
integrated: f64,
envelope: f64,
peak_hold: f64,
peak_hold_counter: usize,
max_peak: f64,
}
impl BallisticsProcessor {
#[must_use]
pub fn new(config: BallisticsConfig) -> Self {
Self {
config,
integrated: 0.0,
envelope: 0.0,
peak_hold: 0.0,
peak_hold_counter: 0,
max_peak: 0.0,
}
}
pub fn process(&mut self, value: f64) -> f64 {
let integration_coeff = self.config.integration_coefficient();
self.integrated = integration_coeff * self.integrated + (1.0 - integration_coeff) * value;
let target = self.integrated;
let coeff = if target > self.envelope {
self.config.attack_coefficient()
} else {
self.config.release_coefficient()
};
self.envelope = coeff * self.envelope + (1.0 - coeff) * target;
if self.envelope > self.peak_hold {
self.peak_hold = self.envelope;
self.peak_hold_counter = self.config.peak_hold_samples();
self.max_peak = self.max_peak.max(self.peak_hold);
} else if self.peak_hold_counter > 0 {
self.peak_hold_counter -= 1;
} else if self.config.return_time > 0.0 {
let return_coeff = self.config.return_coefficient();
self.peak_hold *= return_coeff;
} else {
self.peak_hold = self.envelope;
}
self.envelope
}
#[must_use]
pub fn envelope(&self) -> f64 {
self.envelope
}
#[must_use]
pub fn peak_hold(&self) -> f64 {
self.peak_hold
}
#[must_use]
pub fn max_peak(&self) -> f64 {
self.max_peak
}
pub fn reset(&mut self) {
self.integrated = 0.0;
self.envelope = 0.0;
self.peak_hold = 0.0;
self.peak_hold_counter = 0;
self.max_peak = 0.0;
}
pub fn reset_peak_hold(&mut self) {
self.peak_hold = self.envelope;
self.peak_hold_counter = 0;
}
pub fn reset_max_peak(&mut self) {
self.max_peak = 0.0;
}
}
pub struct RmsWindow {
buffer: VecDeque<f64>,
window_size: usize,
sum_squares: f64,
}
impl RmsWindow {
#[must_use]
pub fn new(window_seconds: f64, sample_rate: f64) -> Self {
let window_size = (window_seconds * sample_rate) as usize;
Self {
buffer: VecDeque::with_capacity(window_size),
window_size,
sum_squares: 0.0,
}
}
pub fn process(&mut self, sample: f64) -> f64 {
let square = sample * sample;
self.sum_squares += square;
self.buffer.push_back(square);
if self.buffer.len() > self.window_size {
if let Some(old) = self.buffer.pop_front() {
self.sum_squares -= old;
}
}
if self.buffer.is_empty() {
0.0
} else {
(self.sum_squares / self.buffer.len() as f64).sqrt()
}
}
#[must_use]
pub fn rms(&self) -> f64 {
if self.buffer.is_empty() {
0.0
} else {
(self.sum_squares / self.buffer.len() as f64).sqrt()
}
}
pub fn reset(&mut self) {
self.buffer.clear();
self.sum_squares = 0.0;
}
}
pub struct PeakDetector {
peak: f64,
hold_counter: usize,
hold_samples: usize,
decay_rate: f64,
}
impl PeakDetector {
#[must_use]
pub fn new(hold_seconds: f64, decay_seconds: f64, sample_rate: f64) -> Self {
let hold_samples = (hold_seconds * sample_rate) as usize;
let decay_rate = if decay_seconds > 0.0 {
(-1.0 / (decay_seconds * sample_rate)).exp()
} else {
0.0
};
Self {
peak: 0.0,
hold_counter: 0,
hold_samples,
decay_rate,
}
}
pub fn process(&mut self, value: f64) -> f64 {
if value > self.peak {
self.peak = value;
self.hold_counter = self.hold_samples;
} else if self.hold_counter > 0 {
self.hold_counter -= 1;
} else {
self.peak *= self.decay_rate;
}
self.peak
}
#[must_use]
pub fn peak(&self) -> f64 {
self.peak
}
pub fn reset(&mut self) {
self.peak = 0.0;
self.hold_counter = 0;
}
}
pub struct OverloadDetector {
threshold: f64,
overload: bool,
overload_counter: usize,
min_duration: usize,
reset_delay: usize,
reset_counter: usize,
}
impl OverloadDetector {
#[must_use]
pub fn new(
threshold_db: f64,
min_duration_ms: f64,
reset_delay_ms: f64,
sample_rate: f64,
) -> Self {
Self {
threshold: db_to_linear(threshold_db),
overload: false,
overload_counter: 0,
min_duration: ((min_duration_ms / 1000.0) * sample_rate) as usize,
reset_delay: ((reset_delay_ms / 1000.0) * sample_rate) as usize,
reset_counter: 0,
}
}
pub fn process(&mut self, value: f64) -> bool {
if value.abs() >= self.threshold {
self.overload_counter += 1;
self.reset_counter = 0;
if self.overload_counter >= self.min_duration {
self.overload = true;
}
} else {
self.overload_counter = 0;
if self.overload {
self.reset_counter += 1;
if self.reset_counter >= self.reset_delay {
self.overload = false;
self.reset_counter = 0;
}
}
}
self.overload
}
#[must_use]
pub fn is_overload(&self) -> bool {
self.overload
}
pub fn reset(&mut self) {
self.overload = false;
self.overload_counter = 0;
self.reset_counter = 0;
}
}
#[must_use]
pub fn db_to_linear(db: f64) -> f64 {
10.0_f64.powf(db / 20.0)
}
#[must_use]
pub fn linear_to_db(linear: f64) -> f64 {
if linear > 0.0 {
20.0 * linear.log10()
} else {
f64::NEG_INFINITY
}
}
#[must_use]
pub fn clamp(value: f64, min: f64, max: f64) -> f64 {
value.max(min).min(max)
}