use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, Mutex, PoisonError};
use cpal::{Error, ErrorKind};
use ringbuf::traits::Producer;
use ringbuf::HeapProd;
use super::{MixCommand, OutputClass, PlayClock, SampleType, Voices};
static NEXT_VOICE: AtomicU64 = AtomicU64::new(1);
pub struct Scheduler<S: SampleType> {
commands: Arc<Mutex<HeapProd<MixCommand<S>>>>,
voices: Arc<Voices>,
clock: PlayClock,
channels: u16,
sample_rate: u32,
}
impl<S: SampleType> Clone for Scheduler<S> {
fn clone(&self) -> Self {
Scheduler {
commands: Arc::clone(&self.commands),
voices: Arc::clone(&self.voices),
clock: self.clock.clone(),
channels: self.channels,
sample_rate: self.sample_rate,
}
}
}
impl<S: SampleType> Scheduler<S> {
pub(super) fn new(
commands: Arc<Mutex<HeapProd<MixCommand<S>>>>,
voices: Arc<Voices>,
clock: PlayClock,
channels: u16,
sample_rate: u32,
) -> Self {
Scheduler {
commands,
voices,
clock,
channels,
sample_rate,
}
}
pub fn new_voice(&self) -> u64 {
NEXT_VOICE.fetch_add(1, Ordering::Relaxed)
}
pub fn schedule(&self, samples: &[S], index: usize, voice: Option<u64>) -> Result<usize, Error> {
let command = MixCommand {
index,
samples: samples.to_vec(),
voice,
};
self.commands
.lock()
.unwrap_or_else(PoisonError::into_inner)
.try_push(command)
.map_err(|_| {
Error::with_message(ErrorKind::ResourceExhausted, "mixer command queue is full")
})?;
Ok(index + samples.len())
}
pub fn cancel(&self, voice: u64) {
self.voices.cancel(voice);
}
pub fn clock(&self) -> PlayClock {
self.clock.clone()
}
pub fn channels(&self) -> u16 {
self.channels
}
pub fn sample_rate(&self) -> u32 {
self.sample_rate
}
pub fn align(&self, samples: &[S], rate: u32, channels: u16) -> Vec<S> {
let source = usize::from(channels.max(1));
let target = usize::from(self.channels.max(1));
let frames = samples.len() / source;
let mut buffer = if source == target {
samples[..frames * source].to_vec()
} else {
OutputClass::<S>::map_channels(samples, frames, source, target)
};
if rate != self.sample_rate && rate > 0 {
buffer = OutputClass::<S>::resample(&buffer, frames, target, rate, self.sample_rate);
}
buffer
}
}