soundwatch
Read-only audio diagnostics, in ten tabs.
SoundWatch is the terminal you open when the audio is wrong and nothing tells you why — before you reach for Audio MIDI Setup, aplay -l, /proc/asound, and a lot of guessing. Where those tell you which devices exist, SoundWatch tells you what they are doing: real levels, real latency, xruns as they happen, and a spectrum analyser that names the fault instead of leaving you to read the shape.
Ten tabs, macOS and Linux. Read-only by construction — it never changes a volume, a mute, a route, or a default, writes nothing back to the audio system, and records no audio to disk. Sibling to netwatch, syswatch and diskwatch, and the first one where getting permission is harder than getting the data.
git clone https://github.com/matthart1983/soundwatch && cd soundwatch && make installInstall
tar xzf soundwatch-macos-aarch64.tar.gz && install -m 755 soundwatch-macos-aarch64 /usr/local/bin/soundwatchFour tarballs per release — macOS aarch64/x86_64 (signed) and Linux x86_64/aarch64 (glibc 2.34 floor). Checksums are published alongside them.
git clone https://github.com/matthart1983/soundwatch && cd soundwatch && make installRust 1.88+. Use make, not cargo build — see the consent note below. This is also the install that gets you a stable code signature, and therefore a permission grant that survives upgrades.
The binary carries an embedded Info.plist, and macOS only honours it if the code signature binds it. Cargo produces a linker-signed binary where it is not bound, so the system has nothing to prompt with, refuses to even ask, and creates the audio tap anyway — which then delivers digital silence forever, with no error anywhere. Every meter reads zero and nothing tells you why. make re-signs after the link, which is the entire fix.
First 60 seconds
No config, no daemon, no agent. The first run on macOS asks for permission to observe system audio; grant it once.
- 1Launch — output metering on, input off$
soundwatch - 2Add the input meter (asks for microphone permission)$
soundwatch --meter-input - 3Meters look flat? Find out which kind of flat$
soundwatch --probe-tap - 4See the whole design without touching an audio device at all$
soundwatch --demo
The tabs
Switch with 1–0.
The permission problem, and why it is interesting
Reading system audio on macOS needs consent, and a command-line tool has no bundle for the system to identify it by. Without one, TCC attributes the request to whichever terminal launched it, refuses to prompt at all, and — this is the part that costs a weekend — creates the tap anyway. The callback fires on schedule, every sample it delivers is zero, and no error is raised at any layer.
The fix is three things that all have to be true at once: an Info.plist embedded in the binary’s __TEXT section, a code signature that binds it, and a re-exec that disclaims responsibility so the request is attributed to SoundWatch rather than to Terminal. soundwatch --probe-tap exists because the failure is otherwise indistinguishable from a quiet machine, and it is the first thing to run when the meters look flat.
This is documented at length in the repository rather than hidden, because anyone building an audio tool on macOS hits it, and almost nothing on the internet says so.
What each platform can actually report
Two backends behind one trait. A capabilities struct declares what the active backend can measure; the UI renders “--” plus one explanatory line for anything it cannot, and never shifts the layout to hide a gap.
| Metric | macOS | Linux |
|---|---|---|
| Output / input levels | ✓ process tap (14.2+) | ✓ monitor source via libpulse |
| Devices, rate, depth, channels | ✓ CoreAudio HAL | ✓ /proc/asound |
| Buffer size and computed latency | ✓ device + stream + safety offset | ✓ from buffer and period size |
| Xrun counts | ✓ ProcessorOverload listener | ✓ /proc/asound status |
| Per-application streams | ✓ with mic-in-use marker | — device holder only; needs the sound server |
| Per-application levels | — needs one tap per process | — needs the sound server |
Keys
| Key | Action |
|---|---|
| 1–0 | Switch tabs |
| ↑ / ↓ | Move selection |
| ↵ | Detail panel |
| / | Filter |
| p | Pause |
| , | Settings — theme, FFT size, floors, decay, refresh rate |
| ? | Help |
| q | Quit |
Devices, formats, latency and xruns come from /proc/asound — no library, no daemon, no permission beyond reading /proc. The level meters open the default monitor through libpulse, which is loaded at runtime rather than linked, so a headless box with no sound server still runs the tool and tells you which part is missing instead of failing to start.
Anti-goals
Not a mixer — it never changes a volume, a mute, a route, or a default device. Not a recorder — no audio is written to disk, ever; the meters read levels and the samples die in a ring buffer. Not a DAW analyser — it diagnoses the system’s audio path, not your mix. Not multi-host, and not a daemon: the session is the database.