diff --git a/README.md b/README.md index af31a3d..6c0de67 100644 --- a/README.md +++ b/README.md @@ -118,6 +118,8 @@ The `rubato` crate requires rustc version 1.61 or newer. ## Changelog +- v0.14.1 + - More bugfixes for buffer allocation and max output length calculation. - v0.14.0 - Add argument to let `input/output_buffer_allocate()` optionally pre-fill buffers with zeros. - Add convenience methods for managing buffers. diff --git a/src/lib.rs b/src/lib.rs index 99009e8..effbfcc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -116,6 +116,8 @@ //! //! # Changelog //! +//! - v0.14.1 +//! - More bugfixes for buffer allocation and max output length calculation. //! - v0.14.0 //! - Add argument to let `input/output_buffer_allocate()` optionally pre-fill buffers with zeros. //! - Add convenience methods for managing buffers. @@ -728,8 +730,13 @@ pub mod tests { ($name:ident, $resampler:ident) => { let mut val = 0.0; let mut prev_last = -0.1; - for n in 0..5 { + let max_input_len = $resampler.input_frames_max(); + let max_output_len = $resampler.output_frames_max(); + 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); let mut waves = vec![vec![0.0f64; frames]; 2]; for m in 0..frames { for ch in 0..2 { diff --git a/src/synchro.rs b/src/synchro.rs index ae29bb3..ec3bd56 100644 --- a/src/synchro.rs +++ b/src/synchro.rs @@ -642,8 +642,10 @@ where } fn output_frames_max(&self) -> usize { - (2.0 * self.chunk_size_in as f32 / self.fft_size_in as f32).floor() as usize - * self.fft_size_out + let max_stored_frames = self.fft_size_in - 1; + let max_available_frames = max_stored_frames + self.chunk_size_in; + let max_subchunks_to_process = max_available_frames / self.fft_size_in; + max_subchunks_to_process * self.fft_size_out } fn output_frames_next(&self) -> usize { @@ -943,4 +945,93 @@ mod tests { let mut resampler = FftFixedInOut::::new(44100, 48000, 4096, 2).unwrap(); check_output!(check_fo_output, resampler); } + + #[test] + fn check_fi_max_output_length() { + // parameters: + // - rate in + // - rate out + // - requested chunksize + // - requested number of subchunks + // - expected fft input length + // - expected fft output length + let params_to_test = [ + // fft sizes < chunksize + [44100, 48000, 1024, 4, 294, 320], + [48000, 44100, 1024, 4, 320, 294], + // fft sizes << chunksize + [44000, 48000, 1024, 100, 11, 12], + // fft sizes > chunksize + [32728, 32000, 1024, 4, 4091, 4000], + [32000, 32728, 1024, 4, 4000, 4091], + // fft sizes >> chunksize + [37199, 39119, 1024, 4, 37199, 39119], + [39119, 37199, 1024, 4, 39119, 37199], + ]; + for params in params_to_test { + println!("params: {:?}", params); + let [rate_in, rate_out, chunksize, subchunks, fft_in_len, fft_out_len] = params; + let resampler = + FftFixedIn::::new(rate_in, rate_out, chunksize, subchunks, 1).unwrap(); + assert_eq!(resampler.fft_size_in, fft_in_len); + assert_eq!(resampler.fft_size_out, fft_out_len); + let resampler_max_output_len = resampler.output_frames_max(); + println!( + "Resampler reports max output length: {}", + resampler_max_output_len + ); + assert!(resampler.output_frames_max() >= fft_out_len); + // expected length + let max_stored_frames = fft_in_len - 1; + let max_available_samples = max_stored_frames + chunksize; + let max_subchunks_to_process = max_available_samples / fft_in_len; + let expected_max_out_len = max_subchunks_to_process * fft_out_len; + println!("Max stored frames: {}, max avail frames: {}, max ready subchunks: {}, expected max output len: {}", max_stored_frames, max_available_samples, max_subchunks_to_process, expected_max_out_len); + assert_eq!(resampler.output_frames_max(), expected_max_out_len); + } + } + + #[test] + fn check_fo_max_input_length() { + // parameters: + // - rate in + // - rate out + // - requested chunksize + // - requested number of subchunks + // - expected fft input length + // - expected fft output length + let params_to_test = [ + // fft sizes < chunksize + [44100, 48000, 1024, 4, 294, 320], + [48000, 44100, 1024, 4, 320, 294], + // fft sizes << chunksize + [44000, 48000, 1024, 100, 11, 12], + // fft sizes > chunksize + [32728, 32000, 1024, 4, 4091, 4000], + [32000, 32728, 1024, 4, 4000, 4091], + // fft sizes >> chunksize + [37199, 39119, 1024, 4, 37199, 39119], + [39119, 37199, 1024, 4, 39119, 37199], + ]; + for params in params_to_test { + println!("params: {:?}", params); + let [rate_in, rate_out, chunksize, subchunks, fft_in_len, fft_out_len] = params; + let resampler = + FftFixedOut::::new(rate_in, rate_out, chunksize, subchunks, 1).unwrap(); + assert_eq!(resampler.fft_size_in, fft_in_len); + assert_eq!(resampler.fft_size_out, fft_out_len); + let resampler_max_input_len = resampler.input_frames_max(); + println!( + "Resampler reports max input length: {}", + resampler_max_input_len + ); + assert!(resampler.input_frames_max() >= fft_in_len); + // max needed is when we have none stored + let max_frames_needed = chunksize; + let max_subchunks_needed = (max_frames_needed as f32 / fft_out_len as f32).ceil() as usize; + let expected_max_in_len = max_subchunks_needed * fft_in_len; + println!("Max frames needed: {}, max subchunks_needed: {}, expected max input len: {}", max_frames_needed, max_subchunks_needed, expected_max_in_len); + assert_eq!(resampler.input_frames_max(), expected_max_in_len); + } + } }