From 80759a4e83d1677ce3dd7bdb41fa5664197f84e5 Mon Sep 17 00:00:00 2001 From: Henrik Date: Mon, 26 Feb 2024 20:38:14 +0100 Subject: [PATCH 1/6] Fix calculation of input and output size --- Cargo.toml | 2 +- README.md | 2 ++ src/synchro.rs | 5 ++--- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index dec2b9d..94a8caa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rubato" -version = "0.14.1" +version = "0.14.2" rust-version = "1.61" authors = ["HEnquist "] description = "Asynchronous resampling library intended for audio data" diff --git a/README.md b/README.md index 3895d9c..dc77dd4 100644 --- a/README.md +++ b/README.md @@ -118,6 +118,8 @@ The `rubato` crate requires rustc version 1.61 or newer. ## Changelog +- v0.14.2 + - Fix calculation of input and output sizes when creating FftFixedInOut resampler. - v0.14.1 - More bugfixes for buffer allocation and max output length calculation. - Fix building with `log` feature. diff --git a/src/synchro.rs b/src/synchro.rs index 5dabf61..05461c2 100644 --- a/src/synchro.rs +++ b/src/synchro.rs @@ -212,9 +212,8 @@ where ); let gcd = integer::gcd(sample_rate_input, sample_rate_output); - let min_chunk_out = sample_rate_output / gcd; - let wanted = chunk_size_in; - let fft_chunks = (wanted as f32 / min_chunk_out as f32).ceil() as usize; + let min_chunk_in = sample_rate_input / gcd; + let fft_chunks = (chunk_size_in as f32 / min_chunk_in as f32).ceil() as usize; let fft_size_out = fft_chunks * sample_rate_output / gcd; let fft_size_in = fft_chunks * sample_rate_input / gcd; From 91ab8a53afc5f8dfe6fac41f92cea30db87d7942 Mon Sep 17 00:00:00 2001 From: Henrik Date: Mon, 26 Feb 2024 21:53:42 +0100 Subject: [PATCH 2/6] Make FFT resamplers optional via feature --- Cargo.toml | 8 ++++++-- README.md | 8 +++++++- examples/process_f64.rs | 10 +++++++--- src/lib.rs | 43 +++++++++++++++++++++++++++++++---------- src/sample.rs | 16 ++++++++++++++- 5 files changed, 68 insertions(+), 17 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 94a8caa..4b1e8c6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rubato" -version = "0.14.2" +version = "0.15.0" rust-version = "1.61" authors = ["HEnquist "] description = "Asynchronous resampling library intended for audio data" @@ -13,9 +13,13 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[features] +default = ["fft_resampler"] +fft_resampler = ["realfft"] + [dependencies] log = { version = "0.4.18", optional = true } -realfft = "3.3.0" +realfft = { version = "3.3.0", optional = true } num-complex = "0.4" num-integer = "0.1.45" num-traits = "0.2" diff --git a/README.md b/README.md index dc77dd4..9ecd133 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,11 @@ The synchronous resamplers benefit from the SIMD support of the RustFFT library. ## Cargo features +### `fft_resampler`: Enable the FFT based synchronous resamplers + +This feature is enabled by default. Disable it if the FFT resamplers are not needed, +to save compile time and reduce the resulting binary size. + ### `log`: Enable logging This feature enables logging via the `log` crate. This is intended for debugging purposes. @@ -118,7 +123,8 @@ The `rubato` crate requires rustc version 1.61 or newer. ## Changelog -- v0.14.2 +- v0.15.0 + - Make FFT resamplers optional via `fft_resampler` feature. - Fix calculation of input and output sizes when creating FftFixedInOut resampler. - v0.14.1 - More bugfixes for buffer allocation and max output length calculation. diff --git a/examples/process_f64.rs b/examples/process_f64.rs index b54ee81..873332c 100644 --- a/examples/process_f64.rs +++ b/examples/process_f64.rs @@ -1,9 +1,10 @@ extern crate rubato; use rubato::{ - calculate_cutoff, implement_resampler, FastFixedIn, FastFixedOut, FftFixedIn, FftFixedInOut, - FftFixedOut, PolynomialDegree, SincFixedIn, SincFixedOut, SincInterpolationParameters, - SincInterpolationType, WindowFunction, + calculate_cutoff, implement_resampler, FastFixedIn, FastFixedOut, PolynomialDegree, + SincFixedIn, SincFixedOut, SincInterpolationParameters, SincInterpolationType, WindowFunction, }; +#[cfg(feature = "fft_resampler")] +use rubato::{FftFixedIn, FftFixedInOut, FftFixedOut}; use std::convert::TryInto; use std::env; use std::fs::File; @@ -164,12 +165,15 @@ fn main() { "FastFixedOut" => { Box::new(FastFixedOut::::new(f_ratio, 1.1, PolynomialDegree::Septic, 1024, channels).unwrap()) } + #[cfg(feature = "fft_resampler")] "FftFixedIn" => { Box::new(FftFixedIn::::new(fs_in, fs_out, 1024, 2, channels).unwrap()) } + #[cfg(feature = "fft_resampler")] "FftFixedOut" => { Box::new(FftFixedOut::::new(fs_in, fs_out, 1024, 2, channels).unwrap()) } + #[cfg(feature = "fft_resampler")] "FftFixedInOut" => { Box::new(FftFixedInOut::::new(fs_in, fs_out, 1024, channels).unwrap()) } diff --git a/src/lib.rs b/src/lib.rs index 2ba72c0..749fe20 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -59,6 +59,11 @@ //! //! # Cargo features //! +//! ## `fft_resampler`: Enable the FFT based synchronous resamplers +//! +//! This feature is enabled by default. Disable it if the FFT resamplers are not needed, +//! to save compile time and reduce the resulting binary size. +//! //! ## `log`: Enable logging //! //! This feature enables logging via the `log` crate. This is intended for debugging purposes. @@ -115,6 +120,9 @@ //! //! # Changelog //! +//! - v0.15.0 +//! - Make FFT resamplers optional via `fft_resampler` feature. +//! - Fix calculation of input and output sizes when creating FftFixedInOut resampler. //! - v0.14.1 //! - More bugfixes for buffer allocation and max output length calculation. //! - Fix building with `log` feature. @@ -187,6 +195,7 @@ mod error; mod interpolation; mod sample; mod sinc; +#[cfg(feature = "fft_resampler")] mod synchro; mod windows; @@ -200,6 +209,7 @@ pub use crate::error::{ CpuFeature, MissingCpuFeature, ResampleError, ResampleResult, ResamplerConstructionError, }; pub use crate::sample::Sample; +#[cfg(feature = "fft_resampler")] pub use crate::synchro::{FftFixedIn, FftFixedInOut, FftFixedOut}; pub use crate::windows::{calculate_cutoff, WindowFunction}; @@ -427,8 +437,8 @@ use crate as rubato; /// `&[AsRef<[T]>]` and `&mut [AsMut<[T]>]` to `&[Vec]` and `&mut [Vec]`. /// This allows a [VecResampler] to be made into a trait object like this: /// ``` -/// # use rubato::{FftFixedIn, VecResampler}; -/// let boxed: Box> = Box::new(FftFixedIn::::new(44100, 88200, 1024, 2, 2).unwrap()); +/// # use rubato::{FastFixedIn, VecResampler, PolynomialDegree}; +/// let boxed: Box> = Box::new(FastFixedIn::::new(44100 as f64 / 88200 as f64, 1.1, PolynomialDegree::Cubic, 2, 2).unwrap()); /// ``` /// Use this implementation as an example if you need to fix the input type to something else. #[macro_export] @@ -689,21 +699,31 @@ pub fn buffer_capacity(buffer: &[Vec]) -> usize { #[cfg(test)] pub mod tests { use crate::{buffer_capacity, buffer_length, make_buffer, resize_buffer, VecResampler}; + use crate::{FastFixedIn, PolynomialDegree, SincFixedIn, SincFixedOut}; + #[cfg(feature = "fft_resampler")] use crate::{FftFixedIn, FftFixedInOut, FftFixedOut}; - use crate::{SincFixedIn, SincFixedOut}; // This tests that a VecResampler can be boxed. #[test] fn boxed_resampler() { - let boxed: Box> = - Box::new(FftFixedIn::::new(44100, 88200, 1024, 2, 2).unwrap()); - let result = process_with_boxed(boxed); + let mut boxed: Box> = Box::new( + FastFixedIn::::new( + 88200 as f64 / 44100 as f64, + 1.1, + PolynomialDegree::Cubic, + 1024, + 2, + ) + .unwrap(), + ); + let _ = process_with_boxed(&mut boxed); + let result = process_with_boxed(&mut boxed); assert_eq!(result.len(), 2); assert_eq!(result[0].len(), 2048); assert_eq!(result[1].len(), 2048); } - fn process_with_boxed(mut resampler: Box>) -> Vec> { + fn process_with_boxed(resampler: &mut Box>) -> Vec> { let frames = resampler.input_frames_next(); let waves = vec![vec![0.0f64; frames]; 2]; resampler.process(&waves, None).unwrap() @@ -713,9 +733,12 @@ pub mod tests { fn is_send() {} is_send::>(); is_send::>(); - is_send::>(); - is_send::>(); - is_send::>(); + #[cfg(feature = "fft_resampler")] + { + is_send::>(); + is_send::>(); + is_send::>(); + } } // This tests that all resamplers are Send. diff --git a/src/sample.rs b/src/sample.rs index e318cbc..5b5c591 100644 --- a/src/sample.rs +++ b/src/sample.rs @@ -1,5 +1,19 @@ use crate::sinc_interpolator::{AvxSample, NeonSample, SseSample}; +#[cfg(feature = "fft_resampler")] +use realfft::FftNum; + +#[cfg(not(feature = "fft_resampler"))] +use num_traits::{FromPrimitive, Signed}; +#[cfg(not(feature = "fft_resampler"))] +use std::fmt::Debug; + +#[cfg(not(feature = "fft_resampler"))] +pub trait FftNum: Copy + FromPrimitive + Signed + Sync + Send + Debug + 'static {} + +#[cfg(not(feature = "fft_resampler"))] +impl FftNum for T where T: Copy + FromPrimitive + Signed + Sync + Send + Debug + 'static {} + /// The trait governing a single sample. /// /// There are two types which implements this trait so far: @@ -11,7 +25,7 @@ where + CoerceFrom + CoerceFrom + CoerceFrom - + realfft::FftNum + + FftNum + std::ops::Mul + std::ops::Div + std::ops::Add From 1fb7cb83e7d8f125dc61d4bb843838022462c42c Mon Sep 17 00:00:00 2001 From: Henrik Date: Mon, 26 Feb 2024 21:56:07 +0100 Subject: [PATCH 3/6] num-complex also optional --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4b1e8c6..9021207 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,12 +15,12 @@ edition = "2021" [features] default = ["fft_resampler"] -fft_resampler = ["realfft"] +fft_resampler = ["realfft", "num-complex"] [dependencies] log = { version = "0.4.18", optional = true } realfft = { version = "3.3.0", optional = true } -num-complex = "0.4" +num-complex = { version = "0.4", optional = true } num-integer = "0.1.45" num-traits = "0.2" From dacae00b36c49fc3a09dedf9677d3cc031eba9a1 Mon Sep 17 00:00:00 2001 From: Henrik Date: Tue, 27 Feb 2024 23:07:23 +0100 Subject: [PATCH 4/6] Improved tests, fix panic for tiny chunksize --- README.md | 1 + src/asynchro_fast.rs | 119 +++++++++++++++++----- src/asynchro_sinc.rs | 231 ++++++++++++++++++++++--------------------- src/lib.rs | 40 +++++++- src/synchro.rs | 6 +- 5 files changed, 254 insertions(+), 143 deletions(-) diff --git a/README.md b/README.md index 9ecd133..470c710 100644 --- a/README.md +++ b/README.md @@ -126,6 +126,7 @@ The `rubato` crate requires rustc version 1.61 or newer. - v0.15.0 - Make FFT resamplers optional via `fft_resampler` feature. - Fix calculation of input and output sizes when creating FftFixedInOut resampler. + - Fix panic when using very small chunksizes (less than 5). - v0.14.1 - More bugfixes for buffer allocation and max output length calculation. - Fix building with `log` feature. diff --git a/src/asynchro_fast.rs b/src/asynchro_fast.rs index 8334a1f..e59f89e 100644 --- a/src/asynchro_fast.rs +++ b/src/asynchro_fast.rs @@ -528,7 +528,7 @@ where validate_ratios(resample_ratio, max_resample_ratio_relative)?; let needed_input_size = - (chunk_size as f64 / resample_ratio).ceil() as usize + 2 + POLYNOMIAL_LEN_U / 2; + (chunk_size as f64 / resample_ratio).ceil() as usize + POLYNOMIAL_LEN_U / 2; let buffer_channel_length = ((max_resample_ratio_relative + 1.0) * needed_input_size as f64) as usize + 2 * POLYNOMIAL_LEN_U; @@ -601,7 +601,7 @@ where match self.interpolation { PolynomialDegree::Septic => { - for n in 0..self.chunk_size { + for frame in 0..self.chunk_size { t_ratio += t_ratio_increment; idx += t_ratio; let idx_floor = idx.floor(); @@ -618,14 +618,14 @@ where *wave_out .get_unchecked_mut(chan) .as_mut() - .get_unchecked_mut(n) = interp_septic(frac_offset, buf); + .get_unchecked_mut(frame) = interp_septic(frac_offset, buf); } } } } } PolynomialDegree::Quintic => { - for n in 0..self.chunk_size { + for frame in 0..self.chunk_size { t_ratio += t_ratio_increment; idx += t_ratio; let idx_floor = idx.floor(); @@ -642,14 +642,14 @@ where *wave_out .get_unchecked_mut(chan) .as_mut() - .get_unchecked_mut(n) = interp_quintic(frac_offset, buf); + .get_unchecked_mut(frame) = interp_quintic(frac_offset, buf); } } } } } PolynomialDegree::Cubic => { - for n in 0..self.chunk_size { + for frame in 0..self.chunk_size { t_ratio += t_ratio_increment; idx += t_ratio; let idx_floor = idx.floor(); @@ -666,14 +666,14 @@ where *wave_out .get_unchecked_mut(chan) .as_mut() - .get_unchecked_mut(n) = interp_cubic(frac_offset, buf); + .get_unchecked_mut(frame) = interp_cubic(frac_offset, buf); } } } } } PolynomialDegree::Linear => { - for n in 0..self.chunk_size { + for frame in 0..self.chunk_size { t_ratio += t_ratio_increment; idx += t_ratio; let idx_floor = idx.floor(); @@ -690,14 +690,14 @@ where *wave_out .get_unchecked_mut(chan) .as_mut() - .get_unchecked_mut(n) = interp_lin(frac_offset, buf); + .get_unchecked_mut(frame) = interp_lin(frac_offset, buf); } } } } } PolynomialDegree::Nearest => { - for n in 0..self.chunk_size { + for frame in 0..self.chunk_size { t_ratio += t_ratio_increment; idx += t_ratio; let start_idx = idx.floor() as isize; @@ -711,7 +711,7 @@ where *wave_out .get_unchecked_mut(chan) .as_mut() - .get_unchecked_mut(n) = *point; + .get_unchecked_mut(frame) = *point; } } } @@ -726,8 +726,7 @@ where self.needed_input_size = (self.last_index as f32 + self.chunk_size as f32 / self.resample_ratio as f32 + POLYNOMIAL_LEN_U as f32) - .ceil() as usize - + 2; + .ceil() as usize; trace!( "Resampling channels {:?}, {} frames in, {} frames out. Next needed length: {} frames, last index {}", active_channels_mask, @@ -740,7 +739,7 @@ where } fn input_frames_max(&self) -> usize { - (self.chunk_size as f64 * self.resample_ratio_original * self.max_relative_ratio).ceil() + (self.chunk_size as f64 / self.resample_ratio_original * self.max_relative_ratio).ceil() as usize + 2 + POLYNOMIAL_LEN_U / 2 @@ -779,8 +778,7 @@ where + self.chunk_size as f32 / (0.5 * self.resample_ratio as f32 + 0.5 * self.target_ratio as f32)) .ceil() as usize - + POLYNOMIAL_LEN_U - + 2; + + POLYNOMIAL_LEN_U; Ok(()) } else { Err(ResampleError::RatioOutOfBounds { @@ -802,7 +800,6 @@ where .for_each(|ch| ch.iter_mut().for_each(|s| *s = T::zero())); self.needed_input_size = (self.chunk_size as f64 / self.resample_ratio_original).ceil() as usize - + 2 + POLYNOMIAL_LEN_U / 2; self.current_buffer_fill = self.needed_input_size; self.last_index = -(POLYNOMIAL_LEN_I / 2) as f64; @@ -814,9 +811,9 @@ where #[cfg(test)] mod tests { - use crate::check_output; use crate::PolynomialDegree; use crate::Resampler; + use crate::{check_output, check_ratio}; use crate::{FastFixedIn, FastFixedOut}; use rand::Rng; @@ -1140,16 +1137,94 @@ mod tests { } #[test] - fn check_fo_output() { + fn check_fo_output_up() { let mut resampler = FastFixedOut::::new(8.0, 1.0, PolynomialDegree::Cubic, 1024, 2).unwrap(); - check_output!(check_fo_output, resampler); + check_output!(resampler); } #[test] - fn check_fi_output() { + fn check_fo_output_down() { + let mut resampler = + FastFixedOut::::new(0.8, 1.0, PolynomialDegree::Cubic, 1024, 2).unwrap(); + check_output!(resampler); + } + + #[test] + fn check_fi_output_up() { let mut resampler = FastFixedIn::::new(8.0, 1.0, PolynomialDegree::Cubic, 1024, 2).unwrap(); - check_output!(check_fo_output, resampler); + check_output!(resampler); + } + + #[test] + fn check_fi_output_down() { + let mut resampler = + FastFixedIn::::new(0.8, 1.0, PolynomialDegree::Cubic, 1024, 2).unwrap(); + check_output!(resampler); + } + + #[test] + fn resample_small_fo_up() { + let ratio = 96000.0 / 44100.0; + let mut resampler = + FastFixedOut::::new(ratio, 100.0, PolynomialDegree::Cubic, 1, 2).unwrap(); + check_ratio!(resampler, ratio, 1000000); + } + + #[test] + fn resample_big_fo_up() { + let ratio = 96000.0 / 44100.0; + let mut resampler = + FastFixedOut::::new(ratio, 100.0, PolynomialDegree::Cubic, 1024, 2).unwrap(); + check_ratio!(resampler, ratio, 1000); + } + + #[test] + fn resample_small_fo_down() { + let ratio = 44100.0 / 96000.0; + let mut resampler = + FastFixedOut::::new(ratio, 100.0, PolynomialDegree::Cubic, 1, 2).unwrap(); + check_ratio!(resampler, ratio, 1000000); + } + + #[test] + fn resample_big_fo_down() { + let ratio = 44100.0 / 96000.0; + let mut resampler = + FastFixedOut::::new(ratio, 100.0, PolynomialDegree::Cubic, 1024, 2).unwrap(); + check_ratio!(resampler, ratio, 1000); + } + + #[test] + fn resample_small_fi_up() { + let ratio = 96000.0 / 44100.0; + let mut resampler = + FastFixedIn::::new(ratio, 100.0, PolynomialDegree::Cubic, 1, 2).unwrap(); + check_ratio!(resampler, ratio, 1000000); + } + + #[test] + fn resample_big_fi_up() { + let ratio = 96000.0 / 44100.0; + let mut resampler = + FastFixedIn::::new(ratio, 100.0, PolynomialDegree::Cubic, 1024, 2).unwrap(); + check_ratio!(resampler, ratio, 1000); + } + + #[test] + fn resample_small_fi_down() { + let ratio = 44100.0 / 96000.0; + let mut resampler = + FastFixedIn::::new(ratio, 100.0, PolynomialDegree::Cubic, 1, 2).unwrap(); + check_ratio!(resampler, ratio, 1000000); + } + + #[test] + fn resample_big_fi_down() { + let ratio = 44100.0 / 96000.0; + let mut resampler = + FastFixedIn::::new(ratio, 100.0, PolynomialDegree::Cubic, 1024, 2).unwrap(); + check_ratio!(resampler, ratio, 1000); } } diff --git a/src/asynchro_sinc.rs b/src/asynchro_sinc.rs index 3d00700..1a56092 100644 --- a/src/asynchro_sinc.rs +++ b/src/asynchro_sinc.rs @@ -600,7 +600,7 @@ where validate_ratios(resample_ratio, max_resample_ratio_relative)?; let needed_input_size = - (chunk_size as f64 / resample_ratio).ceil() as usize + 2 + interpolator.len() / 2; + (chunk_size as f64 / resample_ratio).ceil() as usize + interpolator.len() / 2; let buffer_channel_length = ((max_resample_ratio_relative + 1.0) * needed_input_size as f64) as usize + 2 * interpolator.len(); @@ -677,7 +677,7 @@ where SincInterpolationType::Cubic => { let mut points = [T::zero(); 4]; let mut nearest = [(0isize, 0isize); 4]; - for n in 0..self.chunk_size { + for frame in 0..self.chunk_size { t_ratio += t_ratio_increment; idx += t_ratio; get_nearest_times_4(idx, oversampling_factor as isize, &mut nearest); @@ -694,7 +694,7 @@ where n.1 as usize, ); } - wave_out[chan].as_mut()[n] = interp_cubic(frac_offset, &points); + wave_out[chan].as_mut()[frame] = interp_cubic(frac_offset, &points); } } } @@ -702,7 +702,7 @@ where SincInterpolationType::Quadratic => { let mut points = [T::zero(); 3]; let mut nearest = [(0isize, 0isize); 3]; - for n in 0..self.chunk_size { + for frame in 0..self.chunk_size { t_ratio += t_ratio_increment; idx += t_ratio; get_nearest_times_3(idx, oversampling_factor as isize, &mut nearest); @@ -719,7 +719,7 @@ where n.1 as usize, ); } - wave_out[chan].as_mut()[n] = interp_quad(frac_offset, &points); + wave_out[chan].as_mut()[frame] = interp_quad(frac_offset, &points); } } } @@ -727,7 +727,7 @@ where SincInterpolationType::Linear => { let mut points = [T::zero(); 2]; let mut nearest = [(0isize, 0isize); 2]; - for n in 0..self.chunk_size { + for frame in 0..self.chunk_size { t_ratio += t_ratio_increment; idx += t_ratio; get_nearest_times_2(idx, oversampling_factor as isize, &mut nearest); @@ -744,7 +744,7 @@ where n.1 as usize, ); } - wave_out[chan].as_mut()[n] = interp_lin(frac_offset, &points); + wave_out[chan].as_mut()[frame] = interp_lin(frac_offset, &points); } } } @@ -752,7 +752,7 @@ where SincInterpolationType::Nearest => { let mut point; let mut nearest; - for n in 0..self.chunk_size { + for frame in 0..self.chunk_size { t_ratio += t_ratio_increment; idx += t_ratio; nearest = get_nearest_time(idx, oversampling_factor as isize); @@ -764,7 +764,7 @@ where (nearest.0 + 2 * sinc_len as isize) as usize, nearest.1 as usize, ); - wave_out[chan].as_mut()[n] = point; + wave_out[chan].as_mut()[frame] = point; } } } @@ -778,8 +778,7 @@ where self.needed_input_size = (self.last_index as f32 + self.chunk_size as f32 / self.resample_ratio as f32 + sinc_len as f32) - .ceil() as usize - + 2; + .ceil() as usize; trace!( "Resampling channels {:?}, {} frames in, {} frames out. Next needed length: {} frames, last index {}", active_channels_mask, @@ -792,7 +791,7 @@ where } fn input_frames_max(&self) -> usize { - (self.chunk_size as f64 * self.resample_ratio_original * self.max_relative_ratio).ceil() + (self.chunk_size as f64 / self.resample_ratio_original * self.max_relative_ratio).ceil() as usize + 2 + self.interpolator.len() / 2 @@ -832,8 +831,7 @@ where + self.chunk_size as f32 / (0.5 * self.resample_ratio as f32 + 0.5 * self.target_ratio as f32) + self.interpolator.len() as f32) - .ceil() as usize - + 2; + .ceil() as usize; Ok(()) } else { Err(ResampleError::RatioOutOfBounds { @@ -855,7 +853,6 @@ where .for_each(|ch| ch.iter_mut().for_each(|s| *s = T::zero())); self.needed_input_size = (self.chunk_size as f64 / self.resample_ratio_original).ceil() as usize - + 2 + self.interpolator.len() / 2; self.current_buffer_fill = self.needed_input_size; self.last_index = -((self.interpolator.len() / 2) as f64); @@ -868,23 +865,27 @@ where #[cfg(test)] mod tests { use super::{interp_cubic, interp_lin}; - use crate::check_output; use crate::Resampler; use crate::SincInterpolationParameters; use crate::SincInterpolationType; use crate::WindowFunction; + use crate::{check_output, check_ratio}; use crate::{SincFixedIn, SincFixedOut}; use rand::Rng; - #[test] - fn int_cubic() { - let params = SincInterpolationParameters { + fn basic_params() -> SincInterpolationParameters { + SincInterpolationParameters { sinc_len: 64, f_cutoff: 0.95, interpolation: SincInterpolationType::Cubic, oversampling_factor: 16, window: WindowFunction::BlackmanHarris2, - }; + } + } + + #[test] + fn int_cubic() { + let params = basic_params(); let _resampler = SincFixedIn::::new(1.2, 1.0, params, 1024, 2).unwrap(); let yvals = [0.0f64, 2.0f64, 4.0f64, 6.0f64]; let interp = interp_cubic(0.5f64, &yvals); @@ -893,13 +894,7 @@ mod tests { #[test] fn int_lin_32() { - let params = SincInterpolationParameters { - sinc_len: 64, - f_cutoff: 0.95, - interpolation: SincInterpolationType::Cubic, - oversampling_factor: 16, - window: WindowFunction::BlackmanHarris2, - }; + let params = basic_params(); let _resampler = SincFixedIn::::new(1.2, 1.0, params, 1024, 2).unwrap(); let yvals = [1.0f32, 5.0f32]; let interp = interp_lin(0.25f32, &yvals); @@ -908,13 +903,7 @@ mod tests { #[test] fn int_cubic_32() { - let params = SincInterpolationParameters { - sinc_len: 64, - f_cutoff: 0.95, - interpolation: SincInterpolationType::Cubic, - oversampling_factor: 16, - window: WindowFunction::BlackmanHarris2, - }; + let params = basic_params(); let _resampler = SincFixedIn::::new(1.2, 1.0, params, 1024, 2).unwrap(); let yvals = [0.0f32, 2.0f32, 4.0f32, 6.0f32]; let interp = interp_cubic(0.5f32, &yvals); @@ -923,13 +912,7 @@ mod tests { #[test] fn int_lin() { - let params = SincInterpolationParameters { - sinc_len: 64, - f_cutoff: 0.95, - interpolation: SincInterpolationType::Cubic, - oversampling_factor: 16, - window: WindowFunction::BlackmanHarris2, - }; + let params = basic_params(); let _resampler = SincFixedIn::::new(1.2, 1.0, params, 1024, 2).unwrap(); let yvals = [1.0f64, 5.0f64]; let interp = interp_lin(0.25f64, &yvals); @@ -938,13 +921,7 @@ mod tests { #[test] fn make_resampler_fi() { - let params = SincInterpolationParameters { - sinc_len: 64, - f_cutoff: 0.95, - interpolation: SincInterpolationType::Cubic, - oversampling_factor: 16, - window: WindowFunction::BlackmanHarris2, - }; + let params = basic_params(); let mut resampler = SincFixedIn::::new(1.2, 1.0, params, 1024, 2).unwrap(); let waves = vec![vec![0.0f64; 1024]; 2]; let out = resampler.process(&waves, None).unwrap(); @@ -969,13 +946,7 @@ mod tests { #[test] fn reset_resampler_fi() { - let params = SincInterpolationParameters { - sinc_len: 64, - f_cutoff: 0.95, - interpolation: SincInterpolationType::Cubic, - oversampling_factor: 16, - window: WindowFunction::BlackmanHarris2, - }; + let params = basic_params(); let mut resampler = SincFixedIn::::new(1.2, 1.0, params, 1024, 2).unwrap(); let mut rng = rand::thread_rng(); @@ -994,13 +965,7 @@ mod tests { #[test] fn make_resampler_fi_32() { - let params = SincInterpolationParameters { - sinc_len: 64, - f_cutoff: 0.95, - interpolation: SincInterpolationType::Cubic, - oversampling_factor: 16, - window: WindowFunction::BlackmanHarris2, - }; + let params = basic_params(); let mut resampler = SincFixedIn::::new(1.2, 1.0, params, 1024, 2).unwrap(); let waves = vec![vec![0.0f32; 1024]; 2]; let out = resampler.process(&waves, None).unwrap(); @@ -1025,13 +990,7 @@ mod tests { #[test] fn make_resampler_fi_skipped() { - let params = SincInterpolationParameters { - sinc_len: 64, - f_cutoff: 0.95, - interpolation: SincInterpolationType::Cubic, - oversampling_factor: 16, - window: WindowFunction::BlackmanHarris2, - }; + let params = basic_params(); let mut resampler = SincFixedIn::::new(1.2, 1.0, params, 1024, 2).unwrap(); let waves = vec![vec![0.0f64; 1024], Vec::new()]; let mask = vec![true, false]; @@ -1115,13 +1074,7 @@ mod tests { #[test] fn make_resampler_fo() { - let params = SincInterpolationParameters { - sinc_len: 64, - f_cutoff: 0.95, - interpolation: SincInterpolationType::Cubic, - oversampling_factor: 16, - window: WindowFunction::BlackmanHarris2, - }; + let params = basic_params(); let mut resampler = SincFixedOut::::new(1.2, 1.0, params, 1024, 2).unwrap(); let frames = resampler.input_frames_next(); println!("{}", frames); @@ -1134,13 +1087,7 @@ mod tests { #[test] fn reset_resampler_fo() { - let params = SincInterpolationParameters { - sinc_len: 64, - f_cutoff: 0.95, - interpolation: SincInterpolationType::Cubic, - oversampling_factor: 16, - window: WindowFunction::BlackmanHarris2, - }; + let params = basic_params(); let mut resampler = SincFixedOut::::new(1.2, 1.0, params, 1024, 2).unwrap(); let frames = resampler.input_frames_next(); @@ -1165,13 +1112,7 @@ mod tests { #[test] fn make_resampler_fo_32() { - let params = SincInterpolationParameters { - sinc_len: 64, - f_cutoff: 0.95, - interpolation: SincInterpolationType::Cubic, - oversampling_factor: 16, - window: WindowFunction::BlackmanHarris2, - }; + let params = basic_params(); let mut resampler = SincFixedOut::::new(1.2, 1.0, params, 1024, 2).unwrap(); let frames = resampler.input_frames_next(); println!("{}", frames); @@ -1184,13 +1125,7 @@ mod tests { #[test] fn make_resampler_fo_skipped() { - let params = SincInterpolationParameters { - sinc_len: 64, - f_cutoff: 0.95, - interpolation: SincInterpolationType::Cubic, - oversampling_factor: 16, - window: WindowFunction::BlackmanHarris2, - }; + let params = basic_params(); let mut resampler = SincFixedOut::::new(1.2, 1.0, params, 1024, 2).unwrap(); let frames = resampler.input_frames_next(); println!("{}", frames); @@ -1318,28 +1253,94 @@ mod tests { } #[test] - fn check_fo_output() { - let params = SincInterpolationParameters { - sinc_len: 64, - f_cutoff: 0.95, - interpolation: SincInterpolationType::Cubic, - oversampling_factor: 16, - window: WindowFunction::BlackmanHarris2, - }; + fn check_fo_output_up() { + let params = basic_params(); let mut resampler = SincFixedOut::::new(1.2, 1.0, params, 1024, 2).unwrap(); - check_output!(check_fo_output, resampler); + check_output!(resampler); } #[test] - fn check_fi_output() { - let params = SincInterpolationParameters { - sinc_len: 64, - f_cutoff: 0.95, - interpolation: SincInterpolationType::Cubic, - oversampling_factor: 16, - window: WindowFunction::BlackmanHarris2, - }; + fn check_fo_output_down() { + let params = basic_params(); + let mut resampler = SincFixedOut::::new(0.8, 1.0, params, 1024, 2).unwrap(); + check_output!(resampler); + } + + #[test] + fn check_fi_output_up() { + let params = basic_params(); let mut resampler = SincFixedIn::::new(1.2, 1.0, params, 1024, 2).unwrap(); - check_output!(check_fo_output, resampler); + check_output!(resampler); + } + + #[test] + fn check_fi_output_down() { + let params = basic_params(); + let mut resampler = SincFixedIn::::new(0.8, 1.0, params, 1024, 2).unwrap(); + check_output!(resampler); + } + + #[test] + fn resample_small_fo_up() { + let ratio = 96000.0 / 44100.0; + let params = basic_params(); + let mut resampler = SincFixedOut::::new(ratio, 1.0, params, 1, 2).unwrap(); + check_ratio!(resampler, ratio, 100000); + } + + #[test] + fn resample_big_fo_up() { + let ratio = 96000.0 / 44100.0; + let params = basic_params(); + let mut resampler = SincFixedOut::::new(ratio, 1.0, params, 1024, 2).unwrap(); + check_ratio!(resampler, ratio, 100); + } + + #[test] + fn resample_small_fo_down() { + let ratio = 44100.0 / 96000.0; + let params = basic_params(); + let mut resampler = SincFixedOut::::new(ratio, 1.0, params, 1, 2).unwrap(); + check_ratio!(resampler, ratio, 100000); + } + + #[test] + fn resample_big_fo_down() { + let ratio = 44100.0 / 96000.0; + let params = basic_params(); + let mut resampler = SincFixedOut::::new(ratio, 1.0, params, 1024, 2).unwrap(); + check_ratio!(resampler, ratio, 100); + } + + #[test] + fn resample_small_fi_up() { + let ratio = 96000.0 / 44100.0; + let params = basic_params(); + let mut resampler = SincFixedIn::::new(ratio, 1.0, params, 1, 2).unwrap(); + check_ratio!(resampler, ratio, 100000); + } + + #[test] + fn resample_big_fi_up() { + let ratio = 96000.0 / 44100.0; + let params = basic_params(); + let mut resampler = SincFixedIn::::new(ratio, 1.0, params, 1024, 2).unwrap(); + check_ratio!(resampler, ratio, 100); + } + + #[test] + fn resample_small_fi_down() { + let ratio = 44100.0 / 96000.0; + let params = basic_params(); + let mut resampler = SincFixedIn::::new(ratio, 1.0, params, 1, 2).unwrap(); + check_ratio!(resampler, ratio, 100000); + } + + #[test] + fn resample_big_fi_down() { + let ratio = 44100.0 / 96000.0; + let params = basic_params(); + let mut resampler = SincFixedIn::::new(ratio, 1.0, params, 1024, 2).unwrap(); + check_ratio!(resampler, ratio, 100); } } diff --git a/src/lib.rs b/src/lib.rs index 749fe20..a4ee343 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -123,6 +123,7 @@ //! - v0.15.0 //! - Make FFT resamplers optional via `fft_resampler` feature. //! - Fix calculation of input and output sizes when creating FftFixedInOut resampler. +//! - Fix panic when using very small chunksizes (less than 5). //! - v0.14.1 //! - More bugfixes for buffer allocation and max output length calculation. //! - Fix building with `log` feature. @@ -750,7 +751,7 @@ pub mod tests { #[macro_export] macro_rules! check_output { - ($name:ident, $resampler:ident) => { + ($resampler:ident) => { let mut val = 0.0; let mut prev_last = -0.1; let max_input_len = $resampler.input_frames_max(); @@ -758,8 +759,21 @@ pub mod tests { for n in 0..50 { let frames = $resampler.input_frames_next(); // Check that lengths are within the reported max values - assert!(frames <= max_input_len); - assert!($resampler.output_frames_next() <= max_output_len); + assert!( + frames <= max_input_len, + "Iteration {}, input frames {} larger than max {}", + n, + frames, + max_input_len + ); + let out_frames = $resampler.output_frames_next(); + assert!( + out_frames <= max_output_len, + "Iteration {}, output frames {} larger than max {}", + n, + out_frames, + max_output_len + ); let mut waves = vec![vec![0.0f64; frames]; 2]; for m in 0..frames { for ch in 0..2 { @@ -805,6 +819,26 @@ pub mod tests { }; } + #[macro_export] + macro_rules! check_ratio { + ($resampler:ident, $ratio:ident, $repetitions:literal) => { + let input = $resampler.input_buffer_allocate(true); + let mut output = $resampler.output_buffer_allocate(true); + let mut total_in = 0; + let mut total_out = 0; + for _ in 0..$repetitions { + let out = $resampler + .process_into_buffer(&input, &mut output, None) + .unwrap(); + total_in += out.0; + total_out += out.1 + } + let measured_ratio = total_out as f64 / total_in as f64; + assert!(measured_ratio > 0.999 * $ratio); + assert!(measured_ratio < 1.001 * $ratio); + }; + } + #[test] fn test_buffer_helpers() { let buf1 = vec![vec![0.0f64; 7], vec![0.0f64; 5], vec![0.0f64; 10]]; diff --git a/src/synchro.rs b/src/synchro.rs index 05461c2..ea0df45 100644 --- a/src/synchro.rs +++ b/src/synchro.rs @@ -930,19 +930,19 @@ mod tests { #[test] fn check_fo_output() { let mut resampler = FftFixedOut::::new(44100, 48000, 4096, 4, 2).unwrap(); - check_output!(check_fo_output, resampler); + check_output!(resampler); } #[test] fn check_fi_output() { let mut resampler = FftFixedIn::::new(44100, 48000, 4096, 4, 2).unwrap(); - check_output!(check_fo_output, resampler); + check_output!(resampler); } #[test] fn check_fio_output() { let mut resampler = FftFixedInOut::::new(44100, 48000, 4096, 2).unwrap(); - check_output!(check_fo_output, resampler); + check_output!(resampler); } #[test] From d71f3b3f69b54e620f0256738f89664b780e7633 Mon Sep 17 00:00:00 2001 From: Henrik Date: Tue, 27 Feb 2024 23:26:48 +0100 Subject: [PATCH 5/6] Fix out of bounds panic in example --- examples/process_f64.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/process_f64.rs b/examples/process_f64.rs index 873332c..0807c74 100644 --- a/examples/process_f64.rs +++ b/examples/process_f64.rs @@ -64,7 +64,7 @@ fn write_frames( frames_to_write: usize, ) { let channels = waves.len(); - let end = frames_to_skip + frames_to_write; + let end = (frames_to_skip + frames_to_write).min(waves[0].len() - 1); for frame in frames_to_skip..end { for wave in waves.iter().take(channels) { let value64 = wave[frame]; From 421727182465bfe1eb6b9c7218fe5714004de3a2 Mon Sep 17 00:00:00 2001 From: Henrik Date: Tue, 5 Mar 2024 22:27:16 +0100 Subject: [PATCH 6/6] Fix typos, update test sine generator script --- examples/fastfixedin_ramp64.rs | 2 +- examples/makesineraw.py | 14 ++++++-------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/examples/fastfixedin_ramp64.rs b/examples/fastfixedin_ramp64.rs index 3e05ab5..c75c155 100644 --- a/examples/fastfixedin_ramp64.rs +++ b/examples/fastfixedin_ramp64.rs @@ -18,7 +18,7 @@ use log::LevelFilter; ///! To resample the file `sine_f64_2ch.raw` from 44.1kHz to 192kHz, and assuming the file has two channels, /// and that the resampling ratio should be ramped to 150% during 3 seconds, the command is: ///! ``` -///! cargo run --release --example fixedin_ramp64 sine_f64_2ch.raw test.raw 44100 192000 2 150 3 +///! cargo run --release --example fastfixedin_ramp64 sine_f64_2ch.raw test.raw 44100 192000 2 150 3 ///! ``` ///! There are two helper python scripts for testing. `makesineraw.py` simply writes a stereo file ///! with a 1 second long 1kHz tone (at 44.1kHz). This script takes no aruments. Modify as needed to create other test files. diff --git a/examples/makesineraw.py b/examples/makesineraw.py index ab0453a..7f9a59c 100644 --- a/examples/makesineraw.py +++ b/examples/makesineraw.py @@ -1,11 +1,9 @@ -# Make a simple spike for testing purposes +# Make a sine for testing purposes +# Store as interleaved stereo import numpy as np - -#wave64 = np.zeros((2,44100), dtype="float64") -#wave32 = np.zeros((2,44100), dtype="float32") -t = np.linspace(0, 1.0, num=int(1.0*44100), endpoint=False) -wave = np.sin(10000*2*np.pi*t) +t = np.linspace(0, 10.0, num=int(10.0*44100), endpoint=False) +wave = np.sin(1000*2*np.pi*t) wave= np.reshape(wave,(-1,1)) wave = np.concatenate((wave, wave), axis=1) @@ -14,7 +12,7 @@ #print(wave64) -wave64.tofile("sine_44.1_10000_f64_2ch_1.0s.raw") -wave32.tofile("sine_44.1_10000_f32_2ch_1.0s.raw") +wave64.tofile("sine_44.1_1000_f64_2ch_10.0s.raw") +wave32.tofile("sine_44.1_1000_f32_2ch_10.0s.raw")