From 73f429481404e046f3a5175ba77a64010033f655 Mon Sep 17 00:00:00 2001 From: Sintel Date: Fri, 30 Jan 2026 19:05:37 +0100 Subject: [PATCH 1/4] ASIO: Extension trait + open control panel --- CHANGELOG.md | 1 + asio-sys/build.rs | 2 ++ asio-sys/src/bindings/mod.rs | 5 +++ src/platform/asio.rs | 67 ++++++++++++++++++++++++++++++++++++ src/platform/mod.rs | 2 ++ 5 files changed, 77 insertions(+) create mode 100644 src/platform/asio.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 5dce15ba3..f89c70c0b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `DeviceBusy` error variant for retriable device access errors (EBUSY, EAGAIN). - **ALSA**: `Debug` implementations for `Host`, `Device`, `Stream`, and internal types. - **ALSA**: Example demonstrating ALSA error suppression during enumeration. +- **ASIO**: Extension trait for ASIO devices, which allows opening the control panel. - **WASAPI**: Allow non-native sample rates to be used via as-necessary resampling in the WASAPI server process. ### Changed diff --git a/asio-sys/build.rs b/asio-sys/build.rs index 53b446e98..9dfb08f6b 100644 --- a/asio-sys/build.rs +++ b/asio-sys/build.rs @@ -72,6 +72,7 @@ fn main() { // Print out links to needed libraries println!("cargo:rustc-link-lib=dylib=ole32"); println!("cargo:rustc-link-lib=dylib=user32"); + println!("cargo:rustc-link-lib=dylib=Advapi32"); println!("cargo:rustc-link-search={}", out_dir.display()); println!("cargo:rustc-link-lib=static=asio"); println!("cargo:rustc-cfg=asio"); @@ -237,6 +238,7 @@ fn create_bindings(cpal_asio_dir: &PathBuf) { .allowlist_function("ASIOStart") .allowlist_function("ASIOStop") .allowlist_function("ASIODisposeBuffers") + .allowlist_function("ASIOControlPanel") .allowlist_function("ASIOExit") .allowlist_function("load_asio_driver") .allowlist_function("remove_current_driver") diff --git a/asio-sys/src/bindings/mod.rs b/asio-sys/src/bindings/mod.rs index 83b5d3ef5..fd07ffadb 100644 --- a/asio-sys/src/bindings/mod.rs +++ b/asio-sys/src/bindings/mod.rs @@ -788,6 +788,11 @@ impl Driver { let mut mcb = MESSAGE_CALLBACKS.lock().unwrap(); mcb.retain(|&(id, _)| id != rem_id); } + + /// Opens the ASIO driver's control panel window. + pub fn open_control_panel(&self) -> Result<(), AsioError> { + unsafe { asio_result!(ai::ASIOControlPanel()) } + } } impl DriverState { diff --git a/src/platform/asio.rs b/src/platform/asio.rs new file mode 100644 index 000000000..a8c4ef154 --- /dev/null +++ b/src/platform/asio.rs @@ -0,0 +1,67 @@ +//! Implementations for ASIO-specific device functionality. + +use crate::BackendSpecificError; +use crate::Device; + +/// Extension trait for ASIO-specific device functionality. +pub trait AsioDeviceExt { + /// Returns `true` if this device is an ASIO device. + fn is_asio_device(&self) -> bool; + + /// Opens the ASIO driver's control panel window. + /// + /// This provides access to device-specific settings like buffer size, + /// sample rate, input/output routing, and hardware-specific features. + /// + /// # Blocking Behavior + /// + /// **WARNING**: This call may block until the user closes the control panel. + /// Consider spawning a thread to avoid blocking the main thread. + /// + /// # Errors + /// + /// Returns an error if this device is not an ASIO device. + fn asio_open_control_panel(&self) -> Result<(), BackendSpecificError>; +} + +#[cfg(all(target_os = "windows", feature = "asio"))] +impl AsioDeviceExt for Device { + fn is_asio_device(&self) -> bool { + matches!(self.as_inner(), crate::platform::DeviceInner::Asio(_)) + } + + fn asio_open_control_panel(&self) -> Result<(), BackendSpecificError> { + use crate::platform::DeviceInner; + + if let DeviceInner::Asio(ref asio_device) = self.as_inner() { + asio_device + .driver + .open_control_panel() + .map_err(|e| BackendSpecificError { + description: format!("Failed to open control panel: {:?}", e), + }) + } else { + Err(BackendSpecificError { + description: "Not an ASIO device".to_string(), + }) + } + } +} + +#[cfg(not(all(target_os = "windows", feature = "asio")))] +impl AsioDeviceExt for Device { + fn is_asio_device(&self) -> bool { + false + } + + fn asio_open_control_panel(&self) -> Result<(), BackendSpecificError> { + Err(not_available()) + } +} + +#[cfg(not(all(target_os = "windows", feature = "asio")))] +fn not_available() -> BackendSpecificError { + BackendSpecificError { + description: "ASIO is not available on this platform".to_string(), + } +} diff --git a/src/platform/mod.rs b/src/platform/mod.rs index 0f62026d7..da5f5f973 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -10,6 +10,8 @@ pub use self::platform_impl::*; #[cfg(feature = "custom")] pub use crate::host::custom::{Device as CustomDevice, Host as CustomHost, Stream as CustomStream}; +pub mod asio; + /// A macro to assist with implementing a platform's dynamically dispatched [`Host`] type. /// /// These dynamically dispatched types are necessary to allow for users to switch between hosts at From 3c20149501b138cb498c18a4d8e8a870db23357d Mon Sep 17 00:00:00 2001 From: Sintel Date: Mon, 2 Mar 2026 15:34:13 +0100 Subject: [PATCH 2/4] fix driver loading in device --- src/host/asio/mod.rs | 2 +- src/platform/asio.rs | 20 ++++++++++++++++++-- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/host/asio/mod.rs b/src/host/asio/mod.rs index 87e3adfea..1a4559fae 100644 --- a/src/host/asio/mod.rs +++ b/src/host/asio/mod.rs @@ -25,7 +25,7 @@ mod stream; /// /// ASIO only supports loading a single driver at a time globally, so all Host instances /// must share the same underlying sys::Asio wrapper to properly coordinate driver access. -static GLOBAL_ASIO: OnceLock> = OnceLock::new(); +pub(crate) static GLOBAL_ASIO: OnceLock> = OnceLock::new(); /// The host for ASIO. #[derive(Debug)] diff --git a/src/platform/asio.rs b/src/platform/asio.rs index a8c4ef154..8741f37a6 100644 --- a/src/platform/asio.rs +++ b/src/platform/asio.rs @@ -31,11 +31,27 @@ impl AsioDeviceExt for Device { } fn asio_open_control_panel(&self) -> Result<(), BackendSpecificError> { + use crate::host::asio::GLOBAL_ASIO; use crate::platform::DeviceInner; if let DeviceInner::Asio(ref asio_device) = self.as_inner() { - asio_device - .driver + let description = asio_device + .description() + .map_err(|e| BackendSpecificError { + description: format!("Could not get device name: {:?}", e), + })?; + + let driver = GLOBAL_ASIO + .get() + .ok_or(BackendSpecificError { + description: "ASIO not initialized.".into(), + })? + .load_driver(description.name()) + .map_err(|e| BackendSpecificError { + description: format!("Failed to load driver: {:?}", e), + })?; + + driver .open_control_panel() .map_err(|e| BackendSpecificError { description: format!("Failed to open control panel: {:?}", e), From a9f89aa74777ce71f71c6c7913578e7c65d6293c Mon Sep 17 00:00:00 2001 From: Sintel Date: Mon, 2 Mar 2026 23:58:39 +0100 Subject: [PATCH 3/4] review fixes --- CHANGELOG.md | 3 +-- asio-sys/build.rs | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 74adaf2a3..2d88cd577 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,9 +43,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **ALSA**: `Debug` implementations for `Host`, `Device`, `Stream`, and internal types. - **ALSA**: Example demonstrating ALSA error suppression during enumeration. - **ALSA**: Support for native DSD playback. -- **WASAPI**: Enable as-necessary resampling in the WASAPI server process. - **ASIO**: Extension trait for ASIO devices, which allows opening the control panel. - +- **WASAPI**: Enable as-necessary resampling in the WASAPI server process. ### Changed diff --git a/asio-sys/build.rs b/asio-sys/build.rs index 27166fd8c..cbade5650 100644 --- a/asio-sys/build.rs +++ b/asio-sys/build.rs @@ -73,7 +73,6 @@ fn main() { println!("cargo:rustc-link-lib=dylib=advapi32"); println!("cargo:rustc-link-lib=dylib=ole32"); println!("cargo:rustc-link-lib=dylib=user32"); - println!("cargo:rustc-link-lib=dylib=Advapi32"); println!("cargo:rustc-link-search={}", out_dir.display()); println!("cargo:rustc-link-lib=static=asio"); println!("cargo:rustc-cfg=asio"); From 8307defa411f2437816fdeec8dc4dc107c4c27d3 Mon Sep 17 00:00:00 2001 From: Sintel Date: Tue, 17 Mar 2026 02:20:11 +0100 Subject: [PATCH 4/4] new API --- src/platform/asio.rs | 87 +++++++++++++++++--------------------------- 1 file changed, 34 insertions(+), 53 deletions(-) diff --git a/src/platform/asio.rs b/src/platform/asio.rs index 8741f37a6..222a9f6b8 100644 --- a/src/platform/asio.rs +++ b/src/platform/asio.rs @@ -1,13 +1,23 @@ //! Implementations for ASIO-specific device functionality. +#[allow(unused_imports)] use crate::BackendSpecificError; use crate::Device; -/// Extension trait for ASIO-specific device functionality. +/// Extension trait to get ASIO device. pub trait AsioDeviceExt { - /// Returns `true` if this device is an ASIO device. - fn is_asio_device(&self) -> bool; + fn as_asio(&self) -> Option>; +} + +/// Struct containing for ASIO-specific device functionality. +#[cfg(all(target_os = "windows", feature = "asio"))] +pub struct AsioDevice<'a>(&'a crate::host::asio::Device); +#[cfg(not(all(target_os = "windows", feature = "asio")))] +pub struct AsioDevice<'a>(std::marker::PhantomData<&'a ()>); + +#[cfg(all(target_os = "windows", feature = "asio"))] +impl AsioDevice<'_> { /// Opens the ASIO driver's control panel window. /// /// This provides access to device-specific settings like buffer size, @@ -21,63 +31,34 @@ pub trait AsioDeviceExt { /// # Errors /// /// Returns an error if this device is not an ASIO device. - fn asio_open_control_panel(&self) -> Result<(), BackendSpecificError>; -} - -#[cfg(all(target_os = "windows", feature = "asio"))] -impl AsioDeviceExt for Device { - fn is_asio_device(&self) -> bool { - matches!(self.as_inner(), crate::platform::DeviceInner::Asio(_)) - } - - fn asio_open_control_panel(&self) -> Result<(), BackendSpecificError> { + pub fn open_control_panel(&self) -> Result<(), BackendSpecificError> { use crate::host::asio::GLOBAL_ASIO; - use crate::platform::DeviceInner; - - if let DeviceInner::Asio(ref asio_device) = self.as_inner() { - let description = asio_device - .description() - .map_err(|e| BackendSpecificError { - description: format!("Could not get device name: {:?}", e), - })?; - let driver = GLOBAL_ASIO - .get() - .ok_or(BackendSpecificError { - description: "ASIO not initialized.".into(), - })? - .load_driver(description.name()) - .map_err(|e| BackendSpecificError { - description: format!("Failed to load driver: {:?}", e), - })?; + let description = self.0.description().map_err(|e| BackendSpecificError { + description: format!("{e:?}"), + })?; + let driver_name = description.name(); - driver - .open_control_panel() - .map_err(|e| BackendSpecificError { - description: format!("Failed to open control panel: {:?}", e), - }) - } else { - Err(BackendSpecificError { - description: "Not an ASIO device".to_string(), + GLOBAL_ASIO + .get() + .expect("GLOBAL_ASIO is always set when an ASIO Device exists") + .load_driver(driver_name) + .map_err(|e| BackendSpecificError { + description: format!("{e:?}"), + })? + .open_control_panel() + .map_err(|e| BackendSpecificError { + description: format!("{e:?}"), }) - } } } -#[cfg(not(all(target_os = "windows", feature = "asio")))] impl AsioDeviceExt for Device { - fn is_asio_device(&self) -> bool { - false - } - - fn asio_open_control_panel(&self) -> Result<(), BackendSpecificError> { - Err(not_available()) - } -} - -#[cfg(not(all(target_os = "windows", feature = "asio")))] -fn not_available() -> BackendSpecificError { - BackendSpecificError { - description: "ASIO is not available on this platform".to_string(), + fn as_asio(&self) -> Option> { + match self.as_inner() { + #[cfg(all(target_os = "windows", feature = "asio"))] + crate::platform::DeviceInner::Asio(d) => Some(AsioDevice(d)), + _ => None, + } } }