From 4dafb212fb742f277e6878a25cda1042cfd1d8a9 Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Sat, 29 Jun 2019 03:45:31 +1000 Subject: [PATCH] Refactor build_input_stream callback to, like recent output refactor --- src/host/asio/stream.rs | 543 +++++++++++++++------------------------- 1 file changed, 197 insertions(+), 346 deletions(-) diff --git a/src/host/asio/stream.rs b/src/host/asio/stream.rs index 61b1dfb..c356aa9 100644 --- a/src/host/asio/stream.rs +++ b/src/host/asio/stream.rs @@ -2,7 +2,6 @@ extern crate asio_sys as sys; extern crate num_traits; use self::num_traits::PrimInt; -use super::asio_utils as au; use super::Device; use std; use std::mem; @@ -28,6 +27,7 @@ trait Silence { /// Constraints on the interleaved sample buffer format required by the CPAL API. trait InterleavedSample: Clone + Copy + Silence { + fn unknown_type_input_buffer(&[Self]) -> UnknownTypeInputBuffer; fn unknown_type_output_buffer(&mut [Self]) -> UnknownTypeOutputBuffer; } @@ -40,7 +40,7 @@ pub struct EventLoop { asio_streams: Arc>, /// List of all CPAL streams cpal_streams: Arc>>>, - /// Total stream count + /// Total stream count. stream_count: AtomicUsize, /// The CPAL callback that the user gives to fill the buffers. callbacks: Arc>>, @@ -63,16 +63,6 @@ struct Stream { driver: Arc, } -struct Buffers { - interleaved: Vec, - non_interleaved: Vec, -} - -enum Endian { - Little, - Big, -} - // Used to keep track of whether or not the current current asio stream buffer requires // being silencing before summing audio. #[derive(Default)] @@ -217,328 +207,174 @@ impl EventLoop { device: &Device, format: &Format, ) -> Result { - unimplemented!() - // let Device { driver, .. } = device; - // let num_channels = format.channels.clone(); - // let stream_type = driver.data_type().map_err(build_stream_err)?; - // let stream_buffer_size = self.get_input_stream(&driver, format, device)?; - // let cpal_num_samples = stream_buffer_size * num_channels as usize; - // let count = self.stream_count.fetch_add(1, Ordering::SeqCst); - // let asio_streams = self.asio_streams.clone(); - // let cpal_streams = self.cpal_streams.clone(); - // let callbacks = self.callbacks.clone(); + let Device { driver, .. } = device; + let num_channels = format.channels.clone(); + let stream_type = driver.data_type().map_err(build_stream_err)?; + let stream_buffer_size = self.get_input_stream(&driver, format, device)?; + let cpal_num_samples = stream_buffer_size * num_channels as usize; + let count = self.stream_count.fetch_add(1, Ordering::SeqCst); + let asio_streams = self.asio_streams.clone(); + let cpal_streams = self.cpal_streams.clone(); + let callbacks = self.callbacks.clone(); - // let channel_len = cpal_num_samples / num_channels as usize; + // Create the buffer depending on the size of the data type. + let stream_id = StreamId(count); + let data_type = format.data_type; + let len_bytes = cpal_num_samples * data_type.sample_size(); + let mut interleaved = vec![0u8; len_bytes]; - // // Create buffers depending on data type - // // TODO the naming of cpal and channel is confusing. - // // change it to: - // // cpal -> interleaved - // // channels -> per_channel - // let mut buffers = match format.data_type { - // SampleFormat::I16 => Buffers { - // i16_buff: I16Buffer { - // cpal: vec![0 as i16; cpal_num_samples], - // channel: (0..num_channels) - // .map(|_| Vec::with_capacity(channel_len)) - // .collect(), - // }, - // f32_buff: F32Buffer::default(), - // }, - // SampleFormat::F32 => Buffers { - // i16_buff: I16Buffer::default(), - // f32_buff: F32Buffer { - // cpal: vec![0 as f32; cpal_num_samples], - // channel: (0..num_channels) - // .map(|_| Vec::with_capacity(channel_len)) - // .collect(), - // }, - // }, - // _ => unimplemented!(), - // }; + // Set the input callback. + // This is most performance critical part of the ASIO bindings. + sys::set_callback(move |buffer_index| unsafe { + // If not playing return early. + // TODO: Don't assume `count` is valid - we should search for the matching `StreamId`. + if let Some(s) = cpal_streams.lock().unwrap().get(count) { + if let Some(s) = s { + if !s.playing { + return; + } + } + } - // // Set the input callback. - // // This is most performance critical part of the ASIO bindings. - // sys::set_callback(move |index| unsafe { - // // if not playing return early - // { - // if let Some(s) = cpal_streams.lock().unwrap().get(count) { - // if let Some(s) = s { - // if !s.playing { - // return (); - // } - // } - // } - // } - // // Get the stream - // let stream_lock = asio_streams.lock().unwrap(); - // let ref asio_stream = match stream_lock.input { - // Some(ref asio_stream) => asio_stream, - // None => return (), - // }; + // Acquire the stream and callback. + let stream_lock = asio_streams.lock().unwrap(); + let ref asio_stream = match stream_lock.input { + Some(ref asio_stream) => asio_stream, + None => return, + }; + let mut callbacks = callbacks.lock().unwrap(); + let callback = match callbacks.as_mut() { + Some(callback) => callback, + None => return, + }; - // // Get the callback - // let mut callbacks = callbacks.lock().unwrap(); + /// 1. Write from the ASIO buffer to the interleaved CPAL buffer. + /// 2. Deliver the CPAL buffer to the user callback. + unsafe fn process_input_callback( + stream_id: StreamId, + callback: &mut (dyn FnMut(StreamId, StreamDataResult) + Send), + interleaved: &mut [u8], + asio_stream: &sys::AsioStream, + buffer_index: usize, + from_endianness: F, + to_cpal_sample: G, + ) + where + A: AsioSample, + B: InterleavedSample, + F: Fn(A) -> A, + G: Fn(A) -> B, + { + // 1. Write the ASIO channels to the CPAL buffer. + let interleaved: &mut [B] = cast_slice_mut(interleaved); + let n_channels = interleaved.len() / asio_stream.buffer_size as usize; + for ch_ix in 0..n_channels { + let asio_channel = asio_channel_slice::(asio_stream, buffer_index, ch_ix); + for (frame, s_asio) in interleaved.chunks_mut(n_channels).zip(asio_channel) { + frame[ch_ix] = to_cpal_sample(from_endianness(*s_asio)); + } + } - // // Theres only a single callback because theres only one event loop - // let callback = match callbacks.as_mut() { - // Some(callback) => callback, - // None => return (), - // }; + // 2. Deliver the interleaved buffer to the callback. + callback( + stream_id, + Ok(StreamData::Input { buffer: B::unknown_type_input_buffer(interleaved) }), + ); + } - // // Macro to convert sample from ASIO to CPAL type - // macro_rules! convert_sample { - // // floats types required different conversion - // (f32, - // f32, - // $SampleTypeIdent:ident, - // $Sample:expr - // ) => { - // *$Sample - // }; - // (f64, - // f64, - // $SampleTypeIdent:ident, - // $Sample:expr - // ) => { - // *$Sample - // }; - // (f64, - // f32, - // $SampleTypeIdent:ident, - // $Sample:expr - // ) => { - // *$Sample as f32 - // }; - // (f32, - // f64, - // $SampleTypeIdent:ident, - // $Sample:expr - // ) => { - // *$Sample as f64 - // }; - // ($AsioTypeIdent:ident, - // f32, - // $SampleTypeIdent:ident, - // $Sample:expr - // ) => { - // (*$Sample as f64 / ::std::$AsioTypeIdent::MAX as f64) as f32 - // }; - // ($AsioTypeIdent:ident, - // f64, - // $SampleTypeIdent:ident, - // $Sample:expr - // ) => { - // *$Sample as f64 / ::std::$AsioTypeIdent::MAX as f64 - // }; - // (f32, - // $SampleType:ty, - // $SampleTypeIdent:ident, - // $Sample:expr - // ) => { - // (*$Sample as f64 * ::std::$SampleTypeIdent::MAX as f64) as $SampleType - // }; - // (f64, - // $SampleType:ty, - // $SampleTypeIdent:ident, - // $Sample:expr - // ) => { - // (*$Sample as f64 * ::std::$SampleTypeIdent::MAX as f64) as $SampleType - // }; - // ($AsioTypeIdent:ident, - // $SampleType:ty, - // $SampleTypeIdent:ident, - // $Sample:expr - // ) => { - // (*$Sample as i64 * ::std::$SampleTypeIdent::MAX as i64 - // / ::std::$AsioTypeIdent::MAX as i64) as $SampleType - // }; - // }; - // // This creates gets the buffer and interleaves it. - // // It allows it to be done based on the sample type. - // macro_rules! try_callback { - // ($SampleFormat:ident, - // $SampleType:ty, - // $SampleTypeIdent:ident, - // $AsioType:ty, - // $AsioTypeIdent:ident, - // $Buffers:expr, - // $BuffersType:ty, - // $BuffersTypeIdent:ident, - // $Endianness:expr, - // $ConvertEndian:expr - // ) => { - // // For each channel write the asio buffer to - // // the cpal buffer + match (&stream_type, data_type) { + (&sys::AsioSampleType::ASIOSTInt16LSB, SampleFormat::I16) => { + process_input_callback::( + stream_id, + callback, + &mut interleaved, + asio_stream, + buffer_index as usize, + from_le, + std::convert::identity::, + ); + } + (&sys::AsioSampleType::ASIOSTInt16MSB, SampleFormat::I16) => { + process_input_callback::( + stream_id, + callback, + &mut interleaved, + asio_stream, + buffer_index as usize, + from_be, + std::convert::identity::, + ); + } - // for (i, channel) in $Buffers.channel.iter_mut().enumerate() { - // let buff_ptr = asio_stream.buffer_infos[i].buffers[index as usize] - // as *mut $AsioType; - // let asio_buffer: &'static [$AsioType] = std::slice::from_raw_parts( - // buff_ptr, - // asio_stream.buffer_size as usize, - // ); - // for asio_s in asio_buffer.iter() { - // channel.push($ConvertEndian( - // convert_sample!( - // $AsioTypeIdent, - // $SampleType, - // $SampleTypeIdent, - // asio_s - // ), - // $Endianness, - // )); - // } - // } + // TODO: Handle endianness conversion for floats? We currently use the `PrimInt` + // trait for the `to_le` and `to_be` methods, but this does not support floats. + (&sys::AsioSampleType::ASIOSTFloat32LSB, SampleFormat::F32) | + (&sys::AsioSampleType::ASIOSTFloat32MSB, SampleFormat::F32) => { + process_input_callback::( + stream_id, + callback, + &mut interleaved, + asio_stream, + buffer_index as usize, + std::convert::identity::, + std::convert::identity::, + ); + } - // // interleave all the channels - // { - // let $BuffersTypeIdent { - // cpal: ref mut c_buffer, - // channel: ref mut channels, - // } = $Buffers; - // au::interleave(&channels, c_buffer); - // // Clear the per channel buffers - // for c in channels.iter_mut() { - // c.clear(); - // } - // } + // TODO: Add support for the following sample formats to CPAL and simplify the + // `process_output_callback` function above by removing the unnecessary sample + // conversion function. + (&sys::AsioSampleType::ASIOSTInt32LSB, SampleFormat::I16) => { + process_input_callback::( + stream_id, + callback, + &mut interleaved, + asio_stream, + buffer_index as usize, + from_le, + |s| (s >> 16) as i16, + ); + } + (&sys::AsioSampleType::ASIOSTInt32MSB, SampleFormat::I16) => { + process_input_callback::( + stream_id, + callback, + &mut interleaved, + asio_stream, + buffer_index as usize, + from_be, + |s| (s >> 16) as i16, + ); + } + // TODO: Handle endianness conversion for floats? We currently use the `PrimInt` + // trait for the `to_le` and `to_be` methods, but this does not support floats. + (&sys::AsioSampleType::ASIOSTFloat64LSB, SampleFormat::F32) | + (&sys::AsioSampleType::ASIOSTFloat64MSB, SampleFormat::F32) => { + process_input_callback::( + stream_id, + callback, + &mut interleaved, + asio_stream, + buffer_index as usize, + std::convert::identity::, + |s| s as f32, + ); + } - // // Call the users callback with the buffer - // callback( - // StreamId(count), - // Ok(StreamData::Input { - // buffer: UnknownTypeInputBuffer::$SampleFormat(::InputBuffer { - // buffer: &$Buffers.cpal, - // }), - // }), - // ); - // }; - // }; - // // Call the right buffer handler depending on types - // match stream_type { - // sys::AsioSampleType::ASIOSTInt32LSB => { - // try_callback!( - // I16, - // i16, - // i16, - // i32, - // i32, - // buffers.i16_buff, - // I16Buffer, - // I16Buffer, - // Endian::Little, - // convert_endian_from - // ); - // } - // sys::AsioSampleType::ASIOSTInt16LSB => { - // try_callback!( - // I16, - // i16, - // i16, - // i16, - // i16, - // buffers.i16_buff, - // I16Buffer, - // I16Buffer, - // Endian::Little, - // convert_endian_from - // ); - // } - // sys::AsioSampleType::ASIOSTInt32MSB => { - // try_callback!( - // I16, - // i16, - // i16, - // i32, - // i32, - // buffers.i16_buff, - // I16Buffer, - // I16Buffer, - // Endian::Big, - // convert_endian_from - // ); - // } - // sys::AsioSampleType::ASIOSTInt16MSB => { - // try_callback!( - // I16, - // i16, - // i16, - // i16, - // i16, - // buffers.i16_buff, - // I16Buffer, - // I16Buffer, - // Endian::Big, - // convert_endian_from - // ); - // } - // sys::AsioSampleType::ASIOSTFloat32LSB => { - // try_callback!( - // F32, - // f32, - // f32, - // f32, - // f32, - // buffers.f32_buff, - // F32Buffer, - // F32Buffer, - // Endian::Little, - // |a, _| a - // ); - // } - // sys::AsioSampleType::ASIOSTFloat64LSB => { - // try_callback!( - // F32, - // f32, - // f32, - // f64, - // f64, - // buffers.f32_buff, - // F32Buffer, - // F32Buffer, - // Endian::Little, - // |a, _| a - // ); - // } - // sys::AsioSampleType::ASIOSTFloat32MSB => { - // try_callback!( - // F32, - // f32, - // f32, - // f32, - // f32, - // buffers.f32_buff, - // F32Buffer, - // F32Buffer, - // Endian::Big, - // |a, _| a - // ); - // } - // sys::AsioSampleType::ASIOSTFloat64MSB => { - // try_callback!( - // F32, - // f32, - // f32, - // f64, - // f64, - // buffers.f32_buff, - // F32Buffer, - // F32Buffer, - // Endian::Big, - // |a, _| a - // ); - // } - // _ => println!("unsupported format {:?}", stream_type), - // } - // }); - // // Create stream and set to paused - // self.cpal_streams - // .lock() - // .unwrap() - // .push(Some(Stream { driver: driver.clone(), playing: false })); + unsupported_format_pair => { + unreachable!("`build_input_stream` should have returned with unsupported \ + format {:?}", unsupported_format_pair) + } + } + }); - // Ok(StreamId(count)) + // Create stream and set to paused + self.cpal_streams + .lock() + .unwrap() + .push(Some(Stream { driver: driver.clone(), playing: false })); + + Ok(StreamId(count)) } /// Create the an output cpal stream. @@ -551,7 +387,6 @@ impl EventLoop { let num_channels = format.channels.clone(); let stream_type = driver.data_type().map_err(build_stream_err)?; let stream_buffer_size = self.get_output_stream(&driver, format, device)?; - let channel_len = stream_buffer_size as usize; let cpal_num_samples = stream_buffer_size * num_channels as usize; let count = self.stream_count.fetch_add(1, Ordering::SeqCst); let asio_streams = self.asio_streams.clone(); @@ -567,6 +402,7 @@ impl EventLoop { sys::set_callback(move |buffer_index| unsafe { // If not playing, return early. + // TODO: Don't assume `count` is valid - we should search for the matching `StreamId`. if let Some(s) = cpal_streams.lock().unwrap().get(count) { if let Some(s) = s { if !s.playing { @@ -790,6 +626,8 @@ impl EventLoop { /// Destroy the cpal stream based on the ID. pub fn destroy_stream(&self, stream_id: StreamId) { // TODO: Should we not also remove an ASIO stream here? + // Yes, and we should update the logic in the callbacks to search for the stream with + // the matching ID, rather than assuming the index associated with the ID is valid. let mut streams = self.cpal_streams.lock().unwrap(); streams.get_mut(stream_id.0).take(); } @@ -838,12 +676,20 @@ impl Silence for f64 { } impl InterleavedSample for i16 { + fn unknown_type_input_buffer(buffer: &[Self]) -> UnknownTypeInputBuffer { + UnknownTypeInputBuffer::I16(::InputBuffer { buffer }) + } + fn unknown_type_output_buffer(buffer: &mut [Self]) -> UnknownTypeOutputBuffer { UnknownTypeOutputBuffer::I16(::OutputBuffer { buffer }) } } impl InterleavedSample for f32 { + fn unknown_type_input_buffer(buffer: &[Self]) -> UnknownTypeInputBuffer { + UnknownTypeInputBuffer::F32(::InputBuffer { buffer }) + } + fn unknown_type_output_buffer(buffer: &mut [Self]) -> UnknownTypeOutputBuffer { UnknownTypeOutputBuffer::F32(::OutputBuffer { buffer }) } @@ -857,14 +703,6 @@ impl AsioSample for f32 {} impl AsioSample for f64 {} -/// Cast a byte slice into a (immutable) 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(v: &[u8]) -> &[T] { - debug_assert!(v.len() % std::mem::size_of::() == 0); - std::slice::from_raw_parts(v.as_ptr() as *const T, v.len() / std::mem::size_of::()) -} - /// 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. @@ -873,21 +711,42 @@ unsafe fn cast_slice_mut(v: &mut [u8]) -> &mut [T] { std::slice::from_raw_parts_mut(v.as_mut_ptr() as *mut T, v.len() / std::mem::size_of::()) } -/// Helper function to convert to system endianness +/// Helper function to convert to little endianness. fn to_le(t: T) -> T { t.to_le() } -/// Helper function to convert from system endianness +/// Helper function to convert to big endianness. fn to_be(t: T) -> T { t.to_be() } +/// 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. /// -/// Safety: it's up to the user to ensure the slice is not used beyond the lifetime of -/// the stream and that this function is not called multiple times for the same -/// channel. +/// Safety: it's up to the user to ensure that this function is not called multiple times for the +/// same channel. +unsafe fn asio_channel_slice( + asio_stream: &sys::AsioStream, + buffer_index: usize, + channel_index: usize, +) -> &[T] { + asio_channel_slice_mut(asio_stream, buffer_index, channel_index) +} + +/// Shorthand for retrieving the asio buffer slice associated with a channel. +/// +/// Safety: it's up to the user to ensure that this function is not called multiple times for the +/// same channel. unsafe fn asio_channel_slice_mut( asio_stream: &sys::AsioStream, buffer_index: usize, @@ -900,14 +759,6 @@ unsafe fn asio_channel_slice_mut( std::slice::from_raw_parts_mut(buff_ptr, asio_stream.buffer_size as usize) } -/// Helper function to convert from system endianness -fn convert_endian_from(sample: T, endian: Endian) -> T { - match endian { - Endian::Big => T::from_be(sample), - Endian::Little => T::from_le(sample), - } -} - fn build_stream_err(e: sys::AsioError) -> BuildStreamError { match e { sys::AsioError::NoDrivers |