use alloc::vec::Vec;
#[must_use]
pub fn tpdf_dither(samples: &[f32], target_bits: u32) -> Vec<f32> {
let bits = target_bits.clamp(1, 32);
let quant_step = 1.0 / (1_u64 << (bits - 1)) as f32;
let mut rng_state: u32 = 0x12345678;
samples
.iter()
.map(|&s| {
rng_state ^= rng_state << 13;
rng_state ^= rng_state >> 17;
rng_state ^= rng_state << 5;
let r1 = (rng_state as f32 / u32::MAX as f32) * 2.0 - 1.0;
rng_state ^= rng_state << 13;
rng_state ^= rng_state >> 17;
rng_state ^= rng_state << 5;
let r2 = (rng_state as f32 / u32::MAX as f32) * 2.0 - 1.0;
let dither = (r1 + r2) * 0.5 * quant_step;
s + dither
})
.collect()
}
#[must_use]
pub fn noise_shaped_dither(samples: &[f32], target_bits: u32) -> Vec<f32> {
let bits = target_bits.clamp(1, 32);
let quant_step = 1.0 / (1_u64 << (bits - 1)) as f32;
let mut rng_state: u32 = 0x12345678;
let mut error: f32 = 0.0;
samples
.iter()
.map(|&s| {
let shaped = s - error;
rng_state ^= rng_state << 13;
rng_state ^= rng_state >> 17;
rng_state ^= rng_state << 5;
let r1 = (rng_state as f32 / u32::MAX as f32) * 2.0 - 1.0;
rng_state ^= rng_state << 13;
rng_state ^= rng_state >> 17;
rng_state ^= rng_state << 5;
let r2 = (rng_state as f32 / u32::MAX as f32) * 2.0 - 1.0;
let dither = (r1 + r2) * 0.5 * quant_step;
let dithered = shaped + dither;
let quantized = libm::roundf(dithered / quant_step) * quant_step;
error = quantized - shaped;
quantized
})
.collect()
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
#[test]
fn tpdf_preserves_length() {
let input = alloc::vec![0.5; 1024];
let output = tpdf_dither(&input, 16);
assert_eq!(input.len(), output.len());
}
#[test]
fn tpdf_noise_is_small() {
let input = alloc::vec![0.5; 4096];
let output = tpdf_dither(&input, 16);
let max_diff = input
.iter()
.zip(&output)
.map(|(a, b)| (a - b).abs())
.fold(0.0f32, f32::max);
assert!(max_diff < 0.001, "max diff {max_diff} too large");
}
#[test]
fn noise_shaped_preserves_length() {
let input = alloc::vec![0.5; 1024];
let output = noise_shaped_dither(&input, 16);
assert_eq!(input.len(), output.len());
}
#[test]
fn noise_shaped_noise_is_small() {
let input = alloc::vec![0.5; 4096];
let output = noise_shaped_dither(&input, 16);
let max_diff = input
.iter()
.zip(&output)
.map(|(a, b)| (a - b).abs())
.fold(0.0f32, f32::max);
assert!(max_diff < 0.001, "max diff {max_diff} too large");
}
#[test]
fn empty_input() {
assert!(tpdf_dither(&[], 16).is_empty());
assert!(noise_shaped_dither(&[], 16).is_empty());
}
#[test]
fn serde_not_needed() {
let input = alloc::vec![0.0f32; 10];
let tpdf = tpdf_dither(&input, 16);
let ns = noise_shaped_dither(&input, 16);
assert_eq!(tpdf.len(), 10);
assert_eq!(ns.len(), 10);
}
}