use std::{fmt::Debug, marker::PhantomData};
use cpal::{
traits::{DeviceTrait, HostTrait},
SupportedStreamConfigRange,
};
use crate::{
common::assert_error_traits, microphone::config::InputConfig, ChannelCount, SampleRate,
};
use super::Microphone;
#[allow(missing_docs)]
#[derive(Debug, thiserror::Error, Clone)]
pub enum Error {
#[error("There is no input device")]
NoDevice,
#[error("Could not get default input configuration for input device: '{device_name}'")]
DefaultInputConfig {
#[source]
source: cpal::DefaultStreamConfigError,
device_name: String,
},
#[error("Could not get supported input configurations for input device: '{device_name}'")]
InputConfigs {
#[source]
source: cpal::SupportedStreamConfigsError,
device_name: String,
},
#[error("The input configuration is not supported by input device: '{device_name}'")]
UnsupportedByDevice { device_name: String },
}
assert_error_traits! {Error}
pub struct DeviceIsSet;
pub struct ConfigIsSet;
pub struct ConfigNotSet;
pub struct DeviceNotSet;
#[must_use]
pub struct MicrophoneBuilder<Device, Config, E = fn(cpal::StreamError)>
where
E: FnMut(cpal::StreamError) + Send + Clone + 'static,
{
device: Option<(cpal::Device, Vec<SupportedStreamConfigRange>)>,
config: Option<super::config::InputConfig>,
error_callback: E,
device_set: PhantomData<Device>,
config_set: PhantomData<Config>,
}
impl<Device, Config, E> Debug for MicrophoneBuilder<Device, Config, E>
where
E: FnMut(cpal::StreamError) + Send + Clone + 'static,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("MicrophoneBuilder")
.field(
"device",
&self.device.as_ref().map(|d| {
d.0.description()
.ok()
.map_or("unknown".to_string(), |d| d.name().to_string())
}),
)
.field("config", &self.config)
.finish()
}
}
impl Default for MicrophoneBuilder<DeviceNotSet, ConfigNotSet> {
fn default() -> Self {
Self {
device: None,
config: None,
error_callback: default_error_callback,
device_set: PhantomData,
config_set: PhantomData,
}
}
}
fn default_error_callback(err: cpal::StreamError) {
#[cfg(feature = "tracing")]
tracing::error!("audio stream error: {err}");
#[cfg(not(feature = "tracing"))]
eprintln!("audio stream error: {err}");
}
impl MicrophoneBuilder<DeviceNotSet, ConfigNotSet, fn(cpal::StreamError)> {
pub fn new() -> MicrophoneBuilder<DeviceNotSet, ConfigNotSet, fn(cpal::StreamError)> {
Self::default()
}
}
impl<Device, Config, E> MicrophoneBuilder<Device, Config, E>
where
E: FnMut(cpal::StreamError) + Send + Clone + 'static,
{
pub fn device(
&self,
device: super::Input,
) -> Result<MicrophoneBuilder<DeviceIsSet, ConfigNotSet, E>, Error> {
let device = device.into_inner();
let supported_configs = device
.supported_input_configs()
.map_err(|source| Error::InputConfigs {
source,
device_name: device
.description()
.ok()
.map_or("unknown".to_string(), |d| d.name().to_string()),
})?
.collect();
Ok(MicrophoneBuilder {
device: Some((device, supported_configs)),
config: self.config,
error_callback: self.error_callback.clone(),
device_set: PhantomData,
config_set: PhantomData,
})
}
pub fn default_device(&self) -> Result<MicrophoneBuilder<DeviceIsSet, ConfigNotSet, E>, Error> {
let default_device = cpal::default_host()
.default_input_device()
.ok_or(Error::NoDevice)?;
let supported_configs = default_device
.supported_input_configs()
.map_err(|source| Error::InputConfigs {
source,
device_name: default_device
.description()
.ok()
.map_or("unknown".to_string(), |d| d.name().to_string()),
})?
.collect();
Ok(MicrophoneBuilder {
device: Some((default_device, supported_configs)),
config: self.config,
error_callback: self.error_callback.clone(),
device_set: PhantomData,
config_set: PhantomData,
})
}
}
impl<Config, E> MicrophoneBuilder<DeviceIsSet, Config, E>
where
E: FnMut(cpal::StreamError) + Send + Clone + 'static,
{
pub fn default_config(&self) -> Result<MicrophoneBuilder<DeviceIsSet, ConfigIsSet, E>, Error> {
let device = &self.device.as_ref().expect("DeviceIsSet").0;
let default_config: InputConfig = device
.default_input_config()
.map_err(|source| Error::DefaultInputConfig {
source,
device_name: device
.description()
.ok()
.map_or("unknown".to_string(), |d| d.name().to_string()),
})?
.into();
let config = if self
.check_config(&default_config.with_f32_samples())
.is_ok()
{
default_config.with_f32_samples()
} else {
default_config
};
Ok(MicrophoneBuilder {
device: self.device.clone(),
config: Some(config),
error_callback: self.error_callback.clone(),
device_set: PhantomData,
config_set: PhantomData,
})
}
pub fn config(
&self,
config: InputConfig,
) -> Result<MicrophoneBuilder<DeviceIsSet, ConfigIsSet, E>, Error> {
self.check_config(&config)?;
Ok(MicrophoneBuilder {
device: self.device.clone(),
config: Some(config),
error_callback: self.error_callback.clone(),
device_set: PhantomData,
config_set: PhantomData,
})
}
fn check_config(&self, config: &InputConfig) -> Result<(), Error> {
let (device, supported_configs) = self.device.as_ref().expect("DeviceIsSet");
if !supported_configs
.iter()
.any(|range| config.supported_given(range))
{
Err(Error::UnsupportedByDevice {
device_name: device
.description()
.ok()
.map_or("unknown".to_string(), |d| d.name().to_string()),
})
} else {
Ok(())
}
}
}
impl<E> MicrophoneBuilder<DeviceIsSet, ConfigIsSet, E>
where
E: FnMut(cpal::StreamError) + Send + Clone + 'static,
{
pub fn try_sample_rate(
&self,
sample_rate: SampleRate,
) -> Result<MicrophoneBuilder<DeviceIsSet, ConfigIsSet, E>, Error> {
let mut new_config = self.config.expect("ConfigIsSet");
new_config.sample_rate = sample_rate;
self.check_config(&new_config)?;
Ok(MicrophoneBuilder {
device: self.device.clone(),
config: Some(new_config),
error_callback: self.error_callback.clone(),
device_set: PhantomData,
config_set: PhantomData,
})
}
pub fn prefer_sample_rates(
&self,
sample_rates: impl IntoIterator<Item = SampleRate>,
) -> MicrophoneBuilder<DeviceIsSet, ConfigIsSet, E> {
self.set_preferred_if_supported(sample_rates, |config, sample_rate| {
config.sample_rate = sample_rate
})
}
fn set_preferred_if_supported<T>(
&self,
options: impl IntoIterator<Item = T>,
setter: impl Fn(&mut InputConfig, T),
) -> MicrophoneBuilder<DeviceIsSet, ConfigIsSet, E> {
let mut config = self.config.expect("ConfigIsSet");
let mut final_config = config;
for option in options.into_iter() {
setter(&mut config, option);
if self.check_config(&config).is_ok() {
final_config = config;
break;
}
}
MicrophoneBuilder {
device: self.device.clone(),
config: Some(final_config),
error_callback: self.error_callback.clone(),
device_set: PhantomData,
config_set: PhantomData,
}
}
pub fn try_channels(
&self,
channel_count: ChannelCount,
) -> Result<MicrophoneBuilder<DeviceIsSet, ConfigIsSet, E>, Error> {
let mut new_config = self.config.expect("ConfigIsSet");
new_config.channel_count = channel_count;
self.check_config(&new_config)?;
Ok(MicrophoneBuilder {
device: self.device.clone(),
config: Some(new_config),
error_callback: self.error_callback.clone(),
device_set: PhantomData,
config_set: PhantomData,
})
}
pub fn prefer_channel_counts(
&self,
channel_counts: impl IntoIterator<Item = ChannelCount>,
) -> MicrophoneBuilder<DeviceIsSet, ConfigIsSet, E> {
self.set_preferred_if_supported(channel_counts, |config, count| {
config.channel_count = count
})
}
pub fn try_buffer_size(
&self,
buffer_size: u32,
) -> Result<MicrophoneBuilder<DeviceIsSet, ConfigIsSet, E>, Error> {
let mut new_config = self.config.expect("ConfigIsSet");
new_config.buffer_size = cpal::BufferSize::Fixed(buffer_size);
self.check_config(&new_config)?;
Ok(MicrophoneBuilder {
device: self.device.clone(),
config: Some(new_config),
error_callback: self.error_callback.clone(),
device_set: PhantomData,
config_set: PhantomData,
})
}
pub fn prefer_buffer_sizes(
&self,
buffer_sizes: impl IntoIterator<Item = u32>,
) -> MicrophoneBuilder<DeviceIsSet, ConfigIsSet, E> {
let buffer_sizes = buffer_sizes.into_iter().take_while(|size| *size < 100_000);
self.set_preferred_if_supported(buffer_sizes, |config, size| {
config.buffer_size = cpal::BufferSize::Fixed(size)
})
}
}
impl<Device, E> MicrophoneBuilder<Device, ConfigIsSet, E>
where
E: FnMut(cpal::StreamError) + Send + Clone + 'static,
{
pub fn get_config(&self) -> InputConfig {
self.config.expect("ConfigIsSet")
}
}
impl<E> MicrophoneBuilder<DeviceIsSet, ConfigIsSet, E>
where
E: FnMut(cpal::StreamError) + Send + Clone + 'static,
{
pub fn open_stream(&self) -> Result<Microphone, super::OpenError> {
Microphone::open(
self.device.as_ref().expect("DeviceIsSet").0.clone(),
*self.config.as_ref().expect("ConfigIsSet"),
self.error_callback.clone(),
)
}
}