pub mod pcm;
#[must_use]
pub fn dot_i16(a: &[i16], b: &[i16]) -> i64 {
let n = a.len().min(b.len());
let (a, b) = (&a[..n], &b[..n]);
let (mut a0, mut a1, mut a2, mut a3) = (0i64, 0i64, 0i64, 0i64);
let mut ca = a.chunks_exact(4);
let mut cb = b.chunks_exact(4);
for (x, y) in ca.by_ref().zip(cb.by_ref()) {
a0 += i64::from(i32::from(x[0]) * i32::from(y[0]));
a1 += i64::from(i32::from(x[1]) * i32::from(y[1]));
a2 += i64::from(i32::from(x[2]) * i32::from(y[2]));
a3 += i64::from(i32::from(x[3]) * i32::from(y[3]));
}
let mut tail = 0i64;
for (&x, &y) in ca.remainder().iter().zip(cb.remainder()) {
tail += i64::from(i32::from(x) * i32::from(y));
}
(a0 + a1) + (a2 + a3) + tail
}
#[must_use]
pub fn sum_sq_i16(a: &[i16]) -> i64 {
let (mut a0, mut a1, mut a2, mut a3) = (0i64, 0i64, 0i64, 0i64);
let mut c = a.chunks_exact(4);
for x in c.by_ref() {
let (v0, v1) = (i32::from(x[0]), i32::from(x[1]));
let (v2, v3) = (i32::from(x[2]), i32::from(x[3]));
a0 += i64::from(v0 * v0);
a1 += i64::from(v1 * v1);
a2 += i64::from(v2 * v2);
a3 += i64::from(v3 * v3);
}
let mut tail = 0i64;
for &x in c.remainder() {
let v = i32::from(x);
tail += i64::from(v * v);
}
(a0 + a1) + (a2 + a3) + tail
}
#[must_use]
pub fn sum_sq_i16_le(samples: &[u8]) -> (i64, usize) {
if let Some(v) = rusty_esp_core::pcm::as_i16(samples) {
return (sum_sq_i16(v), v.len());
}
let (mut a0, mut a1, mut a2, mut a3) = (0i64, 0i64, 0i64, 0i64);
let mut n: usize = 0;
let mut c = samples.chunks_exact(8);
for s in c.by_ref() {
let v0 = i32::from(i16::from_le_bytes([s[0], s[1]]));
let v1 = i32::from(i16::from_le_bytes([s[2], s[3]]));
let v2 = i32::from(i16::from_le_bytes([s[4], s[5]]));
let v3 = i32::from(i16::from_le_bytes([s[6], s[7]]));
a0 += i64::from(v0 * v0);
a1 += i64::from(v1 * v1);
a2 += i64::from(v2 * v2);
a3 += i64::from(v3 * v3);
n += 4;
}
let mut tail = 0i64;
for s in c.remainder().chunks_exact(2) {
let v = i32::from(i16::from_le_bytes([s[0], s[1]]));
tail += i64::from(v * v);
n += 1;
}
((a0 + a1) + (a2 + a3) + tail, n)
}
#[must_use]
pub fn peak_abs_i16(a: &[i16]) -> u16 {
let mut m = [0u16; 8];
let mut c = a.chunks_exact(8);
for x in c.by_ref() {
for k in 0..8 {
m[k] = m[k].max(x[k].unsigned_abs());
}
}
let mut best = m.iter().copied().fold(0u16, u16::max);
for &x in c.remainder() {
best = best.max(x.unsigned_abs());
}
best
}
#[must_use]
pub fn dbfs_from_mean_square(mean_square: f32) -> f32 {
if !(mean_square > 0.0) {
return -120.0;
}
const K: f32 = 90.308_998_699_194_36;
10.0 * libm::log10f(mean_square) - K
}
#[must_use]
pub fn rms_dbfs_i16(samples: &[u8]) -> f32 {
let (acc, n) = sum_sq_i16_le(samples);
if n == 0 || acc == 0 {
return -120.0;
}
dbfs_from_mean_square(acc as f32 / n as f32)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn dbfs_of_full_scale_square_is_zero() {
let mut buf = [0u8; 64];
for (i, s) in buf.chunks_exact_mut(2).enumerate() {
let v: i16 = if i % 2 == 0 { 32767 } else { -32767 };
s.copy_from_slice(&v.to_le_bytes());
}
let db = rms_dbfs_i16(&buf);
assert!(db.abs() < 0.001, "{db}");
assert_eq!(rms_dbfs_i16(&[0u8; 8]), -120.0);
assert_eq!(rms_dbfs_i16(&[]), -120.0);
let mut half = [0u8; 8];
for s in half.chunks_exact_mut(2) {
s.copy_from_slice(&16384i16.to_le_bytes());
}
assert!((rms_dbfs_i16(&half) + 6.0206).abs() < 0.001);
}
#[test]
fn reductions_are_exact_at_the_extremes() {
let ext = [i16::MIN, i16::MAX, i16::MIN, 0];
assert_eq!(sum_sq_i16(&ext), 2 * 32768 * 32768 + 32767 * 32767);
assert_eq!(dot_i16(&ext, &ext), sum_sq_i16(&ext));
assert_eq!(dot_i16(&ext, &ext[..2]), 32768 * 32768 + 32767 * 32767);
assert_eq!(dot_i16(&[3, -4], &[-2, 5]), -26);
assert_eq!(peak_abs_i16(&ext), 32768);
assert_eq!(peak_abs_i16(&[]), 0);
let mut bytes = [0u8; 9];
bytes[..8].copy_from_slice(&[0x00, 0x80, 0xFF, 0x7F, 0x00, 0x80, 0, 0]);
assert_eq!(sum_sq_i16_le(&bytes), (sum_sq_i16(&ext), 4));
}
}