coreaudio 0.3.0

A safe and simple wrapper around the CoreAudio HAL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
# 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.
- **IO Procs** — Register audio render callbacks on devices with `add_io_proc` and control playback with `play()` / `pause()`.
- **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 a private 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")]`)
- [`coreaudio-sys`]https://crates.io/crates/coreaudio-sys 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-core-audio`]https://crates.io/crates/objc2-core-audio for `CATapDescription`; a binary built with it won't launch on older 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. With the `process-tap` feature, `ProcessTap` captures what a set of processes output, mixed down to stereo, before it reaches any device. A tap has no IO of its own; `AggregateDevice::with_tap` wraps it in a private aggregate device, which is read with an ordinary IO proc.

```rust
use coreaudio::{
    AggregateDevice, 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();

// Music 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 = AggregateDevice::with_tap("Music tap", "com.example.music-tap", tap.uid())?;

let mut proc = aggregate.device().add_io_proc(Scope::Input, |buffers| {
    // Music's audio, interleaved stereo
})?;
proc.play()?;

// Drop in this order: the IO proc, the aggregate, then the tap.
drop(proc);
drop(aggregate);
drop(tap);
# Ok::<(), coreaudio::CoreAudioError>(())
```

The aggregate is always private: only the creating process can see it, 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.

### 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}");
```

### 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(|buffers| {
    for buffer in buffers {
        buffer.data.fill(0.0); // silence
    }
})?;

io_proc.play()?;
// ... render audio ...
io_proc.pause()?;
io_proc.remove();
```

### 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)

| Constant | Type | Access | Listenable | Extra |
|---|---|---|---|---|
| `OBJECT_BASE_CLASS` | `u32` | Read | No | — |
| `OBJECT_CLASS` | `u32` | Read | No | — |
| `OBJECT_OWNER` | `u32` | Read | No | — |
| `OBJECT_MODEL_NAME` | `String` | Read | No | — |
| `OBJECT_MANUFACTURER` | `String` | Read | No | — |
| `OBJECT_CREATOR` | `String` | Read | No | — |
| `OBJECT_ELEMENT_NAME` | `String` | Read | No | element |
| `OBJECT_ELEMENT_CATEGORY_NAME` | `String` | Read | No | element |
| `OBJECT_ELEMENT_NUMBER_NAME` | `String` | Read | No | element |
| `OBJECT_OWNED_OBJECTS` | `Vec<u32>` | Read | No | qualifier: `Vec<u32>` |

### Device properties


| Constant | Type | Access | Listenable | Extra |
|---|---|---|---|---|
| `DEVICE_NAME` | `String` | Read | Yes | — |
| `DEVICE_UID` | `String` | Read | No | — |
| `DEVICE_MODEL_UID` | `String` | Read | No | — |
| `DEVICE_CONFIGURATION_APPLICATION` | `String` | Read | No | — |
| `DEVICE_TRANSPORT_TYPE` | `TransportType` | Read | No | — |
| `DEVICE_IS_ALIVE` | `bool` | Read | Yes | — |
| `DEVICE_IS_RUNNING` | `bool` | Read | Yes | — |
| `DEVICE_IS_HIDDEN` | `bool` | Read | No | — |
| `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 | No | — |
| `DEVICE_USES_VARIABLE_BUFFER_FRAME_SIZES` | `u32` | Read | No | — |
| `DEVICE_INPUT_LATENCY` | `u32` | Read | No | — |
| `DEVICE_OUTPUT_LATENCY` | `u32` | Read | No | — |
| `DEVICE_SAFETY_OFFSET` | `u32` | Read | No | — |
| `DEVICE_CLOCK_DOMAIN` | `u32` | Read | No | — |
| `DEVICE_HOG_MODE` | `HogMode` | Read/Write | Yes | — |
| `DEVICE_RELATED_DEVICES` | `Vec<u32>` | Read | No | — |
| `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 | No | — |
| `DEVICE_CLOCK_SOURCE` | `u32` | Read/Write | Yes | — |
| `DEVICE_CLOCK_SOURCES` | `Vec<u32>` | Read | No | — |
| `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 | No | element |
| `DEVICE_OUTPUT_ELEMENT_NAME` | `String` | Read | No | 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

| Constant | Type | Access | Listenable | Extra |
|---|---|---|---|---|
| `STREAM_NAME` | `String` | Read | Yes | — |
| `STREAM_IS_ACTIVE` | `bool` | Read | Yes | — |
| `STREAM_DIRECTION` | `Scope` | Read | No | — |
| `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 | No | — |
| `STARTING_CHANNEL` | `u32` | Read | No | — |

### System properties

| Constant | Type | Access | Listenable | Extra |
|---|---|---|---|---|
| `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

| Constant | Type | Access | Listenable | Extra |
|---|---|---|---|---|
| `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

| Constant | Type | Access | Listenable | Extra |
|---|---|---|---|---|
| `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<Tap>` 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
- Properties for device sample format

## 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.