From 8cc6f25ff7f584786b19ba62968e8e6d25ccd0c4 Mon Sep 17 00:00:00 2001 From: xephyris <114379647+GoeyGreen@users.noreply.github.com> Date: Sat, 13 Sep 2025 23:10:46 -0700 Subject: [PATCH 01/22] feat: add device_id support for macOS to DeviceTrait --- src/error.rs | 25 +++++++++++++++++++++++++ src/host/aaudio/mod.rs | 4 ++++ src/host/alsa/mod.rs | 9 +++++++++ src/host/asio/device.rs | 4 ++++ src/host/asio/mod.rs | 4 ++++ src/host/coreaudio/ios/mod.rs | 11 ++++++++++- src/host/coreaudio/macos/mod.rs | 14 +++++++++----- src/host/emscripten/mod.rs | 9 +++++++++ src/host/jack/device.rs | 4 ++++ src/host/null/mod.rs | 10 ++++++---- src/host/wasapi/device.rs | 8 ++++++++ src/host/webaudio/mod.rs | 10 ++++++++++ src/platform/mod.rs | 9 +++++++++ src/traits.rs | 5 ++++- 14 files changed, 115 insertions(+), 11 deletions(-) diff --git a/src/error.rs b/src/error.rs index 2fed3622b..1a5886793 100644 --- a/src/error.rs +++ b/src/error.rs @@ -65,6 +65,31 @@ impl From for DevicesError { } } +/// An error that may occur while attempting to retrieve a device id. +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub enum DeviceIdError { + /// See the [`BackendSpecificError`] docs for more information about this error variant. + BackendSpecific { err: BackendSpecificError }, + UnsupportedOS, +} + +impl Display for DeviceIdError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + Self::BackendSpecific { err } => err.fmt(f), + Self::UnsupportedOS => {f.write_str("Device ids are unsupported for this OS")} + } + } +} + +impl Error for DeviceIdError {} + +impl From for DeviceIdError { + fn from(err: BackendSpecificError) -> Self { + Self::BackendSpecific { err } + } +} + /// An error that may occur while attempting to retrieve a device name. #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub enum DeviceNameError { diff --git a/src/host/aaudio/mod.rs b/src/host/aaudio/mod.rs index f5f024461..418cad070 100644 --- a/src/host/aaudio/mod.rs +++ b/src/host/aaudio/mod.rs @@ -327,6 +327,10 @@ impl DeviceTrait for Device { } } + fn device_id(&self) -> Result { + Err(DeviceIdError::UnsupportedOS) + } + fn supported_input_configs( &self, ) -> Result { diff --git a/src/host/alsa/mod.rs b/src/host/alsa/mod.rs index b1a29f348..38b063d3b 100644 --- a/src/host/alsa/mod.rs +++ b/src/host/alsa/mod.rs @@ -68,6 +68,10 @@ impl DeviceTrait for Device { Device::name(self) } + fn device_id(&self) -> Result { + Device::device_id(self) + } + fn supported_input_configs( &self, ) -> Result { @@ -307,6 +311,11 @@ impl Device { Ok(self.to_string()) } + #[inline] + fn device_id(&self) -> Result { + Err(DeviceIdError::UnsupportedOS) + } + fn supported_configs( &self, stream_t: alsa::Direction, diff --git a/src/host/asio/device.rs b/src/host/asio/device.rs index dd854136e..ae4db9543 100644 --- a/src/host/asio/device.rs +++ b/src/host/asio/device.rs @@ -54,6 +54,10 @@ impl Device { Ok(self.driver.name().to_string()) } + fn device_id(&self) -> Result { + Err(DeviceIdError::UnsupportedOS) + } + /// Gets the supported input configs. /// TODO currently only supports the default. /// Need to find all possible configs. diff --git a/src/host/asio/mod.rs b/src/host/asio/mod.rs index 73d936d99..3af353848 100644 --- a/src/host/asio/mod.rs +++ b/src/host/asio/mod.rs @@ -62,6 +62,10 @@ impl DeviceTrait for Device { Device::name(self) } + fn device_id(&self) -> Result { + Device::device_id(self) + } + fn supported_input_configs( &self, ) -> Result { diff --git a/src/host/coreaudio/ios/mod.rs b/src/host/coreaudio/ios/mod.rs index 753bbd099..65b3885f3 100644 --- a/src/host/coreaudio/ios/mod.rs +++ b/src/host/coreaudio/ios/mod.rs @@ -18,7 +18,7 @@ use super::{asbd_from_config, frames_to_duration, host_time_to_stream_instant}; use crate::traits::{DeviceTrait, HostTrait, StreamTrait}; use crate::{ - BackendSpecificError, BufferSize, BuildStreamError, Data, DefaultStreamConfigError, + BackendSpecificError, BufferSize, BuildStreamError, Data, DefaultStreamConfigError, DeviceIdError, DeviceNameError, DevicesError, InputCallbackInfo, OutputCallbackInfo, PauseStreamError, PlayStreamError, SampleFormat, SampleRate, StreamConfig, StreamError, SupportedBufferSize, SupportedStreamConfig, SupportedStreamConfigRange, SupportedStreamConfigsError, @@ -74,6 +74,10 @@ impl Device { Ok("Default Device".to_owned()) } + fn device_id(&self) -> Result { + Err(DeviceIdError::UnsupportedOS) + } + #[inline] fn supported_input_configs( &self, @@ -140,6 +144,11 @@ impl DeviceTrait for Device { Device::name(self) } + #[inline] + fn device_id(&self) -> Result { + Device::device_id(self) + } + #[inline] fn supported_input_configs( &self, diff --git a/src/host/coreaudio/macos/mod.rs b/src/host/coreaudio/macos/mod.rs index c0809fa2f..314047ea3 100644 --- a/src/host/coreaudio/macos/mod.rs +++ b/src/host/coreaudio/macos/mod.rs @@ -4,11 +4,7 @@ use super::{asbd_from_config, check_os_status, frames_to_duration, host_time_to_ use super::OSStatus; use crate::traits::{DeviceTrait, HostTrait, StreamTrait}; use crate::{ - BackendSpecificError, BufferSize, BuildStreamError, ChannelCount, Data, - DefaultStreamConfigError, DeviceNameError, DevicesError, InputCallbackInfo, OutputCallbackInfo, - PauseStreamError, PlayStreamError, SampleFormat, SampleRate, StreamConfig, StreamError, - SupportedBufferSize, SupportedStreamConfig, SupportedStreamConfigRange, - SupportedStreamConfigsError, + BackendSpecificError, BufferSize, BuildStreamError, ChannelCount, Data, DefaultStreamConfigError, DeviceIdError, DeviceNameError, DevicesError, InputCallbackInfo, OutputCallbackInfo, PauseStreamError, PlayStreamError, SampleFormat, SampleRate, StreamConfig, StreamError, SupportedBufferSize, SupportedStreamConfig, SupportedStreamConfigRange, SupportedStreamConfigsError }; use coreaudio::audio_unit::render_callback::{self, data}; use coreaudio::audio_unit::{AudioUnit, Element, Scope}; @@ -91,6 +87,10 @@ impl DeviceTrait for Device { Device::name(self) } + fn device_id(&self) -> Result{ + Device::id(self) + } + fn supported_input_configs( &self, ) -> Result { @@ -185,6 +185,10 @@ impl Device { }) } + fn id(&self) -> Result { + Ok(self.audio_device_id) + } + // Logic re-used between `supported_input_configs` and `supported_output_configs`. #[allow(clippy::cast_ptr_alignment)] fn supported_configs( diff --git a/src/host/emscripten/mod.rs b/src/host/emscripten/mod.rs index 4d6b3daf8..66f083f1b 100644 --- a/src/host/emscripten/mod.rs +++ b/src/host/emscripten/mod.rs @@ -69,6 +69,11 @@ impl Device { Ok("Default Device".to_owned()) } + #[inline] + fn device_id(&self) -> Result { + Err(DeviceIdError::UnsupportedOS) + } + #[inline] fn supported_input_configs( &self, @@ -144,6 +149,10 @@ impl DeviceTrait for Device { Device::name(self) } + fn device_id(&self) -> Result { + Device::device_id(self) + } + fn supported_input_configs( &self, ) -> Result { diff --git a/src/host/jack/device.rs b/src/host/jack/device.rs index 7a911dce4..3ae08c64f 100644 --- a/src/host/jack/device.rs +++ b/src/host/jack/device.rs @@ -146,6 +146,10 @@ impl DeviceTrait for Device { Ok(self.name.clone()) } + fn device_id(&self) -> Result { + Err(DeviceIdError::UnsupportedOS) + } + fn supported_input_configs( &self, ) -> Result { diff --git a/src/host/null/mod.rs b/src/host/null/mod.rs index 0a9752b70..19a464ebd 100644 --- a/src/host/null/mod.rs +++ b/src/host/null/mod.rs @@ -2,10 +2,7 @@ use std::time::Duration; use crate::traits::{DeviceTrait, HostTrait, StreamTrait}; use crate::{ - BuildStreamError, Data, DefaultStreamConfigError, DeviceNameError, DevicesError, - InputCallbackInfo, OutputCallbackInfo, PauseStreamError, PlayStreamError, SampleFormat, - StreamConfig, StreamError, SupportedStreamConfig, SupportedStreamConfigRange, - SupportedStreamConfigsError, + BuildStreamError, Data, DefaultStreamConfigError, DeviceIdError, DeviceNameError, DevicesError, InputCallbackInfo, OutputCallbackInfo, PauseStreamError, PlayStreamError, SampleFormat, StreamConfig, StreamError, SupportedStreamConfig, SupportedStreamConfigRange, SupportedStreamConfigsError }; #[derive(Default)] @@ -47,6 +44,11 @@ impl DeviceTrait for Device { Ok("null".to_owned()) } + #[inline] + fn device_id(&self) -> Result { + Err(DeviceIdError::UnsupportedOS) + } + #[inline] fn supported_input_configs( &self, diff --git a/src/host/wasapi/device.rs b/src/host/wasapi/device.rs index 4e4cf14b8..232c0fa89 100644 --- a/src/host/wasapi/device.rs +++ b/src/host/wasapi/device.rs @@ -58,6 +58,10 @@ impl DeviceTrait for Device { Device::name(self) } + fn device_id(&self) -> Result { + Device::device_id(self) + } + fn supports_input(&self) -> bool { self.data_flow() == Audio::eCapture } @@ -321,6 +325,10 @@ impl Device { } } + fn device_id(&self) -> Result { + Err(DeviceIdError::UnsupportedOS) + } + #[inline] fn from_immdevice(device: Audio::IMMDevice) -> Self { Device { diff --git a/src/host/webaudio/mod.rs b/src/host/webaudio/mod.rs index fb835b735..2bda974d4 100644 --- a/src/host/webaudio/mod.rs +++ b/src/host/webaudio/mod.rs @@ -84,6 +84,11 @@ impl Device { Ok("Default Device".to_owned()) } + #[inline] + fn device_id(&self) -> Result { + Err(DeviceIdError::UnsupportedOS) + } + #[inline] fn supported_input_configs( &self, @@ -142,6 +147,11 @@ impl DeviceTrait for Device { Device::name(self) } + #[inline] + fn device_id(&self) -> Result { + Device::device_id(self) + } + #[inline] fn supported_input_configs( &self, diff --git a/src/platform/mod.rs b/src/platform/mod.rs index fd12eaaac..783ba4fe6 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -314,6 +314,15 @@ macro_rules! impl_platform_host { } } + fn device_id(&self) -> Result { + match self.0 { + $( + $(#[cfg($feat)])? + DeviceInner::$HostVariant(ref d) => d.device_id(), + )* + } + } + fn supports_input(&self) -> bool { match self.0 { $( diff --git a/src/traits.rs b/src/traits.rs index 2f1bd3469..c824e3ae2 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -3,7 +3,7 @@ use std::time::Duration; use crate::{ - BuildStreamError, Data, DefaultStreamConfigError, DeviceNameError, DevicesError, + BuildStreamError, Data, DefaultStreamConfigError, DeviceIdError, DeviceNameError, DevicesError, InputCallbackInfo, InputDevices, OutputCallbackInfo, OutputDevices, PauseStreamError, PlayStreamError, SampleFormat, SizedSample, StreamConfig, StreamError, SupportedStreamConfig, SupportedStreamConfigRange, SupportedStreamConfigsError, @@ -89,6 +89,9 @@ pub trait DeviceTrait { /// The human-readable name of the device. fn name(&self) -> Result; + /// The device-id of the device. (For supported OS's only) + fn device_id(&self) -> Result; + /// True if the device supports audio input, otherwise false fn supports_input(&self) -> bool { self.supported_input_configs() From 9161bbb533009e29e9f323987dcb29fa4f7d63d3 Mon Sep 17 00:00:00 2001 From: xephyris <114379647+xephyris@users.noreply.github.com> Date: Sun, 14 Sep 2025 22:51:02 -0700 Subject: [PATCH 02/22] fix: fix import statements for other APIs --- src/host/aaudio/mod.rs | 2 +- src/host/alsa/mod.rs | 2 +- src/host/asio/device.rs | 1 + src/host/emscripten/mod.rs | 2 +- src/host/jack/device.rs | 2 +- src/host/wasapi/device.rs | 2 +- src/host/webaudio/mod.rs | 2 +- 7 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/host/aaudio/mod.rs b/src/host/aaudio/mod.rs index 418cad070..366a03522 100644 --- a/src/host/aaudio/mod.rs +++ b/src/host/aaudio/mod.rs @@ -12,7 +12,7 @@ use java_interface::{AudioDeviceDirection, AudioDeviceInfo}; use crate::traits::{DeviceTrait, HostTrait, StreamTrait}; use crate::{ BackendSpecificError, BufferSize, BuildStreamError, Data, DefaultStreamConfigError, - DeviceNameError, DevicesError, InputCallbackInfo, InputStreamTimestamp, OutputCallbackInfo, + DeviceNameError, DeviceIdError, DevicesError, InputCallbackInfo, InputStreamTimestamp, OutputCallbackInfo, OutputStreamTimestamp, PauseStreamError, PlayStreamError, SampleFormat, SampleRate, SizedSample, StreamConfig, StreamError, SupportedBufferSize, SupportedStreamConfig, SupportedStreamConfigRange, SupportedStreamConfigsError, diff --git a/src/host/alsa/mod.rs b/src/host/alsa/mod.rs index 38b063d3b..f64c88be7 100644 --- a/src/host/alsa/mod.rs +++ b/src/host/alsa/mod.rs @@ -16,7 +16,7 @@ pub use self::enumerate::{default_input_device, default_output_device, Devices}; use crate::{ traits::{DeviceTrait, HostTrait, StreamTrait}, BackendSpecificError, BufferSize, BuildStreamError, ChannelCount, Data, - DefaultStreamConfigError, DeviceNameError, DevicesError, FrameCount, InputCallbackInfo, + DefaultStreamConfigError, DeviceNameError, DeviceIdError, DevicesError, FrameCount, InputCallbackInfo, OutputCallbackInfo, PauseStreamError, PlayStreamError, SampleFormat, SampleRate, StreamConfig, StreamError, SupportedBufferSize, SupportedStreamConfig, SupportedStreamConfigRange, SupportedStreamConfigsError, diff --git a/src/host/asio/device.rs b/src/host/asio/device.rs index ae4db9543..b558628ce 100644 --- a/src/host/asio/device.rs +++ b/src/host/asio/device.rs @@ -5,6 +5,7 @@ use super::sys; use crate::BackendSpecificError; use crate::DefaultStreamConfigError; use crate::DeviceNameError; +use crate::DeviceIdError; use crate::DevicesError; use crate::SampleFormat; use crate::SampleRate; diff --git a/src/host/emscripten/mod.rs b/src/host/emscripten/mod.rs index 66f083f1b..e6bfd633c 100644 --- a/src/host/emscripten/mod.rs +++ b/src/host/emscripten/mod.rs @@ -7,7 +7,7 @@ use web_sys::AudioContext; use crate::traits::{DeviceTrait, HostTrait, StreamTrait}; use crate::{ - BufferSize, BuildStreamError, Data, DefaultStreamConfigError, DeviceNameError, DevicesError, + BufferSize, BuildStreamError, Data, DefaultStreamConfigError, DeviceNameError, DeviceIdError, DevicesError, InputCallbackInfo, OutputCallbackInfo, PauseStreamError, PlayStreamError, SampleFormat, SampleRate, StreamConfig, StreamError, SupportedBufferSize, SupportedStreamConfig, SupportedStreamConfigRange, SupportedStreamConfigsError, diff --git a/src/host/jack/device.rs b/src/host/jack/device.rs index 3ae08c64f..a870d8a42 100644 --- a/src/host/jack/device.rs +++ b/src/host/jack/device.rs @@ -1,6 +1,6 @@ use crate::traits::DeviceTrait; use crate::{ - BackendSpecificError, BuildStreamError, Data, DefaultStreamConfigError, DeviceNameError, + BackendSpecificError, BuildStreamError, Data, DefaultStreamConfigError, DeviceNameError, DeviceIdError, InputCallbackInfo, OutputCallbackInfo, SampleFormat, SampleRate, StreamConfig, StreamError, SupportedBufferSize, SupportedStreamConfig, SupportedStreamConfigRange, SupportedStreamConfigsError, diff --git a/src/host/wasapi/device.rs b/src/host/wasapi/device.rs index 232c0fa89..7db74cdb7 100644 --- a/src/host/wasapi/device.rs +++ b/src/host/wasapi/device.rs @@ -1,6 +1,6 @@ use crate::FrameCount; use crate::{ - BackendSpecificError, BufferSize, Data, DefaultStreamConfigError, DeviceNameError, + BackendSpecificError, BufferSize, Data, DefaultStreamConfigError, DeviceNameError, DeviceIdError, DevicesError, InputCallbackInfo, OutputCallbackInfo, SampleFormat, SampleRate, StreamConfig, SupportedBufferSize, SupportedStreamConfig, SupportedStreamConfigRange, SupportedStreamConfigsError, COMMON_SAMPLE_RATES, diff --git a/src/host/webaudio/mod.rs b/src/host/webaudio/mod.rs index 2bda974d4..0802ed344 100644 --- a/src/host/webaudio/mod.rs +++ b/src/host/webaudio/mod.rs @@ -8,7 +8,7 @@ use self::web_sys::{AudioContext, AudioContextOptions}; use crate::traits::{DeviceTrait, HostTrait, StreamTrait}; use crate::{ BackendSpecificError, BufferSize, BuildStreamError, Data, DefaultStreamConfigError, - DeviceNameError, DevicesError, InputCallbackInfo, OutputCallbackInfo, PauseStreamError, + DeviceNameError, DeviceIdError,DevicesError, InputCallbackInfo, OutputCallbackInfo, PauseStreamError, PlayStreamError, SampleFormat, SampleRate, StreamConfig, StreamError, SupportedBufferSize, SupportedStreamConfig, SupportedStreamConfigRange, SupportedStreamConfigsError, }; From 845baac311789b74715ac6f259c1ae77d6608311 Mon Sep 17 00:00:00 2001 From: xephyris <114379647+GoeyGreen@users.noreply.github.com> Date: Mon, 15 Sep 2025 23:16:08 -0700 Subject: [PATCH 03/22] wip: use DeviceId enum instead to work around conflicting device_id implementations --- src/lib.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index efea3a379..c3c299490 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -219,6 +219,11 @@ where /// The desired number of frames for the hardware buffer. pub type FrameCount = u32; +pub enum DeviceId { + MacOS(u32), + Windows(String), +} + /// The buffer size used by the device. /// /// [`Default`] is used when no specific buffer size is set and uses the default From bd9b0710dbeb3b37a2e68f8f93ed3bd53d17939f Mon Sep 17 00:00:00 2001 From: xephyris <114379647+GoeyGreen@users.noreply.github.com> Date: Tue, 16 Sep 2025 21:41:47 -0700 Subject: [PATCH 04/22] feat: transition to using DeviceId for enum for better cross compatibility --- src/host/aaudio/mod.rs | 6 +++--- src/host/alsa/mod.rs | 8 ++++---- src/host/asio/device.rs | 3 ++- src/host/asio/mod.rs | 6 +++--- src/host/coreaudio/ios/mod.rs | 8 ++++---- src/host/coreaudio/macos/mod.rs | 8 ++++---- src/host/emscripten/mod.rs | 8 ++++---- src/host/jack/device.rs | 4 ++-- src/host/null/mod.rs | 8 ++++++-- src/host/wasapi/device.rs | 8 ++++---- src/host/webaudio/mod.rs | 8 ++++---- src/lib.rs | 1 + src/platform/mod.rs | 4 ++-- src/traits.rs | 7 ++----- 14 files changed, 45 insertions(+), 42 deletions(-) diff --git a/src/host/aaudio/mod.rs b/src/host/aaudio/mod.rs index 366a03522..9faf604a4 100644 --- a/src/host/aaudio/mod.rs +++ b/src/host/aaudio/mod.rs @@ -12,7 +12,7 @@ use java_interface::{AudioDeviceDirection, AudioDeviceInfo}; use crate::traits::{DeviceTrait, HostTrait, StreamTrait}; use crate::{ BackendSpecificError, BufferSize, BuildStreamError, Data, DefaultStreamConfigError, - DeviceNameError, DeviceIdError, DevicesError, InputCallbackInfo, InputStreamTimestamp, OutputCallbackInfo, + DeviceNameError, DeviceId, DeviceIdError, DevicesError, InputCallbackInfo, InputStreamTimestamp, OutputCallbackInfo, OutputStreamTimestamp, PauseStreamError, PlayStreamError, SampleFormat, SampleRate, SizedSample, StreamConfig, StreamError, SupportedBufferSize, SupportedStreamConfig, SupportedStreamConfigRange, SupportedStreamConfigsError, @@ -210,7 +210,7 @@ fn configure_for_device( config: &StreamConfig, ) -> ndk::audio::AudioStreamBuilder { let mut builder = if let Some(info) = &device.0 { - builder.device_id(info.id) + builder.id(info.id) } else { builder }; @@ -327,7 +327,7 @@ impl DeviceTrait for Device { } } - fn device_id(&self) -> Result { + fn id(&self) -> Result { Err(DeviceIdError::UnsupportedOS) } diff --git a/src/host/alsa/mod.rs b/src/host/alsa/mod.rs index f64c88be7..389408f8c 100644 --- a/src/host/alsa/mod.rs +++ b/src/host/alsa/mod.rs @@ -16,7 +16,7 @@ pub use self::enumerate::{default_input_device, default_output_device, Devices}; use crate::{ traits::{DeviceTrait, HostTrait, StreamTrait}, BackendSpecificError, BufferSize, BuildStreamError, ChannelCount, Data, - DefaultStreamConfigError, DeviceNameError, DeviceIdError, DevicesError, FrameCount, InputCallbackInfo, + DefaultStreamConfigError, DeviceNameError, DeviceId, DeviceIdError, DevicesError, FrameCount, InputCallbackInfo, OutputCallbackInfo, PauseStreamError, PlayStreamError, SampleFormat, SampleRate, StreamConfig, StreamError, SupportedBufferSize, SupportedStreamConfig, SupportedStreamConfigRange, SupportedStreamConfigsError, @@ -68,8 +68,8 @@ impl DeviceTrait for Device { Device::name(self) } - fn device_id(&self) -> Result { - Device::device_id(self) + fn id(&self) -> Result { + Device::id(self) } fn supported_input_configs( @@ -312,7 +312,7 @@ impl Device { } #[inline] - fn device_id(&self) -> Result { + fn id(&self) -> Result { Err(DeviceIdError::UnsupportedOS) } diff --git a/src/host/asio/device.rs b/src/host/asio/device.rs index b558628ce..95a87755a 100644 --- a/src/host/asio/device.rs +++ b/src/host/asio/device.rs @@ -5,6 +5,7 @@ use super::sys; use crate::BackendSpecificError; use crate::DefaultStreamConfigError; use crate::DeviceNameError; +use crate::DeviceId; use crate::DeviceIdError; use crate::DevicesError; use crate::SampleFormat; @@ -55,7 +56,7 @@ impl Device { Ok(self.driver.name().to_string()) } - fn device_id(&self) -> Result { + fn id(&self) -> Result { Err(DeviceIdError::UnsupportedOS) } diff --git a/src/host/asio/mod.rs b/src/host/asio/mod.rs index 3af353848..34d6fe553 100644 --- a/src/host/asio/mod.rs +++ b/src/host/asio/mod.rs @@ -2,7 +2,7 @@ extern crate asio_sys as sys; use crate::traits::{DeviceTrait, HostTrait, StreamTrait}; use crate::{ - BuildStreamError, Data, DefaultStreamConfigError, DeviceNameError, DevicesError, + BuildStreamError, Data, DefaultStreamConfigError, DeviceNameError, DeviceId, DeviceIdError, DevicesError, InputCallbackInfo, OutputCallbackInfo, PauseStreamError, PlayStreamError, SampleFormat, StreamConfig, StreamError, SupportedStreamConfig, SupportedStreamConfigsError, }; @@ -62,8 +62,8 @@ impl DeviceTrait for Device { Device::name(self) } - fn device_id(&self) -> Result { - Device::device_id(self) + fn id(&self) -> Result { + Device::id(self) } fn supported_input_configs( diff --git a/src/host/coreaudio/ios/mod.rs b/src/host/coreaudio/ios/mod.rs index 65b3885f3..7f587adde 100644 --- a/src/host/coreaudio/ios/mod.rs +++ b/src/host/coreaudio/ios/mod.rs @@ -18,7 +18,7 @@ use super::{asbd_from_config, frames_to_duration, host_time_to_stream_instant}; use crate::traits::{DeviceTrait, HostTrait, StreamTrait}; use crate::{ - BackendSpecificError, BufferSize, BuildStreamError, Data, DefaultStreamConfigError, DeviceIdError, + BackendSpecificError, BufferSize, BuildStreamError, Data, DefaultStreamConfigError, DeviceId, DeviceIdError, DeviceNameError, DevicesError, InputCallbackInfo, OutputCallbackInfo, PauseStreamError, PlayStreamError, SampleFormat, SampleRate, StreamConfig, StreamError, SupportedBufferSize, SupportedStreamConfig, SupportedStreamConfigRange, SupportedStreamConfigsError, @@ -74,7 +74,7 @@ impl Device { Ok("Default Device".to_owned()) } - fn device_id(&self) -> Result { + fn id(&self) -> Result { Err(DeviceIdError::UnsupportedOS) } @@ -145,8 +145,8 @@ impl DeviceTrait for Device { } #[inline] - fn device_id(&self) -> Result { - Device::device_id(self) + fn id(&self) -> Result { + Device::id(self) } #[inline] diff --git a/src/host/coreaudio/macos/mod.rs b/src/host/coreaudio/macos/mod.rs index 314047ea3..aa650643e 100644 --- a/src/host/coreaudio/macos/mod.rs +++ b/src/host/coreaudio/macos/mod.rs @@ -4,7 +4,7 @@ use super::{asbd_from_config, check_os_status, frames_to_duration, host_time_to_ use super::OSStatus; use crate::traits::{DeviceTrait, HostTrait, StreamTrait}; use crate::{ - BackendSpecificError, BufferSize, BuildStreamError, ChannelCount, Data, DefaultStreamConfigError, DeviceIdError, DeviceNameError, DevicesError, InputCallbackInfo, OutputCallbackInfo, PauseStreamError, PlayStreamError, SampleFormat, SampleRate, StreamConfig, StreamError, SupportedBufferSize, SupportedStreamConfig, SupportedStreamConfigRange, SupportedStreamConfigsError + BackendSpecificError, BufferSize, BuildStreamError, ChannelCount, Data, DefaultStreamConfigError, DeviceId, DeviceIdError, DeviceNameError, DevicesError, InputCallbackInfo, OutputCallbackInfo, PauseStreamError, PlayStreamError, SampleFormat, SampleRate, StreamConfig, StreamError, SupportedBufferSize, SupportedStreamConfig, SupportedStreamConfigRange, SupportedStreamConfigsError }; use coreaudio::audio_unit::render_callback::{self, data}; use coreaudio::audio_unit::{AudioUnit, Element, Scope}; @@ -87,7 +87,7 @@ impl DeviceTrait for Device { Device::name(self) } - fn device_id(&self) -> Result{ + fn id(&self) -> Result{ Device::id(self) } @@ -185,8 +185,8 @@ impl Device { }) } - fn id(&self) -> Result { - Ok(self.audio_device_id) + fn id(&self) -> Result { + Ok(DeviceId::MacOS(self.audio_device_id)) } // Logic re-used between `supported_input_configs` and `supported_output_configs`. diff --git a/src/host/emscripten/mod.rs b/src/host/emscripten/mod.rs index e6bfd633c..2bec598ba 100644 --- a/src/host/emscripten/mod.rs +++ b/src/host/emscripten/mod.rs @@ -7,7 +7,7 @@ use web_sys::AudioContext; use crate::traits::{DeviceTrait, HostTrait, StreamTrait}; use crate::{ - BufferSize, BuildStreamError, Data, DefaultStreamConfigError, DeviceNameError, DeviceIdError, DevicesError, + BufferSize, BuildStreamError, Data, DefaultStreamConfigError, DeviceNameError, DeviceId, DeviceIdError, DevicesError, InputCallbackInfo, OutputCallbackInfo, PauseStreamError, PlayStreamError, SampleFormat, SampleRate, StreamConfig, StreamError, SupportedBufferSize, SupportedStreamConfig, SupportedStreamConfigRange, SupportedStreamConfigsError, @@ -70,7 +70,7 @@ impl Device { } #[inline] - fn device_id(&self) -> Result { + fn id(&self) -> Result { Err(DeviceIdError::UnsupportedOS) } @@ -149,8 +149,8 @@ impl DeviceTrait for Device { Device::name(self) } - fn device_id(&self) -> Result { - Device::device_id(self) + fn id(&self) -> Result { + Device::id(self) } fn supported_input_configs( diff --git a/src/host/jack/device.rs b/src/host/jack/device.rs index a870d8a42..31fbd4337 100644 --- a/src/host/jack/device.rs +++ b/src/host/jack/device.rs @@ -1,6 +1,6 @@ use crate::traits::DeviceTrait; use crate::{ - BackendSpecificError, BuildStreamError, Data, DefaultStreamConfigError, DeviceNameError, DeviceIdError, + BackendSpecificError, BuildStreamError, Data, DefaultStreamConfigError, DeviceNameError, DeviceId, DeviceIdError, InputCallbackInfo, OutputCallbackInfo, SampleFormat, SampleRate, StreamConfig, StreamError, SupportedBufferSize, SupportedStreamConfig, SupportedStreamConfigRange, SupportedStreamConfigsError, @@ -146,7 +146,7 @@ impl DeviceTrait for Device { Ok(self.name.clone()) } - fn device_id(&self) -> Result { + fn id(&self) -> Result { Err(DeviceIdError::UnsupportedOS) } diff --git a/src/host/null/mod.rs b/src/host/null/mod.rs index 19a464ebd..9630bc509 100644 --- a/src/host/null/mod.rs +++ b/src/host/null/mod.rs @@ -2,7 +2,11 @@ use std::time::Duration; use crate::traits::{DeviceTrait, HostTrait, StreamTrait}; use crate::{ - BuildStreamError, Data, DefaultStreamConfigError, DeviceIdError, DeviceNameError, DevicesError, InputCallbackInfo, OutputCallbackInfo, PauseStreamError, PlayStreamError, SampleFormat, StreamConfig, StreamError, SupportedStreamConfig, SupportedStreamConfigRange, SupportedStreamConfigsError + BuildStreamError, Data, DefaultStreamConfigError, DeviceId, + DeviceIdError, DeviceNameError, DevicesError, InputCallbackInfo, + OutputCallbackInfo, PauseStreamError, PlayStreamError, + SampleFormat, StreamConfig, StreamError, SupportedStreamConfig, + SupportedStreamConfigRange, SupportedStreamConfigsError }; #[derive(Default)] @@ -45,7 +49,7 @@ impl DeviceTrait for Device { } #[inline] - fn device_id(&self) -> Result { + fn id(&self) -> Result { Err(DeviceIdError::UnsupportedOS) } diff --git a/src/host/wasapi/device.rs b/src/host/wasapi/device.rs index 7db74cdb7..47bcd47c8 100644 --- a/src/host/wasapi/device.rs +++ b/src/host/wasapi/device.rs @@ -1,6 +1,6 @@ use crate::FrameCount; use crate::{ - BackendSpecificError, BufferSize, Data, DefaultStreamConfigError, DeviceNameError, DeviceIdError, + BackendSpecificError, BufferSize, Data, DefaultStreamConfigError, DeviceNameError, DeviceId, DeviceIdError, DevicesError, InputCallbackInfo, OutputCallbackInfo, SampleFormat, SampleRate, StreamConfig, SupportedBufferSize, SupportedStreamConfig, SupportedStreamConfigRange, SupportedStreamConfigsError, COMMON_SAMPLE_RATES, @@ -58,8 +58,8 @@ impl DeviceTrait for Device { Device::name(self) } - fn device_id(&self) -> Result { - Device::device_id(self) + fn id(&self) -> Result { + Device::id(self) } fn supports_input(&self) -> bool { @@ -325,7 +325,7 @@ impl Device { } } - fn device_id(&self) -> Result { + fn id(&self) -> Result { Err(DeviceIdError::UnsupportedOS) } diff --git a/src/host/webaudio/mod.rs b/src/host/webaudio/mod.rs index 0802ed344..af030bd9b 100644 --- a/src/host/webaudio/mod.rs +++ b/src/host/webaudio/mod.rs @@ -8,7 +8,7 @@ use self::web_sys::{AudioContext, AudioContextOptions}; use crate::traits::{DeviceTrait, HostTrait, StreamTrait}; use crate::{ BackendSpecificError, BufferSize, BuildStreamError, Data, DefaultStreamConfigError, - DeviceNameError, DeviceIdError,DevicesError, InputCallbackInfo, OutputCallbackInfo, PauseStreamError, + DeviceNameError, DeviceId, DeviceIdError,DevicesError, InputCallbackInfo, OutputCallbackInfo, PauseStreamError, PlayStreamError, SampleFormat, SampleRate, StreamConfig, StreamError, SupportedBufferSize, SupportedStreamConfig, SupportedStreamConfigRange, SupportedStreamConfigsError, }; @@ -85,7 +85,7 @@ impl Device { } #[inline] - fn device_id(&self) -> Result { + fn id(&self) -> Result { Err(DeviceIdError::UnsupportedOS) } @@ -148,8 +148,8 @@ impl DeviceTrait for Device { } #[inline] - fn device_id(&self) -> Result { - Device::device_id(self) + fn id(&self) -> Result { + Device::id(self) } #[inline] diff --git a/src/lib.rs b/src/lib.rs index c3c299490..7ee0465d5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -219,6 +219,7 @@ where /// The desired number of frames for the hardware buffer. pub type FrameCount = u32; +#[derive(Clone, Debug, Eq, PartialEq)] pub enum DeviceId { MacOS(u32), Windows(String), diff --git a/src/platform/mod.rs b/src/platform/mod.rs index 783ba4fe6..503f17e01 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -314,11 +314,11 @@ macro_rules! impl_platform_host { } } - fn device_id(&self) -> Result { + fn id(&self) -> Result { match self.0 { $( $(#[cfg($feat)])? - DeviceInner::$HostVariant(ref d) => d.device_id(), + DeviceInner::$HostVariant(ref d) => d.id(), )* } } diff --git a/src/traits.rs b/src/traits.rs index c824e3ae2..38612670c 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -3,10 +3,7 @@ use std::time::Duration; use crate::{ - BuildStreamError, Data, DefaultStreamConfigError, DeviceIdError, DeviceNameError, DevicesError, - InputCallbackInfo, InputDevices, OutputCallbackInfo, OutputDevices, PauseStreamError, - PlayStreamError, SampleFormat, SizedSample, StreamConfig, StreamError, SupportedStreamConfig, - SupportedStreamConfigRange, SupportedStreamConfigsError, + BuildStreamError, Data, DefaultStreamConfigError, DeviceId, DeviceIdError, DeviceNameError, DevicesError, InputCallbackInfo, InputDevices, OutputCallbackInfo, OutputDevices, PauseStreamError, PlayStreamError, SampleFormat, SizedSample, StreamConfig, StreamError, SupportedStreamConfig, SupportedStreamConfigRange, SupportedStreamConfigsError }; /// A [`Host`] provides access to the available audio devices on the system. @@ -90,7 +87,7 @@ pub trait DeviceTrait { fn name(&self) -> Result; /// The device-id of the device. (For supported OS's only) - fn device_id(&self) -> Result; + fn id(&self) -> Result; /// True if the device supports audio input, otherwise false fn supports_input(&self) -> bool { From 60c3c26c21e179a80278edc46ba275295cd0df23 Mon Sep 17 00:00:00 2001 From: xephyris <114379647+GoeyGreen@users.noreply.github.com> Date: Wed, 17 Sep 2025 23:09:51 -0700 Subject: [PATCH 05/22] docs: add description for DeviceId --- src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 7ee0465d5..0dc9eb89a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -219,6 +219,8 @@ where /// The desired number of frames for the hardware buffer. pub type FrameCount = u32; +/// The device ID of the audio device, on supported OSs +/// Currently only supports macOS #[derive(Clone, Debug, Eq, PartialEq)] pub enum DeviceId { MacOS(u32), From 2a93a70264b0c8fbb9b2c68a524a1fa4deed9572 Mon Sep 17 00:00:00 2001 From: xephyris <114379647+xephyris@users.noreply.github.com> Date: Fri, 19 Sep 2025 22:01:50 -0700 Subject: [PATCH 06/22] feat: implemented device id for windows wasapi --- src/error.rs | 4 +++- src/host/wasapi/device.rs | 18 +++++++++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/error.rs b/src/error.rs index 1a5886793..05cb5cf52 100644 --- a/src/error.rs +++ b/src/error.rs @@ -71,13 +71,15 @@ pub enum DeviceIdError { /// See the [`BackendSpecificError`] docs for more information about this error variant. BackendSpecific { err: BackendSpecificError }, UnsupportedOS, + ParseError, } impl Display for DeviceIdError { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { Self::BackendSpecific { err } => err.fmt(f), - Self::UnsupportedOS => {f.write_str("Device ids are unsupported for this OS")} + Self::UnsupportedOS => {f.write_str("Device ids are unsupported for this OS")}, + Self::ParseError => {f.write_str("Failed to parse the device_id")}, } } } diff --git a/src/host/wasapi/device.rs b/src/host/wasapi/device.rs index 47bcd47c8..76e81a0f4 100644 --- a/src/host/wasapi/device.rs +++ b/src/host/wasapi/device.rs @@ -326,7 +326,23 @@ impl Device { } fn id(&self) -> Result { - Err(DeviceIdError::UnsupportedOS) + unsafe { + match self.device.GetId() { + Ok(pwstr) => { + match pwstr.to_string() { + Ok(id_str) => { + Ok(DeviceId::Windows(id_str)) + }, + Err(_e) => { + Err(DeviceIdError::ParseError) + } + } + }, + Err(e) => { + Err(DeviceIdError::BackendSpecific { err: e.into() }) + } + } + } } #[inline] From fb1efd07d841790d56592f7a39bc4a4178671466 Mon Sep 17 00:00:00 2001 From: xephyris <114379647+GoeyGreen@users.noreply.github.com> Date: Mon, 22 Sep 2025 19:22:24 -0700 Subject: [PATCH 07/22] docs: update description --- src/lib.rs | 2 +- src/traits.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 0dc9eb89a..01a918a8c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -220,7 +220,7 @@ where pub type FrameCount = u32; /// The device ID of the audio device, on supported OSs -/// Currently only supports macOS +/// Currently only supports macOS and Windows (WASAPI) #[derive(Clone, Debug, Eq, PartialEq)] pub enum DeviceId { MacOS(u32), diff --git a/src/traits.rs b/src/traits.rs index 38612670c..9ab0389fb 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -86,7 +86,7 @@ pub trait DeviceTrait { /// The human-readable name of the device. fn name(&self) -> Result; - /// The device-id of the device. (For supported OS's only) + /// The device-id of the device. (For supported OSs only) fn id(&self) -> Result; /// True if the device supports audio input, otherwise false From 626b8230694c7f9e95d4b74b36624515ee955272 Mon Sep 17 00:00:00 2001 From: xephyris <114379647+GoeyGreen@users.noreply.github.com> Date: Tue, 23 Sep 2025 17:04:04 -0700 Subject: [PATCH 08/22] fix: reformat existing code and fix android build error --- src/error.rs | 8 +++++--- src/host/aaudio/mod.rs | 10 +++++----- src/host/alsa/mod.rs | 8 ++++---- src/host/asio/device.rs | 2 +- src/host/asio/mod.rs | 6 +++--- src/host/coreaudio/ios/mod.rs | 9 +++++---- src/host/coreaudio/macos/mod.rs | 10 +++++++--- src/host/coreaudio/mod.rs | 5 +---- src/host/emscripten/mod.rs | 8 ++++---- src/host/jack/device.rs | 8 ++++---- src/host/null/mod.rs | 9 ++++----- src/host/wasapi/device.rs | 22 +++++++--------------- src/host/webaudio/mod.rs | 9 +++++---- src/traits.rs | 5 ++++- 14 files changed, 59 insertions(+), 60 deletions(-) diff --git a/src/error.rs b/src/error.rs index 05cb5cf52..7e7de1234 100644 --- a/src/error.rs +++ b/src/error.rs @@ -69,7 +69,9 @@ impl From for DevicesError { #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub enum DeviceIdError { /// See the [`BackendSpecificError`] docs for more information about this error variant. - BackendSpecific { err: BackendSpecificError }, + BackendSpecific { + err: BackendSpecificError, + }, UnsupportedOS, ParseError, } @@ -78,8 +80,8 @@ impl Display for DeviceIdError { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { Self::BackendSpecific { err } => err.fmt(f), - Self::UnsupportedOS => {f.write_str("Device ids are unsupported for this OS")}, - Self::ParseError => {f.write_str("Failed to parse the device_id")}, + Self::UnsupportedOS => f.write_str("Device ids are unsupported for this OS"), + Self::ParseError => f.write_str("Failed to parse the device_id"), } } } diff --git a/src/host/aaudio/mod.rs b/src/host/aaudio/mod.rs index 9faf604a4..c14c435a2 100644 --- a/src/host/aaudio/mod.rs +++ b/src/host/aaudio/mod.rs @@ -11,10 +11,10 @@ use java_interface::{AudioDeviceDirection, AudioDeviceInfo}; use crate::traits::{DeviceTrait, HostTrait, StreamTrait}; use crate::{ - BackendSpecificError, BufferSize, BuildStreamError, Data, DefaultStreamConfigError, - DeviceNameError, DeviceId, DeviceIdError, DevicesError, InputCallbackInfo, InputStreamTimestamp, OutputCallbackInfo, - OutputStreamTimestamp, PauseStreamError, PlayStreamError, SampleFormat, SampleRate, - SizedSample, StreamConfig, StreamError, SupportedBufferSize, SupportedStreamConfig, + BackendSpecificError, BufferSize, BuildStreamError, Data, DefaultStreamConfigError, DeviceId, + DeviceIdError, DeviceNameError, DevicesError, InputCallbackInfo, InputStreamTimestamp, + OutputCallbackInfo, OutputStreamTimestamp, PauseStreamError, PlayStreamError, SampleFormat, + SampleRate, SizedSample, StreamConfig, StreamError, SupportedBufferSize, SupportedStreamConfig, SupportedStreamConfigRange, SupportedStreamConfigsError, }; @@ -210,7 +210,7 @@ fn configure_for_device( config: &StreamConfig, ) -> ndk::audio::AudioStreamBuilder { let mut builder = if let Some(info) = &device.0 { - builder.id(info.id) + builder.device_id(info.id) } else { builder }; diff --git a/src/host/alsa/mod.rs b/src/host/alsa/mod.rs index 389408f8c..c1054d4c3 100644 --- a/src/host/alsa/mod.rs +++ b/src/host/alsa/mod.rs @@ -16,10 +16,10 @@ pub use self::enumerate::{default_input_device, default_output_device, Devices}; use crate::{ traits::{DeviceTrait, HostTrait, StreamTrait}, BackendSpecificError, BufferSize, BuildStreamError, ChannelCount, Data, - DefaultStreamConfigError, DeviceNameError, DeviceId, DeviceIdError, DevicesError, FrameCount, InputCallbackInfo, - OutputCallbackInfo, PauseStreamError, PlayStreamError, SampleFormat, SampleRate, StreamConfig, - StreamError, SupportedBufferSize, SupportedStreamConfig, SupportedStreamConfigRange, - SupportedStreamConfigsError, + DefaultStreamConfigError, DeviceId, DeviceIdError, DeviceNameError, DevicesError, FrameCount, + InputCallbackInfo, OutputCallbackInfo, PauseStreamError, PlayStreamError, SampleFormat, + SampleRate, StreamConfig, StreamError, SupportedBufferSize, SupportedStreamConfig, + SupportedStreamConfigRange, SupportedStreamConfigsError, }; pub type SupportedInputConfigs = VecIntoIter; diff --git a/src/host/asio/device.rs b/src/host/asio/device.rs index 95a87755a..bd091223a 100644 --- a/src/host/asio/device.rs +++ b/src/host/asio/device.rs @@ -4,9 +4,9 @@ pub type SupportedOutputConfigs = std::vec::IntoIter use super::sys; use crate::BackendSpecificError; use crate::DefaultStreamConfigError; -use crate::DeviceNameError; use crate::DeviceId; use crate::DeviceIdError; +use crate::DeviceNameError; use crate::DevicesError; use crate::SampleFormat; use crate::SampleRate; diff --git a/src/host/asio/mod.rs b/src/host/asio/mod.rs index 34d6fe553..5c56b155f 100644 --- a/src/host/asio/mod.rs +++ b/src/host/asio/mod.rs @@ -2,9 +2,9 @@ extern crate asio_sys as sys; use crate::traits::{DeviceTrait, HostTrait, StreamTrait}; use crate::{ - BuildStreamError, Data, DefaultStreamConfigError, DeviceNameError, DeviceId, DeviceIdError, DevicesError, - InputCallbackInfo, OutputCallbackInfo, PauseStreamError, PlayStreamError, SampleFormat, - StreamConfig, StreamError, SupportedStreamConfig, SupportedStreamConfigsError, + BuildStreamError, Data, DefaultStreamConfigError, DeviceId, DeviceIdError, DeviceNameError, + DevicesError, InputCallbackInfo, OutputCallbackInfo, PauseStreamError, PlayStreamError, + SampleFormat, StreamConfig, StreamError, SupportedStreamConfig, SupportedStreamConfigsError, }; pub use self::device::{Device, Devices, SupportedInputConfigs, SupportedOutputConfigs}; diff --git a/src/host/coreaudio/ios/mod.rs b/src/host/coreaudio/ios/mod.rs index 7f587adde..ffaabefa1 100644 --- a/src/host/coreaudio/ios/mod.rs +++ b/src/host/coreaudio/ios/mod.rs @@ -18,10 +18,11 @@ use super::{asbd_from_config, frames_to_duration, host_time_to_stream_instant}; use crate::traits::{DeviceTrait, HostTrait, StreamTrait}; use crate::{ - BackendSpecificError, BufferSize, BuildStreamError, Data, DefaultStreamConfigError, DeviceId, DeviceIdError, - DeviceNameError, DevicesError, InputCallbackInfo, OutputCallbackInfo, PauseStreamError, - PlayStreamError, SampleFormat, SampleRate, StreamConfig, StreamError, SupportedBufferSize, - SupportedStreamConfig, SupportedStreamConfigRange, SupportedStreamConfigsError, + BackendSpecificError, BufferSize, BuildStreamError, Data, DefaultStreamConfigError, DeviceId, + DeviceIdError, DeviceNameError, DevicesError, InputCallbackInfo, OutputCallbackInfo, + PauseStreamError, PlayStreamError, SampleFormat, SampleRate, StreamConfig, StreamError, + SupportedBufferSize, SupportedStreamConfig, SupportedStreamConfigRange, + SupportedStreamConfigsError, }; use self::enumerate::{ diff --git a/src/host/coreaudio/macos/mod.rs b/src/host/coreaudio/macos/mod.rs index aa650643e..608427595 100644 --- a/src/host/coreaudio/macos/mod.rs +++ b/src/host/coreaudio/macos/mod.rs @@ -4,7 +4,11 @@ use super::{asbd_from_config, check_os_status, frames_to_duration, host_time_to_ use super::OSStatus; use crate::traits::{DeviceTrait, HostTrait, StreamTrait}; use crate::{ - BackendSpecificError, BufferSize, BuildStreamError, ChannelCount, Data, DefaultStreamConfigError, DeviceId, DeviceIdError, DeviceNameError, DevicesError, InputCallbackInfo, OutputCallbackInfo, PauseStreamError, PlayStreamError, SampleFormat, SampleRate, StreamConfig, StreamError, SupportedBufferSize, SupportedStreamConfig, SupportedStreamConfigRange, SupportedStreamConfigsError + BackendSpecificError, BufferSize, BuildStreamError, ChannelCount, Data, + DefaultStreamConfigError, DeviceId, DeviceIdError, DeviceNameError, DevicesError, + InputCallbackInfo, OutputCallbackInfo, PauseStreamError, PlayStreamError, SampleFormat, + SampleRate, StreamConfig, StreamError, SupportedBufferSize, SupportedStreamConfig, + SupportedStreamConfigRange, SupportedStreamConfigsError, }; use coreaudio::audio_unit::render_callback::{self, data}; use coreaudio::audio_unit::{AudioUnit, Element, Scope}; @@ -87,8 +91,8 @@ impl DeviceTrait for Device { Device::name(self) } - fn id(&self) -> Result{ - Device::id(self) + fn id(&self) -> Result { + Device::id(self) } fn supported_input_configs( diff --git a/src/host/coreaudio/mod.rs b/src/host/coreaudio/mod.rs index a22b86ee6..985167f60 100644 --- a/src/host/coreaudio/mod.rs +++ b/src/host/coreaudio/mod.rs @@ -20,10 +20,7 @@ pub use self::ios::{ }; #[cfg(target_os = "macos")] -pub use self::macos::{ - enumerate::{Devices, SupportedInputConfigs, SupportedOutputConfigs}, - Device, Host, Stream, -}; +pub use self::macos::Host; // Common helper methods used by both macOS and iOS diff --git a/src/host/emscripten/mod.rs b/src/host/emscripten/mod.rs index 2bec598ba..440ac64f5 100644 --- a/src/host/emscripten/mod.rs +++ b/src/host/emscripten/mod.rs @@ -7,10 +7,10 @@ use web_sys::AudioContext; use crate::traits::{DeviceTrait, HostTrait, StreamTrait}; use crate::{ - BufferSize, BuildStreamError, Data, DefaultStreamConfigError, DeviceNameError, DeviceId, DeviceIdError, DevicesError, - InputCallbackInfo, OutputCallbackInfo, PauseStreamError, PlayStreamError, SampleFormat, - SampleRate, StreamConfig, StreamError, SupportedBufferSize, SupportedStreamConfig, - SupportedStreamConfigRange, SupportedStreamConfigsError, + BufferSize, BuildStreamError, Data, DefaultStreamConfigError, DeviceId, DeviceIdError, + DeviceNameError, DevicesError, InputCallbackInfo, OutputCallbackInfo, PauseStreamError, + PlayStreamError, SampleFormat, SampleRate, StreamConfig, StreamError, SupportedBufferSize, + SupportedStreamConfig, SupportedStreamConfigRange, SupportedStreamConfigsError, }; // The emscripten backend currently works by instantiating an `AudioContext` object per `Stream`. diff --git a/src/host/jack/device.rs b/src/host/jack/device.rs index 31fbd4337..68ef79ef2 100644 --- a/src/host/jack/device.rs +++ b/src/host/jack/device.rs @@ -1,9 +1,9 @@ use crate::traits::DeviceTrait; use crate::{ - BackendSpecificError, BuildStreamError, Data, DefaultStreamConfigError, DeviceNameError, DeviceId, DeviceIdError, - InputCallbackInfo, OutputCallbackInfo, SampleFormat, SampleRate, StreamConfig, StreamError, - SupportedBufferSize, SupportedStreamConfig, SupportedStreamConfigRange, - SupportedStreamConfigsError, + BackendSpecificError, BuildStreamError, Data, DefaultStreamConfigError, DeviceId, + DeviceIdError, DeviceNameError, InputCallbackInfo, OutputCallbackInfo, SampleFormat, + SampleRate, StreamConfig, StreamError, SupportedBufferSize, SupportedStreamConfig, + SupportedStreamConfigRange, SupportedStreamConfigsError, }; use std::hash::{Hash, Hasher}; use std::time::Duration; diff --git a/src/host/null/mod.rs b/src/host/null/mod.rs index 9630bc509..24d8d68cc 100644 --- a/src/host/null/mod.rs +++ b/src/host/null/mod.rs @@ -2,11 +2,10 @@ use std::time::Duration; use crate::traits::{DeviceTrait, HostTrait, StreamTrait}; use crate::{ - BuildStreamError, Data, DefaultStreamConfigError, DeviceId, - DeviceIdError, DeviceNameError, DevicesError, InputCallbackInfo, - OutputCallbackInfo, PauseStreamError, PlayStreamError, - SampleFormat, StreamConfig, StreamError, SupportedStreamConfig, - SupportedStreamConfigRange, SupportedStreamConfigsError + BuildStreamError, Data, DefaultStreamConfigError, DeviceId, DeviceIdError, DeviceNameError, + DevicesError, InputCallbackInfo, OutputCallbackInfo, PauseStreamError, PlayStreamError, + SampleFormat, StreamConfig, StreamError, SupportedStreamConfig, SupportedStreamConfigRange, + SupportedStreamConfigsError, }; #[derive(Default)] diff --git a/src/host/wasapi/device.rs b/src/host/wasapi/device.rs index 76e81a0f4..1fe9d1267 100644 --- a/src/host/wasapi/device.rs +++ b/src/host/wasapi/device.rs @@ -1,8 +1,8 @@ use crate::FrameCount; use crate::{ - BackendSpecificError, BufferSize, Data, DefaultStreamConfigError, DeviceNameError, DeviceId, DeviceIdError, - DevicesError, InputCallbackInfo, OutputCallbackInfo, SampleFormat, SampleRate, StreamConfig, - SupportedBufferSize, SupportedStreamConfig, SupportedStreamConfigRange, + BackendSpecificError, BufferSize, Data, DefaultStreamConfigError, DeviceId, DeviceIdError, + DeviceNameError, DevicesError, InputCallbackInfo, OutputCallbackInfo, SampleFormat, SampleRate, + StreamConfig, SupportedBufferSize, SupportedStreamConfig, SupportedStreamConfigRange, SupportedStreamConfigsError, COMMON_SAMPLE_RATES, }; use std::ffi::OsString; @@ -328,19 +328,11 @@ impl Device { fn id(&self) -> Result { unsafe { match self.device.GetId() { - Ok(pwstr) => { - match pwstr.to_string() { - Ok(id_str) => { - Ok(DeviceId::Windows(id_str)) - }, - Err(_e) => { - Err(DeviceIdError::ParseError) - } - } + Ok(pwstr) => match pwstr.to_string() { + Ok(id_str) => Ok(DeviceId::Windows(id_str)), + Err(_e) => Err(DeviceIdError::ParseError), }, - Err(e) => { - Err(DeviceIdError::BackendSpecific { err: e.into() }) - } + Err(e) => Err(DeviceIdError::BackendSpecific { err: e.into() }), } } } diff --git a/src/host/webaudio/mod.rs b/src/host/webaudio/mod.rs index af030bd9b..49f05267d 100644 --- a/src/host/webaudio/mod.rs +++ b/src/host/webaudio/mod.rs @@ -7,10 +7,11 @@ use self::wasm_bindgen::JsCast; use self::web_sys::{AudioContext, AudioContextOptions}; use crate::traits::{DeviceTrait, HostTrait, StreamTrait}; use crate::{ - BackendSpecificError, BufferSize, BuildStreamError, Data, DefaultStreamConfigError, - DeviceNameError, DeviceId, DeviceIdError,DevicesError, InputCallbackInfo, OutputCallbackInfo, PauseStreamError, - PlayStreamError, SampleFormat, SampleRate, StreamConfig, StreamError, SupportedBufferSize, - SupportedStreamConfig, SupportedStreamConfigRange, SupportedStreamConfigsError, + BackendSpecificError, BufferSize, BuildStreamError, Data, DefaultStreamConfigError, DeviceId, + DeviceIdError, DeviceNameError, DevicesError, InputCallbackInfo, OutputCallbackInfo, + PauseStreamError, PlayStreamError, SampleFormat, SampleRate, StreamConfig, StreamError, + SupportedBufferSize, SupportedStreamConfig, SupportedStreamConfigRange, + SupportedStreamConfigsError, }; use std::ops::DerefMut; use std::sync::{Arc, Mutex, RwLock}; diff --git a/src/traits.rs b/src/traits.rs index 9ab0389fb..0c0b0303e 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -3,7 +3,10 @@ use std::time::Duration; use crate::{ - BuildStreamError, Data, DefaultStreamConfigError, DeviceId, DeviceIdError, DeviceNameError, DevicesError, InputCallbackInfo, InputDevices, OutputCallbackInfo, OutputDevices, PauseStreamError, PlayStreamError, SampleFormat, SizedSample, StreamConfig, StreamError, SupportedStreamConfig, SupportedStreamConfigRange, SupportedStreamConfigsError + BuildStreamError, Data, DefaultStreamConfigError, DeviceId, DeviceIdError, DeviceNameError, + DevicesError, InputCallbackInfo, InputDevices, OutputCallbackInfo, OutputDevices, + PauseStreamError, PlayStreamError, SampleFormat, SizedSample, StreamConfig, StreamError, + SupportedStreamConfig, SupportedStreamConfigRange, SupportedStreamConfigsError, }; /// A [`Host`] provides access to the available audio devices on the system. From c9d6b056aac03e818f4abcbfaba45d0afb8d4826 Mon Sep 17 00:00:00 2001 From: xephyris <114379647+xephyris@users.noreply.github.com> Date: Wed, 24 Sep 2025 01:59:28 -0400 Subject: [PATCH 09/22] feat: add ALSA support to device id() function --- src/host/alsa/mod.rs | 2 +- src/lib.rs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/host/alsa/mod.rs b/src/host/alsa/mod.rs index c1054d4c3..e63e583c1 100644 --- a/src/host/alsa/mod.rs +++ b/src/host/alsa/mod.rs @@ -313,7 +313,7 @@ impl Device { #[inline] fn id(&self) -> Result { - Err(DeviceIdError::UnsupportedOS) + Ok(DeviceId::ALSA(self.pcm_id.clone())) } fn supported_configs( diff --git a/src/lib.rs b/src/lib.rs index 01a918a8c..4315904ab 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -225,6 +225,7 @@ pub type FrameCount = u32; pub enum DeviceId { MacOS(u32), Windows(String), + ALSA(String), } /// The buffer size used by the device. From 24ef5ac2c07738e80045b4d826eaaff3e913a2dd Mon Sep 17 00:00:00 2001 From: xephyris <114379647+GoeyGreen@users.noreply.github.com> Date: Wed, 24 Sep 2025 23:43:21 -0700 Subject: [PATCH 10/22] feat: add support for jack and aaudio (untested). fix naming structures, and more --- src/error.rs | 4 +-- src/host/aaudio/mod.rs | 5 +++- src/host/asio/device.rs | 2 +- src/host/coreaudio/ios/mod.rs | 2 +- src/host/coreaudio/macos/device.rs | 2 +- src/host/emscripten/mod.rs | 2 +- src/host/jack/device.rs | 6 +++- src/host/null/mod.rs | 2 +- src/host/wasapi/device.rs | 2 +- src/host/webaudio/mod.rs | 2 +- src/lib.rs | 46 ++++++++++++++++++++++++++++-- src/traits.rs | 9 ++++++ 12 files changed, 71 insertions(+), 13 deletions(-) diff --git a/src/error.rs b/src/error.rs index 7e7de1234..21d649f52 100644 --- a/src/error.rs +++ b/src/error.rs @@ -72,7 +72,7 @@ pub enum DeviceIdError { BackendSpecific { err: BackendSpecificError, }, - UnsupportedOS, + UnsupportedPlatform, ParseError, } @@ -80,7 +80,7 @@ impl Display for DeviceIdError { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { Self::BackendSpecific { err } => err.fmt(f), - Self::UnsupportedOS => f.write_str("Device ids are unsupported for this OS"), + Self::UnsupportedPlatform => f.write_str("Device ids are unsupported for this OS"), Self::ParseError => f.write_str("Failed to parse the device_id"), } } diff --git a/src/host/aaudio/mod.rs b/src/host/aaudio/mod.rs index c14c435a2..c308b5b57 100644 --- a/src/host/aaudio/mod.rs +++ b/src/host/aaudio/mod.rs @@ -328,7 +328,10 @@ impl DeviceTrait for Device { } fn id(&self) -> Result { - Err(DeviceIdError::UnsupportedOS) + match &self.0 { + None => Ok(DeviceId::aaudio(-1)), // Default device + Some(info) => Ok(DeviceId::aaudio(info.id)), + } } fn supported_input_configs( diff --git a/src/host/asio/device.rs b/src/host/asio/device.rs index bd091223a..cdd80bd40 100644 --- a/src/host/asio/device.rs +++ b/src/host/asio/device.rs @@ -57,7 +57,7 @@ impl Device { } fn id(&self) -> Result { - Err(DeviceIdError::UnsupportedOS) + Err(DeviceIdError::UnsupportedPlatform) } /// Gets the supported input configs. diff --git a/src/host/coreaudio/ios/mod.rs b/src/host/coreaudio/ios/mod.rs index ffaabefa1..d9d2d573d 100644 --- a/src/host/coreaudio/ios/mod.rs +++ b/src/host/coreaudio/ios/mod.rs @@ -76,7 +76,7 @@ impl Device { } fn id(&self) -> Result { - Err(DeviceIdError::UnsupportedOS) + Err(DeviceIdError::UnsupportedPlatform) } #[inline] diff --git a/src/host/coreaudio/macos/device.rs b/src/host/coreaudio/macos/device.rs index a3fa9b1bc..b20429040 100644 --- a/src/host/coreaudio/macos/device.rs +++ b/src/host/coreaudio/macos/device.rs @@ -397,7 +397,7 @@ impl Device { } fn id(&self) -> Result { - Ok(DeviceId::MacOS(self.audio_device_id)) + Ok(DeviceId::CoreAudio(self.audio_device_id)) } // Logic re-used between `supported_input_configs` and `supported_output_configs`. diff --git a/src/host/emscripten/mod.rs b/src/host/emscripten/mod.rs index 440ac64f5..480c101cd 100644 --- a/src/host/emscripten/mod.rs +++ b/src/host/emscripten/mod.rs @@ -71,7 +71,7 @@ impl Device { #[inline] fn id(&self) -> Result { - Err(DeviceIdError::UnsupportedOS) + Err(DeviceIdError::UnsupportedPlatform) } #[inline] diff --git a/src/host/jack/device.rs b/src/host/jack/device.rs index 68ef79ef2..3856d28f6 100644 --- a/src/host/jack/device.rs +++ b/src/host/jack/device.rs @@ -64,6 +64,10 @@ impl Device { } } + fn id(&self) -> Result { + Ok(DeviceId::jack(self.name.clone())) + } + pub fn default_output_device( name: &str, connect_ports_automatically: bool, @@ -147,7 +151,7 @@ impl DeviceTrait for Device { } fn id(&self) -> Result { - Err(DeviceIdError::UnsupportedOS) + Device::id(self) } fn supported_input_configs( diff --git a/src/host/null/mod.rs b/src/host/null/mod.rs index 24d8d68cc..031911ba3 100644 --- a/src/host/null/mod.rs +++ b/src/host/null/mod.rs @@ -49,7 +49,7 @@ impl DeviceTrait for Device { #[inline] fn id(&self) -> Result { - Err(DeviceIdError::UnsupportedOS) + Ok(DeviceId::Null) } #[inline] diff --git a/src/host/wasapi/device.rs b/src/host/wasapi/device.rs index abc328b45..bf1bcd23f 100644 --- a/src/host/wasapi/device.rs +++ b/src/host/wasapi/device.rs @@ -330,7 +330,7 @@ impl Device { unsafe { match self.device.GetId() { Ok(pwstr) => match pwstr.to_string() { - Ok(id_str) => Ok(DeviceId::Windows(id_str)), + Ok(id_str) => Ok(DeviceId::WASAPI(id_str)), Err(_e) => Err(DeviceIdError::ParseError), }, Err(e) => Err(DeviceIdError::BackendSpecific { err: e.into() }), diff --git a/src/host/webaudio/mod.rs b/src/host/webaudio/mod.rs index 49f05267d..29cd5baf7 100644 --- a/src/host/webaudio/mod.rs +++ b/src/host/webaudio/mod.rs @@ -87,7 +87,7 @@ impl Device { #[inline] fn id(&self) -> Result { - Err(DeviceIdError::UnsupportedOS) + Err(DeviceIdError::UnsupportedPlatform) } #[inline] diff --git a/src/lib.rs b/src/lib.rs index 8971e4c3c..80ce09b0b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -223,9 +223,51 @@ pub type FrameCount = u32; /// Currently only supports macOS and Windows (WASAPI) #[derive(Clone, Debug, Eq, PartialEq)] pub enum DeviceId { - MacOS(u32), - Windows(String), + CoreAudio(u32), + WASAPI(String), ALSA(String), + AAudio(String), + Jack(String), + Null, +} + +impl std::fmt::Display for DeviceId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + DeviceId::WASAPI(guid) => write!(f, "wasapi:{}", guid), + DeviceId::CoreAudio(uid) => write!(f, "coreaudio:{}", uid), + DeviceId::ALSA(pcm_id) => write!(f, "alsa:{}", pcm_id), + DeviceId::AAudio(id) => write!(f, "aaudio:{}", id), + DeviceId::Jack(name) => write!(f, "jack:{}", name), + DeviceId::Null => write!(f, "null"), + } + } +} + +impl std::str::FromStr for DeviceId { + type Err = DeviceIdError; + + fn from_str(s: &str) -> Result { + let (platform, data) = s.split_once(':').ok_or(DeviceIdError::ParseError)?; + + match platform { + "wasapi" => Ok(DeviceId::WASAPI(data.to_string())), + "coreaudio" => { + if let Ok(id) = data.parse::() { + Ok(DeviceId::CoreAudio(id)) + } else { + Err(DeviceIdError::ParseError) + } + } + "alsa" => Ok(DeviceId::ALSA(data.to_string())), + "aaudio" => { + let id = data.parse().map_err(|_| DeviceIdError::ParseError)?; + Ok(DeviceId::AAudio(id)) + } + "jack" => Ok(DeviceId::Jack(data.to_string())), + _ => Err(DeviceIdError::UnsupportedPlatform), + } + } } /// The buffer size used by the device. diff --git a/src/traits.rs b/src/traits.rs index 0c0b0303e..5b702f3bc 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -44,6 +44,15 @@ pub trait HostTrait { /// Can be empty if the system does not support audio in general. fn devices(&self) -> Result; + /// Fetches a [`Device`](DeviceTrait) based on a [`DeviceId`](DeviceId) if available + /// + /// Returns `None` if no device matching the id is found + fn device_by_id(&self, id: &DeviceId) -> Option { + self.devices() + .ok()? + .find(|device| device.id().ok().as_ref() == Some(id)) + } + /// The default input audio device on the system. /// /// Returns `None` if no input device is available. From 8b5b8d0c0c875185c5070a53e256e0b9a2a1b64b Mon Sep 17 00:00:00 2001 From: xephyris <114379647+GoeyGreen@users.noreply.github.com> Date: Wed, 24 Sep 2025 23:46:39 -0700 Subject: [PATCH 11/22] docs: update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 41c78be07..d769854b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Unreleased +- Add `DeviceId` enum supporting most APIs that returns a system device id for the audio device, allowing it to be reselected using the `HostTrait::device_by_id()` function - Add `Sample::bits_per_sample` method. - ALSA: Fix `BufferSize::Fixed` by selecting the nearest supported frame count. - ALSA: Change `BufferSize::Default` to use the device defaults. From 068e171f14b59222d90dd74ba023e92e19c2e120 Mon Sep 17 00:00:00 2001 From: xephyris <114379647+GoeyGreen@users.noreply.github.com> Date: Wed, 24 Sep 2025 23:49:58 -0700 Subject: [PATCH 12/22] fix: fix function names causing compile errors --- src/host/aaudio/mod.rs | 4 ++-- src/host/jack/device.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/host/aaudio/mod.rs b/src/host/aaudio/mod.rs index c308b5b57..226e9becb 100644 --- a/src/host/aaudio/mod.rs +++ b/src/host/aaudio/mod.rs @@ -329,8 +329,8 @@ impl DeviceTrait for Device { fn id(&self) -> Result { match &self.0 { - None => Ok(DeviceId::aaudio(-1)), // Default device - Some(info) => Ok(DeviceId::aaudio(info.id)), + None => Ok(DeviceId::AAudio(-1)), // Default device + Some(info) => Ok(DeviceId::AAudio(info.id)), } } diff --git a/src/host/jack/device.rs b/src/host/jack/device.rs index 3856d28f6..3d7175d98 100644 --- a/src/host/jack/device.rs +++ b/src/host/jack/device.rs @@ -65,7 +65,7 @@ impl Device { } fn id(&self) -> Result { - Ok(DeviceId::jack(self.name.clone())) + Ok(DeviceId::Jack(self.name.clone())) } pub fn default_output_device( From f9301b8b8ef246ce7026cf3aa1f7e15256538b9a Mon Sep 17 00:00:00 2001 From: xephyris <114379647+GoeyGreen@users.noreply.github.com> Date: Wed, 24 Sep 2025 23:56:17 -0700 Subject: [PATCH 13/22] fix: fix aaudio DeviceId type --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 80ce09b0b..8c5bfb15a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -226,7 +226,7 @@ pub enum DeviceId { CoreAudio(u32), WASAPI(String), ALSA(String), - AAudio(String), + AAudio(i32), Jack(String), Null, } From 2972b52388b47d97d9d9f9a7ed1eef57de6d6cf3 Mon Sep 17 00:00:00 2001 From: xephyris <114379647+GoeyGreen@users.noreply.github.com> Date: Thu, 25 Sep 2025 19:26:47 -0700 Subject: [PATCH 14/22] feat: implement macos DeviceId to use kAudioDevicePropertyDeviceUID instead of u32 --- src/host/coreaudio/macos/device.rs | 38 +++++++++++++++++++++++++++++- src/lib.rs | 10 ++------ 2 files changed, 39 insertions(+), 9 deletions(-) diff --git a/src/host/coreaudio/macos/device.rs b/src/host/coreaudio/macos/device.rs index b20429040..94fcf274e 100644 --- a/src/host/coreaudio/macos/device.rs +++ b/src/host/coreaudio/macos/device.rs @@ -10,12 +10,18 @@ use crate::{ OutputCallbackInfo, SampleFormat, SampleRate, StreamConfig, StreamError, SupportedBufferSize, SupportedStreamConfig, SupportedStreamConfigRange, SupportedStreamConfigsError, }; +use objc2_core_foundation::Type; +use objc2_core_foundation::{ + CFString +}; use coreaudio::audio_unit::render_callback::{self, data}; use coreaudio::audio_unit::{AudioUnit, Element, Scope}; use objc2_audio_toolbox::{ kAudioOutputUnitProperty_CurrentDevice, kAudioOutputUnitProperty_EnableIO, kAudioUnitProperty_StreamFormat, }; +use objc2_core_audio::kAudioDevicePropertyDeviceUID; +use objc2_core_audio::kAudioObjectPropertyElementMain; use objc2_core_audio::{ kAudioDevicePropertyAvailableNominalSampleRates, kAudioDevicePropertyBufferFrameSize, kAudioDevicePropertyBufferFrameSizeRange, kAudioDevicePropertyDeviceIsAlive, @@ -30,6 +36,7 @@ use objc2_core_audio_types::{ AudioBuffer, AudioBufferList, AudioStreamBasicDescription, AudioValueRange, }; + pub use super::enumerate::{ default_input_device, default_output_device, SupportedInputConfigs, SupportedOutputConfigs, }; @@ -44,6 +51,9 @@ use std::time::{Duration, Instant}; use super::property_listener::AudioObjectPropertyListener; use coreaudio::audio_unit::macos_helpers::get_device_name; + +type CFStringRef = *mut std::os::raw::c_void; + /// Attempt to set the device sample rate to the provided rate. /// Return an error if the requested sample rate is not supported by the device. fn set_sample_rate( @@ -397,7 +407,33 @@ impl Device { } fn id(&self) -> Result { - Ok(DeviceId::CoreAudio(self.audio_device_id)) + let property_address = AudioObjectPropertyAddress { + mSelector: kAudioDevicePropertyDeviceUID, + mScope: kAudioObjectPropertyScopeGlobal, + mElement: kAudioObjectPropertyElementMain, + }; + let mut name: CFStringRef = std::ptr::null_mut(); + let data_size = size_of::() as u32; + let status = unsafe { + AudioObjectGetPropertyData( + self.audio_device_id, + NonNull::from(&property_address), + 0, + null(), + NonNull::from(&data_size), + NonNull::from(&mut name).cast(), + ) + }; + if status == 0 { + let name_string = unsafe {CFString::wrap_under_get_rule(name as *mut CFString).to_string()}; + Ok(DeviceId::CoreAudio(name_string)) + } else { + Err(DeviceIdError::BackendSpecific { + err: BackendSpecificError { + description: "Device UID not found".to_string(), + } + }) + } } // Logic re-used between `supported_input_configs` and `supported_output_configs`. diff --git a/src/lib.rs b/src/lib.rs index 8c5bfb15a..e51842a01 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -223,7 +223,7 @@ pub type FrameCount = u32; /// Currently only supports macOS and Windows (WASAPI) #[derive(Clone, Debug, Eq, PartialEq)] pub enum DeviceId { - CoreAudio(u32), + CoreAudio(String), WASAPI(String), ALSA(String), AAudio(i32), @@ -252,13 +252,7 @@ impl std::str::FromStr for DeviceId { match platform { "wasapi" => Ok(DeviceId::WASAPI(data.to_string())), - "coreaudio" => { - if let Ok(id) = data.parse::() { - Ok(DeviceId::CoreAudio(id)) - } else { - Err(DeviceIdError::ParseError) - } - } + "coreaudio" => Ok(DeviceId::CoreAudio(data.to_string())), "alsa" => Ok(DeviceId::ALSA(data.to_string())), "aaudio" => { let id = data.parse().map_err(|_| DeviceIdError::ParseError)?; From 99fc8b7d619f1d87057a70faa088466ce93a0b48 Mon Sep 17 00:00:00 2001 From: xephyris <114379647+GoeyGreen@users.noreply.github.com> Date: Thu, 25 Sep 2025 19:28:02 -0700 Subject: [PATCH 15/22] fmt: reformat with rustfmt --- src/host/coreaudio/macos/device.rs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/host/coreaudio/macos/device.rs b/src/host/coreaudio/macos/device.rs index 94fcf274e..4ca3eef17 100644 --- a/src/host/coreaudio/macos/device.rs +++ b/src/host/coreaudio/macos/device.rs @@ -10,10 +10,6 @@ use crate::{ OutputCallbackInfo, SampleFormat, SampleRate, StreamConfig, StreamError, SupportedBufferSize, SupportedStreamConfig, SupportedStreamConfigRange, SupportedStreamConfigsError, }; -use objc2_core_foundation::Type; -use objc2_core_foundation::{ - CFString -}; use coreaudio::audio_unit::render_callback::{self, data}; use coreaudio::audio_unit::{AudioUnit, Element, Scope}; use objc2_audio_toolbox::{ @@ -35,7 +31,8 @@ use objc2_core_audio::{ use objc2_core_audio_types::{ AudioBuffer, AudioBufferList, AudioStreamBasicDescription, AudioValueRange, }; - +use objc2_core_foundation::CFString; +use objc2_core_foundation::Type; pub use super::enumerate::{ default_input_device, default_output_device, SupportedInputConfigs, SupportedOutputConfigs, @@ -425,13 +422,14 @@ impl Device { ) }; if status == 0 { - let name_string = unsafe {CFString::wrap_under_get_rule(name as *mut CFString).to_string()}; + let name_string = + unsafe { CFString::wrap_under_get_rule(name as *mut CFString).to_string() }; Ok(DeviceId::CoreAudio(name_string)) } else { - Err(DeviceIdError::BackendSpecific { + Err(DeviceIdError::BackendSpecific { err: BackendSpecificError { description: "Device UID not found".to_string(), - } + }, }) } } From 43bc3e01b317b10676540f8fc2a627f0dd6112b0 Mon Sep 17 00:00:00 2001 From: xephyris <114379647+GoeyGreen@users.noreply.github.com> Date: Fri, 26 Sep 2025 19:00:21 -0700 Subject: [PATCH 16/22] docs: update changelog --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d769854b1..5fb2fed48 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Unreleased -- Add `DeviceId` enum supporting most APIs that returns a system device id for the audio device, allowing it to be reselected using the `HostTrait::device_by_id()` function +- Add `HostTrait::id` method that returns a stable audio device ID. +- Add `HostTrait::device_by_id` to select a device by its stable ID. - Add `Sample::bits_per_sample` method. - ALSA: Fix `BufferSize::Fixed` by selecting the nearest supported frame count. - ALSA: Change `BufferSize::Default` to use the device defaults. From 6e93a1b1bc8d53091afda57ccabce609bb3ff876 Mon Sep 17 00:00:00 2001 From: xephyris <114379647+GoeyGreen@users.noreply.github.com> Date: Fri, 26 Sep 2025 19:26:53 -0700 Subject: [PATCH 17/22] feat: add asio support and reformat macos id function --- src/host/asio/device.rs | 2 +- src/host/coreaudio/macos/device.rs | 13 +++++++------ src/lib.rs | 3 +++ 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/host/asio/device.rs b/src/host/asio/device.rs index cdd80bd40..d38d28c70 100644 --- a/src/host/asio/device.rs +++ b/src/host/asio/device.rs @@ -57,7 +57,7 @@ impl Device { } fn id(&self) -> Result { - Err(DeviceIdError::UnsupportedPlatform) + Ok(DeviceId::ASIO(self.driver.name().to_string())) } /// Gets the supported input configs. diff --git a/src/host/coreaudio/macos/device.rs b/src/host/coreaudio/macos/device.rs index 4ca3eef17..9f3b00271 100644 --- a/src/host/coreaudio/macos/device.rs +++ b/src/host/coreaudio/macos/device.rs @@ -409,7 +409,7 @@ impl Device { mScope: kAudioObjectPropertyScopeGlobal, mElement: kAudioObjectPropertyElementMain, }; - let mut name: CFStringRef = std::ptr::null_mut(); + let mut uid: CFStringRef = std::ptr::null_mut(); let data_size = size_of::() as u32; let status = unsafe { AudioObjectGetPropertyData( @@ -418,13 +418,14 @@ impl Device { 0, null(), NonNull::from(&data_size), - NonNull::from(&mut name).cast(), + NonNull::from(&mut uid).cast(), ) }; - if status == 0 { - let name_string = - unsafe { CFString::wrap_under_get_rule(name as *mut CFString).to_string() }; - Ok(DeviceId::CoreAudio(name_string)) + check_os_status(status)?; + if !uid.is_null(){ + let uid_string = + unsafe { CFString::wrap_under_get_rule(uid as *mut CFString).to_string() }; + Ok(DeviceId::CoreAudio(uid_string)) } else { Err(DeviceIdError::BackendSpecific { err: BackendSpecificError { diff --git a/src/lib.rs b/src/lib.rs index e51842a01..fe3d2c975 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -225,6 +225,7 @@ pub type FrameCount = u32; pub enum DeviceId { CoreAudio(String), WASAPI(String), + ASIO(String), ALSA(String), AAudio(i32), Jack(String), @@ -235,6 +236,7 @@ impl std::fmt::Display for DeviceId { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { DeviceId::WASAPI(guid) => write!(f, "wasapi:{}", guid), + DeviceId::ASIO(guid) => write!(f, "asio:{}", guid), DeviceId::CoreAudio(uid) => write!(f, "coreaudio:{}", uid), DeviceId::ALSA(pcm_id) => write!(f, "alsa:{}", pcm_id), DeviceId::AAudio(id) => write!(f, "aaudio:{}", id), @@ -252,6 +254,7 @@ impl std::str::FromStr for DeviceId { match platform { "wasapi" => Ok(DeviceId::WASAPI(data.to_string())), + "asio" => Ok(DeviceId::ASIO(data.to_string())), "coreaudio" => Ok(DeviceId::CoreAudio(data.to_string())), "alsa" => Ok(DeviceId::ALSA(data.to_string())), "aaudio" => { From 59f4a9f6dc84633bfaa14064b970881d9e61899e Mon Sep 17 00:00:00 2001 From: xephyris <114379647+GoeyGreen@users.noreply.github.com> Date: Fri, 26 Sep 2025 19:28:27 -0700 Subject: [PATCH 18/22] fmt: reformat code --- src/host/asio/device.rs | 2 +- src/host/coreaudio/macos/device.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/host/asio/device.rs b/src/host/asio/device.rs index d38d28c70..85d851039 100644 --- a/src/host/asio/device.rs +++ b/src/host/asio/device.rs @@ -57,7 +57,7 @@ impl Device { } fn id(&self) -> Result { - Ok(DeviceId::ASIO(self.driver.name().to_string())) + Ok(DeviceId::ASIO(self.driver.name().to_string())) } /// Gets the supported input configs. diff --git a/src/host/coreaudio/macos/device.rs b/src/host/coreaudio/macos/device.rs index 9f3b00271..fdfc13d4d 100644 --- a/src/host/coreaudio/macos/device.rs +++ b/src/host/coreaudio/macos/device.rs @@ -422,7 +422,7 @@ impl Device { ) }; check_os_status(status)?; - if !uid.is_null(){ + if !uid.is_null() { let uid_string = unsafe { CFString::wrap_under_get_rule(uid as *mut CFString).to_string() }; Ok(DeviceId::CoreAudio(uid_string)) From b811266dade146943763e476f2877ba9d619c01f Mon Sep 17 00:00:00 2001 From: xephyris <114379647+GoeyGreen@users.noreply.github.com> Date: Fri, 26 Sep 2025 19:51:46 -0700 Subject: [PATCH 19/22] feat: return default for ios, emscripten, and webaudio --- src/host/coreaudio/ios/mod.rs | 2 +- src/host/emscripten/mod.rs | 2 +- src/host/webaudio/mod.rs | 2 +- src/lib.rs | 9 +++++++++ 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/host/coreaudio/ios/mod.rs b/src/host/coreaudio/ios/mod.rs index d9d2d573d..58d75fa8e 100644 --- a/src/host/coreaudio/ios/mod.rs +++ b/src/host/coreaudio/ios/mod.rs @@ -76,7 +76,7 @@ impl Device { } fn id(&self) -> Result { - Err(DeviceIdError::UnsupportedPlatform) + Ok(DeviceId::IOS("default".to_string())) } #[inline] diff --git a/src/host/emscripten/mod.rs b/src/host/emscripten/mod.rs index 480c101cd..b3cdd54d0 100644 --- a/src/host/emscripten/mod.rs +++ b/src/host/emscripten/mod.rs @@ -71,7 +71,7 @@ impl Device { #[inline] fn id(&self) -> Result { - Err(DeviceIdError::UnsupportedPlatform) + Ok(DeviceId::Emscripten("default".to_string())) } #[inline] diff --git a/src/host/webaudio/mod.rs b/src/host/webaudio/mod.rs index 29cd5baf7..e9152ed3f 100644 --- a/src/host/webaudio/mod.rs +++ b/src/host/webaudio/mod.rs @@ -87,7 +87,7 @@ impl Device { #[inline] fn id(&self) -> Result { - Err(DeviceIdError::UnsupportedPlatform) + Ok(DeviceId::WebAudio("default".to_string())) } #[inline] diff --git a/src/lib.rs b/src/lib.rs index fe3d2c975..9b35c16b1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -229,6 +229,9 @@ pub enum DeviceId { ALSA(String), AAudio(i32), Jack(String), + WebAudio(String), + Emscripten(String), + IOS(String), Null, } @@ -241,6 +244,9 @@ impl std::fmt::Display for DeviceId { DeviceId::ALSA(pcm_id) => write!(f, "alsa:{}", pcm_id), DeviceId::AAudio(id) => write!(f, "aaudio:{}", id), DeviceId::Jack(name) => write!(f, "jack:{}", name), + DeviceId::WebAudio(default) => write!(f, "webaudio:{}", default), + DeviceId::Emscripten(default) => write!(f, "emscripten:{}", default), + DeviceId::IOS(default) => write!(f, "ios:{}", default), DeviceId::Null => write!(f, "null"), } } @@ -262,6 +268,9 @@ impl std::str::FromStr for DeviceId { Ok(DeviceId::AAudio(id)) } "jack" => Ok(DeviceId::Jack(data.to_string())), + "webaudio" => Ok(DeviceId::WebAudio(data.to_string())), + "emscripten" => Ok(DeviceId::Emscripten(data.to_string())), + "ios" => Ok(DeviceId::IOS(data.to_string())), _ => Err(DeviceIdError::UnsupportedPlatform), } } From f40bb9dcb79c831aa6c66e41bf29992f7f5894ce Mon Sep 17 00:00:00 2001 From: xephyris <114379647+GoeyGreen@users.noreply.github.com> Date: Sun, 28 Sep 2025 23:16:35 -0700 Subject: [PATCH 20/22] feat: add in rest of audio APIs to from_str() --- src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib.rs b/src/lib.rs index 4274a6a0a..e3e6eac3d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -272,6 +272,7 @@ impl std::str::FromStr for DeviceId { "webaudio" => Ok(DeviceId::WebAudio(data.to_string())), "emscripten" => Ok(DeviceId::Emscripten(data.to_string())), "ios" => Ok(DeviceId::IOS(data.to_string())), + "null" => Ok(DeviceId::Null), _ => Err(DeviceIdError::UnsupportedPlatform), } } From 30645a597163f766502eafea0023855c1f143895 Mon Sep 17 00:00:00 2001 From: xephyris <114379647+GoeyGreen@users.noreply.github.com> Date: Tue, 30 Sep 2025 23:27:43 -0700 Subject: [PATCH 21/22] fix: change catch-all to todo! --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 91b158f1c..451800a35 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -273,7 +273,7 @@ impl std::str::FromStr for DeviceId { "emscripten" => Ok(DeviceId::Emscripten(data.to_string())), "ios" => Ok(DeviceId::IOS(data.to_string())), "null" => Ok(DeviceId::Null), - _ => Err(DeviceIdError::UnsupportedPlatform), + &_ => todo!("implement DeviceId::FromStr for {platform}"), } } } From 44ec96be987601381d7194a91c2905b2dc5e3624 Mon Sep 17 00:00:00 2001 From: xephyris <114379647+GoeyGreen@users.noreply.github.com> Date: Thu, 2 Oct 2025 23:44:26 -0700 Subject: [PATCH 22/22] fix: resolve parse error on null and deviceid implementations --- src/error.rs | 2 +- src/lib.rs | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/error.rs b/src/error.rs index 21d649f52..d39b071d7 100644 --- a/src/error.rs +++ b/src/error.rs @@ -66,7 +66,7 @@ impl From for DevicesError { } /// An error that may occur while attempting to retrieve a device id. -#[derive(Clone, Debug, PartialEq, Eq, Hash)] +#[derive(Clone, Debug, Eq, PartialEq)] pub enum DeviceIdError { /// See the [`BackendSpecificError`] docs for more information about this error variant. BackendSpecific { diff --git a/src/lib.rs b/src/lib.rs index 451800a35..eca1e963c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -222,7 +222,8 @@ pub type FrameCount = u32; /// The device ID of the audio device, on supported OSs /// Currently only supports macOS and Windows (WASAPI) -#[derive(Clone, Debug, Eq, PartialEq)] + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] pub enum DeviceId { CoreAudio(String), WASAPI(String), @@ -248,7 +249,7 @@ impl std::fmt::Display for DeviceId { DeviceId::WebAudio(default) => write!(f, "webaudio:{}", default), DeviceId::Emscripten(default) => write!(f, "emscripten:{}", default), DeviceId::IOS(default) => write!(f, "ios:{}", default), - DeviceId::Null => write!(f, "null"), + DeviceId::Null => write!(f, "null:null"), } } }