1use core::f64::consts::PI as PI_F64;
2
3use fearless_simd::{Level, Simd, dispatch};
4
5use super::{CodecError, Sample};
6
7#[cfg(feature = "std")]
8use super::PcmBuf;
9
10pub const NUM_PHASES: usize = 256;
12pub const TAPS_PER_PHASE: usize = 24;
14pub const COEFFS_LEN: usize = NUM_PHASES * TAPS_PER_PHASE;
16
17const Q15_SHIFT: u32 = 15;
20const _: () = assert!(NUM_PHASES == 256);
22const MID_TAPS: core::ops::Range<usize> = 8..16;
26const MAX_PART_ABS_COEFF_SUM: i32 = 65535;
29
30#[inline]
32fn fsin(x: f64) -> f64 {
33 #[cfg(feature = "std")]
34 {
35 f64::sin(x)
36 }
37 #[cfg(not(feature = "std"))]
38 {
39 libm::sin(x)
40 }
41}
42
43#[inline]
45fn fsqrt(x: f64) -> f64 {
46 #[cfg(feature = "std")]
47 {
48 f64::sqrt(x)
49 }
50 #[cfg(not(feature = "std"))]
51 {
52 libm::sqrt(x)
53 }
54}
55
56#[inline]
58fn fround(x: f64) -> f64 {
59 #[cfg(feature = "std")]
60 {
61 x.round()
62 }
63 #[cfg(not(feature = "std"))]
64 {
65 libm::round(x)
66 }
67}
68
69pub struct Resampler<'a> {
80 input_rate: usize,
81 output_rate: usize,
82 coeffs: &'a mut [i16],
83 history: [i16; TAPS_PER_PHASE],
85 pos: usize,
92 level: Level,
94}
95
96fn bessel_i0(x: f64) -> f64 {
97 let mut sum = 1.0_f64;
98 let mut term = 1.0_f64;
99 let x_sq = x * x * 0.25;
100
101 for m in 1..=30 {
102 term *= x_sq / (m * m) as f64;
103 sum += term;
104 if term < 1e-15 * sum {
105 break;
106 }
107 }
108 sum
109}
110
111fn kaiser_window(n: usize, n_total: usize, beta: f64) -> f64 {
112 if n_total <= 1 {
113 return 1.0;
114 }
115 let alpha = (n_total - 1) as f64 / 2.0;
116 let x = (n as f64 - alpha) / alpha;
117 let arg = beta * fsqrt(1.0 - x * x);
118 bessel_i0(arg) / bessel_i0(beta)
119}
120
121impl<'a> Resampler<'a> {
122 pub fn new(
129 input_rate: usize,
130 output_rate: usize,
131 coeffs: &'a mut [i16],
132 ) -> Result<Self, CodecError> {
133 if coeffs.len() < COEFFS_LEN {
134 return Err(CodecError::BufferTooSmall);
135 }
136 if input_rate == 0 || output_rate == 0 {
137 return Err(CodecError::InvalidInput);
138 }
139
140 const KAISER_BETA: f64 = 7.0;
141
142 let ratio = output_rate as f64 / input_rate as f64;
143 let num_phases = NUM_PHASES;
144 let taps_per_phase = TAPS_PER_PHASE;
145 let filter_len = num_phases * taps_per_phase;
146
147 if input_rate.checked_add(output_rate).is_none()
152 || output_rate.checked_mul(NUM_PHASES).is_none()
153 {
154 return Err(CodecError::InvalidInput);
155 }
156
157 let coeffs = &mut coeffs[..filter_len];
158
159 let cutoff = if ratio < 1.0 {
160 ratio * 0.5 * 0.95
161 } else {
162 0.5 * 0.95
163 };
164
165 let center = (taps_per_phase as f64 - 1.0) / 2.0;
166
167 let mut phase_coeffs: [f64; TAPS_PER_PHASE] = [0.0; TAPS_PER_PHASE];
170
171 for p in 0..num_phases {
172 let mut sum = 0.0_f64;
173
174 for t in 0..taps_per_phase {
175 let x = t as f64 - center - (p as f64 / num_phases as f64);
176
177 let sinc_val = if x.abs() < 1e-10 {
178 2.0 * cutoff
179 } else {
180 let x_pi = x * PI_F64;
181 fsin(x_pi * 2.0 * cutoff) / x_pi
182 };
183
184 let full_filter_idx = t * num_phases + p;
185 let window = kaiser_window(full_filter_idx, filter_len, KAISER_BETA);
186
187 phase_coeffs[t] = sinc_val * window;
188 sum += phase_coeffs[t];
189 }
190
191 let mut part_abs_sums = [0i32; 2];
192 for t in 0..taps_per_phase {
193 let q = fround((phase_coeffs[t] / sum) * 32768.0);
195 let q = q.clamp(-32768.0, 32767.0) as i16;
196 coeffs[p * taps_per_phase + t] = q;
197 part_abs_sums[MID_TAPS.contains(&t) as usize] += (q as i32).abs();
198 }
199 if part_abs_sums.iter().any(|&s| s > MAX_PART_ABS_COEFF_SUM) {
202 return Err(CodecError::InvalidInput);
203 }
204 }
205
206 Ok(Self {
207 input_rate,
208 output_rate,
209 coeffs,
210 history: [0; TAPS_PER_PHASE],
211 pos: 0,
212 level: default_level(),
213 })
214 }
215
216 pub fn input_rate(&self) -> usize {
217 self.input_rate
218 }
219
220 pub fn output_rate(&self) -> usize {
221 self.output_rate
222 }
223
224 #[inline(always)]
238 fn dot_q15(history: &[i16; TAPS_PER_PHASE], coeffs: &[i16; TAPS_PER_PHASE]) -> i32 {
239 let mut mid = 0i32;
240 for i in MID_TAPS {
241 mid = mid.wrapping_add((history[i] as i32) * (coeffs[i] as i32));
242 }
243 let mut outer = 0i32;
244 for i in 0..MID_TAPS.start {
245 outer = outer.wrapping_add((history[i] as i32) * (coeffs[i] as i32));
246 }
247 for i in MID_TAPS.end..TAPS_PER_PHASE {
248 outer = outer.wrapping_add((history[i] as i32) * (coeffs[i] as i32));
249 }
250 ((mid as i64 + outer as i64) >> Q15_SHIFT) as i32
251 }
252
253 pub fn resample_into(
259 &mut self,
260 input: &[Sample],
261 out: &mut [Sample],
262 ) -> Result<usize, CodecError> {
263 if self.input_rate == self.output_rate {
264 if out.len() < input.len() {
265 return Err(CodecError::BufferTooSmall);
266 }
267 out[..input.len()].copy_from_slice(input);
268 return Ok(input.len());
269 }
270
271 let expected: u128 = {
277 let ticks = (input.len() as u128) * (self.output_rate as u128);
278 if ticks > self.pos as u128 {
279 (ticks - self.pos as u128).div_ceil(self.input_rate as u128)
280 } else {
281 0
282 }
283 };
284
285 let level = self.level;
288 let written = dispatch!(level, simd => self.resample_simd(simd, input, out))?;
289
290 debug_assert_eq!(
294 written as u128,
295 expected,
296 "resampler produced {written} samples, exact timing requires {expected} \
297 for {} inputs ({input_rate} -> {output_rate} at pos {}): output \
298 sizes would drift across chunk boundaries",
299 input.len(),
300 self.pos,
301 input_rate = self.input_rate,
302 output_rate = self.output_rate,
303 );
304
305 Ok(written)
306 }
307
308 #[inline(always)]
309 fn resample_simd<S: Simd>(
310 &mut self,
311 _simd: S,
312 input: &[Sample],
313 out: &mut [Sample],
314 ) -> Result<usize, CodecError> {
315 const BLOCK: usize = 256;
320
321 let (step, interval) = (self.input_rate, self.output_rate);
323 let (step_whole, step_frac) = (step / interval, step % interval);
327 let (dq, dr) = (
328 step_frac * NUM_PHASES / interval,
329 step_frac * NUM_PHASES % interval,
330 );
331
332 let coeffs: &[[i16; TAPS_PER_PHASE]; NUM_PHASES] =
333 self.coeffs.as_chunks().0[..NUM_PHASES].try_into().unwrap();
334 let mut buf = [0i16; TAPS_PER_PHASE + BLOCK];
338 let mut written = 0usize;
339
340 for chunk in input.chunks(BLOCK) {
341 let n = chunk.len();
342 buf[..TAPS_PER_PHASE].copy_from_slice(&self.history);
343 buf[TAPS_PER_PHASE..TAPS_PER_PHASE + n].copy_from_slice(chunk);
344
345 let pos = self.pos as u64;
348 let end = n as u64 * interval as u64;
349 let count = if pos < end {
350 (end - pos).div_ceil(step as u64) as usize
351 } else {
352 0
353 };
354 let out_block = out
355 .get_mut(written..written + count)
356 .ok_or(CodecError::BufferTooSmall)?;
357
358 let mut idx = self.pos / interval;
364 let frac = self.pos % interval;
365 let mut phase = frac * NUM_PHASES / interval;
366 let mut rem = frac * NUM_PHASES % interval;
367
368 for slot in out_block {
369 let i = idx & (BLOCK - 1);
372 let window = buf[i + 1..i + 1 + TAPS_PER_PHASE].try_into().unwrap();
373 let out_sample = Self::dot_q15(window, &coeffs[phase & (NUM_PHASES - 1)]);
374 *slot = out_sample.clamp(i16::MIN as i32, i16::MAX as i32) as i16;
375
376 rem += dr;
378 if rem >= interval {
379 rem -= interval;
380 phase += 1;
381 }
382 phase += dq;
383 idx += step_whole + phase / NUM_PHASES;
384 phase %= NUM_PHASES;
385 }
386
387 written += count;
388 self.pos = (pos + count as u64 * step as u64 - end) as usize;
389 self.history.copy_from_slice(&buf[n..n + TAPS_PER_PHASE]);
390 }
391
392 Ok(written)
393 }
394
395 pub fn max_output_samples(&self, n_input: usize) -> usize {
398 if self.input_rate == self.output_rate {
399 return n_input;
400 }
401 let count = (n_input as u128 * self.output_rate as u128).div_ceil(self.input_rate as u128);
404 count.min(usize::MAX as u128) as usize
405 }
406
407 #[cfg(feature = "std")]
409 pub fn resample(&mut self, input: &[Sample]) -> PcmBuf {
410 let max = self.max_output_samples(input.len());
411 let mut out = vec![0i16; max];
412 match self.resample_into(input, &mut out) {
413 Ok(n) => {
414 debug_assert!(
415 n <= max,
416 "resampler emitted {n} samples but `max_output_samples` \
417 said {max}; buffer sizing invariant broken"
418 );
419 out.truncate(n);
420 out
421 }
422 Err(e) => {
423 if cfg!(debug_assertions) {
429 panic!("resample_into failed inside `resample`: {e:?}");
430 }
431 Vec::new()
432 }
433 }
434 }
435
436 pub fn reset(&mut self) {
437 self.history.fill(0);
438 self.pos = 0;
439 }
440}
441
442fn default_level() -> Level {
451 let level = Level::try_detect().unwrap_or_else(Level::baseline);
452 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
453 if level.as_avx512().is_some()
454 && let Some(avx2) = level.as_avx2()
455 {
456 return avx2.level();
457 }
458 level
459}
460
461#[cfg(feature = "std")]
465pub fn resample(input: &[Sample], input_sample_rate: u32, output_sample_rate: u32) -> PcmBuf {
466 if input_sample_rate == output_sample_rate {
467 return input.to_vec();
468 }
469 let mut coeffs = vec![0i16; COEFFS_LEN];
470 let mut r = match Resampler::new(
471 input_sample_rate as usize,
472 output_sample_rate as usize,
473 &mut coeffs,
474 ) {
475 Ok(r) => r,
476 Err(_) => return Vec::new(),
477 };
478 r.resample(input)
479}
480
481#[cfg(feature = "std")]
490pub struct BoxedResampler {
491 inner: Resampler<'static>,
496 #[allow(dead_code)]
498 coeffs: Box<[i16]>,
499}
500
501#[cfg(feature = "std")]
502impl BoxedResampler {
503 pub fn new(input_rate: usize, output_rate: usize) -> Result<Self, CodecError> {
508 let mut coeffs = vec![0i16; COEFFS_LEN].into_boxed_slice();
509 let inner = unsafe {
515 let ptr = coeffs.as_mut_ptr();
516 let loan: &'static mut [i16] = core::slice::from_raw_parts_mut(ptr, coeffs.len());
517 Resampler::new(input_rate, output_rate, loan)?
518 };
519 Ok(Self { inner, coeffs })
520 }
521
522 pub fn resample(&mut self, input: &[Sample]) -> PcmBuf {
524 self.inner.resample(input)
525 }
526
527 pub fn resample_into(
529 &mut self,
530 input: &[Sample],
531 out: &mut [Sample],
532 ) -> Result<usize, CodecError> {
533 self.inner.resample_into(input, out)
534 }
535
536 pub fn max_output_samples(&self, n_input: usize) -> usize {
538 self.inner.max_output_samples(n_input)
539 }
540
541 pub fn reset(&mut self) {
543 self.inner.reset();
544 }
545}
546
547#[cfg(test)]
548mod tests {
549 use super::*;
550 use alloc::boxed::Box;
551 use alloc::vec;
552 use alloc::vec::Vec;
553 use core::f32::consts::PI as PI_F32;
554 #[cfg(feature = "std")]
555 use std::time::Instant;
556
557 fn resample(r: &mut Resampler<'_>, input: &[Sample]) -> Vec<Sample> {
561 let mut out = vec![0i16; r.max_output_samples(input.len())];
562 let n = r
563 .resample_into(input, &mut out)
564 .expect("out sized by max_output_samples");
565 out.truncate(n);
566 out
567 }
568
569 fn new_resampler(input_rate: usize, output_rate: usize) -> Resampler<'static> {
570 let coeffs: &'static mut [i16] = Box::leak(vec![0i16; COEFFS_LEN].into_boxed_slice());
572 Resampler::new(input_rate, output_rate, coeffs).expect("resampler init")
573 }
574
575 #[test]
576 fn test_exact_sample_counts_for_repeated_20ms_blocks() {
577 for input_rate in [8000, 16000, 44100, 48000] {
578 for output_rate in [8000, 16000, 44100, 48000] {
579 let mut r = new_resampler(input_rate, output_rate);
580 let input = vec![1000; input_rate / 50];
581 let mut output = vec![0; output_rate / 50];
582 for block in 0..10 {
583 let n = r.resample_into(&input, &mut output).unwrap_or_else(|e| {
584 panic!("{input_rate} -> {output_rate}, block {block}: {e:?}")
585 });
586 assert_eq!(
587 n,
588 output.len(),
589 "{input_rate} -> {output_rate}, block {block}"
590 );
591 }
592 }
593 }
594 }
595
596 #[test]
597 fn test_exact_timing_survives_unaligned_chunks_and_reset() {
598 for (input_rate, output_rate) in [
599 (16000, 48000),
600 (48000, 16000),
601 (44100, 48000),
602 (48000, 44100),
603 ] {
604 let input: Vec<i16> = (0..2000).map(|i| ((i * 37 % 200) - 100) as i16).collect();
605 let mut r = new_resampler(input_rate, output_rate);
606 let expected = resample(&mut r, &input);
607 assert_eq!(
608 expected.len(),
609 (input.len() * output_rate).div_ceil(input_rate)
610 );
611 for chunk_size in [1, 2, 3, 5, 7, 11, 23, 24, 25, 47, 88, 160, 319] {
615 r.reset();
616 let mut got = Vec::new();
617 let mut consumed = 0;
618 for chunk in input.chunks(chunk_size) {
619 got.extend(resample(&mut r, chunk));
620 consumed += chunk.len();
621 assert_eq!(
622 got.len(),
623 (consumed * output_rate).div_ceil(input_rate),
624 "{input_rate} -> {output_rate}, chunk {chunk_size}, consumed {consumed}"
625 );
626 assert!(resample(&mut r, &[]).is_empty());
627 }
628 assert_eq!(
629 got, expected,
630 "{input_rate} -> {output_rate}, chunk {chunk_size}"
631 );
632 }
633 r.reset();
634 assert_eq!(resample(&mut r, &input), expected);
635 }
636 }
637
638 #[cfg(all(feature = "opus", feature = "std"))]
641 #[test]
642 fn test_resampled_20ms_frames_encode_as_opus() {
643 use crate::{Encoder, opus::OpusEncoder};
644
645 for input_rate in [8000, 16000] {
646 let mut r = new_resampler(input_rate, 48000);
647 let mut encoder = OpusEncoder::new_default();
648 for _ in 0..3 {
649 let pcm = resample(&mut r, &vec![0; input_rate / 50]);
650 assert_eq!(pcm.len(), 960);
651 assert!(!encoder.encode(&pcm).is_empty());
652 }
653 }
654 }
655
656 #[test]
657 fn test_resample_8k_to_16k() {
658 let mut resampler = new_resampler(8000, 16000);
659 let input = vec![1000i16; 80];
660 let output = resample(&mut resampler, &input);
661 assert!(output.len() >= 150 && output.len() <= 170);
662 for &s in &output[48..output.len().saturating_sub(48)] {
663 assert!((s - 1000).abs() < 100, "Value {} is too far from 1000", s);
664 }
665 }
666
667 #[test]
668 fn test_resample_16k_to_8k() {
669 let mut resampler = new_resampler(16000, 8000);
670 let input = vec![1000i16; 160];
671 let output = resample(&mut resampler, &input);
672 assert!(output.len() >= 75 && output.len() <= 85);
673 let skip = output.len() / 4;
674 for &s in &output[skip..output.len() - skip] {
675 assert!((s - 1000).abs() < 100, "Value {} is too far from 1000", s);
676 }
677 }
678
679 #[test]
680 fn test_frequency_response_downsample() {
681 let mut resampler = new_resampler(16000, 8000);
682 let freq = 2000.0_f32; let samples: Vec<i16> = (0..160)
684 .map(|i| ((i as f32 * freq * 2.0 * PI_F32 / 16000.0).sin() * 10000.0) as i16)
685 .collect();
686
687 let output = resample(&mut resampler, &samples);
688
689 let input_rms: f32 = samples
691 .iter()
692 .map(|&s| (s as f32).powi(2))
693 .sum::<f32>()
694 .sqrt()
695 / samples.len() as f32;
696 let output_rms: f32 = output
697 .iter()
698 .map(|&s| (s as f32).powi(2))
699 .sum::<f32>()
700 .sqrt()
701 / output.len() as f32;
702
703 assert!(
704 output_rms > input_rms * 0.7,
705 "Too much attenuation: input_rms={}, output_rms={}",
706 input_rms,
707 output_rms
708 );
709 }
710
711 #[test]
712 fn test_aliasing_suppression() {
713 let mut resampler = new_resampler(16000, 8000);
714 let freq = 7000.0_f32; let samples: Vec<i16> = (0..1600)
716 .map(|i| ((i as f32 * freq * 2.0 * PI_F32 / 16000.0).sin() * 10000.0) as i16)
717 .collect();
718
719 let output = resample(&mut resampler, &samples);
720
721 let output_rms: f32 =
722 (output.iter().map(|&s| (s as f32).powi(2)).sum::<f32>() / output.len() as f32).sqrt();
723 let input_rms: f32 = 10000.0 / 1.414; assert!(
726 output_rms < input_rms / 50.0,
727 "Aliasing not sufficiently suppressed: output_rms={}",
728 output_rms
729 );
730 }
731
732 #[cfg(feature = "std")]
735 #[test]
736 fn test_performance_48k_to_8k() {
737 let mut resampler = new_resampler(48000, 8000);
738 let input = vec![0i16; 48000];
739
740 let start = Instant::now();
741 let iterations = 100;
742 for _ in 0..iterations {
743 let _ = resample(&mut resampler, &input);
744 resampler.reset();
745 }
746 let duration = start.elapsed();
747 let per_second = duration.as_secs_f64() / iterations as f64;
748 println!(
749 "Resampling 1s of 48kHz to 8kHz (24 taps) took: {:.4}ms",
750 per_second * 1000.0
751 );
752 assert!(
753 per_second < 0.1,
754 "Performance regression: {}ms",
755 per_second * 1000.0
756 );
757 }
758
759 #[test]
760 fn test_continuity_between_chunks() {
761 let input_rate = 16000;
762 let output_rate = 8000;
763
764 let freq = 1000.0_f32;
765 let total_samples = 3200;
766 let input: Vec<i16> = (0..total_samples)
767 .map(|i| ((i as f32 * freq * 2.0 * PI_F32 / input_rate as f32).sin() * 5000.0) as i16)
768 .collect();
769
770 let mut resampler1 = new_resampler(input_rate, output_rate);
771 let output1 = resample(&mut resampler1, &input);
772
773 let mut resampler2 = new_resampler(input_rate, output_rate);
774 let mid = input.len() / 2;
775 let mut output2 = resample(&mut resampler2, &input[..mid]);
776 output2.extend_from_slice(&resample(&mut resampler2, &input[mid..]));
777
778 assert_eq!(output1.len(), output2.len(), "Output lengths differ");
779
780 let max_diff: i16 = output1
781 .iter()
782 .zip(output2.iter())
783 .map(|(a, b)| (a - b).abs())
784 .max()
785 .unwrap_or(0);
786
787 assert!(
788 max_diff < 100,
789 "Large discontinuity between chunks: max_diff={}",
790 max_diff
791 );
792 }
793
794 #[test]
799 fn test_q15_core_matches_float_reference() {
800 let fin = 16000usize;
801 let fout = 48000usize;
802 let mut coeffs = vec![0i16; COEFFS_LEN];
803 let mut r = Resampler::new(fin, fout, &mut coeffs).unwrap();
804
805 let input: Vec<i16> = (0..2000)
806 .map(|i| (((i * 37) % 200 - 100) * 60) as i16)
807 .collect();
808 let got = resample(&mut r, &input);
809
810 let taps = TAPS_PER_PHASE;
813 let mut hist = [0.0f32; TAPS_PER_PHASE];
814 let mut want = Vec::new();
815 for (input_index, &s) in input.iter().enumerate() {
816 hist.copy_within(1..taps, 0);
817 hist[taps - 1] = s as f32;
818 while want.len() * fin < (input_index + 1) * fout {
819 let phase = (want.len() * fin % fout) * NUM_PHASES / fout;
820 let mut acc = 0.0f32;
821 for t in 0..taps {
822 acc += hist[t] * (coeffs[phase * taps + t] as f32 / 32768.0);
823 }
824 want.push(acc.round().clamp(-32768.0, 32767.0) as i16);
825 }
826 }
827
828 assert_eq!(got.len(), want.len());
829 let se: f64 = got
830 .iter()
831 .zip(&want)
832 .map(|(&a, &b)| {
833 let d = a as f64 - b as f64;
834 d * d
835 })
836 .sum();
837 let sig: f64 = want.iter().map(|&b| (b as f64) * (b as f64)).sum();
838 let snr = 10.0 * (sig / se.max(1.0)).log10();
839 assert!(
840 snr > 60.0,
841 "integer core diverges from float: {:.1} dB",
842 snr
843 );
844 }
845
846 #[cfg(feature = "std")]
850 #[test]
851 fn test_boxed_resampler_matches_borrowed() {
852 let input_rate = 48000usize;
853 let output_rate = 8000usize;
854 let input: Vec<i16> = (0..4800)
855 .map(|i| ((i as f32 * 0.05).sin() * 8000.0) as i16)
856 .collect();
857
858 let expected = {
859 let mut coeffs = vec![0i16; COEFFS_LEN];
860 let mut borrowed = Resampler::new(input_rate, output_rate, &mut coeffs).unwrap();
861 resample(&mut borrowed, &input)
862 };
863
864 let mut boxed = BoxedResampler::new(input_rate, output_rate).unwrap();
865 let expected_max = (input.len() * output_rate).div_ceil(input_rate);
866 assert_eq!(boxed.max_output_samples(input.len()), expected_max);
867 let got = boxed.resample(&input);
868 assert_eq!(
869 expected, got,
870 "BoxedResampler output must match borrowed Resampler"
871 );
872
873 let mut moved = boxed;
878 let again = moved.resample(&input);
879 assert_eq!(expected.len(), again.len());
880
881 moved.reset();
884 let after_reset = moved.resample(&input);
885 assert_eq!(expected, after_reset);
886
887 assert!(
888 BoxedResampler::new(0, 8000).is_err(),
889 "zero input rate must error"
890 );
891 assert!(
892 BoxedResampler::new(48000, 0).is_err(),
893 "zero output rate must error"
894 );
895 }
896
897 const RATIOS: [(usize, usize); 8] = [
898 (8000, 16000),
899 (16000, 8000),
900 (48000, 8000),
901 (8000, 48000),
902 (44100, 48000),
903 (48000, 44100),
904 (8000, 22050),
905 (22050, 16000),
906 ];
907
908 fn test_signals(len: usize) -> [Vec<i16>; 2] {
911 let mut seed = 0x1234_5678u32;
912 let noisy: Vec<i16> = (0..len)
913 .map(|i| {
914 seed = seed.wrapping_mul(1_664_525).wrapping_add(1_013_904_223);
915 let noise = ((seed >> 16) as i16 as f32) * 0.2;
916 ((i as f32 * 0.07).sin() * 15000.0 + noise) as i16
917 })
918 .collect();
919 let square: Vec<i16> = (0..len)
920 .map(|i| {
921 if (i / 10) % 2 == 0 {
922 i16::MAX
923 } else {
924 i16::MIN
925 }
926 })
927 .collect();
928 [noisy, square]
929 }
930
931 fn reference_resample(coeffs: &[i16], fin: usize, fout: usize, input: &[i16]) -> Vec<i16> {
937 let mut history = [0i16; TAPS_PER_PHASE];
938 let mut out = Vec::new();
939 for (input_index, &sample) in input.iter().enumerate() {
940 history.copy_within(1.., 0);
941 history[TAPS_PER_PHASE - 1] = sample;
942 while out.len() * fin < (input_index + 1) * fout {
943 let phase = (out.len() * fin % fout) * NUM_PHASES / fout;
944 let c = &coeffs[phase * TAPS_PER_PHASE..(phase + 1) * TAPS_PER_PHASE];
945 let acc: i64 = c
946 .iter()
947 .zip(&history)
948 .map(|(&c, &h)| c as i64 * h as i64)
949 .sum();
950 out.push((acc >> Q15_SHIFT).clamp(i16::MIN as i64, i16::MAX as i64) as i16);
951 }
952 }
953 out
954 }
955
956 #[test]
959 fn test_simd_matches_scalar_reference() {
960 #[cfg(feature = "std")]
962 let levels = [Level::baseline(), default_level(), Level::new()];
963 #[cfg(not(feature = "std"))]
964 let levels = [Level::baseline(), default_level()];
965 for (from, to) in RATIOS {
966 for input in test_signals(2000) {
967 for level in levels {
968 let mut r = new_resampler(from, to);
969 r.level = level;
970 let expected = reference_resample(r.coeffs, from, to, &input);
971 let got = resample(&mut r, &input);
972 assert_eq!(expected, got, "{from}->{to} {level:?}");
973 }
974 }
975 }
976 }
977
978 #[test]
983 fn test_part_coeff_sums_fit_i32_accumulator() {
984 let mut worst = 0i32;
985 let rates = [
986 8000, 11025, 16000, 22050, 24000, 32000, 44100, 48000, 96000, 192000,
987 ];
988 let sweep = (1..=100).flat_map(|k| [(48000, 480 * k), (480 * k, 48000)]);
991 let pairs = rates
992 .iter()
993 .flat_map(|&from| rates.iter().map(move |&to| (from, to)))
994 .chain(sweep);
995 for (from, to) in pairs {
996 let mut coeffs = vec![0i16; COEFFS_LEN];
997 Resampler::new(from, to, &mut coeffs).unwrap_or_else(|e| panic!("{from}->{to}: {e:?}"));
998 for phase in coeffs.chunks(TAPS_PER_PHASE) {
999 let abs = |c: &i16| (*c as i32).abs();
1000 let mid: i32 = phase[MID_TAPS].iter().map(abs).sum();
1001 let outer: i32 = phase.iter().map(abs).sum::<i32>() - mid;
1002 worst = worst.max(mid).max(outer);
1003 }
1004 }
1005 assert!(worst <= MAX_PART_ABS_COEFF_SUM);
1006 }
1007
1008 #[test]
1013 fn test_dot_q15_exact_beyond_i32_range() {
1014 let r = new_resampler(8000, 48000);
1015 let mut beyond_i32 = 0;
1016 for phase in r.coeffs.chunks(TAPS_PER_PHASE) {
1017 let phase: &[i16; TAPS_PER_PHASE] = phase.try_into().unwrap();
1018 let window: [i16; TAPS_PER_PHASE] =
1019 core::array::from_fn(|i| if phase[i] < 0 { i16::MIN } else { i16::MAX });
1020 let exact: i64 = window
1021 .iter()
1022 .zip(phase)
1023 .map(|(&h, &c)| h as i64 * c as i64)
1024 .sum();
1025 beyond_i32 += (exact > i32::MAX as i64) as usize;
1026 assert_eq!(
1027 Resampler::dot_q15(&window, phase) as i64,
1028 exact >> Q15_SHIFT,
1029 "phase {phase:?}"
1030 );
1031 }
1032 assert!(
1033 beyond_i32 > 0,
1034 "test no longer reaches the i32 overflow range"
1035 );
1036 }
1037
1038 #[test]
1042 fn test_chunked_is_bit_identical() {
1043 let [input, _] = test_signals(3000);
1044 for (from, to) in RATIOS {
1045 let whole = resample(&mut new_resampler(from, to), &input);
1046 for chunk in [1, 2, 7, 23, 24, 25, 47, 160, 255, 256, 257, 1000] {
1047 let mut r = new_resampler(from, to);
1048 let mut chunked = Vec::new();
1049 for part in input.chunks(chunk) {
1050 chunked.extend_from_slice(&resample(&mut r, part));
1051 }
1052 assert_eq!(whole, chunked, "{from}->{to} chunk={chunk}");
1053 }
1054 }
1055 }
1056
1057 #[test]
1060 fn test_dc_gain_all_ratios() {
1061 for (from, to) in RATIOS {
1062 for level in [1000i16, -20000, i16::MAX] {
1063 let output = resample(&mut new_resampler(from, to), &vec![level; 2000]);
1064 let warmup = (TAPS_PER_PHASE * to).div_ceil(from) + 1;
1066 let tolerance = 2 + (level as i32).abs() / 1000;
1068 for (i, &s) in output.iter().enumerate().skip(warmup) {
1069 assert!(
1070 (s as i32 - level as i32).abs() <= tolerance,
1071 "{from}->{to}: sample {i} = {s}, expected ~{level}"
1072 );
1073 }
1074 }
1075 }
1076 }
1077
1078 #[test]
1081 fn test_max_output_samples_is_upper_bound() {
1082 for (from, to) in RATIOS {
1083 let mut r = new_resampler(from, to);
1084 let input = vec![100i16; 200];
1085 for n in 1..=200 {
1086 let mut out = vec![0i16; r.max_output_samples(n)];
1087 r.resample_into(&input[..n], &mut out)
1088 .unwrap_or_else(|e| panic!("{from}->{to} n={n}: {e:?}"));
1089 }
1090 }
1091 }
1092
1093 #[test]
1094 fn test_output_buffer_too_small() {
1095 let mut r = new_resampler(8000, 16000);
1096 let mut out = [0i16; 10];
1097 assert!(matches!(
1098 r.resample_into(&[0i16; 160], &mut out),
1099 Err(CodecError::BufferTooSmall)
1100 ));
1101
1102 let mut r = new_resampler(8000, 8000);
1104 assert!(matches!(
1105 r.resample_into(&[0i16; 160], &mut out),
1106 Err(CodecError::BufferTooSmall)
1107 ));
1108 }
1109
1110 #[test]
1111 fn test_same_rate_passthrough() {
1112 let [input, _] = test_signals(500);
1113 let mut r = new_resampler(16000, 16000);
1114 assert_eq!(r.max_output_samples(input.len()), input.len());
1115 assert_eq!(resample(&mut r, &input), input);
1116 }
1117
1118 #[test]
1119 fn test_new_rejects_bad_args() {
1120 let mut small = vec![0i16; COEFFS_LEN - 1];
1121 assert!(matches!(
1122 Resampler::new(8000, 16000, &mut small),
1123 Err(CodecError::BufferTooSmall)
1124 ));
1125 let mut coeffs = vec![0i16; COEFFS_LEN];
1126 assert!(matches!(
1127 Resampler::new(0, 16000, &mut coeffs),
1128 Err(CodecError::InvalidInput)
1129 ));
1130 assert!(matches!(
1131 Resampler::new(8000, 0, &mut coeffs),
1132 Err(CodecError::InvalidInput)
1133 ));
1134 }
1135
1136 #[test]
1138 fn test_reset_mid_stream() {
1139 let [input, _] = test_signals(1000);
1140 let mut r = new_resampler(44100, 48000);
1141 let fresh = resample(&mut r, &input);
1142 resample(&mut r, &input[..13]);
1144 r.reset();
1145 assert_eq!(resample(&mut r, &input), fresh);
1146 }
1147}