use audioadapter::{Adapter, AdapterMut};
use std::fmt;
use crate::asynchro::FixedAsync;
use crate::error::{ResampleError, ResampleResult, ResamplerConstructionError};
use crate::{get_offsets, get_partial_len, update_mask, Indexing};
use crate::{validate_buffers, Adjustable, Resampler, Resizable, Sample};
const MAX_CROSSFADE_LEN: usize = 128;
pub struct Slip<T> {
nbr_channels: usize,
chunk_size: usize,
max_chunk_size: usize,
crossfade_len: usize,
max_correction: usize,
needed_input_size: usize,
needed_output_size: usize,
correction: i32,
drift_acc: f64,
resample_ratio: f64,
input_scratch: Vec<T>,
output_scratch: Vec<T>,
channel_mask: Vec<bool>,
fixed: FixedAsync,
}
impl<T> fmt::Debug for Slip<T> {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt.debug_struct("Slip")
.field("nbr_channels", &self.nbr_channels)
.field("chunk_size", &self.chunk_size)
.field("max_chunk_size", &self.max_chunk_size)
.field("crossfade_len", &self.crossfade_len)
.field("max_correction", &self.max_correction)
.field("needed_input_size", &self.needed_input_size)
.field("needed_output_size", &self.needed_output_size)
.field("correction", &self.correction)
.field("drift_acc", &self.drift_acc)
.field("resample_ratio", &self.resample_ratio)
.field("channel_mask", &self.channel_mask)
.field("fixed", &self.fixed)
.finish()
}
}
fn crossfade_len_for(chunk_size: usize) -> usize {
(chunk_size.saturating_sub(2) / 2).min(MAX_CROSSFADE_LEN)
}
fn max_corrections(chunk_size: usize, crossfade_len: usize) -> usize {
chunk_size.saturating_sub(1) / (crossfade_len + 2)
}
fn drift_per_chunk(ratio: f64, chunk_size: usize, fixed: FixedAsync) -> f64 {
match fixed {
FixedAsync::Input => (ratio - 1.0) * chunk_size as f64,
FixedAsync::Output => (1.0 - 1.0 / ratio) * chunk_size as f64,
}
}
fn ratio_range(max_correction: usize, chunk_size: usize, fixed: FixedAsync) -> (f64, f64) {
let f = max_correction as f64 / chunk_size as f64;
match fixed {
FixedAsync::Input => (1.0 - f, 1.0 + f),
FixedAsync::Output => (1.0 / (1.0 + f), 1.0 / (1.0 - f)),
}
}
fn place_correction<T: Sample>(
input: &[T],
output: &mut [T],
correction: i32,
crossfade_len: usize,
) {
let out_len = output.len();
let l = crossfade_len;
let n = correction.unsigned_abs() as usize;
if n == 0 {
output.copy_from_slice(&input[..out_len]);
return;
}
let step: isize = if correction > 0 { -1 } else { 1 };
let gap_total = out_len - n * l;
let base_gap = gap_total / (n + 1);
let extra = gap_total % (n + 1);
let mut offset: isize = 0;
let mut pos = 0;
for r in 0..n {
let gap = base_gap + if r < extra { 1 } else { 0 };
let src = (pos as isize + offset) as usize;
output[pos..pos + gap].copy_from_slice(&input[src..src + gap]);
pos += gap;
for j in 0..l {
let w = T::coerce((j as f64 + 0.5) / l as f64);
let i = (pos as isize + offset) as usize;
let a = input[i];
let b = input[(i as isize + step) as usize];
output[pos] = a + w * (b - a);
pos += 1;
}
offset += step;
}
let src = (pos as isize + offset) as usize;
output[pos..].copy_from_slice(&input[src..src + (out_len - pos)]);
}
impl<T> Slip<T>
where
T: Sample,
{
pub fn new(
chunk_size: usize,
nbr_channels: usize,
fixed: FixedAsync,
) -> Result<Self, ResamplerConstructionError> {
debug!(
"Create new Slip with fixed {:?}, chunk_size: {}, channels: {}",
fixed, chunk_size, nbr_channels,
);
let crossfade_len = crossfade_len_for(chunk_size);
if crossfade_len == 0 {
return Err(ResamplerConstructionError::InvalidChunkSize(chunk_size));
}
let max_correction = max_corrections(chunk_size, crossfade_len);
let scratch_len = chunk_size + max_correction;
let mut resampler = Slip {
nbr_channels,
chunk_size,
max_chunk_size: chunk_size,
crossfade_len,
max_correction,
needed_input_size: chunk_size,
needed_output_size: chunk_size,
correction: 0,
drift_acc: 0.0,
resample_ratio: 1.0,
input_scratch: vec![T::zero(); scratch_len],
output_scratch: vec![T::zero(); scratch_len],
channel_mask: vec![true; nbr_channels],
fixed,
};
resampler.replan();
Ok(resampler)
}
fn current_ratio_range(&self) -> (f64, f64) {
ratio_range(
max_corrections(self.chunk_size, self.crossfade_len),
self.chunk_size,
self.fixed,
)
}
fn replan(&mut self) {
let projected =
self.drift_acc + drift_per_chunk(self.resample_ratio, self.chunk_size, self.fixed);
let cap = max_corrections(self.chunk_size, self.crossfade_len) as i32;
self.correction = (projected.trunc() as i32).clamp(-cap, cap);
match self.fixed {
FixedAsync::Input => {
self.needed_input_size = self.chunk_size;
self.needed_output_size =
(self.chunk_size as i64 + self.correction as i64) as usize;
}
FixedAsync::Output => {
self.needed_output_size = self.chunk_size;
self.needed_input_size = (self.chunk_size as i64 - self.correction as i64) as usize;
}
}
}
}
impl<T> Resampler<T> for Slip<T>
where
T: Sample,
{
fn process_into_buffer(
&mut self,
buffer_in: &dyn Adapter<T>,
buffer_out: &mut dyn AdapterMut<T>,
indexing: Option<&Indexing>,
) -> ResampleResult<(usize, usize)> {
update_mask(&indexing, &mut self.channel_mask)?;
let (input_offset, output_offset) = get_offsets(&indexing);
let partial_input_len = get_partial_len(&indexing);
let frames_to_read = if let Some(frames) = partial_input_len {
frames.min(self.needed_input_size)
} else {
self.needed_input_size
};
trace!("Start processing, {:?}", self);
validate_buffers(
buffer_in,
buffer_out,
self.nbr_channels,
frames_to_read + input_offset,
self.needed_output_size + output_offset,
)?;
let input_len = self.needed_input_size;
let output_len = self.needed_output_size;
for (chan, active) in self.channel_mask.iter().enumerate() {
if !*active {
continue;
}
if self.correction == 0 {
buffer_in.copy_from_channel_to_slice(
chan,
input_offset,
&mut self.output_scratch[..frames_to_read],
);
if frames_to_read < output_len {
for value in self.output_scratch[frames_to_read..output_len].iter_mut() {
*value = T::zero();
}
}
} else {
buffer_in.copy_from_channel_to_slice(
chan,
input_offset,
&mut self.input_scratch[..frames_to_read],
);
if frames_to_read < input_len {
for value in self.input_scratch[frames_to_read..input_len].iter_mut() {
*value = T::zero();
}
}
place_correction(
&self.input_scratch[..input_len],
&mut self.output_scratch[..output_len],
self.correction,
self.crossfade_len,
);
}
buffer_out.copy_from_slice_to_channel(
chan,
output_offset,
&self.output_scratch[..output_len],
);
}
self.drift_acc += drift_per_chunk(self.resample_ratio, self.chunk_size, self.fixed);
self.drift_acc -= self.correction as f64;
self.replan();
trace!(
"Resampling channels {:?}, {} frames in, {} frames out",
self.channel_mask,
input_len,
output_len,
);
Ok((input_len, output_len))
}
fn output_frames_max(&self) -> usize {
match self.fixed {
FixedAsync::Input => self.max_chunk_size + self.max_correction,
FixedAsync::Output => self.max_chunk_size,
}
}
fn output_frames_next(&self) -> usize {
self.needed_output_size
}
fn output_delay(&self) -> usize {
0
}
fn nbr_channels(&self) -> usize {
self.nbr_channels
}
fn input_frames_max(&self) -> usize {
match self.fixed {
FixedAsync::Input => self.max_chunk_size,
FixedAsync::Output => self.max_chunk_size + self.max_correction,
}
}
fn input_frames_next(&self) -> usize {
self.needed_input_size
}
fn resample_ratio(&self) -> f64 {
self.resample_ratio
}
fn reset(&mut self) {
self.channel_mask.iter_mut().for_each(|val| *val = true);
self.drift_acc = 0.0;
self.resample_ratio = 1.0;
self.chunk_size = self.max_chunk_size;
self.crossfade_len = crossfade_len_for(self.max_chunk_size);
self.replan();
}
fn as_adjustable(&mut self) -> Option<&mut dyn Adjustable<T>> {
Some(self)
}
fn is_adjustable(&self) -> bool {
true
}
fn as_resizable(&mut self) -> Option<&mut dyn Resizable<T>> {
Some(self)
}
fn is_resizable(&self) -> bool {
true
}
}
impl<T> Adjustable<T> for Slip<T>
where
T: Sample,
{
fn set_resample_ratio(&mut self, new_ratio: f64, _ramp: bool) -> ResampleResult<()> {
trace!("Change resample ratio to {}", new_ratio);
let (min, max) = self.current_ratio_range();
let in_range = new_ratio >= min && new_ratio <= max;
self.resample_ratio = if new_ratio > max {
max
} else if new_ratio < min || new_ratio.is_nan() {
min
} else {
new_ratio
};
self.replan();
if in_range {
Ok(())
} else {
Err(ResampleError::RatioOutsideRange {
provided: new_ratio,
min,
max,
})
}
}
fn set_resample_ratio_relative(&mut self, rel_ratio: f64, ramp: bool) -> ResampleResult<()> {
self.set_resample_ratio(rel_ratio, ramp)
}
}
impl<T> Resizable<T> for Slip<T>
where
T: Sample,
{
fn set_chunk_size(&mut self, chunksize: usize) -> ResampleResult<()> {
let crossfade_len = crossfade_len_for(chunksize);
if chunksize > self.max_chunk_size || crossfade_len == 0 {
return Err(ResampleError::InvalidChunkSize {
max: self.max_chunk_size,
requested: chunksize,
});
}
self.chunk_size = chunksize;
self.crossfade_len = crossfade_len;
self.replan();
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::{crossfade_len_for, Slip, MAX_CROSSFADE_LEN};
use crate::tests::expected_output_value;
use crate::FixedAsync;
use crate::Indexing;
use crate::{check_input_offset, check_masked, check_output, check_output_offset, check_reset};
use crate::{Adjustable, Resampler, Resizable};
use audioadapter_buffers::direct::SequentialSliceOfVecs;
use test_case::test_matrix;
#[test]
fn crossfade_len_scales_with_chunk() {
assert_eq!(crossfade_len_for(4096), MAX_CROSSFADE_LEN);
assert_eq!(
crossfade_len_for(2 * MAX_CROSSFADE_LEN + 2),
MAX_CROSSFADE_LEN
);
assert!(crossfade_len_for(100) < MAX_CROSSFADE_LEN);
for &chunk in &[4usize, 5, 17, 50, 100, 257] {
let l = crossfade_len_for(chunk);
assert!(l >= 1 && 2 * l + 2 <= chunk, "chunk {} -> len {}", chunk, l);
}
assert_eq!(crossfade_len_for(3), 0);
}
#[test_log::test(test_matrix(
[50, 1024],
[1.0, 1.0005, 0.9995],
[FixedAsync::Input, FixedAsync::Output]
))]
fn drift_output(chunksize: usize, ratio: f64, fixed: FixedAsync) {
let mut resampler = Slip::<f64>::new(chunksize, 2, fixed).unwrap();
resampler.set_resample_ratio(ratio, false).unwrap();
check_output!(resampler, f64);
}
#[test_log::test(test_matrix(
[50, 1024],
[FixedAsync::Input, FixedAsync::Output]
))]
fn drift_reset(chunksize: usize, fixed: FixedAsync) {
let mut resampler = Slip::<f64>::new(chunksize, 2, fixed).unwrap();
check_reset!(resampler);
}
#[test_log::test(test_matrix(
[FixedAsync::Input, FixedAsync::Output]
))]
fn drift_masked(fixed: FixedAsync) {
let mut resampler = Slip::<f64>::new(1024, 2, fixed).unwrap();
check_masked!(resampler);
}
#[test_log::test(test_matrix(
[FixedAsync::Input, FixedAsync::Output]
))]
fn drift_input_offset(fixed: FixedAsync) {
let mut resampler = Slip::<f64>::new(1024, 2, fixed).unwrap();
check_input_offset!(resampler);
}
#[test_log::test(test_matrix(
[FixedAsync::Input, FixedAsync::Output]
))]
fn drift_output_offset(fixed: FixedAsync) {
let mut resampler = Slip::<f64>::new(1024, 2, fixed).unwrap();
check_output_offset!(resampler);
}
#[test]
fn rejects_short_chunk() {
assert!(Slip::<f64>::new(3, 1, FixedAsync::Input).is_err());
assert!(Slip::<f64>::new(4, 1, FixedAsync::Input).is_ok());
}
#[test]
fn unit_ratio_is_identity() {
let mut resampler = Slip::<f64>::new(64, 1, FixedAsync::Input).unwrap();
let input_data: Vec<Vec<f64>> = vec![(0..64).map(|i| (i as f64 * 0.3).sin()).collect()];
let input = SequentialSliceOfVecs::new(&input_data, 1, 64).unwrap();
let mut output_data = vec![vec![0.0; 65]; 1];
let mut output = SequentialSliceOfVecs::new_mut(&mut output_data, 1, 65).unwrap();
let (frames_in, frames_out) = resampler
.process_into_buffer(&input, &mut output, None)
.unwrap();
assert_eq!((frames_in, frames_out), (64, 64));
assert_eq!(&output_data[0][..64], &input_data[0][..]);
}
#[test]
fn dc_stays_flat_across_correction() {
let chunk = 64;
let mut resampler = Slip::<f64>::new(chunk, 1, FixedAsync::Input).unwrap();
resampler.set_resample_ratio(1.01, false).unwrap();
let mut corrected = false;
for _ in 0..10 {
let out_len = resampler.output_frames_next();
if out_len != chunk {
corrected = true;
}
let input_data = vec![vec![0.5f64; chunk]];
let input = SequentialSliceOfVecs::new(&input_data, 1, chunk).unwrap();
let mut output_data = vec![vec![0.0; out_len]; 1];
let mut output = SequentialSliceOfVecs::new_mut(&mut output_data, 1, out_len).unwrap();
resampler
.process_into_buffer(&input, &mut output, None)
.unwrap();
for &v in output_data[0].iter() {
assert!((v - 0.5).abs() < 1e-12, "DC not preserved: {}", v);
}
}
assert!(corrected, "expected at least one correction in the run");
}
#[test_log::test(test_matrix(
[1.005, 0.995],
[FixedAsync::Input, FixedAsync::Output]
))]
fn multiple_corrections_per_chunk(ratio: f64, fixed: FixedAsync) {
let chunk = 1024;
let mut resampler = Slip::<f64>::new(chunk, 1, fixed).unwrap();
resampler.set_resample_ratio(ratio, false).unwrap();
let mut total_in = 0;
let mut total_out = 0;
let mut max_delta = 0;
let mut ramp = 0.0f64;
for _ in 0..500 {
let frames_in = resampler.input_frames_next();
let frames_out = resampler.output_frames_next();
max_delta = max_delta.max((frames_in as isize - frames_out as isize).unsigned_abs());
let input_data: Vec<Vec<f64>> = vec![(0..frames_in).map(|i| ramp + i as f64).collect()];
ramp += frames_in as f64;
let input = SequentialSliceOfVecs::new(&input_data, 1, frames_in).unwrap();
let mut output_data = vec![vec![0.0; frames_out]; 1];
let mut output =
SequentialSliceOfVecs::new_mut(&mut output_data, 1, frames_out).unwrap();
let (got_in, got_out) = resampler
.process_into_buffer(&input, &mut output, None)
.unwrap();
total_in += got_in;
total_out += got_out;
for w in output_data[0].windows(2) {
assert!(
w[1] >= w[0] - 1e-9,
"ramp not monotonic: {} -> {}",
w[0],
w[1]
);
}
}
assert!(
max_delta > 1,
"expected more than one correction in a chunk, got max delta {}",
max_delta
);
let measured = total_out as f64 / total_in as f64;
assert!(
(measured - ratio).abs() < 1e-4,
"measured ratio {} too far from target {}",
measured,
ratio
);
}
#[test_log::test(test_matrix(
[1.0003, 0.9997],
[FixedAsync::Input, FixedAsync::Output]
))]
fn realized_ratio_tracks_target(ratio: f64, fixed: FixedAsync) {
let mut resampler = Slip::<f64>::new(1024, 2, fixed).unwrap();
resampler.set_resample_ratio(ratio, false).unwrap();
let mut total_in = 0;
let mut total_out = 0;
for _ in 0..2000 {
let frames_in = resampler.input_frames_next();
let frames_out = resampler.output_frames_next();
let input_data = vec![vec![0.0f64; frames_in]; 2];
let input = SequentialSliceOfVecs::new(&input_data, 2, frames_in).unwrap();
let mut output_data = vec![vec![0.0; frames_out]; 2];
let mut output =
SequentialSliceOfVecs::new_mut(&mut output_data, 2, frames_out).unwrap();
let (got_in, got_out) = resampler
.process_into_buffer(&input, &mut output, None)
.unwrap();
total_in += got_in;
total_out += got_out;
}
let measured = total_out as f64 / total_in as f64;
assert!(
(measured - ratio).abs() < 1e-4,
"measured ratio {} too far from target {}",
measured,
ratio
);
}
#[test_log::test(test_matrix(
[FixedAsync::Input, FixedAsync::Output]
))]
fn drift_resize(fixed: FixedAsync) {
let mut resampler = Slip::<f64>::new(1024, 2, fixed).unwrap();
resampler.set_resample_ratio(1.0005, false).unwrap();
resampler.set_chunk_size(600).unwrap();
check_output!(resampler, f64);
}
#[test]
fn set_ratio_respects_range() {
let mut resampler = Slip::<f64>::new(1024, 2, FixedAsync::Input).unwrap();
let (min, max) = resampler.current_ratio_range();
let in_range = 1.0 + (max - 1.0) * 0.5;
assert!(resampler.set_resample_ratio(in_range, false).is_ok());
assert_eq!(resampler.resample_ratio(), in_range);
assert!(resampler.set_resample_ratio(2.0, false).is_err());
assert_eq!(resampler.resample_ratio(), max);
assert!(resampler.set_resample_ratio(0.5, false).is_err());
assert_eq!(resampler.resample_ratio(), min);
assert!(resampler.set_resample_ratio(-1.0, false).is_err());
assert_eq!(resampler.resample_ratio(), min);
}
}