use std::sync::mpsc::{Receiver, SyncSender, sync_channel};
use std::thread;
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
use cpal::{FromSample, SizedSample};
use crate::sound::data::SampleRate;
use crate::sound::mixer::Command;
use crate::sound::output::MixRate;
use crate::sound::voices::{MixFrame, Mixer};
const BLOCK: usize = 256;
const BLOCKS_AHEAD: usize = 4;
const MAX_WAITING_COMMANDS: usize = 256;
pub(crate) struct Backend {
commands: SyncSender<Command>,
rate: MixRate,
_stream: cpal::Stream,
}
impl Backend {
pub(crate) fn rate(&self) -> Option<MixRate> {
Some(self.rate)
}
pub(crate) fn frame(&mut self, commands: impl Iterator<Item = Command>) {
for command in commands {
if self.commands.try_send(command).is_err() {
log::debug!("the mix is not keeping up; ignoring a sound");
}
}
}
pub(crate) fn unlocked(&self) -> bool {
true
}
pub(crate) fn unlock(&mut self) {}
}
pub(crate) fn open() -> Option<Backend> {
let Some(device) = cpal::default_host().default_output_device() else {
log::debug!("this machine has no audio device; playing nothing");
return None;
};
let supported = device
.default_output_config()
.inspect_err(|error| log::debug!("the audio device offered no format: {error}"))
.ok()?;
let (commands, taken) = sync_channel(MAX_WAITING_COMMANDS);
let (mixed, played) = sync_channel(BLOCKS_AHEAD);
let (spent, reused) = sync_channel(BLOCKS_AHEAD);
let rate = MixRate(SampleRate::new(supported.sample_rate()));
let width = usize::from(supported.channels());
let stream = match supported.sample_format() {
cpal::SampleFormat::I16 => stream::<i16>(&device, &supported, width, played, spent),
cpal::SampleFormat::U16 => stream::<u16>(&device, &supported, width, played, spent),
_ => stream::<f32>(&device, &supported, width, played, spent),
};
let stream = stream
.inspect_err(|error| log::debug!("the audio device refused a stream: {error}"))
.ok()?;
stream
.play()
.inspect_err(|error| log::debug!("the audio device would not start: {error}"))
.ok()?;
thread::spawn(move || mix(rate, taken, mixed, reused));
Some(Backend {
commands,
rate,
_stream: stream,
})
}
fn mix(
rate: MixRate,
commands: Receiver<Command>,
mixed: SyncSender<Vec<MixFrame>>,
reused: Receiver<Vec<MixFrame>>,
) {
let mut mixer = Mixer::new(rate);
loop {
for command in commands.try_iter() {
mixer.apply(command);
}
let mut block = reused
.try_recv()
.unwrap_or_else(|_| vec![MixFrame::SILENT; BLOCK]);
block.resize(BLOCK, MixFrame::SILENT);
mixer.mix(&mut block);
if mixed.send(block).is_err() {
return;
}
}
}
fn stream<T: SizedSample + FromSample<f32>>(
device: &cpal::Device,
supported: &cpal::SupportedStreamConfig,
width: usize,
mixed: Receiver<Vec<MixFrame>>,
spent: SyncSender<Vec<MixFrame>>,
) -> Result<cpal::Stream, cpal::Error> {
let mut block: Vec<MixFrame> = Vec::new();
let mut taken = 0;
device.build_output_stream(
supported.config(),
move |data: &mut [T], _: &cpal::OutputCallbackInfo| {
for frame in data.chunks_mut(width) {
if taken == block.len() {
taken = 0;
if !block.is_empty() {
let _ = spent.try_send(core::mem::take(&mut block));
}
match mixed.try_recv() {
Ok(next) => block = next,
Err(_) => {
silence(frame);
continue;
}
}
}
let Some(&heard) = block.get(taken) else {
silence(frame);
continue;
};
taken += 1;
for (channel, sample) in frame.iter_mut().enumerate() {
*sample = T::from_sample(heard.ear(channel));
}
}
},
|error| log::debug!("the audio device stopped: {error}"),
None,
)
}
fn silence<T: SizedSample + FromSample<f32>>(frame: &mut [T]) {
frame.fill(T::from_sample(0.0));
}