netwatch ~ ~/labs/soundwatch.md
preview·v0.2.0
cd~/labs/soundwatch
← all tools
soundwatch.tui

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 install
~ $ soundwatchtui
// soundwatch · loops · no audio▶ playing
// 01 · install

Install

From a release
$tar xzf soundwatch-macos-aarch64.tar.gz && install -m 755 soundwatch-macos-aarch64 /usr/local/bin/soundwatch

Four tarballs per release — macOS aarch64/x86_64 (signed) and Linux x86_64/aarch64 (glibc 2.34 floor). Checksums are published alongside them.

From source
$git clone https://github.com/matthart1983/soundwatch && cd soundwatch && make install

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

// 02 · on macos, build with make — not cargo build
On macOS, build with make — not cargo build

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.

// 03 · first 60 seconds

First 60 seconds

No config, no daemon, no agent. The first run on macOS asks for permission to observe system audio; grant it once.

  1. 1
    Launch — output metering on, input off
    $soundwatch
  2. 2
    Add the input meter (asks for microphone permission)
    $soundwatch --meter-input
  3. 3
    Meters look flat? Find out which kind of flat
    $soundwatch --probe-tap
  4. 4
    See the whole design without touching an audio device at all
    $soundwatch --demo
// 04 · the tabs

The tabs

Switch with 1–0.

1 · OverviewOutput device, rate, buffer, computed latency and 60s xrun count across the top; live output and input meters; active streams underneath. The five-second answer.
2 · DevicesEvery input and output device with transport, rate, bit depth, channel count, buffer size and running state. Select one for the full detail panel.
3 · StreamsWhich applications are playing or recording right now, with the mic-in-use marker. On Linux this is the process holding the ALSA device, which under PipeWire is the sound server — stated plainly rather than shown as an empty table.
4 · MetersPeak, RMS, crest factor and peak-hold for output and input, with a btop-style colour gradient. Crest factor is the number that tells you a track is crushed before your ears do.
5 · SpectrumReal-time FFT, log-frequency, 20 Hz to Nyquist, with peak-hold and configurable decay ballistics — and fault detection on top: mains hum at 50/60 Hz and their harmonics, and band-limiting from a codec that is throwing away the top octave.
6 · LatencyRound-trip latency broken into its parts — device latency, stream latency, safety offset and buffer — so you can see which one to argue with.
7 · XrunsDropouts as they happen, with a 60s window and a running log. The tab that proves the crackle you heard was real.
8 · RoutingWhere audio is actually going: default devices, aggregate and multi-output members, and the sample-rate mismatches that quietly cost you quality.
9 · TimelineSession history — device changes, stream starts and stops, xrun bursts — so an intermittent fault has somewhere to leave a trace.
0 · InsightsPlain-English cards over everything above: sample-rate mismatch, buffer too small for the workload, a device running at a rate nothing asked for, hum, band-limiting, clipping.
// 05 · the permission problem, and why it is interesting

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.

// 06 · what each platform can actually report

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.

MetricmacOSLinux
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
// 07 · keys

Keys

KeyAction
1–0Switch tabs
↑ / ↓Move selection
Detail panel
/Filter
pPause
, Settings — theme, FFT size, floors, decay, refresh rate
?Help
qQuit
// 08 · linux needs nothing installed
Linux needs nothing installed

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.

// 09 · anti-goals

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.

// related