extern crate asio_sys as sys; extern crate num_traits; use crate::host::com; use crate::I24; use self::num_traits::{FromPrimitive, PrimInt}; use super::Device; use crate::{ BackendSpecificError, BufferSize, BuildStreamError, Data, InputCallbackInfo, OutputCallbackInfo, PauseStreamError, PlayStreamError, SampleFormat, StreamConfig, StreamError, }; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::sync::{Arc, Mutex}; use std::time::Duration; pub struct Stream { playing: Arc, // Ensure the `Driver` does not terminate until the last stream is dropped. driver: Arc, #[allow(dead_code)] asio_streams: Arc>, callback_id: sys::BufferCallbackId, driver_event_callback_id: sys::DriverEventCallbackId, } // Compile-time assertion that Stream is Send and Sync crate::assert_stream_send!(Stream); crate::assert_stream_sync!(Stream); impl Stream { pub fn play(&self) -> Result<(), PlayStreamError> { self.playing.store(true, Ordering::Release); Ok(()) } pub fn pause(&self) -> Result<(), PauseStreamError> { self.playing.store(false, Ordering::Release); Ok(()) } pub fn buffer_size(&self) -> Option { let streams = self.asio_streams.lock().ok()?; streams .output .as_ref() .or(streams.input.as_ref()) .map(|s| s.buffer_size as crate::FrameCount) } } impl Device { pub fn build_input_stream_raw( &self, config: StreamConfig, sample_format: SampleFormat, mut data_callback: D, error_callback: E, _timeout: Option, ) -> Result where D: FnMut(&Data, &InputCallbackInfo) + Send + 'static, E: FnMut(StreamError) + Send + 'static, { com::com_initialized(); let description = self .description() .map_err(|_| BuildStreamError::DeviceNotAvailable)?; let driver = super::GLOBAL_ASIO .get() .ok_or(BuildStreamError::DeviceNotAvailable)? .load_driver(description.name()) .map_err(load_driver_err)?; let stream_type = driver.input_data_type().map_err(build_stream_err)?; // Ensure that the desired sample type is supported. let expected_sample_format = super::device::convert_data_type(&stream_type) .ok_or(BuildStreamError::StreamConfigNotSupported)?; if sample_format != expected_sample_format { return Err(BuildStreamError::StreamConfigNotSupported); } 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; // Create the buffer depending on the size of the data type. let len_bytes = cpal_num_samples * sample_format.sample_size(); let mut interleaved = vec![0u8; len_bytes]; // Query hardware input latency (order matters: needs buffers created above). // Wrapped in Arc so the message callback can update it on // kAsioLatenciesChanged without touching the buffer callback. let hardware_input_latency = Arc::new(AtomicUsize::new( driver .latencies() .map(|latencies| latencies.input.max(0) as usize) .unwrap_or(0), )); let driver_event_callback_id = self.add_event_callback( &driver, error_callback, Arc::clone(&hardware_input_latency), true, ); let stream_playing = Arc::new(AtomicBool::new(false)); let playing = Arc::clone(&stream_playing); let asio_streams = self.asio_streams.clone(); let mut current_buffer_size = buffer_size as i32; let mut last_buffer_index: i32 = -1; // Set the input callback. // This is most performance critical part of the ASIO bindings. let callback_id = driver.add_callback(move |callback_info| unsafe { // If not playing return early. if !playing.load(Ordering::Acquire) { return; } // Guard against non-conformant drivers (e.g. Focusrite USB ASIO, ReaRoute) that // fire the buffer callback multiple times per buffer cycle with the same buffer // index. if callback_info.buffer_index == last_buffer_index { return; } last_buffer_index = callback_info.buffer_index; // There is 0% chance of lock contention the host only locks when recreating streams. let stream_lock = asio_streams.lock().unwrap(); let asio_stream = match stream_lock.input { Some(ref asio_stream) => asio_stream, None => return, }; // Resize the buffer only when the driver issues a buffer size change request. // In normal operation this branch is never taken. 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); /// 1. Write from the ASIO buffer to the interleaved CPAL buffer. /// 2. Deliver the CPAL buffer to the user callback. #[allow(clippy::too_many_arguments)] unsafe fn process_input_callback( data_callback: &mut D, interleaved: &mut [u8], asio_stream: &sys::AsioStream, asio_info: &sys::CallbackInfo, sample_rate: crate::SampleRate, format: SampleFormat, from_endianness: F, hardware_latency_frames: usize, ) where A: Copy, D: FnMut(&Data, &InputCallbackInfo) + Send + 'static, F: Fn(A) -> A, { // 1. Write the ASIO channels to the CPAL buffer. 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::(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); } } // 2. Deliver the interleaved buffer to the callback. apply_input_callback_to_data::( data_callback, interleaved, asio_info, sample_rate, format, hardware_latency_frames, ); } match (&stream_type, sample_format) { (&sys::AsioSampleType::ASIOSTInt16LSB, SampleFormat::I16) => { process_input_callback::( &mut data_callback, &mut interleaved, asio_stream, callback_info, config.sample_rate, SampleFormat::I16, from_le, hardware_input_latency, ); } (&sys::AsioSampleType::ASIOSTInt16MSB, SampleFormat::I16) => { process_input_callback::( &mut data_callback, &mut interleaved, asio_stream, callback_info, config.sample_rate, SampleFormat::I16, from_be, hardware_input_latency, ); } (&sys::AsioSampleType::ASIOSTFloat32LSB, SampleFormat::F32) => { process_input_callback::( &mut data_callback, &mut interleaved, asio_stream, callback_info, config.sample_rate, SampleFormat::F32, from_le, hardware_input_latency, ); } (&sys::AsioSampleType::ASIOSTFloat32MSB, SampleFormat::F32) => { process_input_callback::( &mut data_callback, &mut interleaved, asio_stream, callback_info, config.sample_rate, SampleFormat::F32, from_be, hardware_input_latency, ); } (&sys::AsioSampleType::ASIOSTInt32LSB, SampleFormat::I32) => { process_input_callback::( &mut data_callback, &mut interleaved, asio_stream, callback_info, config.sample_rate, SampleFormat::I32, from_le, hardware_input_latency, ); } (&sys::AsioSampleType::ASIOSTInt32MSB, SampleFormat::I32) => { process_input_callback::( &mut data_callback, &mut interleaved, asio_stream, callback_info, config.sample_rate, SampleFormat::I32, from_be, hardware_input_latency, ); } (&sys::AsioSampleType::ASIOSTFloat64LSB, SampleFormat::F64) => { process_input_callback::( &mut data_callback, &mut interleaved, asio_stream, callback_info, config.sample_rate, SampleFormat::F64, from_le, hardware_input_latency, ); } (&sys::AsioSampleType::ASIOSTFloat64MSB, SampleFormat::F64) => { process_input_callback::( &mut data_callback, &mut interleaved, asio_stream, callback_info, config.sample_rate, SampleFormat::F64, from_be, hardware_input_latency, ); } (&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, ); } (&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, ); } 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(); driver.start().map_err(build_stream_err)?; Ok(Stream { playing: stream_playing, driver, asio_streams, callback_id, driver_event_callback_id, }) } pub fn build_output_stream_raw( &self, config: StreamConfig, sample_format: SampleFormat, mut data_callback: D, error_callback: E, _timeout: Option, ) -> Result where D: FnMut(&mut Data, &OutputCallbackInfo) + Send + 'static, E: FnMut(StreamError) + Send + 'static, { com::com_initialized(); let description = self .description() .map_err(|_| BuildStreamError::DeviceNotAvailable)?; let driver = super::GLOBAL_ASIO .get() .ok_or(BuildStreamError::DeviceNotAvailable)? .load_driver(description.name()) .map_err(load_driver_err)?; let stream_type = driver.output_data_type().map_err(build_stream_err)?; // Ensure that the desired sample type is supported. let expected_sample_format = super::device::convert_data_type(&stream_type) .ok_or(BuildStreamError::StreamConfigNotSupported)?; if sample_format != expected_sample_format { return Err(BuildStreamError::StreamConfigNotSupported); } 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; // Create the buffer depending on data type. 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(); // Query hardware output latency (order matters: needs buffers created above). // Wrapped in Arc so the message callback can update it on // kAsioLatenciesChanged without touching the buffer callback. let hardware_output_latency = Arc::new(AtomicUsize::new( driver .latencies() .map(|latencies| latencies.output.max(0) as usize) .unwrap_or(0), )); let driver_event_callback_id = self.add_event_callback( &driver, error_callback, Arc::clone(&hardware_output_latency), false, ); let stream_playing = Arc::new(AtomicBool::new(false)); let playing = Arc::clone(&stream_playing); let asio_streams = self.asio_streams.clone(); let mut current_buffer_size = buffer_size as i32; let mut last_buffer_index: i32 = -1; let callback_id = driver.add_callback(move |callback_info| unsafe { // If not playing, return early. if !playing.load(Ordering::Acquire) { return; } // Guard against non-conformant drivers (e.g. Focusrite USB ASIO, ReaRoute) that // fire the buffer callback multiple times per buffer cycle with the same buffer // index. if callback_info.buffer_index == last_buffer_index { return; } last_buffer_index = callback_info.buffer_index; // There is 0% chance of lock contention the host only locks when recreating streams. let mut stream_lock = asio_streams.lock().unwrap(); let asio_stream = match stream_lock.output { Some(ref mut asio_stream) => asio_stream, None => return, }; // Resize the buffer only when the driver issues a buffer size change request. // In normal operation this branch is never taken. 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); // Silence the ASIO buffer that is about to be used. // // Check if any other callbacks have already silenced the buffer associated with // the current callback. The flag is updated once per buffer switch. let silence = current_callback_flag.load(Ordering::Acquire) != callback_info.callback_flag; if silence { current_callback_flag.store(callback_info.callback_flag, Ordering::Release); } /// 1. Render the given callback to the given buffer of interleaved samples. /// 2. If required, silence the ASIO buffer. /// 3. Finally, write the interleaved data to the non-interleaved ASIO buffer, /// performing endianness conversions as necessary. #[allow(clippy::too_many_arguments)] unsafe fn process_output_callback( data_callback: &mut D, interleaved: &mut [u8], silence_asio_buffer: bool, asio_stream: &mut sys::AsioStream, asio_info: &sys::CallbackInfo, sample_rate: crate::SampleRate, format: SampleFormat, mix_samples: F, hardware_latency_frames: usize, ) 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::( data_callback, interleaved, asio_info, 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; // Write interleaved samples to ASIO channels, one channel at a time. for ch_ix in 0..n_channels { let asio_channel = asio_channel_slice_mut::(asio_stream, buffer_index, ch_ix, None); if silence_asio_buffer { asio_channel.align_to_mut::().1.fill(0); } for (frame, s_asio) in interleaved.chunks(n_channels).zip(asio_channel) { *s_asio = mix_samples(*s_asio, frame[ch_ix]); } } } match (sample_format, &stream_type) { (SampleFormat::I16, &sys::AsioSampleType::ASIOSTInt16LSB) => { process_output_callback::( &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, ); } (SampleFormat::I16, &sys::AsioSampleType::ASIOSTInt16MSB) => { process_output_callback::( &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, ); } (SampleFormat::F32, &sys::AsioSampleType::ASIOSTFloat32LSB) => { process_output_callback::( &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, ); } (SampleFormat::F32, &sys::AsioSampleType::ASIOSTFloat32MSB) => { process_output_callback::( &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, ); } (SampleFormat::I32, &sys::AsioSampleType::ASIOSTInt32LSB) => { process_output_callback::( &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, ); } (SampleFormat::I32, &sys::AsioSampleType::ASIOSTInt32MSB) => { process_output_callback::( &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, ); } (SampleFormat::F64, &sys::AsioSampleType::ASIOSTFloat64LSB) => { process_output_callback::( &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, ); } (SampleFormat::F64, &sys::AsioSampleType::ASIOSTFloat64MSB) => { process_output_callback::( &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, ); } (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, ); } (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, ); } 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(); driver.start().map_err(build_stream_err)?; Ok(Stream { playing: stream_playing, driver, asio_streams, callback_id, driver_event_callback_id, }) } /// Create a new CPAL Input Stream. /// /// If there is no existing ASIO Input Stream it will be created. /// /// On success, the buffer size of the stream is returned. fn get_or_create_input_stream( &self, driver: &sys::Driver, config: StreamConfig, sample_format: SampleFormat, ) -> Result { let num_asio_channels = self .default_input_config() .map_err(|_| BuildStreamError::StreamConfigNotSupported)? .channels; check_config(driver, config, sample_format, num_asio_channels)?; let num_channels = config.channels as usize; let mut streams = self.asio_streams.lock().unwrap(); let buffer_size = match config.buffer_size { BufferSize::Fixed(v) => Some(v as i32), BufferSize::Default => None, }; // Either create a stream if thers none or had back the // size of the current one. 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(|_| BuildStreamError::DeviceNotAvailable) } } } /// Create a new CPAL Output Stream. /// /// If there is no existing ASIO Output Stream it will be created. fn get_or_create_output_stream( &self, driver: &sys::Driver, config: StreamConfig, sample_format: SampleFormat, ) -> Result { let num_asio_channels = self .default_output_config() .map_err(|_| BuildStreamError::StreamConfigNotSupported)? .channels; check_config(driver, config, sample_format, num_asio_channels)?; let num_channels = config.channels as usize; let mut streams = self.asio_streams.lock().unwrap(); let buffer_size = match config.buffer_size { BufferSize::Fixed(v) => Some(v as i32), BufferSize::Default => None, }; // Either create a stream if thers none or had back the // size of the current one. 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(|_| BuildStreamError::DeviceNotAvailable) } } } fn add_event_callback( &self, driver: &sys::Driver, error_callback: E, hardware_latency: Arc, is_input: bool, ) -> sys::DriverEventCallbackId where E: FnMut(StreamError) + Send + 'static, { let error_callback_shared = Arc::new(Mutex::new(error_callback)); let configured_sample_rate = driver.sample_rate().ok().filter(|&r| r > 0.0); let driver_for_latency = driver.clone(); let asio_streams_for_event = self.asio_streams.clone(); driver.add_event_callback(move |event| { match event { sys::AsioDriverEvent::Message { selector: msg, value, } => match msg { sys::AsioMessageSelectors::kAsioSelectorSupported => { // Signal which selectors this stream opts into. matches!( sys::AsioMessageSelectors::from_i64(value as i64), Some(sys::AsioMessageSelectors::kAsioBufferSizeChange) ) } sys::AsioMessageSelectors::kAsioResetRequest => { if let Ok(mut cb) = error_callback_shared.lock() { cb(StreamError::StreamInvalidated); } false } sys::AsioMessageSelectors::kAsioResyncRequest => { if let Ok(mut cb) = error_callback_shared.lock() { cb(StreamError::BufferUnderrun); } false } 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 usize, Ordering::Relaxed); } false } sys::AsioMessageSelectors::kAsioBufferSizeChange => { if value > 0 { if let Ok(mut streams) = asio_streams_for_event.lock() { 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) => { if let Some(rate) = configured_sample_rate { if (new_rate - rate).abs() >= 1.0 { if let Ok(mut cb) = error_callback_shared.lock() { cb(StreamError::StreamInvalidated); } } } 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); } } // Convert the given duration in frames at the given sample rate to a `std::time::Duration`. #[inline] fn frames_to_duration(frames: usize, rate: crate::SampleRate) -> std::time::Duration { let secsf = frames as f64 / rate as f64; let secs = secsf as u64; let nanos = ((secsf - secs as f64) * 1_000_000_000.0) as u32; std::time::Duration::new(secs, nanos) } /// Check whether or not the desired config is supported by the stream. /// /// Checks sample rate, data type, number of channels, and buffer size. fn check_config( driver: &sys::Driver, config: StreamConfig, sample_format: SampleFormat, num_asio_channels: u16, ) -> Result<(), BuildStreamError> { let StreamConfig { channels, sample_rate, buffer_size, } = config; // Validate buffer size if `Fixed` is specified. This is necessary because ASIO's // `create_buffers` only validates the upper bound (returns `InvalidBufferSize` if > max) but // does NOT validate the lower bound. Passing a buffer size below min would be accepted but // behavior is unspecified. 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(BuildStreamError::StreamConfigNotSupported); } } // Try and set the sample rate to what the user selected. 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(BuildStreamError::StreamConfigNotSupported); } } // unsigned formats are not supported by asio match sample_format { SampleFormat::I16 | SampleFormat::I24 | SampleFormat::I32 | SampleFormat::F32 => (), _ => return Err(BuildStreamError::StreamConfigNotSupported), } if channels > num_asio_channels { return Err(BuildStreamError::StreamConfigNotSupported); } Ok(()) } /// Cast a byte slice into a mutable slice of desired type. /// /// Safety: it's up to the caller to ensure that the input slice has valid bit representations. unsafe fn cast_slice_mut(v: &mut [u8]) -> &mut [T] { debug_assert!(v.len() % std::mem::size_of::() == 0); std::slice::from_raw_parts_mut(v.as_mut_ptr() as *mut T, v.len() / std::mem::size_of::()) } /// Helper function to convert from little endianness. fn from_le(t: T) -> T { T::from_le(t) } /// Helper function to convert from little endianness. fn from_be(t: T) -> T { T::from_be(t) } /// Shorthand for retrieving the asio buffer slice associated with a channel. /// /// The channel length is automatically inferred from the buffer size or some /// value can be passed to enforce a certain length (for odd sized sample formats) unsafe fn asio_channel_slice( asio_stream: &sys::AsioStream, buffer_index: usize, channel_index: usize, requested_channel_length: Option, ) -> &[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) } /// Shorthand for retrieving the asio buffer slice associated with a channel. /// /// The channel length is automatically inferred from the buffer size or some /// value can be passed to enforce a certain length (for odd sized sample formats) unsafe fn asio_channel_slice_mut( asio_stream: &mut sys::AsioStream, buffer_index: usize, channel_index: usize, requested_channel_length: Option, ) -> &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) -> BuildStreamError { match e { sys::LoadDriverError::LoadDriverFailed | sys::LoadDriverError::DriverAlreadyExists => { BuildStreamError::DeviceNotAvailable } sys::LoadDriverError::InitializationFailed(asio_err) => build_stream_err(asio_err), } } fn build_stream_err(e: sys::AsioError) -> BuildStreamError { match e { sys::AsioError::NoDrivers | sys::AsioError::HardwareMalfunction => { BuildStreamError::DeviceNotAvailable } sys::AsioError::InvalidInput | sys::AsioError::BadMode => BuildStreamError::InvalidArgument, err => { let description = format!("{}", err); BackendSpecificError { description }.into() } } } /// Convert i24 bytes to i32 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( 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: crate::SampleRate, hardware_latency_frames: usize, ) 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::( data_callback, interleaved, asio_info, sample_rate, format, hardware_latency_frames, ); // Size of samples in the ASIO buffer (has to be 3 in this case) 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; // Write interleaved samples to ASIO channels, one channel at a time. for ch_ix in 0..n_channels { // Take channel as u8 array ([u8; 3] packets to represent i24) 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::().1.fill(0); } // Fill in every channel from the interleaved vector 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)) { // Add samples from buffer if no silence was applied, otherwise just overwrite 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]; } } } } unsafe fn process_input_callback_i24( data_callback: &mut D, interleaved: &mut [u8], asio_stream: &sys::AsioStream, asio_info: &sys::CallbackInfo, sample_rate: crate::SampleRate, little_endian: bool, hardware_latency_frames: usize, ) where D: FnMut(&Data, &InputCallbackInfo) + Send + 'static, { let format = SampleFormat::I24; // 1. Write the ASIO channels to the CPAL buffer. 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::( 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(); } } // 2. Deliver the interleaved buffer to the callback. apply_input_callback_to_data::( data_callback, interleaved, asio_info, sample_rate, format, hardware_latency_frames, ); } /// Apply the output callback to the interleaved buffer. unsafe fn apply_output_callback_to_data( data_callback: &mut D, interleaved: &mut [A], asio_info: &sys::CallbackInfo, sample_rate: crate::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 callback = crate::StreamInstant::from_nanos_i128(asio_info.system_time as i128) .expect("`system_time` out of range of `StreamInstant` representation"); let delay = frames_to_duration(hardware_latency_frames, sample_rate); let playback = callback .add(delay) .expect("`playback` occurs beyond representation supported by `StreamInstant`"); let timestamp = crate::OutputStreamTimestamp { callback, playback }; let info = OutputCallbackInfo { timestamp }; data_callback(&mut data, &info); } /// Apply the input callback to the interleaved buffer. unsafe fn apply_input_callback_to_data( data_callback: &mut D, interleaved: &mut [A], asio_info: &sys::CallbackInfo, sample_rate: crate::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 callback = crate::StreamInstant::from_nanos_i128(asio_info.system_time as i128) .expect("`system_time` out of range of `StreamInstant` representation"); let delay = frames_to_duration(hardware_latency_frames, sample_rate); let capture = callback .sub(delay) .expect("`capture` occurs before origin of alsa `StreamInstant`"); let timestamp = crate::InputStreamTimestamp { callback, capture }; let info = InputCallbackInfo { timestamp }; data_callback(&data, &info); }