Last active
June 16, 2023 09:58
-
-
Save JasonLG1979/8e9f4a9af489b68cc0ab463d6b32b6f6 to your computer and use it in GitHub Desktop.
Dual threaded Stereo Linear or Windowed Sinc Resampler from 44.1kHz to 48kHz, 88.2kHz or 96kHz with no dependencies outside std.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// This is free and unencumbered software released into the public domain. | |
// | |
// Anyone is free to copy, modify, publish, use, compile, sell, or | |
// distribute this software, either in source code form or as a compiled | |
// binary, for any purpose, commercial or non-commercial, and by any | |
// means. | |
// | |
// In jurisdictions that recognize copyright laws, the author or authors | |
// of this software dedicate any and all copyright interest in the | |
// software to the public domain. We make this dedication for the benefit | |
// of the public at large and to the detriment of our heirs and | |
// successors. We intend this dedication to be an overt act of | |
// relinquishment in perpetuity of all present and future rights to this | |
// software under copyright law. | |
// | |
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | |
// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR | |
// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, | |
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR | |
// OTHER DEALINGS IN THE SOFTWARE. | |
// | |
// For more information, please refer to <http://unlicense.org/> | |
const INPUT_SIZE: usize = 147; | |
const SOURCE_SAMPLE_RATE: usize = 44_100; | |
// Reciprocals allow us to multiply instead of divide during interpolation. | |
const HZ48000_RESAMPLE_FACTOR_RECIPROCAL: f64 = SOURCE_SAMPLE_RATE as f64 / 48_000.0; | |
const HZ88200_RESAMPLE_FACTOR_RECIPROCAL: f64 = SOURCE_SAMPLE_RATE as f64 / 88_200.0; | |
const HZ96000_RESAMPLE_FACTOR_RECIPROCAL: f64 = SOURCE_SAMPLE_RATE as f64 / 96_000.0; | |
const HZ48000_INTERPOLATION_OUTPUT_SIZE: usize = | |
(INPUT_SIZE as f64 * (1.0 / HZ48000_RESAMPLE_FACTOR_RECIPROCAL)) as usize; | |
const HZ88200_INTERPOLATION_OUTPUT_SIZE: usize = | |
(INPUT_SIZE as f64 * (1.0 / HZ88200_RESAMPLE_FACTOR_RECIPROCAL)) as usize; | |
const HZ96000_INTERPOLATION_OUTPUT_SIZE: usize = | |
(INPUT_SIZE as f64 * (1.0 / HZ96000_RESAMPLE_FACTOR_RECIPROCAL)) as usize; | |
// Hann coefficients | |
const HANN_A0: f64 = 0.5; | |
const HANN_A1: f64 = 1.0; | |
// Hamming coefficients | |
const HAMMING_A0: f64 = 0.53836; | |
const HAMMING_A1: f64 = 0.46164; | |
// Nuttall coefficients | |
const NUTTALL_A0: f64 = 0.355768; | |
const NUTTALL_A1: f64 = 0.487396; | |
const NUTTALL_A2: f64 = 0.144232; | |
const NUTTALL_A3: f64 = 0.012604; | |
// Blackman coefficients | |
const BLACKMAN_A0: f64 = 0.42; | |
const BLACKMAN_A1: f64 = 0.5; | |
const BLACKMAN_A2: f64 = 0.08; | |
// Blackman-Harris coefficients | |
const BLACKMAN_HARRIS_A0: f64 = 0.355768; | |
const BLACKMAN_HARRIS_A1: f64 = 0.487396; | |
const BLACKMAN_HARRIS_A2: f64 = 0.144232; | |
const BLACKMAN_HARRIS_A3: f64 = 0.012604; | |
// Blackman-Nuttall coefficients | |
const BLACKMAN_NUTTALL_A0: f64 = 0.3635819; | |
const BLACKMAN_NUTTALL_A1: f64 = 0.4891775; | |
const BLACKMAN_NUTTALL_A2: f64 = 0.1365995; | |
const BLACKMAN_NUTTALL_A3: f64 = 0.0106411; | |
// Constants for calculations | |
const TWO_PI: f64 = 2.0 * std::f64::consts::PI; | |
const FOUR_PI: f64 = 4.0 * std::f64::consts::PI; | |
const SIX_PI: f64 = 6.0 * std::f64::consts::PI; | |
/// Enum representing different levels of interpolation quality. | |
#[derive(Clone, Copy, Debug, Default)] | |
pub enum InterpolationQuality { | |
/// Low interpolation quality using Linear Interpolation. | |
#[default] | |
Low, | |
/// Medium interpolation quality using Windowed Sinc Interpolation. | |
Medium, | |
/// High interpolation quality using Windowed Sinc Interpolation. | |
High, | |
} | |
impl std::str::FromStr for InterpolationQuality { | |
type Err = (); | |
fn from_str(s: &str) -> Result<Self, Self::Err> { | |
use InterpolationQuality::*; | |
let lowercase_input = s.to_lowercase(); | |
match lowercase_input.as_str() { | |
"low" => Ok(Low), | |
"medium" => Ok(Medium), | |
"high" => Ok(High), | |
_ => Err(()), | |
} | |
} | |
} | |
impl std::fmt::Display for InterpolationQuality { | |
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | |
use InterpolationQuality::*; | |
match self { | |
Low => write!(f, "Low"), | |
Medium => write!(f, "Medium"), | |
High => write!(f, "High"), | |
} | |
} | |
} | |
impl InterpolationQuality { | |
/// Returns the length of interpolation coefficients based on the quality level. | |
pub fn get_interpolation_coefficients_length(&self) -> usize { | |
use InterpolationQuality::*; | |
match self { | |
Low => 0, | |
Medium => 129, | |
High => 257, | |
} | |
} | |
} | |
/// Enum representing different sample rates. | |
#[derive(Clone, Copy, Debug, Default)] | |
pub enum SampleRate { | |
/// Represents a sample rate of 44.1kHz. Signifies a bypass. | |
#[default] | |
Hz44100, | |
/// Represents a sample rate of 48kHz. | |
Hz48000, | |
/// Represents a sample rate of 88.2kHz. | |
Hz88200, | |
/// Represents a sample rate of 96kHz. | |
Hz96000, | |
} | |
impl std::ops::Deref for SampleRate { | |
type Target = u32; | |
fn deref(&self) -> &Self::Target { | |
use SampleRate::*; | |
match self { | |
Hz44100 => &44100, | |
Hz48000 => &48000, | |
Hz88200 => &88200, | |
Hz96000 => &96000, | |
} | |
} | |
} | |
impl std::str::FromStr for SampleRate { | |
type Err = (); | |
fn from_str(s: &str) -> Result<Self, Self::Err> { | |
use SampleRate::*; | |
let lowercase_input = s.to_lowercase(); | |
// Match against both the actual | |
// stringified value and how most | |
// humans would write a sample rate. | |
match lowercase_input.as_str() { | |
"hz44100" | "44100hz" | "44100" | "44.1khz" => Ok(Hz44100), | |
"hz48000" | "48000hz" | "48000" | "48khz" => Ok(Hz48000), | |
"hz88200" | "88200hz" | "88200" | "88.2khz" => Ok(Hz88200), | |
"hz96000" | "96000hz" | "96000" | "96khz" => Ok(Hz96000), | |
_ => Err(()), | |
} | |
} | |
} | |
impl std::fmt::Display for SampleRate { | |
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | |
use SampleRate::*; | |
match self { | |
// Let's make these more human readable. | |
// "Hz44100" is just awkward. | |
Hz44100 => write!(f, "44.1kHz"), | |
Hz48000 => write!(f, "48kHz"), | |
Hz88200 => write!(f, "88.2kHz"), | |
Hz96000 => write!(f, "96kHz"), | |
} | |
} | |
} | |
/// Specifies the resampling parameters. | |
/// | |
/// Where: | |
/// resample factor is output sample rate / 44100 | |
/// We use the reciprocal of that because it allows us to multiply instead of divide during interpolation. | |
/// And: | |
/// interpolation_output_size is 147 * resample factor | |
/// | |
/// 147 was specifically chosen because 147 * resample factor for 48kHz, 88.2kHz, and 96kHz is an integer. | |
/// Having an integer for the Interpolator input and output means no fractional samples, less rounding error, | |
/// and perfect pitch and time continuity. | |
#[derive(Clone, Copy, Debug, Default)] | |
pub struct ResampleSpec { | |
resample_factor_reciprocal: f64, | |
interpolation_output_size: usize, | |
} | |
impl SampleRate { | |
/// Returns the `ResampleSpec` for the sample rate. | |
pub fn get_resample_spec(&self) -> ResampleSpec { | |
use SampleRate::*; | |
match self { | |
// Dummy values to satisfy | |
// the match statement. | |
// 44.1kHz will be bypassed. | |
Hz44100 => ResampleSpec { | |
resample_factor_reciprocal: 1.0, | |
interpolation_output_size: INPUT_SIZE, | |
}, | |
Hz48000 => ResampleSpec { | |
resample_factor_reciprocal: HZ48000_RESAMPLE_FACTOR_RECIPROCAL, | |
interpolation_output_size: HZ48000_INTERPOLATION_OUTPUT_SIZE, | |
}, | |
Hz88200 => ResampleSpec { | |
resample_factor_reciprocal: HZ88200_RESAMPLE_FACTOR_RECIPROCAL, | |
interpolation_output_size: HZ88200_INTERPOLATION_OUTPUT_SIZE, | |
}, | |
Hz96000 => ResampleSpec { | |
resample_factor_reciprocal: HZ96000_RESAMPLE_FACTOR_RECIPROCAL, | |
interpolation_output_size: HZ96000_INTERPOLATION_OUTPUT_SIZE, | |
}, | |
} | |
} | |
} | |
/// Enum representing different window functions. | |
/// | |
/// Although how a window function affects a signal can certainly be objectively measured, There is | |
/// no objectively "Best Sounding" window, it's a matter of personal taste. | |
#[derive(Clone, Copy, Debug, Default)] | |
pub enum WindowFunction { | |
Hann, | |
Hamming, | |
Nuttall, | |
Blackman, | |
#[default] | |
BlackmanHarris, | |
BlackmanNuttall, | |
} | |
impl std::str::FromStr for WindowFunction { | |
type Err = (); | |
fn from_str(s: &str) -> Result<Self, Self::Err> { | |
use WindowFunction::*; | |
let lowercase_input = s.to_lowercase(); | |
// Match against the hyphenated | |
// version of the window also | |
// where applicable. | |
match lowercase_input.as_str() { | |
"hann" => Ok(Hann), | |
"hamming" => Ok(Hamming), | |
"nuttall" => Ok(Nuttall), | |
"blackman" => Ok(Blackman), | |
"blackmanharris" | "blackman-harris" => Ok(BlackmanHarris), | |
"blackmannuttall" | "blackman-nuttall" => Ok(BlackmanNuttall), | |
_ => Err(()), | |
} | |
} | |
} | |
impl std::fmt::Display for WindowFunction { | |
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | |
use WindowFunction::*; | |
// Add the hyphen in the window | |
// name where applicable. | |
match self { | |
Hann => write!(f, "Hann"), | |
Hamming => write!(f, "Hamming"), | |
Nuttall => write!(f, "Nuttall"), | |
Blackman => write!(f, "Blackman"), | |
BlackmanHarris => write!(f, "Blackman-Harris"), | |
BlackmanNuttall => write!(f, "Blackman-Nuttall"), | |
} | |
} | |
} | |
impl WindowFunction { | |
/// Returns the interpolation coefficients for the specified window function. | |
/// | |
/// # Arguments | |
/// | |
/// * `interpolation_coefficients_length` - The length of interpolation coefficients to generate. | |
/// * `resample_factor_reciprocal` - The resample factor reciprocal used in the interpolation. | |
/// | |
/// # Returns | |
/// | |
/// The interpolation coefficients as a vector of `f64` values. | |
pub fn get_interpolation_coefficients( | |
&self, | |
interpolation_coefficients_length: usize, | |
resample_factor_reciprocal: f64, | |
) -> Vec<f64> { | |
// Pre-calculate weighted coefficients. This saves us | |
// from having to calculate very possibly the same values | |
// over and over again on-the-fly. This saves us multiple | |
// math operations per sample which is a big deal when you're | |
// dealing with up to 192000 (sample rate * channels) samples a sec. | |
// With these pre-calculate weighted coefficients all we have to | |
// do during runtime is the temporal convolution part of the | |
// interpolation. | |
let mut coefficients = Vec::with_capacity(interpolation_coefficients_length); | |
let sinc_center = (interpolation_coefficients_length as f64 - 1.0) * 0.5; | |
let mut coefficient_sum = 0.0; | |
coefficients.extend((0..interpolation_coefficients_length).map( | |
|interpolation_coefficient_index| { | |
let index_float = interpolation_coefficient_index as f64; | |
let sample_index_fractional = (index_float * resample_factor_reciprocal).fract(); | |
let sinc_center_offset = index_float - sinc_center; | |
let sample_index_fractional_sinc_weight = Self::sinc(sample_index_fractional); | |
let sinc_value = Self::sinc(sinc_center_offset); | |
let window_value = self.window( | |
interpolation_coefficient_index, | |
interpolation_coefficients_length, | |
); | |
let sinc_window = sinc_value * window_value; | |
let coefficient = sinc_window * sample_index_fractional_sinc_weight; | |
coefficient_sum += coefficient; | |
coefficient | |
}, | |
)); | |
// Normalize the window. | |
coefficients | |
.iter_mut() | |
.for_each(|coefficient| *coefficient /= coefficient_sum); | |
coefficients | |
} | |
fn sinc(x: f64) -> f64 { | |
// calculate the sinc value for x. | |
// If x is 0.0, or at least as close | |
// to 0.0 as can be tested with | |
// floating point math, return 1.0. | |
if x.abs() < f64::EPSILON { | |
1.0 | |
} else { | |
let pi_x = std::f64::consts::PI * x; | |
pi_x.sin() / pi_x | |
} | |
} | |
fn window( | |
&self, | |
interpolation_coefficient_index: usize, | |
interpolation_coefficients_length: usize, | |
) -> f64 { | |
// Generate window values for a WindowFunction variant based on coefficient_index and interpolation_coefficients_length, | |
// which represent n and N in the window function equations. | |
use WindowFunction::*; | |
// Common values shared by all windows | |
// n | |
let n = interpolation_coefficient_index as f64; | |
// 2πn | |
let two_pi_n = TWO_PI * n; | |
// N-1 | |
let cap_n_minus_one = interpolation_coefficients_length as f64 - 1.0; | |
match self { | |
Hann => { | |
// Calculate the Hann window function for the given center offset | |
// w(n) = 0.5 * (1 - cos(2πn / (N-1))), | |
// where n is the center offset and N is the window size | |
HANN_A0 * (HANN_A1 - (two_pi_n / cap_n_minus_one).cos()) | |
} | |
Hamming => { | |
// Calculate the Hamming window function for the given center offset | |
// w(n) = A0 - A1 * cos(2πn / (N-1)), | |
// where n is the center offset, N is the window size, | |
// and A0, A1 are precalculated coefficients | |
HAMMING_A0 - HAMMING_A1 * (two_pi_n / cap_n_minus_one).cos() | |
} | |
Nuttall => { | |
// Calculate the Nuttall window function for the given center offset | |
// w(n) = A0 - A1*cos(2πn / (N-1)) + A2*cos(4πn / (N-1)) - A3*cos(6πn / (N-1)), | |
// where n is the center offset, N is the window size, | |
// and A0, A1, A2, A3 are precalculated coefficients | |
let four_pi_n = FOUR_PI * n; | |
let six_pi_n = SIX_PI * n; | |
NUTTALL_A0 - NUTTALL_A1 * (two_pi_n / cap_n_minus_one).cos() | |
+ NUTTALL_A2 * (four_pi_n / cap_n_minus_one).cos() | |
- NUTTALL_A3 * (six_pi_n / cap_n_minus_one).cos() | |
} | |
Blackman => { | |
// Calculate the Blackman window function for the given center offset | |
// w(n) = A0 - A1*cos(2πn / (N-1)) + A2*cos(4πn / (N-1)), | |
// where n is the center offset, N is the window size, | |
// and A0, A1, A2 are precalculated coefficients | |
let four_pi_n = FOUR_PI * n; | |
BLACKMAN_A0 - BLACKMAN_A1 * (two_pi_n / cap_n_minus_one).cos() | |
+ BLACKMAN_A2 * (four_pi_n / cap_n_minus_one).cos() | |
} | |
BlackmanHarris => { | |
// Calculate the Blackman-Harris window function for the given center offset | |
// w(n) = A0 - A1*cos(2πn / (N-1)) + A2*cos(4πn / (N-1)) - A3*cos(6πn / (N-1)), | |
// where n is the center offset, N is the window size, | |
// and A0, A1, A2, A3 are precalculated coefficients | |
let four_pi_n = FOUR_PI * n; | |
let six_pi_n = SIX_PI * n; | |
BLACKMAN_HARRIS_A0 - BLACKMAN_HARRIS_A1 * (two_pi_n / cap_n_minus_one).cos() | |
+ BLACKMAN_HARRIS_A2 * (four_pi_n / cap_n_minus_one).cos() | |
- BLACKMAN_HARRIS_A3 * (six_pi_n / cap_n_minus_one).cos() | |
} | |
BlackmanNuttall => { | |
// Calculate the Blackman-Nuttall window function for the given center offset | |
// w(n) = A0 - A1*cos(2πn / (N-1)) + A2*cos(4πn / (N-1)) - A3*cos(6πn / (N-1)), | |
// where n is the center offset, N is the window size, | |
// and A0, A1, A2, A3 are precalculated coefficients | |
let four_pi_n = FOUR_PI * n; | |
let six_pi_n = SIX_PI * n; | |
BLACKMAN_NUTTALL_A0 - BLACKMAN_NUTTALL_A1 * (two_pi_n / cap_n_minus_one).cos() | |
+ BLACKMAN_NUTTALL_A2 * (four_pi_n / cap_n_minus_one).cos() | |
- BLACKMAN_NUTTALL_A3 * (six_pi_n / cap_n_minus_one).cos() | |
} | |
} | |
} | |
} | |
/// A delay line buffer that stores previous input samples for interpolation. | |
/// | |
/// The delay line is a First-In-First-Out (FIFO) buffer that maintains a fixed-length history of | |
/// samples, preserving their order. This history is accessed and processed by the Interpolator to | |
/// perform temporal convolution and produce interpolated samples. | |
pub struct DelayLine { | |
buffer: std::collections::VecDeque<f64>, | |
interpolation_coefficients_length: usize, | |
} | |
impl DelayLine { | |
/// Creates a new `DelayLine` with the specified sinc_window_length (buffer size). | |
/// | |
/// # Arguments | |
/// | |
/// * `interpolation_coefficients_length` - The size of the delay line buffer. | |
/// | |
/// # Returns | |
/// | |
/// A new `DelayLine` instance. | |
pub fn new(interpolation_coefficients_length: usize) -> DelayLine { | |
Self { | |
buffer: std::collections::VecDeque::with_capacity(interpolation_coefficients_length), | |
interpolation_coefficients_length, | |
} | |
} | |
/// Adds a new sample to the delay line. | |
/// | |
/// If the buffer exceeds its capacity, the oldest sample is removed. | |
/// | |
/// # Arguments | |
/// | |
/// * `sample` - The new sample to add to the delay line. | |
pub fn push(&mut self, sample: f64) { | |
self.buffer.push_back(sample); | |
while self.buffer.len() > self.interpolation_coefficients_length { | |
self.buffer.pop_front(); | |
} | |
} | |
/// Clears the delay line, discarding all its contents. | |
pub fn clear(&mut self) { | |
self.buffer.clear(); | |
} | |
} | |
impl<'a> IntoIterator for &'a DelayLine { | |
type Item = &'a f64; | |
type IntoIter = std::collections::vec_deque::Iter<'a, f64>; | |
fn into_iter(self) -> Self::IntoIter { | |
self.buffer.iter() | |
} | |
} | |
/// A Windowed Sinc Interpolator that performs sample interpolation using pre-calculated coefficients. | |
pub struct WindowedSincInterpolator { | |
interpolation_coefficients: Vec<f64>, | |
delay_line: DelayLine, | |
} | |
impl WindowedSincInterpolator { | |
/// Creates a new `WindowedSincInterpolator` with the specified interpolation quality, window function, and resample factor. | |
/// | |
/// # Arguments | |
/// | |
/// * `interpolation_quality` - The interpolation quality determining interpolation coefficients length. | |
/// * `window_function` - The window function to use for interpolation. | |
/// * `resample_factor_reciprocal` - The resampling factor reciprocal for interpolation. | |
/// | |
/// # Returns | |
/// | |
/// A new `WindowedSincInterpolator` instance. | |
pub fn new( | |
interpolation_quality: InterpolationQuality, | |
window_function: WindowFunction, | |
resample_factor_reciprocal: f64, | |
) -> Self { | |
let interpolation_coefficients_length = | |
interpolation_quality.get_interpolation_coefficients_length(); | |
let delay_line = DelayLine::new(interpolation_coefficients_length); | |
let interpolation_coefficients = window_function.get_interpolation_coefficients( | |
interpolation_coefficients_length, | |
resample_factor_reciprocal, | |
); | |
Self { | |
interpolation_coefficients, | |
delay_line, | |
} | |
} | |
/// Interpolates a new sample using the pre-calculated interpolation coefficients. | |
/// | |
/// The sample is pushed into the delay line, and then temporal convolution is performed | |
/// to produce the interpolated sample. | |
/// | |
/// # Arguments | |
/// | |
/// * `sample` - The input sample to be interpolated. | |
/// | |
/// # Returns | |
/// | |
/// The interpolated sample. | |
pub fn interpolate(&mut self, sample: f64) -> f64 { | |
// Since our interpolation coefficients are pre-calculated | |
// we can basically pretend like the Interpolator is a FIR filter. | |
self.delay_line.push(sample); | |
// Temporal convolution | |
self.interpolation_coefficients | |
.iter() | |
.zip(&self.delay_line) | |
.fold(0.0, |acc, (coefficient, delay_line_sample)| { | |
acc + coefficient * delay_line_sample | |
}) | |
} | |
/// Clears the delay line, discarding all its contents. | |
/// | |
/// This resets the Interpolator while keeping the existing interpolation coefficients. | |
pub fn clear(&mut self) { | |
self.delay_line.clear(); | |
} | |
} | |
/// Trait representing a mono audio resampler. | |
pub trait MonoResampler { | |
/// Creates a new instance of the resampler with the specified sample rate, interpolation quality, and window function. | |
/// | |
/// # Arguments | |
/// | |
/// * `sample_rate` - The sample rate of the audio to be resampled. | |
/// * `interpolation_quality` - The interpolation quality to be used by the resampler (applicable for Windowed Sinc Interpolation). | |
/// * `window_function` - The window function to be used by the resampler (applicable for Windowed Sinc Interpolation). | |
/// | |
/// # Returns | |
/// | |
/// A new instance of the resampler. | |
/// | |
/// # Generic Constraints | |
/// | |
/// This method has a generic constraint that requires the implementor of the `MonoResampler` trait to be `Sized`. | |
fn new( | |
sample_rate: SampleRate, | |
interpolation_quality: InterpolationQuality, | |
window_function: WindowFunction, | |
) -> Self | |
where | |
Self: Sized; | |
/// Stops the resampler and clears its internal state. | |
/// | |
/// This method resets the resampler by clearing the input buffer and `WindowedSincInterpolator` | |
/// (in the case of Windowed Sinc Interpolation). | |
fn stop(&mut self); | |
/// Resamples the provided mono audio samples. | |
/// | |
/// # Arguments | |
/// | |
/// * `samples` - The mono audio samples to be resampled as a slice of `f64` values. | |
/// | |
/// # Returns | |
/// | |
/// An `Option` containing the resampled audio samples as a vector of `f64` values. | |
/// | |
/// If resampling is not possible due to insufficient samples, `None` is returned. | |
fn resample(&mut self, samples: &[f64]) -> Option<Vec<f64>>; | |
} | |
/// A struct representing a mono audio sinc resampler. | |
pub struct MonoSincResampler { | |
interpolator: WindowedSincInterpolator, | |
input_buffer: Vec<f64>, | |
resample_factor_reciprocal: f64, | |
interpolation_output_size: usize, | |
} | |
impl MonoResampler for MonoSincResampler { | |
/// Creates a new `MonoSincResampler` instance with the specified sample rate, interpolation quality, and window function. | |
/// | |
/// # Arguments | |
/// | |
/// * `sample_rate` - The sample rate of the audio to be resampled. | |
/// * `interpolation_quality` - The interpolation quality to be used by the resampler. | |
/// * `window_function` - The window function to be used by the resampler. | |
/// | |
/// # Returns | |
/// | |
/// A new `MonoSincResampler` instance. | |
fn new( | |
sample_rate: SampleRate, | |
interpolation_quality: InterpolationQuality, | |
window_function: WindowFunction, | |
) -> Self { | |
let spec = sample_rate.get_resample_spec(); | |
Self { | |
interpolator: WindowedSincInterpolator::new( | |
interpolation_quality, | |
window_function, | |
spec.resample_factor_reciprocal, | |
), | |
input_buffer: Vec::with_capacity(SOURCE_SAMPLE_RATE), | |
resample_factor_reciprocal: spec.resample_factor_reciprocal, | |
interpolation_output_size: spec.interpolation_output_size, | |
} | |
} | |
/// Stops the resampler and clears its internal state. | |
/// | |
/// This method resets the resampler by clearing the `WindowedSincInterpolator` | |
/// and input buffer. | |
fn stop(&mut self) { | |
self.interpolator.clear(); | |
self.input_buffer.clear(); | |
} | |
/// Resamples the provided mono audio samples using sinc interpolation. | |
/// | |
/// # Arguments | |
/// | |
/// * `samples` - The mono audio samples to be resampled as a slice of `f64` values. | |
/// | |
/// # Returns | |
/// | |
/// An `Option` containing the resampled audio samples as a vector of `f64` values. | |
/// | |
/// If resampling is not possible due to insufficient samples, `None` is returned. | |
fn resample(&mut self, samples: &[f64]) -> Option<Vec<f64>> { | |
self.input_buffer.extend_from_slice(samples); | |
let num_buffer_chunks = self.input_buffer.len().saturating_div(INPUT_SIZE); | |
if num_buffer_chunks == 0 { | |
return None; | |
} | |
// The amount of the input buffer we are going to interpolate. | |
let input_size = num_buffer_chunks * INPUT_SIZE; | |
// The size of the output after interpolation. | |
let output_size = num_buffer_chunks * self.interpolation_output_size; | |
let mut output = Vec::with_capacity(output_size); | |
output.extend((0..output_size).map(|ouput_index| { | |
// The factional weights are already calculated and factored | |
// into our interpolation coefficients so all we have to | |
// do is pretend we're doing nearest-neighbor interpolation | |
// and push samples though the Interpolator and what comes | |
// out the other side is Sinc Windowed Interpolated samples. | |
let sample_index = (ouput_index as f64 * self.resample_factor_reciprocal) as usize; | |
let sample = self.input_buffer[sample_index]; | |
self.interpolator.interpolate(sample) | |
})); | |
// Remove the interpolated samples from the input buffer. | |
self.input_buffer.drain(..input_size); | |
Some(output) | |
} | |
} | |
/// A struct representing a mono audio linear resampler. | |
pub struct MonoLinearResampler { | |
input_buffer: Vec<f64>, | |
resample_factor_reciprocal: f64, | |
interpolation_output_size: usize, | |
} | |
impl MonoResampler for MonoLinearResampler { | |
/// Creates a new `MonoLinearResampler` instance with the specified sample rate. | |
/// | |
/// # Arguments | |
/// | |
/// * `sample_rate` - The sample rate of the audio to be resampled. | |
/// * `interpolation_quality` - [Ignored] The interpolation quality (ignored by this resampler). | |
/// * `window_function` - [Ignored] The window function (ignored by this resampler). | |
/// | |
/// # Returns | |
/// | |
/// A new `MonoLinearResampler` instance. | |
fn new(sample_rate: SampleRate, _: InterpolationQuality, _: WindowFunction) -> Self { | |
let spec = sample_rate.get_resample_spec(); | |
Self { | |
input_buffer: Vec::with_capacity(SOURCE_SAMPLE_RATE), | |
resample_factor_reciprocal: spec.resample_factor_reciprocal, | |
interpolation_output_size: spec.interpolation_output_size, | |
} | |
} | |
/// Stops the resampler and clears its internal state. | |
/// | |
/// This method resets the resampler by clearing the input buffer. | |
fn stop(&mut self) { | |
self.input_buffer.clear(); | |
} | |
/// Resamples the provided mono audio samples using linear interpolation. | |
/// | |
/// # Arguments | |
/// | |
/// * `samples` - The mono audio samples to be resampled as a slice of `f64` values. | |
/// | |
/// # Returns | |
/// | |
/// An `Option` containing the resampled audio samples as a vector of `f64` values. | |
/// | |
/// If resampling is not possible due to insufficient samples, `None` is returned. | |
fn resample(&mut self, samples: &[f64]) -> Option<Vec<f64>> { | |
self.input_buffer.extend_from_slice(samples); | |
let num_buffer_chunks = self.input_buffer.len().saturating_div(INPUT_SIZE); | |
if num_buffer_chunks == 0 { | |
return None; | |
} | |
// The amount of the input buffer we are going to interpolate. | |
let input_size = num_buffer_chunks * INPUT_SIZE; | |
// The size of the output after interpolation. | |
// We have to account for the fact that to do effective linear | |
// interpolation we need an extra sample to be able to throw away later. | |
let output_size = num_buffer_chunks * self.interpolation_output_size + 1; | |
let mut output = Vec::with_capacity(output_size); | |
output.extend((0..output_size).map(|output_index| { | |
let sample_index = output_index as f64 * self.resample_factor_reciprocal; | |
let sample_index_fractional = sample_index.fract(); | |
let sample_index = sample_index as usize; | |
let sample = *self.input_buffer.get(sample_index).unwrap_or(&0.0); | |
let next_sample = *self.input_buffer.get(sample_index + 1).unwrap_or(&0.0); | |
let sample_index_fractional_complementary = 1.0 - sample_index_fractional; | |
sample * sample_index_fractional_complementary + next_sample * sample_index_fractional | |
})); | |
// Remove the last garbage sample. | |
output.pop(); | |
// Remove the interpolated samples from the input buffer. | |
self.input_buffer.drain(..input_size); | |
Some(output) | |
} | |
} | |
enum ResampleTask { | |
Stop, | |
Terminate, | |
ProcessSamples(Vec<f64>), | |
} | |
enum ResampleResult { | |
ProcessedSamples(Option<Vec<f64>>), | |
} | |
/// A struct representing a resample worker that performs resampling in a worker thread. | |
pub struct ResampleWorker { | |
task_sender: Option<std::sync::mpsc::Sender<ResampleTask>>, | |
result_receiver: Option<std::sync::mpsc::Receiver<ResampleResult>>, | |
handle: Option<std::thread::JoinHandle<()>>, | |
} | |
impl ResampleWorker { | |
/// Creates a new `ResampleWorker` instance that performs resampling in a worker thread. | |
/// | |
/// # Arguments | |
/// | |
/// * `resampler` - A resampler implementing the `MonoResampler` trait, which performs the actual resampling. | |
/// | |
/// # Returns | |
/// | |
/// A new `ResampleWorker` instance. | |
pub fn new(mut resampler: impl MonoResampler + std::marker::Send + 'static) -> Self { | |
// A thread worker for the resampler. | |
let (task_sender, task_receiver) = std::sync::mpsc::channel(); | |
let (result_sender, result_receiver) = std::sync::mpsc::channel(); | |
let handle = std::thread::spawn(move || loop { | |
// Handle ResampleTask's in a worker thread. | |
match task_receiver.recv() { | |
Err(_) => break, | |
Ok(task) => match task { | |
ResampleTask::Terminate => break, | |
ResampleTask::Stop => resampler.stop(), | |
ResampleTask::ProcessSamples(samples) => { | |
let samples = resampler.resample(&samples); | |
result_sender | |
.send(ResampleResult::ProcessedSamples(samples)) | |
.ok(); | |
} | |
}, | |
} | |
}); | |
Self { | |
task_sender: Some(task_sender), | |
result_receiver: Some(result_receiver), | |
handle: Some(handle), | |
} | |
} | |
/// Stops the resample | |
pub fn stop(&mut self) { | |
// Shout Stop into the void. | |
self.task_sender | |
.as_mut() | |
.and_then(|sender| sender.send(ResampleTask::Stop).ok()); | |
} | |
/// Processes the samples using the resample worker in a worker thread. | |
/// | |
/// # Arguments | |
/// | |
/// * `samples` - The samples to be processed as a vector of `f64` values. | |
pub fn process(&mut self, samples: Vec<f64>) { | |
// Shout our task into the void. | |
self.task_sender | |
.as_mut() | |
.and_then(|sender| sender.send(ResampleTask::ProcessSamples(samples)).ok()); | |
} | |
/// Receives the result of the resample operation performed by the worker thread. | |
/// | |
/// # Returns | |
/// | |
/// An `Option` containing the processed output samples as a vector of `f64` values. | |
/// | |
/// If resampling is not possible due to insufficient samples, `None` is returned. | |
pub fn receive_result(&mut self) -> Option<Vec<f64>> { | |
self.result_receiver | |
.as_mut() | |
.and_then(|result_receiver| result_receiver.recv().ok()) | |
.and_then(|result| match result { | |
ResampleResult::ProcessedSamples(samples) => samples, | |
}) | |
} | |
} | |
impl Drop for ResampleWorker { | |
fn drop(&mut self) { | |
// Tell the worker thread to die. | |
self.task_sender | |
.take() | |
.and_then(|sender| sender.send(ResampleTask::Terminate).ok()); | |
// Drain the receiver. | |
self.result_receiver | |
.take() | |
.and_then(|result_receiver| loop { | |
let drained = result_receiver.recv().ok(); | |
if drained.is_none() { | |
break drained; | |
} | |
}); | |
// Join the worker thread. | |
self.handle.take().and_then(|handle| handle.join().ok()); | |
} | |
} | |
#[derive(Default)] | |
pub enum Resampler { | |
#[default] | |
Bypass, | |
Worker { | |
left_resampler: ResampleWorker, | |
right_resampler: ResampleWorker, | |
}, | |
} | |
/// A struct representing a stereo interleaved resampler. | |
#[derive(Default)] | |
pub struct StereoInterleavedResampler { | |
resampler: Resampler, | |
} | |
impl StereoInterleavedResampler { | |
/// Creates a new `StereoInterleavedResampler` instance. | |
/// | |
/// # Arguments | |
/// | |
/// * `sample_rate` - An optional `SampleRate` for resampling. If not provided, the default sample rate is used. | |
/// * `interpolation_quality` - An optional `InterpolationQuality` for resampling. If not provided, the default interpolation quality is used. | |
/// * `window_function` - An optional `WindowFunction` for resampling. If not provided, the default window function is used. | |
/// | |
/// # Returns | |
/// | |
/// A new `StereoInterleavedResampler` instance. | |
pub fn new( | |
sample_rate: Option<SampleRate>, | |
interpolation_quality: Option<InterpolationQuality>, | |
window_function: Option<WindowFunction>, | |
) -> Self { | |
// Creates a Dual threaded resampler wherein the | |
// incoming sample packets of stereo interleaved | |
// PCM are de-interleaved into separate left and | |
// right channels and sent to worker threads to | |
// be resampled. The calling thread then waits | |
// for both results and re-interleaves them. | |
let sample_rate = sample_rate.unwrap_or_default(); | |
let resampler = match sample_rate { | |
// Our sample rate is the same as our input sample rate. | |
// We don't need thread workers since we're not resampling. | |
SampleRate::Hz44100 => Resampler::Bypass, | |
_ => { | |
let interpolation_quality = interpolation_quality.unwrap_or_default(); | |
let window_function = window_function.unwrap_or_default(); | |
match interpolation_quality { | |
// Create our workers. | |
InterpolationQuality::Low => { | |
// Low = Linear Interpolation. | |
let left = MonoLinearResampler::new( | |
sample_rate, | |
interpolation_quality, | |
window_function, | |
); | |
let right = MonoLinearResampler::new( | |
sample_rate, | |
interpolation_quality, | |
window_function, | |
); | |
Resampler::Worker { | |
left_resampler: ResampleWorker::new(left), | |
right_resampler: ResampleWorker::new(right), | |
} | |
} | |
_ => { | |
// Medium or High = Windowed Sinc interpolation. | |
let left = MonoSincResampler::new( | |
sample_rate, | |
interpolation_quality, | |
window_function, | |
); | |
let right = MonoSincResampler::new( | |
sample_rate, | |
interpolation_quality, | |
window_function, | |
); | |
Resampler::Worker { | |
left_resampler: ResampleWorker::new(left), | |
right_resampler: ResampleWorker::new(right), | |
} | |
} | |
} | |
} | |
}; | |
Self { resampler } | |
} | |
/// Processes the input samples using the resampler. | |
/// | |
/// # Arguments | |
/// | |
/// * `input_samples` - The input samples to be processed as a slice of `f64` values. | |
/// | |
/// # Returns | |
/// | |
/// An `Option` containing the processed output samples as a vector of `f64` values. | |
/// | |
/// If resampling is not possible due to insufficient samples, `None` is returned. | |
pub fn process(&mut self, input_samples: &[f64]) -> Option<Vec<f64>> { | |
match &mut self.resampler { | |
// Bypass is basically a no-op. | |
Resampler::Bypass => Some(input_samples.to_vec()), | |
// Do magic. | |
Resampler::Worker { | |
left_resampler, | |
right_resampler, | |
} => { | |
// Split the stereo interleaved samples into left and right channels. | |
let (left_samples, right_samples) = Self::deinterleave_samples(input_samples); | |
// Send the resample tasks to the workers. | |
left_resampler.process(left_samples); | |
right_resampler.process(right_samples); | |
// Wait for the results. | |
let left_samples = left_resampler.receive_result(); | |
let right_samples = right_resampler.receive_result(); | |
// Re-interleave the resampled channels. | |
left_samples.and_then(|left_samples| { | |
right_samples.map(|right_samples| { | |
Self::interleave_samples(&left_samples, &right_samples) | |
}) | |
}) | |
} | |
} | |
} | |
/// Stops the resampler. | |
pub fn stop(&mut self) { | |
match &mut self.resampler { | |
// Stop does nothing | |
// if we're bypassed. | |
Resampler::Bypass => (), | |
// Shout stop into the void. | |
Resampler::Worker { | |
left_resampler, | |
right_resampler, | |
} => { | |
left_resampler.stop(); | |
right_resampler.stop(); | |
} | |
} | |
} | |
fn interleave_samples(left_samples: &[f64], right_samples: &[f64]) -> Vec<f64> { | |
// Re-interleave the resampled channels. | |
left_samples | |
.iter() | |
.zip(right_samples.iter()) | |
.flat_map(|(&x, &y)| vec![x, y]) | |
.collect() | |
} | |
fn deinterleave_samples(samples: &[f64]) -> (Vec<f64>, Vec<f64>) { | |
// Split the stereo interleaved samples into left and right channels. | |
let (left_samples, right_samples): (Vec<f64>, Vec<f64>) = samples | |
.chunks(2) | |
.map(|chunk| { | |
let [left_sample, right_sample] = [chunk[0], chunk[1]]; | |
(left_sample, right_sample) | |
}) | |
.unzip(); | |
(left_samples, right_samples) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment