diff --git a/src/sink.rs b/src/sink.rs index 2d7478b9..f682ab2b 100644 --- a/src/sink.rs +++ b/src/sink.rs @@ -64,6 +64,7 @@ struct Controls { speed: Mutex, to_clear: Mutex, seek: Mutex>, + position: Mutex, } impl Sink { @@ -90,6 +91,7 @@ impl Sink { speed: Mutex::new(1.0), to_clear: Mutex::new(0), seek: Mutex::new(None), + position: Mutex::new(0.0), }), sound_count: Arc::new(AtomicUsize::new(0)), detached: false, @@ -119,6 +121,7 @@ impl Sink { let source = source .speed(1.0) + .trackable() .pausable(false) .amplify(1.0) .skippable() @@ -127,12 +130,16 @@ impl Sink { .periodic_access(Duration::from_millis(5), move |src| { if controls.stopped.load(Ordering::SeqCst) { src.stop(); + *controls.position.lock().unwrap() = 0.0; } { let mut to_clear = controls.to_clear.lock().unwrap(); if *to_clear > 0 { src.inner_mut().skip(); *to_clear -= 1; + *controls.position.lock().unwrap() = 0.0; + } else { + *controls.position.lock().unwrap() = src.inner().inner().inner().inner().get_pos(); } } let amp = src.inner_mut().inner_mut(); @@ -140,6 +147,7 @@ impl Sink { amp.inner_mut() .set_paused(controls.pause.load(Ordering::SeqCst)); amp.inner_mut() + .inner_mut() .inner_mut() .set_factor(*controls.speed.lock().unwrap()); if let Some(seek) = controls.seek.lock().unwrap().take() { @@ -309,6 +317,12 @@ impl Sink { pub fn len(&self) -> usize { self.sound_count.load(Ordering::Relaxed) } + + /// Returns the position of the sound that's being played. + #[inline] + pub fn get_pos(&self) -> f64 { + *self.controls.position.lock().unwrap() + } } impl Drop for Sink { diff --git a/src/source/mod.rs b/src/source/mod.rs index 789f1695..8d1a63bb 100644 --- a/src/source/mod.rs +++ b/src/source/mod.rs @@ -21,6 +21,7 @@ pub use self::from_iter::{from_iter, FromIter}; 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::samples_converter::SamplesConverter; pub use self::sine::SineWave; @@ -48,6 +49,7 @@ mod from_iter; mod mix; mod pausable; mod periodic; +mod position; mod repeat; mod samples_converter; mod sine; @@ -333,6 +335,13 @@ where skippable::skippable(self) } + fn trackable(self) -> TrackPosition + where + Self: Sized, + { + position::trackable(self) + } + /// Applies a low-pass filter to the source. /// **Warning**: Probably buggy. #[inline] diff --git a/src/source/position.rs b/src/source/position.rs new file mode 100644 index 00000000..8a23900d --- /dev/null +++ b/src/source/position.rs @@ -0,0 +1,133 @@ +use std::time::Duration; + +use crate::{Sample, Source}; + +use super::SeekError; + +/// Internal function that builds a `TrackPosition` object. +pub fn trackable(source: I) -> TrackPosition { + TrackPosition { + input: source, + samples_elapsed: 0, + } +} + +#[derive(Clone, Debug)] +pub struct TrackPosition { + input: I, + samples_elapsed: usize, +} + +impl TrackPosition { + /// Returns a reference to the inner source. + #[inline] + pub fn inner(&self) -> &I { + &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 TrackPosition +where + I: Source, + I::Item: Sample, +{ + /// Returns the inner source. + #[inline] + pub fn get_pos(&self) -> f64 { + self.samples_elapsed as f64 / self.input.sample_rate() as f64 / self.input.channels() as f64 + } +} + +impl Iterator for TrackPosition +where + I: Source, + I::Item: Sample, +{ + type Item = I::Item; + + #[inline] + fn next(&mut self) -> Option { + let item = self.input.next(); + if item.is_some() { + self.samples_elapsed += 1; + }; + item + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + self.input.size_hint() + } +} + +impl Source for TrackPosition +where + I: Source, + I::Item: Sample, +{ + #[inline] + fn current_frame_len(&self) -> Option { + self.input.current_frame_len() + } + + #[inline] + fn channels(&self) -> u16 { + self.input.channels() + } + + #[inline] + fn sample_rate(&self) -> u32 { + self.input.sample_rate() + } + + #[inline] + fn total_duration(&self) -> Option { + self.input.total_duration() + } + + #[inline] + fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> { + let result = self.input.try_seek(pos); + if result.is_ok() { + self.samples_elapsed = (pos.as_secs_f64() + * self.input.sample_rate() as f64 + * self.input.channels() as f64) as usize; + } + 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![10i16, -10, 10, -10, 20, -20]); + let mut source = inner.trackable(); + + assert_eq!(source.get_pos(), 0.0); + source.next(); + assert_eq!(source.get_pos(), 1.0); + source.next(); + assert_eq!(source.get_pos(), 2.0); + + assert_eq!(source.try_seek(Duration::new(1, 0)).is_ok(), true); + assert_eq!(source.get_pos(), 1.0); + } +} diff --git a/src/spatial_sink.rs b/src/spatial_sink.rs index cb787bc0..4de5fd1d 100644 --- a/src/spatial_sink.rs +++ b/src/spatial_sink.rs @@ -195,4 +195,10 @@ impl SpatialSink { pub fn try_seek(&self, pos: Duration) -> Result<(), SeekError> { self.sink.try_seek(pos) } + + /// Returns the position of the sound that's being played. + #[inline] + pub fn get_pos(&self) -> f64 { + self.sink.get_pos() + } }