simple_src 0.4.0

A simple sample rate conversion lib for audio.
Documentation
//! Linear interpolation converter
//!
//! ```
//! use simple_src::{linear, Convert};
//!
//! let samples = vec![1.0, 2.0, 3.0, 4.0];
//! let manager = linear::Manager::new(2.0).unwrap();
//! let mut converter = manager.converter();
//! for s in converter.process(samples.into_iter()) {
//!     println!("{s}");
//! }
//! ```

use super::{Convert, ConvertMode, Ratio, Rational, Result, convert_with, output_len};

enum State {
    First,
    Normal,
    Suspend,
}

pub(crate) struct FloatConverter {
    state: State,
    last_in: [f64; 2],
    step: f64,
    pos: f64,
}

pub(crate) struct RationalConverter {
    state: State,
    last_in: [f64; 2],
    numer: usize,
    denom: usize,
    pos: usize,
    recip: f64,
}

pub(crate) struct RationalFastConverter {
    state: State,
    last_in: [f64; 2],
    numer: usize,
    denom: usize,
    pos: usize,
    coef: Vec<f64>,
}

enum ConverterKind {
    Float(FloatConverter),
    Rational(RationalConverter),
    RationalFast(RationalFastConverter),
}

/// Opaque sample-rate converter created by [`Manager::converter`].
pub struct Converter {
    inner: ConverterKind,
}

impl FloatConverter {
    fn new(step: f64) -> Self {
        Self {
            step,
            pos: 0.0,
            last_in: [0.0; 2],
            state: State::First,
        }
    }
}

impl Convert for FloatConverter {
    fn next_sample<I>(&mut self, iter: &mut I) -> Option<f64>
    where
        I: Iterator<Item = f64>,
        Self: Sized,
    {
        loop {
            match self.state {
                State::First => {
                    let s = iter.next()?;
                    self.last_in[1] = s;
                    self.pos = 1.0;
                    self.state = State::Normal;
                }
                State::Normal => {
                    while self.pos >= 1.0 {
                        self.pos -= 1.0;
                        self.last_in[0] = self.last_in[1];
                        if let Some(s) = iter.next() {
                            self.last_in[1] = s;
                        } else {
                            self.state = State::Suspend;
                            return None;
                        }
                    }
                    let coef = self.pos;
                    let interp = self.last_in[0] + (self.last_in[1] - self.last_in[0]) * coef;
                    self.pos += self.step;
                    return Some(interp);
                }
                State::Suspend => {
                    let s = iter.next()?;
                    self.last_in[1] = s;
                    self.state = State::Normal;
                }
            }
        }
    }
}

impl RationalConverter {
    fn new(step: Rational) -> Self {
        let numer = *step.numer() as usize;
        let denom = *step.denom() as usize;
        Self {
            state: State::First,
            last_in: [0.0; 2],
            numer,
            denom,
            pos: 0,
            recip: (denom as f64).recip(),
        }
    }
}

impl Convert for RationalConverter {
    fn next_sample<I>(&mut self, iter: &mut I) -> Option<f64>
    where
        I: Iterator<Item = f64>,
    {
        loop {
            match self.state {
                State::First => {
                    let s = iter.next()?;
                    self.last_in[1] = s;
                    self.pos = self.numer;
                    self.state = State::Normal;
                }
                State::Normal => {
                    while self.pos >= self.denom {
                        self.pos -= self.denom;
                        self.last_in[0] = self.last_in[1];
                        if let Some(s) = iter.next() {
                            self.last_in[1] = s;
                        } else {
                            self.state = State::Suspend;
                            return None;
                        }
                    }
                    let coef = self.pos as f64 * self.recip;
                    let interp = self.last_in[0] + (self.last_in[1] - self.last_in[0]) * coef;
                    self.pos += self.numer;
                    return Some(interp);
                }
                State::Suspend => {
                    let s = iter.next()?;
                    self.last_in[1] = s;
                    self.state = State::Normal;
                }
            }
        }
    }
}

impl RationalFastConverter {
    fn new(step: Rational) -> Self {
        let numer = *step.numer() as usize;
        let denom = *step.denom() as usize;
        let coef = (0..denom).map(|i| i as f64 / denom as f64).collect();
        Self {
            numer,
            denom,
            pos: 0,
            coef,
            last_in: [0.0; 2],
            state: State::First,
        }
    }
}

impl Convert for RationalFastConverter {
    fn next_sample<I>(&mut self, iter: &mut I) -> Option<f64>
    where
        I: Iterator<Item = f64>,
    {
        loop {
            match self.state {
                State::First => {
                    let s = iter.next()?;
                    self.last_in[1] = s;
                    self.pos = self.numer;
                    self.state = State::Normal;
                }
                State::Normal => {
                    while self.pos >= self.denom {
                        self.pos -= self.denom;
                        self.last_in[0] = self.last_in[1];
                        if let Some(s) = iter.next() {
                            self.last_in[1] = s;
                        } else {
                            self.state = State::Suspend;
                            return None;
                        }
                    }
                    let coef = self.coef[self.pos];
                    let interp = self.last_in[0] + (self.last_in[1] - self.last_in[0]) * coef;
                    self.pos += self.numer;
                    return Some(interp);
                }
                State::Suspend => {
                    let s = iter.next()?;
                    self.last_in[1] = s;
                    self.state = State::Normal;
                }
            }
        }
    }
}

impl Converter {
    fn delay_empty(&self) -> bool {
        match &self.inner {
            ConverterKind::Float(c) => {
                matches!(c.state, State::First) || (c.last_in[0] == 0.0 && c.last_in[1] == 0.0)
            }
            ConverterKind::Rational(c) => {
                matches!(c.state, State::First) || (c.last_in[0] == 0.0 && c.last_in[1] == 0.0)
            }
            ConverterKind::RationalFast(c) => {
                matches!(c.state, State::First) || (c.last_in[0] == 0.0 && c.last_in[1] == 0.0)
            }
        }
    }

    fn new(ratio: Ratio) -> Self {
        let inner = match ratio {
            Ratio::Float(ratio) => ConverterKind::Float(FloatConverter::new(ratio.recip())),
            Ratio::Rational(ratio) => {
                if *ratio.numer() <= 16384 {
                    ConverterKind::RationalFast(RationalFastConverter::new(ratio.recip()))
                } else {
                    ConverterKind::Rational(RationalConverter::new(ratio.recip()))
                }
            }
        };
        Self { inner }
    }
}

impl Convert for Converter {
    #[inline]
    fn next_sample<I>(&mut self, iter: &mut I) -> Option<f64>
    where
        I: Iterator<Item = f64>,
        Self: Sized,
    {
        match &mut self.inner {
            ConverterKind::Float(converter) => converter.next_sample(iter),
            ConverterKind::Rational(converter) => converter.next_sample(iter),
            ConverterKind::RationalFast(converter) => converter.next_sample(iter),
        }
    }

    fn flush(&mut self, output: &mut [f64]) -> usize
    where
        Self: Sized,
    {
        // Overrides Convert::flush: stop when the two-sample delay is empty.
        // Still call until 0 if `output` fills first.
        if self.delay_empty() {
            return 0;
        }
        let mut zeros = std::iter::repeat(0.0);
        let mut produced = 0;
        while produced < output.len() {
            match self.next_sample(&mut zeros) {
                Some(sample) => {
                    output[produced] = sample;
                    produced += 1;
                    if self.delay_empty() {
                        break;
                    }
                }
                None => break,
            }
        }
        produced
    }
}

#[derive(Clone, Copy)]
pub struct Manager {
    ratio: Ratio,
}

impl Manager {
    #[inline]
    pub fn new(ratio: f64) -> Result<Self> {
        let ratio = Ratio::try_from_float(ratio)?;
        Ok(Self { ratio })
    }

    #[inline]
    pub fn with_sample_rate(old_sr: u32, new_sr: u32) -> Result<Self> {
        let ratio = Ratio::try_from_integers(new_sr, old_sr)?;
        Ok(Self { ratio })
    }

    #[inline]
    pub fn converter(&self) -> Converter {
        Converter::new(self.ratio)
    }

    #[inline]
    pub fn ratio(&self) -> f64 {
        self.ratio.as_float()
    }

    /// Reduced integer ratio when a bounded float approximation (or integer
    /// sample rates) selected a rational; `None` for float phase. See
    /// [`ConvertMode`].
    #[inline]
    pub fn ratio_parts(&self) -> Option<(i64, i64)> {
        self.ratio.parts()
    }

    /// Which interpolation path this manager will construct. See [`ConvertMode`].
    #[inline]
    pub fn mode(&self) -> ConvertMode {
        self.ratio.linear_mode()
    }

    #[inline]
    pub fn latency(&self) -> usize {
        0
    }

    #[inline]
    pub fn output_len(&self, input_len: usize) -> usize {
        output_len(self.ratio(), input_len)
    }

    /// Convert a complete buffer, padding the end with zeros.
    pub fn convert(&self, input: &[f64]) -> Vec<f64> {
        convert_with(self.converter(), self.latency(), self.ratio(), input)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_manager_ok() {
        let ratio_ok = vec![0.0625, 0.063, 1.0, 15.9, 16.0, 0.123456];
        for ratio in ratio_ok {
            assert!(Manager::new(ratio).is_ok());
        }
    }

    #[test]
    fn test_manager_err() {
        let ratio_err = vec![
            -1.0,
            0.0,
            0.0624,
            16.01,
            f64::INFINITY,
            f64::NEG_INFINITY,
            f64::NAN,
        ];
        for ratio in ratio_err {
            assert!(Manager::new(ratio).is_err());
        }
    }

    #[test]
    fn test_mode_and_sample_rate() {
        let m = Manager::with_sample_rate(44100, 48000).unwrap();
        assert_eq!(m.mode(), ConvertMode::RationalFast);
        assert_eq!(m.ratio_parts(), Some((160, 147)));
        let two = Manager::new(2.0).unwrap();
        assert_eq!(two.mode(), ConvertMode::RationalFast);
        let generic = Manager::with_sample_rate(19999, 20000).unwrap();
        assert_eq!(generic.mode(), ConvertMode::Rational);
        let pi = Manager::new(std::f64::consts::PI).unwrap();
        assert_eq!(pi.mode(), ConvertMode::Float);
        assert_eq!(pi.ratio_parts(), None);
    }
}