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▶ play demo
// 01 · install

Install

Release binaries
Download releases →

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

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

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

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

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.

// 03 · first 60 seconds

First 60 seconds

On macOS, grant system-audio permission when prompted. An update can require consent again; microphone metering is a separate opt-in.

  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 metering dependencies
Linux metering dependencies

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