use super::types::SampleType;
use ringbuf::traits::{Consumer, Observer, Producer};
use ringbuf::{HeapCons, HeapProd};
use std::collections::HashMap;
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::{Arc, Mutex, PoisonError};
use std::thread::{self, JoinHandle};
use std::time::Duration;
const IDLE_POLL: Duration = Duration::from_micros(500);
const MAX_SCHEDULE_AHEAD: usize = 1 << 20;
#[derive(Debug, Default)]
pub struct ClearSignal {
epoch: AtomicUsize,
ack: AtomicUsize,
}
impl ClearSignal {
pub fn request(&self) -> usize {
self.epoch.fetch_add(1, Ordering::Release) + 1
}
pub fn epoch(&self) -> usize {
self.epoch.load(Ordering::Acquire)
}
pub fn acked(&self) -> usize {
self.ack.load(Ordering::Acquire)
}
fn ack(&self, epoch: usize) {
self.ack.store(epoch, Ordering::Release);
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct MixCommand<S: SampleType> {
pub index: usize,
pub samples: Vec<S>,
pub voice: Option<u64>,
}
#[derive(Debug, Default)]
pub struct Voices {
cancelled: Mutex<Vec<u64>>,
}
impl Voices {
pub fn cancel(&self, voice: u64) {
self.cancelled
.lock()
.unwrap_or_else(PoisonError::into_inner)
.push(voice);
}
fn take(&self) -> Vec<u64> {
std::mem::take(&mut *self.cancelled.lock().unwrap_or_else(PoisonError::into_inner))
}
}
pub struct Mixer<S: SampleType> {
commands: HeapCons<MixCommand<S>>,
output: HeapProd<S>,
mix: Vec<S>,
cursor: usize,
late: usize,
dropped: usize,
clear: Arc<ClearSignal>,
cleared: usize,
voices: HashMap<u64, Vec<(usize, Vec<S>)>>,
cancels: Arc<Voices>,
}
impl<S: SampleType> Mixer<S> {
pub fn new(commands: HeapCons<MixCommand<S>>, output: HeapProd<S>) -> Self {
Mixer {
commands,
output,
mix: Vec::new(),
cursor: 0,
late: 0,
dropped: 0,
clear: Arc::new(ClearSignal::default()),
cleared: 0,
voices: HashMap::new(),
cancels: Arc::new(Voices::default()),
}
}
pub fn voices(&self) -> Arc<Voices> {
Arc::clone(&self.cancels)
}
pub fn clear_signal(&self) -> Arc<ClearSignal> {
Arc::clone(&self.clear)
}
fn clear_pending(&mut self) {
self.commands.clear();
self.mix.clear();
self.voices.clear();
}
fn apply(&mut self, command: MixCommand<S>) {
let MixCommand {
index,
samples,
voice,
} = command;
let skip = self.cursor.saturating_sub(index);
if skip >= samples.len() {
self.late += samples.len();
return;
}
self.late += skip;
let start = (index + skip) - self.cursor;
let end = start + (samples.len() - skip);
if end > MAX_SCHEDULE_AHEAD {
self.dropped += samples.len() - skip;
return;
}
if self.mix.len() < end {
self.mix.resize(end, S::SILENCE);
}
if let Some(voice) = voice {
self.voices
.entry(voice)
.or_default()
.push((index + skip, samples[skip..].to_vec()));
return;
}
for (slot, sample) in self.mix[start..end].iter_mut().zip(&samples[skip..]) {
*slot = slot.mix(*sample);
}
}
fn commit_voices(&mut self, ready: usize) {
let (from, to) = (self.cursor, self.cursor + ready);
for segments in self.voices.values_mut() {
for (index, samples) in segments.iter() {
let (start, end) = (*index, index + samples.len());
let (low, high) = (start.max(from), end.min(to));
for absolute in low..high {
let slot = &mut self.mix[absolute - from];
*slot = slot.mix(samples[absolute - start]);
}
}
segments.retain(|(index, samples)| index + samples.len() > to);
}
self.voices.retain(|_, segments| !segments.is_empty());
}
fn flush(&mut self) -> usize {
let ready = self.output.vacant_len();
if ready == 0 {
return 0;
}
if self.mix.len() < ready {
self.mix.resize(ready, S::SILENCE);
}
self.commit_voices(ready);
let pushed = self.output.push_slice(&self.mix[..ready]);
self.mix.drain(..pushed);
self.cursor += pushed;
pushed
}
pub fn tick(&mut self) -> bool {
let mut worked = false;
let epoch = self.clear.epoch();
if epoch != self.cleared {
self.clear_pending();
self.cleared = epoch;
self.clear.ack(epoch);
worked = true;
}
for voice in self.cancels.take() {
self.voices.remove(&voice);
worked = true;
}
while let Some(command) = self.commands.try_pop() {
self.apply(command);
worked = true;
}
self.flush() > 0 || worked
}
pub fn run(mut self, running: Arc<AtomicBool>) {
while running.load(Ordering::Relaxed) {
if !self.tick() {
thread::sleep(IDLE_POLL);
}
}
}
pub fn cursor(&self) -> usize {
self.cursor
}
pub fn late_samples(&self) -> usize {
self.late
}
pub fn dropped_samples(&self) -> usize {
self.dropped
}
}
pub struct MixerHandle {
running: Arc<AtomicBool>,
clear: Arc<ClearSignal>,
voices: Arc<Voices>,
thread: Option<JoinHandle<()>>,
}
impl MixerHandle {
pub fn spawn<S: SampleType>(mixer: Mixer<S>) -> Self {
let running = Arc::new(AtomicBool::new(true));
let flag = Arc::clone(&running);
let clear = mixer.clear_signal();
let voices = mixer.voices();
let thread = thread::Builder::new()
.name("atome_mixer".to_string())
.spawn(move || mixer.run(flag))
.expect("failed to spawn mixer thread");
MixerHandle {
running,
clear,
voices,
thread: Some(thread),
}
}
pub fn clear_samples(&self) {
self.clear.request();
}
pub fn clear_signal(&self) -> Arc<ClearSignal> {
Arc::clone(&self.clear)
}
pub fn voices(&self) -> Arc<Voices> {
Arc::clone(&self.voices)
}
}
impl Drop for MixerHandle {
fn drop(&mut self) {
self.running.store(false, Ordering::Relaxed);
if let Some(thread) = self.thread.take() {
let _ = thread.join();
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use ringbuf::traits::Split;
use ringbuf::HeapRb;
fn mixer(output: usize) -> (HeapProd<MixCommand<f32>>, Mixer<f32>, HeapCons<f32>) {
let (commands, pending) = HeapRb::<MixCommand<f32>>::new(16).split();
let (audio, device) = HeapRb::<f32>::new(output).split();
(commands, Mixer::new(pending, audio), device)
}
fn command(index: usize, samples: Vec<f32>, voice: Option<u64>) -> MixCommand<f32> {
MixCommand {
index,
samples,
voice,
}
}
#[test]
fn a_voice_is_mixed_as_it_is_committed() {
let (mut commands, mut mixer, mut device) = mixer(4);
let _ = commands.try_push(command(0, vec![0.25; 4], None));
let _ = commands.try_push(command(2, vec![0.5; 2], Some(7)));
mixer.tick();
let mut heard = [0.0; 4];
device.pop_slice(&mut heard);
assert_eq!(heard, [0.25, 0.25, 0.75, 0.75]);
}
#[test]
fn a_cancelled_voice_is_taken_back() {
let (mut commands, mut mixer, mut device) = mixer(4);
let _ = commands.try_push(command(0, vec![0.25; 8], None));
let _ = commands.try_push(command(0, vec![0.5; 8], Some(9)));
mixer.tick();
let mut first = [0.0; 4];
device.pop_slice(&mut first);
assert_eq!(first, [0.75; 4], "committed before the cancel");
mixer.voices().cancel(9);
mixer.tick();
let mut second = [0.0; 4];
device.pop_slice(&mut second);
assert_eq!(second, [0.25; 4], "the voice is gone, the rest is not");
}
#[test]
fn an_idle_mixer_keeps_pace_with_the_device() {
let (mut commands, mut mixer, mut device) = mixer(4);
let quiet = MAX_SCHEDULE_AHEAD * 2;
let mut heard = [1.0; 4];
for _ in 0..quiet / 4 {
mixer.tick();
assert_eq!(device.pop_slice(&mut heard), 4, "the ring never runs dry");
assert_eq!(heard, [0.0; 4]);
}
mixer.tick();
assert_eq!(mixer.cursor(), quiet + 4, "one buffer ahead of what was played");
let _ = commands.try_push(command(quiet + 4, vec![0.5; 4], None));
mixer.tick();
device.pop_slice(&mut heard);
assert_eq!(heard, [0.0; 4], "the buffer already committed");
mixer.tick();
device.pop_slice(&mut heard);
assert_eq!(heard, [0.5; 4]);
assert_eq!((mixer.late_samples(), mixer.dropped_samples()), (0, 0));
}
}