phonic 0.16.0

Audio playback library
Documentation
use assume::assume;

use super::{AudioResampler, ResamplingSpecs};
use crate::Error;

// -------------------------------------------------------------------------------------------------

/// Interpolate a single channel of interleaved audio with cubic interpolation.
#[derive(Clone)]
struct CubicInterpolator {
    input: [f32; 4],
    sub_pos: f32,
    ratio: f32,
    is_initialized: bool,
}

impl CubicInterpolator {
    pub fn new(ratio: f32) -> Self {
        let input = [0.0, 0.0, 0.0, 0.0];
        let sub_pos = 0.0;
        let is_initialized = false;
        Self {
            input,
            sub_pos,
            ratio,
            is_initialized,
        }
    }

    pub fn reset(&mut self) {
        self.input.fill(0.0);
        self.sub_pos = 0.0;
        self.is_initialized = false;
    }

    pub fn process(
        &mut self,
        input: &[f32],
        output: &mut [f32],
        channel_index: usize,
        channel_count: usize,
    ) -> (usize, usize) {
        debug_assert!(input.len().is_multiple_of(channel_count));
        debug_assert!(output.len().is_multiple_of(channel_count));
        debug_assert!(channel_index < channel_count);

        let num_in = input.len() / channel_count;
        let num_out = output.len() / channel_count;

        let mut num_consumed = 0;
        let mut num_produced = 0;

        if (self.ratio - 1.0).abs() < 0.000001 {
            // Bypass conversion in case the sample rates are equal.
            let min = input.len().min(output.len());
            output[..min].copy_from_slice(&input[..min]);
            return (min, min);
        }

        // Preload buffer
        if !self.is_initialized && num_in >= 3 {
            self.is_initialized = true;
            for f in 0..3 {
                let index = f * channel_count + channel_index;
                assume!(unsafe: index < input.len()); // eliminate bound checks
                self.push_sample(input[index]);
                num_consumed += 1;
            }
        }

        // Downsample
        if self.ratio < 1.0 {
            while num_produced < num_out {
                if self.sub_pos >= 1.0 {
                    if num_consumed >= num_in {
                        break;
                    }
                    let input_index = num_consumed * channel_count + channel_index;
                    assume!(unsafe: input_index < input.len()); // eliminate bound checks
                    self.push_sample(input[input_index]);
                    num_consumed += 1;
                    self.sub_pos -= 1.0;
                }

                let output_index = num_produced * channel_count + channel_index;
                assume!(unsafe: output_index < output.len()); // eliminate bound checks
                output[output_index] = self.interpolate(self.sub_pos);
                num_produced += 1;
                self.sub_pos += self.ratio;
            }
        } else {
            // Upsample
            'outer_loop: while num_produced < num_out {
                while self.sub_pos < self.ratio {
                    if num_consumed >= num_in {
                        break 'outer_loop;
                    }
                    let input_index = num_consumed * channel_count + channel_index;
                    assume!(unsafe: input_index < input.len()); // eliminate bound checks
                    self.push_sample(input[input_index]);
                    num_consumed += 1;
                    self.sub_pos += 1.0;
                }

                self.sub_pos -= self.ratio;
                let output_index = num_produced * channel_count + channel_index;
                assume!(unsafe: output_index < output.len()); // eliminate bound checks
                output[output_index] = self.interpolate(1.0 - self.sub_pos);
                num_produced += 1;
            }
        }

        (num_consumed * channel_count, num_produced * channel_count)
    }

    #[inline(always)]
    fn push_sample(&mut self, new_value: f32) {
        self.input[3] = self.input[2];
        self.input[2] = self.input[1];
        self.input[1] = self.input[0];
        self.input[0] = new_value;
    }

    #[inline(always)]
    fn interpolate(&self, fraction: f32) -> f32 {
        debug_assert!((0.0..=1.0).contains(&fraction));

        // Given a previous frame, a current frame, the two next frames, and a fraction from
        // 0.0 to 1.0 between the current frame and next frame, get an approximated frame.
        // This is the 4-point, 3rd-order Hermite interpolation x-form algorithm from "Polynomial
        // Interpolators for High-Quality Resampling of Oversampled Audio" by Olli Niemitalo, p. 43:
        // http://yehar.com/blog/wp-content/uploads/2009/08/deip.pdf
        let ym1 = self.input[3];
        let y0 = self.input[2];
        let y1 = self.input[1];
        let y2 = self.input[0];
        let c0 = y0;
        let c1 = (y1 - ym1) * 0.5;
        let c2 = ym1 - y0 * 2.5 + y1 * 2.0 - y2 * 0.5;
        let c3 = (y2 - ym1) * 0.5 + (y0 - y1) * 1.5;
        ((c3 * fraction + c2) * fraction + c1) * fraction + c0
    }
}

// -------------------------------------------------------------------------------------------------

/// Simple cubic interpolater without bandlimiting. Designed to sound good while being fast and
/// not necessarily as HQ as possible. Suitable for samplers which are playing loads of samples at
/// the same time.
pub struct CubicResampler {
    spec: ResamplingSpecs,
    interpolators: Vec<CubicInterpolator>,
}

impl CubicResampler {
    pub fn new(spec: ResamplingSpecs) -> Result<Self, Error> {
        assert!(
            spec.output_ratio() > 0.0 && spec.output_ratio().is_finite(),
            "Invalid resampling ratio",
        );
        Ok(Self {
            spec,
            interpolators: vec![
                CubicInterpolator::new(spec.input_ratio() as f32);
                spec.channel_count
            ],
        })
    }
}

impl AudioResampler for CubicResampler {
    fn required_input_buffer_size(&self) -> Option<usize> {
        None
    }
    fn max_input_buffer_size(&self) -> Option<usize> {
        None
    }

    fn process(&mut self, input: &[f32], output: &mut [f32]) -> Result<(usize, usize), Error> {
        let channel_count = self.spec.channel_count;
        let mut result = (0, 0);
        for (channel_index, interpolator) in self.interpolators.iter_mut().enumerate() {
            result = interpolator.process(input, output, channel_index, channel_count);
        }
        Ok(result)
    }

    fn update(&mut self, input_rate: u32, output_rate: u32) -> Result<(), Error> {
        self.spec.input_rate = input_rate;
        self.spec.output_rate = output_rate;
        let new_ratio = self.spec.input_ratio() as f32;
        assert!(
            new_ratio > 0.0 && new_ratio.is_finite(),
            "Invalid resampling ratio",
        );
        for interpolator in self.interpolators.iter_mut() {
            interpolator.ratio = new_ratio;
        }
        Ok(())
    }

    fn reset(&mut self) {
        for interpolator in self.interpolators.iter_mut() {
            interpolator.reset();
        }
    }
}