use std::f64::consts::PI;
use crate::{DspError, invalid, pairwise_sum};
pub const MIN_RESAMPLE_RATE: u32 = 8_000;
pub const MAX_RESAMPLE_RATE: u32 = 384_000;
const HALF_WIDTH: usize = 96;
const CUTOFF: f64 = 0.96;
#[derive(Debug, Clone, Copy)]
pub struct IrResamplePlan {
input_len: usize,
up: usize,
down: usize,
half: usize,
prepad: usize,
output_len: usize,
leading: usize,
}
impl IrResamplePlan {
pub fn new(input_len: usize, input_hz: u32, output_hz: u32) -> Result<Self, DspError> {
if input_len == 0 {
return Err(invalid("IR resampling needs nonempty input"));
}
if !(MIN_RESAMPLE_RATE..=MAX_RESAMPLE_RATE).contains(&input_hz)
|| !(MIN_RESAMPLE_RATE..=MAX_RESAMPLE_RATE).contains(&output_hz)
{
return Err(invalid(
"IR resampling supports sample rates from 8000 to 384000 Hz",
));
}
let mut a = input_hz;
let mut b = output_hz;
while b != 0 {
(a, b) = (b, a % b);
}
let up = (output_hz / a) as usize;
let down = (input_hz / a) as usize;
if up == down {
input_len
.checked_mul(size_of::<f64>())
.ok_or_else(|| invalid("IR resampling output byte size overflow"))?;
return Ok(Self {
input_len,
up,
down,
half: 0,
prepad: 0,
output_len: input_len,
leading: 0,
});
}
let half = HALF_WIDTH
.checked_mul(up.max(down))
.ok_or_else(|| invalid("IR resampling kernel length overflow"))?;
let prepad = (down - half % down) % down;
let last = (input_len - 1)
.checked_mul(up)
.and_then(|n| n.checked_add(2 * half + prepad))
.ok_or_else(|| invalid("IR resampling output length overflow"))?;
let output_len = (last / down)
.checked_add(1)
.ok_or_else(|| invalid("IR resampling output length overflow"))?;
output_len
.checked_mul(size_of::<f64>())
.ok_or_else(|| invalid("IR resampling output byte size overflow"))?;
(2 * half + 1)
.checked_mul(size_of::<f64>())
.ok_or_else(|| invalid("IR resampling kernel byte size overflow"))?;
Ok(Self {
input_len,
up,
down,
half,
prepad,
output_len,
leading: (half + prepad) / down,
})
}
pub fn output_len(&self) -> usize {
self.output_len
}
pub fn leading_output_samples(&self) -> usize {
self.leading
}
pub fn kernel_bytes(&self) -> usize {
if self.half == 0 {
0
} else {
(2 * self.half + 1) * size_of::<f64>()
}
}
pub fn process<E>(
&self,
sample: impl Fn(usize) -> f64,
mut check: impl FnMut() -> Result<(), E>,
) -> Result<Vec<f64>, E>
where
E: From<DspError>,
{
check()?;
let mut output = Vec::new();
output
.try_reserve_exact(self.output_len)
.map_err(|_| E::from(invalid("IR resampling output allocation failed")))?;
if self.half == 0 {
for n in 0..self.input_len {
if n % 256 == 0 {
check()?;
}
output.push(sample(n));
}
check()?;
return Ok(output);
}
let cutoff = CUTOFF / self.up.max(self.down) as f64;
let mut kernel = Vec::new();
kernel
.try_reserve_exact(2 * self.half + 1)
.map_err(|_| E::from(invalid("IR resampling kernel allocation failed")))?;
for i in 0..=2 * self.half {
if i % 256 == 0 {
check()?;
}
let x = cutoff * (i as f64 - self.half as f64);
let sinc = if x == 0.0 {
1.0
} else {
(PI * x).sin() / (PI * x)
};
let phase = PI * i as f64 / self.half as f64;
let window = 0.35875 - 0.48829 * phase.cos() + 0.14128 * (2.0 * phase).cos()
- 0.01168 * (3.0 * phase).cos();
kernel.push(cutoff * sinc * window);
}
check()?;
let scale = self.down as f64 / pairwise_sum(&kernel);
check()?;
for (i, value) in kernel.iter_mut().enumerate() {
if i % 256 == 0 {
check()?;
}
*value *= scale;
}
for k in 0..self.output_len {
if k % 256 == 0 {
check()?;
}
let time = k * self.down;
let mut value = 0.0;
if time >= self.prepad {
let time = time - self.prepad;
let first = time.saturating_sub(2 * self.half).div_ceil(self.up);
let last = (time / self.up).min(self.input_len - 1);
for n in first..=last {
value += sample(n) * kernel[time - n * self.up];
}
}
if !value.is_finite() {
return Err(E::from(invalid(
"IR resampling produced a nonfinite sample",
)));
}
output.push(value);
}
check()?;
Ok(output)
}
}