Skip to content

ops(gate): the capture leg — stream to WAV, measured, speech log checked (STORY-445, gh-#777) - #789

Merged
genwave-radio merged 5 commits into
mainfrom
ops/capture-leg-445
Sep 16, 2026
Merged

genwave-radio merged 5 commits into
mainfrom
ops/capture-leg-445

Conversation

@genwave-radio

Copy link
Copy Markdown
Collaborator

PR-5 of the gh-#777 epic (STORY-445, SPEC F178.3, F178.5). The gate now listens to what it launched: a real capture off the stream, measured, with the speech log checked.

🏗️ What

  • tools/gate/make_media.sh <outdir> (T489): two 45 s MP3 tones at −12 and −30 LUFS (two-pass loudnorm, verified with ebur128 to ±1 LU) plus two CC0 clips from tools/gate/media/ (≤ 1 MB each, SOURCES.md names creator and URL). The gate's inline synth is gone; it calls this script.
  • tools/gate/measure_audio.sh <wav> --target <lufs> (T490): silencedetect (SILENCE_FLOOR −45 dB, SILENCE_SECS 2 s) event count and ebur128 integrated loudness within TOL_LU 5 of the target. One summary line on stdout; exit 0 pass, 1 fail, 2 usage or an input ffmpeg cannot measure.
  • The capture leg (T491): after a passed fresh leg, --capture logs in to the api with the scratch's own admin password (grepped from its .env, passed on stdin, never sourced or exported), reads Loudness:TargetLufs from GET /api/settings, records CAPTURE_SECS (default 360) of the stream with ffmpeg -reconnect 1, measures it, and counts station.booth_log rows with a non-null segment_kind in the last 7 minutes. Every number lands in the md and json report; a miss fails the leg naming the assertion (login, target, capture, silence, loudness, measure, booth_log). --capture without --fresh exits 2.
  • GateHarness.FakeStation now mirrors the real api: login cookie, 401 without it, settings as an array of {key, value} with a string value.
  • Story445_CapturedAudio.cs: all facts green, plus two sad-path scenarios added in review (an off-level capture fails loudness, a silent gap fails silence).

🔌 T492 — the real capture on the dev box (v5.8.3)

tools/gate/stack_gate.sh --tag v5.8.3 --fresh --capture --report <dir>
# exit 0, wall 436 s (fresh leg ~70 s, capture 360 s); docker compose ls | grep gw-gate → empty afterwards
leg result detail
fresh ran/passed -
upgrade skipped (--upgrade not given) -
capture ran/passed -
chaos skipped (--chaos not given) -
measurement value
capture secs 360
target −16 LUFS
silence events 0
integrated −20.7 LUFS
booth_log rows (speech, last 7 min) 15

The integrated reading sits 4.7 LU under the target, inside the ±5 LU tolerance but close to it. The first minutes of a fresh station are mostly speech and the gate tones at −12/−30 average out under the target, so a longer capture on a fuller library should land nearer −16. Worth an ear on the next LLL rather than a tighter tolerance now.

🔍 Review rounds worth knowing

  • T490 round 1: a corrupt WAV exited 183 with nothing on stderr (set -e inside a $(ffmpeg …) capture). Fixed; exit 2 with ffmpeg's tail.
  • T491 round 1: if ! v=$(cmd); then s=$? reads the negated pipeline's 0, so a silence or loudness miss reported passed. Fixed to s=0; v=$(cmd) || s=$?, and the two new sad-path facts pin it.

✅ Gate

dotnet test GenWave.sln --filter "Category!=Integration" -- xUnit.MaxParallelThreads=3: all nine projects green (Host 2965 passed / 153 skipped; 0 failed). bash -n and shellcheck clean on all three scripts.

📝 Follow-ups

  • The booth_log window is the SPEC's fixed 7 minutes; a CAPTURE_SECS above 420 undercounts (commented in the script).
  • launch.sh prunes unused images older than 7 days on success; the gate triggers it. Stop a co-located station with docker compose stop, never down.

#777)

Renders the gate leg's synthesised library (tone-loud.mp3 -12 LUFS,
tone-quiet.mp3 -30 LUFS, sine + pink noise, loudnorm two-pass with an
ebur128 measure-and-verify pass) and commits two size-capped CC0 clips
with sourcing in SOURCES.md, per SPEC F178.3. Unpends the 7 T489 facts
in Story445_CapturedAudio.cs.
tools/gate/measure_audio.sh <wav> --target <lufs> counts silence events (silencedetect, SILENCE_FLOOR -45 dB, SILENCE_SECS 2 s) and compares ebur128 integrated loudness with the target within TOL_LU 5. Prints one summary line on stdout; exit 0 pass, 1 fail, 2 usage or unreadable input. Un-skips the six PendingMeasure facts in Story445, which drive the real script through GateHarness.
…#777)

After a passed fresh leg, --capture logs in to the api with the scratch's own admin password (read from its .env, never sourced, passed on stdin), reads Loudness:TargetLufs from GET /api/settings, records CAPTURE_SECS (default 360) of the stream with ffmpeg -reconnect 1, runs measure_audio.sh against it, and counts station.booth_log rows with a non-null segment_kind in the last 7 minutes. Every number lands in gate-report.md and .json (capture.target_lufs, silence_events, integrated_lufs, booth_log); a miss fails the leg naming the assertion: login, target, capture, silence, loudness, measure, booth_log. --capture without --fresh exits 2. The inline media synth now calls tools/gate/make_media.sh. FakeStation gains the real api's login cookie and settings array shape. Story445: the nine capture facts un-skipped plus two new sad-path scenarios (off-level, silent gap).
@genwave-radio
genwave-radio merged commit f3940c2 into main Sep 16, 2026
11 checks passed
@genwave-radio
genwave-radio deleted the ops/capture-leg-445 branch September 16, 2026 14:32
@github-actions github-actions Bot locked and limited conversation to collaborators Sep 16, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant