From 6aa3d0a1183fe65da0895b4620236ceaed78a5f8 Mon Sep 17 00:00:00 2001 From: dvdsk Date: Fri, 10 Jan 2025 14:00:42 +0100 Subject: [PATCH 01/10] Adds split_once to Source split_once can be used chop a source into two sources without duplicating the first. Those sources can then not be played simultaneously. This is usefull for adding a fadeout at the end of a song for example. Though we could later specialize fadeout at end. The implementation uses a `SplitAt` struct, `split_once` returns an array of two of those. In the future we can introduce methods like `split` that return a vec of SplitAt's allowing users to chop a source into multiple sections --- examples/fadeout_end.rs | 25 ++++++++ src/source/mod.rs | 18 ++++++ src/source/split_at.rs | 123 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 166 insertions(+) create mode 100644 examples/fadeout_end.rs create mode 100644 src/source/split_at.rs diff --git a/examples/fadeout_end.rs b/examples/fadeout_end.rs new file mode 100644 index 00000000..61b68879 --- /dev/null +++ b/examples/fadeout_end.rs @@ -0,0 +1,25 @@ +use std::error::Error; +use std::io::BufReader; +use std::time::Duration; + +use rodio::Source; + +fn main() -> Result<(), Box> { + let stream_handle = rodio::OutputStreamBuilder::open_default_stream()?; + let sink = rodio::Sink::connect_new(&stream_handle.mixer()); + + let file = std::fs::File::open("assets/music.wav")?; + let music = rodio::Decoder::new(BufReader::new(file))?; + let [start, end] = music.split_once(Duration::from_secs(3)); + let end_duration = end + .total_duration() + .expect("can only fade out at the end if we know the total duration"); + let end = end.fade_out(end_duration); + + sink.append(start); + sink.append(end); + + sink.sleep_until_end(); + + Ok(()) +} diff --git a/src/source/mod.rs b/src/source/mod.rs index 605057e8..d672f3d1 100644 --- a/src/source/mod.rs +++ b/src/source/mod.rs @@ -6,6 +6,7 @@ use core::time::Duration; use crate::common::{ChannelCount, SampleRate}; use crate::Sample; use dasp_sample::FromSample; +use split_at::SplitAt; pub use self::agc::AutomaticGainControl; pub use self::amplify::Amplify; @@ -72,6 +73,7 @@ mod skip; mod skippable; mod spatial; mod speed; +mod split_at; mod square; mod stoppable; mod take; @@ -498,6 +500,15 @@ where skippable::skippable(self) } + /// returns two sources, the second source is inactive and will return + /// `None` until the first has passed the split_point. + fn split_once(self, at: Duration) -> [SplitAt; 2] + where + Self: Sized, + { + SplitAt::new(self, at) + } + /// Start tracking the elapsed duration since the start of the underlying /// source. /// @@ -606,6 +617,8 @@ pub enum SeekError { // own `try_seek` implementations. /// Any other error probably in a custom Source Other(Box), + /// Can not seek, this part of the split is not active + SplitNotActive, } impl fmt::Display for SeekError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -622,6 +635,9 @@ impl fmt::Display for SeekError { #[cfg(feature = "wav")] SeekError::HoundDecoder(err) => write!(f, "Error seeking in wav source: {}", err), SeekError::Other(_) => write!(f, "An error occurred"), + SeekError::SplitNotActive => { + write!(f, "Can not seek, this part of the split is still active") + } } } } @@ -634,6 +650,7 @@ impl std::error::Error for SeekError { #[cfg(feature = "wav")] SeekError::HoundDecoder(err) => Some(err), SeekError::Other(err) => Some(err.as_ref()), + SeekError::SplitNotActive => None, } } } @@ -656,6 +673,7 @@ impl SeekError { #[cfg(feature = "wav")] SeekError::HoundDecoder(_) => false, SeekError::Other(_) => false, + SeekError::SplitNotActive => true, } } } diff --git a/src/source/split_at.rs b/src/source/split_at.rs new file mode 100644 index 00000000..0d866727 --- /dev/null +++ b/src/source/split_at.rs @@ -0,0 +1,123 @@ +use std::sync::Arc; +use std::sync::Mutex; +use std::time::Duration; + +use crate::ChannelCount; +use crate::SampleRate; + +use super::Source; +use super::TrackPosition; + +pub struct SplitAt { + shared_source: Arc>>>, + active: Option>, + split_duration: Option, + split_point: Duration, + first_span_sample_rate: SampleRate, + first_span_channel_count: ChannelCount, +} + +impl SplitAt +where + S: Source, + ::Item: crate::Sample, +{ + /// returns two sources, the second is inactive and will return + /// none until the first has passed the split_point. + pub(crate) fn new(input: S, split_point: Duration) -> [Self; 2] { + let shared_source = Arc::new(Mutex::new(None)); + let first_span_sample_rate = input.sample_rate(); + let first_span_channel_count = input.channels(); + let total_duration = input.total_duration(); + [ + Self { + shared_source: shared_source.clone(), + active: Some(input.track_position()), + split_duration: Some(split_point), + split_point, + first_span_sample_rate, + first_span_channel_count, + }, + Self { + shared_source, + active: None, + split_duration: total_duration.map(|d| d.saturating_sub(split_point)), + split_point: Duration::MAX, + first_span_sample_rate, + first_span_channel_count, + }, + ] + } +} + +impl Iterator for SplitAt +where + S: Source, + S::Item: crate::Sample, +{ + type Item = ::Item; + + fn next(&mut self) -> Option { + let input = if let Some(active) = self.active.as_mut() { + active + } else { + // did they other stop? + let shared = self + .shared_source + .lock() + .expect("audio thread should not panic") + .take(); + self.active = shared; + self.active.as_mut()? + }; + + // There is some optimization potential here we are not using currently. + // Calling get_pos once per span should be enough + if input.get_pos() < self.split_point { + input.next() + } else { + let source = self.active.take(); + *self + .shared_source + .lock() + .expect("audio thread should not panic") = source; + None + } + } +} + +impl Source for SplitAt +where + S: Source, + S::Item: crate::Sample, +{ + fn current_span_len(&self) -> Option { + self.active.as_ref()?.current_span_len() + } + + fn channels(&self) -> ChannelCount { + self.active + .as_ref() + .map(Source::channels) + .unwrap_or(self.first_span_channel_count) + } + + fn sample_rate(&self) -> SampleRate { + self.active + .as_ref() + .map(Source::sample_rate) + .unwrap_or(self.first_span_sample_rate) + } + + fn total_duration(&self) -> Option { + self.split_duration + } + + fn try_seek(&mut self, pos: Duration) -> Result<(), super::SeekError> { + if let Some(active) = self.active.as_mut() { + active.try_seek(pos) + } else { + Err(super::SeekError::SplitNotActive) + } + } +} From 44bc9cfad640808250de88dad18efd2018b54163 Mon Sep 17 00:00:00 2001 From: dvdsk Date: Fri, 10 Jan 2025 17:04:23 +0100 Subject: [PATCH 02/10] make CI happy --- src/source/blt.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/source/blt.rs b/src/source/blt.rs index 6a973d7b..74d09d85 100644 --- a/src/source/blt.rs +++ b/src/source/blt.rs @@ -122,10 +122,7 @@ where self.applier = Some(self.formula.to_applier(self.input.sample_rate())); } - let sample = match self.input.next() { - None => return None, - Some(s) => s, - }; + let sample = self.input.next()?; let result = self .applier From c21645a6db70f4b87c8f34e61b5708d07074f92d Mon Sep 17 00:00:00 2001 From: dvdsk Date: Tue, 14 Jan 2025 14:50:52 +0100 Subject: [PATCH 03/10] split_once next() in inactive segment no longer steals back input --- src/source/split_at.rs | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/source/split_at.rs b/src/source/split_at.rs index 0d866727..89da8a5a 100644 --- a/src/source/split_at.rs +++ b/src/source/split_at.rs @@ -1,3 +1,4 @@ +use std::ops::Range; use std::sync::Arc; use std::sync::Mutex; use std::time::Duration; @@ -11,6 +12,7 @@ use super::TrackPosition; pub struct SplitAt { shared_source: Arc>>>, active: Option>, + segment_range: Range, split_duration: Option, split_point: Duration, first_span_sample_rate: SampleRate, @@ -37,6 +39,7 @@ where split_point, first_span_sample_rate, first_span_channel_count, + segment_range: todo!(), }, Self { shared_source, @@ -45,6 +48,7 @@ where split_point: Duration::MAX, first_span_sample_rate, first_span_channel_count, + segment_range: todo!(), }, ] } @@ -61,14 +65,18 @@ where let input = if let Some(active) = self.active.as_mut() { active } else { - // did they other stop? - let shared = self + // did they other stop and is it in our segment? + let mut shared = self .shared_source .lock() - .expect("audio thread should not panic") - .take(); - self.active = shared; - self.active.as_mut()? + .expect("audio thread should not panic"); + let input_pos = shared.as_mut()?.get_pos(); + if self.segment_range.contains(&input_pos) { + self.active = shared.take(); + self.active.as_mut()? + } else { + return None; + } }; // There is some optimization potential here we are not using currently. From dc4aa99852b5338c301008f00e1c4ad0b55f6370 Mon Sep 17 00:00:00 2001 From: dvdsk Date: Tue, 14 Jan 2025 15:03:33 +0100 Subject: [PATCH 04/10] fix seeking behaviour --- src/source/split_at.rs | 39 +++++++++++++++++++++++++-------------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/src/source/split_at.rs b/src/source/split_at.rs index 89da8a5a..8a2f6427 100644 --- a/src/source/split_at.rs +++ b/src/source/split_at.rs @@ -14,7 +14,6 @@ pub struct SplitAt { active: Option>, segment_range: Range, split_duration: Option, - split_point: Duration, first_span_sample_rate: SampleRate, first_span_channel_count: ChannelCount, } @@ -24,8 +23,15 @@ where S: Source, ::Item: crate::Sample, { - /// returns two sources, the second is inactive and will return - /// none until the first has passed the split_point. + /// returns two new sources that are continues segments of `self`. + /// The second segment is inactive and will return None until the + /// first segment has passed the provided split_point. + /// + /// # Seeking + /// If you seek outside the range of a segment the segment will + /// deactivate itself such that the other segment can play. This + /// works well when searching forward. Seeking back can require you + /// to play the previous segment again. pub(crate) fn new(input: S, split_point: Duration) -> [Self; 2] { let shared_source = Arc::new(Mutex::new(None)); let first_span_sample_rate = input.sample_rate(); @@ -36,22 +42,27 @@ where shared_source: shared_source.clone(), active: Some(input.track_position()), split_duration: Some(split_point), - split_point, first_span_sample_rate, first_span_channel_count, - segment_range: todo!(), + segment_range: Duration::ZERO..split_point, }, Self { shared_source, active: None, split_duration: total_duration.map(|d| d.saturating_sub(split_point)), - split_point: Duration::MAX, first_span_sample_rate, first_span_channel_count, - segment_range: todo!(), + segment_range: split_point..Duration::MAX, }, ] } + + fn deactivate(&mut self) { + let Some(input) = self.active.take() else { + return; + }; + *self.shared_source.lock().expect("todo") = Some(input); + } } impl Iterator for SplitAt @@ -81,14 +92,10 @@ where // There is some optimization potential here we are not using currently. // Calling get_pos once per span should be enough - if input.get_pos() < self.split_point { + if input.get_pos() < self.segment_range.end { input.next() } else { - let source = self.active.take(); - *self - .shared_source - .lock() - .expect("audio thread should not panic") = source; + self.deactivate(); None } } @@ -123,7 +130,11 @@ where fn try_seek(&mut self, pos: Duration) -> Result<(), super::SeekError> { if let Some(active) = self.active.as_mut() { - active.try_seek(pos) + active.try_seek(pos)?; + if !self.segment_range.contains(&pos) { + self.deactivate(); + } + Ok(()) } else { Err(super::SeekError::SplitNotActive) } From bb212dddd16c3345f7f796c9a214da98c033e2d0 Mon Sep 17 00:00:00 2001 From: dvdsk Date: Tue, 14 Jan 2025 15:13:48 +0100 Subject: [PATCH 05/10] split_once move docs + improve expect msg --- src/source/mod.rs | 11 +++++++++-- src/source/split_at.rs | 18 +++++++----------- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/src/source/mod.rs b/src/source/mod.rs index d672f3d1..10ec10a1 100644 --- a/src/source/mod.rs +++ b/src/source/mod.rs @@ -500,8 +500,15 @@ where skippable::skippable(self) } - /// returns two sources, the second source is inactive and will return - /// `None` until the first has passed the split_point. + /// returns two new sources that are continues segments of `self`. + /// The second segment is inactive and will return None until the + /// first segment has passed the provided split_point. + /// + /// # Seeking + /// If you seek outside the range of a segment the segment will + /// deactivate itself such that the other segment can play. This + /// works well when searching forward. Seeking back can require you + /// to play the previous segment again. fn split_once(self, at: Duration) -> [SplitAt; 2] where Self: Sized, diff --git a/src/source/split_at.rs b/src/source/split_at.rs index 8a2f6427..371405a6 100644 --- a/src/source/split_at.rs +++ b/src/source/split_at.rs @@ -23,15 +23,7 @@ where S: Source, ::Item: crate::Sample, { - /// returns two new sources that are continues segments of `self`. - /// The second segment is inactive and will return None until the - /// first segment has passed the provided split_point. - /// - /// # Seeking - /// If you seek outside the range of a segment the segment will - /// deactivate itself such that the other segment can play. This - /// works well when searching forward. Seeking back can require you - /// to play the previous segment again. + /// see docs at [Source::split_once]; pub(crate) fn new(input: S, split_point: Duration) -> [Self; 2] { let shared_source = Arc::new(Mutex::new(None)); let first_span_sample_rate = input.sample_rate(); @@ -61,7 +53,11 @@ where let Some(input) = self.active.take() else { return; }; - *self.shared_source.lock().expect("todo") = Some(input); + let mut shared = self + .shared_source + .lock() + .expect("The audio thread can not panic while taking the shared source"); + *shared = Some(input); } } @@ -80,7 +76,7 @@ where let mut shared = self .shared_source .lock() - .expect("audio thread should not panic"); + .expect("The audio thread cant panic deactivating"); let input_pos = shared.as_mut()?.get_pos(); if self.segment_range.contains(&input_pos) { self.active = shared.take(); From 642891af6daab1d2ad13aa6f49abfbc289b87e3c Mon Sep 17 00:00:00 2001 From: dvdsk Date: Tue, 14 Jan 2025 15:19:59 +0100 Subject: [PATCH 06/10] split_once: dummy channel & sample_rate never really used --- src/source/split_at.rs | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/source/split_at.rs b/src/source/split_at.rs index 371405a6..5a182bcf 100644 --- a/src/source/split_at.rs +++ b/src/source/split_at.rs @@ -14,8 +14,6 @@ pub struct SplitAt { active: Option>, segment_range: Range, split_duration: Option, - first_span_sample_rate: SampleRate, - first_span_channel_count: ChannelCount, } impl SplitAt @@ -26,24 +24,18 @@ where /// see docs at [Source::split_once]; pub(crate) fn new(input: S, split_point: Duration) -> [Self; 2] { let shared_source = Arc::new(Mutex::new(None)); - let first_span_sample_rate = input.sample_rate(); - let first_span_channel_count = input.channels(); let total_duration = input.total_duration(); [ Self { shared_source: shared_source.clone(), active: Some(input.track_position()), split_duration: Some(split_point), - first_span_sample_rate, - first_span_channel_count, segment_range: Duration::ZERO..split_point, }, Self { shared_source, active: None, split_duration: total_duration.map(|d| d.saturating_sub(split_point)), - first_span_sample_rate, - first_span_channel_count, segment_range: split_point..Duration::MAX, }, ] @@ -103,21 +95,28 @@ where S::Item: crate::Sample, { fn current_span_len(&self) -> Option { - self.active.as_ref()?.current_span_len() + if let Some(input) = self.active.as_ref() { + input.current_span_len() + } else { + // We do not know the channel count nor sample rate if the source + // is inactive. We will provide dummy values. This ensures the + // caller will recheck when we become active + Some(1) + } } fn channels(&self) -> ChannelCount { self.active .as_ref() .map(Source::channels) - .unwrap_or(self.first_span_channel_count) + .unwrap_or_default() } fn sample_rate(&self) -> SampleRate { self.active .as_ref() .map(Source::sample_rate) - .unwrap_or(self.first_span_sample_rate) + .unwrap_or_default() } fn total_duration(&self) -> Option { From f645fec379352b347a5262b868c6c431301f3dbd Mon Sep 17 00:00:00 2001 From: dvdsk Date: Tue, 14 Jan 2025 15:23:47 +0100 Subject: [PATCH 07/10] rename split -> segment --- src/source/mod.rs | 16 ++++++++-------- src/source/split_at.rs | 10 +++++----- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/source/mod.rs b/src/source/mod.rs index 10ec10a1..21108e1f 100644 --- a/src/source/mod.rs +++ b/src/source/mod.rs @@ -6,7 +6,7 @@ use core::time::Duration; use crate::common::{ChannelCount, SampleRate}; use crate::Sample; use dasp_sample::FromSample; -use split_at::SplitAt; +use split_at::Segment; pub use self::agc::AutomaticGainControl; pub use self::amplify::Amplify; @@ -509,11 +509,11 @@ where /// deactivate itself such that the other segment can play. This /// works well when searching forward. Seeking back can require you /// to play the previous segment again. - fn split_once(self, at: Duration) -> [SplitAt; 2] + fn split_once(self, at: Duration) -> [Segment; 2] where Self: Sized, { - SplitAt::new(self, at) + Segment::new(self, at) } /// Start tracking the elapsed duration since the start of the underlying @@ -625,7 +625,7 @@ pub enum SeekError { /// Any other error probably in a custom Source Other(Box), /// Can not seek, this part of the split is not active - SplitNotActive, + SegmentNotActive, } impl fmt::Display for SeekError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -642,8 +642,8 @@ impl fmt::Display for SeekError { #[cfg(feature = "wav")] SeekError::HoundDecoder(err) => write!(f, "Error seeking in wav source: {}", err), SeekError::Other(_) => write!(f, "An error occurred"), - SeekError::SplitNotActive => { - write!(f, "Can not seek, this part of the split is still active") + SeekError::SegmentNotActive => { + write!(f, "Can not seek, this segement is still active") } } } @@ -657,7 +657,7 @@ impl std::error::Error for SeekError { #[cfg(feature = "wav")] SeekError::HoundDecoder(err) => Some(err), SeekError::Other(err) => Some(err.as_ref()), - SeekError::SplitNotActive => None, + SeekError::SegmentNotActive => None, } } } @@ -680,7 +680,7 @@ impl SeekError { #[cfg(feature = "wav")] SeekError::HoundDecoder(_) => false, SeekError::Other(_) => false, - SeekError::SplitNotActive => true, + SeekError::SegmentNotActive => true, } } } diff --git a/src/source/split_at.rs b/src/source/split_at.rs index 5a182bcf..732b3187 100644 --- a/src/source/split_at.rs +++ b/src/source/split_at.rs @@ -9,14 +9,14 @@ use crate::SampleRate; use super::Source; use super::TrackPosition; -pub struct SplitAt { +pub struct Segment { shared_source: Arc>>>, active: Option>, segment_range: Range, split_duration: Option, } -impl SplitAt +impl Segment where S: Source, ::Item: crate::Sample, @@ -53,7 +53,7 @@ where } } -impl Iterator for SplitAt +impl Iterator for Segment where S: Source, S::Item: crate::Sample, @@ -89,7 +89,7 @@ where } } -impl Source for SplitAt +impl Source for Segment where S: Source, S::Item: crate::Sample, @@ -131,7 +131,7 @@ where } Ok(()) } else { - Err(super::SeekError::SplitNotActive) + Err(super::SeekError::SegmentNotActive) } } } From d703fb8af110af181dfffc04eb94845666f36f38 Mon Sep 17 00:00:00 2001 From: dvdsk Date: Tue, 14 Jan 2025 16:40:30 +0100 Subject: [PATCH 08/10] add tests for split_once --- tests/split.rs | 96 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 tests/split.rs diff --git a/tests/split.rs b/tests/split.rs new file mode 100644 index 00000000..73129354 --- /dev/null +++ b/tests/split.rs @@ -0,0 +1,96 @@ +use rodio::Source; +use std::time::Duration; + +struct TestSource { + samples: Vec, + pos: usize, + channels: rodio::ChannelCount, + sample_rate: rodio::SampleRate, + total_duration: Option, +} + +impl TestSource { + fn new<'a>(samples: impl IntoIterator) -> Self { + Self { + samples: samples.into_iter().copied().collect::>(), + pos: 0, + channels: 1, + sample_rate: 1, + total_duration: None, + } + } + + fn with_sample_rate(mut self, rate: rodio::SampleRate) -> Self { + self.sample_rate = rate; + self + } + fn with_channels(mut self, count: rodio::ChannelCount) -> Self { + self.channels = count; + self + } + fn with_total_duration(mut self, duration: Duration) -> Self { + self.total_duration = Some(duration); + self + } +} + +impl Iterator for TestSource { + type Item = f32; + + fn next(&mut self) -> Option { + let res = self.samples.get(self.pos).copied(); + self.pos += 1; + res + } +} + +impl rodio::Source for TestSource { + fn current_span_len(&self) -> Option { + None // must be None or seek will not work + } + fn channels(&self) -> rodio::ChannelCount { + self.channels + } + fn sample_rate(&self) -> rodio::SampleRate { + self.sample_rate + } + fn total_duration(&self) -> Option { + self.total_duration + } + fn try_seek(&mut self, pos: Duration) -> Result<(), rodio::source::SeekError> { + let duration_per_sample = Duration::from_secs(1) / self.sample_rate; + let offset = pos.div_duration_f64(duration_per_sample).floor() as usize; + self.pos = offset; + + Ok(()) + } +} + +#[test] +fn split_contains_all_samples() { + let input = [0, 1, 2, 3, 4].map(|s| s as f32); + let source = TestSource::new(&input) + .with_channels(1) + .with_sample_rate(1) + .with_total_duration(Duration::from_secs(5)); + + let [start, end] = source.split_once(Duration::from_secs(3)); + + let played: Vec<_> = start.chain(end).collect(); + assert_eq!(input.as_slice(), played.as_slice()); +} + +#[test] +fn seek_over_segment_boundry() { + let input = [0, 1, 2, 3, 4, 5, 6, 7].map(|s| s as f32); + let source = TestSource::new(&input) + .with_channels(1) + .with_sample_rate(1) + .with_total_duration(Duration::from_secs(5)); + + let [mut start, mut end] = source.split_once(Duration::from_secs(3)); + assert_eq!(start.next(), Some(0.0)); + start.try_seek(Duration::from_secs(6)).unwrap(); + assert_eq!(end.next(), Some(6.0)); + assert_eq!(end.next(), Some(7.0)); +} From f1b9ea862c1f3433c323dbe4321d26ae8cda475d Mon Sep 17 00:00:00 2001 From: dvdsk Date: Tue, 14 Jan 2025 16:51:15 +0100 Subject: [PATCH 09/10] changelog entry for split_once --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a23a6f1..aea5f947 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Adds a function to write a `Source` to a `wav` file, see `output_to_wav`. - Output audio stream buffer size can now be adjusted. +- Adds a method to split sources in two: `Source::split_once` - Sources for directly generating square waves, triangle waves, square waves, and sawtooths have been added. - An interface for defining `SignalGenerator` patterns with an `fn`, see From 6f82731f6c2b7d6b6336068c0d16d4f2ad7f3f15 Mon Sep 17 00:00:00 2001 From: dvdsk Date: Wed, 15 Jan 2025 02:32:58 +0100 Subject: [PATCH 10/10] replace TestSource with already existing SamplesBuffer --- tests/split.rs | 77 ++------------------------------------------------ 1 file changed, 3 insertions(+), 74 deletions(-) diff --git a/tests/split.rs b/tests/split.rs index 73129354..8f7aa8be 100644 --- a/tests/split.rs +++ b/tests/split.rs @@ -1,78 +1,10 @@ -use rodio::Source; +use rodio::{buffer::SamplesBuffer, Source}; use std::time::Duration; -struct TestSource { - samples: Vec, - pos: usize, - channels: rodio::ChannelCount, - sample_rate: rodio::SampleRate, - total_duration: Option, -} - -impl TestSource { - fn new<'a>(samples: impl IntoIterator) -> Self { - Self { - samples: samples.into_iter().copied().collect::>(), - pos: 0, - channels: 1, - sample_rate: 1, - total_duration: None, - } - } - - fn with_sample_rate(mut self, rate: rodio::SampleRate) -> Self { - self.sample_rate = rate; - self - } - fn with_channels(mut self, count: rodio::ChannelCount) -> Self { - self.channels = count; - self - } - fn with_total_duration(mut self, duration: Duration) -> Self { - self.total_duration = Some(duration); - self - } -} - -impl Iterator for TestSource { - type Item = f32; - - fn next(&mut self) -> Option { - let res = self.samples.get(self.pos).copied(); - self.pos += 1; - res - } -} - -impl rodio::Source for TestSource { - fn current_span_len(&self) -> Option { - None // must be None or seek will not work - } - fn channels(&self) -> rodio::ChannelCount { - self.channels - } - fn sample_rate(&self) -> rodio::SampleRate { - self.sample_rate - } - fn total_duration(&self) -> Option { - self.total_duration - } - fn try_seek(&mut self, pos: Duration) -> Result<(), rodio::source::SeekError> { - let duration_per_sample = Duration::from_secs(1) / self.sample_rate; - let offset = pos.div_duration_f64(duration_per_sample).floor() as usize; - self.pos = offset; - - Ok(()) - } -} - #[test] fn split_contains_all_samples() { let input = [0, 1, 2, 3, 4].map(|s| s as f32); - let source = TestSource::new(&input) - .with_channels(1) - .with_sample_rate(1) - .with_total_duration(Duration::from_secs(5)); + let source = SamplesBuffer::new(1, 1, input); let [start, end] = source.split_once(Duration::from_secs(3)); @@ -83,10 +15,7 @@ fn split_contains_all_samples() { #[test] fn seek_over_segment_boundry() { let input = [0, 1, 2, 3, 4, 5, 6, 7].map(|s| s as f32); - let source = TestSource::new(&input) - .with_channels(1) - .with_sample_rate(1) - .with_total_duration(Duration::from_secs(5)); + let source = SamplesBuffer::new(1, 1, input); let [mut start, mut end] = source.split_once(Duration::from_secs(3)); assert_eq!(start.next(), Some(0.0));