use std::{error::Error, f64::consts::PI, fmt};
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct ResamplerPolicy {
pub phases: usize,
pub taps: usize,
pub cutoff_ratio: f64,
pub max_input_frames: usize,
}
impl Default for ResamplerPolicy {
fn default() -> Self {
Self {
phases: 1_024,
taps: 32,
cutoff_ratio: 0.94,
max_input_frames: 4_096,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ResampleError {
InvalidFormat,
InvalidPolicy,
MisalignedBuffer,
InputLimit {
supplied: usize,
maximum: usize,
},
OutputTooSmall {
required: usize,
available: usize,
},
TimeOverflow,
}
impl fmt::Display for ResampleError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidFormat => write!(f, "resampler rates and channels must be positive"),
Self::InvalidPolicy => write!(f, "resampler policy is outside supported bounds"),
Self::MisalignedBuffer => write!(f, "resampler buffer ends mid-frame"),
Self::InputLimit { supplied, maximum } => {
write!(
f,
"resampler input has {supplied} frames, exceeding {maximum}"
)
}
Self::OutputTooSmall {
required,
available,
} => write!(
f,
"resampler needs {required} output frames, but only {available} are available"
),
Self::TimeOverflow => write!(f, "resampler rational time counter overflowed"),
}
}
}
impl Error for ResampleError {}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ResampleReport {
pub input_frames: usize,
pub output_frames: usize,
pub latency_input_frames: usize,
}
#[derive(Clone, Debug, PartialEq)]
pub struct PolyphaseResampler {
input_rate_hz: u32,
output_rate_hz: u32,
channels: usize,
policy: ResamplerPolicy,
coefficients: Vec<f32>,
history: Vec<f32>,
input_frames_seen: u128,
next_output_time: u128,
}
impl PolyphaseResampler {
pub fn new(
input_rate_hz: u32,
output_rate_hz: u32,
channels: usize,
policy: ResamplerPolicy,
) -> Result<Self, ResampleError> {
if input_rate_hz == 0 || output_rate_hz == 0 || channels == 0 {
return Err(ResampleError::InvalidFormat);
}
if policy.phases < 2
|| policy.phases > 65_536
|| policy.taps < 8
|| policy.taps > 256
|| !policy.taps.is_multiple_of(2)
|| !policy.cutoff_ratio.is_finite()
|| !(0.0..=1.0).contains(&policy.cutoff_ratio)
|| policy.cutoff_ratio == 0.0
|| policy.max_input_frames == 0
{
return Err(ResampleError::InvalidPolicy);
}
let coefficients = coefficient_bank(input_rate_hz, output_rate_hz, policy);
let history_len = policy
.taps
.checked_mul(channels)
.ok_or(ResampleError::InvalidPolicy)?;
Ok(Self {
input_rate_hz,
output_rate_hz,
channels,
policy,
coefficients,
history: vec![0.0; history_len],
input_frames_seen: 0,
next_output_time: 0,
})
}
pub fn input_rate_hz(&self) -> u32 {
self.input_rate_hz
}
pub fn output_rate_hz(&self) -> u32 {
self.output_rate_hz
}
pub fn channels(&self) -> usize {
self.channels
}
pub fn latency_input_frames(&self) -> usize {
self.policy.taps / 2
}
pub fn required_output_frames(&self, input_frames: usize) -> Result<usize, ResampleError> {
if input_frames > self.policy.max_input_frames {
return Err(ResampleError::InputLimit {
supplied: input_frames,
maximum: self.policy.max_input_frames,
});
}
if input_frames == 0 {
return Ok(0);
}
let last_seen = self
.input_frames_seen
.checked_add(input_frames as u128)
.and_then(|value| value.checked_sub(1))
.ok_or(ResampleError::TimeOverflow)?;
let right = self.latency_input_frames() as u128;
let denominator = u128::from(self.output_rate_hz);
let step = u128::from(self.input_rate_hz);
let mut time = self.next_output_time;
let mut count = 0usize;
while time / denominator + right <= last_seen {
count = count.checked_add(1).ok_or(ResampleError::TimeOverflow)?;
time = time.checked_add(step).ok_or(ResampleError::TimeOverflow)?;
}
Ok(count)
}
pub fn process_interleaved(
&mut self,
input: &[f32],
output: &mut [f32],
) -> Result<ResampleReport, ResampleError> {
if !input.len().is_multiple_of(self.channels) || !output.len().is_multiple_of(self.channels)
{
return Err(ResampleError::MisalignedBuffer);
}
let input_frames = input.len() / self.channels;
let required = self.required_output_frames(input_frames)?;
let available = output.len() / self.channels;
if available < required {
return Err(ResampleError::OutputTooSmall {
required,
available,
});
}
let mut produced = 0usize;
for frame in 0..input_frames {
let absolute = self.input_frames_seen;
let ring_frame = (absolute % self.policy.taps as u128) as usize;
let ring_base = ring_frame * self.channels;
let input_base = frame * self.channels;
self.history[ring_base..ring_base + self.channels]
.copy_from_slice(&input[input_base..input_base + self.channels]);
self.input_frames_seen = self
.input_frames_seen
.checked_add(1)
.ok_or(ResampleError::TimeOverflow)?;
while self.output_ready(absolute) {
self.render_output_frame(produced, output)?;
produced += 1;
self.next_output_time = self
.next_output_time
.checked_add(u128::from(self.input_rate_hz))
.ok_or(ResampleError::TimeOverflow)?;
}
}
debug_assert_eq!(produced, required);
Ok(ResampleReport {
input_frames,
output_frames: produced,
latency_input_frames: self.latency_input_frames(),
})
}
pub fn reset(&mut self) {
self.history.fill(0.0);
self.input_frames_seen = 0;
self.next_output_time = 0;
}
fn output_ready(&self, current_input: u128) -> bool {
self.next_output_time / u128::from(self.output_rate_hz)
+ self.latency_input_frames() as u128
<= current_input
}
fn render_output_frame(
&self,
output_frame: usize,
output: &mut [f32],
) -> Result<(), ResampleError> {
let denominator = u128::from(self.output_rate_hz);
let center = self.next_output_time / denominator;
let remainder = self.next_output_time % denominator;
let phase = ((remainder * self.policy.phases as u128) / denominator) as usize;
let coefficient_base = phase.min(self.policy.phases - 1) * self.policy.taps;
let left = self.policy.taps / 2 - 1;
for channel in 0..self.channels {
let mut sample = 0.0f64;
for tap in 0..self.policy.taps {
let source = signed_source(center, tap, left);
let value = source
.and_then(|absolute| self.history_sample(absolute, channel))
.unwrap_or(0.0);
sample += f64::from(value) * f64::from(self.coefficients[coefficient_base + tap]);
}
let at = output_frame
.checked_mul(self.channels)
.and_then(|base| base.checked_add(channel))
.ok_or(ResampleError::TimeOverflow)?;
output[at] = sample as f32;
}
Ok(())
}
fn history_sample(&self, absolute: u128, channel: usize) -> Option<f32> {
if absolute >= self.input_frames_seen {
return None;
}
let age = self.input_frames_seen - 1 - absolute;
if age >= self.policy.taps as u128 {
return None;
}
let frame = (absolute % self.policy.taps as u128) as usize;
Some(self.history[frame * self.channels + channel])
}
#[cfg(test)]
pub(crate) fn realtime_state_snapshot(&self) -> [usize; 2] {
[self.coefficients.capacity(), self.history.capacity()]
}
}
fn signed_source(center: u128, tap: usize, left: usize) -> Option<u128> {
if tap >= left {
center.checked_add((tap - left) as u128)
} else {
center.checked_sub((left - tap) as u128)
}
}
fn coefficient_bank(input_rate_hz: u32, output_rate_hz: u32, policy: ResamplerPolicy) -> Vec<f32> {
let rate_ratio = f64::from(output_rate_hz) / f64::from(input_rate_hz);
let cutoff = 0.5 * rate_ratio.min(1.0) * policy.cutoff_ratio;
let left = policy.taps / 2 - 1;
let mut coefficients = Vec::with_capacity(policy.phases * policy.taps);
for phase in 0..policy.phases {
let fraction = phase as f64 / policy.phases as f64;
let start = coefficients.len();
for tap in 0..policy.taps {
let distance = tap as f64 - left as f64 - fraction;
let ideal = 2.0 * cutoff * sinc(2.0 * cutoff * distance);
let window_position = tap as f64 / (policy.taps - 1) as f64;
let blackman = 0.42 - 0.5 * (2.0 * PI * window_position).cos()
+ 0.08 * (4.0 * PI * window_position).cos();
coefficients.push((ideal * blackman) as f32);
}
let sum = coefficients[start..]
.iter()
.map(|value| f64::from(*value))
.sum::<f64>();
for value in &mut coefficients[start..] {
*value = (f64::from(*value) / sum) as f32;
}
}
coefficients
}
fn sinc(value: f64) -> f64 {
if value.abs() <= f64::EPSILON {
1.0
} else {
(PI * value).sin() / (PI * value)
}
}