Add TrackPosition to Source

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 #457
Closes #510
This commit is contained in:
Gusted 2024-06-09 22:58:15 +02:00
parent ae9eaedc22
commit e1108abe71
No known key found for this signature in database
GPG key ID: FD821B732837125F
4 changed files with 162 additions and 0 deletions

View file

@ -64,6 +64,7 @@ struct Controls {
speed: Mutex<f32>,
to_clear: Mutex<u32>,
seek: Mutex<Option<SeekOrder>>,
position: Mutex<f64>,
}
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 {

View file

@ -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<Self>
where
Self: Sized,
{
position::trackable(self)
}
/// Applies a low-pass filter to the source.
/// **Warning**: Probably buggy.
#[inline]

133
src/source/position.rs Normal file
View file

@ -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);
}
}

View file

@ -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()
}
}