use crate::{KaError, Renderer, RendererHandle};
use cpal::{
traits::{DeviceTrait, HostTrait, StreamTrait},
FromSample, SampleFormat, SizedSample, StreamConfig,
};
use parking_lot::Mutex;
use std::{sync::Arc, time::Duration};
#[derive(Default)]
pub enum Device {
#[default]
Default,
Name(String),
Custom(cpal::Device),
}
impl Device {
pub fn from_name(name: &str) -> Result<Self, KaError> {
let host = cpal::default_host();
Ok(Self::Custom(
host.output_devices()?
.find(|d| device_name(d) == name)
.ok_or(KaError::NoOutputDevice)?,
))
}
pub fn default_device() -> Result<Self, KaError> {
let host = cpal::default_host();
Ok(Self::Custom(
host.default_output_device()
.ok_or(KaError::NoOutputDevice)?,
))
}
pub fn cpal_device(self, host: cpal::Host) -> Result<cpal::Device, KaError> {
Ok(match self {
Device::Default => host
.default_output_device()
.ok_or(KaError::NoOutputDevice)?,
Device::Name(name) => host
.output_devices()?
.find(|d| device_name(d) == name)
.ok_or(KaError::NoOutputDevice)?,
Device::Custom(device) => device,
})
}
pub fn supported_buffer_size(
self,
host: cpal::Host,
) -> Result<cpal::SupportedBufferSize, KaError> {
Ok(*self
.cpal_device(host)?
.default_output_config()?
.buffer_size())
}
pub fn name(self) -> Result<String, KaError> {
Ok(self.cpal_device(cpal::default_host())?.name()?)
}
}
pub fn device_names() -> Result<Vec<String>, KaError> {
let host = cpal::default_host();
Ok(host.output_devices()?.map(|d| device_name(&d)).collect())
}
#[inline]
fn default_device_and_config() -> Result<(cpal::Device, StreamConfig), KaError> {
let host = cpal::default_host();
let device = host
.default_output_device()
.ok_or(KaError::NoOutputDevice)?;
let config = device.default_output_config()?.config();
Ok((device, config))
}
#[inline]
fn device_name(device: &cpal::Device) -> String {
device
.name()
.unwrap_or_else(|_| "<unavailable>".to_string())
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct StreamSettings {
pub channels: Option<u16>,
pub sample_rate: Option<u32>,
pub buffer_size: Option<u32>,
pub sample_format: Option<SampleFormat>,
pub check_stream: bool,
pub check_stream_interval: Duration,
}
impl Default for StreamSettings {
fn default() -> Self {
Self {
channels: None,
sample_rate: None,
buffer_size: None,
sample_format: None,
check_stream: true,
check_stream_interval: Duration::from_millis(500),
}
}
}
#[derive(Default)]
pub struct Backend {
pub error_queue: Arc<Mutex<Vec<cpal::StreamError>>>,
pub check_stream_interval: Duration,
pub check_stream: bool,
stop_stream: bool,
}
impl Backend {
#[inline]
pub fn new() -> Self {
Self {
error_queue: Arc::new(Mutex::new(Vec::new())),
check_stream_interval: Duration::from_millis(500),
check_stream: true,
stop_stream: false,
}
}
#[inline]
pub fn handle_errors(&mut self, err_fn: impl FnMut(cpal::StreamError)) {
self.error_queue.lock().drain(..).for_each(err_fn)
}
pub fn start_audio_thread<R>(
&mut self,
device: Device,
settings: StreamSettings,
renderer: RendererHandle<R>,
) -> Result<(), KaError>
where
R: Renderer,
{
let host = cpal::default_host();
let device = device.cpal_device(host)?;
let default_config = device.default_output_config()?;
let sample_format = settings
.sample_format
.unwrap_or_else(|| default_config.sample_format());
let config = StreamConfig {
channels: settings
.channels
.unwrap_or_else(|| default_config.config().channels),
sample_rate: settings
.sample_rate
.map(cpal::SampleRate)
.unwrap_or_else(|| default_config.sample_rate()),
buffer_size: settings
.buffer_size
.map(cpal::BufferSize::Fixed)
.unwrap_or(cpal::BufferSize::Default),
};
self.check_stream = settings.check_stream;
self.check_stream_interval = settings.check_stream_interval;
let custom_device =
if let Ok((default_device, default_config)) = default_device_and_config() {
device_name(&device) != device_name(&default_device)
|| config.sample_rate != default_config.sample_rate
} else {
false
};
use SampleFormat::*;
match sample_format {
I8 => self.start_stream::<i8, R>(&device, &config, renderer, custom_device)?,
I16 => self.start_stream::<i16, R>(&device, &config, renderer, custom_device)?,
I32 => self.start_stream::<i32, R>(&device, &config, renderer, custom_device)?,
I64 => self.start_stream::<i64, R>(&device, &config, renderer, custom_device)?,
U8 => self.start_stream::<u8, R>(&device, &config, renderer, custom_device)?,
U16 => self.start_stream::<u16, R>(&device, &config, renderer, custom_device)?,
U32 => self.start_stream::<u32, R>(&device, &config, renderer, custom_device)?,
U64 => self.start_stream::<u64, R>(&device, &config, renderer, custom_device)?,
F32 => self.start_stream::<f32, R>(&device, &config, renderer, custom_device)?,
F64 => self.start_stream::<f64, R>(&device, &config, renderer, custom_device)?,
sample_format => return Err(KaError::UnsupportedSampleFormat(sample_format)),
}
Ok(())
}
#[inline(always)]
pub fn stop_stream(&mut self) {
self.stop_stream = true;
}
fn check_stream(
&mut self,
device: &cpal::Device,
config: &cpal::StreamConfig,
custom_device: bool,
) -> bool {
let error_queue = self.error_queue.clone();
for err in error_queue.lock().drain(..) {
if matches!(err, cpal::StreamError::DeviceNotAvailable) {
return true;
}
}
#[cfg(not(target_os = "macos"))]
if !custom_device {
if let Ok((default_device, default_config)) = default_device_and_config() {
if device_name(device) != device_name(&default_device)
|| config.sample_rate != default_config.sample_rate
{
return true;
}
}
}
false
}
fn start_stream<T, R>(
&mut self,
device: &cpal::Device,
config: &cpal::StreamConfig,
renderer: RendererHandle<R>,
custom_device: bool,
) -> Result<(), KaError>
where
T: SizedSample + FromSample<f32>,
R: Renderer,
{
let channels = config.channels as usize; let sample_rate = config.sample_rate.0; let error_queue = self.error_queue.clone();
let renderer_moved = renderer.clone();
let stream = device.build_output_stream(
config,
move |data: &mut [T], _: &cpal::OutputCallbackInfo| {
for frame in data.chunks_exact_mut(channels) {
let out = renderer_moved.guard().next_frame(sample_rate);
if channels == 1 {
frame[0] = T::from_sample((out.left + out.right) / 2.0);
} else {
frame[0] = T::from_sample(out.left);
frame[1] = T::from_sample(out.right);
for channel in frame.iter_mut().skip(2) {
*channel = T::from_sample(0.);
}
}
}
renderer_moved.guard().on_buffer(data);
},
move |err| {
error_queue.lock().push(err)
},
None,
)?;
stream.play()?;
loop {
std::thread::sleep(self.check_stream_interval);
if self.check_stream && self.check_stream(device, config, custom_device) {
drop(stream); return self.start_audio_thread(
Device::Default,
StreamSettings::default(),
renderer,
);
}
if self.stop_stream {
self.stop_stream = false;
drop(stream); break;
}
}
Ok(())
}
}