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
15const Q15_SHIFT: u32 = 15;
18const PHASE_BITS: u32 = 24;
21const PHASE_ONE: u32 = 1 << PHASE_BITS;
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
50#[inline]
52fn fround(x: f64) -> f64 {
53 #[cfg(feature = "std")]
54 {
55 x.round()
56 }
57 #[cfg(not(feature = "std"))]
58 {
59 libm::round(x)
60 }
61}
62
63pub struct Resampler<'a> {
74 input_rate: usize,
75 output_rate: usize,
76 ratio: f64,
77 coeffs: &'a mut [i16],
78 taps_per_phase: usize,
79 history: [i16; 2 * TAPS_PER_PHASE],
84 w: usize,
86 pos: u32,
88 step: u32,
90}
91
92fn bessel_i0(x: f64) -> f64 {
93 let mut sum = 1.0_f64;
94 let mut term = 1.0_f64;
95 let x_sq = x * x * 0.25;
96
97 for m in 1..=30 {
98 term *= x_sq / (m * m) as f64;
99 sum += term;
100 if term < 1e-15 * sum {
101 break;
102 }
103 }
104 sum
105}
106
107fn kaiser_window(n: usize, n_total: usize, beta: f64) -> f64 {
108 if n_total <= 1 {
109 return 1.0;
110 }
111 let alpha = (n_total - 1) as f64 / 2.0;
112 let x = (n as f64 - alpha) / alpha;
113 let arg = beta * fsqrt(1.0 - x * x);
114 bessel_i0(arg) / bessel_i0(beta)
115}
116
117impl<'a> Resampler<'a> {
118 pub fn new(
125 input_rate: usize,
126 output_rate: usize,
127 coeffs: &'a mut [i16],
128 ) -> Result<Self, CodecError> {
129 if coeffs.len() < COEFFS_LEN {
130 return Err(CodecError::BufferTooSmall);
131 }
132 if input_rate == 0 || output_rate == 0 {
133 return Err(CodecError::InvalidInput);
134 }
135
136 const KAISER_BETA: f64 = 7.0;
137
138 let ratio = output_rate as f64 / input_rate as f64;
139 let num_phases = NUM_PHASES;
140 let taps_per_phase = TAPS_PER_PHASE;
141 let filter_len = num_phases * taps_per_phase;
142
143 let step_f = (input_rate as f64 / output_rate as f64) * PHASE_ONE as f64;
145 if !(step_f >= 1.0 && step_f < u32::MAX as f64) {
146 return Err(CodecError::InvalidInput);
147 }
148 let step = fround(step_f) as u32;
149
150 let coeffs = &mut coeffs[..filter_len];
151
152 let cutoff = if ratio < 1.0 {
153 ratio * 0.5 * 0.95
154 } else {
155 0.5 * 0.95
156 };
157
158 let center = (taps_per_phase as f64 - 1.0) / 2.0;
159
160 let mut phase_coeffs: [f64; TAPS_PER_PHASE] = [0.0; TAPS_PER_PHASE];
163
164 for p in 0..num_phases {
165 let mut sum = 0.0_f64;
166
167 for t in 0..taps_per_phase {
168 let x = t as f64 - center - (p as f64 / num_phases as f64);
169
170 let sinc_val = if x.abs() < 1e-10 {
171 2.0 * cutoff
172 } else {
173 let x_pi = x * PI_F64;
174 fsin(x_pi * 2.0 * cutoff) / x_pi
175 };
176
177 let full_filter_idx = t * num_phases + p;
178 let window = kaiser_window(full_filter_idx, filter_len, KAISER_BETA);
179
180 phase_coeffs[t] = sinc_val * window;
181 sum += phase_coeffs[t];
182 }
183
184 for t in 0..taps_per_phase {
185 let q = fround((phase_coeffs[t] / sum) * 32768.0);
187 coeffs[p * taps_per_phase + t] = q.clamp(-32768.0, 32767.0) as i16;
188 }
189 }
190
191 Ok(Self {
192 input_rate,
193 output_rate,
194 ratio,
195 coeffs,
196 taps_per_phase,
197 history: [0; 2 * TAPS_PER_PHASE],
198 w: TAPS_PER_PHASE,
199 pos: 0,
200 step,
201 })
202 }
203
204 pub fn input_rate(&self) -> usize {
205 self.input_rate
206 }
207
208 pub fn output_rate(&self) -> usize {
209 self.output_rate
210 }
211
212 #[inline(always)]
219 fn dot_q15(history: &[i16; TAPS_PER_PHASE], coeffs: &[i16; TAPS_PER_PHASE]) -> i32 {
220 let mut acc: i64 = 0;
221 for i in 0..TAPS_PER_PHASE {
222 acc += ((history[i] as i32) * (coeffs[i] as i32)) as i64;
223 }
224 (acc >> Q15_SHIFT) as i32
225 }
226
227 pub fn resample_into(
233 &mut self,
234 input: &[Sample],
235 out: &mut [Sample],
236 ) -> Result<usize, CodecError> {
237 if self.input_rate == self.output_rate {
238 if out.len() < input.len() {
239 return Err(CodecError::BufferTooSmall);
240 }
241 out[..input.len()].copy_from_slice(input);
242 return Ok(input.len());
243 }
244
245 let taps = self.taps_per_phase;
246 let two_taps = 2 * taps;
247 let mut written = 0usize;
248
249 for &sample in input {
250 if self.w == two_taps {
252 self.history.copy_within(taps..two_taps, 0);
253 self.w = taps;
254 }
255 self.history[self.w] = sample;
256 self.w += 1;
257
258 if self.pos < PHASE_ONE {
259 let base = self.w - taps;
260 let win: &[i16; TAPS_PER_PHASE] = self.history[base..base + taps]
261 .try_into()
262 .unwrap();
263
264 while self.pos < PHASE_ONE {
265 let phase_idx = (self.pos >> (PHASE_BITS - 8)) as usize;
267 let offset = phase_idx * taps;
268 let phase_coeffs: &[i16; TAPS_PER_PHASE] =
269 self.coeffs[offset..offset + taps].try_into().unwrap();
270 let out_sample = Self::dot_q15(win, phase_coeffs);
271
272 if written >= out.len() {
273 return Err(CodecError::BufferTooSmall);
274 }
275 out[written] = out_sample.clamp(i16::MIN as i32, i16::MAX as i32) as i16;
276 written += 1;
277 self.pos += self.step;
278 }
279 }
280 self.pos -= PHASE_ONE;
281 }
282
283 Ok(written)
284 }
285
286 pub fn max_output_samples(&self, n_input: usize) -> usize {
289 if self.input_rate == self.output_rate {
290 return n_input;
291 }
292 ((n_input as f64 * self.ratio) as usize) + 1
293 }
294
295 #[cfg(feature = "std")]
297 pub fn resample(&mut self, input: &[Sample]) -> PcmBuf {
298 let max = self.max_output_samples(input.len());
299 let mut out = vec![0i16; max];
300 match self.resample_into(input, &mut out) {
301 Ok(n) => {
302 out.truncate(n);
303 out
304 }
305 Err(_) => Vec::new(),
306 }
307 }
308
309 pub fn reset(&mut self) {
310 self.history.fill(0);
311 self.w = self.taps_per_phase;
312 self.pos = 0;
313 }
314}
315
316#[cfg(feature = "std")]
320pub fn resample(input: &[Sample], input_sample_rate: u32, output_sample_rate: u32) -> PcmBuf {
321 if input_sample_rate == output_sample_rate {
322 return input.to_vec();
323 }
324 let mut coeffs = vec![0i16; COEFFS_LEN];
325 let mut r = match Resampler::new(
326 input_sample_rate as usize,
327 output_sample_rate as usize,
328 &mut coeffs,
329 ) {
330 Ok(r) => r,
331 Err(_) => return Vec::new(),
332 };
333 r.resample(input)
334}
335
336#[cfg(feature = "std")]
345pub struct BoxedResampler {
346 inner: Resampler<'static>,
351 #[allow(dead_code)]
353 coeffs: Box<[i16]>,
354}
355
356#[cfg(feature = "std")]
357impl BoxedResampler {
358 pub fn new(input_rate: usize, output_rate: usize) -> Result<Self, CodecError> {
363 let mut coeffs = vec![0i16; COEFFS_LEN].into_boxed_slice();
364 let inner = unsafe {
370 let ptr = coeffs.as_mut_ptr();
371 let loan: &'static mut [i16] = core::slice::from_raw_parts_mut(ptr, coeffs.len());
372 Resampler::new(input_rate, output_rate, loan)?
373 };
374 Ok(Self { inner, coeffs })
375 }
376
377 pub fn resample(&mut self, input: &[Sample]) -> PcmBuf {
379 self.inner.resample(input)
380 }
381
382 pub fn resample_into(
384 &mut self,
385 input: &[Sample],
386 out: &mut [Sample],
387 ) -> Result<usize, CodecError> {
388 self.inner.resample_into(input, out)
389 }
390
391 pub fn max_output_samples(&self, n_input: usize) -> usize {
393 self.inner.max_output_samples(n_input)
394 }
395
396 pub fn reset(&mut self) {
398 self.inner.reset();
399 }
400}
401
402#[cfg(test)]
403mod tests {
404 use super::*;
405 use std::f32::consts::PI as PI_F32;
406 use std::time::Instant;
407
408 fn new_resampler(input_rate: usize, output_rate: usize) -> Resampler<'static> {
409 let coeffs: &'static mut [i16] = Box::leak(vec![0i16; COEFFS_LEN].into_boxed_slice());
411 Resampler::new(input_rate, output_rate, coeffs).expect("resampler init")
412 }
413
414 #[test]
415 fn test_resample_8k_to_16k() {
416 let mut resampler = new_resampler(8000, 16000);
417 let input = vec![1000i16; 80];
418 let output = resampler.resample(&input);
419 assert!(output.len() >= 150 && output.len() <= 170);
420 for &s in &output[48..output.len().saturating_sub(48)] {
421 assert!((s - 1000).abs() < 100, "Value {} is too far from 1000", s);
422 }
423 }
424
425 #[test]
426 fn test_resample_16k_to_8k() {
427 let mut resampler = new_resampler(16000, 8000);
428 let input = vec![1000i16; 160];
429 let output = resampler.resample(&input);
430 assert!(output.len() >= 75 && output.len() <= 85);
431 let skip = output.len() / 4;
432 for &s in &output[skip..output.len() - skip] {
433 assert!((s - 1000).abs() < 100, "Value {} is too far from 1000", s);
434 }
435 }
436
437 #[test]
438 fn test_frequency_response_downsample() {
439 let mut resampler = new_resampler(16000, 8000);
440 let freq = 2000.0_f32; let samples: Vec<i16> = (0..160)
442 .map(|i| ((i as f32 * freq * 2.0 * PI_F32 / 16000.0).sin() * 10000.0) as i16)
443 .collect();
444
445 let output = resampler.resample(&samples);
446
447 let input_rms: f32 = samples
449 .iter()
450 .map(|&s| (s as f32).powi(2))
451 .sum::<f32>()
452 .sqrt()
453 / samples.len() as f32;
454 let output_rms: f32 = output
455 .iter()
456 .map(|&s| (s as f32).powi(2))
457 .sum::<f32>()
458 .sqrt()
459 / output.len() as f32;
460
461 assert!(
462 output_rms > input_rms * 0.7,
463 "Too much attenuation: input_rms={}, output_rms={}",
464 input_rms,
465 output_rms
466 );
467 }
468
469 #[test]
470 fn test_aliasing_suppression() {
471 let mut resampler = new_resampler(16000, 8000);
472 let freq = 7000.0_f32; let samples: Vec<i16> = (0..1600)
474 .map(|i| ((i as f32 * freq * 2.0 * PI_F32 / 16000.0).sin() * 10000.0) as i16)
475 .collect();
476
477 let output = resampler.resample(&samples);
478
479 let output_rms: f32 =
480 (output.iter().map(|&s| (s as f32).powi(2)).sum::<f32>() / output.len() as f32).sqrt();
481 let input_rms: f32 = 10000.0 / 1.414; assert!(
484 output_rms < input_rms / 50.0,
485 "Aliasing not sufficiently suppressed: output_rms={}",
486 output_rms
487 );
488 }
489
490 #[test]
491 fn test_performance_48k_to_8k() {
492 let mut resampler = new_resampler(48000, 8000);
493 let input = vec![0i16; 48000];
494
495 let start = Instant::now();
496 let iterations = 100;
497 for _ in 0..iterations {
498 let _ = resampler.resample(&input);
499 resampler.reset();
500 }
501 let duration = start.elapsed();
502 let per_second = duration.as_secs_f64() / iterations as f64;
503 println!(
504 "Resampling 1s of 48kHz to 8kHz (24 taps) took: {:.4}ms",
505 per_second * 1000.0
506 );
507 assert!(
508 per_second < 0.1,
509 "Performance regression: {}ms",
510 per_second * 1000.0
511 );
512 }
513
514 #[test]
515 fn test_continuity_between_chunks() {
516 let input_rate = 16000;
517 let output_rate = 8000;
518
519 let freq = 1000.0_f32;
520 let total_samples = 3200;
521 let input: Vec<i16> = (0..total_samples)
522 .map(|i| ((i as f32 * freq * 2.0 * PI_F32 / input_rate as f32).sin() * 5000.0) as i16)
523 .collect();
524
525 let mut resampler1 = new_resampler(input_rate, output_rate);
526 let output1 = resampler1.resample(&input);
527
528 let mut resampler2 = new_resampler(input_rate, output_rate);
529 let mid = input.len() / 2;
530 let mut output2 = resampler2.resample(&input[..mid]);
531 output2.extend_from_slice(&resampler2.resample(&input[mid..]));
532
533 assert_eq!(output1.len(), output2.len(), "Output lengths differ");
534
535 let max_diff: i16 = output1
536 .iter()
537 .zip(output2.iter())
538 .map(|(a, b)| (a - b).abs())
539 .max()
540 .unwrap_or(0);
541
542 assert!(
543 max_diff < 100,
544 "Large discontinuity between chunks: max_diff={}",
545 max_diff
546 );
547 }
548
549 #[test]
554 fn test_q15_core_matches_float_reference() {
555 let fin = 16000usize;
556 let fout = 48000usize;
557 let mut coeffs = vec![0i16; COEFFS_LEN];
558 let mut r = Resampler::new(fin, fout, &mut coeffs).unwrap();
559
560 let input: Vec<i16> = (0..2000).map(|i| (((i * 37) % 200 - 100) * 60) as i16).collect();
561 let got = r.resample(&input);
562
563 let taps = TAPS_PER_PHASE;
565 let step = ((fin as f64 / fout as f64) * PHASE_ONE as f64).round() as u32;
566 let mut hist = [0.0f32; TAPS_PER_PHASE];
567 let mut pos = 0u32;
568 let mut want = Vec::new();
569 for &s in &input {
570 hist.copy_within(1..taps, 0);
571 hist[taps - 1] = s as f32;
572 while pos < PHASE_ONE {
573 let phase = (pos >> (PHASE_BITS - 8)) as usize;
574 let mut acc = 0.0f32;
575 for t in 0..taps {
576 acc += hist[t] * (coeffs[phase * taps + t] as f32 / 32768.0);
577 }
578 want.push(acc.round().clamp(-32768.0, 32767.0) as i16);
579 pos += step;
580 }
581 pos -= PHASE_ONE;
582 }
583
584 assert_eq!(got.len(), want.len());
585 let se: f64 = got
586 .iter()
587 .zip(&want)
588 .map(|(&a, &b)| {
589 let d = a as f64 - b as f64;
590 d * d
591 })
592 .sum();
593 let sig: f64 = want.iter().map(|&b| (b as f64) * (b as f64)).sum();
594 let snr = 10.0 * (sig / se.max(1.0)).log10();
595 println!("Q15 vs f32-accumulator SNR = {:.1} dB", snr);
596 assert!(snr > 60.0, "integer core diverges from float: {:.1} dB", snr);
597 }
598
599 #[cfg(feature = "std")]
603 #[test]
604 fn test_boxed_resampler_matches_borrowed() {
605 let input_rate = 48000usize;
606 let output_rate = 8000usize;
607 let input: Vec<i16> = (0..4800)
608 .map(|i| ((i as f32 * 0.05).sin() * 8000.0) as i16)
609 .collect();
610
611 let expected = {
612 let mut coeffs = vec![0i16; COEFFS_LEN];
613 let mut borrowed = Resampler::new(input_rate, output_rate, &mut coeffs).unwrap();
614 borrowed.resample(&input)
615 };
616
617 let mut boxed = BoxedResampler::new(input_rate, output_rate).unwrap();
618 let expected_max = ((input.len() * output_rate + input_rate - 1) / input_rate) + 1;
619 assert_eq!(boxed.max_output_samples(input.len()), expected_max);
620 let got = boxed.resample(&input);
621 assert_eq!(
622 expected, got,
623 "BoxedResampler output must match borrowed Resampler"
624 );
625
626 let mut moved = boxed;
631 let again = moved.resample(&input);
632 assert_eq!(expected.len(), again.len());
633
634 moved.reset();
637 let after_reset = moved.resample(&input);
638 assert_eq!(expected, after_reset);
639
640 assert!(
641 BoxedResampler::new(0, 8000).is_err(),
642 "zero input rate must error"
643 );
644 assert!(
645 BoxedResampler::new(48000, 0).is_err(),
646 "zero output rate must error"
647 );
648 }
649}