diff --git a/src/conversions/sample_rate.rs b/src/conversions/sample_rate.rs index 2fd26c5c..36aa5a5a 100644 --- a/src/conversions/sample_rate.rs +++ b/src/conversions/sample_rate.rs @@ -259,6 +259,12 @@ mod test { use cpal::SampleRate; use quickcheck::quickcheck; + // TODO: Remove once cpal 0.12.2 is released and the dependency is updated + // (cpal#483 implemented ops::Mul on SampleRate) + const fn multiply_rate(r: SampleRate, k: u32) -> SampleRate { + SampleRate(k * r.0) + } + quickcheck! { /// Check that resampling an empty input produces no output. fn empty(from: u32, to: u32, n: u16) -> () { @@ -289,9 +295,9 @@ mod test { /// Check that dividing the sample rate by k (integer) is the same as /// dropping a sample from each channel. fn divide_sample_rate(to: u32, k: u32, input: Vec, n: u16) -> () { - if k == 0 || n == 0 || to.checked_mul(k).is_none() { return; } let to = if to == 0 { return; } else { SampleRate(to) }; - let from = to * k; + let from = multiply_rate(to, k); + if k == 0 || n == 0 { return; } // Truncate the input, so it contains an integer number of frames. let input = { @@ -313,9 +319,9 @@ mod test { /// Check that, after multiplying the sample rate by k, every k-th /// sample in the output matches exactly with the input. fn multiply_sample_rate(from: u32, k: u32, input: Vec, n: u16) -> () { - if k == 0 || n == 0 || from.checked_mul(k).is_none() { return; } let from = if from == 0 { return; } else { SampleRate(from) }; - let to = from * k; + let to = multiply_rate(from, k); + if k == 0 || n == 0 { return; } // Truncate the input, so it contains an integer number of frames. let input = { diff --git a/src/dynamic_mixer.rs b/src/dynamic_mixer.rs index da5a4939..61c13ee9 100644 --- a/src/dynamic_mixer.rs +++ b/src/dynamic_mixer.rs @@ -1,4 +1,5 @@ //! Mixer that plays multiple sounds at the same time. + use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Mutex}; use std::time::Duration; @@ -27,6 +28,8 @@ where current_sources: Vec::with_capacity(16), input: input.clone(), sample_count: 0, + still_pending: vec![], + still_current: vec![], }; (input, output) @@ -51,8 +54,10 @@ where T: Source + Send + 'static, { let uniform_source = UniformSourceIterator::new(source, self.channels, self.sample_rate); - let mut pending = self.pending_sources.lock().unwrap(); - pending.push(Box::new(uniform_source) as Box<_>); + self.pending_sources + .lock() + .unwrap() + .push(Box::new(uniform_source) as Box<_>); self.has_pending.store(true, Ordering::SeqCst); // TODO: can we relax this ordering? } } @@ -67,6 +72,12 @@ pub struct MixerSource { // The number of samples produced so far. sample_count: usize, + + // A temporary vec used in start_pending_sources. + still_pending: Vec + Send>>, + + // A temporary vec used in sum_current_sources. + still_current: Vec + Send>>, } impl Source for MixerSource @@ -168,16 +179,18 @@ where // in-step with the modulo of the samples produced so far. Otherwise, the // sound will play on the wrong channels, e.g. left / right will be reversed. fn start_pending_sources(&mut self) { - let mut pending = self.input.pending_sources.lock().unwrap(); - let mut i = 0; - while i < pending.len() { - let in_step = self.sample_count % pending[i].channels() as usize == 0; + let mut pending = self.input.pending_sources.lock().unwrap(); // TODO: relax ordering? + + for source in pending.drain(..) { + let in_step = self.sample_count % source.channels() as usize == 0; + if in_step { - self.current_sources.push(pending.swap_remove(i)); + self.current_sources.push(source); } else { - i += 1; + self.still_pending.push(source); } } + std::mem::swap(&mut self.still_pending, &mut pending); let has_pending = !pending.is_empty(); self.input.has_pending.store(has_pending, Ordering::SeqCst); // TODO: relax ordering? @@ -185,15 +198,15 @@ where fn sum_current_sources(&mut self) -> S { let mut sum = S::zero_value(); - let mut i = 0; - while i < self.current_sources.len() { - if let Some(value) = self.current_sources[i].next() { + + for mut source in self.current_sources.drain(..) { + if let Some(value) = source.next() { sum = sum.saturating_add(value); - i += 1; - } else { - self.current_sources.swap_remove(i); + self.still_current.push(source); } } + std::mem::swap(&mut self.still_current, &mut self.current_sources); + sum } } diff --git a/src/source/mod.rs b/src/source/mod.rs index 33ca9347..4802e53f 100644 --- a/src/source/mod.rs +++ b/src/source/mod.rs @@ -145,6 +145,7 @@ pub use self::noise::{pink, white, PinkNoise, WhiteNoise}; /// In order to properly handle this situation, the `current_frame_len()` method should return /// the number of samples that remain in the iterator before the samples rate and number of /// channels can potentially change. +/// pub trait Source: Iterator where Self::Item: Sample, diff --git a/src/source/uniform.rs b/src/source/uniform.rs index 7d25a8b8..b9d146d1 100644 --- a/src/source/uniform.rs +++ b/src/source/uniform.rs @@ -41,9 +41,9 @@ where target_sample_rate: u32, ) -> UniformSourceIterator { let total_duration = input.total_duration(); - let input = Self::bootstrap(input, target_channels, target_sample_rate); + let input = UniformSourceIterator::bootstrap(input, target_channels, target_sample_rate); - Self { + UniformSourceIterator { inner: Some(input), target_channels, target_sample_rate, @@ -102,7 +102,8 @@ where .into_inner() .iter; - let mut input = Self::bootstrap(input, self.target_channels, self.target_sample_rate); + let mut input = + UniformSourceIterator::bootstrap(input, self.target_channels, self.target_sample_rate); let value = input.next(); self.inner = Some(input); diff --git a/src/spatial_sink.rs b/src/spatial_sink.rs index 8f2e2e81..bddd679e 100644 --- a/src/spatial_sink.rs +++ b/src/spatial_sink.rs @@ -96,10 +96,20 @@ impl SpatialSink { self.sink.set_volume(value); } - /// Gets the speed of the sound. + /// Changes the play speed of the sound. Does not adjust the samples, only the playback speed. /// - /// The value `1.0` is the "normal" speed (unfiltered input). Any value other than `1.0` will - /// change the play speed of the sound. + /// # Note: + /// 1. **Increasing the speed will increase the pitch by the same factor** + /// - If you set the speed to 0.5 this will halve the frequency of the sound + /// lowering its pitch. + /// - If you set the speed to 2 the frequency will double raising the + /// pitch of the sound. + /// 2. **Change in the speed affect the total duration inversely** + /// - If you set the speed to 0.5, the total duration will be twice as long. + /// - If you set the speed to 2 the total duration will be halve of what it + /// was. + /// + /// See [`Speed`] for details #[inline] pub fn speed(&self) -> f32 { self.sink.speed() @@ -138,6 +148,14 @@ impl SpatialSink { self.sink.is_paused() } + /// Removes all currently loaded `Source`s from the `SpatialSink` and pauses it. + /// + /// See `pause()` for information about pausing a `Sink`. + #[inline] + pub fn clear(&self) { + self.sink.clear(); + } + /// Stops the sink by emptying the queue. #[inline] pub fn stop(&self) { @@ -163,8 +181,43 @@ impl SpatialSink { } /// Returns the number of sounds currently in the queue. + #[allow(clippy::len_without_is_empty)] #[inline] pub fn len(&self) -> usize { self.sink.len() } + + /// Attempts to seek to a given position in the current source. + /// + /// This blocks between 0 and ~5 milliseconds. + /// + /// As long as the duration of the source is known seek is guaranteed to saturate + /// at the end of the source. For example given a source that reports a total duration + /// of 42 seconds calling `try_seek()` with 60 seconds as argument will seek to + /// 42 seconds. + /// + /// # Errors + /// This function will return [`SeekError::NotSupported`] if one of the underlying + /// sources does not support seeking. + /// + /// It will return an error if an implementation ran + /// into one during the seek. + /// + /// When seeking beyond the end of a source this + /// function might return an error if the duration of the source is not known. + pub fn try_seek(&self, pos: Duration) -> Result<(), SeekError> { + self.sink.try_seek(pos) + } + + /// Returns the position of the sound that's being played. + /// + /// This takes into account any speedup or delay applied. + /// + /// Example: if you apply a speedup of *2* to an mp3 decoder source and + /// [`get_pos()`](Sink::get_pos) returns *5s* then the position in the mp3 + /// recording is *10s* from its start. + #[inline] + pub fn get_pos(&self) -> Duration { + self.sink.get_pos() + } } diff --git a/src/stream.rs b/src/stream.rs index 3d1ae73f..9437930c 100644 --- a/src/stream.rs +++ b/src/stream.rs @@ -18,8 +18,8 @@ const HZ_44100: cpal::SampleRate = cpal::SampleRate(44_100); /// /// If this is dropped, playback will end, and the associated output stream will be disposed. pub struct OutputStream { - _stream: cpal::Stream, mixer: Arc>, + _stream: cpal::Stream, } impl OutputStream {