1use core::f64::consts::PI as PI_F64;
2
3use super::{CodecError, Sample};
4
5#[cfg(feature = "std")]
6use super::PcmBuf;
7
8pub const NUM_PHASES: usize = 256;
10pub const TAPS_PER_PHASE: usize = 24;
12pub const COEFFS_LEN: usize = NUM_PHASES * TAPS_PER_PHASE;
14
15#[inline]
17fn fsin(x: f64) -> f64 {
18 #[cfg(feature = "std")]
19 {
20 f64::sin(x)
21 }
22 #[cfg(not(feature = "std"))]
23 {
24 libm::sin(x)
25 }
26}
27
28#[inline]
30fn fsqrt(x: f64) -> f64 {
31 #[cfg(feature = "std")]
32 {
33 f64::sqrt(x)
34 }
35 #[cfg(not(feature = "std"))]
36 {
37 libm::sqrt(x)
38 }
39}
40
41pub struct Resampler<'a> {
42 input_rate: usize,
43 output_rate: usize,
44 ratio: f64,
45 coeffs: &'a mut [f32],
46 num_phases: usize,
47 taps_per_phase: usize,
48 history: [f32; TAPS_PER_PHASE],
49 current_pos: f64,
50}
51
52fn bessel_i0(x: f64) -> f64 {
53 let mut sum = 1.0_f64;
54 let mut term = 1.0_f64;
55 let x_sq = x * x * 0.25;
56
57 for m in 1..=30 {
58 term *= x_sq / (m * m) as f64;
59 sum += term;
60 if term < 1e-15 * sum {
61 break;
62 }
63 }
64 sum
65}
66
67fn kaiser_window(n: usize, n_total: usize, beta: f64) -> f64 {
68 if n_total <= 1 {
69 return 1.0;
70 }
71 let alpha = (n_total - 1) as f64 / 2.0;
72 let x = (n as f64 - alpha) / alpha;
73 let arg = beta * fsqrt(1.0 - x * x);
74 bessel_i0(arg) / bessel_i0(beta)
75}
76
77impl<'a> Resampler<'a> {
78 pub fn new(
85 input_rate: usize,
86 output_rate: usize,
87 coeffs: &'a mut [f32],
88 ) -> Result<Self, CodecError> {
89 if coeffs.len() < COEFFS_LEN {
90 return Err(CodecError::BufferTooSmall);
91 }
92 if input_rate == 0 || output_rate == 0 {
93 return Err(CodecError::InvalidInput);
94 }
95
96 const KAISER_BETA: f64 = 7.0;
97
98 let ratio = output_rate as f64 / input_rate as f64;
99 let num_phases = NUM_PHASES;
100 let taps_per_phase = TAPS_PER_PHASE;
101 let filter_len = num_phases * taps_per_phase;
102
103 let coeffs = &mut coeffs[..filter_len];
104
105 let cutoff = if ratio < 1.0 {
106 ratio * 0.5 * 0.95
107 } else {
108 0.5 * 0.95
109 };
110
111 let center = (taps_per_phase as f64 - 1.0) / 2.0;
112
113 let mut phase_coeffs: [f64; TAPS_PER_PHASE] = [0.0; TAPS_PER_PHASE];
116
117 for p in 0..num_phases {
118 let mut sum = 0.0_f64;
119
120 for t in 0..taps_per_phase {
121 let x = t as f64 - center - (p as f64 / num_phases as f64);
122
123 let sinc_val = if x.abs() < 1e-10 {
124 2.0 * cutoff
125 } else {
126 let x_pi = x * PI_F64;
127 fsin(x_pi * 2.0 * cutoff) / x_pi
128 };
129
130 let full_filter_idx = t * num_phases + p;
131 let window = kaiser_window(full_filter_idx, filter_len, KAISER_BETA);
132
133 phase_coeffs[t] = sinc_val * window;
134 sum += phase_coeffs[t];
135 }
136
137 for t in 0..taps_per_phase {
138 let normalized = (phase_coeffs[t] / sum) as f32;
139 coeffs[p * taps_per_phase + t] = normalized;
140 }
141 }
142
143 Ok(Self {
144 input_rate,
145 output_rate,
146 ratio,
147 coeffs,
148 num_phases,
149 taps_per_phase,
150 history: [0.0; TAPS_PER_PHASE],
151 current_pos: 0.0,
152 })
153 }
154
155 pub fn input_rate(&self) -> usize {
156 self.input_rate
157 }
158
159 pub fn output_rate(&self) -> usize {
160 self.output_rate
161 }
162
163 #[inline(always)]
164 fn dot_product(a: &[f32], b: &[f32]) -> f32 {
165 debug_assert_eq!(a.len(), TAPS_PER_PHASE);
166 debug_assert_eq!(b.len(), TAPS_PER_PHASE);
167
168 #[cfg(target_arch = "aarch64")]
169 {
170 unsafe {
172 use core::arch::aarch64::*;
173 let mut sumv = vdupq_n_f32(0.0);
174 for i in (0..TAPS_PER_PHASE).step_by(4) {
175 let av = vld1q_f32(a.as_ptr().add(i));
176 let bv = vld1q_f32(b.as_ptr().add(i));
177 sumv = vfmaq_f32(sumv, av, bv);
178 }
179 vaddvq_f32(sumv)
180 }
181 }
182 #[cfg(all(target_arch = "x86_64", target_feature = "avx"))]
183 {
184 unsafe {
185 use core::arch::x86_64::*;
186 let mut sumv = _mm256_setzero_ps();
187 for i in (0..TAPS_PER_PHASE).step_by(8) {
188 let av = _mm256_loadu_ps(a.as_ptr().add(i));
189 let bv = _mm256_loadu_ps(b.as_ptr().add(i));
190 sumv = _mm256_add_ps(sumv, _mm256_mul_ps(av, bv));
191 }
192 let x128 = _mm_add_ps(_mm256_extractf128_ps(sumv, 1), _mm256_castps256_ps128(sumv));
194 let x64 = _mm_add_ps(x128, _mm_movehl_ps(x128, x128));
195 let x32 = _mm_add_ss(x64, _mm_shuffle_ps(x64, x64, 0x55));
196 _mm_cvtss_f32(x32)
197 }
198 }
199 #[cfg(all(
200 target_arch = "x86_64",
201 target_feature = "sse2",
202 not(target_feature = "avx")
203 ))]
204 {
205 unsafe {
206 use core::arch::x86_64::*;
207 let mut sumv = _mm_setzero_ps();
208 for i in (0..TAPS_PER_PHASE).step_by(4) {
209 let av = _mm_loadu_ps(a.as_ptr().add(i));
210 let bv = _mm_loadu_ps(b.as_ptr().add(i));
211 sumv = _mm_add_ps(sumv, _mm_mul_ps(av, bv));
212 }
213 let x64 = _mm_add_ps(sumv, _mm_shuffle_ps(sumv, sumv, 0x4e));
214 let x32 = _mm_add_ss(x64, _mm_shuffle_ps(x64, x64, 0x11));
215 _mm_cvtss_f32(x32)
216 }
217 }
218 #[cfg(not(any(
219 target_arch = "aarch64",
220 all(target_arch = "x86_64", target_feature = "sse2")
221 )))]
222 {
223 let mut s = 0.0f32;
224 for i in 0..TAPS_PER_PHASE {
225 s += a[i] * b[i];
226 }
227 s
228 }
229 }
230
231 pub fn resample_into(
237 &mut self,
238 input: &[Sample],
239 out: &mut [Sample],
240 ) -> Result<usize, CodecError> {
241 if self.input_rate == self.output_rate {
242 if out.len() < input.len() {
243 return Err(CodecError::BufferTooSmall);
244 }
245 out[..input.len()].copy_from_slice(input);
246 return Ok(input.len());
247 }
248
249 let inv_ratio = 1.0 / self.ratio;
250 let taps = self.taps_per_phase;
251 let num_phases_f = self.num_phases as f64;
252
253 let mut written = 0usize;
254
255 for &sample in input {
256 self.history.copy_within(1..taps, 0);
257 self.history[taps - 1] = sample as f32;
258
259 while self.current_pos < 1.0 {
260 let phase_idx = (self.current_pos * num_phases_f) as usize;
261 let phase_idx = phase_idx.min(self.num_phases - 1); let offset = phase_idx * taps;
263 let phase_coeffs = &self.coeffs[offset..offset + taps];
264
265 let out_sample = Self::dot_product(phase_coeffs, &self.history);
266
267 if written >= out.len() {
268 return Err(CodecError::BufferTooSmall);
269 }
270 out[written] = out_sample.clamp(i16::MIN as f32, i16::MAX as f32) as i16;
271 written += 1;
272 self.current_pos += inv_ratio;
273 }
274 self.current_pos -= 1.0;
275 }
276
277 Ok(written)
278 }
279
280 pub fn max_output_samples(&self, n_input: usize) -> usize {
283 if self.input_rate == self.output_rate {
284 return n_input;
285 }
286 ((n_input as f64 * self.ratio) as usize) + 1
287 }
288
289 #[cfg(feature = "std")]
291 pub fn resample(&mut self, input: &[Sample]) -> PcmBuf {
292 let max = self.max_output_samples(input.len());
293 let mut out = vec![0i16; max];
294 match self.resample_into(input, &mut out) {
295 Ok(n) => {
296 out.truncate(n);
297 out
298 }
299 Err(_) => Vec::new(),
300 }
301 }
302
303 pub fn reset(&mut self) {
304 self.history.fill(0.0);
305 self.current_pos = 0.0;
306 }
307}
308
309#[cfg(feature = "std")]
313pub fn resample(input: &[Sample], input_sample_rate: u32, output_sample_rate: u32) -> PcmBuf {
314 if input_sample_rate == output_sample_rate {
315 return input.to_vec();
316 }
317 let mut coeffs = vec![0.0f32; COEFFS_LEN];
318 let mut r = match Resampler::new(
319 input_sample_rate as usize,
320 output_sample_rate as usize,
321 &mut coeffs,
322 ) {
323 Ok(r) => r,
324 Err(_) => return Vec::new(),
325 };
326 r.resample(input)
327}
328
329#[cfg(feature = "std")]
338pub struct BoxedResampler {
339 inner: Resampler<'static>,
344 coeffs: Box<[f32]>,
345}
346
347#[cfg(feature = "std")]
348impl BoxedResampler {
349 pub fn new(input_rate: usize, output_rate: usize) -> Result<Self, CodecError> {
354 let mut coeffs = vec![0.0f32; COEFFS_LEN].into_boxed_slice();
355 let inner = unsafe {
361 let ptr = coeffs.as_mut_ptr();
362 let loan: &'static mut [f32] = core::slice::from_raw_parts_mut(ptr, coeffs.len());
363 Resampler::new(input_rate, output_rate, loan)?
364 };
365 Ok(Self { inner, coeffs })
366 }
367
368 pub fn resample(&mut self, input: &[Sample]) -> PcmBuf {
370 self.inner.resample(input)
371 }
372
373 pub fn resample_into(
375 &mut self,
376 input: &[Sample],
377 out: &mut [Sample],
378 ) -> Result<usize, CodecError> {
379 self.inner.resample_into(input, out)
380 }
381
382 pub fn max_output_samples(&self, n_input: usize) -> usize {
384 self.inner.max_output_samples(n_input)
385 }
386
387 pub fn reset(&mut self) {
389 self.inner.reset();
390 }
391}
392
393#[cfg(test)]
394mod tests {
395 use super::*;
396 use std::f32::consts::PI as PI_F32;
397 use std::time::Instant;
398
399 fn new_resampler(input_rate: usize, output_rate: usize) -> Resampler<'static> {
400 let coeffs: &'static mut [f32] = Box::leak(vec![0.0f32; COEFFS_LEN].into_boxed_slice());
402 Resampler::new(input_rate, output_rate, coeffs).expect("resampler init")
403 }
404
405 #[test]
406 fn test_resample_8k_to_16k() {
407 let mut resampler = new_resampler(8000, 16000);
408 let input = vec![1000i16; 80];
409 let output = resampler.resample(&input);
410 assert!(output.len() >= 150 && output.len() <= 170);
411 for &s in &output[48..output.len().saturating_sub(48)] {
412 assert!((s - 1000).abs() < 100, "Value {} is too far from 1000", s);
413 }
414 }
415
416 #[test]
417 fn test_resample_16k_to_8k() {
418 let mut resampler = new_resampler(16000, 8000);
419 let input = vec![1000i16; 160];
420 let output = resampler.resample(&input);
421 assert!(output.len() >= 75 && output.len() <= 85);
422 let skip = output.len() / 4;
423 for &s in &output[skip..output.len() - skip] {
424 assert!((s - 1000).abs() < 100, "Value {} is too far from 1000", s);
425 }
426 }
427
428 #[test]
429 fn test_frequency_response_downsample() {
430 let mut resampler = new_resampler(16000, 8000);
431 let freq = 2000.0_f32; let samples: Vec<i16> = (0..160)
433 .map(|i| ((i as f32 * freq * 2.0 * PI_F32 / 16000.0).sin() * 10000.0) as i16)
434 .collect();
435
436 let output = resampler.resample(&samples);
437
438 let input_rms: f32 = samples
440 .iter()
441 .map(|&s| (s as f32).powi(2))
442 .sum::<f32>()
443 .sqrt()
444 / samples.len() as f32;
445 let output_rms: f32 = output
446 .iter()
447 .map(|&s| (s as f32).powi(2))
448 .sum::<f32>()
449 .sqrt()
450 / output.len() as f32;
451
452 assert!(
453 output_rms > input_rms * 0.7,
454 "Too much attenuation: input_rms={}, output_rms={}",
455 input_rms,
456 output_rms
457 );
458 }
459
460 #[test]
461 fn test_aliasing_suppression() {
462 let mut resampler = new_resampler(16000, 8000);
463 let freq = 7000.0_f32; let samples: Vec<i16> = (0..1600)
465 .map(|i| ((i as f32 * freq * 2.0 * PI_F32 / 16000.0).sin() * 10000.0) as i16)
466 .collect();
467
468 let output = resampler.resample(&samples);
469
470 let output_rms: f32 =
471 (output.iter().map(|&s| (s as f32).powi(2)).sum::<f32>() / output.len() as f32).sqrt();
472 let input_rms: f32 = 10000.0 / 1.414; assert!(
475 output_rms < input_rms / 50.0,
476 "Aliasing not sufficiently suppressed: output_rms={}",
477 output_rms
478 );
479 }
480
481 #[test]
482 fn test_performance_48k_to_8k() {
483 let mut resampler = new_resampler(48000, 8000);
484 let input = vec![0i16; 48000];
485
486 let start = Instant::now();
487 let iterations = 100;
488 for _ in 0..iterations {
489 let _ = resampler.resample(&input);
490 resampler.reset();
491 }
492 let duration = start.elapsed();
493 let per_second = duration.as_secs_f64() / iterations as f64;
494 println!(
495 "Resampling 1s of 48kHz to 8kHz (24 taps) took: {:.4}ms",
496 per_second * 1000.0
497 );
498 assert!(
499 per_second < 0.1,
500 "Performance regression: {}ms",
501 per_second * 1000.0
502 );
503 }
504
505 #[test]
506 fn test_continuity_between_chunks() {
507 let input_rate = 16000;
508 let output_rate = 8000;
509
510 let freq = 1000.0_f32;
511 let total_samples = 3200;
512 let input: Vec<i16> = (0..total_samples)
513 .map(|i| ((i as f32 * freq * 2.0 * PI_F32 / input_rate as f32).sin() * 5000.0) as i16)
514 .collect();
515
516 let mut resampler1 = new_resampler(input_rate, output_rate);
517 let output1 = resampler1.resample(&input);
518
519 let mut resampler2 = new_resampler(input_rate, output_rate);
520 let mid = input.len() / 2;
521 let mut output2 = resampler2.resample(&input[..mid]);
522 output2.extend_from_slice(&resampler2.resample(&input[mid..]));
523
524 assert_eq!(output1.len(), output2.len(), "Output lengths differ");
525
526 let max_diff: i16 = output1
527 .iter()
528 .zip(output2.iter())
529 .map(|(a, b)| (a - b).abs())
530 .max()
531 .unwrap_or(0);
532
533 assert!(
534 max_diff < 100,
535 "Large discontinuity between chunks: max_diff={}",
536 max_diff
537 );
538 }
539
540 #[cfg(feature = "std")]
544 #[test]
545 fn test_boxed_resampler_matches_borrowed() {
546 let input_rate = 48000usize;
547 let output_rate = 8000usize;
548 let input: Vec<i16> = (0..4800)
549 .map(|i| ((i as f32 * 0.05).sin() * 8000.0) as i16)
550 .collect();
551
552 let expected = {
553 let mut coeffs = vec![0.0f32; COEFFS_LEN];
554 let mut borrowed = Resampler::new(input_rate, output_rate, &mut coeffs).unwrap();
555 borrowed.resample(&input)
556 };
557
558 let mut boxed = BoxedResampler::new(input_rate, output_rate).unwrap();
559 let expected_max = ((input.len() * output_rate + input_rate - 1) / input_rate) + 1;
560 assert_eq!(boxed.max_output_samples(input.len()), expected_max);
561 let got = boxed.resample(&input);
562 assert_eq!(
563 expected, got,
564 "BoxedResampler output must match borrowed Resampler"
565 );
566
567 let mut moved = boxed;
572 let again = moved.resample(&input);
573 assert_eq!(expected.len(), again.len());
574
575 moved.reset();
578 let after_reset = moved.resample(&input);
579 assert_eq!(expected, after_reset);
580
581 assert!(
582 BoxedResampler::new(0, 8000).is_err(),
583 "zero input rate must error"
584 );
585 assert!(
586 BoxedResampler::new(48000, 0).is_err(),
587 "zero output rate must error"
588 );
589 }
590}
591#[cfg(test)]
592mod debug_probe2 {
593 use super::*;
594
595 #[cfg(feature = "std")]
596 #[test]
597 fn probe_order() {
598 let input: Vec<i16> = (0..4800)
599 .map(|i| ((i as f32 * 0.05).sin() * 8000.0) as i16)
600 .collect();
601 let expected = {
603 let mut coeffs = vec![0.0f32; COEFFS_LEN];
604 let mut r = Resampler::new(48000, 8000, &mut coeffs).unwrap();
605 r.resample(&input)
606 };
607 let mut boxed = BoxedResampler::new(48000, 8000).unwrap();
608 let got = boxed.resample(&input);
609 let diffs: Vec<usize> = expected
610 .iter()
611 .zip(got.iter())
612 .enumerate()
613 .filter(|(_, (a, b))| a != b)
614 .map(|(i, _)| i)
615 .collect();
616 println!(
617 "diff_count={} first_diffs={:?} expected_len={} got_len={}",
618 diffs.len(),
619 &diffs[..diffs.len().min(8)],
620 expected.len(),
621 got.len()
622 );
623 }
624}