Refactoring

This commit is contained in:
Gusted 2024-06-16 18:07:36 +02:00
parent 0e6d5a1da2
commit ca43ad0885
No known key found for this signature in database
GPG key ID: FD821B732837125F
3 changed files with 68 additions and 36 deletions

View file

@ -1,4 +1,4 @@
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::atomic::{AtomicBool, AtomicU64, AtomicUsize, Ordering};
use std::sync::{Arc, Mutex};
use std::time::Duration;
@ -12,6 +12,28 @@ use crate::stream::{OutputStreamHandle, PlayError};
use crate::{queue, source::Done, Sample, Source};
use cpal::FromSample;
#[derive(Debug)]
pub struct AtomicF64 {
storage: AtomicU64,
}
impl AtomicF64 {
pub fn new(value: f64) -> Self {
let as_u64 = value.to_bits();
Self {
storage: AtomicU64::new(as_u64),
}
}
pub fn store(&self, value: f64, ordering: Ordering) {
let as_u64 = value.to_bits();
self.storage.store(as_u64, ordering)
}
pub fn load(&self, ordering: Ordering) -> f64 {
let as_u64 = self.storage.load(ordering);
f64::from_bits(as_u64)
}
}
/// Handle to a device that outputs sounds.
///
/// Dropping the `Sink` stops all sounds. You can use `detach` if you want the sounds to continue
@ -22,6 +44,7 @@ pub struct Sink {
controls: Arc<Controls>,
sound_count: Arc<AtomicUsize>,
position: Arc<AtomicF64>,
detached: bool,
}
@ -64,7 +87,6 @@ struct Controls {
speed: Mutex<f32>,
to_clear: Mutex<u32>,
seek: Mutex<Option<SeekOrder>>,
position: Mutex<f64>,
}
impl Sink {
@ -91,9 +113,9 @@ 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)),
position: Arc::new(AtomicF64::new(0.0)),
detached: false,
};
(sink, queue_rx)
@ -121,7 +143,7 @@ impl Sink {
let source = source
.speed(1.0)
.trackable()
.trackable(self.position.clone())
.pausable(false)
.amplify(1.0)
.skippable()
@ -130,16 +152,12 @@ 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();
@ -321,7 +339,7 @@ impl Sink {
/// Returns the position of the sound that's being played.
#[inline]
pub fn get_pos(&self) -> f64 {
*self.controls.position.lock().unwrap()
self.position.load(Ordering::Relaxed)
}
}

View file

@ -1,9 +1,11 @@
//! Sources of sound and various filters.
use std::sync::Arc;
use std::time::Duration;
use cpal::FromSample;
use crate::sink::AtomicF64;
use crate::Sample;
pub use self::amplify::Amplify;
@ -335,11 +337,11 @@ where
skippable::skippable(self)
}
fn trackable(self) -> TrackPosition<Self>
fn trackable(self, position: Arc<AtomicF64>) -> TrackPosition<Self>
where
Self: Sized,
{
position::trackable(self)
position::trackable(self, position)
}
/// Applies a low-pass filter to the source.

View file

@ -1,26 +1,31 @@
use std::time::Duration;
use std::{
sync::{atomic::Ordering, Arc},
time::Duration,
};
use crate::{Sample, Source};
use crate::{sink::AtomicF64, Sample, Source};
use super::SeekError;
/// Internal function that builds a `TrackPosition` object.
pub fn trackable<I>(source: I) -> TrackPosition<I> {
pub fn trackable<I>(source: I, position: Arc<AtomicF64>) -> TrackPosition<I> {
TrackPosition {
input: source,
samples_collected: 0,
samples_counted: 0,
offset_duration: 0.0,
position,
current_frame_sample_rate: 0,
current_frame_channels: 0,
current_frame_len: None,
}
}
#[derive(Clone, Debug)]
#[derive(Debug)]
pub struct TrackPosition<I> {
input: I,
samples_collected: usize,
samples_counted: usize,
offset_duration: f64,
position: Arc<AtomicF64>,
current_frame_sample_rate: u32,
current_frame_channels: u16,
current_frame_len: Option<usize>,
@ -53,10 +58,8 @@ where
{
/// Returns the position of the source.
#[inline]
pub fn get_pos(&self) -> f64 {
self.samples_collected as f64
/ self.input.sample_rate() as f64
/ self.input.channels() as f64
fn get_pos(&self) -> f64 {
self.samples_counted as f64 / self.input.sample_rate() as f64 / self.input.channels() as f64
+ self.offset_duration
}
@ -84,22 +87,21 @@ where
let item = self.input.next();
if item.is_some() {
self.samples_collected += 1;
self.samples_counted += 1;
// At the end of a frame add the duration of this frame to
// offset_duration and start collecting samples again.
if let Some(frame_len) = self.current_frame_len() {
if self.samples_collected == frame_len {
self.offset_duration += self.samples_collected as f64
/ self.current_frame_sample_rate as f64
/ self.current_frame_channels as f64;
if Some(self.samples_counted) == self.current_frame_len() {
self.offset_duration += self.samples_counted as f64
/ self.current_frame_sample_rate as f64
/ self.current_frame_channels as f64;
// Reset.
self.samples_collected = 0;
self.set_current_frame();
};
// Reset.
self.samples_counted = 0;
self.set_current_frame();
};
};
self.position.store(self.get_pos(), Ordering::Relaxed);
item
}
@ -139,6 +141,11 @@ where
let result = self.input.try_seek(pos);
if result.is_ok() {
self.offset_duration = pos.as_secs_f64();
// This assumes that the seek implementation of the codec always
// starts again at the beginning of a frame. Which is the case with
// symphonia.
self.samples_counted = 0;
self.position.store(self.get_pos(), Ordering::Relaxed);
}
result
}
@ -146,23 +153,28 @@ where
#[cfg(test)]
mod tests {
use std::sync::atomic::Ordering;
use std::sync::Arc;
use std::time::Duration;
use crate::buffer::SamplesBuffer;
use crate::sink::AtomicF64;
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();
let position = Arc::new(AtomicF64::new(0.0));
let mut source = inner.trackable(position.clone());
assert_eq!(source.get_pos(), 0.0);
assert_eq!(position.load(Ordering::Relaxed), 0.0);
source.next();
assert_eq!(source.get_pos(), 1.0);
assert_eq!(position.load(Ordering::Relaxed), 1.0);
source.next();
assert_eq!(source.get_pos(), 2.0);
assert_eq!(position.load(Ordering::Relaxed), 2.0);
assert_eq!(source.try_seek(Duration::new(1, 0)).is_ok(), true);
assert_eq!(source.get_pos(), 1.0);
assert_eq!(position.load(Ordering::Relaxed), 1.0);
}
}