# coreaudio
A safe, idiomatic Rust wrapper around the macOS CoreAudio Hardware Abstraction Layer (HAL).
This crate provides typed access to audio devices, streams, and system-level audio objects, with compile-time guarantees around property access permissions and listener support.
## Features
- **Type-safe object model** — `AudioObject<System>`, `AudioObject<Device>`, `AudioObject<Stream>`, `AudioObject<Process>` and `AudioObject<Tap>` expose only the operations valid for each object type.
- **Compile-time property safety** — Properties carry phantom types encoding their value type, owning object, read/write access, and listenability. Attempting to write a read-only property or listen to a non-listenable one is a compile error.
- **Property builder methods** — Properties that require an element (channel) or qualifier data expose `.for_element(n)` and `.with_qualifier(value)` builder methods. Forgetting to call them is a compile error.
- **Property listeners** — Subscribe to property changes with `add_listener`, then poll with `latest()`, drain with `all_since_last_check()`, or block with `block_until_change()` / `block_for_duration()`.
- **Callback listeners** — `add_listener_with` hands every change straight to a closure on CoreAudio's notification thread, in order, with nothing blocking a thread to wait for it.
- **Force methods** — `force_set_property`, `force_add_listener` and `force_add_listener_with` skip the property checks for devices that don't follow the usual conventions.
- **IO Procs** — Register audio render callbacks on devices with `add_io_proc` and control playback with `play()` / `pause()`. Every buffer also carries the cycle's `Time` stamps, with the sample, host, word clock and SMPTE clocks each returned only if the device filled them in.
- **Aggregate devices** — Combine several devices into one with `DeviceAggregateDevice`, or (with the `process-tap` feature) wrap a tap in an aggregate with `AppTapAggregateDevice`. Both can be private to your process or public, and are destroyed when dropped.
- **Processes and taps** — List the processes using CoreAudio, and (with the `process-tap` feature) tap one app's audio before it's mixed, read through an aggregate device like any input.
- **Channel layouts** — Read, write and listen to a device's speaker layout (`DEVICE_PREFERRED_CHANNEL_LAYOUT_OUTPUT`), expand predefined layouts with `layout_for_tag`, and get macOS's names for layouts and speakers.
- **Structured error handling** — All CoreAudio `OSStatus` codes are mapped to a typed `ErrorKind` enum with human-readable four-character-code formatting.
- **Format support** — Rich enums for audio format IDs (Linear PCM, AAC variants, ALAC, AC3, Opus, MP3, etc.), format flags, sample formats, transport types, terminal types, and sample resampling utilities.
## Requirements
- macOS (the crate is gated with `#[cfg(target_os = "macos")]`)
- [`objc2-core-audio`](https://crates.io/crates/objc2-core-audio), [`objc2-core-audio-types`](https://crates.io/crates/objc2-core-audio-types) and [`objc2-audio-toolbox`](https://crates.io/crates/objc2-audio-toolbox) for raw FFI bindings
- [`core-foundation`](https://crates.io/crates/core-foundation) for `CFString` handling
- macOS 14.2+ for process objects and taps. The optional `process-tap` feature adds [`objc2`](https://crates.io/crates/objc2) and [`objc2-foundation`](https://crates.io/crates/objc2-foundation) which won't compile on older versions of macOS.
## Quick start
```rust
use coreaudio::{AudioObject, System, Scope, DEVICE_NAME};
fn main() -> Result<(), coreaudio::CoreAudioError> {
let system = AudioObject::<System>::default();
// List all output devices
let devices = system.devices_with_scope(Scope::Output)?;
for device in &devices {
let name: String = device.get_property(DEVICE_NAME)?;
println!("{}", name);
}
Ok(())
}
```
## Usage
### Querying device properties
```rust
use coreaudio::{
AudioObject, System, Scope,
DEVICE_NAME, DEVICE_UID, DEVICE_NOMINAL_SAMPLE_RATE, DEVICE_IS_ALIVE,
};
let system = AudioObject::<System>::default();
let device = system.current_device(Scope::Output)?;
let name: String = device.get_property(DEVICE_NAME)?;
let uid: String = device.get_property(DEVICE_UID)?;
let sample_rate: f64 = device.get_property(DEVICE_NOMINAL_SAMPLE_RATE)?;
let alive: bool = device.get_property(DEVICE_IS_ALIVE)?;
println!("{name} ({uid}) — {sample_rate} Hz, alive: {alive}");
```
### Setting writable properties
```rust
use coreaudio::{AudioObject, System, Scope, DEVICE_NOMINAL_SAMPLE_RATE, DEVICE_BUFFER_FRAME_SIZE};
let system = AudioObject::<System>::default();
let device = system.current_device(Scope::Output)?;
device.set_property(DEVICE_NOMINAL_SAMPLE_RATE, 48000.0)?;
device.set_property(DEVICE_BUFFER_FRAME_SIZE, 512u32)?;
```
### Listening for property changes
```rust
use coreaudio::{AudioObject, System, Scope, DEVICE_NOMINAL_SAMPLE_RATE};
use std::time::Duration;
let system = AudioObject::<System>::default();
let device = system.current_device(Scope::Output)?;
let listener = device.add_listener(DEVICE_NOMINAL_SAMPLE_RATE)?;
// Non-blocking — returns the most recent change, or None
if let Some(new_rate) = listener.latest() {
println!("Sample rate changed to {new_rate}");
}
// Blocking with timeout
match listener.block_for_duration(Duration::from_secs(5)) {
Ok(rate) => println!("Changed to {rate}"),
Err(e) => println!("Timed out or error: {e}"),
}
```
### Calling a closure on every change
`add_listener_with` skips the channel: every change is handed straight to a closure, in the order CoreAudio reports it, along with any error reading the new value. The closure runs on CoreAudio's notification thread, so keep it short and hand the value on to wherever the work happens. The returned `CallbackListener` can be moved between threads, and dropping it unregisters the listener.
```rust
use coreaudio::{AudioObject, System, Scope, DEVICE_NOMINAL_SAMPLE_RATE};
use std::sync::mpsc;
let system = AudioObject::<System>::default();
let device = system.current_device(Scope::Output)?;
let (tx, rx) = mpsc::channel();
let listener = device.add_listener_with(DEVICE_NOMINAL_SAMPLE_RATE, move |rate| {
// `rate` is `Result<f64, CoreAudioError>`
let _ = tx.send(rate);
})?;
// ... later
drop(listener);
```
### Tapping an app's audio
Every process that has used CoreAudio has a process object. A tap has no IO of its own; `AppTapAggregateDevice::with_tap` wraps it in a private aggregate device, which is read with an ordinary IO proc. `with_tap_public` makes the aggregate visible system-wide instead.
```rust
use coreaudio::{
AppTapAggregateDevice, AudioObject, ProcessTap, Scope, System, TapMute,
PROCESS_BUNDLE_ID,
};
let system = AudioObject::<System>::default();
let music: Vec<_> = system
.processes()?
.into_iter()
.filter(|process| process.get_property(PROCESS_BUNDLE_ID).is_ok_and(|id| id == "com.apple.Music"))
.collect();
// Sound goes silent while this is read, and plays normally again once it isn't.
let tap = ProcessTap::stereo_mixdown(&music, "Music tap", TapMute::MutedWhenTapped)?;
let aggregate = AppTapAggregateDevice::with_tap("Music tap", "com.example.music-tap", tap.uid())?;
})?;
proc.play()?;
// Drop in this order:
// IO Proc
// Aggregate device
// Tap
drop(proc);
drop(aggregate);
drop(tap);
```
`stereo_mixdown` folds everything the processes play into two channels, which on a wide device comes out far quieter than they actually play. `ProcessTap::device_stream(&processes, device_uid, 0, name, mute)` instead taps what they play into one stream of a device, channel for channel at full level, with that stream's channel count and the device's rate.
A private aggregate can only be seen by the creating process, and macOS removes it if that process exits. Tapping needs the user's **System Audio Recording** permission (Privacy & Security). Without it a tap is still created but delivers silence, and a muting tap still mutes, so check the permission first.
### Combining devices into an aggregate
`DeviceAggregateDevice::new` combines several devices into one, so their channels can be read and written through a single IO proc. The first device is the clock source and every other device is drift corrected to it. The aggregate gets a UID that is unique within your process, and is destroyed when it is dropped, so drop it before the devices it contains.
```rust
use coreaudio::{AudioObject, DeviceAggregateDevice, Scope, System};
let system = AudioObject::<System>::default();
let inputs = system.devices_with_scope(Scope::Input)?;
// True to create a device private to the proccess, false for a public device
let aggregate = DeviceAggregateDevice::new("My aggregate", inputs, true)?;
let device = aggregate.device();
```
### Properties that require an element or qualifier
Some properties target a specific channel (element) or need a qualifier value before they can be used. Call `.for_element()`, `.with_qualifier()`, or both — in either order — to complete the property. Forgetting is a compile error.
Channel names are usually per direction, so read them with `DEVICE_INPUT_ELEMENT_NAME` or `DEVICE_OUTPUT_ELEMENT_NAME`; the global `OBJECT_ELEMENT_NAME` often reads empty on the same device.
```rust
use coreaudio::{AudioObject, System, Scope, DEVICE_INPUT_ELEMENT_NAME, MissingElement};
let system = AudioObject::<System>::default();
let device = system.current_device(Scope::Input)?;
// e.g. "Channel 1" on an iPhone, "Mic/Line 1" on an audio interface
let name: String = device.get_property(DEVICE_INPUT_ELEMENT_NAME.for_element(1))?;
```
```rust
use coreaudio::{
AudioObject, System, Scope,
DEVICE_OUTPUT_VOLUME_SCALAR, DEVICE_OUTPUT_MUTE,
DEVICE_DATA_SOURCE, DEVICE_DATA_SOURCE_NAME,
MissingElement, MissingQualifier,
};
let system = AudioObject::<System>::default();
let device = system.current_device(Scope::Output)?;
// Element-only: read the output volume of channel 1
let volume: f32 = device.get_property(DEVICE_OUTPUT_VOLUME_SCALAR.for_element(1))?;
// Element-only: mute output channel 1
device.set_property(DEVICE_OUTPUT_MUTE.for_element(1), true)?;
// Element-only: read the active data source on channel 1
let source_id: u32 = device.get_property(DEVICE_DATA_SOURCE.for_element(1))?;
// Element + qualifier: look up the name of that source
let source_name: String = device.get_property(
DEVICE_DATA_SOURCE_NAME
.for_element(1)
.with_qualifier(source_id)
)?;
println!("Active source: {source_name}");
```
### Bypassing the property checks
The typed `get_property`, `set_property` and `add_listener` refuse properties the crate marks as unsupported. The markers describe what was tested, though some devices don't follow the usual conventions. The `force_` methods skip the checks and leave it to you to know the property behaves.
- `force_set_property` skips the writable, element, qualifier and object-kind checks. It is `unsafe`, because a bad write can change a device in ways the crate can't predict.
- `force_add_listener` and `force_add_listener_with` skip the `Listenable`, element, qualifier, and object-kind checks.
The listener reads the property with no qualifier data, so a property that needs a qualifier fails to read. `force_add_listener_with` hands that error to your closure, so use it when you aren't sure the property works.
```rust
use coreaudio::{AudioObject, System, Scope, DEVICE_CAN_BE_DEFAULT, DEVICE_OUTPUT_VOLUME_SCALAR};
let system = AudioObject::<System>::default();
let device = system.current_device(Scope::Output)?;
// `DEVICE_CAN_BE_DEFAULT` is not marked `Listenable`, so `add_listener` won't take it.
println!("{value:?}");
})}?;
// Writes the main element without going through `.for_element()`.
unsafe { device.force_set_property(DEVICE_OUTPUT_VOLUME_SCALAR, 0.5)?; }
```
### Working with streams
```rust
use coreaudio::{AudioObject, System, Scope, STREAM_VIRTUAL_FORMAT, STREAM_NAME};
let system = AudioObject::<System>::default();
let device = system.current_device(Scope::Output)?;
let streams = device.streams_with_scope(Scope::Output)?;
for stream in &streams {
let name: String = stream.get_property(STREAM_NAME)?;
let format = stream.get_property(STREAM_VIRTUAL_FORMAT)?;
println!(
"{name}: {:?}, {} Hz, {} ch",
format.format_id(),
format.sample_rate(),
format.channels_per_frame(),
);
}
```
### Registering an IO proc (audio callback)
```rust
use coreaudio::{AudioObject, System, Scope};
let system = AudioObject::<System>::default();
let device = system.current_device(Scope::Output)?;
let mut io_proc = device.add_io_proc(Scope::Output, |buffers| {
for buffer in buffers {
buffer.data.fill(0.0); // silence
}
})?;
io_proc.play()?;
// ... render audio ...
io_proc.pause()?;
io_proc.remove();
```
A proc is created stopped, so nothing is delivered until `play()` is called.
Each buffer also carries the time stamps for its I/O cycle. A clock the device didn't fill in is `None`, so a zero is never mistaken for a real time:
```rust
let mut io_proc = device.add_io_proc(Scope::Input, |buffers| {
for buffer in buffers {
let now = buffer.get_now_time();
if let (Some(sample), Some(host)) = (now.sample_time(), now.host_time()) {
println!("{} channels, sample {sample}, host {host}", buffer.get_channels());
}
}
})?;
```
### Available buffer sizes and sample rates
```rust
use coreaudio::{DEVICE_BUFFER_FRAME_SIZE_RANGE, DEVICE_AVAILABLE_SAMPLE_RATES};
let buffer_range = device.get_property(DEVICE_BUFFER_FRAME_SIZE_RANGE)?;
println!("Valid buffer sizes: {:?}", buffer_range.valid_sizes());
let sample_rates = device.get_property(DEVICE_AVAILABLE_SAMPLE_RATES)?;
println!("Valid sample rates: {:?}", sample_rates.valid_rates());
```
## Property reference
Properties marked **element** require `.for_element(channel)` before use.
Properties marked **qualifier** require `.with_qualifier(value)` before use.
Properties marked **both** require both calls (in either order).
### Object properties (all object types)
| `OBJECT_BASE_CLASS` | `u32` | Read | Yes | — |
| `OBJECT_CLASS` | `u32` | Read | Yes | — |
| `OBJECT_OWNER` | `u32` | Read | Yes | — |
| `OBJECT_MODEL_NAME` | `String` | Read | No | — |
| `OBJECT_MANUFACTURER` | `String` | Read | Yes | — |
| `OBJECT_CREATOR` | `String` | Read | Yes | — |
| `OBJECT_ELEMENT_NAME` | `String` | Read/Write | Yes | element |
| `OBJECT_ELEMENT_CATEGORY_NAME` | `String` | Read | No | element |
| `OBJECT_ELEMENT_NUMBER_NAME` | `String` | Read | No | element |
| `OBJECT_OWNED_OBJECTS` | `Vec<u32>` | Read | Yes | qualifier: `Vec<u32>` |
### Device properties
| `DEVICE_NAME` | `String` | Read | Yes | — |
| `DEVICE_UID` | `String` | Read | Yes | — |
| `DEVICE_MODEL_UID` | `String` | Read | Yes | — |
| `DEVICE_CONFIGURATION_APPLICATION` | `String` | Read | Yes | — |
| `DEVICE_TRANSPORT_TYPE` | `TransportType` | Read | Yes | — |
| `DEVICE_IS_ALIVE` | `bool` | Read | Yes | — |
| `DEVICE_IS_RUNNING` | `bool` | Read | Yes | — |
| `DEVICE_IS_HIDDEN` | `bool` | Read | Yes | — |
| `DEVICE_CAN_BE_DEFAULT` | `bool` | Read | No | — |
| `DEVICE_CAN_BE_DEFAULT_SYSTEM` | `bool` | Read | No | — |
| `DEVICE_NOMINAL_SAMPLE_RATE` | `f64` | Read/Write | Yes | — |
| `DEVICE_AVAILABLE_SAMPLE_RATES` | `Vec<SampleRateRange>` | Read | Yes | — |
| `DEVICE_BUFFER_FRAME_SIZE` | `u32` | Read/Write | Yes | — |
| `DEVICE_BUFFER_FRAME_SIZE_RANGE` | `BufferFrameSizeRange` | Read | Yes | — |
| `DEVICE_USES_VARIABLE_BUFFER_FRAME_SIZES` | `u32` | Read | No | — |
| `DEVICE_INPUT_LATENCY` | `u32` | Read | Yes | — |
| `DEVICE_OUTPUT_LATENCY` | `u32` | Read | Yes | — |
| `DEVICE_SAFETY_OFFSET_INPUT` | `u32` | Read | No | — |
| `DEVICE_SAFETY_OFFSET_OUTPUT` | `u32` | Read | No | — |
| `DEVICE_CLOCK_DOMAIN` | `u32` | Read | Yes | — |
| `DEVICE_HOG_MODE` | `HogMode` | Read/Write | Yes | — |
| `DEVICE_RELATED_DEVICES` | `Vec<u32>` | Read | Yes | — |
| `DEVICE_PREFERRED_CHANNELS_FOR_STEREO` | `ChannelPair` | Read/Write | No | — |
| `DEVICE_PREFERRED_CHANNEL_LAYOUT_INPUT` | `ChannelLayout` | Read/Write | Yes | — |
| `DEVICE_PREFERRED_CHANNEL_LAYOUT_OUTPUT` | `ChannelLayout` | Read/Write | Yes | — |
| `DEVICE_PROCESSOR_OVERLOAD` | `u32` | Read | Yes | — |
| `DEVICE_IO_STOPPED_ABNORMALLY` | `u32` | Read | Yes | — |
| `DEVICE_IO_CYCLE_USAGE` | `f32` | Read/Write | Yes | — |
| `DEVICE_CLOCK_SOURCE` | `u32` | Read/Write | Yes | — |
| `DEVICE_CLOCK_SOURCES` | `Vec<u32>` | Read | Yes | — |
| `DEVICE_CLOCK_SOURCE_NAME` | `String` | Read | No | qualifier: `u32` |
| `DEVICE_PLAY_THRU_DESTINATION` | `u32` | Read/Write | Yes | — |
| `DEVICE_PLAY_THRU_DESTINATIONS` | `Vec<u32>` | Read | No | — |
| `DEVICE_PLAY_THRU_DESTINATION_NAME` | `String` | Read | No | qualifier: `u32` |
| `DEVICE_INPUT_ELEMENT_NAME` | `String` | Read/Write | Yes | element |
| `DEVICE_OUTPUT_ELEMENT_NAME` | `String` | Read/Write | Yes | element |
| `DEVICE_GLOBAL_VOLUME_SCALAR` | `f32` | Read/Write | Yes | element |
| `DEVICE_INPUT_VOLUME_SCALAR` | `f32` | Read/Write | Yes | element |
| `DEVICE_OUTPUT_VOLUME_SCALAR` | `f32` | Read/Write | Yes | element |
| `DEVICE_VOLUME_DECIBELS` | `f32` | Read/Write | Yes | element |
| `DEVICE_VOLUME_RANGE_DECIBELS` | `DBRange` | Read | No | element |
| `DEVICE_VOLUME_SCALAR_TO_DECIBELS` | `f32` | Read | No | element |
| `DEVICE_VOLUME_DECIBELS_TO_SCALAR` | `f32` | Read | No | element |
| `DEVICE_SUB_VOLUME_SCALAR` | `f32` | Read/Write | Yes | element |
| `DEVICE_SUB_VOLUME_DECIBELS` | `f32` | Read/Write | Yes | element |
| `DEVICE_SUB_VOLUME_RANGE_DECIBELS` | `DBRange` | Read | No | element |
| `DEVICE_SUB_VOLUME_SCALAR_TO_DECIBELS` | `f32` | Read | No | element |
| `DEVICE_SUB_VOLUME_DECIBELS_TO_SCALAR` | `f32` | Read | No | element |
| `DEVICE_STEREO_PAN` | `f32` | Read/Write | Yes | element |
| `DEVICE_STEREO_PAN_CHANNELS` | `ChannelPair` | Read | No | element |
| `DEVICE_GLOBAL_MUTE` | `bool` | Read/Write | Yes | element |
| `DEVICE_INPUT_MUTE` | `bool` | Read/Write | Yes | element |
| `DEVICE_OUTPUT_MUTE` | `bool` | Read/Write | Yes | element |
| `DEVICE_SUB_MUTE` | `bool` | Read/Write | Yes | element |
| `DEVICE_SOLO` | `bool` | Read/Write | Yes | element |
| `DEVICE_PHANTOM_POWER` | `bool` | Read/Write | Yes | element |
| `DEVICE_PHASE_INVERT` | `bool` | Read/Write | Yes | element |
| `DEVICE_CLIP_LIGHT` | `bool` | Read/Write | Yes | element |
| `DEVICE_TALKBACK` | `bool` | Read/Write | Yes | element |
| `DEVICE_LISTENBACK` | `bool` | Read/Write | Yes | element |
| `DEVICE_JACK_IS_CONNECTED` | `bool` | Read | Yes | element |
| `DEVICE_DATA_SOURCE` | `u32` | Read/Write | Yes | element |
| `DEVICE_DATA_SOURCES` | `Vec<u32>` | Read | No | element |
| `DEVICE_DATA_SOURCE_NAME` | `String` | Read | No | element + qualifier: `u32` |
| `DEVICE_CHANNEL_NOMINAL_LINE_LEVEL` | `u32` | Read/Write | Yes | element |
| `DEVICE_CHANNEL_NOMINAL_LINE_LEVELS` | `Vec<u32>` | Read | No | element |
| `DEVICE_CHANNEL_NOMINAL_LINE_LEVEL_NAME` | `String` | Read | No | element + qualifier: `u32` |
| `DEVICE_HIGH_PASS_FILTER_SETTING` | `u32` | Read/Write | Yes | element |
| `DEVICE_HIGH_PASS_FILTER_SETTINGS` | `Vec<u32>` | Read | No | element |
| `DEVICE_HIGH_PASS_FILTER_SETTING_NAME` | `String` | Read | No | element + qualifier: `u32` |
### Stream properties
| `STREAM_NAME` | `String` | Read | Yes | — |
| `STREAM_IS_ACTIVE` | `bool` | Read/Write | Yes | — |
| `STREAM_DIRECTION` | `Scope` | Read | Yes | — |
| `STREAM_LATENCY` | `u32` | Read | Yes | — |
| `STREAM_VIRTUAL_FORMAT` | `StreamDescription` | Read/Write | Yes | — |
| `STREAM_PHYSICAL_FORMAT` | `StreamDescription` | Read/Write | Yes | — |
| `STREAM_AVAILABLE_VIRTUAL_FORMATS` | `Vec<StreamRangedDescription>` | Read | Yes | — |
| `STREAM_AVAILABLE_PHYSICAL_FORMATS` | `Vec<StreamRangedDescription>` | Read | Yes | — |
| `TERMINAL_TYPE` | `TerminalType` | Read | Yes | — |
| `STARTING_CHANNEL` | `u32` | Read | Yes | — |
### System properties
| `SYSTEM_NAME` | `String` | Read | No | — |
| `SYSTEM_DEVICES` | `Vec<u32>` | Read | Yes | — |
| `SYSTEM_DEFAULT_INPUT` | `u32` | Read/Write | Yes | — |
| `SYSTEM_DEFAULT_OUTPUT` | `u32` | Read/Write | Yes | — |
| `SYSTEM_IS_INITING_OR_EXITING` | `bool` | Read | No | — |
| `SYSTEM_SLEEPING_IS_ALLOWED` | `bool` | Read/Write | Yes | — |
| `SYSTEM_UNLOADING_IS_ALLOWED` | `bool` | Read/Write | No | — |
| `SYSTEM_HOG_MODE_IS_ALLOWED` | `bool` | Read/Write | No | — |
| `SYSTEM_MIX_STEREO_TO_MONO` | `bool` | Read/Write | No | — |
| `SYSTEM_POWER_HINT` | `PowerHint` | Read/Write | No | — |
| `SYSTEM_PROCESS_IS_AUDIBLE` | `bool` | Read/Write | Yes | — |
| `SYSTEM_PROCESS_IS_MASTER` | `bool` | Read | No | — |
| `SYSTEM_USER_SESSION_IS_ACTIVE_OR_HEADLESS` | `bool` | Read | Yes | — |
| `SYSTEM_USER_ID_CHANGED` | `u32` | Read/Write | Yes | — |
| `SYSTEM_SERVICE_RESTARTED` | `u32` | Read | Yes | — |
| `SYSTEM_DEFAULT_SYSTEM_OUTPUT` | `u32` | Read/Write | Yes | — |
| `SYSTEM_BOX_LIST` | `Vec<u32>` | Read | Yes | — |
| `SYSTEM_CLOCK_DEVICE_LIST` | `Vec<u32>` | Read | Yes | — |
| `SYSTEM_PLUGIN_LIST` | `Vec<u32>` | Read | Yes | — |
| `SYSTEM_TAP_LIST` | `Vec<u32>` | Read | Yes | — |
| `SYSTEM_PROCESS_OBJECT_LIST` | `Vec<u32>` | Read | Yes | — |
| `SYSTEM_TRANSPORT_MANAGER_LIST` | `Vec<u32>` | Read | No | — |
| `SYSTEM_TRANSLATE_UID_TO_DEVICE` | `u32` | Read | No | qualifier: `String` |
| `SYSTEM_TRANSLATE_UID_TO_BOX` | `u32` | Read | No | qualifier: `String` |
| `SYSTEM_TRANSLATE_UID_TO_CLOCK_DEVICE` | `u32` | Read | No | qualifier: `String` |
| `SYSTEM_TRANSLATE_BUNDLE_ID_TO_PLUGIN` | `u32` | Read | No | qualifier: `String` |
| `SYSTEM_TRANSLATE_BUNDLE_ID_TO_TRANSPORT_MANAGER` | `u32` | Read | No | qualifier: `String` |
### Process properties
| `PROCESS_PID` | `i32` | Read | No | — |
| `PROCESS_BUNDLE_ID` | `String` | Read | No | — |
| `PROCESS_IS_RUNNING` | `bool` | Read | Yes | — |
| `PROCESS_IS_RUNNING_INPUT` | `bool` | Read | Yes | — |
| `PROCESS_IS_RUNNING_OUTPUT` | `bool` | Read | Yes | — |
| `PROCESS_INPUT_DEVICES` | `Vec<u32>` | Read | Yes | — |
| `PROCESS_OUTPUT_DEVICES` | `Vec<u32>` | Read | Yes | — |
### Tap properties
| `TAP_UID` | `String` | Read | No | — |
| `TAP_FORMAT` | `StreamDescription` | Read | Yes | — |
## Error handling
All fallible operations return `Result<T, CoreAudioError>`. The error type wraps a typed `ErrorKind` enum and the raw `OSStatus` code. You can match on the kind or inspect the four-character code string:
```rust
use coreaudio::ErrorKind;
match device.get_property(DEVICE_NAME) {
Ok(name) => println!("{name}"),
Err(e) => match e.kind() {
ErrorKind::BadDevice => println!("Invalid device"),
ErrorKind::Permissions => println!("Device is hogged by another process"),
_ => println!("Error: {} ('{}')", e, e.stringify_code()),
}
}
```
## Supported audio formats
The `FormatId` enum covers Linear PCM, AAC (Standard, HE, HEv2, LD, ELD, ELDv2, ELD+SBR, Spatial), Apple Lossless, AC-3, Enhanced AC-3, APAC, AES3, A-Law, AMR, AMR-WB, Opus, and MP3. Unrecognised format IDs are preserved as `FormatId::Unknown(u32)`.
## Roadmap
- Dedicated `AudioObject<Clock>`, `AudioObject<Box>`, and `AudioObject<Unit>` with unique methods
- Add new wrappers and 'multi-properties' that combine multiple properties into one for things that shouldn't have to be seperate calls
## Disclaimer
Apple's documentation on what properties can be listened to is pretty much non existant.
Because of this, almost all writable properties have been made listenable and it will return an error if it turns out not to be.
If you know of any documentation or if a specific property is incorrectly set, please make an issue in the repository and I will fix it at my earliest convenience.
## License
See LICENSE file for details.