1use core::f64::consts::PI as PI_F64;
11
12use super::{CodecError, Sample};
13
14#[cfg(feature = "std")]
15use super::PcmBuf;
16
17pub const NUM_PHASES: usize = 256;
19pub const TAPS_PER_PHASE: usize = 24;
21pub const COEFFS_LEN: usize = NUM_PHASES * TAPS_PER_PHASE;
23
24#[inline]
26fn fsin(x: f64) -> f64 {
27 #[cfg(feature = "std")]
28 {
29 f64::sin(x)
30 }
31 #[cfg(not(feature = "std"))]
32 {
33 libm::sin(x)
34 }
35}
36
37#[inline]
39fn fsqrt(x: f64) -> f64 {
40 #[cfg(feature = "std")]
41 {
42 f64::sqrt(x)
43 }
44 #[cfg(not(feature = "std"))]
45 {
46 libm::sqrt(x)
47 }
48}
49
50pub struct Resampler<'a> {
51 input_rate: usize,
52 output_rate: usize,
53 coeffs: &'a mut [f32],
54 taps_per_phase: usize,
55 history: [f32; TAPS_PER_PHASE],
57 pos: usize,
60}
61
62fn bessel_i0(x: f64) -> f64 {
63 let mut sum = 1.0_f64;
64 let mut term = 1.0_f64;
65 let x_sq = x * x * 0.25;
66
67 for m in 1..=30 {
68 term *= x_sq / (m * m) as f64;
69 sum += term;
70 if term < 1e-15 * sum {
71 break;
72 }
73 }
74 sum
75}
76
77fn kaiser_window(n: usize, n_total: usize, beta: f64) -> f64 {
78 if n_total <= 1 {
79 return 1.0;
80 }
81 let alpha = (n_total - 1) as f64 / 2.0;
82 let x = (n as f64 - alpha) / alpha;
83 let arg = beta * fsqrt(1.0 - x * x);
84 bessel_i0(arg) / bessel_i0(beta)
85}
86
87impl<'a> Resampler<'a> {
88 pub fn new(
95 input_rate: usize,
96 output_rate: usize,
97 coeffs: &'a mut [f32],
98 ) -> Result<Self, CodecError> {
99 if coeffs.len() < COEFFS_LEN {
100 return Err(CodecError::BufferTooSmall);
101 }
102 if input_rate == 0 || output_rate == 0 {
103 return Err(CodecError::InvalidInput);
104 }
105 if input_rate.checked_add(output_rate).is_none()
108 || output_rate.checked_mul(NUM_PHASES).is_none()
109 {
110 return Err(CodecError::InvalidInput);
111 }
112
113 const KAISER_BETA: f64 = 7.0;
114
115 let ratio = output_rate as f64 / input_rate as f64;
116 let num_phases = NUM_PHASES;
117 let taps_per_phase = TAPS_PER_PHASE;
118 let filter_len = num_phases * taps_per_phase;
119
120 let coeffs = &mut coeffs[..filter_len];
121
122 let cutoff = if ratio < 1.0 {
123 ratio * 0.5 * 0.95
124 } else {
125 0.5 * 0.95
126 };
127
128 let center = (taps_per_phase as f64 - 1.0) / 2.0;
129
130 let mut phase_coeffs: [f64; TAPS_PER_PHASE] = [0.0; TAPS_PER_PHASE];
133
134 for p in 0..num_phases {
135 let mut sum = 0.0_f64;
136
137 for t in 0..taps_per_phase {
138 let x = t as f64 - center - (p as f64 / num_phases as f64);
139
140 let sinc_val = if x.abs() < 1e-10 {
141 2.0 * cutoff
142 } else {
143 let x_pi = x * PI_F64;
144 fsin(x_pi * 2.0 * cutoff) / x_pi
145 };
146
147 let full_filter_idx = t * num_phases + p;
148 let window = kaiser_window(full_filter_idx, filter_len, KAISER_BETA);
149
150 phase_coeffs[t] = sinc_val * window;
151 sum += phase_coeffs[t];
152 }
153
154 for t in 0..taps_per_phase {
155 let normalized = (phase_coeffs[t] / sum) as f32;
156 coeffs[p * taps_per_phase + t] = normalized;
157 }
158 }
159
160 Ok(Self {
161 input_rate,
162 output_rate,
163 coeffs,
164 taps_per_phase,
165 history: [0.0; TAPS_PER_PHASE],
166 pos: 0,
167 })
168 }
169
170 pub fn input_rate(&self) -> usize {
171 self.input_rate
172 }
173
174 pub fn output_rate(&self) -> usize {
175 self.output_rate
176 }
177
178 #[inline(always)]
179 fn dot_product(a: &[f32], b: &[f32]) -> f32 {
180 debug_assert_eq!(a.len(), TAPS_PER_PHASE);
181 debug_assert_eq!(b.len(), TAPS_PER_PHASE);
182
183 #[cfg(target_arch = "aarch64")]
184 {
185 unsafe {
187 use core::arch::aarch64::*;
188 let mut sumv = vdupq_n_f32(0.0);
189 for i in (0..TAPS_PER_PHASE).step_by(4) {
190 let av = vld1q_f32(a.as_ptr().add(i));
191 let bv = vld1q_f32(b.as_ptr().add(i));
192 sumv = vfmaq_f32(sumv, av, bv);
193 }
194 vaddvq_f32(sumv)
195 }
196 }
197 #[cfg(all(target_arch = "x86_64", target_feature = "avx"))]
198 {
199 unsafe {
200 use core::arch::x86_64::*;
201 let mut sumv = _mm256_setzero_ps();
202 for i in (0..TAPS_PER_PHASE).step_by(8) {
203 let av = _mm256_loadu_ps(a.as_ptr().add(i));
204 let bv = _mm256_loadu_ps(b.as_ptr().add(i));
205 sumv = _mm256_add_ps(sumv, _mm256_mul_ps(av, bv));
206 }
207 let x128 = _mm_add_ps(_mm256_extractf128_ps(sumv, 1), _mm256_castps256_ps128(sumv));
209 let x64 = _mm_add_ps(x128, _mm_movehl_ps(x128, x128));
210 let x32 = _mm_add_ss(x64, _mm_shuffle_ps(x64, x64, 0x55));
211 _mm_cvtss_f32(x32)
212 }
213 }
214 #[cfg(all(
215 target_arch = "x86_64",
216 target_feature = "sse2",
217 not(target_feature = "avx")
218 ))]
219 {
220 unsafe {
221 use core::arch::x86_64::*;
222 let mut sumv = _mm_setzero_ps();
223 for i in (0..TAPS_PER_PHASE).step_by(4) {
224 let av = _mm_loadu_ps(a.as_ptr().add(i));
225 let bv = _mm_loadu_ps(b.as_ptr().add(i));
226 sumv = _mm_add_ps(sumv, _mm_mul_ps(av, bv));
227 }
228 let x64 = _mm_add_ps(sumv, _mm_shuffle_ps(sumv, sumv, 0x4e));
229 let x32 = _mm_add_ss(x64, _mm_shuffle_ps(x64, x64, 0x11));
230 _mm_cvtss_f32(x32)
231 }
232 }
233 #[cfg(not(any(
234 target_arch = "aarch64",
235 all(target_arch = "x86_64", target_feature = "sse2")
236 )))]
237 {
238 let mut s = 0.0f32;
239 for i in 0..TAPS_PER_PHASE {
240 s += a[i] * b[i];
241 }
242 s
243 }
244 }
245
246 pub fn resample_into(
252 &mut self,
253 input: &[Sample],
254 out: &mut [Sample],
255 ) -> Result<usize, CodecError> {
256 if self.input_rate == self.output_rate {
257 if out.len() < input.len() {
258 return Err(CodecError::BufferTooSmall);
259 }
260 out[..input.len()].copy_from_slice(input);
261 return Ok(input.len());
262 }
263
264 let taps = self.taps_per_phase;
265 let (step, interval) = (self.input_rate, self.output_rate);
269 let mut pos = self.pos;
270 let expected: u128 = {
276 let ticks = (input.len() as u128) * (interval as u128);
277 if ticks > pos as u128 {
278 (ticks - pos as u128).div_ceil(step as u128)
279 } else {
280 0
281 }
282 };
283 let mut written = 0usize;
284
285 for &sample in input {
286 self.history.copy_within(1..taps, 0);
287 self.history[taps - 1] = sample as f32;
288
289 while pos < interval {
290 let phase_idx = pos * NUM_PHASES / interval;
293 let offset = phase_idx * taps;
294 let phase_coeffs: &[f32; TAPS_PER_PHASE] =
295 self.coeffs[offset..offset + taps].try_into().unwrap();
296
297 let out_sample = Self::dot_product(phase_coeffs, &self.history);
298
299 if written >= out.len() {
300 self.pos = pos;
301 return Err(CodecError::BufferTooSmall);
302 }
303 out[written] = out_sample.clamp(i16::MIN as f32, i16::MAX as f32) as i16;
304 written += 1;
305 pos += step;
306 }
307 pos -= interval;
308 }
309 self.pos = pos;
310
311 debug_assert_eq!(
312 written as u128,
313 expected,
314 "resampler produced {written} samples, exact timing requires {expected} \
315 for {} inputs ({} -> {}): output sizes would drift across chunk \
316 boundaries",
317 input.len(),
318 self.input_rate,
319 self.output_rate,
320 );
321
322 Ok(written)
323 }
324
325 pub fn max_output_samples(&self, n_input: usize) -> usize {
328 if self.input_rate == self.output_rate {
329 return n_input;
330 }
331 let count = (n_input as u128 * self.output_rate as u128).div_ceil(self.input_rate as u128);
334 count.min(usize::MAX as u128) as usize
335 }
336
337 #[cfg(feature = "std")]
339 pub fn resample(&mut self, input: &[Sample]) -> PcmBuf {
340 let max = self.max_output_samples(input.len());
341 let mut out = vec![0i16; max];
342 match self.resample_into(input, &mut out) {
343 Ok(n) => {
344 out.truncate(n);
345 out
346 }
347 Err(_) => Vec::new(),
348 }
349 }
350
351 pub fn reset(&mut self) {
352 self.history.fill(0.0);
353 self.pos = 0;
354 }
355}
356
357#[cfg(feature = "std")]
361pub fn resample(input: &[Sample], input_sample_rate: u32, output_sample_rate: u32) -> PcmBuf {
362 if input_sample_rate == output_sample_rate {
363 return input.to_vec();
364 }
365 let mut coeffs = vec![0.0f32; COEFFS_LEN];
366 let mut r = match Resampler::new(
367 input_sample_rate as usize,
368 output_sample_rate as usize,
369 &mut coeffs,
370 ) {
371 Ok(r) => r,
372 Err(_) => return Vec::new(),
373 };
374 r.resample(input)
375}
376
377#[cfg(feature = "std")]
386pub struct BoxedResampler {
387 inner: Resampler<'static>,
392 #[allow(dead_code)]
394 coeffs: Box<[f32]>,
395}
396
397#[cfg(feature = "std")]
398impl BoxedResampler {
399 pub fn new(input_rate: usize, output_rate: usize) -> Result<Self, CodecError> {
404 let mut coeffs = vec![0.0f32; COEFFS_LEN].into_boxed_slice();
405 let inner = unsafe {
411 let ptr = coeffs.as_mut_ptr();
412 let loan: &'static mut [f32] = core::slice::from_raw_parts_mut(ptr, coeffs.len());
413 Resampler::new(input_rate, output_rate, loan)?
414 };
415 Ok(Self { inner, coeffs })
416 }
417
418 pub fn resample(&mut self, input: &[Sample]) -> PcmBuf {
420 self.inner.resample(input)
421 }
422
423 pub fn resample_into(
425 &mut self,
426 input: &[Sample],
427 out: &mut [Sample],
428 ) -> Result<usize, CodecError> {
429 self.inner.resample_into(input, out)
430 }
431
432 pub fn max_output_samples(&self, n_input: usize) -> usize {
434 self.inner.max_output_samples(n_input)
435 }
436
437 pub fn reset(&mut self) {
439 self.inner.reset();
440 }
441}
442
443#[cfg(test)]
444mod tests {
445 use super::*;
446 use std::f32::consts::PI as PI_F32;
447 use std::time::Instant;
448
449 fn new_resampler(input_rate: usize, output_rate: usize) -> Resampler<'static> {
450 let coeffs: &'static mut [f32] = Box::leak(vec![0.0f32; COEFFS_LEN].into_boxed_slice());
452 Resampler::new(input_rate, output_rate, coeffs).expect("resampler init")
453 }
454
455 #[test]
459 fn test_exact_sample_counts_for_repeated_20ms_blocks() {
460 for input_rate in [8000, 16000, 44100, 48000] {
461 for output_rate in [8000, 16000, 44100, 48000] {
462 let mut r = new_resampler(input_rate, output_rate);
463 let input = vec![1000; input_rate / 50];
464 let mut output = vec![0; output_rate / 50];
465 for block in 0..10 {
466 let n = r.resample_into(&input, &mut output).unwrap_or_else(|e| {
467 panic!("{input_rate} -> {output_rate}, block {block}: {e:?}")
468 });
469 assert_eq!(
470 n,
471 output.len(),
472 "{input_rate} -> {output_rate}, block {block}"
473 );
474 }
475 }
476 }
477 }
478
479 #[test]
482 fn test_8k_to_48k_is_always_960() {
483 let mut r = new_resampler(8000, 48000);
484 for block in 0..10 {
485 let out = r.resample(&vec![1000i16; 160]);
486 assert_eq!(out.len(), 960, "block {block}");
487 }
488 }
489
490 #[test]
491 fn test_resample_8k_to_16k() {
492 let mut resampler = new_resampler(8000, 16000);
493 let input = vec![1000i16; 80];
494 let output = resampler.resample(&input);
495 assert!(output.len() >= 150 && output.len() <= 170);
496 for &s in &output[48..output.len().saturating_sub(48)] {
497 assert!((s - 1000).abs() < 100, "Value {} is too far from 1000", s);
498 }
499 }
500
501 #[test]
502 fn test_resample_16k_to_8k() {
503 let mut resampler = new_resampler(16000, 8000);
504 let input = vec![1000i16; 160];
505 let output = resampler.resample(&input);
506 assert!(output.len() >= 75 && output.len() <= 85);
507 let skip = output.len() / 4;
508 for &s in &output[skip..output.len() - skip] {
509 assert!((s - 1000).abs() < 100, "Value {} is too far from 1000", s);
510 }
511 }
512
513 #[test]
514 fn test_frequency_response_downsample() {
515 let mut resampler = new_resampler(16000, 8000);
516 let freq = 2000.0_f32; let samples: Vec<i16> = (0..160)
518 .map(|i| ((i as f32 * freq * 2.0 * PI_F32 / 16000.0).sin() * 10000.0) as i16)
519 .collect();
520
521 let output = resampler.resample(&samples);
522
523 let input_rms: f32 = samples
525 .iter()
526 .map(|&s| (s as f32).powi(2))
527 .sum::<f32>()
528 .sqrt()
529 / samples.len() as f32;
530 let output_rms: f32 = output
531 .iter()
532 .map(|&s| (s as f32).powi(2))
533 .sum::<f32>()
534 .sqrt()
535 / output.len() as f32;
536
537 assert!(
538 output_rms > input_rms * 0.7,
539 "Too much attenuation: input_rms={}, output_rms={}",
540 input_rms,
541 output_rms
542 );
543 }
544
545 #[test]
546 fn test_aliasing_suppression() {
547 let mut resampler = new_resampler(16000, 8000);
548 let freq = 7000.0_f32; let samples: Vec<i16> = (0..1600)
550 .map(|i| ((i as f32 * freq * 2.0 * PI_F32 / 16000.0).sin() * 10000.0) as i16)
551 .collect();
552
553 let output = resampler.resample(&samples);
554
555 let output_rms: f32 =
556 (output.iter().map(|&s| (s as f32).powi(2)).sum::<f32>() / output.len() as f32).sqrt();
557 let input_rms: f32 = 10000.0 / 1.414; assert!(
560 output_rms < input_rms / 50.0,
561 "Aliasing not sufficiently suppressed: output_rms={}",
562 output_rms
563 );
564 }
565
566 #[test]
567 fn test_performance_48k_to_8k() {
568 let mut resampler = new_resampler(48000, 8000);
569 let input = vec![0i16; 48000];
570
571 let start = Instant::now();
572 let iterations = 100;
573 for _ in 0..iterations {
574 let _ = resampler.resample(&input);
575 resampler.reset();
576 }
577 let duration = start.elapsed();
578 let per_second = duration.as_secs_f64() / iterations as f64;
579 println!(
580 "Resampling 1s of 48kHz to 8kHz (24 taps) took: {:.4}ms",
581 per_second * 1000.0
582 );
583 assert!(
584 per_second < 0.1,
585 "Performance regression: {}ms",
586 per_second * 1000.0
587 );
588 }
589
590 #[test]
591 fn test_continuity_between_chunks() {
592 let input_rate = 16000;
593 let output_rate = 8000;
594
595 let freq = 1000.0_f32;
596 let total_samples = 3200;
597 let input: Vec<i16> = (0..total_samples)
598 .map(|i| ((i as f32 * freq * 2.0 * PI_F32 / input_rate as f32).sin() * 5000.0) as i16)
599 .collect();
600
601 let mut resampler1 = new_resampler(input_rate, output_rate);
602 let output1 = resampler1.resample(&input);
603
604 let mut resampler2 = new_resampler(input_rate, output_rate);
605 let mid = input.len() / 2;
606 let mut output2 = resampler2.resample(&input[..mid]);
607 output2.extend_from_slice(&resampler2.resample(&input[mid..]));
608
609 assert_eq!(output1.len(), output2.len(), "Output lengths differ");
610
611 let max_diff: i16 = output1
612 .iter()
613 .zip(output2.iter())
614 .map(|(a, b)| (a - b).abs())
615 .max()
616 .unwrap_or(0);
617
618 assert!(
619 max_diff < 100,
620 "Large discontinuity between chunks: max_diff={}",
621 max_diff
622 );
623 }
624
625 #[cfg(feature = "std")]
629 #[test]
630 fn test_boxed_resampler_matches_borrowed() {
631 let input_rate = 48000usize;
632 let output_rate = 8000usize;
633 let input: Vec<i16> = (0..4800)
634 .map(|i| ((i as f32 * 0.05).sin() * 8000.0) as i16)
635 .collect();
636
637 let expected = {
638 let mut coeffs = vec![0.0f32; COEFFS_LEN];
639 let mut borrowed = Resampler::new(input_rate, output_rate, &mut coeffs).unwrap();
640 borrowed.resample(&input)
641 };
642
643 let mut boxed = BoxedResampler::new(input_rate, output_rate).unwrap();
644 let expected_max = (input.len() * output_rate).div_ceil(input_rate);
646 assert_eq!(boxed.max_output_samples(input.len()), expected_max);
647 let got = boxed.resample(&input);
648 assert_eq!(
649 expected, got,
650 "BoxedResampler output must match borrowed Resampler"
651 );
652
653 let mut moved = boxed;
658 let again = moved.resample(&input);
659 assert_eq!(expected.len(), again.len());
660
661 moved.reset();
664 let after_reset = moved.resample(&input);
665 assert_eq!(expected, after_reset);
666
667 assert!(
668 BoxedResampler::new(0, 8000).is_err(),
669 "zero input rate must error"
670 );
671 assert!(
672 BoxedResampler::new(48000, 0).is_err(),
673 "zero output rate must error"
674 );
675 }
676}