diff --git a/CHANGELOG.md b/CHANGELOG.md index 6631262bc..be29170b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -139,6 +139,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 ee085ee83..ba1f7aff0 100644 --- a/asio-sys/build.rs +++ b/asio-sys/build.rs @@ -224,6 +224,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 360546754..6965155bd 100644 --- a/asio-sys/src/bindings/mod.rs +++ b/asio-sys/src/bindings/mod.rs @@ -926,6 +926,11 @@ impl Driver { let mut dcb = DRIVER_EVENT_CALLBACKS.lock().unwrap(); dcb.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/device.rs b/src/host/asio/device.rs index 26fc0c658..21cde158f 100644 --- a/src/host/asio/device.rs +++ b/src/host/asio/device.rs @@ -6,12 +6,12 @@ use std::{ use super::sys; pub use crate::iter::{SupportedInputConfigs, SupportedOutputConfigs}; use crate::{ - host::com, ChannelCount, DeviceDescription, DeviceDescriptionBuilder, DeviceId, Error, - ErrorKind, FrameCount, SampleFormat, SampleRate, SupportedBufferSize, SupportedStreamConfig, - SupportedStreamConfigRange, + host::asio::stream::load_driver_err, host::com, ChannelCount, DeviceDescription, + DeviceDescriptionBuilder, DeviceId, Error, ErrorKind, FrameCount, SampleFormat, SampleRate, + SupportedBufferSize, SupportedStreamConfig, SupportedStreamConfigRange, }; -/// A ASIO Device +/// An ASIO Device #[derive(Clone)] pub struct Device { name: String, @@ -140,6 +140,18 @@ impl Device { } configs } + + /// Opens the ASIO driver's control panel window. + pub fn open_control_panel(&self) -> Result<(), Error> { + com::com_initialized(); + super::GLOBAL_ASIO + .get() + .expect("GLOBAL_ASIO is always set when an ASIO device exists") + .load_driver(&self.name) + .map_err(load_driver_err)? + .open_control_panel() + .map_err(|e| Error::with_message(ErrorKind::UnsupportedOperation, format!("{e:?}"))) + } } impl Devices { diff --git a/src/host/asio/stream.rs b/src/host/asio/stream.rs index 51da63646..3f5115c10 100644 --- a/src/host/asio/stream.rs +++ b/src/host/asio/stream.rs @@ -1092,7 +1092,7 @@ unsafe fn asio_channel_slice_mut( std::slice::from_raw_parts_mut(buff_ptr, channel_length) } -fn load_driver_err(e: sys::LoadDriverError) -> Error { +pub(crate) fn load_driver_err(e: sys::LoadDriverError) -> Error { match e { sys::LoadDriverError::LoadDriverFailed | sys::LoadDriverError::DriverAlreadyExists => { Error::with_message(ErrorKind::DeviceNotAvailable, e.to_string()) diff --git a/src/platform/asio.rs b/src/platform/asio.rs new file mode 100644 index 000000000..13517b449 --- /dev/null +++ b/src/platform/asio.rs @@ -0,0 +1,51 @@ +//! Implementations for ASIO-specific device functionality. + +use crate::{Device, Error}; + +/// Extension trait to get the ASIO device. +pub trait AsioDeviceExt { + /// Returns the [AsioDevice] interface if this is an ASIO device. + fn as_asio(&self) -> Option>; +} + +/// A wrapper providing access to ASIO-specific device functionality. +#[derive(Clone)] +pub struct AsioDevice<'a> { + #[cfg(all(target_os = "windows", feature = "asio"))] + inner: &'a crate::host::asio::Device, + + // Dummy marker for lifetime 'a. + #[cfg(not(all(target_os = "windows", feature = "asio")))] + _marker: std::marker::PhantomData<&'a ()>, +} + +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 + /// + /// This call may block until the user closes the control panel. + /// Consider spawning a thread to avoid blocking the main thread. + pub fn open_control_panel(&self) -> Result<(), Error> { + #[cfg(all(target_os = "windows", feature = "asio"))] + { + self.inner.open_control_panel() + } + + #[cfg(not(all(target_os = "windows", feature = "asio")))] + unreachable!("AsioDevice cannot be constructed on non-ASIO platforms") + } +} + +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 { inner: d }), + _ => None, + } + } +} diff --git a/src/platform/mod.rs b/src/platform/mod.rs index b3f618007..5bc188c17 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -9,6 +9,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