Skip to content
Open
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,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
Expand Down
1 change: 1 addition & 0 deletions asio-sys/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,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")
Expand Down
5 changes: 5 additions & 0 deletions asio-sys/src/bindings/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -924,6 +924,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 {
Expand Down
2 changes: 1 addition & 1 deletion src/host/asio/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Arc<sys::Asio>> = OnceLock::new();
pub(crate) static GLOBAL_ASIO: OnceLock<Arc<sys::Asio>> = OnceLock::new();

/// The host for ASIO.
#[derive(Debug)]
Expand Down
72 changes: 72 additions & 0 deletions src/platform/asio.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
//! Implementations for ASIO-specific device functionality.

#[allow(unused_imports)]
use crate::BackendSpecificError;
use crate::Device;
#[allow(unused_imports)]
use std::marker::PhantomData;

/// 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<AsioDevice<'_>>;
}

/// 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: 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<(), BackendSpecificError> {
#[cfg(all(target_os = "windows", feature = "asio"))]
{
use crate::host::asio::GLOBAL_ASIO;

let description = self.inner.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:?}"),
})
}

#[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<AsioDevice<'_>> {
match self.as_inner() {
#[cfg(all(target_os = "windows", feature = "asio"))]
crate::platform::DeviceInner::Asio(d) => Some(AsioDevice { inner: d }),
_ => None,
}
}
}
2 changes: 2 additions & 0 deletions src/platform/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading