Skip to content

Channel names: CoreAudio and ASIO - #1254

Open
nico-franco-gomez wants to merge 14 commits into
RustAudio:masterfrom
nico-franco-gomez:feature/channel-names-coreaudio-asio
Open

Channel names: CoreAudio and ASIO#1254
nico-franco-gomez wants to merge 14 commits into
RustAudio:masterfrom
nico-franco-gomez:feature/channel-names-coreaudio-asio

Conversation

@nico-franco-gomez

Copy link
Copy Markdown
Contributor

This PR refers to this issue.

I implemented a version of this to showcase how I imagined it. When I wrote the issue, I hadn't realized that the error convention had changed from 0.16 to 0.17. This PR adheres to the current error convention.

@roderickvd

Copy link
Copy Markdown
Member

Sorry for the late reply on this one.

I think this is a worthwhile addition as a trait method. You could add a default implementation to the trait method to prevent the duplication at each backend that doesn't support it.

Before we go further, could you rebase this onto the develop branch? We're staging upcoming feature work there rather than master right now. I intend to promote it to master for a v0.19 release after we push out a v0.18.2 bug fix release.

Happy to take a deeper look once it's rebased!

@nico-franco-gomez
nico-franco-gomez force-pushed the feature/channel-names-coreaudio-asio branch from c27ac0a to f2ba085 Compare July 23, 2026 17:55
@nico-franco-gomez
nico-franco-gomez changed the base branch from master to develop July 23, 2026 17:55
@nico-franco-gomez
nico-franco-gomez force-pushed the feature/channel-names-coreaudio-asio branch from f2ba085 to 8374eff Compare July 23, 2026 17:57
@nico-franco-gomez

Copy link
Copy Markdown
Contributor Author

No problem regarding the delay.

I rebased and implemented the comments while trying to keep a compact, readable git history. Not sure why that specific Linux check is failing, though...

@roderickvd roderickvd left a comment

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.

Thanks again, please find my first review attached.

I've promoted develop to master, so please rebase on master where we'll continue the 0.19.0 work. This should be easier to the last rebase, as it's just continuing the develop branch under a new name, basically.


fn get_channel_name_for_device(
device_id: AudioDeviceID,
channel_index: u16,

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.

Querying an out-of-range channel_index will return a BackendError I think, when InvalidInput would be preferred.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good point, I introduced a new check for that in here

Comment thread src/host/asio/device.rs Outdated
.collect();

let input_channel_names: Box<[String]> = (0..channels.ins)
.map(|ch| driver.channel_name(ch, true).unwrap_or_default())

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.

Question: rather than defaulting to an empty string, what if we returned something recognizable like "Channel {ch}"?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I introduced these default names, but I thought about the case where the driver leaves that unfilled knowingly, so I'd argue that for the low-level crate, the caller should decide what the default name in that case should be. What do you think?

pub fn channel_name(&self, channel: i32, is_input: bool) -> Result<String, AsioError> {
let _guard = self.inner.lock_state();
let info = asio_channel_info(channel, is_input)?;
Ok(driver_name_to_utf8(&info.name).into_owned())

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.

I think this was already the case elsewhere with other names, but it's occurring to me that this will UB if there's ever a driver that doesn't NUL-terminate.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good catch

Comment thread src/host/asio/device.rs Outdated
.filter(|&r| driver.can_sample_rate(r.into()).unwrap_or(false))
.collect();

let input_channel_names: Box<[String]> = (0..channels.ins)

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.

We may want to check that the number of channels is greater than (or equal to) 0. I remember that ASIO often returns an i32 and we wouldn't want a negative value overflowing this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done

Comment thread src/host/coreaudio/macos/device.rs Outdated
channel_index: u16,
input: bool,
) -> Result<String, Error> {
let mut channel_name: *mut CFString = std::ptr::null_mut();

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.

As this CFString stuff is getting used more often, we could consider having a helper for it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done

Comment thread src/host/coreaudio/macos/device.rs Outdated
}

fn get_channel_name(&self, channel_index: u16, input: bool) -> Result<String, Error> {
if input && !self.supports_input() {

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.

supports_input/output aren't free, so if it's just to detail the error message then maybe we should just use the input argument but not re-query the device (or cache it, but that may be scope creep).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

True. Since there's now the channel_index check, the error message is descriptive enough so that these checks are redundant.

Comment thread src/host/asio/device.rs
input_sample_format: Option<SampleFormat>,
output_sample_format: Option<SampleFormat>,
supported_sample_rates: Box<[SampleRate]>,
input_channel_names: Box<[String]>,

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.

CoreAudio doesn't seem to cache this. What's the preferred approach? Lazily like CoreAudio or caching it during enumeration here?

@nico-franco-gomez nico-franco-gomez Aug 28, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I honestly prefer to do it lazily, my rationale is the following: the operation for CoreAudio is low-cost and can be done on demand. Since most users won't need it, I prefer for a lower-level crate to avoid such implicit operations. The operation for ASIO on the other hand is not low-cost because it requires loading the driver and potentially unloading the previous one. It's also risky if there's a callback running in another thread. That's why I find it reasonable to cache it there, but here, I would argue it's unnecessary.

Comment thread asio-sys/src/bindings/mod.rs Outdated
@@ -943,6 +943,18 @@ impl Driver {
let mut dcb = DRIVER_EVENT_CALLBACKS.lock().unwrap();
dcb.retain(|&(id, _)| id != rem_id);

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.

cpal doesn't use get_ for accessors.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Here you're referring to the get_channel_name method, right?

/// (`false`) direction.
///
/// The driver must already be loaded (i.e. this `Driver` instance must be alive).
pub fn channel_name(&self, channel: i32, is_input: bool) -> Result<String, AsioError> {

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.

Not sure about the input: bool or is_input: bool arguments in public functions. In the rest of cpal, that's split between supports_input/output, default_input/output_config, etc. That's more readable than channel_name(1, true) - what argument isn't self-explanatory.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

before rewriting this completely, what do you think could be a better API? Maybe input_channel_name(1) and output_channel_name(1) ?

@nico-franco-gomez
nico-franco-gomez force-pushed the feature/channel-names-coreaudio-asio branch from 8374eff to 6bd1356 Compare August 27, 2026 00:48
@nico-franco-gomez
nico-franco-gomez changed the base branch from develop to master August 27, 2026 00:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants