1#![allow(clippy::cast_possible_truncation, clippy::cast_precision_loss)]
16
17use core::fmt;
18use core::ops::Range;
19
20const HALF_TAPS: u64 = 16;
22const KAISER_BETA: f64 = 9.0;
24const CUTOFF: f64 = 0.97;
26const MAX_TABLE_COEFFICIENTS: u64 = 1 << 18;
30pub const MAX_RESAMPLE_RATE: u32 = 768_000;
32pub const MAX_DOWNSAMPLE_FACTOR: u32 = (MAX_TABLE_COEFFICIENTS / (2 * HALF_TAPS)) as u32;
37
38#[derive(Clone, Copy, Debug, PartialEq, Eq)]
40pub struct ResampleError {
41 pub source_rate: u32,
43 pub target_rate: u32,
45}
46
47impl fmt::Display for ResampleError {
48 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
49 write!(
50 f,
51 "cannot resample {} Hz to {} Hz: both rates must be 1 to {MAX_RESAMPLE_RATE} Hz \
52 and the source rate at most {MAX_DOWNSAMPLE_FACTOR} times the target rate",
53 self.source_rate, self.target_rate
54 )
55 }
56}
57
58impl std::error::Error for ResampleError {}
59
60#[derive(Clone, Debug)]
62pub struct Resampler {
63 source_rate: u32,
64 target_rate: u32,
65 source_step: u64,
67 target_step: u64,
69 half: u64,
71 phases: u64,
73 table: Vec<f32>,
76}
77
78impl Resampler {
79 pub fn new(source_rate: u32, target_rate: u32) -> Result<Self, ResampleError> {
87 if !(1..=MAX_RESAMPLE_RATE).contains(&source_rate)
88 || !(1..=MAX_RESAMPLE_RATE).contains(&target_rate)
89 || u64::from(source_rate) > u64::from(MAX_DOWNSAMPLE_FACTOR) * u64::from(target_rate)
90 {
91 return Err(ResampleError {
92 source_rate,
93 target_rate,
94 });
95 }
96 let divisor = gcd(u64::from(source_rate), u64::from(target_rate));
97 let source_step = u64::from(source_rate) / divisor;
98 let target_step = u64::from(target_rate) / divisor;
99 if source_rate == target_rate {
100 return Ok(Self {
101 source_rate,
102 target_rate,
103 source_step,
104 target_step,
105 half: 0,
106 phases: 1,
107 table: Vec::new(),
108 });
109 }
110 let factor = (f64::from(target_rate) / f64::from(source_rate)).min(1.0);
113 let half = (HALF_TAPS * u64::from(source_rate)).div_ceil(u64::from(target_rate));
114 let half = half.max(HALF_TAPS);
115 let taps = 2 * half;
116 debug_assert!(taps <= MAX_TABLE_COEFFICIENTS, "the fall is bounded above");
117 let phases = target_step.min((MAX_TABLE_COEFFICIENTS / taps).max(1));
118 let cutoff = CUTOFF * factor;
119 let window_scale = 1.0 / bessel_i0(KAISER_BETA);
120 let mut table = Vec::with_capacity(usize::try_from(phases * taps).unwrap_or(0));
121 let mut row = vec![0.0_f64; usize::try_from(taps).unwrap_or(0)];
122 for phase in 0..phases {
123 let offset = phase as f64 / phases as f64;
124 let mut sum = 0.0;
125 for (tap, weight) in row.iter_mut().enumerate() {
126 let distance = (tap as f64 + 1.0 - half as f64) - offset;
128 let x = distance / half as f64;
129 let window = if x.abs() >= 1.0 {
130 0.0
131 } else {
132 bessel_i0(KAISER_BETA * libm::sqrt(1.0 - x * x)) * window_scale
133 };
134 *weight = cutoff * sinc(cutoff * distance) * window;
135 sum += *weight;
136 }
137 table.extend(row.iter().map(|weight| (*weight / sum) as f32));
139 }
140 Ok(Self {
141 source_rate,
142 target_rate,
143 source_step,
144 target_step,
145 half,
146 phases,
147 table,
148 })
149 }
150
151 #[must_use]
153 pub const fn source_rate(&self) -> u32 {
154 self.source_rate
155 }
156
157 #[must_use]
159 pub const fn target_rate(&self) -> u32 {
160 self.target_rate
161 }
162
163 #[must_use]
165 pub const fn is_identity(&self) -> bool {
166 self.half == 0
167 }
168
169 #[must_use]
171 pub fn half_taps(&self) -> i64 {
172 i64::try_from(self.half).unwrap_or(i64::MAX)
173 }
174
175 #[must_use]
177 pub fn target_at_or_after(&self, source: i64) -> i64 {
178 div_ceil(
179 i128::from(source) * i128::from(self.target_step),
180 i128::from(self.source_step),
181 )
182 }
183
184 #[must_use]
188 pub fn target_known_before(&self, source_end: i64) -> i64 {
189 let rounded = i64::from(self.phases < self.target_step);
191 self.target_at_or_after(source_end.saturating_sub(self.half_taps() + rounded))
192 }
193
194 #[must_use]
196 pub fn source_span(&self, target: Range<i64>) -> Range<i64> {
197 if target.is_empty() {
198 return 0..0;
199 }
200 let (first, _) = self.position(target.start);
201 let (last, _) = self.position(target.end - 1);
202 let half = self.half_taps();
203 if half == 0 {
204 return first..last + 1;
205 }
206 first - half + 1..last + half + 1
207 }
208
209 pub fn resample(&self, input: &[f32], input_start: i64, target_start: i64, output: &mut [f32]) {
213 let len = i64::try_from(input.len()).unwrap_or(i64::MAX);
214 let half = self.half_taps();
215 let taps = usize::try_from(2 * self.half).unwrap_or(0);
216 for (n, sample) in (target_start..).zip(output.iter_mut()) {
217 let (index, phase) = self.position(n);
218 if half == 0 {
219 let at = index - input_start;
220 *sample = if (0..len).contains(&at) {
221 input[usize::try_from(at).unwrap_or(0)]
222 } else {
223 0.0
224 };
225 continue;
226 }
227 let first = index - half + 1 - input_start;
228 let row = &self.table[phase * taps..(phase + 1) * taps];
229 let mut sum = 0.0_f64;
230 if first >= 0 && first + 2 * half <= len {
231 let from = usize::try_from(first).unwrap_or(0);
232 for (weight, value) in row.iter().zip(&input[from..from + taps]) {
233 sum += f64::from(*weight) * f64::from(*value);
234 }
235 } else {
236 for (offset, weight) in (first..).zip(row) {
237 if (0..len).contains(&offset) {
238 sum += f64::from(*weight)
239 * f64::from(input[usize::try_from(offset).unwrap_or(0)]);
240 }
241 }
242 }
243 *sample = sum as f32;
244 }
245 }
246
247 fn position(&self, target: i64) -> (i64, usize) {
250 let numerator = i128::from(target) * i128::from(self.source_step);
251 let step = i128::from(self.target_step);
252 let mut index = numerator.div_euclid(step);
253 let remainder = numerator.rem_euclid(step);
254 let phases = i128::from(self.phases);
255 let mut phase = (remainder * phases * 2 + step).div_euclid(step * 2);
257 if phase == phases {
258 index += 1;
259 phase = 0;
260 }
261 (
262 i64::try_from(index).unwrap_or(i64::MAX),
263 usize::try_from(phase).unwrap_or(0),
264 )
265 }
266}
267
268pub const MAX_FILTERED_SPEED: f64 = 64.0;
272const KERNEL_RESOLUTION: usize = 1_024;
274
275#[derive(Clone, Copy, Debug)]
288pub struct Interpolator {
289 scale: f64,
292 half: i64,
294}
295
296impl Interpolator {
297 #[must_use]
301 pub fn new(speed: f64) -> Self {
302 let scale = if speed.is_finite() {
303 speed.clamp(1.0, MAX_FILTERED_SPEED)
304 } else {
305 1.0
306 };
307 Self {
308 scale,
309 half: libm::ceil(HALF_TAPS as f64 * scale) as i64,
310 }
311 }
312
313 #[must_use]
317 pub const fn half_taps(&self) -> i64 {
318 self.half
319 }
320
321 pub fn weights(&self, fraction: f64, weights: &mut Vec<f64>) {
325 let table = kernel_table();
326 let taps = usize::try_from(2 * self.half).unwrap_or(0);
327 weights.resize(taps, 0.0);
328 let step = KERNEL_RESOLUTION as f64 / self.scale;
329 let last = table.len() - 1;
330 let weigh = |distance: f64| {
331 let at = distance * step;
332 #[allow(clippy::cast_sign_loss)]
334 let index = at as usize;
335 if index >= last {
336 0.0
337 } else {
338 let low = table[index];
339 low + (table[index + 1] - low) * (at - index as f64)
340 }
341 };
342 let before = usize::try_from(self.half).unwrap_or(0);
344 let mut sum = 0.0;
345 for (tap, weight) in weights[..before].iter_mut().enumerate() {
346 *weight = weigh((before - 1 - tap) as f64 + fraction);
347 sum += *weight;
348 }
349 for (tap, weight) in weights[before..].iter_mut().enumerate() {
350 *weight = weigh((tap + 1) as f64 - fraction);
351 sum += *weight;
352 }
353 let scale = 1.0 / sum;
355 for weight in weights.iter_mut() {
356 *weight *= scale;
357 }
358 }
359}
360
361fn kernel_table() -> &'static [f64] {
364 static TABLE: std::sync::OnceLock<Vec<f64>> = std::sync::OnceLock::new();
365 TABLE.get_or_init(|| {
366 let points = HALF_TAPS as usize * KERNEL_RESOLUTION;
367 let window_scale = 1.0 / bessel_i0(KAISER_BETA);
368 (0..=points)
369 .map(|index| {
370 let u = index as f64 / KERNEL_RESOLUTION as f64;
371 let x = u / HALF_TAPS as f64;
372 if x >= 1.0 {
373 return 0.0;
374 }
375 let window = bessel_i0(KAISER_BETA * libm::sqrt(1.0 - x * x)) * window_scale;
376 CUTOFF * sinc(CUTOFF * u) * window
377 })
378 .collect()
379 })
380}
381
382fn div_ceil(numerator: i128, denominator: i128) -> i64 {
383 let quotient =
384 numerator.div_euclid(denominator) + i128::from(numerator.rem_euclid(denominator) != 0);
385 i64::try_from(quotient).unwrap_or(if quotient < 0 { i64::MIN } else { i64::MAX })
386}
387
388const fn gcd(mut a: u64, mut b: u64) -> u64 {
389 while b != 0 {
390 (a, b) = (b, a % b);
391 }
392 a
393}
394
395fn sinc(x: f64) -> f64 {
396 if x == 0.0 {
397 return 1.0;
398 }
399 let angle = core::f64::consts::PI * x;
400 libm::sin(angle) / angle
401}
402
403fn bessel_i0(x: f64) -> f64 {
405 let quarter = x * x / 4.0;
406 let mut term = 1.0;
407 let mut sum = 1.0;
408 let mut k = 1.0;
409 while term > sum * 1e-17 {
410 term *= quarter / (k * k);
411 sum += term;
412 k += 1.0;
413 }
414 sum
415}