use cpal::traits::DeviceTrait;
use cpal::{Device, Error, ErrorKind, SampleFormat, Stream, StreamConfig};
use std::marker::PhantomData;
pub mod utils;
pub use utils::{default_device, find_device, list_device_names, list_devices};
pub use crate::output::OutputType as InputType;
pub use crate::output::{device_name, list_hosts, SampleRate, SampleType};
pub struct InputClass<S: SampleType> {
channels: u16,
device: Device,
in_type: InputType,
buffer_size: Option<i32>,
sample_rate: SampleRate,
device_rate: SampleRate,
stream: Option<Stream>,
stream_config: StreamConfig,
callback: Option<Callback<S>>,
sample_type: PhantomData<S>,
output_routing: Option<Vec<Device>>,
}
type Callback<S> = Box<dyn FnMut(&[S]) + Send + 'static>;
impl<S: SampleType> InputClass<S> {
pub fn new(
device: Option<Device>,
in_type: InputType,
sample_rate: SampleRate,
buffer_size: Option<i32>,
callback: impl FnMut(&[S]) + Send + 'static,
) -> Self {
let device =
device.unwrap_or_else(|| default_device().expect("no default input device available"));
let buffer_size_form = match buffer_size {
Some(frames) => cpal::BufferSize::Fixed(frames as u32),
None => cpal::BufferSize::Default,
};
let channels = device
.default_input_config()
.map(|config| config.channels())
.unwrap_or(1);
let stream_config = StreamConfig {
channels,
sample_rate: sample_rate as u32,
buffer_size: buffer_size_form,
};
InputClass {
channels,
device,
in_type,
buffer_size,
sample_rate,
device_rate: sample_rate,
stream: None,
stream_config,
callback: Some(Box::new(callback)),
sample_type: PhantomData,
output_routing: None,
}
}
pub fn with_routing(mut self, devices: Vec<Device>) -> Self {
self.output_routing = Some(devices);
self
}
pub fn set_routing(&mut self, devices: Option<Vec<Device>>) {
self.output_routing = devices;
}
pub fn output_routing(&self) -> Option<&[Device]> {
self.output_routing.as_deref()
}
pub fn channels(&self) -> u16 {
self.channels
}
pub fn device(&self) -> Device {
self.device.clone()
}
pub fn name(&self) -> String {
device_name(&self.device)
}
pub fn buffer_size(&self) -> Option<i32> {
self.buffer_size
}
pub fn device_rate(&self) -> i32 {
self.device_rate as i32
}
pub fn sample_rate(&self) -> i32 {
self.sample_rate as i32
}
pub fn sample_format(&self) -> SampleFormat {
S::format()
}
pub fn in_type(&self) -> InputType {
self.in_type
}
pub fn build_stream(&mut self) -> Result<&Stream, Error> {
let mut callback = self.callback.take().ok_or_else(|| {
Error::with_message(
ErrorKind::UnsupportedOperation,
"stream already built for this input",
)
})?;
let usable = self.usable_rate();
self.device_rate = usable;
self.stream_config.sample_rate = usable.hz();
let channels = usize::from(self.channels.max(1));
let (from, to) = (usable.hz(), self.sample_rate.hz());
let stream = if from == to {
self.device.build_input_stream(
self.stream_config,
move |data: &[S], _: &cpal::InputCallbackInfo| callback(data),
Self::err_fn,
None,
)?
} else {
let mut scratch: Vec<S> = Vec::with_capacity(
self.buffer_size.unwrap_or(crate::output::DEFAULT_BUFFER_FRAMES).max(1) as usize * channels * 2 + channels,
);
self.device.build_input_stream(
self.stream_config,
move |data: &[S], _: &cpal::InputCallbackInfo| {
let frames = data.len() / channels;
crate::output::resample_into(data, frames, channels, from, to, &mut scratch);
callback(&scratch);
},
Self::err_fn,
None,
)?
};
self.stream = Some(stream);
Ok(self.stream.as_ref().unwrap())
}
fn opens_at(&self, rate: SampleRate) -> bool {
let config = StreamConfig {
sample_rate: rate.hz(),
..self.stream_config
};
self.device
.build_input_stream(
config,
move |_data: &[S], _info: &cpal::InputCallbackInfo| {},
|_error| {},
None,
)
.is_ok()
}
fn usable_rate(&self) -> SampleRate {
let capturing_at = self.device.default_input_config().ok().map(|config| config.sample_rate());
if capturing_at == Some(self.sample_rate.hz()) || self.opens_at(self.sample_rate) {
return self.sample_rate;
}
let native = capturing_at
.and_then(SampleRate::from_hz)
.filter(|rate| *rate != self.sample_rate);
if let Some(native) = native.filter(|rate| self.opens_at(*rate)) {
return native;
}
crate::output::fallback_rates(self.sample_rate, native)
.into_iter()
.find(|rate| self.opens_at(*rate))
.unwrap_or(self.sample_rate)
}
pub fn set_callback(&mut self, callback: impl FnMut(&[S]) + Send + 'static) {
self.callback = Some(Box::new(callback));
}
pub fn close(&mut self) {
self.stream.take();
}
pub fn stream(&self) -> Option<&Stream> {
self.stream.as_ref()
}
fn err_fn(err: cpal::Error) {
eprintln!("audio input stream error: {}", err);
}
}
#[cfg(test)]
mod rate_tests {
use super::*;
fn opens_at(device: &Device, rate: SampleRate) -> bool {
let channels = device.default_input_config().map(|config| config.channels()).unwrap_or(1);
let config = StreamConfig {
channels,
sample_rate: rate.hz(),
buffer_size: cpal::BufferSize::Fixed(512),
};
device
.build_input_stream(
config,
|_data: &[f32], _info: &cpal::InputCallbackInfo| {},
|_error| {},
None,
)
.is_ok()
}
#[test]
fn an_input_runs_at_the_rate_asked_for_and_moves_only_when_it_must() {
let Ok(devices) = list_devices(None) else { return };
for device in devices.iter() {
let name = device.to_string();
for asked in [SampleRate::Hz44_1k, SampleRate::Hz48k] {
let natively = opens_at(device, asked) || opens_at(device, asked);
let mut input = InputClass::<f32>::new(
Some(device.clone()),
InputType::WASAPI,
asked,
Some(512),
|_data: &[f32]| {},
);
if input.build_stream().is_err() {
continue;
}
assert_eq!(
input.sample_rate(),
asked.hz() as i32,
"{name}: the engine is handed a rate other than the one it asked for"
);
assert!(
SampleRate::from_hz(input.device_rate() as u32).is_some(),
"{name}: opened at {} Hz, which is not one of atome's rates",
input.device_rate()
);
if natively {
assert_eq!(
input.device_rate(),
asked.hz() as i32,
"{name} takes {} Hz, so nothing should have been resampled",
asked.hz()
);
} else {
assert_ne!(
input.device_rate(),
asked.hz() as i32,
"{name} will not take {} Hz, so it should be running at its own",
asked.hz()
);
}
}
}
}
}