1use core::f64::consts::PI as PI_F64;
11
12use fearless_simd::{Level, Simd, SimdBase, SimdFloat, dispatch, f32x8};
13
14use super::{CodecError, Sample};
15
16#[cfg(feature = "std")]
17use super::PcmBuf;
18
19pub const NUM_PHASES: usize = 256;
21pub const TAPS_PER_PHASE: usize = 24;
23pub const COEFFS_LEN: usize = NUM_PHASES * TAPS_PER_PHASE;
25
26#[inline]
28fn fsin(x: f64) -> f64 {
29 #[cfg(feature = "std")]
30 {
31 f64::sin(x)
32 }
33 #[cfg(not(feature = "std"))]
34 {
35 libm::sin(x)
36 }
37}
38
39#[inline]
41fn fsqrt(x: f64) -> f64 {
42 #[cfg(feature = "std")]
43 {
44 f64::sqrt(x)
45 }
46 #[cfg(not(feature = "std"))]
47 {
48 libm::sqrt(x)
49 }
50}
51
52pub struct Resampler<'a> {
53 input_rate: usize,
54 output_rate: usize,
55 coeffs: &'a mut [f32],
56 history: [f32; TAPS_PER_PHASE],
58 pos: usize,
61 level: Level,
63}
64
65fn bessel_i0(x: f64) -> f64 {
66 let mut sum = 1.0_f64;
67 let mut term = 1.0_f64;
68 let x_sq = x * x * 0.25;
69
70 for m in 1..=30 {
71 term *= x_sq / (m * m) as f64;
72 sum += term;
73 if term < 1e-15 * sum {
74 break;
75 }
76 }
77 sum
78}
79
80fn kaiser_window(n: usize, n_total: usize, beta: f64) -> f64 {
81 if n_total <= 1 {
82 return 1.0;
83 }
84 let alpha = (n_total - 1) as f64 / 2.0;
85 let x = (n as f64 - alpha) / alpha;
86 let arg = beta * fsqrt(1.0 - x * x);
87 bessel_i0(arg) / bessel_i0(beta)
88}
89
90impl<'a> Resampler<'a> {
91 pub fn new(
98 input_rate: usize,
99 output_rate: usize,
100 coeffs: &'a mut [f32],
101 ) -> Result<Self, CodecError> {
102 if coeffs.len() < COEFFS_LEN {
103 return Err(CodecError::BufferTooSmall);
104 }
105 if input_rate == 0 || output_rate == 0 {
106 return Err(CodecError::InvalidInput);
107 }
108 if input_rate.checked_add(output_rate).is_none()
111 || output_rate.checked_mul(NUM_PHASES).is_none()
112 {
113 return Err(CodecError::InvalidInput);
114 }
115
116 const KAISER_BETA: f64 = 7.0;
117
118 let ratio = output_rate as f64 / input_rate as f64;
119 let num_phases = NUM_PHASES;
120 let taps_per_phase = TAPS_PER_PHASE;
121 let filter_len = num_phases * taps_per_phase;
122
123 let coeffs = &mut coeffs[..filter_len];
124
125 let cutoff = if ratio < 1.0 {
126 ratio * 0.5 * 0.95
127 } else {
128 0.5 * 0.95
129 };
130
131 let center = (taps_per_phase as f64 - 1.0) / 2.0;
132
133 let mut phase_coeffs: [f64; TAPS_PER_PHASE] = [0.0; TAPS_PER_PHASE];
136
137 for p in 0..num_phases {
138 let mut sum = 0.0_f64;
139
140 for t in 0..taps_per_phase {
141 let x = t as f64 - center - (p as f64 / num_phases as f64);
142
143 let sinc_val = if x.abs() < 1e-10 {
144 2.0 * cutoff
145 } else {
146 let x_pi = x * PI_F64;
147 fsin(x_pi * 2.0 * cutoff) / x_pi
148 };
149
150 let full_filter_idx = t * num_phases + p;
151 let window = kaiser_window(full_filter_idx, filter_len, KAISER_BETA);
152
153 phase_coeffs[t] = sinc_val * window;
154 sum += phase_coeffs[t];
155 }
156
157 for t in 0..taps_per_phase {
158 let normalized = (phase_coeffs[t] / sum) as f32;
159 coeffs[p * taps_per_phase + t] = normalized;
160 }
161 }
162
163 Ok(Self {
164 input_rate,
165 output_rate,
166 coeffs,
167 history: [0.0; TAPS_PER_PHASE],
168 pos: 0,
169 level: default_level(),
170 })
171 }
172
173 pub fn input_rate(&self) -> usize {
174 self.input_rate
175 }
176
177 pub fn output_rate(&self) -> usize {
178 self.output_rate
179 }
180
181 #[inline(always)]
183 fn dot_product<S: Simd>(simd: S, a: &[f32; TAPS_PER_PHASE], b: &[f32; TAPS_PER_PHASE]) -> f32 {
184 let a0 = f32x8::from_slice(simd, &a[0..8]);
185 let a1 = f32x8::from_slice(simd, &a[8..16]);
186 let a2 = f32x8::from_slice(simd, &a[16..24]);
187 let b0 = f32x8::from_slice(simd, &b[0..8]);
188 let b1 = f32x8::from_slice(simd, &b[8..16]);
189 let b2 = f32x8::from_slice(simd, &b[16..24]);
190 a0.mul_add(b0, a1.mul_add(b1, a2 * b2)).reduce_sum()
191 }
192
193 pub fn resample_into(
199 &mut self,
200 input: &[Sample],
201 out: &mut [Sample],
202 ) -> Result<usize, CodecError> {
203 if self.input_rate == self.output_rate {
204 if out.len() < input.len() {
205 return Err(CodecError::BufferTooSmall);
206 }
207 out[..input.len()].copy_from_slice(input);
208 return Ok(input.len());
209 }
210
211 let expected: u128 = {
217 let ticks = (input.len() as u128) * (self.output_rate as u128);
218 if ticks > self.pos as u128 {
219 (ticks - self.pos as u128).div_ceil(self.input_rate as u128)
220 } else {
221 0
222 }
223 };
224
225 let level = self.level;
228 let written = dispatch!(level, simd => self.resample_simd(simd, input, out))?;
229
230 debug_assert_eq!(
231 written as u128,
232 expected,
233 "resampler produced {written} samples, exact timing requires {expected} \
234 for {} inputs ({} -> {}): output sizes would drift across chunk \
235 boundaries",
236 input.len(),
237 self.input_rate,
238 self.output_rate,
239 );
240
241 Ok(written)
242 }
243
244 #[inline(always)]
245 fn resample_simd<S: Simd>(
246 &mut self,
247 simd: S,
248 input: &[Sample],
249 out: &mut [Sample],
250 ) -> Result<usize, CodecError> {
251 const BLOCK: usize = 256;
256
257 let (step, interval) = (self.input_rate, self.output_rate);
261 let (step_whole, step_frac) = (step / interval, step % interval);
265 let (dq, dr) = (
266 step_frac * NUM_PHASES / interval,
267 step_frac * NUM_PHASES % interval,
268 );
269
270 let coeffs: &[[f32; TAPS_PER_PHASE]; NUM_PHASES] =
271 self.coeffs.as_chunks().0[..NUM_PHASES].try_into().unwrap();
272 let mut buf = [0.0f32; TAPS_PER_PHASE + BLOCK];
276 let mut written = 0usize;
277
278 for chunk in input.chunks(BLOCK) {
279 let n = chunk.len();
280 buf[..TAPS_PER_PHASE].copy_from_slice(&self.history);
281 for (dst, &src) in buf[TAPS_PER_PHASE..TAPS_PER_PHASE + n]
282 .iter_mut()
283 .zip(chunk)
284 {
285 *dst = src as f32;
286 }
287
288 let pos = self.pos as u64;
291 let end = n as u64 * interval as u64;
292 let count = if pos < end {
293 (end - pos).div_ceil(step as u64) as usize
294 } else {
295 0
296 };
297 let out_block = out
298 .get_mut(written..written + count)
299 .ok_or(CodecError::BufferTooSmall)?;
300
301 let mut idx = self.pos / interval;
307 let frac = self.pos % interval;
308 let mut phase = frac * NUM_PHASES / interval;
309 let mut rem = frac * NUM_PHASES % interval;
310
311 for slot in out_block {
312 let i = idx & (BLOCK - 1);
315 let window = buf[i + 1..i + 1 + TAPS_PER_PHASE].try_into().unwrap();
316 *slot = Self::dot_product(simd, &coeffs[phase & (NUM_PHASES - 1)], window) as i16;
318
319 rem += dr;
321 if rem >= interval {
322 rem -= interval;
323 phase += 1;
324 }
325 phase += dq;
326 idx += step_whole + phase / NUM_PHASES;
327 phase %= NUM_PHASES;
328 }
329
330 written += count;
331 self.pos = (pos + count as u64 * step as u64 - end) as usize;
332 self.history.copy_from_slice(&buf[n..n + TAPS_PER_PHASE]);
333 }
334
335 Ok(written)
336 }
337
338 pub fn max_output_samples(&self, n_input: usize) -> usize {
341 if self.input_rate == self.output_rate {
342 return n_input;
343 }
344 let count = (n_input as u128 * self.output_rate as u128).div_ceil(self.input_rate as u128);
347 count.min(usize::MAX as u128) as usize
348 }
349
350 #[cfg(feature = "std")]
352 pub fn resample(&mut self, input: &[Sample]) -> PcmBuf {
353 let max = self.max_output_samples(input.len());
354 let mut out = vec![0i16; max];
355 match self.resample_into(input, &mut out) {
356 Ok(n) => {
357 out.truncate(n);
358 out
359 }
360 Err(_) => Vec::new(),
361 }
362 }
363
364 pub fn reset(&mut self) {
365 self.history.fill(0.0);
366 self.pos = 0;
367 }
368}
369
370fn default_level() -> Level {
378 let level = Level::try_detect().unwrap_or_else(Level::baseline);
379 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
380 if level.as_avx512().is_some()
381 && let Some(avx2) = level.as_avx2()
382 {
383 return avx2.level();
384 }
385 level
386}
387
388#[cfg(feature = "std")]
392pub fn resample(input: &[Sample], input_sample_rate: u32, output_sample_rate: u32) -> PcmBuf {
393 if input_sample_rate == output_sample_rate {
394 return input.to_vec();
395 }
396 let mut coeffs = vec![0.0f32; COEFFS_LEN];
397 let mut r = match Resampler::new(
398 input_sample_rate as usize,
399 output_sample_rate as usize,
400 &mut coeffs,
401 ) {
402 Ok(r) => r,
403 Err(_) => return Vec::new(),
404 };
405 r.resample(input)
406}
407
408#[cfg(feature = "std")]
417pub struct BoxedResampler {
418 inner: Resampler<'static>,
423 #[allow(dead_code)]
425 coeffs: Box<[f32]>,
426}
427
428#[cfg(feature = "std")]
429impl BoxedResampler {
430 pub fn new(input_rate: usize, output_rate: usize) -> Result<Self, CodecError> {
435 let mut coeffs = vec![0.0f32; COEFFS_LEN].into_boxed_slice();
436 let inner = unsafe {
442 let ptr = coeffs.as_mut_ptr();
443 let loan: &'static mut [f32] = core::slice::from_raw_parts_mut(ptr, coeffs.len());
444 Resampler::new(input_rate, output_rate, loan)?
445 };
446 Ok(Self { inner, coeffs })
447 }
448
449 pub fn resample(&mut self, input: &[Sample]) -> PcmBuf {
451 self.inner.resample(input)
452 }
453
454 pub fn resample_into(
456 &mut self,
457 input: &[Sample],
458 out: &mut [Sample],
459 ) -> Result<usize, CodecError> {
460 self.inner.resample_into(input, out)
461 }
462
463 pub fn max_output_samples(&self, n_input: usize) -> usize {
465 self.inner.max_output_samples(n_input)
466 }
467
468 pub fn reset(&mut self) {
470 self.inner.reset();
471 }
472}
473
474#[cfg(test)]
475mod tests {
476 use super::*;
477 use alloc::boxed::Box;
478 use alloc::vec;
479 use alloc::vec::Vec;
480 use core::f32::consts::PI as PI_F32;
481 #[cfg(feature = "std")]
482 use std::time::Instant;
483
484 fn resample(r: &mut Resampler<'_>, input: &[Sample]) -> Vec<Sample> {
488 let mut out = vec![0i16; r.max_output_samples(input.len())];
489 let n = r
490 .resample_into(input, &mut out)
491 .expect("out sized by max_output_samples");
492 out.truncate(n);
493 out
494 }
495
496 fn new_resampler(input_rate: usize, output_rate: usize) -> Resampler<'static> {
497 let coeffs: &'static mut [f32] = Box::leak(vec![0.0f32; COEFFS_LEN].into_boxed_slice());
499 Resampler::new(input_rate, output_rate, coeffs).expect("resampler init")
500 }
501
502 #[test]
506 fn test_exact_sample_counts_for_repeated_20ms_blocks() {
507 for input_rate in [8000, 16000, 44100, 48000] {
508 for output_rate in [8000, 16000, 44100, 48000] {
509 let mut r = new_resampler(input_rate, output_rate);
510 let input = vec![1000; input_rate / 50];
511 let mut output = vec![0; output_rate / 50];
512 for block in 0..10 {
513 let n = r.resample_into(&input, &mut output).unwrap_or_else(|e| {
514 panic!("{input_rate} -> {output_rate}, block {block}: {e:?}")
515 });
516 assert_eq!(
517 n,
518 output.len(),
519 "{input_rate} -> {output_rate}, block {block}"
520 );
521 }
522 }
523 }
524 }
525
526 #[test]
529 fn test_8k_to_48k_is_always_960() {
530 let mut r = new_resampler(8000, 48000);
531 for block in 0..10 {
532 let out = resample(&mut r, &vec![1000i16; 160]);
533 assert_eq!(out.len(), 960, "block {block}");
534 }
535 }
536
537 #[test]
538 fn test_resample_8k_to_16k() {
539 let mut resampler = new_resampler(8000, 16000);
540 let input = vec![1000i16; 80];
541 let output = resample(&mut resampler, &input);
542 assert!(output.len() >= 150 && output.len() <= 170);
543 for &s in &output[48..output.len().saturating_sub(48)] {
544 assert!((s - 1000).abs() < 100, "Value {} is too far from 1000", s);
545 }
546 }
547
548 #[test]
549 fn test_resample_16k_to_8k() {
550 let mut resampler = new_resampler(16000, 8000);
551 let input = vec![1000i16; 160];
552 let output = resample(&mut resampler, &input);
553 assert!(output.len() >= 75 && output.len() <= 85);
554 let skip = output.len() / 4;
555 for &s in &output[skip..output.len() - skip] {
556 assert!((s - 1000).abs() < 100, "Value {} is too far from 1000", s);
557 }
558 }
559
560 #[test]
561 fn test_frequency_response_downsample() {
562 let mut resampler = new_resampler(16000, 8000);
563 let freq = 2000.0_f32; let samples: Vec<i16> = (0..160)
565 .map(|i| ((i as f32 * freq * 2.0 * PI_F32 / 16000.0).sin() * 10000.0) as i16)
566 .collect();
567
568 let output = resample(&mut resampler, &samples);
569
570 let input_rms: f32 = samples
572 .iter()
573 .map(|&s| (s as f32).powi(2))
574 .sum::<f32>()
575 .sqrt()
576 / samples.len() as f32;
577 let output_rms: f32 = output
578 .iter()
579 .map(|&s| (s as f32).powi(2))
580 .sum::<f32>()
581 .sqrt()
582 / output.len() as f32;
583
584 assert!(
585 output_rms > input_rms * 0.7,
586 "Too much attenuation: input_rms={}, output_rms={}",
587 input_rms,
588 output_rms
589 );
590 }
591
592 #[test]
593 fn test_aliasing_suppression() {
594 let mut resampler = new_resampler(16000, 8000);
595 let freq = 7000.0_f32; let samples: Vec<i16> = (0..1600)
597 .map(|i| ((i as f32 * freq * 2.0 * PI_F32 / 16000.0).sin() * 10000.0) as i16)
598 .collect();
599
600 let output = resample(&mut resampler, &samples);
601
602 let output_rms: f32 =
603 (output.iter().map(|&s| (s as f32).powi(2)).sum::<f32>() / output.len() as f32).sqrt();
604 let input_rms: f32 = 10000.0 / 1.414; assert!(
607 output_rms < input_rms / 50.0,
608 "Aliasing not sufficiently suppressed: output_rms={}",
609 output_rms
610 );
611 }
612
613 #[cfg(feature = "std")]
616 #[test]
617 fn test_performance_48k_to_8k() {
618 let mut resampler = new_resampler(48000, 8000);
619 let input = vec![0i16; 48000];
620
621 let start = Instant::now();
622 let iterations = 100;
623 for _ in 0..iterations {
624 let _ = resample(&mut resampler, &input);
625 resampler.reset();
626 }
627 let duration = start.elapsed();
628 let per_second = duration.as_secs_f64() / iterations as f64;
629 println!(
630 "Resampling 1s of 48kHz to 8kHz (24 taps) took: {:.4}ms",
631 per_second * 1000.0
632 );
633 assert!(
634 per_second < 0.1,
635 "Performance regression: {}ms",
636 per_second * 1000.0
637 );
638 }
639
640 #[test]
641 fn test_continuity_between_chunks() {
642 let input_rate = 16000;
643 let output_rate = 8000;
644
645 let freq = 1000.0_f32;
646 let total_samples = 3200;
647 let input: Vec<i16> = (0..total_samples)
648 .map(|i| ((i as f32 * freq * 2.0 * PI_F32 / input_rate as f32).sin() * 5000.0) as i16)
649 .collect();
650
651 let mut resampler1 = new_resampler(input_rate, output_rate);
652 let output1 = resample(&mut resampler1, &input);
653
654 let mut resampler2 = new_resampler(input_rate, output_rate);
655 let mid = input.len() / 2;
656 let mut output2 = resample(&mut resampler2, &input[..mid]);
657 output2.extend_from_slice(&resample(&mut resampler2, &input[mid..]));
658
659 assert_eq!(output1.len(), output2.len(), "Output lengths differ");
660
661 let max_diff: i16 = output1
662 .iter()
663 .zip(output2.iter())
664 .map(|(a, b)| (a - b).abs())
665 .max()
666 .unwrap_or(0);
667
668 assert!(
669 max_diff < 100,
670 "Large discontinuity between chunks: max_diff={}",
671 max_diff
672 );
673 }
674
675 #[cfg(feature = "std")]
679 #[test]
680 fn test_boxed_resampler_matches_borrowed() {
681 let input_rate = 48000usize;
682 let output_rate = 8000usize;
683 let input: Vec<i16> = (0..4800)
684 .map(|i| ((i as f32 * 0.05).sin() * 8000.0) as i16)
685 .collect();
686
687 let expected = {
688 let mut coeffs = vec![0.0f32; COEFFS_LEN];
689 let mut borrowed = Resampler::new(input_rate, output_rate, &mut coeffs).unwrap();
690 resample(&mut borrowed, &input)
691 };
692
693 let mut boxed = BoxedResampler::new(input_rate, output_rate).unwrap();
694 let expected_max = (input.len() * output_rate).div_ceil(input_rate);
696 assert_eq!(boxed.max_output_samples(input.len()), expected_max);
697 let got = boxed.resample(&input);
698 assert_eq!(
699 expected, got,
700 "BoxedResampler output must match borrowed Resampler"
701 );
702
703 let mut moved = boxed;
708 let again = moved.resample(&input);
709 assert_eq!(expected.len(), again.len());
710
711 moved.reset();
714 let after_reset = moved.resample(&input);
715 assert_eq!(expected, after_reset);
716
717 assert!(
718 BoxedResampler::new(0, 8000).is_err(),
719 "zero input rate must error"
720 );
721 assert!(
722 BoxedResampler::new(48000, 0).is_err(),
723 "zero output rate must error"
724 );
725 }
726
727 #[test]
731 fn test_exact_timing_survives_unaligned_chunks_and_reset() {
732 for (input_rate, output_rate) in [
733 (16000, 48000),
734 (48000, 16000),
735 (44100, 48000),
736 (48000, 44100),
737 ] {
738 let input: Vec<i16> = (0..2000).map(|i| ((i * 37 % 200) - 100) as i16).collect();
739 let mut r = new_resampler(input_rate, output_rate);
740 let expected = resample(&mut r, &input);
741 assert_eq!(
742 expected.len(),
743 (input.len() * output_rate).div_ceil(input_rate)
744 );
745 for chunk_size in [
746 1, 2, 3, 5, 7, 11, 23, 24, 25, 47, 88, 160, 255, 256, 257, 319,
747 ] {
748 r.reset();
749 let mut got = Vec::new();
750 let mut consumed = 0;
751 for chunk in input.chunks(chunk_size) {
752 got.extend(resample(&mut r, chunk));
753 consumed += chunk.len();
754 assert_eq!(
755 got.len(),
756 (consumed * output_rate).div_ceil(input_rate),
757 "{input_rate} -> {output_rate}, chunk {chunk_size}, consumed {consumed}"
758 );
759 assert!(resample(&mut r, &[]).is_empty());
760 }
761 assert_eq!(
762 got, expected,
763 "{input_rate} -> {output_rate}, chunk {chunk_size}"
764 );
765 }
766 }
767 }
768
769 const RATIOS: [(usize, usize); 8] = [
770 (8000, 16000),
771 (16000, 8000),
772 (48000, 8000),
773 (8000, 48000),
774 (44100, 48000),
775 (48000, 44100),
776 (8000, 22050),
777 (22050, 16000),
778 ];
779
780 fn test_signals(len: usize) -> [Vec<i16>; 2] {
783 let mut seed = 0x1234_5678u32;
784 let noisy: Vec<i16> = (0..len)
785 .map(|i| {
786 seed = seed.wrapping_mul(1_664_525).wrapping_add(1_013_904_223);
787 let noise = ((seed >> 16) as i16 as f32) * 0.2;
788 ((i as f32 * 0.07).sin() * 15000.0 + noise) as i16
789 })
790 .collect();
791 let square: Vec<i16> = (0..len)
792 .map(|i| {
793 if (i / 10) % 2 == 0 {
794 i16::MAX
795 } else {
796 i16::MIN
797 }
798 })
799 .collect();
800 [noisy, square]
801 }
802
803 fn reference_resample(coeffs: &[f32], fin: usize, fout: usize, input: &[i16]) -> Vec<i16> {
807 let mut history = [0.0f32; TAPS_PER_PHASE];
808 let mut out = Vec::new();
809 for (input_index, &sample) in input.iter().enumerate() {
810 history.copy_within(1.., 0);
811 history[TAPS_PER_PHASE - 1] = sample as f32;
812 while out.len() * fin < (input_index + 1) * fout {
813 let phase = (out.len() * fin % fout) * NUM_PHASES / fout;
814 let c = &coeffs[phase * TAPS_PER_PHASE..(phase + 1) * TAPS_PER_PHASE];
815 let acc: f64 = c
816 .iter()
817 .zip(&history)
818 .map(|(&c, &h)| c as f64 * h as f64)
819 .sum();
820 out.push(acc.clamp(i16::MIN as f64, i16::MAX as f64) as i16);
821 }
822 }
823 out
824 }
825
826 #[test]
829 fn test_simd_matches_scalar_reference() {
830 #[cfg(feature = "std")]
832 let levels = [Level::baseline(), default_level(), Level::new()];
833 #[cfg(not(feature = "std"))]
834 let levels = [Level::baseline(), default_level()];
835 for (from, to) in RATIOS {
836 for input in test_signals(2000) {
837 for level in levels {
838 let mut r = new_resampler(from, to);
839 r.level = level;
840 let expected = reference_resample(r.coeffs, from, to, &input);
841 let got = resample(&mut r, &input);
842 assert_eq!(expected.len(), got.len(), "{from}->{to} {level:?}");
843 for (i, (&e, &g)) in expected.iter().zip(&got).enumerate() {
844 assert!(
845 (e as i32 - g as i32).abs() <= 1,
846 "{from}->{to} {level:?}: sample {i}: expected {e}, got {g}"
847 );
848 }
849 }
850 }
851 }
852 }
853
854 #[test]
858 fn test_chunked_is_bit_identical() {
859 let [input, _] = test_signals(3000);
860 for (from, to) in RATIOS {
861 let whole = resample(&mut new_resampler(from, to), &input);
862 for chunk in [1, 2, 7, 23, 24, 25, 47, 160, 255, 256, 257, 1000] {
863 let mut r = new_resampler(from, to);
864 let mut chunked = Vec::new();
865 for part in input.chunks(chunk) {
866 chunked.extend_from_slice(&resample(&mut r, part));
867 }
868 assert_eq!(whole, chunked, "{from}->{to} chunk={chunk}");
869 }
870 }
871 }
872
873 #[test]
876 fn test_dc_gain_all_ratios() {
877 for (from, to) in RATIOS {
878 for level in [1000i16, -20000, i16::MAX] {
879 let output = resample(&mut new_resampler(from, to), &vec![level; 2000]);
880 let warmup = (TAPS_PER_PHASE * to).div_ceil(from) + 1;
882 for (i, &s) in output.iter().enumerate().skip(warmup) {
883 assert!(
884 (s as i32 - level as i32).abs() <= 2,
885 "{from}->{to}: sample {i} = {s}, expected ~{level}"
886 );
887 }
888 }
889 }
890 }
891
892 #[test]
895 fn test_max_output_samples_is_upper_bound() {
896 for (from, to) in RATIOS {
897 let mut r = new_resampler(from, to);
898 let input = vec![100i16; 200];
899 for n in 1..=200 {
900 let mut out = vec![0i16; r.max_output_samples(n)];
901 r.resample_into(&input[..n], &mut out)
902 .unwrap_or_else(|e| panic!("{from}->{to} n={n}: {e:?}"));
903 }
904 }
905 }
906
907 #[test]
908 fn test_output_buffer_too_small() {
909 let mut r = new_resampler(8000, 16000);
910 let mut out = [0i16; 10];
911 assert!(matches!(
912 r.resample_into(&[0i16; 160], &mut out),
913 Err(CodecError::BufferTooSmall)
914 ));
915
916 let mut r = new_resampler(8000, 8000);
918 assert!(matches!(
919 r.resample_into(&[0i16; 160], &mut out),
920 Err(CodecError::BufferTooSmall)
921 ));
922 }
923
924 #[test]
925 fn test_same_rate_passthrough() {
926 let [input, _] = test_signals(500);
927 let mut r = new_resampler(16000, 16000);
928 assert_eq!(r.max_output_samples(input.len()), input.len());
929 assert_eq!(resample(&mut r, &input), input);
930 }
931
932 #[test]
933 fn test_new_rejects_bad_args() {
934 let mut small = vec![0.0f32; COEFFS_LEN - 1];
935 assert!(matches!(
936 Resampler::new(8000, 16000, &mut small),
937 Err(CodecError::BufferTooSmall)
938 ));
939 let mut coeffs = vec![0.0f32; COEFFS_LEN];
940 assert!(matches!(
941 Resampler::new(0, 16000, &mut coeffs),
942 Err(CodecError::InvalidInput)
943 ));
944 assert!(matches!(
945 Resampler::new(8000, 0, &mut coeffs),
946 Err(CodecError::InvalidInput)
947 ));
948 }
949
950 #[test]
952 fn test_reset_mid_stream() {
953 let [input, _] = test_signals(1000);
954 let mut r = new_resampler(44100, 48000);
955 let fresh = resample(&mut r, &input);
956 resample(&mut r, &input[..13]);
958 r.reset();
959 assert_eq!(resample(&mut r, &input), fresh);
960 }
961}