diff --git a/Cargo.lock b/Cargo.lock index 636ea8cc..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]] @@ -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" @@ -679,7 +688,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -729,7 +738,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -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" @@ -826,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" @@ -865,6 +894,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" @@ -922,9 +960,11 @@ dependencies = [ "minimp3_fixed", "num-rational", "quickcheck", + "quickcheck_macros", "rand", "rstest", "rstest_reuse", + "rubato", "symphonia", "tracing", ] @@ -954,7 +994,7 @@ dependencies = [ "regex", "relative-path", "rustc_version", - "syn", + "syn 2.0.89", "unicode-ident", ] @@ -967,7 +1007,19 @@ dependencies = [ "quote", "rand", "rustc_version", - "syn", + "syn 2.0.89", +] + +[[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]] @@ -985,6 +1037,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 +1106,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" @@ -1195,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" @@ -1233,7 +1317,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -1287,7 +1371,7 @@ checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] [[package]] @@ -1299,12 +1383,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" @@ -1343,7 +1443,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn", + "syn 2.0.89", "wasm-bindgen-shared", ] @@ -1378,7 +1478,7 @@ checksum = "bf59002391099644be3524e23b781fa43d2be0c5aa0719a18c0731b9d195cab6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -1634,5 +1734,5 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.89", ] diff --git a/Cargo.toml b/Cargo.toml index 599cb059..fa72adf7 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,9 +26,10 @@ 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"] +# experimental-hifi-resampler = ["dep:rubato"] flac = ["claxon"] vorbis = ["lewton"] @@ -49,6 +51,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/mod.rs b/src/conversions/mod.rs index e6ad817e..73631b85 100644 --- a/src/conversions/mod.rs +++ b/src/conversions/mod.rs @@ -5,13 +5,9 @@ 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::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.rs b/src/conversions/sample.rs index 3164f75a..1db85068 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. /// @@ -85,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; @@ -113,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) @@ -142,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) @@ -170,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 b76c23d0..60f426f2 100644 --- a/src/conversions/sample_rate.rs +++ b/src/conversions/sample_rate.rs @@ -1,400 +1,85 @@ -use crate::conversions::Sample; +use crate::Sample; -use num_rational::Ratio; -use std::mem; +pub(crate) mod fast_inhouse; +// #[cfg(feature = "experimental-hifi-resampler")] +pub(crate) mod hifi_rubato; -/// Iterator that converts from a certain sample rate to another. -#[derive(Clone, Debug)] -pub struct SampleRateConverter +pub trait Resampler: Iterator where I: Iterator, + I::Item: Sample + Clone, + O: Sample, { - /// 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, -} + type Parts; + fn new_parts() -> Self::Parts; -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, + fn from_parts( + input: I, + parts: Self::Parts, 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; - } - } - } + ) -> Self; + fn inner_mut(&mut self) -> &mut I; + fn into_source_and_parts(self) -> (I, Self::Parts); } -impl Iterator for SampleRateConverter +impl Resampler for fast_inhouse::SampleRateConverter where I: Iterator, I::Item: Sample + Clone, + O: Sample + cpal::FromSample, { - 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); + type Parts = (); - 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 - } - } + fn new_parts() -> Self::Parts { + () } - #[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() - }; + 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) + } - if self.from == self.to { - self.input.size_hint() - } else { - let (min, max) = self.input.size_hint(); - (apply(min), max.map(apply)) - } + fn inner_mut(&mut self) -> &mut I { + self.inner_mut() + } + fn into_source_and_parts(self) -> (I, Self::Parts) { + (self.into_inner(), ()) } } -impl ExactSizeIterator for SampleRateConverter +impl Resampler for hifi_rubato::SampleRateConverter where - I: ExactSizeIterator, + I: Iterator, I::Item: Sample + Clone, + O: Sample, { -} - -#[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::>(); + type Parts = (); - 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)) - } + fn new_parts() -> Self::Parts { + () } - #[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]); + 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) } - #[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); + fn inner_mut(&mut self) -> &mut I { + self.inner_mut() } - - #[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); + 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 new file mode 100644 index 00000000..d380ac52 --- /dev/null +++ b/src/conversions/sample_rate/fast_inhouse.rs @@ -0,0 +1,261 @@ +use crate::conversions::Sample; + +use num_rational::Ratio; +use std::marker::PhantomData; +use std::mem; + +#[cfg(test)] +mod test; + +/// Iterator that converts from a certain sample rate to another. +#[derive(Clone, Debug)] +pub struct SampleRateConverter +where + I: Iterator, + O: Sample, +{ + /// 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, + + output_type: PhantomData, +} + +impl SampleRateConverter +where + I: Iterator, + I::Item: Sample, + O: 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), + output_type: 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 + } + + 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, + O: Sample + cpal::FromSample, +{ + type Item = O; + + 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().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| cpal::Sample::from_sample(s)); + } + + // 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.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| cpal::Sample::from_sample(s)) + } 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, + O: Sample + cpal::FromSample, +{ +} 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..ab35d268 --- /dev/null +++ b/src/conversions/sample_rate/fast_inhouse/test.rs @@ -0,0 +1,187 @@ +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::<_, 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 { + 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::<_, 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::>(); + 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::<_, 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]); + 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::<_, 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]); + 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..c7704126 --- /dev/null +++ b/src/conversions/sample_rate/hifi_rubato.rs @@ -0,0 +1,266 @@ +// 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::marker::PhantomData; +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 ResamplerOutput { + channel_buffers: Vec>, + frames_in_buffer: usize, + next_channel: usize, + next_frame: usize, +} + +impl ResamplerOutput { + fn for_resampler(resampler: &rubato::SincFixedOut) -> Self { + Self { + channel_buffers: resampler.output_buffer_allocate(true), + frames_in_buffer: 0, + next_channel: 0, + next_frame: 0, + } + } + + fn empty_buffers(&mut self) -> &mut Vec> { + &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 == 0f32).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 ResamplerOutput { + type Item = f32; + + fn next(&mut self) -> Option { + 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) + } + } +} + +pub struct SampleRateConverter +where + I: Iterator, + I::Item: Sample, + O: Sample, +{ + input: I, + + // for size hint + resample_ratio: f64, + + resampled: ResamplerOutput, + resampler_input: Vec>, + resampler: rubato::SincFixedOut, + + output_type: PhantomData, +} + +impl std::fmt::Debug for SampleRateConverter +where + I: Iterator, + I::Item: Sample, + O: Sample, +{ + 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: Sample, +{ + #[inline] + pub fn new( + 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 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_chunk_size = 1024; + let resampler = rubato::SincFixedOut::::new( + resample_ratio, + max_resample_ratio_relative, + params, + resampler_chunk_size, + num_channels as usize, + ) + .unwrap(); + + SampleRateConverter { + input, + resample_ratio, + resampled: ResamplerOutput::for_resampler(&resampler), + resampler_input: resampler.input_buffer_allocate(false), + resampler, + output_type: 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 + } + + fn fill_resampler_input(&mut self) { + 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 f32); + } else { + break; + } + } + } + } +} + +impl Iterator for SampleRateConverter +where + I: Iterator, + I::Item: Sample + Clone, + O: Sample, +{ + type Item = O; + + fn next(&mut self) -> Option { + if let Some(sample) = self.resampled.next() { + return Some(O::from_f32(sample)); + } + + self.fill_resampler_input(); + + 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(), 0f32); + } + } + + 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().map(O::from_f32) + } + + #[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) + } +} 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..bb87f832 --- /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/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/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 f2396546..6bac7b0c 100644 --- a/src/source/mix.rs +++ b/src/source/mix.rs @@ -1,39 +1,46 @@ use std::cmp; use std::time::Duration; +use crate::conversions::sample_rate::{fast_inhouse, hifi_rubato}; 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 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), } } /// Filter that modifies each sample by a given value. -#[derive(Clone)] pub struct Mix where I1: Source, - I1::Item: FromSample + Sample, I2: Source, + I1::Item: Sample, I2::Item: Sample, { - input1: UniformSourceIterator, - input2: UniformSourceIterator, + input1: I1, + input2: UniformSourceIterator< + I2, + I2::Item, + // fast_inhouse::SampleRateConverter, I2::Item>, + hifi_rubato::SampleRateConverter, I2::Item>, + >, } impl Iterator for Mix @@ -75,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 e730e802..73d43dcb 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; @@ -182,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, { @@ -344,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 b9d146d1..0746d8a8 100644 --- a/src/source/uniform.rs +++ b/src/source/uniform.rs @@ -1,9 +1,11 @@ use std::cmp; +use std::marker::PhantomData; use std::time::Duration; use cpal::FromSample; -use crate::conversions::{ChannelCountConverter, DataConverter, SampleRateConverter}; +use crate::conversions::sample_rate::Resampler; +use crate::conversions::ChannelCountConverter; use crate::{Sample, Source}; use super::SeekError; @@ -13,24 +15,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. @@ -39,71 +47,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); @@ -116,11 +129,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 { @@ -145,11 +159,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(()) @@ -158,42 +171,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); @@ -204,9 +231,7 @@ where (lower, upper) } else { - self.iter.size_hint() + self.inner.size_hint() } } } - -impl ExactSizeIterator for Take where I: ExactSizeIterator {}