pub fn audio_digest(samples: &[i64]) -> String {
let n = samples.len();
let peak = samples.iter().map(|s| s.unsigned_abs()).max().unwrap_or(0);
let sum_sq: u128 = samples.iter().fold(0u128, |acc, s| {
let a = i128::from(*s);
acc.saturating_add((a * a) as u128)
});
let rms = if n == 0 {
0
} else {
(sum_sq / n as u128).isqrt()
};
format!("(audio-digest (samples {n}) (peak {peak}) (rms {rms}))")
}
pub fn frame_digest(bytes: &[u8]) -> String {
format!(
"(frame (bytes {}) (hash {}))",
bytes.len(),
fnv1a64_hex(bytes)
)
}
pub fn fnv1a64(bytes: &[u8]) -> u64 {
const FNV_OFFSET: u64 = 0xcbf2_9ce4_8422_2325;
const FNV_PRIME: u64 = 0x0000_0100_0000_01b3;
bytes.iter().fold(FNV_OFFSET, |hash, byte| {
(hash ^ u64::from(*byte)).wrapping_mul(FNV_PRIME)
})
}
pub fn fnv1a64_hex(bytes: &[u8]) -> String {
format!("{:016x}", fnv1a64(bytes))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn audio_digest_uses_integer_rms_and_peak() {
assert_eq!(
audio_digest(&[3, -4]),
"(audio-digest (samples 2) (peak 4) (rms 3))"
);
assert_eq!(
audio_digest(&[-5, 5, -5, 5]),
"(audio-digest (samples 4) (peak 5) (rms 5))"
);
assert_eq!(
audio_digest(&[]),
"(audio-digest (samples 0) (peak 0) (rms 0))"
);
}
#[test]
fn digests_are_deterministic_across_repeated_calls() {
let samples = [1, -2, 3, -4, 5];
assert_eq!(audio_digest(&samples), audio_digest(&samples));
let bytes = b"a deterministic frame";
assert_eq!(frame_digest(bytes), frame_digest(bytes));
}
#[test]
fn frame_digest_is_content_sensitive() {
assert_eq!(
frame_digest(&[]),
"(frame (bytes 0) (hash cbf29ce484222325))"
);
assert_eq!(frame_digest(b"abc"), frame_digest(b"abc"));
assert_ne!(frame_digest(b"abc"), frame_digest(b"abd"));
assert!(frame_digest(b"abc").starts_with("(frame (bytes 3) (hash "));
}
#[test]
fn fnv1a64_uses_standard_basis_and_is_content_sensitive() {
assert_eq!(fnv1a64(&[]), 0xcbf2_9ce4_8422_2325);
assert_eq!(fnv1a64_hex(&[]), "cbf29ce484222325");
assert_eq!(fnv1a64_hex(b"abc"), "e71fa2190541574b");
assert_eq!(fnv1a64(b"abc"), fnv1a64(b"abc"));
assert_ne!(fnv1a64(b"abc"), fnv1a64(b"abd"));
}
}