From 225b460381f2108fa6d1b162c8190a864a45a71e Mon Sep 17 00:00:00 2001 From: Dr George Atkinson Date: Mon, 17 Mar 2025 14:30:33 +0000 Subject: [PATCH 1/6] Update WASAPI backend for new API --- src/duplex.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/duplex.rs b/src/duplex.rs index 6cd4e9b..4edfa9b 100644 --- a/src/duplex.rs +++ b/src/duplex.rs @@ -2,7 +2,8 @@ //! //! This module includes a proxy for gathering an input audio stream, and optionally process it to resample it to the //! output sample rate. -use crate::device::{AudioInputDevice, AudioOutputDevice}; +use crate::channel_map::Bitset; +use crate::device::{AudioDevice, AudioInputDevice, AudioOutputDevice}; use crate::stream::{ AudioCallbackContext, AudioInputCallback, AudioOutputCallback, AudioStreamHandle, StreamConfig, }; From 14dcc52670dd458153cd0e5ad01f2a816fd453a2 Mon Sep 17 00:00:00 2001 From: Dr George Atkinson Date: Mon, 17 Mar 2025 15:09:22 +0000 Subject: [PATCH 2/6] feat: Add ASIO backend --- Cargo.toml | 16 +- build.rs | 4 +- examples/enumerate_asio.rs | 14 + src/backends/asio/device.rs | 484 +++++++++++++++++++++++++++++++++++ src/backends/asio/driver.rs | 83 ++++++ src/backends/asio/error.rs | 16 ++ src/backends/asio/mod.rs | 7 + src/backends/asio/prelude.rs | 3 + src/backends/asio/stream.rs | 32 +++ src/backends/mod.rs | 9 +- 10 files changed, 661 insertions(+), 7 deletions(-) create mode 100644 examples/enumerate_asio.rs create mode 100644 src/backends/asio/device.rs create mode 100644 src/backends/asio/driver.rs create mode 100644 src/backends/asio/error.rs create mode 100644 src/backends/asio/mod.rs create mode 100644 src/backends/asio/prelude.rs create mode 100644 src/backends/asio/stream.rs diff --git a/Cargo.toml b/Cargo.toml index 394bc0b..8323ccb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,8 +5,11 @@ edition = "2021" rust-version = "1.80" license = "MIT" +[features] +default = ["asio"] +asio = ["asio-sys", "num-traits"] + [dependencies] -bitflags = "2.9.0" duplicate = "2.0.0" fixed-resample = "0.8.0" libspa = { version = "0.8.0", optional = true } @@ -31,7 +34,7 @@ pipewire = ["dep:pipewire", "dep:libspa", "dep:libspa-sys", "dep:zerocopy"] [target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd"))'.dependencies] alsa = "0.9.1" -libc = "0.2.172" +libc = "0.2.171" nix = "0.29.0" pipewire = { version = "0.8.0", optional = true } @@ -52,6 +55,8 @@ windows = { version = "0.61.1", features = [ "Win32_Media_Multimedia", "Win32_UI_Shell_PropertiesSystem" ]} +asio-sys ={ version = "0.2.2", optional = true } +num-traits = {version = "0.2.19", optional = true } [[example]] name = "enumerate_alsa" @@ -64,8 +69,13 @@ path = "examples/enumerate_coreaudio.rs" [[example]] name = "enumerate_wasapi" path = "examples/enumerate_wasapi.rs" +required-features = ["pipewire"] [[example]] name = "enumerate_pipewire" path = "examples/enumerate_pipewire.rs" -required-features = ["pipewire"] \ No newline at end of file + +[[example]] +name = "enumerate_asio" +path = "examples/enumerate_asio.rs" +features = ["asio"] diff --git a/build.rs b/build.rs index f843958..1a02ed0 100644 --- a/build.rs +++ b/build.rs @@ -7,8 +7,8 @@ fn main() { os_alsa: { any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd") }, os_coreaudio: { any (target_os = "macos", target_os = "ios") }, - os_pipewire: { any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd") }, os_wasapi: { target_os = "windows" }, - unsupported: { not(any(os_alsa, os_coreaudio, os_wasapi)) } + os_asio: { all(target_os = "windows", feature = "asio") }, + unsupported: { not(any(os_alsa, os_coreaudio, os_wasapi))} } } diff --git a/examples/enumerate_asio.rs b/examples/enumerate_asio.rs new file mode 100644 index 0000000..63d083b --- /dev/null +++ b/examples/enumerate_asio.rs @@ -0,0 +1,14 @@ +mod util; + +#[cfg(os_asio)] +fn main() -> Result<(), Box> { + use crate::util::enumerate::enumerate_devices; + use interflow::backends::asio::AsioDriver; + enumerate_devices(AsioDriver::new()?)?; + Ok(()) +} + +#[cfg(not(os_asio))] +fn main() { + println!("ASIO driver is not available on this platform"); +} diff --git a/src/backends/asio/device.rs b/src/backends/asio/device.rs new file mode 100644 index 0000000..b7e23ab --- /dev/null +++ b/src/backends/asio/device.rs @@ -0,0 +1,484 @@ + +use std::{borrow::Cow, sync::{atomic::AtomicBool, Arc, Mutex}}; + + +use asio_sys as asio; +use num_traits::PrimInt; + +use crate::{audio_buffer::{AudioMut, AudioRef}, device::{AudioDevice, AudioDuplexDevice, AudioInputDevice, AudioOutputDevice, Channel, DeviceType}, duplex::AudioDuplexCallback, stream::{AudioCallbackContext, AudioInput, AudioInputCallback, AudioOutput, AudioOutputCallback, AudioStreamHandle, StreamConfig}, timestamp::{self, Timestamp}, SendEverywhereButOnWeb}; + +use super::{error::AsioError, stream::AsioStream}; + + + +#[derive(Clone)] +pub struct AsioDevice { + driver: Arc, + device_type: DeviceType, + asio_streams: Arc>, +} + +impl AsioDevice { + pub fn new(driver: Arc) -> Result { + let is_input = driver.channels()?.ins > 0; + let is_output = driver.channels()?.outs > 0; + let device_type = match (is_input, is_output) { + (true, true) => DeviceType::Duplex, + (true, false) => DeviceType::Input, + (false, true) => DeviceType::Output, + // todo + (false, false) => return Err(AsioError::BackendError(asio::AsioError::NoDrivers)), + }; + let asio_streams = Arc::new(Mutex::new(asio::AsioStreams { + input: None, + output: None, + })); + Ok(AsioDevice { driver, device_type, asio_streams }) + } + + fn create_input_stream(&self, stream_config: StreamConfig) -> Result { + + let num_channels = stream_config.channels as usize; + let buffer_size = match stream_config.buffer_size_range { + (Some(min), Some(max)) if min == max => { + Some(min as i32) + } + + + _ => None + }; + + self.driver.set_sample_rate(stream_config.samplerate)?; + + let mut streams = self.asio_streams.lock().unwrap(); + + match streams.input { + Some(ref input) => Ok(input.buffer_size as usize), + None => { + let output = streams.output.take(); + self.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(|e| { + AsioError::BackendError(e) + }) + } + } + + } + + fn create_output_stream(&self, stream_config: StreamConfig) -> Result { + let num_channels = stream_config.channels as usize; + let buffer_size = match stream_config.buffer_size_range { + (Some(min), Some(max)) if min == max => { + Some(min as i32) + } + _ => None + }; + + self.driver.set_sample_rate(stream_config.samplerate)?; + + let mut streams = self.asio_streams.lock().unwrap(); + + match streams.output { + Some(ref output) => Ok(output.buffer_size as usize), + None => { + let input = streams.input.take(); + self.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(|e| { + AsioError::BackendError(e) + }) + } + } + } +} + +impl AudioDevice for AsioDevice { + type Error = AsioError; + + fn name(&self) -> Cow { + Cow::Borrowed(self.driver.name()) + } + + fn device_type(&self) -> DeviceType { + self.device_type + } + + fn is_config_supported(&self, config: &StreamConfig) -> bool { + todo!() + } + + fn enumerate_configurations(&self) -> Option> { + None::<[StreamConfig; 0]> + } +} + +impl AudioInputDevice for AsioDevice { + fn input_channel_map(&self) -> impl Iterator { + [].into_iter() + } + + type StreamHandle = AsioStream; + + fn default_input_config(&self) -> Result { + let channels = self.driver.channels()?.ins as u32; + let samplerate = self.driver.sample_rate()?; + let (min_buffer_size, max_buffer_size) = self.driver.buffersize_range()?; + Ok(StreamConfig { + channels, + samplerate, + buffer_size_range: (Some(min_buffer_size as usize), Some(max_buffer_size as usize)), + exclusive: false, + }) + } + + fn create_input_stream( + &self, + stream_config: StreamConfig, + mut callback: Callback, + ) -> Result, Self::Error> { + let input_data_type = self.driver.input_data_type()?; + + let num_channels = stream_config.channels as usize; + + let buffer_size = self.create_input_stream(stream_config)?; + let num_samples = buffer_size * num_channels; + + let mut buffer = vec![0.0f32; num_samples]; + + let asio_streams = self.asio_streams.clone(); + + let stream_playing = Arc::new(AtomicBool::new(false)); + let playing = Arc::clone(&stream_playing); + + let callback_id = self.driver.add_callback(move |callback_info| unsafe { + let streams = asio_streams.lock().unwrap(); + let asio_stream = match &streams.input { + Some(asio_stream) => asio_stream, + None => return + }; + + + let buffer_index = callback_info.buffer_index as usize; + + unsafe fn create_buffer<'a, SampleType: Copy>( + asio_stream: &asio::AsioStream, + buffer: &'a mut [f32], + buffer_index: usize, + num_channels: usize, + from_endian: impl Fn(SampleType) -> SampleType, + to_f32: impl Fn(SampleType) -> f32, + ) -> AudioRef<'a, f32> { + + + for channel_index in 0..num_channels { + let channel_buffer = asio_channel_slice::(asio_stream, buffer_index, channel_index); + for (frame, asio_sample) in buffer.chunks_mut(num_channels).zip(channel_buffer) { + frame[channel_index] = to_f32(from_endian(*asio_sample)); + } + } + AudioRef::from_interleaved(buffer, num_channels).unwrap() + } + + + let audio_buffer = match &input_data_type { + asio::AsioSampleType::ASIOSTInt16MSB => { + create_buffer(&asio_stream, &mut buffer, buffer_index, num_channels, from_be, i16_to_f32) + }, + asio::AsioSampleType::ASIOSTInt16LSB => { + create_buffer(&asio_stream, &mut buffer, buffer_index, num_channels, from_le, i16_to_f32) + }, + asio::AsioSampleType::ASIOSTInt24MSB => { + create_buffer(&asio_stream, &mut buffer, buffer_index, num_channels, from_be, i24_to_f32) + }, + asio::AsioSampleType::ASIOSTInt24LSB => { + create_buffer(&asio_stream, &mut buffer, buffer_index, num_channels, from_le, i24_to_f32) + }, + asio::AsioSampleType::ASIOSTInt32MSB => { + create_buffer(&asio_stream, &mut buffer, buffer_index, num_channels, from_be, i32_to_f32) + }, + asio::AsioSampleType::ASIOSTInt32LSB => { + create_buffer(&asio_stream, &mut buffer, buffer_index, num_channels, from_le, i32_to_f32) + }, + asio::AsioSampleType::ASIOSTFloat32MSB => { + create_buffer::(&asio_stream, &mut buffer, buffer_index, num_channels, from_be, f32::from_bits) + }, + asio::AsioSampleType::ASIOSTFloat32LSB => { + create_buffer::(&asio_stream, &mut buffer, buffer_index, num_channels, from_le, f32::from_bits) + }, + // asio::AsioSampleType::ASIOSTFloat64MSB => todo!(), + // asio::AsioSampleType::ASIOSTFloat64LSB => todo!(), + unsupported_format => unreachable!( + "returned with unsupported format {:?}", + unsupported_format + ), + }; + + let timestamp = Timestamp { + samplerate: 41000.0, + counter: 0, + }; + let context = AudioCallbackContext { + stream_config, + timestamp, + }; + let input = AudioInput { + timestamp, + buffer: audio_buffer, + }; + + callback.on_input_data(context, input); + }); + + Ok(AsioStream::new(playing, callback_id)) + } +} + +impl AudioOutputDevice for AsioDevice { + fn output_channel_map(&self) -> impl Iterator { + [].into_iter() + } + + type StreamHandle = AsioStream; + + fn default_output_config(&self) -> Result { + let channels = self.driver.channels()?.outs as u32; + let samplerate = self.driver.sample_rate()?; + let (min_buffer_size, max_buffer_size) = self.driver.buffersize_range()?; + Ok(StreamConfig { + channels, + samplerate, + buffer_size_range: (Some(min_buffer_size as usize), Some(max_buffer_size as usize)), + exclusive: false, + }) + } + + fn create_output_stream( + &self, + stream_config: StreamConfig, + mut callback: Callback, + ) -> Result, Self::Error> { + let output_data_type = self.driver.output_data_type()?; + + let num_channels = stream_config.channels as usize; + + let buffer_size = self.create_output_stream(stream_config)?; + let num_samples = buffer_size * num_channels; + + let mut buffer = vec![0.0f32; num_samples]; + + let asio_streams = self.asio_streams.clone(); + + let stream_playing = Arc::new(AtomicBool::new(false)); + let playing = Arc::clone(&stream_playing); + + let callback_id = self.driver.add_callback(move |callback_info| unsafe { + let streams = asio_streams.lock().unwrap(); + let asio_stream = match &streams.output { + Some(asio_stream) => asio_stream, + None => return + }; + + let buffer_index = callback_info.buffer_index as usize; + + unsafe fn create_buffer<'a, SampleType: Copy>( + asio_stream: &asio::AsioStream, + buffer: &'a mut [f32], + buffer_index: usize, + num_channels: usize, + to_endian: impl Fn(SampleType) -> SampleType, + from_f32: impl Fn(f32) -> SampleType, + ) -> AudioMut<'a, f32> { + + + for channel_index in 0..num_channels { + let channel_buffer = asio_channel_slice_mut::(asio_stream, buffer_index, channel_index); + for (frame, asio_sample) in buffer.chunks_mut(num_channels).zip(channel_buffer) { + *asio_sample = to_endian(from_f32(frame[channel_index])); + } + } + AudioMut::from_interleaved_mut(buffer, num_channels).unwrap() + } + + + let audio_buffer = match &output_data_type { + asio::AsioSampleType::ASIOSTInt16MSB => { + create_buffer(&asio_stream, &mut buffer, buffer_index, num_channels, to_be, f32_to_i16) + }, + asio::AsioSampleType::ASIOSTInt16LSB => { + create_buffer(&asio_stream, &mut buffer, buffer_index, num_channels, to_le, f32_to_i16) + }, + asio::AsioSampleType::ASIOSTInt24MSB => { + create_buffer(&asio_stream, &mut buffer, buffer_index, num_channels, to_be, f32_to_i24) + }, + asio::AsioSampleType::ASIOSTInt24LSB => { + create_buffer(&asio_stream, &mut buffer, buffer_index, num_channels, to_le, f32_to_i24) + }, + asio::AsioSampleType::ASIOSTInt32MSB => { + create_buffer(&asio_stream, &mut buffer, buffer_index, num_channels, to_be, f32_to_i32) + }, + asio::AsioSampleType::ASIOSTInt32LSB => { + create_buffer(&asio_stream, &mut buffer, buffer_index, num_channels, to_le, f32_to_i32) + }, + asio::AsioSampleType::ASIOSTFloat32MSB => { + create_buffer::(&asio_stream, &mut buffer, buffer_index, num_channels, to_be, f32::to_bits) + }, + asio::AsioSampleType::ASIOSTFloat32LSB => { + create_buffer::(&asio_stream, &mut buffer, buffer_index, num_channels, to_le, f32::to_bits) + }, + // asio::AsioSampleType::ASIOSTFloat64MSB => todo!(), + // asio::AsioSampleType::ASIOSTFloat64LSB => todo!(), + unsupported_format => unreachable!( + "returned with unsupported format {:?}", + unsupported_format + ), + }; + + let timestamp = Timestamp { + samplerate: 41000.0, + counter: 0, + }; + let context = AudioCallbackContext { + stream_config, + timestamp, + }; + let output = AudioOutput { + timestamp, + buffer: audio_buffer, + }; + + callback.on_output_data(context, output); + }); + + self.driver.start()?; + + Ok(AsioStream::new(playing, callback_id)) + } +} + +impl AudioDuplexDevice for AsioDevice { + fn default_duplex_config(&self) -> Result { + let channels = self.driver.channels()?.ins as u32; + let samplerate = self.driver.sample_rate()?; + let (min_buffer_size, max_buffer_size) = self.driver.buffersize_range()?; + Ok(StreamConfig { + channels, + samplerate, + buffer_size_range: (Some(min_buffer_size as usize), Some(max_buffer_size as usize)), + exclusive: false, + }) + } + + fn create_duplex_stream( + &self, + stream_config: StreamConfig, + callback: Callback, + ) -> Result, Self::Error> { + todo!() + } + + type StreamHandle = AsioStream; +} + +// HELPERS + +unsafe fn asio_channel_slice( + asio_stream: &asio::AsioStream, + buffer_index: usize, + channel_index: usize, +) -> &[T] { + let buffer_size = asio_stream.buffer_size as usize; + let buff_ptr: *const T = + asio_stream.buffer_infos[channel_index].buffers[buffer_index as usize] as *const _; + std::slice::from_raw_parts(buff_ptr, buffer_size) +} + +unsafe fn asio_channel_slice_mut( + asio_stream: &asio::AsioStream, + buffer_index: usize, + channel_index: usize, +) -> &mut [T] { + let buffer_size = asio_stream.buffer_size as usize; + let buff_ptr: *mut T = + asio_stream.buffer_infos[channel_index].buffers[buffer_index as usize] as *mut _; + std::slice::from_raw_parts_mut(buff_ptr, buffer_size) +} + +/// Helper function to convert from little endianness. +fn from_le(t: T) -> T { + T::from_le(t) +} + +/// Helper function to convert from big endianness. +fn from_be(t: T) -> T { + T::from_be(t) +} + +/// Helper function to convert to little endianness. +fn to_le(t: T) -> T { + t.to_le() +} + +/// Helper function to convert to big endianness. +fn to_be(t: T) -> T { + t.to_be() +} + +/// Helper function to convert from i16 to f32. +fn i16_to_f32(i: i16) -> f32 { + i as f32 / i16::MAX as f32 +} + +/// Helper function to convert from i32 to f32. +fn i32_to_f32(i: i32) -> f32 { + i as f32 / i32::MAX as f32 +} + +/// Helper function to convert from i24 to f32. +fn i24_to_f32(i: i32) -> f32 { + i as f32 / 0x7FFFFF as f32 +} + +/// Helper function to convert from f32 to i16. +fn f32_to_i16(f: f32) -> i16 { + (f * i16::MAX as f32) as i16 +} + +/// Helper function to convert from f32 to i32. +fn f32_to_i32(f: f32) -> i32 { + (f * i32::MAX as f32) as i32 +} + +/// Helper function to convert from f32 to i24. +fn f32_to_i24(f: f32) -> i32 { + (f * 0x7FFFFF as f32) as i32 +} + + +// 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 +// } + +// fn system_time_to_timestamp(system_time: asio::AsioTime) -> timestamp::Timestamp { +// 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; + +// } \ No newline at end of file diff --git a/src/backends/asio/driver.rs b/src/backends/asio/driver.rs new file mode 100644 index 0000000..f1fc820 --- /dev/null +++ b/src/backends/asio/driver.rs @@ -0,0 +1,83 @@ +use std::{borrow::Cow, sync::Arc}; + +use asio_sys as asio; + + +use crate::{device::{AudioDevice, DeviceType}, driver::AudioDriver}; + +use super::{device::AsioDevice, error::AsioError}; + +/// The ASIO driver. +#[derive(Debug, Clone, Default)] +pub struct AsioDriver { + asio: Arc, +} + +impl AsioDriver { + pub fn new() -> Result { + let asio = Arc::new(asio::Asio::new()); + Ok(AsioDriver { asio }) + } +} + +impl AudioDriver for AsioDriver { + type Error = AsioError; + type Device = AsioDevice; + + const DISPLAY_NAME: &'static str = "ASIO"; + + fn version(&self) -> Result, Self::Error> { + Ok(Cow::Borrowed("unknown")) + } + + fn default_device(&self, device_type: DeviceType) -> Result, Self::Error> { + let iter = AsioDeviceList::new(self.asio.clone())?; + + let dd = iter.filter(|device| { + match (device.device_type(), device_type) { + (DeviceType::Input | DeviceType::Duplex, DeviceType::Input) => true, + (DeviceType::Output | DeviceType::Duplex, DeviceType::Output) => true, + (a, b) => a == b, + + } + }).next(); + Ok(dd) + } + + fn list_devices(&self) -> Result, Self::Error> { + AsioDeviceList::new(self.asio.clone()) + } +} + +pub struct AsioDeviceList { + asio: Arc, + drivers: std::vec::IntoIter, +} + +impl AsioDeviceList { + pub fn new(asio: Arc) -> Result { + let drivers = asio.driver_names().into_iter(); + Ok(AsioDeviceList { asio, drivers }) + } +} + +impl Iterator for AsioDeviceList { + type Item = AsioDevice; + + fn next(&mut self) -> Option { + loop { + match self.drivers.next() { + Some(name) => match self.asio.load_driver(&name) { + Ok(driver) => { + let driver = Arc::new(driver); + return AsioDevice::new(driver).ok(); + } + Err(_) => continue, + }, + None => return None, + } + } + } +} + + diff --git a/src/backends/asio/error.rs b/src/backends/asio/error.rs new file mode 100644 index 0000000..5f4b047 --- /dev/null +++ b/src/backends/asio/error.rs @@ -0,0 +1,16 @@ +use thiserror::Error; +use asio_sys::AsioError as AsioSysError; + +/// Type of errors from the ASIO backend. +#[derive(Debug, Error)] +#[error("ASIO error: ")] +pub enum AsioError { + /// Error originating from ASIO. + #[error("{0}")] + BackendError(#[from] AsioSysError), + /// Requested WASAPI device configuration is not available + #[error("Configuration not available")] + ConfigurationNotAvailable, + #[error("Device unavailable")] + DeviceUnavailable, +} diff --git a/src/backends/asio/mod.rs b/src/backends/asio/mod.rs new file mode 100644 index 0000000..b40cf71 --- /dev/null +++ b/src/backends/asio/mod.rs @@ -0,0 +1,7 @@ + +mod device; +pub(crate) mod driver; +mod error; +mod stream; +pub mod prelude; +pub use prelude::*; diff --git a/src/backends/asio/prelude.rs b/src/backends/asio/prelude.rs new file mode 100644 index 0000000..e840c46 --- /dev/null +++ b/src/backends/asio/prelude.rs @@ -0,0 +1,3 @@ +pub use super::{ + device::AsioDevice, driver::AsioDriver, +}; \ No newline at end of file diff --git a/src/backends/asio/stream.rs b/src/backends/asio/stream.rs new file mode 100644 index 0000000..22daa43 --- /dev/null +++ b/src/backends/asio/stream.rs @@ -0,0 +1,32 @@ +use std::{marker::PhantomData, sync::{atomic::AtomicBool, Arc}}; + +use asio_sys::{self as asio, CallbackId}; + +use crate::stream::AudioStreamHandle; + +use super::error::AsioError; + +pub struct AsioStream { + playing: Arc, + // driver: Arc, + // streams: Arc, + callback_id: asio::CallbackId, + callback: PhantomData, +} + +impl AudioStreamHandle for AsioStream { + + type Error = AsioError; + + fn eject(self) -> Result { + todo!() + } +} + +impl AsioStream { + pub fn new(playing: Arc, callback_id: CallbackId) -> Self { + AsioStream { playing, callback_id, callback: PhantomData } + } + + +} \ No newline at end of file diff --git a/src/backends/mod.rs b/src/backends/mod.rs index 009a8c1..4429ef0 100644 --- a/src/backends/mod.rs +++ b/src/backends/mod.rs @@ -24,6 +24,9 @@ pub mod wasapi; #[cfg(all(os_pipewire, feature = "pipewire"))] pub mod pipewire; +#[cfg(os_asio)] +pub mod asio; + /// Returns the default driver. /// /// "Default" here means that it is a supported driver that is available on the platform. @@ -110,8 +113,10 @@ pub fn default_output_device() -> impl AudioOutputDevice { return default_output_device_from(&alsa::AlsaDriver); #[cfg(os_coreaudio)] return default_output_device_from(&coreaudio::CoreAudioDriver); - #[cfg(os_wasapi)] - return default_output_device_from(&wasapi::WasapiDriver); + // #[cfg(os_wasapi)] + // return default_output_device_from(&wasapi::WasapiDriver); + #[cfg(os_asio)] + return default_output_device_from(&asio::AsioDriver::new().unwrap()); } /// Default duplex device from the default driver of this platform. From 832b493d3e961a0541835c5f491c9a9c159153b6 Mon Sep 17 00:00:00 2001 From: Dr George Atkinson Date: Thu, 20 Mar 2025 15:13:26 +0000 Subject: [PATCH 3/6] Implement duplex stream --- src/backends/asio/device.rs | 570 ++++++++++++++++++++++++----------- src/backends/asio/driver.rs | 20 +- src/backends/asio/error.rs | 4 +- src/backends/asio/mod.rs | 9 +- src/backends/asio/prelude.rs | 4 +- src/backends/asio/stream.rs | 30 +- src/backends/mod.rs | 2 +- src/prelude.rs | 2 + 8 files changed, 431 insertions(+), 210 deletions(-) diff --git a/src/backends/asio/device.rs b/src/backends/asio/device.rs index b7e23ab..e517fec 100644 --- a/src/backends/asio/device.rs +++ b/src/backends/asio/device.rs @@ -1,16 +1,28 @@ - -use std::{borrow::Cow, sync::{atomic::AtomicBool, Arc, Mutex}}; - +use std::{ + borrow::Cow, + sync::{Arc, Mutex}, +}; use asio_sys as asio; use num_traits::PrimInt; -use crate::{audio_buffer::{AudioMut, AudioRef}, device::{AudioDevice, AudioDuplexDevice, AudioInputDevice, AudioOutputDevice, Channel, DeviceType}, duplex::AudioDuplexCallback, stream::{AudioCallbackContext, AudioInput, AudioInputCallback, AudioOutput, AudioOutputCallback, AudioStreamHandle, StreamConfig}, timestamp::{self, Timestamp}, SendEverywhereButOnWeb}; +use crate::{ + audio_buffer::{AudioMut, AudioRef}, + device::{ + AudioDevice, AudioDuplexDevice, AudioInputDevice, AudioOutputDevice, Channel, DeviceType, + }, + duplex::AudioDuplexCallback, + stream::{ + AudioCallbackContext, AudioInput, AudioInputCallback, AudioOutput, AudioOutputCallback, + StreamConfig, + }, + timestamp::{self, Timestamp}, + SendEverywhereButOnWeb, +}; use super::{error::AsioError, stream::AsioStream}; - - +/// The ASIO device. #[derive(Clone)] pub struct AsioDevice { driver: Arc, @@ -19,6 +31,7 @@ pub struct AsioDevice { } impl AsioDevice { + /// Create a new ASIO device. pub fn new(driver: Arc) -> Result { let is_input = driver.channels()?.ins > 0; let is_output = driver.channels()?.outs > 0; @@ -33,19 +46,20 @@ impl AsioDevice { input: None, output: None, })); - Ok(AsioDevice { driver, device_type, asio_streams }) + Ok(AsioDevice { + driver, + device_type, + asio_streams, + }) } + /// Create an input stream with the given configuration. fn create_input_stream(&self, stream_config: StreamConfig) -> Result { - let num_channels = stream_config.channels as usize; let buffer_size = match stream_config.buffer_size_range { - (Some(min), Some(max)) if min == max => { - Some(min as i32) - } - + (Some(min), Some(max)) if min == max => Some(min as i32), - _ => None + _ => None, }; self.driver.set_sample_rate(stream_config.samplerate)?; @@ -66,21 +80,17 @@ impl AsioDevice { *streams = new_streams; bs }) - .map_err(|e| { - AsioError::BackendError(e) - }) + .map_err(|e| AsioError::BackendError(e)) } } - } + /// Creates an output stream with the given configuration. fn create_output_stream(&self, stream_config: StreamConfig) -> Result { let num_channels = stream_config.channels as usize; let buffer_size = match stream_config.buffer_size_range { - (Some(min), Some(max)) if min == max => { - Some(min as i32) - } - _ => None + (Some(min), Some(max)) if min == max => Some(min as i32), + _ => None, }; self.driver.set_sample_rate(stream_config.samplerate)?; @@ -101,9 +111,7 @@ impl AsioDevice { *streams = new_streams; bs }) - .map_err(|e| { - AsioError::BackendError(e) - }) + .map_err(|e| AsioError::BackendError(e)) } } } @@ -121,7 +129,26 @@ impl AudioDevice for AsioDevice { } fn is_config_supported(&self, config: &StreamConfig) -> bool { - todo!() + let sample_rate = config.samplerate; + self.driver.can_sample_rate(sample_rate).unwrap_or(false) + && self.driver.channels().map_or(false, |channels| { + let num_channels = config.channels as i32; + match self.device_type { + DeviceType::Input => channels.ins >= num_channels, + DeviceType::Output => channels.outs >= num_channels, + DeviceType::Duplex => { + channels.ins >= num_channels && channels.outs >= num_channels + } + } + }) + && self.driver.buffersize_range().map_or(false, |(min, max)| { + match config.buffer_size_range { + (Some(min_config), Some(max_config)) => { + min_config >= min as usize && max_config <= max as usize + } + _ => false, + } + }) } fn enumerate_configurations(&self) -> Option> { @@ -130,12 +157,12 @@ impl AudioDevice for AsioDevice { } impl AudioInputDevice for AsioDevice { + type StreamHandle = AsioStream; + fn input_channel_map(&self) -> impl Iterator { [].into_iter() } - type StreamHandle = AsioStream; - fn default_input_config(&self) -> Result { let channels = self.driver.channels()?.ins as u32; let samplerate = self.driver.sample_rate()?; @@ -143,7 +170,10 @@ impl AudioInputDevice for AsioDevice { Ok(StreamConfig { channels, samplerate, - buffer_size_range: (Some(min_buffer_size as usize), Some(max_buffer_size as usize)), + buffer_size_range: ( + Some(min_buffer_size as usize), + Some(max_buffer_size as usize), + ), exclusive: false, }) } @@ -151,7 +181,7 @@ impl AudioInputDevice for AsioDevice { fn create_input_stream( &self, stream_config: StreamConfig, - mut callback: Callback, + callback: Callback, ) -> Result, Self::Error> { let input_data_type = self.driver.input_data_type()?; @@ -162,101 +192,61 @@ impl AudioInputDevice for AsioDevice { let mut buffer = vec![0.0f32; num_samples]; - let asio_streams = self.asio_streams.clone(); + let mut streams = self.asio_streams.lock().unwrap(); + let input_stream = streams.input.take().ok_or(AsioError::MultipleStreams)?; - let stream_playing = Arc::new(AtomicBool::new(false)); - let playing = Arc::clone(&stream_playing); + let (tx, rx) = oneshot::channel::>(); + let mut callback = Some(callback); let callback_id = self.driver.add_callback(move |callback_info| unsafe { - let streams = asio_streams.lock().unwrap(); - let asio_stream = match &streams.input { - Some(asio_stream) => asio_stream, - None => return - }; - + if let Ok(sender) = rx.try_recv() { + sender.send(callback.take().unwrap()).unwrap(); + return; + } let buffer_index = callback_info.buffer_index as usize; - unsafe fn create_buffer<'a, SampleType: Copy>( - asio_stream: &asio::AsioStream, - buffer: &'a mut [f32], - buffer_index: usize, - num_channels: usize, - from_endian: impl Fn(SampleType) -> SampleType, - to_f32: impl Fn(SampleType) -> f32, - ) -> AudioRef<'a, f32> { - - - for channel_index in 0..num_channels { - let channel_buffer = asio_channel_slice::(asio_stream, buffer_index, channel_index); - for (frame, asio_sample) in buffer.chunks_mut(num_channels).zip(channel_buffer) { - frame[channel_index] = to_f32(from_endian(*asio_sample)); - } - } - AudioRef::from_interleaved(buffer, num_channels).unwrap() - } - - - let audio_buffer = match &input_data_type { - asio::AsioSampleType::ASIOSTInt16MSB => { - create_buffer(&asio_stream, &mut buffer, buffer_index, num_channels, from_be, i16_to_f32) - }, - asio::AsioSampleType::ASIOSTInt16LSB => { - create_buffer(&asio_stream, &mut buffer, buffer_index, num_channels, from_le, i16_to_f32) - }, - asio::AsioSampleType::ASIOSTInt24MSB => { - create_buffer(&asio_stream, &mut buffer, buffer_index, num_channels, from_be, i24_to_f32) - }, - asio::AsioSampleType::ASIOSTInt24LSB => { - create_buffer(&asio_stream, &mut buffer, buffer_index, num_channels, from_le, i24_to_f32) - }, - asio::AsioSampleType::ASIOSTInt32MSB => { - create_buffer(&asio_stream, &mut buffer, buffer_index, num_channels, from_be, i32_to_f32) - }, - asio::AsioSampleType::ASIOSTInt32LSB => { - create_buffer(&asio_stream, &mut buffer, buffer_index, num_channels, from_le, i32_to_f32) - }, - asio::AsioSampleType::ASIOSTFloat32MSB => { - create_buffer::(&asio_stream, &mut buffer, buffer_index, num_channels, from_be, f32::from_bits) - }, - asio::AsioSampleType::ASIOSTFloat32LSB => { - create_buffer::(&asio_stream, &mut buffer, buffer_index, num_channels, from_le, f32::from_bits) - }, - // asio::AsioSampleType::ASIOSTFloat64MSB => todo!(), - // asio::AsioSampleType::ASIOSTFloat64LSB => todo!(), - unsupported_format => unreachable!( - "returned with unsupported format {:?}", - unsupported_format - ), - }; - let timestamp = Timestamp { samplerate: 41000.0, counter: 0, }; + let context = AudioCallbackContext { stream_config, timestamp, }; - let input = AudioInput { + + let input = create_input( + &input_data_type, + &input_stream, + &mut buffer, + buffer_index, + num_channels, timestamp, - buffer: audio_buffer, - }; + ); - callback.on_input_data(context, input); + if let Some(callback) = &mut callback { + callback.on_input_data(context, input); + } }); - Ok(AsioStream::new(playing, callback_id)) + self.driver.start()?; + + Ok(AsioStream { + driver: self.driver.clone(), + callback_id, + callback_retrieve: tx, + }) } } impl AudioOutputDevice for AsioDevice { + type StreamHandle = AsioStream; + fn output_channel_map(&self) -> impl Iterator { [].into_iter() } - type StreamHandle = AsioStream; - fn default_output_config(&self) -> Result { let channels = self.driver.channels()?.outs as u32; let samplerate = self.driver.sample_rate()?; @@ -264,7 +254,10 @@ impl AudioOutputDevice for AsioDevice { Ok(StreamConfig { channels, samplerate, - buffer_size_range: (Some(min_buffer_size as usize), Some(max_buffer_size as usize)), + buffer_size_range: ( + Some(min_buffer_size as usize), + Some(max_buffer_size as usize), + ), exclusive: false, }) } @@ -272,7 +265,7 @@ impl AudioOutputDevice for AsioDevice { fn create_output_stream( &self, stream_config: StreamConfig, - mut callback: Callback, + callback: Callback, ) -> Result, Self::Error> { let output_data_type = self.driver.output_data_type()?; @@ -283,96 +276,57 @@ impl AudioOutputDevice for AsioDevice { let mut buffer = vec![0.0f32; num_samples]; - let asio_streams = self.asio_streams.clone(); + let mut streams = self.asio_streams.lock().unwrap(); + let output_stream = streams.output.take().ok_or(AsioError::MultipleStreams)?; - let stream_playing = Arc::new(AtomicBool::new(false)); - let playing = Arc::clone(&stream_playing); + let (tx, rx) = oneshot::channel::>(); + let mut callback = Some(callback); let callback_id = self.driver.add_callback(move |callback_info| unsafe { - let streams = asio_streams.lock().unwrap(); - let asio_stream = match &streams.output { - Some(asio_stream) => asio_stream, - None => return - }; + if let Ok(sender) = rx.try_recv() { + sender.send(callback.take().unwrap()).unwrap(); + return; + } let buffer_index = callback_info.buffer_index as usize; - unsafe fn create_buffer<'a, SampleType: Copy>( - asio_stream: &asio::AsioStream, - buffer: &'a mut [f32], - buffer_index: usize, - num_channels: usize, - to_endian: impl Fn(SampleType) -> SampleType, - from_f32: impl Fn(f32) -> SampleType, - ) -> AudioMut<'a, f32> { - - - for channel_index in 0..num_channels { - let channel_buffer = asio_channel_slice_mut::(asio_stream, buffer_index, channel_index); - for (frame, asio_sample) in buffer.chunks_mut(num_channels).zip(channel_buffer) { - *asio_sample = to_endian(from_f32(frame[channel_index])); - } - } - AudioMut::from_interleaved_mut(buffer, num_channels).unwrap() - } - - - let audio_buffer = match &output_data_type { - asio::AsioSampleType::ASIOSTInt16MSB => { - create_buffer(&asio_stream, &mut buffer, buffer_index, num_channels, to_be, f32_to_i16) - }, - asio::AsioSampleType::ASIOSTInt16LSB => { - create_buffer(&asio_stream, &mut buffer, buffer_index, num_channels, to_le, f32_to_i16) - }, - asio::AsioSampleType::ASIOSTInt24MSB => { - create_buffer(&asio_stream, &mut buffer, buffer_index, num_channels, to_be, f32_to_i24) - }, - asio::AsioSampleType::ASIOSTInt24LSB => { - create_buffer(&asio_stream, &mut buffer, buffer_index, num_channels, to_le, f32_to_i24) - }, - asio::AsioSampleType::ASIOSTInt32MSB => { - create_buffer(&asio_stream, &mut buffer, buffer_index, num_channels, to_be, f32_to_i32) - }, - asio::AsioSampleType::ASIOSTInt32LSB => { - create_buffer(&asio_stream, &mut buffer, buffer_index, num_channels, to_le, f32_to_i32) - }, - asio::AsioSampleType::ASIOSTFloat32MSB => { - create_buffer::(&asio_stream, &mut buffer, buffer_index, num_channels, to_be, f32::to_bits) - }, - asio::AsioSampleType::ASIOSTFloat32LSB => { - create_buffer::(&asio_stream, &mut buffer, buffer_index, num_channels, to_le, f32::to_bits) - }, - // asio::AsioSampleType::ASIOSTFloat64MSB => todo!(), - // asio::AsioSampleType::ASIOSTFloat64LSB => todo!(), - unsupported_format => unreachable!( - "returned with unsupported format {:?}", - unsupported_format - ), - }; - let timestamp = Timestamp { samplerate: 41000.0, counter: 0, }; + let context = AudioCallbackContext { stream_config, timestamp, }; - let output = AudioOutput { + + let output = create_output( + &output_data_type, + &output_stream, + &mut buffer, + buffer_index, + num_channels, timestamp, - buffer: audio_buffer, - }; + ); - callback.on_output_data(context, output); + if let Some(callback) = &mut callback { + callback.on_output_data(context, output); + } }); self.driver.start()?; - Ok(AsioStream::new(playing, callback_id)) + Ok(AsioStream { + driver: self.driver.clone(), + callback_id, + callback_retrieve: tx, + }) } } impl AudioDuplexDevice for AsioDevice { + type StreamHandle = AsioStream; + fn default_duplex_config(&self) -> Result { let channels = self.driver.channels()?.ins as u32; let samplerate = self.driver.sample_rate()?; @@ -380,7 +334,10 @@ impl AudioDuplexDevice for AsioDevice { Ok(StreamConfig { channels, samplerate, - buffer_size_range: (Some(min_buffer_size as usize), Some(max_buffer_size as usize)), + buffer_size_range: ( + Some(min_buffer_size as usize), + Some(max_buffer_size as usize), + ), exclusive: false, }) } @@ -390,14 +347,284 @@ impl AudioDuplexDevice for AsioDevice { stream_config: StreamConfig, callback: Callback, ) -> Result, Self::Error> { - todo!() + let output_data_type = self.driver.output_data_type()?; + let input_data_type = self.driver.input_data_type()?; + + let num_channels = stream_config.channels as usize; + + // This creates both input and output streams if available + let buffer_size = self.create_output_stream(stream_config)?; + let num_samples = buffer_size * num_channels; + + let mut input_buffer = vec![0.0f32; num_samples]; + let mut output_buffer = vec![0.0f32; num_samples]; + + let mut streams = self.asio_streams.lock().unwrap(); + let output_stream = streams.output.take().ok_or(AsioError::MultipleStreams)?; + let input_stream = streams.input.take().ok_or(AsioError::MultipleStreams)?; + + let (tx, rx) = oneshot::channel::>(); + let mut callback = Some(callback); + + let callback_id = self.driver.add_callback(move |callback_info| unsafe { + if let Ok(sender) = rx.try_recv() { + sender.send(callback.take().unwrap()).unwrap(); + return; + } + + let buffer_index = callback_info.buffer_index as usize; + + let timestamp = Timestamp { + samplerate: 41000.0, + counter: 0, + }; + + let input = create_input( + &input_data_type, + &input_stream, + &mut input_buffer, + buffer_index, + num_channels, + timestamp, + ); + + let output = create_output( + &output_data_type, + &output_stream, + &mut output_buffer, + buffer_index, + num_channels, + timestamp, + ); + + let context = AudioCallbackContext { + stream_config, + timestamp, + }; + + if let Some(callback) = &mut callback { + callback.on_audio_data(context, input, output); + } + }); + + self.driver.start()?; + + Ok(AsioStream { + driver: self.driver.clone(), + callback_id, + callback_retrieve: tx, + }) } - - type StreamHandle = AsioStream; } // HELPERS +/// Create an `AudioOutput` from the ASIO stream and the buffer. +unsafe fn create_output<'a>( + output_data_type: &asio::AsioSampleType, + asio_stream: &asio::AsioStream, + buffer: &'a mut [f32], + buffer_index: usize, + num_channels: usize, + timestamp: timestamp::Timestamp, +) -> AudioOutput<'a, f32> { + let audio_output_buffer = match output_data_type { + asio::AsioSampleType::ASIOSTInt16MSB => create_output_buffer( + &asio_stream, + buffer, + buffer_index, + num_channels, + to_be, + f32_to_i16, + ), + asio::AsioSampleType::ASIOSTInt16LSB => create_output_buffer( + &asio_stream, + buffer, + buffer_index, + num_channels, + to_le, + f32_to_i16, + ), + asio::AsioSampleType::ASIOSTInt24MSB => create_output_buffer( + &asio_stream, + buffer, + buffer_index, + num_channels, + to_be, + f32_to_i24, + ), + asio::AsioSampleType::ASIOSTInt24LSB => create_output_buffer( + &asio_stream, + buffer, + buffer_index, + num_channels, + to_le, + f32_to_i24, + ), + asio::AsioSampleType::ASIOSTInt32MSB => create_output_buffer( + &asio_stream, + buffer, + buffer_index, + num_channels, + to_be, + f32_to_i32, + ), + asio::AsioSampleType::ASIOSTInt32LSB => create_output_buffer( + &asio_stream, + buffer, + buffer_index, + num_channels, + to_le, + f32_to_i32, + ), + asio::AsioSampleType::ASIOSTFloat32MSB => create_output_buffer::( + &asio_stream, + buffer, + buffer_index, + num_channels, + to_be, + f32::to_bits, + ), + asio::AsioSampleType::ASIOSTFloat32LSB => create_output_buffer::( + &asio_stream, + buffer, + buffer_index, + num_channels, + to_le, + f32::to_bits, + ), + unsupported_format => { + unreachable!("returned with unsupported format {:?}", unsupported_format) + } + }; + + AudioOutput { + timestamp, + buffer: audio_output_buffer, + } +} + +/// Create an `AudioInput` from the ASIO stream and the buffer. +unsafe fn create_input<'a>( + input_data_type: &asio::AsioSampleType, + asio_stream: &asio::AsioStream, + buffer: &'a mut [f32], + buffer_index: usize, + num_channels: usize, + timestamp: timestamp::Timestamp, +) -> AudioInput<'a, f32> { + let audio_input_buffer = match input_data_type { + asio::AsioSampleType::ASIOSTInt16MSB => create_input_buffer( + &asio_stream, + buffer, + buffer_index, + num_channels, + from_be, + i16_to_f32, + ), + asio::AsioSampleType::ASIOSTInt16LSB => create_input_buffer( + &asio_stream, + buffer, + buffer_index, + num_channels, + from_le, + i16_to_f32, + ), + asio::AsioSampleType::ASIOSTInt24MSB => create_input_buffer( + &asio_stream, + buffer, + buffer_index, + num_channels, + from_be, + i24_to_f32, + ), + asio::AsioSampleType::ASIOSTInt24LSB => create_input_buffer( + &asio_stream, + buffer, + buffer_index, + num_channels, + from_le, + i24_to_f32, + ), + asio::AsioSampleType::ASIOSTInt32MSB => create_input_buffer( + &asio_stream, + buffer, + buffer_index, + num_channels, + from_be, + i32_to_f32, + ), + asio::AsioSampleType::ASIOSTInt32LSB => create_input_buffer( + &asio_stream, + buffer, + buffer_index, + num_channels, + from_le, + i32_to_f32, + ), + asio::AsioSampleType::ASIOSTFloat32MSB => create_input_buffer::( + &asio_stream, + buffer, + buffer_index, + num_channels, + from_be, + f32::from_bits, + ), + asio::AsioSampleType::ASIOSTFloat32LSB => create_input_buffer::( + &asio_stream, + buffer, + buffer_index, + num_channels, + from_le, + f32::from_bits, + ), + unsupported_format => { + unreachable!("returned with unsupported format {:?}", unsupported_format) + } + }; + + AudioInput { + timestamp, + buffer: audio_input_buffer, + } +} + +unsafe fn create_input_buffer<'a, SampleType: Copy>( + asio_stream: &asio::AsioStream, + buffer: &'a mut [f32], + buffer_index: usize, + num_channels: usize, + from_endian: impl Fn(SampleType) -> SampleType, + to_f32: impl Fn(SampleType) -> f32, +) -> AudioRef<'a, f32> { + for channel_index in 0..num_channels { + let channel_buffer = + asio_channel_slice::(asio_stream, buffer_index, channel_index); + for (frame, asio_sample) in buffer.chunks_mut(num_channels).zip(channel_buffer) { + frame[channel_index] = to_f32(from_endian(*asio_sample)); + } + } + AudioRef::from_interleaved(buffer, num_channels).unwrap() +} + +unsafe fn create_output_buffer<'a, SampleType: Copy>( + asio_stream: &asio::AsioStream, + buffer: &'a mut [f32], + buffer_index: usize, + num_channels: usize, + to_endian: impl Fn(SampleType) -> SampleType, + from_f32: impl Fn(f32) -> SampleType, +) -> AudioMut<'a, f32> { + for channel_index in 0..num_channels { + let channel_buffer = + asio_channel_slice_mut::(asio_stream, buffer_index, channel_index); + for (frame, asio_sample) in buffer.chunks_mut(num_channels).zip(channel_buffer) { + *asio_sample = to_endian(from_f32(frame[channel_index])); + } + } + AudioMut::from_interleaved_mut(buffer, num_channels).unwrap() +} + unsafe fn asio_channel_slice( asio_stream: &asio::AsioStream, buffer_index: usize, @@ -470,7 +697,6 @@ fn f32_to_i24(f: f32) -> i32 { (f * 0x7FFFFF as f32) as i32 } - // 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 @@ -481,4 +707,4 @@ fn f32_to_i24(f: f32) -> i32 { // let secs = systime_ns as i64 / 1_000_000_000; // let nanos = (systime_ns as i64 - secs * 1_000_000_000) as u32; -// } \ No newline at end of file +// } diff --git a/src/backends/asio/driver.rs b/src/backends/asio/driver.rs index f1fc820..021b370 100644 --- a/src/backends/asio/driver.rs +++ b/src/backends/asio/driver.rs @@ -2,8 +2,10 @@ use std::{borrow::Cow, sync::Arc}; use asio_sys as asio; - -use crate::{device::{AudioDevice, DeviceType}, driver::AudioDriver}; +use crate::{ + device::{AudioDevice, DeviceType}, + driver::AudioDriver, +}; use super::{device::AsioDevice, error::AsioError}; @@ -14,6 +16,7 @@ pub struct AsioDriver { } impl AsioDriver { + /// Create a new ASIO driver. pub fn new() -> Result { let asio = Arc::new(asio::Asio::new()); Ok(AsioDriver { asio }) @@ -32,15 +35,14 @@ impl AudioDriver for AsioDriver { fn default_device(&self, device_type: DeviceType) -> Result, Self::Error> { let iter = AsioDeviceList::new(self.asio.clone())?; - - let dd = iter.filter(|device| { - match (device.device_type(), device_type) { + + let dd = iter + .filter(|device| match (device.device_type(), device_type) { (DeviceType::Input | DeviceType::Duplex, DeviceType::Input) => true, (DeviceType::Output | DeviceType::Duplex, DeviceType::Output) => true, (a, b) => a == b, - - } - }).next(); + }) + .next(); Ok(dd) } @@ -79,5 +81,3 @@ impl Iterator for AsioDeviceList { } } } - - diff --git a/src/backends/asio/error.rs b/src/backends/asio/error.rs index 5f4b047..26d89ac 100644 --- a/src/backends/asio/error.rs +++ b/src/backends/asio/error.rs @@ -1,5 +1,5 @@ -use thiserror::Error; use asio_sys::AsioError as AsioSysError; +use thiserror::Error; /// Type of errors from the ASIO backend. #[derive(Debug, Error)] @@ -13,4 +13,6 @@ pub enum AsioError { ConfigurationNotAvailable, #[error("Device unavailable")] DeviceUnavailable, + #[error("Multiple streams not supported")] + MultipleStreams, } diff --git a/src/backends/asio/mod.rs b/src/backends/asio/mod.rs index b40cf71..398c4e8 100644 --- a/src/backends/asio/mod.rs +++ b/src/backends/asio/mod.rs @@ -1,7 +1,6 @@ - -mod device; +pub(crate) mod device; pub(crate) mod driver; -mod error; -mod stream; +pub use driver::AsioDriver; +pub(crate) mod error; pub mod prelude; -pub use prelude::*; +pub(crate) mod stream; diff --git a/src/backends/asio/prelude.rs b/src/backends/asio/prelude.rs index e840c46..4bc5bb8 100644 --- a/src/backends/asio/prelude.rs +++ b/src/backends/asio/prelude.rs @@ -1,3 +1 @@ -pub use super::{ - device::AsioDevice, driver::AsioDriver, -}; \ No newline at end of file +pub use super::{device::AsioDevice, driver::AsioDriver, error::AsioError, stream::AsioStream}; diff --git a/src/backends/asio/stream.rs b/src/backends/asio/stream.rs index 22daa43..6da4835 100644 --- a/src/backends/asio/stream.rs +++ b/src/backends/asio/stream.rs @@ -1,32 +1,26 @@ -use std::{marker::PhantomData, sync::{atomic::AtomicBool, Arc}}; +use std::sync::Arc; -use asio_sys::{self as asio, CallbackId}; +use asio_sys as asio; use crate::stream::AudioStreamHandle; use super::error::AsioError; pub struct AsioStream { - playing: Arc, - // driver: Arc, - // streams: Arc, - callback_id: asio::CallbackId, - callback: PhantomData, + pub driver: Arc, + pub callback_id: asio::CallbackId, + pub callback_retrieve: oneshot::Sender>, } impl AudioStreamHandle for AsioStream { - type Error = AsioError; - + fn eject(self) -> Result { - todo!() + let (tx, rx) = oneshot::channel(); + self.callback_retrieve.send(tx).unwrap(); + let callback = rx.recv().unwrap(); + self.driver.stop()?; + self.driver.remove_callback(self.callback_id); + Ok(callback) } } - -impl AsioStream { - pub fn new(playing: Arc, callback_id: CallbackId) -> Self { - AsioStream { playing, callback_id, callback: PhantomData } - } - - -} \ No newline at end of file diff --git a/src/backends/mod.rs b/src/backends/mod.rs index 4429ef0..b9c9347 100644 --- a/src/backends/mod.rs +++ b/src/backends/mod.rs @@ -116,7 +116,7 @@ pub fn default_output_device() -> impl AudioOutputDevice { // #[cfg(os_wasapi)] // return default_output_device_from(&wasapi::WasapiDriver); #[cfg(os_asio)] - return default_output_device_from(&asio::AsioDriver::new().unwrap()); + return default_output_device_from(&asio::driver::AsioDriver::new().unwrap()); } /// Default duplex device from the default driver of this platform. diff --git a/src/prelude.rs b/src/prelude.rs index 076e175..0b0ea82 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -1,6 +1,8 @@ #![allow(unused)] //! Prelude module for `interflow`. Use as a star-import. +#[cfg(os_asio)] +pub use crate::backends::asio::prelude::*; #[cfg(os_wasapi)] pub use crate::backends::wasapi::prelude::*; pub use crate::backends::*; From ecf1e31895741a613eb24d888c21dff1d125f5a4 Mon Sep 17 00:00:00 2001 From: Dr George Atkinson Date: Thu, 20 Mar 2025 16:26:34 +0000 Subject: [PATCH 4/6] Add timestamps --- src/backends/asio/device.rs | 44 ++++++++++++++++-------------------- src/backends/asio/mod.rs | 9 ++++---- src/backends/asio/prelude.rs | 1 - src/backends/mod.rs | 2 +- src/prelude.rs | 2 -- 5 files changed, 24 insertions(+), 34 deletions(-) delete mode 100644 src/backends/asio/prelude.rs diff --git a/src/backends/asio/device.rs b/src/backends/asio/device.rs index e517fec..c3b9075 100644 --- a/src/backends/asio/device.rs +++ b/src/backends/asio/device.rs @@ -198,6 +198,11 @@ impl AudioInputDevice for AsioDevice { let (tx, rx) = oneshot::channel::>(); let mut callback = Some(callback); + let mut timestamp = Timestamp { + samplerate: stream_config.samplerate, + counter: 0, + }; + let callback_id = self.driver.add_callback(move |callback_info| unsafe { if let Ok(sender) = rx.try_recv() { sender.send(callback.take().unwrap()).unwrap(); @@ -206,10 +211,7 @@ impl AudioInputDevice for AsioDevice { let buffer_index = callback_info.buffer_index as usize; - let timestamp = Timestamp { - samplerate: 41000.0, - counter: 0, - }; + timestamp += input_stream.buffer_size as u64; let context = AudioCallbackContext { stream_config, @@ -282,6 +284,11 @@ impl AudioOutputDevice for AsioDevice { let (tx, rx) = oneshot::channel::>(); let mut callback = Some(callback); + let mut timestamp = Timestamp { + samplerate: stream_config.samplerate, + counter: 0, + }; + let callback_id = self.driver.add_callback(move |callback_info| unsafe { if let Ok(sender) = rx.try_recv() { sender.send(callback.take().unwrap()).unwrap(); @@ -290,10 +297,7 @@ impl AudioOutputDevice for AsioDevice { let buffer_index = callback_info.buffer_index as usize; - let timestamp = Timestamp { - samplerate: 41000.0, - counter: 0, - }; + timestamp += output_stream.buffer_size as u64; let context = AudioCallbackContext { stream_config, @@ -366,6 +370,11 @@ impl AudioDuplexDevice for AsioDevice { let (tx, rx) = oneshot::channel::>(); let mut callback = Some(callback); + let mut timestamp = Timestamp { + samplerate: stream_config.samplerate, + counter: 0, + }; + let callback_id = self.driver.add_callback(move |callback_info| unsafe { if let Ok(sender) = rx.try_recv() { sender.send(callback.take().unwrap()).unwrap(); @@ -374,10 +383,7 @@ impl AudioDuplexDevice for AsioDevice { let buffer_index = callback_info.buffer_index as usize; - let timestamp = Timestamp { - samplerate: 41000.0, - counter: 0, - }; + timestamp += output_stream.buffer_size as u64; let input = create_input( &input_data_type, @@ -695,16 +701,4 @@ fn f32_to_i32(f: f32) -> i32 { /// Helper function to convert from f32 to i24. fn f32_to_i24(f: f32) -> i32 { (f * 0x7FFFFF as f32) as i32 -} - -// 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 -// } - -// fn system_time_to_timestamp(system_time: asio::AsioTime) -> timestamp::Timestamp { -// 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; - -// } +} \ No newline at end of file diff --git a/src/backends/asio/mod.rs b/src/backends/asio/mod.rs index 398c4e8..cf5ef49 100644 --- a/src/backends/asio/mod.rs +++ b/src/backends/asio/mod.rs @@ -1,6 +1,5 @@ -pub(crate) mod device; -pub(crate) mod driver; +mod device; +mod driver; pub use driver::AsioDriver; -pub(crate) mod error; -pub mod prelude; -pub(crate) mod stream; +mod error; +mod stream; diff --git a/src/backends/asio/prelude.rs b/src/backends/asio/prelude.rs deleted file mode 100644 index 4bc5bb8..0000000 --- a/src/backends/asio/prelude.rs +++ /dev/null @@ -1 +0,0 @@ -pub use super::{device::AsioDevice, driver::AsioDriver, error::AsioError, stream::AsioStream}; diff --git a/src/backends/mod.rs b/src/backends/mod.rs index b9c9347..4429ef0 100644 --- a/src/backends/mod.rs +++ b/src/backends/mod.rs @@ -116,7 +116,7 @@ pub fn default_output_device() -> impl AudioOutputDevice { // #[cfg(os_wasapi)] // return default_output_device_from(&wasapi::WasapiDriver); #[cfg(os_asio)] - return default_output_device_from(&asio::driver::AsioDriver::new().unwrap()); + return default_output_device_from(&asio::AsioDriver::new().unwrap()); } /// Default duplex device from the default driver of this platform. diff --git a/src/prelude.rs b/src/prelude.rs index 0b0ea82..076e175 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -1,8 +1,6 @@ #![allow(unused)] //! Prelude module for `interflow`. Use as a star-import. -#[cfg(os_asio)] -pub use crate::backends::asio::prelude::*; #[cfg(os_wasapi)] pub use crate::backends::wasapi::prelude::*; pub use crate::backends::*; From 2ddddc73a13cd642b82a30f928cfa9e6c1fc00aa Mon Sep 17 00:00:00 2001 From: Dr George Atkinson Date: Sat, 29 Mar 2025 14:06:45 +0000 Subject: [PATCH 5/6] Enumerate Configs + Cleanup --- Cargo.toml | 11 ++--- build.rs | 1 + src/backends/asio/device.rs | 86 ++++++++++++++++++++++++------------- src/backends/asio/driver.rs | 13 +++--- src/backends/mod.rs | 6 +-- src/duplex.rs | 1 + 6 files changed, 69 insertions(+), 49 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8323ccb..ecdb1c0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,10 +5,6 @@ edition = "2021" rust-version = "1.80" license = "MIT" -[features] -default = ["asio"] -asio = ["asio-sys", "num-traits"] - [dependencies] duplicate = "2.0.0" fixed-resample = "0.8.0" @@ -31,6 +27,7 @@ cfg_aliases = "0.2.1" [features] pipewire = ["dep:pipewire", "dep:libspa", "dep:libspa-sys", "dep:zerocopy"] +asio = ["asio-sys", "num-traits"] [target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd"))'.dependencies] alsa = "0.9.1" @@ -55,7 +52,7 @@ windows = { version = "0.61.1", features = [ "Win32_Media_Multimedia", "Win32_UI_Shell_PropertiesSystem" ]} -asio-sys ={ version = "0.2.2", optional = true } +asio-sys = { version = "0.2.2", optional = true } num-traits = {version = "0.2.19", optional = true } [[example]] @@ -69,13 +66,13 @@ path = "examples/enumerate_coreaudio.rs" [[example]] name = "enumerate_wasapi" path = "examples/enumerate_wasapi.rs" -required-features = ["pipewire"] [[example]] name = "enumerate_pipewire" path = "examples/enumerate_pipewire.rs" +required-features = ["pipewire"] [[example]] name = "enumerate_asio" path = "examples/enumerate_asio.rs" -features = ["asio"] +required-features = ["asio"] diff --git a/build.rs b/build.rs index 1a02ed0..75158a8 100644 --- a/build.rs +++ b/build.rs @@ -7,6 +7,7 @@ fn main() { os_alsa: { any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd") }, os_coreaudio: { any (target_os = "macos", target_os = "ios") }, + os_pipewire: { any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd") }, os_wasapi: { target_os = "windows" }, os_asio: { all(target_os = "windows", feature = "asio") }, unsupported: { not(any(os_alsa, os_coreaudio, os_wasapi))} diff --git a/src/backends/asio/device.rs b/src/backends/asio/device.rs index c3b9075..e57ecbd 100644 --- a/src/backends/asio/device.rs +++ b/src/backends/asio/device.rs @@ -80,7 +80,7 @@ impl AsioDevice { *streams = new_streams; bs }) - .map_err(|e| AsioError::BackendError(e)) + .map_err(AsioError::BackendError) } } } @@ -111,7 +111,7 @@ impl AsioDevice { *streams = new_streams; bs }) - .map_err(|e| AsioError::BackendError(e)) + .map_err(AsioError::BackendError) } } } @@ -131,7 +131,7 @@ impl AudioDevice for AsioDevice { fn is_config_supported(&self, config: &StreamConfig) -> bool { let sample_rate = config.samplerate; self.driver.can_sample_rate(sample_rate).unwrap_or(false) - && self.driver.channels().map_or(false, |channels| { + && self.driver.channels().is_ok_and(|channels| { let num_channels = config.channels as i32; match self.device_type { DeviceType::Input => channels.ins >= num_channels, @@ -141,7 +141,7 @@ impl AudioDevice for AsioDevice { } } }) - && self.driver.buffersize_range().map_or(false, |(min, max)| { + && self.driver.buffersize_range().is_ok_and(|(min, max)| { match config.buffer_size_range { (Some(min_config), Some(max_config)) => { min_config >= min as usize && max_config <= max as usize @@ -152,7 +152,31 @@ impl AudioDevice for AsioDevice { } fn enumerate_configurations(&self) -> Option> { - None::<[StreamConfig; 0]> + let sample_rates = [ + 44100.0, 48000.0, 96000.0, 192000.0, 384000.0, 768000.0, + ]; + + let buffer_size_range = self.driver.buffersize_range().ok().map(|(min, max)| { + (Some(min as usize), Some(max as usize)) + })?; + + let channels = self.driver.channels().ok().map(|channels| { + match self.device_type { + DeviceType::Input => channels.ins as u32, + DeviceType::Output => channels.outs as u32, + DeviceType::Duplex => channels.ins.max(channels.outs) as u32, + + } + })?; + + Some(sample_rates.into_iter().filter_map(move |samplerate| { + self.driver.can_sample_rate(samplerate).ok().filter(|&can| can).map(|_| StreamConfig { + channels, + samplerate, + buffer_size_range, + exclusive: false, + }) + })) } } @@ -279,7 +303,7 @@ impl AudioOutputDevice for AsioDevice { let mut buffer = vec![0.0f32; num_samples]; let mut streams = self.asio_streams.lock().unwrap(); - let output_stream = streams.output.take().ok_or(AsioError::MultipleStreams)?; + let mut output_stream = streams.output.take().ok_or(AsioError::MultipleStreams)?; let (tx, rx) = oneshot::channel::>(); let mut callback = Some(callback); @@ -306,7 +330,7 @@ impl AudioOutputDevice for AsioDevice { let output = create_output( &output_data_type, - &output_stream, + &mut output_stream, &mut buffer, buffer_index, num_channels, @@ -364,7 +388,7 @@ impl AudioDuplexDevice for AsioDevice { let mut output_buffer = vec![0.0f32; num_samples]; let mut streams = self.asio_streams.lock().unwrap(); - let output_stream = streams.output.take().ok_or(AsioError::MultipleStreams)?; + let mut output_stream = streams.output.take().ok_or(AsioError::MultipleStreams)?; let input_stream = streams.input.take().ok_or(AsioError::MultipleStreams)?; let (tx, rx) = oneshot::channel::>(); @@ -396,7 +420,7 @@ impl AudioDuplexDevice for AsioDevice { let output = create_output( &output_data_type, - &output_stream, + &mut output_stream, &mut output_buffer, buffer_index, num_channels, @@ -428,7 +452,7 @@ impl AudioDuplexDevice for AsioDevice { /// Create an `AudioOutput` from the ASIO stream and the buffer. unsafe fn create_output<'a>( output_data_type: &asio::AsioSampleType, - asio_stream: &asio::AsioStream, + asio_stream: &mut asio::AsioStream, buffer: &'a mut [f32], buffer_index: usize, num_channels: usize, @@ -436,7 +460,7 @@ unsafe fn create_output<'a>( ) -> AudioOutput<'a, f32> { let audio_output_buffer = match output_data_type { asio::AsioSampleType::ASIOSTInt16MSB => create_output_buffer( - &asio_stream, + asio_stream, buffer, buffer_index, num_channels, @@ -444,7 +468,7 @@ unsafe fn create_output<'a>( f32_to_i16, ), asio::AsioSampleType::ASIOSTInt16LSB => create_output_buffer( - &asio_stream, + asio_stream, buffer, buffer_index, num_channels, @@ -452,7 +476,7 @@ unsafe fn create_output<'a>( f32_to_i16, ), asio::AsioSampleType::ASIOSTInt24MSB => create_output_buffer( - &asio_stream, + asio_stream, buffer, buffer_index, num_channels, @@ -460,7 +484,7 @@ unsafe fn create_output<'a>( f32_to_i24, ), asio::AsioSampleType::ASIOSTInt24LSB => create_output_buffer( - &asio_stream, + asio_stream, buffer, buffer_index, num_channels, @@ -468,7 +492,7 @@ unsafe fn create_output<'a>( f32_to_i24, ), asio::AsioSampleType::ASIOSTInt32MSB => create_output_buffer( - &asio_stream, + asio_stream, buffer, buffer_index, num_channels, @@ -476,7 +500,7 @@ unsafe fn create_output<'a>( f32_to_i32, ), asio::AsioSampleType::ASIOSTInt32LSB => create_output_buffer( - &asio_stream, + asio_stream, buffer, buffer_index, num_channels, @@ -484,7 +508,7 @@ unsafe fn create_output<'a>( f32_to_i32, ), asio::AsioSampleType::ASIOSTFloat32MSB => create_output_buffer::( - &asio_stream, + asio_stream, buffer, buffer_index, num_channels, @@ -492,7 +516,7 @@ unsafe fn create_output<'a>( f32::to_bits, ), asio::AsioSampleType::ASIOSTFloat32LSB => create_output_buffer::( - &asio_stream, + asio_stream, buffer, buffer_index, num_channels, @@ -521,7 +545,7 @@ unsafe fn create_input<'a>( ) -> AudioInput<'a, f32> { let audio_input_buffer = match input_data_type { asio::AsioSampleType::ASIOSTInt16MSB => create_input_buffer( - &asio_stream, + asio_stream, buffer, buffer_index, num_channels, @@ -529,7 +553,7 @@ unsafe fn create_input<'a>( i16_to_f32, ), asio::AsioSampleType::ASIOSTInt16LSB => create_input_buffer( - &asio_stream, + asio_stream, buffer, buffer_index, num_channels, @@ -537,7 +561,7 @@ unsafe fn create_input<'a>( i16_to_f32, ), asio::AsioSampleType::ASIOSTInt24MSB => create_input_buffer( - &asio_stream, + asio_stream, buffer, buffer_index, num_channels, @@ -545,7 +569,7 @@ unsafe fn create_input<'a>( i24_to_f32, ), asio::AsioSampleType::ASIOSTInt24LSB => create_input_buffer( - &asio_stream, + asio_stream, buffer, buffer_index, num_channels, @@ -553,7 +577,7 @@ unsafe fn create_input<'a>( i24_to_f32, ), asio::AsioSampleType::ASIOSTInt32MSB => create_input_buffer( - &asio_stream, + asio_stream, buffer, buffer_index, num_channels, @@ -561,7 +585,7 @@ unsafe fn create_input<'a>( i32_to_f32, ), asio::AsioSampleType::ASIOSTInt32LSB => create_input_buffer( - &asio_stream, + asio_stream, buffer, buffer_index, num_channels, @@ -569,7 +593,7 @@ unsafe fn create_input<'a>( i32_to_f32, ), asio::AsioSampleType::ASIOSTFloat32MSB => create_input_buffer::( - &asio_stream, + asio_stream, buffer, buffer_index, num_channels, @@ -577,7 +601,7 @@ unsafe fn create_input<'a>( f32::from_bits, ), asio::AsioSampleType::ASIOSTFloat32LSB => create_input_buffer::( - &asio_stream, + asio_stream, buffer, buffer_index, num_channels, @@ -614,7 +638,7 @@ unsafe fn create_input_buffer<'a, SampleType: Copy>( } unsafe fn create_output_buffer<'a, SampleType: Copy>( - asio_stream: &asio::AsioStream, + asio_stream: &mut asio::AsioStream, buffer: &'a mut [f32], buffer_index: usize, num_channels: usize, @@ -638,18 +662,18 @@ unsafe fn asio_channel_slice( ) -> &[T] { let buffer_size = asio_stream.buffer_size as usize; let buff_ptr: *const T = - asio_stream.buffer_infos[channel_index].buffers[buffer_index as usize] as *const _; + asio_stream.buffer_infos[channel_index].buffers[buffer_index] as *const _; std::slice::from_raw_parts(buff_ptr, buffer_size) } unsafe fn asio_channel_slice_mut( - asio_stream: &asio::AsioStream, + asio_stream: &mut asio::AsioStream, buffer_index: usize, channel_index: usize, ) -> &mut [T] { let buffer_size = asio_stream.buffer_size as usize; let buff_ptr: *mut T = - asio_stream.buffer_infos[channel_index].buffers[buffer_index as usize] as *mut _; + asio_stream.buffer_infos[channel_index].buffers[buffer_index] as *mut _; std::slice::from_raw_parts_mut(buff_ptr, buffer_size) } @@ -701,4 +725,4 @@ fn f32_to_i32(f: f32) -> i32 { /// Helper function to convert from f32 to i24. fn f32_to_i24(f: f32) -> i32 { (f * 0x7FFFFF as f32) as i32 -} \ No newline at end of file +} diff --git a/src/backends/asio/driver.rs b/src/backends/asio/driver.rs index 021b370..33039c5 100644 --- a/src/backends/asio/driver.rs +++ b/src/backends/asio/driver.rs @@ -34,15 +34,14 @@ impl AudioDriver for AsioDriver { } fn default_device(&self, device_type: DeviceType) -> Result, Self::Error> { - let iter = AsioDeviceList::new(self.asio.clone())?; + let mut iter = AsioDeviceList::new(self.asio.clone())?; let dd = iter - .filter(|device| match (device.device_type(), device_type) { - (DeviceType::Input | DeviceType::Duplex, DeviceType::Input) => true, - (DeviceType::Output | DeviceType::Duplex, DeviceType::Output) => true, - (a, b) => a == b, - }) - .next(); + .find(|device| match (device.device_type(), device_type) { + (DeviceType::Input | DeviceType::Duplex, DeviceType::Input) => true, + (DeviceType::Output | DeviceType::Duplex, DeviceType::Output) => true, + (a, b) => a == b, + }); Ok(dd) } diff --git a/src/backends/mod.rs b/src/backends/mod.rs index 4429ef0..693a07f 100644 --- a/src/backends/mod.rs +++ b/src/backends/mod.rs @@ -113,10 +113,8 @@ pub fn default_output_device() -> impl AudioOutputDevice { return default_output_device_from(&alsa::AlsaDriver); #[cfg(os_coreaudio)] return default_output_device_from(&coreaudio::CoreAudioDriver); - // #[cfg(os_wasapi)] - // return default_output_device_from(&wasapi::WasapiDriver); - #[cfg(os_asio)] - return default_output_device_from(&asio::AsioDriver::new().unwrap()); + #[cfg(os_wasapi)] + return default_output_device_from(&wasapi::WasapiDriver); } /// Default duplex device from the default driver of this platform. diff --git a/src/duplex.rs b/src/duplex.rs index 4edfa9b..4a3e9b9 100644 --- a/src/duplex.rs +++ b/src/duplex.rs @@ -2,6 +2,7 @@ //! //! This module includes a proxy for gathering an input audio stream, and optionally process it to resample it to the //! output sample rate. +use crate::audio_buffer::AudioRef; use crate::channel_map::Bitset; use crate::device::{AudioDevice, AudioInputDevice, AudioOutputDevice}; use crate::stream::{ From b68d6e124149d95af0e10cd1ad6c54f07552da28 Mon Sep 17 00:00:00 2001 From: Dr George Atkinson Date: Thu, 17 Apr 2025 15:18:31 +0100 Subject: [PATCH 6/6] Update ASIO backend for new `DeviceType` bitflag --- Cargo.lock | 142 +++++++++++++++++++++++++++++++++- Cargo.toml | 5 +- src/backends/asio/device.rs | 81 ++++++++++--------- src/backends/asio/driver.rs | 13 +--- src/backends/wasapi/stream.rs | 3 +- src/device.rs | 4 +- src/duplex.rs | 4 +- src/lib.rs | 4 +- 8 files changed, 194 insertions(+), 62 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d1f74fd..00b7aa7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -79,7 +79,7 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" dependencies = [ - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -89,7 +89,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" dependencies = [ "anstyle", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -104,6 +104,20 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +[[package]] +name = "asio-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb111d0b927c4b860b42a8bd869b94f5f973b0395ed5892f1ab2be2dc6831ea8" +dependencies = [ + "bindgen", + "cc", + "num-derive", + "num-traits", + "parse_cfg", + "walkdir", +] + [[package]] name = "autocfg" version = "1.3.0" @@ -123,12 +137,15 @@ dependencies = [ "itertools", "lazy_static", "lazycell", + "log", + "prettyplease", "proc-macro2", "quote", "regex", "rustc-hash", "shlex", "syn", + "which", ] [[package]] @@ -216,7 +233,7 @@ dependencies = [ "lazy_static", "libc", "unicode-width 0.1.13", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -321,6 +338,16 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +[[package]] +name = "errno" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "fast-interleave" version = "0.1.1" @@ -446,6 +473,15 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "home" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" +dependencies = [ + "windows-sys 0.59.0", +] + [[package]] name = "indexmap" version = "2.8.0" @@ -475,6 +511,7 @@ version = "0.1.0" dependencies = [ "alsa", "anyhow", + "asio-sys", "bitflags 2.9.0", "cfg_aliases", "coreaudio-rs", @@ -488,6 +525,7 @@ dependencies = [ "log", "ndarray", "nix 0.29.0", + "num-traits", "oneshot", "pipewire", "rtrb", @@ -601,6 +639,12 @@ dependencies = [ "system-deps", ] +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + [[package]] name = "log" version = "0.4.27" @@ -686,6 +730,17 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "num-integer" version = "0.1.46" @@ -722,6 +777,15 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4ce411919553d3f9fa53a0880544cda985a112117a0444d5ff1e870a893d6ea" +[[package]] +name = "parse_cfg" +version = "4.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "905787a434a2c721408e7c9a252e85f3d93ca0f118a5283022636c0e05a7ea49" +dependencies = [ + "nom", +] + [[package]] name = "pin-project-lite" version = "0.2.16" @@ -783,6 +847,16 @@ dependencies = [ "portable-atomic", ] +[[package]] +name = "prettyplease" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "664ec5419c51e34154eec046ebcba56312d5a2fc3b09a06da188e1ad21afadf6" +dependencies = [ + "proc-macro2", + "syn", +] + [[package]] name = "primal-check" version = "0.3.4" @@ -916,6 +990,28 @@ dependencies = [ "version_check", ] +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags 2.9.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "serde" version = "1.0.219" @@ -1128,6 +1224,16 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "wasm-bindgen" version = "0.2.100" @@ -1195,6 +1301,18 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix", +] + [[package]] name = "winapi" version = "0.3.9" @@ -1211,6 +1329,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.52.0", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" @@ -1327,6 +1454,15 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-targets" version = "0.52.6" diff --git a/Cargo.toml b/Cargo.toml index ecdb1c0..00fe84d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ rust-version = "1.80" license = "MIT" [dependencies] +bitflags = "2.9.0" duplicate = "2.0.0" fixed-resample = "0.8.0" libspa = { version = "0.8.0", optional = true } @@ -27,11 +28,11 @@ cfg_aliases = "0.2.1" [features] pipewire = ["dep:pipewire", "dep:libspa", "dep:libspa-sys", "dep:zerocopy"] -asio = ["asio-sys", "num-traits"] +asio = ["dep:asio-sys", "dep:num-traits"] [target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd"))'.dependencies] alsa = "0.9.1" -libc = "0.2.171" +libc = "0.2.172" nix = "0.29.0" pipewire = { version = "0.8.0", optional = true } diff --git a/src/backends/asio/device.rs b/src/backends/asio/device.rs index e57ecbd..03c2dd4 100644 --- a/src/backends/asio/device.rs +++ b/src/backends/asio/device.rs @@ -36,9 +36,9 @@ impl AsioDevice { let is_input = driver.channels()?.ins > 0; let is_output = driver.channels()?.outs > 0; let device_type = match (is_input, is_output) { - (true, true) => DeviceType::Duplex, - (true, false) => DeviceType::Input, - (false, true) => DeviceType::Output, + (true, true) => DeviceType::DUPLEX, + (true, false) => DeviceType::INPUT, + (false, true) => DeviceType::OUTPUT, // todo (false, false) => return Err(AsioError::BackendError(asio::AsioError::NoDrivers)), }; @@ -53,6 +53,10 @@ impl AsioDevice { }) } + pub fn device_type(&self) -> DeviceType { + self.device_type + } + /// Create an input stream with the given configuration. fn create_input_stream(&self, stream_config: StreamConfig) -> Result { let num_channels = stream_config.channels as usize; @@ -124,21 +128,19 @@ impl AudioDevice for AsioDevice { Cow::Borrowed(self.driver.name()) } - fn device_type(&self) -> DeviceType { - self.device_type - } - fn is_config_supported(&self, config: &StreamConfig) -> bool { let sample_rate = config.samplerate; self.driver.can_sample_rate(sample_rate).unwrap_or(false) && self.driver.channels().is_ok_and(|channels| { let num_channels = config.channels as i32; - match self.device_type { - DeviceType::Input => channels.ins >= num_channels, - DeviceType::Output => channels.outs >= num_channels, - DeviceType::Duplex => { - channels.ins >= num_channels && channels.outs >= num_channels - } + if self.device_type.contains(DeviceType::DUPLEX) { + channels.ins >= num_channels && channels.outs >= num_channels + } else if self.device_type.contains(DeviceType::INPUT) { + channels.ins >= num_channels + } else if self.device_type.contains(DeviceType::OUTPUT) { + channels.outs >= num_channels + } else { + false } }) && self.driver.buffersize_range().is_ok_and(|(min, max)| { @@ -152,30 +154,38 @@ impl AudioDevice for AsioDevice { } fn enumerate_configurations(&self) -> Option> { - let sample_rates = [ - 44100.0, 48000.0, 96000.0, 192000.0, 384000.0, 768000.0, - ]; - - let buffer_size_range = self.driver.buffersize_range().ok().map(|(min, max)| { - (Some(min as usize), Some(max as usize)) - })?; - - let channels = self.driver.channels().ok().map(|channels| { - match self.device_type { - DeviceType::Input => channels.ins as u32, - DeviceType::Output => channels.outs as u32, - DeviceType::Duplex => channels.ins.max(channels.outs) as u32, - + let sample_rates = [44100.0, 48000.0, 96000.0, 192000.0, 384000.0, 768000.0]; + + let buffer_size_range = self + .driver + .buffersize_range() + .ok() + .map(|(min, max)| (Some(min as usize), Some(max as usize)))?; + + let channels = self.driver.channels().ok().and_then(|channels| { + if self.device_type.contains(DeviceType::DUPLEX) { + Some(channels.ins.max(channels.outs) as u32) + } else if self.device_type.contains(DeviceType::INPUT) { + Some(channels.ins as u32) + } else if self.device_type.contains(DeviceType::OUTPUT) { + Some(channels.outs as u32) + } else { + // Return None if device type is neither input nor output + None } })?; - + Some(sample_rates.into_iter().filter_map(move |samplerate| { - self.driver.can_sample_rate(samplerate).ok().filter(|&can| can).map(|_| StreamConfig { - channels, - samplerate, - buffer_size_range, - exclusive: false, - }) + self.driver + .can_sample_rate(samplerate) + .ok() + .filter(|&can| can) + .map(|_| StreamConfig { + channels, + samplerate, + buffer_size_range, + exclusive: false, + }) })) } } @@ -672,8 +682,7 @@ unsafe fn asio_channel_slice_mut( channel_index: usize, ) -> &mut [T] { let buffer_size = asio_stream.buffer_size as usize; - let buff_ptr: *mut T = - asio_stream.buffer_infos[channel_index].buffers[buffer_index] as *mut _; + let buff_ptr: *mut T = asio_stream.buffer_infos[channel_index].buffers[buffer_index] as *mut _; std::slice::from_raw_parts_mut(buff_ptr, buffer_size) } diff --git a/src/backends/asio/driver.rs b/src/backends/asio/driver.rs index 33039c5..1901356 100644 --- a/src/backends/asio/driver.rs +++ b/src/backends/asio/driver.rs @@ -2,11 +2,7 @@ use std::{borrow::Cow, sync::Arc}; use asio_sys as asio; -use crate::{ - device::{AudioDevice, DeviceType}, - driver::AudioDriver, -}; - +use crate::{device::DeviceType, driver::AudioDriver}; use super::{device::AsioDevice, error::AsioError}; /// The ASIO driver. @@ -36,12 +32,7 @@ impl AudioDriver for AsioDriver { fn default_device(&self, device_type: DeviceType) -> Result, Self::Error> { let mut iter = AsioDeviceList::new(self.asio.clone())?; - let dd = iter - .find(|device| match (device.device_type(), device_type) { - (DeviceType::Input | DeviceType::Duplex, DeviceType::Input) => true, - (DeviceType::Output | DeviceType::Duplex, DeviceType::Output) => true, - (a, b) => a == b, - }); + let dd = iter.find(|device| device_type.intersects(device.device_type())); Ok(dd) } diff --git a/src/backends/wasapi/stream.rs b/src/backends/wasapi/stream.rs index 0317552..be10dd3 100644 --- a/src/backends/wasapi/stream.rs +++ b/src/backends/wasapi/stream.rs @@ -3,7 +3,8 @@ use crate::audio_buffer::{AudioMut, AudioRef}; use crate::backends::wasapi::util::WasapiMMDevice; use crate::channel_map::Bitset; use crate::stream::{ - AudioCallbackContext, AudioInput, AudioInputCallback, AudioOutput, AudioOutputCallback, AudioStreamHandle, StreamConfig + AudioCallbackContext, AudioInput, AudioInputCallback, AudioOutput, AudioOutputCallback, + AudioStreamHandle, StreamConfig, }; use crate::timestamp::Timestamp; use duplicate::duplicate_item; diff --git a/src/device.rs b/src/device.rs index 8ae8cd2..77b2d6d 100644 --- a/src/device.rs +++ b/src/device.rs @@ -1,7 +1,7 @@ -use bitflags::bitflags; use crate::duplex::AudioDuplexCallback; use crate::stream::{AudioInputCallback, AudioOutputCallback, AudioStreamHandle, StreamConfig}; use crate::SendEverywhereButOnWeb; +use bitflags::bitflags; use std::borrow::Cow; /// Trait for types describing audio devices. Audio devices have zero or more inputs and outputs, @@ -130,7 +130,6 @@ pub trait AudioDuplexDevice: AudioDevice { } } - bitflags! { /// Represents the types/capabilities of an audio device. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] @@ -155,7 +154,6 @@ bitflags! { } } - impl DeviceType { /// Returns true if this device type has the input capability. pub fn is_input(&self) -> bool { diff --git a/src/duplex.rs b/src/duplex.rs index 4a3e9b9..6cd4e9b 100644 --- a/src/duplex.rs +++ b/src/duplex.rs @@ -2,9 +2,7 @@ //! //! This module includes a proxy for gathering an input audio stream, and optionally process it to resample it to the //! output sample rate. -use crate::audio_buffer::AudioRef; -use crate::channel_map::Bitset; -use crate::device::{AudioDevice, AudioInputDevice, AudioOutputDevice}; +use crate::device::{AudioInputDevice, AudioOutputDevice}; use crate::stream::{ AudioCallbackContext, AudioInputCallback, AudioOutputCallback, AudioStreamHandle, StreamConfig, }; diff --git a/src/lib.rs b/src/lib.rs index c26369b..163b950 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,8 +7,8 @@ use std::fmt::Formatter; use crate::audio_buffer::{AudioMut, AudioRef}; use crate::channel_map::ChannelMap32; -use crate::timestamp::Timestamp; use crate::device::DeviceType; +use crate::timestamp::Timestamp; pub mod audio_buffer; pub mod backends; @@ -20,7 +20,6 @@ pub mod prelude; pub mod stream; pub mod timestamp; - /// Audio drivers provide access to the inputs and outputs of devices. /// Several drivers might provide the same accesses, some sharing it with other applications, /// while others work in exclusive mode. @@ -45,7 +44,6 @@ pub trait AudioDriver { fn list_devices(&self) -> Result, Self::Error>; } - /// Configuration for an audio stream. #[derive(Debug, Clone, Copy, PartialEq)] pub struct StreamConfig {