diff --git a/asio-sys/src/bindings/mod.rs b/asio-sys/src/bindings/mod.rs index 0964685..86f4d6c 100644 --- a/asio-sys/src/bindings/mod.rs +++ b/asio-sys/src/bindings/mod.rs @@ -84,8 +84,15 @@ pub struct SampleRate { pub rate: u32, } +/// Information provided to the BufferCallback. +#[derive(Debug)] +pub struct CallbackInfo { + pub buffer_index: i32, + pub system_time: ai::ASIOTimeStamp, +} + /// Holds the pointer to the callbacks that come from cpal -struct BufferCallback(Box); +struct BufferCallback(Box); /// Input and Output streams. /// @@ -355,9 +362,9 @@ impl Asio { impl BufferCallback { /// Calls the inner callback. - fn run(&mut self, index: i32) { + fn run(&mut self, callback_info: &CallbackInfo) { let cb = &mut self.0; - cb(index); + cb(callback_info); } } @@ -610,7 +617,7 @@ impl Driver { /// Returns an ID uniquely associated with the given callback so that it may be removed later. pub fn add_callback(&self, callback: F) -> CallbackId where - F: 'static + FnMut(i32) + Send, + F: 'static + FnMut(&CallbackInfo) + Send, { let mut bc = BUFFER_CALLBACK.lock().unwrap(); let id = bc @@ -889,7 +896,7 @@ extern "C" fn asio_message( // Informs the driver whether the application is interested in time code info. If an // application does not need to know about time code, the driver has less work to do. // TODO: Provide an option for this? - 0 + 1 } _ => 0, // Unknown/unhandled message type. @@ -909,8 +916,15 @@ extern "C" fn buffer_switch_time_info( ) -> *mut ai::ASIOTime { // This lock is probably unavoidable, but locks in the audio stream are not great. let mut bcs = BUFFER_CALLBACK.lock().unwrap(); + let asio_time: &mut AsioTime = unsafe { + &mut *(time as *mut AsioTime) + }; + let callback_info = CallbackInfo { + buffer_index: double_buffer_index, + system_time: asio_time.time_info.system_time, + }; for &mut (_, ref mut bc) in bcs.iter_mut() { - bc.run(double_buffer_index); + bc.run(&callback_info); } time } @@ -952,6 +966,10 @@ fn check_type_sizes() { std::mem::size_of::(), std::mem::size_of::() ); + assert_eq!( + std::mem::size_of::(), + std::mem::size_of::(), + ); assert_eq!( std::mem::size_of::(), std::mem::size_of::() diff --git a/src/host/asio/stream.rs b/src/host/asio/stream.rs index 766e9d2..e8fbc2d 100644 --- a/src/host/asio/stream.rs +++ b/src/host/asio/stream.rs @@ -87,7 +87,8 @@ impl Device { // Set the input callback. // This is most performance critical part of the ASIO bindings. - let callback_id = self.driver.add_callback(move |buffer_index| unsafe { + let config = config.clone(); + let callback_id = self.driver.add_callback(move |callback_info| unsafe { // If not playing return early. if !playing.load(Ordering::SeqCst) { return; @@ -103,10 +104,11 @@ impl Device { /// 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( - callback: &mut D, + data_callback: &mut D, interleaved: &mut [u8], asio_stream: &sys::AsioStream, - buffer_index: usize, + asio_info: &sys::CallbackInfo, + sample_rate: crate::SampleRate, from_endianness: F, ) where A: AsioSample, @@ -116,7 +118,9 @@ impl Device { { // 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; + 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); for (frame, s_asio) in interleaved.chunks_mut(n_channels).zip(asio_channel) { @@ -128,8 +132,14 @@ impl Device { let data = interleaved.as_mut_ptr() as *mut (); let len = interleaved.len(); let data = Data::from_parts(data, len, B::FORMAT); - let info = InputCallbackInfo {}; - callback(&data, &info); + let callback = system_time_to_stream_instant(asio_info.system_time); + let delay = frames_to_duration(n_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); } match (&stream_type, sample_format) { @@ -138,7 +148,8 @@ impl Device { &mut data_callback, &mut interleaved, asio_stream, - buffer_index as usize, + callback_info, + config.sample_rate, from_le, ); } @@ -147,7 +158,8 @@ impl Device { &mut data_callback, &mut interleaved, asio_stream, - buffer_index as usize, + callback_info, + config.sample_rate, from_be, ); } @@ -160,7 +172,8 @@ impl Device { &mut data_callback, &mut interleaved, asio_stream, - buffer_index as usize, + callback_info, + config.sample_rate, std::convert::identity::, ); } @@ -173,7 +186,8 @@ impl Device { &mut data_callback, &mut interleaved, asio_stream, - buffer_index as usize, + callback_info, + config.sample_rate, from_le, ); } @@ -182,7 +196,8 @@ impl Device { &mut data_callback, &mut interleaved, asio_stream, - buffer_index as usize, + callback_info, + config.sample_rate, from_be, ); } @@ -194,7 +209,8 @@ impl Device { &mut data_callback, &mut interleaved, asio_stream, - buffer_index as usize, + callback_info, + config.sample_rate, std::convert::identity::, ); } @@ -254,7 +270,8 @@ impl Device { let playing = Arc::clone(&stream_playing); let asio_streams = self.asio_streams.clone(); - let callback_id = self.driver.add_callback(move |buffer_index| unsafe { + let config = config.clone(); + let callback_id = self.driver.add_callback(move |callback_info| unsafe { // If not playing, return early. if !playing.load(Ordering::SeqCst) { return; @@ -273,7 +290,7 @@ impl Device { // the current `buffer_index`. // // If not, we will silence it and set the opposite buffer half to unsilenced. - let silence = match buffer_index { + let silence = match callback_info.buffer_index { 0 if !silence_asio_buffer.first => { silence_asio_buffer.first = true; silence_asio_buffer.second = false; @@ -294,11 +311,12 @@ impl Device { /// 3. Finally, write the interleaved data to the non-interleaved ASIO buffer, /// performing endianness conversions as necessary. unsafe fn process_output_callback( - callback: &mut D, + data_callback: &mut D, interleaved: &mut [u8], silence_asio_buffer: bool, asio_stream: &sys::AsioStream, - buffer_index: usize, + asio_info: &sys::CallbackInfo, + sample_rate: crate::SampleRate, to_endianness: F, ) where A: Sample, @@ -311,11 +329,19 @@ impl Device { let data = interleaved.as_mut_ptr() as *mut (); let len = interleaved.len(); let mut data = Data::from_parts(data, len, A::FORMAT); - let info = OutputCallbackInfo {}; - callback(&mut data, &info); + let callback = system_time_to_stream_instant(asio_info.system_time); + let n_frames = asio_stream.buffer_size as usize; + let delay = frames_to_duration(n_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); // 2. Silence ASIO channels if necessary. - let n_channels = interleaved.len() / asio_stream.buffer_size as usize; + let n_channels = interleaved.len() / n_frames; + let buffer_index = asio_info.buffer_index as usize; if silence_asio_buffer { for ch_ix in 0..n_channels { let asio_channel = @@ -343,7 +369,8 @@ impl Device { &mut interleaved, silence, asio_stream, - buffer_index as usize, + callback_info, + config.sample_rate, to_le, ); } @@ -353,7 +380,8 @@ impl Device { &mut interleaved, silence, asio_stream, - buffer_index as usize, + callback_info, + config.sample_rate, to_be, ); } @@ -367,7 +395,8 @@ impl Device { &mut interleaved, silence, asio_stream, - buffer_index as usize, + callback_info, + config.sample_rate, std::convert::identity::, ); } @@ -381,7 +410,8 @@ impl Device { &mut interleaved, silence, asio_stream, - buffer_index as usize, + callback_info, + config.sample_rate, to_le, ); } @@ -391,7 +421,8 @@ impl Device { &mut interleaved, silence, asio_stream, - buffer_index as usize, + callback_info, + config.sample_rate, to_be, ); } @@ -404,7 +435,8 @@ impl Device { &mut interleaved, silence, asio_stream, - buffer_index as usize, + callback_info, + config.sample_rate, std::convert::identity::, ); } @@ -578,6 +610,29 @@ impl AsioSample for f64 { } } +fn asio_ns_to_double(val: sys::bindings::asio_import::ASIOTimeStamp) -> f64 { + let two_raised_to_32 = 4294967296.0; + val.lo as f64 + val.hi as f64 * two_raised_to_32 +} + +/// Asio retrieves system time via `timeGetTime` which returns the time in milliseconds. +fn system_time_to_stream_instant( + system_time: sys::bindings::asio_import::ASIOTimeStamp, +) -> crate::StreamInstant { + let systime_ns = asio_ns_to_double(system_time); + let secs = systime_ns as i64 / 1_000_000_000; + let nanos = (systime_ns as i64 - secs * 1_000_000_000) as u32; + crate::StreamInstant::new(secs, nanos) +} + +/// Convert the given duration in frames at the given sample rate to a `std::time::Duration`. +fn frames_to_duration(frames: usize, rate: crate::SampleRate) -> std::time::Duration { + let secsf = frames as f64 / rate.0 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 and then finally the number of channels.