use std::{
collections::VecDeque,
sync::{
Arc,
mpsc::{Receiver, SendError, Sender, channel},
},
};
use cpal::{
Device, DeviceDescription, Stream, SupportedStreamConfig,
traits::{DeviceTrait, HostTrait, StreamTrait},
};
use lunar_lib::debug;
use ringbuf::{
CachingCons, CachingProd, HeapRb, SharedRb,
storage::Heap,
traits::{Consumer, Producer, Split},
};
use crate::AUDIO_BUFFER_SIZE;
pub struct DeviceConfig {
pub device: Device,
pub config: SupportedStreamConfig,
}
impl DeviceConfig {
pub fn default_config() -> Result<Self, CpalError> {
let host = cpal::default_host();
let device = host
.default_output_device()
.ok_or(CpalError::NoDefaultDevice)?;
let config = device.default_output_config()?;
Ok(Self { device, config })
}
}
pub struct CpalHandle {
_stream: Stream,
pub tx: Sender<()>,
pub audio_buf: CachingProd<Arc<SharedRb<Heap<f32>>>>,
pub(crate) pending_packet: VecDeque<f32>,
}
impl Drop for CpalHandle {
fn drop(&mut self) {
debug!("Cpal handle was dropped");
}
}
impl CpalHandle {
pub fn open(device_config: &DeviceConfig) -> Result<Self, CpalError> {
let (tx, rx) = channel();
let rb = HeapRb::new(AUDIO_BUFFER_SIZE);
let (prod, cons) = rb.split();
let stream = open_cpal_stream(cons, rx, device_config)?;
Ok(Self {
_stream: stream,
tx,
audio_buf: prod,
pending_packet: VecDeque::new(),
})
}
pub fn clear_buf(&self) -> Result<(), SendError<()>> {
self.tx.send(())
}
pub fn consume_packet(&mut self, volume: f32) -> Option<bool> {
if !self.pending_packet.is_empty() {
let pushed = self
.audio_buf
.push_iter(self.pending_packet.iter().map(|a| a * volume));
self.pending_packet.drain(..pushed);
return Some(self.pending_packet.is_empty());
}
None
}
}
#[derive(Debug, thiserror::Error)]
pub enum CpalError {
#[error("{0}")]
PlayStreamError(#[from] cpal::PlayStreamError),
#[error("{0}")]
BuildStreamError(#[from] cpal::BuildStreamError),
#[error("{0}")]
DefaultStreamConfigError(#[from] cpal::DefaultStreamConfigError),
#[error("{0}")]
DeviceIdError(#[from] cpal::DeviceIdError),
#[error("Failed to find the default device")]
NoDefaultDevice,
}
fn open_cpal_stream(
mut audio_buf: CachingCons<Arc<SharedRb<Heap<f32>>>>,
rx: Receiver<()>,
device_config: &DeviceConfig,
) -> Result<Stream, CpalError> {
let data_callback = move |output: &mut [f32], _: &cpal::OutputCallbackInfo| {
if let Ok(()) = rx.try_recv() {
audio_buf.clear();
}
for sample in output.iter_mut() {
*sample = audio_buf.try_pop().unwrap_or(0.0);
}
};
let error_callback = |err| eprintln!("Audio stream error: {err:?}");
let stream = device_config.device.build_output_stream(
&device_config.config.clone().into(),
data_callback,
error_callback,
None,
)?;
debug!(
"CPAL stream opened for {}",
device_config
.device
.description()
.as_ref()
.map_or("Unknown Device", DeviceDescription::name)
);
stream.play()?;
Ok(stream)
}