use std::sync::atomic::{AtomicU32, Ordering};
use crossbeam_queue::ArrayQueue;
use crate::buffer_estimate::QueueDepthSource;
use crate::resample::{CatmullInterp, StreamingResampler};
pub(crate) struct AudioSinkState<P> {
pub(crate) queue: ArrayQueue<P>,
sample_rate: u32,
last_a_bits: AtomicU32,
last_b_bits: AtomicU32,
}
impl<P> AudioSinkState<P> {
pub(crate) fn new(capacity: usize, sample_rate: u32) -> Self {
Self {
queue: ArrayQueue::new(capacity),
sample_rate,
last_a_bits: AtomicU32::new(0.0f32.to_bits()),
last_b_bits: AtomicU32::new(0.0f32.to_bits()),
}
}
pub(crate) fn sample_rate(&self) -> u32 {
self.sample_rate
}
pub(crate) fn remaining_capacity(&self) -> usize {
self.queue.capacity().saturating_sub(self.queue.len())
}
pub(crate) fn has_capacity_for(&self, count: usize) -> bool {
count == 0 || self.remaining_capacity() >= count
}
pub(crate) fn queued_points(&self) -> u64 {
self.queue.len() as u64
}
pub(crate) fn clear_queue(&self) {
while self.queue.pop().is_some() {}
}
pub(crate) fn pop(&self) -> Option<P> {
self.queue.pop()
}
pub(crate) fn push_point(&self, point: P) {
let pushed = self.queue.push(point);
debug_assert!(pushed.is_ok(), "queue capacity validated before push");
}
pub(crate) fn set_last_output(&self, a: f32, b: f32) {
self.last_a_bits.store(a.to_bits(), Ordering::Release);
self.last_b_bits.store(b.to_bits(), Ordering::Release);
}
pub(crate) fn last_output(&self) -> (f32, f32) {
(
f32::from_bits(self.last_a_bits.load(Ordering::Acquire)),
f32::from_bits(self.last_b_bits.load(Ordering::Acquire)),
)
}
}
impl<P: Send> QueueDepthSource for AudioSinkState<P> {
fn queued_points(&self) -> u64 {
AudioSinkState::queued_points(self)
}
fn sample_rate(&self) -> u32 {
AudioSinkState::sample_rate(self)
}
}
pub(crate) trait RunningAudioStream {}
pub(crate) struct CpalStreamHandle {
_stream: cpal::Stream,
}
impl RunningAudioStream for CpalStreamHandle {}
impl CpalStreamHandle {
pub(crate) fn boxed(stream: cpal::Stream) -> Box<dyn RunningAudioStream> {
Box::new(CpalStreamHandle { _stream: stream })
}
}
pub(crate) fn push_chunk_resampled<P: CatmullInterp>(
sink: &AudioSinkState<P>,
scratch: &mut Vec<P>,
fill: impl FnOnce(&mut Vec<P>),
resampler: &mut StreamingResampler<P>,
) {
scratch.clear();
fill(scratch);
let taken = std::mem::take(scratch);
resampler.process(&taken, |p| sink.push_point(p));
*scratch = taken;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn capacity_accounting_and_clear() {
let state = AudioSinkState::<(f32, f32)>::new(4, 48_000);
assert_eq!(state.sample_rate(), 48_000);
assert!(state.has_capacity_for(0));
assert!(state.has_capacity_for(4));
assert!(!state.has_capacity_for(5));
state.push_point((1.0, -1.0));
assert_eq!(state.queued_points(), 1);
assert!(state.has_capacity_for(3));
state.push_point((0.0, 0.0));
state.push_point((0.0, 0.0));
state.push_point((0.0, 0.0));
assert_eq!(state.queued_points(), 4);
assert!(!state.has_capacity_for(1));
state.clear_queue();
assert_eq!(state.queued_points(), 0);
assert!(state.has_capacity_for(4));
}
#[test]
fn held_output_round_trips_through_bits() {
let state = AudioSinkState::<u8>::new(2, 48_000);
assert_eq!(state.last_output(), (0.0, 0.0));
state.set_last_output(0.25, -0.75);
assert_eq!(state.last_output(), (0.25, -0.75));
}
}