From 7cf2caf4be229fa175a611ea6ae2c8d881c4b574 Mon Sep 17 00:00:00 2001 From: dvdsk Date: Mon, 24 Feb 2025 12:19:11 +0100 Subject: [PATCH 01/25] find replace current_span_len with parameters changed --- src/buffer.rs | 2 +- src/decoder/flac.rs | 2 +- src/decoder/mod.rs | 20 ++++++++++---------- src/decoder/mp3.rs | 4 ++-- src/decoder/symphonia.rs | 2 +- src/decoder/vorbis.rs | 2 +- src/decoder/wav.rs | 2 +- src/mixer.rs | 2 +- src/queue.rs | 6 +++--- src/source/agc.rs | 4 ++-- src/source/amplify.rs | 4 ++-- src/source/blt.rs | 6 +++--- src/source/buffered.rs | 4 ++-- src/source/channel_volume.rs | 4 ++-- src/source/chirp.rs | 2 +- src/source/delay.rs | 4 ++-- src/source/done.rs | 4 ++-- src/source/empty.rs | 2 +- src/source/empty_callback.rs | 2 +- src/source/fadein.rs | 4 ++-- src/source/fadeout.rs | 4 ++-- src/source/from_iter.rs | 6 +++--- src/source/linear_ramp.rs | 4 ++-- src/source/mix.rs | 6 +++--- src/source/mod.rs | 8 ++++---- src/source/noise.rs | 4 ++-- src/source/pausable.rs | 4 ++-- src/source/periodic.rs | 4 ++-- src/source/position.rs | 8 ++++---- src/source/repeat.rs | 10 +++++----- src/source/sawtooth.rs | 2 +- src/source/signal_generator.rs | 2 +- src/source/sine.rs | 2 +- src/source/skip.rs | 10 +++++----- src/source/skippable.rs | 4 ++-- src/source/spatial.rs | 4 ++-- src/source/speed.rs | 4 ++-- src/source/square.rs | 2 +- src/source/stoppable.rs | 4 ++-- src/source/take.rs | 8 ++++---- src/source/triangle.rs | 2 +- src/source/uniform.rs | 4 ++-- src/source/zero.rs | 2 +- src/static_buffer.rs | 2 +- 44 files changed, 96 insertions(+), 96 deletions(-) diff --git a/src/buffer.rs b/src/buffer.rs index 192245e6..e1417821 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -63,7 +63,7 @@ impl SamplesBuffer { impl Source for SamplesBuffer { #[inline] - fn current_span_len(&self) -> Option { + fn parameters_changed(&self) -> bool { None } diff --git a/src/decoder/flac.rs b/src/decoder/flac.rs index dd21d871..2c784adf 100644 --- a/src/decoder/flac.rs +++ b/src/decoder/flac.rs @@ -77,7 +77,7 @@ where R: Read + Seek, { #[inline] - fn current_span_len(&self) -> Option { + fn parameters_changed(&self) -> bool { None } diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index 84aa469e..11e32f63 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -102,18 +102,18 @@ impl DecoderImpl { } #[inline] - fn current_span_len(&self) -> Option { + fn parameters_changed(&self) -> bool { match self { #[cfg(all(feature = "wav", not(feature = "symphonia-wav")))] - DecoderImpl::Wav(source) => source.current_span_len(), + DecoderImpl::Wav(source) => source.parameters_changed(), #[cfg(all(feature = "vorbis", not(feature = "symphonia-vorbis")))] - DecoderImpl::Vorbis(source) => source.current_span_len(), + DecoderImpl::Vorbis(source) => source.parameters_changed(), #[cfg(all(feature = "flac", not(feature = "symphonia-flac")))] - DecoderImpl::Flac(source) => source.current_span_len(), + DecoderImpl::Flac(source) => source.parameters_changed(), #[cfg(all(feature = "minimp3", not(feature = "symphonia-mp3")))] - DecoderImpl::Mp3(source) => source.current_span_len(), + DecoderImpl::Mp3(source) => source.parameters_changed(), #[cfg(feature = "symphonia")] - DecoderImpl::Symphonia(source) => source.current_span_len(), + DecoderImpl::Symphonia(source) => source.parameters_changed(), DecoderImpl::None(_) => Some(0), } } @@ -414,8 +414,8 @@ where R: Read + Seek, { #[inline] - fn current_span_len(&self) -> Option { - self.0.current_span_len() + fn parameters_changed(&self) -> bool { + self.0.parameters_changed() } #[inline] @@ -512,8 +512,8 @@ where R: Read + Seek, { #[inline] - fn current_span_len(&self) -> Option { - self.0.current_span_len() + fn parameters_changed(&self) -> bool { + self.0.parameters_changed() } #[inline] diff --git a/src/decoder/mp3.rs b/src/decoder/mp3.rs index e0680cbf..4e8cf4a6 100644 --- a/src/decoder/mp3.rs +++ b/src/decoder/mp3.rs @@ -55,7 +55,7 @@ where R: Read + Seek, { #[inline] - fn current_span_len(&self) -> Option { + fn parameters_changed(&self) -> bool { Some(self.current_span.data.len()) } @@ -96,7 +96,7 @@ where type Item = DecoderSample; fn next(&mut self) -> Option { - let current_span_len = self.current_span_len()?; + let current_span_len = self.parameters_changed()?; if self.current_span_offset == current_span_len { if let Ok(span) = self.decoder.next_frame() { // if let Ok(span) = self.decoder.decode_frame() { diff --git a/src/decoder/symphonia.rs b/src/decoder/symphonia.rs index 42d0d834..50259e48 100644 --- a/src/decoder/symphonia.rs +++ b/src/decoder/symphonia.rs @@ -158,7 +158,7 @@ impl SymphoniaDecoder { impl Source for SymphoniaDecoder { #[inline] - fn current_span_len(&self) -> Option { + fn parameters_changed(&self) -> bool { Some(self.buffer.samples().len()) } diff --git a/src/decoder/vorbis.rs b/src/decoder/vorbis.rs index 656d96ca..f2cb34a4 100644 --- a/src/decoder/vorbis.rs +++ b/src/decoder/vorbis.rs @@ -63,7 +63,7 @@ where R: Read + Seek, { #[inline] - fn current_span_len(&self) -> Option { + fn parameters_changed(&self) -> bool { Some(self.current_data.len()) } diff --git a/src/decoder/wav.rs b/src/decoder/wav.rs index 421a75e0..a9c666f6 100644 --- a/src/decoder/wav.rs +++ b/src/decoder/wav.rs @@ -147,7 +147,7 @@ where R: Read + Seek, { #[inline] - fn current_span_len(&self) -> Option { + fn parameters_changed(&self) -> bool { None } diff --git a/src/mixer.rs b/src/mixer.rs index 9c4b402e..6e106653 100644 --- a/src/mixer.rs +++ b/src/mixer.rs @@ -81,7 +81,7 @@ pub struct MixerSource { impl Source for MixerSource { #[inline] - fn current_span_len(&self) -> Option { + fn parameters_changed(&self) -> bool { None } diff --git a/src/queue.rs b/src/queue.rs index ccae005f..4c8bf138 100644 --- a/src/queue.rs +++ b/src/queue.rs @@ -115,11 +115,11 @@ const THRESHOLD: usize = 512; impl Source for SourcesQueueOutput { #[inline] - fn current_span_len(&self) -> Option { + fn parameters_changed(&self) -> bool { // This function is non-trivial because the boundary between two sounds in the queue should // be a span boundary as well. // - // The current sound is free to return `None` for `current_span_len()`, in which case + // The current sound is free to return `None` for `parameters_changed()`, in which case // we *should* return the number of samples remaining the current sound. // This can be estimated with `size_hint()`. // @@ -128,7 +128,7 @@ impl Source for SourcesQueueOutput { // constant. // Try the current `current_span_len`. - if let Some(val) = self.current.current_span_len() { + if let Some(val) = self.current.parameters_changed() { if val != 0 { return Some(val); } else if self.input.keep_alive_if_empty.load(Ordering::Acquire) diff --git a/src/source/agc.rs b/src/source/agc.rs index 39a7019b..224f569d 100644 --- a/src/source/agc.rs +++ b/src/source/agc.rs @@ -459,8 +459,8 @@ where I: Source, { #[inline] - fn current_span_len(&self) -> Option { - self.input.current_span_len() + fn parameters_changed(&self) -> bool { + self.input.parameters_changed() } #[inline] diff --git a/src/source/amplify.rs b/src/source/amplify.rs index 87f41fa3..fdc155c3 100644 --- a/src/source/amplify.rs +++ b/src/source/amplify.rs @@ -69,8 +69,8 @@ where I: Source, { #[inline] - fn current_span_len(&self) -> Option { - self.input.current_span_len() + fn parameters_changed(&self) -> bool { + self.input.parameters_changed() } #[inline] diff --git a/src/source/blt.rs b/src/source/blt.rs index 2aad5b76..0b920ba3 100644 --- a/src/source/blt.rs +++ b/src/source/blt.rs @@ -116,7 +116,7 @@ where #[inline] fn next(&mut self) -> Option { - let last_in_span = self.input.current_span_len() == Some(1); + let last_in_span = self.input.parameters_changed() == Some(1); if self.applier.is_none() { self.applier = Some(self.formula.to_applier(self.input.sample_rate())); @@ -154,8 +154,8 @@ where I: Source, { #[inline] - fn current_span_len(&self) -> Option { - self.input.current_span_len() + fn parameters_changed(&self) -> bool { + self.input.parameters_changed() } #[inline] diff --git a/src/source/buffered.rs b/src/source/buffered.rs index 402e63bb..fa455ff9 100644 --- a/src/source/buffered.rs +++ b/src/source/buffered.rs @@ -98,7 +98,7 @@ fn extract(mut input: I) -> Arc> where I: Source, { - let span_len = input.current_span_len(); + let span_len = input.parameters_changed(); if span_len == Some(0) { return Arc::new(Span::End); @@ -202,7 +202,7 @@ where I: Source, { #[inline] - fn current_span_len(&self) -> Option { + fn parameters_changed(&self) -> bool { match &*self.current_span { Span::Data(SpanData { data, .. }) => Some(data.len() - self.position_in_span), Span::End => Some(0), diff --git a/src/source/channel_volume.rs b/src/source/channel_volume.rs index 89b2280d..703ed098 100644 --- a/src/source/channel_volume.rs +++ b/src/source/channel_volume.rs @@ -102,8 +102,8 @@ where I: Source, { #[inline] - fn current_span_len(&self) -> Option { - self.input.current_span_len() + fn parameters_changed(&self) -> bool { + self.input.parameters_changed() } #[inline] diff --git a/src/source/chirp.rs b/src/source/chirp.rs index 4b99eb85..80205438 100644 --- a/src/source/chirp.rs +++ b/src/source/chirp.rs @@ -57,7 +57,7 @@ impl Iterator for Chirp { } impl Source for Chirp { - fn current_span_len(&self) -> Option { + fn parameters_changed(&self) -> bool { None } diff --git a/src/source/delay.rs b/src/source/delay.rs index f2476ddd..9929fc30 100644 --- a/src/source/delay.rs +++ b/src/source/delay.rs @@ -88,9 +88,9 @@ where I: Iterator + Source, { #[inline] - fn current_span_len(&self) -> Option { + fn parameters_changed(&self) -> bool { self.input - .current_span_len() + .parameters_changed() .map(|val| val + self.remaining_samples) } diff --git a/src/source/done.rs b/src/source/done.rs index 5bc922b7..ca56d0ca 100644 --- a/src/source/done.rs +++ b/src/source/done.rs @@ -71,8 +71,8 @@ where I: Source, { #[inline] - fn current_span_len(&self) -> Option { - self.input.current_span_len() + fn parameters_changed(&self) -> bool { + self.input.parameters_changed() } #[inline] diff --git a/src/source/empty.rs b/src/source/empty.rs index 8c8b4853..c36f596d 100644 --- a/src/source/empty.rs +++ b/src/source/empty.rs @@ -35,7 +35,7 @@ impl Iterator for Empty { impl Source for Empty { #[inline] - fn current_span_len(&self) -> Option { + fn parameters_changed(&self) -> bool { None } diff --git a/src/source/empty_callback.rs b/src/source/empty_callback.rs index 08d8b0fb..6ead0914 100644 --- a/src/source/empty_callback.rs +++ b/src/source/empty_callback.rs @@ -32,7 +32,7 @@ impl Iterator for EmptyCallback { impl Source for EmptyCallback { #[inline] - fn current_span_len(&self) -> Option { + fn parameters_changed(&self) -> bool { None } diff --git a/src/source/fadein.rs b/src/source/fadein.rs index 85e19293..88c5c4a2 100644 --- a/src/source/fadein.rs +++ b/src/source/fadein.rs @@ -67,8 +67,8 @@ where I: Source, { #[inline] - fn current_span_len(&self) -> Option { - self.inner().current_span_len() + fn parameters_changed(&self) -> bool { + self.inner().parameters_changed() } #[inline] diff --git a/src/source/fadeout.rs b/src/source/fadeout.rs index 14a41569..b987e074 100644 --- a/src/source/fadeout.rs +++ b/src/source/fadeout.rs @@ -67,8 +67,8 @@ where I: Source, { #[inline] - fn current_span_len(&self) -> Option { - self.inner().current_span_len() + fn parameters_changed(&self) -> bool { + self.inner().parameters_changed() } #[inline] diff --git a/src/source/from_iter.rs b/src/source/from_iter.rs index 48251763..fe8e02a1 100644 --- a/src/source/from_iter.rs +++ b/src/source/from_iter.rs @@ -76,11 +76,11 @@ where I::Item: Iterator + Source, { #[inline] - fn current_span_len(&self) -> Option { + fn parameters_changed(&self) -> bool { // This function is non-trivial because the boundary between the current source and the // next must be a span boundary as well. // - // The current sound is free to return `None` for `current_span_len()`, in which case + // The current sound is free to return `None` for `parameters_changed()`, in which case // we *should* return the number of samples remaining the current sound. // This can be estimated with `size_hint()`. // @@ -91,7 +91,7 @@ where // Try the current `current_span_len`. if let Some(src) = &self.current_source { - if let Some(val) = src.current_span_len() { + if let Some(val) = src.parameters_changed() { if val != 0 { return Some(val); } diff --git a/src/source/linear_ramp.rs b/src/source/linear_ramp.rs index af830a5e..b14a9e56 100644 --- a/src/source/linear_ramp.rs +++ b/src/source/linear_ramp.rs @@ -108,8 +108,8 @@ where I: Source, { #[inline] - fn current_span_len(&self) -> Option { - self.input.current_span_len() + fn parameters_changed(&self) -> bool { + self.input.parameters_changed() } #[inline] diff --git a/src/source/mix.rs b/src/source/mix.rs index c5fedfb6..8c996964 100644 --- a/src/source/mix.rs +++ b/src/source/mix.rs @@ -80,9 +80,9 @@ where I2: Source, { #[inline] - fn current_span_len(&self) -> Option { - let f1 = self.input1.current_span_len(); - let f2 = self.input2.current_span_len(); + fn parameters_changed(&self) -> bool { + let f1 = self.input1.parameters_changed(); + let f2 = self.input2.parameters_changed(); match (f1, f2) { (Some(f1), Some(f2)) => Some(cmp::min(f1, f2)), diff --git a/src/source/mod.rs b/src/source/mod.rs index aefbd1b2..d754415d 100644 --- a/src/source/mod.rs +++ b/src/source/mod.rs @@ -146,7 +146,7 @@ pub use self::noise::{pink, white, PinkNoise, WhiteNoise}; /// stay the same for long periods of time and avoids calling `channels()` and /// `sample_rate` too frequently. /// -/// In order to properly handle this situation, the `current_span_len()` method should return +/// In order to properly handle this situation, the `parameters_changed()` method should return /// the number of samples that remain in the iterator before the samples rate and number of /// channels can potentially change. /// @@ -157,7 +157,7 @@ pub trait Source: Iterator { /// /// After the engine has finished reading the specified number of samples, it will check /// whether the value of `channels()` and/or `sample_rate()` have changed. - fn current_span_len(&self) -> Option; + fn parameters_changed(&self) -> bool; /// Returns the number of channels. Channels are always interleaved. fn channels(&self) -> ChannelCount; @@ -646,8 +646,8 @@ macro_rules! source_pointer_impl { ($($sig:tt)+) => { impl $($sig)+ { #[inline] - fn current_span_len(&self) -> Option { - (**self).current_span_len() + fn parameters_changed(&self) -> bool { + (**self).parameters_changed() } #[inline] diff --git a/src/source/noise.rs b/src/source/noise.rs index 03a2e2dd..350eb3dd 100644 --- a/src/source/noise.rs +++ b/src/source/noise.rs @@ -60,7 +60,7 @@ impl Iterator for WhiteNoise { impl Source for WhiteNoise { #[inline] - fn current_span_len(&self) -> Option { + fn parameters_changed(&self) -> bool { None } @@ -136,7 +136,7 @@ impl Iterator for PinkNoise { } impl Source for PinkNoise { - fn current_span_len(&self) -> Option { + fn parameters_changed(&self) -> bool { None } diff --git a/src/source/pausable.rs b/src/source/pausable.rs index f9fddadc..56af86d9 100644 --- a/src/source/pausable.rs +++ b/src/source/pausable.rs @@ -101,8 +101,8 @@ where I: Source, { #[inline] - fn current_span_len(&self) -> Option { - self.input.current_span_len() + fn parameters_changed(&self) -> bool { + self.input.parameters_changed() } #[inline] diff --git a/src/source/periodic.rs b/src/source/periodic.rs index 31260fd1..75a6ddcb 100644 --- a/src/source/periodic.rs +++ b/src/source/periodic.rs @@ -100,8 +100,8 @@ where F: FnMut(&mut I), { #[inline] - fn current_span_len(&self) -> Option { - self.input.current_span_len() + fn parameters_changed(&self) -> bool { + self.input.parameters_changed() } #[inline] diff --git a/src/source/position.rs b/src/source/position.rs index aa6b63f8..6fd8753a 100644 --- a/src/source/position.rs +++ b/src/source/position.rs @@ -73,7 +73,7 @@ where #[inline] fn set_current_span(&mut self) { - self.current_span_len = self.current_span_len(); + self.current_span_len = self.parameters_changed(); self.current_span_sample_rate = self.sample_rate(); self.current_span_channels = self.channels(); } @@ -98,7 +98,7 @@ where // At the end of a span add the duration of this span to // offset_duration and start collecting samples again. - if Some(self.samples_counted) == self.current_span_len() { + if Some(self.samples_counted) == self.parameters_changed() { self.offset_duration += self.samples_counted as f64 / self.current_span_sample_rate as f64 / self.current_span_channels as f64; @@ -122,8 +122,8 @@ where I: Source, { #[inline] - fn current_span_len(&self) -> Option { - self.input.current_span_len() + fn parameters_changed(&self) -> bool { + self.input.parameters_changed() } #[inline] diff --git a/src/source/repeat.rs b/src/source/repeat.rs index 12a6b947..2faed57c 100644 --- a/src/source/repeat.rs +++ b/src/source/repeat.rs @@ -55,16 +55,16 @@ where I: Iterator + Source, { #[inline] - fn current_span_len(&self) -> Option { - match self.inner.current_span_len() { - Some(0) => self.next.current_span_len(), + fn parameters_changed(&self) -> bool { + match self.inner.parameters_changed() { + Some(0) => self.next.parameters_changed(), a => a, } } #[inline] fn channels(&self) -> ChannelCount { - match self.inner.current_span_len() { + match self.inner.parameters_changed() { Some(0) => self.next.channels(), _ => self.inner.channels(), } @@ -72,7 +72,7 @@ where #[inline] fn sample_rate(&self) -> SampleRate { - match self.inner.current_span_len() { + match self.inner.parameters_changed() { Some(0) => self.next.sample_rate(), _ => self.inner.sample_rate(), } diff --git a/src/source/sawtooth.rs b/src/source/sawtooth.rs index c6ae01f3..57cee3c8 100644 --- a/src/source/sawtooth.rs +++ b/src/source/sawtooth.rs @@ -39,7 +39,7 @@ impl Iterator for SawtoothWave { impl Source for SawtoothWave { #[inline] - fn current_span_len(&self) -> Option { + fn parameters_changed(&self) -> bool { None } diff --git a/src/source/signal_generator.rs b/src/source/signal_generator.rs index e0c33bb8..e51fb598 100644 --- a/src/source/signal_generator.rs +++ b/src/source/signal_generator.rs @@ -137,7 +137,7 @@ impl Iterator for SignalGenerator { impl Source for SignalGenerator { #[inline] - fn current_span_len(&self) -> Option { + fn parameters_changed(&self) -> bool { None } diff --git a/src/source/sine.rs b/src/source/sine.rs index e3814435..58e37d07 100644 --- a/src/source/sine.rs +++ b/src/source/sine.rs @@ -39,7 +39,7 @@ impl Iterator for SineWave { impl Source for SineWave { #[inline] - fn current_span_len(&self) -> Option { + fn parameters_changed(&self) -> bool { None } diff --git a/src/source/skip.rs b/src/source/skip.rs index ee3c7365..f052e8da 100644 --- a/src/source/skip.rs +++ b/src/source/skip.rs @@ -24,15 +24,15 @@ where I: Source, { while duration > Duration::new(0, 0) { - if input.current_span_len().is_none() { + if input.parameters_changed().is_none() { // Sample rate and the amount of channels will be the same till the end. do_skip_duration_unchecked(input, duration); return; } - // .unwrap() safety: if `current_span_len()` is None, the body of the `if` statement + // .unwrap() safety: if `parameters_changed()` is None, the body of the `if` statement // above returns before we get here. - let span_len: usize = input.current_span_len().unwrap(); + let span_len: usize = input.parameters_changed().unwrap(); // If span_len is zero, then there is no more data to skip. Instead // just bail out. if span_len == 0 { @@ -131,8 +131,8 @@ where I: Source, { #[inline] - fn current_span_len(&self) -> Option { - self.input.current_span_len() + fn parameters_changed(&self) -> bool { + self.input.parameters_changed() } #[inline] diff --git a/src/source/skippable.rs b/src/source/skippable.rs index ba4d43d9..1ec2ee6f 100644 --- a/src/source/skippable.rs +++ b/src/source/skippable.rs @@ -75,8 +75,8 @@ where I: Source, { #[inline] - fn current_span_len(&self) -> Option { - self.input.current_span_len() + fn parameters_changed(&self) -> bool { + self.input.parameters_changed() } #[inline] diff --git a/src/source/spatial.rs b/src/source/spatial.rs index 6ea3555d..259522d3 100644 --- a/src/source/spatial.rs +++ b/src/source/spatial.rs @@ -93,8 +93,8 @@ where I: Source, { #[inline] - fn current_span_len(&self) -> Option { - self.input.current_span_len() + fn parameters_changed(&self) -> bool { + self.input.parameters_changed() } #[inline] diff --git a/src/source/speed.rs b/src/source/speed.rs index 25e25efc..ec3aeebd 100644 --- a/src/source/speed.rs +++ b/src/source/speed.rs @@ -116,8 +116,8 @@ where I: Source, { #[inline] - fn current_span_len(&self) -> Option { - self.input.current_span_len() + fn parameters_changed(&self) -> bool { + self.input.parameters_changed() } #[inline] diff --git a/src/source/square.rs b/src/source/square.rs index ac6bd678..3aea0578 100644 --- a/src/source/square.rs +++ b/src/source/square.rs @@ -39,7 +39,7 @@ impl Iterator for SquareWave { impl Source for SquareWave { #[inline] - fn current_span_len(&self) -> Option { + fn parameters_changed(&self) -> bool { None } diff --git a/src/source/stoppable.rs b/src/source/stoppable.rs index 57cc7dae..dae04ff5 100644 --- a/src/source/stoppable.rs +++ b/src/source/stoppable.rs @@ -71,8 +71,8 @@ where I: Source, { #[inline] - fn current_span_len(&self) -> Option { - self.input.current_span_len() + fn parameters_changed(&self) -> bool { + self.input.parameters_changed() } #[inline] diff --git a/src/source/take.rs b/src/source/take.rs index 9abcf7f6..23d91afe 100644 --- a/src/source/take.rs +++ b/src/source/take.rs @@ -10,7 +10,7 @@ where I: Source, { TakeDuration { - current_span_len: input.current_span_len(), + current_span_len: input.parameters_changed(), duration_per_sample: TakeDuration::get_duration_per_sample(&input), input, remaining_duration: duration, @@ -104,7 +104,7 @@ where if span_len > 0 { self.current_span_len = Some(span_len - 1); } else { - self.current_span_len = self.input.current_span_len(); + self.current_span_len = self.input.parameters_changed(); // Sample rate might have changed self.duration_per_sample = Self::get_duration_per_sample(&self.input); } @@ -134,7 +134,7 @@ where I: Iterator + Source, { #[inline] - fn current_span_len(&self) -> Option { + fn parameters_changed(&self) -> bool { let remaining_nanos = self.remaining_duration.as_secs() * NANOS_PER_SEC + self.remaining_duration.subsec_nanos() as u64; let nanos_per_sample = self.duration_per_sample.as_secs() * NANOS_PER_SEC @@ -142,7 +142,7 @@ where let remaining_samples = (remaining_nanos / nanos_per_sample) as usize; self.input - .current_span_len() + .parameters_changed() .filter(|value| *value < remaining_samples) .or(Some(remaining_samples)) } diff --git a/src/source/triangle.rs b/src/source/triangle.rs index eb73801d..af660dc7 100644 --- a/src/source/triangle.rs +++ b/src/source/triangle.rs @@ -39,7 +39,7 @@ impl Iterator for TriangleWave { impl Source for TriangleWave { #[inline] - fn current_span_len(&self) -> Option { + fn parameters_changed(&self) -> bool { None } diff --git a/src/source/uniform.rs b/src/source/uniform.rs index b92642fa..66f1f682 100644 --- a/src/source/uniform.rs +++ b/src/source/uniform.rs @@ -52,7 +52,7 @@ where target_sample_rate: SampleRate, ) -> ChannelCountConverter>> { // Limit the span length to something reasonable - let span_len = input.current_span_len().map(|x| x.min(32768)); + let span_len = input.parameters_changed().map(|x| x.min(32768)); let from_channels = input.channels(); let from_sample_rate = input.sample_rate(); @@ -100,7 +100,7 @@ where I: Iterator + Source, { #[inline] - fn current_span_len(&self) -> Option { + fn parameters_changed(&self) -> bool { None } diff --git a/src/source/zero.rs b/src/source/zero.rs index aba88aba..404aa20a 100644 --- a/src/source/zero.rs +++ b/src/source/zero.rs @@ -59,7 +59,7 @@ impl Iterator for Zero { impl Source for Zero { #[inline] - fn current_span_len(&self) -> Option { + fn parameters_changed(&self) -> bool { self.num_samples } diff --git a/src/static_buffer.rs b/src/static_buffer.rs index 014d824c..9226d426 100644 --- a/src/static_buffer.rs +++ b/src/static_buffer.rs @@ -63,7 +63,7 @@ impl StaticSamplesBuffer { impl Source for StaticSamplesBuffer { #[inline] - fn current_span_len(&self) -> Option { + fn parameters_changed(&self) -> bool { None } From 0eac7bcf59199017937c0843e7023414823a46b0 Mon Sep 17 00:00:00 2001 From: dvdsk Date: Mon, 24 Feb 2025 16:07:40 +0100 Subject: [PATCH 02/25] implement `parameters_changed` for simple and medium complexity sources All other sources have been marked as TODO using `todo!()`. This is such that the test suite can run. New tests need to be made for all non trivial implementations (those that do not simply forward the call to `parameters_changed`) - trivial implementations: TestSource, SamplesBuffer, chirp, delay, empty, empty_callback, mix, sawtooth, signal_generator, sine, square, triangle, zero, static_buffer - implemented: blt, buffered, position, repeat, skip, take_duration, uniform - marked as todo: all decoders, Mixer, Queue, source/from_iter Added some sources to make the implementation easier: - TakeSpan, takes up to a span of samples - TakeSamples, takes n samples or slightly less. Always ends frame aligned - Peekable, similar to Iterator::peekable. --- benches/shared.rs | 4 +- src/buffer.rs | 2 +- src/decoder/flac.rs | 2 +- src/decoder/mod.rs | 2 +- src/decoder/symphonia.rs | 3 +- src/decoder/vorbis.rs | 3 +- src/decoder/wav.rs | 3 +- src/mixer.rs | 3 +- src/queue.rs | 42 +++--- src/source/blt.rs | 11 +- src/source/buffered.rs | 29 ++-- src/source/chirp.rs | 2 +- src/source/delay.rs | 4 +- src/source/empty.rs | 2 +- src/source/empty_callback.rs | 2 +- src/source/from_iter.rs | 46 +++--- src/source/mix.rs | 8 +- src/source/mod.rs | 40 ++++-- src/source/peekable.rs | 78 +++++++++++ src/source/position.rs | 21 +-- src/source/repeat.rs | 34 +++-- src/source/sawtooth.rs | 2 +- src/source/signal_generator.rs | 2 +- src/source/sine.rs | 2 +- src/source/skip.rs | 169 +++-------------------- src/source/square.rs | 2 +- src/source/{take.rs => take_duration.rs} | 34 ++--- src/source/take_samples.rs | 96 +++++++++++++ src/source/take_span.rs | 80 +++++++++++ src/source/triangle.rs | 2 +- src/source/uniform.rs | 82 +++-------- src/source/zero.rs | 2 +- src/static_buffer.rs | 2 +- 33 files changed, 442 insertions(+), 374 deletions(-) create mode 100644 src/source/peekable.rs rename src/source/{take.rs => take_duration.rs} (75%) create mode 100644 src/source/take_samples.rs create mode 100644 src/source/take_span.rs diff --git a/benches/shared.rs b/benches/shared.rs index 442621f4..00d5042f 100644 --- a/benches/shared.rs +++ b/benches/shared.rs @@ -29,8 +29,8 @@ impl ExactSizeIterator for TestSource { impl Source for TestSource { #[inline] - fn current_span_len(&self) -> Option { - None // forever + fn parameters_changed(&self) -> bool { + false } #[inline] diff --git a/src/buffer.rs b/src/buffer.rs index e1417821..463106d0 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -64,7 +64,7 @@ impl SamplesBuffer { impl Source for SamplesBuffer { #[inline] fn parameters_changed(&self) -> bool { - None + false } #[inline] diff --git a/src/decoder/flac.rs b/src/decoder/flac.rs index 2c784adf..0a1836c4 100644 --- a/src/decoder/flac.rs +++ b/src/decoder/flac.rs @@ -78,7 +78,7 @@ where { #[inline] fn parameters_changed(&self) -> bool { - None + todo!() } #[inline] diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index 11e32f63..6feb28eb 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -114,7 +114,7 @@ impl DecoderImpl { DecoderImpl::Mp3(source) => source.parameters_changed(), #[cfg(feature = "symphonia")] DecoderImpl::Symphonia(source) => source.parameters_changed(), - DecoderImpl::None(_) => Some(0), + DecoderImpl::None(_) => false, } } diff --git a/src/decoder/symphonia.rs b/src/decoder/symphonia.rs index 50259e48..0fa4d066 100644 --- a/src/decoder/symphonia.rs +++ b/src/decoder/symphonia.rs @@ -159,7 +159,8 @@ impl SymphoniaDecoder { impl Source for SymphoniaDecoder { #[inline] fn parameters_changed(&self) -> bool { - Some(self.buffer.samples().len()) + todo!() + // Some(self.buffer.samples().len()) } #[inline] diff --git a/src/decoder/vorbis.rs b/src/decoder/vorbis.rs index f2cb34a4..8f5e86fe 100644 --- a/src/decoder/vorbis.rs +++ b/src/decoder/vorbis.rs @@ -64,7 +64,8 @@ where { #[inline] fn parameters_changed(&self) -> bool { - Some(self.current_data.len()) + todo!() + //Some(self.current_data.len()) } #[inline] diff --git a/src/decoder/wav.rs b/src/decoder/wav.rs index a9c666f6..22751773 100644 --- a/src/decoder/wav.rs +++ b/src/decoder/wav.rs @@ -148,7 +148,8 @@ where { #[inline] fn parameters_changed(&self) -> bool { - None + todo!() + // None } #[inline] diff --git a/src/mixer.rs b/src/mixer.rs index 6e106653..1dc7ca8f 100644 --- a/src/mixer.rs +++ b/src/mixer.rs @@ -82,7 +82,8 @@ pub struct MixerSource { impl Source for MixerSource { #[inline] fn parameters_changed(&self) -> bool { - None + todo!() + // None } #[inline] diff --git a/src/queue.rs b/src/queue.rs index 4c8bf138..c571e90f 100644 --- a/src/queue.rs +++ b/src/queue.rs @@ -128,27 +128,29 @@ impl Source for SourcesQueueOutput { // constant. // Try the current `current_span_len`. - if let Some(val) = self.current.parameters_changed() { - if val != 0 { - return Some(val); - } else if self.input.keep_alive_if_empty.load(Ordering::Acquire) - && self.input.next_sounds.lock().unwrap().is_empty() - { - // The next source will be a filler silence which will have the length of `THRESHOLD` - return Some(THRESHOLD); - } - } - - // Try the size hint. - let (lower_bound, _) = self.current.size_hint(); - // The iterator default implementation just returns 0. - // That's a problematic value, so skip it. - if lower_bound > 0 { - return Some(lower_bound); - } - // Otherwise we use the constant value. - Some(THRESHOLD) + todo!() + // if let Some(val) = self.current.parameters_changed() { + // if val != 0 { + // return Some(val); + // } else if self.input.keep_alive_if_empty.load(Ordering::Acquire) + // && self.input.next_sounds.lock().unwrap().is_empty() + // { + // // The next source will be a filler silence which will have the length of `THRESHOLD` + // return Some(THRESHOLD); + // } + // } + // + // // Try the size hint. + // let (lower_bound, _) = self.current.size_hint(); + // // The iterator default implementation just returns 0. + // // That's a problematic value, so skip it. + // if lower_bound > 0 { + // return Some(lower_bound); + // } + // + // // Otherwise we use the constant value. + // Some(THRESHOLD) } #[inline] diff --git a/src/source/blt.rs b/src/source/blt.rs index 0b920ba3..5330efb9 100644 --- a/src/source/blt.rs +++ b/src/source/blt.rs @@ -116,9 +116,7 @@ where #[inline] fn next(&mut self) -> Option { - let last_in_span = self.input.parameters_changed() == Some(1); - - if self.applier.is_none() { + if self.input.parameters_changed() { self.applier = Some(self.formula.to_applier(self.input.sample_rate())); } @@ -126,7 +124,8 @@ where let result = self .applier .as_ref() - .unwrap() + .expect("just set above") // TODO we can do without the Option now we have + // `parameters_changed` .apply(sample, self.x_n1, self.x_n2, self.y_n1, self.y_n2); self.y_n2 = self.y_n1; @@ -134,10 +133,6 @@ where self.y_n1 = result; self.x_n1 = sample; - if last_in_span { - self.applier = None; - } - Some(result) } diff --git a/src/source/buffered.rs b/src/source/buffered.rs index fa455ff9..4c51fc3c 100644 --- a/src/source/buffered.rs +++ b/src/source/buffered.rs @@ -1,4 +1,3 @@ -use std::cmp; use std::mem; use std::sync::{Arc, Mutex}; use std::time::Duration; @@ -98,18 +97,24 @@ fn extract(mut input: I) -> Arc> where I: Source, { - let span_len = input.parameters_changed(); - - if span_len == Some(0) { + if input.parameters_changed() { return Arc::new(Span::End); } let channels = input.channels(); let rate = input.sample_rate(); - let data: Vec = input - .by_ref() - .take(cmp::min(span_len.unwrap_or(32768), 32768)) - .collect(); + + let mut data = Vec::new(); + loop { + let Some(element) = input.next() else { break }; + data.push(element); + if input.parameters_changed() { + break; + } + if data.len() > 32768 { + break; + } + } if data.is_empty() { return Arc::new(Span::End); @@ -193,9 +198,7 @@ where } } -// TODO: uncomment when `size_hint` is fixed -/*impl ExactSizeIterator for Amplify where I: Source + ExactSizeIterator, I::Item: Sample { -}*/ +// TODO: implement exactsize iterator when size_hint is done impl Source for Buffered where @@ -204,8 +207,8 @@ where #[inline] fn parameters_changed(&self) -> bool { match &*self.current_span { - Span::Data(SpanData { data, .. }) => Some(data.len() - self.position_in_span), - Span::End => Some(0), + Span::Data(_) => false, + Span::End => true, Span::Input(_) => unreachable!(), } } diff --git a/src/source/chirp.rs b/src/source/chirp.rs index 80205438..d52e7936 100644 --- a/src/source/chirp.rs +++ b/src/source/chirp.rs @@ -58,7 +58,7 @@ impl Iterator for Chirp { impl Source for Chirp { fn parameters_changed(&self) -> bool { - None + false } fn channels(&self) -> ChannelCount { diff --git a/src/source/delay.rs b/src/source/delay.rs index 9929fc30..49751bd4 100644 --- a/src/source/delay.rs +++ b/src/source/delay.rs @@ -89,9 +89,7 @@ where { #[inline] fn parameters_changed(&self) -> bool { - self.input - .parameters_changed() - .map(|val| val + self.remaining_samples) + self.input.parameters_changed() } #[inline] diff --git a/src/source/empty.rs b/src/source/empty.rs index c36f596d..f26284e5 100644 --- a/src/source/empty.rs +++ b/src/source/empty.rs @@ -36,7 +36,7 @@ impl Iterator for Empty { impl Source for Empty { #[inline] fn parameters_changed(&self) -> bool { - None + false } #[inline] diff --git a/src/source/empty_callback.rs b/src/source/empty_callback.rs index 6ead0914..c56f0f56 100644 --- a/src/source/empty_callback.rs +++ b/src/source/empty_callback.rs @@ -33,7 +33,7 @@ impl Iterator for EmptyCallback { impl Source for EmptyCallback { #[inline] fn parameters_changed(&self) -> bool { - None + false } #[inline] diff --git a/src/source/from_iter.rs b/src/source/from_iter.rs index fe8e02a1..bca67444 100644 --- a/src/source/from_iter.rs +++ b/src/source/from_iter.rs @@ -87,28 +87,30 @@ where // If the `size_hint` is `None` as well, we are in the worst case scenario. To handle this // situation we force a span to have a maximum number of samples indicate by this // constant. - const THRESHOLD: usize = 10240; - - // Try the current `current_span_len`. - if let Some(src) = &self.current_source { - if let Some(val) = src.parameters_changed() { - if val != 0 { - return Some(val); - } - } - } - - // Try the size hint. - if let Some(src) = &self.current_source { - if let Some(val) = src.size_hint().1 { - if val < THRESHOLD && val != 0 { - return Some(val); - } - } - } - - // Otherwise we use the constant value. - Some(THRESHOLD) + + todo!() + // const THRESHOLD: usize = 10240; + // + // // Try the current `current_span_len`. + // if let Some(src) = &self.current_source { + // if let Some(val) = src.parameters_changed() { + // if val != 0 { + // return Some(val); + // } + // } + // } + // + // // Try the size hint. + // if let Some(src) = &self.current_source { + // if let Some(val) = src.size_hint().1 { + // if val < THRESHOLD && val != 0 { + // return Some(val); + // } + // } + // } + // + // // Otherwise we use the constant value. + // Some(THRESHOLD) } #[inline] diff --git a/src/source/mix.rs b/src/source/mix.rs index 8c996964..bcb521aa 100644 --- a/src/source/mix.rs +++ b/src/source/mix.rs @@ -81,13 +81,7 @@ where { #[inline] fn parameters_changed(&self) -> bool { - let f1 = self.input1.parameters_changed(); - let f2 = self.input2.parameters_changed(); - - match (f1, f2) { - (Some(f1), Some(f2)) => Some(cmp::min(f1, f2)), - _ => None, - } + self.input1.parameters_changed() || self.input2.parameters_changed() } #[inline] diff --git a/src/source/mod.rs b/src/source/mod.rs index d754415d..990d422a 100644 --- a/src/source/mod.rs +++ b/src/source/mod.rs @@ -6,6 +6,8 @@ use core::time::Duration; use crate::common::{ChannelCount, SampleRate}; use crate::Sample; use dasp_sample::FromSample; +use take_samples::TakeSamples; +use take_span::TakeSpan; pub use self::agc::AutomaticGainControl; pub use self::amplify::Amplify; @@ -37,7 +39,7 @@ pub use self::spatial::Spatial; pub use self::speed::Speed; pub use self::square::SquareWave; pub use self::stoppable::Stoppable; -pub use self::take::TakeDuration; +pub use self::take_duration::TakeDuration; pub use self::triangle::TriangleWave; pub use self::uniform::UniformSourceIterator; pub use self::zero::Zero; @@ -60,6 +62,7 @@ mod from_iter; mod linear_ramp; mod mix; mod pausable; +mod peekable; mod periodic; mod position; mod repeat; @@ -72,7 +75,9 @@ mod spatial; mod speed; mod square; mod stoppable; -mod take; +mod take_duration; +mod take_samples; +mod take_span; mod triangle; mod uniform; mod zero; @@ -151,12 +156,9 @@ pub use self::noise::{pink, white, PinkNoise, WhiteNoise}; /// channels can potentially change. /// pub trait Source: Iterator { - /// Returns the number of samples before the current span ends. `None` means "infinite" or - /// "until the sound ends". - /// Should never return 0 unless there's no more data. - /// - /// After the engine has finished reading the specified number of samples, it will check - /// whether the value of `channels()` and/or `sample_rate()` have changed. + /// Whether the value of `channels()` and/or `sample_rate()` have changed. + /// This is true before the next call to `Source::next`. After the first + /// `Source::next` call this will be false again. fn parameters_changed(&self) -> bool; /// Returns the number of channels. Channels are always interleaved. @@ -207,7 +209,27 @@ pub trait Source: Iterator { where Self: Sized, { - take::take_duration(self, duration) + take_duration::take_duration(self, duration) + } + + /// Takes a samples until the current span ends, which is when any of + /// the source parameters_change. + #[inline] + fn take_span(self) -> TakeSpan + where + Self: Sized, + { + TakeSpan::new(self) + } + + /// Takes n samples. N might be slightly adjusted to make sure + /// this source ends on a frame boundary + #[inline] + fn take_samples(self, n: usize) -> TakeSamples + where + Self: Sized, + { + TakeSamples::new(self, n) } /// Delays the sound by a certain duration. diff --git a/src/source/peekable.rs b/src/source/peekable.rs new file mode 100644 index 00000000..ce14c517 --- /dev/null +++ b/src/source/peekable.rs @@ -0,0 +1,78 @@ +use std::time::Duration; + +use crate::{ChannelCount, Sample, SampleRate}; + +use super::Source; + +pub struct Peekable { + next: Option, + channels: ChannelCount, + sample_rate: SampleRate, + parameters_changed_after_next: bool, + parameters_changed: bool, + inner: I, +} + +impl Clone for Peekable +where + I: Clone, +{ + fn clone(&self) -> Self { + Self { + next: self.next, + channels: self.channels, + sample_rate: self.sample_rate, + parameters_changed_after_next: self.parameters_changed_after_next, + parameters_changed: self.parameters_changed, + inner: self.inner.clone(), + } + } +} + +impl Peekable { + pub(crate) fn new(mut inner: I) -> Peekable { + Self { + // field order is critical! do not change + channels: inner.channels(), + sample_rate: inner.sample_rate(), + next: inner.next(), + parameters_changed_after_next: inner.parameters_changed(), + parameters_changed: false, + inner, + } + } + + pub fn peek(&self) -> Option { + self.next + } +} + +impl Iterator for Peekable { + type Item = Sample; + + fn next(&mut self) -> Option { + let item = self.next.take()?; + self.next = self.inner.next(); + self.parameters_changed = self.parameters_changed_after_next; + self.parameters_changed_after_next = self.inner.parameters_changed(); + Some(item) + } +} + +impl Source for Peekable { + fn parameters_changed(&self) -> bool { + self.parameters_changed + } + + fn channels(&self) -> ChannelCount { + self.channels + } + + fn sample_rate(&self) -> SampleRate { + self.sample_rate + } + + fn total_duration(&self) -> Option { + self.inner.total_duration() + } +} diff --git a/src/source/position.rs b/src/source/position.rs index 6fd8753a..f146d028 100644 --- a/src/source/position.rs +++ b/src/source/position.rs @@ -13,7 +13,6 @@ pub fn track_position(source: I) -> TrackPosition { offset_duration: 0.0, current_span_sample_rate: 0, current_span_channels: 0, - current_span_len: None, } } @@ -25,7 +24,6 @@ pub struct TrackPosition { offset_duration: f64, current_span_sample_rate: SampleRate, current_span_channels: ChannelCount, - current_span_len: Option, } impl TrackPosition { @@ -59,7 +57,7 @@ where /// returned by [`get_pos`](TrackPosition::get_pos). /// /// This can get confusing when using [`get_pos()`](TrackPosition::get_pos) - /// together with [`Source::try_seek()`] as the the latter does take all + /// together with [`Source::try_seek()`] as the latter does take all /// speedup's and delay's into account. Its recommended therefore to apply /// track_position after speedup's and delay's. #[inline] @@ -70,13 +68,6 @@ where + self.offset_duration; Duration::from_secs_f64(seconds) } - - #[inline] - fn set_current_span(&mut self) { - self.current_span_len = self.parameters_changed(); - self.current_span_sample_rate = self.sample_rate(); - self.current_span_channels = self.channels(); - } } impl Iterator for TrackPosition @@ -87,25 +78,21 @@ where #[inline] fn next(&mut self) -> Option { - // This should only be executed once at the first call to next. - if self.current_span_len.is_none() { - self.set_current_span(); - } - let item = self.input.next(); if item.is_some() { self.samples_counted += 1; // At the end of a span add the duration of this span to // offset_duration and start collecting samples again. - if Some(self.samples_counted) == self.parameters_changed() { + if self.parameters_changed() { self.offset_duration += self.samples_counted as f64 / self.current_span_sample_rate as f64 / self.current_span_channels as f64; // Reset. self.samples_counted = 0; - self.set_current_span(); + self.current_span_sample_rate = self.sample_rate(); + self.current_span_channels = self.channels(); }; }; item diff --git a/src/source/repeat.rs b/src/source/repeat.rs index 2faed57c..6547b34a 100644 --- a/src/source/repeat.rs +++ b/src/source/repeat.rs @@ -2,6 +2,7 @@ use std::time::Duration; use crate::source::buffered::Buffered; +use super::peekable::Peekable; use super::SeekError; use crate::common::{ChannelCount, SampleRate}; use crate::Source; @@ -13,7 +14,7 @@ where { let input = input.buffered(); Repeat { - inner: input.clone(), + inner: Peekable::new(input.clone()), next: input, } } @@ -23,7 +24,7 @@ pub struct Repeat where I: Source, { - inner: Buffered, + inner: Peekable>, next: Buffered, } @@ -36,11 +37,11 @@ where #[inline] fn next(&mut self) -> Option<::Item> { if let Some(value) = self.inner.next() { - return Some(value); + Some(value) + } else { + self.inner = Peekable::new(self.next.clone()); + self.inner.next() } - - self.inner = self.next.clone(); - self.inner.next() } #[inline] @@ -56,25 +57,28 @@ where { #[inline] fn parameters_changed(&self) -> bool { - match self.inner.parameters_changed() { - Some(0) => self.next.parameters_changed(), - a => a, + if self.inner.peek().is_none() { + true // back to beginning of source source + } else { + self.inner.parameters_changed() } } #[inline] fn channels(&self) -> ChannelCount { - match self.inner.parameters_changed() { - Some(0) => self.next.channels(), - _ => self.inner.channels(), + if self.inner.peek().is_none() { + self.next.channels() + } else { + self.inner.channels() } } #[inline] fn sample_rate(&self) -> SampleRate { - match self.inner.parameters_changed() { - Some(0) => self.next.sample_rate(), - _ => self.inner.sample_rate(), + if self.inner.peek().is_none() { + self.next.sample_rate() + } else { + self.inner.sample_rate() } } diff --git a/src/source/sawtooth.rs b/src/source/sawtooth.rs index 57cee3c8..45a4f89b 100644 --- a/src/source/sawtooth.rs +++ b/src/source/sawtooth.rs @@ -40,7 +40,7 @@ impl Iterator for SawtoothWave { impl Source for SawtoothWave { #[inline] fn parameters_changed(&self) -> bool { - None + false } #[inline] diff --git a/src/source/signal_generator.rs b/src/source/signal_generator.rs index e51fb598..e9de2db0 100644 --- a/src/source/signal_generator.rs +++ b/src/source/signal_generator.rs @@ -138,7 +138,7 @@ impl Iterator for SignalGenerator { impl Source for SignalGenerator { #[inline] fn parameters_changed(&self) -> bool { - None + false } #[inline] diff --git a/src/source/sine.rs b/src/source/sine.rs index 58e37d07..0da6a210 100644 --- a/src/source/sine.rs +++ b/src/source/sine.rs @@ -40,7 +40,7 @@ impl Iterator for SineWave { impl Source for SineWave { #[inline] fn parameters_changed(&self) -> bool { - None + false } #[inline] diff --git a/src/source/skip.rs b/src/source/skip.rs index f052e8da..f45ae014 100644 --- a/src/source/skip.rs +++ b/src/source/skip.rs @@ -4,7 +4,7 @@ use super::SeekError; use crate::common::{ChannelCount, SampleRate}; use crate::Source; -const NS_PER_SECOND: u128 = 1_000_000_000; +const US_PER_SECOND: u64 = 1_000_000; /// Internal function that builds a `SkipDuration` object. pub fn skip_duration(mut input: I, duration: Duration) -> SkipDuration @@ -18,67 +18,6 @@ where } } -/// Skips specified `duration` of the given `input` source from it's current position. -fn do_skip_duration(input: &mut I, mut duration: Duration) -where - I: Source, -{ - while duration > Duration::new(0, 0) { - if input.parameters_changed().is_none() { - // Sample rate and the amount of channels will be the same till the end. - do_skip_duration_unchecked(input, duration); - return; - } - - // .unwrap() safety: if `parameters_changed()` is None, the body of the `if` statement - // above returns before we get here. - let span_len: usize = input.parameters_changed().unwrap(); - // If span_len is zero, then there is no more data to skip. Instead - // just bail out. - if span_len == 0 { - return; - } - - let ns_per_sample: u128 = - NS_PER_SECOND / input.sample_rate() as u128 / input.channels() as u128; - - // Check if we need to skip only part of the current span. - if span_len as u128 * ns_per_sample > duration.as_nanos() { - skip_samples(input, (duration.as_nanos() / ns_per_sample) as usize); - return; - } - - skip_samples(input, span_len); - - duration -= Duration::from_nanos((span_len * ns_per_sample as usize) as u64); - } -} - -/// Skips specified `duration` from the `input` source assuming that sample rate -/// and amount of channels are not changing. -fn do_skip_duration_unchecked(input: &mut I, duration: Duration) -where - I: Source, -{ - let samples_per_channel: u128 = - duration.as_nanos() * input.sample_rate() as u128 / NS_PER_SECOND; - let samples_to_skip: u128 = samples_per_channel * input.channels() as u128; - - skip_samples(input, samples_to_skip as usize); -} - -/// Skips `n` samples from the given `input` source. -fn skip_samples(input: &mut I, n: usize) -where - I: Source, -{ - for _ in 0..n { - if input.next().is_none() { - break; - } - } -} - /// A source that skips specified duration of the given source from it's current position. #[derive(Clone, Debug)] pub struct SkipDuration { @@ -159,95 +98,27 @@ where } } -#[cfg(test)] -mod tests { - use std::time::Duration; - - use crate::buffer::SamplesBuffer; - use crate::common::{ChannelCount, SampleRate}; - use crate::source::Source; - - fn test_skip_duration_samples_left( - channels: ChannelCount, - sample_rate: SampleRate, - seconds: u32, - seconds_to_skip: u32, - ) { - 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); - - let samples_left_expected = (sample_rate * channels as u32 * seconds_left) as usize; - let samples_left = test_buffer - .skip_duration(Duration::from_secs(seconds_to_skip as u64)) - .count(); - - assert_eq!(samples_left, samples_left_expected); - } - - macro_rules! skip_duration_test_block { - ($(channels: $ch:expr, sample rate: $sr:expr, seconds: $sec:expr, seconds to skip: $sec_to_skip:expr;)+) => { - $( - test_skip_duration_samples_left($ch, $sr, $sec, $sec_to_skip); - )+ - } - } - - #[test] - fn skip_duration_shorter_than_source() { - skip_duration_test_block! { - channels: 1, sample rate: 44100, seconds: 5, seconds to skip: 3; - channels: 1, sample rate: 96000, seconds: 5, seconds to skip: 3; - - channels: 2, sample rate: 44100, seconds: 5, seconds to skip: 3; - channels: 2, sample rate: 96000, seconds: 5, seconds to skip: 3; - - channels: 4, sample rate: 44100, seconds: 5, seconds to skip: 3; - channels: 4, sample rate: 96000, seconds: 5, seconds to skip: 3; - } - } - - #[test] - fn skip_duration_zero_duration() { - skip_duration_test_block! { - channels: 1, sample rate: 44100, seconds: 5, seconds to skip: 0; - channels: 1, sample rate: 96000, seconds: 5, seconds to skip: 0; - - channels: 2, sample rate: 44100, seconds: 5, seconds to skip: 0; - channels: 2, sample rate: 96000, seconds: 5, seconds to skip: 0; - - channels: 4, sample rate: 44100, seconds: 5, seconds to skip: 0; - channels: 4, sample rate: 96000, seconds: 5, seconds to skip: 0; - } - } - - #[test] - fn skip_duration_longer_than_source() { - skip_duration_test_block! { - channels: 1, sample rate: 44100, seconds: 1, seconds to skip: 5; - channels: 1, sample rate: 96000, seconds: 10, seconds to skip: 11; - - channels: 2, sample rate: 44100, seconds: 1, seconds to skip: 5; - channels: 2, sample rate: 96000, seconds: 10, seconds to skip: 11; - - channels: 4, sample rate: 44100, seconds: 1, seconds to skip: 5; - channels: 4, sample rate: 96000, seconds: 10, seconds to skip: 11; +/// Skips specified `duration` of the given `input` source from it's current position. +fn do_skip_duration(input: &mut I, mut duration: Duration) +where + I: Source, +{ + while !duration.is_zero() { + let us_per_sample: u64 = + US_PER_SECOND / input.sample_rate() as u64 / input.channels() as u64; + let mut samples_to_skip = duration.as_micros() as u64 / us_per_sample; + + while samples_to_skip > 0 && !input.parameters_changed() { + samples_to_skip -= 1; + if input.next().is_none() { + return; + } } - } - #[test] - fn skip_duration_equal_to_source_length() { - skip_duration_test_block! { - channels: 1, sample rate: 44100, seconds: 1, seconds to skip: 1; - channels: 1, sample rate: 96000, seconds: 10, seconds to skip: 10; - - channels: 2, sample rate: 44100, seconds: 1, seconds to skip: 1; - channels: 2, sample rate: 96000, seconds: 10, seconds to skip: 10; - - channels: 4, sample rate: 44100, seconds: 1, seconds to skip: 1; - channels: 4, sample rate: 96000, seconds: 10, seconds to skip: 10; + if samples_to_skip == 0 { + return; + } else { + duration -= Duration::from_micros(samples_to_skip * us_per_sample); } } } diff --git a/src/source/square.rs b/src/source/square.rs index 3aea0578..df6e63bf 100644 --- a/src/source/square.rs +++ b/src/source/square.rs @@ -40,7 +40,7 @@ impl Iterator for SquareWave { impl Source for SquareWave { #[inline] fn parameters_changed(&self) -> bool { - None + false } #[inline] diff --git a/src/source/take.rs b/src/source/take_duration.rs similarity index 75% rename from src/source/take.rs rename to src/source/take_duration.rs index 23d91afe..e4c19a44 100644 --- a/src/source/take.rs +++ b/src/source/take_duration.rs @@ -10,7 +10,6 @@ where I: Source, { TakeDuration { - current_span_len: input.parameters_changed(), duration_per_sample: TakeDuration::get_duration_per_sample(&input), input, remaining_duration: duration, @@ -45,9 +44,7 @@ pub struct TakeDuration { remaining_duration: Duration, requested_duration: Duration, filter: Option, - // Remaining samples in current span. - current_span_len: Option, - // Only updated when the current span len is exhausted. + // Only updated when the current span is exhausted. duration_per_sample: Duration, } @@ -81,7 +78,7 @@ where self.input } - /// Make the truncated source end with a FadeOut. The fadeout covers the + /// Make the truncated source end with a FadeOut. The fade-out covers the /// entire length of the take source. pub fn set_filter_fadeout(&mut self) { self.filter = Some(DurationFilter::FadeOut); @@ -99,15 +96,10 @@ where { type Item = ::Item; + // TODO guarantee end on frame boundary fn next(&mut self) -> Option<::Item> { - if let Some(span_len) = self.current_span_len.take() { - if span_len > 0 { - self.current_span_len = Some(span_len - 1); - } else { - self.current_span_len = self.input.parameters_changed(); - // Sample rate might have changed - self.duration_per_sample = Self::get_duration_per_sample(&self.input); - } + if self.input.parameters_changed() { + self.duration_per_sample = Self::get_duration_per_sample(&self.input); } if self.remaining_duration <= self.duration_per_sample { @@ -119,32 +111,22 @@ where }; self.remaining_duration -= self.duration_per_sample; - Some(sample) } else { None } } - - // TODO: size_hint } +// TODO: size_hint + impl Source for TakeDuration where I: Iterator + Source, { #[inline] fn parameters_changed(&self) -> bool { - let remaining_nanos = self.remaining_duration.as_secs() * NANOS_PER_SEC - + self.remaining_duration.subsec_nanos() as u64; - let nanos_per_sample = self.duration_per_sample.as_secs() * NANOS_PER_SEC - + self.duration_per_sample.subsec_nanos() as u64; - let remaining_samples = (remaining_nanos / nanos_per_sample) as usize; - - self.input - .parameters_changed() - .filter(|value| *value < remaining_samples) - .or(Some(remaining_samples)) + self.input.parameters_changed() } #[inline] diff --git a/src/source/take_samples.rs b/src/source/take_samples.rs new file mode 100644 index 00000000..2cec4af2 --- /dev/null +++ b/src/source/take_samples.rs @@ -0,0 +1,96 @@ +use std::time::Duration; + +use super::SeekError; +use crate::common::{ChannelCount, SampleRate}; +use crate::Source; + +/// A source that truncates the given source to a certain duration. +#[derive(Clone, Debug)] +pub struct TakeSamples { + input: I, + taken: usize, + target: usize, +} + +impl TakeSamples +where + I: Source, +{ + pub fn new(input: I, n: usize) -> Self { + Self { + input, + taken: 0, + target: n, + } + } + + /// Returns a mutable reference to the inner source. + #[inline] + pub fn inner_mut(&mut self) -> &mut I { + &mut self.input + } + + /// Returns the inner source. + #[inline] + pub fn into_inner(self) -> I { + self.input + } +} + +impl Iterator for TakeSamples +where + I: Source, +{ + type Item = ::Item; + + fn next(&mut self) -> Option<::Item> { + if self.taken >= self.target.prev_multiple_of(self.input.channels()) { + None + } else { + self.taken += 1; + self.input.next() + } + } +} + +// TODO: size_hint + +impl Source for TakeSamples +where + I: Iterator + Source, +{ + #[inline] + fn parameters_changed(&self) -> bool { + self.input.parameters_changed() + } + + #[inline] + fn channels(&self) -> ChannelCount { + self.input.channels() + } + + #[inline] + fn sample_rate(&self) -> SampleRate { + self.input.sample_rate() + } + + #[inline] + fn total_duration(&self) -> Option { + self.input.total_duration() + } + + #[inline] + fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> { + self.input.try_seek(pos) + } +} + +trait PrevMultipleOf { + fn prev_multiple_of(self, n: u16) -> usize; +} + +impl PrevMultipleOf for usize { + fn prev_multiple_of(self, n: u16) -> usize { + self.next_multiple_of(n as usize).saturating_sub(n as usize) + } +} diff --git a/src/source/take_span.rs b/src/source/take_span.rs new file mode 100644 index 00000000..d9a3320f --- /dev/null +++ b/src/source/take_span.rs @@ -0,0 +1,80 @@ +use std::time::Duration; + +use super::SeekError; +use crate::common::{ChannelCount, SampleRate}; +use crate::Source; + +/// A source that truncates the given source to a certain duration. +#[derive(Clone, Debug)] +pub struct TakeSpan { + input: I, +} + +impl TakeSpan +where + I: Source, +{ + pub fn new(input: I) -> Self { + Self { input } + } + + /// Returns a mutable reference to the inner source. + #[inline] + pub fn inner_mut(&mut self) -> &mut I { + &mut self.input + } + + /// Returns the inner source. + #[inline] + pub fn into_inner(self) -> I { + self.input + } +} + +impl Iterator for TakeSpan +where + I: Source, +{ + type Item = ::Item; + + fn next(&mut self) -> Option<::Item> { + if self.input.parameters_changed() { + None + } else { + let sample = self.input.next()?; + Some(sample) + } + } +} + +// TODO: size_hint + +impl Source for TakeSpan +where + I: Iterator + Source, +{ + #[inline] + fn parameters_changed(&self) -> bool { + false + } + + #[inline] + fn channels(&self) -> ChannelCount { + self.input.channels() + } + + #[inline] + fn sample_rate(&self) -> SampleRate { + self.input.sample_rate() + } + + #[inline] + fn total_duration(&self) -> Option { + self.input.total_duration() + } + + #[inline] + fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> { + self.input.try_seek(pos) + } +} diff --git a/src/source/triangle.rs b/src/source/triangle.rs index af660dc7..3e20e7e3 100644 --- a/src/source/triangle.rs +++ b/src/source/triangle.rs @@ -40,7 +40,7 @@ impl Iterator for TriangleWave { impl Source for TriangleWave { #[inline] fn parameters_changed(&self) -> bool { - None + false } #[inline] diff --git a/src/source/uniform.rs b/src/source/uniform.rs index 66f1f682..edd7b68f 100644 --- a/src/source/uniform.rs +++ b/src/source/uniform.rs @@ -1,11 +1,14 @@ -use std::cmp; use std::time::Duration; +use super::take_samples::TakeSamples; +use super::take_span::TakeSpan; use super::SeekError; use crate::common::{ChannelCount, SampleRate}; use crate::conversions::{ChannelCountConverter, SampleRateConverter}; use crate::Source; +type Converted = ChannelCountConverter>>>; + /// An iterator that reads from a `Source` and converts the samples to a /// specific type, sample-rate and channels count. /// @@ -16,7 +19,7 @@ pub struct UniformSourceIterator where I: Source, { - inner: Option>>>, + inner: Option>, target_channels: ChannelCount, target_sample_rate: SampleRate, total_duration: Option, @@ -50,17 +53,11 @@ where input: I, target_channels: ChannelCount, target_sample_rate: SampleRate, - ) -> ChannelCountConverter>> { - // Limit the span length to something reasonable - let span_len = input.parameters_changed().map(|x| x.min(32768)); - + ) -> Converted { let from_channels = input.channels(); let from_sample_rate = input.sample_rate(); - let input = Take { - iter: input, - n: span_len, - }; + let input = input.take_span().take_samples(32768); let input = SampleRateConverter::new(input, from_sample_rate, target_sample_rate, from_channels); ChannelCountConverter::new(input, from_channels, target_channels) @@ -79,7 +76,14 @@ where return Some(value); } - let input = self.inner.take().unwrap().into_inner().into_inner().iter; + let input = self + .inner + .take() + .unwrap() + .into_inner() + .into_inner() + .into_inner() + .into_inner(); let mut input = UniformSourceIterator::bootstrap(input, self.target_channels, self.target_sample_rate); @@ -101,7 +105,7 @@ where { #[inline] fn parameters_changed(&self) -> bool { - None + false } #[inline] @@ -128,57 +132,3 @@ where } } } - -#[derive(Clone, Debug)] -struct Take { - iter: I, - n: Option, -} - -impl Take { - #[inline] - pub fn inner_mut(&mut self) -> &mut I { - &mut self.iter - } -} - -impl Iterator for Take -where - I: Iterator, -{ - type Item = ::Item; - - #[inline] - fn next(&mut self) -> Option<::Item> { - if let Some(n) = &mut self.n { - if *n != 0 { - *n -= 1; - self.iter.next() - } else { - None - } - } else { - self.iter.next() - } - } - - #[inline] - fn size_hint(&self) -> (usize, Option) { - if let Some(n) = self.n { - let (lower, upper) = self.iter.size_hint(); - - let lower = cmp::min(lower, n); - - let upper = match upper { - Some(x) if x < n => Some(x), - _ => Some(n), - }; - - (lower, upper) - } else { - self.iter.size_hint() - } - } -} - -impl ExactSizeIterator for Take where I: ExactSizeIterator {} diff --git a/src/source/zero.rs b/src/source/zero.rs index 404aa20a..71e3a405 100644 --- a/src/source/zero.rs +++ b/src/source/zero.rs @@ -60,7 +60,7 @@ impl Iterator for Zero { impl Source for Zero { #[inline] fn parameters_changed(&self) -> bool { - self.num_samples + false } #[inline] diff --git a/src/static_buffer.rs b/src/static_buffer.rs index 9226d426..92a7a93a 100644 --- a/src/static_buffer.rs +++ b/src/static_buffer.rs @@ -64,7 +64,7 @@ impl StaticSamplesBuffer { impl Source for StaticSamplesBuffer { #[inline] fn parameters_changed(&self) -> bool { - None + false } #[inline] From 0e40dbf53efa476e8279de2df61419afdc82b9be Mon Sep 17 00:00:00 2001 From: dvdsk Date: Mon, 24 Feb 2025 21:26:15 +0100 Subject: [PATCH 03/25] (re)add tests for skip_duration, fixes skip duration also adds a new mod test support with a special test source that can be used to test span boundary/parameters_changed handling. adds two extra tests for `skip_duration` testing - parameters_changing during skip - ending on frame boundary --- src/math.rs | 24 ++++ src/source/from_iter.rs | 2 +- src/source/mod.rs | 6 +- src/source/{skip.rs => skip_duration.rs} | 74 ++++++---- src/source/take_samples.rs | 11 +- tests/skip_duration.rs | 85 ++++++++++++ tests/test_support/mod.rs | 163 +++++++++++++++++++++++ 7 files changed, 324 insertions(+), 41 deletions(-) rename src/source/{skip.rs => skip_duration.rs} (53%) create mode 100644 tests/skip_duration.rs create mode 100644 tests/test_support/mod.rs diff --git a/src/math.rs b/src/math.rs index 2f020748..e759247a 100644 --- a/src/math.rs +++ b/src/math.rs @@ -10,6 +10,30 @@ pub fn lerp(first: &f32, second: &f32, numerator: u32, denominator: u32) -> f32 first + (second - first) * numerator as f32 / denominator as f32 } +/// will hopefully get stabilized, this is slightly different to the future +/// std's version since it does some casting already. When the std's version gets +/// stable remove this trait. +pub(crate) trait PrevMultipleOf { + fn prev_multiple_of(self, n: u16) -> Self; +} + +macro_rules! impl_prev_multiple_of { + ($type:ty) => { + impl PrevMultipleOf for $type { + fn prev_multiple_of(self, n: u16) -> $type { + if self.next_multiple_of(n as $type) > self { + self.next_multiple_of(n as $type) - n as $type + } else { + self.next_multiple_of(n as $type) + } + } + } + }; +} + +impl_prev_multiple_of! {usize} +impl_prev_multiple_of! {u64} + #[cfg(test)] mod test { use super::*; diff --git a/src/source/from_iter.rs b/src/source/from_iter.rs index bca67444..049710c3 100644 --- a/src/source/from_iter.rs +++ b/src/source/from_iter.rs @@ -87,7 +87,7 @@ where // If the `size_hint` is `None` as well, we are in the worst case scenario. To handle this // situation we force a span to have a maximum number of samples indicate by this // constant. - + todo!() // const THRESHOLD: usize = 10240; // diff --git a/src/source/mod.rs b/src/source/mod.rs index 990d422a..0b1107e9 100644 --- a/src/source/mod.rs +++ b/src/source/mod.rs @@ -33,7 +33,7 @@ pub use self::repeat::Repeat; pub use self::sawtooth::SawtoothWave; pub use self::signal_generator::{Function, SignalGenerator}; pub use self::sine::SineWave; -pub use self::skip::SkipDuration; +pub use self::skip_duration::SkipDuration; pub use self::skippable::Skippable; pub use self::spatial::Spatial; pub use self::speed::Speed; @@ -69,7 +69,7 @@ mod repeat; mod sawtooth; mod signal_generator; mod sine; -mod skip; +mod skip_duration; mod skippable; mod spatial; mod speed; @@ -252,7 +252,7 @@ pub trait Source: Iterator { where Self: Sized, { - skip::skip_duration(self, duration) + SkipDuration::new(self, duration) } /// Amplifies the sound by the given value. diff --git a/src/source/skip.rs b/src/source/skip_duration.rs similarity index 53% rename from src/source/skip.rs rename to src/source/skip_duration.rs index f45ae014..2e10af7a 100644 --- a/src/source/skip.rs +++ b/src/source/skip_duration.rs @@ -2,22 +2,9 @@ use std::time::Duration; use super::SeekError; use crate::common::{ChannelCount, SampleRate}; +use crate::math::PrevMultipleOf; use crate::Source; -const US_PER_SECOND: u64 = 1_000_000; - -/// Internal function that builds a `SkipDuration` object. -pub fn skip_duration(mut input: I, duration: Duration) -> SkipDuration -where - I: Source, -{ - do_skip_duration(&mut input, duration); - SkipDuration { - input, - skipped_duration: duration, - } -} - /// A source that skips specified duration of the given source from it's current position. #[derive(Clone, Debug)] pub struct SkipDuration { @@ -25,6 +12,19 @@ pub struct SkipDuration { skipped_duration: Duration, } +impl SkipDuration { + pub(crate) fn new(mut input: I, duration: Duration) -> SkipDuration + where + I: Source, + { + do_skip_duration(&mut input, duration); + Self { + input, + skipped_duration: duration, + } + } +} + impl SkipDuration where I: Source, @@ -99,26 +99,46 @@ where } /// Skips specified `duration` of the given `input` source from it's current position. -fn do_skip_duration(input: &mut I, mut duration: Duration) +/// +/// # Panics +/// If trying to skip more than 584 days ahead. If you need that functionality +/// please open an issue I would love to know why (and help out). +fn do_skip_duration(input: &mut I, duration: Duration) where I: Source, { - while !duration.is_zero() { - let us_per_sample: u64 = - US_PER_SECOND / input.sample_rate() as u64 / input.channels() as u64; - let mut samples_to_skip = duration.as_micros() as u64 / us_per_sample; - - while samples_to_skip > 0 && !input.parameters_changed() { - samples_to_skip -= 1; + const NS_PER_SECOND: u64 = 1_000_000_000; + + // `u64::MAX` can store 584 days of nanosecond precision time. To not be off by + // a single sample (that would be regression) we first multiply the time by + // `samples_per second`. Which for a 96kHz 10 channel audio stream is + // 960_000 samples. That would only leave 0.87 hour of potential skip time. Hence + // we use an `u128` to calculate samples to skip. + let mut duration: u64 = duration + .as_nanos() + .try_into() + .expect("can not skip more then 584 days of audio"); + let mut ns_per_frame: u64 = 0; + + while duration > ns_per_frame { + ns_per_frame = NS_PER_SECOND / input.sample_rate() as u64; + + let samples_per_second = input.sample_rate() as u64 * input.channels() as u64; + let samples_to_skip = + (duration as u128 * samples_per_second as u128 / NS_PER_SECOND as u128) as u64; + let samples_to_skip = samples_to_skip.prev_multiple_of(input.channels()); + + let mut skipped = 0; + while skipped < samples_to_skip { if input.next().is_none() { return; } + skipped += 1; + if input.parameters_changed() { + break; + } } - if samples_to_skip == 0 { - return; - } else { - duration -= Duration::from_micros(samples_to_skip * us_per_sample); - } + duration -= skipped * NS_PER_SECOND / samples_per_second; } } diff --git a/src/source/take_samples.rs b/src/source/take_samples.rs index 2cec4af2..48f819e9 100644 --- a/src/source/take_samples.rs +++ b/src/source/take_samples.rs @@ -2,6 +2,7 @@ use std::time::Duration; use super::SeekError; use crate::common::{ChannelCount, SampleRate}; +use crate::math::PrevMultipleOf; use crate::Source; /// A source that truncates the given source to a certain duration. @@ -84,13 +85,3 @@ where self.input.try_seek(pos) } } - -trait PrevMultipleOf { - fn prev_multiple_of(self, n: u16) -> usize; -} - -impl PrevMultipleOf for usize { - fn prev_multiple_of(self, n: u16) -> usize { - self.next_multiple_of(n as usize).saturating_sub(n as usize) - } -} diff --git a/tests/skip_duration.rs b/tests/skip_duration.rs new file mode 100644 index 00000000..95818707 --- /dev/null +++ b/tests/skip_duration.rs @@ -0,0 +1,85 @@ +use std::time::Duration; + +use rodio::buffer::SamplesBuffer; +use rodio::source::Source; +use rodio::{ChannelCount, SampleRate}; +use rstest::rstest; + +mod test_support; +use test_support::{TestSource, TestSpan}; + +#[rstest] +fn ends_on_frame_boundary(#[values(1.483, 2.999)] seconds_to_skip: f32) { + let source = TestSource::new().with_span( + TestSpan::silence(30) + .with_channel_count(10) + .with_sample_rate(1), + ); + let leftover = source + .clone() + .skip_duration(Duration::from_secs_f32(seconds_to_skip)) + .count(); + assert!(leftover % source.channels() as usize == 0) +} + +#[rstest] +fn param_changes_during_skip(#[values(6, 11)] seconds_to_skip: u64) { + let source = TestSource::new() // each span is 5 seconds + .with_span( + TestSpan::silence(5 * 10 * 1) + .with_sample_rate(10) + .with_channel_count(1), + ) + .with_span( + TestSpan::silence(5 * 20 * 2) + .with_sample_rate(20) + .with_channel_count(2), + ) + .with_span( + TestSpan::silence(5 * 5 * 3) + .with_sample_rate(5) + .with_channel_count(3), + ); + + let leftover = source + .clone() + .skip_duration(Duration::from_secs(seconds_to_skip)) + .count(); + + let spans = source.spans; + let expected_leftover = match seconds_to_skip { + 6 => 4 * spans[1].sample_rate as usize * spans[1].channels as usize + spans[2].len(), + 11 => 4 * spans[2].sample_rate as usize * spans[2].channels as usize, + _ => unreachable!(), + }; + + assert_eq!(leftover, expected_leftover); +} + +#[rstest] +fn samples_left( + #[values(1, 2, 4)] channels: ChannelCount, + #[values(100_000)] sample_rate: SampleRate, + #[values(0, 5)] seconds: u32, + #[values(0, 3, 5)] seconds_to_skip: u32, +) { + println!( + "channels: {channels}, sample_rate: {sample_rate}, \ + seconds: {seconds}, seconds_to_skip: {seconds_to_skip}" + ); + 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); + + let samples_left_expected = (sample_rate * channels as u32 * seconds_left) as usize; + let samples_left = test_buffer + .skip_duration(Duration::from_secs(seconds_to_skip as u64)) + .count(); + + assert!( + samples_left == samples_left_expected, + "expected {samples_left_expected} samples left, counted: {samples_left}" + ); +} diff --git a/tests/test_support/mod.rs b/tests/test_support/mod.rs new file mode 100644 index 00000000..de3a9da0 --- /dev/null +++ b/tests/test_support/mod.rs @@ -0,0 +1,163 @@ +// #![allow(dead_code)] +/// in separate folder so its not ran as integration test +/// should probably be moved to its own crate (rodio-test-support) +/// that would fix the unused code warnings. +use std::time::Duration; + +use rodio::{ChannelCount, SampleRate, Source}; + +#[derive(Debug, Clone)] +pub struct TestSpan { + pub data: Vec, + pub sample_rate: SampleRate, + pub channels: ChannelCount, +} + +impl TestSpan { + pub fn silence(numb_samples: usize) -> Self { + Self { + data: vec![0f32; numb_samples], + sample_rate: 1, + channels: 1, + } + } + pub fn from_samples<'a>(samples: impl IntoIterator) -> Self { + let samples = samples.into_iter().copied().collect::>(); + Self { + data: samples, + sample_rate: 1, + channels: 1, + } + } + pub fn with_sample_rate(mut self, sample_rate: SampleRate) -> Self { + self.sample_rate = sample_rate; + self + } + pub fn with_channel_count(mut self, channel_count: ChannelCount) -> Self { + self.channels = channel_count; + self + } + pub fn len(&self) -> usize { + self.data.len() + } +} + +#[derive(Debug, Clone)] +pub struct TestSource { + pub spans: Vec, + current_span: usize, + pos_in_span: usize, + total_duration: Option, + parameters_changed: bool, +} + +impl TestSource { + pub fn new() -> Self { + Self { + spans: Vec::new(), + current_span: 0, + pos_in_span: 0, + total_duration: None, + parameters_changed: false, + } + } + pub fn with_span(mut self, span: TestSpan) -> Self { + self.spans.push(span); + self + } + pub 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 current_span = self.spans.get_mut(self.current_span)?; + let sample = current_span.data.get(self.pos_in_span).copied()?; + self.pos_in_span += 1; + + // if span is out of samples + // - next set parameters_changed now + // - switch to the next span + if self.pos_in_span == current_span.data.len() { + self.pos_in_span = 0; + self.current_span += 1; + self.parameters_changed = true; + } else { + self.parameters_changed = false; + } + + Some(sample) + } +} + +impl rodio::Source for TestSource { + fn parameters_changed(&self) -> bool { + self.parameters_changed + } + fn channels(&self) -> rodio::ChannelCount { + self.spans + .get(self.current_span) + .map(|span| span.channels) + .unwrap_or_default() + } + fn sample_rate(&self) -> rodio::SampleRate { + self.spans + .get(self.current_span) + .map(|span| span.sample_rate) + .unwrap_or_default() + } + fn total_duration(&self) -> Option { + self.total_duration + } + fn try_seek(&mut self, _pos: Duration) -> Result<(), rodio::source::SeekError> { + todo!(); + // 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 for your tests of course. Leave these in, they guard regression when we +// expand the functionally which we probably will. +#[test] +fn parameters_change_correct() { + let mut source = TestSource::new() + .with_span(TestSpan::silence(10)) + .with_span(TestSpan::silence(10)); + + assert_eq!(source.by_ref().take(10).count(), 10); + assert!(source.parameters_changed); + + assert!(source.next().is_some()); + assert!(!source.parameters_changed); + + assert_eq!(source.count(), 9); +} + +#[test] +fn channel_count_changes() { + let mut source = TestSource::new() + .with_span(TestSpan::silence(10).with_channel_count(1)) + .with_span(TestSpan::silence(10).with_channel_count(2)); + + assert_eq!(source.channels(), 1); + assert_eq!(source.by_ref().take(10).count(), 10); + assert_eq!(source.channels(), 2); +} + +#[test] +fn sample_rate_changes() { + let mut source = TestSource::new() + .with_span(TestSpan::silence(10).with_sample_rate(10)) + .with_span(TestSpan::silence(10).with_sample_rate(20)); + + assert_eq!(source.sample_rate(), 10); + assert_eq!(source.by_ref().take(10).count(), 10); + assert_eq!(source.sample_rate(), 20); +} From 5fc542da25816cddd9c71785ccbe1167775e6677 Mon Sep 17 00:00:00 2001 From: dvdsk Date: Tue, 25 Feb 2025 15:45:46 +0100 Subject: [PATCH 04/25] rewrite and test take_duration similar to skip_duration --- src/math.rs | 2 + src/source/mod.rs | 2 +- src/source/skip_duration.rs | 4 +- src/source/take_duration.rs | 111 +++++++++++++++++------------------- tests/take_duration.rs | 92 ++++++++++++++++++++++++++++++ tests/test_support/mod.rs | 2 +- 6 files changed, 150 insertions(+), 63 deletions(-) create mode 100644 tests/take_duration.rs diff --git a/src/math.rs b/src/math.rs index e759247a..198a4e9a 100644 --- a/src/math.rs +++ b/src/math.rs @@ -10,6 +10,8 @@ pub fn lerp(first: &f32, second: &f32, numerator: u32, denominator: u32) -> f32 first + (second - first) * numerator as f32 / denominator as f32 } +pub(crate) const NS_PER_SECOND: u64 = 1_000_000_000; + /// will hopefully get stabilized, this is slightly different to the future /// std's version since it does some casting already. When the std's version gets /// stable remove this trait. diff --git a/src/source/mod.rs b/src/source/mod.rs index 0b1107e9..6ccc468c 100644 --- a/src/source/mod.rs +++ b/src/source/mod.rs @@ -209,7 +209,7 @@ pub trait Source: Iterator { where Self: Sized, { - take_duration::take_duration(self, duration) + TakeDuration::new(self, duration) } /// Takes a samples until the current span ends, which is when any of diff --git a/src/source/skip_duration.rs b/src/source/skip_duration.rs index 2e10af7a..97253a1a 100644 --- a/src/source/skip_duration.rs +++ b/src/source/skip_duration.rs @@ -2,7 +2,7 @@ use std::time::Duration; use super::SeekError; use crate::common::{ChannelCount, SampleRate}; -use crate::math::PrevMultipleOf; +use crate::math::{PrevMultipleOf, NS_PER_SECOND}; use crate::Source; /// A source that skips specified duration of the given source from it's current position. @@ -107,8 +107,6 @@ fn do_skip_duration(input: &mut I, duration: Duration) where I: Source, { - const NS_PER_SECOND: u64 = 1_000_000_000; - // `u64::MAX` can store 584 days of nanosecond precision time. To not be off by // a single sample (that would be regression) we first multiply the time by // `samples_per second`. Which for a 96kHz 10 channel audio stream is diff --git a/src/source/take_duration.rs b/src/source/take_duration.rs index e4c19a44..cc73b4f7 100644 --- a/src/source/take_duration.rs +++ b/src/source/take_duration.rs @@ -2,62 +2,48 @@ use std::time::Duration; use super::SeekError; use crate::common::{ChannelCount, SampleRate}; -use crate::{Sample, Source}; - -/// Internal function that builds a `TakeDuration` object. -pub fn take_duration(input: I, duration: Duration) -> TakeDuration -where - I: Source, -{ - TakeDuration { - duration_per_sample: TakeDuration::get_duration_per_sample(&input), - input, - remaining_duration: duration, - requested_duration: duration, - filter: None, - } -} - -/// A filter that can be applied to a `TakeDuration`. -#[derive(Clone, Debug)] -enum DurationFilter { - FadeOut, -} -impl DurationFilter { - fn apply(&self, sample: Sample, parent: &TakeDuration) -> Sample { - match self { - DurationFilter::FadeOut => { - let remaining = parent.remaining_duration.as_millis() as f32; - let total = parent.requested_duration.as_millis() as f32; - sample * remaining / total - } - } - } -} - -const NANOS_PER_SEC: u64 = 1_000_000_000; +use crate::math::{PrevMultipleOf, NS_PER_SECOND}; +use crate::Source; /// A source that truncates the given source to a certain duration. #[derive(Clone, Debug)] pub struct TakeDuration { input: I, - remaining_duration: Duration, requested_duration: Duration, - filter: Option, - // Only updated when the current span is exhausted. - duration_per_sample: Duration, + remaining_ns: u64, + samples_per_second: u64, + samples_to_take: u64, + samples_taken: u64, + fadeout: bool, } impl TakeDuration where I: Source, { - /// Returns the duration elapsed for each sample extracted. - #[inline] - fn get_duration_per_sample(input: &I) -> Duration { - let ns = NANOS_PER_SEC / (input.sample_rate() as u64 * input.channels() as u64); - // \|/ the maximum value of `ns` is one billion, so this can't fail - Duration::new(0, ns as u32) + pub(crate) fn new(input: I, duration: Duration) -> TakeDuration + where + I: Source, + { + let remaining_ns: u64 = duration + .as_nanos() + .try_into() + .expect("can not take more then 584 days of audio"); + + let samples_per_second = input.sample_rate() as u64 * input.channels() as u64; + let samples_to_take = + (remaining_ns as u128 * samples_per_second as u128 / NS_PER_SECOND as u128) as u64; + let samples_to_take = samples_to_take.prev_multiple_of(input.channels()); + + Self { + input, + remaining_ns, + fadeout: false, + samples_per_second, + samples_to_take, + samples_taken: 0, + requested_duration: duration, + } } /// Returns a reference to the inner source. @@ -81,12 +67,12 @@ where /// Make the truncated source end with a FadeOut. The fade-out covers the /// entire length of the take source. pub fn set_filter_fadeout(&mut self) { - self.filter = Some(DurationFilter::FadeOut); + self.fadeout = true; } /// Remove any filter set. pub fn clear_filter(&mut self) { - self.filter = None; + self.fadeout = false; } } @@ -96,24 +82,33 @@ where { type Item = ::Item; - // TODO guarantee end on frame boundary + // implementation is adapted of skip_duration fn next(&mut self) -> Option<::Item> { if self.input.parameters_changed() { - self.duration_per_sample = Self::get_duration_per_sample(&self.input); + self.remaining_ns -= self.samples_taken * NS_PER_SECOND / self.samples_per_second; + + self.samples_per_second = + self.input.sample_rate() as u64 * self.input.channels() as u64; + self.samples_to_take = (self.remaining_ns as u128 * self.samples_per_second as u128 + / NS_PER_SECOND as u128) as u64; + self.samples_to_take = self.samples_to_take.prev_multiple_of(self.input.channels()); + self.samples_taken = 0; } - if self.remaining_duration <= self.duration_per_sample { - None - } else if let Some(sample) = self.input.next() { - let sample = match &self.filter { - Some(filter) => filter.apply(sample, self), - None => sample, - }; + if self.samples_taken >= self.samples_to_take { + return None; + } - self.remaining_duration -= self.duration_per_sample; - Some(sample) + let Some(sample) = self.input.next() else { + return None; + }; + + self.samples_taken += 1; + if self.fadeout { + let total = self.requested_duration.as_nanos() as u64; + Some(sample * self.remaining_ns as f32 / total as f32) } else { - None + Some(sample) } } } diff --git a/tests/take_duration.rs b/tests/take_duration.rs new file mode 100644 index 00000000..99bd9a52 --- /dev/null +++ b/tests/take_duration.rs @@ -0,0 +1,92 @@ +use std::time::Duration; + +use rodio::buffer::SamplesBuffer; +use rodio::source::Source; +use rodio::{ChannelCount, SampleRate}; +use rstest::rstest; + +mod test_support; +use test_support::{TestSource, TestSpan}; + +#[rstest] +fn ends_on_frame_boundary(#[values(1.483, 2.999)] seconds_to_skip: f32) { + let source = TestSource::new().with_span( + TestSpan::silence(30) + .with_channel_count(10) + .with_sample_rate(1), + ); + let got = source + .clone() + .take_duration(Duration::from_secs_f32(seconds_to_skip)) + .count(); + assert!(got % source.channels() as usize == 0) +} + +#[rstest] +fn param_changes_during_skip(#[values(6, 11)] seconds_to_skip: u64) { + let source = TestSource::new() // each span is 5 seconds + .with_span( + TestSpan::silence(5 * 10 * 1) + .with_sample_rate(10) + .with_channel_count(1), + ) + .with_span( + TestSpan::silence(5 * 20 * 2) + .with_sample_rate(20) + .with_channel_count(2), + ) + .with_span( + TestSpan::silence(5 * 5 * 3) + .with_sample_rate(5) + .with_channel_count(3), + ); + + let took = source + .clone() + .take_duration(Duration::from_secs(seconds_to_skip)) + .count(); + + let spans = source.spans; + let expected = match seconds_to_skip { + 6 => spans[0].len() + 1 * spans[1].sample_rate as usize * spans[1].channels as usize, + 11 => { + spans[0].len() + + spans[1].len() + + 1 * spans[2].sample_rate as usize * spans[2].channels as usize + } + _ => unreachable!(), + }; + + assert!( + took == expected, + "expected {expected} samples, took only: {took}" + ); +} + +#[rstest] +fn samples_taken( + #[values(1, 2, 4)] channels: ChannelCount, + #[values(100_000)] sample_rate: SampleRate, + #[values(0, 5)] seconds: u32, + #[values(0, 3, 5)] seconds_to_take: u32, +) { + println!( + "channels: {channels}, sample_rate: {sample_rate}, \ + seconds: {seconds}, seconds_to_take: {seconds_to_take}" + ); + 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 samples = test_buffer + .take_duration(Duration::from_secs(seconds_to_take as u64)) + .count(); + + let seconds_we_can_take = seconds_to_take.min(seconds); + let samples_expected = (sample_rate * channels as u32 * seconds_we_can_take) as usize; + assert!( + samples == samples_expected, + "expected {samples_expected} samples, took only: {samples}" + ); +} diff --git a/tests/test_support/mod.rs b/tests/test_support/mod.rs index de3a9da0..2b1156df 100644 --- a/tests/test_support/mod.rs +++ b/tests/test_support/mod.rs @@ -1,4 +1,4 @@ -// #![allow(dead_code)] +#![allow(dead_code)] /// in separate folder so its not ran as integration test /// should probably be moved to its own crate (rodio-test-support) /// that would fix the unused code warnings. From 25cfb9e032be76bb912d1bec3cfbfddc57e5bf9a Mon Sep 17 00:00:00 2001 From: dvdsk Date: Tue, 25 Feb 2025 15:53:13 +0100 Subject: [PATCH 05/25] refactor take_duration --- src/source/take_duration.rs | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/src/source/take_duration.rs b/src/source/take_duration.rs index cc73b4f7..64a07af3 100644 --- a/src/source/take_duration.rs +++ b/src/source/take_duration.rs @@ -74,6 +74,21 @@ where pub fn clear_filter(&mut self) { self.fadeout = false; } + + fn next_samples_per_second(&mut self) -> u64 { + self.input.sample_rate() as u64 * self.input.channels() as u64 + } + + fn next_samples_to_take(&mut self) -> u64 { + let samples_to_take = (self.remaining_ns as u128 * self.samples_per_second as u128 + / NS_PER_SECOND as u128) as u64; + let samples_to_take = samples_to_take.prev_multiple_of(self.input.channels()); + samples_to_take + } + + fn duration_taken(&mut self) -> u64 { + self.samples_taken * NS_PER_SECOND / self.samples_per_second + } } impl Iterator for TakeDuration @@ -85,13 +100,9 @@ where // implementation is adapted of skip_duration fn next(&mut self) -> Option<::Item> { if self.input.parameters_changed() { - self.remaining_ns -= self.samples_taken * NS_PER_SECOND / self.samples_per_second; - - self.samples_per_second = - self.input.sample_rate() as u64 * self.input.channels() as u64; - self.samples_to_take = (self.remaining_ns as u128 * self.samples_per_second as u128 - / NS_PER_SECOND as u128) as u64; - self.samples_to_take = self.samples_to_take.prev_multiple_of(self.input.channels()); + self.remaining_ns -= self.duration_taken(); + self.samples_per_second = self.next_samples_per_second(); + self.samples_to_take = self.next_samples_to_take(); self.samples_taken = 0; } From e5f1b1a8775283d66732e4da46e97935f7e82630 Mon Sep 17 00:00:00 2001 From: dvdsk Date: Wed, 26 Feb 2025 11:07:50 +0100 Subject: [PATCH 06/25] Adds test to blt_filter. Notes removal of source factories in changelog The removal of factories (like `source::amplify(unamplified, 1.2)` -> `unamplified.amplify(1.2)`) was already planned and is tracked in issue 622. Its not done now. I am working through it as I add tests for all the non-trivial source's. --- CHANGELOG.md | 4 ++ Cargo.lock | 60 ++++++++++++++++++ Cargo.toml | 1 + src/source/blt.rs | 97 ++++++++++++---------------- src/source/mod.rs | 8 +-- src/source/signal_generator.rs | 2 +- tests/blt_filter.rs | 112 +++++++++++++++++++++++++++++++++ tests/test_support/mod.rs | 110 +++++++++++++++++++++++++++++--- 8 files changed, 323 insertions(+), 71 deletions(-) create mode 100644 tests/blt_filter.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 81f49f11..cda9b4bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Breaking: The term 'frame' was renamed to 'span' in the crate and documentation. - Breaking: Sources now use `f32` samples. To convert to and from other types of samples use functions from `dasp_sample` crate. For example `DaspSample::from_sample(sample)`. Remove `integer-decoder` feature. +- Breaking: Sources wrapping an existing source had a public factory method + which is now removed. Something like: `source::amplify(unamplified, 1.2)` must now be + written as `unamplified.amplify(1.2)`. +- `SignalGenerator`'s `Function` is now Copy. ### Fixed diff --git a/Cargo.lock b/Cargo.lock index 9f881afd..0fdf1d0b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -353,6 +353,15 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af9673d8203fcb076b19dfd17e38b3d4ae9f44959416ea532ce72415a6020365" +[[package]] +name = "float-cmp" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b09cf3155332e944990140d967ff5eceb70df778b34f77d8075db46e4704e6d8" +dependencies = [ + "num-traits", +] + [[package]] name = "futures" version = "0.3.31" @@ -582,6 +591,12 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "libm" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" + [[package]] name = "linux-raw-sys" version = "0.4.15" @@ -609,6 +624,17 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +[[package]] +name = "microfft" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2b6673eb0cc536241d6734c2ca45abfdbf90e9e7791c66a36a7ba3c315b76cf" +dependencies = [ + "cfg-if", + "num-complex", + "static_assertions", +] + [[package]] name = "minimal-lexical" version = "0.2.1" @@ -684,6 +710,15 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + [[package]] name = "num-derive" version = "0.4.2" @@ -783,6 +818,12 @@ version = "1.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + [[package]] name = "pin-project-lite" version = "0.2.16" @@ -969,6 +1010,7 @@ dependencies = [ "rand 0.9.0", "rstest", "rstest_reuse", + "spectrum-analyzer", "symphonia", "tracing", ] @@ -1089,6 +1131,24 @@ dependencies = [ "winapi", ] +[[package]] +name = "spectrum-analyzer" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d06eea3d02718333cc1e726db92b5af1bf92e66f857f81268ff1633acd8a2d3" +dependencies = [ + "float-cmp", + "libm", + "microfft", + "paste", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "symphonia" version = "0.5.4" diff --git a/Cargo.toml b/Cargo.toml index eed950d3..23a3048a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -66,6 +66,7 @@ rstest_reuse = "0.6.0" approx = "0.5.1" dasp_sample = "0.11.0" divan = "0.1.14" +spectrum-analyzer = "1.6.0" [[bench]] name = "effects" diff --git a/src/source/blt.rs b/src/source/blt.rs index 5330efb9..0860e2a4 100644 --- a/src/source/blt.rs +++ b/src/source/blt.rs @@ -6,67 +6,55 @@ use std::time::Duration; use super::SeekError; // Implemented following http://www.musicdsp.org/files/Audio-EQ-Cookbook.txt - -/// Internal function that builds a `BltFilter` object. -pub fn low_pass(input: I, freq: u32) -> BltFilter -where - I: Source, -{ - low_pass_with_q(input, freq, 0.5) -} - -pub fn high_pass(input: I, freq: u32) -> BltFilter -where - I: Source, -{ - high_pass_with_q(input, freq, 0.5) -} - -/// Same as low_pass but allows the q value (bandwidth) to be changed -pub fn low_pass_with_q(input: I, freq: u32, q: f32) -> BltFilter -where - I: Source, -{ - BltFilter { - input, - formula: BltFormula::LowPass { freq, q }, - applier: None, - x_n1: 0.0, - x_n2: 0.0, - y_n1: 0.0, - y_n2: 0.0, - } -} - -/// Same as high_pass but allows the q value (bandwidth) to be changed -pub fn high_pass_with_q(input: I, freq: u32, q: f32) -> BltFilter -where - I: Source, -{ - BltFilter { - input, - formula: BltFormula::HighPass { freq, q }, - applier: None, - x_n1: 0.0, - x_n2: 0.0, - y_n1: 0.0, - y_n2: 0.0, - } -} - /// This applies an audio filter, it can be a high or low pass filter. #[derive(Clone, Debug)] pub struct BltFilter { input: I, formula: BltFormula, - applier: Option, + applier: BltApplier, x_n1: f32, x_n2: f32, y_n1: f32, y_n2: f32, } -impl BltFilter { +impl> BltFilter { + pub(crate) fn low_pass(input: I, freq: u32) -> BltFilter { + Self::low_pass_with_q(input, freq, 0.5) + } + + pub(crate) fn high_pass(input: I, freq: u32) -> BltFilter { + Self::high_pass_with_q(input, freq, 0.5) + } + + pub(crate) fn low_pass_with_q(input: I, freq: u32, q: f32) -> BltFilter { + let formula = BltFormula::LowPass { freq, q }; + let applier = formula.to_applier(input.sample_rate()); + BltFilter { + input, + formula, + applier, + x_n1: 0.0, + x_n2: 0.0, + y_n1: 0.0, + y_n2: 0.0, + } + } + + pub(crate) fn high_pass_with_q(input: I, freq: u32, q: f32) -> BltFilter { + let formula = BltFormula::HighPass { freq, q }; + let applier = formula.to_applier(input.sample_rate()); + BltFilter { + input, + formula, + applier, + x_n1: 0.0, + x_n2: 0.0, + y_n1: 0.0, + y_n2: 0.0, + } + } + /// Modifies this filter so that it becomes a low-pass filter. pub fn to_low_pass(&mut self, freq: u32) { self.to_low_pass_with_q(freq, 0.5); @@ -80,13 +68,13 @@ impl BltFilter { /// Same as to_low_pass but allows the q value (bandwidth) to be changed pub fn to_low_pass_with_q(&mut self, freq: u32, q: f32) { self.formula = BltFormula::LowPass { freq, q }; - self.applier = None; + self.applier = self.formula.to_applier(self.input.sample_rate()); } /// Same as to_high_pass but allows the q value (bandwidth) to be changed pub fn to_high_pass_with_q(&mut self, freq: u32, q: f32) { self.formula = BltFormula::HighPass { freq, q }; - self.applier = None; + self.applier = self.formula.to_applier(self.input.sample_rate()); } /// Returns a reference to the inner source. @@ -117,15 +105,12 @@ where #[inline] fn next(&mut self) -> Option { if self.input.parameters_changed() { - self.applier = Some(self.formula.to_applier(self.input.sample_rate())); + self.applier = self.formula.to_applier(self.input.sample_rate()); } let sample = self.input.next()?; let result = self .applier - .as_ref() - .expect("just set above") // TODO we can do without the Option now we have - // `parameters_changed` .apply(sample, self.x_n1, self.x_n2, self.y_n1, self.y_n2); self.y_n2 = self.y_n1; diff --git a/src/source/mod.rs b/src/source/mod.rs index 6ccc468c..99a49ee6 100644 --- a/src/source/mod.rs +++ b/src/source/mod.rs @@ -527,7 +527,7 @@ pub trait Source: Iterator { Self: Sized, Self: Source, { - blt::low_pass(self, freq) + BltFilter::low_pass(self, freq) } /// Applies a high-pass filter to the source. @@ -537,7 +537,7 @@ pub trait Source: Iterator { Self: Sized, Self: Source, { - blt::high_pass(self, freq) + BltFilter::high_pass(self, freq) } /// Applies a low-pass filter to the source while allowing the q (bandwidth) to be changed. @@ -547,7 +547,7 @@ pub trait Source: Iterator { Self: Sized, Self: Source, { - blt::low_pass_with_q(self, freq, q) + BltFilter::low_pass_with_q(self, freq, q) } /// Applies a high-pass filter to the source while allowing the q (bandwidth) to be changed. @@ -557,7 +557,7 @@ pub trait Source: Iterator { Self: Sized, Self: Source, { - blt::high_pass_with_q(self, freq, q) + BltFilter::high_pass_with_q(self, freq, q) } // There is no `can_seek()` method as it is impossible to use correctly. Between diff --git a/src/source/signal_generator.rs b/src/source/signal_generator.rs index e9de2db0..4f686eef 100644 --- a/src/source/signal_generator.rs +++ b/src/source/signal_generator.rs @@ -36,7 +36,7 @@ use std::time::Duration; pub type GeneratorFunction = fn(f32) -> f32; /// Waveform functions. -#[derive(Clone, Debug)] +#[derive(Clone, Copy, Debug)] pub enum Function { /// A sinusoidal waveform. Sine, diff --git a/tests/blt_filter.rs b/tests/blt_filter.rs new file mode 100644 index 00000000..e4189aae --- /dev/null +++ b/tests/blt_filter.rs @@ -0,0 +1,112 @@ +mod test_support; +use rodio::{SampleRate, Source}; +use test_support::{TestSource, TestSpan}; + +use spectrum_analyzer::scaling::scale_to_zero_to_one; +use spectrum_analyzer::windows::hann_window; +use spectrum_analyzer::{samples_fft_to_spectrum, FrequencyLimit}; + +const SAMPLE_RATE: SampleRate = 44_100; + +#[test] +fn low_pass_attenuates() { + attenuate_test( + |s: TestSource| s.low_pass(200), + |ratio_before, ratio_after| { + assert!( + ratio_after < ratio_before / 10.0, + "expected ratio between frequencies above freq (400) and below to have \ + decreased by at least a factor 10 after the low pass filter.\ + \n\tratio before: {ratio_before},\n\tratio_after: {ratio_after}" + ) + }, + ); +} + +#[test] +fn high_pass_attenuates() { + attenuate_test( + |s: TestSource| s.high_pass(200), + |ratio_before, ratio_after| { + assert!( + ratio_after > ratio_before * 4.0, + "expected ratio between frequencies above freq (400) and below to have \ + increased by at least a factor 4 after the low pass filter.\ + \n\tratio before: {ratio_before},\n\tratio_after: {ratio_after}" + ) + }, + ); +} + +fn attenuate_test(filter: impl Fn(TestSource) -> S, assert: impl Fn(f32, f32)) { + let source = TestSource::new() + .with_span( + TestSpan::square(40.0) + .with_sample_rate(SAMPLE_RATE) + .with_channel_count(1) + .with_sample_count(2048), + ) + .with_span( + TestSpan::square(40.0) + .with_sample_rate(SAMPLE_RATE / 2) + .with_channel_count(1) + .with_sample_count(1024), + ); + + let span0_ratio_before = power_above_freq_vs_below( + source.clone().take(source.spans[0].len()), + 400.0, + source.spans[0].sample_rate, + ); + let span1_ratio_before = power_above_freq_vs_below( + source.clone().skip(source.spans[0].len()), + 400.0, + source.spans[1].sample_rate, + ); + + let filterd = filter(source.clone()); + + let span0_ratio_after = power_above_freq_vs_below( + filterd.clone().take(source.spans[0].len()), + 400.0, + source.spans[0].sample_rate, + ); + let span1_ratio_after = power_above_freq_vs_below( + filterd.skip(source.spans[0].len()), + 400.0, + source.spans[1].sample_rate, + ); + + assert(span0_ratio_before, span0_ratio_after); + assert(span1_ratio_before, span1_ratio_after); +} + +fn power_above_freq_vs_below( + source: impl Iterator, + split: f32, + sample_rate: SampleRate, +) -> f32 { + let samples: Vec = source.collect(); + let hann_window = hann_window(&samples); + let spectrum = samples_fft_to_spectrum( + &hann_window, + sample_rate, + FrequencyLimit::All, + Some(&scale_to_zero_to_one), + ) + .unwrap(); + + let data = spectrum.data(); + let below: f32 = data + .iter() + .take_while(|(freq, _)| freq.val() < split) + .map(|(_, val)| val.val()) + .sum(); + let above: f32 = data + .iter() + .skip_while(|(freq, _)| freq.val() < split) + .map(|(_, val)| val.val()) + .sum(); + + above / below +} diff --git a/tests/test_support/mod.rs b/tests/test_support/mod.rs index 2b1156df..5fd8affd 100644 --- a/tests/test_support/mod.rs +++ b/tests/test_support/mod.rs @@ -1,14 +1,58 @@ #![allow(dead_code)] +use std::iter; /// in separate folder so its not ran as integration test /// should probably be moved to its own crate (rodio-test-support) /// that would fix the unused code warnings. use std::time::Duration; -use rodio::{ChannelCount, SampleRate, Source}; +use rodio::source::{self, Function, SignalGenerator}; +use rodio::{ChannelCount, Sample, SampleRate, Source}; + +#[derive(Debug, Clone)] +pub enum SampleSource { + SignalGen { + function: Function, + samples: Vec, + frequency: f32, + numb_samples: usize, + }, + Silence { + numb_samples: usize, + }, + List(Vec), +} + +impl SampleSource { + fn get( + &mut self, + pos: usize, + sample_rate: SampleRate, + channels: ChannelCount, + ) -> Option { + match self { + SampleSource::SignalGen { + function, + samples, + frequency, + numb_samples, + } if samples.len() != *numb_samples => { + *samples = SignalGenerator::new(sample_rate, *frequency, function.clone()) + .take(*numb_samples) + .flat_map(|sample| iter::repeat_n(sample, channels.into())) + .collect(); + samples.get(pos).copied() + } + SampleSource::SignalGen { samples, .. } => samples.get(pos).copied(), + SampleSource::Silence { numb_samples } if pos < *numb_samples => Some(0.0), + SampleSource::Silence { .. } => None, + SampleSource::List(list) => list.get(pos).copied(), + } + } +} #[derive(Debug, Clone)] pub struct TestSpan { - pub data: Vec, + pub sample_source: SampleSource, pub sample_rate: SampleRate, pub channels: ChannelCount, } @@ -16,7 +60,31 @@ pub struct TestSpan { impl TestSpan { pub fn silence(numb_samples: usize) -> Self { Self { - data: vec![0f32; numb_samples], + sample_source: SampleSource::Silence { numb_samples }, + sample_rate: 1, + channels: 1, + } + } + pub fn sine(frequency: f32, numb_samples: usize) -> Self { + Self { + sample_source: SampleSource::SignalGen { + frequency, + numb_samples, + samples: Vec::new(), + function: Function::Sine, + }, + sample_rate: 1, + channels: 1, + } + } + pub fn square(frequency: f32, numb_samples: usize) -> Self { + Self { + sample_source: SampleSource::SignalGen { + frequency, + numb_samples, + samples: Vec::new(), + function: Function::Square, + }, sample_rate: 1, channels: 1, } @@ -24,7 +92,7 @@ impl TestSpan { pub fn from_samples<'a>(samples: impl IntoIterator) -> Self { let samples = samples.into_iter().copied().collect::>(); Self { - data: samples, + sample_source: SampleSource::List(samples), sample_rate: 1, channels: 1, } @@ -37,8 +105,15 @@ impl TestSpan { self.channels = channel_count; self } + fn get(&mut self, pos: usize) -> Option { + self.sample_source.get(pos, self.sample_rate, self.channels) + } pub fn len(&self) -> usize { - self.data.len() + match &self.sample_source { + SampleSource::SignalGen { numb_samples, .. } => *numb_samples, + SampleSource::Silence { numb_samples } => *numb_samples, + SampleSource::List(list) => list.len(), + } } } @@ -76,13 +151,13 @@ impl Iterator for TestSource { fn next(&mut self) -> Option { let current_span = self.spans.get_mut(self.current_span)?; - let sample = current_span.data.get(self.pos_in_span).copied()?; + let sample = current_span.get(self.pos_in_span)?; self.pos_in_span += 1; // if span is out of samples // - next set parameters_changed now // - switch to the next span - if self.pos_in_span == current_span.data.len() { + if self.pos_in_span == current_span.len() { self.pos_in_span = 0; self.current_span += 1; self.parameters_changed = true; @@ -113,19 +188,18 @@ impl rodio::Source for TestSource { fn total_duration(&self) -> Option { self.total_duration } - fn try_seek(&mut self, _pos: Duration) -> Result<(), rodio::source::SeekError> { + fn try_seek(&mut self, _pos: Duration) -> Result<(), source::SeekError> { todo!(); // 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(()) + // Ok(()) } } // test for your tests of course. Leave these in, they guard regression when we // expand the functionally which we probably will. -#[test] fn parameters_change_correct() { let mut source = TestSource::new() .with_span(TestSpan::silence(10)) @@ -161,3 +235,19 @@ fn sample_rate_changes() { assert_eq!(source.by_ref().take(10).count(), 10); assert_eq!(source.sample_rate(), 20); } + +#[test] +fn sine_is_avg_zero() { + let sine = TestSource::new().with_span(TestSpan::sine(400.0, 500).with_sample_rate(10_000)); + + let avg = sine.clone().sum::() / sine.spans[0].len() as f32; + assert!(avg < 0.00001f32); +} + +#[test] +fn sine_abs_avg_not_zero() { + let sine = TestSource::new().with_span(TestSpan::sine(400.0, 500).with_sample_rate(10_000)); + + let avg = sine.clone().map(f32::abs).sum::() / sine.spans[0].len() as f32; + assert!(avg > 0.5); +} From 7dacb2ee752ede80e91e92ef26950b9115b78c60 Mon Sep 17 00:00:00 2001 From: dvdsk Date: Wed, 26 Feb 2025 13:43:11 +0100 Subject: [PATCH 07/25] fixes position and provides `frame_change` test for it This also improves the `TestSpan` in tests/test_support to prevent expected total duration not matching `sample_rate` * `channels` * `sample_count` --- src/source/mod.rs | 2 +- src/source/position.rs | 81 ++++++----------- src/source/skip_duration.rs | 3 + tests/position.rs | 84 +++++++++++++++++ tests/skip_duration.rs | 23 +++-- tests/take_duration.rs | 23 +++-- tests/test_support/mod.rs | 177 ++++++++++++++++++++++++++++-------- 7 files changed, 284 insertions(+), 109 deletions(-) create mode 100644 tests/position.rs diff --git a/src/source/mod.rs b/src/source/mod.rs index 99a49ee6..63b41ff6 100644 --- a/src/source/mod.rs +++ b/src/source/mod.rs @@ -516,7 +516,7 @@ pub trait Source: Iterator { where Self: Sized, { - position::track_position(self) + TrackPosition::new(self) } /// Applies a low-pass filter to the source. diff --git a/src/source/position.rs b/src/source/position.rs index f146d028..5993204b 100644 --- a/src/source/position.rs +++ b/src/source/position.rs @@ -4,20 +4,7 @@ use super::SeekError; use crate::common::{ChannelCount, SampleRate}; use crate::Source; -/// Internal function that builds a `TrackPosition` object. See trait docs for -/// details -pub fn track_position(source: I) -> TrackPosition { - TrackPosition { - input: source, - samples_counted: 0, - offset_duration: 0.0, - current_span_sample_rate: 0, - current_span_channels: 0, - } -} - /// Tracks the elapsed duration since the start of the underlying source. -#[derive(Debug)] pub struct TrackPosition { input: I, samples_counted: usize, @@ -26,7 +13,30 @@ pub struct TrackPosition { current_span_channels: ChannelCount, } -impl TrackPosition { +impl std::fmt::Debug for TrackPosition { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("TrackPosition") + .field("samples_counted", &self.samples_counted) + .field("offset_duration", &self.offset_duration) + .field("current_span_sample_rate", &self.current_span_sample_rate) + .field("current_span_channels", &self.current_span_channels) + .finish() + } +} + +impl TrackPosition { + pub(crate) fn new(source: I) -> TrackPosition { + assert!(source.sample_rate() > 0); + assert!(source.channels() > 0); + TrackPosition { + samples_counted: 0, + offset_duration: 0.0, + current_span_sample_rate: source.sample_rate(), + current_span_channels: source.channels(), + input: source, + } + } + /// Returns a reference to the inner source. #[inline] pub fn inner(&self) -> &I { @@ -62,10 +72,12 @@ where /// track_position after speedup's and delay's. #[inline] pub fn get_pos(&self) -> Duration { + dbg!(self); let seconds = self.samples_counted as f64 / self.input.sample_rate() as f64 / self.input.channels() as f64 + self.offset_duration; + dbg!(seconds); Duration::from_secs_f64(seconds) } } @@ -85,6 +97,7 @@ where // At the end of a span add the duration of this span to // offset_duration and start collecting samples again. if self.parameters_changed() { + dbg!(&self); self.offset_duration += self.samples_counted as f64 / self.current_span_sample_rate as f64 / self.current_span_channels as f64; @@ -141,43 +154,3 @@ where result } } - -#[cfg(test)] -mod tests { - use std::time::Duration; - - use crate::buffer::SamplesBuffer; - use crate::source::Source; - - #[test] - fn test_position() { - let inner = SamplesBuffer::new(1, 1, vec![10.0, -10.0, 10.0, -10.0, 20.0, -20.0]); - let mut source = inner.track_position(); - - assert_eq!(source.get_pos().as_secs_f32(), 0.0); - source.next(); - assert_eq!(source.get_pos().as_secs_f32(), 1.0); - - source.next(); - assert_eq!(source.get_pos().as_secs_f32(), 2.0); - - assert_eq!(source.try_seek(Duration::new(1, 0)).is_ok(), true); - assert_eq!(source.get_pos().as_secs_f32(), 1.0); - } - - #[test] - fn test_position_in_presence_of_speedup() { - let inner = SamplesBuffer::new(1, 1, vec![10.0, -10.0, 10.0, -10.0, 20.0, -20.0]); - let mut source = inner.speed(2.0).track_position(); - - assert_eq!(source.get_pos().as_secs_f32(), 0.0); - source.next(); - assert_eq!(source.get_pos().as_secs_f32(), 0.5); - - source.next(); - assert_eq!(source.get_pos().as_secs_f32(), 1.0); - - assert_eq!(source.try_seek(Duration::new(1, 0)).is_ok(), true); - assert_eq!(source.get_pos().as_secs_f32(), 1.0); - } -} diff --git a/src/source/skip_duration.rs b/src/source/skip_duration.rs index 97253a1a..890fc73f 100644 --- a/src/source/skip_duration.rs +++ b/src/source/skip_duration.rs @@ -119,6 +119,9 @@ where let mut ns_per_frame: u64 = 0; while duration > ns_per_frame { + assert!(input.sample_rate() > 0); + assert!(input.channels() > 0); + ns_per_frame = NS_PER_SECOND / input.sample_rate() as u64; let samples_per_second = input.sample_rate() as u64 * input.channels() as u64; diff --git a/tests/position.rs b/tests/position.rs new file mode 100644 index 00000000..4970dcac --- /dev/null +++ b/tests/position.rs @@ -0,0 +1,84 @@ +use std::time::Duration; + +use rodio::buffer::SamplesBuffer; +use rodio::Source; + +mod test_support; +use rstest::rstest; +use test_support::{TestSource, TestSpan}; + +#[rstest] +fn frame_changes( + #[values( + Duration::from_secs(0), + Duration::from_secs_f32(4.8), + Duration::from_secs(5), + Duration::from_secs_f32(9.5), + Duration::from_secs(12) + )] + to_skip: Duration, +) { + // 5 seconds per span + let source = TestSource::new() + .with_span( + TestSpan::silence() + .with_sample_rate(100) + .with_channel_count(2) + .with_exact_duration(Duration::from_secs(5)), + ) + .with_span( + TestSpan::silence() + .with_sample_rate(200) + .with_channel_count(10) + .with_exact_duration(Duration::from_secs(5)), + ) + .with_span( + TestSpan::silence() + .with_sample_rate(50) + .with_channel_count(1) + .with_exact_duration(Duration::from_secs(5)), + ); + + let tracked = source.clone().track_position(); + let skipped = tracked.skip_duration(to_skip); + let diff = to_skip + .checked_sub(skipped.inner().get_pos()) + .expect("Should never report position beyond where we are") + .as_secs_f32(); + + let curr_span = (to_skip.as_secs_f32() / 5.0) as usize; + let sample_rate = source.spans[curr_span].sample_rate as f32; + assert!(diff < 1. / sample_rate) // +} + +#[test] +fn basic_and_seek() { + let inner = SamplesBuffer::new(1, 1, vec![10.0, -10.0, 10.0, -10.0, 20.0, -20.0]); + let mut source = inner.track_position(); + + assert_eq!(source.get_pos().as_secs_f32(), 0.0); + source.next(); + assert_eq!(source.get_pos().as_secs_f32(), 1.0); + + source.next(); + assert_eq!(source.get_pos().as_secs_f32(), 2.0); + + assert_eq!(source.try_seek(Duration::new(1, 0)).is_ok(), true); + assert_eq!(source.get_pos().as_secs_f32(), 1.0); +} + +#[test] +fn basic_and_seek_in_presence_of_speedup() { + let inner = SamplesBuffer::new(1, 1, vec![10.0, -10.0, 10.0, -10.0, 20.0, -20.0]); + let mut source = inner.speed(2.0).track_position(); + + assert_eq!(source.get_pos().as_secs_f32(), 0.0); + source.next(); + assert_eq!(source.get_pos().as_secs_f32(), 0.5); + + source.next(); + assert_eq!(source.get_pos().as_secs_f32(), 1.0); + + assert_eq!(source.try_seek(Duration::new(1, 0)).is_ok(), true); + assert_eq!(source.get_pos().as_secs_f32(), 1.0); +} diff --git a/tests/skip_duration.rs b/tests/skip_duration.rs index 95818707..f2191c99 100644 --- a/tests/skip_duration.rs +++ b/tests/skip_duration.rs @@ -11,9 +11,10 @@ use test_support::{TestSource, TestSpan}; #[rstest] fn ends_on_frame_boundary(#[values(1.483, 2.999)] seconds_to_skip: f32) { let source = TestSource::new().with_span( - TestSpan::silence(30) + TestSpan::silence() .with_channel_count(10) - .with_sample_rate(1), + .with_sample_rate(1) + .with_exact_duration(Duration::from_secs(3)), ); let leftover = source .clone() @@ -24,21 +25,25 @@ fn ends_on_frame_boundary(#[values(1.483, 2.999)] seconds_to_skip: f32) { #[rstest] fn param_changes_during_skip(#[values(6, 11)] seconds_to_skip: u64) { - let source = TestSource::new() // each span is 5 seconds + let span_duration = Duration::from_secs(5); + let source = TestSource::new() .with_span( - TestSpan::silence(5 * 10 * 1) + TestSpan::silence() .with_sample_rate(10) - .with_channel_count(1), + .with_channel_count(1) + .with_exact_duration(span_duration), ) .with_span( - TestSpan::silence(5 * 20 * 2) + TestSpan::silence() .with_sample_rate(20) - .with_channel_count(2), + .with_channel_count(2) + .with_exact_duration(span_duration), ) .with_span( - TestSpan::silence(5 * 5 * 3) + TestSpan::silence() .with_sample_rate(5) - .with_channel_count(3), + .with_channel_count(3) + .with_exact_duration(span_duration), ); let leftover = source diff --git a/tests/take_duration.rs b/tests/take_duration.rs index 99bd9a52..b95139fa 100644 --- a/tests/take_duration.rs +++ b/tests/take_duration.rs @@ -11,9 +11,10 @@ use test_support::{TestSource, TestSpan}; #[rstest] fn ends_on_frame_boundary(#[values(1.483, 2.999)] seconds_to_skip: f32) { let source = TestSource::new().with_span( - TestSpan::silence(30) + TestSpan::silence() .with_channel_count(10) - .with_sample_rate(1), + .with_sample_rate(1) + .with_exact_duration(Duration::from_secs(3)), ); let got = source .clone() @@ -24,21 +25,25 @@ fn ends_on_frame_boundary(#[values(1.483, 2.999)] seconds_to_skip: f32) { #[rstest] fn param_changes_during_skip(#[values(6, 11)] seconds_to_skip: u64) { - let source = TestSource::new() // each span is 5 seconds + let span_duration = Duration::from_secs(5); + let source = TestSource::new() .with_span( - TestSpan::silence(5 * 10 * 1) + TestSpan::silence() .with_sample_rate(10) - .with_channel_count(1), + .with_channel_count(1) + .with_exact_duration(span_duration), ) .with_span( - TestSpan::silence(5 * 20 * 2) + TestSpan::silence() .with_sample_rate(20) - .with_channel_count(2), + .with_channel_count(2) + .with_exact_duration(span_duration), ) .with_span( - TestSpan::silence(5 * 5 * 3) + TestSpan::silence() .with_sample_rate(5) - .with_channel_count(3), + .with_channel_count(3) + .with_exact_duration(span_duration), ); let took = source diff --git a/tests/test_support/mod.rs b/tests/test_support/mod.rs index 5fd8affd..42cb36ae 100644 --- a/tests/test_support/mod.rs +++ b/tests/test_support/mod.rs @@ -14,11 +14,8 @@ pub enum SampleSource { function: Function, samples: Vec, frequency: f32, - numb_samples: usize, - }, - Silence { - numb_samples: usize, }, + Silence, List(Vec), } @@ -28,22 +25,22 @@ impl SampleSource { pos: usize, sample_rate: SampleRate, channels: ChannelCount, + numb_samples: usize, ) -> Option { match self { SampleSource::SignalGen { function, samples, frequency, - numb_samples, - } if samples.len() != *numb_samples => { + } if samples.len() != numb_samples => { *samples = SignalGenerator::new(sample_rate, *frequency, function.clone()) - .take(*numb_samples) + .take(numb_samples) .flat_map(|sample| iter::repeat_n(sample, channels.into())) .collect(); samples.get(pos).copied() } SampleSource::SignalGen { samples, .. } => samples.get(pos).copied(), - SampleSource::Silence { numb_samples } if pos < *numb_samples => Some(0.0), + SampleSource::Silence { .. } if pos < numb_samples => Some(0.0), SampleSource::Silence { .. } => None, SampleSource::List(list) => list.get(pos).copied(), } @@ -55,21 +52,28 @@ pub struct TestSpan { pub sample_source: SampleSource, pub sample_rate: SampleRate, pub channels: ChannelCount, + numb_samples: usize, +} + +#[derive(Debug, Clone)] +pub struct TestSpanBuilder { + pub sample_source: SampleSource, + pub sample_rate: SampleRate, + pub channels: ChannelCount, } impl TestSpan { - pub fn silence(numb_samples: usize) -> Self { - Self { - sample_source: SampleSource::Silence { numb_samples }, + pub fn silence() -> TestSpanBuilder { + TestSpanBuilder { + sample_source: SampleSource::Silence, sample_rate: 1, channels: 1, } } - pub fn sine(frequency: f32, numb_samples: usize) -> Self { - Self { + pub fn sine(frequency: f32) -> TestSpanBuilder { + TestSpanBuilder { sample_source: SampleSource::SignalGen { frequency, - numb_samples, samples: Vec::new(), function: Function::Sine, }, @@ -77,11 +81,10 @@ impl TestSpan { channels: 1, } } - pub fn square(frequency: f32, numb_samples: usize) -> Self { - Self { + pub fn square(frequency: f32) -> TestSpanBuilder { + TestSpanBuilder { sample_source: SampleSource::SignalGen { frequency, - numb_samples, samples: Vec::new(), function: Function::Square, }, @@ -89,14 +92,26 @@ impl TestSpan { channels: 1, } } - pub fn from_samples<'a>(samples: impl IntoIterator) -> Self { + pub fn from_samples<'a>(samples: impl IntoIterator) -> TestSpanBuilder { let samples = samples.into_iter().copied().collect::>(); - Self { + TestSpanBuilder { sample_source: SampleSource::List(samples), sample_rate: 1, channels: 1, } } + + fn get(&mut self, pos: usize) -> Option { + self.sample_source + .get(pos, self.sample_rate, self.channels, self.numb_samples) + } + + pub fn len(&self) -> usize { + self.numb_samples + } +} + +impl TestSpanBuilder { pub fn with_sample_rate(mut self, sample_rate: SampleRate) -> Self { self.sample_rate = sample_rate; self @@ -105,15 +120,81 @@ impl TestSpan { self.channels = channel_count; self } - fn get(&mut self, pos: usize) -> Option { - self.sample_source.get(pos, self.sample_rate, self.channels) + pub fn with_sample_count(self, n: usize) -> TestSpan { + TestSpan { + sample_source: self.sample_source, + sample_rate: self.sample_rate, + channels: self.channels, + numb_samples: n, + } } - pub fn len(&self) -> usize { - match &self.sample_source { - SampleSource::SignalGen { numb_samples, .. } => *numb_samples, - SampleSource::Silence { numb_samples } => *numb_samples, - SampleSource::List(list) => list.len(), + /// is allowed to be 1% off + pub fn with_rough_duration(self, duration: Duration) -> TestSpan { + let (needed_samples, _) = self.needed_samples(duration); + + if let SampleSource::List(list) = &self.sample_source { + let allowed_deviation = needed_samples as usize / 10; + if list.len().abs_diff(needed_samples as usize) > allowed_deviation { + panic!( + "provided sample list does not provide the correct amount + of samples for a test span with the given duration" + ) + } + } + + TestSpan { + numb_samples: needed_samples + .try_into() + .expect("too many samples for test source"), + sample_source: self.sample_source, + sample_rate: self.sample_rate, + channels: self.channels, + } + } + pub fn with_exact_duration(self, duration: Duration) -> TestSpan { + let (needed_samples, deviation) = self.needed_samples(duration); + + if deviation > 0 { + panic!( + "requested duration {:?} is, at the highest precision not a \ + multiple of sample_rate {} and channels {}. Consider using \ + `with_rough_duration`", + duration, self.sample_rate, self.channels + ) + } + + if let SampleSource::List(list) = &self.sample_source { + if list.len() != needed_samples as usize { + panic!( + "provided sample list does not provide the correct amount + of samples for a test span with the given duration" + ) + } } + + TestSpan { + numb_samples: needed_samples + .try_into() + .expect("too many samples for test source"), + sample_source: self.sample_source, + sample_rate: self.sample_rate, + channels: self.channels, + } + } + + fn needed_samples(&self, duration: Duration) -> (u64, u64) { + const NS_PER_SECOND: u64 = 1_000_000_000; + let duration_ns: u64 = duration + .as_nanos() + .try_into() + .expect("Test duration should not be more then ~500 days"); + + let needed_samples = + duration_ns * self.sample_rate as u64 * self.channels as u64 / NS_PER_SECOND; + let duration_of_those_samples = + needed_samples * NS_PER_SECOND / self.sample_rate as u64 / self.channels as u64; + let deviation = duration_ns.abs_diff(duration_of_those_samples); + (needed_samples, deviation) } } @@ -177,13 +258,13 @@ impl rodio::Source for TestSource { self.spans .get(self.current_span) .map(|span| span.channels) - .unwrap_or_default() + .unwrap() // not correct behavior for a source but useful during testing } fn sample_rate(&self) -> rodio::SampleRate { self.spans .get(self.current_span) .map(|span| span.sample_rate) - .unwrap_or_default() + .unwrap() // not correct behavior for a source but useful during testing } fn total_duration(&self) -> Option { self.total_duration @@ -202,8 +283,8 @@ impl rodio::Source for TestSource { // expand the functionally which we probably will. fn parameters_change_correct() { let mut source = TestSource::new() - .with_span(TestSpan::silence(10)) - .with_span(TestSpan::silence(10)); + .with_span(TestSpan::silence().with_sample_count(10)) + .with_span(TestSpan::silence().with_sample_count(10)); assert_eq!(source.by_ref().take(10).count(), 10); assert!(source.parameters_changed); @@ -217,8 +298,16 @@ fn parameters_change_correct() { #[test] fn channel_count_changes() { let mut source = TestSource::new() - .with_span(TestSpan::silence(10).with_channel_count(1)) - .with_span(TestSpan::silence(10).with_channel_count(2)); + .with_span( + TestSpan::silence() + .with_channel_count(1) + .with_sample_count(10), + ) + .with_span( + TestSpan::silence() + .with_channel_count(2) + .with_sample_count(10), + ); assert_eq!(source.channels(), 1); assert_eq!(source.by_ref().take(10).count(), 10); @@ -228,8 +317,16 @@ fn channel_count_changes() { #[test] fn sample_rate_changes() { let mut source = TestSource::new() - .with_span(TestSpan::silence(10).with_sample_rate(10)) - .with_span(TestSpan::silence(10).with_sample_rate(20)); + .with_span( + TestSpan::silence() + .with_sample_rate(10) + .with_sample_count(10), + ) + .with_span( + TestSpan::silence() + .with_sample_rate(20) + .with_sample_count(10), + ); assert_eq!(source.sample_rate(), 10); assert_eq!(source.by_ref().take(10).count(), 10); @@ -238,7 +335,11 @@ fn sample_rate_changes() { #[test] fn sine_is_avg_zero() { - let sine = TestSource::new().with_span(TestSpan::sine(400.0, 500).with_sample_rate(10_000)); + let sine = TestSource::new().with_span( + TestSpan::sine(400.0) + .with_sample_rate(10_000) + .with_sample_count(500), + ); let avg = sine.clone().sum::() / sine.spans[0].len() as f32; assert!(avg < 0.00001f32); @@ -246,7 +347,11 @@ fn sine_is_avg_zero() { #[test] fn sine_abs_avg_not_zero() { - let sine = TestSource::new().with_span(TestSpan::sine(400.0, 500).with_sample_rate(10_000)); + let sine = TestSource::new().with_span( + TestSpan::sine(400.0) + .with_sample_rate(10_000) + .with_sample_count(500), + ); let avg = sine.clone().map(f32::abs).sum::() / sine.spans[0].len() as f32; assert!(avg > 0.5); From 5b40b664a5dee3eaa73bb1c5ec4a0e37cc89cbb1 Mon Sep 17 00:00:00 2001 From: dvdsk Date: Wed, 26 Feb 2025 14:05:27 +0100 Subject: [PATCH 08/25] rename peekable -> peekablesource, document it, add test file fix peekable & peekable tests + improve TestSource performance: remove `if` from PeekableSource::next --- src/source/mod.rs | 17 ++++++++ src/source/peekable.rs | 36 ++++++++++++---- src/source/repeat.rs | 8 ++-- tests/peekable.rs | 86 +++++++++++++++++++++++++++++++++++++++ tests/test_support/mod.rs | 72 ++++++++++++++++++++------------ 5 files changed, 179 insertions(+), 40 deletions(-) create mode 100644 tests/peekable.rs diff --git a/src/source/mod.rs b/src/source/mod.rs index 63b41ff6..a2dc7605 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 peekable::PeekableSource; use take_samples::TakeSamples; use take_span::TakeSpan; @@ -519,6 +520,22 @@ pub trait Source: Iterator { TrackPosition::new(self) } + /// This is the [`Source`] equivalent of + /// [`Iterator::peek`](std::iter::Iterator::peekable). Creates an iterator + /// which can use the [`peek`](PeekableSource::peek) method to look at the next + /// element of the iterator without consuming it. See their documentation + /// for more information. + + /// # Note + /// The underlying source is advanced by one sample when you call this + /// function. Any side effects of the next method will occur. + fn peekable_source(self) -> PeekableSource + where + Self: Sized, + { + PeekableSource::new(self) + } + /// Applies a low-pass filter to the source. /// **Warning**: Probably buggy. #[inline] diff --git a/src/source/peekable.rs b/src/source/peekable.rs index ce14c517..efbf5725 100644 --- a/src/source/peekable.rs +++ b/src/source/peekable.rs @@ -4,19 +4,18 @@ use crate::{ChannelCount, Sample, SampleRate}; use super::Source; -pub struct Peekable { +pub struct PeekableSource { next: Option, channels: ChannelCount, sample_rate: SampleRate, + channels_after_next: ChannelCount, + sample_rate_after_next: SampleRate, parameters_changed_after_next: bool, parameters_changed: bool, inner: I, } -impl Clone for Peekable -where - I: Clone, -{ +impl Clone for PeekableSource { fn clone(&self) -> Self { Self { next: self.next, @@ -25,41 +24,60 @@ where parameters_changed_after_next: self.parameters_changed_after_next, parameters_changed: self.parameters_changed, inner: self.inner.clone(), + channels_after_next: self.channels_after_next, + sample_rate_after_next: self.sample_rate_after_next, } } } -impl Peekable { - pub(crate) fn new(mut inner: I) -> Peekable { +impl PeekableSource { + pub(crate) fn new(mut inner: I) -> PeekableSource { Self { // field order is critical! do not change channels: inner.channels(), sample_rate: inner.sample_rate(), next: inner.next(), + channels_after_next: inner.channels(), + sample_rate_after_next: inner.sample_rate(), parameters_changed_after_next: inner.parameters_changed(), parameters_changed: false, inner, } } + /// Look at the next sample. This does not advance the source. + /// Can be used to determine if the current sample was the last. pub fn peek(&self) -> Option { self.next } } -impl Iterator for Peekable { +impl Iterator for PeekableSource { type Item = Sample; fn next(&mut self) -> Option { let item = self.next.take()?; self.next = self.inner.next(); + self.parameters_changed = self.parameters_changed_after_next; + self.channels = self.channels_after_next; + self.sample_rate = self.sample_rate_after_next; + self.parameters_changed_after_next = self.inner.parameters_changed(); + self.channels_after_next = self.inner.channels(); + self.sample_rate_after_next = self.inner.sample_rate(); + Some(item) } + + fn size_hint(&self) -> (usize, Option) { + self.inner.size_hint() + } } -impl Source for Peekable { +impl ExactSizeIterator for PeekableSource {} + +impl Source for PeekableSource { fn parameters_changed(&self) -> bool { self.parameters_changed } diff --git a/src/source/repeat.rs b/src/source/repeat.rs index 6547b34a..c5fe3792 100644 --- a/src/source/repeat.rs +++ b/src/source/repeat.rs @@ -2,7 +2,7 @@ use std::time::Duration; use crate::source::buffered::Buffered; -use super::peekable::Peekable; +use super::peekable::PeekableSource; use super::SeekError; use crate::common::{ChannelCount, SampleRate}; use crate::Source; @@ -14,7 +14,7 @@ where { let input = input.buffered(); Repeat { - inner: Peekable::new(input.clone()), + inner: PeekableSource::new(input.clone()), next: input, } } @@ -24,7 +24,7 @@ pub struct Repeat where I: Source, { - inner: Peekable>, + inner: PeekableSource>, next: Buffered, } @@ -39,7 +39,7 @@ where if let Some(value) = self.inner.next() { Some(value) } else { - self.inner = Peekable::new(self.next.clone()); + self.inner = PeekableSource::new(self.next.clone()); self.inner.next() } } diff --git a/tests/peekable.rs b/tests/peekable.rs new file mode 100644 index 00000000..bdf93169 --- /dev/null +++ b/tests/peekable.rs @@ -0,0 +1,86 @@ +mod test_support; +use rodio::Source; +use test_support::{TestSource, TestSpan}; + +#[test] +fn peeked_matches_next() { + let source = TestSource::new() + .with_span( + TestSpan::from_samples(&(0..10).map(|n| n as f32).collect::>()) + .with_sample_count(10), + ) + .with_span( + TestSpan::from_samples(&(10..20).map(|n| n as f32).collect::>()) + .with_sample_count(10), + ); + + let mut peekable = source.peekable_source(); + + for _ in 0..peekable.len() { + let peeked = peekable.peek(); + let next = peekable.next(); + assert!( + peeked == next, + "peeked: {peeked:?} does not match following next: {next:?}" + ); + } +} + +#[test] +fn parameters_change_correct() { + let source = TestSource::new() + .with_span(TestSpan::silence().with_sample_count(10)) + .with_span(TestSpan::silence().with_sample_count(10)); + let mut peekable = source.peekable_source(); + peekable.peek(); + + assert_eq!(peekable.by_ref().take(10).count(), 10); + assert!(peekable.parameters_changed()); + + assert!(peekable.next().is_some()); + assert!(!peekable.parameters_changed()); + + assert_eq!(peekable.count(), 9); +} + +#[test] +fn channel_count_changes() { + let source = TestSource::new() + .with_span( + TestSpan::silence() + .with_channel_count(1) + .with_sample_count(10), + ) + .with_span( + TestSpan::silence() + .with_channel_count(2) + .with_sample_count(10), + ); + let mut peekable = source.peekable_source(); + peekable.peek(); + + assert_eq!(peekable.channels(), 1); + assert_eq!(peekable.by_ref().take(10).count(), 10); + assert_eq!(peekable.channels(), 2); +} + +#[test] +fn sample_rate_changes() { + let source = TestSource::new() + .with_span( + TestSpan::silence() + .with_sample_rate(10) + .with_sample_count(10), + ) + .with_span( + TestSpan::silence() + .with_sample_rate(20) + .with_sample_count(10), + ); + let mut peekable = source.peekable_source(); + peekable.peek(); + + assert_eq!(peekable.sample_rate(), 10); + assert_eq!(peekable.by_ref().take(10).count(), 10); + assert_eq!(peekable.sample_rate(), 20); +} diff --git a/tests/test_support/mod.rs b/tests/test_support/mod.rs index 42cb36ae..8b1f3cef 100644 --- a/tests/test_support/mod.rs +++ b/tests/test_support/mod.rs @@ -121,6 +121,16 @@ impl TestSpanBuilder { self } pub fn with_sample_count(self, n: usize) -> TestSpan { + if let SampleSource::List(list) = &self.sample_source { + assert!( + list.len() == n, + "The list providing samples is a different length \ + ({}) as the required sample count {}", + list.len(), + n + ); + } + TestSpan { sample_source: self.sample_source, sample_rate: self.sample_rate, @@ -134,12 +144,11 @@ impl TestSpanBuilder { if let SampleSource::List(list) = &self.sample_source { let allowed_deviation = needed_samples as usize / 10; - if list.len().abs_diff(needed_samples as usize) > allowed_deviation { - panic!( - "provided sample list does not provide the correct amount + assert!( + list.len().abs_diff(needed_samples as usize) > allowed_deviation, + "provided sample list does not provide the correct amount of samples for a test span with the given duration" - ) - } + ) } TestSpan { @@ -154,22 +163,21 @@ impl TestSpanBuilder { pub fn with_exact_duration(self, duration: Duration) -> TestSpan { let (needed_samples, deviation) = self.needed_samples(duration); - if deviation > 0 { - panic!( - "requested duration {:?} is, at the highest precision not a \ + assert_eq!( + deviation, 0, + "requested duration {:?} is, at the highest precision not a \ multiple of sample_rate {} and channels {}. Consider using \ `with_rough_duration`", - duration, self.sample_rate, self.channels - ) - } + duration, self.sample_rate, self.channels + ); if let SampleSource::List(list) = &self.sample_source { - if list.len() != needed_samples as usize { - panic!( - "provided sample list does not provide the correct amount + assert_eq!( + list.len(), + needed_samples as usize, + "provided sample list does not provide the correct amount of samples for a test span with the given duration" - ) - } + ) } TestSpan { @@ -203,7 +211,6 @@ pub struct TestSource { pub spans: Vec, current_span: usize, pos_in_span: usize, - total_duration: Option, parameters_changed: bool, } @@ -213,7 +220,6 @@ impl TestSource { spans: Vec::new(), current_span: 0, pos_in_span: 0, - total_duration: None, parameters_changed: false, } } @@ -221,10 +227,6 @@ impl TestSource { self.spans.push(span); self } - pub fn with_total_duration(mut self, duration: Duration) -> Self { - self.total_duration = Some(duration); - self - } } impl Iterator for TestSource { @@ -248,8 +250,16 @@ impl Iterator for TestSource { Some(sample) } + + fn size_hint(&self) -> (usize, Option) { + let len = self.spans.iter().map(TestSpan::len).sum(); + dbg!(len); + (len, Some(len)) + } } +impl ExactSizeIterator for TestSource {} + impl rodio::Source for TestSource { fn parameters_changed(&self) -> bool { self.parameters_changed @@ -258,16 +268,24 @@ impl rodio::Source for TestSource { self.spans .get(self.current_span) .map(|span| span.channels) - .unwrap() // not correct behavior for a source but useful during testing + .unwrap_or_else(|| { + self.spans + .last() + .expect("TestSource must have at least one span").channels + }) } fn sample_rate(&self) -> rodio::SampleRate { self.spans .get(self.current_span) .map(|span| span.sample_rate) - .unwrap() // not correct behavior for a source but useful during testing + .unwrap_or_else(|| { + self.spans + .last() + .expect("TestSource must have at least one span").sample_rate + }) } fn total_duration(&self) -> Option { - self.total_duration + None } fn try_seek(&mut self, _pos: Duration) -> Result<(), source::SeekError> { todo!(); @@ -287,10 +305,10 @@ fn parameters_change_correct() { .with_span(TestSpan::silence().with_sample_count(10)); assert_eq!(source.by_ref().take(10).count(), 10); - assert!(source.parameters_changed); + assert!(source.parameters_changed()); assert!(source.next().is_some()); - assert!(!source.parameters_changed); + assert!(!source.parameters_changed()); assert_eq!(source.count(), 9); } From e1253f3a386974c3a77a962c37dd8556b36a1e75 Mon Sep 17 00:00:00 2001 From: dvdsk Date: Wed, 26 Feb 2025 17:37:21 +0100 Subject: [PATCH 09/25] refactor buffered + fix size_hint for TestSource add tests for buffered, fix buffered `parameters_changed` --- src/source/buffered.rs | 248 +++++++++++++++++++------------------- src/source/mod.rs | 2 +- tests/buffered.rs | 82 +++++++++++++ tests/peekable.rs | 4 +- tests/test_support/mod.rs | 42 ++++++- 5 files changed, 245 insertions(+), 133 deletions(-) create mode 100644 tests/buffered.rs diff --git a/src/source/buffered.rs b/src/source/buffered.rs index 4c51fc3c..a5b669bb 100644 --- a/src/source/buffered.rs +++ b/src/source/buffered.rs @@ -1,28 +1,16 @@ +/// A iterator that stores extracted data in memory while allowing +/// concurrent reading in real time. use std::mem; use std::sync::{Arc, Mutex}; use std::time::Duration; use super::SeekError; use crate::common::{ChannelCount, SampleRate}; +use crate::math::PrevMultipleOf; use crate::Source; -/// Internal function that builds a `Buffered` object. -#[inline] -pub fn buffered(input: I) -> Buffered -where - I: Source, -{ - let total_duration = input.total_duration(); - let first_span = extract(input); - - Buffered { - current_span: first_span, - position_in_span: 0, - total_duration, - } -} - -/// Iterator that at the same time extracts data from the iterator and stores it in a buffer. +/// Iterator that at the same time extracts data from the iterator and +/// stores it in a buffer. pub struct Buffered where I: Source, @@ -30,6 +18,8 @@ where /// Immutable reference to the next span of data. Cannot be `Span::Input`. current_span: Arc>, + parameters_changed: bool, + /// The position in number of samples of this iterator inside `current_span`. position_in_span: usize, @@ -37,101 +27,19 @@ where total_duration: Option, } -enum Span -where - I: Source, -{ - /// Data that has already been extracted from the iterator. Also contains a pointer to the - /// next span. - Data(SpanData), - - /// No more data. - End, - - /// Unextracted data. The `Option` should never be `None` and is only here for easier data - /// processing. - Input(Mutex>), -} - -struct SpanData -where - I: Source, -{ - data: Vec, - channels: ChannelCount, - rate: SampleRate, - next: Mutex>>, -} - -impl Drop for SpanData -where - I: Source, -{ - fn drop(&mut self) { - // This is necessary to prevent stack overflows deallocating long chains of the mutually - // recursive `Span` and `SpanData` types. This iteratively traverses as much of the - // chain as needs to be deallocated, and repeatedly "pops" the head off the list. This - // solves the problem, as when the time comes to actually deallocate the `SpanData`, - // the `next` field will contain a `Span::End`, or an `Arc` with additional references, - // so the depth of recursive drops will be bounded. - while let Ok(arc_next) = self.next.get_mut() { - if let Some(next_ref) = Arc::get_mut(arc_next) { - // This allows us to own the next Span. - let next = mem::replace(next_ref, Span::End); - if let Span::Data(next_data) = next { - // Swap the current SpanData with the next one, allowing the current one - // to go out of scope. - *self = next_data; - } else { - break; - } - } else { - break; - } - } - } -} - -/// Builds a span from the input iterator. -fn extract(mut input: I) -> Arc> -where - I: Source, -{ - if input.parameters_changed() { - return Arc::new(Span::End); - } - - let channels = input.channels(); - let rate = input.sample_rate(); +impl Buffered { + pub(crate) fn new(input: I) -> Buffered { + let total_duration = input.total_duration(); + let first_span = extract(input); - let mut data = Vec::new(); - loop { - let Some(element) = input.next() else { break }; - data.push(element); - if input.parameters_changed() { - break; - } - if data.len() > 32768 { - break; + Buffered { + current_span: first_span, + position_in_span: 0, + total_duration, + parameters_changed: false, } } - if data.is_empty() { - return Arc::new(Span::End); - } - - Arc::new(Span::Data(SpanData { - data, - channels, - rate, - next: Mutex::new(Arc::new(Span::Input(Mutex::new(Some(input))))), - })) -} - -impl Buffered -where - I: Source, -{ /// Advances to the next span. fn next_span(&mut self) { let next_span = { @@ -145,6 +53,7 @@ where Span::End => next_span_ptr.clone(), Span::Input(input) => { let input = input.lock().unwrap().take().unwrap(); + dbg!(); extract(input) } }; @@ -169,7 +78,7 @@ where let current_sample; let advance_span; - match &*self.current_span { + match dbg!(&*self.current_span) { Span::Data(SpanData { data, .. }) => { current_sample = Some(data[self.position_in_span]); self.position_in_span += 1; @@ -185,39 +94,31 @@ where }; if advance_span { + dbg!(); + self.parameters_changed = true; self.next_span(); + } else { + self.parameters_changed = false; } current_sample } - - #[inline] - fn size_hint(&self) -> (usize, Option) { - // TODO: - (0, None) - } } -// TODO: implement exactsize iterator when size_hint is done - impl Source for Buffered where I: Source, { #[inline] fn parameters_changed(&self) -> bool { - match &*self.current_span { - Span::Data(_) => false, - Span::End => true, - Span::Input(_) => unreachable!(), - } + self.parameters_changed } #[inline] fn channels(&self) -> ChannelCount { match *self.current_span { Span::Data(SpanData { channels, .. }) => channels, - Span::End => 1, + Span::End => 0, Span::Input(_) => unreachable!(), } } @@ -226,7 +127,7 @@ where fn sample_rate(&self) -> SampleRate { match *self.current_span { Span::Data(SpanData { rate, .. }) => rate, - Span::End => 44100, + Span::End => 0, Span::Input(_) => unreachable!(), } } @@ -256,6 +157,105 @@ where current_span: self.current_span.clone(), position_in_span: self.position_in_span, total_duration: self.total_duration, + parameters_changed: self.parameters_changed, + } + } +} + +enum Span +where + I: Source, +{ + /// Data that has already been extracted from the iterator. + /// Also contains a pointer to the next span. + Data(SpanData), + + /// No more data. + End, + + /// Unextracted data. The `Option` should never be `None` and is only here for easier data + /// processing. + Input(Mutex>), +} + +impl std::fmt::Debug for Span { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Span::Data(_) => f.write_str("Span::Data"), + Span::End => f.write_str("Span::End"), + Span::Input(_) => f.write_str("Span::Input"), } } } + +struct SpanData +where + I: Source, +{ + data: Vec, + channels: ChannelCount, + rate: SampleRate, + next: Mutex>>, +} + +impl Drop for SpanData +where + I: Source, +{ + fn drop(&mut self) { + // This is necessary to prevent stack overflows deallocating long chains of the mutually + // recursive `Span` and `SpanData` types. This iteratively traverses as much of the + // chain as needs to be deallocated, and repeatedly "pops" the head off the list. This + // solves the problem, as when the time comes to actually deallocate the `SpanData`, + // the `next` field will contain a `Span::End`, or an `Arc` with additional references, + // so the depth of recursive drops will be bounded. + while let Ok(arc_next) = self.next.get_mut() { + if let Some(next_ref) = Arc::get_mut(arc_next) { + // This allows us to own the next Span. + let next = mem::replace(next_ref, Span::End); + if let Span::Data(next_data) = next { + // Swap the current SpanData with the next one, allowing the current one + // to go out of scope. + *self = next_data; + } else { + break; + } + } else { + break; + } + } + } +} + +/// Builds a span from the input iterator. +fn extract(mut input: I) -> Arc> +where + I: Source, +{ + let channels = input.channels(); + let rate = input.sample_rate(); + + let mut data = Vec::new(); + loop { + let Some(sample) = input.next() else { break }; + data.push(sample); + dbg!(sample); + if input.parameters_changed() { + break; + } + if data.len() > 32768.prev_multiple_of(channels) { + break; + } + } + + if dbg!(data.is_empty()) { + return Arc::new(Span::End); + } + + Arc::new(Span::Data(SpanData { + data, + channels, + rate, + next: Mutex::new(Arc::new(Span::Input(Mutex::new(Some(input))))), + })) +} diff --git a/src/source/mod.rs b/src/source/mod.rs index a2dc7605..8ab09b9f 100644 --- a/src/source/mod.rs +++ b/src/source/mod.rs @@ -179,7 +179,7 @@ pub trait Source: Iterator { where Self: Sized, { - buffered::buffered(self) + Buffered::new(self) } /// Mixes this source with another one. diff --git a/tests/buffered.rs b/tests/buffered.rs new file mode 100644 index 00000000..fa900027 --- /dev/null +++ b/tests/buffered.rs @@ -0,0 +1,82 @@ +mod test_support; +use rodio::Source; +use test_support::{TestSource, TestSpan}; + +#[test] +fn parameters_change_correct() { + let mut source = TestSource::new() + .with_span(TestSpan::silence().with_sample_count(10)) + .with_span(TestSpan::silence().with_sample_count(10)) + .buffered(); + + assert_eq!(source.by_ref().take(10).count(), 10); + assert!(source.parameters_changed()); + + assert!(source.next().is_some()); + assert!(!source.parameters_changed()); + + assert_eq!(source.count(), 9); +} + +#[test] +fn channel_count_changes() { + let mut source = TestSource::new() + .with_span( + TestSpan::silence() + .with_channel_count(1) + .with_sample_count(10), + ) + .with_span( + TestSpan::silence() + .with_channel_count(2) + .with_sample_count(10), + ) + .buffered(); + + assert_eq!(source.channels(), 1); + assert_eq!(source.by_ref().take(10).count(), 10); + assert_eq!(source.channels(), 2); +} + +#[test] +fn buffered_sample_rate_changes() { + let mut source = TestSource::new() + .with_span( + TestSpan::silence() + .with_sample_rate(10) + .with_sample_count(10), + ) + .with_span( + TestSpan::silence() + .with_sample_rate(20) + .with_sample_count(10), + ) + .buffered(); + + assert_eq!(source.sample_rate(), 10); + assert_eq!(source.by_ref().take(10).count(), 10); + assert_eq!(source.sample_rate(), 20); +} + +#[test] +fn equals_unbuffered() { + let mut source = TestSource::new() + .with_span( + TestSpan::from_samples((0..10).into_iter().map(|n| n as f32)) + .with_sample_rate(10) + .with_sample_count(10), + ) + .with_span( + TestSpan::silence() + .with_sample_rate(20) + .with_sample_count(10), + ); + + let mut buffered = source.clone().buffered(); + for (sample, expected) in buffered.by_ref().zip(source.by_ref()) { + assert_eq!(sample, expected); + } + + assert!(buffered.next().is_none()); + assert!(source.next().is_none()); +} diff --git a/tests/peekable.rs b/tests/peekable.rs index bdf93169..cfd45de9 100644 --- a/tests/peekable.rs +++ b/tests/peekable.rs @@ -6,11 +6,11 @@ use test_support::{TestSource, TestSpan}; fn peeked_matches_next() { let source = TestSource::new() .with_span( - TestSpan::from_samples(&(0..10).map(|n| n as f32).collect::>()) + TestSpan::from_samples((0..10).map(|n| n as f32)) .with_sample_count(10), ) .with_span( - TestSpan::from_samples(&(10..20).map(|n| n as f32).collect::>()) + TestSpan::from_samples((10..20).map(|n| n as f32)) .with_sample_count(10), ); diff --git a/tests/test_support/mod.rs b/tests/test_support/mod.rs index 8b1f3cef..fe67510b 100644 --- a/tests/test_support/mod.rs +++ b/tests/test_support/mod.rs @@ -92,8 +92,8 @@ impl TestSpan { channels: 1, } } - pub fn from_samples<'a>(samples: impl IntoIterator) -> TestSpanBuilder { - let samples = samples.into_iter().copied().collect::>(); + pub fn from_samples<'a>(samples: impl IntoIterator) -> TestSpanBuilder { + let samples = samples.into_iter().collect::>(); TestSpanBuilder { sample_source: SampleSource::List(samples), sample_rate: 1, @@ -252,8 +252,13 @@ impl Iterator for TestSource { } fn size_hint(&self) -> (usize, Option) { - let len = self.spans.iter().map(TestSpan::len).sum(); - dbg!(len); + let len = self + .spans + .iter() + .skip(self.current_span) + .map(TestSpan::len) + .sum::() + - self.pos_in_span; (len, Some(len)) } } @@ -271,7 +276,8 @@ impl rodio::Source for TestSource { .unwrap_or_else(|| { self.spans .last() - .expect("TestSource must have at least one span").channels + .expect("TestSource must have at least one span") + .channels }) } fn sample_rate(&self) -> rodio::SampleRate { @@ -281,7 +287,8 @@ impl rodio::Source for TestSource { .unwrap_or_else(|| { self.spans .last() - .expect("TestSource must have at least one span").sample_rate + .expect("TestSource must have at least one span") + .sample_rate }) } fn total_duration(&self) -> Option { @@ -299,6 +306,7 @@ impl rodio::Source for TestSource { // test for your tests of course. Leave these in, they guard regression when we // expand the functionally which we probably will. +#[test] fn parameters_change_correct() { let mut source = TestSource::new() .with_span(TestSpan::silence().with_sample_count(10)) @@ -374,3 +382,25 @@ fn sine_abs_avg_not_zero() { let avg = sine.clone().map(f32::abs).sum::() / sine.spans[0].len() as f32; assert!(avg > 0.5); } + +#[test] +fn size_hint() { + let mut source = TestSource::new() + .with_span( + TestSpan::silence() + .with_sample_rate(10) + .with_sample_count(10), + ) + .with_span( + TestSpan::silence() + .with_sample_rate(20) + .with_sample_count(10), + ); + + assert_eq!(source.len(), 20); + assert_eq!(source.by_ref().take(10).count(), 10); + assert_eq!(source.len(), 10); + assert_eq!(source.by_ref().take(10).count(), 10); + assert_eq!(source.len(), 0); + assert!(source.next().is_none()) +} From af6b92ebd8c820e08bea6402b625c267b10c7342 Mon Sep 17 00:00:00 2001 From: dvdsk Date: Wed, 26 Feb 2025 18:30:02 +0100 Subject: [PATCH 10/25] note parameters_changed behaviour before first next --- src/source/mod.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/source/mod.rs b/src/source/mod.rs index 8ab09b9f..76f39336 100644 --- a/src/source/mod.rs +++ b/src/source/mod.rs @@ -158,8 +158,10 @@ pub use self::noise::{pink, white, PinkNoise, WhiteNoise}; /// pub trait Source: Iterator { /// Whether the value of `channels()` and/or `sample_rate()` have changed. - /// This is true before the next call to `Source::next`. After the first - /// `Source::next` call this will be false again. + /// This is true before the next call to `Source::next`. After that + /// `Source::next` call this will be false again. + /// + /// The value before the first call to next is not defined. fn parameters_changed(&self) -> bool; /// Returns the number of channels. Channels are always interleaved. From 92eb10ad0c73b3c97b7ecd0fd40dc009be69a344 Mon Sep 17 00:00:00 2001 From: dvdsk Date: Wed, 26 Feb 2025 18:55:40 +0100 Subject: [PATCH 11/25] move crossfade tests to integration tests --- src/source/crossfade.rs | 48 +---------------------------------------- tests/crossfade.rs | 38 ++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 47 deletions(-) create mode 100644 tests/crossfade.rs diff --git a/src/source/crossfade.rs b/src/source/crossfade.rs index 0109c5e4..a67957f0 100644 --- a/src/source/crossfade.rs +++ b/src/source/crossfade.rs @@ -7,7 +7,7 @@ use std::time::Duration; /// /// Only the crossfaded portion (beginning of fadeout, beginning of fadein) is /// returned. -pub fn crossfade( +pub(crate) fn crossfade( input_fadeout: I1, input_fadein: I2, duration: Duration, @@ -28,49 +28,3 @@ where /// Only the crossfaded portion (beginning of fadeout, beginning of fadein) is /// covered. pub type Crossfade = Mix, FadeIn>>; - -#[cfg(test)] -mod tests { - use super::*; - use crate::buffer::SamplesBuffer; - use crate::source::Zero; - - fn dummy_source(length: u8) -> SamplesBuffer { - let data: Vec = (1..=length).map(f32::from).collect(); - SamplesBuffer::new(1, 1, data) - } - - #[test] - fn test_crossfade_with_self() { - let source1 = dummy_source(10); - let source2 = dummy_source(10); - let mut mixed = crossfade( - source1, - source2, - Duration::from_secs(5) + Duration::from_nanos(1), - ); - assert_eq!(mixed.next(), Some(1.0)); - assert_eq!(mixed.next(), Some(2.0)); - assert_eq!(mixed.next(), Some(3.0)); - assert_eq!(mixed.next(), Some(4.0)); - assert_eq!(mixed.next(), Some(5.0)); - assert_eq!(mixed.next(), None); - } - - #[test] - fn test_crossfade() { - let source1 = dummy_source(10); - let source2 = Zero::new(1, 1); - let mixed = crossfade( - source1, - source2, - Duration::from_secs(5) + Duration::from_nanos(1), - ); - let result = mixed.collect::>(); - assert_eq!(result.len(), 5); - assert!(result - .iter() - .zip(vec![1.0, 2.0 * 0.8, 3.0 * 0.6, 4.0 * 0.4, 5.0 * 0.2]) - .all(|(a, b)| (a - b).abs() < 1e-6)); - } -} diff --git a/tests/crossfade.rs b/tests/crossfade.rs new file mode 100644 index 00000000..a9f1b766 --- /dev/null +++ b/tests/crossfade.rs @@ -0,0 +1,38 @@ +use std::time::Duration; + +use rodio::buffer::SamplesBuffer; +use rodio::source::Zero; +use rodio::Source; + +fn dummy_source(length: u8) -> SamplesBuffer { + let data: Vec = (1..=length).map(f32::from).collect(); + SamplesBuffer::new(1, 1, data) +} + +#[test] +fn test_crossfade_with_self() { + let source1 = dummy_source(10); + let source2 = dummy_source(10); + let mut mixed = + source1.take_crossfade_with(source2, Duration::from_secs(5) + Duration::from_nanos(1)); + assert_eq!(mixed.next(), Some(1.0)); + assert_eq!(mixed.next(), Some(2.0)); + assert_eq!(mixed.next(), Some(3.0)); + assert_eq!(mixed.next(), Some(4.0)); + assert_eq!(mixed.next(), Some(5.0)); + assert_eq!(mixed.next(), None); +} + +#[test] +fn test_crossfade() { + let source1 = dummy_source(10); + let source2 = Zero::new(1, 1); + let mixed = + source1.take_crossfade_with(source2, Duration::from_secs(5) + Duration::from_nanos(1)); + let result = mixed.collect::>(); + assert_eq!(result.len(), 5); + assert!(result + .iter() + .zip(vec![1.0, 2.0 * 0.8, 3.0 * 0.6, 4.0 * 0.4, 5.0 * 0.2]) + .all(|(a, b)| (a - b).abs() < 1e-6)); +} From f9231150de909a38b415d7269c79341b93ea71f5 Mon Sep 17 00:00:00 2001 From: dvdsk Date: Wed, 26 Feb 2025 18:55:52 +0100 Subject: [PATCH 12/25] adds integration tests for repeat --- src/source/mod.rs | 4 ++-- src/source/repeat.rs | 27 +++++++++------------ tests/repeat.rs | 56 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 69 insertions(+), 18 deletions(-) create mode 100644 tests/repeat.rs diff --git a/src/source/mod.rs b/src/source/mod.rs index 76f39336..ca98504b 100644 --- a/src/source/mod.rs +++ b/src/source/mod.rs @@ -159,7 +159,7 @@ pub use self::noise::{pink, white, PinkNoise, WhiteNoise}; pub trait Source: Iterator { /// Whether the value of `channels()` and/or `sample_rate()` have changed. /// This is true before the next call to `Source::next`. After that - /// `Source::next` call this will be false again. + /// `Source::next` call this will be false again. /// /// The value before the first call to next is not defined. fn parameters_changed(&self) -> bool; @@ -203,7 +203,7 @@ pub trait Source: Iterator { where Self: Sized, { - repeat::repeat(self) + Repeat::new(self) } /// Takes a certain duration of this source and then stops. diff --git a/src/source/repeat.rs b/src/source/repeat.rs index c5fe3792..7d4fe77f 100644 --- a/src/source/repeat.rs +++ b/src/source/repeat.rs @@ -7,18 +7,6 @@ use super::SeekError; use crate::common::{ChannelCount, SampleRate}; use crate::Source; -/// Internal function that builds a `Repeat` object. -pub fn repeat(input: I) -> Repeat -where - I: Source, -{ - let input = input.buffered(); - Repeat { - inner: PeekableSource::new(input.clone()), - next: input, - } -} - /// A source that repeats the given source. pub struct Repeat where @@ -28,10 +16,17 @@ where next: Buffered, } -impl Iterator for Repeat -where - I: Source, -{ +impl Repeat { + pub(crate) fn new(input: I) -> Repeat { + let input = input.buffered(); + Repeat { + inner: PeekableSource::new(input.clone()), + next: input, + } + } +} + +impl Iterator for Repeat { type Item = ::Item; #[inline] diff --git a/tests/repeat.rs b/tests/repeat.rs new file mode 100644 index 00000000..fcef6a54 --- /dev/null +++ b/tests/repeat.rs @@ -0,0 +1,56 @@ +use rodio::Source; +use test_support::{TestSource, TestSpan}; + +mod test_support; + +#[test] +fn frame_boundray_at_start_of_repeat() { + let source = TestSource::new() + .with_span(TestSpan::from_samples((0..10).map(|n| n as f32)).with_sample_count(10)) + .with_span(TestSpan::from_samples((10..20).map(|n| n as f32)).with_sample_count(10)); + + let mut repeating = source.clone().repeat_infinite(); + repeating.by_ref().take(source.len()).count(); + assert!(repeating.parameters_changed()); + + assert!(repeating.next().is_some()); + assert!(!repeating.parameters_changed()); +} + +#[test] +fn parameters_identical_on_second_run() { + let source = TestSource::new() + .with_span(TestSpan::from_samples((0..10).map(|n| n as f32)).with_sample_count(10)) + .with_span(TestSpan::from_samples((10..20).map(|n| n as f32)).with_sample_count(10)); + + let mut repeating = source.clone().repeat_infinite(); + + let mut first_run_params = Vec::new(); + let mut second_run_params = Vec::new(); + + for params in [&mut first_run_params, &mut second_run_params] { + for _ in 0..source.len() { + assert!(repeating.by_ref().next().is_some()); + params.push(( + repeating.parameters_changed(), + repeating.channels(), + repeating.sample_rate(), + )); + } + } + + assert_eq!(first_run_params, second_run_params); +} + +#[test] +fn same_samples_on_second_run() { + let source = TestSource::new() + .with_span(TestSpan::from_samples((0..10).map(|n| n as f32)).with_sample_count(10)) + .with_span(TestSpan::from_samples((10..20).map(|n| n as f32)).with_sample_count(10)); + + let mut repeating = source.clone().repeat_infinite(); + let first_run: Vec<_> = repeating.by_ref().take(source.len()).collect(); + let second_run: Vec<_> = repeating.take(source.len()).collect(); + + assert_eq!(first_run, second_run); +} From 0168f24d95ec5d77f22993e1330af2d55960050c Mon Sep 17 00:00:00 2001 From: dvdsk Date: Thu, 27 Feb 2025 11:26:41 +0100 Subject: [PATCH 13/25] add test and fix take_span --- src/source/mix.rs | 2 +- src/source/take_span.rs | 11 +++++------ tests/take_span.rs | 35 +++++++++++++++++++++++++++++++++++ tests/test_support/mod.rs | 27 +++++++++++++++++++++++---- 4 files changed, 64 insertions(+), 11 deletions(-) create mode 100644 tests/take_span.rs diff --git a/src/source/mix.rs b/src/source/mix.rs index bcb521aa..21837ddb 100644 --- a/src/source/mix.rs +++ b/src/source/mix.rs @@ -81,7 +81,7 @@ where { #[inline] fn parameters_changed(&self) -> bool { - self.input1.parameters_changed() || self.input2.parameters_changed() + false } #[inline] diff --git a/src/source/take_span.rs b/src/source/take_span.rs index d9a3320f..ca7c1a8a 100644 --- a/src/source/take_span.rs +++ b/src/source/take_span.rs @@ -7,6 +7,7 @@ use crate::Source; /// A source that truncates the given source to a certain duration. #[derive(Clone, Debug)] pub struct TakeSpan { + first: bool, input: I, } @@ -15,7 +16,7 @@ where I: Source, { pub fn new(input: I) -> Self { - Self { input } + Self { first: true, input } } /// Returns a mutable reference to the inner source. @@ -31,16 +32,14 @@ where } } -impl Iterator for TakeSpan -where - I: Source, -{ +impl Iterator for TakeSpan { type Item = ::Item; fn next(&mut self) -> Option<::Item> { - if self.input.parameters_changed() { + if self.input.parameters_changed() && !self.first { None } else { + self.first = false; let sample = self.input.next()?; Some(sample) } diff --git a/tests/take_span.rs b/tests/take_span.rs new file mode 100644 index 00000000..86bb97c1 --- /dev/null +++ b/tests/take_span.rs @@ -0,0 +1,35 @@ +use rodio::Source; +use test_support::{TestSource, TestSpan}; + +mod test_support; + +#[test] +fn param_changes_during_skip() { + let source = TestSource::new() + .with_span( + TestSpan::sample_indexes() + .with_sample_rate(10) + .with_channel_count(1) + .with_sample_count(10), + ) + .with_span( + TestSpan::sample_indexes() + .with_sample_rate(20) + .with_channel_count(2) + .with_sample_count(10), + ); + + let mut span_1 = source.take_span(); + assert_eq!(span_1.by_ref().take(9).count(), 9); + assert_eq!(span_1.channels(), 1); + assert_eq!(span_1.sample_rate(), 10); + assert!(span_1.by_ref().next().is_some()); + assert_eq!(span_1.by_ref().next(), None); + + let mut span_2 = span_1.into_inner().take_span(); + assert_eq!(span_2.by_ref().take(9).count(), 9); + assert_eq!(span_2.channels(), 2); + assert_eq!(span_2.sample_rate(), 20); + assert!(span_2.by_ref().next().is_some()); + assert_eq!(span_2.by_ref().next(), None); +} diff --git a/tests/test_support/mod.rs b/tests/test_support/mod.rs index fe67510b..0f01e839 100644 --- a/tests/test_support/mod.rs +++ b/tests/test_support/mod.rs @@ -17,12 +17,14 @@ pub enum SampleSource { }, Silence, List(Vec), + SampleIndex, } impl SampleSource { fn get( &mut self, pos: usize, + pos_in_source: usize, sample_rate: SampleRate, channels: ChannelCount, numb_samples: usize, @@ -39,6 +41,8 @@ impl SampleSource { .collect(); samples.get(pos).copied() } + SampleSource::SampleIndex if pos < numb_samples => Some(pos_in_source as f32), + SampleSource::SampleIndex => None, SampleSource::SignalGen { samples, .. } => samples.get(pos).copied(), SampleSource::Silence { .. } if pos < numb_samples => Some(0.0), SampleSource::Silence { .. } => None, @@ -70,6 +74,13 @@ impl TestSpan { channels: 1, } } + pub fn sample_indexes() -> TestSpanBuilder { + TestSpanBuilder { + sample_source: SampleSource::SampleIndex, + sample_rate: 1, + channels: 1, + } + } pub fn sine(frequency: f32) -> TestSpanBuilder { TestSpanBuilder { sample_source: SampleSource::SignalGen { @@ -101,9 +112,14 @@ impl TestSpan { } } - fn get(&mut self, pos: usize) -> Option { - self.sample_source - .get(pos, self.sample_rate, self.channels, self.numb_samples) + fn get(&mut self, pos: usize, pos_in_source: usize) -> Option { + self.sample_source.get( + pos, + pos_in_source, + self.sample_rate, + self.channels, + self.numb_samples, + ) } pub fn len(&self) -> usize { @@ -211,6 +227,7 @@ pub struct TestSource { pub spans: Vec, current_span: usize, pos_in_span: usize, + pos_in_source: usize, parameters_changed: bool, } @@ -221,6 +238,7 @@ impl TestSource { current_span: 0, pos_in_span: 0, parameters_changed: false, + pos_in_source: 0, } } pub fn with_span(mut self, span: TestSpan) -> Self { @@ -234,7 +252,8 @@ impl Iterator for TestSource { fn next(&mut self) -> Option { let current_span = self.spans.get_mut(self.current_span)?; - let sample = current_span.get(self.pos_in_span)?; + let sample = current_span.get(self.pos_in_span, self.pos_in_source)?; + self.pos_in_source += 1; self.pos_in_span += 1; // if span is out of samples From 6902aaa751a5b2b7886f9de043d824857ed5eabd Mon Sep 17 00:00:00 2001 From: dvdsk Date: Thu, 27 Feb 2025 13:25:51 +0100 Subject: [PATCH 14/25] test and fix take_duration fadeout --- CHANGELOG.md | 2 ++ src/source/crossfade.rs | 3 +-- src/source/linear_ramp.rs | 2 +- src/source/mix.rs | 2 +- src/source/take_duration.rs | 29 +++++++++++++++++++++++------ tests/take_duration.rs | 32 ++++++++++++++++++++++++++++++++ tests/take_span.rs | 4 ++-- tests/test_support/mod.rs | 21 ++++++++++++++++----- 8 files changed, 78 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cda9b4bf..a5701a84 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Minimal builds without `cpal` audio output are now supported. See `README.md` for instructions. (#349) - Added `Sample::is_zero()` method for checking zero samples. +- Added `TakeDuration::with_fadeout` similar to `TakeDuration::fadeout` but returns `Self` ### Changed - Breaking: `OutputStreamBuilder` should now be used to initialize an audio output stream. @@ -34,6 +35,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Breaking: Sources wrapping an existing source had a public factory method which is now removed. Something like: `source::amplify(unamplified, 1.2)` must now be written as `unamplified.amplify(1.2)`. +- Breaking: `TakeDuration::set_filter_fadeout()` has been removed. Use `TakeDuration.fadeout(true)`. - `SignalGenerator`'s `Function` is now Copy. diff --git a/src/source/crossfade.rs b/src/source/crossfade.rs index a67957f0..91315648 100644 --- a/src/source/crossfade.rs +++ b/src/source/crossfade.rs @@ -16,8 +16,7 @@ where I1: Source, I2: Source, { - let mut input_fadeout = input_fadeout.take_duration(duration); - input_fadeout.set_filter_fadeout(); + let input_fadeout = input_fadeout.take_duration(duration).with_fadeout(true); let input_fadein = input_fadein.take_duration(duration).fade_in(duration); input_fadeout.mix(input_fadein) } diff --git a/src/source/linear_ramp.rs b/src/source/linear_ramp.rs index b14a9e56..f1fb521f 100644 --- a/src/source/linear_ramp.rs +++ b/src/source/linear_ramp.rs @@ -143,7 +143,7 @@ mod tests { use crate::Sample; /// Create a SamplesBuffer of identical samples with value `value`. - /// Returned buffer is one channel and has a sample rate of 1 hz. + /// Returned buffer is one channel and has a sample rate of 1 Hz. fn const_source(length: u8, value: Sample) -> SamplesBuffer { let data: Vec = (1..=length).map(|_| value).collect(); SamplesBuffer::new(1, 1, data) diff --git a/src/source/mix.rs b/src/source/mix.rs index 21837ddb..5e59a276 100644 --- a/src/source/mix.rs +++ b/src/source/mix.rs @@ -44,7 +44,7 @@ where let s1 = self.input1.next(); let s2 = self.input2.next(); - match (s1, s2) { + match dbg!((s1, s2)) { (Some(s1), Some(s2)) => Some(s1 + s2), (Some(s1), None) => Some(s1), (None, Some(s2)) => Some(s2), diff --git a/src/source/take_duration.rs b/src/source/take_duration.rs index 64a07af3..b3f888db 100644 --- a/src/source/take_duration.rs +++ b/src/source/take_duration.rs @@ -64,10 +64,17 @@ where self.input } + /// Optionally the truncated source end with a FadeOut. The fade-out + /// covers the entire length of the take source. + pub fn fadeout(&mut self, enabled: bool) { + self.fadeout = enabled; + } + /// Make the truncated source end with a FadeOut. The fade-out covers the /// entire length of the take source. - pub fn set_filter_fadeout(&mut self) { - self.fadeout = true; + pub fn with_fadeout(mut self, enabled: bool) -> Self { + self.fadeout = enabled; + self } /// Remove any filter set. @@ -98,6 +105,14 @@ where type Item = ::Item; // implementation is adapted of skip_duration + // + // if tuples are frames you could define fadeout as this: + // [(1.0, 1.0), (1.0, 1.0), (1.0, 1.0)] + // -> [(1.0, 1.0), (0.5, 0.5), (0.0, 0.0)] + // instead because its simpler, faster and what previous rodio versions did we do: + // [(1.0, 1.0), (1.0, 1.0), (1.0, 1.0)] + // -> [(1.0, .83), (.66, 0.5), (.33, .16)] + // at normal sample_rates you do not hear a difference fn next(&mut self) -> Option<::Item> { if self.input.parameters_changed() { self.remaining_ns -= self.duration_taken(); @@ -114,13 +129,15 @@ where return None; }; - self.samples_taken += 1; - if self.fadeout { + let ret = if self.fadeout { let total = self.requested_duration.as_nanos() as u64; - Some(sample * self.remaining_ns as f32 / total as f32) + let remaining = self.remaining_ns - self.duration_taken(); + Some(sample * remaining as f32 / total as f32) } else { Some(sample) - } + }; + self.samples_taken += 1; + ret } } diff --git a/tests/take_duration.rs b/tests/take_duration.rs index b95139fa..d6f84458 100644 --- a/tests/take_duration.rs +++ b/tests/take_duration.rs @@ -19,6 +19,9 @@ fn ends_on_frame_boundary(#[values(1.483, 2.999)] seconds_to_skip: f32) { let got = source .clone() .take_duration(Duration::from_secs_f32(seconds_to_skip)) + // fadeout enables extra logic, run it too to check for bounds/overflow issues + .with_fadeout(true) + .count(); assert!(got % source.channels() as usize == 0) } @@ -49,6 +52,7 @@ fn param_changes_during_skip(#[values(6, 11)] seconds_to_skip: u64) { let took = source .clone() .take_duration(Duration::from_secs(seconds_to_skip)) + .with_fadeout(true) .count(); let spans = source.spans; @@ -68,6 +72,33 @@ fn param_changes_during_skip(#[values(6, 11)] seconds_to_skip: u64) { ); } +#[test] +fn fadeout() { + let span_duration = Duration::from_secs(5); + let source = TestSource::new() + .with_span( + TestSpan::ones() + .with_sample_rate(5) + .with_channel_count(1) + .with_exact_duration(span_duration), + ) + .with_span( + TestSpan::ones() + .with_sample_rate(5) + .with_channel_count(2) + .with_exact_duration(span_duration), + ); + + let fade_out = source + .take_duration(span_duration.mul_f32(1.5)) + .with_fadeout(true) + .collect::>(); + dbg!(&fade_out); + assert_eq!(fade_out.first(), Some(&1.0)); + // fade_out ends the step before zero + assert!(fade_out.last().unwrap() > &0.0); +} + #[rstest] fn samples_taken( #[values(1, 2, 4)] channels: ChannelCount, @@ -86,6 +117,7 @@ fn samples_taken( let samples = test_buffer .take_duration(Duration::from_secs(seconds_to_take as u64)) + .with_fadeout(true) .count(); let seconds_we_can_take = seconds_to_take.min(seconds); diff --git a/tests/take_span.rs b/tests/take_span.rs index 86bb97c1..5d3e6f4b 100644 --- a/tests/take_span.rs +++ b/tests/take_span.rs @@ -7,13 +7,13 @@ mod test_support; fn param_changes_during_skip() { let source = TestSource::new() .with_span( - TestSpan::sample_indexes() + TestSpan::sample_counter() .with_sample_rate(10) .with_channel_count(1) .with_sample_count(10), ) .with_span( - TestSpan::sample_indexes() + TestSpan::sample_counter() .with_sample_rate(20) .with_channel_count(2) .with_sample_count(10), diff --git a/tests/test_support/mod.rs b/tests/test_support/mod.rs index 0f01e839..68011e94 100644 --- a/tests/test_support/mod.rs +++ b/tests/test_support/mod.rs @@ -18,6 +18,7 @@ pub enum SampleSource { Silence, List(Vec), SampleIndex, + Ones, } impl SampleSource { @@ -29,6 +30,10 @@ impl SampleSource { channels: ChannelCount, numb_samples: usize, ) -> Option { + if pos >= numb_samples { + return None; + } + match self { SampleSource::SignalGen { function, @@ -41,12 +46,11 @@ impl SampleSource { .collect(); samples.get(pos).copied() } - SampleSource::SampleIndex if pos < numb_samples => Some(pos_in_source as f32), - SampleSource::SampleIndex => None, SampleSource::SignalGen { samples, .. } => samples.get(pos).copied(), - SampleSource::Silence { .. } if pos < numb_samples => Some(0.0), - SampleSource::Silence { .. } => None, SampleSource::List(list) => list.get(pos).copied(), + SampleSource::SampleIndex => Some(pos_in_source as f32), + SampleSource::Silence { .. } => Some(0.0), + SampleSource::Ones => Some(1.0), } } } @@ -74,7 +78,14 @@ impl TestSpan { channels: 1, } } - pub fn sample_indexes() -> TestSpanBuilder { + pub fn ones() -> TestSpanBuilder { + TestSpanBuilder { + sample_source: SampleSource::Ones, + sample_rate: 1, + channels: 1, + } + } + pub fn sample_counter() -> TestSpanBuilder { TestSpanBuilder { sample_source: SampleSource::SampleIndex, sample_rate: 1, From 9090c557a9ada40354b74f66fd12dbc693fdd63c Mon Sep 17 00:00:00 2001 From: dvdsk Date: Thu, 27 Feb 2025 14:38:13 +0100 Subject: [PATCH 15/25] implement parameters_changed for wav decoder --- src/decoder/wav.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/decoder/wav.rs b/src/decoder/wav.rs index 22751773..cb46f4ef 100644 --- a/src/decoder/wav.rs +++ b/src/decoder/wav.rs @@ -148,8 +148,7 @@ where { #[inline] fn parameters_changed(&self) -> bool { - todo!() - // None + false // wav never changes sample rate or channel count } #[inline] From c9e7795d7ddc90b614cc12b4af043d772217e060 Mon Sep 17 00:00:00 2001 From: dvdsk Date: Thu, 27 Feb 2025 14:39:59 +0100 Subject: [PATCH 16/25] updates pipeline bench to new API changes --- benches/pipeline.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/benches/pipeline.rs b/benches/pipeline.rs index e4a0d8a9..c60c0f9c 100644 --- a/benches/pipeline.rs +++ b/benches/pipeline.rs @@ -13,7 +13,7 @@ fn main() { #[divan::bench] fn long(bencher: Bencher) { bencher.with_inputs(|| music_wav()).bench_values(|source| { - let mut take_dur = source + let effects_applied = source .high_pass(300) .amplify(1.2) .speed(0.9) @@ -25,9 +25,8 @@ fn long(bencher: Bencher) { ) .delay(Duration::from_secs_f32(0.5)) .fade_in(Duration::from_secs_f32(2.0)) - .take_duration(Duration::from_secs(10)); - take_dur.set_filter_fadeout(); - let effects_applied = take_dur + .take_duration(Duration::from_secs(10)) + .with_fadeout(true) .buffered() .reverb(Duration::from_secs_f32(0.05), 0.3) .skippable(); From 1fddd8975114afecdfb4c444c6e555aa35140ece Mon Sep 17 00:00:00 2001 From: dvdsk Date: Thu, 27 Feb 2025 14:43:34 +0100 Subject: [PATCH 17/25] removes left over debug prints --- src/source/buffered.rs | 7 ++----- src/source/mix.rs | 2 +- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/source/buffered.rs b/src/source/buffered.rs index a5b669bb..441ffc0d 100644 --- a/src/source/buffered.rs +++ b/src/source/buffered.rs @@ -53,7 +53,6 @@ impl Buffered { Span::End => next_span_ptr.clone(), Span::Input(input) => { let input = input.lock().unwrap().take().unwrap(); - dbg!(); extract(input) } }; @@ -78,7 +77,7 @@ where let current_sample; let advance_span; - match dbg!(&*self.current_span) { + match &*self.current_span { Span::Data(SpanData { data, .. }) => { current_sample = Some(data[self.position_in_span]); self.position_in_span += 1; @@ -94,7 +93,6 @@ where }; if advance_span { - dbg!(); self.parameters_changed = true; self.next_span(); } else { @@ -239,7 +237,6 @@ where loop { let Some(sample) = input.next() else { break }; data.push(sample); - dbg!(sample); if input.parameters_changed() { break; } @@ -248,7 +245,7 @@ where } } - if dbg!(data.is_empty()) { + if data.is_empty() { return Arc::new(Span::End); } diff --git a/src/source/mix.rs b/src/source/mix.rs index 5e59a276..21837ddb 100644 --- a/src/source/mix.rs +++ b/src/source/mix.rs @@ -44,7 +44,7 @@ where let s1 = self.input1.next(); let s2 = self.input2.next(); - match dbg!((s1, s2)) { + match (s1, s2) { (Some(s1), Some(s2)) => Some(s1 + s2), (Some(s1), None) => Some(s1), (None, Some(s2)) => Some(s2), From 5e7d8d81be7e02761b11111119e26c715bebd923 Mon Sep 17 00:00:00 2001 From: dvdsk Date: Fri, 28 Feb 2025 12:22:26 +0100 Subject: [PATCH 18/25] This makes ChannelCount NonZero and channels not zero asserts I ran into a lot of bugs while adding tests that had to do with channel being set to zero somewhere. While this change makes the API slightly less easy to use it prevents very hard to debug crashes/underflows etc. Performance might drop in decoders, the current implementation makes the bound check every time `channels` is called which is once per span. This could be cached to alleviate that. --- benches/pipeline.rs | 4 ++- benches/shared.rs | 2 +- examples/mix_multiple_sources.rs | 3 +- src/buffer.rs | 32 ++++++++---------- src/common.rs | 6 ++-- src/conversions/channels.rs | 47 ++++++++++++++------------- src/conversions/sample_rate.rs | 56 ++++++++++++++++---------------- src/decoder/flac.rs | 11 +++++-- src/decoder/mod.rs | 3 +- src/decoder/symphonia.rs | 13 ++++++-- src/decoder/vorbis.rs | 3 +- src/decoder/wav.rs | 7 ++-- src/math.rs | 25 ++++++++++++++ src/mixer.rs | 35 ++++++++++---------- src/queue.rs | 16 +++++---- src/sink.rs | 17 +++++----- src/source/buffered.rs | 12 ++++--- src/source/channel_volume.rs | 15 +++++---- src/source/chirp.rs | 3 +- src/source/delay.rs | 2 +- src/source/empty.rs | 3 +- src/source/empty_callback.rs | 3 +- src/source/from_iter.rs | 10 +++--- src/source/linear_ramp.rs | 9 ++--- src/source/mod.rs | 1 + src/source/pausable.rs | 4 +-- src/source/periodic.rs | 8 +++-- src/source/position.rs | 5 ++- src/source/sawtooth.rs | 3 +- src/source/signal_generator.rs | 3 +- src/source/sine.rs | 3 +- src/source/skip_duration.rs | 5 ++- src/source/square.rs | 3 +- src/source/take_duration.rs | 6 ++-- src/source/take_samples.rs | 2 +- src/source/take_span.rs | 2 -- src/source/triangle.rs | 3 +- src/static_buffer.rs | 18 ++++------ src/stream.rs | 18 +++++----- src/wav_output.rs | 7 ++-- tests/buffered.rs | 4 +-- tests/crossfade.rs | 5 +-- tests/peekable.rs | 4 +-- tests/position.rs | 13 ++++++-- tests/seek.rs | 28 +++++++--------- tests/skip_duration.rs | 14 ++++---- tests/take_duration.rs | 14 ++++---- tests/take_span.rs | 4 +-- tests/test_support/mod.rs | 18 +++++----- 49 files changed, 297 insertions(+), 235 deletions(-) diff --git a/benches/pipeline.rs b/benches/pipeline.rs index c60c0f9c..ac0c3be0 100644 --- a/benches/pipeline.rs +++ b/benches/pipeline.rs @@ -1,6 +1,7 @@ use std::time::Duration; use divan::Bencher; +use rodio::ChannelCount; use rodio::{source::UniformSourceIterator, Source}; mod shared; @@ -30,7 +31,8 @@ fn long(bencher: Bencher) { .buffered() .reverb(Duration::from_secs_f32(0.05), 0.3) .skippable(); - let resampled = UniformSourceIterator::new(effects_applied, 2, 40_000); + let resampled = + UniformSourceIterator::new(effects_applied, ChannelCount::new(2).unwrap(), 40_000); resampled.for_each(divan::black_box_drop) }) } diff --git a/benches/shared.rs b/benches/shared.rs index 00d5042f..43708542 100644 --- a/benches/shared.rs +++ b/benches/shared.rs @@ -6,7 +6,7 @@ use rodio::{ChannelCount, Sample, SampleRate, Source}; pub struct TestSource { samples: vec::IntoIter, - channels: u16, + channels: ChannelCount, sample_rate: u32, total_duration: Duration, } diff --git a/examples/mix_multiple_sources.rs b/examples/mix_multiple_sources.rs index 065b6585..a6d0a962 100644 --- a/examples/mix_multiple_sources.rs +++ b/examples/mix_multiple_sources.rs @@ -1,11 +1,12 @@ use rodio::mixer; use rodio::source::{SineWave, Source}; use std::error::Error; +use std::num::NonZero; use std::time::Duration; fn main() -> Result<(), Box> { // Construct a dynamic controller and mixer, stream_handle, and sink. - let (controller, mixer) = mixer::mixer(2, 44_100); + let (controller, mixer) = mixer::mixer(NonZero::new(2).unwrap(), 44_100); let stream_handle = rodio::OutputStreamBuilder::open_default_stream()?; let sink = rodio::Sink::connect_new(&stream_handle.mixer()); diff --git a/src/buffer.rs b/src/buffer.rs index 463106d0..fa2cddf0 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -30,7 +30,6 @@ impl SamplesBuffer { /// /// # Panic /// - /// - Panics if the number of channels is zero. /// - Panics if the samples rate is zero. /// - Panics if the length of the buffer is larger than approximately 16 billion elements. /// This is because the calculation of the duration would overflow. @@ -39,13 +38,12 @@ impl SamplesBuffer { where D: Into>, { - 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() / sample_rate as u64 - / channels as u64; + / channels.get() as u64; let duration = Duration::new( duration_ns / 1_000_000_000, (duration_ns % 1_000_000_000) as u32, @@ -89,14 +87,14 @@ impl Source for SamplesBuffer { // and due to the constant sample_rate we can jump to the right // sample directly. - let curr_channel = self.pos % self.channels() as usize; - let new_pos = pos.as_secs_f32() * self.sample_rate() as f32 * self.channels() as f32; + let curr_channel = self.pos % self.channels().get() as usize; + let new_pos = pos.as_secs_f32() * self.sample_rate() as f32 * self.channels().get() as f32; // saturate pos at the end of the source let new_pos = new_pos as usize; let new_pos = new_pos.min(self.data.len()); // make sure the next sample is for the right channel - let new_pos = new_pos.next_multiple_of(self.channels() as usize); + let new_pos = new_pos.next_multiple_of(self.channels().get() as usize); let new_pos = new_pos - curr_channel; self.pos = new_pos; @@ -123,28 +121,23 @@ impl Iterator for SamplesBuffer { #[cfg(test)] mod tests { use crate::buffer::SamplesBuffer; + use crate::math::ch; use crate::source::Source; #[test] fn basic() { - let _ = SamplesBuffer::new(1, 44100, vec![0.0, 0.0, 0.0, 0.0, 0.0, 0.0]); - } - - #[test] - #[should_panic] - fn panic_if_zero_channels() { - SamplesBuffer::new(0, 44100, vec![0.0, 0.0, 0.0, 0.0, 0.0, 0.0]); + let _ = SamplesBuffer::new(ch!(1), 44100, vec![0.0, 0.0, 0.0, 0.0, 0.0, 0.0]); } #[test] #[should_panic] fn panic_if_zero_sample_rate() { - SamplesBuffer::new(1, 0, vec![0.0, 0.0, 0.0, 0.0, 0.0, 0.0]); + SamplesBuffer::new(ch!(1), 0, vec![0.0, 0.0, 0.0, 0.0, 0.0, 0.0]); } #[test] fn duration_basic() { - let buf = SamplesBuffer::new(2, 2, vec![0.0, 0.0, 0.0, 0.0, 0.0, 0.0]); + let buf = SamplesBuffer::new(ch!(2), 2, vec![0.0, 0.0, 0.0, 0.0, 0.0, 0.0]); let dur = buf.total_duration().unwrap(); assert_eq!(dur.as_secs(), 1); assert_eq!(dur.subsec_nanos(), 500_000_000); @@ -152,7 +145,7 @@ mod tests { #[test] fn iteration() { - let mut buf = SamplesBuffer::new(1, 44100, vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]); + let mut buf = SamplesBuffer::new(ch!(1), 44100, vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]); assert_eq!(buf.next(), Some(1.0)); assert_eq!(buf.next(), Some(2.0)); assert_eq!(buf.next(), Some(3.0)); @@ -172,7 +165,7 @@ mod tests { #[test] fn channel_order_stays_correct() { const SAMPLE_RATE: SampleRate = 100; - const CHANNELS: ChannelCount = 2; + const CHANNELS: ChannelCount = ch!(2); let mut buf = SamplesBuffer::new( CHANNELS, SAMPLE_RATE, @@ -182,7 +175,10 @@ mod tests { .collect::>(), ); buf.try_seek(Duration::from_secs(5)).unwrap(); - assert_eq!(buf.next(), Some(5.0 * SAMPLE_RATE as f32 * CHANNELS as f32)); + assert_eq!( + buf.next(), + Some(5.0 * SAMPLE_RATE as f32 * CHANNELS.get() as f32) + ); assert!(buf.next().is_some_and(|s| s.trunc() as i32 % 2 == 1)); assert!(buf.next().is_some_and(|s| s.trunc() as i32 % 2 == 0)); diff --git a/src/common.rs b/src/common.rs index 12b3a94d..bf3f7acb 100644 --- a/src/common.rs +++ b/src/common.rs @@ -1,8 +1,10 @@ +use std::num::NonZero; + /// Stream sample rate (a frame rate or samples per second per channel). pub type SampleRate = u32; -/// Number of channels in a stream. -pub type ChannelCount = u16; +/// Number of channels in a stream. Can never be Zero +pub type ChannelCount = NonZero; /// Represents value of a single sample. /// Silence corresponds to the value `0.0`. The expected amplitude range is -1.0...1.0. diff --git a/src/conversions/channels.rs b/src/conversions/channels.rs index 17887673..980ebf98 100644 --- a/src/conversions/channels.rs +++ b/src/conversions/channels.rs @@ -11,7 +11,7 @@ where from: ChannelCount, to: ChannelCount, sample_repeat: Option, - next_output_sample_pos: ChannelCount, + next_output_sample_pos: u16, } impl ChannelCountConverter @@ -26,9 +26,6 @@ where /// #[inline] pub fn new(input: I, from: ChannelCount, to: ChannelCount) -> ChannelCountConverter { - assert!(from >= 1); - assert!(to >= 1); - ChannelCountConverter { input, from, @@ -65,7 +62,7 @@ where self.sample_repeat = value; value } - x if x < self.from => self.input.next(), + x if x < self.from.get() => self.input.next(), 1 => self.sample_repeat, _ => Some(0.0), }; @@ -74,11 +71,11 @@ where self.next_output_sample_pos += 1; } - if self.next_output_sample_pos == self.to { + if self.next_output_sample_pos == self.to.get() { self.next_output_sample_pos = 0; if self.from > self.to { - for _ in self.to..self.from { + for _ in self.to.get()..self.from.get() { self.input.next(); // discarding extra input } } @@ -91,13 +88,13 @@ where fn size_hint(&self) -> (usize, Option) { let (min, max) = self.input.size_hint(); - let consumed = std::cmp::min(self.from, self.next_output_sample_pos) as usize; + let consumed = std::cmp::min(self.from.get(), self.next_output_sample_pos) as usize; - let min = ((min + consumed) / self.from as usize * self.to as usize) + let min = ((min + consumed) / self.from.get() as usize * self.to.get() as usize) .saturating_sub(self.next_output_sample_pos as usize); let max = max.map(|max| { - ((max + consumed) / self.from as usize * self.to as usize) + ((max + consumed) / self.from.get() as usize * self.to.get() as usize) .saturating_sub(self.next_output_sample_pos as usize) }); @@ -111,31 +108,37 @@ impl ExactSizeIterator for ChannelCountConverter where I: ExactSizeIterato mod test { use super::ChannelCountConverter; use crate::common::ChannelCount; + use crate::math::ch; use crate::Sample; #[test] fn remove_channels() { let input = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]; - let output = ChannelCountConverter::new(input.into_iter(), 3, 2).collect::>(); + let output = + ChannelCountConverter::new(input.into_iter(), ch!(3), ch!(2)).collect::>(); assert_eq!(output, [1.0, 2.0, 4.0, 5.0]); let input = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0]; - let output = ChannelCountConverter::new(input.into_iter(), 4, 1).collect::>(); + let output = + ChannelCountConverter::new(input.into_iter(), ch!(4), ch!(1)).collect::>(); assert_eq!(output, [1.0, 5.0]); } #[test] fn add_channels() { let input = vec![1.0, 2.0, 3.0, 4.0]; - let output = ChannelCountConverter::new(input.into_iter(), 1, 2).collect::>(); + let output = + ChannelCountConverter::new(input.into_iter(), ch!(1), ch!(2)).collect::>(); assert_eq!(output, [1.0, 1.0, 2.0, 2.0, 3.0, 3.0, 4.0, 4.0]); let input = vec![1.0, 2.0]; - let output = ChannelCountConverter::new(input.into_iter(), 1, 4).collect::>(); + let output = + ChannelCountConverter::new(input.into_iter(), ch!(1), ch!(4)).collect::>(); assert_eq!(output, [1.0, 1.0, 0.0, 0.0, 2.0, 2.0, 0.0, 0.0]); let input = vec![1.0, 2.0, 3.0, 4.0]; - let output = ChannelCountConverter::new(input.into_iter(), 2, 4).collect::>(); + let output = + ChannelCountConverter::new(input.into_iter(), ch!(2), ch!(4)).collect::>(); assert_eq!(output, [1.0, 2.0, 0.0, 0.0, 3.0, 4.0, 0.0, 0.0]); } @@ -152,24 +155,24 @@ mod test { assert_eq!(converter.size_hint(), (0, Some(0))); } - test(&[1.0, 2.0, 3.0], 1, 2); - test(&[1.0, 2.0, 3.0, 4.0], 2, 4); - test(&[1.0, 2.0, 3.0, 4.0], 4, 2); - test(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], 3, 8); - test(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0], 4, 1); + test(&[1.0, 2.0, 3.0], ch!(1), ch!(2)); + test(&[1.0, 2.0, 3.0, 4.0], ch!(2), ch!(4)); + test(&[1.0, 2.0, 3.0, 4.0], ch!(4), ch!(2)); + test(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0], ch!(3), ch!(8)); + test(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0], ch!(4), ch!(1)); } #[test] fn len_more() { let input = vec![1.0, 2.0, 3.0, 4.0]; - let output = ChannelCountConverter::new(input.into_iter(), 2, 3); + let output = ChannelCountConverter::new(input.into_iter(), ch!(2), ch!(3)); assert_eq!(output.len(), 6); } #[test] fn len_less() { let input = vec![1.0, 2.0, 3.0, 4.0]; - let output = ChannelCountConverter::new(input.into_iter(), 2, 1); + let output = ChannelCountConverter::new(input.into_iter(), ch!(2), ch!(1)); assert_eq!(output.len(), 2); } } diff --git a/src/conversions/sample_rate.rs b/src/conversions/sample_rate.rs index da85f52e..363f8ca9 100644 --- a/src/conversions/sample_rate.rs +++ b/src/conversions/sample_rate.rs @@ -54,7 +54,6 @@ where to: SampleRate, num_channels: ChannelCount, ) -> SampleRateConverter { - assert!(num_channels >= 1); assert!(from >= 1); assert!(to >= 1); @@ -64,11 +63,11 @@ where } else { let first = input .by_ref() - .take(num_channels as usize) + .take(num_channels.get() as usize) .collect::>(); let next = input .by_ref() - .take(num_channels as usize) + .take(num_channels.get() as usize) .collect::>(); (first, next) }; @@ -85,7 +84,7 @@ where next_output_span_pos_in_chunk: 0, current_span: first_samples, next_frame: next_samples, - output_buffer: Vec::with_capacity(num_channels as usize - 1), + output_buffer: Vec::with_capacity(num_channels.get() as usize - 1), } } @@ -106,7 +105,7 @@ where mem::swap(&mut self.current_span, &mut self.next_frame); self.next_frame.clear(); - for _ in 0..self.channels { + for _ in 0..self.channels.get() { if let Some(i) = self.input.next() { self.next_frame.push(i); } else { @@ -213,7 +212,7 @@ where // removing the samples of the current chunk that have not yet been read let samples_after_chunk = samples_after_chunk.saturating_sub( self.from.saturating_sub(self.current_span_pos_in_chunk + 2) as usize - * usize::from(self.channels), + * usize::from(self.channels.get()), ); // calculating the number of samples after the transformation // TODO: this is wrong here \|/ @@ -222,7 +221,7 @@ where // `samples_current_chunk` will contain the number of samples remaining to be output // for the chunk currently being processed let samples_current_chunk = (self.to - self.next_output_span_pos_in_chunk) as usize - * usize::from(self.channels); + * usize::from(self.channels.get()); samples_current_chunk + samples_after_chunk + self.output_buffer.len() }; @@ -242,14 +241,15 @@ impl ExactSizeIterator for SampleRateConverter where I: ExactSizeIterator< mod test { use super::SampleRateConverter; use crate::common::{ChannelCount, SampleRate}; + use crate::math::ch; use crate::Sample; use core::time::Duration; use quickcheck::{quickcheck, TestResult}; quickcheck! { /// Check that resampling an empty input produces no output. - fn empty(from: u16, to: u16, channels: u8) -> TestResult { - if channels == 0 || channels > 128 + fn empty(from: u16, to: u16, channels: ChannelCount) -> TestResult { + if channels.get() > 128 || from == 0 || to == 0 { @@ -260,7 +260,7 @@ mod test { let input: Vec = Vec::new(); let output = - SampleRateConverter::new(input.into_iter(), from, to, channels as ChannelCount) + SampleRateConverter::new(input.into_iter(), from, to, channels) .collect::>(); assert_eq!(output, []); @@ -268,13 +268,13 @@ mod test { } /// Check that resampling to the same rate does not change the signal. - fn identity(from: u16, channels: u8, input: Vec) -> TestResult { - if channels == 0 || channels > 128 || from == 0 { return TestResult::discard(); } + fn identity(from: u16, channels: ChannelCount, input: Vec) -> TestResult { + if channels.get() > 128 || from == 0 { return TestResult::discard(); } let from = from as SampleRate; let input = Vec::from_iter(input.iter().map(|x| *x as Sample)); let output = - SampleRateConverter::new(input.clone().into_iter(), from, from, channels as ChannelCount) + SampleRateConverter::new(input.clone().into_iter(), from, from, channels) .collect::>(); TestResult::from_bool(input == output) @@ -282,8 +282,8 @@ 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 > 48000 { + fn divide_sample_rate(to: u16, k: u16, input: Vec, channels: ChannelCount) -> TestResult { + if k == 0 || channels.get() > 128 || to == 0 || to > 48000 { return TestResult::discard(); } let input = Vec::from_iter(input.iter().map(|x| *x as Sample)); @@ -293,24 +293,24 @@ mod test { // Truncate the input, so it contains an integer number of spans. let input = { - let ns = channels as usize; + let ns = channels.get() as usize; let mut i = input; i.truncate(ns * (i.len() / ns)); i }; let output = - SampleRateConverter::new(input.clone().into_iter(), from, to, channels as ChannelCount) + SampleRateConverter::new(input.clone().into_iter(), from, to, channels) .collect::>(); - TestResult::from_bool(input.chunks_exact(channels.into()) + TestResult::from_bool(input.chunks_exact(channels.get().into()) .step_by(k as usize).collect::>().concat() == output) } /// Check that, after multiplying the sample rate by k, every k-th /// sample in the output matches exactly with the input. - fn multiply_sample_rate(from: u16, k: u8, input: Vec, channels: u8) -> TestResult { - if k == 0 || channels == 0 || channels > 128 || from == 0 { + fn multiply_sample_rate(from: u16, k: u8, input: Vec, channels: ChannelCount) -> TestResult { + if k == 0 || channels.get() > 128 || from == 0 { return TestResult::discard(); } let input = Vec::from_iter(input.iter().map(|x| *x as Sample)); @@ -320,24 +320,24 @@ mod test { // Truncate the input, so it contains an integer number of spans. let input = { - let ns = channels as usize; + let ns = channels.get() as usize; let mut i = input; i.truncate(ns * (i.len() / ns)); i }; let output = - SampleRateConverter::new(input.clone().into_iter(), from, to, channels as ChannelCount) + SampleRateConverter::new(input.clone().into_iter(), from, to, channels) .collect::>(); TestResult::from_bool(input == - output.chunks_exact(channels.into()) + output.chunks_exact(channels.get().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. + /// except by a negligible amount (± 1ms). Reproduces #316. /// Ignored, pending a bug fix. fn preserve_durations(d: Duration, freq: f32, to: SampleRate) -> TestResult { if to == 0 { return TestResult::discard(); } @@ -348,7 +348,7 @@ mod test { let from = source.sample_rate(); let resampled = - SampleRateConverter::new(source, from, to, 1); + SampleRateConverter::new(source, from, to, ch!(1)); let duration = Duration::from_secs_f32(resampled.count() as f32 / to as f32); @@ -360,7 +360,7 @@ mod test { #[test] fn upsample() { let input = vec![2.0, 16.0, 4.0, 18.0, 6.0, 20.0, 8.0, 22.0]; - let output = SampleRateConverter::new(input.into_iter(), 2000, 3000, 2); + let output = SampleRateConverter::new(input.into_iter(), 2000, 3000, ch!(2)); assert_eq!(output.len(), 12); // Test the source's Iterator::size_hint() let output = output.map(|x| x.trunc()).collect::>(); @@ -373,7 +373,7 @@ mod test { #[test] fn upsample2() { let input = vec![1.0, 14.0]; - let output = SampleRateConverter::new(input.into_iter(), 1000, 7000, 1); + let output = SampleRateConverter::new(input.into_iter(), 1000, 7000, ch!(1)); let size_estimation = output.len(); let output = output.map(|x| x.trunc()).collect::>(); assert_eq!(output, [1.0, 2.0, 4.0, 6.0, 8.0, 10.0, 12.0, 14.0]); @@ -383,7 +383,7 @@ mod test { #[test] fn downsample() { let input = Vec::from_iter((0..17).map(|x| x as Sample)); - let output = SampleRateConverter::new(input.into_iter(), 12000, 2400, 1); + let output = SampleRateConverter::new(input.into_iter(), 12000, 2400, ch!(1)); let size_estimation = output.len(); let output = output.collect::>(); assert_eq!(output, [0.0, 5.0, 10.0, 15.0]); diff --git a/src/decoder/flac.rs b/src/decoder/flac.rs index 0a1836c4..15a9072b 100644 --- a/src/decoder/flac.rs +++ b/src/decoder/flac.rs @@ -61,7 +61,12 @@ where current_block_off: 0, bits_per_sample: spec.bits_per_sample, sample_rate, - channels: spec.channels as ChannelCount, + channels: ChannelCount::new( + spec.channels + .try_into() + .expect("rodio supports only up to u16::MAX (65_535) channels"), + ) + .expect("flac should never have zero channels"), total_duration, }) } @@ -115,9 +120,9 @@ where loop { if self.current_block_off < self.current_block.len() { // Read from current block. - let real_offset = (self.current_block_off % self.channels as usize) + let real_offset = (self.current_block_off % self.channels.get() as usize) * self.current_block_channel_len - + self.current_block_off / self.channels as usize; + + self.current_block_off / self.channels.get() as usize; let raw_val = self.current_block[real_offset]; self.current_block_off += 1; let bits = self.bits_per_sample; diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index 6feb28eb..0ad970ea 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -8,6 +8,7 @@ use std::mem; use std::str::FromStr; use std::time::Duration; +use crate::math::ch; use crate::source::SeekError; use crate::{Sample, Source}; @@ -131,7 +132,7 @@ impl DecoderImpl { DecoderImpl::Mp3(source) => source.channels(), #[cfg(feature = "symphonia")] DecoderImpl::Symphonia(source) => source.channels(), - DecoderImpl::None(_) => 0, + DecoderImpl::None(_) => ch!(1), } } diff --git a/src/decoder/symphonia.rs b/src/decoder/symphonia.rs index 0fa4d066..275b64eb 100644 --- a/src/decoder/symphonia.rs +++ b/src/decoder/symphonia.rs @@ -165,7 +165,14 @@ impl Source for SymphoniaDecoder { #[inline] fn channels(&self) -> ChannelCount { - self.spec.channels.count() as ChannelCount + ChannelCount::new( + self.spec + .channels + .count() + .try_into() + .expect("rodio only support up to u16::MAX channels (65_535)"), + ) + .expect("audio should always have at least one channel") } #[inline] @@ -193,7 +200,7 @@ impl Source for SymphoniaDecoder { }; // make sure the next sample is for the right channel - let to_skip = self.current_span_offset % self.channels() as usize; + let to_skip = self.current_span_offset % self.channels().get() as usize; let seek_res = self .format @@ -290,7 +297,7 @@ impl SymphoniaDecoder { let decoded = decoded.map_err(SeekError::Decoding)?; decoded.spec().clone_into(&mut self.spec); self.buffer = SymphoniaDecoder::get_buffer(decoded, &self.spec); - self.current_span_offset = samples_to_pass as usize * self.channels() as usize; + self.current_span_offset = samples_to_pass as usize * self.channels().get() as usize; Ok(()) } } diff --git a/src/decoder/vorbis.rs b/src/decoder/vorbis.rs index 8f5e86fe..cfd80d24 100644 --- a/src/decoder/vorbis.rs +++ b/src/decoder/vorbis.rs @@ -70,7 +70,8 @@ where #[inline] fn channels(&self) -> ChannelCount { - self.stream_reader.ident_hdr.audio_channels as ChannelCount + ChannelCount::new(self.stream_reader.ident_hdr.audio_channels.into()) + .expect("audio should have at least one channel") } #[inline] diff --git a/src/decoder/wav.rs b/src/decoder/wav.rs index cb46f4ef..4c35c8f4 100644 --- a/src/decoder/wav.rs +++ b/src/decoder/wav.rs @@ -41,6 +41,7 @@ where let sample_rate = spec.sample_rate; let channels = spec.channels; + assert!(channels > 0); let total_duration = { let data_rate = sample_rate as u64 * channels as u64; @@ -53,7 +54,7 @@ where reader, total_duration, sample_rate: sample_rate as SampleRate, - channels: channels as ChannelCount, + channels: ChannelCount::new(channels).expect("wav should have a least one channel"), }) } @@ -175,13 +176,13 @@ where let new_pos = new_pos.min(file_len); // saturate pos at the end of the source // make sure the next sample is for the right channel - let to_skip = self.reader.samples_read % self.channels() as u32; + let to_skip = self.reader.samples_read % self.channels().get() as u32; self.reader .reader .seek(new_pos) .map_err(SeekError::HoundDecoder)?; - self.reader.samples_read = new_pos * self.channels() as u32; + self.reader.samples_read = new_pos * self.channels().get() as u32; for _ in 0..to_skip { self.next(); diff --git a/src/math.rs b/src/math.rs index 198a4e9a..efa902f1 100644 --- a/src/math.rs +++ b/src/math.rs @@ -12,16 +12,31 @@ pub fn lerp(first: &f32, second: &f32, numerator: u32, denominator: u32) -> f32 pub(crate) const NS_PER_SECOND: u64 = 1_000_000_000; +/// short macro to generate a `ChannelCount` for tests +/// this panics during compile if the passed in literal is zero +macro_rules! ch { + ($n:literal) => { + const { core::num::NonZeroU16::new($n).unwrap() } + }; +} + +pub(crate) use ch; + /// will hopefully get stabilized, this is slightly different to the future /// std's version since it does some casting already. When the std's version gets /// stable remove this trait. pub(crate) trait PrevMultipleOf { fn prev_multiple_of(self, n: u16) -> Self; } +pub(crate) trait PrevMultipleOfNonZero { + fn prev_multiple_of(self, n: core::num::NonZero) -> Self; +} macro_rules! impl_prev_multiple_of { ($type:ty) => { impl PrevMultipleOf for $type { + /// # Panics + /// Like std's `next_multiple_of` this panics if n is zero fn prev_multiple_of(self, n: u16) -> $type { if self.next_multiple_of(n as $type) > self { self.next_multiple_of(n as $type) - n as $type @@ -30,6 +45,16 @@ macro_rules! impl_prev_multiple_of { } } } + impl PrevMultipleOfNonZero for $type { + /// Never panics thanks to n being NonZero + fn prev_multiple_of(self, n: core::num::NonZero) -> $type { + if self.next_multiple_of(n.get() as $type) > self { + self.next_multiple_of(n.get() as $type) - n.get() as $type + } else { + self.next_multiple_of(n.get() as $type) + } + } + } }; } diff --git a/src/mixer.rs b/src/mixer.rs index 1dc7ca8f..331bf1a5 100644 --- a/src/mixer.rs +++ b/src/mixer.rs @@ -173,7 +173,7 @@ impl MixerSource { let mut pending = self.input.0.pending_sources.lock().unwrap(); // TODO: relax ordering? for source in pending.drain(..) { - let in_step = self.sample_count % source.channels() as usize == 0; + let in_step = self.sample_count % source.channels().get() as usize == 0; if in_step { self.current_sources.push(source); @@ -207,17 +207,18 @@ impl MixerSource { #[cfg(test)] mod tests { use crate::buffer::SamplesBuffer; + use crate::math::ch; use crate::mixer; use crate::source::Source; #[test] fn basic() { - let (tx, mut rx) = mixer::mixer(1, 48000); + let (tx, mut rx) = mixer::mixer(ch!(1), 48000); - tx.add(SamplesBuffer::new(1, 48000, vec![10.0, -10.0, 10.0, -10.0])); - tx.add(SamplesBuffer::new(1, 48000, vec![5.0, 5.0, 5.0, 5.0])); + tx.add(SamplesBuffer::new(ch!(1), 48000, vec![10.0, -10.0, 10.0, -10.0])); + tx.add(SamplesBuffer::new(ch!(1), 48000, vec![5.0, 5.0, 5.0, 5.0])); - assert_eq!(rx.channels(), 1); + assert_eq!(rx.channels(), ch!(1)); assert_eq!(rx.sample_rate(), 48000); assert_eq!(rx.next(), Some(15.0)); assert_eq!(rx.next(), Some(-5.0)); @@ -228,12 +229,12 @@ mod tests { #[test] fn channels_conv() { - let (tx, mut rx) = mixer::mixer(2, 48000); + let (tx, mut rx) = mixer::mixer(ch!(2), 48000); - tx.add(SamplesBuffer::new(1, 48000, vec![10.0, -10.0, 10.0, -10.0])); - tx.add(SamplesBuffer::new(1, 48000, vec![5.0, 5.0, 5.0, 5.0])); + tx.add(SamplesBuffer::new(ch!(1), 48000, vec![10.0, -10.0, 10.0, -10.0])); + tx.add(SamplesBuffer::new(ch!(1), 48000, vec![5.0, 5.0, 5.0, 5.0])); - assert_eq!(rx.channels(), 2); + assert_eq!(rx.channels(), ch!(2)); assert_eq!(rx.sample_rate(), 48000); assert_eq!(rx.next(), Some(15.0)); assert_eq!(rx.next(), Some(15.0)); @@ -248,12 +249,12 @@ mod tests { #[test] fn rate_conv() { - let (tx, mut rx) = mixer::mixer(1, 96000); + let (tx, mut rx) = mixer::mixer(ch!(1), 96000); - tx.add(SamplesBuffer::new(1, 48000, vec![10.0, -10.0, 10.0, -10.0])); - tx.add(SamplesBuffer::new(1, 48000, vec![5.0, 5.0, 5.0, 5.0])); + tx.add(SamplesBuffer::new(ch!(1), 48000, vec![10.0, -10.0, 10.0, -10.0])); + tx.add(SamplesBuffer::new(ch!(1), 48000, vec![5.0, 5.0, 5.0, 5.0])); - assert_eq!(rx.channels(), 1); + assert_eq!(rx.channels(), ch!(1)); assert_eq!(rx.sample_rate(), 96000); assert_eq!(rx.next(), Some(15.0)); assert_eq!(rx.next(), Some(5.0)); @@ -267,15 +268,15 @@ mod tests { #[test] fn start_afterwards() { - let (tx, mut rx) = mixer::mixer(1, 48000); + let (tx, mut rx) = mixer::mixer(ch!(1), 48000); - tx.add(SamplesBuffer::new(1, 48000, vec![10.0, -10.0, 10.0, -10.0])); + tx.add(SamplesBuffer::new(ch!(1), 48000, vec![10.0, -10.0, 10.0, -10.0])); assert_eq!(rx.next(), Some(10.0)); assert_eq!(rx.next(), Some(-10.0)); tx.add(SamplesBuffer::new( - 1, + ch!(1), 48000, vec![5.0, 5.0, 6.0, 6.0, 7.0, 7.0, 7.0], )); @@ -286,7 +287,7 @@ mod tests { assert_eq!(rx.next(), Some(6.0)); assert_eq!(rx.next(), Some(6.0)); - tx.add(SamplesBuffer::new(1, 48000, vec![2.0])); + tx.add(SamplesBuffer::new(ch!(1), 48000, vec![2.0])); assert_eq!(rx.next(), Some(9.0)); assert_eq!(rx.next(), Some(7.0)); diff --git a/src/queue.rs b/src/queue.rs index c571e90f..2996b8d4 100644 --- a/src/queue.rs +++ b/src/queue.rs @@ -4,6 +4,7 @@ use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Mutex}; use std::time::Duration; +use crate::math::ch; use crate::source::{Empty, SeekError, Source, Zero}; use crate::Sample; @@ -222,7 +223,7 @@ impl SourcesQueueOutput { let mut next = self.input.next_sounds.lock().unwrap(); if next.len() == 0 { - let silence = Box::new(Zero::new_samples(1, 44100, THRESHOLD)) as Box<_>; + let silence = Box::new(Zero::new_samples(ch!(1), 44100, THRESHOLD)) as Box<_>; if self.input.keep_alive_if_empty.load(Ordering::Acquire) { // Play a short silence in order to avoid spinlocking. (silence, None) @@ -243,6 +244,7 @@ impl SourcesQueueOutput { #[cfg(test)] mod tests { use crate::buffer::SamplesBuffer; + use crate::math::ch; use crate::queue; use crate::source::Source; @@ -251,16 +253,16 @@ mod tests { fn basic() { let (tx, mut rx) = queue::queue(false); - tx.append(SamplesBuffer::new(1, 48000, vec![10.0, -10.0, 10.0, -10.0])); - tx.append(SamplesBuffer::new(2, 96000, vec![5.0, 5.0, 5.0, 5.0])); + tx.append(SamplesBuffer::new(ch!(1), 48000, vec![10.0, -10.0, 10.0, -10.0])); + tx.append(SamplesBuffer::new(ch!(2), 96000, vec![5.0, 5.0, 5.0, 5.0])); - assert_eq!(rx.channels(), 1); + assert_eq!(rx.channels(), ch!(1)); assert_eq!(rx.sample_rate(), 48000); assert_eq!(rx.next(), Some(10.0)); assert_eq!(rx.next(), Some(-10.0)); assert_eq!(rx.next(), Some(10.0)); assert_eq!(rx.next(), Some(-10.0)); - assert_eq!(rx.channels(), 2); + assert_eq!(rx.channels(), ch!(2)); assert_eq!(rx.sample_rate(), 96000); assert_eq!(rx.next(), Some(5.0)); assert_eq!(rx.next(), Some(5.0)); @@ -278,7 +280,7 @@ mod tests { #[test] fn keep_alive() { let (tx, mut rx) = queue::queue(true); - tx.append(SamplesBuffer::new(1, 48000, vec![10.0, -10.0, 10.0, -10.0])); + tx.append(SamplesBuffer::new(ch!(1), 48000, vec![10.0, -10.0, 10.0, -10.0])); assert_eq!(rx.next(), Some(10.0)); assert_eq!(rx.next(), Some(-10.0)); @@ -299,7 +301,7 @@ mod tests { assert_eq!(rx.next(), Some(0.0)); } - tx.append(SamplesBuffer::new(1, 48000, vec![10.0, -10.0, 10.0, -10.0])); + tx.append(SamplesBuffer::new(ch!(1), 48000, vec![10.0, -10.0, 10.0, -10.0])); assert_eq!(rx.next(), Some(10.0)); assert_eq!(rx.next(), Some(-10.0)); assert_eq!(rx.next(), Some(10.0)); diff --git a/src/sink.rs b/src/sink.rs index 0ba019af..94181c86 100644 --- a/src/sink.rs +++ b/src/sink.rs @@ -366,6 +366,7 @@ mod tests { use std::sync::atomic::Ordering; use crate::buffer::SamplesBuffer; + use crate::math::ch; use crate::{Sink, Source}; #[test] @@ -382,8 +383,8 @@ mod tests { let v = vec![10.0, -10.0, 20.0, -20.0, 30.0, -30.0]; // Low rate to ensure immediate control. - sink.append(SamplesBuffer::new(1, 1, v.clone())); - let mut reference_src = SamplesBuffer::new(1, 1, v); + sink.append(SamplesBuffer::new(ch!(1), 1, v.clone())); + let mut reference_src = SamplesBuffer::new(ch!(1), 1, v); assert_eq!(source.next(), reference_src.next()); assert_eq!(source.next(), reference_src.next()); @@ -410,8 +411,8 @@ mod tests { let v = vec![10.0, -10.0, 20.0, -20.0, 30.0, -30.0]; - sink.append(SamplesBuffer::new(1, 1, v.clone())); - let mut src = SamplesBuffer::new(1, 1, v.clone()); + sink.append(SamplesBuffer::new(ch!(1), 1, v.clone())); + let mut src = SamplesBuffer::new(ch!(1), 1, v.clone()); assert_eq!(queue_rx.next(), src.next()); assert_eq!(queue_rx.next(), src.next()); @@ -421,8 +422,8 @@ mod tests { assert!(sink.controls.stopped.load(Ordering::SeqCst)); assert_eq!(queue_rx.next(), Some(0.0)); - src = SamplesBuffer::new(1, 1, v.clone()); - sink.append(SamplesBuffer::new(1, 1, v)); + src = SamplesBuffer::new(ch!(1), 1, v.clone()); + sink.append(SamplesBuffer::new(ch!(1), 1, v)); assert!(!sink.controls.stopped.load(Ordering::SeqCst)); // Flush silence @@ -439,8 +440,8 @@ mod tests { let v = vec![10.0, -10.0, 20.0, -20.0, 30.0, -30.0]; // High rate to avoid immediate control. - sink.append(SamplesBuffer::new(2, 44100, v.clone())); - let src = SamplesBuffer::new(2, 44100, v.clone()); + sink.append(SamplesBuffer::new(ch!(2), 44100, v.clone())); + let src = SamplesBuffer::new(ch!(2), 44100, v.clone()); let mut src = src.amplify(0.5); sink.set_volume(0.5); diff --git a/src/source/buffered.rs b/src/source/buffered.rs index 441ffc0d..e9148794 100644 --- a/src/source/buffered.rs +++ b/src/source/buffered.rs @@ -6,7 +6,7 @@ use std::time::Duration; use super::SeekError; use crate::common::{ChannelCount, SampleRate}; -use crate::math::PrevMultipleOf; +use crate::math::{ch, PrevMultipleOf}; use crate::Source; /// Iterator that at the same time extracts data from the iterator and @@ -116,7 +116,7 @@ where fn channels(&self) -> ChannelCount { match *self.current_span { Span::Data(SpanData { channels, .. }) => channels, - Span::End => 0, + Span::End => ch!(1), Span::Input(_) => unreachable!(), } } @@ -125,7 +125,7 @@ where fn sample_rate(&self) -> SampleRate { match *self.current_span { Span::Data(SpanData { rate, .. }) => rate, - Span::End => 0, + Span::End => dbg!(0), Span::Input(_) => unreachable!(), } } @@ -235,12 +235,14 @@ where let mut data = Vec::new(); loop { - let Some(sample) = input.next() else { break }; + let Some(sample) = input.next() else { + break; + }; data.push(sample); if input.parameters_changed() { break; } - if data.len() > 32768.prev_multiple_of(channels) { + if data.len() > 32768.prev_multiple_of(channels.into()) { break; } } diff --git a/src/source/channel_volume.rs b/src/source/channel_volume.rs index 703ed098..3ee1508f 100644 --- a/src/source/channel_volume.rs +++ b/src/source/channel_volume.rs @@ -24,10 +24,10 @@ where /// Wrap the input source and make it mono. Play that mono sound to each /// channel at the volume set by the user. The volume can be changed using /// [`ChannelVolume::set_volume`]. - pub fn new(input: I, channel_volumes: Vec) -> ChannelVolume - where - I: Source, - { + /// + /// # Panics if channel_volumes is empty + pub fn new(input: I, channel_volumes: Vec) -> ChannelVolume { + assert!(!channel_volumes.is_empty()); let channel_count = channel_volumes.len(); // See next() implementation. ChannelVolume { input, @@ -75,12 +75,12 @@ where self.current_channel = 0; self.current_sample = None; let num_channels = self.input.channels(); - for _ in 0..num_channels { + for _ in 0..num_channels.get() { if let Some(s) = self.input.next() { self.current_sample = Some(self.current_sample.unwrap_or(0.0) + s); } } - self.current_sample.map(|s| s / num_channels as f32); + self.current_sample.map(|s| s / num_channels.get() as f32); } let result = self .current_sample @@ -108,7 +108,8 @@ where #[inline] fn channels(&self) -> ChannelCount { - self.channel_volumes.len() as ChannelCount + ChannelCount::new(self.channel_volumes.len() as u16) + .expect("checked to be non-empty in new implementation") } #[inline] diff --git a/src/source/chirp.rs b/src/source/chirp.rs index d52e7936..9768c940 100644 --- a/src/source/chirp.rs +++ b/src/source/chirp.rs @@ -1,6 +1,7 @@ //! Chirp/sweep source. use crate::common::{ChannelCount, SampleRate}; +use crate::math::ch; use crate::Source; use std::{f32::consts::TAU, time::Duration}; @@ -62,7 +63,7 @@ impl Source for Chirp { } fn channels(&self) -> ChannelCount { - 1 + ch!(1) } fn sample_rate(&self) -> SampleRate { diff --git a/src/source/delay.rs b/src/source/delay.rs index 49751bd4..6edab2eb 100644 --- a/src/source/delay.rs +++ b/src/source/delay.rs @@ -10,7 +10,7 @@ fn remaining_samples( channels: ChannelCount, ) -> usize { let ns = until_playback.as_secs() * 1_000_000_000 + until_playback.subsec_nanos() as u64; - let samples = ns * channels as u64 * sample_rate as u64 / 1_000_000_000; + let samples = ns * channels.get() as u64 * sample_rate as u64 / 1_000_000_000; samples as usize } diff --git a/src/source/empty.rs b/src/source/empty.rs index f26284e5..6608ce59 100644 --- a/src/source/empty.rs +++ b/src/source/empty.rs @@ -2,6 +2,7 @@ use std::time::Duration; use super::SeekError; use crate::common::{ChannelCount, SampleRate}; +use crate::math::ch; use crate::{Sample, Source}; /// An empty source. @@ -41,7 +42,7 @@ impl Source for Empty { #[inline] fn channels(&self) -> ChannelCount { - 1 + ch!(1) } #[inline] diff --git a/src/source/empty_callback.rs b/src/source/empty_callback.rs index c56f0f56..e239a471 100644 --- a/src/source/empty_callback.rs +++ b/src/source/empty_callback.rs @@ -2,6 +2,7 @@ use std::time::Duration; use super::SeekError; use crate::common::{ChannelCount, SampleRate}; +use crate::math::ch; use crate::{Sample, Source}; /// An empty source that executes a callback function @@ -38,7 +39,7 @@ impl Source for EmptyCallback { #[inline] fn channels(&self) -> ChannelCount { - 1 + ch!(1) } #[inline] diff --git a/src/source/from_iter.rs b/src/source/from_iter.rs index 049710c3..acd712b1 100644 --- a/src/source/from_iter.rs +++ b/src/source/from_iter.rs @@ -2,6 +2,7 @@ use std::time::Duration; use super::SeekError; use crate::common::{ChannelCount, SampleRate}; +use crate::math::ch; use crate::Source; /// Builds a source that chains sources provided by an iterator. @@ -119,7 +120,7 @@ where src.channels() } else { // Dummy value that only happens if the iterator was empty. - 2 + ch!(2) } } @@ -151,21 +152,22 @@ where #[cfg(test)] mod tests { use crate::buffer::SamplesBuffer; + use crate::math::ch; use crate::source::{from_iter, Source}; #[test] fn basic() { let mut rx = from_iter((0..2).map(|n| { if n == 0 { - SamplesBuffer::new(1, 48000, vec![10.0, -10.0, 10.0, -10.0]) + SamplesBuffer::new(ch!(1), 48000, vec![10.0, -10.0, 10.0, -10.0]) } else if n == 1 { - SamplesBuffer::new(2, 96000, vec![5.0, 5.0, 5.0, 5.0]) + SamplesBuffer::new(ch!(2), 96000, vec![5.0, 5.0, 5.0, 5.0]) } else { unreachable!() } })); - assert_eq!(rx.channels(), 1); + assert_eq!(rx.channels(), ch!(1)); assert_eq!(rx.sample_rate(), 48000); assert_eq!(rx.next(), Some(10.0)); assert_eq!(rx.next(), Some(-10.0)); diff --git a/src/source/linear_ramp.rs b/src/source/linear_ramp.rs index f1fb521f..9fef3d9d 100644 --- a/src/source/linear_ramp.rs +++ b/src/source/linear_ramp.rs @@ -45,7 +45,7 @@ impl LinearGainRamp where I: Source, { - /// Returns a reference to the innner source. + /// Returns a reference to the inner source. #[inline] pub fn inner(&self) -> &I { &self.input @@ -88,7 +88,7 @@ where factor = self.start_gain * (1.0f32 - p) + self.end_gain * p; } - if self.sample_idx % (self.channels() as u64) == 0 { + if self.sample_idx % (self.channels().get() as u64) == 0 { self.elapsed_ns += 1000000000.0 / (self.input.sample_rate() as f32); } @@ -140,13 +140,14 @@ mod tests { use super::*; use crate::buffer::SamplesBuffer; + use crate::math::ch; use crate::Sample; /// Create a SamplesBuffer of identical samples with value `value`. /// Returned buffer is one channel and has a sample rate of 1 Hz. fn const_source(length: u8, value: Sample) -> SamplesBuffer { let data: Vec = (1..=length).map(|_| value).collect(); - SamplesBuffer::new(1, 1, data) + SamplesBuffer::new(ch!(1), 1, data) } /// Create a SamplesBuffer of repeating sample values from `values`. @@ -156,7 +157,7 @@ mod tests { .map(|(i, _)| values[i % values.len()]) .collect(); - SamplesBuffer::new(1, 1, data) + SamplesBuffer::new(ch!(1), 1, data) } #[test] diff --git a/src/source/mod.rs b/src/source/mod.rs index ca98504b..b703b299 100644 --- a/src/source/mod.rs +++ b/src/source/mod.rs @@ -165,6 +165,7 @@ pub trait Source: Iterator { fn parameters_changed(&self) -> bool; /// Returns the number of channels. Channels are always interleaved. + /// Should never be Zero fn channels(&self) -> ChannelCount; /// Returns the rate at which the source should be played. In number of samples per second. diff --git a/src/source/pausable.rs b/src/source/pausable.rs index 56af86d9..8c8800f0 100644 --- a/src/source/pausable.rs +++ b/src/source/pausable.rs @@ -31,7 +31,7 @@ where pub struct Pausable { input: I, paused_channels: Option, - remaining_paused_samples: ChannelCount, + remaining_paused_samples: u16, } impl Pausable @@ -83,7 +83,7 @@ where } if let Some(paused_channels) = self.paused_channels { - self.remaining_paused_samples = paused_channels - 1; + self.remaining_paused_samples = paused_channels.get() - 1; return Some(0.0); } diff --git a/src/source/periodic.rs b/src/source/periodic.rs index 75a6ddcb..da960a0c 100644 --- a/src/source/periodic.rs +++ b/src/source/periodic.rs @@ -12,7 +12,8 @@ where // TODO: handle the fact that the samples rate can change // TODO: generally, just wrong let update_ms = period.as_secs() as u32 * 1_000 + period.subsec_millis(); - let update_frequency = (update_ms * source.sample_rate()) / 1000 * source.channels() as u32; + let update_frequency = + (update_ms * source.sample_rate()) / 1000 * source.channels().get() as u32; PeriodicAccess { input: source, @@ -131,12 +132,13 @@ mod tests { use std::time::Duration; use crate::buffer::SamplesBuffer; + use crate::math::ch; use crate::source::Source; #[test] fn stereo_access() { // Stereo, 1Hz audio buffer - let inner = SamplesBuffer::new(2, 1, vec![10.0, -10.0, 10.0, -10.0, 20.0, -20.0]); + let inner = SamplesBuffer::new(ch!(2), 1, vec![10.0, -10.0, 10.0, -10.0, 20.0, -20.0]); let cnt = RefCell::new(0); @@ -164,7 +166,7 @@ mod tests { #[test] fn fast_access_overflow() { // 1hz is lower than 0.5 samples per 5ms - let inner = SamplesBuffer::new(1, 1, vec![10.0, -10.0, 10.0, -10.0, 20.0, -20.0]); + let inner = SamplesBuffer::new(ch!(1), 1, vec![10.0, -10.0, 10.0, -10.0, 20.0, -20.0]); let mut source = inner.periodic_access(Duration::from_millis(5), |_src| {}); source.next(); diff --git a/src/source/position.rs b/src/source/position.rs index 5993204b..a7ea1fbd 100644 --- a/src/source/position.rs +++ b/src/source/position.rs @@ -27,7 +27,6 @@ impl std::fmt::Debug for TrackPosition { impl TrackPosition { pub(crate) fn new(source: I) -> TrackPosition { assert!(source.sample_rate() > 0); - assert!(source.channels() > 0); TrackPosition { samples_counted: 0, offset_duration: 0.0, @@ -75,7 +74,7 @@ where dbg!(self); let seconds = self.samples_counted as f64 / self.input.sample_rate() as f64 - / self.input.channels() as f64 + / self.input.channels().get() as f64 + self.offset_duration; dbg!(seconds); Duration::from_secs_f64(seconds) @@ -100,7 +99,7 @@ where dbg!(&self); self.offset_duration += self.samples_counted as f64 / self.current_span_sample_rate as f64 - / self.current_span_channels as f64; + / self.current_span_channels.get() as f64; // Reset. self.samples_counted = 0; diff --git a/src/source/sawtooth.rs b/src/source/sawtooth.rs index 45a4f89b..1ce19dc6 100644 --- a/src/source/sawtooth.rs +++ b/src/source/sawtooth.rs @@ -1,4 +1,5 @@ use crate::common::{ChannelCount, SampleRate}; +use crate::math::ch; use crate::source::{Function, SignalGenerator}; use crate::Source; use std::time::Duration; @@ -45,7 +46,7 @@ impl Source for SawtoothWave { #[inline] fn channels(&self) -> ChannelCount { - 1 + ch!(1) } #[inline] diff --git a/src/source/signal_generator.rs b/src/source/signal_generator.rs index 4f686eef..1f41a0ba 100644 --- a/src/source/signal_generator.rs +++ b/src/source/signal_generator.rs @@ -13,6 +13,7 @@ //! ``` use super::SeekError; use crate::common::{ChannelCount, SampleRate}; +use crate::math::ch; use crate::Source; use std::f32::consts::TAU; use std::time::Duration; @@ -143,7 +144,7 @@ impl Source for SignalGenerator { #[inline] fn channels(&self) -> ChannelCount { - 1 + ch!(1) } #[inline] diff --git a/src/source/sine.rs b/src/source/sine.rs index 0da6a210..073f59db 100644 --- a/src/source/sine.rs +++ b/src/source/sine.rs @@ -1,4 +1,5 @@ use crate::common::{ChannelCount, SampleRate}; +use crate::math::ch; use crate::source::{Function, SignalGenerator}; use crate::Source; use std::time::Duration; @@ -45,7 +46,7 @@ impl Source for SineWave { #[inline] fn channels(&self) -> ChannelCount { - 1 + ch!(1) } #[inline] diff --git a/src/source/skip_duration.rs b/src/source/skip_duration.rs index 890fc73f..b6595e7e 100644 --- a/src/source/skip_duration.rs +++ b/src/source/skip_duration.rs @@ -2,7 +2,7 @@ use std::time::Duration; use super::SeekError; use crate::common::{ChannelCount, SampleRate}; -use crate::math::{PrevMultipleOf, NS_PER_SECOND}; +use crate::math::{PrevMultipleOfNonZero, NS_PER_SECOND}; use crate::Source; /// A source that skips specified duration of the given source from it's current position. @@ -120,11 +120,10 @@ where while duration > ns_per_frame { assert!(input.sample_rate() > 0); - assert!(input.channels() > 0); ns_per_frame = NS_PER_SECOND / input.sample_rate() as u64; - let samples_per_second = input.sample_rate() as u64 * input.channels() as u64; + let samples_per_second = input.sample_rate() as u64 * input.channels().get() as u64; let samples_to_skip = (duration as u128 * samples_per_second as u128 / NS_PER_SECOND as u128) as u64; let samples_to_skip = samples_to_skip.prev_multiple_of(input.channels()); diff --git a/src/source/square.rs b/src/source/square.rs index df6e63bf..7654cf21 100644 --- a/src/source/square.rs +++ b/src/source/square.rs @@ -1,4 +1,5 @@ use crate::common::{ChannelCount, SampleRate}; +use crate::math::ch; use crate::source::{Function, SignalGenerator}; use crate::Source; use std::time::Duration; @@ -45,7 +46,7 @@ impl Source for SquareWave { #[inline] fn channels(&self) -> ChannelCount { - 1 + ch!(1) } #[inline] diff --git a/src/source/take_duration.rs b/src/source/take_duration.rs index b3f888db..b59a9d5b 100644 --- a/src/source/take_duration.rs +++ b/src/source/take_duration.rs @@ -2,7 +2,7 @@ use std::time::Duration; use super::SeekError; use crate::common::{ChannelCount, SampleRate}; -use crate::math::{PrevMultipleOf, NS_PER_SECOND}; +use crate::math::{PrevMultipleOfNonZero, NS_PER_SECOND}; use crate::Source; /// A source that truncates the given source to a certain duration. @@ -30,7 +30,7 @@ where .try_into() .expect("can not take more then 584 days of audio"); - let samples_per_second = input.sample_rate() as u64 * input.channels() as u64; + let samples_per_second = input.sample_rate() as u64 * input.channels().get() as u64; let samples_to_take = (remaining_ns as u128 * samples_per_second as u128 / NS_PER_SECOND as u128) as u64; let samples_to_take = samples_to_take.prev_multiple_of(input.channels()); @@ -83,7 +83,7 @@ where } fn next_samples_per_second(&mut self) -> u64 { - self.input.sample_rate() as u64 * self.input.channels() as u64 + self.input.sample_rate() as u64 * self.input.channels().get() as u64 } fn next_samples_to_take(&mut self) -> u64 { diff --git a/src/source/take_samples.rs b/src/source/take_samples.rs index 48f819e9..777ebdf9 100644 --- a/src/source/take_samples.rs +++ b/src/source/take_samples.rs @@ -45,7 +45,7 @@ where type Item = ::Item; fn next(&mut self) -> Option<::Item> { - if self.taken >= self.target.prev_multiple_of(self.input.channels()) { + if self.taken >= self.target.prev_multiple_of(self.input.channels().get()) { None } else { self.taken += 1; diff --git a/src/source/take_span.rs b/src/source/take_span.rs index ca7c1a8a..b5b1a191 100644 --- a/src/source/take_span.rs +++ b/src/source/take_span.rs @@ -46,8 +46,6 @@ impl Iterator for TakeSpan { } } -// TODO: size_hint - impl Source for TakeSpan where I: Iterator + Source, diff --git a/src/source/triangle.rs b/src/source/triangle.rs index 3e20e7e3..c08174d2 100644 --- a/src/source/triangle.rs +++ b/src/source/triangle.rs @@ -1,4 +1,5 @@ use crate::common::{ChannelCount, SampleRate}; +use crate::math::ch; use crate::source::{Function, SignalGenerator}; use crate::Source; use std::time::Duration; @@ -45,7 +46,7 @@ impl Source for TriangleWave { #[inline] fn channels(&self) -> ChannelCount { - 1 + ch!(1) } #[inline] diff --git a/src/static_buffer.rs b/src/static_buffer.rs index 92a7a93a..4d7990b8 100644 --- a/src/static_buffer.rs +++ b/src/static_buffer.rs @@ -41,12 +41,11 @@ impl StaticSamplesBuffer { sample_rate: SampleRate, data: &'static [Sample], ) -> StaticSamplesBuffer { - assert!(channels != 0); assert!(sample_rate != 0); let duration_ns = 1_000_000_000u64.checked_mul(data.len() as u64).unwrap() / sample_rate as u64 - / channels as u64; + / channels.get() as u64; let duration = Duration::new( duration_ns / 1_000_000_000, (duration_ns % 1_000_000_000) as u32, @@ -106,29 +105,24 @@ impl Iterator for StaticSamplesBuffer { #[cfg(test)] mod tests { + use crate::math::ch; use crate::source::Source; use crate::static_buffer::StaticSamplesBuffer; #[test] fn basic() { - let _ = StaticSamplesBuffer::new(1, 44100, &[0.0, 0.0, 0.0, 0.0, 0.0, 0.0]); - } - - #[test] - #[should_panic] - fn panic_if_zero_channels() { - StaticSamplesBuffer::new(0, 44100, &[0.0, 0.0, 0.0, 0.0, 0.0, 0.0]); + let _ = StaticSamplesBuffer::new(ch!(1), 44100, &[0.0, 0.0, 0.0, 0.0, 0.0, 0.0]); } #[test] #[should_panic] fn panic_if_zero_sample_rate() { - StaticSamplesBuffer::new(1, 0, &[0.0, 0.0, 0.0, 0.0, 0.0, 0.0]); + StaticSamplesBuffer::new(ch!(1), 0, &[0.0, 0.0, 0.0, 0.0, 0.0, 0.0]); } #[test] fn duration_basic() { - let buf = StaticSamplesBuffer::new(2, 2, &[0.0, 0.0, 0.0, 0.0, 0.0, 0.0]); + let buf = StaticSamplesBuffer::new(ch!(2), 2, &[0.0, 0.0, 0.0, 0.0, 0.0, 0.0]); let dur = buf.total_duration().unwrap(); assert_eq!(dur.as_secs(), 1); assert_eq!(dur.subsec_nanos(), 500_000_000); @@ -136,7 +130,7 @@ mod tests { #[test] fn iteration() { - let mut buf = StaticSamplesBuffer::new(1, 44100, &[1.0, 2.0, 3.0, 4.0, 5.0, 6.0]); + let mut buf = StaticSamplesBuffer::new(ch!(1), 44100, &[1.0, 2.0, 3.0, 4.0, 5.0, 6.0]); assert_eq!(buf.next(), Some(1.0)); assert_eq!(buf.next(), Some(2.0)); assert_eq!(buf.next(), Some(3.0)); diff --git a/src/stream.rs b/src/stream.rs index 6e5fffa8..51871bd6 100644 --- a/src/stream.rs +++ b/src/stream.rs @@ -1,5 +1,6 @@ use crate::common::{ChannelCount, SampleRate}; use crate::decoder; +use crate::math::ch; use crate::mixer::{mixer, Mixer, MixerSource}; use crate::sink::Sink; use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; @@ -36,7 +37,7 @@ struct OutputStreamConfig { impl Default for OutputStreamConfig { fn default() -> Self { Self { - channel_count: 2, + channel_count: ch!(2), sample_rate: HZ_44100, buffer_size: BufferSize::Default, sample_format: SampleFormat::F32, @@ -97,7 +98,6 @@ impl OutputStreamBuilder { /// Sets number of output stream's channels. pub fn with_channels(mut self, channel_count: ChannelCount) -> OutputStreamBuilder { - assert!(channel_count > 0); self.config.channel_count = channel_count; self } @@ -122,14 +122,15 @@ impl OutputStreamBuilder { self } - /// Set available parameters from a CPAL supported config. You can ge list of + /// Set available parameters from a CPAL supported config. You can get list of /// such configurations for an output device using [crate::stream::supported_output_configs()] pub fn with_supported_config( mut self, config: &cpal::SupportedStreamConfig, ) -> OutputStreamBuilder { self.config = OutputStreamConfig { - channel_count: config.channels() as ChannelCount, + channel_count: ChannelCount::new(config.channels()) + .expect("cpal should never return a zero channel output"), sample_rate: config.sample_rate().0 as SampleRate, // In case of supported range limit buffer size to avoid unexpectedly long playback delays. buffer_size: clamp_supported_buffer_size(config.buffer_size(), 1024), @@ -141,7 +142,8 @@ impl OutputStreamBuilder { /// Set all output stream parameters at once from CPAL stream config. pub fn with_config(mut self, config: &cpal::StreamConfig) -> OutputStreamBuilder { self.config = OutputStreamConfig { - channel_count: config.channels as ChannelCount, + channel_count: ChannelCount::new(config.channels) + .expect("cpal should never return a zero channel output"), sample_rate: config.sample_rate.0 as SampleRate, buffer_size: config.buffer_size, ..self.config @@ -233,7 +235,7 @@ where impl From<&OutputStreamConfig> for StreamConfig { fn from(config: &OutputStreamConfig) -> Self { cpal::StreamConfig { - channels: config.channel_count as cpal::ChannelCount, + channels: config.channel_count.get() as cpal::ChannelCount, sample_rate: cpal::SampleRate(config.sample_rate), buffer_size: config.buffer_size, } @@ -326,10 +328,6 @@ impl OutputStream { assert!(sz > 0, "fixed buffer size is greater than zero"); } assert!(config.sample_rate > 0, "sample rate is greater than zero"); - assert!( - config.channel_count > 0, - "channel number is greater than zero" - ); } fn open( diff --git a/src/wav_output.rs b/src/wav_output.rs index 3b91d8a4..fabcc1b9 100644 --- a/src/wav_output.rs +++ b/src/wav_output.rs @@ -1,4 +1,4 @@ -use crate::{ChannelCount, Source}; +use crate::Source; use hound::{SampleFormat, WavSpec}; use std::path; @@ -10,7 +10,7 @@ pub fn output_to_wav( wav_file: impl AsRef, ) -> Result<(), Box> { let format = WavSpec { - channels: source.channels() as ChannelCount, + channels: source.channels().get(), sample_rate: source.sample_rate(), bits_per_sample: 32, sample_format: SampleFormat::Float, @@ -26,7 +26,6 @@ pub fn output_to_wav( #[cfg(test)] mod test { use super::output_to_wav; - use crate::common::ChannelCount; use crate::Source; use std::io::BufReader; use std::time::Duration; @@ -47,7 +46,7 @@ mod test { hound::WavReader::new(BufReader::new(file)).expect("wav file can be read back"); let reference = make_source(); assert_eq!(reference.sample_rate(), reader.spec().sample_rate); - assert_eq!(reference.channels(), reader.spec().channels as ChannelCount); + assert_eq!(reference.channels().get(), reader.spec().channels); let actual_samples: Vec = reader.samples::().map(|x| x.unwrap()).collect(); let expected_samples: Vec = reference.collect(); diff --git a/tests/buffered.rs b/tests/buffered.rs index fa900027..819eef9e 100644 --- a/tests/buffered.rs +++ b/tests/buffered.rs @@ -33,9 +33,9 @@ fn channel_count_changes() { ) .buffered(); - assert_eq!(source.channels(), 1); + assert_eq!(source.channels().get(), 1); assert_eq!(source.by_ref().take(10).count(), 10); - assert_eq!(source.channels(), 2); + assert_eq!(source.channels().get(), 2); } #[test] diff --git a/tests/crossfade.rs b/tests/crossfade.rs index a9f1b766..8c7c6546 100644 --- a/tests/crossfade.rs +++ b/tests/crossfade.rs @@ -1,3 +1,4 @@ +use std::num::NonZero; use std::time::Duration; use rodio::buffer::SamplesBuffer; @@ -6,7 +7,7 @@ use rodio::Source; fn dummy_source(length: u8) -> SamplesBuffer { let data: Vec = (1..=length).map(f32::from).collect(); - SamplesBuffer::new(1, 1, data) + SamplesBuffer::new(NonZero::new(1).unwrap(), 1, data) } #[test] @@ -26,7 +27,7 @@ fn test_crossfade_with_self() { #[test] fn test_crossfade() { let source1 = dummy_source(10); - let source2 = Zero::new(1, 1); + let source2 = Zero::new(NonZero::new(1).unwrap(), 1); let mixed = source1.take_crossfade_with(source2, Duration::from_secs(5) + Duration::from_nanos(1)); let result = mixed.collect::>(); diff --git a/tests/peekable.rs b/tests/peekable.rs index cfd45de9..a0d4ee9a 100644 --- a/tests/peekable.rs +++ b/tests/peekable.rs @@ -59,9 +59,9 @@ fn channel_count_changes() { let mut peekable = source.peekable_source(); peekable.peek(); - assert_eq!(peekable.channels(), 1); + assert_eq!(peekable.channels().get(), 1); assert_eq!(peekable.by_ref().take(10).count(), 10); - assert_eq!(peekable.channels(), 2); + assert_eq!(peekable.channels().get(), 2); } #[test] diff --git a/tests/position.rs b/tests/position.rs index 4970dcac..b0f09d7c 100644 --- a/tests/position.rs +++ b/tests/position.rs @@ -1,3 +1,4 @@ +use std::num::NonZero; use std::time::Duration; use rodio::buffer::SamplesBuffer; @@ -53,7 +54,11 @@ fn frame_changes( #[test] fn basic_and_seek() { - let inner = SamplesBuffer::new(1, 1, vec![10.0, -10.0, 10.0, -10.0, 20.0, -20.0]); + let inner = SamplesBuffer::new( + NonZero::new(1).unwrap(), + 1, + vec![10.0, -10.0, 10.0, -10.0, 20.0, -20.0], + ); let mut source = inner.track_position(); assert_eq!(source.get_pos().as_secs_f32(), 0.0); @@ -69,7 +74,11 @@ fn basic_and_seek() { #[test] fn basic_and_seek_in_presence_of_speedup() { - let inner = SamplesBuffer::new(1, 1, vec![10.0, -10.0, 10.0, -10.0, 20.0, -20.0]); + let inner = SamplesBuffer::new( + NonZero::new(1).unwrap(), + 1, + vec![10.0, -10.0, 10.0, -10.0, 20.0, -20.0], + ); let mut source = inner.speed(2.0).track_position(); assert_eq!(source.get_pos().as_secs_f32(), 0.0); diff --git a/tests/seek.rs b/tests/seek.rs index 38691144..9f9f8d96 100644 --- a/tests/seek.rs +++ b/tests/seek.rs @@ -1,4 +1,4 @@ -use rodio::{ChannelCount, Decoder, Source}; +use rodio::{Decoder, Source}; use rstest::rstest; use rstest_reuse::{self, *}; use std::io::{BufReader, Read, Seek}; @@ -121,11 +121,11 @@ fn seek_does_not_break_channel_order( ) { let mut source = get_rl(format); let channels = source.channels(); - assert_eq!(channels, 2, "test needs a stereo beep file"); + assert_eq!(channels.get(), 2, "test needs a stereo beep file"); let beep_range = second_channel_beep_range(&mut source); let beep_start = Duration::from_secs_f32( - beep_range.start as f32 / source.channels() as f32 / source.sample_rate() as f32, + beep_range.start as f32 / source.channels().get() as f32 / source.sample_rate() as f32, ); let mut source = get_rl(format); @@ -144,7 +144,7 @@ fn seek_does_not_break_channel_order( let samples: Vec<_> = source.by_ref().take(100).collect(); let channel0 = 0 + channel_offset; assert!( - is_silent(&samples, source.channels(), channel0), + is_silent(&samples, source.channels().get() as usize, channel0), "channel0 should be silent, channel0 starts at idx: {channel0} seek: {beep_start:?} + {offset:?} @@ -152,7 +152,7 @@ fn seek_does_not_break_channel_order( ); let channel1 = (1 + channel_offset) % 2; assert!( - !is_silent(&samples, source.channels(), channel1), + !is_silent(&samples, source.channels().get() as usize, channel1), "channel1 should not be silent, channel1; starts at idx: {channel1} seek: {beep_start:?} + {offset:?} @@ -165,7 +165,7 @@ fn second_channel_beep_range(source: &mut R) -> std::ops::Rang where R: Iterator, { - let channels = source.channels() as usize; + let channels = source.channels().get() as usize; let samples: Vec = source.by_ref().collect(); const WINDOW: usize = 50; @@ -202,21 +202,15 @@ where .next_multiple_of(channels); let samples = &samples[beep_starts..beep_starts + 100]; - assert!( - is_silent(samples, channels as ChannelCount, 0), - "{samples:?}" - ); - assert!( - !is_silent(samples, channels as ChannelCount, 1), - "{samples:?}" - ); + assert!(is_silent(samples, channels, 0), "{samples:?}"); + assert!(!is_silent(samples, channels, 1), "{samples:?}"); beep_starts..beep_ends } -fn is_silent(samples: &[f32], channels: ChannelCount, channel: usize) -> bool { +fn is_silent(samples: &[f32], channels: usize, channel: usize) -> bool { assert_eq!(samples.len(), 100); - let channel = samples.iter().skip(channel).step_by(channels as usize); + let channel = samples.iter().skip(channel).step_by(channels); let volume = channel.map(|s| s.abs()).sum::() / samples.len() as f32 * channels as f32; const BASICALLY_ZERO: f32 = 0.0001; @@ -225,7 +219,7 @@ fn is_silent(samples: &[f32], channels: ChannelCount, channel: usize) -> bool { fn time_remaining(decoder: Decoder) -> Duration { let rate = decoder.sample_rate() as f64; - let n_channels = decoder.channels() as f64; + let n_channels = decoder.channels().get() as f64; let n_samples = decoder.into_iter().count() as f64; Duration::from_secs_f64(n_samples / rate / n_channels) } diff --git a/tests/skip_duration.rs b/tests/skip_duration.rs index f2191c99..e024f799 100644 --- a/tests/skip_duration.rs +++ b/tests/skip_duration.rs @@ -20,7 +20,7 @@ fn ends_on_frame_boundary(#[values(1.483, 2.999)] seconds_to_skip: f32) { .clone() .skip_duration(Duration::from_secs_f32(seconds_to_skip)) .count(); - assert!(leftover % source.channels() as usize == 0) + assert!(leftover % source.channels().get() as usize == 0) } #[rstest] @@ -53,8 +53,8 @@ fn param_changes_during_skip(#[values(6, 11)] seconds_to_skip: u64) { let spans = source.spans; let expected_leftover = match seconds_to_skip { - 6 => 4 * spans[1].sample_rate as usize * spans[1].channels as usize + spans[2].len(), - 11 => 4 * spans[2].sample_rate as usize * spans[2].channels as usize, + 6 => 4 * spans[1].sample_rate as usize * spans[1].channels.get() as usize + spans[2].len(), + 11 => 4 * spans[2].sample_rate as usize * spans[2].channels.get() as usize, _ => unreachable!(), }; @@ -63,7 +63,7 @@ fn param_changes_during_skip(#[values(6, 11)] seconds_to_skip: u64) { #[rstest] fn samples_left( - #[values(1, 2, 4)] channels: ChannelCount, + #[values(1, 2, 4)] channels: u16, #[values(100_000)] sample_rate: SampleRate, #[values(0, 5)] seconds: u32, #[values(0, 3, 5)] seconds_to_skip: u32, @@ -72,13 +72,15 @@ fn samples_left( "channels: {channels}, sample_rate: {sample_rate}, \ seconds: {seconds}, seconds_to_skip: {seconds_to_skip}" ); - let buf_len = (sample_rate * channels as u32 * seconds) as usize; + let channels = ChannelCount::new(channels).unwrap(); + + let buf_len = (sample_rate * channels.get() 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); - let samples_left_expected = (sample_rate * channels as u32 * seconds_left) as usize; + let samples_left_expected = (sample_rate * channels.get() as u32 * seconds_left) as usize; let samples_left = test_buffer .skip_duration(Duration::from_secs(seconds_to_skip as u64)) .count(); diff --git a/tests/take_duration.rs b/tests/take_duration.rs index d6f84458..2eb946ea 100644 --- a/tests/take_duration.rs +++ b/tests/take_duration.rs @@ -23,7 +23,7 @@ fn ends_on_frame_boundary(#[values(1.483, 2.999)] seconds_to_skip: f32) { .with_fadeout(true) .count(); - assert!(got % source.channels() as usize == 0) + assert!(got % source.channels().get() as usize == 0) } #[rstest] @@ -57,11 +57,11 @@ fn param_changes_during_skip(#[values(6, 11)] seconds_to_skip: u64) { let spans = source.spans; let expected = match seconds_to_skip { - 6 => spans[0].len() + 1 * spans[1].sample_rate as usize * spans[1].channels as usize, + 6 => spans[0].len() + 1 * spans[1].sample_rate as usize * spans[1].channels.get() as usize, 11 => { spans[0].len() + spans[1].len() - + 1 * spans[2].sample_rate as usize * spans[2].channels as usize + + 1 * spans[2].sample_rate as usize * spans[2].channels.get() as usize } _ => unreachable!(), }; @@ -101,7 +101,7 @@ fn fadeout() { #[rstest] fn samples_taken( - #[values(1, 2, 4)] channels: ChannelCount, + #[values(1, 2, 4)] channels: u16, #[values(100_000)] sample_rate: SampleRate, #[values(0, 5)] seconds: u32, #[values(0, 3, 5)] seconds_to_take: u32, @@ -110,7 +110,9 @@ fn samples_taken( "channels: {channels}, sample_rate: {sample_rate}, \ seconds: {seconds}, seconds_to_take: {seconds_to_take}" ); - let buf_len = (sample_rate * channels as u32 * seconds) as usize; + let channels = ChannelCount::new(channels).unwrap(); + + let buf_len = (sample_rate * channels.get() 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); @@ -121,7 +123,7 @@ fn samples_taken( .count(); let seconds_we_can_take = seconds_to_take.min(seconds); - let samples_expected = (sample_rate * channels as u32 * seconds_we_can_take) as usize; + let samples_expected = (sample_rate * channels.get() as u32 * seconds_we_can_take) as usize; assert!( samples == samples_expected, "expected {samples_expected} samples, took only: {samples}" diff --git a/tests/take_span.rs b/tests/take_span.rs index 5d3e6f4b..d0066a61 100644 --- a/tests/take_span.rs +++ b/tests/take_span.rs @@ -21,14 +21,14 @@ fn param_changes_during_skip() { let mut span_1 = source.take_span(); assert_eq!(span_1.by_ref().take(9).count(), 9); - assert_eq!(span_1.channels(), 1); + assert_eq!(span_1.channels().get(), 1); assert_eq!(span_1.sample_rate(), 10); assert!(span_1.by_ref().next().is_some()); assert_eq!(span_1.by_ref().next(), None); let mut span_2 = span_1.into_inner().take_span(); assert_eq!(span_2.by_ref().take(9).count(), 9); - assert_eq!(span_2.channels(), 2); + assert_eq!(span_2.channels().get(), 2); assert_eq!(span_2.sample_rate(), 20); assert!(span_2.by_ref().next().is_some()); assert_eq!(span_2.by_ref().next(), None); diff --git a/tests/test_support/mod.rs b/tests/test_support/mod.rs index 68011e94..6cf225da 100644 --- a/tests/test_support/mod.rs +++ b/tests/test_support/mod.rs @@ -42,7 +42,7 @@ impl SampleSource { } if samples.len() != numb_samples => { *samples = SignalGenerator::new(sample_rate, *frequency, function.clone()) .take(numb_samples) - .flat_map(|sample| iter::repeat_n(sample, channels.into())) + .flat_map(|sample| iter::repeat_n(sample, channels.get().into())) .collect(); samples.get(pos).copied() } @@ -67,7 +67,7 @@ pub struct TestSpan { pub struct TestSpanBuilder { pub sample_source: SampleSource, pub sample_rate: SampleRate, - pub channels: ChannelCount, + pub channels: u16, } impl TestSpan { @@ -143,7 +143,9 @@ impl TestSpanBuilder { self.sample_rate = sample_rate; self } - pub fn with_channel_count(mut self, channel_count: ChannelCount) -> Self { + /// # Panics + /// if channel_count is zero + pub fn with_channel_count(mut self, channel_count: u16) -> Self { self.channels = channel_count; self } @@ -161,7 +163,7 @@ impl TestSpanBuilder { TestSpan { sample_source: self.sample_source, sample_rate: self.sample_rate, - channels: self.channels, + channels: ChannelCount::new(self.channels).expect("Channel count must be > 0"), numb_samples: n, } } @@ -184,7 +186,7 @@ impl TestSpanBuilder { .expect("too many samples for test source"), sample_source: self.sample_source, sample_rate: self.sample_rate, - channels: self.channels, + channels: ChannelCount::new(self.channels).expect("Channel count must be > 0"), } } pub fn with_exact_duration(self, duration: Duration) -> TestSpan { @@ -213,7 +215,7 @@ impl TestSpanBuilder { .expect("too many samples for test source"), sample_source: self.sample_source, sample_rate: self.sample_rate, - channels: self.channels, + channels: ChannelCount::new(self.channels).expect("Channel count must be > 0"), } } @@ -365,9 +367,9 @@ fn channel_count_changes() { .with_sample_count(10), ); - assert_eq!(source.channels(), 1); + assert_eq!(source.channels().get(), 1); assert_eq!(source.by_ref().take(10).count(), 10); - assert_eq!(source.channels(), 2); + assert_eq!(source.channels().get(), 2); } #[test] From 6447d9e1360f8dff701b5a16046dff1e7158d0c2 Mon Sep 17 00:00:00 2001 From: dvdsk Date: Fri, 28 Feb 2025 12:50:57 +0100 Subject: [PATCH 19/25] adds test for reverb --- tests/reverb.rs | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 tests/reverb.rs diff --git a/tests/reverb.rs b/tests/reverb.rs new file mode 100644 index 00000000..0191a09d --- /dev/null +++ b/tests/reverb.rs @@ -0,0 +1,27 @@ +mod test_support; +use std::time::Duration; + +use rodio::Source; +use test_support::{TestSource, TestSpan}; + +#[test] +fn without_buffered() { + let source = TestSource::new() + .with_span(TestSpan::silence().with_exact_duration(Duration::from_secs(5))); + + let reverb = source.clone().reverb(Duration::from_secs_f32(0.05), 0.3); + let n_samples = reverb.count(); + + assert_eq!(n_samples, source.len(),); +} + +#[test] +fn with_buffered() { + let source = TestSource::new() + .with_span(TestSpan::silence().with_exact_duration(Duration::from_secs(5))); + + let reverb = source.clone().buffered().reverb(Duration::from_secs_f32(0.05), 0.3); + let n_samples = reverb.count(); + + assert_eq!(n_samples, source.len(),); +} From 58cd76632f2652b741f3285beb4ea8fe6ef2e476 Mon Sep 17 00:00:00 2001 From: dvdsk Date: Fri, 28 Feb 2025 19:11:52 +0100 Subject: [PATCH 20/25] fix buffered SpanData::End crashing other sources + remove dbg!'s --- src/source/buffered.rs | 2 +- src/source/position.rs | 3 --- tests/seek.rs | 1 - tests/take_duration.rs | 1 - 4 files changed, 1 insertion(+), 6 deletions(-) diff --git a/src/source/buffered.rs b/src/source/buffered.rs index e9148794..5a184092 100644 --- a/src/source/buffered.rs +++ b/src/source/buffered.rs @@ -125,7 +125,7 @@ where fn sample_rate(&self) -> SampleRate { match *self.current_span { Span::Data(SpanData { rate, .. }) => rate, - Span::End => dbg!(0), + Span::End => 1, Span::Input(_) => unreachable!(), } } diff --git a/src/source/position.rs b/src/source/position.rs index a7ea1fbd..f5a1dba1 100644 --- a/src/source/position.rs +++ b/src/source/position.rs @@ -71,12 +71,10 @@ where /// track_position after speedup's and delay's. #[inline] pub fn get_pos(&self) -> Duration { - dbg!(self); let seconds = self.samples_counted as f64 / self.input.sample_rate() as f64 / self.input.channels().get() as f64 + self.offset_duration; - dbg!(seconds); Duration::from_secs_f64(seconds) } } @@ -96,7 +94,6 @@ where // At the end of a span add the duration of this span to // offset_duration and start collecting samples again. if self.parameters_changed() { - dbg!(&self); self.offset_duration += self.samples_counted as f64 / self.current_span_sample_rate as f64 / self.current_span_channels.get() as f64; diff --git a/tests/seek.rs b/tests/seek.rs index 9f9f8d96..987f8d12 100644 --- a/tests/seek.rs +++ b/tests/seek.rs @@ -81,7 +81,6 @@ fn seek_results_in_correct_remaining_playtime( let decoder = get_music(format); let total_duration = time_remaining(decoder); - dbg!(total_duration); const SEEK_BEFORE_END: Duration = Duration::from_secs(5); let mut decoder = get_music(format); diff --git a/tests/take_duration.rs b/tests/take_duration.rs index 2eb946ea..b322c4e1 100644 --- a/tests/take_duration.rs +++ b/tests/take_duration.rs @@ -93,7 +93,6 @@ fn fadeout() { .take_duration(span_duration.mul_f32(1.5)) .with_fadeout(true) .collect::>(); - dbg!(&fade_out); assert_eq!(fade_out.first(), Some(&1.0)); // fade_out ends the step before zero assert!(fade_out.last().unwrap() > &0.0); From 23af4e4f76970b3a5a5d1bbd5869775f2ce4f4f9 Mon Sep 17 00:00:00 2001 From: dvdsk Date: Sat, 1 Mar 2025 17:25:50 +0100 Subject: [PATCH 21/25] adds tests for queue and channel vol. + work on queue Interrupted because: ### Semantics of channels, sample_rate & parameters_changed ## Current situation: greedy Semantics are best explained with an example: - `next` -> (sample at sample_rate _S1_ and channel count _C1_) - `parameters_changed` -> false - `next` -> (sample at sample_rate _S1_ and channel count _C1_) - `parameters_changed` -> true - `next` -> (sample at sample_rate _S2_ and channel count _C2_) - `parameters_changed` -> false - `next` -> (sample at sample_rate _S2_ and channel count _C2_) - `parameters_changed` -> false - `next` -> (sample at sample_rate _S2_ and channel count _C2_) In words, before you call next you get that the parameters are going to change. Said differently you get whether the next sample has a different channel count or sample rate then the previous sample. **Pro:** - Sources that need to handle *channel count* and *sample rate* changes are simpler. - Similar semantics to `current_span_length`, you know ahead of time when to change. **Cons:** - Ever source that can cause sample_rate or channel_count gets rather complex, needing to know ahead of time if that is going to happen. - Seems slower to me because of all the `buffering` ## Alternative: lazy Semantics again explained with an example: - `next` -> (sample at sample_rate _S1_ and channel count _C1_) - `parameters_changed` -> false - `next` -> (sample at sample_rate _S1_ and channel count _C1_) - `parameters_changed` -> false - `next` -> (sample at sample_rate _S2_ and channel count _C2_) - `parameters_changed` -> true - `next` -> (sample at sample_rate _S2_ and channel count _C2_) - `parameters_changed` -> false - `next` -> (sample at sample_rate _S2_ and channel count _C2_) In words, after you call next you learn whether the sample you just got has a different sample rate or channel count then the samples before. **Pro:** - Ever source that can cause sample_rate or channel_count get simpler when they cause a change they can flip a `bool`, reset it after the next sample has been emitted (consumer calls `next`). - Similar semantics to `current_span_length`, you know ahead of time when to change. - Sources that need to handle *channel count* and *sample rate* changes are simpler. **Cons:** - Larger difference to how things are now - I just wrote 2.5k lines with the approach above and I do not want to change them :cry: Note that sources that need to know if the sample_rate or channel_count changes do not need to buffer across calls to `next`. They: - call next - call parameters_changed - realize they did - query the new parameters using `channel_count` and `sample_rate` - handle the sample together with the new parameters --- Cargo.lock | 12 +- Cargo.toml | 1 + src/queue.rs | 246 +++++++++----------------------------- src/sink.rs | 27 +++-- src/source/mod.rs | 84 +++++++------ tests/channel_volume.rs | 46 +++++++ tests/queue.rs | 105 ++++++++++++++++ tests/test_support/mod.rs | 1 - 8 files changed, 284 insertions(+), 238 deletions(-) create mode 100644 tests/channel_volume.rs create mode 100644 tests/queue.rs diff --git a/Cargo.lock b/Cargo.lock index 0fdf1d0b..4820b450 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -75,7 +75,7 @@ dependencies = [ "bitflags 2.8.0", "cexpr", "clang-sys", - "itertools", + "itertools 0.13.0", "proc-macro2", "quote", "regex", @@ -517,6 +517,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + [[package]] name = "jni" version = "0.21.1" @@ -1003,6 +1012,7 @@ dependencies = [ "dasp_sample", "divan", "hound", + "itertools 0.14.0", "lewton", "minimp3_fixed", "num-rational", diff --git a/Cargo.toml b/Cargo.toml index 23a3048a..dda04bc9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -67,6 +67,7 @@ approx = "0.5.1" dasp_sample = "0.11.0" divan = "0.1.14" spectrum-analyzer = "1.6.0" +itertools = "0.14.0" [[bench]] name = "effects" diff --git a/src/queue.rs b/src/queue.rs index 2996b8d4..af93f262 100644 --- a/src/queue.rs +++ b/src/queue.rs @@ -6,152 +6,90 @@ use std::time::Duration; use crate::math::ch; use crate::source::{Empty, SeekError, Source, Zero}; +use crate::source::PeekableSource; use crate::Sample; use crate::common::{ChannelCount, SampleRate}; -#[cfg(feature = "crossbeam-channel")] -use crossbeam_channel::{unbounded as channel, Receiver, Sender}; -#[cfg(not(feature = "crossbeam-channel"))] -use std::sync::mpsc::{channel, Receiver, Sender}; - -/// Builds a new queue. It consists of an input and an output. -/// -/// The input can be used to add sounds to the end of the queue, while the output implements -/// `Source` and plays the sounds. -/// -/// The parameter indicates how the queue should behave if the queue becomes empty: -/// -/// - If you pass `true`, then the queue is infinite and will play a silence instead until you add -/// a new sound. -/// - If you pass `false`, then the queue will report that it has finished playing. -/// -pub fn queue(keep_alive_if_empty: bool) -> (Arc, SourcesQueueOutput) { - let input = Arc::new(SourcesQueueInput { - next_sounds: Mutex::new(Vec::new()), - keep_alive_if_empty: AtomicBool::new(keep_alive_if_empty), - }); - - let output = SourcesQueueOutput { - current: Box::new(Empty::new()) as Box<_>, - signal_after_end: None, - input: input.clone(), - }; - - (input, output) -} - -// TODO: consider reimplementing this with `from_factory` type Sound = Box; -type SignalDone = Option>; +struct Inner { + next_sounds: Mutex>>, + keep_alive_if_empty: AtomicBool, +} /// The input of the queue. -pub struct SourcesQueueInput { - next_sounds: Mutex>, +pub struct Queue(Arc); - // See constructor. - keep_alive_if_empty: AtomicBool, -} +impl Queue { + /// Builds a new queue. It consists of an input and an output. + /// + /// The input can be used to add sounds to the end of the queue, while the output implements + /// `Source` and plays the sounds. + /// + /// The parameter indicates how the queue should behave if the queue becomes empty: + /// + /// - If you pass `true`, then the queue is infinite and will play a silence instead until you add + /// a new sound. + /// - If you pass `false`, then the queue will report that it has finished playing. + pub fn new(keep_alive_if_empty: bool) -> (Self, QueueSource) { + let input = Arc::new(Inner { + next_sounds: Mutex::new(Vec::new()), + keep_alive_if_empty: AtomicBool::new(keep_alive_if_empty), + }); + + let output = QueueSource { + current: Empty::new().type_erased().peekable_source(), + input: input.clone(), + starting_new_source: false, + }; -impl SourcesQueueInput { - /// Adds a new source to the end of the queue. - #[inline] - pub fn append(&self, source: T) - where - T: Source + Send + 'static, - { - self.next_sounds - .lock() - .unwrap() - .push((Box::new(source) as Box<_>, None)); + (Self(input), output) } /// Adds a new source to the end of the queue. - /// - /// The `Receiver` will be signalled when the sound has finished playing. - /// - /// Enable the feature flag `crossbeam-channel` in rodio to use a `crossbeam_channel::Receiver` instead. #[inline] - pub fn append_with_signal(&self, source: T) -> Receiver<()> + pub fn append(&self, source: T) where T: Source + Send + 'static, { - let (tx, rx) = channel(); - self.next_sounds + self.0 + .next_sounds .lock() .unwrap() - .push((Box::new(source) as Box<_>, Some(tx))); - rx + .push(source.type_erased().peekable_source()); } /// Sets whether the queue stays alive if there's no more sound to play. /// /// See also the constructor. - pub fn set_keep_alive_if_empty(&self, keep_alive_if_empty: bool) { - self.keep_alive_if_empty - .store(keep_alive_if_empty, Ordering::Release); + pub fn keep_alive_if_empty(&self, keep_alive_if_empty: bool) { + self.0 // relaxed: no one depends on the exact order + .keep_alive_if_empty + .store(keep_alive_if_empty, Ordering::Relaxed); } /// Removes all the sounds from the queue. Returns the number of sounds cleared. pub fn clear(&self) -> usize { - let mut sounds = self.next_sounds.lock().unwrap(); + let mut sounds = self.0.next_sounds.lock().unwrap(); let len = sounds.len(); sounds.clear(); len } } -/// The output of the queue. Implements `Source`. -pub struct SourcesQueueOutput { - // The current iterator that produces samples. - current: Box, - // Signal this sender before picking from `next`. - signal_after_end: Option>, - - // The next sounds. - input: Arc, +/// Play this source to hear sounds appended to the Queue +pub struct QueueSource { + current: PeekableSource>, + starting_new_source: bool, + input: Arc, } const THRESHOLD: usize = 512; -impl Source for SourcesQueueOutput { +impl Source for QueueSource { #[inline] fn parameters_changed(&self) -> bool { - // This function is non-trivial because the boundary between two sounds in the queue should - // be a span boundary as well. - // - // The current sound is free to return `None` for `parameters_changed()`, in which case - // we *should* return the number of samples remaining the current sound. - // This can be estimated with `size_hint()`. - // - // If the `size_hint` is `None` as well, we are in the worst case scenario. To handle this - // situation we force a span to have a maximum number of samples indicate by this - // constant. - - // Try the current `current_span_len`. - - todo!() - // if let Some(val) = self.current.parameters_changed() { - // if val != 0 { - // return Some(val); - // } else if self.input.keep_alive_if_empty.load(Ordering::Acquire) - // && self.input.next_sounds.lock().unwrap().is_empty() - // { - // // The next source will be a filler silence which will have the length of `THRESHOLD` - // return Some(THRESHOLD); - // } - // } - // - // // Try the size hint. - // let (lower_bound, _) = self.current.size_hint(); - // // The iterator default implementation just returns 0. - // // That's a problematic value, so skip it. - // if lower_bound > 0 { - // return Some(lower_bound); - // } - // - // // Otherwise we use the constant value. - // Some(THRESHOLD) + self.current.peek().is_none() || self.current.parameters_changed() } #[inline] @@ -174,7 +112,7 @@ impl Source for SourcesQueueOutput { // that it advances the queue if the position is beyond the current song. // // We would then however need to enable seeking backwards across sources too. - // That no longer seems in line with the queue behaviour. + // That no longer seems in line with the queue behavior. // // A final pain point is that we would need the total duration for the // next few songs. @@ -184,19 +122,21 @@ impl Source for SourcesQueueOutput { } } -impl Iterator for SourcesQueueOutput { +impl Iterator for QueueSource { type Item = Sample; #[inline] fn next(&mut self) -> Option { loop { // Basic situation that will happen most of the time. + self.starting_new_source = false; if let Some(sample) = self.current.next() { return Some(sample); } // Since `self.current` has finished, we need to pick the next sound. - // In order to avoid inlining this expensive operation, the code is in another function. + // In order to avoid inlining this expensive operation, + // the code is in another function. if self.go_next().is_err() { return None; } @@ -209,24 +149,20 @@ impl Iterator for SourcesQueueOutput { } } -impl SourcesQueueOutput { +impl QueueSource { // Called when `current` is empty, and we must jump to the next element. // Returns `Ok` if the sound should continue playing, or an error if it should stop. // // This method is separate so that it is not inlined. fn go_next(&mut self) -> Result<(), ()> { - if let Some(signal_after_end) = self.signal_after_end.take() { - let _ = signal_after_end.send(()); - } - - let (next, signal_after_end) = { + let next = { let mut next = self.input.next_sounds.lock().unwrap(); - if next.len() == 0 { - let silence = Box::new(Zero::new_samples(ch!(1), 44100, THRESHOLD)) as Box<_>; - if self.input.keep_alive_if_empty.load(Ordering::Acquire) { + if next.is_empty() { + let silence = Zero::new_samples(ch!(1), 44100, THRESHOLD).type_erased().peekable_source(); + if self.input.keep_alive_if_empty.load(Ordering::Relaxed) { // Play a short silence in order to avoid spinlocking. - (silence, None) + silence } else { return Err(()); } @@ -235,76 +171,8 @@ impl SourcesQueueOutput { } }; + self.starting_new_source = true; self.current = next; - self.signal_after_end = signal_after_end; Ok(()) } } - -#[cfg(test)] -mod tests { - use crate::buffer::SamplesBuffer; - use crate::math::ch; - use crate::queue; - use crate::source::Source; - - #[test] - #[ignore] // FIXME: samples rate and channel not updated immediately after transition - fn basic() { - let (tx, mut rx) = queue::queue(false); - - tx.append(SamplesBuffer::new(ch!(1), 48000, vec![10.0, -10.0, 10.0, -10.0])); - tx.append(SamplesBuffer::new(ch!(2), 96000, vec![5.0, 5.0, 5.0, 5.0])); - - assert_eq!(rx.channels(), ch!(1)); - assert_eq!(rx.sample_rate(), 48000); - assert_eq!(rx.next(), Some(10.0)); - assert_eq!(rx.next(), Some(-10.0)); - assert_eq!(rx.next(), Some(10.0)); - assert_eq!(rx.next(), Some(-10.0)); - assert_eq!(rx.channels(), ch!(2)); - assert_eq!(rx.sample_rate(), 96000); - assert_eq!(rx.next(), Some(5.0)); - assert_eq!(rx.next(), Some(5.0)); - assert_eq!(rx.next(), Some(5.0)); - assert_eq!(rx.next(), Some(5.0)); - assert_eq!(rx.next(), None); - } - - #[test] - fn immediate_end() { - let (_, mut rx) = queue::queue(false); - assert_eq!(rx.next(), None); - } - - #[test] - fn keep_alive() { - let (tx, mut rx) = queue::queue(true); - tx.append(SamplesBuffer::new(ch!(1), 48000, vec![10.0, -10.0, 10.0, -10.0])); - - assert_eq!(rx.next(), Some(10.0)); - assert_eq!(rx.next(), Some(-10.0)); - assert_eq!(rx.next(), Some(10.0)); - assert_eq!(rx.next(), Some(-10.0)); - - for _ in 0..100000 { - assert_eq!(rx.next(), Some(0.0)); - } - } - - #[test] - #[ignore] // TODO: not yet implemented - fn no_delay_when_added() { - let (tx, mut rx) = queue::queue(true); - - for _ in 0..500 { - assert_eq!(rx.next(), Some(0.0)); - } - - tx.append(SamplesBuffer::new(ch!(1), 48000, vec![10.0, -10.0, 10.0, -10.0])); - assert_eq!(rx.next(), Some(10.0)); - assert_eq!(rx.next(), Some(-10.0)); - assert_eq!(rx.next(), Some(10.0)); - assert_eq!(rx.next(), Some(-10.0)); - } -} diff --git a/src/sink.rs b/src/sink.rs index 94181c86..8cbd1344 100644 --- a/src/sink.rs +++ b/src/sink.rs @@ -1,5 +1,5 @@ use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; -use std::sync::{Arc, Mutex}; +use std::sync::{mpsc, Arc, Mutex}; use std::time::Duration; #[cfg(feature = "crossbeam-channel")] @@ -9,15 +9,16 @@ use dasp_sample::FromSample; use std::sync::mpsc::{Receiver, Sender}; use crate::mixer::Mixer; +use crate::queue::{Queue, QueueSource}; use crate::source::SeekError; -use crate::{queue, source::Done, Source}; +use crate::{queue, source::Done, source::EmptyCallback, Source}; /// Handle to a device that outputs sounds. /// /// Dropping the `Sink` stops all its sounds. You can use `detach` if you want the sounds to continue /// playing. pub struct Sink { - queue_tx: Arc, + queue: queue::Queue, sleep_until_end: Mutex>>, controls: Arc, @@ -77,11 +78,11 @@ impl Sink { /// Builds a new `Sink`. #[inline] - pub fn new() -> (Sink, queue::SourcesQueueOutput) { - let (queue_tx, queue_rx) = queue::queue(true); + pub fn new() -> (Sink, QueueSource) { + let (queue, source) = Queue::new(true); let sink = Sink { - queue_tx, + queue, sleep_until_end: Mutex::new(None), controls: Arc::new(Controls { pause: AtomicBool::new(false), @@ -95,7 +96,7 @@ impl Sink { sound_count: Arc::new(AtomicUsize::new(0)), detached: false, }; - (sink, queue_rx) + (sink, source) } /// Appends a sound to the queue of sounds to play. @@ -156,7 +157,15 @@ impl Sink { }); self.sound_count.fetch_add(1, Ordering::Relaxed); let source = Done::new(source, self.sound_count.clone()); - *self.sleep_until_end.lock().unwrap() = Some(self.queue_tx.append_with_signal(source)); + self.queue.append(source); + + let (tx, rx) = mpsc::channel(); + let callback_source = EmptyCallback::new(Box::new(move || { + let _ = tx.send(()); + })); + let callback_source = Box::new(callback_source) as Box; + self.queue.append(callback_source); + *self.sleep_until_end.lock().unwrap() = Some(rx); } /// Gets the volume of the sound. @@ -353,7 +362,7 @@ impl Sink { impl Drop for Sink { #[inline] fn drop(&mut self) { - self.queue_tx.set_keep_alive_if_empty(false); + self.queue.keep_alive_if_empty(false); if !self.detached { self.controls.stopped.store(true, Ordering::Relaxed); diff --git a/src/source/mod.rs b/src/source/mod.rs index b703b299..c1f22549 100644 --- a/src/source/mod.rs +++ b/src/source/mod.rs @@ -6,44 +6,44 @@ use core::time::Duration; use crate::common::{ChannelCount, SampleRate}; use crate::Sample; use dasp_sample::FromSample; -use peekable::PeekableSource; -use take_samples::TakeSamples; -use take_span::TakeSpan; - -pub use self::agc::AutomaticGainControl; -pub use self::amplify::Amplify; -pub use self::blt::BltFilter; -pub use self::buffered::Buffered; -pub use self::channel_volume::ChannelVolume; -pub use self::chirp::{chirp, Chirp}; -pub use self::crossfade::Crossfade; -pub use self::delay::Delay; -pub use self::done::Done; -pub use self::empty::Empty; -pub use self::empty_callback::EmptyCallback; -pub use self::fadein::FadeIn; -pub use self::fadeout::FadeOut; -pub use self::from_factory::{from_factory, FromFactoryIter}; -pub use self::from_iter::{from_iter, FromIter}; -pub use self::linear_ramp::LinearGainRamp; -pub use self::mix::Mix; -pub use self::pausable::Pausable; -pub use self::periodic::PeriodicAccess; -pub use self::position::TrackPosition; -pub use self::repeat::Repeat; -pub use self::sawtooth::SawtoothWave; -pub use self::signal_generator::{Function, SignalGenerator}; -pub use self::sine::SineWave; -pub use self::skip_duration::SkipDuration; -pub use self::skippable::Skippable; -pub use self::spatial::Spatial; -pub use self::speed::Speed; -pub use self::square::SquareWave; -pub use self::stoppable::Stoppable; -pub use self::take_duration::TakeDuration; -pub use self::triangle::TriangleWave; -pub use self::uniform::UniformSourceIterator; -pub use self::zero::Zero; + +pub use agc::AutomaticGainControl; +pub use amplify::Amplify; +pub use blt::BltFilter; +pub use buffered::Buffered; +pub use channel_volume::ChannelVolume; +pub use chirp::{chirp, Chirp}; +pub use crossfade::Crossfade; +pub use delay::Delay; +pub use done::Done; +pub use empty::Empty; +pub use empty_callback::EmptyCallback; +pub use fadein::FadeIn; +pub use fadeout::FadeOut; +pub use from_factory::{from_factory, FromFactoryIter}; +pub use from_iter::{from_iter, FromIter}; +pub use linear_ramp::LinearGainRamp; +pub use mix::Mix; +pub use pausable::Pausable; +pub use peekable::PeekableSource; +pub use periodic::PeriodicAccess; +pub use position::TrackPosition; +pub use repeat::Repeat; +pub use sawtooth::SawtoothWave; +pub use signal_generator::{Function, SignalGenerator}; +pub use sine::SineWave; +pub use skip_duration::SkipDuration; +pub use skippable::Skippable; +pub use spatial::Spatial; +pub use speed::Speed; +pub use square::SquareWave; +pub use stoppable::Stoppable; +pub use take_duration::TakeDuration; +pub use take_samples::TakeSamples; +pub use take_span::TakeSpan; +pub use triangle::TriangleWave; +pub use uniform::UniformSourceIterator; +pub use zero::Zero; mod agc; mod amplify; @@ -377,6 +377,14 @@ pub trait Source: Iterator { crossfade::crossfade(self, other, duration) } + /// Erases the type of self by placing it on the heap + fn type_erased(self) -> Box + where + Self: Sized + Send + 'static, + { + Box::new(self) + } + /// Fades in the sound. #[inline] fn fade_in(self, duration: Duration) -> FadeIn diff --git a/tests/channel_volume.rs b/tests/channel_volume.rs new file mode 100644 index 00000000..5a077d38 --- /dev/null +++ b/tests/channel_volume.rs @@ -0,0 +1,46 @@ +use std::fs; +use std::io::BufReader; + +use itertools::Itertools; + +use rodio::queue::Queue; +use rodio::source::ChannelVolume; +use rodio::{Decoder, Source}; + +#[test] +fn no_queue() { + let file = fs::File::open("assets/music.mp3").unwrap(); + let decoder = Decoder::new(BufReader::new(file)).unwrap(); + assert_eq!(decoder.channels().get(), 2); + let channel_volume = ChannelVolume::new(decoder, vec![1.0, 1.0, 0.0, 0.0, 0.0, 0.0]); + assert_eq!(channel_volume.channels().get(), 6); + + assert_output_only_on_channel_1_and_2(channel_volume); +} + +#[test] +fn with_queue_in_between() { + let file = fs::File::open("assets/music.mp3").unwrap(); + let decoder = Decoder::new(BufReader::new(file)).unwrap(); + assert_eq!(decoder.channels().get(), 2); + let channel_volume = ChannelVolume::new(decoder, vec![1.0, 1.0, 0.0, 0.0, 0.0, 0.0]); + assert_eq!(channel_volume.channels().get(), 6); + + let (controls, queue) = Queue::new(false); + controls.append(channel_volume); + + assert_output_only_on_channel_1_and_2(queue); +} + +fn assert_output_only_on_channel_1_and_2(source: impl Source) { + for (frame_number, mut frame) in source.chunks(6).into_iter().enumerate() { + let frame: [_; 6] = frame.next_array().expect(&format!( + "Source should contain whole frames, frame {frame_number} was partial" + )); + assert_eq!( + &frame[2..], + &[0., 0., 0., 0.], + "frame {frame_number} had nonzero volume on a channel that should be zero" + ) + } +} diff --git a/tests/queue.rs b/tests/queue.rs new file mode 100644 index 00000000..237bb2e1 --- /dev/null +++ b/tests/queue.rs @@ -0,0 +1,105 @@ +use rodio::queue::Queue; +use rodio::source::Source; + +mod test_support; +use test_support::{TestSource, TestSpan}; + +#[test] +fn basic() { + let (controls, mut source) = Queue::new(false); + + let mut source1 = TestSource::new().with_span( + TestSpan::silence() + .with_sample_rate(48000) + .with_sample_count(4), + ); + let mut source2 = TestSource::new().with_span( + TestSpan::silence() + .with_sample_rate(96000) + .with_channel_count(2) + .with_sample_count(4), + ); + controls.append(source1.clone()); + controls.append(source2.clone()); + + assert_eq!(source.parameters_changed(), true); + assert_eq!(source.channels(), source1.channels()); + assert_eq!(source.sample_rate(), source1.sample_rate()); + assert_eq!(source.next(), source1.next()); + assert_eq!(source.next(), source1.next()); + assert_eq!(source.next(), source1.next()); + assert_eq!(source.next(), source1.next()); + assert_eq!(None, source1.next()); + assert_eq!(source.parameters_changed(), true); + + assert_eq!(source.parameters_changed(), true); + assert_eq!(source.channels(), source2.channels()); + assert_eq!(source.sample_rate(), source2.sample_rate()); + assert_eq!(source.next(), source2.next()); + assert_eq!(source.next(), source2.next()); + assert_eq!(source.parameters_changed(), false); + assert_eq!(source.next(), source2.next()); + assert_eq!(source.next(), source2.next()); + assert_eq!(None, source2.next()); + + assert_eq!(source.next(), None); +} + +#[test] +fn immediate_end() { + let (_, mut source) = Queue::new(false); + assert_eq!(source.next(), None); +} + +#[test] +fn keep_alive() { + let (controls, mut source) = Queue::new(true); + controls.append( + TestSource::new() + .with_span(TestSpan::from_samples([0.1, -0.1, 0.1, -0.1]).with_sample_count(4)), + ); + + assert_eq!(source.next(), Some(0.1)); + assert_eq!(source.next(), Some(-0.1)); + assert_eq!(source.next(), Some(0.1)); + assert_eq!(source.next(), Some(-0.1)); + + for _ in 0..1000 { + assert_eq!(source.next(), Some(0.0)); + } +} + +#[test] +fn no_delay_when_added_with_keep_alive() { + let (controls, mut source) = Queue::new(true); + + for _ in 0..500 { + assert_eq!(source.next(), Some(0.0)); + } + + controls.append( + TestSource::new().with_span( + TestSpan::from_samples([0.1, -0.1, 0.1, -0.1]) + .with_channel_count(4) + .with_sample_count(4), + ), + ); + + assert_eq!(source.next(), Some(0.1)); + assert_eq!(source.next(), Some(-0.1)); + assert_eq!(source.next(), Some(0.1)); + assert_eq!(source.next(), Some(-0.1)); +} + +#[test] +fn parameters_queried_before_next() { + let test_source = + TestSource::new().with_span(TestSpan::ones().with_channel_count(5).with_sample_count(20)); + + let (controls, mut source) = Queue::new(true); + + assert_eq!(source.channels().get(), 1); + controls.append(test_source); + assert_eq!(source.channels().get(), 5); + assert_eq!(source.next(), Some(1.0)); +} diff --git a/tests/test_support/mod.rs b/tests/test_support/mod.rs index 6cf225da..6ad751ff 100644 --- a/tests/test_support/mod.rs +++ b/tests/test_support/mod.rs @@ -275,7 +275,6 @@ impl Iterator for TestSource { if self.pos_in_span == current_span.len() { self.pos_in_span = 0; self.current_span += 1; - self.parameters_changed = true; } else { self.parameters_changed = false; } From 32f2039350dc02bcea89099417b11f2b15d4b835 Mon Sep 17 00:00:00 2001 From: dvdsk Date: Sun, 2 Mar 2025 12:39:40 +0100 Subject: [PATCH 22/25] update docs for peekable --- src/queue.rs | 2 +- src/source/buffered.rs | 22 +++++++++------------- src/source/mod.rs | 12 ------------ src/source/noise.rs | 2 +- src/source/peekable.rs | 12 +++++++++++- src/source/repeat.rs | 6 +++--- src/source/take_samples.rs | 6 ++++-- tests/peekable.rs | 13 +++++++------ 8 files changed, 36 insertions(+), 39 deletions(-) diff --git a/src/queue.rs b/src/queue.rs index af93f262..b06101bd 100644 --- a/src/queue.rs +++ b/src/queue.rs @@ -89,7 +89,7 @@ const THRESHOLD: usize = 512; impl Source for QueueSource { #[inline] fn parameters_changed(&self) -> bool { - self.current.peek().is_none() || self.current.parameters_changed() + self.current.peek_next().is_none() || self.current.parameters_changed() } #[inline] diff --git a/src/source/buffered.rs b/src/source/buffered.rs index 5a184092..08d1a0a6 100644 --- a/src/source/buffered.rs +++ b/src/source/buffered.rs @@ -4,7 +4,7 @@ use std::mem; use std::sync::{Arc, Mutex}; use std::time::Duration; -use super::SeekError; +use super::{PeekableSource, SeekError}; use crate::common::{ChannelCount, SampleRate}; use crate::math::{ch, PrevMultipleOf}; use crate::Source; @@ -16,9 +16,7 @@ where I: Source, { /// Immutable reference to the next span of data. Cannot be `Span::Input`. - current_span: Arc>, - - parameters_changed: bool, + current_span: Arc>>, /// The position in number of samples of this iterator inside `current_span`. position_in_span: usize, @@ -30,13 +28,12 @@ where impl Buffered { pub(crate) fn new(input: I) -> Buffered { let total_duration = input.total_duration(); - let first_span = extract(input); + let first_span = extract(input.peekable_source()); Buffered { current_span: first_span, position_in_span: 0, total_duration, - parameters_changed: false, } } @@ -74,6 +71,7 @@ where #[inline] fn next(&mut self) -> Option { + dbg!(); let current_sample; let advance_span; @@ -81,6 +79,7 @@ where Span::Data(SpanData { data, .. }) => { current_sample = Some(data[self.position_in_span]); self.position_in_span += 1; + dbg!(self.position_in_span); advance_span = self.position_in_span >= data.len(); } @@ -93,10 +92,8 @@ where }; if advance_span { - self.parameters_changed = true; + dbg!(); self.next_span(); - } else { - self.parameters_changed = false; } current_sample @@ -109,7 +106,7 @@ where { #[inline] fn parameters_changed(&self) -> bool { - self.parameters_changed + dbg!(self.position_in_span) == 1 } #[inline] @@ -155,7 +152,6 @@ where current_span: self.current_span.clone(), position_in_span: self.position_in_span, total_duration: self.total_duration, - parameters_changed: self.parameters_changed, } } } @@ -226,7 +222,7 @@ where } /// Builds a span from the input iterator. -fn extract(mut input: I) -> Arc> +fn extract(mut input: PeekableSource) -> Arc>> where I: Source, { @@ -239,7 +235,7 @@ where break; }; data.push(sample); - if input.parameters_changed() { + if input.peek_parameters_changed() { break; } if data.len() > 32768.prev_multiple_of(channels.into()) { diff --git a/src/source/mod.rs b/src/source/mod.rs index c1f22549..ffd3b235 100644 --- a/src/source/mod.rs +++ b/src/source/mod.rs @@ -40,7 +40,6 @@ pub use square::SquareWave; pub use stoppable::Stoppable; pub use take_duration::TakeDuration; pub use take_samples::TakeSamples; -pub use take_span::TakeSpan; pub use triangle::TriangleWave; pub use uniform::UniformSourceIterator; pub use zero::Zero; @@ -78,7 +77,6 @@ mod square; mod stoppable; mod take_duration; mod take_samples; -mod take_span; mod triangle; mod uniform; mod zero; @@ -216,16 +214,6 @@ pub trait Source: Iterator { TakeDuration::new(self, duration) } - /// Takes a samples until the current span ends, which is when any of - /// the source parameters_change. - #[inline] - fn take_span(self) -> TakeSpan - where - Self: Sized, - { - TakeSpan::new(self) - } - /// Takes n samples. N might be slightly adjusted to make sure /// this source ends on a frame boundary #[inline] diff --git a/src/source/noise.rs b/src/source/noise.rs index 350eb3dd..be4a1587 100644 --- a/src/source/noise.rs +++ b/src/source/noise.rs @@ -61,7 +61,7 @@ impl Iterator for WhiteNoise { impl Source for WhiteNoise { #[inline] fn parameters_changed(&self) -> bool { - None + false } #[inline] diff --git a/src/source/peekable.rs b/src/source/peekable.rs index efbf5725..0eb08cd4 100644 --- a/src/source/peekable.rs +++ b/src/source/peekable.rs @@ -4,6 +4,10 @@ use crate::{ChannelCount, Sample, SampleRate}; use super::Source; +/// A source with a `peek()` method that returns the next sample without +/// advancing the source. This `struct` is created by the +/// [`peekable_source`](Source::peekable_source) method on [Source]. See its +/// documentation for more. pub struct PeekableSource { next: Option, channels: ChannelCount, @@ -47,9 +51,15 @@ impl PeekableSource { /// Look at the next sample. This does not advance the source. /// Can be used to determine if the current sample was the last. - pub fn peek(&self) -> Option { + pub fn peek_next(&self) -> Option { self.next } + + /// Do the parameters change after the next sample? This does not advance + /// the source. + pub fn peek_parameters_changed(&self) -> bool { + self.parameters_changed_after_next + } } impl Iterator for PeekableSource { diff --git a/src/source/repeat.rs b/src/source/repeat.rs index 7d4fe77f..1deb030f 100644 --- a/src/source/repeat.rs +++ b/src/source/repeat.rs @@ -52,7 +52,7 @@ where { #[inline] fn parameters_changed(&self) -> bool { - if self.inner.peek().is_none() { + if self.inner.peek_next().is_none() { true // back to beginning of source source } else { self.inner.parameters_changed() @@ -61,7 +61,7 @@ where #[inline] fn channels(&self) -> ChannelCount { - if self.inner.peek().is_none() { + if self.inner.peek_next().is_none() { self.next.channels() } else { self.inner.channels() @@ -70,7 +70,7 @@ where #[inline] fn sample_rate(&self) -> SampleRate { - if self.inner.peek().is_none() { + if self.inner.peek_next().is_none() { self.next.sample_rate() } else { self.inner.sample_rate() diff --git a/src/source/take_samples.rs b/src/source/take_samples.rs index 777ebdf9..1a3d9b54 100644 --- a/src/source/take_samples.rs +++ b/src/source/take_samples.rs @@ -5,7 +5,9 @@ use crate::common::{ChannelCount, SampleRate}; use crate::math::PrevMultipleOf; use crate::Source; -/// A source that truncates the given source to a certain duration. +/// A source that truncates the given source to a certain duration. This +/// `struct` is created by the [`take_samples`](Source::take_samples) method +/// on [Source]. See its documentation for more. #[derive(Clone, Debug)] pub struct TakeSamples { input: I, @@ -17,7 +19,7 @@ impl TakeSamples where I: Source, { - pub fn new(input: I, n: usize) -> Self { + pub(crate) fn new(input: I, n: usize) -> Self { Self { input, taken: 0, diff --git a/tests/peekable.rs b/tests/peekable.rs index a0d4ee9a..f9487740 100644 --- a/tests/peekable.rs +++ b/tests/peekable.rs @@ -17,7 +17,7 @@ fn peeked_matches_next() { let mut peekable = source.peekable_source(); for _ in 0..peekable.len() { - let peeked = peekable.peek(); + let peeked = peekable.peek_next(); let next = peekable.next(); assert!( peeked == next, @@ -32,13 +32,14 @@ fn parameters_change_correct() { .with_span(TestSpan::silence().with_sample_count(10)) .with_span(TestSpan::silence().with_sample_count(10)); let mut peekable = source.peekable_source(); - peekable.peek(); + peekable.peek_next(); assert_eq!(peekable.by_ref().take(10).count(), 10); - assert!(peekable.parameters_changed()); + assert!(!peekable.parameters_changed()); + // end of first span assert!(peekable.next().is_some()); - assert!(!peekable.parameters_changed()); + assert!(peekable.parameters_changed()); assert_eq!(peekable.count(), 9); } @@ -57,7 +58,7 @@ fn channel_count_changes() { .with_sample_count(10), ); let mut peekable = source.peekable_source(); - peekable.peek(); + peekable.peek_next(); assert_eq!(peekable.channels().get(), 1); assert_eq!(peekable.by_ref().take(10).count(), 10); @@ -78,7 +79,7 @@ fn sample_rate_changes() { .with_sample_count(10), ); let mut peekable = source.peekable_source(); - peekable.peek(); + peekable.peek_next(); assert_eq!(peekable.sample_rate(), 10); assert_eq!(peekable.by_ref().take(10).count(), 10); From 3634210dac27936e5afb9fe78f0b1c0eb42b68ba Mon Sep 17 00:00:00 2001 From: dvdsk Date: Sun, 2 Mar 2025 12:40:54 +0100 Subject: [PATCH 23/25] partially implements lazy parameters_changed for testsource & uniform --- src/source/take_span.rs | 77 --------------------------------------- src/source/uniform.rs | 6 +-- tests/buffered.rs | 20 ++++++++-- tests/take_span.rs | 35 ------------------ tests/test_support/mod.rs | 12 +++--- 5 files changed, 24 insertions(+), 126 deletions(-) delete mode 100644 src/source/take_span.rs delete mode 100644 tests/take_span.rs diff --git a/src/source/take_span.rs b/src/source/take_span.rs deleted file mode 100644 index b5b1a191..00000000 --- a/src/source/take_span.rs +++ /dev/null @@ -1,77 +0,0 @@ -use std::time::Duration; - -use super::SeekError; -use crate::common::{ChannelCount, SampleRate}; -use crate::Source; - -/// A source that truncates the given source to a certain duration. -#[derive(Clone, Debug)] -pub struct TakeSpan { - first: bool, - input: I, -} - -impl TakeSpan -where - I: Source, -{ - pub fn new(input: I) -> Self { - Self { first: true, input } - } - - /// Returns a mutable reference to the inner source. - #[inline] - pub fn inner_mut(&mut self) -> &mut I { - &mut self.input - } - - /// Returns the inner source. - #[inline] - pub fn into_inner(self) -> I { - self.input - } -} - -impl Iterator for TakeSpan { - type Item = ::Item; - - fn next(&mut self) -> Option<::Item> { - if self.input.parameters_changed() && !self.first { - None - } else { - self.first = false; - let sample = self.input.next()?; - Some(sample) - } - } -} - -impl Source for TakeSpan -where - I: Iterator + Source, -{ - #[inline] - fn parameters_changed(&self) -> bool { - false - } - - #[inline] - fn channels(&self) -> ChannelCount { - self.input.channels() - } - - #[inline] - fn sample_rate(&self) -> SampleRate { - self.input.sample_rate() - } - - #[inline] - fn total_duration(&self) -> Option { - self.input.total_duration() - } - - #[inline] - fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> { - self.input.try_seek(pos) - } -} diff --git a/src/source/uniform.rs b/src/source/uniform.rs index edd7b68f..7c9849f5 100644 --- a/src/source/uniform.rs +++ b/src/source/uniform.rs @@ -1,13 +1,12 @@ use std::time::Duration; use super::take_samples::TakeSamples; -use super::take_span::TakeSpan; use super::SeekError; use crate::common::{ChannelCount, SampleRate}; use crate::conversions::{ChannelCountConverter, SampleRateConverter}; use crate::Source; -type Converted = ChannelCountConverter>>>; +type Converted = ChannelCountConverter>>; /// An iterator that reads from a `Source` and converts the samples to a /// specific type, sample-rate and channels count. @@ -57,7 +56,7 @@ where let from_channels = input.channels(); let from_sample_rate = input.sample_rate(); - let input = input.take_span().take_samples(32768); + let input = input.take_samples(32768); let input = SampleRateConverter::new(input, from_sample_rate, target_sample_rate, from_channels); ChannelCountConverter::new(input, from_channels, target_channels) @@ -82,7 +81,6 @@ where .unwrap() .into_inner() .into_inner() - .into_inner() .into_inner(); let mut input = diff --git a/tests/buffered.rs b/tests/buffered.rs index 819eef9e..3e86c69b 100644 --- a/tests/buffered.rs +++ b/tests/buffered.rs @@ -9,13 +9,17 @@ fn parameters_change_correct() { .with_span(TestSpan::silence().with_sample_count(10)) .buffered(); - assert_eq!(source.by_ref().take(10).count(), 10); + assert!(source.next().is_some()); assert!(source.parameters_changed()); + assert_eq!(source.by_ref().take(9).count(), 9); + // span ends + assert!(source.next().is_some()); + assert!(source.parameters_changed()); assert!(source.next().is_some()); assert!(!source.parameters_changed()); - assert_eq!(source.count(), 9); + assert_eq!(source.count(), 8); } #[test] @@ -33,8 +37,12 @@ fn channel_count_changes() { ) .buffered(); + assert!(source.next().is_some()); assert_eq!(source.channels().get(), 1); - assert_eq!(source.by_ref().take(10).count(), 10); + assert_eq!(source.by_ref().take(9).count(), 9); + + // next span starts + assert!(source.next().is_some()); assert_eq!(source.channels().get(), 2); } @@ -53,8 +61,12 @@ fn buffered_sample_rate_changes() { ) .buffered(); + assert!(source.next().is_some()); assert_eq!(source.sample_rate(), 10); - assert_eq!(source.by_ref().take(10).count(), 10); + assert_eq!(source.by_ref().take(9).count(), 9); + + // next span starts + assert!(source.next().is_some()); assert_eq!(source.sample_rate(), 20); } diff --git a/tests/take_span.rs b/tests/take_span.rs deleted file mode 100644 index d0066a61..00000000 --- a/tests/take_span.rs +++ /dev/null @@ -1,35 +0,0 @@ -use rodio::Source; -use test_support::{TestSource, TestSpan}; - -mod test_support; - -#[test] -fn param_changes_during_skip() { - let source = TestSource::new() - .with_span( - TestSpan::sample_counter() - .with_sample_rate(10) - .with_channel_count(1) - .with_sample_count(10), - ) - .with_span( - TestSpan::sample_counter() - .with_sample_rate(20) - .with_channel_count(2) - .with_sample_count(10), - ); - - let mut span_1 = source.take_span(); - assert_eq!(span_1.by_ref().take(9).count(), 9); - assert_eq!(span_1.channels().get(), 1); - assert_eq!(span_1.sample_rate(), 10); - assert!(span_1.by_ref().next().is_some()); - assert_eq!(span_1.by_ref().next(), None); - - let mut span_2 = span_1.into_inner().take_span(); - assert_eq!(span_2.by_ref().take(9).count(), 9); - assert_eq!(span_2.channels().get(), 2); - assert_eq!(span_2.sample_rate(), 20); - assert!(span_2.by_ref().next().is_some()); - assert_eq!(span_2.by_ref().next(), None); -} diff --git a/tests/test_support/mod.rs b/tests/test_support/mod.rs index 6ad751ff..cce584d1 100644 --- a/tests/test_support/mod.rs +++ b/tests/test_support/mod.rs @@ -275,8 +275,6 @@ impl Iterator for TestSource { if self.pos_in_span == current_span.len() { self.pos_in_span = 0; self.current_span += 1; - } else { - self.parameters_changed = false; } Some(sample) @@ -298,7 +296,7 @@ impl ExactSizeIterator for TestSource {} impl rodio::Source for TestSource { fn parameters_changed(&self) -> bool { - self.parameters_changed + self.pos_in_span == 1 } fn channels(&self) -> rodio::ChannelCount { self.spans @@ -343,12 +341,14 @@ fn parameters_change_correct() { .with_span(TestSpan::silence().with_sample_count(10)) .with_span(TestSpan::silence().with_sample_count(10)); - assert_eq!(source.by_ref().take(10).count(), 10); - assert!(source.parameters_changed()); - assert!(source.next().is_some()); + assert!(source.parameters_changed()); + assert_eq!(source.by_ref().take(9).count(), 9); assert!(!source.parameters_changed()); + // end first span + assert!(source.next().is_some()); + assert!(source.parameters_changed()); assert_eq!(source.count(), 9); } From 756ba5132b5e12d7bf07c8600151b9670baf6dad Mon Sep 17 00:00:00 2001 From: dvdsk Date: Sun, 2 Mar 2025 12:41:10 +0100 Subject: [PATCH 24/25] write part of alternative lock-free buffered --- Cargo.lock | 7 +++ Cargo.toml | 1 + src/source/buffered2.rs | 114 ++++++++++++++++++++++++++++++++++++++++ src/source/mod.rs | 4 +- 4 files changed, 125 insertions(+), 1 deletion(-) create mode 100644 src/source/buffered2.rs diff --git a/Cargo.lock b/Cargo.lock index 4820b450..20084ff6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -39,6 +39,12 @@ version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" +[[package]] +name = "append-only-vec" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7992085ec035cfe96992dd31bfd495a2ebd31969bb95f624471cb6c0b349e571" + [[package]] name = "approx" version = "0.5.1" @@ -1004,6 +1010,7 @@ checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" name = "rodio" version = "0.20.1" dependencies = [ + "append-only-vec", "approx", "atomic_float", "claxon", diff --git a/Cargo.toml b/Cargo.toml index dda04bc9..0ac87b10 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ tracing = { version = "0.1.40", optional = true } atomic_float = { version = "1.1.0", optional = true } num-rational = "0.4.2" +append-only-vec = "0.1.7" [features] default = ["playback", "flac", "vorbis", "wav", "mp3"] diff --git a/src/source/buffered2.rs b/src/source/buffered2.rs new file mode 100644 index 00000000..fbf2a4b7 --- /dev/null +++ b/src/source/buffered2.rs @@ -0,0 +1,114 @@ +use std::sync::{Arc, Mutex, MutexGuard, TryLockError}; + +use append_only_vec::AppendOnlyVec; + +use crate::{ChannelCount, Sample, SampleRate}; + +use super::Source; + +struct ParameterChange { + index: usize, + channel_count: ChannelCount, + sample_rate: SampleRate, +} + +struct Shared { + input: Mutex, + parameter_changes: AppendOnlyVec, + samples_in_memory: AppendOnlyVec, +} + +#[derive(Clone)] +struct Buffered { + shared: Arc>, + next_parameter_change_at: usize, + channel_count: ChannelCount, + sample_rate: SampleRate, + samples_index: usize, + parameter_changes_index: usize, +} + +impl Buffered { + fn next_parameter_change_at(&self) -> usize { + if self.parameter_changes_index > self.shared.parameter_changes.len() { + usize::MAX + } else { + self.shared.parameter_changes[self.parameter_changes_index].index + } + } +} + +impl Iterator for Buffered { + type Item = Sample; + + fn next(&mut self) -> Option { + loop { + if self.shared.samples_in_memory.len() < self.samples_index { + let sample = self.shared.samples_in_memory[self.samples_index]; + + // sample after sample where flag a parameter_change + if self.samples_index > self.next_parameter_change_at { + self.next_parameter_change_at = self.next_parameter_change_at(); + } + + self.samples_index += 1; + return Some(sample); + } + + match self.shared.input.try_lock() { + Ok(input) => read_chunk( + self.samples_index, + input, + &self.shared.samples_in_memory, + &self.shared.parameter_changes, + ), + Err(TryLockError::WouldBlock) => { + let _wait_for_other_to_finish_read_chunk = self.shared.input.lock(); + } + Err(TryLockError::Poisoned(_)) => panic!("reader panicked in Buffered"), + } + } + } +} + +fn read_chunk( + start_index: usize, + mut input: MutexGuard<'_, I>, + in_memory: &AppendOnlyVec, + parameter_changes: &AppendOnlyVec, +) { + let taken = 0; + loop { + if let Some(sample) = input.next() { + in_memory.push(sample); + } + if input.parameters_changed() { + parameter_changes.push(ParameterChange { + index: start_index + taken, + channel_count: input.channels(), + sample_rate: input.sample_rate(), + }); + } + if taken < 2usize.pow(15) { + break; + } + } +} + +impl Source for Buffered { + fn parameters_changed(&self) -> bool { + self.next_parameter_change_at != self.samples_index + } + + fn channels(&self) -> crate::ChannelCount { + self.channel_count + } + + fn sample_rate(&self) -> crate::SampleRate { + self.sample_rate + } + + fn total_duration(&self) -> Option { + todo!() + } +} diff --git a/src/source/mod.rs b/src/source/mod.rs index ffd3b235..35dbdc6f 100644 --- a/src/source/mod.rs +++ b/src/source/mod.rs @@ -48,6 +48,7 @@ mod agc; mod amplify; mod blt; mod buffered; +mod buffered2; mod channel_volume; mod chirp; mod crossfade; @@ -174,7 +175,8 @@ pub trait Source: Iterator { /// `None` indicates at the same time "infinite" or "unknown". fn total_duration(&self) -> Option; - /// Stores the source in a buffer in addition to returning it. This iterator can be cloned. + /// Stores the source in a buffer in addition to returning it. This iterator can + /// be cloned. Note that this incurs significant additional latency. #[inline] fn buffered(self) -> Buffered where From ea2c3b6cb0649a155e0228e46c61c2884c99563a Mon Sep 17 00:00:00 2001 From: dvdsk Date: Mon, 3 Mar 2025 12:38:21 +0100 Subject: [PATCH 25/25] more buffered2 work --- src/source/buffered2.rs | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/source/buffered2.rs b/src/source/buffered2.rs index fbf2a4b7..a40d8c37 100644 --- a/src/source/buffered2.rs +++ b/src/source/buffered2.rs @@ -14,7 +14,7 @@ struct ParameterChange { struct Shared { input: Mutex, - parameter_changes: AppendOnlyVec, + parameters: AppendOnlyVec, samples_in_memory: AppendOnlyVec, } @@ -30,10 +30,10 @@ struct Buffered { impl Buffered { fn next_parameter_change_at(&self) -> usize { - if self.parameter_changes_index > self.shared.parameter_changes.len() { + if self.parameter_changes_index > self.shared.parameters.len() { usize::MAX } else { - self.shared.parameter_changes[self.parameter_changes_index].index + self.shared.parameters[self.parameter_changes_index].index } } } @@ -46,9 +46,20 @@ impl Iterator for Buffered { if self.shared.samples_in_memory.len() < self.samples_index { let sample = self.shared.samples_in_memory[self.samples_index]; + if self.samples_index == self.next_parameter_change_at { + let new_params = &self.shared.parameters[self.parameter_changes_index]; + self.sample_rate = new_params.sample_rate; + self.channel_count = new_params.channel_count; + } + // sample after sample where flag a parameter_change if self.samples_index > self.next_parameter_change_at { - self.next_parameter_change_at = self.next_parameter_change_at(); + self.next_parameter_change_at = + if self.parameter_changes_index > self.shared.parameters.len() { + usize::MAX + } else { + self.shared.parameters[self.parameter_changes_index].index + }; } self.samples_index += 1; @@ -60,7 +71,7 @@ impl Iterator for Buffered { self.samples_index, input, &self.shared.samples_in_memory, - &self.shared.parameter_changes, + &self.shared.parameters, ), Err(TryLockError::WouldBlock) => { let _wait_for_other_to_finish_read_chunk = self.shared.input.lock();