From 5be291d3647e61944ab429cf12c0a5179ec8729f Mon Sep 17 00:00:00 2001 From: Petr Gladkikh Date: Tue, 12 Nov 2024 23:38:55 +0400 Subject: [PATCH 01/22] Fix doc tests, update documentation --- examples/automatic_gain_control.rs | 39 +++++++++++++++++++++--------- src/source/agc.rs | 9 ++++--- src/source/mod.rs | 29 +++++++++++++--------- src/source/speed.rs | 27 ++++++++++----------- 4 files changed, 64 insertions(+), 40 deletions(-) diff --git a/examples/automatic_gain_control.rs b/examples/automatic_gain_control.rs index 17603ac7..e2f02167 100644 --- a/examples/automatic_gain_control.rs +++ b/examples/automatic_gain_control.rs @@ -18,18 +18,28 @@ fn main() { // Apply automatic gain control to the source let agc_source = source.automatic_gain_control(1.0, 4.0, 0.005, 5.0); - // Make it so that the source checks if automatic gain control should be - // enabled or disabled every 5 milliseconds. We must clone `agc_enabled` - // or we would lose it when we move it into the periodic access. - let agc_enabled = Arc::new(AtomicBool::new(true)); - let agc_enabled_clone = agc_enabled.clone(); - let controlled = agc_source.periodic_access(Duration::from_millis(5), move |agc_source| { - agc_source.set_enabled(agc_enabled_clone.load(Ordering::Relaxed)); - }); - - // Add the source now equipped with automatic gain control and controlled via - // periodic_access to the sink for playback - sink.append(controlled); + let agc_enabled : Arc; + + #[cfg(not(feature = "experimental"))] + { + agc_enabled = Arc::new(AtomicBool::new(true)); + // Make it so that the source checks if automatic gain control should be + // enabled or disabled every 5 milliseconds. We must clone `agc_enabled`, + // or we would lose it when we move it into the periodic access. + let agc_enabled_clone = agc_enabled.clone(); + let controlled = agc_source.periodic_access(Duration::from_millis(5), move |agc_source| { + agc_source.set_enabled(agc_enabled_clone.load(Ordering::Relaxed)); + }); + + // Add the source now equipped with automatic gain control and controlled via + // periodic_access to the sink for playback + sink.append(controlled); + } + #[cfg(feature = "experimental")] + { + agc_enabled = agc_source.get_agc_control(); + sink.append(agc_source); + } // after 5 seconds of playback disable automatic gain control using the // shared AtomicBool `agc_enabled`. You could do this from another part @@ -39,6 +49,11 @@ fn main() { // Note that disabling the AGC takes up to 5 millis because periodic_access // controls the source every 5 millis. thread::sleep(Duration::from_secs(5)); + #[cfg(not(feature = "experimental"))] + agc_enabled.store(false, Ordering::Relaxed); + + // AGC on/off control using direct access to the boolean variable. + #[cfg(feature = "experimental")] agc_enabled.store(false, Ordering::Relaxed); // Keep the program running until playback is complete diff --git a/src/source/agc.rs b/src/source/agc.rs index 5685faa5..1685b22f 100644 --- a/src/source/agc.rs +++ b/src/source/agc.rs @@ -288,10 +288,13 @@ where } #[cfg(feature = "experimental")] - /// Access the AGC on/off control for real-time adjustment. - /// + /// Access the AGC on/off control. /// Use this to dynamically enable or disable AGC processing during runtime. - /// Useful for comparing processed and unprocessed audio or for disabling/enabling AGC at runtime. + /// + /// AGC is on by default. `false` is disabled state, `true` is enabled. + /// In disabled state the sound is passed through AGC unchanged. + /// + /// In particular, this control is useful for comparing processed and unprocessed audio. #[inline] pub fn get_agc_control(&self) -> Arc { Arc::clone(&self.is_enabled) diff --git a/src/source/mod.rs b/src/source/mod.rs index 7f3a4ee1..73085a95 100644 --- a/src/source/mod.rs +++ b/src/source/mod.rs @@ -93,7 +93,7 @@ pub use self::noise::{pink, white, PinkNoise, WhiteNoise}; /// amplitude every 20µs). By doing so we obtain a list of numerical values, each value being /// called a *sample*. /// -/// Therefore a sound can be represented in memory by a frequency and a list of samples. The +/// Therefore, a sound can be represented in memory by a frequency and a list of samples. The /// frequency is expressed in hertz and corresponds to the number of samples that have been /// read per second. For example if we read one sample every 20µs, the frequency would be /// 50000 Hz. In reality, common values for the frequency are 44100, 48000 and 96000. @@ -114,7 +114,7 @@ pub use self::noise::{pink, white, PinkNoise, WhiteNoise}; /// channel, then the second sample of the second channel, and so on. The same applies if you have /// more than two channels. The rodio library only supports this schema. /// -/// Therefore in order to represent a sound in memory in fact we need three characteristics: the +/// Therefore, in order to represent a sound in memory in fact we need three characteristics: the /// frequency, the number of channels, and the list of samples. /// /// ## The `Source` trait @@ -297,25 +297,32 @@ where /// A recommended value for `absolute_max_gain` is `5`, which provides a good balance between /// amplification capability and protection against distortion in most scenarios. /// - /// Use `get_agc_control` to obtain a handle for real-time enabling/disabling of the AGC. + /// Use [AutomaticGainControl::get_agc_control()] to obtain a handle for real-time + /// enabling/disabling of the AGC. /// /// # Example (Quick start) /// /// ```rust - /// // Apply Automatic Gain Control to the source (AGC is on by default) - /// let agc_source = source.automatic_gain_control(1.0, 4.0, 0.005, 5.0); + /// // Apply Automatic Gain Control to the source. + /// use rodio::source::{Source, SineWave, AutomaticGainControl}; + /// use rodio::Sink; + /// let source = SineWave::new(444.0); // An example. + /// let (sink, output) = Sink::new_idle(); // An example, makes no sound unless connected to an output. /// - /// // Get a handle to control the AGC's enabled state (optional) - /// let agc_control = agc_source.get_agc_control(); + /// let agc_source = source.automatic_gain_control(1.0, 4.0, 0.005, 5.0); /// - /// // You can toggle AGC on/off at any time (optional) - /// agc_control.store(false, std::sync::atomic::Ordering::Relaxed); + /// #[cfg(feature = "experimental")] + /// { + /// // It is possible to get or change AGC's enabled state. AGC is on by default. + /// // See documentation for `AutomaticGainControl::get_agc_control()` for details. + /// let agc_enabled = agc_source.get_agc_control(); + /// // You can toggle AGC on/off at any time. + /// agc_enabled.store(false, std::sync::atomic::Ordering::Relaxed); + /// } /// /// // Add the AGC-controlled source to the sink /// sink.append(agc_source); /// - /// // Note: Using agc_control is optional. If you don't need to toggle AGC, - /// // you can simply use the agc_source directly without getting agc_control. /// ``` #[inline] fn automatic_gain_control( diff --git a/src/source/speed.rs b/src/source/speed.rs index 5287883f..71e29fd0 100644 --- a/src/source/speed.rs +++ b/src/source/speed.rs @@ -4,12 +4,12 @@ //! encapsulates playback speed controls of the current sink. //! //! In order to speed up a sink, the speed struct: -//! - Increases the current sample rate by the given factor -//! - Updates the total duration function to cover for the new factor by dividing by the factor -//! - Updates the try_seek function by multiplying the audio position by the factor +//! - Increases the current sample rate by the given factor. +//! - Updates the total duration function to cover for the new factor by dividing by the factor. +//! - Updates the try_seek function by multiplying the audio position by the factor. //! //! To speed up a source from sink all you need to do is call the `set_speed(factor: f32)` function -//! For example, here is how you speed up your sound by using sink or playing raw +//! For example, here is how you speed up your sound by using sink or playing raw. //! //! ```no_run //!# use std::fs::File; @@ -17,24 +17,23 @@ //!# use rodio::{Decoder, Sink, OutputStream, source::{Source, SineWave}}; //! //! // Get an output stream handle to the default physical sound device. -//! // Note that no sound will be played if _stream is dropped +//! // Note that no sound will be played if _stream is dropped. //! let (_stream, stream_handle) = OutputStream::try_default().unwrap(); //! // Load a sound from a file, using a path relative to Cargo.toml -//! let file = BufReader::new(File::open("examples/music.ogg").unwrap()); +//! let file = BufReader::new(File::open("assets/music.ogg").unwrap()); //! // Decode that sound file into a source //! let source = Decoder::new(file).unwrap(); //! // Play the sound directly on the device 2x faster -//! stream_handle.play_raw(source.convert_samples().speed(2.0)); - +//! stream_handle.play_raw(source.convert_samples().speed(2.0)).unwrap(); //! std::thread::sleep(std::time::Duration::from_secs(5)); -//! ``` -//! here is how you would do it using the sink -//! ``` +//! +//! // Here is how you would do it using the sink. +//! //! let source = SineWave::new(440.0) -//! .take_duration(Duration::from_secs_f32(20.25)) -//! .amplify(0.20); +//! .take_duration(std::time::Duration::from_secs_f32(3.25)) +//! .amplify(0.20); //! -//! let sink = Sink::try_new(&stream_handle)?; +//! let sink = Sink::try_new(&stream_handle).unwrap(); //! sink.set_speed(2.0); //! sink.append(source); //! std::thread::sleep(std::time::Duration::from_secs(5)); From 6071f147ed3efb63df1fa049003be0cf5cc363ff Mon Sep 17 00:00:00 2001 From: Petr Gladkikh Date: Tue, 12 Nov 2024 23:40:57 +0400 Subject: [PATCH 02/22] Update test --- src/conversions/sample_rate.rs | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/conversions/sample_rate.rs b/src/conversions/sample_rate.rs index 36aa5a5a..b0e3a909 100644 --- a/src/conversions/sample_rate.rs +++ b/src/conversions/sample_rate.rs @@ -53,7 +53,7 @@ where assert!(from >= 1); assert!(to >= 1); - // finding greatest common divisor + // finding the greatest common divisor let gcd = { #[inline] fn gcd(a: u32, b: u32) -> u32 { @@ -259,12 +259,6 @@ mod test { use cpal::SampleRate; use quickcheck::quickcheck; - // TODO: Remove once cpal 0.12.2 is released and the dependency is updated - // (cpal#483 implemented ops::Mul on SampleRate) - const fn multiply_rate(r: SampleRate, k: u32) -> SampleRate { - SampleRate(k * r.0) - } - quickcheck! { /// Check that resampling an empty input produces no output. fn empty(from: u32, to: u32, n: u16) -> () { @@ -296,7 +290,7 @@ mod test { /// dropping a sample from each channel. fn divide_sample_rate(to: u32, k: u32, input: Vec, n: u16) -> () { let to = if to == 0 { return; } else { SampleRate(to) }; - let from = multiply_rate(to, k); + let from = to * k; if k == 0 || n == 0 { return; } // Truncate the input, so it contains an integer number of frames. @@ -320,7 +314,7 @@ mod test { /// sample in the output matches exactly with the input. fn multiply_sample_rate(from: u32, k: u32, input: Vec, n: u16) -> () { let from = if from == 0 { return; } else { SampleRate(from) }; - let to = multiply_rate(from, k); + let to = from * k; if k == 0 || n == 0 { return; } // Truncate the input, so it contains an integer number of frames. From d2a508540459ec29f5372a2463bdadd08047f0f4 Mon Sep 17 00:00:00 2001 From: Petr Gladkikh Date: Tue, 12 Nov 2024 23:42:50 +0400 Subject: [PATCH 03/22] Test experimental features and docs in CI `cargo test --all-targets` excludes doc tests. Experimental features are sometimes mutually exclusive with the stable code. --- .github/workflows/ci.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1890c992..3c7936f3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -47,7 +47,10 @@ jobs: if: matrix.toolchain == 'stable' && matrix.os == 'ubuntu-latest' - run: cargo test --all-targets - - run: cargo test --features=symphonia-all --all-targets + - run: cargo test --all-targets --features=experimental + - run: cargo test --all-targets --features=symphonia-all + - run: cargo test --doc + - run: cargo test --doc --features=experimental cargo-publish: if: github.event_name == 'push' && github.ref == 'refs/heads/master' env: From 574b809549f44d348c44021c528e97562d215e32 Mon Sep 17 00:00:00 2001 From: Petr Gladkikh Date: Wed, 13 Nov 2024 00:10:48 +0400 Subject: [PATCH 04/22] Upgrade quickcheck, make tests more reliable --- Cargo.toml | 2 +- src/conversions/sample_rate.rs | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 60574ffc..0e55fb44 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,7 +47,7 @@ symphonia-alac = ["symphonia/isomp4", "symphonia/alac"] symphonia-aiff = ["symphonia/aiff", "symphonia/pcm"] [dev-dependencies] -quickcheck = "0.9.2" +quickcheck = "1.0.3" 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 b0e3a909..683c5de5 100644 --- a/src/conversions/sample_rate.rs +++ b/src/conversions/sample_rate.rs @@ -50,6 +50,7 @@ where let from = from.0; let to = to.0; + assert!(num_channels >= 1); assert!(from >= 1); assert!(to >= 1); @@ -262,9 +263,9 @@ mod test { quickcheck! { /// Check that resampling an empty input produces no output. fn empty(from: u32, to: u32, n: u16) -> () { + if n == 0 { return; } let from = if from == 0 { return; } else { SampleRate(from) }; let to = if to == 0 { return; } else { SampleRate(to) }; - if n == 0 { return; } let input: Vec = Vec::new(); let output = @@ -276,8 +277,8 @@ mod test { /// Check that resampling to the same rate does not change the signal. fn identity(from: u32, n: u16, input: Vec) -> () { - let from = if from == 0 { return; } else { SampleRate(from) }; if n == 0 { return; } + let from = if from == 0 { return; } else { SampleRate(from) }; let output = SampleRateConverter::new(input.clone().into_iter(), from, from, n) @@ -289,9 +290,9 @@ mod test { /// Check that dividing the sample rate by k (integer) is the same as /// dropping a sample from each channel. fn divide_sample_rate(to: u32, k: u32, input: Vec, n: u16) -> () { + if k == 0 || n == 0 || to.checked_mul(k).is_none() { return; } let to = if to == 0 { return; } else { SampleRate(to) }; let from = to * k; - if k == 0 || n == 0 { return; } // Truncate the input, so it contains an integer number of frames. let input = { @@ -313,9 +314,9 @@ mod test { /// 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: u32, k: u32, input: Vec, n: u16) -> () { + if k == 0 || n == 0 || from.checked_mul(k).is_none() { return; } let from = if from == 0 { return; } else { SampleRate(from) }; let to = from * k; - if k == 0 || n == 0 { return; } // Truncate the input, so it contains an integer number of frames. let input = { From 197390fc401996e93729c48c17dfa6c9c8e386b8 Mon Sep 17 00:00:00 2001 From: Petr Gladkikh Date: Wed, 13 Nov 2024 03:53:29 +0400 Subject: [PATCH 05/22] Update quickcheck, update tests --- Cargo.toml | 2 +- src/buffer.rs | 4 +- src/conversions/sample.rs | 34 +++++++++++--- src/conversions/sample_rate.rs | 86 +++++++++++++++++++--------------- src/source/skip.rs | 6 +-- 5 files changed, 82 insertions(+), 50 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0e55fb44..fd8be068 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,7 +47,7 @@ symphonia-alac = ["symphonia/isomp4", "symphonia/alac"] symphonia-aiff = ["symphonia/aiff", "symphonia/pcm"] [dev-dependencies] -quickcheck = "1.0.3" +quickcheck = "1" rstest = "0.18.2" rstest_reuse = "0.6.0" approx = "0.5.1" diff --git a/src/buffer.rs b/src/buffer.rs index e8d71718..fa0c6d6e 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -42,8 +42,8 @@ where where D: Into>, { - assert!(channels != 0); - assert!(sample_rate != 0); + assert!(channels > 0); + assert!(sample_rate > 0); let data = data.into(); let duration_ns = 1_000_000_000u64.checked_mul(data.len() as u64).unwrap() diff --git a/src/conversions/sample.rs b/src/conversions/sample.rs index a78f5c14..46c96cb8 100644 --- a/src/conversions/sample.rs +++ b/src/conversions/sample.rs @@ -55,8 +55,7 @@ where I: ExactSizeIterator, I::Item: Sample, O: FromSample + Sample, -{ -} +{} /// Represents a value of a single sample. /// @@ -93,10 +92,10 @@ pub trait Sample: CpalSample { impl Sample for u16 { #[inline] fn lerp(first: u16, second: u16, numerator: u32, denominator: u32) -> u16 { - let a = first as i32; - let b = second as i32; - let n = numerator as i32; - let d = denominator as i32; + let a = first as i64; + let b = second as i64; + let n = numerator as i64; + let d = denominator as i64; (a + (b - a) * n / d) as u16 } @@ -178,3 +177,26 @@ impl Sample for f32 { 0.0 } } + + +#[cfg(test)] +mod test { + use quickcheck::{quickcheck, TestResult}; + use super::*; + + quickcheck! { + fn lerp_u16(first: u16, second: u16, numerator: u32, denominator: u32) -> TestResult { + if denominator == 0 { return TestResult::discard(); } + let a = first as f64; + let b = second as f64; + let c = numerator as f64 / denominator as f64; + // if c < 0.0 || c > 1.0 { return TestResult::discard(); }; + // let reference = a * c + b * (1.0 - c); + let reference = a + (b - a) * c; + let x = Sample::lerp(first, second, numerator, denominator) as f64; + let d = x - reference; + dbg!(x, reference, d); + TestResult::from_bool(d.abs() < 1.0) + } + } +} diff --git a/src/conversions/sample_rate.rs b/src/conversions/sample_rate.rs index 683c5de5..a7700d8c 100644 --- a/src/conversions/sample_rate.rs +++ b/src/conversions/sample_rate.rs @@ -51,8 +51,11 @@ where let to = to.0; assert!(num_channels >= 1); + assert!(num_channels <= 128); assert!(from >= 1); + assert!(from <= 500_000); assert!(to >= 1); + assert!(to <= 500_000); // finding the greatest common divisor let gcd = { @@ -250,100 +253,109 @@ impl ExactSizeIterator for SampleRateConverter where I: ExactSizeIterator, I::Item: Sample + Clone, -{ -} +{} #[cfg(test)] mod test { use super::SampleRateConverter; use core::time::Duration; - use cpal::SampleRate; - use quickcheck::quickcheck; + use cpal::{ChannelCount, SampleRate}; + use quickcheck::{quickcheck, TestResult}; quickcheck! { /// Check that resampling an empty input produces no output. - fn empty(from: u32, to: u32, n: u16) -> () { - if n == 0 { return; } - let from = if from == 0 { return; } else { SampleRate(from) }; - let to = if to == 0 { return; } else { SampleRate(to) }; + 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, n) + 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: u32, n: u16, input: Vec) -> () { - if n == 0 { return; } - let from = if from == 0 { return; } else { SampleRate(from) }; + 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, n) + SampleRateConverter::new(input.clone().into_iter(), from, from, channels as ChannelCount) .collect::>(); - assert_eq!(input, output); + 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: u32, k: u32, input: Vec, n: u16) -> () { - if k == 0 || n == 0 || to.checked_mul(k).is_none() { return; } - let to = if to == 0 { return; } else { SampleRate(to) }; - let from = to * k; + fn divide_sample_rate(to: u16, k: u16, input: Vec, channels: u8) -> TestResult { + if k == 0 || channels == 0 || channels > 128 || to == 0 || to.checked_mul(k).is_none() { + 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 = n as usize; + 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, n) + SampleRateConverter::new(input.clone().into_iter(), from, to, channels as ChannelCount) .collect::>(); - assert_eq!(input.chunks_exact(n.into()) - .step_by(k as usize).collect::>().concat(), - output) + 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: u32, k: u32, input: Vec, n: u16) -> () { - if k == 0 || n == 0 || from.checked_mul(k).is_none() { return; } - let from = if from == 0 { return; } else { SampleRate(from) }; - let to = from * k; + fn multiply_sample_rate(from: u16, k: u16, input: Vec, channels: u8) -> TestResult { + if k == 0 || channels == 0 || channels > 128 || from == 0 || from.checked_mul(k).is_none() { + 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 = n as usize; + 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, n) + SampleRateConverter::new(input.clone().into_iter(), from, to, channels as ChannelCount) .collect::>(); - assert_eq!(input, - output.chunks_exact(n.into()) - .step_by(k as usize).collect::>().concat() - ) + 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) -> () { + fn preserve_durations(d: Duration, freq: f32, to: u32) -> TestResult { + if to == 0 { return TestResult::discard(); } + use crate::source::{SineWave, Source}; - let to = if to == 0 { return; } else { SampleRate(to) }; + let to = SampleRate(to); let source = SineWave::new(freq).take_duration(d); let from = SampleRate(source.sample_rate()); @@ -353,9 +365,7 @@ mod test { Duration::from_secs_f32(resampled.count() as f32 / to.0 as f32); let delta = if d < duration { duration - d } else { d - duration }; - assert!(delta < Duration::from_millis(1), - "Resampled duration ({:?}) is not close to original ({:?}); Δ = {:?}", - duration, d, delta); + TestResult::from_bool(delta < Duration::from_millis(1)) } } diff --git a/src/source/skip.rs b/src/source/skip.rs index 06cc1bdb..a836ae87 100644 --- a/src/source/skip.rs +++ b/src/source/skip.rs @@ -179,9 +179,9 @@ mod tests { seconds: u32, seconds_to_skip: u32, ) { - let data: Vec = (1..=(sample_rate * channels as u32 * seconds)) - .map(|_| 0f32) - .collect(); + let buf_len = (sample_rate * channels as u32 * seconds) as usize; + assert!(buf_len < 10 * 1024 * 1024); + let data: Vec = vec![0f32; buf_len]; let test_buffer = SamplesBuffer::new(channels, sample_rate, data); let seconds_left = seconds.saturating_sub(seconds_to_skip); From ee90203e70885f024f0fc7b9c7ef98d152174b7f Mon Sep 17 00:00:00 2001 From: Petr Gladkikh Date: Wed, 13 Nov 2024 17:55:36 +0400 Subject: [PATCH 06/22] Correct linear interpolation Overflows chould have distort sound without triggering any errors. --- src/conversions/sample.rs | 75 ++++++++++++++++++++++++++++++++------- src/stream.rs | 2 +- 2 files changed, 64 insertions(+), 13 deletions(-) diff --git a/src/conversions/sample.rs b/src/conversions/sample.rs index 46c96cb8..1a867b9b 100644 --- a/src/conversions/sample.rs +++ b/src/conversions/sample.rs @@ -79,7 +79,7 @@ pub trait Sample: CpalSample { /// Multiplies the value of this sample by the given amount. fn amplify(self, value: f32) -> Self; - /// Converts the sample to an f32 value. + /// Converts the sample to a f32 value. fn to_f32(self) -> f32; /// Calls `saturating_add` on the sample. @@ -92,11 +92,9 @@ pub trait Sample: CpalSample { impl Sample for u16 { #[inline] fn lerp(first: u16, second: u16, numerator: u32, denominator: u32) -> u16 { - let a = first as i64; - let b = second as i64; - let n = numerator as i64; - let d = denominator as i64; - (a + (b - a) * n / d) as u16 + let d = first as i64 + (second as i64 - first as i64) * numerator as i64 / denominator as i64; + u16::try_from(d) + .expect("numerator / denominator is within [0, 1] range") } #[inline] @@ -124,8 +122,9 @@ impl Sample for u16 { impl Sample for i16 { #[inline] fn lerp(first: i16, second: i16, numerator: u32, denominator: u32) -> i16 { - (first as i32 + (second as i32 - first as i32) * numerator as i32 / denominator as i32) - as i16 + let d = first as i64 + (second as i64 - first as i64) * numerator as i64 / denominator as i64; + i16::try_from(d) + .expect("numerator / denominator is within [0, 1] range") } #[inline] @@ -184,18 +183,70 @@ mod test { use quickcheck::{quickcheck, TestResult}; use super::*; + + #[test] + fn lerp_u16_constraints() { + let a = 12u16; + let b = 31u16; + assert_eq!(Sample::lerp(a, b, 0, 1), a); + assert_eq!(Sample::lerp(a, b, 1, 1), b); + + assert_eq!(Sample::lerp(0, u16::MAX, 0, 1), 0); + assert_eq!(Sample::lerp(0, u16::MAX, 1, 1), u16::MAX); + // Zeroes + assert_eq!(Sample::lerp(0u16, 0, 0, 1), 0); + assert_eq!(Sample::lerp(0u16, 0, 1, 1), 0); + // Downward changes + assert_eq!(Sample::lerp(1u16, 0, 0, 1), 1); + assert_eq!(Sample::lerp(1u16, 0, 1, 1), 0); + } + + #[test] + #[should_panic] + fn lerp_u16_overflow() { + Sample::lerp(0u16, 1, u16::MAX as u32 + 1, 1); + } + + #[test] + fn lerp_i16_constraints() { + let a = 12i16; + let b = 31i16; + assert_eq!(Sample::lerp(a, b, 0, 1), a); + assert_eq!(Sample::lerp(a, b, 1, 1), b); + + assert_eq!(Sample::lerp(0, i16::MAX, 0, 1), 0); + assert_eq!(Sample::lerp(0, i16::MAX, 1, 1), i16::MAX); + assert_eq!(Sample::lerp(0, i16::MIN, 1, 1), i16::MIN); + // Zeroes + assert_eq!(Sample::lerp(0u16, 0, 0, 1), 0); + assert_eq!(Sample::lerp(0u16, 0, 1, 1), 0); + // Downward changes + assert_eq!(Sample::lerp(a, i16::MIN, 0, 1), a); + assert_eq!(Sample::lerp(a, i16::MIN, 1, 1), i16::MIN); + } + + #[test] + #[should_panic] + fn lerp_i16_overflow_max() { + Sample::lerp(0i16, 1, i16::MAX as u32 + 1, 1); + } + + #[test] + #[should_panic] + fn lerp_i16_overflow_min() { + Sample::lerp(0i16, -1, (i16::MIN.abs() + 1) as u32, 1); + } + quickcheck! { - fn lerp_u16(first: u16, second: u16, numerator: u32, denominator: u32) -> TestResult { + fn lerp_u16_random(first: u16, second: u16, numerator: u32, denominator: u32) -> TestResult { if denominator == 0 { return TestResult::discard(); } let a = first as f64; let b = second as f64; let c = numerator as f64 / denominator as f64; - // if c < 0.0 || c > 1.0 { return TestResult::discard(); }; - // let reference = a * c + b * (1.0 - c); + if c < 0.0 || c > 1.0 { return TestResult::discard(); }; let reference = a + (b - a) * c; let x = Sample::lerp(first, second, numerator, denominator) as f64; let d = x - reference; - dbg!(x, reference, d); TestResult::from_bool(d.abs() < 1.0) } } diff --git a/src/stream.rs b/src/stream.rs index dd491244..3e34b2e5 100644 --- a/src/stream.rs +++ b/src/stream.rs @@ -11,7 +11,7 @@ use cpal::{Sample, SupportedStreamConfig}; /// `cpal::Stream` container. Also see the more useful `OutputStreamHandle`. /// -/// If this is dropped playback will end & attached `OutputStreamHandle`s will no longer work. +/// If this is dropped, playback will end & attached `OutputStreamHandle`s will no longer work. pub struct OutputStream { mixer: Arc>, _stream: cpal::Stream, From b40caca6a0f678a1f94973c6ee1b098cc12f9b2f Mon Sep 17 00:00:00 2001 From: Petr Gladkikh Date: Wed, 13 Nov 2024 18:36:11 +0400 Subject: [PATCH 07/22] Correct Sample::lerp documentation --- examples/automatic_gain_control.rs | 4 ++-- src/conversions/sample.rs | 23 +++++++++++------------ src/conversions/sample_rate.rs | 14 +++++++++++--- 3 files changed, 24 insertions(+), 17 deletions(-) diff --git a/examples/automatic_gain_control.rs b/examples/automatic_gain_control.rs index e2f02167..8d866db4 100644 --- a/examples/automatic_gain_control.rs +++ b/examples/automatic_gain_control.rs @@ -18,7 +18,7 @@ fn main() { // Apply automatic gain control to the source let agc_source = source.automatic_gain_control(1.0, 4.0, 0.005, 5.0); - let agc_enabled : Arc; + let agc_enabled: Arc; #[cfg(not(feature = "experimental"))] { @@ -30,7 +30,7 @@ fn main() { let controlled = agc_source.periodic_access(Duration::from_millis(5), move |agc_source| { agc_source.set_enabled(agc_enabled_clone.load(Ordering::Relaxed)); }); - + // Add the source now equipped with automatic gain control and controlled via // periodic_access to the sink for playback sink.append(controlled); diff --git a/src/conversions/sample.rs b/src/conversions/sample.rs index 1a867b9b..cad751b8 100644 --- a/src/conversions/sample.rs +++ b/src/conversions/sample.rs @@ -55,7 +55,8 @@ where I: ExactSizeIterator, I::Item: Sample, O: FromSample + Sample, -{} +{ +} /// Represents a value of a single sample. /// @@ -73,8 +74,8 @@ where pub trait Sample: CpalSample { /// Linear interpolation between two samples. /// - /// The result should be equal to - /// `first * numerator / denominator + second * (1 - numerator / denominator)`. + /// The result should be equvivalent to + /// `first * (1 - numerator / denominator) + second * numerator / denominator`. fn lerp(first: Self, second: Self, numerator: u32, denominator: u32) -> Self; /// Multiplies the value of this sample by the given amount. fn amplify(self, value: f32) -> Self; @@ -92,9 +93,9 @@ pub trait Sample: CpalSample { impl Sample for u16 { #[inline] fn lerp(first: u16, second: u16, numerator: u32, denominator: u32) -> u16 { - let d = first as i64 + (second as i64 - first as i64) * numerator as i64 / denominator as i64; - u16::try_from(d) - .expect("numerator / denominator is within [0, 1] range") + let d = + first as i64 + (second as i64 - first as i64) * numerator as i64 / denominator as i64; + u16::try_from(d).expect("numerator / denominator is within [0, 1] range") } #[inline] @@ -122,9 +123,9 @@ impl Sample for u16 { impl Sample for i16 { #[inline] fn lerp(first: i16, second: i16, numerator: u32, denominator: u32) -> i16 { - let d = first as i64 + (second as i64 - first as i64) * numerator as i64 / denominator as i64; - i16::try_from(d) - .expect("numerator / denominator is within [0, 1] range") + let d = + first as i64 + (second as i64 - first as i64) * numerator as i64 / denominator as i64; + i16::try_from(d).expect("numerator / denominator is within [0, 1] range") } #[inline] @@ -177,12 +178,10 @@ impl Sample for f32 { } } - #[cfg(test)] mod test { - use quickcheck::{quickcheck, TestResult}; use super::*; - + use quickcheck::{quickcheck, TestResult}; #[test] fn lerp_u16_constraints() { diff --git a/src/conversions/sample_rate.rs b/src/conversions/sample_rate.rs index a7700d8c..a9d37b5e 100644 --- a/src/conversions/sample_rate.rs +++ b/src/conversions/sample_rate.rs @@ -253,7 +253,8 @@ impl ExactSizeIterator for SampleRateConverter where I: ExactSizeIterator, I::Item: Sample + Clone, -{} +{ +} #[cfg(test)] mod test { @@ -374,9 +375,16 @@ mod test { 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); - 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 output = output.collect::>(); + assert_eq!(output, [1, 2, 4, 6, 8, 10, 12, 14]); + } } From 69f9e11aaa2824b51ba009a744f3f6ec1b497eb5 Mon Sep 17 00:00:00 2001 From: Petr Gladkikh Date: Wed, 13 Nov 2024 21:02:16 +0400 Subject: [PATCH 08/22] Test Sample::lerp according to the API documentation --- src/conversions/sample.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/conversions/sample.rs b/src/conversions/sample.rs index cad751b8..70ad448e 100644 --- a/src/conversions/sample.rs +++ b/src/conversions/sample.rs @@ -243,7 +243,7 @@ mod test { let b = second as f64; let c = numerator as f64 / denominator as f64; if c < 0.0 || c > 1.0 { return TestResult::discard(); }; - let reference = a + (b - a) * c; + let reference = a * (1.0 - c) + b * c; let x = Sample::lerp(first, second, numerator, denominator) as f64; let d = x - reference; TestResult::from_bool(d.abs() < 1.0) From cd7964e2a56318e5ed1579b349b1c6cc5f8b99a1 Mon Sep 17 00:00:00 2001 From: Petr Gladkikh Date: Thu, 14 Nov 2024 02:30:04 +0400 Subject: [PATCH 09/22] Better variable names --- src/conversions/sample.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/conversions/sample.rs b/src/conversions/sample.rs index 70ad448e..ec804604 100644 --- a/src/conversions/sample.rs +++ b/src/conversions/sample.rs @@ -93,9 +93,9 @@ pub trait Sample: CpalSample { impl Sample for u16 { #[inline] fn lerp(first: u16, second: u16, numerator: u32, denominator: u32) -> u16 { - let d = + let sample = first as i64 + (second as i64 - first as i64) * numerator as i64 / denominator as i64; - u16::try_from(d).expect("numerator / denominator is within [0, 1] range") + u16::try_from(sample).expect("numerator / denominator is within [0, 1] range") } #[inline] @@ -123,9 +123,9 @@ impl Sample for u16 { impl Sample for i16 { #[inline] fn lerp(first: i16, second: i16, numerator: u32, denominator: u32) -> i16 { - let d = + let sample = first as i64 + (second as i64 - first as i64) * numerator as i64 / denominator as i64; - i16::try_from(d).expect("numerator / denominator is within [0, 1] range") + i16::try_from(sample).expect("numerator / denominator is within [0, 1] range") } #[inline] @@ -245,8 +245,8 @@ mod test { if c < 0.0 || c > 1.0 { return TestResult::discard(); }; let reference = a * (1.0 - c) + b * c; let x = Sample::lerp(first, second, numerator, denominator) as f64; - let d = x - reference; - TestResult::from_bool(d.abs() < 1.0) + let diff = x - reference; + TestResult::from_bool(diff.abs() < 1.0) } } } From e983383a7f8c1ae99b35604b645e3cbe2ad67e96 Mon Sep 17 00:00:00 2001 From: Petr Gladkikh Date: Thu, 14 Nov 2024 23:36:43 +0400 Subject: [PATCH 10/22] Remove experimental features from documentation --- .github/workflows/ci.yml | 1 - examples/automatic_gain_control.rs | 2 +- src/source/mod.rs | 14 ++------------ 3 files changed, 3 insertions(+), 14 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3c7936f3..14a1b331 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -50,7 +50,6 @@ jobs: - run: cargo test --all-targets --features=experimental - run: cargo test --all-targets --features=symphonia-all - run: cargo test --doc - - run: cargo test --doc --features=experimental cargo-publish: if: github.event_name == 'push' && github.ref == 'refs/heads/master' env: diff --git a/examples/automatic_gain_control.rs b/examples/automatic_gain_control.rs index 8d866db4..d8b00696 100644 --- a/examples/automatic_gain_control.rs +++ b/examples/automatic_gain_control.rs @@ -41,7 +41,7 @@ fn main() { sink.append(agc_source); } - // after 5 seconds of playback disable automatic gain control using the + // After 5 seconds of playback disable automatic gain control using the // shared AtomicBool `agc_enabled`. You could do this from another part // of the program since `agc_enabled` is of type Arc which // is freely clone-able and move-able. diff --git a/src/source/mod.rs b/src/source/mod.rs index 73085a95..8b95291a 100644 --- a/src/source/mod.rs +++ b/src/source/mod.rs @@ -297,8 +297,8 @@ where /// A recommended value for `absolute_max_gain` is `5`, which provides a good balance between /// amplification capability and protection against distortion in most scenarios. /// - /// Use [AutomaticGainControl::get_agc_control()] to obtain a handle for real-time - /// enabling/disabling of the AGC. + /// `automatic_gain_control` example in this project shows a pattern you can use + /// to enable/disable the AGC filter dynamically. /// /// # Example (Quick start) /// @@ -311,18 +311,8 @@ where /// /// let agc_source = source.automatic_gain_control(1.0, 4.0, 0.005, 5.0); /// - /// #[cfg(feature = "experimental")] - /// { - /// // It is possible to get or change AGC's enabled state. AGC is on by default. - /// // See documentation for `AutomaticGainControl::get_agc_control()` for details. - /// let agc_enabled = agc_source.get_agc_control(); - /// // You can toggle AGC on/off at any time. - /// agc_enabled.store(false, std::sync::atomic::Ordering::Relaxed); - /// } - /// /// // Add the AGC-controlled source to the sink /// sink.append(agc_source); - /// /// ``` #[inline] fn automatic_gain_control( From de29cb3925d47bd7b0d0110203580c3fe071bb1f Mon Sep 17 00:00:00 2001 From: Petr Gladkikh Date: Fri, 15 Nov 2024 00:23:30 +0400 Subject: [PATCH 11/22] Reduce number of assertions --- src/conversions/sample_rate.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/conversions/sample_rate.rs b/src/conversions/sample_rate.rs index a9d37b5e..3ad3cd33 100644 --- a/src/conversions/sample_rate.rs +++ b/src/conversions/sample_rate.rs @@ -51,11 +51,8 @@ where let to = to.0; assert!(num_channels >= 1); - assert!(num_channels <= 128); assert!(from >= 1); - assert!(from <= 500_000); assert!(to >= 1); - assert!(to <= 500_000); // finding the greatest common divisor let gcd = { From 6734d0178beda977bc09497b43d7c1a72f1b9788 Mon Sep 17 00:00:00 2001 From: Petr Gladkikh Date: Fri, 15 Nov 2024 00:33:26 +0400 Subject: [PATCH 12/22] Cleanup Make assert conditions more uniform. --- src/buffer.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/buffer.rs b/src/buffer.rs index fa0c6d6e..de21fdcb 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -42,8 +42,8 @@ where where D: Into>, { - assert!(channels > 0); - assert!(sample_rate > 0); + assert!(channels >= 1); + assert!(sample_rate >= 1); let data = data.into(); let duration_ns = 1_000_000_000u64.checked_mul(data.len() as u64).unwrap() From b6b000b61fdb83f9baf975ae83afbf955311a4a7 Mon Sep 17 00:00:00 2001 From: Petr Gladkikh Date: Fri, 15 Nov 2024 19:21:04 +0400 Subject: [PATCH 13/22] Add test Restoring len() test, it is actually useful to check `SampleRateConverter::size_hint()` implementation. Although its implementation is not precise (see TODOs). --- src/conversions/sample_rate.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/conversions/sample_rate.rs b/src/conversions/sample_rate.rs index 3ad3cd33..9e1e41e5 100644 --- a/src/conversions/sample_rate.rs +++ b/src/conversions/sample_rate.rs @@ -372,6 +372,8 @@ mod test { 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]); } @@ -381,7 +383,9 @@ mod test { 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); } } From 7b70230a6044552386b33185b4d93e6d3e6c7f13 Mon Sep 17 00:00:00 2001 From: Petr Gladkikh Date: Tue, 19 Nov 2024 17:49:10 +0400 Subject: [PATCH 14/22] Remove redundant code from AGC example --- examples/automatic_gain_control.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/examples/automatic_gain_control.rs b/examples/automatic_gain_control.rs index d8b00696..6d1e6ff0 100644 --- a/examples/automatic_gain_control.rs +++ b/examples/automatic_gain_control.rs @@ -48,12 +48,7 @@ fn main() { // // Note that disabling the AGC takes up to 5 millis because periodic_access // controls the source every 5 millis. - thread::sleep(Duration::from_secs(5)); - #[cfg(not(feature = "experimental"))] - agc_enabled.store(false, Ordering::Relaxed); - - // AGC on/off control using direct access to the boolean variable. - #[cfg(feature = "experimental")] + thread::sleep(Duration::from_secs(4)); agc_enabled.store(false, Ordering::Relaxed); // Keep the program running until playback is complete From 1182681256ce4e80babf43abdf8ecd1fafd31b5b Mon Sep 17 00:00:00 2001 From: Petr Gladkikh Date: Thu, 21 Nov 2024 19:40:36 +0400 Subject: [PATCH 15/22] Clarify CI documentation test command --- .github/workflows/ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 14a1b331..48c93124 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -49,6 +49,8 @@ jobs: - run: cargo test --all-targets - run: cargo test --all-targets --features=experimental - run: cargo test --all-targets --features=symphonia-all + # `cargo test` does not check benchmarks and `cargo test --all-targets` excludes + # documentation tests. Therefore, we need an additional docs test command here. - run: cargo test --doc cargo-publish: if: github.event_name == 'push' && github.ref == 'refs/heads/master' From 808d51b9379aa6534345ab28f2f372bd7d2b1516 Mon Sep 17 00:00:00 2001 From: Petr Gladkikh Date: Thu, 21 Nov 2024 22:46:44 +0400 Subject: [PATCH 16/22] Revert changes in AGC example --- examples/automatic_gain_control.rs | 40 +++++++++++------------------- 1 file changed, 15 insertions(+), 25 deletions(-) diff --git a/examples/automatic_gain_control.rs b/examples/automatic_gain_control.rs index 6d1e6ff0..17603ac7 100644 --- a/examples/automatic_gain_control.rs +++ b/examples/automatic_gain_control.rs @@ -18,37 +18,27 @@ fn main() { // Apply automatic gain control to the source let agc_source = source.automatic_gain_control(1.0, 4.0, 0.005, 5.0); - let agc_enabled: Arc; - - #[cfg(not(feature = "experimental"))] - { - agc_enabled = Arc::new(AtomicBool::new(true)); - // Make it so that the source checks if automatic gain control should be - // enabled or disabled every 5 milliseconds. We must clone `agc_enabled`, - // or we would lose it when we move it into the periodic access. - let agc_enabled_clone = agc_enabled.clone(); - let controlled = agc_source.periodic_access(Duration::from_millis(5), move |agc_source| { - agc_source.set_enabled(agc_enabled_clone.load(Ordering::Relaxed)); - }); - - // Add the source now equipped with automatic gain control and controlled via - // periodic_access to the sink for playback - sink.append(controlled); - } - #[cfg(feature = "experimental")] - { - agc_enabled = agc_source.get_agc_control(); - sink.append(agc_source); - } - - // After 5 seconds of playback disable automatic gain control using the + // Make it so that the source checks if automatic gain control should be + // enabled or disabled every 5 milliseconds. We must clone `agc_enabled` + // or we would lose it when we move it into the periodic access. + let agc_enabled = Arc::new(AtomicBool::new(true)); + let agc_enabled_clone = agc_enabled.clone(); + let controlled = agc_source.periodic_access(Duration::from_millis(5), move |agc_source| { + agc_source.set_enabled(agc_enabled_clone.load(Ordering::Relaxed)); + }); + + // Add the source now equipped with automatic gain control and controlled via + // periodic_access to the sink for playback + sink.append(controlled); + + // after 5 seconds of playback disable automatic gain control using the // shared AtomicBool `agc_enabled`. You could do this from another part // of the program since `agc_enabled` is of type Arc which // is freely clone-able and move-able. // // Note that disabling the AGC takes up to 5 millis because periodic_access // controls the source every 5 millis. - thread::sleep(Duration::from_secs(4)); + thread::sleep(Duration::from_secs(5)); agc_enabled.store(false, Ordering::Relaxed); // Keep the program running until playback is complete From de53b87a061021889f324b2208b5108455d7fcaa Mon Sep 17 00:00:00 2001 From: Petr Gladkikh Date: Thu, 21 Nov 2024 23:01:01 +0400 Subject: [PATCH 17/22] Fix AGC example compilation `experimental` flag removes some API, just keep the example minimally functional in that case. --- examples/automatic_gain_control.rs | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/examples/automatic_gain_control.rs b/examples/automatic_gain_control.rs index 17603ac7..78ae21bb 100644 --- a/examples/automatic_gain_control.rs +++ b/examples/automatic_gain_control.rs @@ -19,19 +19,27 @@ fn main() { let agc_source = source.automatic_gain_control(1.0, 4.0, 0.005, 5.0); // Make it so that the source checks if automatic gain control should be - // enabled or disabled every 5 milliseconds. We must clone `agc_enabled` + // enabled or disabled every 5 milliseconds. We must clone `agc_enabled`, // or we would lose it when we move it into the periodic access. let agc_enabled = Arc::new(AtomicBool::new(true)); - let agc_enabled_clone = agc_enabled.clone(); - let controlled = agc_source.periodic_access(Duration::from_millis(5), move |agc_source| { - agc_source.set_enabled(agc_enabled_clone.load(Ordering::Relaxed)); - }); - // Add the source now equipped with automatic gain control and controlled via - // periodic_access to the sink for playback - sink.append(controlled); + #[cfg(not(feature = "experimental"))] + { + let agc_enabled_clone = agc_enabled.clone(); + let controlled = agc_source.periodic_access(Duration::from_millis(5), move |agc_source| { + agc_source.set_enabled(agc_enabled_clone.load(Ordering::Relaxed)); + }); - // after 5 seconds of playback disable automatic gain control using the + // Add the source now equipped with automatic gain control and controlled via + // periodic_access to the sink for playback. + sink.append(controlled); + } + #[cfg(feature = "experimental")] + { + sink.append(agc_source); + } + + // After 5 seconds of playback disable automatic gain control using the // shared AtomicBool `agc_enabled`. You could do this from another part // of the program since `agc_enabled` is of type Arc which // is freely clone-able and move-able. From bf60185d4bb97489ce2a0c2fc02ccb5413a678e2 Mon Sep 17 00:00:00 2001 From: Petr Gladkikh Date: Fri, 22 Nov 2024 20:58:01 +0400 Subject: [PATCH 18/22] Exclude examples from experimental build tests --- .github/workflows/ci.yml | 2 +- examples/automatic_gain_control.rs | 23 ++++++++--------------- 2 files changed, 9 insertions(+), 16 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 48c93124..4fed37b7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -47,7 +47,7 @@ jobs: if: matrix.toolchain == 'stable' && matrix.os == 'ubuntu-latest' - run: cargo test --all-targets - - run: cargo test --all-targets --features=experimental + - run: cargo test --lib --bins --tests --benches --features=experimental - run: cargo test --all-targets --features=symphonia-all # `cargo test` does not check benchmarks and `cargo test --all-targets` excludes # documentation tests. Therefore, we need an additional docs test command here. diff --git a/examples/automatic_gain_control.rs b/examples/automatic_gain_control.rs index 78ae21bb..6bf78d5e 100644 --- a/examples/automatic_gain_control.rs +++ b/examples/automatic_gain_control.rs @@ -23,21 +23,14 @@ fn main() { // or we would lose it when we move it into the periodic access. let agc_enabled = Arc::new(AtomicBool::new(true)); - #[cfg(not(feature = "experimental"))] - { - let agc_enabled_clone = agc_enabled.clone(); - let controlled = agc_source.periodic_access(Duration::from_millis(5), move |agc_source| { - agc_source.set_enabled(agc_enabled_clone.load(Ordering::Relaxed)); - }); - - // Add the source now equipped with automatic gain control and controlled via - // periodic_access to the sink for playback. - sink.append(controlled); - } - #[cfg(feature = "experimental")] - { - sink.append(agc_source); - } + let agc_enabled_clone = agc_enabled.clone(); + let controlled = agc_source.periodic_access(Duration::from_millis(5), move |agc_source| { + agc_source.set_enabled(agc_enabled_clone.load(Ordering::Relaxed)); + }); + + // Add the source now equipped with automatic gain control and controlled via + // periodic_access to the sink for playback. + sink.append(controlled); // After 5 seconds of playback disable automatic gain control using the // shared AtomicBool `agc_enabled`. You could do this from another part From d9d7606560f21d66545705c63a883f24d06ac96a Mon Sep 17 00:00:00 2001 From: Petr Gladkikh Date: Fri, 29 Nov 2024 20:21:15 +0400 Subject: [PATCH 19/22] Remove explicit overflow check in sample interpolation --- Cargo.toml | 1 + src/conversions/sample.rs | 47 +++++++++++------------------ src/conversions/sample_rate.rs | 55 ++++++++++++++++++++-------------- 3 files changed, 51 insertions(+), 52 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a8d03f95..599cb059 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ rand = { version = "0.8.5", features = ["small_rng"], optional = true } tracing = { version = "0.1.40", optional = true } atomic_float = { version = "1.1.0", optional = true } +num-rational = "0.4.2" [features] default = ["flac", "vorbis", "wav", "mp3"] diff --git a/src/conversions/sample.rs b/src/conversions/sample.rs index ec804604..3164f75a 100644 --- a/src/conversions/sample.rs +++ b/src/conversions/sample.rs @@ -74,8 +74,10 @@ where pub trait Sample: CpalSample { /// Linear interpolation between two samples. /// - /// The result should be equvivalent to + /// The result should be equivalent to /// `first * (1 - numerator / denominator) + second * numerator / denominator`. + /// + /// To avoid numeric overflows pick smaller numerator. fn lerp(first: Self, second: Self, numerator: u32, denominator: u32) -> Self; /// Multiplies the value of this sample by the given amount. fn amplify(self, value: f32) -> Self; @@ -93,9 +95,11 @@ pub trait Sample: CpalSample { impl Sample for u16 { #[inline] fn lerp(first: u16, second: u16, numerator: u32, denominator: u32) -> u16 { - let sample = - first as i64 + (second as i64 - first as i64) * numerator as i64 / denominator as i64; - u16::try_from(sample).expect("numerator / denominator is within [0, 1] range") + let a = first as i32; + let b = second as i32; + let n = numerator as i32; + let d = denominator as i32; + (a + (b - a) * n / d) as u16 } #[inline] @@ -123,9 +127,8 @@ impl Sample for u16 { impl Sample for i16 { #[inline] fn lerp(first: i16, second: i16, numerator: u32, denominator: u32) -> i16 { - let sample = - first as i64 + (second as i64 - first as i64) * numerator as i64 / denominator as i64; - i16::try_from(sample).expect("numerator / denominator is within [0, 1] range") + (first as i32 + (second as i32 - first as i32) * numerator as i32 / denominator as i32) + as i16 } #[inline] @@ -181,6 +184,7 @@ impl Sample for f32 { #[cfg(test)] mod test { use super::*; + use num_rational::Ratio; use quickcheck::{quickcheck, TestResult}; #[test] @@ -200,12 +204,6 @@ mod test { assert_eq!(Sample::lerp(1u16, 0, 1, 1), 0); } - #[test] - #[should_panic] - fn lerp_u16_overflow() { - Sample::lerp(0u16, 1, u16::MAX as u32 + 1, 1); - } - #[test] fn lerp_i16_constraints() { let a = 12i16; @@ -224,29 +222,20 @@ mod test { assert_eq!(Sample::lerp(a, i16::MIN, 1, 1), i16::MIN); } - #[test] - #[should_panic] - fn lerp_i16_overflow_max() { - Sample::lerp(0i16, 1, i16::MAX as u32 + 1, 1); - } - - #[test] - #[should_panic] - fn lerp_i16_overflow_min() { - Sample::lerp(0i16, -1, (i16::MIN.abs() + 1) as u32, 1); - } - quickcheck! { - fn lerp_u16_random(first: u16, second: u16, numerator: u32, denominator: u32) -> TestResult { + fn lerp_u16_random(first: u16, second: u16, numerator: u16, denominator: u16) -> TestResult { if denominator == 0 { return TestResult::discard(); } + + let (numerator, denominator) = Ratio::new(numerator, denominator).into_raw(); + if numerator > 5000 { return TestResult::discard(); } + let a = first as f64; let b = second as f64; let c = numerator as f64 / denominator as f64; if c < 0.0 || c > 1.0 { return TestResult::discard(); }; let reference = a * (1.0 - c) + b * c; - let x = Sample::lerp(first, second, numerator, denominator) as f64; - let diff = x - reference; - TestResult::from_bool(diff.abs() < 1.0) + let x = Sample::lerp(first, second, numerator as u32, denominator as u32) as f64; + TestResult::from_bool((x - reference).abs() < 1.0) } } } diff --git a/src/conversions/sample_rate.rs b/src/conversions/sample_rate.rs index 9e1e41e5..26e0d907 100644 --- a/src/conversions/sample_rate.rs +++ b/src/conversions/sample_rate.rs @@ -1,5 +1,6 @@ use crate::conversions::Sample; +use num_rational::Ratio; use std::mem; /// Iterator that converts from a certain sample rate to another. @@ -34,12 +35,19 @@ 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)). /// - /// # Panic - /// - /// Panics if `from` or `to` are equal to 0. + /// # 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, @@ -54,23 +62,8 @@ where assert!(from >= 1); assert!(to >= 1); - // finding the greatest common divisor - let gcd = { - #[inline] - fn gcd(a: u32, b: u32) -> u32 { - if b == 0 { - a - } else { - gcd(b, a % b) - } - } - - gcd(from, to) - }; - let (first_samples, next_samples) = if from == to { // if `from` == `to` == 1, then we just pass through - debug_assert_eq!(from, gcd); (Vec::new(), Vec::new()) } else { let first = input @@ -84,10 +77,13 @@ where (first, next) }; + // Reducing nominator to avoid numeric overflows during interpolation. + let (to, from) = Ratio::new(to, from).into_raw(); + SampleRateConverter { input, - from: from / gcd, - to: to / gcd, + from, + to, channels: num_channels, current_frame_pos_in_chunk: 0, next_output_frame_pos_in_chunk: 0, @@ -296,9 +292,10 @@ mod test { /// 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.checked_mul(k).is_none() { + 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; @@ -320,10 +317,11 @@ mod test { /// 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: u16, input: Vec, channels: u8) -> TestResult { - if k == 0 || channels == 0 || channels > 128 || from == 0 || from.checked_mul(k).is_none() { + 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; @@ -388,4 +386,15 @@ mod test { 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); + } } From 31035599a3f5b453145ec4448816189ee990fedd Mon Sep 17 00:00:00 2001 From: Petr Gladkikh Date: Fri, 29 Nov 2024 21:39:13 +0400 Subject: [PATCH 20/22] Fix rustdoc --- src/source/mod.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/source/mod.rs b/src/source/mod.rs index 6ef633c7..e730e802 100644 --- a/src/source/mod.rs +++ b/src/source/mod.rs @@ -170,7 +170,6 @@ where fn total_duration(&self) -> Option; /// Stores the source in a buffer in addition to returning it. This iterator can be cloned. - #[inline] fn buffered(self) -> Buffered where From 6eb27229a943bbd16ab770169a53748e8c06b14a Mon Sep 17 00:00:00 2001 From: Petr Gladkikh Date: Fri, 29 Nov 2024 23:06:14 +0400 Subject: [PATCH 21/22] Track Cargo.lock It is necessary to make tests and benches reproducible. --- .gitignore | 2 +- Cargo.lock | 1638 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 1639 insertions(+), 1 deletion(-) create mode 100644 Cargo.lock diff --git a/.gitignore b/.gitignore index a9d37c56..d913617b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ target -Cargo.lock + diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 00000000..636ea8cc --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1638 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "alsa" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed7572b7ba83a31e20d1b48970ee402d2e3e0537dcfe0a3ff4d6eb7508617d43" +dependencies = [ + "alsa-sys", + "bitflags 2.6.0", + "cfg-if", + "libc", +] + +[[package]] +name = "alsa-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db8fee663d06c4e303404ef5f40488a53e062f89ba8bfed81f42325aafad1527" +dependencies = [ + "libc", + "pkg-config", +] + +[[package]] +name = "anstyle" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" + +[[package]] +name = "approx" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" +dependencies = [ + "num-traits", +] + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "atomic_float" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "628d228f918ac3b82fe590352cc719d30664a0c13ca3a60266fe02c7132d480a" + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "bindgen" +version = "0.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f49d8fed880d473ea71efb9bf597651e77201bdd4893efe54c9e5d65ae04ce6f" +dependencies = [ + "bitflags 2.6.0", + "cexpr", + "clang-sys", + "itertools", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + +[[package]] +name = "bytemuck" +version = "1.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b37c88a63ffd85d15b406896cc343916d7cf57838a847b3a6f2ca5d39a5695a" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" + +[[package]] +name = "cc" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f34d93e62b03caf570cccc334cbc6c2fceca82f39211051345108adcba3eebdc" +dependencies = [ + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "clap" +version = "4.5.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb3b4b9e5a7c7514dfa52869339ee98b3156b0bfb4e8a77c4ff4babb64b1604f" +dependencies = [ + "clap_builder", +] + +[[package]] +name = "clap_builder" +version = "4.5.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b17a95aa67cc7b5ebd32aa5370189aa0d79069ef1c64ce893bd30fb24bff20ec" +dependencies = [ + "anstyle", + "clap_lex", + "terminal_size", +] + +[[package]] +name = "clap_lex" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afb84c814227b90d6895e01398aee0d8033c00e7466aca416fb6a8e0eb19d8a7" + +[[package]] +name = "claxon" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bfbf56724aa9eca8afa4fcfadeb479e722935bb2a0900c2d37e0cc477af0688" + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "condtype" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf0a07a401f374238ab8e2f11a104d2851bf9ce711ec69804834de8af45c7af" + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "coreaudio-rs" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "321077172d79c662f64f5071a03120748d5bb652f5231570141be24cfcd2bace" +dependencies = [ + "bitflags 1.3.2", + "core-foundation-sys", + "coreaudio-sys", +] + +[[package]] +name = "coreaudio-sys" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ce857aa0b77d77287acc1ac3e37a05a8c95a2af3647d23b15f263bdaeb7562b" +dependencies = [ + "bindgen", +] + +[[package]] +name = "cpal" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "873dab07c8f743075e57f524c583985fbaf745602acbe916a01539364369a779" +dependencies = [ + "alsa", + "core-foundation-sys", + "coreaudio-rs", + "dasp_sample", + "jni", + "js-sys", + "libc", + "mach2", + "ndk", + "ndk-context", + "oboe", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + +[[package]] +name = "dasp_sample" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c87e182de0887fd5361989c677c4e8f5000cd9491d6d563161a8f3a5519fc7f" + +[[package]] +name = "divan" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccc40f214f0d9e897cfc72e2edfa5c225d3252f758c537f11ac0a80371c073a6" +dependencies = [ + "cfg-if", + "clap", + "condtype", + "divan-macros", + "libc", + "regex-lite", +] + +[[package]] +name = "divan-macros" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bdb5411188f7f878a17964798c1264b6b0a9f915bd39b20bf99193c923e1b4e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "env_logger" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "extended" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af9673d8203fcb076b19dfd17e38b3d4ae9f44959416ea532ce72415a6020365" + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-timer" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" + +[[package]] +name = "hound" +version = "3.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62adaabb884c94955b19907d60019f4e145d091c75345379e70d1ee696f7854f" + +[[package]] +name = "indexmap" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys", + "log", + "thiserror", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "jobserver" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" +dependencies = [ + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb15147158e79fd8b8afd0252522769c4f48725460b37338544d8379d94fc8f9" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "lewton" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "777b48df9aaab155475a83a7df3070395ea1ac6902f5cd062b8f2b028075c030" +dependencies = [ + "byteorder", + "ogg", + "tinyvec", +] + +[[package]] +name = "libc" +version = "0.2.167" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc" + +[[package]] +name = "libloading" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" +dependencies = [ + "cfg-if", + "windows-targets 0.52.6", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "mach2" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b955cdeb2a02b9117f121ce63aa52d08ade45de53e48fe6a38b39c10f6f709" +dependencies = [ + "libc", +] + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "minimp3-sys" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e21c73734c69dc95696c9ed8926a2b393171d98b3f5f5935686a26a487ab9b90" +dependencies = [ + "cc", +] + +[[package]] +name = "minimp3_fixed" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42b0f14e7e75da97ae396c2656b10262a3d4afa2ec98f35795630eff0c8b951b" +dependencies = [ + "minimp3-sys", + "slice-ring-buffer", + "thiserror", +] + +[[package]] +name = "ndk" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2076a31b7010b17a38c01907c45b945e8f11495ee4dd588309718901b1f7a5b7" +dependencies = [ + "bitflags 2.6.0", + "jni-sys", + "log", + "ndk-sys", + "num_enum", + "thiserror", +] + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "ndk-sys" +version = "0.5.0+25.2.9519653" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c196769dd60fd4f363e11d948139556a344e79d451aeb2fa2fd040738ef7691" +dependencies = [ + "jni-sys", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_enum" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "oboe" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8b61bebd49e5d43f5f8cc7ee2891c16e0f41ec7954d36bcb6c14c5e0de867fb" +dependencies = [ + "jni", + "ndk", + "ndk-context", + "num-derive", + "num-traits", + "oboe-sys", +] + +[[package]] +name = "oboe-sys" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8bb09a4a2b1d668170cfe0a7d5bc103f8999fb316c98099b6a9939c9f2e79d" +dependencies = [ + "cc", +] + +[[package]] +name = "ogg" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6951b4e8bf21c8193da321bcce9c9dd2e13c858fe078bf9054a288b419ae5d6e" +dependencies = [ + "byteorder", +] + +[[package]] +name = "once_cell" +version = "1.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" + +[[package]] +name = "pin-project-lite" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" + +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro-crate" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro2" +version = "1.0.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quickcheck" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "588f6378e4dd99458b60ec275b4477add41ce4fa9f64dcba6f15adccb19b50d6" +dependencies = [ + "env_logger", + "log", + "rand", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-lite" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53a49587ad06b26609c52e423de037e7f57f20d53535d66e08c695f347df952a" + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "relative-path" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" + +[[package]] +name = "rodio" +version = "0.20.1" +dependencies = [ + "approx", + "atomic_float", + "claxon", + "cpal", + "crossbeam-channel", + "dasp_sample", + "divan", + "hound", + "lewton", + "minimp3_fixed", + "num-rational", + "quickcheck", + "rand", + "rstest", + "rstest_reuse", + "symphonia", + "tracing", +] + +[[package]] +name = "rstest" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97eeab2f3c0a199bc4be135c36c924b6590b88c377d416494288c14f2db30199" +dependencies = [ + "futures", + "futures-timer", + "rstest_macros", + "rustc_version", +] + +[[package]] +name = "rstest_macros" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d428f8247852f894ee1be110b375111b586d4fa431f6c46e64ba5a0dcccbe605" +dependencies = [ + "cfg-if", + "glob", + "proc-macro2", + "quote", + "regex", + "relative-path", + "rustc_version", + "syn", + "unicode-ident", +] + +[[package]] +name = "rstest_reuse" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88530b681abe67924d42cca181d070e3ac20e0740569441a9e35a7cedd2b34a4" +dependencies = [ + "quote", + "rand", + "rustc_version", + "syn", +] + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "0.38.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7f649912bc1495e167a6edee79151c84b1bad49748cb4f1f1167f459f6224f6" +dependencies = [ + "bitflags 2.6.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "semver" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "slice-ring-buffer" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84ae312bda09b2368f79f985fdb4df4a0b5cbc75546b511303972d195f8c27d6" +dependencies = [ + "libc", + "mach2", + "winapi", +] + +[[package]] +name = "symphonia" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "815c942ae7ee74737bb00f965fa5b5a2ac2ce7b6c01c0cc169bbeaf7abd5f5a9" +dependencies = [ + "lazy_static", + "symphonia-bundle-flac", + "symphonia-bundle-mp3", + "symphonia-codec-aac", + "symphonia-codec-adpcm", + "symphonia-codec-alac", + "symphonia-codec-pcm", + "symphonia-codec-vorbis", + "symphonia-core", + "symphonia-format-isomp4", + "symphonia-format-riff", + "symphonia-metadata", +] + +[[package]] +name = "symphonia-bundle-flac" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72e34f34298a7308d4397a6c7fbf5b84c5d491231ce3dd379707ba673ab3bd97" +dependencies = [ + "log", + "symphonia-core", + "symphonia-metadata", + "symphonia-utils-xiph", +] + +[[package]] +name = "symphonia-bundle-mp3" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c01c2aae70f0f1fb096b6f0ff112a930b1fb3626178fba3ae68b09dce71706d4" +dependencies = [ + "lazy_static", + "log", + "symphonia-core", + "symphonia-metadata", +] + +[[package]] +name = "symphonia-codec-aac" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdbf25b545ad0d3ee3e891ea643ad115aff4ca92f6aec472086b957a58522f70" +dependencies = [ + "lazy_static", + "log", + "symphonia-core", +] + +[[package]] +name = "symphonia-codec-adpcm" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c94e1feac3327cd616e973d5be69ad36b3945f16b06f19c6773fc3ac0b426a0f" +dependencies = [ + "log", + "symphonia-core", +] + +[[package]] +name = "symphonia-codec-alac" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d8a6666649a08412906476a8b0efd9b9733e241180189e9f92b09c08d0e38f3" +dependencies = [ + "log", + "symphonia-core", +] + +[[package]] +name = "symphonia-codec-pcm" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f395a67057c2ebc5e84d7bb1be71cce1a7ba99f64e0f0f0e303a03f79116f89b" +dependencies = [ + "log", + "symphonia-core", +] + +[[package]] +name = "symphonia-codec-vorbis" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a98765fb46a0a6732b007f7e2870c2129b6f78d87db7987e6533c8f164a9f30" +dependencies = [ + "log", + "symphonia-core", + "symphonia-utils-xiph", +] + +[[package]] +name = "symphonia-core" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "798306779e3dc7d5231bd5691f5a813496dc79d3f56bf82e25789f2094e022c3" +dependencies = [ + "arrayvec", + "bitflags 1.3.2", + "bytemuck", + "lazy_static", + "log", +] + +[[package]] +name = "symphonia-format-isomp4" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abfdf178d697e50ce1e5d9b982ba1b94c47218e03ec35022d9f0e071a16dc844" +dependencies = [ + "encoding_rs", + "log", + "symphonia-core", + "symphonia-metadata", + "symphonia-utils-xiph", +] + +[[package]] +name = "symphonia-format-riff" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f7be232f962f937f4b7115cbe62c330929345434c834359425e043bfd15f50" +dependencies = [ + "extended", + "log", + "symphonia-core", + "symphonia-metadata", +] + +[[package]] +name = "symphonia-metadata" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc622b9841a10089c5b18e99eb904f4341615d5aa55bbf4eedde1be721a4023c" +dependencies = [ + "encoding_rs", + "lazy_static", + "log", + "symphonia-core", +] + +[[package]] +name = "symphonia-utils-xiph" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "484472580fa49991afda5f6550ece662237b00c6f562c7d9638d1b086ed010fe" +dependencies = [ + "symphonia-core", + "symphonia-metadata", +] + +[[package]] +name = "syn" +version = "2.0.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "terminal_size" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f599bd7ca042cfdf8f4512b277c02ba102247820f9d9d4a9f521f496751a6ef" +dependencies = [ + "rustix", + "windows-sys 0.59.0", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tinyvec" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "toml_datetime" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" + +[[package]] +name = "toml_edit" +version = "0.22.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow", +] + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +dependencies = [ + "once_cell", +] + +[[package]] +name = "unicode-ident" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.96" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21d3b25c3ea1126a2ad5f4f9068483c2af1e64168f847abe863a526b8dbfe00b" +dependencies = [ + "cfg-if", + "once_cell", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.96" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52857d4c32e496dc6537646b5b117081e71fd2ff06de792e3577a150627db283" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "951fe82312ed48443ac78b66fa43eded9999f738f6022e67aead7b708659e49a" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.96" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "920b0ffe069571ebbfc9ddc0b36ba305ef65577c94b06262ed793716a1afd981" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.96" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf59002391099644be3524e23b781fa43d2be0c5aa0719a18c0731b9d195cab6" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.96" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5047c5392700766601942795a436d7d2599af60dcc3cc1248c9120bfb0827b0" + +[[package]] +name = "web-sys" +version = "0.3.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "476364ff87d0ae6bfb661053a9104ab312542658c3d8f963b7ace80b6f9b26b9" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.54.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9252e5725dbed82865af151df558e754e4a3c2c30818359eb17465f1346a1b49" +dependencies = [ + "windows-core", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-core" +version = "0.54.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12661b9c89351d684a50a8a643ce5f608e20243b9fb84687800163429f161d65" +dependencies = [ + "windows-result", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-result" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" +dependencies = [ + "memchr", +] + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] From 62ee3b33099335b6ea2c29174d8b9525acabcf9b Mon Sep 17 00:00:00 2001 From: Petr Gladkikh Date: Sun, 1 Dec 2024 14:59:46 +0400 Subject: [PATCH 22/22] Correct misprint --- src/conversions/sample_rate.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/conversions/sample_rate.rs b/src/conversions/sample_rate.rs index 26e0d907..b76c23d0 100644 --- a/src/conversions/sample_rate.rs +++ b/src/conversions/sample_rate.rs @@ -77,7 +77,7 @@ where (first, next) }; - // Reducing nominator to avoid numeric overflows during interpolation. + // Reducing numerator to avoid numeric overflows during interpolation. let (to, from) = Ratio::new(to, from).into_raw(); SampleRateConverter {