Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- **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.


### Changed

Expand Down
2 changes: 2 additions & 0 deletions asio-sys/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ 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");
Expand Down Expand Up @@ -238,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 @@ -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 {
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 @@ -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<Arc<sys::Asio>> = OnceLock::new();
pub(crate) static GLOBAL_ASIO: OnceLock<Arc<sys::Asio>> = OnceLock::new();

/// The host for ASIO.
#[derive(Debug)]
Expand Down
83 changes: 83 additions & 0 deletions src/platform/asio.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
//! 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"))]
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's make it discoverable on docs.rs:

#[cfg_attr(docsrs, doc(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::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 {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe expect here instead of returning silently - this should never be possible when we've got a Device constructed.

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),
})
} else {
Err(BackendSpecificError {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should use unreachable here instead.

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(),
}
}
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