Recording, Composition & Streaming is a 14-part deep dive into the output half of GopherTrunk — everything that happens after the vocoders produce PCM. A decoded call has to become a crash-safe WAV on disk, a row in the call log, a live stream a browser can play, and an upload to the public call aggregators — and each of those is an independent, optional subsystem that reacts to a handful of bus events instead of calling its neighbours.
Where SDR Internals gave this whole area one survey episode, and Voice Coding covered the codec that produces the PCM, this series takes the subsystem/plumbing view: the event contracts, the session lifecycle, file-integrity guarantees, metadata and persistence, retention, the live-listen path, the fan-out manager, and the real API quirks of each aggregator backend. One thread runs the length of the series — the life of a single call, from the instant its PCM exists to the moment it lands in a Broadcastify feed.
Every post reads three ways: a TL;DR + cheat-sheet for skimmers, bold headers, tables, and diagrams for the medium read, and full prose with real code for the deep read.
New here? Start with the antenna-to-audio lesson for the last mile, then come back for the implementation.<ol class="post-list series-list"><li class="post-card"> <h2 class="post-card__title">Recording, Composition & Streaming, Part 1: The Output Half</h2> <p class="post-card__meta"> Deep dives </p><p class="post-card__desc">How GopherTrunk turns a decoded call into files, feeds, and uploads — four independent, config-gated subsystems that subscribe to a handful of bus events instead of calling each other, so recording, persistence, live audio, and streaming never block one another.</p></li><li class="post-card"> <h2 class="post-card__title">Recording, Composition & Streaming, Part 2: The Composition Contract</h2> <p class="post-card__meta"> Deep dives </p><p class="post-card__desc">How composed audio crosses from GopherTrunk’s demod composer into its recorder through three consumer-owned interfaces, why the recorder is the one place a call is decoded, and how the PCM path and the raw-vocoder-frame path differ.</p></li><li class="post-card"> <h2 class="post-card__title">Recording, Composition & Streaming, Part 3: Assembling a Call</h2> <p class="post-card__meta"> Deep dives </p><p class="post-card__desc">How GopherTrunk turns a burst of separate overs into one continuous call — hangtime, talkgroup gating and per-transmission splitting surfacing as segment events that roll the recorder’s files, from the output side rather than the DSP.</p></li><li class="post-card"> <h2 class="post-card__title">Recording, Composition & Streaming, Part 4: The Recording Session</h2> <p class="post-card__meta"> Deep dives </p><p class="post-card__desc">How GopherTrunk’s per-call recording session opens files lazily on the first write so dead-keys leave nothing on disk, carries identity forward through dormant post-segment parks, and runs decode-only when no recordings directory is configured.</p></li><li class="post-card"> <h2 class="post-card__title">Recording, Composition & Streaming, Part 5: WAV on Disk — Crash-Safety, the Tolerant Reader & Clean Cuts</h2> <p class="post-card__meta"> Deep dives </p><p class="post-card__desc">How GopherTrunk’s recorder turns decoded PCM into a WAV that survives a crash — placeholder-then-patch RIFF headers, a chunk-walking reader that clamps a truncated final chunk, an end-of-call fade tail, and dead-key suppression that removes empty files.</p></li><li class="post-card"> <h2 class="post-card__title">Recording, Composition & Streaming, Part 6: Segmentation, Naming & Raw Sidecars</h2> <p class="post-card__meta"> Deep dives </p><p class="post-card__desc">How a trunked call maps to the filesystem in GopherTrunk — the system/talkgroup directory scheme, timezone-aware timestamped filenames tagged with frequency, source and timeslot, per-transmission file rolls, and the .raw sidecar written for DMR, ProVoice and TETRA.</p></li><li class="post-card"> <h2 class="post-card__title">Recording, Composition & Streaming, Part 7: Correctness Guards — Cross-Call Fencing, Encryption Skip & Runtime Toggles</h2> <p class="post-card__meta"> Deep dives </p><p class="post-card__desc">The subtle safety machinery in GopherTrunk’s recorder and audio publisher — a CallID fence that stops a reused voice-tap serial bleeding one call’s tail into the next, mid-stream encryption abort-and-delete, grant backfill for late-resolving IDs, and runtime record on/off that never drops an in-flight call.</p></li><li class="post-card"> <h2 class="post-card__title">Recording, Composition & Streaming, Part 8: Loudness at the Output Stage</h2> <p class="post-card__meta"> Deep dives </p><p class="post-card__desc">Where and when GopherTrunk applies loudness leveling to a finished call — an atomic per-call rewrite of the on-disk WAV, an independent in-memory gain on the distributed MP3 copy, and an optional real-time AGC on the live browser stream — each a separate config switch, none re-deriving the BS.1770 math.</p></li><li class="post-card"> <h2 class="post-card__title">Recording, Composition & Streaming, Part 9: CallComplete — The Seam to Everything Downstream</h2> <p class="post-card__meta"> Deep dives </p><p class="post-card__desc">How GopherTrunk’s single CallComplete event decouples recording from streaming and persistence — what finalizeLocked builds, the identity and file metadata it carries, the per-call VoiceStats quality log emitted alongside it, and why the demod signal, EVM, and SNR figures ride the earlier CallEnd instead.</p></li><li class="post-card"> <h2 class="post-card__title">Recording, Composition & Streaming, Part 10: The Call Log — SQLite Persistence & Never-Downgrade Updates</h2> <p class="post-card__meta"> Deep dives </p><p class="post-card__desc">How GopherTrunk indexes every call into a CGO-free SQLite call_log by subscribing to the bus — an INSERT on start, an UPDATE on end keyed by device and timestamp, and a COALESCE/NULLIF discipline that backfills late RID and encryption without ever clobbering a value it already knew.</p></li><li class="post-card"> <h2 class="post-card__title">Recording, Composition & Streaming, Part 11: Retention & Housekeeping</h2> <p class="post-card__meta"> Deep dives </p><p class="post-card__desc">How GopherTrunk bounds disk and database growth with one idempotent background sweeper — call-row age, an allow-listed set of decoder-log tables, and mtime-based deletion of only .wav and .raw artifacts under an opt-in files root, each lane gated independently by config.</p></li><li class="post-card"> <h2 class="post-card__title">Recording, Composition & Streaming, Part 12: Live Listening — The Local Audio Path</h2> <p class="post-card__meta"> Deep dives </p><p class="post-card__desc">How GopherTrunk lets you hear a call the instant it decodes — a lock-free AudioPublisher fan-out, an open-ended WAV HTTP stream that even Safari will play, a gRPC StreamAudio path, and a host speaker player that deliberately refuses digital PCM to avoid an echo.</p></li><li class="post-card"> <h2 class="post-card__title">Recording, Composition & Streaming, Part 13: Outbound Streaming — The Broadcast Manager</h2> <p class="post-card__meta"> Deep dives </p><p class="post-card__desc">How GopherTrunk ships a finished call to the outside world — one bus subscriber that gates on stream/duration, encodes MP3 exactly once, and fans out through a bounded worker pool with drop-on-full and exponential-backoff retry so a wedged aggregator can never back-pressure the decode path.</p></li><li class="post-card"> <h2 class="post-card__title">Recording, Composition & Streaming, Part 14: The Aggregator Backends — Broadcastify, RdioScanner, OpenMHz, Icecast & Webhook</h2> <p class="post-card__meta"> Deep dives </p><p class="post-card__desc">A closing tour of every outbound destination’s real wire protocol — Broadcastify’s two-step metadata-then-PUT upload, the multipart POSTs for RdioScanner and OpenMHz, Icecast’s paced silence-topped source connection, and the generic JSON webhook — capped by the pure-Go, zero-CGO testing story that verifies them all.</p></li></ol><p class="blog-feed-link"> See all deep dives or subscribe via RSS. </p>