#![forbid(unsafe_code)]
#![allow(clippy::cast_precision_loss)]
#[derive(Clone, Debug)]
#[allow(dead_code)]
struct Xorshift32 {
state: u32,
}
impl Xorshift32 {
fn new(seed: u32) -> Self {
Self {
state: if seed == 0 { 0xBAD_5EED } else { seed },
}
}
fn next_u32(&mut self) -> u32 {
let mut x = self.state;
x ^= x << 13;
x ^= x >> 17;
x ^= x << 5;
self.state = x;
x
}
fn next_f32(&mut self) -> f32 {
let u = self.next_u32() as f32 / u32::MAX as f32;
u * 2.0 - 1.0
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[allow(dead_code)]
pub enum DitherType {
None,
Rectangular,
Triangular,
Tpdf,
Highpass,
}
impl DitherType {
#[must_use]
pub fn noise_shape(&self) -> &str {
match self {
Self::None => "none",
Self::Rectangular => "rectangular",
Self::Triangular => "triangular",
Self::Tpdf => "tpdf",
Self::Highpass => "highpass",
}
}
}
#[derive(Clone, Debug)]
#[allow(dead_code)]
pub struct NoiseShaper {
feedback: Vec<f32>,
coeffs: Vec<f32>,
pos: usize,
}
impl NoiseShaper {
#[must_use]
pub fn new(order: usize) -> Self {
let coeffs = match order {
0 => vec![],
1 => vec![1.0_f32],
2 => vec![1.5, -0.5],
3 => vec![1.75, -1.0, 0.25],
_ => {
(0..order).map(|i| 1.0 - i as f32 / order as f32).collect()
}
};
let len = coeffs.len().max(1);
Self {
feedback: vec![0.0; len],
coeffs,
pos: 0,
}
}
pub fn process(&mut self, quantize_error: f32) -> f32 {
if self.coeffs.is_empty() {
return 0.0;
}
let mut shaped = 0.0_f32;
for (k, coeff) in self.coeffs.iter().enumerate() {
let idx = (self.pos + self.feedback.len() - k) % self.feedback.len();
shaped += coeff * self.feedback[idx];
}
self.feedback[self.pos] = quantize_error;
self.pos = (self.pos + 1) % self.feedback.len();
shaped
}
pub fn reset(&mut self) {
for x in &mut self.feedback {
*x = 0.0;
}
self.pos = 0;
}
#[must_use]
pub fn order(&self) -> usize {
self.coeffs.len()
}
}
#[derive(Clone, Debug)]
#[allow(dead_code)]
pub struct Ditherer {
dither_type: DitherType,
shaper: Option<NoiseShaper>,
prng: Xorshift32,
}
impl Ditherer {
#[must_use]
pub fn new(dither_type: DitherType) -> Self {
let shaper = match dither_type {
DitherType::Highpass => Some(NoiseShaper::new(1)),
_ => None,
};
Self {
dither_type,
shaper,
prng: Xorshift32::new(0x1234_5678),
}
}
#[must_use]
pub fn with_shaper(dither_type: DitherType, shaper: NoiseShaper) -> Self {
Self {
dither_type,
shaper: Some(shaper),
prng: Xorshift32::new(0xDEAD_BEEF),
}
}
pub fn process(&mut self, sample: f32, target_bits: u8) -> f32 {
let levels = (1u32 << (target_bits - 1)) as f32; let lsb = 1.0 / levels;
let dither_noise = match self.dither_type {
DitherType::None => 0.0,
DitherType::Rectangular => self.prng.next_f32() * 0.5 * lsb,
DitherType::Triangular | DitherType::Tpdf => {
let r1 = self.prng.next_f32();
let r2 = self.prng.next_f32();
(r1 + r2) * 0.5 * lsb
}
DitherType::Highpass => {
self.prng.next_f32() * 0.5 * lsb
}
};
let dithered = sample + dither_noise;
let quantised = (dithered * levels).round() / levels;
let quantised = quantised.clamp(-1.0, 1.0 - lsb);
if let Some(ref mut shaper) = self.shaper {
let error = quantised - sample;
shaper.process(error);
}
quantised
}
#[must_use]
pub fn dither_type(&self) -> DitherType {
self.dither_type
}
}
#[must_use]
#[allow(dead_code)]
pub fn dither_buffer(samples: &[f32], target_bits: u8, dither_type: DitherType) -> Vec<f32> {
let mut ditherer = Ditherer::new(dither_type);
samples
.iter()
.map(|&s| ditherer.process(s, target_bits))
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_dither_type_noise_shape_none() {
assert_eq!(DitherType::None.noise_shape(), "none");
}
#[test]
fn test_dither_type_noise_shape_rectangular() {
assert_eq!(DitherType::Rectangular.noise_shape(), "rectangular");
}
#[test]
fn test_dither_type_noise_shape_triangular() {
assert_eq!(DitherType::Triangular.noise_shape(), "triangular");
}
#[test]
fn test_dither_type_noise_shape_tpdf() {
assert_eq!(DitherType::Tpdf.noise_shape(), "tpdf");
}
#[test]
fn test_dither_type_noise_shape_highpass() {
assert_eq!(DitherType::Highpass.noise_shape(), "highpass");
}
#[test]
fn test_noise_shaper_order_zero_returns_zero() {
let mut shaper = NoiseShaper::new(0);
let out = shaper.process(0.5);
assert_eq!(out, 0.0);
}
#[test]
fn test_noise_shaper_order_returns_expected() {
let shaper = NoiseShaper::new(3);
assert_eq!(shaper.order(), 3);
}
#[test]
fn test_noise_shaper_reset() {
let mut shaper = NoiseShaper::new(2);
shaper.process(1.0);
shaper.process(0.5);
shaper.reset();
let out = shaper.process(0.0);
assert_eq!(out, 0.0);
}
#[test]
fn test_noise_shaper_first_order_feedback() {
let mut shaper = NoiseShaper::new(1);
let _first = shaper.process(1.0);
let second = shaper.process(0.0);
assert!(
second.abs() > 0.0,
"Shaper should produce non-zero output after non-zero error, got {second}"
);
}
#[test]
fn test_dither_none_output_is_finite() {
let mut d = Ditherer::new(DitherType::None);
assert!(d.process(0.5, 16).is_finite());
}
#[test]
fn test_dither_none_quantises_cleanly() {
let mut d = Ditherer::new(DitherType::None);
let out = d.process(0.0, 16);
assert!(out.abs() < 1.0 / 32768.0 + 1e-8);
}
#[test]
fn test_dither_rectangular_output_in_range() {
let mut d = Ditherer::new(DitherType::Rectangular);
for _ in 0..1000 {
let out = d.process(0.5, 16);
assert!(out >= -1.0 && out <= 1.0, "Output out of range: {out}");
}
}
#[test]
fn test_dither_tpdf_output_in_range() {
let mut d = Ditherer::new(DitherType::Tpdf);
for _ in 0..1000 {
let out = d.process(-0.5, 16);
assert!(out >= -1.0 && out <= 1.0);
}
}
#[test]
fn test_dither_triangular_output_is_finite() {
let mut d = Ditherer::new(DitherType::Triangular);
for _ in 0..500 {
assert!(d.process(0.25, 24).is_finite());
}
}
#[test]
fn test_dither_highpass_output_is_finite() {
let mut d = Ditherer::new(DitherType::Highpass);
for _ in 0..500 {
assert!(d.process(0.1, 16).is_finite());
}
}
#[test]
fn test_dither_type_returned_correctly() {
let d = Ditherer::new(DitherType::Tpdf);
assert_eq!(d.dither_type(), DitherType::Tpdf);
}
#[test]
fn test_dither_buffer_length_preserved() {
let input = vec![0.1_f32; 256];
let output = dither_buffer(&input, 16, DitherType::Tpdf);
assert_eq!(output.len(), input.len());
}
#[test]
fn test_dither_buffer_all_finite() {
let input: Vec<f32> = (0..256).map(|i| (i as f32 / 128.0) - 1.0).collect();
let output = dither_buffer(&input, 16, DitherType::Rectangular);
assert!(output.iter().all(|x| x.is_finite()));
}
#[test]
fn test_dither_buffer_values_in_range() {
let input = vec![0.9_f32; 128];
let output = dither_buffer(&input, 16, DitherType::Tpdf);
for &v in &output {
assert!(v >= -1.0 && v <= 1.0, "Out of range: {v}");
}
}
}