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()
.map(|s| {
let a = i128::from(*s);
(a * a) as u128
})
.sum();
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 {
const FNV_OFFSET: u64 = 0xcbf2_9ce4_8422_2325;
const FNV_PRIME: u64 = 0x0000_0100_0000_01b3;
let mut hash = FNV_OFFSET;
for &byte in bytes {
hash ^= u64::from(byte);
hash = hash.wrapping_mul(FNV_PRIME);
}
format!("(frame (bytes {}) (hash {hash:016x}))", bytes.len())
}
#[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 "));
}
}