-
Notifications
You must be signed in to change notification settings - Fork 535
Channel names: CoreAudio and ASIO #1254
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
46222e6
3aae792
828e896
8374eff
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -943,6 +943,18 @@ impl Driver { | |
| let mut dcb = DRIVER_EVENT_CALLBACKS.lock().unwrap(); | ||
| dcb.retain(|&(id, _)| id != rem_id); | ||
| } | ||
|
|
||
| /// Returns the name of the channel at the given index. | ||
| /// | ||
| /// `channel` is a 0-based channel index. `is_input` selects the input (`true`) or output | ||
| /// (`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> { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure about the |
||
| let _guard = self.inner.lock_state(); | ||
| let info = asio_channel_info(channel, is_input)?; | ||
| Ok(driver_name_to_utf8(&info.name).into_owned()) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. |
||
| } | ||
| } | ||
|
|
||
| impl DriverState { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -26,6 +26,8 @@ pub struct Device { | |
| input_sample_format: Option<SampleFormat>, | ||
| output_sample_format: Option<SampleFormat>, | ||
| supported_sample_rates: Box<[SampleRate]>, | ||
| input_channel_names: Box<[String]>, | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? |
||
| output_channel_names: Box<[String]>, | ||
|
|
||
| // Input and/or Output stream. | ||
| // A driver can only have one of each. | ||
|
|
@@ -127,6 +129,26 @@ impl Device { | |
| } | ||
| configs | ||
| } | ||
|
|
||
| pub fn get_channel_name(&self, channel_index: u16, input: bool) -> Result<String, Error> { | ||
| let names = if input { | ||
| &self.input_channel_names | ||
| } else { | ||
| &self.output_channel_names | ||
| }; | ||
|
|
||
| names.get(channel_index as usize).cloned().ok_or_else(|| { | ||
| Error::with_message( | ||
| ErrorKind::InvalidInput, | ||
| format!( | ||
| "channel index {} is out of range (device has {} {} channels)", | ||
| channel_index, | ||
| names.len(), | ||
| if input { "input" } else { "output" }, | ||
| ), | ||
| ) | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| impl PartialEq for Device { | ||
|
|
@@ -213,6 +235,13 @@ impl Iterator for Devices { | |
| .filter(|&r| driver.can_sample_rate(r.into()).unwrap_or(false)) | ||
| .collect(); | ||
|
|
||
| let input_channel_names: Box<[String]> = (0..channels.ins) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
| .map(|ch| driver.channel_name(ch, true).unwrap_or_default()) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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}"? |
||
| .collect(); | ||
| let output_channel_names: Box<[String]> = (0..channels.outs) | ||
| .map(|ch| driver.channel_name(ch, false).unwrap_or_default()) | ||
| .collect(); | ||
|
|
||
| self.current_driver = Some(driver); | ||
|
|
||
| let asio_streams = Arc::new(Mutex::new(sys::AsioStreams { | ||
|
|
@@ -230,6 +259,8 @@ impl Iterator for Devices { | |
| input_sample_format, | ||
| output_sample_format, | ||
| supported_sample_rates, | ||
| input_channel_names, | ||
| output_channel_names, | ||
| asio_streams, | ||
| // Initialize with sentinel value so it never matches global flag state (0 or 1). | ||
| current_callback_flag: Arc::new(AtomicU32::new(u32::MAX)), | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -31,8 +31,8 @@ use objc2_core_audio::{ | |
| kAudioDevicePropertyLatency, kAudioDevicePropertyNominalSampleRate, | ||
| kAudioDevicePropertySafetyOffset, kAudioDevicePropertyStreamConfiguration, | ||
| kAudioDevicePropertyStreamFormat, kAudioObjectPropertyClass, kAudioObjectPropertyElementMain, | ||
| kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyScopeInput, | ||
| kAudioObjectPropertyScopeOutput, | ||
| kAudioObjectPropertyElementName, kAudioObjectPropertyScopeGlobal, | ||
| kAudioObjectPropertyScopeInput, kAudioObjectPropertyScopeOutput, | ||
| }; | ||
| use objc2_core_audio_types::{ | ||
| AudioBuffer, AudioBufferList, AudioStreamBasicDescription, AudioValueRange, | ||
|
|
@@ -357,6 +357,10 @@ impl DeviceTrait for Device { | |
| timeout, | ||
| ) | ||
| } | ||
|
|
||
| fn get_channel_name(&self, channel_index: u16, input: bool) -> Result<String, Error> { | ||
| Device::get_channel_name(self, channel_index, input) | ||
| } | ||
| } | ||
|
|
||
| #[derive(Clone)] | ||
|
|
@@ -690,6 +694,24 @@ impl Device { | |
| .map(|mut configs| configs.next().is_some()) | ||
| .unwrap_or(false) | ||
| } | ||
|
|
||
| fn get_channel_name(&self, channel_index: u16, input: bool) -> Result<String, Error> { | ||
| if input && !self.supports_input() { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| return Err(Error::with_message( | ||
| ErrorKind::InvalidInput, | ||
| "Device does not support input", | ||
| )); | ||
| } | ||
|
|
||
| if !input && !self.supports_output() { | ||
| return Err(Error::with_message( | ||
| ErrorKind::InvalidInput, | ||
| "Device does not support output", | ||
| )); | ||
| } | ||
|
|
||
| get_channel_name_for_device(self.audio_device_id, channel_index, input) | ||
| } | ||
| } | ||
|
|
||
| impl Device { | ||
|
|
@@ -1116,3 +1138,45 @@ pub(crate) fn get_device_buffer_frame_size( | |
| )?; | ||
| Ok(frames as usize) | ||
| } | ||
|
|
||
| fn get_channel_name_for_device( | ||
| device_id: AudioDeviceID, | ||
| channel_index: u16, | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Querying an out-of-range |
||
| input: bool, | ||
| ) -> Result<String, Error> { | ||
| let mut channel_name: *mut CFString = std::ptr::null_mut(); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As this |
||
| let mut data_size = size_of::<*mut CFString>() as u32; | ||
|
|
||
| let property_address = AudioObjectPropertyAddress { | ||
| mSelector: kAudioObjectPropertyElementName, | ||
| mScope: if input { | ||
| kAudioObjectPropertyScopeInput | ||
| } else { | ||
| kAudioObjectPropertyScopeOutput | ||
| }, | ||
| // Channels numbers start on 1 here | ||
| mElement: channel_index as u32 + 1, | ||
| }; | ||
|
|
||
| let status = unsafe { | ||
| AudioObjectGetPropertyData( | ||
| device_id, | ||
| NonNull::from(&property_address), | ||
| 0, | ||
| null(), | ||
| NonNull::from(&mut data_size), | ||
| NonNull::from(&mut channel_name).cast(), | ||
| ) | ||
| }; | ||
| check_os_status(status)?; | ||
|
|
||
| if !channel_name.is_null() { | ||
| let raw_name = unsafe { CFRetained::from_raw(NonNull::new(channel_name).unwrap()) }; | ||
| Ok(raw_name.to_string()) | ||
| } else { | ||
| Err(Error::with_message( | ||
| ErrorKind::Other, | ||
| "channel name is null", | ||
| )) | ||
| } | ||
| } | ||
There was a problem hiding this comment.
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.