Add an STFT watermark path inspired by Kirovski & Malvar, including the frequency-domain embedder/decoder, FFT support, and round-trip coverage. Wire the generator and CLI tools to use the new analysis/synthesis flow for watermark experiments on the watermark-rework branch.
Prevent the fallback FFmpeg decoder from deadlocking on longer-running streams.
The decoder previously drained stderr with io.ReadAll() before reading PCM from stdout. Once FFmpeg filled the stdout pipe buffer, the process blocked on further stdout writes, never closed stderr, and io.ReadAll(stderr) never returned. That stalled the decoder before readPCM() could even start.
Drain stderr concurrently in its own goroutine so stdin, stdout, and stderr can all make progress in parallel. This matches the expected pipe handling model for long-running FFmpeg processes and keeps the fallback decoder usable for real streams.
Improve reliability in two critical paths:
- make config saves atomic by writing to a temp file in the target directory, syncing it, and renaming it into place so crashes cannot leave a half-written JSON config behind
- serialize runtime state transitions with a dedicated mutex so concurrent state updates from run() and writerLoop() cannot double-record transitions or increment counters twice
Also remove an unreachable nil-check after cloneFrame() to keep the engine loop honest and easier to reason about.
Address a set of production-facing edge cases discovered during bug hunting.
Included fixes:
- make FrameQueue close handling race-safe by replacing the TOCTOU close check with a dedicated close signal channel
- relax tone frequency validation when tone amplitude is zero, and default tone amplitude to 0 to avoid unintended test-tone output
- validate PI codes consistently whenever provided, and require a PI when RDS is enabled
- harden Icecast reconnect backoff against duration overflow
- prevent duplicate hard-reload goroutines from rapid repeated ingest-save requests
- clamp BS.412 power accumulation against negative float drift before sqrt to avoid NaN gain propagation
These changes focus on shutdown safety, config correctness, reconnect robustness, and long-running DSP stability.
Wire tone frequency, tone amplitude, and audio gain through the live control path so the UI's live-update behavior matches the engine.
This changes the generator live params to carry tone and gain values, propagates them through Engine.UpdateConfig and txBridge.UpdateConfig, and extends the control-plane patch types accordingly.
It also refines the control API behavior:
- avoid holding the server config mutex across tx.UpdateConfig()
- report live=true only when a request contains at least one genuinely live-applicable field
Together these fixes align the UI semantics with the actual runtime behavior and remove a lock hazard in the config update path.
rejectBody() returns true when the request body is acceptable and false when a body must be rejected. The TX and fault-reset handlers treated the return value the wrong way around and returned early on valid empty POST requests. This prevented actions like /tx/stop from running in the normal no-body case.
Update the handlers to only abort when rejectBody() reports an actual rejection, so empty POST control actions proceed as intended.