use crate::Adapter;
pub trait StatsSample: Copy + PartialOrd {
const ZERO: Self;
fn as_f64(self) -> f64;
}
macro_rules! impl_stats_sample {
($($type:ty),*) => {
$(
impl StatsSample for $type {
const ZERO: Self = 0 as $type;
#[inline]
fn as_f64(self) -> f64 {
self as f64
}
}
)*
};
}
impl_stats_sample!(
i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize, f32, f64
);
fn sqrt_newton(value: f64) -> f64 {
if value.is_nan() {
return value;
}
if value.is_infinite() {
return if value.is_sign_positive() {
f64::INFINITY
} else {
0.0
};
}
if value <= 0.0 {
return 0.0;
}
let mut estimate = f64::from_bits((value.to_bits() + (1023_u64 << 52)) >> 1);
for _ in 0..5 {
estimate = 0.5 * (estimate + value / estimate);
}
estimate
}
pub trait AdapterStats<T>: Adapter<T>
where
T: StatsSample,
{
fn channel_sum(&self, channel: usize) -> f64 {
let mut sum = 0.0;
for frame in 0..self.frames() {
sum += self.read_sample(channel, frame).unwrap_or(T::ZERO).as_f64();
}
sum
}
fn frame_sum(&self, frame: usize) -> f64 {
let mut sum = 0.0;
for channel in 0..self.channels() {
sum += self.read_sample(channel, frame).unwrap_or(T::ZERO).as_f64();
}
sum
}
fn channel_sum_of_squares(&self, channel: usize) -> f64 {
let mut square_sum = 0.0;
for frame in 0..self.frames() {
let sample = self.read_sample(channel, frame).unwrap_or(T::ZERO).as_f64();
square_sum += sample * sample;
}
square_sum
}
fn frame_sum_of_squares(&self, frame: usize) -> f64 {
let mut square_sum = 0.0;
for channel in 0..self.channels() {
let sample = self.read_sample(channel, frame).unwrap_or(T::ZERO).as_f64();
square_sum += sample * sample;
}
square_sum
}
fn channel_rms(&self, channel: usize) -> f64 {
if self.frames() == 0 || self.channels() == 0 {
return 0.0;
}
sqrt_newton(self.channel_sum_of_squares(channel) / self.frames() as f64)
}
fn frame_rms(&self, frame: usize) -> f64 {
if self.frames() == 0 || self.channels() == 0 {
return 0.0;
}
sqrt_newton(self.frame_sum_of_squares(frame) / self.channels() as f64)
}
fn channel_mean(&self, channel: usize) -> f64 {
if self.frames() == 0 || self.channels() == 0 {
return 0.0;
}
self.channel_sum(channel) / self.frames() as f64
}
fn frame_mean(&self, frame: usize) -> f64 {
if self.frames() == 0 || self.channels() == 0 {
return 0.0;
}
self.frame_sum(frame) / self.channels() as f64
}
fn channel_min_and_max(&self, channel: usize) -> (T, T) {
let Some(first) = self.read_sample(channel, 0) else {
return (T::ZERO, T::ZERO);
};
let mut min = first;
let mut max = first;
for frame in 1..self.frames() {
let sample = self.read_sample(channel, frame).unwrap_or(first);
if sample < min {
min = sample;
} else if sample > max {
max = sample;
}
}
(min, max)
}
fn channel_peak_to_peak(&self, channel: usize) -> f64 {
let (min, max) = self.channel_min_and_max(channel);
max.as_f64() - min.as_f64()
}
fn channel_peak(&self, channel: usize) -> f64 {
let (min, max) = self.channel_min_and_max(channel);
min.as_f64().abs().max(max.as_f64().abs())
}
fn frame_min_and_max(&self, frame: usize) -> (T, T) {
let Some(first) = self.read_sample(0, frame) else {
return (T::ZERO, T::ZERO);
};
let mut min = first;
let mut max = first;
for channel in 1..self.channels() {
let sample = self.read_sample(channel, frame).unwrap_or(first);
if sample < min {
min = sample;
} else if sample > max {
max = sample;
}
}
(min, max)
}
fn frame_peak_to_peak(&self, frame: usize) -> f64 {
let (min, max) = self.frame_min_and_max(frame);
max.as_f64() - min.as_f64()
}
fn frame_peak(&self, frame: usize) -> f64 {
let (min, max) = self.frame_min_and_max(frame);
min.as_f64().abs().max(max.as_f64().abs())
}
}
impl<T, U> AdapterStats<T> for U
where
T: StatsSample,
U: Adapter<T>,
{
}
#[cfg(test)]
mod tests {
extern crate alloc;
use super::AdapterStats;
use crate::tests::MinimalAdapter;
use alloc::vec;
#[test]
fn stats_integer() {
let data = vec![1_i32, 1, -1, -1, 1, 1, -1, -1];
let buffer = MinimalAdapter::new_from_vec(data, 2, 4);
assert_eq!(buffer.channel_rms(0), 1.0);
assert_eq!(buffer.channel_min_and_max(0), (-1, 1));
assert_eq!(buffer.channel_peak_to_peak(0), 2.0);
}
#[test]
fn stats_float() {
let data = vec![1.0_f32, 1.0, -1.0, -1.0, 1.0, 1.0, -1.0, -1.0];
let buffer = MinimalAdapter::new_from_vec(data, 2, 4);
assert_eq!(buffer.channel_rms(0), 1.0);
assert_eq!(buffer.channel_min_and_max(0), (-1.0, 1.0));
assert_eq!(buffer.channel_peak_to_peak(0), 2.0);
}
#[test]
fn stats_frame_integer() {
let data = vec![-1_i32, 1, -1, 1, -1, 1, -1, 1];
let buffer = MinimalAdapter::new_from_vec(data, 2, 4);
assert_eq!(buffer.frame_rms(0), 1.0);
assert_eq!(buffer.frame_min_and_max(0), (-1, 1));
assert_eq!(buffer.frame_peak_to_peak(0), 2.0);
}
#[test]
fn stats_frame_float() {
let data = vec![-1.0_f32, 1.0, -1.0, 1.0, -1.0, 1.0, -1.0, 1.0];
let buffer = MinimalAdapter::new_from_vec(data, 2, 4);
assert_eq!(buffer.frame_rms(0), 1.0);
assert_eq!(buffer.frame_min_and_max(0), (-1.0, 1.0));
assert_eq!(buffer.frame_peak_to_peak(0), 2.0);
}
#[test]
fn stats_mean_integer() {
let data = vec![1_i32, 5, 2, 5, 3, 5, 4, 5];
let buffer = MinimalAdapter::new_from_vec(data, 2, 4);
assert_eq!(buffer.channel_mean(0), 2.5);
assert_eq!(buffer.channel_mean(1), 5.0);
assert_eq!(buffer.frame_mean(0), 3.0);
assert_eq!(buffer.frame_mean(3), 4.5);
}
#[test]
fn stats_mean_float() {
let data = vec![1.0_f32, 5.0, 2.0, 5.0, 3.0, 5.0, 4.0, 5.0];
let buffer = MinimalAdapter::new_from_vec(data, 2, 4);
assert_eq!(buffer.channel_mean(0), 2.5);
assert_eq!(buffer.channel_mean(1), 5.0);
assert_eq!(buffer.frame_mean(0), 3.0);
assert_eq!(buffer.frame_mean(3), 4.5);
}
#[test]
fn stats_min_and_max_without_zero_crossings() {
let data = vec![30000_u16, 1, 31000, 1, 32000, 1, 33000, 1];
let buffer = MinimalAdapter::new_from_vec(data, 2, 4);
assert_eq!(buffer.channel_min_and_max(0), (30000, 33000));
assert_eq!(buffer.channel_peak_to_peak(0), 3000.0);
assert_eq!(buffer.frame_min_and_max(0), (1, 30000));
assert_eq!(buffer.frame_peak_to_peak(0), 29999.0);
let data = vec![-4_i32, 0, -3, 0, -2, 0, -1, 0];
let buffer = MinimalAdapter::new_from_vec(data, 2, 4);
assert_eq!(buffer.channel_min_and_max(0), (-4, -1));
assert_eq!(buffer.channel_peak_to_peak(0), 3.0);
}
#[test]
fn stats_peak() {
let data = vec![1_i32, 5, -7, 5, 3, 5, 4, 5];
let buffer = MinimalAdapter::new_from_vec(data, 2, 4);
assert_eq!(buffer.channel_peak(0), 7.0);
assert_eq!(buffer.channel_peak(1), 5.0);
assert_eq!(buffer.frame_peak(1), 7.0);
}
#[test]
fn stats_peak_most_negative_sample() {
let data = vec![i16::MIN, 0, 100, 0];
let buffer = MinimalAdapter::new_from_vec(data, 2, 2);
assert_eq!(buffer.channel_peak(0), 32768.0);
}
#[test]
fn stats_min_and_max_out_of_bounds() {
let data = vec![1.0_f32, 5.0, 2.0, 5.0];
let buffer = MinimalAdapter::new_from_vec(data, 2, 2);
assert_eq!(buffer.channel_min_and_max(2), (0.0, 0.0));
assert_eq!(buffer.frame_min_and_max(2), (0.0, 0.0));
}
#[test]
fn stats_sums() {
let data = vec![1_i32, 5, 2, 5, 3, 5, 4, 5];
let buffer = MinimalAdapter::new_from_vec(data, 2, 4);
assert_eq!(buffer.channel_sum(0), 10.0);
assert_eq!(buffer.channel_sum(1), 20.0);
assert_eq!(buffer.frame_sum(0), 6.0);
assert_eq!(buffer.channel_sum_of_squares(0), 30.0);
assert_eq!(buffer.channel_sum_of_squares(1), 100.0);
assert_eq!(buffer.frame_sum_of_squares(0), 26.0);
}
#[test]
fn stats_sums_out_of_bounds() {
let data = vec![1.0_f32, 5.0, 2.0, 5.0];
let buffer = MinimalAdapter::new_from_vec(data, 2, 2);
assert_eq!(buffer.channel_sum(2), 0.0);
assert_eq!(buffer.frame_sum(2), 0.0);
assert_eq!(buffer.channel_sum_of_squares(2), 0.0);
assert_eq!(buffer.frame_sum_of_squares(2), 0.0);
}
#[test]
fn stats_empty_buffer() {
let buffer = MinimalAdapter::new_from_vec(vec![] as vec::Vec<f32>, 0, 0);
assert_eq!(buffer.channel_sum(0), 0.0);
assert_eq!(buffer.channel_sum_of_squares(0), 0.0);
assert_eq!(buffer.channel_rms(0), 0.0);
assert_eq!(buffer.frame_rms(0), 0.0);
assert_eq!(buffer.channel_mean(0), 0.0);
assert_eq!(buffer.frame_mean(0), 0.0);
}
#[test]
fn sqrt_newton_accuracy() {
let test_values: [f64; 12] = [
1.0e-12_f64,
1.0e-9_f64,
1.0e-6_f64,
1.0e-3_f64,
0.1_f64,
0.5_f64,
1.0_f64,
2.0_f64,
10.0_f64,
100.0_f64,
1.0e6_f64,
1.0e12_f64,
];
for value in test_values {
let expected = value.sqrt();
let actual = super::sqrt_newton(value);
let rel_err = (actual - expected).abs() / expected.max(1.0);
assert!(
rel_err < 1.0e-12,
"value={value}, expected={expected}, actual={actual}, rel_err={rel_err}"
);
}
}
#[test]
fn sqrt_newton_special_values() {
assert!(super::sqrt_newton(f64::NAN).is_nan());
assert_eq!(super::sqrt_newton(f64::INFINITY), f64::INFINITY);
assert_eq!(super::sqrt_newton(f64::NEG_INFINITY), 0.0);
}
#[test]
fn sqrt_newton_subnormal_value() {
let values = [
f64::from_bits(1),
f64::from_bits(f64::MIN_POSITIVE.to_bits() - 1),
];
for value in values {
let expected = value.sqrt();
let actual = super::sqrt_newton(value);
let rel_err = (actual - expected).abs() / expected.max(1.0);
assert!(
rel_err < 1.0e-12,
"value={value:e}, expected={expected:e}, actual={actual:e}, rel_err={rel_err:e}"
);
}
}
}