Skip to main content

au_sys/
lib.rs

1//! Rust FFI bindings for the macOS AudioUnit v2 (AUv2) C API.
2//!
3//! These are hand-written bindings against the stable macOS AudioUnit C API.
4//! The structs and constants are derived from:
5//!   - AudioToolbox/AUComponent.h
6//!   - AudioToolbox/AudioUnitProperties.h
7//!   - AudioToolbox/AudioComponent.h
8//!   - CoreAudioTypes/CoreAudioBaseTypes.h
9//!
10//! This crate is macOS-only.
11
12#![allow(non_camel_case_types, non_snake_case, non_upper_case_globals, dead_code)]
13#![cfg(target_os = "macos")]
14
15use std::ffi::c_void;
16use std::os::raw::{c_char, c_int};
17
18// ─── Primitive type aliases ────────────────────────────────────────────────────
19
20pub type OSStatus = i32;
21pub type UInt8 = u8;
22pub type UInt16 = u16;
23pub type UInt32 = u32;
24pub type UInt64 = u64;
25pub type SInt16 = i16;
26pub type SInt32 = i32;
27pub type Float32 = f32;
28pub type Float64 = f64;
29pub type Boolean = u8;
30
31/// An opaque type for an AudioComponent instance (i.e. an AudioUnit).
32pub enum OpaqueAudioComponentInstance {}
33pub type AudioComponentInstance = *mut OpaqueAudioComponentInstance;
34pub type AudioUnit = AudioComponentInstance;
35
36// ─── AudioComponent / PlugIn interface ────────────────────────────────────────
37
38/// Prototype for every method dispatched through [`AudioComponentPlugInInterface::Lookup`].
39pub type AudioComponentMethod = unsafe extern "C" fn(*mut c_void, ...) -> OSStatus;
40
41/// The vtable that every AUv2 plugin must provide.
42///
43/// The factory function allocates this struct (embedded in the plugin instance)
44/// and returns a pointer to it.  `Open` / `Close` manage the instance lifetime;
45/// `Lookup` returns the method pointer for a given selector.
46#[repr(C)]
47pub struct AudioComponentPlugInInterface {
48    /// Called by the host shortly after the factory function returns.
49    /// `self_ptr` is the same pointer returned by the factory.
50    pub Open: unsafe extern "C" fn(self_ptr: *mut c_void, instance: AudioUnit) -> OSStatus,
51    /// Called to destroy the plugin instance.
52    pub Close: unsafe extern "C" fn(self_ptr: *mut c_void) -> OSStatus,
53    /// Returns a method pointer for `selector`, or null if not supported.
54    pub Lookup: unsafe extern "C" fn(selector: SInt16) -> Option<AudioComponentMethod>,
55    /// Must be null.
56    pub reserved: *mut c_void,
57}
58
59/// Identifies an Audio Component (type + subtype + manufacturer).
60#[repr(C)]
61#[derive(Clone, Copy, Debug, Default)]
62pub struct AudioComponentDescription {
63    pub componentType: UInt32,
64    pub componentSubType: UInt32,
65    pub componentManufacturer: UInt32,
66    pub componentFlags: UInt32,
67    pub componentFlagsMask: UInt32,
68}
69
70// ─── Audio Unit type four-char codes ──────────────────────────────────────────
71
72pub const kAudioUnitType_Output: UInt32 = 0x61756F75; // 'auou'
73pub const kAudioUnitType_MusicDevice: UInt32 = 0x61756D75; // 'aumu'
74pub const kAudioUnitType_MusicEffect: UInt32 = 0x61756D66; // 'aumf'
75pub const kAudioUnitType_FormatConverter: UInt32 = 0x61756663; // 'aufc'
76pub const kAudioUnitType_Effect: UInt32 = 0x61756678; // 'aufx'
77pub const kAudioUnitType_Mixer: UInt32 = 0x61756D78; // 'aumx'
78pub const kAudioUnitType_Panner: UInt32 = 0x6175706E; // 'aupn'
79pub const kAudioUnitType_Generator: UInt32 = 0x6175676E; // 'augn'
80pub const kAudioUnitType_OfflineEffect: UInt32 = 0x61756F6C; // 'auol'
81
82// ─── Selector constants (AudioUnitRange) ──────────────────────────────────────
83
84pub const kAudioUnitInitializeSelect: SInt16 = 0x0001;
85pub const kAudioUnitUninitializeSelect: SInt16 = 0x0002;
86pub const kAudioUnitGetPropertyInfoSelect: SInt16 = 0x0003;
87pub const kAudioUnitGetPropertySelect: SInt16 = 0x0004;
88pub const kAudioUnitSetPropertySelect: SInt16 = 0x0005;
89pub const kAudioUnitGetParameterSelect: SInt16 = 0x0006;
90pub const kAudioUnitSetParameterSelect: SInt16 = 0x0007;
91pub const kAudioUnitResetSelect: SInt16 = 0x0009;
92pub const kAudioUnitAddPropertyListenerSelect: SInt16 = 0x000A;
93pub const kAudioUnitRemovePropertyListenerSelect: SInt16 = 0x000B;
94pub const kAudioUnitRenderSelect: SInt16 = 0x000E;
95pub const kAudioUnitAddRenderNotifySelect: SInt16 = 0x000F;
96pub const kAudioUnitRemoveRenderNotifySelect: SInt16 = 0x0010;
97pub const kAudioUnitScheduleParametersSelect: SInt16 = 0x0011;
98pub const kAudioUnitRemovePropertyListenerWithUserDataSelect: SInt16 = 0x0012;
99
100// ─── Dispatch function prototypes ─────────────────────────────────────────────
101
102pub type AudioUnitPropertyID = UInt32;
103pub type AudioUnitScope = UInt32;
104pub type AudioUnitElement = UInt32;
105pub type AudioUnitParameterID = UInt32;
106pub type AudioUnitParameterValue = Float32;
107pub type AudioUnitRenderActionFlags = UInt32;
108
109pub type AudioUnitInitializeProc =
110    unsafe extern "C" fn(self_ptr: *mut c_void) -> OSStatus;
111
112pub type AudioUnitUninitializeProc =
113    unsafe extern "C" fn(self_ptr: *mut c_void) -> OSStatus;
114
115pub type AudioUnitGetPropertyInfoProc = unsafe extern "C" fn(
116    self_ptr: *mut c_void,
117    inID: AudioUnitPropertyID,
118    inScope: AudioUnitScope,
119    inElement: AudioUnitElement,
120    outDataSize: *mut UInt32,
121    outWritable: *mut Boolean,
122) -> OSStatus;
123
124pub type AudioUnitGetPropertyProc = unsafe extern "C" fn(
125    self_ptr: *mut c_void,
126    inID: AudioUnitPropertyID,
127    inScope: AudioUnitScope,
128    inElement: AudioUnitElement,
129    outData: *mut c_void,
130    ioDataSize: *mut UInt32,
131) -> OSStatus;
132
133pub type AudioUnitSetPropertyProc = unsafe extern "C" fn(
134    self_ptr: *mut c_void,
135    inID: AudioUnitPropertyID,
136    inScope: AudioUnitScope,
137    inElement: AudioUnitElement,
138    inData: *const c_void,
139    inDataSize: UInt32,
140) -> OSStatus;
141
142pub type AudioUnitGetParameterProc = unsafe extern "C" fn(
143    self_ptr: *mut c_void,
144    inID: AudioUnitParameterID,
145    inScope: AudioUnitScope,
146    inElement: AudioUnitElement,
147    outValue: *mut AudioUnitParameterValue,
148) -> OSStatus;
149
150pub type AudioUnitSetParameterProc = unsafe extern "C" fn(
151    self_ptr: *mut c_void,
152    inID: AudioUnitParameterID,
153    inScope: AudioUnitScope,
154    inElement: AudioUnitElement,
155    inValue: AudioUnitParameterValue,
156    inBufferOffsetInFrames: UInt32,
157) -> OSStatus;
158
159pub type AudioUnitRenderProc = unsafe extern "C" fn(
160    self_ptr: *mut c_void,
161    ioActionFlags: *mut AudioUnitRenderActionFlags,
162    inTimeStamp: *const AudioTimeStamp,
163    inOutputBusNumber: UInt32,
164    inNumberFrames: UInt32,
165    ioData: *mut AudioBufferList,
166) -> OSStatus;
167
168pub type AudioUnitResetProc = unsafe extern "C" fn(
169    self_ptr: *mut c_void,
170    inScope: AudioUnitScope,
171    inElement: AudioUnitElement,
172) -> OSStatus;
173
174pub type AudioUnitAddPropertyListenerProc = unsafe extern "C" fn(
175    self_ptr: *mut c_void,
176    inID: AudioUnitPropertyID,
177    inProc: AudioUnitPropertyListenerProc,
178    inProcUserData: *mut c_void,
179) -> OSStatus;
180
181pub type AudioUnitRemovePropertyListenerWithUserDataProc = unsafe extern "C" fn(
182    self_ptr: *mut c_void,
183    inID: AudioUnitPropertyID,
184    inProc: AudioUnitPropertyListenerProc,
185    inProcUserData: *mut c_void,
186) -> OSStatus;
187
188/// Callback the host registers to be notified of property changes.
189pub type AudioUnitPropertyListenerProc = unsafe extern "C" fn(
190    inRefCon: *mut c_void,
191    inUnit: AudioUnit,
192    inID: AudioUnitPropertyID,
193    inScope: AudioUnitScope,
194    inElement: AudioUnitElement,
195);
196
197/// Callback the host can register to be called at the beginning/end of each render cycle.
198pub type AURenderCallback = unsafe extern "C" fn(
199    inRefCon: *mut c_void,
200    ioActionFlags: *mut AudioUnitRenderActionFlags,
201    inTimeStamp: *const AudioTimeStamp,
202    inBusNumber: UInt32,
203    inNumberFrames: UInt32,
204    ioData: *mut AudioBufferList,
205) -> OSStatus;
206
207/// A render callback + user-data pair, used with `kAudioUnitProperty_SetRenderCallback`.
208#[repr(C)]
209#[derive(Clone, Copy)]
210pub struct AURenderCallbackStruct {
211    pub inputProc: Option<AURenderCallback>,
212    pub inputProcRefCon: *mut c_void,
213}
214unsafe impl Send for AURenderCallbackStruct {}
215
216// ─── Render action flags ───────────────────────────────────────────────────────
217
218pub const kAudioUnitRenderAction_PreRender: AudioUnitRenderActionFlags = 1 << 2;
219pub const kAudioUnitRenderAction_PostRender: AudioUnitRenderActionFlags = 1 << 3;
220pub const kAudioUnitRenderAction_OutputIsSilence: AudioUnitRenderActionFlags = 1 << 4;
221
222// ─── OSStatus error codes ──────────────────────────────────────────────────────
223
224pub const noErr: OSStatus = 0;
225pub const kAudioUnitErr_InvalidProperty: OSStatus = -10879;
226pub const kAudioUnitErr_InvalidParameter: OSStatus = -10878;
227pub const kAudioUnitErr_InvalidElement: OSStatus = -10877;
228pub const kAudioUnitErr_NoConnection: OSStatus = -10876;
229pub const kAudioUnitErr_FailedInitialization: OSStatus = -10875;
230pub const kAudioUnitErr_TooManyFramesToProcess: OSStatus = -10874;
231pub const kAudioUnitErr_InvalidFile: OSStatus = -10871;
232pub const kAudioUnitErr_FormatNotSupported: OSStatus = -10868;
233pub const kAudioUnitErr_Uninitialized: OSStatus = -10867;
234pub const kAudioUnitErr_InvalidScope: OSStatus = -10866;
235pub const kAudioUnitErr_PropertyNotWritable: OSStatus = -10865;
236pub const kAudioUnitErr_CannotDoInCurrentContext: OSStatus = -10863;
237pub const kAudioUnitErr_InvalidPropertyValue: OSStatus = -10851;
238pub const kAudioUnitErr_PropertyNotInUse: OSStatus = -10850;
239pub const kAudioUnitErr_Initialized: OSStatus = -10849;
240pub const kAudioUnitErr_InvalidOfflineRender: OSStatus = -10848;
241pub const kAudioUnitErr_Unauthorized: OSStatus = -10847;
242pub const kAudioComponentErr_InstanceInvalidated: OSStatus = -66749;
243
244// ─── Scope constants ───────────────────────────────────────────────────────────
245
246pub const kAudioUnitScope_Global: AudioUnitScope = 0;
247pub const kAudioUnitScope_Input: AudioUnitScope = 1;
248pub const kAudioUnitScope_Output: AudioUnitScope = 2;
249pub const kAudioUnitScope_Group: AudioUnitScope = 3;
250pub const kAudioUnitScope_Part: AudioUnitScope = 4;
251pub const kAudioUnitScope_Note: AudioUnitScope = 5;
252
253// ─── Property IDs ─────────────────────────────────────────────────────────────
254
255pub const kAudioUnitProperty_ClassInfo: AudioUnitPropertyID = 0;
256pub const kAudioUnitProperty_MakeConnection: AudioUnitPropertyID = 1;
257pub const kAudioUnitProperty_SampleRate: AudioUnitPropertyID = 2;
258pub const kAudioUnitProperty_ParameterList: AudioUnitPropertyID = 3;
259pub const kAudioUnitProperty_ParameterInfo: AudioUnitPropertyID = 4;
260pub const kAudioUnitProperty_FastDispatch: AudioUnitPropertyID = 5;
261pub const kAudioUnitProperty_CPULoad: AudioUnitPropertyID = 6;
262pub const kAudioUnitProperty_StreamFormat: AudioUnitPropertyID = 8;
263pub const kAudioUnitProperty_ElementCount: AudioUnitPropertyID = 11;
264pub const kAudioUnitProperty_Latency: AudioUnitPropertyID = 12;
265pub const kAudioUnitProperty_SupportedNumChannels: AudioUnitPropertyID = 13;
266pub const kAudioUnitProperty_MaximumFramesPerSlice: AudioUnitPropertyID = 14;
267pub const kAudioUnitProperty_SetExternalBuffer: AudioUnitPropertyID = 15;
268pub const kAudioUnitProperty_ParameterValueStrings: AudioUnitPropertyID = 16;
269pub const kAudioUnitProperty_GetUIComponentList: AudioUnitPropertyID = 18;
270pub const kAudioUnitProperty_AudioChannelLayout: AudioUnitPropertyID = 19;
271pub const kAudioUnitProperty_TailTime: AudioUnitPropertyID = 20;
272pub const kAudioUnitProperty_BypassEffect: AudioUnitPropertyID = 21;
273pub const kAudioUnitProperty_LastRenderError: AudioUnitPropertyID = 22;
274pub const kAudioUnitProperty_SetRenderCallback: AudioUnitPropertyID = 23;
275pub const kAudioUnitProperty_FactoryPresets: AudioUnitPropertyID = 24;
276pub const kAudioUnitProperty_ContextName: AudioUnitPropertyID = 25;
277pub const kAudioUnitProperty_RenderQuality: AudioUnitPropertyID = 26;
278pub const kAudioUnitProperty_HostCallbacks: AudioUnitPropertyID = 27;
279pub const kAudioUnitProperty_InPlaceProcessing: AudioUnitPropertyID = 29;
280pub const kAudioUnitProperty_ElementName: AudioUnitPropertyID = 30;
281pub const kAudioUnitProperty_CocoaUI: AudioUnitPropertyID = 31;
282pub const kAudioUnitProperty_SupportedChannelLayoutTags: AudioUnitPropertyID = 32;
283pub const kAudioUnitProperty_ParameterStringFromValue: AudioUnitPropertyID = 33;
284pub const kAudioUnitProperty_ParameterIDName: AudioUnitPropertyID = 34;
285pub const kAudioUnitProperty_ParameterClumpName: AudioUnitPropertyID = 35;
286pub const kAudioUnitProperty_PresentPreset: AudioUnitPropertyID = 36;
287pub const kAudioUnitProperty_OfflineRender: AudioUnitPropertyID = 37;
288pub const kAudioUnitProperty_ParameterValueFromString: AudioUnitPropertyID = 38;
289pub const kAudioUnitProperty_IconLocation: AudioUnitPropertyID = 39;
290pub const kAudioUnitProperty_PresentationLatency: AudioUnitPropertyID = 40;
291pub const kAudioUnitProperty_DependentParameters: AudioUnitPropertyID = 45;
292pub const kAudioUnitProperty_AUHostIdentifier: AudioUnitPropertyID = 46;
293pub const kAudioUnitProperty_MIDIOutputCallbackInfo: AudioUnitPropertyID = 47;
294pub const kAudioUnitProperty_MIDIOutputCallback: AudioUnitPropertyID = 48;
295pub const kAudioUnitProperty_InputSamplesInOutput: AudioUnitPropertyID = 49;
296pub const kAudioUnitProperty_ClassInfoFromDocument: AudioUnitPropertyID = 50;
297pub const kAudioUnitProperty_ShouldAllocateBuffer: AudioUnitPropertyID = 51;
298pub const kAudioUnitProperty_FrequencyResponse: AudioUnitPropertyID = 52;
299pub const kAudioUnitProperty_ParameterHistoryInfo: AudioUnitPropertyID = 53;
300pub const kAudioUnitProperty_NickName: AudioUnitPropertyID = 54;
301
302// ─── AudioFormatID ─────────────────────────────────────────────────────────────
303
304pub type AudioFormatID = UInt32;
305pub type AudioFormatFlags = UInt32;
306
307pub const kAudioFormatLinearPCM: AudioFormatID = 0x6C70636D; // 'lpcm'
308
309pub const kAudioFormatFlagIsFloat: AudioFormatFlags = 1 << 0;
310pub const kAudioFormatFlagIsBigEndian: AudioFormatFlags = 1 << 1;
311pub const kAudioFormatFlagIsSignedInteger: AudioFormatFlags = 1 << 2;
312pub const kAudioFormatFlagIsPacked: AudioFormatFlags = 1 << 3;
313pub const kAudioFormatFlagIsAlignedHigh: AudioFormatFlags = 1 << 4;
314pub const kAudioFormatFlagIsNonInterleaved: AudioFormatFlags = 1 << 5;
315pub const kAudioFormatFlagIsNonMixable: AudioFormatFlags = 1 << 6;
316
317/// Native-endian, non-interleaved, packed 32-bit float – the standard AU processing format.
318pub const kAudioFormatFlagsNativeFloatPacked: AudioFormatFlags =
319    kAudioFormatFlagIsFloat | kAudioFormatFlagIsPacked;
320
321// ─── AudioStreamBasicDescription ───────────────────────────────────────────────
322
323/// Describes a PCM audio stream format.
324#[repr(C)]
325#[derive(Clone, Copy, Debug, Default)]
326pub struct AudioStreamBasicDescription {
327    pub mSampleRate: Float64,
328    pub mFormatID: AudioFormatID,
329    pub mFormatFlags: AudioFormatFlags,
330    pub mBytesPerPacket: UInt32,
331    pub mFramesPerPacket: UInt32,
332    pub mBytesPerFrame: UInt32,
333    pub mChannelsPerFrame: UInt32,
334    pub mBitsPerChannel: UInt32,
335    pub mReserved: UInt32,
336}
337
338impl AudioStreamBasicDescription {
339    /// Helper: build an ASBD for non-interleaved 32-bit float PCM.
340    pub fn noninterleaved_float32(sample_rate: f64, num_channels: u32) -> Self {
341        Self {
342            mSampleRate: sample_rate,
343            mFormatID: kAudioFormatLinearPCM,
344            mFormatFlags: kAudioFormatFlagsNativeFloatPacked
345                | kAudioFormatFlagIsNonInterleaved,
346            mBytesPerPacket: 4,
347            mFramesPerPacket: 1,
348            mBytesPerFrame: 4,
349            mChannelsPerFrame: num_channels,
350            mBitsPerChannel: 32,
351            mReserved: 0,
352        }
353    }
354}
355
356// ─── AudioBuffer / AudioBufferList ─────────────────────────────────────────────
357
358/// A single audio buffer in a buffer list.
359#[repr(C)]
360#[derive(Clone, Copy, Debug)]
361pub struct AudioBuffer {
362    pub mNumberChannels: UInt32,
363    pub mDataByteSize: UInt32,
364    pub mData: *mut c_void,
365}
366
367unsafe impl Send for AudioBuffer {}
368
369/// A variable-length list of [`AudioBuffer`]s.
370///
371/// # Layout note
372/// The C struct has a fixed-size `mBuffers[1]` field; in practice the host
373/// allocates enough memory for `mNumberBuffers` entries.  In Rust we represent
374/// this via raw pointer arithmetic (see [`AudioBufferList::buffers`]).
375#[repr(C)]
376pub struct AudioBufferList {
377    pub mNumberBuffers: UInt32,
378    pub mBuffers: [AudioBuffer; 1], // variable-length – use buffers() accessor
379}
380
381impl AudioBufferList {
382    /// Return a slice over all buffers.
383    ///
384    /// # Safety
385    /// The caller must ensure `self` points to a valid `AudioBufferList` with
386    /// `mNumberBuffers` entries allocated after the struct.
387    pub unsafe fn buffers(&self) -> &[AudioBuffer] {
388        std::slice::from_raw_parts(
389            self.mBuffers.as_ptr(),
390            self.mNumberBuffers as usize,
391        )
392    }
393
394    /// Return a mutable slice over all buffers.
395    ///
396    /// # Safety
397    /// Same as [`buffers`], plus `self` must not be aliased.
398    pub unsafe fn buffers_mut(&mut self) -> &mut [AudioBuffer] {
399        std::slice::from_raw_parts_mut(
400            self.mBuffers.as_mut_ptr(),
401            self.mNumberBuffers as usize,
402        )
403    }
404}
405
406// ─── SMPTETime / AudioTimeStamp ───────────────────────────────────────────────
407
408#[repr(C)]
409#[derive(Clone, Copy, Debug, Default)]
410pub struct SMPTETime {
411    pub mSubframes: SInt16,
412    pub mSubframeDivisor: SInt16,
413    pub mCounter: UInt32,
414    pub mType: UInt32,
415    pub mFlags: UInt32,
416    pub mHours: SInt16,
417    pub mMinutes: SInt16,
418    pub mSeconds: SInt16,
419    pub mFrames: SInt16,
420}
421
422pub const kAudioTimeStampSampleTimeValid: UInt32 = 1 << 0;
423pub const kAudioTimeStampHostTimeValid: UInt32 = 1 << 1;
424pub const kAudioTimeStampRateScalarValid: UInt32 = 1 << 2;
425
426/// Represents a point in time in several coordinate systems.
427#[repr(C)]
428#[derive(Clone, Copy, Debug, Default)]
429pub struct AudioTimeStamp {
430    pub mSampleTime: Float64,
431    pub mHostTime: UInt64,
432    pub mRateScalar: Float64,
433    pub mWordClockTime: UInt64,
434    pub mSMPTETime: SMPTETime,
435    pub mFlags: UInt32,
436    pub mReserved: UInt32,
437}
438
439// ─── AudioUnitParameterInfo ────────────────────────────────────────────────────
440
441pub type CFStringRef = *mut c_void; // opaque CoreFoundation type
442
443pub type AudioUnitParameterUnit = UInt32;
444pub type AudioUnitParameterOptions = UInt32;
445
446/// Metadata about one plugin parameter, returned via `kAudioUnitProperty_ParameterInfo`.
447#[repr(C)]
448pub struct AudioUnitParameterInfo {
449    /// Deprecated legacy C-string name (set to all-zeros).
450    pub name: [c_char; 52],
451    // 4 bytes implicit padding here (arm64 aligns pointers to 8 bytes)
452    /// Custom unit name string; only valid when `unit == kAudioUnitParameterUnit_CustomUnit`.
453    /// Set to `NULL` unless using a custom unit.
454    pub unitName: CFStringRef,
455    /// Optional grouping ID (0 = no group).
456    pub clumpID: UInt32,
457    // 4 bytes implicit padding here
458    /// Human-readable CFString name; only valid when `kAudioUnitParameterFlag_HasCFNameString` is set.
459    /// Set to `NULL` unless that flag is present.
460    pub cfNameString: CFStringRef,
461    pub unit: AudioUnitParameterUnit,
462    pub minValue: AudioUnitParameterValue,
463    pub maxValue: AudioUnitParameterValue,
464    pub defaultValue: AudioUnitParameterValue,
465    pub flags: AudioUnitParameterOptions,
466}
467
468// Parameter unit constants
469pub const kAudioUnitParameterUnit_Generic: AudioUnitParameterUnit = 0;
470pub const kAudioUnitParameterUnit_Indexed: AudioUnitParameterUnit = 1;
471pub const kAudioUnitParameterUnit_Boolean: AudioUnitParameterUnit = 2;
472pub const kAudioUnitParameterUnit_Percent: AudioUnitParameterUnit = 3;
473pub const kAudioUnitParameterUnit_Seconds: AudioUnitParameterUnit = 4;
474pub const kAudioUnitParameterUnit_Hertz: AudioUnitParameterUnit = 8;
475pub const kAudioUnitParameterUnit_Decibels: AudioUnitParameterUnit = 13;
476pub const kAudioUnitParameterUnit_LinearGain: AudioUnitParameterUnit = 14;
477pub const kAudioUnitParameterUnit_CustomUnit: AudioUnitParameterUnit = 26;
478
479// Parameter option flags
480pub const kAudioUnitParameterFlag_CFNameRelease: AudioUnitParameterOptions = 1 << 4;
481pub const kAudioUnitParameterFlag_HasCFNameString: AudioUnitParameterOptions = 1 << 27;
482pub const kAudioUnitParameterFlag_IsReadable: AudioUnitParameterOptions = 1 << 30;
483pub const kAudioUnitParameterFlag_IsWritable: AudioUnitParameterOptions = 1 << 31;
484pub const kAudioUnitParameterFlag_IsHighResolution: AudioUnitParameterOptions = 1 << 23;
485pub const kAudioUnitParameterFlag_CanRamp: AudioUnitParameterOptions = 1 << 25;
486pub const kAudioUnitParameterFlag_IsGlobalMeta: AudioUnitParameterOptions = 1 << 28;
487pub const kAudioUnitParameterFlag_ValuesHaveStrings: AudioUnitParameterOptions = 1 << 21;
488pub const kAudioUnitParameterFlag_DisplayLogarithmic: AudioUnitParameterOptions = 1 << 22;
489pub const kAudioUnitParameterFlag_NonRealTime: AudioUnitParameterOptions = 1 << 24;
490pub const kAudioUnitParameterFlag_ExpertMode: AudioUnitParameterOptions = 1 << 26;
491pub const kAudioUnitParameterFlag_OmitFromPresets: AudioUnitParameterOptions = 1 << 13;
492
493// ─── AUPreset ─────────────────────────────────────────────────────────────────
494
495/// A factory preset entry returned by `kAudioUnitProperty_FactoryPresets`.
496#[repr(C)]
497pub struct AUPreset {
498    pub presetNumber: SInt32,
499    pub presetName: CFStringRef,
500}
501
502// ─── AUCocoaViewInfo ──────────────────────────────────────────────────────────
503
504/// Returned by `kAudioUnitProperty_CocoaUI` to tell the host where the view
505/// factory class lives.
506#[repr(C)]
507pub struct AUCocoaViewInfo {
508    /// A `CFBundleRef` for the bundle that contains the view.
509    pub mCocoaAUViewBundleLocation: CFStringRef, // actually CFURLRef, but opaque here
510    /// A `CFStringRef` naming the `AUCocoaUIBase` subclass in that bundle.
511    pub mCocoaAUViewClass: [CFStringRef; 1], // variable length array of 1+
512}
513
514// ─── Supported channel info ────────────────────────────────────────────────────
515
516/// One entry in the array returned by `kAudioUnitProperty_SupportedNumChannels`.
517#[repr(C)]
518#[derive(Clone, Copy, Debug)]
519pub struct AUChannelInfo {
520    /// Number of input channels (-1 = any, -2 = any matching output channel count).
521    pub inChannels: SInt16,
522    /// Number of output channels (-1 = any).
523    pub outChannels: SInt16,
524}
525
526// ─── Host callback info ────────────────────────────────────────────────────────
527
528/// Host callbacks registered via `kAudioUnitProperty_HostCallbacks`.
529#[repr(C)]
530pub struct HostCallbackInfo {
531    pub hostUserData: *mut c_void,
532    pub beatAndTempoProc: Option<
533        unsafe extern "C" fn(
534            inHostUserData: *mut c_void,
535            outCurrentBeat: *mut Float64,
536            outCurrentTempo: *mut Float64,
537        ) -> OSStatus,
538    >,
539    pub musicalTimeLocationProc: Option<
540        unsafe extern "C" fn(
541            inHostUserData: *mut c_void,
542            outDeltaSampleOffsetToNextBeat: *mut UInt32,
543            outTimeSig_Numerator: *mut Float32,
544            outTimeSig_Denominator: *mut UInt32,
545            outCurrentMeasureDownBeat: *mut Float64,
546        ) -> OSStatus,
547    >,
548    pub transportStateProc: Option<
549        unsafe extern "C" fn(
550            inHostUserData: *mut c_void,
551            outIsPlaying: *mut Boolean,
552            outTransportStateChanged: *mut Boolean,
553            outCurrentSampleInTimeLine: *mut Float64,
554            outIsCycling: *mut Boolean,
555            outCycleStartBeat: *mut Float64,
556            outCycleEndBeat: *mut Float64,
557        ) -> OSStatus,
558    >,
559    pub transportStateProc2: Option<
560        unsafe extern "C" fn(
561            inHostUserData: *mut c_void,
562            outIsPlaying: *mut Boolean,
563            outIsRecording: *mut Boolean,
564            outTransportStateChanged: *mut Boolean,
565            outCurrentSampleInTimeLine: *mut Float64,
566            outIsCycling: *mut Boolean,
567            outCycleStartBeat: *mut Float64,
568            outCycleEndBeat: *mut Float64,
569        ) -> OSStatus,
570    >,
571}
572
573unsafe impl Send for HostCallbackInfo {}
574unsafe impl Sync for HostCallbackInfo {}
575
576// ─── CoreFoundation helpers (minimal) ─────────────────────────────────────────
577
578/// Create a `CFStringRef` from a Rust `&str` (UTF-8 → UTF-16).
579/// Returns null on failure.
580///
581/// # Safety
582/// The caller is responsible for calling `CFRelease` on the returned string.
583pub unsafe fn cf_string_create(s: &str) -> CFStringRef {
584    extern "C" {
585        fn CFStringCreateWithBytes(
586            alloc: *mut c_void,
587            bytes: *const u8,
588            numBytes: c_int,
589            encoding: UInt32,
590            isExternalRepresentation: Boolean,
591        ) -> CFStringRef;
592    }
593    // kCFStringEncodingUTF8 = 0x08000100
594    CFStringCreateWithBytes(
595        std::ptr::null_mut(),
596        s.as_ptr(),
597        s.len() as c_int,
598        0x0800_0100,
599        0,
600    )
601}
602
603/// Release a `CFTypeRef`.
604///
605/// # Safety
606/// `ptr` must be a valid CoreFoundation object (or null).
607pub unsafe fn cf_release(ptr: CFStringRef) {
608    if !ptr.is_null() {
609        extern "C" {
610            fn CFRelease(cf: CFStringRef);
611        }
612        CFRelease(ptr);
613    }
614}