1#![warn(missing_docs)]
34
35use std::collections::VecDeque;
36
37use nnnoiseless::DenoiseState;
38
39pub const FRAME_SIZE: usize = DenoiseState::FRAME_SIZE;
41
42const I16_SCALE: f32 = 32768.0;
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq)]
48pub enum DenoiseError {
49 InvalidChannels(u16),
51 InvalidLength {
53 len: usize,
55 channels: u16,
57 },
58}
59
60impl std::fmt::Display for DenoiseError {
61 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
62 match self {
63 DenoiseError::InvalidChannels(c) => {
64 write!(f, "invalid channel count {c} (expected 1 or 2)")
65 }
66 DenoiseError::InvalidLength { len, channels } => {
67 write!(
68 f,
69 "interleaved length {len} is not a multiple of channel count {channels}"
70 )
71 }
72 }
73 }
74}
75
76impl std::error::Error for DenoiseError {}
77
78pub struct Denoiser {
83 channels: usize,
84 states: Vec<Box<DenoiseState<'static>>>,
87 in_buf: Vec<Vec<f32>>,
90 out_buf: VecDeque<f32>,
92 frame_in: Vec<f32>,
94 frame_out: Vec<Vec<f32>>,
96}
97
98impl Denoiser {
99 pub fn new(channels: u16) -> Result<Denoiser, DenoiseError> {
101 if !(1..=2).contains(&channels) {
102 return Err(DenoiseError::InvalidChannels(channels));
103 }
104 let ch = channels as usize;
105 let mut dn = Denoiser {
106 channels: ch,
107 states: (0..ch).map(|_| DenoiseState::new()).collect(),
108 in_buf: vec![Vec::new(); ch],
109 out_buf: VecDeque::new(),
110 frame_in: vec![0.0; FRAME_SIZE],
111 frame_out: vec![vec![0.0; FRAME_SIZE]; ch],
112 };
113 dn.prime_delay();
114 Ok(dn)
115 }
116
117 pub fn process(&mut self, interleaved: &mut [f32]) -> Result<(), DenoiseError> {
125 if interleaved.len() % self.channels != 0 {
126 return Err(DenoiseError::InvalidLength {
127 len: interleaved.len(),
128 channels: self.channels as u16,
129 });
130 }
131
132 for frame in interleaved.chunks_exact(self.channels) {
134 for (ch, &s) in frame.iter().enumerate() {
135 self.in_buf[ch].push(s);
136 }
137 }
138
139 self.process_ready_frames();
140
141 for s in interleaved.iter_mut() {
144 *s = self
145 .out_buf
146 .pop_front()
147 .expect("delay line must hold enough processed samples");
148 }
149 Ok(())
150 }
151
152 pub fn flush(&mut self) -> Vec<f32> {
159 if !self.in_buf[0].is_empty() {
160 for buf in &mut self.in_buf {
161 buf.resize(FRAME_SIZE, 0.0);
162 }
163 self.process_ready_frames();
164 }
165 let take = FRAME_SIZE * self.channels;
166 let mut out = Vec::with_capacity(take);
167 for _ in 0..take {
168 out.push(self.out_buf.pop_front().unwrap_or(0.0));
170 }
171 self.reset();
172 out
173 }
174
175 pub fn reset(&mut self) {
179 for st in &mut self.states {
180 *st = DenoiseState::new();
181 }
182 for buf in &mut self.in_buf {
183 buf.clear();
184 }
185 self.out_buf.clear();
186 self.prime_delay();
187 }
188
189 pub fn channels(&self) -> u16 {
191 self.channels as u16
192 }
193
194 fn prime_delay(&mut self) {
198 for buf in &mut self.in_buf {
199 buf.resize(FRAME_SIZE, 0.0);
200 }
201 }
202
203 fn process_ready_frames(&mut self) {
205 while self.in_buf[0].len() >= FRAME_SIZE {
207 for ch in 0..self.channels {
208 for (dst, &src) in self.frame_in.iter_mut().zip(&self.in_buf[ch][..FRAME_SIZE]) {
210 *dst = src * I16_SCALE;
211 }
212 self.states[ch].process_frame(&mut self.frame_out[ch], &self.frame_in);
213 self.in_buf[ch].drain(..FRAME_SIZE);
214 }
215 for i in 0..FRAME_SIZE {
217 for out_ch in &self.frame_out {
218 self.out_buf
219 .push_back((out_ch[i] / I16_SCALE).clamp(-1.0, 1.0));
220 }
221 }
222 }
223 }
224}
225
226#[cfg(test)]
227mod tests {
228 use super::*;
229
230 struct Lcg(u64);
232
233 impl Lcg {
234 fn next_unit(&mut self) -> f32 {
236 self.0 = self
237 .0
238 .wrapping_mul(6364136223846793005)
239 .wrapping_add(1442695040888963407);
240 ((self.0 >> 40) as f32) / (1u32 << 24) as f32
241 }
242 }
243
244 fn white_noise(n: usize, amp: f32) -> Vec<f32> {
246 let mut lcg = Lcg(0x5EED_1234_5678_9ABC);
247 (0..n)
248 .map(|_| (lcg.next_unit() * 2.0 - 1.0) * amp)
249 .collect()
250 }
251
252 fn lowpass(xs: &[f32], a: f32) -> Vec<f32> {
255 let mut y = 0.0f32;
256 xs.iter()
257 .map(|&x| {
258 y += a * (x - y);
259 y
260 })
261 .collect()
262 }
263
264 fn sine(n: usize, freq: f32, amp: f32) -> Vec<f32> {
265 (0..n)
266 .map(|i| (2.0 * std::f32::consts::PI * freq * i as f32 / 48_000.0).sin() * amp)
267 .collect()
268 }
269
270 fn rms(xs: &[f32]) -> f64 {
271 let sum: f64 = xs.iter().map(|&x| (x as f64) * (x as f64)).sum();
272 (sum / xs.len() as f64).sqrt()
273 }
274
275 fn run_aligned(channels: u16, input: &[f32]) -> Vec<f32> {
277 let mut dn = Denoiser::new(channels).unwrap();
278 let mut buf = input.to_vec();
279 dn.process(&mut buf).unwrap();
280 buf.extend_from_slice(&dn.flush());
281 buf.split_off(FRAME_SIZE * channels as usize)
283 }
284
285 #[test]
288 #[ignore]
289 fn measure_rms_ratios() {
290 let white = white_noise(96_000, 0.3);
291 let fan = lowpass(&white, 0.1);
292 let tone = sine(96_000, 440.0, 0.5);
293 for (name, input) in [
294 ("white noise amp=0.3", &white),
295 ("lowpass(a=0.1) noise", &fan),
296 ("440Hz sine amp=0.5", &tone),
297 ] {
298 let output = run_aligned(1, input);
299 println!(
300 "{name}: in_rms={:.6} out_rms={:.6} ratio={:.6}",
301 rms(input),
302 rms(&output),
303 rms(&output) / rms(input)
304 );
305 }
306 }
307
308 #[test]
309 fn stationary_noise_rms_strongly_reduced() {
310 let input = lowpass(&white_noise(96_000, 0.3), 0.1);
315 let output = run_aligned(1, &input);
316 let (in_rms, out_rms) = (rms(&input), rms(&output));
317 assert!(
318 out_rms < in_rms * 0.25,
319 "stationary noise must be strongly attenuated: \
320 in_rms={in_rms:.4} out_rms={out_rms:.4}"
321 );
322 }
323
324 #[test]
325 fn white_noise_rms_reduced() {
326 let input = white_noise(96_000, 0.3);
331 let output = run_aligned(1, &input);
332 let (in_rms, out_rms) = (rms(&input), rms(&output));
333 assert!(
334 out_rms < in_rms * 0.90,
335 "white noise must not be amplified: in_rms={in_rms:.4} out_rms={out_rms:.4}"
336 );
337 }
338
339 #[test]
340 fn sine_output_sane() {
341 let input = sine(96_000, 440.0, 0.5);
346 let output = run_aligned(1, &input);
347 assert!(
348 output.iter().all(|x| x.is_finite()),
349 "output must not contain NaN/inf"
350 );
351 assert!(
352 output.iter().all(|&x| (-1.0..=1.0).contains(&x)),
353 "output must stay within +/-1.0"
354 );
355 let (in_rms, out_rms) = (rms(&input), rms(&output));
356 assert!(
357 out_rms > in_rms * 0.50,
358 "sine must pass through mostly intact: in_rms={in_rms:.4} out_rms={out_rms:.4}"
359 );
360 }
361
362 #[test]
363 fn first_delay_block_is_silence() {
364 let mut dn = Denoiser::new(1).unwrap();
366 let mut buf = white_noise(FRAME_SIZE * 2, 0.3);
367 dn.process(&mut buf).unwrap();
368 assert!(
369 buf[..FRAME_SIZE].iter().all(|&x| x == 0.0),
370 "first FRAME_SIZE output samples must be exactly zero"
371 );
372 }
373
374 #[test]
375 fn chunked_equals_oneshot() {
376 let input = white_noise(96_000, 0.3);
379
380 let mut oneshot = input.clone();
381 let mut dn1 = Denoiser::new(1).unwrap();
382 dn1.process(&mut oneshot).unwrap();
383 let tail1 = dn1.flush();
384
385 let mut chunked = Vec::with_capacity(input.len());
386 let mut dn2 = Denoiser::new(1).unwrap();
387 for chunk in input.chunks(1000) {
388 let mut buf = chunk.to_vec();
389 dn2.process(&mut buf).unwrap();
390 assert_eq!(
391 buf.len(),
392 chunk.len(),
393 "process must emit in place, same length"
394 );
395 chunked.extend_from_slice(&buf);
396 }
397 let tail2 = dn2.flush();
398
399 assert_eq!(
400 chunked.len(),
401 input.len(),
402 "total process output == total input"
403 );
404 assert_eq!(
405 tail1.len(),
406 FRAME_SIZE,
407 "flush must emit exactly FRAME_SIZE per channel"
408 );
409 assert_eq!(
410 oneshot, chunked,
411 "chunk granularity must not change the output"
412 );
413 assert_eq!(tail1, tail2, "flush residue must also match");
414 }
415
416 #[test]
417 fn stereo_keeps_channels_independent_and_interleaved() {
418 let n = 48_000; let left = white_noise(n, 0.3);
423 let mut stereo = Vec::with_capacity(n * 2);
424 for &l in &left {
425 stereo.push(l);
426 stereo.push(0.0);
427 }
428
429 let mut dn = Denoiser::new(2).unwrap();
430 let mut stereo_out = Vec::with_capacity(stereo.len());
431 for chunk in stereo.chunks(1000) {
432 let mut buf = chunk.to_vec();
433 dn.process(&mut buf).unwrap();
434 stereo_out.extend_from_slice(&buf);
435 }
436 let tail = dn.flush();
437 assert_eq!(
438 tail.len(),
439 FRAME_SIZE * 2,
440 "stereo flush is FRAME_SIZE per channel"
441 );
442 stereo_out.extend_from_slice(&tail);
443
444 let left_out: Vec<f32> = stereo_out.iter().step_by(2).copied().collect();
445 let right_out: Vec<f32> = stereo_out.iter().skip(1).step_by(2).copied().collect();
446 assert!(
447 right_out.iter().all(|&x| x == 0.0),
448 "silent right channel must stay exactly zero (no crosstalk)"
449 );
450
451 let mut mono_ref = left.clone();
452 let mut dn_mono = Denoiser::new(1).unwrap();
453 dn_mono.process(&mut mono_ref).unwrap();
454 mono_ref.extend_from_slice(&dn_mono.flush());
455 assert_eq!(
456 left_out, mono_ref,
457 "stereo left must equal the mono reference"
458 );
459 }
460
461 #[test]
462 fn reset_restores_initial_state() {
463 let input = white_noise(10_000, 0.3);
465
466 let mut dn = Denoiser::new(1).unwrap();
467 let mut first = input.clone();
468 dn.process(&mut first).unwrap();
469
470 dn.reset();
471 let mut second = input.clone();
472 dn.process(&mut second).unwrap();
473 assert_eq!(first, second, "reset must restore the initial state");
474
475 dn.flush();
477 let mut third = input.clone();
478 dn.process(&mut third).unwrap();
479 assert_eq!(first, third, "flush must leave the denoiser reusable");
480 }
481
482 #[test]
483 fn rejects_invalid_channels() {
484 assert_eq!(
485 Denoiser::new(0).err(),
486 Some(DenoiseError::InvalidChannels(0))
487 );
488 assert_eq!(
489 Denoiser::new(3).err(),
490 Some(DenoiseError::InvalidChannels(3))
491 );
492 }
493
494 #[test]
495 fn rejects_misaligned_length() {
496 let mut dn = Denoiser::new(2).unwrap();
497 let mut buf = vec![0.0f32; 999]; let err = dn.process(&mut buf).unwrap_err();
499 assert_eq!(
500 err,
501 DenoiseError::InvalidLength {
502 len: 999,
503 channels: 2
504 }
505 );
506 assert!(buf.iter().all(|&x| x == 0.0));
508 }
509
510 #[test]
511 fn empty_process_and_bare_flush() {
512 let mut dn = Denoiser::new(1).unwrap();
513 let mut empty: [f32; 0] = [];
514 dn.process(&mut empty).unwrap(); assert_eq!(dn.channels(), 1);
516
517 let tail = dn.flush();
519 assert_eq!(tail.len(), FRAME_SIZE);
520 assert!(tail.iter().all(|&x| x == 0.0));
521 }
522}