use rubato::audioadapter_buffers::direct::InterleavedSlice;
use rubato::{
Async, FixedAsync, Resampler, SincInterpolationParameters, SincInterpolationType,
WindowFunction, calculate_cutoff,
};
use crate::error::{Error, Result};
use crate::resample::math::gcd;
use crate::wav::decode::{WavData, to_mono};
pub const DEFAULT_TAPS_PER_PHASE: usize = 64;
const WINDOW: WindowFunction = WindowFunction::Blackman2;
const ROLLOFF: f32 = 0.95;
const OVERSAMPLING: usize = 128;
const CHUNK_FRAMES: usize = 1024;
pub const MAX_FILTER_TAPS: usize = 1 << 20;
const MAX_RATIO_TERM: u64 = 16_383;
fn bad(arg: &'static str, reason: String) -> Error {
Error::InvalidArgument { arg, reason }
}
fn output_len(in_len: usize, from_rate: u32, to_rate: u32) -> Result<usize> {
let scaled = (in_len as u64).checked_mul(to_rate as u64).ok_or_else(|| {
bad(
"samples",
format!("{in_len} samples at {to_rate} Hz overflows a u64 output length"),
)
})?;
let from = from_rate as u64;
let out = scaled.div_ceil(from);
usize::try_from(out).map_err(|_| {
bad(
"samples",
format!("output length {out} does not fit in a usize"),
)
})
}
fn check_ratio(from_rate: u32, to_rate: u32) -> Result<()> {
let divisor = gcd(u64::from(from_rate), u64::from(to_rate)).max(1);
let up = u64::from(to_rate) / divisor;
let down = u64::from(from_rate) / divisor;
if up.max(down) > MAX_RATIO_TERM {
return Err(bad(
"from_rate",
format!(
"resampling {from_rate} Hz to {to_rate} Hz reduces to {up}/{down}, a ratio \
with a term above {MAX_RATIO_TERM}; real rates share a larger common \
factor, so check both rates for a typo"
),
));
}
Ok(())
}
fn sinc_len(taps_per_phase: usize, from_rate: u32, to_rate: u32) -> Result<usize> {
let decimation = (from_rate as f64 / to_rate as f64).max(1.0);
let len = (2.0 * taps_per_phase as f64 * decimation).ceil();
if len * OVERSAMPLING as f64 > MAX_FILTER_TAPS as f64 {
return Err(bad(
"taps_per_phase",
format!(
"{taps_per_phase} taps per phase at {from_rate} Hz to {to_rate} Hz needs a \
{len}-sample sinc, over the {MAX_FILTER_TAPS}-tap table limit at \
{OVERSAMPLING}x oversampling; lower taps_per_phase"
),
));
}
Ok(len as usize)
}
fn best_lead(early: f64, from_rate: u32, to_rate: u32) -> usize {
let ratio = to_rate as f64 / from_rate as f64;
let period =
(u64::from(from_rate) / gcd(u64::from(from_rate), u64::from(to_rate)).max(1)) as usize;
let first = (early.max(0.0) / ratio).ceil() as usize + 1;
let miss = |lead: usize| {
let x = lead as f64 * ratio - early;
(x - x.round()).abs()
};
(first..first + period.max(1))
.min_by(|&a, &b| miss(a).total_cmp(&miss(b)))
.unwrap_or(first)
}
pub fn resample(samples: &[f32], from_rate: u32, to_rate: u32) -> Result<Vec<f32>> {
resample_with_taps(samples, from_rate, to_rate, DEFAULT_TAPS_PER_PHASE)
}
pub fn resample_with_taps(
samples: &[f32],
from_rate: u32,
to_rate: u32,
taps_per_phase: usize,
) -> Result<Vec<f32>> {
if from_rate == 0 {
return Err(bad("from_rate", "source sample rate is 0 Hz".to_string()));
}
if to_rate == 0 {
return Err(bad("to_rate", "target sample rate is 0 Hz".to_string()));
}
if taps_per_phase == 0 {
return Err(bad(
"taps_per_phase",
"filter length is 0 taps per phase".to_string(),
));
}
if from_rate == to_rate {
return Ok(samples.to_vec());
}
let out_len = output_len(samples.len(), from_rate, to_rate)?;
if samples.is_empty() || out_len == 0 {
return Ok(Vec::new());
}
check_ratio(from_rate, to_rate)?;
let sinc_len = sinc_len(taps_per_phase, from_rate, to_rate)?;
let failed = |what: &str, err: &dyn std::fmt::Display| Error::DataError {
reason: format!(
"rubato failed to {what} for {} samples at {from_rate} Hz to {to_rate} Hz \
with a {sinc_len}-sample sinc: {err}; the inputs passed every check, so \
report this as a voxr bug",
samples.len()
),
};
let params = SincInterpolationParameters::new(sinc_len, WINDOW)
.f_cutoff(ROLLOFF.min(calculate_cutoff(sinc_len, WINDOW)))
.oversampling_factor(OVERSAMPLING)
.interpolation(SincInterpolationType::Cubic);
let ratio = to_rate as f64 / from_rate as f64;
let mut resampler =
Async::<f32>::new_sinc(ratio, 1.0, ¶ms, CHUNK_FRAMES, 1, FixedAsync::Input)
.map_err(|e| failed("build the resampler", &e))?;
let points = sinc_len.next_multiple_of(8) as f64;
let delay = points * ratio / 2.0 - 1.0 - ratio / OVERSAMPLING as f64;
let early = resampler.output_delay() as f64 - delay;
let lead = best_lead(early, from_rate, to_rate);
let skip = (lead as f64 * ratio - early).round().max(0.0) as usize;
let tail = (2.0 / ratio).ceil() as usize + 1;
let mut padded = vec![0.0f32; lead];
padded.extend_from_slice(samples);
padded.resize(lead + samples.len() + tail, 0.0);
let input = InterleavedSlice::new(&padded, 1, padded.len())
.map_err(|e| failed("wrap the input", &e))?;
let mut out = resampler
.process_all(&input, padded.len(), None)
.map_err(|e| failed("resample", &e))?
.take_data();
out.drain(..skip.min(out.len()));
out.resize(out_len, 0.0);
Ok(out)
}
pub fn to_mono_at_rate(data: &WavData, target_rate: u32) -> Result<Vec<f32>> {
let mono = to_mono(&data.samples, data.channels)?;
if data.sample_rate == target_rate {
return Ok(mono);
}
resample(&mono, data.sample_rate, target_rate)
}