Skip to content

Commit

Permalink
Add TrackPosition to Source
Browse files Browse the repository at this point in the history
This adds a new TrackPosition trait which counts the amount of times
`next()` is called (where it returns `Some`) and provide a function to
get the position, which is a simple calculation.

This is added to the Sink struct by default and the accuracy of this depends on
the interval of `periodic_access`.

I wasn't able to add testing to the sink counter part, because I
wanted to also test `try_seek`, but it seems like I would need to have
some threading code to call `next` on sink in another thread, which
didn't look that good and resulted in a flaky test so only a 'unit test'
in ``position.rs`.

Resolves RustAudio#457
Closes RustAudio#510
  • Loading branch information
Gusted committed Jun 9, 2024
1 parent ae9eaed commit ddebbc0
Show file tree
Hide file tree
Showing 4 changed files with 162 additions and 0 deletions.
14 changes: 14 additions & 0 deletions src/sink.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ struct Controls {
speed: Mutex<f32>,
to_clear: Mutex<u32>,
seek: Mutex<Option<SeekOrder>>,
position: Mutex<f64>,
}

impl Sink {
Expand All @@ -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,
Expand Down Expand Up @@ -119,6 +121,7 @@ impl Sink {

let source = source
.speed(1.0)
.trackable()
.pausable(false)
.amplify(1.0)
.skippable()
Expand All @@ -127,19 +130,24 @@ 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();
amp.set_factor(*controls.volume.lock().unwrap());
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() {
Expand Down Expand Up @@ -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 {
Expand Down
9 changes: 9 additions & 0 deletions src/source/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -48,6 +49,7 @@ mod from_iter;
mod mix;
mod pausable;
mod periodic;
mod position;
mod repeat;
mod samples_converter;
mod sine;
Expand Down Expand Up @@ -333,6 +335,13 @@ where
skippable::skippable(self)
}

fn trackable(self) -> TrackPosition<Self>
where
Self: Sized,
{
position::trackable(self)
}

/// Applies a low-pass filter to the source.
/// **Warning**: Probably buggy.
#[inline]
Expand Down
133 changes: 133 additions & 0 deletions src/source/position.rs
Original file line number Diff line number Diff line change
@@ -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<I>(source: I) -> TrackPosition<I> {
TrackPosition {
input: source,
samples_elapsed: 0,
}
}

#[derive(Clone, Debug)]
pub struct TrackPosition<I> {
input: I,
samples_elapsed: usize,
}

impl<I> TrackPosition<I> {
/// 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<I> TrackPosition<I>
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<I> Iterator for TrackPosition<I>
where
I: Source,
I::Item: Sample,
{
type Item = I::Item;

#[inline]
fn next(&mut self) -> Option<I::Item> {
let item = self.input.next();
if item.is_some() {
self.samples_elapsed += 1;
};
item
}

#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
self.input.size_hint()
}
}

impl<I> Source for TrackPosition<I>
where
I: Source,
I::Item: Sample,
{
#[inline]
fn current_frame_len(&self) -> Option<usize> {
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<Duration> {
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);
}
}
6 changes: 6 additions & 0 deletions src/spatial_sink.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
}

0 comments on commit ddebbc0

Please sign in to comment.