From a044b929192ba4e23080e457c81a57c3a88a4fa9 Mon Sep 17 00:00:00 2001 From: dvdsk Date: Sun, 22 Dec 2024 12:15:22 +0100 Subject: [PATCH 1/6] adds split in fast and hifi resampler module --- Cargo.lock | 77 ++++ Cargo.toml | 3 +- src/conversions/mod.rs | 13 +- src/conversions/sample_rate.rs | 401 +------------------- src/conversions/sample_rate/fast_inhouse.rs | 400 +++++++++++++++++++ src/conversions/sample_rate/hifi_rubato.rs | 60 +++ src/source/uniform.rs | 3 +- 7 files changed, 548 insertions(+), 409 deletions(-) create mode 100644 src/conversions/sample_rate/fast_inhouse.rs create mode 100644 src/conversions/sample_rate/hifi_rubato.rs diff --git a/Cargo.lock b/Cargo.lock index 636ea8cc..d94de360 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -671,6 +671,15 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + [[package]] name = "num-derive" version = "0.4.2" @@ -797,6 +806,15 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "primal-check" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc0d895b311e3af9902528fbb8f928688abbd95872819320517cc24ca6b2bd08" +dependencies = [ + "num-integer", +] + [[package]] name = "proc-macro-crate" version = "3.2.0" @@ -865,6 +883,15 @@ dependencies = [ "getrandom", ] +[[package]] +name = "realfft" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "390252372b7f2aac8360fc5e72eba10136b166d6faeed97e6d0c8324eb99b2b1" +dependencies = [ + "rustfft", +] + [[package]] name = "regex" version = "1.11.1" @@ -925,6 +952,7 @@ dependencies = [ "rand", "rstest", "rstest_reuse", + "rubato", "symphonia", "tracing", ] @@ -970,6 +998,18 @@ dependencies = [ "syn", ] +[[package]] +name = "rubato" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdd96992d7e24b3d7f35fdfe02af037a356ac90d41b466945cf3333525a86eea" +dependencies = [ + "num-complex", + "num-integer", + "num-traits", + "realfft", +] + [[package]] name = "rustc-hash" version = "1.1.0" @@ -985,6 +1025,21 @@ dependencies = [ "semver", ] +[[package]] +name = "rustfft" +version = "6.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43806561bc506d0c5d160643ad742e3161049ac01027b5e6d7524091fd401d86" +dependencies = [ + "num-complex", + "num-integer", + "num-traits", + "primal-check", + "strength_reduce", + "transpose", + "version_check", +] + [[package]] name = "rustix" version = "0.38.41" @@ -1039,6 +1094,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "strength_reduce" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe895eb47f22e2ddd4dabc02bce419d2e643c8e3b585c78158b349195bc24d82" + [[package]] name = "symphonia" version = "0.5.4" @@ -1299,12 +1360,28 @@ dependencies = [ "once_cell", ] +[[package]] +name = "transpose" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad61aed86bc3faea4300c7aee358b4c6d0c8d6ccc36524c96e4c92ccf26e77e" +dependencies = [ + "num-integer", + "strength_reduce", +] + [[package]] name = "unicode-ident" version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + [[package]] name = "walkdir" version = "2.5.0" diff --git a/Cargo.toml b/Cargo.toml index 599cb059..6d34530b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ lewton = { version = "0.10", optional = true } minimp3_fixed = { version = "0.5.4", optional = true} symphonia = { version = "0.5.4", optional = true, default-features = false } crossbeam-channel = { version = "0.5.8", optional = true } +rubato = { version = "0.16.1", optional = true } rand = { version = "0.8.5", features = ["small_rng"], optional = true } tracing = { version = "0.1.40", optional = true } @@ -25,7 +26,7 @@ atomic_float = { version = "1.1.0", optional = true } num-rational = "0.4.2" [features] -default = ["flac", "vorbis", "wav", "mp3"] +default = ["flac", "vorbis", "wav", "mp3", "rubato"] tracing = ["dep:tracing"] experimental = ["dep:atomic_float"] diff --git a/src/conversions/mod.rs b/src/conversions/mod.rs index e6ad817e..16879114 100644 --- a/src/conversions/mod.rs +++ b/src/conversions/mod.rs @@ -5,13 +5,10 @@ This includes conversion between sample formats, channels or sample rates. */ -pub use self::channels::ChannelCountConverter; -pub use self::sample::DataConverter; -pub use self::sample::Sample; -pub use self::sample_rate::SampleRateConverter; +pub use channels::ChannelCountConverter; +pub use sample::DataConverter; +pub use sample::Sample; mod channels; -// TODO: < shouldn't be public ; there's a bug in Rust 1.4 and below that makes This -// `pub` mandatory -pub mod sample; -mod sample_rate; +mod sample; +pub(crate) mod sample_rate; diff --git a/src/conversions/sample_rate.rs b/src/conversions/sample_rate.rs index b76c23d0..d1f54963 100644 --- a/src/conversions/sample_rate.rs +++ b/src/conversions/sample_rate.rs @@ -1,400 +1,3 @@ -use crate::conversions::Sample; +pub(crate) mod fast_inhouse; +pub(crate) mod hifi_rubato; -use num_rational::Ratio; -use std::mem; - -/// Iterator that converts from a certain sample rate to another. -#[derive(Clone, Debug)] -pub struct SampleRateConverter -where - I: Iterator, -{ - /// The iterator that gives us samples. - input: I, - /// We convert chunks of `from` samples into chunks of `to` samples. - from: u32, - /// We convert chunks of `from` samples into chunks of `to` samples. - to: u32, - /// Number of channels in the stream - channels: cpal::ChannelCount, - /// One sample per channel, extracted from `input`. - current_frame: Vec, - /// Position of `current_sample` modulo `from`. - current_frame_pos_in_chunk: u32, - /// The samples right after `current_sample` (one per channel), extracted from `input`. - next_frame: Vec, - /// The position of the next sample that the iterator should return, modulo `to`. - /// This counter is incremented (modulo `to`) every time the iterator is called. - next_output_frame_pos_in_chunk: u32, - /// The buffer containing the samples waiting to be output. - output_buffer: Vec, -} - -impl SampleRateConverter -where - I: Iterator, - I::Item: Sample, -{ - /// Create new sample rate converter. - /// - /// The converter uses simple linear interpolation for up-sampling - /// and discards samples for down-sampling. This may introduce audible - /// distortions in some cases (see [#584](https://github.com/RustAudio/rodio/issues/584)). - /// - /// # Limitations - /// Some rate conversions where target rate is high and rates are mutual primes the sample - /// interpolation may cause numeric overflows. Conversion between usual sample rates - /// 2400, 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000, ... is expected to work. - /// - /// # Panic - /// Panics if `from`, `to` or `num_channels` are 0. - #[inline] - pub fn new( - mut input: I, - from: cpal::SampleRate, - to: cpal::SampleRate, - num_channels: cpal::ChannelCount, - ) -> SampleRateConverter { - let from = from.0; - let to = to.0; - - assert!(num_channels >= 1); - assert!(from >= 1); - assert!(to >= 1); - - let (first_samples, next_samples) = if from == to { - // if `from` == `to` == 1, then we just pass through - (Vec::new(), Vec::new()) - } else { - let first = input - .by_ref() - .take(num_channels as usize) - .collect::>(); - let next = input - .by_ref() - .take(num_channels as usize) - .collect::>(); - (first, next) - }; - - // Reducing numerator to avoid numeric overflows during interpolation. - let (to, from) = Ratio::new(to, from).into_raw(); - - SampleRateConverter { - input, - from, - to, - channels: num_channels, - current_frame_pos_in_chunk: 0, - next_output_frame_pos_in_chunk: 0, - current_frame: first_samples, - next_frame: next_samples, - output_buffer: Vec::with_capacity(num_channels as usize - 1), - } - } - - /// Destroys this iterator and returns the underlying iterator. - #[inline] - pub fn into_inner(self) -> I { - self.input - } - - /// get mutable access to the iterator - #[inline] - pub fn inner_mut(&mut self) -> &mut I { - &mut self.input - } - - fn next_input_frame(&mut self) { - self.current_frame_pos_in_chunk += 1; - - mem::swap(&mut self.current_frame, &mut self.next_frame); - self.next_frame.clear(); - for _ in 0..self.channels { - if let Some(i) = self.input.next() { - self.next_frame.push(i); - } else { - break; - } - } - } -} - -impl Iterator for SampleRateConverter -where - I: Iterator, - I::Item: Sample + Clone, -{ - type Item = I::Item; - - fn next(&mut self) -> Option { - // the algorithm below doesn't work if `self.from == self.to` - if self.from == self.to { - debug_assert_eq!(self.from, 1); - return self.input.next(); - } - - // Short circuit if there are some samples waiting. - if !self.output_buffer.is_empty() { - return Some(self.output_buffer.remove(0)); - } - - // The frame we are going to return from this function will be a linear interpolation - // between `self.current_frame` and `self.next_frame`. - - if self.next_output_frame_pos_in_chunk == self.to { - // If we jump to the next frame, we reset the whole state. - self.next_output_frame_pos_in_chunk = 0; - - self.next_input_frame(); - while self.current_frame_pos_in_chunk != self.from { - self.next_input_frame(); - } - self.current_frame_pos_in_chunk = 0; - } else { - // Finding the position of the first sample of the linear interpolation. - let req_left_sample = - (self.from * self.next_output_frame_pos_in_chunk / self.to) % self.from; - - // Advancing `self.current_frame`, `self.next_frame` and - // `self.current_frame_pos_in_chunk` until the latter variable - // matches `req_left_sample`. - while self.current_frame_pos_in_chunk != req_left_sample { - self.next_input_frame(); - debug_assert!(self.current_frame_pos_in_chunk < self.from); - } - } - - // Merging `self.current_frame` and `self.next_frame` into `self.output_buffer`. - // Note that `self.output_buffer` can be truncated if there is not enough data in - // `self.next_frame`. - let mut result = None; - let numerator = (self.from * self.next_output_frame_pos_in_chunk) % self.to; - for (off, (cur, next)) in self - .current_frame - .iter() - .zip(self.next_frame.iter()) - .enumerate() - { - let sample = Sample::lerp(*cur, *next, numerator, self.to); - - if off == 0 { - result = Some(sample); - } else { - self.output_buffer.push(sample); - } - } - - // Incrementing the counter for the next iteration. - self.next_output_frame_pos_in_chunk += 1; - - if result.is_some() { - result - } else { - // draining `self.current_frame` - if !self.current_frame.is_empty() { - let r = Some(self.current_frame.remove(0)); - mem::swap(&mut self.output_buffer, &mut self.current_frame); - self.current_frame.clear(); - r - } else { - None - } - } - } - - #[inline] - fn size_hint(&self) -> (usize, Option) { - let apply = |samples: usize| { - // `samples_after_chunk` will contain the number of samples remaining after the chunk - // currently being processed - let samples_after_chunk = samples; - // adding the samples of the next chunk that may have already been read - let samples_after_chunk = if self.current_frame_pos_in_chunk == self.from - 1 { - samples_after_chunk + self.next_frame.len() - } else { - samples_after_chunk - }; - // removing the samples of the current chunk that have not yet been read - let samples_after_chunk = samples_after_chunk.saturating_sub( - self.from - .saturating_sub(self.current_frame_pos_in_chunk + 2) as usize - * usize::from(self.channels), - ); - // calculating the number of samples after the transformation - // TODO: this is wrong here \|/ - let samples_after_chunk = samples_after_chunk * self.to as usize / self.from as usize; - - // `samples_current_chunk` will contain the number of samples remaining to be output - // for the chunk currently being processed - let samples_current_chunk = (self.to - self.next_output_frame_pos_in_chunk) as usize - * usize::from(self.channels); - - samples_current_chunk + samples_after_chunk + self.output_buffer.len() - }; - - if self.from == self.to { - self.input.size_hint() - } else { - let (min, max) = self.input.size_hint(); - (apply(min), max.map(apply)) - } - } -} - -impl ExactSizeIterator for SampleRateConverter -where - I: ExactSizeIterator, - I::Item: Sample + Clone, -{ -} - -#[cfg(test)] -mod test { - use super::SampleRateConverter; - use core::time::Duration; - use cpal::{ChannelCount, SampleRate}; - use quickcheck::{quickcheck, TestResult}; - - quickcheck! { - /// Check that resampling an empty input produces no output. - fn empty(from: u16, to: u16, channels: u8) -> TestResult { - if channels == 0 || channels > 128 - || from == 0 - || to == 0 - { - return TestResult::discard(); - } - let from = SampleRate(from as u32); - let to = SampleRate(to as u32); - - let input: Vec = Vec::new(); - let output = - SampleRateConverter::new(input.into_iter(), from, to, channels as ChannelCount) - .collect::>(); - - assert_eq!(output, []); - TestResult::passed() - } - - /// Check that resampling to the same rate does not change the signal. - fn identity(from: u16, channels: u8, input: Vec) -> TestResult { - if channels == 0 || channels > 128 || from == 0 { return TestResult::discard(); } - let from = SampleRate(from as u32); - - let output = - SampleRateConverter::new(input.clone().into_iter(), from, from, channels as ChannelCount) - .collect::>(); - - TestResult::from_bool(input == output) - } - - /// Check that dividing the sample rate by k (integer) is the same as - /// dropping a sample from each channel. - fn divide_sample_rate(to: u16, k: u16, input: Vec, channels: u8) -> TestResult { - if k == 0 || channels == 0 || channels > 128 || to == 0 || to > 48000 { - return TestResult::discard(); - } - - let to = SampleRate(to as u32); - let from = to * k as u32; - - // Truncate the input, so it contains an integer number of frames. - let input = { - let ns = channels as usize; - let mut i = input; - i.truncate(ns * (i.len() / ns)); - i - }; - - let output = - SampleRateConverter::new(input.clone().into_iter(), from, to, channels as ChannelCount) - .collect::>(); - - TestResult::from_bool(input.chunks_exact(channels.into()) - .step_by(k as usize).collect::>().concat() == output) - } - - /// Check that, after multiplying the sample rate by k, every k-th - /// sample in the output matches exactly with the input. - fn multiply_sample_rate(from: u16, k: u8, input: Vec, channels: u8) -> TestResult { - if k == 0 || channels == 0 || channels > 128 || from == 0 { - return TestResult::discard(); - } - - let from = SampleRate(from as u32); - let to = from * k as u32; - - // Truncate the input, so it contains an integer number of frames. - let input = { - let ns = channels as usize; - let mut i = input; - i.truncate(ns * (i.len() / ns)); - i - }; - - let output = - SampleRateConverter::new(input.clone().into_iter(), from, to, channels as ChannelCount) - .collect::>(); - - TestResult::from_bool(input == - output.chunks_exact(channels.into()) - .step_by(k as usize).collect::>().concat()) - } - - #[ignore] - /// Check that resampling does not change the audio duration, - /// except by a negligible amount (± 1ms). Reproduces #316. - /// Ignored, pending a bug fix. - fn preserve_durations(d: Duration, freq: f32, to: u32) -> TestResult { - if to == 0 { return TestResult::discard(); } - - use crate::source::{SineWave, Source}; - - let to = SampleRate(to); - let source = SineWave::new(freq).take_duration(d); - let from = SampleRate(source.sample_rate()); - - let resampled = - SampleRateConverter::new(source, from, to, 1); - let duration = - Duration::from_secs_f32(resampled.count() as f32 / to.0 as f32); - - let delta = if d < duration { duration - d } else { d - duration }; - TestResult::from_bool(delta < Duration::from_millis(1)) - } - } - - #[test] - fn upsample() { - let input = vec![2u16, 16, 4, 18, 6, 20, 8, 22]; - let output = - SampleRateConverter::new(input.into_iter(), SampleRate(2000), SampleRate(3000), 2); - assert_eq!(output.len(), 12); // Test the source's Iterator::size_hint() - - let output = output.collect::>(); - assert_eq!(output, [2, 16, 3, 17, 4, 18, 6, 20, 7, 21, 8, 22]); - } - - #[test] - fn upsample2() { - let input = vec![1u16, 14]; - let output = - SampleRateConverter::new(input.into_iter(), SampleRate(1000), SampleRate(7000), 1); - let size_estimation = output.len(); - let output = output.collect::>(); - assert_eq!(output, [1, 2, 4, 6, 8, 10, 12, 14]); - assert!((size_estimation as f32 / output.len() as f32).abs() < 2.0); - } - - #[test] - fn downsample() { - let input = Vec::from_iter(0u16..17); - let output = - SampleRateConverter::new(input.into_iter(), SampleRate(12000), SampleRate(2400), 1); - let size_estimation = output.len(); - let output = output.collect::>(); - assert_eq!(output, [0, 5, 10, 15]); - assert!((size_estimation as f32 / output.len() as f32).abs() < 2.0); - } -} diff --git a/src/conversions/sample_rate/fast_inhouse.rs b/src/conversions/sample_rate/fast_inhouse.rs new file mode 100644 index 00000000..b76c23d0 --- /dev/null +++ b/src/conversions/sample_rate/fast_inhouse.rs @@ -0,0 +1,400 @@ +use crate::conversions::Sample; + +use num_rational::Ratio; +use std::mem; + +/// Iterator that converts from a certain sample rate to another. +#[derive(Clone, Debug)] +pub struct SampleRateConverter +where + I: Iterator, +{ + /// The iterator that gives us samples. + input: I, + /// We convert chunks of `from` samples into chunks of `to` samples. + from: u32, + /// We convert chunks of `from` samples into chunks of `to` samples. + to: u32, + /// Number of channels in the stream + channels: cpal::ChannelCount, + /// One sample per channel, extracted from `input`. + current_frame: Vec, + /// Position of `current_sample` modulo `from`. + current_frame_pos_in_chunk: u32, + /// The samples right after `current_sample` (one per channel), extracted from `input`. + next_frame: Vec, + /// The position of the next sample that the iterator should return, modulo `to`. + /// This counter is incremented (modulo `to`) every time the iterator is called. + next_output_frame_pos_in_chunk: u32, + /// The buffer containing the samples waiting to be output. + output_buffer: Vec, +} + +impl SampleRateConverter +where + I: Iterator, + I::Item: Sample, +{ + /// Create new sample rate converter. + /// + /// The converter uses simple linear interpolation for up-sampling + /// and discards samples for down-sampling. This may introduce audible + /// distortions in some cases (see [#584](https://github.com/RustAudio/rodio/issues/584)). + /// + /// # Limitations + /// Some rate conversions where target rate is high and rates are mutual primes the sample + /// interpolation may cause numeric overflows. Conversion between usual sample rates + /// 2400, 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000, ... is expected to work. + /// + /// # Panic + /// Panics if `from`, `to` or `num_channels` are 0. + #[inline] + pub fn new( + mut input: I, + from: cpal::SampleRate, + to: cpal::SampleRate, + num_channels: cpal::ChannelCount, + ) -> SampleRateConverter { + let from = from.0; + let to = to.0; + + assert!(num_channels >= 1); + assert!(from >= 1); + assert!(to >= 1); + + let (first_samples, next_samples) = if from == to { + // if `from` == `to` == 1, then we just pass through + (Vec::new(), Vec::new()) + } else { + let first = input + .by_ref() + .take(num_channels as usize) + .collect::>(); + let next = input + .by_ref() + .take(num_channels as usize) + .collect::>(); + (first, next) + }; + + // Reducing numerator to avoid numeric overflows during interpolation. + let (to, from) = Ratio::new(to, from).into_raw(); + + SampleRateConverter { + input, + from, + to, + channels: num_channels, + current_frame_pos_in_chunk: 0, + next_output_frame_pos_in_chunk: 0, + current_frame: first_samples, + next_frame: next_samples, + output_buffer: Vec::with_capacity(num_channels as usize - 1), + } + } + + /// Destroys this iterator and returns the underlying iterator. + #[inline] + pub fn into_inner(self) -> I { + self.input + } + + /// get mutable access to the iterator + #[inline] + pub fn inner_mut(&mut self) -> &mut I { + &mut self.input + } + + fn next_input_frame(&mut self) { + self.current_frame_pos_in_chunk += 1; + + mem::swap(&mut self.current_frame, &mut self.next_frame); + self.next_frame.clear(); + for _ in 0..self.channels { + if let Some(i) = self.input.next() { + self.next_frame.push(i); + } else { + break; + } + } + } +} + +impl Iterator for SampleRateConverter +where + I: Iterator, + I::Item: Sample + Clone, +{ + type Item = I::Item; + + fn next(&mut self) -> Option { + // the algorithm below doesn't work if `self.from == self.to` + if self.from == self.to { + debug_assert_eq!(self.from, 1); + return self.input.next(); + } + + // Short circuit if there are some samples waiting. + if !self.output_buffer.is_empty() { + return Some(self.output_buffer.remove(0)); + } + + // The frame we are going to return from this function will be a linear interpolation + // between `self.current_frame` and `self.next_frame`. + + if self.next_output_frame_pos_in_chunk == self.to { + // If we jump to the next frame, we reset the whole state. + self.next_output_frame_pos_in_chunk = 0; + + self.next_input_frame(); + while self.current_frame_pos_in_chunk != self.from { + self.next_input_frame(); + } + self.current_frame_pos_in_chunk = 0; + } else { + // Finding the position of the first sample of the linear interpolation. + let req_left_sample = + (self.from * self.next_output_frame_pos_in_chunk / self.to) % self.from; + + // Advancing `self.current_frame`, `self.next_frame` and + // `self.current_frame_pos_in_chunk` until the latter variable + // matches `req_left_sample`. + while self.current_frame_pos_in_chunk != req_left_sample { + self.next_input_frame(); + debug_assert!(self.current_frame_pos_in_chunk < self.from); + } + } + + // Merging `self.current_frame` and `self.next_frame` into `self.output_buffer`. + // Note that `self.output_buffer` can be truncated if there is not enough data in + // `self.next_frame`. + let mut result = None; + let numerator = (self.from * self.next_output_frame_pos_in_chunk) % self.to; + for (off, (cur, next)) in self + .current_frame + .iter() + .zip(self.next_frame.iter()) + .enumerate() + { + let sample = Sample::lerp(*cur, *next, numerator, self.to); + + if off == 0 { + result = Some(sample); + } else { + self.output_buffer.push(sample); + } + } + + // Incrementing the counter for the next iteration. + self.next_output_frame_pos_in_chunk += 1; + + if result.is_some() { + result + } else { + // draining `self.current_frame` + if !self.current_frame.is_empty() { + let r = Some(self.current_frame.remove(0)); + mem::swap(&mut self.output_buffer, &mut self.current_frame); + self.current_frame.clear(); + r + } else { + None + } + } + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + let apply = |samples: usize| { + // `samples_after_chunk` will contain the number of samples remaining after the chunk + // currently being processed + let samples_after_chunk = samples; + // adding the samples of the next chunk that may have already been read + let samples_after_chunk = if self.current_frame_pos_in_chunk == self.from - 1 { + samples_after_chunk + self.next_frame.len() + } else { + samples_after_chunk + }; + // removing the samples of the current chunk that have not yet been read + let samples_after_chunk = samples_after_chunk.saturating_sub( + self.from + .saturating_sub(self.current_frame_pos_in_chunk + 2) as usize + * usize::from(self.channels), + ); + // calculating the number of samples after the transformation + // TODO: this is wrong here \|/ + let samples_after_chunk = samples_after_chunk * self.to as usize / self.from as usize; + + // `samples_current_chunk` will contain the number of samples remaining to be output + // for the chunk currently being processed + let samples_current_chunk = (self.to - self.next_output_frame_pos_in_chunk) as usize + * usize::from(self.channels); + + samples_current_chunk + samples_after_chunk + self.output_buffer.len() + }; + + if self.from == self.to { + self.input.size_hint() + } else { + let (min, max) = self.input.size_hint(); + (apply(min), max.map(apply)) + } + } +} + +impl ExactSizeIterator for SampleRateConverter +where + I: ExactSizeIterator, + I::Item: Sample + Clone, +{ +} + +#[cfg(test)] +mod test { + use super::SampleRateConverter; + use core::time::Duration; + use cpal::{ChannelCount, SampleRate}; + use quickcheck::{quickcheck, TestResult}; + + quickcheck! { + /// Check that resampling an empty input produces no output. + fn empty(from: u16, to: u16, channels: u8) -> TestResult { + if channels == 0 || channels > 128 + || from == 0 + || to == 0 + { + return TestResult::discard(); + } + let from = SampleRate(from as u32); + let to = SampleRate(to as u32); + + let input: Vec = Vec::new(); + let output = + SampleRateConverter::new(input.into_iter(), from, to, channels as ChannelCount) + .collect::>(); + + assert_eq!(output, []); + TestResult::passed() + } + + /// Check that resampling to the same rate does not change the signal. + fn identity(from: u16, channels: u8, input: Vec) -> TestResult { + if channels == 0 || channels > 128 || from == 0 { return TestResult::discard(); } + let from = SampleRate(from as u32); + + let output = + SampleRateConverter::new(input.clone().into_iter(), from, from, channels as ChannelCount) + .collect::>(); + + TestResult::from_bool(input == output) + } + + /// Check that dividing the sample rate by k (integer) is the same as + /// dropping a sample from each channel. + fn divide_sample_rate(to: u16, k: u16, input: Vec, channels: u8) -> TestResult { + if k == 0 || channels == 0 || channels > 128 || to == 0 || to > 48000 { + return TestResult::discard(); + } + + let to = SampleRate(to as u32); + let from = to * k as u32; + + // Truncate the input, so it contains an integer number of frames. + let input = { + let ns = channels as usize; + let mut i = input; + i.truncate(ns * (i.len() / ns)); + i + }; + + let output = + SampleRateConverter::new(input.clone().into_iter(), from, to, channels as ChannelCount) + .collect::>(); + + TestResult::from_bool(input.chunks_exact(channels.into()) + .step_by(k as usize).collect::>().concat() == output) + } + + /// Check that, after multiplying the sample rate by k, every k-th + /// sample in the output matches exactly with the input. + fn multiply_sample_rate(from: u16, k: u8, input: Vec, channels: u8) -> TestResult { + if k == 0 || channels == 0 || channels > 128 || from == 0 { + return TestResult::discard(); + } + + let from = SampleRate(from as u32); + let to = from * k as u32; + + // Truncate the input, so it contains an integer number of frames. + let input = { + let ns = channels as usize; + let mut i = input; + i.truncate(ns * (i.len() / ns)); + i + }; + + let output = + SampleRateConverter::new(input.clone().into_iter(), from, to, channels as ChannelCount) + .collect::>(); + + TestResult::from_bool(input == + output.chunks_exact(channels.into()) + .step_by(k as usize).collect::>().concat()) + } + + #[ignore] + /// Check that resampling does not change the audio duration, + /// except by a negligible amount (± 1ms). Reproduces #316. + /// Ignored, pending a bug fix. + fn preserve_durations(d: Duration, freq: f32, to: u32) -> TestResult { + if to == 0 { return TestResult::discard(); } + + use crate::source::{SineWave, Source}; + + let to = SampleRate(to); + let source = SineWave::new(freq).take_duration(d); + let from = SampleRate(source.sample_rate()); + + let resampled = + SampleRateConverter::new(source, from, to, 1); + let duration = + Duration::from_secs_f32(resampled.count() as f32 / to.0 as f32); + + let delta = if d < duration { duration - d } else { d - duration }; + TestResult::from_bool(delta < Duration::from_millis(1)) + } + } + + #[test] + fn upsample() { + let input = vec![2u16, 16, 4, 18, 6, 20, 8, 22]; + let output = + SampleRateConverter::new(input.into_iter(), SampleRate(2000), SampleRate(3000), 2); + assert_eq!(output.len(), 12); // Test the source's Iterator::size_hint() + + let output = output.collect::>(); + assert_eq!(output, [2, 16, 3, 17, 4, 18, 6, 20, 7, 21, 8, 22]); + } + + #[test] + fn upsample2() { + let input = vec![1u16, 14]; + let output = + SampleRateConverter::new(input.into_iter(), SampleRate(1000), SampleRate(7000), 1); + let size_estimation = output.len(); + let output = output.collect::>(); + assert_eq!(output, [1, 2, 4, 6, 8, 10, 12, 14]); + assert!((size_estimation as f32 / output.len() as f32).abs() < 2.0); + } + + #[test] + fn downsample() { + let input = Vec::from_iter(0u16..17); + let output = + SampleRateConverter::new(input.into_iter(), SampleRate(12000), SampleRate(2400), 1); + let size_estimation = output.len(); + let output = output.collect::>(); + assert_eq!(output, [0, 5, 10, 15]); + assert!((size_estimation as f32 / output.len() as f32).abs() < 2.0); + } +} diff --git a/src/conversions/sample_rate/hifi_rubato.rs b/src/conversions/sample_rate/hifi_rubato.rs new file mode 100644 index 00000000..8b69423a --- /dev/null +++ b/src/conversions/sample_rate/hifi_rubato.rs @@ -0,0 +1,60 @@ +use crate::Sample; + +#[derive(Clone, Debug)] +pub struct SampleRateConverter +where + I: Iterator, +{ + /// The iterator that gives us samples. + input: I, + /// We convert chunks of `from` samples into chunks of `to` samples. + from: u32, + /// We convert chunks of `from` samples into chunks of `to` samples. + to: u32, + /// Number of channels in the stream + channels: cpal::ChannelCount, + /// One sample per channel, extracted from `input`. + current_frame: Vec, + /// Position of `current_sample` modulo `from`. + current_frame_pos_in_chunk: u32, + /// The samples right after `current_sample` (one per channel), extracted from `input`. + next_frame: Vec, + /// The position of the next sample that the iterator should return, modulo `to`. + /// This counter is incremented (modulo `to`) every time the iterator is called. + next_output_frame_pos_in_chunk: u32, + /// The buffer containing the samples waiting to be output. + output_buffer: Vec, +} + +impl SampleRateConverter +where + I: Iterator, + I::Item: Sample, +{ + #[inline] + pub fn new( + mut input: I, + from: cpal::SampleRate, + to: cpal::SampleRate, + num_channels: cpal::ChannelCount, + ) -> SampleRateConverter { + let from = from.0; + let to = to.0; + + assert!(num_channels >= 1); + assert!(from >= 1); + assert!(to >= 1); + + SampleRateConverter { + input, + from, + to, + channels: num_channels, + current_frame_pos_in_chunk: 0, + next_output_frame_pos_in_chunk: 0, + current_frame: first_samples, + next_frame: next_samples, + output_buffer: Vec::with_capacity(num_channels as usize - 1), + } + } +} diff --git a/src/source/uniform.rs b/src/source/uniform.rs index b9d146d1..dda61ea9 100644 --- a/src/source/uniform.rs +++ b/src/source/uniform.rs @@ -3,7 +3,8 @@ use std::time::Duration; use cpal::FromSample; -use crate::conversions::{ChannelCountConverter, DataConverter, SampleRateConverter}; +use crate::conversions::sample_rate::fast_inhouse::SampleRateConverter; +use crate::conversions::{ChannelCountConverter, DataConverter}; use crate::{Sample, Source}; use super::SeekError; From 2060d57d452f14d7f39b36688bf3412dcc297fcd Mon Sep 17 00:00:00 2001 From: dvdsk Date: Fri, 3 Jan 2025 14:45:46 +0100 Subject: [PATCH 2/6] implements untested rubato resampler --- src/conversions/sample_rate/hifi_rubato.rs | 190 ++++++++++++++++++--- 1 file changed, 162 insertions(+), 28 deletions(-) diff --git a/src/conversions/sample_rate/hifi_rubato.rs b/src/conversions/sample_rate/hifi_rubato.rs index 8b69423a..16628079 100644 --- a/src/conversions/sample_rate/hifi_rubato.rs +++ b/src/conversions/sample_rate/hifi_rubato.rs @@ -1,29 +1,64 @@ +// high fidelity resampler using the rubato crate. In contradiction to the +// fast in house resampler this does not provide ExactSizeIterator. We cannot +// do that since rubato does not guaranteed the amount of samples returned + +use std::vec; + +use rubato::{Resampler, SincInterpolationParameters}; + use crate::Sample; -#[derive(Clone, Debug)] +/// Rubato requires the samples for each channel to be in separate buffers. +/// This wrapper around Vec> provides an iterator that returns +/// samples interleaved. +struct Resampled { + channel_buffers: Vec>, + next_channel: usize, + next_frame: usize, +} + +impl Resampled { + fn new(channels: u16, capacity_per_channel: usize) -> Self { + Self { + channel_buffers: vec![Vec::with_capacity(capacity_per_channel); channels as usize], + next_channel: 0, + next_frame: 0, + } + } + + fn empty_buffers(&mut self) -> &mut Vec> { + self.channel_buffers.clear(); + &mut self.channel_buffers + } +} + +impl Iterator for Resampled { + type Item = f64; + + fn next(&mut self) -> Option { + let sample = self.channel_buffers[self.next_channel].get(self.next_frame)?; + self.next_channel = (self.next_frame + 1) % self.channel_buffers.len(); + self.next_frame += 1; + + Some(*sample) + } +} + pub struct SampleRateConverter where I: Iterator, { - /// The iterator that gives us samples. input: I, - /// We convert chunks of `from` samples into chunks of `to` samples. - from: u32, - /// We convert chunks of `from` samples into chunks of `to` samples. - to: u32, - /// Number of channels in the stream - channels: cpal::ChannelCount, - /// One sample per channel, extracted from `input`. - current_frame: Vec, - /// Position of `current_sample` modulo `from`. - current_frame_pos_in_chunk: u32, - /// The samples right after `current_sample` (one per channel), extracted from `input`. - next_frame: Vec, - /// The position of the next sample that the iterator should return, modulo `to`. - /// This counter is incremented (modulo `to`) every time the iterator is called. - next_output_frame_pos_in_chunk: u32, - /// The buffer containing the samples waiting to be output. - output_buffer: Vec, + + // for size hint + resample_ratio: f64, + + resampled: Resampled, + /// in number of audio frames where one frame is all the samples + /// for all channels. + resampler_chunk_size: usize, + resampler_input: Vec>, + resampler: rubato::SincFixedOut, } impl SampleRateConverter @@ -33,7 +68,7 @@ where { #[inline] pub fn new( - mut input: I, + input: I, from: cpal::SampleRate, to: cpal::SampleRate, num_channels: cpal::ChannelCount, @@ -45,16 +80,115 @@ where assert!(from >= 1); assert!(to >= 1); + let resample_ratio = to as f64 / from as f64; + let max_resample_ratio_relative = 1.1; + let window = rubato::WindowFunction::Blackman2; + let sinc_len = 128; + let params = SincInterpolationParameters { + sinc_len, + f_cutoff: rubato::calculate_cutoff(sinc_len, window), + oversampling_factor: 256, + interpolation: rubato::SincInterpolationType::Quadratic, + window, + }; + + let resampler_input_size = 1024; + let resampler_output_size = + ((resampler_input_size as f64) * resample_ratio).ceil() as usize; + let resampler = rubato::SincFixedOut::::new( + resample_ratio, + max_resample_ratio_relative, + params, + resampler_input_size, + num_channels as usize, + ) + .unwrap(); + SampleRateConverter { input, - from, - to, - channels: num_channels, - current_frame_pos_in_chunk: 0, - next_output_frame_pos_in_chunk: 0, - current_frame: first_samples, - next_frame: next_samples, - output_buffer: Vec::with_capacity(num_channels as usize - 1), + resample_ratio, + resampled: Resampled::new(num_channels, resampler_output_size), + resampler_chunk_size: 1024, + resampler, + resampler_input: vec![Vec::with_capacity(resampler_input_size); num_channels as usize], + } + } + + /// Destroys this iterator and returns the underlying iterator. + #[inline] + pub fn into_inner(self) -> I { + self.input + } + + /// get mutable access to the iterator + #[inline] + pub fn inner_mut(&mut self) -> &mut I { + &mut self.input + } + + fn fill_resampler_input(&mut self) { + self.resampler_input.clear(); + for _ in 0..self.resampler_chunk_size { + for channel_buffer in self.resampler_input.iter_mut() { + if let Some(item) = self.input.next() { + channel_buffer.push(item.to_f32() as f64); + } else { + break; + } + } + } + } +} + +impl Iterator for SampleRateConverter +where + I: Iterator, + I::Item: Sample + Clone, +{ + type Item = f64; + + fn next(&mut self) -> Option { + if let Some(item) = self.resampled.next() { + return Some(item); } + + self.fill_resampler_input(); + + if self.resampler_input.len() >= self.resampler_chunk_size { + self.resampler + .process_into_buffer( + &self.resampler_input, + self.resampled.empty_buffers(), + None, // all channels active + ) + .expect( + "Input and output have correct number of channels, \ + input is long enough", + ); + } else { + self.resampler + // gets the last samples out of the resampler + .process_partial_into_buffer( + // might have to pass in None if the input is empty + // something to check if this fails near the end of a source + Some(&self.resampler_input), + self.resampled.empty_buffers(), + None, // all channels active + ) + .expect("Input and output have correct number of channels, \ + input is long enough"); + } + + self.resampled.next() + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + let (lower_bound, upper_bound) = self.input.size_hint(); + let lower_bound = (lower_bound as f64 * self.resample_ratio).floor() as usize; + let upper_bound = upper_bound + .map(|lower_bound| lower_bound as f64 * self.resample_ratio) + .map(|lower_bound| lower_bound.ceil() as usize); + (lower_bound, upper_bound) } } From 4ce294d1fbdeaca498cab0ee697be10fd5540d00 Mon Sep 17 00:00:00 2001 From: dvdsk Date: Fri, 3 Jan 2025 15:19:57 +0100 Subject: [PATCH 3/6] re-use fast_inhous resampler test suite for hifi_rubato --- Cargo.lock | 47 +++-- Cargo.toml | 1 + src/conversions/sample_rate.rs | 3 +- src/conversions/sample_rate/fast_inhouse.rs | 150 -------------- src/conversions/sample_rate/test.rs | 204 ++++++++++++++++++++ 5 files changed, 242 insertions(+), 163 deletions(-) create mode 100644 src/conversions/sample_rate/test.rs diff --git a/Cargo.lock b/Cargo.lock index d94de360..cc5b2231 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -81,7 +81,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn", + "syn 2.0.89", ] [[package]] @@ -303,7 +303,7 @@ checksum = "7bdb5411188f7f878a17964798c1264b6b0a9f915bd39b20bf99193c923e1b4e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -409,7 +409,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -688,7 +688,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -738,7 +738,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -844,6 +844,17 @@ dependencies = [ "rand", ] +[[package]] +name = "quickcheck_macros" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b22a693222d716a9587786f37ac3f6b4faedb5b80c23914e7303ff5a1d8016e9" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "quote" version = "1.0.37" @@ -949,6 +960,7 @@ dependencies = [ "minimp3_fixed", "num-rational", "quickcheck", + "quickcheck_macros", "rand", "rstest", "rstest_reuse", @@ -982,7 +994,7 @@ dependencies = [ "regex", "relative-path", "rustc_version", - "syn", + "syn 2.0.89", "unicode-ident", ] @@ -995,7 +1007,7 @@ dependencies = [ "quote", "rand", "rustc_version", - "syn", + "syn 2.0.89", ] [[package]] @@ -1256,6 +1268,17 @@ dependencies = [ "symphonia-metadata", ] +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.89" @@ -1294,7 +1317,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -1348,7 +1371,7 @@ checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -1420,7 +1443,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn", + "syn 2.0.89", "wasm-bindgen-shared", ] @@ -1455,7 +1478,7 @@ checksum = "bf59002391099644be3524e23b781fa43d2be0c5aa0719a18c0731b9d195cab6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -1711,5 +1734,5 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] diff --git a/Cargo.toml b/Cargo.toml index 6d34530b..43352ad8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,6 +50,7 @@ symphonia-aiff = ["symphonia/aiff", "symphonia/pcm"] [dev-dependencies] quickcheck = "1" +quickcheck_macros = "1" rstest = "0.18.2" rstest_reuse = "0.6.0" approx = "0.5.1" diff --git a/src/conversions/sample_rate.rs b/src/conversions/sample_rate.rs index d1f54963..ee284f97 100644 --- a/src/conversions/sample_rate.rs +++ b/src/conversions/sample_rate.rs @@ -1,3 +1,4 @@ pub(crate) mod fast_inhouse; pub(crate) mod hifi_rubato; - +#[cfg(test)] +mod test; diff --git a/src/conversions/sample_rate/fast_inhouse.rs b/src/conversions/sample_rate/fast_inhouse.rs index b76c23d0..499aa846 100644 --- a/src/conversions/sample_rate/fast_inhouse.rs +++ b/src/conversions/sample_rate/fast_inhouse.rs @@ -248,153 +248,3 @@ where I::Item: Sample + Clone, { } - -#[cfg(test)] -mod test { - use super::SampleRateConverter; - use core::time::Duration; - use cpal::{ChannelCount, SampleRate}; - use quickcheck::{quickcheck, TestResult}; - - quickcheck! { - /// Check that resampling an empty input produces no output. - fn empty(from: u16, to: u16, channels: u8) -> TestResult { - if channels == 0 || channels > 128 - || from == 0 - || to == 0 - { - return TestResult::discard(); - } - let from = SampleRate(from as u32); - let to = SampleRate(to as u32); - - let input: Vec = Vec::new(); - let output = - SampleRateConverter::new(input.into_iter(), from, to, channels as ChannelCount) - .collect::>(); - - assert_eq!(output, []); - TestResult::passed() - } - - /// Check that resampling to the same rate does not change the signal. - fn identity(from: u16, channels: u8, input: Vec) -> TestResult { - if channels == 0 || channels > 128 || from == 0 { return TestResult::discard(); } - let from = SampleRate(from as u32); - - let output = - SampleRateConverter::new(input.clone().into_iter(), from, from, channels as ChannelCount) - .collect::>(); - - TestResult::from_bool(input == output) - } - - /// Check that dividing the sample rate by k (integer) is the same as - /// dropping a sample from each channel. - fn divide_sample_rate(to: u16, k: u16, input: Vec, channels: u8) -> TestResult { - if k == 0 || channels == 0 || channels > 128 || to == 0 || to > 48000 { - return TestResult::discard(); - } - - let to = SampleRate(to as u32); - let from = to * k as u32; - - // Truncate the input, so it contains an integer number of frames. - let input = { - let ns = channels as usize; - let mut i = input; - i.truncate(ns * (i.len() / ns)); - i - }; - - let output = - SampleRateConverter::new(input.clone().into_iter(), from, to, channels as ChannelCount) - .collect::>(); - - TestResult::from_bool(input.chunks_exact(channels.into()) - .step_by(k as usize).collect::>().concat() == output) - } - - /// Check that, after multiplying the sample rate by k, every k-th - /// sample in the output matches exactly with the input. - fn multiply_sample_rate(from: u16, k: u8, input: Vec, channels: u8) -> TestResult { - if k == 0 || channels == 0 || channels > 128 || from == 0 { - return TestResult::discard(); - } - - let from = SampleRate(from as u32); - let to = from * k as u32; - - // Truncate the input, so it contains an integer number of frames. - let input = { - let ns = channels as usize; - let mut i = input; - i.truncate(ns * (i.len() / ns)); - i - }; - - let output = - SampleRateConverter::new(input.clone().into_iter(), from, to, channels as ChannelCount) - .collect::>(); - - TestResult::from_bool(input == - output.chunks_exact(channels.into()) - .step_by(k as usize).collect::>().concat()) - } - - #[ignore] - /// Check that resampling does not change the audio duration, - /// except by a negligible amount (± 1ms). Reproduces #316. - /// Ignored, pending a bug fix. - fn preserve_durations(d: Duration, freq: f32, to: u32) -> TestResult { - if to == 0 { return TestResult::discard(); } - - use crate::source::{SineWave, Source}; - - let to = SampleRate(to); - let source = SineWave::new(freq).take_duration(d); - let from = SampleRate(source.sample_rate()); - - let resampled = - SampleRateConverter::new(source, from, to, 1); - let duration = - Duration::from_secs_f32(resampled.count() as f32 / to.0 as f32); - - let delta = if d < duration { duration - d } else { d - duration }; - TestResult::from_bool(delta < Duration::from_millis(1)) - } - } - - #[test] - fn upsample() { - let input = vec![2u16, 16, 4, 18, 6, 20, 8, 22]; - let output = - SampleRateConverter::new(input.into_iter(), SampleRate(2000), SampleRate(3000), 2); - assert_eq!(output.len(), 12); // Test the source's Iterator::size_hint() - - let output = output.collect::>(); - assert_eq!(output, [2, 16, 3, 17, 4, 18, 6, 20, 7, 21, 8, 22]); - } - - #[test] - fn upsample2() { - let input = vec![1u16, 14]; - let output = - SampleRateConverter::new(input.into_iter(), SampleRate(1000), SampleRate(7000), 1); - let size_estimation = output.len(); - let output = output.collect::>(); - assert_eq!(output, [1, 2, 4, 6, 8, 10, 12, 14]); - assert!((size_estimation as f32 / output.len() as f32).abs() < 2.0); - } - - #[test] - fn downsample() { - let input = Vec::from_iter(0u16..17); - let output = - SampleRateConverter::new(input.into_iter(), SampleRate(12000), SampleRate(2400), 1); - let size_estimation = output.len(); - let output = output.collect::>(); - assert_eq!(output, [0, 5, 10, 15]); - assert!((size_estimation as f32 / output.len() as f32).abs() < 2.0); - } -} diff --git a/src/conversions/sample_rate/test.rs b/src/conversions/sample_rate/test.rs new file mode 100644 index 00000000..b4facd29 --- /dev/null +++ b/src/conversions/sample_rate/test.rs @@ -0,0 +1,204 @@ +macro_rules! test_resampler { + ($resampler:path) => { + /// Check that resampling an empty input produces no output. + #[quickcheck] + fn empty(from: u16, to: u16, channels: u8) -> TestResult { + if channels == 0 || channels > 128 || from == 0 || to == 0 { + return TestResult::discard(); + } + let from = SampleRate(from as u32); + let to = SampleRate(to as u32); + + let input: Vec = Vec::new(); + let output = + SampleRateConverter::new(input.into_iter(), from, to, channels as ChannelCount) + .collect::>(); + + assert_eq!(output, []); + TestResult::passed() + } + + /// Check that resampling to the same rate does not change the signal. + #[quickcheck] + fn identity(from: u16, channels: u8, input: Vec) -> TestResult { + if channels == 0 || channels > 128 || from == 0 { + return TestResult::discard(); + } + let from = SampleRate(from as u32); + + let output = SampleRateConverter::new( + input.clone().into_iter(), + from, + from, + channels as ChannelCount, + ) + .map(|output| output as u16) + .collect::>(); + + TestResult::from_bool(input == output) + } + + /// Check that dividing the sample rate by k (integer) is the same as + /// dropping a sample from each channel. + #[quickcheck] + fn divide_sample_rate(to: u16, k: u16, input: Vec, channels: u8) -> TestResult { + if k == 0 || channels == 0 || channels > 128 || to == 0 || to > 48000 { + return TestResult::discard(); + } + + let to = SampleRate(to as u32); + let from = to * k as u32; + + // Truncate the input, so it contains an integer number of frames. + let input = { + let ns = channels as usize; + let mut i = input; + i.truncate(ns * (i.len() / ns)); + i + }; + + let output = SampleRateConverter::new( + input.clone().into_iter(), + from, + to, + channels as ChannelCount, + ) + .map(|output| output as u16) + .collect::>(); + + TestResult::from_bool( + input + .chunks_exact(channels.into()) + .step_by(k as usize) + .collect::>() + .concat() + == output, + ) + } + + /// Check that, after multiplying the sample rate by k, every k-th + /// sample in the output matches exactly with the input. + #[quickcheck] + fn multiply_sample_rate(from: u16, k: u8, input: Vec, channels: u8) -> TestResult { + if k == 0 || channels == 0 || channels > 128 || from == 0 { + return TestResult::discard(); + } + + let from = SampleRate(from as u32); + let to = from * k as u32; + + // Truncate the input, so it contains an integer number of frames. + let input = { + let ns = channels as usize; + let mut i = input; + i.truncate(ns * (i.len() / ns)); + i + }; + + let output = SampleRateConverter::new( + input.clone().into_iter(), + from, + to, + channels as ChannelCount, + ) + .map(|output| output as u16) + .collect::>(); + + TestResult::from_bool( + input + == output + .chunks_exact(channels.into()) + .step_by(k as usize) + .collect::>() + .concat(), + ) + } + + #[ignore] + /// Check that resampling does not change the audio duration, + /// except by a negligible amount (± 1ms). Reproduces #316. + /// Ignored, pending a bug fix. + #[quickcheck] + fn preserve_durations(d: Duration, freq: f32, to: u32) -> TestResult { + if to == 0 { + return TestResult::discard(); + } + + use crate::source::{SineWave, Source}; + + let to = SampleRate(to); + let source = SineWave::new(freq).take_duration(d); + let from = SampleRate(source.sample_rate()); + + let resampled = SampleRateConverter::new(source, from, to, 1); + let duration = Duration::from_secs_f32(resampled.count() as f32 / to.0 as f32); + + let delta = if d < duration { + duration - d + } else { + d - duration + }; + TestResult::from_bool(delta < Duration::from_millis(1)) + } + + #[test] + fn upsample() { + let input = vec![2u16, 16, 4, 18, 6, 20, 8, 22]; + let output = + SampleRateConverter::new(input.into_iter(), SampleRate(2000), SampleRate(3000), 2) + .map(|s| s as u16); + assert_eq!(output.size_hint().0, 12); // Test the source's Iterator::size_hint() + + let output = output.collect::>(); + assert_eq!(output, [2, 16, 3, 17, 4, 18, 6, 20, 7, 21, 8, 22]); + } + + #[test] + fn upsample2() { + let input = vec![1u16, 14]; + let output = + SampleRateConverter::new(input.into_iter(), SampleRate(1000), SampleRate(7000), 1) + .map(|s| s as u16); + let (lower_bound, upper_bound) = output.size_hint(); + let output = output.collect::>(); + assert_eq!(output, [1, 2, 4, 6, 8, 10, 12, 14]); + + assert!((lower_bound as f32 / output.len() as f32).abs() < 2.0); + assert!((upper_bound.unwrap() as f32 / output.len() as f32).abs() < 2.0); + } + + #[test] + fn downsample() { + let input = Vec::from_iter(0u16..17); + let output = + SampleRateConverter::new(input.into_iter(), SampleRate(12000), SampleRate(2400), 1) + .map(|s| s as u16); + let (lower_bound, upper_bound) = output.size_hint(); + let output = output.collect::>(); + assert_eq!(output, [0, 5, 10, 15]); + + assert!((lower_bound as f32 / output.len() as f32).abs() < 2.0); + assert!((upper_bound.unwrap() as f32 / output.len() as f32).abs() < 2.0); + } + }; +} + +// mod fast_inhouse { +// use super::super::fast_inhouse::SampleRateConverter; +// use core::time::Duration; +// use cpal::{ChannelCount, SampleRate}; +// use quickcheck::TestResult; +// use quickcheck_macros::quickcheck; +// +// test_resampler!(SampleRateConverter); +// } + +mod hifi_rubato { + use super::super::hifi_rubato::SampleRateConverter; + use core::time::Duration; + use cpal::{ChannelCount, SampleRate}; + use quickcheck::TestResult; + use quickcheck_macros::quickcheck; + + test_resampler!(SampleRateConverter); +} From 50df79dd01fcb2ff86dff4ffcc0bf744ed5f5947 Mon Sep 17 00:00:00 2001 From: dvdsk Date: Fri, 3 Jan 2025 23:24:13 +0100 Subject: [PATCH 4/6] splits test suite again + fixes in rubato resampler integration --- src/conversions/sample_rate.rs | 2 - src/conversions/sample_rate/fast_inhouse.rs | 3 + .../sample_rate/fast_inhouse/test.rs | 174 +++++++++++++++ src/conversions/sample_rate/hifi_rubato.rs | 148 ++++++++----- .../sample_rate/hifi_rubato/test.rs | 177 +++++++++++++++ src/conversions/sample_rate/test.rs | 204 ------------------ 6 files changed, 453 insertions(+), 255 deletions(-) create mode 100644 src/conversions/sample_rate/fast_inhouse/test.rs create mode 100644 src/conversions/sample_rate/hifi_rubato/test.rs delete mode 100644 src/conversions/sample_rate/test.rs diff --git a/src/conversions/sample_rate.rs b/src/conversions/sample_rate.rs index ee284f97..75039451 100644 --- a/src/conversions/sample_rate.rs +++ b/src/conversions/sample_rate.rs @@ -1,4 +1,2 @@ pub(crate) mod fast_inhouse; pub(crate) mod hifi_rubato; -#[cfg(test)] -mod test; diff --git a/src/conversions/sample_rate/fast_inhouse.rs b/src/conversions/sample_rate/fast_inhouse.rs index 499aa846..87da97a6 100644 --- a/src/conversions/sample_rate/fast_inhouse.rs +++ b/src/conversions/sample_rate/fast_inhouse.rs @@ -3,6 +3,9 @@ use crate::conversions::Sample; use num_rational::Ratio; use std::mem; +#[cfg(test)] +mod test; + /// Iterator that converts from a certain sample rate to another. #[derive(Clone, Debug)] pub struct SampleRateConverter diff --git a/src/conversions/sample_rate/fast_inhouse/test.rs b/src/conversions/sample_rate/fast_inhouse/test.rs new file mode 100644 index 00000000..c4fa14b5 --- /dev/null +++ b/src/conversions/sample_rate/fast_inhouse/test.rs @@ -0,0 +1,174 @@ +use super::SampleRateConverter; +use core::time::Duration; +use cpal::{ChannelCount, SampleRate}; +use quickcheck::TestResult; +use quickcheck_macros::quickcheck; + + +/// Check that resampling an empty input produces no output. +#[quickcheck] +fn empty(from: u16, to: u16, channels: u8) -> TestResult { + if channels == 0 || channels > 128 || from == 0 || to == 0 { + return TestResult::discard(); + } + let from = SampleRate(from as u32); + let to = SampleRate(to as u32); + + let input: Vec = Vec::new(); + let output = SampleRateConverter::new(input.into_iter(), from, to, channels as ChannelCount) + .collect::>(); + + assert_eq!(output, []); + TestResult::passed() +} + +/// Check that resampling to the same rate does not change the signal. +#[quickcheck] +fn identity(from: u16, channels: u8, input: Vec) -> TestResult { + if channels == 0 || channels > 128 || from == 0 { + return TestResult::discard(); + } + let from = SampleRate(from as u32); + + let output = SampleRateConverter::new( + input.clone().into_iter(), + from, + from, + channels as ChannelCount, + ) + .collect::>(); + + TestResult::from_bool(input == output) +} + +/// Check that dividing the sample rate by k (integer) is the same as +/// dropping a sample from each channel. +#[quickcheck] +fn divide_sample_rate(to: u16, k: u16, input: Vec, channels: u8) -> TestResult { + if k == 0 || channels == 0 || channels > 128 || to == 0 || to > 48000 { + return TestResult::discard(); + } + + let to = SampleRate(to as u32); + let from = to * k as u32; + + // Truncate the input, so it contains an integer number of frames. + let input = { + let ns = channels as usize; + let mut i = input; + i.truncate(ns * (i.len() / ns)); + i + }; + + let output = SampleRateConverter::new( + input.clone().into_iter(), + from, + to, + channels as ChannelCount, + ) + .collect::>(); + + TestResult::from_bool( + input + .chunks_exact(channels.into()) + .step_by(k as usize) + .collect::>() + .concat() + == output, + ) +} + +/// Check that, after multiplying the sample rate by k, every k-th +/// sample in the output matches exactly with the input. +#[quickcheck] +fn multiply_sample_rate(from: u16, k: u8, input: Vec, channels: u8) -> TestResult { + if k == 0 || channels == 0 || channels > 128 || from == 0 { + return TestResult::discard(); + } + + let from = SampleRate(from as u32); + let to = from * k as u32; + + // Truncate the input, so it contains an integer number of frames. + let input = { + let ns = channels as usize; + let mut i = input; + i.truncate(ns * (i.len() / ns)); + i + }; + + let output = SampleRateConverter::new( + input.clone().into_iter(), + from, + to, + channels as ChannelCount, + ) + .collect::>(); + + TestResult::from_bool( + input + == output + .chunks_exact(channels.into()) + .step_by(k as usize) + .collect::>() + .concat(), + ) +} + +#[ignore] +/// Check that resampling does not change the audio duration, +/// except by a negligible amount (± 1ms). Reproduces #316. +/// Ignored, pending a bug fix. +#[quickcheck] +fn preserve_durations(d: Duration, freq: f32, to: u32) -> TestResult { + if to == 0 { + return TestResult::discard(); + } + + use crate::source::{SineWave, Source}; + + let to = SampleRate(to); + let source = SineWave::new(freq).take_duration(d); + let from = SampleRate(source.sample_rate()); + + let resampled = SampleRateConverter::new(source, from, to, 1); + let duration = Duration::from_secs_f32(resampled.count() as f32 / to.0 as f32); + + let delta = if d < duration { + duration - d + } else { + d - duration + }; + TestResult::from_bool(delta < Duration::from_millis(1)) +} + +#[test] +fn upsample() { + let input = vec![2u16, 16, 4, 18, 6, 20, 8, 22]; + let output = SampleRateConverter::new(input.into_iter(), SampleRate(2000), SampleRate(3000), 2); + assert_eq!(output.len(), 12); // Test the source's Iterator::size_hint() + + let output = output.collect::>(); + assert_eq!(output, [2, 16, 3, 17, 4, 18, 6, 20, 7, 21, 8, 22]); +} + +#[test] +fn upsample2() { + let input = vec![1u16, 14]; + let output = SampleRateConverter::new(input.into_iter(), SampleRate(1000), SampleRate(7000), 1); + let size_estimation = output.len(); + let output = output.collect::>(); + assert_eq!(output, [1, 2, 4, 6, 8, 10, 12, 14]); + assert!((size_estimation as f32 / output.len() as f32).abs() < 2.0); +} + +#[test] +fn downsample() { + let input = Vec::from_iter(0u16..17); + let output = + SampleRateConverter::new(input.into_iter(), SampleRate(12000), SampleRate(2400), 1); + let size_estimation = output.len(); + let output = output.collect::>(); + assert_eq!(output, [0, 5, 10, 15]); + assert!((size_estimation as f32 / output.len() as f32).abs() < 2.0); +} diff --git a/src/conversions/sample_rate/hifi_rubato.rs b/src/conversions/sample_rate/hifi_rubato.rs index 16628079..22a5fd06 100644 --- a/src/conversions/sample_rate/hifi_rubato.rs +++ b/src/conversions/sample_rate/hifi_rubato.rs @@ -2,45 +2,77 @@ // fast in house resampler this does not provide ExactSizeIterator. We cannot // do that since rubato does not guaranteed the amount of samples returned -use std::vec; - use rubato::{Resampler, SincInterpolationParameters}; use crate::Sample; +#[cfg(test)] +mod test; + /// Rubato requires the samples for each channel to be in separate buffers. /// This wrapper around Vec> provides an iterator that returns /// samples interleaved. -struct Resampled { +struct ResamplerOutput { channel_buffers: Vec>, + frames_in_buffer: usize, next_channel: usize, next_frame: usize, } -impl Resampled { - fn new(channels: u16, capacity_per_channel: usize) -> Self { +impl ResamplerOutput { + fn for_resampler(resampler: &rubato::SincFixedOut) -> Self { Self { - channel_buffers: vec![Vec::with_capacity(capacity_per_channel); channels as usize], + channel_buffers: resampler.output_buffer_allocate(true), + frames_in_buffer: 0, next_channel: 0, next_frame: 0, } } fn empty_buffers(&mut self) -> &mut Vec> { - self.channel_buffers.clear(); &mut self.channel_buffers } + + fn trim_silent_end(&mut self) { + let Some(longest_trimmed_len) = self + .channel_buffers + .iter() + .take(self.frames_in_buffer) + .map(|buf| { + let silence = buf.iter().rev().take_while(|s| **s == 0f64).count(); + self.frames_in_buffer - silence + }) + .max() + else { + return; + }; + + self.frames_in_buffer = longest_trimmed_len; + } + + fn mark_filled(&mut self, frames_in_output: usize) { + self.frames_in_buffer = frames_in_output; + self.next_frame = 0; + } } -impl Iterator for Resampled { +impl Iterator for ResamplerOutput { type Item = f64; fn next(&mut self) -> Option { - let sample = self.channel_buffers[self.next_channel].get(self.next_frame)?; - self.next_channel = (self.next_frame + 1) % self.channel_buffers.len(); - self.next_frame += 1; - - Some(*sample) + if self.next_frame >= self.frames_in_buffer { + None + } else { + dbg!(self.frames_in_buffer); + let sample = self + .channel_buffers + .get(self.next_channel) + .expect("num channels larger then zero") + .get(self.next_frame)?; + self.next_channel = (self.next_frame + 1) % self.channel_buffers.len(); + self.next_frame += 1; + Some(*sample) + } } } @@ -53,10 +85,7 @@ where // for size hint resample_ratio: f64, - resampled: Resampled, - /// in number of audio frames where one frame is all the samples - /// for all channels. - resampler_chunk_size: usize, + resampled: ResamplerOutput, resampler_input: Vec>, resampler: rubato::SincFixedOut, } @@ -92,14 +121,12 @@ where window, }; - let resampler_input_size = 1024; - let resampler_output_size = - ((resampler_input_size as f64) * resample_ratio).ceil() as usize; + let resampler_chunk_size = 1024; let resampler = rubato::SincFixedOut::::new( resample_ratio, max_resample_ratio_relative, params, - resampler_input_size, + resampler_chunk_size, num_channels as usize, ) .unwrap(); @@ -107,10 +134,9 @@ where SampleRateConverter { input, resample_ratio, - resampled: Resampled::new(num_channels, resampler_output_size), - resampler_chunk_size: 1024, + resampled: ResamplerOutput::for_resampler(&resampler), + resampler_input: resampler.input_buffer_allocate(false), resampler, - resampler_input: vec![Vec::with_capacity(resampler_input_size); num_channels as usize], } } @@ -127,8 +153,12 @@ where } fn fill_resampler_input(&mut self) { - self.resampler_input.clear(); - for _ in 0..self.resampler_chunk_size { + for channel_buffer in self.resampler_input.iter_mut() { + channel_buffer.clear(); + } + + let needed_frames = self.resampler.input_frames_max(); + for _ in 0..needed_frames { for channel_buffer in self.resampler_input.iter_mut() { if let Some(item) = self.input.next() { channel_buffer.push(item.to_f32() as f64); @@ -154,29 +184,49 @@ where self.fill_resampler_input(); - if self.resampler_input.len() >= self.resampler_chunk_size { - self.resampler - .process_into_buffer( - &self.resampler_input, - self.resampled.empty_buffers(), - None, // all channels active - ) - .expect( - "Input and output have correct number of channels, \ - input is long enough", - ); - } else { - self.resampler - // gets the last samples out of the resampler - .process_partial_into_buffer( - // might have to pass in None if the input is empty - // something to check if this fails near the end of a source - Some(&self.resampler_input), - self.resampled.empty_buffers(), - None, // all channels active - ) - .expect("Input and output have correct number of channels, \ - input is long enough"); + let input_len = self + .resampler_input + .get(0) + .expect("num channels must be larger then zero") + .len(); + + if input_len == 0 { + return None; + } + + let mut padded_with_silence = false; + if input_len < self.resampler.input_frames_max() { + // resampler needs more frames then the input could provide, + // pad with silence + padded_with_silence = true; + for channel in &mut self.resampler_input { + channel.resize(self.resampler.input_frames_max(), 0f64); + } + } + + self.resampler_input + .iter() + .inspect(|buf| println!("{:?}", &buf[0..20])) + .for_each(drop); + + let (_, frames_in_output) = self + .resampler + .process_into_buffer( + &self.resampler_input, + self.resampled.empty_buffers(), + None, // all channels active + ) + .expect("buffer sizes are correct"); + self.resampled.channel_buffers + .iter() + .inspect(|buf| println!("{:?}", &buf[0..20])) + .for_each(drop); + self.resampled.mark_filled(frames_in_output); + + + if padded_with_silence { + // remove padding + self.resampled.trim_silent_end(); } self.resampled.next() diff --git a/src/conversions/sample_rate/hifi_rubato/test.rs b/src/conversions/sample_rate/hifi_rubato/test.rs new file mode 100644 index 00000000..4475d54e --- /dev/null +++ b/src/conversions/sample_rate/hifi_rubato/test.rs @@ -0,0 +1,177 @@ +use super::SampleRateConverter; +use core::time::Duration; +use cpal::{ChannelCount, SampleRate}; +use quickcheck::TestResult; +use quickcheck_macros::quickcheck; + +// /// Check that resampling an empty input produces no output. +// #[quickcheck] +// fn empty(from: u16, to: u16, channels: u8) -> TestResult { +// if channels == 0 || channels > 128 || from == 0 || to == 0 { +// return TestResult::discard(); +// } +// let from = SampleRate(from as u32); +// let to = SampleRate(to as u32); +// +// let input: Vec = Vec::new(); +// let output = SampleRateConverter::new(input.into_iter(), from, to, channels as ChannelCount) +// .collect::>(); +// +// assert_eq!(output, []); +// TestResult::passed() +// } +// +// /// Check that resampling to the same rate does not change the signal. +// #[quickcheck] +// fn identity(from: u16, channels: u8, input: Vec) -> TestResult { +// if channels == 0 || channels > 128 || from == 0 { +// return TestResult::discard(); +// } +// let from = SampleRate(from as u32); +// +// let output = SampleRateConverter::new( +// input.clone().into_iter(), +// from, +// from, +// channels as ChannelCount, +// ) +// .collect::>(); +// +// TestResult::from_bool(input == output) +// } +// +// /// Check that dividing the sample rate by k (integer) is the same as +// /// dropping a sample from each channel. +// #[quickcheck] +// fn divide_sample_rate(to: u16, k: u16, input: Vec, channels: u8) -> TestResult { +// if k == 0 || channels == 0 || channels > 128 || to == 0 || to > 48000 { +// return TestResult::discard(); +// } +// +// let to = SampleRate(to as u32); +// let from = to * k as u32; +// +// // Truncate the input, so it contains an integer number of frames. +// let input = { +// let ns = channels as usize; +// let mut i = input; +// i.truncate(ns * (i.len() / ns)); +// i +// }; +// +// let output = SampleRateConverter::new( +// input.clone().into_iter(), +// from, +// to, +// channels as ChannelCount, +// ) +// .collect::>(); +// +// TestResult::from_bool( +// input +// .chunks_exact(channels.into()) +// .step_by(k as usize) +// .collect::>() +// .concat() +// == output, +// ) +// } +// +// /// Check that, after multiplying the sample rate by k, every k-th +// /// sample in the output matches exactly with the input. +// #[quickcheck] +// fn multiply_sample_rate(from: u16, k: u8, input: Vec, channels: u8) -> TestResult { +// if k == 0 || channels == 0 || channels > 128 || from == 0 { +// return TestResult::discard(); +// } +// +// let from = SampleRate(from as u32); +// let to = from * k as u32; +// +// // Truncate the input, so it contains an integer number of frames. +// let input = { +// let ns = channels as usize; +// let mut i = input; +// i.truncate(ns * (i.len() / ns)); +// i +// }; +// +// let output = SampleRateConverter::new( +// input.clone().into_iter(), +// from, +// to, +// channels as ChannelCount, +// ) +// .collect::>(); +// +// TestResult::from_bool( +// input +// == output +// .chunks_exact(channels.into()) +// .step_by(k as usize) +// .collect::>() +// .concat(), +// ) +// } +// +// #[ignore] +// /// Check that resampling does not change the audio duration, +// /// except by a negligible amount (± 1ms). Reproduces #316. +// /// Ignored, pending a bug fix. +// #[quickcheck] +// fn preserve_durations(d: Duration, freq: f32, to: u32) -> TestResult { +// if to == 0 { +// return TestResult::discard(); +// } +// +// use crate::source::{SineWave, Source}; +// +// let to = SampleRate(to); +// let source = SineWave::new(freq).take_duration(d); +// let from = SampleRate(source.sample_rate()); +// +// let resampled = SampleRateConverter::new(source, from, to, 1); +// let duration = Duration::from_secs_f32(resampled.count() as f32 / to.0 as f32); +// +// let delta = if d < duration { +// duration - d +// } else { +// d - duration +// }; +// TestResult::from_bool(delta < Duration::from_millis(1)) +// } +// +// #[test] +// fn upsample() { +// let input = vec![2u16, 16, 4, 18, 6, 20, 8, 22]; +// let output = SampleRateConverter::new(input.into_iter(), SampleRate(2000), SampleRate(3000), 2); +// assert_eq!(output.len(), 12); // Test the source's Iterator::size_hint() +// +// let output = output.collect::>(); +// assert_eq!(output, [2, 16, 3, 17, 4, 18, 6, 20, 7, 21, 8, 22]); +// } +// +// #[test] +// fn upsample2() { +// let input = vec![1u16, 14]; +// let output = SampleRateConverter::new(input.into_iter(), SampleRate(1000), SampleRate(7000), 1); +// let size_estimation = output.len(); +// let output = output.collect::>(); +// assert_eq!(output, [1, 2, 4, 6, 8, 10, 12, 14]); +// assert!((size_estimation as f32 / output.len() as f32).abs() < 2.0); +// } + +#[test] +fn downsample() { + let input = [0.0, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5]; + let output = + SampleRateConverter::new(input.into_iter(), SampleRate(12000), SampleRate(2400), 1); + let (lower_bound, Some(upper_bound)) = output.size_hint() else { + panic!("expected known upper bound") + }; + + let output = output.collect::>(); + assert_eq!(output, [0.0, 0.5]); + assert!(lower_bound <= output.len()); + assert!(upper_bound >= output.len()); +} diff --git a/src/conversions/sample_rate/test.rs b/src/conversions/sample_rate/test.rs deleted file mode 100644 index b4facd29..00000000 --- a/src/conversions/sample_rate/test.rs +++ /dev/null @@ -1,204 +0,0 @@ -macro_rules! test_resampler { - ($resampler:path) => { - /// Check that resampling an empty input produces no output. - #[quickcheck] - fn empty(from: u16, to: u16, channels: u8) -> TestResult { - if channels == 0 || channels > 128 || from == 0 || to == 0 { - return TestResult::discard(); - } - let from = SampleRate(from as u32); - let to = SampleRate(to as u32); - - let input: Vec = Vec::new(); - let output = - SampleRateConverter::new(input.into_iter(), from, to, channels as ChannelCount) - .collect::>(); - - assert_eq!(output, []); - TestResult::passed() - } - - /// Check that resampling to the same rate does not change the signal. - #[quickcheck] - fn identity(from: u16, channels: u8, input: Vec) -> TestResult { - if channels == 0 || channels > 128 || from == 0 { - return TestResult::discard(); - } - let from = SampleRate(from as u32); - - let output = SampleRateConverter::new( - input.clone().into_iter(), - from, - from, - channels as ChannelCount, - ) - .map(|output| output as u16) - .collect::>(); - - TestResult::from_bool(input == output) - } - - /// Check that dividing the sample rate by k (integer) is the same as - /// dropping a sample from each channel. - #[quickcheck] - fn divide_sample_rate(to: u16, k: u16, input: Vec, channels: u8) -> TestResult { - if k == 0 || channels == 0 || channels > 128 || to == 0 || to > 48000 { - return TestResult::discard(); - } - - let to = SampleRate(to as u32); - let from = to * k as u32; - - // Truncate the input, so it contains an integer number of frames. - let input = { - let ns = channels as usize; - let mut i = input; - i.truncate(ns * (i.len() / ns)); - i - }; - - let output = SampleRateConverter::new( - input.clone().into_iter(), - from, - to, - channels as ChannelCount, - ) - .map(|output| output as u16) - .collect::>(); - - TestResult::from_bool( - input - .chunks_exact(channels.into()) - .step_by(k as usize) - .collect::>() - .concat() - == output, - ) - } - - /// Check that, after multiplying the sample rate by k, every k-th - /// sample in the output matches exactly with the input. - #[quickcheck] - fn multiply_sample_rate(from: u16, k: u8, input: Vec, channels: u8) -> TestResult { - if k == 0 || channels == 0 || channels > 128 || from == 0 { - return TestResult::discard(); - } - - let from = SampleRate(from as u32); - let to = from * k as u32; - - // Truncate the input, so it contains an integer number of frames. - let input = { - let ns = channels as usize; - let mut i = input; - i.truncate(ns * (i.len() / ns)); - i - }; - - let output = SampleRateConverter::new( - input.clone().into_iter(), - from, - to, - channels as ChannelCount, - ) - .map(|output| output as u16) - .collect::>(); - - TestResult::from_bool( - input - == output - .chunks_exact(channels.into()) - .step_by(k as usize) - .collect::>() - .concat(), - ) - } - - #[ignore] - /// Check that resampling does not change the audio duration, - /// except by a negligible amount (± 1ms). Reproduces #316. - /// Ignored, pending a bug fix. - #[quickcheck] - fn preserve_durations(d: Duration, freq: f32, to: u32) -> TestResult { - if to == 0 { - return TestResult::discard(); - } - - use crate::source::{SineWave, Source}; - - let to = SampleRate(to); - let source = SineWave::new(freq).take_duration(d); - let from = SampleRate(source.sample_rate()); - - let resampled = SampleRateConverter::new(source, from, to, 1); - let duration = Duration::from_secs_f32(resampled.count() as f32 / to.0 as f32); - - let delta = if d < duration { - duration - d - } else { - d - duration - }; - TestResult::from_bool(delta < Duration::from_millis(1)) - } - - #[test] - fn upsample() { - let input = vec![2u16, 16, 4, 18, 6, 20, 8, 22]; - let output = - SampleRateConverter::new(input.into_iter(), SampleRate(2000), SampleRate(3000), 2) - .map(|s| s as u16); - assert_eq!(output.size_hint().0, 12); // Test the source's Iterator::size_hint() - - let output = output.collect::>(); - assert_eq!(output, [2, 16, 3, 17, 4, 18, 6, 20, 7, 21, 8, 22]); - } - - #[test] - fn upsample2() { - let input = vec![1u16, 14]; - let output = - SampleRateConverter::new(input.into_iter(), SampleRate(1000), SampleRate(7000), 1) - .map(|s| s as u16); - let (lower_bound, upper_bound) = output.size_hint(); - let output = output.collect::>(); - assert_eq!(output, [1, 2, 4, 6, 8, 10, 12, 14]); - - assert!((lower_bound as f32 / output.len() as f32).abs() < 2.0); - assert!((upper_bound.unwrap() as f32 / output.len() as f32).abs() < 2.0); - } - - #[test] - fn downsample() { - let input = Vec::from_iter(0u16..17); - let output = - SampleRateConverter::new(input.into_iter(), SampleRate(12000), SampleRate(2400), 1) - .map(|s| s as u16); - let (lower_bound, upper_bound) = output.size_hint(); - let output = output.collect::>(); - assert_eq!(output, [0, 5, 10, 15]); - - assert!((lower_bound as f32 / output.len() as f32).abs() < 2.0); - assert!((upper_bound.unwrap() as f32 / output.len() as f32).abs() < 2.0); - } - }; -} - -// mod fast_inhouse { -// use super::super::fast_inhouse::SampleRateConverter; -// use core::time::Duration; -// use cpal::{ChannelCount, SampleRate}; -// use quickcheck::TestResult; -// use quickcheck_macros::quickcheck; -// -// test_resampler!(SampleRateConverter); -// } - -mod hifi_rubato { - use super::super::hifi_rubato::SampleRateConverter; - use core::time::Duration; - use cpal::{ChannelCount, SampleRate}; - use quickcheck::TestResult; - use quickcheck_macros::quickcheck; - - test_resampler!(SampleRateConverter); -} From 8f3a5cb6c25adfd0b9ba12cdb5a8cb1f74452463 Mon Sep 17 00:00:00 2001 From: dvdsk Date: Sat, 4 Jan 2025 15:19:24 +0100 Subject: [PATCH 5/6] Introductes Resampler trait and removes DataConverter Matching rubato's interface to the current inhouse solution does not work. Rubato always outputs f32, inhouse matches input. You can not have an Iterator that sometimes outputs a fixed type and sometimes is generic (generic generics don't exist). Solution: do away with DataConverter and make the resampler convert to the target sample type. Now both rubato and inhouse are generic, both output the target sample type --- Cargo.toml | 1 + src/conversions/mod.rs | 1 - src/conversions/sample.rs | 60 +------- src/conversions/sample_rate.rs | 81 +++++++++++ src/conversions/sample_rate/fast_inhouse.rs | 30 ++-- .../sample_rate/fast_inhouse/test.rs | 27 +++- src/conversions/sample_rate/hifi_rubato.rs | 66 ++++++--- .../sample_rate/hifi_rubato/test.rs | 2 +- src/mixer.rs | 6 +- src/source/mix.rs | 18 ++- src/source/mod.rs | 1 + src/source/uniform.rs | 128 +++++++++++------- 12 files changed, 264 insertions(+), 157 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 43352ad8..fa72adf7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,7 @@ num-rational = "0.4.2" default = ["flac", "vorbis", "wav", "mp3", "rubato"] tracing = ["dep:tracing"] experimental = ["dep:atomic_float"] +# experimental-hifi-resampler = ["dep:rubato"] flac = ["claxon"] vorbis = ["lewton"] diff --git a/src/conversions/mod.rs b/src/conversions/mod.rs index 16879114..73631b85 100644 --- a/src/conversions/mod.rs +++ b/src/conversions/mod.rs @@ -6,7 +6,6 @@ This includes conversion between sample formats, channels or sample rates. */ pub use channels::ChannelCountConverter; -pub use sample::DataConverter; pub use sample::Sample; mod channels; diff --git a/src/conversions/sample.rs b/src/conversions/sample.rs index 3164f75a..29d1537d 100644 --- a/src/conversions/sample.rs +++ b/src/conversions/sample.rs @@ -1,62 +1,4 @@ -use cpal::{FromSample, Sample as CpalSample}; -use std::marker::PhantomData; - -/// Converts the samples data type to `O`. -#[derive(Clone, Debug)] -pub struct DataConverter { - input: I, - marker: PhantomData, -} - -impl DataConverter { - /// Builds a new converter. - #[inline] - pub fn new(input: I) -> DataConverter { - DataConverter { - input, - marker: PhantomData, - } - } - - /// Destroys this iterator and returns the underlying iterator. - #[inline] - pub fn into_inner(self) -> I { - self.input - } - - /// get mutable access to the iterator - #[inline] - pub fn inner_mut(&mut self) -> &mut I { - &mut self.input - } -} - -impl Iterator for DataConverter -where - I: Iterator, - I::Item: Sample, - O: FromSample + Sample, -{ - type Item = O; - - #[inline] - fn next(&mut self) -> Option { - self.input.next().map(|s| CpalSample::from_sample(s)) - } - - #[inline] - fn size_hint(&self) -> (usize, Option) { - self.input.size_hint() - } -} - -impl ExactSizeIterator for DataConverter -where - I: ExactSizeIterator, - I::Item: Sample, - O: FromSample + Sample, -{ -} +use cpal::Sample as CpalSample; /// Represents a value of a single sample. /// diff --git a/src/conversions/sample_rate.rs b/src/conversions/sample_rate.rs index 75039451..ff735caf 100644 --- a/src/conversions/sample_rate.rs +++ b/src/conversions/sample_rate.rs @@ -1,2 +1,83 @@ pub(crate) mod fast_inhouse; +// #[cfg(feature = "experimental-hifi-resampler")] pub(crate) mod hifi_rubato; + +pub trait Resampler: Iterator +where + I: Iterator, + I::Item: crate::Sample + Clone, + O: crate::Sample, +{ + type Parts; + fn new_parts() -> Self::Parts; + + fn from_parts( + input: I, + parts: Self::Parts, + from: cpal::SampleRate, + to: cpal::SampleRate, + num_channels: cpal::ChannelCount, + ) -> Self; + fn inner_mut(&mut self) -> &mut I; + fn into_source_and_parts(self) -> (I, Self::Parts); +} + +impl Resampler for fast_inhouse::SampleRateConverter +where + I: Iterator, + I::Item: crate::Sample + Clone, + O: crate::Sample + cpal::FromSample, +{ + type Parts = (); + + fn new_parts() -> Self::Parts { + () + } + + fn from_parts( + input: I, + _: Self::Parts, + from: cpal::SampleRate, + to: cpal::SampleRate, + num_channels: cpal::ChannelCount, + ) -> fast_inhouse::SampleRateConverter { + fast_inhouse::SampleRateConverter::new(input, from, to, num_channels) + } + + fn inner_mut(&mut self) -> &mut I { + self.inner_mut() + } + fn into_source_and_parts(self) -> (I, Self::Parts) { + (self.into_inner(), ()) + } +} + +impl Resampler for hifi_rubato::SampleRateConverter +where + I: Iterator, + I::Item: crate::Sample + Clone, + O: crate::Sample + cpal::FromSample, +{ + type Parts = (); + + fn new_parts() -> Self::Parts { + () + } + + fn from_parts( + input: I, + _: Self::Parts, + from: cpal::SampleRate, + to: cpal::SampleRate, + num_channels: cpal::ChannelCount, + ) -> hifi_rubato::SampleRateConverter { + hifi_rubato::SampleRateConverter::new(input, from, to, num_channels) + } + + fn inner_mut(&mut self) -> &mut I { + self.inner_mut() + } + fn into_source_and_parts(self) -> (I, Self::Parts) { + (self.into_inner(), ()) + } +} diff --git a/src/conversions/sample_rate/fast_inhouse.rs b/src/conversions/sample_rate/fast_inhouse.rs index 87da97a6..34235599 100644 --- a/src/conversions/sample_rate/fast_inhouse.rs +++ b/src/conversions/sample_rate/fast_inhouse.rs @@ -1,6 +1,7 @@ use crate::conversions::Sample; use num_rational::Ratio; +use std::marker::PhantomData; use std::mem; #[cfg(test)] @@ -8,9 +9,10 @@ mod test; /// Iterator that converts from a certain sample rate to another. #[derive(Clone, Debug)] -pub struct SampleRateConverter +pub struct SampleRateConverter where I: Iterator, + O: cpal::FromSample, { /// The iterator that gives us samples. input: I, @@ -31,12 +33,15 @@ where next_output_frame_pos_in_chunk: u32, /// The buffer containing the samples waiting to be output. output_buffer: Vec, + + output_type: PhantomData, } -impl SampleRateConverter +impl SampleRateConverter where I: Iterator, I::Item: Sample, + O: cpal::FromSample, { /// Create new sample rate converter. /// @@ -57,7 +62,7 @@ where from: cpal::SampleRate, to: cpal::SampleRate, num_channels: cpal::ChannelCount, - ) -> SampleRateConverter { + ) -> SampleRateConverter { let from = from.0; let to = to.0; @@ -93,6 +98,7 @@ where current_frame: first_samples, next_frame: next_samples, output_buffer: Vec::with_capacity(num_channels as usize - 1), + output_type: PhantomData, } } @@ -123,23 +129,24 @@ where } } -impl Iterator for SampleRateConverter +impl Iterator for SampleRateConverter where I: Iterator, I::Item: Sample + Clone, + O: cpal::FromSample, { - type Item = I::Item; + type Item = O; - fn next(&mut self) -> Option { + fn next(&mut self) -> Option { // the algorithm below doesn't work if `self.from == self.to` if self.from == self.to { debug_assert_eq!(self.from, 1); - return self.input.next(); + return self.input.next().map(|s| O::from_sample_(s)); } // Short circuit if there are some samples waiting. if !self.output_buffer.is_empty() { - return Some(self.output_buffer.remove(0)); + return Some(self.output_buffer.remove(0)).map(|s| O::from_sample_(s)); } // The frame we are going to return from this function will be a linear interpolation @@ -192,14 +199,14 @@ where self.next_output_frame_pos_in_chunk += 1; if result.is_some() { - result + result.map(|s| O::from_sample_(s)) } else { // draining `self.current_frame` if !self.current_frame.is_empty() { let r = Some(self.current_frame.remove(0)); mem::swap(&mut self.output_buffer, &mut self.current_frame); self.current_frame.clear(); - r + r.map(|s| O::from_sample_(s)) } else { None } @@ -245,9 +252,10 @@ where } } -impl ExactSizeIterator for SampleRateConverter +impl ExactSizeIterator for SampleRateConverter where I: ExactSizeIterator, I::Item: Sample + Clone, + O: cpal::FromSample, { } diff --git a/src/conversions/sample_rate/fast_inhouse/test.rs b/src/conversions/sample_rate/fast_inhouse/test.rs index c4fa14b5..ab35d268 100644 --- a/src/conversions/sample_rate/fast_inhouse/test.rs +++ b/src/conversions/sample_rate/fast_inhouse/test.rs @@ -4,7 +4,6 @@ use cpal::{ChannelCount, SampleRate}; use quickcheck::TestResult; use quickcheck_macros::quickcheck; - /// Check that resampling an empty input produces no output. #[quickcheck] fn empty(from: u16, to: u16, channels: u8) -> TestResult { @@ -16,7 +15,7 @@ fn empty(from: u16, to: u16, channels: u8) -> TestResult { let input: Vec = Vec::new(); let output = SampleRateConverter::new(input.into_iter(), from, to, channels as ChannelCount) - .collect::>(); + .collect::>(); assert_eq!(output, []); TestResult::passed() @@ -131,7 +130,7 @@ fn preserve_durations(d: Duration, freq: f32, to: u32) -> TestResult { let source = SineWave::new(freq).take_duration(d); let from = SampleRate(source.sample_rate()); - let resampled = SampleRateConverter::new(source, from, to, 1); + let resampled = SampleRateConverter::<_, f32>::new(source, from, to, 1); let duration = Duration::from_secs_f32(resampled.count() as f32 / to.0 as f32); let delta = if d < duration { @@ -145,7 +144,12 @@ fn preserve_durations(d: Duration, freq: f32, to: u32) -> TestResult { #[test] fn upsample() { let input = vec![2u16, 16, 4, 18, 6, 20, 8, 22]; - let output = SampleRateConverter::new(input.into_iter(), SampleRate(2000), SampleRate(3000), 2); + let output = SampleRateConverter::<_, u16>::new( + input.into_iter(), + SampleRate(2000), + SampleRate(3000), + 2, + ); assert_eq!(output.len(), 12); // Test the source's Iterator::size_hint() let output = output.collect::>(); @@ -155,7 +159,12 @@ fn upsample() { #[test] fn upsample2() { let input = vec![1u16, 14]; - let output = SampleRateConverter::new(input.into_iter(), SampleRate(1000), SampleRate(7000), 1); + let output = SampleRateConverter::<_, u16>::new( + input.into_iter(), + SampleRate(1000), + SampleRate(7000), + 1, + ); let size_estimation = output.len(); let output = output.collect::>(); assert_eq!(output, [1, 2, 4, 6, 8, 10, 12, 14]); @@ -165,8 +174,12 @@ fn upsample2() { #[test] fn downsample() { let input = Vec::from_iter(0u16..17); - let output = - SampleRateConverter::new(input.into_iter(), SampleRate(12000), SampleRate(2400), 1); + let output = SampleRateConverter::<_, u16>::new( + input.into_iter(), + SampleRate(12000), + SampleRate(2400), + 1, + ); let size_estimation = output.len(); let output = output.collect::>(); assert_eq!(output, [0, 5, 10, 15]); diff --git a/src/conversions/sample_rate/hifi_rubato.rs b/src/conversions/sample_rate/hifi_rubato.rs index 22a5fd06..afa650f4 100644 --- a/src/conversions/sample_rate/hifi_rubato.rs +++ b/src/conversions/sample_rate/hifi_rubato.rs @@ -2,6 +2,7 @@ // fast in house resampler this does not provide ExactSizeIterator. We cannot // do that since rubato does not guaranteed the amount of samples returned +use std::marker::PhantomData; use rubato::{Resampler, SincInterpolationParameters}; use crate::Sample; @@ -10,17 +11,17 @@ use crate::Sample; mod test; /// Rubato requires the samples for each channel to be in separate buffers. -/// This wrapper around Vec> provides an iterator that returns +/// This wrapper around Vec> provides an iterator that returns /// samples interleaved. struct ResamplerOutput { - channel_buffers: Vec>, + channel_buffers: Vec>, frames_in_buffer: usize, next_channel: usize, next_frame: usize, } impl ResamplerOutput { - fn for_resampler(resampler: &rubato::SincFixedOut) -> Self { + fn for_resampler(resampler: &rubato::SincFixedOut) -> Self { Self { channel_buffers: resampler.output_buffer_allocate(true), frames_in_buffer: 0, @@ -29,7 +30,7 @@ impl ResamplerOutput { } } - fn empty_buffers(&mut self) -> &mut Vec> { + fn empty_buffers(&mut self) -> &mut Vec> { &mut self.channel_buffers } @@ -39,7 +40,7 @@ impl ResamplerOutput { .iter() .take(self.frames_in_buffer) .map(|buf| { - let silence = buf.iter().rev().take_while(|s| **s == 0f64).count(); + let silence = buf.iter().rev().take_while(|s| **s == 0f32).count(); self.frames_in_buffer - silence }) .max() @@ -57,7 +58,7 @@ impl ResamplerOutput { } impl Iterator for ResamplerOutput { - type Item = f64; + type Item = f32; fn next(&mut self) -> Option { if self.next_frame >= self.frames_in_buffer { @@ -76,9 +77,11 @@ impl Iterator for ResamplerOutput { } } -pub struct SampleRateConverter +pub struct SampleRateConverter where I: Iterator, + I::Item: Sample, + O: cpal::FromSample, { input: I, @@ -86,14 +89,31 @@ where resample_ratio: f64, resampled: ResamplerOutput, - resampler_input: Vec>, - resampler: rubato::SincFixedOut, + resampler_input: Vec>, + resampler: rubato::SincFixedOut, + + output_type: PhantomData, } -impl SampleRateConverter +impl std::fmt::Debug for SampleRateConverter where I: Iterator, I::Item: Sample, + O: cpal::FromSample, +{ + fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + fmt.debug_struct("SampleRateConverter") + .field("resample_ratio", &self.resample_ratio) + .field("channels", &self.resampler_input.len()) + .finish() + } +} + +impl SampleRateConverter +where + I: Iterator, + I::Item: Sample, + O: cpal::FromSample, { #[inline] pub fn new( @@ -101,7 +121,7 @@ where from: cpal::SampleRate, to: cpal::SampleRate, num_channels: cpal::ChannelCount, - ) -> SampleRateConverter { + ) -> SampleRateConverter { let from = from.0; let to = to.0; @@ -122,7 +142,7 @@ where }; let resampler_chunk_size = 1024; - let resampler = rubato::SincFixedOut::::new( + let resampler = rubato::SincFixedOut::::new( resample_ratio, max_resample_ratio_relative, params, @@ -137,6 +157,7 @@ where resampled: ResamplerOutput::for_resampler(&resampler), resampler_input: resampler.input_buffer_allocate(false), resampler, + output_type: PhantomData, } } @@ -161,7 +182,7 @@ where for _ in 0..needed_frames { for channel_buffer in self.resampler_input.iter_mut() { if let Some(item) = self.input.next() { - channel_buffer.push(item.to_f32() as f64); + channel_buffer.push(item.to_f32() as f32); } else { break; } @@ -170,16 +191,17 @@ where } } -impl Iterator for SampleRateConverter +impl Iterator for SampleRateConverter where I: Iterator, I::Item: Sample + Clone, + O: cpal::FromSample, { - type Item = f64; + type Item = O; - fn next(&mut self) -> Option { - if let Some(item) = self.resampled.next() { - return Some(item); + fn next(&mut self) -> Option { + if let Some(sample) = self.resampled.next() { + return Some(O::from_sample_(sample)); } self.fill_resampler_input(); @@ -200,7 +222,7 @@ where // pad with silence padded_with_silence = true; for channel in &mut self.resampler_input { - channel.resize(self.resampler.input_frames_max(), 0f64); + channel.resize(self.resampler.input_frames_max(), 0f32); } } @@ -217,19 +239,19 @@ where None, // all channels active ) .expect("buffer sizes are correct"); - self.resampled.channel_buffers + self.resampled + .channel_buffers .iter() .inspect(|buf| println!("{:?}", &buf[0..20])) .for_each(drop); self.resampled.mark_filled(frames_in_output); - if padded_with_silence { // remove padding self.resampled.trim_silent_end(); } - self.resampled.next() + self.resampled.next().map(|s| O::from_sample_(s)) } #[inline] diff --git a/src/conversions/sample_rate/hifi_rubato/test.rs b/src/conversions/sample_rate/hifi_rubato/test.rs index 4475d54e..bb87f832 100644 --- a/src/conversions/sample_rate/hifi_rubato/test.rs +++ b/src/conversions/sample_rate/hifi_rubato/test.rs @@ -170,7 +170,7 @@ fn downsample() { panic!("expected known upper bound") }; - let output = output.collect::>(); + let output = output.collect::>(); assert_eq!(output, [0.0, 0.5]); assert!(lower_bound <= output.len()); assert!(upper_bound >= output.len()); diff --git a/src/mixer.rs b/src/mixer.rs index 92b3e4eb..6fe43548 100644 --- a/src/mixer.rs +++ b/src/mixer.rs @@ -4,7 +4,7 @@ use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Mutex}; use std::time::Duration; -use crate::source::{SeekError, Source, UniformSourceIterator}; +use crate::source::{SeekError, Source, UniformSourceIterator, TakeFrame}; use crate::Sample; /// Builds a new mixer. @@ -53,7 +53,9 @@ where where T: Source + Send + 'static, { - let uniform_source = UniformSourceIterator::new(source, self.channels, self.sample_rate); + use crate::conversions::sample_rate::fast_inhouse::SampleRateConverter; + let uniform_source: UniformSourceIterator<_, _, SampleRateConverter, S>> = + UniformSourceIterator::new(source, self.channels, self.sample_rate); self.pending_sources .lock() .unwrap() diff --git a/src/source/mix.rs b/src/source/mix.rs index f2396546..57e79347 100644 --- a/src/source/mix.rs +++ b/src/source/mix.rs @@ -1,11 +1,14 @@ use std::cmp; use std::time::Duration; +use crate::conversions::sample_rate::fast_inhouse; use crate::source::uniform::UniformSourceIterator; use crate::source::SeekError; use crate::{Sample, Source}; use cpal::{FromSample, Sample as CpalSample}; +use super::TakeFrame; + /// Internal function that builds a `Mix` object. pub fn mix(input1: I1, input2: I2) -> Mix where @@ -24,7 +27,6 @@ where } /// Filter that modifies each sample by a given value. -#[derive(Clone)] pub struct Mix where I1: Source, @@ -32,8 +34,18 @@ where I2: Source, I2::Item: Sample, { - input1: UniformSourceIterator, - input2: UniformSourceIterator, + input1: UniformSourceIterator< + I1, + I1::Item, +// #[cfg(not(feature = "experimental-hifi-resampler"))] + fast_inhouse::SampleRateConverter, I1::Item>, +// #[cfg(feature = "experimental-hifi-resampler")] + >, + input2: UniformSourceIterator< + I2, + I2::Item, + fast_inhouse::SampleRateConverter, I2::Item>, + >, } impl Iterator for Mix diff --git a/src/source/mod.rs b/src/source/mod.rs index e730e802..553c1a59 100644 --- a/src/source/mod.rs +++ b/src/source/mod.rs @@ -6,6 +6,7 @@ use core::time::Duration; use cpal::FromSample; use crate::Sample; +pub(crate) use uniform::TakeFrame; pub use self::agc::AutomaticGainControl; pub use self::amplify::Amplify; diff --git a/src/source/uniform.rs b/src/source/uniform.rs index dda61ea9..e6e2000f 100644 --- a/src/source/uniform.rs +++ b/src/source/uniform.rs @@ -1,10 +1,13 @@ use std::cmp; +use std::marker::PhantomData; use std::time::Duration; use cpal::FromSample; -use crate::conversions::sample_rate::fast_inhouse::SampleRateConverter; -use crate::conversions::{ChannelCountConverter, DataConverter}; +// #[cfg(not(feature = "experimental-hifi-resampler"))] +// #[cfg(feature = "experimental-hifi-resampler")] +use crate::conversions::sample_rate::Resampler; +use crate::conversions::ChannelCountConverter; use crate::{Sample, Source}; use super::SeekError; @@ -14,24 +17,30 @@ use super::SeekError; /// /// It implements `Source` as well, but all the data is guaranteed to be in a /// single frame whose channels and samples rate have been passed to `new`. -#[derive(Clone)] -pub struct UniformSourceIterator +pub struct UniformSourceIterator where I: Source, I::Item: Sample, - D: Sample, + D: FromSample + Sample, + R: Resampler, D>, { - inner: Option>>, D>>, + // recreated each frame as each frame the channel + // count and sample rate may change. + /// only none while setting up the next frame + inner: Option>, target_channels: u16, target_sample_rate: u32, total_duration: Option, + source_type: PhantomData, + output_type: PhantomData, } -impl UniformSourceIterator +impl UniformSourceIterator where I: Source, I::Item: Sample, - D: Sample, + D: FromSample + Sample, + R: Resampler, D>, { /// Wrap a `Source` and lazily convert its samples to a specific type, /// sample-rate and channels count. @@ -40,71 +49,76 @@ where input: I, target_channels: u16, target_sample_rate: u32, - ) -> UniformSourceIterator { + ) -> UniformSourceIterator { let total_duration = input.total_duration(); - let input = UniformSourceIterator::bootstrap(input, target_channels, target_sample_rate); + let resampler_parts = R::new_parts(); + let input = + Self::convert_frame(input, resampler_parts, target_channels, target_sample_rate); UniformSourceIterator { inner: Some(input), target_channels, target_sample_rate, total_duration, + source_type: PhantomData, + output_type: PhantomData, } } #[inline] - fn bootstrap( + fn convert_frame( input: I, + resampler_parts: R::Parts, target_channels: u16, target_sample_rate: u32, - ) -> DataConverter>>, D> { + ) -> ChannelCountConverter { // Limit the frame length to something reasonable let frame_len = input.current_frame_len().map(|x| x.min(32768)); let from_channels = input.channels(); let from_sample_rate = input.sample_rate(); - let input = Take { - iter: input, + let input = TakeFrame { + inner: input, n: frame_len, }; - let input = SampleRateConverter::new( + let input = R::from_parts( input, + resampler_parts, cpal::SampleRate(from_sample_rate), cpal::SampleRate(target_sample_rate), from_channels, ); - let input = ChannelCountConverter::new(input, from_channels, target_channels); - - DataConverter::new(input) + ChannelCountConverter::new(input, from_channels, target_channels) } } -impl Iterator for UniformSourceIterator +impl Iterator for UniformSourceIterator where I: Source, I::Item: Sample, D: FromSample + Sample, + R: Resampler, D>, { type Item = D; #[inline] fn next(&mut self) -> Option { - if let Some(value) = self.inner.as_mut().unwrap().next() { + if let Some(value) = self.inner.as_mut().expect("not setting up frame").next() { return Some(value); } - let input = self - .inner - .take() - .unwrap() - .into_inner() - .into_inner() - .into_inner() - .iter; + let channel_count_converter = self.inner.take().expect("not setting up frame"); + let resampler = channel_count_converter.into_inner(); + let (single_frame, resampler_parts) = resampler.into_source_and_parts(); + let source = single_frame.into_inner(); - let mut input = - UniformSourceIterator::bootstrap(input, self.target_channels, self.target_sample_rate); + let mut input = UniformSourceIterator::convert_frame( + source, + resampler_parts, + self.target_channels, + self.target_sample_rate, + ); let value = input.next(); self.inner = Some(input); @@ -117,11 +131,12 @@ where } } -impl Source for UniformSourceIterator +impl Source for UniformSourceIterator where I: Iterator + Source, I::Item: Sample, D: FromSample + Sample, + R: Resampler, D>, { #[inline] fn current_frame_len(&self) -> Option { @@ -146,11 +161,10 @@ where #[inline] fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> { if let Some(input) = self.inner.as_mut() { - input - .inner_mut() - .inner_mut() - .inner_mut() - .inner_mut() + input // UniformSourceIterator + .inner_mut() // ChannelCountConverter + .inner_mut() // Resampler + .inner_mut() // TakeFrame .try_seek(pos) } else { Ok(()) @@ -159,42 +173,56 @@ where } #[derive(Clone, Debug)] -struct Take { - iter: I, +pub struct TakeFrame +where + S: Source, + ::Item: crate::Sample, +{ + inner: S, n: Option, } -impl Take { +impl TakeFrame +where + S: Source, + ::Item: crate::Sample, +{ + #[inline] + pub fn inner_mut(&mut self) -> &mut S { + &mut self.inner + } + #[inline] - pub fn inner_mut(&mut self) -> &mut I { - &mut self.iter + pub fn into_inner(self) -> S { + self.inner } } -impl Iterator for Take +impl Iterator for TakeFrame where - I: Iterator, + S: Source, + ::Item: crate::Sample, { - type Item = ::Item; + type Item = ::Item; #[inline] - fn next(&mut self) -> Option<::Item> { + fn next(&mut self) -> Option<::Item> { if let Some(n) = &mut self.n { if *n != 0 { *n -= 1; - self.iter.next() + self.inner.next() } else { None } } else { - self.iter.next() + self.inner.next() } } #[inline] fn size_hint(&self) -> (usize, Option) { if let Some(n) = self.n { - let (lower, upper) = self.iter.size_hint(); + let (lower, upper) = self.inner.size_hint(); let lower = cmp::min(lower, n); @@ -205,9 +233,7 @@ where (lower, upper) } else { - self.iter.size_hint() + self.inner.size_hint() } } } - -impl ExactSizeIterator for Take where I: ExactSizeIterator {} From 05bb5ee6a7723333cafc3dbf2e78c3da58b5c81c Mon Sep 17 00:00:00 2001 From: dvdsk Date: Sat, 4 Jan 2025 16:53:33 +0100 Subject: [PATCH 6/6] fixes trait bound issues --- src/conversions/sample.rs | 18 +++++++++++++ src/conversions/sample_rate.rs | 14 +++++----- src/conversions/sample_rate/fast_inhouse.rs | 16 ++++++------ src/conversions/sample_rate/hifi_rubato.rs | 12 ++++----- src/source/crossfade.rs | 4 +-- src/source/mix.rs | 29 +++++++++------------ src/source/mod.rs | 13 ++++----- src/source/uniform.rs | 2 -- 8 files changed, 61 insertions(+), 47 deletions(-) diff --git a/src/conversions/sample.rs b/src/conversions/sample.rs index 29d1537d..1db85068 100644 --- a/src/conversions/sample.rs +++ b/src/conversions/sample.rs @@ -27,6 +27,9 @@ pub trait Sample: CpalSample { /// Converts the sample to a f32 value. fn to_f32(self) -> f32; + /// Construct the sample from a f32 value. + fn from_f32(sample: f32) -> Self; + /// Calls `saturating_add` on the sample. fn saturating_add(self, other: Self) -> Self; @@ -55,6 +58,11 @@ impl Sample for u16 { (self as f32 - 32768.0) / 32768.0 } + #[inline] + fn from_f32(sample: f32) -> Self { + cpal::Sample::from_sample(sample) + } + #[inline] fn saturating_add(self, other: u16) -> u16 { self.saturating_add(other) @@ -84,6 +92,11 @@ impl Sample for i16 { self as f32 / 32768.0 } + #[inline] + fn from_f32(sample: f32) -> Self { + cpal::Sample::from_sample(sample) + } + #[inline] fn saturating_add(self, other: i16) -> i16 { self.saturating_add(other) @@ -112,6 +125,11 @@ impl Sample for f32 { self } + #[inline] + fn from_f32(sample: f32) -> Self { + cpal::Sample::from_sample(sample) + } + #[inline] fn saturating_add(self, other: f32) -> f32 { self + other diff --git a/src/conversions/sample_rate.rs b/src/conversions/sample_rate.rs index ff735caf..60f426f2 100644 --- a/src/conversions/sample_rate.rs +++ b/src/conversions/sample_rate.rs @@ -1,3 +1,5 @@ +use crate::Sample; + pub(crate) mod fast_inhouse; // #[cfg(feature = "experimental-hifi-resampler")] pub(crate) mod hifi_rubato; @@ -5,8 +7,8 @@ pub(crate) mod hifi_rubato; pub trait Resampler: Iterator where I: Iterator, - I::Item: crate::Sample + Clone, - O: crate::Sample, + I::Item: Sample + Clone, + O: Sample, { type Parts; fn new_parts() -> Self::Parts; @@ -25,8 +27,8 @@ where impl Resampler for fast_inhouse::SampleRateConverter where I: Iterator, - I::Item: crate::Sample + Clone, - O: crate::Sample + cpal::FromSample, + I::Item: Sample + Clone, + O: Sample + cpal::FromSample, { type Parts = (); @@ -55,8 +57,8 @@ where impl Resampler for hifi_rubato::SampleRateConverter where I: Iterator, - I::Item: crate::Sample + Clone, - O: crate::Sample + cpal::FromSample, + I::Item: Sample + Clone, + O: Sample, { type Parts = (); diff --git a/src/conversions/sample_rate/fast_inhouse.rs b/src/conversions/sample_rate/fast_inhouse.rs index 34235599..d380ac52 100644 --- a/src/conversions/sample_rate/fast_inhouse.rs +++ b/src/conversions/sample_rate/fast_inhouse.rs @@ -12,7 +12,7 @@ mod test; pub struct SampleRateConverter where I: Iterator, - O: cpal::FromSample, + O: Sample, { /// The iterator that gives us samples. input: I, @@ -41,7 +41,7 @@ impl SampleRateConverter where I: Iterator, I::Item: Sample, - O: cpal::FromSample, + O: Sample, { /// Create new sample rate converter. /// @@ -133,7 +133,7 @@ impl Iterator for SampleRateConverter where I: Iterator, I::Item: Sample + Clone, - O: cpal::FromSample, + O: Sample + cpal::FromSample, { type Item = O; @@ -141,12 +141,12 @@ where // the algorithm below doesn't work if `self.from == self.to` if self.from == self.to { debug_assert_eq!(self.from, 1); - return self.input.next().map(|s| O::from_sample_(s)); + return self.input.next().map(|s| cpal::Sample::from_sample(s)); } // Short circuit if there are some samples waiting. if !self.output_buffer.is_empty() { - return Some(self.output_buffer.remove(0)).map(|s| O::from_sample_(s)); + return Some(self.output_buffer.remove(0)).map(|s| cpal::Sample::from_sample(s)); } // The frame we are going to return from this function will be a linear interpolation @@ -199,14 +199,14 @@ where self.next_output_frame_pos_in_chunk += 1; if result.is_some() { - result.map(|s| O::from_sample_(s)) + result.map(|s| cpal::Sample::from_sample(s)) } else { // draining `self.current_frame` if !self.current_frame.is_empty() { let r = Some(self.current_frame.remove(0)); mem::swap(&mut self.output_buffer, &mut self.current_frame); self.current_frame.clear(); - r.map(|s| O::from_sample_(s)) + r.map(|s| cpal::Sample::from_sample(s)) } else { None } @@ -256,6 +256,6 @@ impl ExactSizeIterator for SampleRateConverter where I: ExactSizeIterator, I::Item: Sample + Clone, - O: cpal::FromSample, + O: Sample + cpal::FromSample, { } diff --git a/src/conversions/sample_rate/hifi_rubato.rs b/src/conversions/sample_rate/hifi_rubato.rs index afa650f4..c7704126 100644 --- a/src/conversions/sample_rate/hifi_rubato.rs +++ b/src/conversions/sample_rate/hifi_rubato.rs @@ -81,7 +81,7 @@ pub struct SampleRateConverter where I: Iterator, I::Item: Sample, - O: cpal::FromSample, + O: Sample, { input: I, @@ -99,7 +99,7 @@ impl std::fmt::Debug for SampleRateConverter where I: Iterator, I::Item: Sample, - O: cpal::FromSample, + O: Sample, { fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { fmt.debug_struct("SampleRateConverter") @@ -113,7 +113,7 @@ impl SampleRateConverter where I: Iterator, I::Item: Sample, - O: cpal::FromSample, + O: Sample, { #[inline] pub fn new( @@ -195,13 +195,13 @@ impl Iterator for SampleRateConverter where I: Iterator, I::Item: Sample + Clone, - O: cpal::FromSample, + O: Sample, { type Item = O; fn next(&mut self) -> Option { if let Some(sample) = self.resampled.next() { - return Some(O::from_sample_(sample)); + return Some(O::from_f32(sample)); } self.fill_resampler_input(); @@ -251,7 +251,7 @@ where self.resampled.trim_silent_end(); } - self.resampled.next().map(|s| O::from_sample_(s)) + self.resampled.next().map(O::from_f32) } #[inline] diff --git a/src/source/crossfade.rs b/src/source/crossfade.rs index 03c21178..afe0ea3b 100644 --- a/src/source/crossfade.rs +++ b/src/source/crossfade.rs @@ -1,7 +1,5 @@ use std::time::Duration; -use cpal::FromSample; - use crate::source::{FadeIn, Mix, TakeDuration}; use crate::{Sample, Source}; @@ -18,7 +16,7 @@ pub fn crossfade( where I1: Source, I2: Source, - I1::Item: FromSample + Sample, + I1::Item: cpal::FromSample + Sample, I2::Item: Sample, { let mut input_fadeout = input_fadeout.take_duration(duration); diff --git a/src/source/mix.rs b/src/source/mix.rs index 57e79347..6bac7b0c 100644 --- a/src/source/mix.rs +++ b/src/source/mix.rs @@ -1,7 +1,7 @@ use std::cmp; use std::time::Duration; -use crate::conversions::sample_rate::fast_inhouse; +use crate::conversions::sample_rate::{fast_inhouse, hifi_rubato}; use crate::source::uniform::UniformSourceIterator; use crate::source::SeekError; use crate::{Sample, Source}; @@ -13,15 +13,15 @@ use super::TakeFrame; pub fn mix(input1: I1, input2: I2) -> Mix where I1: Source, - I1::Item: FromSample + Sample, I2: Source, + I1::Item: Sample, I2::Item: Sample, { let channels = input1.channels(); let rate = input1.sample_rate(); Mix { - input1: UniformSourceIterator::new(input1, channels, rate), + input1, input2: UniformSourceIterator::new(input2, channels, rate), } } @@ -30,21 +30,16 @@ where pub struct Mix where I1: Source, - I1::Item: FromSample + Sample, I2: Source, + I1::Item: Sample, I2::Item: Sample, { - input1: UniformSourceIterator< - I1, - I1::Item, -// #[cfg(not(feature = "experimental-hifi-resampler"))] - fast_inhouse::SampleRateConverter, I1::Item>, -// #[cfg(feature = "experimental-hifi-resampler")] - >, + input1: I1, input2: UniformSourceIterator< I2, I2::Item, - fast_inhouse::SampleRateConverter, I2::Item>, + // fast_inhouse::SampleRateConverter, I2::Item>, + hifi_rubato::SampleRateConverter, I2::Item>, >, } @@ -87,19 +82,21 @@ where impl ExactSizeIterator for Mix where - I1: Source + ExactSizeIterator, - I1::Item: FromSample + Sample, - I2: Source + ExactSizeIterator, + I1: Source, + I2: Source, + I1::Item: Sample, I2::Item: Sample, + I1::Item: cpal::FromSample, { } impl Source for Mix where I1: Source, - I1::Item: FromSample + Sample, I2: Source, + I1::Item: Sample, I2::Item: Sample, + I1::Item: cpal::FromSample, { #[inline] fn current_frame_len(&self) -> Option { diff --git a/src/source/mod.rs b/src/source/mod.rs index 553c1a59..73d43dcb 100644 --- a/src/source/mod.rs +++ b/src/source/mod.rs @@ -183,8 +183,8 @@ where #[inline] fn mix(self, other: S) -> Mix where - Self: Sized, - Self::Item: FromSample, + Self: Source + Sized, + Self::Item: FromSample + Sample, S: Source, S::Item: Sample, { @@ -345,11 +345,12 @@ where /// /// Only the crossfaded portion (beginning of self, beginning of other) is returned. #[inline] - fn take_crossfade_with(self, other: S, duration: Duration) -> Crossfade + fn take_crossfade_with(self, other: S, duration: Duration) -> Crossfade where - Self: Sized, - Self::Item: FromSample, - ::Item: Sample, + Self: Source + Sized, + Self::Item: FromSample + Sample, + S: Source, + S::Item: Sample, { crossfade::crossfade(self, other, duration) } diff --git a/src/source/uniform.rs b/src/source/uniform.rs index e6e2000f..0746d8a8 100644 --- a/src/source/uniform.rs +++ b/src/source/uniform.rs @@ -4,8 +4,6 @@ use std::time::Duration; use cpal::FromSample; -// #[cfg(not(feature = "experimental-hifi-resampler"))] -// #[cfg(feature = "experimental-hifi-resampler")] use crate::conversions::sample_rate::Resampler; use crate::conversions::ChannelCountConverter; use crate::{Sample, Source};