Audio CoreAudio
Context: $ARGUMENTS
Quick start
- List input/output devices: → Step 1 (
macaudio.py list-devices) - Change default output: → Step 2 (
macaudio.py set-default) - Inspect an audio file: → Step 3 (
macaudio.py info) - Play a file: → Step 3 (
macaudio.py play) - Convert format/rate/bits: → Step 4 (
macaudio.py convert) - Text-to-speech: → Step 5 (
macaudio.py tts) - Build an Aggregate Device: → Step 6 (
macaudio.py aggregate-create) - List HAL virtual drivers: → Step 7 (
macaudio.py hal-plugins) - Install BlackHole virtual cable: → Step 8 (
macaudio.py blackhole-install)
When to use
- User is on macOS and needs terminal-driven audio control.
- Wants to change the default input/output/system device without clicking through System Settings → Sound.
- Wants to convert audio files with Apple's built-in
afconvert(more format coverage than SoX for Apple formats; handles CAF, M4A, ALAC natively). - Wants to build an Aggregate Device (multi-input) or Multi-Output Device (mirror output to several devices).
- Wants to install / evaluate virtual audio drivers (BlackHole, Loopback, Background Music).
Not for: Linux (use audio-pipewire), Windows (use audio-wasapi).
The script exits 2 with a helpful message if run on non-macOS.
Step 1 — List devices
uv run ${CLAUDE_SKILL_DIR}/scripts/macaudio.py list-devices
uv run ${CLAUDE_SKILL_DIR}/scripts/macaudio.py list-devices --format json
SwitchAudioSource -a underneath. Each line is <name> (<type>) where type is
input / output. If SwitchAudioSource isn't installed the script tells you
to run brew install switchaudio-osx.
Step 2 — Set default input/output/system device
macOS distinguishes three defaults: output (music/TV audio), input (mic), system (alert sounds, which can be a different device).
uv run ${CLAUDE_SKILL_DIR}/scripts/macaudio.py set-default "MacBook Pro Speakers"
uv run ${CLAUDE_SKILL_DIR}/scripts/macaudio.py set-default "Scarlett Solo" --type input
uv run ${CLAUDE_SKILL_DIR}/scripts/macaudio.py set-default "MacBook Pro Speakers" --type system
Device name must match exactly (spaces matter, quoting required). Use
list-devices first.
Step 3 — Inspect and play files (afinfo / afplay)
afinfo + afplay ship with macOS. No install needed.
uv run ${CLAUDE_SKILL_DIR}/scripts/macaudio.py info track.caf
uv run ${CLAUDE_SKILL_DIR}/scripts/macaudio.py play track.caf
uv run ${CLAUDE_SKILL_DIR}/scripts/macaudio.py play track.caf --volume 128 # 0-255
afinfo reads every Apple format (CAF, M4A/AAC/ALAC, AIFF, WAV, MP3) plus
anything the system's registered AudioToolbox components understand.
Step 4 — Convert with afconvert
afconvert is Apple's native format converter. Preferred over SoX for
Apple-specific flows (CAF, ALAC, AAC for M4A/M4R, lossless 32-bit float).
# WAV -> Apple Lossless in an M4A
uv run ${CLAUDE_SKILL_DIR}/scripts/macaudio.py convert \
--input in.wav --output out.m4a --format m4af
# 24-bit 96k WAV -> 16-bit 48k WAV
uv run ${CLAUDE_SKILL_DIR}/scripts/macaudio.py convert \
--input hi.wav --output lo.wav --format WAVE \
--rate 48000 --bit-depth 16
# Stereo -> 5.1 upmix (container must support it — CAF does)
uv run ${CLAUDE_SKILL_DIR}/scripts/macaudio.py convert \
--input stereo.wav --output surround.caf --format caff \
--channel-layout 5.1
FourCC codes the --format flag accepts: caff (CAF), m4af (MPEG-4 audio),
WAVE, AIFF, AIFC. Full list: afconvert -hf.
Step 5 — say + afplay TTS
uv run ${CLAUDE_SKILL_DIR}/scripts/macaudio.py tts "Build complete"
uv run ${CLAUDE_SKILL_DIR}/scripts/macaudio.py tts "hola" --voice Monica
uv run ${CLAUDE_SKILL_DIR}/scripts/macaudio.py tts "saved" --output voicemail.aiff
say -v '?' lists installed voices. Voices beyond the defaults (Alex, Samantha,
Daniel) require a one-time download via System Settings → Accessibility →
Spoken Content → System Voice → Customize.
Step 6 — Aggregate Device / Multi-Output Device
macOS lets you combine physical devices two ways:
- Aggregate Device — sum inputs and outputs of several devices into one logical interface. DAWs see it as a single multi-channel device. Needs drift correction when the member devices don't share a word clock.
- Multi-Output Device — mirror playback to several outputs (e.g. speakers + BlackHole + AirPods). Output-only.
This wrapper opens Audio MIDI Setup and prints the clickable steps, because
programmatic creation needs AudioHardwareCreateAggregateDevice from
CoreAudio/AudioHardware.h (Swift / Objective-C). Use when wiring for
automation:
uv run ${CLAUDE_SKILL_DIR}/scripts/macaudio.py aggregate-create \
--name "Studio Aggregate" \
--devices "AppleHDAEngineOutput:1B,0,0,1:0" "BlackHole2ch_UID" \
--drift-correct "BlackHole2ch_UID"
Member device UIDs come from list-devices (pass --format json for the full
UID strings).
For a truly automatable path, write the Swift snippet that calls
AudioHardwareCreateAggregateDevice — see references/virtual-drivers.md.
Step 7 — List HAL plug-ins
HAL plug-ins are .driver bundles that register virtual audio devices:
uv run ${CLAUDE_SKILL_DIR}/scripts/macaudio.py hal-plugins
Typical output includes BlackHole2ch.driver, BackgroundMusic.driver,
Loopback.driver, Aggregate/Multi-Output synthesizer drivers from Apple.
Global path: /Library/Audio/Plug-Ins/HAL/*.driver.
User path: ~/Library/Audio/Plug-Ins/HAL/*.driver (rare; per-user installs).
After installing or removing a HAL plug-in, coreaudiod has to rescan:
sudo pkill coreaudiod # wrapper intentionally does NOT run this
Step 8 — BlackHole (virtual cable)
BlackHole is the go-to free open-source virtual driver — works on Apple Silicon (DriverKit / AudioDriverKit) and Intel, 2ch / 16ch / 64ch variants.
The wrapper prints install instructions rather than installing automatically:
uv run ${CLAUDE_SKILL_DIR}/scripts/macaudio.py blackhole-install --variant 2ch
uv run ${CLAUDE_SKILL_DIR}/scripts/macaudio.py blackhole-install --variant 16ch
Runs the install only if the user copies the printed brew install --cask line.
See references/virtual-drivers.md for the
full BlackHole vs Loopback vs Background Music comparison.
Gotchas
- Soundflower is abandoned. It does not load on macOS 11+ / Apple Silicon. Never recommend it. Use BlackHole instead.
SwitchAudioSourcedevice names must match exactly. Trailing spaces, capitalization, and the full name (including brand prefix) all matter. Quote-wrap everything.list-devices --format jsongives you a copy-pasteable UID.- There are two "audiodevice" CLIs in the wild. An npm package
(
node-audiodevice) and a Go binary. Both predate SwitchAudioSource and have different flags. Always specify the package/binary when pointing at them; default toSwitchAudioSourcein docs. - BlackHole, Loopback, and Background Music all use DriverKit/AudioDriverKit
on Apple Silicon. Legacy kext audio drivers (Soundflower, old Loopback
versions) won't load. If a driver supposedly installs but
list-devicesdoesn't show it, SIP or kernel-extension approval is likely blocking it (System Settings → Privacy & Security → Allow). - Aggregate Device needs drift correction when members don't share a clock. Without drift correction, multi-USB-device aggregates will desync by seconds over a long session.
- Multi-Output Device cannot be a system alert device. macOS rejects it — system sounds require a real output endpoint.
afconvertfourCC codes are case-sensitive.WAVEwith-f, notwave. LikewiseAIFF/AIFC/caff/m4af.- After installing a HAL plug-in, new processes pick it up; existing ones don't.
Apps that cached
AudioDeviceIDvalues will miss it until restart. System Settings → Sound usually re-enumerates immediately. sayruns through the system output by default, not whateverset-default --type inputwas last set to. If you need say → file, pipe through--output(AIFF) orafconvertafterward for M4A/MP3.- AppleScript via osascript can hit Sound preferences via UI automation, but
it's fragile across macOS versions and requires accessibility permissions.
Prefer
SwitchAudioSourcefor scripted default changes. - CoreAudio HAL IDs are not stable across reboots. Use the device
UID (string) instead of the numeric
AudioDeviceIDwhen persisting references in scripts / plist / DAW configs. - BlackHole per-channel variants are not upgradeable in place. Uninstall
blackhole-2chbefore installingblackhole-16ch, or both drivers show up simultaneously (which is fine — they are different devices, separately named).
Examples
Example 1 — "Swap default output to my headphones"
uv run ${CLAUDE_SKILL_DIR}/scripts/macaudio.py list-devices # find exact name
uv run ${CLAUDE_SKILL_DIR}/scripts/macaudio.py set-default "Sony WH-1000XM5"
Example 2 — "Capture system audio into OBS"
Install BlackHole 2ch:
uv run ${CLAUDE_SKILL_DIR}/scripts/macaudio.py blackhole-install --variant 2ch # copy the printed brew command and run itIn Audio MIDI Setup build a Multi-Output Device combining your speakers + BlackHole; set it as system output.
Point OBS's "Audio Input Capture" at BlackHole.
Example 3 — "Convert a WAV archive to Apple Lossless"
uv run ${CLAUDE_SKILL_DIR}/scripts/macaudio.py convert \
--input master.wav --output master.m4a --format m4af
Example 4 — "Read EXIF from an AIFF"
afinfo covers it:
uv run ${CLAUDE_SKILL_DIR}/scripts/macaudio.py info recording.aiff
Example 5 — "Speak build results to me"
uv run ${CLAUDE_SKILL_DIR}/scripts/macaudio.py tts "Tests passed, zero failures" --voice Samantha
Troubleshooting
SwitchAudioSource: command not found
Cause: Not installed.
Fix: brew install switchaudio-osx. The wrapper points at this in its error
output.
Default device "silently" reverts after plugging in USB interface
Cause: macOS default-reassign behavior when "When playing sound through" is
set to "whatever device is connected". Overrides your scripted default.
Fix: System Settings → Sound → Output → uncheck "Automatically switch to
newly connected devices" (macOS 14+), or persist the default in a LaunchAgent
that re-runs set-default on device change.
afconvert: Error -50 (invalid parameter)
Cause: Requested an output format/rate/bit-depth combo that the codec
rejects (e.g. ALAC in WAV container, 32-bit float into M4A).
Fix: Drop mismatched flags. ALAC belongs in m4af only; bit-depth is
AAC-ignored.
BlackHole doesn't appear after install
Cause: System Extension blocked by macOS. Fix: System Settings → Privacy & Security → scroll to the "System software was blocked" banner → Allow. Reboot. Retry.
afplay works but no sound
Cause: afplay uses the current system default output. If you set a weird
output recently (aggregate without speakers as a member), nothing audible is
reached.
Fix: Re-run set-default with a known-good physical output and retry.
Aggregate Device session drifts
Cause: No drift correction, or drift master isn't the most-stable clock. Fix: In Audio MIDI Setup, check "Drift Correction" on every non-master member. Put the interface with the best clock (Thunderbolt → PCIe card → USB 2) as master.
Reference docs
- BlackHole vs Loopback vs Background Music comparison, plus the Swift snippet
for programmatic Aggregate Device creation →
references/virtual-drivers.md.