core-audio 0.1.0

Safe bindings to CoreAudio framework
Documentation
use std::{mem, ptr::NonNull};

use core_foundation::base::OSStatus;
use libc::c_void;
use objc2_core_audio as sys;
pub use sys::{
    AudioClassID, AudioObjectID, AudioObjectPropertyAddress, AudioObjectPropertyElement, AudioObjectPropertyScope, AudioObjectPropertySelector,
};

use crate::base::status_to_result;

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct AudioObject {
    id: AudioObjectID,
}

impl AudioObject {
    #[inline]
    pub const fn new(id: AudioObjectID) -> Self {
        Self {
            id,
        }
    }

    #[inline]
    pub const fn system() -> Self {
        Self::new(sys::kAudioObjectSystemObject as AudioObjectID)
    }

    #[inline]
    pub const fn unknown() -> Self {
        Self::new(sys::kAudioObjectUnknown)
    }

    #[inline]
    pub const fn id(self) -> AudioObjectID {
        self.id
    }

    #[inline]
    pub fn show(self) {
        unsafe { sys::AudioObjectShow(self.id) }
    }

    #[inline]
    pub fn has_property(self, address: &AudioObjectPropertyAddress) -> bool {
        unsafe { sys::AudioObjectHasProperty(self.id, NonNull::from(address)) }
    }

    #[inline]
    pub fn is_property_settable(self, address: &AudioObjectPropertyAddress) -> Result<bool, OSStatus> {
        let mut is_settable = 0;
        let status = unsafe { sys::AudioObjectIsPropertySettable(self.id, NonNull::from(address), NonNull::from(&mut is_settable)) };
        status_to_result(status).map(|_| is_settable != 0)
    }

    #[inline]
    pub fn property_data_size(self, address: &AudioObjectPropertyAddress) -> Result<u32, OSStatus> {
        self.property_data_size_with_qualifier(address, &[])
    }

    pub fn property_data_size_with_qualifier(self, address: &AudioObjectPropertyAddress, qualifier: &[u8]) -> Result<u32, OSStatus> {
        let mut data_size = 0;
        let status = unsafe {
            sys::AudioObjectGetPropertyDataSize(
                self.id,
                NonNull::from(address),
                qualifier.len() as u32,
                qualifier.as_ptr().cast(),
                NonNull::from(&mut data_size),
            )
        };
        status_to_result(status).map(|_| data_size)
    }

    #[inline]
    pub fn get_property<T: Copy>(self, address: &AudioObjectPropertyAddress) -> Result<T, OSStatus> {
        self.get_property_with_qualifier(address, &[])
    }

    pub fn get_property_with_qualifier<T: Copy>(self, address: &AudioObjectPropertyAddress, qualifier: &[u8]) -> Result<T, OSStatus> {
        let mut value = mem::MaybeUninit::<T>::uninit();
        let mut data_size = mem::size_of::<T>() as u32;
        let status = unsafe {
            sys::AudioObjectGetPropertyData(
                self.id,
                NonNull::from(address),
                qualifier.len() as u32,
                qualifier.as_ptr().cast(),
                NonNull::from(&mut data_size),
                NonNull::new_unchecked(value.as_mut_ptr().cast()),
            )
        };
        status_to_result(status)?;
        Ok(unsafe { value.assume_init() })
    }

    #[inline]
    pub fn get_property_bytes(self, address: &AudioObjectPropertyAddress) -> Result<Vec<u8>, OSStatus> {
        self.get_property_bytes_with_qualifier(address, &[])
    }

    pub fn get_property_bytes_with_qualifier(self, address: &AudioObjectPropertyAddress, qualifier: &[u8]) -> Result<Vec<u8>, OSStatus> {
        let mut bytes = vec![0; self.property_data_size_with_qualifier(address, qualifier)? as usize];
        let mut data_size = bytes.len() as u32;
        let out_data = NonNull::new(bytes.as_mut_ptr().cast::<c_void>()).unwrap_or_else(NonNull::dangling);
        let status = unsafe {
            sys::AudioObjectGetPropertyData(
                self.id,
                NonNull::from(address),
                qualifier.len() as u32,
                qualifier.as_ptr().cast(),
                NonNull::from(&mut data_size),
                out_data,
            )
        };
        status_to_result(status)?;
        bytes.truncate(data_size as usize);
        Ok(bytes)
    }

    #[inline]
    pub fn get_property_array<T: Copy>(self, address: &AudioObjectPropertyAddress) -> Result<Vec<T>, OSStatus> {
        self.get_property_array_with_qualifier(address, &[])
    }

    pub fn get_property_array_with_qualifier<T: Copy>(self, address: &AudioObjectPropertyAddress, qualifier: &[u8]) -> Result<Vec<T>, OSStatus> {
        let element_size = mem::size_of::<T>();
        let data_size = self.property_data_size_with_qualifier(address, qualifier)? as usize;
        if element_size == 0 || !data_size.is_multiple_of(element_size) {
            return Err(sys::kAudioHardwareBadPropertySizeError);
        }

        let mut values = Vec::<T>::with_capacity(data_size / element_size);
        let mut io_data_size = data_size as u32;
        let out_data = NonNull::new(values.as_mut_ptr().cast::<c_void>()).unwrap_or_else(NonNull::dangling);
        let status = unsafe {
            sys::AudioObjectGetPropertyData(
                self.id,
                NonNull::from(address),
                qualifier.len() as u32,
                qualifier.as_ptr().cast(),
                NonNull::from(&mut io_data_size),
                out_data,
            )
        };
        status_to_result(status)?;
        if !(io_data_size as usize).is_multiple_of(element_size) {
            return Err(sys::kAudioHardwareBadPropertySizeError);
        }
        unsafe { values.set_len(io_data_size as usize / element_size) };
        Ok(values)
    }

    #[inline]
    pub fn set_property<T: Copy>(self, address: &AudioObjectPropertyAddress, value: &T) -> Result<(), OSStatus> {
        self.set_property_with_qualifier(address, &[], value)
    }

    pub fn set_property_with_qualifier<T: Copy>(self, address: &AudioObjectPropertyAddress, qualifier: &[u8], value: &T) -> Result<(), OSStatus> {
        let status = unsafe {
            sys::AudioObjectSetPropertyData(
                self.id,
                NonNull::from(address),
                qualifier.len() as u32,
                qualifier.as_ptr().cast(),
                mem::size_of::<T>() as u32,
                NonNull::from(value).cast(),
            )
        };
        status_to_result(status)
    }

    #[inline]
    pub fn set_property_bytes(self, address: &AudioObjectPropertyAddress, bytes: &[u8]) -> Result<(), OSStatus> {
        self.set_property_bytes_with_qualifier(address, &[], bytes)
    }

    pub fn set_property_bytes_with_qualifier(self, address: &AudioObjectPropertyAddress, qualifier: &[u8], bytes: &[u8]) -> Result<(), OSStatus> {
        let data = NonNull::new(bytes.as_ptr() as *mut c_void).unwrap_or_else(NonNull::dangling);
        let status = unsafe {
            sys::AudioObjectSetPropertyData(
                self.id,
                NonNull::from(address),
                qualifier.len() as u32,
                qualifier.as_ptr().cast(),
                bytes.len() as u32,
                data,
            )
        };
        status_to_result(status)
    }
}

impl From<AudioObjectID> for AudioObject {
    #[inline]
    fn from(id: AudioObjectID) -> Self {
        Self::new(id)
    }
}

#[inline]
pub const fn property_address(
    selector: AudioObjectPropertySelector,
    scope: AudioObjectPropertyScope,
    element: AudioObjectPropertyElement,
) -> AudioObjectPropertyAddress {
    AudioObjectPropertyAddress {
        mSelector: selector,
        mScope: scope,
        mElement: element,
    }
}

#[inline]
pub const fn global_property_address(selector: AudioObjectPropertySelector) -> AudioObjectPropertyAddress {
    property_address(selector, sys::kAudioObjectPropertyScopeGlobal, sys::kAudioObjectPropertyElementMain)
}

#[inline]
pub const fn input_property_address(selector: AudioObjectPropertySelector) -> AudioObjectPropertyAddress {
    property_address(selector, sys::kAudioObjectPropertyScopeInput, sys::kAudioObjectPropertyElementMain)
}

#[inline]
pub const fn output_property_address(selector: AudioObjectPropertySelector) -> AudioObjectPropertyAddress {
    property_address(selector, sys::kAudioObjectPropertyScopeOutput, sys::kAudioObjectPropertyElementMain)
}