diff --git a/CHANGELOG.md b/CHANGELOG.md index e7cc8e2d1..5102ed486 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -54,6 +54,7 @@ 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. +- **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 16f3af863..cbade5650 100644 --- a/asio-sys/build.rs +++ b/asio-sys/build.rs @@ -238,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/host/asio/mod.rs b/src/host/asio/mod.rs index aea5675b6..45c362bb4 100644 --- a/src/host/asio/mod.rs +++ b/src/host/asio/mod.rs @@ -26,7 +26,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 new file mode 100644 index 000000000..222a9f6b8 --- /dev/null +++ b/src/platform/asio.rs @@ -0,0 +1,64 @@ +//! Implementations for ASIO-specific device functionality. + +#[allow(unused_imports)] +use crate::BackendSpecificError; +use crate::Device; + +/// Extension trait to get ASIO device. +pub trait AsioDeviceExt { + 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, + /// 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. + pub fn open_control_panel(&self) -> Result<(), BackendSpecificError> { + use crate::host::asio::GLOBAL_ASIO; + + let description = self.0.description().map_err(|e| BackendSpecificError { + description: format!("{e:?}"), + })?; + let driver_name = description.name(); + + 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:?}"), + }) + } +} + +impl AsioDeviceExt for Device { + 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, + } + } +} diff --git a/src/platform/mod.rs b/src/platform/mod.rs index f738fdd0e..38bc89415 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