extern crate asio_sys as sys;
extern crate num_traits;
use std::{
sync::{
atomic::{AtomicU32, AtomicU64, AtomicU8, Ordering},
mpsc, Arc, Mutex,
},
time::Duration,
};
use self::num_traits::{FromPrimitive, PrimInt};
use super::Device;
use crate::{
host::{
com,
error_emit::{emit_error, try_emit_error},
frames_to_duration,
},
BufferSize, Data, Error, ErrorKind, FrameCount, InputCallbackInfo, InputStreamTimestamp,
OutputCallbackInfo, OutputStreamTimestamp, SampleFormat, SampleRate, StreamConfig,
StreamInstant, I24,
};
#[derive(Default)]
struct TimeBase {
last_ns: AtomicU64,
epoch_ns: AtomicU64,
}
const TIMEGETIME_WRAP_NS: u64 = (u32::MAX as u64 + 1) * 1_000_000;
impl TimeBase {
fn to_stream_instant(&self, ns: u64) -> StreamInstant {
let prev = self.last_ns.swap(ns, Ordering::Relaxed);
let epoch = if ns < prev {
self.epoch_ns
.fetch_add(TIMEGETIME_WRAP_NS, Ordering::Relaxed)
+ TIMEGETIME_WRAP_NS
} else {
self.epoch_ns.load(Ordering::Relaxed)
};
StreamInstant::from_nanos(epoch + ns)
}
}
const ASIO_EVENT_DEBOUNCE: Duration = Duration::from_millis(500);
#[repr(u8)]
#[derive(Clone, Copy, PartialEq, Eq)]
enum StreamState {
Starting = 0,
Paused = 1,
Playing = 2,
}
impl StreamState {
fn load(atom: &AtomicU8, order: Ordering) -> Self {
match atom.load(order) {
1 => Self::Paused,
2 => Self::Playing,
_ => Self::Starting,
}
}
fn store(self, atom: &AtomicU8, order: Ordering) {
atom.store(self as u8, order);
}
}
pub struct Stream {
state: Arc<AtomicU8>,
driver: Arc<sys::Driver>,
asio_streams: Arc<Mutex<sys::AsioStreams>>,
callback_id: sys::BufferCallbackId,
driver_event_callback_id: sys::DriverEventCallbackId,
time_base: Arc<TimeBase>,
}
crate::assert_stream_send!(Stream);
crate::assert_stream_sync!(Stream);
impl Stream {
pub fn now(&self) -> StreamInstant {
let ms = unsafe { windows::Win32::Media::timeGetTime() };
self.time_base.to_stream_instant(ms as u64 * 1_000_000)
}
pub fn play(&self) -> Result<(), Error> {
StreamState::Playing.store(&self.state, Ordering::Release);
Ok(())
}
pub fn pause(&self) -> Result<(), Error> {
StreamState::Paused.store(&self.state, Ordering::Release);
Ok(())
}
pub fn buffer_size(&self) -> Result<FrameCount, Error> {
let streams = self.asio_streams.lock().map_err(|_| {
Error::with_message(ErrorKind::StreamInvalidated, "Stream lock poisoned")
})?;
Ok(streams
.output
.as_ref()
.or(streams.input.as_ref())
.expect("ASIO stream has neither input nor output")
.buffer_size as FrameCount)
}
}
impl Device {
pub fn build_input_stream_raw<D, E>(
&self,
config: StreamConfig,
sample_format: SampleFormat,
mut data_callback: D,
error_callback: E,
_timeout: Option<Duration>,
) -> Result<Stream, Error>
where
D: FnMut(&Data, &InputCallbackInfo) + Send + 'static,
E: FnMut(Error) + Send + 'static,
{
crate::validate_stream_config(&config)?;
com::com_initialized();
let description = self.description()?;
let driver = super::GLOBAL_ASIO
.get()
.ok_or_else(|| {
Error::with_message(
ErrorKind::DeviceNotAvailable,
"ASIO driver is not initialized",
)
})?
.load_driver(description.name())
.map_err(load_driver_err)?;
let stream_type = driver.input_data_type().map_err(build_stream_err)?;
let expected_sample_format =
super::device::convert_data_type(&stream_type).ok_or_else(|| {
Error::with_message(
ErrorKind::UnsupportedConfig,
"Input sample format is not supported",
)
})?;
if sample_format != expected_sample_format {
return Err(Error::with_message(
ErrorKind::UnsupportedConfig,
format!(
"Sample format {sample_format} is not supported; expected {expected_sample_format}"
),
));
}
let num_channels = config.channels;
let buffer_size = self.get_or_create_input_stream(&driver, config, sample_format)?;
let cpal_num_samples = buffer_size * num_channels as usize;
let len_bytes = cpal_num_samples * sample_format.sample_size();
let mut interleaved = vec![0u8; len_bytes];
let hardware_input_latency = Arc::new(AtomicU32::new(
driver
.latencies()
.map(|latencies| latencies.input.max(0) as u32)
.unwrap_or(0),
));
let state = Arc::new(AtomicU8::new(StreamState::Starting as u8));
let driver_event_callback_id = self
.add_event_callback(
&driver,
error_callback,
Arc::clone(&hardware_input_latency),
true,
Arc::clone(&state),
)
.inspect_err(|_| {
if let Ok(mut streams) = self.asio_streams.lock() {
streams.input = None;
}
})?;
let state_cb = Arc::clone(&state);
let asio_streams = self.asio_streams.clone();
let mut current_buffer_size = buffer_size as i32;
let mut last_buffer_index: i32 = -1;
let time_base = Arc::new(TimeBase::default());
let time_base_cb = Arc::clone(&time_base);
let callback_id = driver.add_callback(move |callback_info| unsafe {
if StreamState::load(&state_cb, Ordering::Acquire) != StreamState::Playing {
return;
}
if callback_info.buffer_index == last_buffer_index {
return;
}
last_buffer_index = callback_info.buffer_index;
let stream_lock = asio_streams.lock().unwrap();
let asio_stream = match stream_lock.input {
Some(ref asio_stream) => asio_stream,
None => return,
};
if asio_stream.buffer_size != current_buffer_size {
current_buffer_size = asio_stream.buffer_size;
interleaved.resize(
current_buffer_size as usize
* num_channels as usize
* sample_format.sample_size(),
0,
);
}
let hardware_input_latency = hardware_input_latency.load(Ordering::Relaxed) as usize;
let callback_instant = time_base_cb.to_stream_instant(callback_info.system_time);
#[allow(clippy::too_many_arguments)]
unsafe fn process_input_callback<A, D, F>(
data_callback: &mut D,
interleaved: &mut [u8],
asio_stream: &sys::AsioStream,
asio_info: &sys::CallbackInfo,
sample_rate: SampleRate,
format: SampleFormat,
from_endianness: F,
hardware_latency_frames: usize,
callback_instant: StreamInstant,
) where
A: Copy,
D: FnMut(&Data, &InputCallbackInfo) + Send + 'static,
F: Fn(A) -> A,
{
let interleaved: &mut [A] = cast_slice_mut(interleaved);
let n_frames = asio_stream.buffer_size as usize;
let n_channels = interleaved.len() / n_frames;
let buffer_index = asio_info.buffer_index as usize;
for ch_ix in 0..n_channels {
let asio_channel =
asio_channel_slice::<A>(asio_stream, buffer_index, ch_ix, None);
for (frame, s_asio) in interleaved.chunks_mut(n_channels).zip(asio_channel) {
frame[ch_ix] = from_endianness(*s_asio);
}
}
apply_input_callback_to_data::<A, _>(
data_callback,
interleaved,
callback_instant,
sample_rate,
format,
hardware_latency_frames,
);
}
match (&stream_type, sample_format) {
(&sys::AsioSampleType::ASIOSTInt16LSB, SampleFormat::I16) => {
process_input_callback::<i16, _, _>(
&mut data_callback,
&mut interleaved,
asio_stream,
callback_info,
config.sample_rate,
SampleFormat::I16,
from_le,
hardware_input_latency,
callback_instant,
);
}
(&sys::AsioSampleType::ASIOSTInt16MSB, SampleFormat::I16) => {
process_input_callback::<i16, _, _>(
&mut data_callback,
&mut interleaved,
asio_stream,
callback_info,
config.sample_rate,
SampleFormat::I16,
from_be,
hardware_input_latency,
callback_instant,
);
}
(&sys::AsioSampleType::ASIOSTFloat32LSB, SampleFormat::F32) => {
process_input_callback::<u32, _, _>(
&mut data_callback,
&mut interleaved,
asio_stream,
callback_info,
config.sample_rate,
SampleFormat::F32,
from_le,
hardware_input_latency,
callback_instant,
);
}
(&sys::AsioSampleType::ASIOSTFloat32MSB, SampleFormat::F32) => {
process_input_callback::<u32, _, _>(
&mut data_callback,
&mut interleaved,
asio_stream,
callback_info,
config.sample_rate,
SampleFormat::F32,
from_be,
hardware_input_latency,
callback_instant,
);
}
(&sys::AsioSampleType::ASIOSTInt32LSB, SampleFormat::I32) => {
process_input_callback::<i32, _, _>(
&mut data_callback,
&mut interleaved,
asio_stream,
callback_info,
config.sample_rate,
SampleFormat::I32,
from_le,
hardware_input_latency,
callback_instant,
);
}
(&sys::AsioSampleType::ASIOSTInt32MSB, SampleFormat::I32) => {
process_input_callback::<i32, _, _>(
&mut data_callback,
&mut interleaved,
asio_stream,
callback_info,
config.sample_rate,
SampleFormat::I32,
from_be,
hardware_input_latency,
callback_instant,
);
}
(&sys::AsioSampleType::ASIOSTFloat64LSB, SampleFormat::F64) => {
process_input_callback::<u64, _, _>(
&mut data_callback,
&mut interleaved,
asio_stream,
callback_info,
config.sample_rate,
SampleFormat::F64,
from_le,
hardware_input_latency,
callback_instant,
);
}
(&sys::AsioSampleType::ASIOSTFloat64MSB, SampleFormat::F64) => {
process_input_callback::<u64, _, _>(
&mut data_callback,
&mut interleaved,
asio_stream,
callback_info,
config.sample_rate,
SampleFormat::F64,
from_be,
hardware_input_latency,
callback_instant,
);
}
(&sys::AsioSampleType::ASIOSTInt24LSB, SampleFormat::I24) => {
process_input_callback_i24(
&mut data_callback,
&mut interleaved,
asio_stream,
callback_info,
config.sample_rate,
true,
hardware_input_latency,
callback_instant,
);
}
(&sys::AsioSampleType::ASIOSTInt24MSB, SampleFormat::I24) => {
process_input_callback_i24(
&mut data_callback,
&mut interleaved,
asio_stream,
callback_info,
config.sample_rate,
false,
hardware_input_latency,
callback_instant,
);
}
unsupported_format_pair => unreachable!(
"`build_input_stream_raw` should have returned with unsupported \
format {:?}",
unsupported_format_pair
),
}
});
let driver = Arc::new(driver);
let asio_streams = self.asio_streams.clone();
if let Err(e) = driver.start() {
driver.remove_event_callback(driver_event_callback_id);
driver.remove_callback(callback_id);
if let Ok(mut streams) = asio_streams.lock() {
streams.input = None;
}
return Err(build_stream_err(e));
}
StreamState::Paused.store(&state, Ordering::Release);
Ok(Stream {
state,
driver,
asio_streams,
callback_id,
driver_event_callback_id,
time_base: Arc::clone(&time_base),
})
}
pub fn build_output_stream_raw<D, E>(
&self,
config: StreamConfig,
sample_format: SampleFormat,
mut data_callback: D,
error_callback: E,
_timeout: Option<Duration>,
) -> Result<Stream, Error>
where
D: FnMut(&mut Data, &OutputCallbackInfo) + Send + 'static,
E: FnMut(Error) + Send + 'static,
{
crate::validate_stream_config(&config)?;
com::com_initialized();
let description = self.description()?;
let driver = super::GLOBAL_ASIO
.get()
.ok_or_else(|| {
Error::with_message(
ErrorKind::DeviceNotAvailable,
"ASIO driver is not initialized",
)
})?
.load_driver(description.name())
.map_err(load_driver_err)?;
let stream_type = driver.output_data_type().map_err(build_stream_err)?;
let expected_sample_format =
super::device::convert_data_type(&stream_type).ok_or_else(|| {
Error::with_message(
ErrorKind::UnsupportedConfig,
"Output sample format is not supported",
)
})?;
if sample_format != expected_sample_format {
return Err(Error::with_message(
ErrorKind::UnsupportedConfig,
format!(
"Sample format {sample_format} is not supported; expected {expected_sample_format}"
),
));
}
let num_channels = config.channels;
let buffer_size = self.get_or_create_output_stream(&driver, config, sample_format)?;
let cpal_num_samples = buffer_size * num_channels as usize;
let len_bytes = cpal_num_samples * sample_format.sample_size();
let mut interleaved = vec![0u8; len_bytes];
let current_callback_flag = self.current_callback_flag.clone();
let hardware_output_latency = Arc::new(AtomicU32::new(
driver
.latencies()
.map(|latencies| latencies.output.max(0) as u32)
.unwrap_or(0),
));
let state = Arc::new(AtomicU8::new(StreamState::Starting as u8));
let driver_event_callback_id = self
.add_event_callback(
&driver,
error_callback,
Arc::clone(&hardware_output_latency),
false,
Arc::clone(&state),
)
.inspect_err(|_| {
if let Ok(mut streams) = self.asio_streams.lock() {
streams.output = None;
}
})?;
let state_cb = Arc::clone(&state);
let asio_streams = self.asio_streams.clone();
let mut current_buffer_size = buffer_size as i32;
let mut last_buffer_index: i32 = -1;
let time_base = Arc::new(TimeBase::default());
let time_base_cb = Arc::clone(&time_base);
let callback_id = driver.add_callback(move |callback_info| unsafe {
if StreamState::load(&state_cb, Ordering::Acquire) != StreamState::Playing {
return;
}
if callback_info.buffer_index == last_buffer_index {
return;
}
last_buffer_index = callback_info.buffer_index;
let mut stream_lock = asio_streams.lock().unwrap();
let asio_stream = match stream_lock.output {
Some(ref mut asio_stream) => asio_stream,
None => return,
};
if asio_stream.buffer_size != current_buffer_size {
current_buffer_size = asio_stream.buffer_size;
interleaved.resize(
current_buffer_size as usize
* num_channels as usize
* sample_format.sample_size(),
0,
);
}
let hardware_output_latency = hardware_output_latency.load(Ordering::Relaxed) as usize;
let callback_instant = time_base_cb.to_stream_instant(callback_info.system_time);
let silence =
current_callback_flag.load(Ordering::Acquire) != callback_info.callback_flag;
if silence {
current_callback_flag.store(callback_info.callback_flag, Ordering::Release);
}
#[allow(clippy::too_many_arguments)]
unsafe fn process_output_callback<A, D, F>(
data_callback: &mut D,
interleaved: &mut [u8],
silence_asio_buffer: bool,
asio_stream: &mut sys::AsioStream,
asio_info: &sys::CallbackInfo,
sample_rate: SampleRate,
format: SampleFormat,
mix_samples: F,
hardware_latency_frames: usize,
callback_instant: StreamInstant,
) where
A: Copy,
D: FnMut(&mut Data, &OutputCallbackInfo) + Send + 'static,
F: Fn(A, A) -> A,
{
let interleaved: &mut [A] = cast_slice_mut(interleaved);
apply_output_callback_to_data::<A, _>(
data_callback,
interleaved,
callback_instant,
sample_rate,
format,
hardware_latency_frames,
);
let n_channels = interleaved.len() / asio_stream.buffer_size as usize;
let buffer_index = asio_info.buffer_index as usize;
for ch_ix in 0..n_channels {
let asio_channel =
asio_channel_slice_mut::<A>(asio_stream, buffer_index, ch_ix, None);
if silence_asio_buffer {
asio_channel.align_to_mut::<u8>().1.fill(0);
}
for (frame, s_asio) in interleaved.chunks(n_channels).zip(asio_channel) {
*s_asio = mix_samples(*s_asio, frame[ch_ix]);
}
}
}
interleaved.fill(0);
match (sample_format, &stream_type) {
(SampleFormat::I16, &sys::AsioSampleType::ASIOSTInt16LSB) => {
process_output_callback::<i16, _, _>(
&mut data_callback,
&mut interleaved,
silence,
asio_stream,
callback_info,
config.sample_rate,
SampleFormat::I16,
|old_sample, new_sample| {
from_le(old_sample).saturating_add(new_sample).to_le()
},
hardware_output_latency,
callback_instant,
);
}
(SampleFormat::I16, &sys::AsioSampleType::ASIOSTInt16MSB) => {
process_output_callback::<i16, _, _>(
&mut data_callback,
&mut interleaved,
silence,
asio_stream,
callback_info,
config.sample_rate,
SampleFormat::I16,
|old_sample, new_sample| {
from_be(old_sample).saturating_add(new_sample).to_be()
},
hardware_output_latency,
callback_instant,
);
}
(SampleFormat::F32, &sys::AsioSampleType::ASIOSTFloat32LSB) => {
process_output_callback::<u32, _, _>(
&mut data_callback,
&mut interleaved,
silence,
asio_stream,
callback_info,
config.sample_rate,
SampleFormat::F32,
|old_sample, new_sample| {
(f32::from_bits(from_le(old_sample)) + f32::from_bits(new_sample))
.to_bits()
.to_le()
},
hardware_output_latency,
callback_instant,
);
}
(SampleFormat::F32, &sys::AsioSampleType::ASIOSTFloat32MSB) => {
process_output_callback::<u32, _, _>(
&mut data_callback,
&mut interleaved,
silence,
asio_stream,
callback_info,
config.sample_rate,
SampleFormat::F32,
|old_sample, new_sample| {
(f32::from_bits(from_be(old_sample)) + f32::from_bits(new_sample))
.to_bits()
.to_be()
},
hardware_output_latency,
callback_instant,
);
}
(SampleFormat::I32, &sys::AsioSampleType::ASIOSTInt32LSB) => {
process_output_callback::<i32, _, _>(
&mut data_callback,
&mut interleaved,
silence,
asio_stream,
callback_info,
config.sample_rate,
SampleFormat::I32,
|old_sample, new_sample| {
from_le(old_sample).saturating_add(new_sample).to_le()
},
hardware_output_latency,
callback_instant,
);
}
(SampleFormat::I32, &sys::AsioSampleType::ASIOSTInt32MSB) => {
process_output_callback::<i32, _, _>(
&mut data_callback,
&mut interleaved,
silence,
asio_stream,
callback_info,
config.sample_rate,
SampleFormat::I32,
|old_sample, new_sample| {
from_be(old_sample).saturating_add(new_sample).to_be()
},
hardware_output_latency,
callback_instant,
);
}
(SampleFormat::F64, &sys::AsioSampleType::ASIOSTFloat64LSB) => {
process_output_callback::<u64, _, _>(
&mut data_callback,
&mut interleaved,
silence,
asio_stream,
callback_info,
config.sample_rate,
SampleFormat::F64,
|old_sample, new_sample| {
(f64::from_bits(from_le(old_sample)) + f64::from_bits(new_sample))
.to_bits()
.to_le()
},
hardware_output_latency,
callback_instant,
);
}
(SampleFormat::F64, &sys::AsioSampleType::ASIOSTFloat64MSB) => {
process_output_callback::<u64, _, _>(
&mut data_callback,
&mut interleaved,
silence,
asio_stream,
callback_info,
config.sample_rate,
SampleFormat::F64,
|old_sample, new_sample| {
(f64::from_bits(from_be(old_sample)) + f64::from_bits(new_sample))
.to_bits()
.to_be()
},
hardware_output_latency,
callback_instant,
);
}
(SampleFormat::I24, &sys::AsioSampleType::ASIOSTInt24LSB) => {
process_output_callback_i24::<_>(
&mut data_callback,
&mut interleaved,
silence,
true,
asio_stream,
callback_info,
config.sample_rate,
hardware_output_latency,
callback_instant,
);
}
(SampleFormat::I24, &sys::AsioSampleType::ASIOSTInt24MSB) => {
process_output_callback_i24::<_>(
&mut data_callback,
&mut interleaved,
silence,
false,
asio_stream,
callback_info,
config.sample_rate,
hardware_output_latency,
callback_instant,
);
}
unsupported_format_pair => unreachable!(
"`build_output_stream_raw` should have returned with unsupported \
format {unsupported_format_pair:?}"
),
}
});
let driver = Arc::new(driver);
let asio_streams = self.asio_streams.clone();
if let Err(e) = driver.start() {
driver.remove_event_callback(driver_event_callback_id);
driver.remove_callback(callback_id);
if let Ok(mut streams) = asio_streams.lock() {
streams.output = None;
}
return Err(build_stream_err(e));
}
StreamState::Paused.store(&state, Ordering::Release);
Ok(Stream {
state,
driver,
asio_streams,
callback_id,
driver_event_callback_id,
time_base: Arc::clone(&time_base),
})
}
fn get_or_create_input_stream(
&self,
driver: &sys::Driver,
config: StreamConfig,
sample_format: SampleFormat,
) -> Result<usize, Error> {
let num_asio_channels = self.default_input_config()?.channels;
check_config(driver, config, sample_format, num_asio_channels)?;
let num_channels = config.channels as usize;
let mut streams = self.asio_streams.lock().map_err(|_| {
Error::with_message(ErrorKind::StreamInvalidated, "Stream lock poisoned")
})?;
let buffer_size = match config.buffer_size {
BufferSize::Fixed(v) => Some(v as i32),
BufferSize::Default => None,
};
match streams.input {
Some(ref input) => Ok(input.buffer_size as usize),
None => {
let output = streams.output.take();
driver
.prepare_input_stream(output, num_channels, buffer_size)
.map(|new_streams| {
let bs = match new_streams.input {
Some(ref inp) => inp.buffer_size as usize,
None => unreachable!(),
};
*streams = new_streams;
bs
})
.map_err(build_stream_err)
}
}
}
fn get_or_create_output_stream(
&self,
driver: &sys::Driver,
config: StreamConfig,
sample_format: SampleFormat,
) -> Result<usize, Error> {
let num_asio_channels = self.default_output_config()?.channels;
check_config(driver, config, sample_format, num_asio_channels)?;
let num_channels = config.channels as usize;
let mut streams = self.asio_streams.lock().map_err(|_| {
Error::with_message(ErrorKind::StreamInvalidated, "Stream lock poisoned")
})?;
let buffer_size = match config.buffer_size {
BufferSize::Fixed(v) => Some(v as i32),
BufferSize::Default => None,
};
match streams.output {
Some(ref output) => Ok(output.buffer_size as usize),
None => {
let input = streams.input.take();
driver
.prepare_output_stream(input, num_channels, buffer_size)
.map(|new_streams| {
let bs = match new_streams.output {
Some(ref out) => out.buffer_size as usize,
None => unreachable!(),
};
*streams = new_streams;
bs
})
.map_err(build_stream_err)
}
}
}
fn add_event_callback<E>(
&self,
driver: &sys::Driver,
error_callback: E,
hardware_latency: Arc<AtomicU32>,
is_input: bool,
state: Arc<AtomicU8>,
) -> Result<sys::DriverEventCallbackId, Error>
where
E: FnMut(Error) + Send + 'static,
{
let error_callback_shared = Arc::new(Mutex::new(error_callback));
let configured_sample_rate = match driver.sample_rate() {
Ok(r) if r > 0.0 => Some(r),
_ => {
None
}
};
let driver_for_latency = driver.clone();
let asio_streams_for_event = self.asio_streams.clone();
let (timer_tx, timer_rx) = mpsc::channel::<Error>();
let error_cb_for_timer = Arc::clone(&error_callback_shared);
std::thread::Builder::new()
.name("cpal-asio-event-timer".into())
.spawn(move || {
let mut pending: Option<Error> = None;
loop {
let result = if pending.is_some() {
timer_rx.recv_timeout(ASIO_EVENT_DEBOUNCE)
} else {
timer_rx
.recv()
.map_err(|_| mpsc::RecvTimeoutError::Disconnected)
};
match result {
Ok(err) => {
pending = Some(err);
}
Err(mpsc::RecvTimeoutError::Timeout) => {
if let Some(err) = pending.take() {
emit_error(&error_cb_for_timer, err);
}
}
Err(mpsc::RecvTimeoutError::Disconnected) => return,
}
}
})
.map_err(|e| {
Error::with_message(
ErrorKind::ResourceExhausted,
format!("Failed to spawn event timer thread: {e}"),
)
})?;
Ok(driver.add_event_callback(move |event| {
match event {
sys::AsioDriverEvent::Message {
selector: msg,
value,
} => match msg {
sys::AsioMessageSelectors::kAsioSelectorSupported => {
matches!(
sys::AsioMessageSelectors::from_i64(value as i64),
Some(sys::AsioMessageSelectors::kAsioBufferSizeChange)
| Some(sys::AsioMessageSelectors::kAsioOverload)
)
}
sys::AsioMessageSelectors::kAsioResetRequest => {
if StreamState::load(&state, Ordering::Acquire) != StreamState::Starting {
let _ = timer_tx.send(Error::with_message(
ErrorKind::StreamInvalidated,
"Stream reset was requested by the ASIO driver",
));
}
true
}
sys::AsioMessageSelectors::kAsioResyncRequest => {
if StreamState::load(&state, Ordering::Acquire) != StreamState::Starting {
let _ = timer_tx.send(Error::with_message(
ErrorKind::StreamInvalidated,
"Stream resynchronization was requested by the ASIO driver",
));
}
true
}
sys::AsioMessageSelectors::kAsioOverload => {
if StreamState::load(&state, Ordering::Acquire) == StreamState::Playing {
let _ =
try_emit_error(&error_callback_shared, Error::new(ErrorKind::Xrun));
}
true
}
sys::AsioMessageSelectors::kAsioLatenciesChanged => {
if let Ok(latencies) = driver_for_latency.latencies() {
let latency = if is_input {
latencies.input
} else {
latencies.output
};
hardware_latency.store(latency.max(0) as u32, Ordering::Relaxed);
}
false
}
sys::AsioMessageSelectors::kAsioBufferSizeChange => {
if value > 0 {
let mut streams = asio_streams_for_event
.lock()
.unwrap_or_else(|e| e.into_inner());
let stream = if is_input {
streams.input.as_mut()
} else {
streams.output.as_mut()
};
if let Some(s) = stream {
s.buffer_size = value;
}
}
true
}
_ => false,
},
sys::AsioDriverEvent::SampleRateChanged(new_rate) => {
let should_notify = match configured_sample_rate {
Some(rate) => (new_rate - rate).abs() >= 1.0,
None => {
true
}
};
if should_notify
&& StreamState::load(&state, Ordering::Acquire) != StreamState::Starting
{
let _ = timer_tx.send(Error::with_message(
ErrorKind::StreamInvalidated,
format!("Sample rate changed to {new_rate} Hz by the ASIO driver"),
));
}
false
}
}
}))
}
}
impl Drop for Stream {
fn drop(&mut self) {
self.driver.remove_callback(self.callback_id);
self.driver
.remove_event_callback(self.driver_event_callback_id);
}
}
fn check_config(
driver: &sys::Driver,
config: StreamConfig,
sample_format: SampleFormat,
num_asio_channels: u16,
) -> Result<(), Error> {
let StreamConfig {
channels,
sample_rate,
buffer_size,
} = config;
if let BufferSize::Fixed(requested_size) = buffer_size {
let range = driver.buffersize_range().map_err(build_stream_err)?;
let requested_size_i32 = requested_size as i32;
if !(range.min..=range.max).contains(&requested_size_i32) {
return Err(Error::with_message(
ErrorKind::UnsupportedConfig,
format!(
"Buffer size {requested_size} is not in the supported range {min}..={max}",
min = range.min,
max = range.max
),
));
}
if let sys::BufferPreference::Stepped { step, .. } = range.preferred {
let offset = requested_size_i32 - range.min;
if offset % step as i32 != 0 {
return Err(Error::with_message(
ErrorKind::UnsupportedConfig,
format!(
"Buffer size {requested_size} is not valid; sizes must start at {min} and increment by {step}",
min = range.min
),
));
}
}
}
let sample_rate = sample_rate.into();
if sample_rate != driver.sample_rate().map_err(build_stream_err)? {
if driver
.can_sample_rate(sample_rate)
.map_err(build_stream_err)?
{
driver
.set_sample_rate(sample_rate)
.map_err(build_stream_err)?;
} else {
return Err(Error::with_message(
ErrorKind::UnsupportedConfig,
format!("Sample rate {sample_rate} Hz is not supported"),
));
}
}
match sample_format {
SampleFormat::I16 | SampleFormat::I24 | SampleFormat::I32 | SampleFormat::F32 => (),
_ => {
return Err(Error::with_message(
ErrorKind::UnsupportedConfig,
format!("Sample format {sample_format} is not supported"),
))
}
}
if channels > num_asio_channels {
return Err(Error::with_message(
ErrorKind::UnsupportedConfig,
format!("Channel count {channels} exceeds the maximum of {num_asio_channels}"),
));
}
Ok(())
}
#[inline]
unsafe fn cast_slice_mut<T>(v: &mut [u8]) -> &mut [T] {
debug_assert!(v.len() % std::mem::size_of::<T>() == 0);
std::slice::from_raw_parts_mut(v.as_mut_ptr() as *mut T, v.len() / std::mem::size_of::<T>())
}
#[inline]
fn from_le<T: PrimInt>(t: T) -> T {
T::from_le(t)
}
#[inline]
fn from_be<T: PrimInt>(t: T) -> T {
T::from_be(t)
}
#[inline]
unsafe fn asio_channel_slice<T>(
asio_stream: &sys::AsioStream,
buffer_index: usize,
channel_index: usize,
requested_channel_length: Option<usize>,
) -> &[T] {
let channel_length = requested_channel_length.unwrap_or(asio_stream.buffer_size as usize);
let buff_ptr: *const T =
asio_stream.buffer_infos[channel_index].buffers[buffer_index] as *const _;
std::slice::from_raw_parts(buff_ptr, channel_length)
}
#[inline]
unsafe fn asio_channel_slice_mut<T>(
asio_stream: &mut sys::AsioStream,
buffer_index: usize,
channel_index: usize,
requested_channel_length: Option<usize>,
) -> &mut [T] {
let channel_length = requested_channel_length.unwrap_or(asio_stream.buffer_size as usize);
let buff_ptr: *mut T = asio_stream.buffer_infos[channel_index].buffers[buffer_index] as *mut _;
std::slice::from_raw_parts_mut(buff_ptr, channel_length)
}
fn load_driver_err(e: sys::LoadDriverError) -> Error {
match e {
sys::LoadDriverError::LoadDriverFailed | sys::LoadDriverError::DriverAlreadyExists => {
Error::with_message(ErrorKind::DeviceNotAvailable, e.to_string())
}
sys::LoadDriverError::InitializationFailed(asio_err) => build_stream_err(asio_err),
}
}
fn build_stream_err(e: sys::AsioError) -> Error {
match e {
sys::AsioError::NoDrivers | sys::AsioError::HardwareMalfunction => {
Error::with_message(ErrorKind::DeviceNotAvailable, e.to_string())
}
sys::AsioError::InvalidInput | sys::AsioError::BadMode => {
Error::with_message(ErrorKind::InvalidInput, e.to_string())
}
sys::AsioError::InvalidBufferSize | sys::AsioError::NoRate => {
Error::with_message(ErrorKind::UnsupportedConfig, e.to_string())
}
sys::AsioError::HardwareStuck => Error::with_message(ErrorKind::DeviceBusy, e.to_string()),
err => Error::with_message(ErrorKind::BackendError, err.to_string()),
}
}
#[inline]
fn i24_bytes_to_i32(i24_bytes: &[u8; 3], little_endian: bool) -> i32 {
let sample = if little_endian {
i32::from_le_bytes([i24_bytes[0], i24_bytes[1], i24_bytes[2], 0u8])
} else {
i32::from_le_bytes([i24_bytes[2], i24_bytes[1], i24_bytes[0], 0u8])
};
if sample & 0x800000 != 0 {
sample | -0x1000000
} else {
sample
}
}
#[allow(clippy::too_many_arguments)]
unsafe fn process_output_callback_i24<D>(
data_callback: &mut D,
interleaved: &mut [u8],
silence_asio_buffer: bool,
little_endian: bool,
asio_stream: &mut sys::AsioStream,
asio_info: &sys::CallbackInfo,
sample_rate: SampleRate,
hardware_latency_frames: usize,
callback_instant: StreamInstant,
) where
D: FnMut(&mut Data, &OutputCallbackInfo) + Send + 'static,
{
let format = SampleFormat::I24;
let interleaved: &mut [I24] = cast_slice_mut(interleaved);
apply_output_callback_to_data::<I24, _>(
data_callback,
interleaved,
callback_instant,
sample_rate,
format,
hardware_latency_frames,
);
let asio_sample_size_bytes = 3;
let n_channels = interleaved.len() / asio_stream.buffer_size as usize;
let buffer_index = asio_info.buffer_index as usize;
for ch_ix in 0..n_channels {
let asio_channel = asio_channel_slice_mut(
asio_stream,
buffer_index,
ch_ix,
Some(asio_stream.buffer_size as usize * asio_sample_size_bytes),
);
if silence_asio_buffer {
asio_channel.align_to_mut::<u8>().1.fill(0);
}
for (channel_sample, sample_in_buffer) in asio_channel
.chunks_mut(asio_sample_size_bytes)
.zip(interleaved.iter().skip(ch_ix).step_by(n_channels))
{
let result = if silence_asio_buffer {
sample_in_buffer.inner()
} else {
let sample = i24_bytes_to_i32(
&[channel_sample[0], channel_sample[1], channel_sample[2]],
little_endian,
);
(sample_in_buffer.inner() + sample).clamp(-8388608, 8388607)
};
let bytes = result.to_le_bytes();
if little_endian {
channel_sample[0] = bytes[0];
channel_sample[1] = bytes[1];
channel_sample[2] = bytes[2];
} else {
channel_sample[2] = bytes[0];
channel_sample[1] = bytes[1];
channel_sample[0] = bytes[2];
}
}
}
}
#[allow(clippy::too_many_arguments)]
unsafe fn process_input_callback_i24<D>(
data_callback: &mut D,
interleaved: &mut [u8],
asio_stream: &sys::AsioStream,
asio_info: &sys::CallbackInfo,
sample_rate: SampleRate,
little_endian: bool,
hardware_latency_frames: usize,
callback_instant: StreamInstant,
) where
D: FnMut(&Data, &InputCallbackInfo) + Send + 'static,
{
let format = SampleFormat::I24;
let interleaved: &mut [I24] = cast_slice_mut(interleaved);
let n_frames = asio_stream.buffer_size as usize;
let n_channels = interleaved.len() / n_frames;
let buffer_index = asio_info.buffer_index as usize;
let asio_sample_size_bytes = 3;
for ch_ix in 0..n_channels {
let asio_channel = asio_channel_slice::<u8>(
asio_stream,
buffer_index,
ch_ix,
Some(n_frames * asio_sample_size_bytes),
);
for (channel_sample, sample_in_buffer) in asio_channel
.chunks(asio_sample_size_bytes)
.zip(interleaved.iter_mut().skip(ch_ix).step_by(n_channels))
{
let sample = i24_bytes_to_i32(
&[channel_sample[0], channel_sample[1], channel_sample[2]],
little_endian,
);
*sample_in_buffer = I24::new(sample).unwrap();
}
}
apply_input_callback_to_data::<I24, _>(
data_callback,
interleaved,
callback_instant,
sample_rate,
format,
hardware_latency_frames,
);
}
#[inline]
unsafe fn apply_output_callback_to_data<A, D>(
data_callback: &mut D,
interleaved: &mut [A],
callback_instant: StreamInstant,
sample_rate: SampleRate,
sample_format: SampleFormat,
hardware_latency_frames: usize,
) where
A: Copy,
D: FnMut(&mut Data, &OutputCallbackInfo) + Send + 'static,
{
let mut data = Data::from_parts(
interleaved.as_mut_ptr() as *mut (),
interleaved.len(),
sample_format,
);
let delay = frames_to_duration(hardware_latency_frames as FrameCount, sample_rate);
let playback = callback_instant + delay;
let timestamp = OutputStreamTimestamp {
callback: callback_instant,
playback,
};
let info = OutputCallbackInfo { timestamp };
data_callback(&mut data, &info);
}
#[inline]
unsafe fn apply_input_callback_to_data<A, D>(
data_callback: &mut D,
interleaved: &mut [A],
callback_instant: StreamInstant,
sample_rate: SampleRate,
format: SampleFormat,
hardware_latency_frames: usize,
) where
A: Copy,
D: FnMut(&Data, &InputCallbackInfo) + Send + 'static,
{
let data = Data::from_parts(
interleaved.as_mut_ptr() as *mut (),
interleaved.len(),
format,
);
let delay = frames_to_duration(hardware_latency_frames as FrameCount, sample_rate);
let capture = callback_instant
.checked_sub(delay)
.unwrap_or(StreamInstant::ZERO);
let timestamp = InputStreamTimestamp {
callback: callback_instant,
capture,
};
let info = InputCallbackInfo { timestamp };
data_callback(&data, &info);
}