Skip to content
Open
Show file tree
Hide file tree
Changes from all 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 asio-sys/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,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");
Expand Down Expand Up @@ -218,6 +219,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
4 changes: 4 additions & 0 deletions asio-sys/src/bindings/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -777,6 +777,10 @@ impl Driver {
let mut mcb = MESSAGE_CALLBACKS.lock().unwrap();
mcb.retain(|&(id, _)| id != rem_id);
}

pub fn open_control_panel(&self) -> Result<(), AsioError> {
unsafe { asio_result!(ai::ASIOControlPanel()) }
}
}

impl DriverState {
Expand Down
45 changes: 45 additions & 0 deletions examples/asio_panel.rs
Copy link
Member

Choose a reason for hiding this comment

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

What do you think about making this an example in asio-sys/examples instead?

Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#[cfg(all(windows, feature = "asio"))]
fn main() -> anyhow::Result<()> {
use cpal::platform::asio::AsioDeviceExt;
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
use cpal::HostId;

let host = cpal::host_from_id(HostId::Asio)?;

let device = host
.default_output_device()
.ok_or_else(|| anyhow::anyhow!("No ASIO device found"))?;

println!(
"Opening control panel for: {}",
device.description()?.name()
);

let config = device.default_output_config()?;

let err_fn = move |err| println!("Stream Error: {:?}", err);

let stream = device.build_output_stream(
&config.config(),
move |_data: &mut [i32], _: &cpal::OutputCallbackInfo| { /* play silence */ },
err_fn,
None,
)?;

stream.play()?;

// This is a blocking call on some devices, so spawn it in its own thread.
std::thread::spawn(move || {
if let Err(e) = device.asio_open_control_panel() {
eprintln!("Could not open panel: {:?}", e);
}
});

// Keep the thread alive so the window doesn't close immediately
std::thread::sleep(std::time::Duration::from_secs(5));

Ok(())
}

#[cfg(not(all(windows, feature = "asio")))]
fn main() {}
24 changes: 24 additions & 0 deletions src/platform/asio.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
use crate::platform::DeviceInner;
use crate::BackendSpecificError;
use crate::Device;

pub trait AsioDeviceExt {
Copy link
Member

Choose a reason for hiding this comment

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

I'd like the public API to have good documentation. Proposal:

/// Extension trait for ASIO-specific device functionality.

fn asio_open_control_panel(&self) -> Result<(), BackendSpecificError>;
Copy link
Member

Choose a reason for hiding this comment

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

/// 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 blocks 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.

}

impl AsioDeviceExt for Device {
fn asio_open_control_panel(&self) -> Result<(), BackendSpecificError> {
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(),
})
}
}
}
3 changes: 3 additions & 0 deletions src/platform/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ pub use self::platform_impl::*;
#[cfg(feature = "custom")]
pub use crate::host::custom::{Device as CustomDevice, Host as CustomHost, Stream as CustomStream};

#[cfg(all(target_os = "windows", feature = "asio"))]
Copy link
Member

Choose a reason for hiding this comment

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

For cross-platform compatibility in downstream code, the configuration guard should be moved from here into platform/asio.rs like so:

#[cfg(all(target_os = "windows", feature = "asio"))]
impl AsioDeviceExt for Device {
    // ...
}

#[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())
    }

    fn asio_reset_device(&self) -> Result<(), BackendSpecificError> {
        Err(not_available())
    }
}

fn not_available() -> BackendSpecificError {
    BackendSpecificError {
        description: "ASIO is not available on this platform".to_string(),
    }
}

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