feat: true no-X headless --render (CPU spectrogram bitmap + overlay)
Rewrite --render to compute the spectrogram and write a PNG entirely on the CPU, with no window, no GL context, and no X server. Previously it opened a hidden GL window and grabbed LoadImageFromScreen(), which still required an X server (Xvfb); the output was a UI screenshot rather than the spectrogram data. The new path (RunHeadlessRender) loads the WAV, computes the STFT, colorizes the bitmap at native STFT resolution, bakes the mLnL annotation overlay onto it, and exports — all CPU-only. render.c gains a GL-free colorize (BuildSpectrogramImageCPU), a CPU font loader (LoadFontCPU), and a CPU overlay drawer (DrawAnnotationsToImage). Annotations draw outline + label only: mLnL captures contain many overlapping full-band boxes whose translucent fills alpha-stack to opaque and bury the signal. The outline marks each region while the spectrogram reads through; a dark backing strip keeps labels legible. Note: MeasureTextEx/ImageText* bail when font.texture.id == 0, so the CPU font sets a sentinel non-zero id (the draw path reads glyph images, never the texture). Render options: --annotation-opacity (overlay strength), --annotation-kinds (comma-separated kind filter), --width (resize; default native). Removed the obsolete --pane/--height window options and the screenshot workaround. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -34,7 +34,7 @@ works as a general-purpose spectrogram tool for plain WAVs with no annotations.
|
||||
- **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 (see below).
|
||||
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.
|
||||
@@ -95,34 +95,50 @@ floor, dynamic range, annotation opacity, grid, etc.).
|
||||
|
||||
## Headless rendering (CLI)
|
||||
|
||||
rspektrum can render an annotated spectrogram to a PNG without opening a window —
|
||||
useful for batch capture and for agents reviewing test output:
|
||||
`--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:
|
||||
|
||||
```bash
|
||||
./bin/Debug/rspektrum --render OUT.png INPUT.wav [options]
|
||||
```
|
||||
|
||||
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) |
|
||||
| `-a, --annotations` | Force the annotation overlay **on** (solid, for review) |
|
||||
| `-r, --render OUT.png` | Render to `OUT.png` and exit (no window/GL/X) |
|
||||
| `-a, --annotations` | Force the annotation overlay **on** |
|
||||
| `--no-annotations` | Force the overlay off |
|
||||
| `--annotation-opacity=V` | Resting overlay alpha `0..1` (default `0.06`, faint) |
|
||||
| `--pane` | Capture only the spectrogram pane (no sidebar/scope) |
|
||||
| `--width N` / `--height N` | Output size (default `1280×800`) |
|
||||
| `--annotation-opacity=V` | Overlay strength `0..1` (default `0.5`) |
|
||||
| `--annotation-kinds=LIST` | Comma-separated kinds to draw (default: all) |
|
||||
| `--width N` | Resize output to `N` px wide (default: native STFT size) |
|
||||
| `-h, --help` | Usage |
|
||||
|
||||
Default annotation opacity is intentionally faint (the overlay is meant to stay
|
||||
out of the way until hovered), so for a screenshot you actually want to *review*,
|
||||
pass `--annotations`. Example against the bundled sample:
|
||||
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`:
|
||||
|
||||
```bash
|
||||
./bin/Debug/rspektrum --render /tmp/review.png mlnl_samples.wav --annotations
|
||||
# everything, brighter overlay
|
||||
./bin/Debug/rspektrum --render /tmp/all.png mlnl_samples.wav --annotation-opacity=0.7
|
||||
|
||||
# only on-air frames and failed assertions
|
||||
./bin/Debug/rspektrum --render /tmp/tx.png mlnl_samples.wav \
|
||||
--annotation-kinds=tx_frame,assertion_failed
|
||||
```
|
||||
|
||||
`mlnl_samples.wav` is an in-repo WAV that carries an embedded `mLnL` chunk.
|
||||
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.
|
||||
|
||||
> 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
|
||||
|
||||
Reference in New Issue
Block a user