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
macOS and Linux, x86_64 and ARM64. Choose the matching archive and check SHA256SUMS. macOS binaries are signed; system-audio metering requires macOS 14.2+.
git clone https://github.com/matthart1983/soundwatch && cd soundwatch && make installRequires Rust 1.88+ and make. On macOS, make signs the binary correctly; ordinary ad-hoc signatures change on rebuild, so updates can ask for consent again. The default install directory is /usr/local/bin; writing there may require administrator access.
make re-signs the binary so macOS can associate its embedded usage description with the audio-capture permission request. Cargo alone does not complete that step. Run make probe if meters stay flat. Ordinary ad-hoc signatures change on rebuild; a stable signing identity is a separate configuration.
First 60 seconds
On macOS, grant system-audio permission when prompted. An update can require consent again; microphone metering is a separate opt-in.
- 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.