use crate::asynchro::InnerResampler;
use crate::interpolation::*;
#[cfg(target_arch = "x86_64")]
use crate::sinc_interpolator::sinc_interpolator_avx::AvxInterpolator;
#[cfg(target_arch = "aarch64")]
use crate::sinc_interpolator::sinc_interpolator_neon::NeonInterpolator;
#[cfg(target_arch = "x86_64")]
use crate::sinc_interpolator::sinc_interpolator_sse::SseInterpolator;
use crate::sinc_interpolator::{
AlignedBuf, AnyInterpolator, AvxSample, NeonSample, ScalarInterpolator, SincInterpolator,
SseSample,
};
use crate::windows::WindowFunction;
use crate::Sample;
use audioadapter::AdapterMut;
macro_rules! t {
($expression:expr) => {
T::coerce($expression)
};
}
#[derive(Debug, Clone, Copy)]
pub struct SincInterpolationParameters {
pub sinc_len: usize,
pub f_cutoff: f32,
pub oversampling_factor: usize,
pub interpolation: SincInterpolationType,
pub window: WindowFunction,
}
#[derive(Debug, Clone, Copy)]
pub enum SincInterpolationType {
Cubic,
Quadratic,
Linear,
Nearest,
}
pub fn make_interpolator<T>(
sinc_len: usize,
resample_ratio: f64,
f_cutoff: f32,
oversampling_factor: usize,
window: WindowFunction,
) -> AnyInterpolator<T>
where
T: AvxSample + SseSample + NeonSample + Sample,
{
let sinc_len = 8 * (((sinc_len as f32) / 8.0).ceil() as usize);
let f_cutoff = if resample_ratio >= 1.0 {
f_cutoff
} else {
f_cutoff * resample_ratio as f32
};
#[cfg(target_arch = "x86_64")]
if let Ok(interpolator) =
AvxInterpolator::<T>::new(sinc_len, oversampling_factor, f_cutoff, window)
{
return AnyInterpolator::Avx(interpolator);
}
#[cfg(target_arch = "x86_64")]
if let Ok(interpolator) =
SseInterpolator::<T>::new(sinc_len, oversampling_factor, f_cutoff, window)
{
return AnyInterpolator::Sse(interpolator);
}
#[cfg(target_arch = "aarch64")]
if let Ok(interpolator) =
NeonInterpolator::<T>::new(sinc_len, oversampling_factor, f_cutoff, window)
{
return AnyInterpolator::Neon(interpolator);
}
AnyInterpolator::Scalar(ScalarInterpolator::<T>::new(
sinc_len,
oversampling_factor,
f_cutoff,
window,
))
}
#[inline]
pub fn interp_cubic<T>(x: T, yvals: &[T; 4]) -> T
where
T: Sample,
{
let a0 = yvals[1];
let a1 = -t!(1.0 / 3.0) * yvals[0] - t!(0.5) * yvals[1] + yvals[2] - t!(1.0 / 6.0) * yvals[3];
let a2 = t!(0.5) * (yvals[0] + yvals[2]) - yvals[1];
let a3 = t!(0.5) * (yvals[1] - yvals[2]) + t!(1.0 / 6.0) * (yvals[3] - yvals[0]);
let x2 = x * x;
let x3 = x2 * x;
a0 + a1 * x + a2 * x2 + a3 * x3
}
#[inline]
pub fn interp_cubic_weights<T>(x: T) -> [T; 4]
where
T: Sample,
{
let x2 = x * x;
let x3 = x2 * x;
[
t!(-1.0 / 3.0) * x + t!(0.5) * x2 - t!(1.0 / 6.0) * x3,
t!(1.0) - t!(0.5) * x - x2 + t!(0.5) * x3,
x + t!(0.5) * x2 - t!(0.5) * x3,
-t!(1.0 / 6.0) * x + t!(1.0 / 6.0) * x3,
]
}
#[inline]
pub fn interp_quad<T>(x: T, yvals: &[T; 3]) -> T
where
T: Sample,
{
let a2 = yvals[0] - t!(2.0) * yvals[1] + yvals[2];
let a1 = -t!(3.0) * yvals[0] + t!(4.0) * yvals[1] - yvals[2];
let a0 = t!(2.0) * yvals[0];
let x2 = x * x;
t!(0.5) * (a0 + a1 * x + a2 * x2)
}
#[inline]
pub fn interp_quad_weights<T>(x: T) -> [T; 3]
where
T: Sample,
{
let x2 = x * x;
[
t!(0.5) * (t!(2.0) - t!(3.0) * x + x2),
t!(0.5) * (t!(4.0) * x - t!(2.0) * x2),
t!(0.5) * (x2 - x),
]
}
#[inline]
pub fn interp_lin<T>(x: T, yvals: &[T; 2]) -> T
where
T: Sample,
{
yvals[0] + x * (yvals[1] - yvals[0])
}
#[inline]
pub fn interp_lin_weights<T>(x: T) -> [T; 2]
where
T: Sample,
{
[t!(1.0) - x, x]
}
pub(crate) struct InnerSinc<T>
where
T: AvxSample + SseSample + NeonSample + Sample,
{
pub interpolator: AnyInterpolator<T>,
pub interpolation: SincInterpolationType,
combined: AlignedBuf<T>,
}
impl<T> InnerSinc<T>
where
T: AvxSample + SseSample + NeonSample + Sample,
{
pub(crate) fn new(
interpolator: AnyInterpolator<T>,
interpolation: SincInterpolationType,
) -> Self {
let len = interpolator.nbr_points() + 1;
Self {
interpolator,
interpolation,
combined: AlignedBuf::zeroed(len),
}
}
#[inline(always)]
#[allow(clippy::too_many_arguments)]
fn process_combined_frame(
&mut self,
nearest: &[(isize, isize)],
weights: &[T],
interpolator_len: usize,
channel_mask: &[bool],
wave_in: &[Vec<T>],
wave_out: &mut dyn AdapterMut<'_, T>,
frame: usize,
output_offset: usize,
) {
for n in nearest {
self.interpolator.prefetch_sinc(n.1 as usize);
}
let min_idx = self
.interpolator
.make_combined_sinc(nearest, weights, &mut self.combined);
let base = (min_idx + 2 * interpolator_len as isize) as usize;
for (chan, active) in channel_mask.iter().enumerate() {
if *active {
let buf = &wave_in[chan];
let result = self.interpolator.get_sinc_dot_product(
buf,
base,
&self.combined[..interpolator_len],
) + self.combined[interpolator_len] * buf[base + interpolator_len];
wave_out.write_sample(chan, frame + output_offset, &result);
}
}
}
#[inline(always)]
#[allow(clippy::too_many_arguments)]
fn process_direct_frame(
&self,
nearest: &[(isize, isize)],
interpolator_len: usize,
channel_mask: &[bool],
wave_in: &[Vec<T>],
wave_out: &mut dyn AdapterMut<'_, T>,
frame: usize,
output_offset: usize,
interp: impl Fn(&[T]) -> T,
) {
let n = nearest.len();
let mut points = [T::zero(); 4];
for (chan, active) in channel_mask.iter().enumerate() {
if *active {
let buf = &wave_in[chan];
for (ni, p) in nearest.iter().zip(points[..n].iter_mut()) {
*p = self.interpolator.get_sinc_interpolated(
buf,
(ni.0 + 2 * interpolator_len as isize) as usize,
ni.1 as usize,
);
}
wave_out.write_sample(chan, frame + output_offset, &interp(&points[..n]));
}
}
}
}
impl<T> InnerResampler<T> for InnerSinc<T>
where
T: AvxSample + SseSample + NeonSample + Sample,
{
fn process(
&mut self,
idx: f64,
nbr_frames: usize,
channel_mask: &[bool],
t_ratio: f64,
t_ratio_increment: f64,
wave_in: &[Vec<T>],
wave_out: &mut dyn AdapterMut<'_, T>,
output_offset: usize,
) -> f64 {
let mut t_ratio = t_ratio;
let mut idx = idx;
let interpolator_len = self.interpolator.nbr_points();
let oversampling_factor = self.interpolator.nbr_sincs();
let active_count = channel_mask.iter().filter(|&&a| a).count();
match self.interpolation {
SincInterpolationType::Cubic => {
let use_combined = active_count >= 2;
let mut nearest = [(0isize, 0isize); 4];
for frame in 0..nbr_frames {
t_ratio += t_ratio_increment;
idx += t_ratio;
get_nearest_times_4(idx, oversampling_factor as isize, &mut nearest);
let frac_offset = t!(idx * oversampling_factor as f64
- (idx * oversampling_factor as f64).floor());
if use_combined {
let weights = interp_cubic_weights(frac_offset);
self.process_combined_frame(
&nearest,
&weights,
interpolator_len,
channel_mask,
wave_in,
wave_out,
frame,
output_offset,
);
} else {
self.process_direct_frame(
&nearest,
interpolator_len,
channel_mask,
wave_in,
wave_out,
frame,
output_offset,
|pts| interp_cubic(frac_offset, &[pts[0], pts[1], pts[2], pts[3]]),
);
}
}
}
SincInterpolationType::Quadratic => {
let use_combined = active_count > 2;
let mut nearest = [(0isize, 0isize); 3];
for frame in 0..nbr_frames {
t_ratio += t_ratio_increment;
idx += t_ratio;
get_nearest_times_3(idx, oversampling_factor as isize, &mut nearest);
let frac_offset = t!(idx * oversampling_factor as f64
- (idx * oversampling_factor as f64).floor());
if use_combined {
let weights = interp_quad_weights(frac_offset);
self.process_combined_frame(
&nearest,
&weights,
interpolator_len,
channel_mask,
wave_in,
wave_out,
frame,
output_offset,
);
} else {
self.process_direct_frame(
&nearest,
interpolator_len,
channel_mask,
wave_in,
wave_out,
frame,
output_offset,
|pts| interp_quad(frac_offset, &[pts[0], pts[1], pts[2]]),
);
}
}
}
SincInterpolationType::Linear => {
let use_combined = active_count > 2;
let mut nearest = [(0isize, 0isize); 2];
for frame in 0..nbr_frames {
t_ratio += t_ratio_increment;
idx += t_ratio;
get_nearest_times_2(idx, oversampling_factor as isize, &mut nearest);
let frac_offset = t!(idx * oversampling_factor as f64
- (idx * oversampling_factor as f64).floor());
if use_combined {
let weights = interp_lin_weights(frac_offset);
self.process_combined_frame(
&nearest,
&weights,
interpolator_len,
channel_mask,
wave_in,
wave_out,
frame,
output_offset,
);
} else {
self.process_direct_frame(
&nearest,
interpolator_len,
channel_mask,
wave_in,
wave_out,
frame,
output_offset,
|pts| interp_lin(frac_offset, &[pts[0], pts[1]]),
);
}
}
}
SincInterpolationType::Nearest => {
let oversampling_factor = self.interpolator.nbr_sincs();
let mut point;
let mut nearest;
for frame in 0..nbr_frames {
t_ratio += t_ratio_increment;
idx += t_ratio;
nearest = get_nearest_time(idx, oversampling_factor as isize);
for (chan, active) in channel_mask.iter().enumerate() {
if *active {
let buf = &wave_in[chan];
point = self.interpolator.get_sinc_interpolated(
buf,
(nearest.0 + 2 * interpolator_len as isize) as usize,
nearest.1 as usize,
);
wave_out.write_sample(chan, frame + output_offset, &point);
}
}
}
}
}
idx
}
fn nbr_points(&self) -> usize {
self.interpolator.nbr_points()
}
fn init_last_index(&self) -> f64 {
-(self.interpolator.nbr_points() as f64 - 1.0)
}
}
#[cfg(test)]
mod tests {
use super::{
interp_cubic, interp_cubic_weights, interp_lin, interp_lin_weights, interp_quad,
interp_quad_weights,
};
#[test]
fn int_cubic() {
let yvals = [0.0f64, 2.0f64, 4.0f64, 6.0f64];
let interp = interp_cubic(0.5f64, &yvals);
assert_eq!(interp, 3.0f64);
}
#[test]
fn int_lin_32() {
let yvals = [1.0f32, 5.0f32];
let interp = interp_lin(0.25f32, &yvals);
assert_eq!(interp, 2.0f32);
}
#[test]
fn int_cubic_32() {
let yvals = [0.0f32, 2.0f32, 4.0f32, 6.0f32];
let interp = interp_cubic(0.5f32, &yvals);
assert_eq!(interp, 3.0f32);
}
#[test]
fn cubic_weights_match_cubic() {
let yvals = [1.3f64, -0.7f64, 2.1f64, 0.4f64];
for x_int in 0..=10 {
let x = x_int as f64 / 10.0;
let direct = interp_cubic(x, &yvals);
let w = interp_cubic_weights(x);
let blended = w[0] * yvals[0] + w[1] * yvals[1] + w[2] * yvals[2] + w[3] * yvals[3];
assert!((direct - blended).abs() < 1.0e-12, "mismatch at x={x}");
}
}
#[test]
fn quad_weights_match_quad() {
let yvals = [1.3f64, -0.7f64, 2.1f64];
for x_int in 0..=10 {
let x = x_int as f64 / 10.0;
let direct = interp_quad(x, &yvals);
let w = interp_quad_weights(x);
let blended = w[0] * yvals[0] + w[1] * yvals[1] + w[2] * yvals[2];
assert!((direct - blended).abs() < 1.0e-12, "mismatch at x={x}");
}
}
#[test]
fn lin_weights_match_lin() {
let yvals = [1.3f64, -0.7f64];
for x_int in 0..=10 {
let x = x_int as f64 / 10.0;
let direct = interp_lin(x, &yvals);
let w = interp_lin_weights(x);
let blended = w[0] * yvals[0] + w[1] * yvals[1];
assert!((direct - blended).abs() < 1.0e-12, "mismatch at x={x}");
}
}
}