use std::collections::VecDeque;
use crate::sound::data::{Channels, ClipFrame, SampleRate};
const HALF: usize = 32;
const TAPS: usize = 2 * HALF;
const PHASES: usize = 256;
const PASSED: f64 = 0.95;
const BLACKMAN_HARRIS: [f64; 4] = [0.35875, 0.48829, 0.14128, 0.01168];
pub(crate) struct Resampler {
channels: usize,
from: u64,
to: u64,
taps: Option<Taps>,
at: u64,
first: u64,
held: VecDeque<f32>,
ready: VecDeque<f32>,
}
impl Resampler {
pub(crate) fn new(from: SampleRate, to: SampleRate, channels: Channels) -> Self {
let (from, to) = (u64::from(from), u64::from(to));
Self {
channels: channels.count(),
from,
to,
taps: (from != to).then(|| Taps::new(from, to)),
at: 0,
first: 0,
held: VecDeque::new(),
ready: VecDeque::new(),
}
}
pub(crate) fn whole(
input: &[f32],
from: SampleRate,
to: SampleRate,
channels: Channels,
) -> Vec<f32> {
let mut resampler = Self::new(from, to, channels);
let frames = from.frames_at(input.len() as u64 / channels.count() as u64, to);
let mut made = Vec::with_capacity(frames as usize * channels.count());
resampler.feed(input);
resampler.end();
resampler.take(frames as usize, &mut made);
made
}
pub(crate) fn restart(&mut self, at: ClipFrame) -> ClipFrame {
self.held.clear();
self.ready.clear();
self.at = at.get();
self.first = self.reads(self.at).0.saturating_sub(self.lead());
ClipFrame::new(self.first)
}
pub(crate) fn feed(&mut self, input: &[f32]) {
if self.taps.is_none() {
self.ready.extend(input);
return;
}
self.held.extend(input);
self.produce(false);
}
pub(crate) fn end(&mut self) {
self.produce(true);
}
pub(crate) fn take(&mut self, frames: usize, into: &mut impl Extend<f32>) -> usize {
let taking = frames.min(self.ready.len() / self.channels);
into.extend(self.ready.drain(..taking * self.channels));
taking
}
fn produce(&mut self, ended: bool) {
let Some(taps) = &self.taps else {
return;
};
loop {
let (frame, over) = self.reads(self.at);
let held = self.first + (self.held.len() / self.channels) as u64;
let reach = match ended {
true => frame,
false => frame + HALF as u64,
};
if reach >= held {
return;
}
let read = frame as i64 - HALF as i64 + 1;
for channel in 0..self.channels {
let made = taps
.weights(over)
.enumerate()
.map(|(tap, weight)| weight * self.sample(read + tap as i64, channel))
.sum();
self.ready.push_back(made);
}
self.at += 1;
let dropped = (read.max(0) as u64).saturating_sub(self.first);
self.held.drain(..dropped as usize * self.channels);
self.first += dropped;
}
}
fn reads(&self, at: u64) -> (u64, f64) {
let read = at * self.from;
(read / self.to, (read % self.to) as f64 / self.to as f64)
}
fn lead(&self) -> u64 {
match self.taps {
Some(_) => HALF as u64 - 1,
None => 0,
}
}
fn sample(&self, frame: i64, channel: usize) -> f32 {
let Ok(held) = usize::try_from(frame - self.first as i64) else {
return 0.0;
};
self.held
.get(held * self.channels + channel)
.copied()
.unwrap_or_default()
}
}
struct Taps(Vec<f32>);
impl Taps {
fn new(from: u64, to: u64) -> Self {
let band = PASSED * to.min(from) as f64 / (2.0 * from as f64);
let mut rows = Vec::with_capacity((PHASES + 1) * TAPS);
for phase in 0..=PHASES {
let over = phase as f64 / PHASES as f64;
let row: Vec<f64> = (0..TAPS)
.map(|tap| {
let at = (tap + 1) as f64 - HALF as f64 - over;
2.0 * band * sinc(2.0 * band * at) * window(at)
})
.collect();
let whole: f64 = row.iter().sum();
rows.extend(row.iter().map(|weight| (weight / whole) as f32));
}
Self(rows)
}
fn weights(&self, over: f64) -> impl Iterator<Item = f32> {
let place = over * PHASES as f64;
let row = place as usize;
let between = (place - row as f64) as f32;
let (near, next) = (row * TAPS, (row + 1) * TAPS);
(0..TAPS).map(move |tap| {
let (near, next) = (self.0[near + tap], self.0[next + tap]);
near + (next - near) * between
})
}
}
fn sinc(at: f64) -> f64 {
let turn = core::f64::consts::PI * at;
match at == 0.0 {
true => 1.0,
false => turn.sin() / turn,
}
}
fn window(at: f64) -> f64 {
let over = (at + HALF as f64) / TAPS as f64;
let turn = core::f64::consts::TAU * over;
BLACKMAN_HARRIS[0] - BLACKMAN_HARRIS[1] * turn.cos() + BLACKMAN_HARRIS[2] * (2.0 * turn).cos()
- BLACKMAN_HARRIS[3] * (3.0 * turn).cos()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::sound::mixer::tests::tone;
const CLIP: SampleRate = SampleRate::new(48_000);
const DEVICE: SampleRate = SampleRate::new(44_100);
#[test]
fn a_clip_keeps_its_length_at_the_rate_the_mix_runs_at() {
let made = Resampler::whole(&tone(CLIP, 1_000.0, 48_000), CLIP, DEVICE, Channels::Mono);
assert_eq!(made.len(), 44_100, "a second of it either way");
assert_eq!(
Resampler::whole(&[0.25; 64], CLIP, CLIP, Channels::Stereo),
[0.25; 64],
"and one already at the mix's rate passes through as it is"
);
}
#[test]
fn frames_read_from_the_middle_are_the_ones_read_from_the_start() {
let clip = tone(CLIP, 1_000.0, 48_000);
let whole = Resampler::whole(&clip, CLIP, DEVICE, Channels::Mono);
let mut resampler = Resampler::new(CLIP, DEVICE, Channels::Mono);
let from = resampler.restart(ClipFrame::new(20_000)).get() as usize;
resampler.feed(&clip[from..]);
let mut made = Vec::new();
resampler.take(1_000, &mut made);
assert_eq!(made, whole[20_000..21_000]);
}
#[test]
fn a_resampled_tone_keeps_the_loudness_it_was_recorded_at() {
let made = Resampler::whole(&tone(CLIP, 1_000.0, 48_000), CLIP, DEVICE, Channels::Mono);
let loudest = made[1_000..43_000]
.iter()
.fold(0.0f32, |loudest, &sample| loudest.max(sample.abs()));
assert!(
(loudest - 0.5).abs() < 0.001,
"half of full scale: {loudest}"
);
}
}