Revert "Replace linear resampler with fft based one" (#39120)

David Kleingeld created

Reverts zed-industries/zed#39098

robot voices all over

Change summary

Cargo.lock                               |  13 
crates/audio/Cargo.toml                  |   1 
crates/audio/src/rodio_ext.rs            | 347 +++++++++++++++++++++++--
crates/audio/src/rodio_ext/replayable.rs | 308 -----------------------
crates/audio/src/rodio_ext/resample.rs   |  98 -------
5 files changed, 318 insertions(+), 449 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -1411,7 +1411,6 @@ dependencies = [
  "log",
  "parking_lot",
  "rodio",
- "rubato",
  "serde",
  "settings",
  "smol",
@@ -13511,18 +13510,6 @@ version = "0.3.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ad8388ea1a9e0ea807e442e8263a699e7edcb320ecbcd21b4fa8ff859acce3ba"
 
-[[package]]
-name = "rubato"
-version = "0.16.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5258099699851cfd0082aeb645feb9c084d9a5e1f1b8d5372086b989fc5e56a1"
-dependencies = [
- "num-complex",
- "num-integer",
- "num-traits",
- "realfft",
-]
-
 [[package]]
 name = "rules_library"
 version = "0.1.0"

crates/audio/Cargo.toml 🔗

@@ -22,7 +22,6 @@ denoise = { path = "../denoise" }
 log.workspace = true
 parking_lot.workspace = true
 rodio = { workspace = true, features = [ "wav", "playback", "wav_output" ] }
-rubato = "0.16.2"
 serde.workspace = true
 settings.workspace = true
 smol.workspace = true

crates/audio/src/rodio_ext.rs 🔗

@@ -1,17 +1,26 @@
-use std::{num::NonZero, time::Duration};
-
+use std::{
+    num::NonZero,
+    sync::{
+        Arc, Mutex,
+        atomic::{AtomicBool, Ordering},
+    },
+    time::Duration,
+};
+
+use crossbeam::queue::ArrayQueue;
 use denoise::{Denoiser, DenoiserError};
 use log::warn;
-use rodio::{ChannelCount, Sample, SampleRate, Source, conversions::ChannelCountConverter, nz};
-
-use crate::rodio_ext::resample::FixedResampler;
-pub use replayable::{Replay, ReplayDurationTooShort, Replayable};
-
-mod replayable;
-mod resample;
+use rodio::{
+    ChannelCount, Sample, SampleRate, Source, conversions::SampleRateConverter, nz,
+    source::UniformSourceIterator,
+};
 
 const MAX_CHANNELS: usize = 8;
 
+#[derive(Debug, thiserror::Error)]
+#[error("Replay duration is too short must be >= 100ms")]
+pub struct ReplayDurationTooShort;
+
 // These all require constant sources (so the span is infinitely long)
 // this is not guaranteed by rodio however we know it to be true in all our
 // applications. Rodio desperately needs a constant source concept.
@@ -32,8 +41,8 @@ pub trait RodioExt: Source + Sized {
         self,
         channel_count: ChannelCount,
         sample_rate: SampleRate,
-    ) -> ConstantChannelCount<FixedResampler<Self>>;
-    fn constant_samplerate(self, sample_rate: SampleRate) -> FixedResampler<Self>;
+    ) -> UniformSourceIterator<Self>;
+    fn constant_samplerate(self, sample_rate: SampleRate) -> ConstantSampleRate<Self>;
     fn possibly_disconnected_channels_to_mono(self) -> ToMono<Self>;
 }
 
@@ -72,7 +81,38 @@ impl<S: Source> RodioExt for S {
         self,
         duration: Duration,
     ) -> Result<(Replay, Replayable<Self>), ReplayDurationTooShort> {
-        replayable::replayable(self, duration)
+        if duration < Duration::from_millis(100) {
+            return Err(ReplayDurationTooShort);
+        }
+
+        let samples_per_second = self.sample_rate().get() as usize * self.channels().get() as usize;
+        let samples_to_queue = duration.as_secs_f64() * samples_per_second as f64;
+        let samples_to_queue =
+            (samples_to_queue as usize).next_multiple_of(self.channels().get().into());
+
+        let chunk_size =
+            (samples_per_second.div_ceil(10)).next_multiple_of(self.channels().get() as usize);
+        let chunks_to_queue = samples_to_queue.div_ceil(chunk_size);
+
+        let is_active = Arc::new(AtomicBool::new(true));
+        let queue = Arc::new(ReplayQueue::new(chunks_to_queue, chunk_size));
+        Ok((
+            Replay {
+                rx: Arc::clone(&queue),
+                buffer: Vec::new().into_iter(),
+                sleep_duration: duration / 2,
+                sample_rate: self.sample_rate(),
+                channel_count: self.channels(),
+                source_is_active: is_active.clone(),
+            },
+            Replayable {
+                tx: queue,
+                inner: self,
+                buffer: Vec::with_capacity(chunk_size),
+                chunk_size,
+                is_active,
+            },
+        ))
     }
     fn take_samples(self, n: usize) -> TakeSamples<S> {
         TakeSamples {
@@ -88,37 +128,37 @@ impl<S: Source> RodioExt for S {
         self,
         channel_count: ChannelCount,
         sample_rate: SampleRate,
-    ) -> ConstantChannelCount<FixedResampler<Self>> {
-        ConstantChannelCount::new(self.constant_samplerate(sample_rate), channel_count)
+    ) -> UniformSourceIterator<Self> {
+        UniformSourceIterator::new(self, channel_count, sample_rate)
     }
-    fn constant_samplerate(self, sample_rate: SampleRate) -> FixedResampler<Self> {
-        FixedResampler::new(self, sample_rate)
+    fn constant_samplerate(self, sample_rate: SampleRate) -> ConstantSampleRate<Self> {
+        ConstantSampleRate::new(self, sample_rate)
     }
     fn possibly_disconnected_channels_to_mono(self) -> ToMono<Self> {
         ToMono::new(self)
     }
 }
 
-pub struct ConstantChannelCount<S: Source> {
-    inner: ChannelCountConverter<S>,
+pub struct ConstantSampleRate<S: Source> {
+    inner: SampleRateConverter<S>,
     channels: ChannelCount,
     sample_rate: SampleRate,
 }
 
-impl<S: Source> ConstantChannelCount<S> {
-    fn new(source: S, target_channels: ChannelCount) -> Self {
-        let input_channels = source.channels();
-        let sample_rate = source.sample_rate();
-        let inner = ChannelCountConverter::new(source, input_channels, target_channels);
+impl<S: Source> ConstantSampleRate<S> {
+    fn new(source: S, target_rate: SampleRate) -> Self {
+        let input_sample_rate = source.sample_rate();
+        let channels = source.channels();
+        let inner = SampleRateConverter::new(source, input_sample_rate, target_rate, channels);
         Self {
-            sample_rate,
             inner,
-            channels: target_channels,
+            channels,
+            sample_rate: target_rate,
         }
     }
 }
 
-impl<S: Source> Iterator for ConstantChannelCount<S> {
+impl<S: Source> Iterator for ConstantSampleRate<S> {
     type Item = rodio::Sample;
 
     fn next(&mut self) -> Option<Self::Item> {
@@ -130,7 +170,7 @@ impl<S: Source> Iterator for ConstantChannelCount<S> {
     }
 }
 
-impl<S: Source> Source for ConstantChannelCount<S> {
+impl<S: Source> Source for ConstantSampleRate<S> {
     fn current_span_len(&self) -> Option<usize> {
         None
     }
@@ -267,6 +307,53 @@ impl<S: Source> Source for TakeSamples<S> {
     }
 }
 
+/// constant source, only works on a single span
+#[derive(Debug)]
+struct ReplayQueue {
+    inner: ArrayQueue<Vec<Sample>>,
+    normal_chunk_len: usize,
+    /// The last chunk in the queue may be smaller than
+    /// the normal chunk size. This is always equal to the
+    /// size of the last element in the queue.
+    /// (so normally chunk_size)
+    last_chunk: Mutex<Vec<Sample>>,
+}
+
+impl ReplayQueue {
+    fn new(queue_len: usize, chunk_size: usize) -> Self {
+        Self {
+            inner: ArrayQueue::new(queue_len),
+            normal_chunk_len: chunk_size,
+            last_chunk: Mutex::new(Vec::new()),
+        }
+    }
+    /// Returns the length in samples
+    fn len(&self) -> usize {
+        self.inner.len().saturating_sub(1) * self.normal_chunk_len
+            + self
+                .last_chunk
+                .lock()
+                .expect("Self::push_last can not poison this lock")
+                .len()
+    }
+
+    fn pop(&self) -> Option<Vec<Sample>> {
+        self.inner.pop() // removes element that was inserted first
+    }
+
+    fn push_last(&self, mut samples: Vec<Sample>) {
+        let mut last_chunk = self
+            .last_chunk
+            .lock()
+            .expect("Self::len can not poison this lock");
+        std::mem::swap(&mut *last_chunk, &mut samples);
+    }
+
+    fn push_normal(&self, samples: Vec<Sample>) {
+        let _pushed_out_of_ringbuf = self.inner.force_push(samples);
+    }
+}
+
 /// constant source, only works on a single span
 pub struct ProcessBuffer<const N: usize, S, F>
 where
@@ -400,15 +487,147 @@ where
     }
 }
 
+/// constant source, only works on a single span
+#[derive(Debug)]
+pub struct Replayable<S: Source> {
+    inner: S,
+    buffer: Vec<Sample>,
+    chunk_size: usize,
+    tx: Arc<ReplayQueue>,
+    is_active: Arc<AtomicBool>,
+}
+
+impl<S: Source> Iterator for Replayable<S> {
+    type Item = Sample;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        if let Some(sample) = self.inner.next() {
+            self.buffer.push(sample);
+            // If the buffer is full send it
+            if self.buffer.len() == self.chunk_size {
+                self.tx.push_normal(std::mem::take(&mut self.buffer));
+            }
+            Some(sample)
+        } else {
+            let last_chunk = std::mem::take(&mut self.buffer);
+            self.tx.push_last(last_chunk);
+            self.is_active.store(false, Ordering::Relaxed);
+            None
+        }
+    }
+
+    fn size_hint(&self) -> (usize, Option<usize>) {
+        self.inner.size_hint()
+    }
+}
+
+impl<S: Source> Source for Replayable<S> {
+    fn current_span_len(&self) -> Option<usize> {
+        self.inner.current_span_len()
+    }
+
+    fn channels(&self) -> ChannelCount {
+        self.inner.channels()
+    }
+
+    fn sample_rate(&self) -> SampleRate {
+        self.inner.sample_rate()
+    }
+
+    fn total_duration(&self) -> Option<Duration> {
+        self.inner.total_duration()
+    }
+}
+
+/// constant source, only works on a single span
+#[derive(Debug)]
+pub struct Replay {
+    rx: Arc<ReplayQueue>,
+    buffer: std::vec::IntoIter<Sample>,
+    sleep_duration: Duration,
+    sample_rate: SampleRate,
+    channel_count: ChannelCount,
+    source_is_active: Arc<AtomicBool>,
+}
+
+impl Replay {
+    pub fn source_is_active(&self) -> bool {
+        // - source could return None and not drop
+        // - source could be dropped before returning None
+        self.source_is_active.load(Ordering::Relaxed) && Arc::strong_count(&self.rx) < 2
+    }
+
+    /// Duration of what is in the buffer and can be returned without blocking.
+    pub fn duration_ready(&self) -> Duration {
+        let samples_per_second = self.channels().get() as u32 * self.sample_rate().get();
+
+        let seconds_queued = self.samples_ready() as f64 / samples_per_second as f64;
+        Duration::from_secs_f64(seconds_queued)
+    }
+
+    /// Number of samples in the buffer and can be returned without blocking.
+    pub fn samples_ready(&self) -> usize {
+        self.rx.len() + self.buffer.len()
+    }
+}
+
+impl Iterator for Replay {
+    type Item = Sample;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        if let Some(sample) = self.buffer.next() {
+            return Some(sample);
+        }
+
+        loop {
+            if let Some(new_buffer) = self.rx.pop() {
+                self.buffer = new_buffer.into_iter();
+                return self.buffer.next();
+            }
+
+            if !self.source_is_active() {
+                return None;
+            }
+
+            // The queue does not support blocking on a next item. We want this queue as it
+            // is quite fast and provides a fixed size. We know how many samples are in a
+            // buffer so if we do not get one now we must be getting one after `sleep_duration`.
+            std::thread::sleep(self.sleep_duration);
+        }
+    }
+
+    fn size_hint(&self) -> (usize, Option<usize>) {
+        ((self.rx.len() + self.buffer.len()), None)
+    }
+}
+
+impl Source for Replay {
+    fn current_span_len(&self) -> Option<usize> {
+        None // source is not compatible with spans
+    }
+
+    fn channels(&self) -> ChannelCount {
+        self.channel_count
+    }
+
+    fn sample_rate(&self) -> SampleRate {
+        self.sample_rate
+    }
+
+    fn total_duration(&self) -> Option<Duration> {
+        None
+    }
+}
+
 #[cfg(test)]
 mod tests {
     use rodio::{nz, static_buffer::StaticSamplesBuffer};
 
     use super::*;
 
-    pub const SAMPLES: [Sample; 5] = [0.0, 1.0, 2.0, 3.0, 4.0];
+    const SAMPLES: [Sample; 5] = [0.0, 1.0, 2.0, 3.0, 4.0];
 
-    pub fn test_source() -> StaticSamplesBuffer {
+    fn test_source() -> StaticSamplesBuffer {
         StaticSamplesBuffer::new(nz!(1), nz!(1), &SAMPLES)
     }
 
@@ -471,4 +690,74 @@ mod tests {
             assert_eq!(yielded, SAMPLES.len())
         }
     }
+
+    mod instant_replay {
+        use super::*;
+
+        #[test]
+        fn continues_after_history() {
+            let input = test_source();
+
+            let (mut replay, mut source) = input
+                .replayable(Duration::from_secs(3))
+                .expect("longer than 100ms");
+
+            source.by_ref().take(3).count();
+            let yielded: Vec<Sample> = replay.by_ref().take(3).collect();
+            assert_eq!(&yielded, &SAMPLES[0..3],);
+
+            source.count();
+            let yielded: Vec<Sample> = replay.collect();
+            assert_eq!(&yielded, &SAMPLES[3..5],);
+        }
+
+        #[test]
+        fn keeps_only_latest() {
+            let input = test_source();
+
+            let (mut replay, mut source) = input
+                .replayable(Duration::from_secs(2))
+                .expect("longer than 100ms");
+
+            source.by_ref().take(5).count(); // get all items but do not end the source
+            let yielded: Vec<Sample> = replay.by_ref().take(2).collect();
+            assert_eq!(&yielded, &SAMPLES[3..5]);
+            source.count(); // exhaust source
+            assert_eq!(replay.next(), None);
+        }
+
+        #[test]
+        fn keeps_correct_amount_of_seconds() {
+            let input = StaticSamplesBuffer::new(nz!(1), nz!(16_000), &[0.0; 40_000]);
+
+            let (replay, mut source) = input
+                .replayable(Duration::from_secs(2))
+                .expect("longer than 100ms");
+
+            // exhaust but do not yet end source
+            source.by_ref().take(40_000).count();
+
+            // take all samples we can without blocking
+            let ready = replay.samples_ready();
+            let n_yielded = replay.take_samples(ready).count();
+
+            let max = source.sample_rate().get() * source.channels().get() as u32 * 2;
+            let margin = 16_000 / 10; // 100ms
+            assert!(n_yielded as u32 >= max - margin);
+        }
+
+        #[test]
+        fn samples_ready() {
+            let input = StaticSamplesBuffer::new(nz!(1), nz!(16_000), &[0.0; 40_000]);
+            let (mut replay, source) = input
+                .replayable(Duration::from_secs(2))
+                .expect("longer than 100ms");
+            assert_eq!(replay.by_ref().samples_ready(), 0);
+
+            source.take(8000).count(); // half a second
+            let margin = 16_000 / 10; // 100ms
+            let ready = replay.samples_ready();
+            assert!(ready >= 8000 - margin);
+        }
+    }
 }

crates/audio/src/rodio_ext/replayable.rs 🔗

@@ -1,308 +0,0 @@
-use std::{
-    sync::{
-        Arc, Mutex,
-        atomic::{AtomicBool, Ordering},
-    },
-    time::Duration,
-};
-
-use crossbeam::queue::ArrayQueue;
-use rodio::{ChannelCount, Sample, SampleRate, Source};
-
-#[derive(Debug, thiserror::Error)]
-#[error("Replay duration is too short must be >= 100ms")]
-pub struct ReplayDurationTooShort;
-
-pub fn replayable<S: Source>(
-    source: S,
-    duration: Duration,
-) -> Result<(Replay, Replayable<S>), ReplayDurationTooShort> {
-    if duration < Duration::from_millis(100) {
-        return Err(ReplayDurationTooShort);
-    }
-
-    let samples_per_second = source.sample_rate().get() as usize * source.channels().get() as usize;
-    let samples_to_queue = duration.as_secs_f64() * samples_per_second as f64;
-    let samples_to_queue =
-        (samples_to_queue as usize).next_multiple_of(source.channels().get().into());
-
-    let chunk_size =
-        (samples_per_second.div_ceil(10)).next_multiple_of(source.channels().get() as usize);
-    let chunks_to_queue = samples_to_queue.div_ceil(chunk_size);
-
-    let is_active = Arc::new(AtomicBool::new(true));
-    let queue = Arc::new(ReplayQueue::new(chunks_to_queue, chunk_size));
-    Ok((
-        Replay {
-            rx: Arc::clone(&queue),
-            buffer: Vec::new().into_iter(),
-            sleep_duration: duration / 2,
-            sample_rate: source.sample_rate(),
-            channel_count: source.channels(),
-            source_is_active: is_active.clone(),
-        },
-        Replayable {
-            tx: queue,
-            inner: source,
-            buffer: Vec::with_capacity(chunk_size),
-            chunk_size,
-            is_active,
-        },
-    ))
-}
-
-/// constant source, only works on a single span
-#[derive(Debug)]
-struct ReplayQueue {
-    inner: ArrayQueue<Vec<Sample>>,
-    normal_chunk_len: usize,
-    /// The last chunk in the queue may be smaller than
-    /// the normal chunk size. This is always equal to the
-    /// size of the last element in the queue.
-    /// (so normally chunk_size)
-    last_chunk: Mutex<Vec<Sample>>,
-}
-
-impl ReplayQueue {
-    fn new(queue_len: usize, chunk_size: usize) -> Self {
-        Self {
-            inner: ArrayQueue::new(queue_len),
-            normal_chunk_len: chunk_size,
-            last_chunk: Mutex::new(Vec::new()),
-        }
-    }
-    /// Returns the length in samples
-    fn len(&self) -> usize {
-        self.inner.len().saturating_sub(1) * self.normal_chunk_len
-            + self
-                .last_chunk
-                .lock()
-                .expect("Self::push_last can not poison this lock")
-                .len()
-    }
-
-    fn pop(&self) -> Option<Vec<Sample>> {
-        self.inner.pop() // removes element that was inserted first
-    }
-
-    fn push_last(&self, mut samples: Vec<Sample>) {
-        let mut last_chunk = self
-            .last_chunk
-            .lock()
-            .expect("Self::len can not poison this lock");
-        std::mem::swap(&mut *last_chunk, &mut samples);
-    }
-
-    fn push_normal(&self, samples: Vec<Sample>) {
-        let _pushed_out_of_ringbuf = self.inner.force_push(samples);
-    }
-}
-
-/// constant source, only works on a single span
-#[derive(Debug)]
-pub struct Replayable<S: Source> {
-    inner: S,
-    buffer: Vec<Sample>,
-    chunk_size: usize,
-    tx: Arc<ReplayQueue>,
-    is_active: Arc<AtomicBool>,
-}
-
-impl<S: Source> Iterator for Replayable<S> {
-    type Item = Sample;
-
-    fn next(&mut self) -> Option<Self::Item> {
-        if let Some(sample) = self.inner.next() {
-            self.buffer.push(sample);
-            // If the buffer is full send it
-            if self.buffer.len() == self.chunk_size {
-                self.tx.push_normal(std::mem::take(&mut self.buffer));
-            }
-            Some(sample)
-        } else {
-            let last_chunk = std::mem::take(&mut self.buffer);
-            self.tx.push_last(last_chunk);
-            self.is_active.store(false, Ordering::Relaxed);
-            None
-        }
-    }
-
-    fn size_hint(&self) -> (usize, Option<usize>) {
-        self.inner.size_hint()
-    }
-}
-
-impl<S: Source> Source for Replayable<S> {
-    fn current_span_len(&self) -> Option<usize> {
-        self.inner.current_span_len()
-    }
-
-    fn channels(&self) -> ChannelCount {
-        self.inner.channels()
-    }
-
-    fn sample_rate(&self) -> SampleRate {
-        self.inner.sample_rate()
-    }
-
-    fn total_duration(&self) -> Option<Duration> {
-        self.inner.total_duration()
-    }
-}
-
-/// constant source, only works on a single span
-#[derive(Debug)]
-pub struct Replay {
-    rx: Arc<ReplayQueue>,
-    buffer: std::vec::IntoIter<Sample>,
-    sleep_duration: Duration,
-    sample_rate: SampleRate,
-    channel_count: ChannelCount,
-    source_is_active: Arc<AtomicBool>,
-}
-
-impl Replay {
-    pub fn source_is_active(&self) -> bool {
-        // - source could return None and not drop
-        // - source could be dropped before returning None
-        self.source_is_active.load(Ordering::Relaxed) && Arc::strong_count(&self.rx) < 2
-    }
-
-    /// Duration of what is in the buffer and can be returned without blocking.
-    pub fn duration_ready(&self) -> Duration {
-        let samples_per_second = self.channels().get() as u32 * self.sample_rate().get();
-
-        let seconds_queued = self.samples_ready() as f64 / samples_per_second as f64;
-        Duration::from_secs_f64(seconds_queued)
-    }
-
-    /// Number of samples in the buffer and can be returned without blocking.
-    pub fn samples_ready(&self) -> usize {
-        self.rx.len() + self.buffer.len()
-    }
-}
-
-impl Iterator for Replay {
-    type Item = Sample;
-
-    fn next(&mut self) -> Option<Self::Item> {
-        if let Some(sample) = self.buffer.next() {
-            return Some(sample);
-        }
-
-        loop {
-            if let Some(new_buffer) = self.rx.pop() {
-                self.buffer = new_buffer.into_iter();
-                return self.buffer.next();
-            }
-
-            if !self.source_is_active() {
-                return None;
-            }
-
-            // The queue does not support blocking on a next item. We want this queue as it
-            // is quite fast and provides a fixed size. We know how many samples are in a
-            // buffer so if we do not get one now we must be getting one after `sleep_duration`.
-            std::thread::sleep(self.sleep_duration);
-        }
-    }
-
-    fn size_hint(&self) -> (usize, Option<usize>) {
-        ((self.rx.len() + self.buffer.len()), None)
-    }
-}
-
-impl Source for Replay {
-    fn current_span_len(&self) -> Option<usize> {
-        None // source is not compatible with spans
-    }
-
-    fn channels(&self) -> ChannelCount {
-        self.channel_count
-    }
-
-    fn sample_rate(&self) -> SampleRate {
-        self.sample_rate
-    }
-
-    fn total_duration(&self) -> Option<Duration> {
-        None
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use rodio::{nz, static_buffer::StaticSamplesBuffer};
-
-    use super::*;
-    use crate::{
-        RodioExt,
-        rodio_ext::tests::{SAMPLES, test_source},
-    };
-
-    #[test]
-    fn continues_after_history() {
-        let input = test_source();
-
-        let (mut replay, mut source) = input
-            .replayable(Duration::from_secs(3))
-            .expect("longer than 100ms");
-
-        source.by_ref().take(3).count();
-        let yielded: Vec<Sample> = replay.by_ref().take(3).collect();
-        assert_eq!(&yielded, &SAMPLES[0..3],);
-
-        source.count();
-        let yielded: Vec<Sample> = replay.collect();
-        assert_eq!(&yielded, &SAMPLES[3..5],);
-    }
-
-    #[test]
-    fn keeps_only_latest() {
-        let input = test_source();
-
-        let (mut replay, mut source) = input
-            .replayable(Duration::from_secs(2))
-            .expect("longer than 100ms");
-
-        source.by_ref().take(5).count(); // get all items but do not end the source
-        let yielded: Vec<Sample> = replay.by_ref().take(2).collect();
-        assert_eq!(&yielded, &SAMPLES[3..5]);
-        source.count(); // exhaust source
-        assert_eq!(replay.next(), None);
-    }
-
-    #[test]
-    fn keeps_correct_amount_of_seconds() {
-        let input = StaticSamplesBuffer::new(nz!(1), nz!(16_000), &[0.0; 40_000]);
-
-        let (replay, mut source) = input
-            .replayable(Duration::from_secs(2))
-            .expect("longer than 100ms");
-
-        // exhaust but do not yet end source
-        source.by_ref().take(40_000).count();
-
-        // take all samples we can without blocking
-        let ready = replay.samples_ready();
-        let n_yielded = replay.take_samples(ready).count();
-
-        let max = source.sample_rate().get() * source.channels().get() as u32 * 2;
-        let margin = 16_000 / 10; // 100ms
-        assert!(n_yielded as u32 >= max - margin);
-    }
-
-    #[test]
-    fn samples_ready() {
-        let input = StaticSamplesBuffer::new(nz!(1), nz!(16_000), &[0.0; 40_000]);
-        let (mut replay, source) = input
-            .replayable(Duration::from_secs(2))
-            .expect("longer than 100ms");
-        assert_eq!(replay.by_ref().samples_ready(), 0);
-
-        source.take(8000).count(); // half a second
-        let margin = 16_000 / 10; // 100ms
-        let ready = replay.samples_ready();
-        assert!(ready >= 8000 - margin);
-    }
-}

crates/audio/src/rodio_ext/resample.rs 🔗

@@ -1,98 +0,0 @@
-use std::time::Duration;
-
-use rodio::{Sample, SampleRate, Source};
-use rubato::{FftFixedInOut, Resampler};
-
-pub struct FixedResampler<S> {
-    input: S,
-    next_channel: usize,
-    next_frame: usize,
-    output_buffer: Vec<Vec<Sample>>,
-    input_buffer: Vec<Vec<Sample>>,
-    target_sample_rate: SampleRate,
-    resampler: FftFixedInOut<Sample>,
-}
-
-impl<S: Source> FixedResampler<S> {
-    pub fn new(input: S, target_sample_rate: SampleRate) -> Self {
-        let chunk_size_in =
-            Duration::from_millis(50).as_secs_f32() * input.sample_rate().get() as f32;
-        let chunk_size_in = chunk_size_in.ceil() as usize;
-
-        let resampler = FftFixedInOut::new(
-            input.sample_rate().get() as usize,
-            target_sample_rate.get() as usize,
-            chunk_size_in,
-            input.channels().get() as usize,
-        )
-        .expect(
-            "sample rates are non zero, and we are not changing it so there is no resample ratio",
-        );
-
-        Self {
-            next_channel: 0,
-            next_frame: 0,
-            output_buffer: resampler.output_buffer_allocate(true),
-            input_buffer: resampler.input_buffer_allocate(false),
-            target_sample_rate,
-            resampler,
-            input,
-        }
-    }
-}
-
-impl<S: Source> Source for FixedResampler<S> {
-    fn current_span_len(&self) -> Option<usize> {
-        None
-    }
-
-    fn channels(&self) -> rodio::ChannelCount {
-        self.input.channels()
-    }
-
-    fn sample_rate(&self) -> rodio::SampleRate {
-        self.target_sample_rate
-    }
-
-    fn total_duration(&self) -> Option<std::time::Duration> {
-        self.input.total_duration()
-    }
-}
-
-impl<S: Source> FixedResampler<S> {
-    fn next_sample(&mut self) -> Option<Sample> {
-        let sample = self.output_buffer[self.next_channel]
-            .get(self.next_frame)
-            .copied();
-        self.next_channel = (self.next_channel + 1) % self.input.channels().get() as usize;
-        self.next_frame += 1;
-
-        sample
-    }
-}
-
-impl<S: Source> Iterator for FixedResampler<S> {
-    type Item = Sample;
-
-    fn next(&mut self) -> Option<Self::Item> {
-        if let Some(sample) = self.next_sample() {
-            return Some(sample);
-        }
-
-        for input_channel in &mut self.input_buffer {
-            input_channel.clear();
-        }
-
-        for _ in 0..self.resampler.input_frames_next() {
-            for input_channel in &mut self.input_buffer {
-                input_channel.push(self.input.next()?);
-            }
-        }
-
-        self.resampler
-            .process_into_buffer(&mut self.input_buffer, &mut self.output_buffer, None).expect("Input and output buffer channels are correct as they have been set by the resampler. The buffer for each channel is the same length. The buffer length is what is requested the resampler.");
-
-        self.next_frame = 0;
-        self.next_sample()
-    }
-}