use crate::Device;
use cpal::traits::StreamTrait;
use std;
use std::any::{Any, TypeId};
use std::marker::PhantomData;
use std::sync::atomic::{self, AtomicBool};
use std::sync::{mpsc, Arc, Mutex};
use thiserror::Error;
pub mod input;
pub mod output;
pub mod duplex {}
pub trait ErrorFn<M>: Fn(&mut M, cpal::StreamError) {}
pub type UpdateFn<M> = dyn FnOnce(&mut M) + Send + 'static;
pub type DefaultErrorFn<M> = fn(&mut M, err: cpal::StreamError);
pub struct Stream<M> {
update_tx: mpsc::Sender<Box<dyn FnMut(&mut M) + 'static + Send>>,
shared: Arc<Shared<M>>,
cpal_config: cpal::StreamConfig,
}
struct Shared<M> {
stream: cpal::Stream,
model: Arc<Mutex<Option<M>>>,
is_paused: AtomicBool,
}
pub struct Builder<M, S = f32> {
pub(crate) host: Arc<cpal::Host>,
pub model: M,
pub sample_rate: Option<u32>,
pub channels: Option<usize>,
pub frames_per_buffer: Option<usize>,
pub device_buffer_size: Option<cpal::BufferSize>,
pub device: Option<Device>,
pub(crate) sample_format: PhantomData<S>,
}
#[derive(Debug, Error)]
pub enum BuildError {
#[error("failed to get default device")]
DefaultDevice,
#[error("failed to enumerate available configs: {err}")]
SupportedStreamConfigs {
err: cpal::SupportedStreamConfigsError,
},
#[error("failed to build stream: {err}")]
BuildStream { err: cpal::BuildStreamError },
}
#[derive(Debug)]
struct DesiredStreamConfig {
sample_format: Option<cpal::SampleFormat>,
channels: Option<usize>,
sample_rate: Option<cpal::SampleRate>,
device_buffer_size: Option<cpal::BufferSize>,
}
pub const DEFAULT_SAMPLE_RATE: u32 = 44_100;
impl<M> Stream<M> {
pub fn play(&self) -> Result<(), cpal::PlayStreamError> {
self.shared.play()
}
pub fn pause(&self) -> Result<(), cpal::PauseStreamError> {
self.shared.pause()
}
pub fn is_playing(&self) -> bool {
self.shared.is_playing()
}
pub fn is_paused(&self) -> bool {
self.shared.is_paused()
}
pub fn send<F>(
&self,
update: F,
) -> Result<(), mpsc::SendError<Box<dyn FnMut(&mut M) + Send + 'static>>>
where
F: FnOnce(&mut M) + Send + 'static,
{
if self.shared.is_paused.load(atomic::Ordering::Relaxed) {
if let Ok(mut guard) = self.shared.model.lock() {
let mut model = guard.take().unwrap();
update(&mut model);
*guard = Some(model);
}
} else {
let mut update_opt = Some(update);
let update_fn = move |audio: &mut M| {
if let Some(update) = update_opt.take() {
update(audio);
}
};
self.update_tx.send(Box::new(update_fn))?;
}
Ok(())
}
pub fn cpal_config(&self) -> &cpal::StreamConfig {
&self.cpal_config
}
}
impl<M> Shared<M> {
fn play(&self) -> Result<(), cpal::PlayStreamError> {
self.stream.play()?;
self.is_paused.store(false, atomic::Ordering::Relaxed);
Ok(())
}
fn pause(&self) -> Result<(), cpal::PauseStreamError> {
self.stream.pause()?;
self.is_paused.store(true, atomic::Ordering::Relaxed);
Ok(())
}
fn is_playing(&self) -> bool {
!self.is_paused.load(atomic::Ordering::Relaxed)
}
fn is_paused(&self) -> bool {
self.is_paused.load(atomic::Ordering::Relaxed)
}
}
impl<M, F> ErrorFn<M> for F where F: Fn(&mut M, cpal::StreamError) {}
impl<M> Clone for Stream<M> {
fn clone(&self) -> Self {
let update_tx = self.update_tx.clone();
let shared = self.shared.clone();
let cpal_config = self.cpal_config.clone();
Stream {
update_tx,
shared,
cpal_config,
}
}
}
impl<M> Drop for Shared<M> {
fn drop(&mut self) {
if self.is_playing() {
self.pause().ok();
}
}
}
impl From<cpal::BuildStreamError> for BuildError {
fn from(err: cpal::BuildStreamError) -> Self {
BuildError::BuildStream { err }
}
}
impl From<cpal::SupportedStreamConfigsError> for BuildError {
fn from(err: cpal::SupportedStreamConfigsError) -> Self {
BuildError::SupportedStreamConfigs { err }
}
}
fn cpal_sample_format<S: Any>() -> Option<cpal::SampleFormat> {
let type_id = TypeId::of::<S>();
if type_id == TypeId::of::<f32>() {
Some(cpal::SampleFormat::F32)
} else if type_id == TypeId::of::<i16>() {
Some(cpal::SampleFormat::I16)
} else if type_id == TypeId::of::<u16>() {
Some(cpal::SampleFormat::U16)
} else {
None
}
}
fn matching_supported_config(
desired: &DesiredStreamConfig,
supported_stream_config_range: &cpal::SupportedStreamConfigRange,
) -> Option<MatchingConfig> {
if let Some(cpal::BufferSize::Fixed(size)) = desired.device_buffer_size {
let supported_size = supported_stream_config_range.buffer_size();
if let cpal::SupportedBufferSize::Range { min, max } = *supported_size {
if size > max || size < min {
return None;
}
}
}
let supported_sample_format = supported_stream_config_range.sample_format();
if let Some(sample_format) = desired.sample_format {
if supported_sample_format != sample_format {
return None;
}
}
let sample_format = desired.sample_format.unwrap_or(supported_sample_format);
if let Some(channels) = desired.channels {
let supported_channels = supported_stream_config_range.channels() as usize;
if supported_channels < channels {
return None;
}
}
if let Some(sample_rate) = desired.sample_rate {
if supported_stream_config_range.min_sample_rate() > sample_rate
|| supported_stream_config_range.max_sample_rate() < sample_rate
{
return None;
}
let mut config = supported_stream_config_range
.clone()
.with_sample_rate(sample_rate)
.config();
config.buffer_size = desired
.device_buffer_size
.clone()
.unwrap_or(cpal::BufferSize::Default);
config.channels = desired.channels.unwrap_or(config.channels as usize) as u16;
let matching = MatchingConfig {
config,
sample_format,
};
return Some(matching);
}
let mut config = supported_stream_config_range
.clone()
.with_max_sample_rate()
.config();
config.buffer_size = desired
.device_buffer_size
.clone()
.unwrap_or(cpal::BufferSize::Default);
config.channels = desired.channels.unwrap_or(config.channels as usize) as u16;
let matching = MatchingConfig {
config,
sample_format,
};
Some(matching)
}
fn desired_device_buffer_size_matches_default(
desired: Option<&cpal::BufferSize>,
supported: &cpal::SupportedBufferSize,
) -> bool {
match desired {
None | Some(&cpal::BufferSize::Default) => true,
Some(&cpal::BufferSize::Fixed(size)) => match *supported {
cpal::SupportedBufferSize::Range { min, max } if size >= min && size <= max => true,
_ => false,
},
}
}
#[derive(Debug)]
struct MatchingConfig {
sample_format: cpal::SampleFormat,
config: cpal::StreamConfig,
}
fn desired_config_matches_default(
desired: &DesiredStreamConfig,
default: &cpal::SupportedStreamConfig,
) -> Option<MatchingConfig> {
if desired.sample_format == Some(default.sample_format())
&& desired
.channels
.map(|ch| ch <= default.channels() as usize)
.unwrap_or(true)
&& desired.sample_rate == Some(default.sample_rate())
&& desired_device_buffer_size_matches_default(
desired.device_buffer_size.as_ref(),
default.buffer_size(),
)
{
let mut config = default.config();
config.buffer_size = desired
.device_buffer_size
.clone()
.unwrap_or(cpal::BufferSize::Default);
let sample_format = desired.sample_format.unwrap_or(default.sample_format());
let matching = MatchingConfig {
config,
sample_format,
};
Some(matching)
} else {
None
}
}
fn find_best_matching_config<F>(
device: &cpal::Device,
mut desired: DesiredStreamConfig,
default: Option<cpal::SupportedStreamConfig>,
supported_configs: F,
) -> Result<Option<MatchingConfig>, cpal::SupportedStreamConfigsError>
where
F: Fn(
&cpal::Device,
) -> Result<Vec<cpal::SupportedStreamConfigRange>, cpal::SupportedStreamConfigsError>,
{
let mut trying_default_sample_rate = false;
if desired.sample_rate.is_none() {
desired.sample_rate = Some(cpal::SampleRate(DEFAULT_SAMPLE_RATE));
trying_default_sample_rate = true;
}
loop {
{
if let Some(ref default) = default {
if let Some(conf) = desired_config_matches_default(&desired, default) {
return Ok(Some(conf));
}
}
let stream_configs = supported_configs(device)?
.into_iter()
.filter_map(|config| matching_supported_config(&desired, &config));
if let Some(matching) = stream_configs.max_by_key(|matching| matching.config.channels) {
return Ok(Some(matching));
}
}
let default_sample_format = default.as_ref().map(|f| f.sample_format());
if desired.sample_format.is_some() && desired.sample_format != default_sample_format {
desired.sample_format = default_sample_format;
continue;
}
if trying_default_sample_rate {
trying_default_sample_rate = false;
desired.sample_rate = None;
continue;
}
return Ok(None);
}
}
pub(crate) fn default_error_fn<M>(_: &mut M, err: cpal::StreamError) {
eprintln!("A `StreamError` occurred: {}", err);
}