1#![allow(clippy::cast_possible_truncation, clippy::cast_precision_loss)]
10
11use core::fmt;
12use core::ops::Range;
13
14const HALF_TAPS: u64 = 16;
16const KAISER_BETA: f64 = 9.0;
18const CUTOFF: f64 = 0.97;
20const MAX_TABLE_COEFFICIENTS: u64 = 1 << 18;
23pub const MAX_RESAMPLE_RATE: u32 = 768_000;
25pub const MAX_DOWNSAMPLE_FACTOR: u32 = (MAX_TABLE_COEFFICIENTS / (2 * HALF_TAPS)) as u32;
28
29#[derive(Clone, Copy, Debug, PartialEq, Eq)]
31pub struct ResampleError {
32 pub source_rate: u32,
34 pub target_rate: u32,
36}
37
38impl fmt::Display for ResampleError {
39 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40 write!(
41 f,
42 "cannot resample {} Hz to {} Hz: both rates must be 1 to {MAX_RESAMPLE_RATE} Hz \
43 and the source rate at most {MAX_DOWNSAMPLE_FACTOR} times the target rate",
44 self.source_rate, self.target_rate
45 )
46 }
47}
48
49impl std::error::Error for ResampleError {}
50
51#[derive(Clone, Debug)]
53pub struct Resampler {
54 source_rate: u32,
55 target_rate: u32,
56 source_step: u64,
58 target_step: u64,
60 half: u64,
62 phases: u64,
64 table: Vec<f32>,
67}
68
69impl Resampler {
70 pub fn new(source_rate: u32, target_rate: u32) -> Result<Self, ResampleError> {
78 if !(1..=MAX_RESAMPLE_RATE).contains(&source_rate)
79 || !(1..=MAX_RESAMPLE_RATE).contains(&target_rate)
80 || u64::from(source_rate) > u64::from(MAX_DOWNSAMPLE_FACTOR) * u64::from(target_rate)
81 {
82 return Err(ResampleError {
83 source_rate,
84 target_rate,
85 });
86 }
87 let divisor = gcd(u64::from(source_rate), u64::from(target_rate));
88 let source_step = u64::from(source_rate) / divisor;
89 let target_step = u64::from(target_rate) / divisor;
90 if source_rate == target_rate {
91 return Ok(Self {
92 source_rate,
93 target_rate,
94 source_step,
95 target_step,
96 half: 0,
97 phases: 1,
98 table: Vec::new(),
99 });
100 }
101 let factor = (f64::from(target_rate) / f64::from(source_rate)).min(1.0);
103 let half = (HALF_TAPS * u64::from(source_rate)).div_ceil(u64::from(target_rate));
104 let half = half.max(HALF_TAPS);
105 let taps = 2 * half;
106 debug_assert!(taps <= MAX_TABLE_COEFFICIENTS, "the fall is bounded above");
107 let phases = target_step.min((MAX_TABLE_COEFFICIENTS / taps).max(1));
108 let cutoff = CUTOFF * factor;
109 let window_scale = 1.0 / bessel_i0(KAISER_BETA);
110 let mut table = Vec::with_capacity(usize::try_from(phases * taps).unwrap_or(0));
111 let mut row = vec![0.0_f64; usize::try_from(taps).unwrap_or(0)];
112 for phase in 0..phases {
113 let offset = phase as f64 / phases as f64;
114 let mut sum = 0.0;
115 for (tap, weight) in row.iter_mut().enumerate() {
116 let distance = (tap as f64 + 1.0 - half as f64) - offset;
118 let x = distance / half as f64;
119 let window = if x.abs() >= 1.0 {
120 0.0
121 } else {
122 bessel_i0(KAISER_BETA * libm::sqrt(1.0 - x * x)) * window_scale
123 };
124 *weight = cutoff * sinc(cutoff * distance) * window;
125 sum += *weight;
126 }
127 table.extend(row.iter().map(|weight| (*weight / sum) as f32));
129 }
130 Ok(Self {
131 source_rate,
132 target_rate,
133 source_step,
134 target_step,
135 half,
136 phases,
137 table,
138 })
139 }
140
141 #[must_use]
143 pub const fn source_rate(&self) -> u32 {
144 self.source_rate
145 }
146
147 #[must_use]
149 pub const fn target_rate(&self) -> u32 {
150 self.target_rate
151 }
152
153 #[must_use]
155 pub const fn is_identity(&self) -> bool {
156 self.half == 0
157 }
158
159 #[must_use]
161 pub fn half_taps(&self) -> i64 {
162 i64::try_from(self.half).unwrap_or(i64::MAX)
163 }
164
165 #[must_use]
167 pub fn target_at_or_after(&self, source: i64) -> i64 {
168 div_ceil(
169 i128::from(source) * i128::from(self.target_step),
170 i128::from(self.source_step),
171 )
172 }
173
174 #[must_use]
176 pub fn target_known_before(&self, source_end: i64) -> i64 {
177 let rounded = i64::from(self.phases < self.target_step);
179 self.target_at_or_after(source_end.saturating_sub(self.half_taps() + rounded))
180 }
181
182 #[must_use]
184 pub fn source_span(&self, target: Range<i64>) -> Range<i64> {
185 if target.is_empty() {
186 return 0..0;
187 }
188 let (first, _) = self.position(target.start);
189 let (last, _) = self.position(target.end - 1);
190 let half = self.half_taps();
191 if half == 0 {
192 return first..last + 1;
193 }
194 first - half + 1..last + half + 1
195 }
196
197 pub fn resample(&self, input: &[f32], input_start: i64, target_start: i64, output: &mut [f32]) {
200 let len = i64::try_from(input.len()).unwrap_or(i64::MAX);
201 let half = self.half_taps();
202 let taps = usize::try_from(2 * self.half).unwrap_or(0);
203 for (n, sample) in (target_start..).zip(output.iter_mut()) {
204 let (index, phase) = self.position(n);
205 if half == 0 {
206 let at = index - input_start;
207 *sample = if (0..len).contains(&at) {
208 input[usize::try_from(at).unwrap_or(0)]
209 } else {
210 0.0
211 };
212 continue;
213 }
214 let first = index - half + 1 - input_start;
215 let row = &self.table[phase * taps..(phase + 1) * taps];
216 let mut sum = 0.0_f64;
217 if first >= 0 && first + 2 * half <= len {
218 let from = usize::try_from(first).unwrap_or(0);
219 for (weight, value) in row.iter().zip(&input[from..from + taps]) {
220 sum += f64::from(*weight) * f64::from(*value);
221 }
222 } else {
223 for (offset, weight) in (first..).zip(row) {
224 if (0..len).contains(&offset) {
225 sum += f64::from(*weight)
226 * f64::from(input[usize::try_from(offset).unwrap_or(0)]);
227 }
228 }
229 }
230 *sample = sum as f32;
231 }
232 }
233
234 fn position(&self, target: i64) -> (i64, usize) {
236 let numerator = i128::from(target) * i128::from(self.source_step);
237 let step = i128::from(self.target_step);
238 let mut index = numerator.div_euclid(step);
239 let remainder = numerator.rem_euclid(step);
240 let phases = i128::from(self.phases);
241 let mut phase = (remainder * phases * 2 + step).div_euclid(step * 2);
243 if phase == phases {
244 index += 1;
245 phase = 0;
246 }
247 (
248 i64::try_from(index).unwrap_or(i64::MAX),
249 usize::try_from(phase).unwrap_or(0),
250 )
251 }
252}
253
254pub const MAX_FILTERED_SPEED: f64 = 64.0;
257const KERNEL_RESOLUTION: usize = 1_024;
259
260#[derive(Clone, Copy, Debug)]
268pub struct Interpolator {
269 scale: f64,
271 half: i64,
273}
274
275impl Interpolator {
276 #[must_use]
278 pub fn new(speed: f64) -> Self {
279 let scale = if speed.is_finite() {
280 speed.clamp(1.0, MAX_FILTERED_SPEED)
281 } else {
282 1.0
283 };
284 Self {
285 scale,
286 half: libm::ceil(HALF_TAPS as f64 * scale) as i64,
287 }
288 }
289
290 #[must_use]
293 pub const fn half_taps(&self) -> i64 {
294 self.half
295 }
296
297 pub fn weights(&self, fraction: f64, weights: &mut Vec<f64>) {
300 let table = kernel_table();
301 let taps = usize::try_from(2 * self.half).unwrap_or(0);
302 weights.resize(taps, 0.0);
303 let step = KERNEL_RESOLUTION as f64 / self.scale;
304 let last = table.len() - 1;
305 let weigh = |distance: f64| {
306 let at = distance * step;
307 #[allow(clippy::cast_sign_loss)]
309 let index = at as usize;
310 if index >= last {
311 0.0
312 } else {
313 let low = table[index];
314 low + (table[index + 1] - low) * (at - index as f64)
315 }
316 };
317 let before = usize::try_from(self.half).unwrap_or(0);
319 let mut sum = 0.0;
320 for (tap, weight) in weights[..before].iter_mut().enumerate() {
321 *weight = weigh((before - 1 - tap) as f64 + fraction);
322 sum += *weight;
323 }
324 for (tap, weight) in weights[before..].iter_mut().enumerate() {
325 *weight = weigh((tap + 1) as f64 - fraction);
326 sum += *weight;
327 }
328 let scale = 1.0 / sum;
330 for weight in weights.iter_mut() {
331 *weight *= scale;
332 }
333 }
334}
335
336fn kernel_table() -> &'static [f64] {
339 static TABLE: std::sync::OnceLock<Vec<f64>> = std::sync::OnceLock::new();
340 TABLE.get_or_init(|| {
341 let points = HALF_TAPS as usize * KERNEL_RESOLUTION;
342 let window_scale = 1.0 / bessel_i0(KAISER_BETA);
343 (0..=points)
344 .map(|index| {
345 let u = index as f64 / KERNEL_RESOLUTION as f64;
346 let x = u / HALF_TAPS as f64;
347 if x >= 1.0 {
348 return 0.0;
349 }
350 let window = bessel_i0(KAISER_BETA * libm::sqrt(1.0 - x * x)) * window_scale;
351 CUTOFF * sinc(CUTOFF * u) * window
352 })
353 .collect()
354 })
355}
356
357fn div_ceil(numerator: i128, denominator: i128) -> i64 {
358 let quotient =
359 numerator.div_euclid(denominator) + i128::from(numerator.rem_euclid(denominator) != 0);
360 i64::try_from(quotient).unwrap_or(if quotient < 0 { i64::MIN } else { i64::MAX })
361}
362
363const fn gcd(mut a: u64, mut b: u64) -> u64 {
364 while b != 0 {
365 (a, b) = (b, a % b);
366 }
367 a
368}
369
370fn sinc(x: f64) -> f64 {
371 if x == 0.0 {
372 return 1.0;
373 }
374 let angle = core::f64::consts::PI * x;
375 libm::sin(angle) / angle
376}
377
378fn bessel_i0(x: f64) -> f64 {
380 let quarter = x * x / 4.0;
381 let mut term = 1.0;
382 let mut sum = 1.0;
383 let mut k = 1.0;
384 while term > sum * 1e-17 {
385 term *= quarter / (k * k);
386 sum += term;
387 k += 1.0;
388 }
389 sum
390}