From 5c3c45213306e2856acf65c1198311a8f294212a Mon Sep 17 00:00:00 2001 From: Tyler Date: Fri, 5 Jun 2026 15:40:44 -0700 Subject: [PATCH] docs: rewrite README as a fresh project overview + usage guide Co-Authored-By: Claude Opus 4.8 --- README.md | 171 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 89 insertions(+), 82 deletions(-) diff --git a/README.md b/README.md index d8efdeb..9edee7e 100644 --- a/README.md +++ b/README.md @@ -1,73 +1,90 @@ # rspektrum -An interactive spectrogram viewer for inspecting **mLink** radio captures (and -any other audio). It computes an STFT of a WAV file, draws it as a zoomable, -pannable spectrogram, and can overlay **mLnL annotations** — labelled regions +**rspektrum** is an interactive spectrogram viewer for inspecting radio captures +and arbitrary audio. It loads a WAV file, computes a short-time Fourier transform +(STFT), and draws the result as a zoomable, pannable time–frequency image. Its +distinguishing feature is support for **mLnL annotations** — labelled regions (TX frames, assertion outcomes, impairment fires, …) carried *inside* the WAV -file itself. You can box a time/frequency region, hear it back through a -bandpass filter, and export either the picture (PNG) or the isolated audio -(WAV). It runs as a native desktop app (C + raylib), as a headless CLI renderer, -and as a WebAssembly build in the browser. +file itself — which it overlays on the measured spectrogram so you can compare +what a modem *intended* to transmit against what actually hit the air. + +You can box a time/frequency region, hear it back through a bandpass filter, and +export either the picture (PNG) or the isolated audio (WAV). rspektrum runs three +ways: a native desktop app (C + raylib), a headless command-line renderer, and a +WebAssembly build in the browser. + +--- ## What it's for -The primary use case is reviewing captures from the mLink stack: a WAV recording -of an over-the-air signal, with an embedded `mLnL` chunk describing what the -modem/daemon believed it was transmitting at each instant. rspektrum renders -those annotations on top of the measured spectrogram so you can see, frame by -frame, what was *intended* versus what actually hit the air. See -[`mlnl_chunk_spec.md`](mlnl_chunk_spec.md) for the annotation format. It also -works as a general-purpose spectrogram tool for plain WAVs with no annotations. +The primary use case is reviewing captures from the **mLink** radio stack: a WAV +recording of an over-the-air signal with an embedded `mLnL` chunk describing what +the modem/daemon believed it was transmitting at each instant. rspektrum renders +those annotations on top of the measured spectrogram, frame by frame, so intent +and reality sit side by side. + +It also works as a general-purpose spectrogram tool for plain WAVs with no +annotations. See [`mlnl_chunk_spec.md`](mlnl_chunk_spec.md) for the annotation +format. + +--- ## Features -- **STFT spectrogram** with selectable colormaps, adjustable dB floor / dynamic - range, and absolute (dBFS) or relative amplitude scaling. -- **mLnL annotation overlay** — labelled boxes drawn from the WAV's embedded - annotation chunk; hover a box (or its region on the waveform scope) for a - tooltip with per-frame detail (sequence, channel, rate, scheduling offset…). -- **Zoom & pan** the time/frequency view (mouse wheel + Alt/middle-drag). -- **Region selection**: box a time **and** frequency range with the mouse. -- **Filtered playback**: play just the selected region, band-limited to the - selected frequency box (FFT bandpass — "what you hear is what you'd export"). -- **Waveform scope** (toggle) showing the time-domain signal under the spectrum. -- **Marker / ruler tool** and a **spectrum slice (PSD)** readout. -- **Export**: save the view as a **PNG**, or the selected region as a **WAV**. -- **Headless render mode**: produce an annotated PNG from the command line with - no window, no GL, and no X server — pure CPU, runs anywhere (see below). -- **Broad input support**: WAV directly (8/16-bit PCM, 32-bit float; stereo - downmixed to mono); other formats transparently transcoded via `ffmpeg` if it - is on `PATH`. Drag-and-drop loading. -- **Cross-platform**: Linux/desktop, Windows, and a WebAssembly build. +- **STFT spectrogram** — selectable colormaps, adjustable dB floor / dynamic + range, absolute (dBFS) or relative amplitude scaling. +- **mLnL annotation overlay** — labelled boxes from the WAV's embedded annotation + chunk; hover a box (or its region on the scope) for per-frame detail (sequence, + channel, rate, scheduling offset…). +- **Zoom & pan** the time/frequency view. +- **Region selection** — box a time *and* frequency range with the mouse. +- **Filtered playback** — play just the selected region, band-limited to the + selected frequency box via an FFT bandpass. What you hear is what you'd export. +- **Waveform scope** — toggleable time-domain view beneath the spectrum. +- **Marker / ruler** and a **spectrum slice (PSD)** readout. +- **Export** — save the view as a PNG, or the selected region as a WAV. +- **Headless render mode** — produce an annotated PNG from the CLI with no + window, no GL, and no X server. Pure CPU; runs in CI, containers, or over SSH. +- **Broad input** — WAV directly (8/16-bit PCM, 32-bit float; stereo downmixed to + mono); other formats transcoded via `ffmpeg` if it's on `PATH`. Drag-and-drop. +- **Cross-platform** — Linux/desktop, Windows, and a WebAssembly build. + +--- ## Building -The build is driven by a checked-in Makefile (premake5 is **not** required to -build — only to regenerate the makefiles): +The build is driven by a checked-in Makefile. `premake5` is **not** required to +build — only to regenerate the makefiles. ```bash make -f rspektrum.make config=debug_x64 # -> bin/Debug/rspektrum make -f rspektrum.make config=release_x64 # -> bin/Release/rspektrum ``` -The web build: +Web (WebAssembly) build: ```bash ./build_web.sh # emscripten; emits the WebAssembly bundle ``` -> Note: the release build enables `-O2`, which turns on extra warnings -> (`-Wformat-truncation`) that the debug build doesn't — build release before +> The release build enables `-O2`, which turns on extra warnings +> (`-Wformat-truncation`) that the debug build doesn't. Build release before > declaring a change clean. -## Running (GUI) +--- + +## Usage (desktop GUI) ```bash ./bin/Debug/rspektrum [input.wav] ``` Load a file by passing it on the command line, dragging a `.wav` onto the window, -or pressing **O** for the file browser. +or pressing **O** for the file browser. Try the bundled sample: + +```bash +./bin/Debug/rspektrum mlnl_samples.wav # in-repo WAV with an embedded mLnL chunk +``` ### Controls @@ -90,15 +107,17 @@ or pressing **O** for the file browser. | **F1** | About / help | | **Esc** | Clear selection / close dialog | -Most controls are also available as buttons in the left sidebar (colormap, -floor, dynamic range, annotation opacity, grid, etc.). +Most controls are also available as buttons in the left sidebar (colormap, floor, +dynamic range, annotation opacity, grid, …). -## Headless rendering (CLI) +--- + +## Usage (headless render) `--render` writes the spectrogram straight to a PNG **with no window, no GL -context, and no X server** — it computes the STFT, colorizes the bitmap, bakes -the annotation overlay onto it, and exports, all on the CPU. This runs anywhere -(CI, a bare SSH session, a container with no display), not just under Xvfb: +context, and no X server**. It computes the STFT, colorizes the bitmap, bakes the +annotation overlay onto it, and exports — all on the CPU — so it runs anywhere +(CI, a bare SSH session, a container with no display): ```bash ./bin/Debug/rspektrum --render OUT.png INPUT.wav [options] @@ -108,8 +127,6 @@ The output is the **real spectrogram bitmap** at native STFT resolution (not a screenshot of the UI), so it carries no sidebar/scope chrome — just the time–frequency image with the annotation overlay. -Options: - | Flag | Effect | |------|--------| | `-r, --render OUT.png` | Render to `OUT.png` and exit (no window/GL/X) | @@ -123,8 +140,7 @@ Options: Annotation boxes are drawn **outline + label only** (no translucent fill): mLnL captures contain many overlapping full-band boxes whose fills would alpha-stack to opaque and bury the signal, so the outline marks each region while the -spectrogram reads through. `--annotation-opacity` controls outline/label -strength. Filter to just the kinds you care about with `--annotation-kinds`: +spectrogram reads through. ```bash # everything, brighter overlay @@ -135,20 +151,20 @@ strength. Filter to just the kinds you care about with `--annotation-kinds`: --annotation-kinds=tx_frame,assertion_failed ``` -Kinds: `tx_frame`, `tx_burst`, `control`, `channel_up`, `channel_down`, -`assertion_passed`, `assertion_failed`, `impairment_fire`, `gain_change`, -`unknown`. `mlnl_samples.wav` is an in-repo WAV that carries an embedded `mLnL` -chunk. +Annotation kinds: `tx_frame`, `tx_burst`, `control`, `channel_up`, +`channel_down`, `assertion_passed`, `assertion_failed`, `impairment_fire`, +`gain_change`, `unknown`. -> The hover tooltip (sched offset, per-frame detail) only appears with a live -> mouse over a box, so it cannot show up in a static `--render`. To verify -> tooltip behaviour you need a real (or virtual) display driving the GUI — see -> below. +> The hover tooltip only appears with a live mouse over a box, so it cannot show +> up in a static `--render`. To verify tooltip behaviour you need a real (or +> virtual) display driving the GUI — see below. -## Driving the GUI headlessly (for agents / CI) +--- + +## Driving the GUI headlessly (agents / CI) The app can be run, screenshotted, and clicked on a virtual X display with no -monitor or GPU (Mesa software GL under Xvfb). The accumulated playbook lives in +monitor or GPU (Mesa software GL under Xvfb). The full playbook lives in [`AGENTS.md`](AGENTS.md); the working reference implementation is [`shot_input.sh`](shot_input.sh). @@ -164,35 +180,25 @@ DISPLAY=:99 import -window root /tmp/shot.png # 4. grab the frame Prerequisites (Debian/Ubuntu): `sudo apt-get install xvfb imagemagick xdotool` (plus `libgl1-mesa-dri` and `LIBGL_ALWAYS_SOFTWARE=1` if GL fails / frames are -black). To exercise UI paths, synthesize input with `xdotool` against -`DISPLAY=:99` — e.g. move the mouse over an annotation box and re-grab to capture -the hover tooltip: +black). Synthesize input with `xdotool` against `DISPLAY=:99` to exercise UI +paths. -```bash -DISPLAY=:99 xdotool mousemove 640 400 # hover a box (coords from the spectrogram) -DISPLAY=:99 import -window root /tmp/hover.png -DISPLAY=:99 xdotool key space # play the selection, etc. -``` - -There is no window manager, so the window sits at `0,0` and fills the Xvfb -screen — match the screen size to the window and a root grab equals the app's -frame. Always capture stdout/stderr to a log; it's your only view of `TraceLog` -output and crashes. `shot_input.sh` wraps all of this (start → settle → -screenshot → optional input → diff); read `AGENTS.md` for the traps (input focus, -ImageMagick v6 vs v7 command names, software-GL timing). +--- ## Technical notes -- **STFT**: Hann-windowed, 2048-point FFT with 50% overlap by default; - frequency resolution `sampleRate / fftSize` Hz per bin. Amplitude shown in dB. -- **Axes**: X = time (s), Y = frequency (Hz, scaled to the file's Nyquist), +- **STFT** — Hann-windowed, 2048-point FFT with 50% overlap by default; + frequency resolution `sampleRate / fftSize` Hz per bin. Amplitude in dB. +- **Axes** — X = time (s), Y = frequency (Hz, scaled to the file's Nyquist), colour = amplitude. -- **Playback / WAV export** share the same processing path: the selected time - span, FFT-bandpassed to the selected frequency box, peak-normalised. -- **mLnL parsing**: walks the WAV's RIFF chunks for the four-CC `mLnL` chunk - (UTF-8 JSON Lines); unknown chunks are skipped, so annotated files remain +- **Playback / WAV export** share one processing path: the selected time span, + FFT-bandpassed to the selected frequency box, peak-normalised. +- **mLnL parsing** — walks the WAV's RIFF chunks for the four-CC `mLnL` chunk + (UTF-8 JSON Lines); unknown chunks are skipped, so annotated files stay standards-compliant audio everywhere else. +--- + ## Source layout ``` @@ -207,4 +213,5 @@ src/ ``` See [`raylib_for_desktop_applications.md`](raylib_for_desktop_applications.md) -for the performance/idle-CPU lessons behind the desktop build. +for the performance / idle-CPU lessons behind the desktop build, and +[`AGENTS.md`](AGENTS.md) for the headless-testing playbook.