-
DMR End to End, Part 11: AMBE+2 Voice & the Silence-Frame Bug
How a frame-by-frame diff against two mbelib lineages refuted the 'AMBE+2 2450 high-band deficit' theory, found the real DMR voice bug in the silence frames that reset the gain predictor 24 dB low at every utterance onset, and calibrated the unvoiced band and radio tilt against dsd-neo — measured, not tuned by ear.
-
Beyond Voice, Part 11: LoRa — A Pure-Go Chirp Receiver
How GopherTrunk decodes LoRa without Semtech silicon — chirp spread spectrum as cyclic shifts, the dechirp-and-fold FFT that reads a symbol as a bin, preamble and SFD sync that separates carrier offset from timing, the Gray, interleave, Hamming and whitening chain, LDRO, and LoRaWAN MIC verification and decryption with operator-held keys.
-
DMR End to End, Part 10: Wideband DMR — Bin Edges & the Deaf Heal
Two IPSC repeaters on one X310 channelizer, one tap deaf for minutes at a time — how the polyphase bin edge folded half a DMR channel back on itself, why the fix was a 2x-oversampled channelizer, and how the wideband engine now heals a receiver deaf at its own decoding level and rejects a coarse offset decode evidence contradicts.
-
Beyond Voice, Part 10: DSC — Marine Digital Selective Calling
How GopherTrunk decodes DSC on marine channel 70 — 1200-baud FFSK at 1300/2100 Hz, ten-bit characters with a detect-only check code, the phasing hunt that locks a 20-bit DX grid, the format and category tables, MMSI and position codecs, and what stays unverified without a capture.
-
DMR End to End, Part 9: Direct Mode — The Gaps That Blinded the Receiver
Why a DMR handheld on a simplex frequency never decoded for the whole life of issue 836 — the 32.5 ms gaps between bursts fed receiver noise to every slow tracker — and the three fixes that make it decode from the first header: a variance carrier gate, feed-forward timing acquisition and an acquirer that keeps its lock.
-
Beyond Voice, Part 9: AIS — GMSK, NRZI & Bit Stuffing
How GopherTrunk decodes marine AIS in pure Go — a 9600-baud GMSK front end behind an FM discriminator, the NRZI line code where the bit is the transition, the HDLC framer borrowed from AX.25 with its bit-stuffing rule, six-bit ASCII, and the message types it really parses.
-
DMR End to End, Part 8: Tier III Trunking — C_ALOHA, Grants & LCNs
How GopherTrunk reads a DMR Tier III control channel — the 96-bit CSBK and the 0x5A5A CRC mask that once rejected every real one, locking on C_ALOHA, topology from C_BCAST, voice grants that carry a 12-bit LPCN instead of a frequency, vendor feature IDs, multi-block control, and the −56 dBFS WARN that contradicted live decodes.
-
Beyond Voice, Part 8: ADS-B — Mode S Squitters & CRC-24
How GopherTrunk decodes 1090 MHz aircraft transponders: a 2 Msps magnitude-domain preamble detector and PPM slicer, a CRC-24 that verifies extended squitters and doubles as the address on interrogation replies, the even/odd Compact Position Reporting pair, and a BEAST client that lets a running dump1090 feed the same aircraft table.
-
DMR End to End, Part 7: The Idle Beacon & the CSBK Train
What a keyed-but-idle IPSC repeater puts on the air, how GopherTrunk pinned the ETSI Idle burst against MMDVMHost's constant and turned it into a site-alive lock, how CSBK beacons are gated by CRC — and the cc=7 CSBK train on a cc=12 system that is still, honestly, unresolved.
-
Beyond Voice, Part 7: APRS, AX.25 & the Location Layer
How GopherTrunk turns 1200-baud Bell 202 tones into APRS positions: Mueller-Müller timing on the FFSK discriminator, NRZI, an HDLC framer that reverses bit-stuffing, an AX.25 parser with a reflected CRC-16, the Mic-E format that hides latitude in the destination address, and the location tables that feed the map.
-
DMR End to End, Part 6: Conventional IPSC — Two Slots as Two Calls
How GopherTrunk's conventional DMR state machine turned one IPSC repeater's two timeslots into two concurrent calls — a per-destination call map, a synthetic slot token the engine keys on, two same-carrier voice taps routed by embedded LC, and the re-key rule that recovered replies lost inside hangtime.
-
Beyond Voice, Part 6: POCSAG & FLEX — Paging Networks
How GopherTrunk decodes the two paging protocols that still carry fire, EMS and hospital dispatch: POCSAG's polarity-agnostic sync, BCH(31,21) codewords and frame-slot RIC arithmetic, the FLEX decoder that reuses the same BCH primitive bit-reversed for its one supported mode, and the wideband DDC bank that puts both on one dongle.
-
DMR End to End, Part 5: Link Control, Embedded LC & Late Entry
How a DMR call names itself three times over — the 72-bit Full Link Control in the Voice LC Header, the Terminator with LC, and the fragments embedded in voice bursts B–E — and how GopherTrunk turns the embedded copy into late entry, a re-key anchor, and a talker alias.
-
Beyond Voice, Part 5: Two-Tone Paging — Goertzel, Profiles & Tone-Out
How GopherTrunk's tone-out detector turns decoded PCM into fire-paging alerts: a block Goertzel resonator per configured tone, a per-device sequence matcher with duration, gap and cooldown gates, and the two places where this decoder deliberately skips the eleven-place pattern because it listens to audio, not RF.
-
DMR End to End, Part 4: The FEC Stack & the Forged Terminator
The error-correcting codes a DMR burst passes through in GopherTrunk — BPTC(196,96) with its stride-181 interleave, the (20,8,7) slot-type code, RS(12,9) with three context seeds, the two CRC masks whose convention hid a bug — and how a voice burst's AMBE bits once Golay-decoded to "Terminator with LC" and ended a live call mid-sentence.
-
Beyond Voice, Part 4: FleetSync — Cloning the Template & the WAV That Lied
How Kenwood FleetSync was cloned from the MDC1200 template — a preamble-plus-sync framer, a bit-serial block check, the FleetSync II single-error-correcting code — and how the reporter's two SDR# captures were decoded only after three traps fell: a WAV reader that read 32-bit float as int16, a baseband recording whose carrier sat 424.5 kHz off centre, and a carrier estimator that looked before the radio keyed up.
-
DMR End to End, Part 3: Two Slots, One Carrier — Repeater vs Simplex Cadence
How GopherTrunk follows a DMR voice superframe across six bursts when only the first carries a sync — the 132, 264 and 288-dibit cadences, embedded-LC and AMBE-FEC cadence detection, the relative Phase label separating two interleaved calls, and the simplex handheld whose empty slot a single-slot decoder sliced as voice.
-
Beyond Voice, Part 3: MDC1200 — The Template Decoder
The Motorola MDC1200 burst from sync word to web panel — the 40-bit sync hunted with five errors of tolerance and its complement for inverted polarity, the 16×7 column interleave, the reflected CRC-16, the opcode table, double packets, the receiver → bus → mdc1200_log → REST → panel chain later decoders were cloned from, and what its tests do not yet prove.
-
DMR End to End, Part 2: Bursts, Sync Words & Polarity
How GopherTrunk turns a raw DMR dibit stream into bursts — the 132-dibit layout with its central sync, the nine 48-bit sync words and the polarity flip that maps every data sync onto a voice sync, the Golay-protected slot type that routes each burst, and the one FEC-valid burst that fixes a stream's polarity.
-
Beyond Voice, Part 2: AFSK & FFSK — Two Tones, One Bit
How GopherTrunk turns an FM voice channel into a bit stream for MDC1200, FleetSync and APRS — the FM discriminator, the resample to 9600 Hz, demod.FFSK's mix-filter-discriminate tone detector, Mueller-Müller timing at eight samples per bit, and the slicer lesson that made FleetSync slice at a fixed zero while APRS tracks DC.
-
DMR End to End, Part 1: The 4FSK Carrier & the Tier Family
What a DMR carrier actually is — 4800 symbols per second of four-level FSK in a 12.5 kHz channel, dibits riding on ±648 and ±1944 Hz deviations, two 30 ms timeslots sharing one frequency, three tiers on one wire format, and the 48 kHz FM-discriminator chain GopherTrunk sizes around all of it.
-
Beyond Voice, Part 1: Why a Scanner Decodes Data — The Eleven-Place Pattern
Why a trunking scanner decodes in-band data at all, and the one wiring shape every non-voice decoder in GopherTrunk shares — front end, framer, bus event, SQLite log, REST route, web panel, config editors, doctor preflight and example config — eleven places and three policing tests, walked through FleetSync's landing.
-
P25 End to End, Part 14: The P25 Playbook
The finale — the whole P25 stack folded into one layer map from antenna to WAV: which symptom points at which layer, the instrument that confirms it, the failure signatures operators actually report, the twin-path ledger, and the honest list of what's still open.
-
From Spec to Shipping, Part 14: The Definition of Verified
The finale — closing an issue is a claim the problem is gone, and GopherTrunk defines exactly when that claim is earned. The issue-closing policy, the guard hook that asks a human before any close, Refs versus Closes, and the full shipping checklist from spec to verified.
-
P25 End to End, Part 13: Testing P25 Without a Tower
How GopherTrunk tests a P25 decoder with no transmitter in reach — literal byte vectors that catch what round-trips cannot, impairment-swept synthetic carriers with a hard CI loss budget, committed real-air capture fixtures, and rate-invariance tests that prove where a bug is not.
-
From Spec to Shipping, Part 13: Instruments, Not Logs
How GopherTrunk designs decoder diagnostics as instruments — counters on every branch including the failing ones, verdict lines written for operators, WARNs gated on persistence instead of one sampled blip, and health checks that never judge a channel by absolute dBFS.
-
P25 End to End, Part 12: The Weak-Signal Gap — P1 Voice's Missing Levers
Why P25 Phase 1 C4FM voice is GopherTrunk's least-equipped decode path — an FM-discriminator chain with no channel equalizer and hard-decision IMBE FEC, a measured ~24 dB gap to the coherent reference, and the weak-signal voice capture that gates the fix.
-
From Spec to Shipping, Part 12: Failing First — The Regression Rule
Why every GopherTrunk bug fix is one narrow commit plus a regression test that fails without the fix — with three worked examples where writing the failing test was the diagnosis, from the DMO colour-0 descramble to the SmartNet rebuild to the noise-grant trio.
-
P25 End to End, Part 11: Wideband P25 — Watching the Whole System at Once
One wide capture, many control channels — the tuner-bank architecture behind role wideband, per-call voice taps carved from the same IQ stream, and the twin down-converter paths (the wideband DDCBank vs the single-channel Downconverter) whose drift let the fix for issue 764 miss the replay symptom in issue 771.
-
From Spec to Shipping, Part 11: Capture-Driven Development
How GopherTrunk turns operator IQ recordings into first-class test fixtures — the samples/ conventions, .metadata.json sidecars whose bounds turn log lines into pass/fail gates, env-gated replay harnesses that skip in CI and become field instruments, and the discipline of baselining before fixing.
-
P25 End to End, Part 10: Sites, WACNs & Roaming a Multi-Site System
The P25 identity ladder — a 20-bit WACN, 12-bit System ID, RFSS and Site — and the status broadcasts that carry it in TSBK and multi-block AMBT forms. How GopherTrunk votes a system's identity out of noisy frames, accumulates a neighbour map, and roams by hunting control channels.
-
From Spec to Shipping, Part 10: The On-Air Gate — Green Synthetics Prove Nothing
Why every decoder claim in GopherTrunk carries a verification status — synthetic-green, capture-verified, or on-air-verified — and how the TETRA DMO "encrypted" misdiagnosis was overturned three times by three successive operator captures before the real causes surfaced.
-
P25 End to End, Part 9: Encryption Signalling — Flags, Metadata & Policy
Where a P25 call's encryption actually lives on the air — the grant's protected bit, the LDU2 Encryption Sync carrying the Message Indicator, Algorithm ID and Key ID, and Phase 2's MAC_PTT — and how GopherTrunk turns those signals into per-system tuner and recording policy.
-
From Spec to Shipping, Part 9: Wire Protocols Without Schemas
How GopherTrunk shipped a SoapyRemote client that asked for HAS_DC_OFFSET_MODE when it meant SET_ANTENNA — opcode 600 instead of 501 — and the rules that catch schemaless wire drift: pin constants against upstream literals, validate through introspection, read back what you set.
-
P25 End to End, Part 8: IMBE Voice — From LDU to WAV
How P25 Phase 1 voice travels — the 1728-bit LDU carrying nine IMBE frames woven between link-control words, the Golay and Hamming FEC that turns 144 channel bits into 88, and the composer chain in GopherTrunk that gates, decodes and records a call.
-
From Spec to Shipping, Part 8: Case Study — Rebuilding SmartNet From Proven Decoders
The full #1143 arc as a method case study: a Motorola SmartNet decoder green on every synthetic test yet unable to lock any real system, diagnosed as fabricated framing and rebuilt from OP25 and trunk-recorder — 8-bit sync 0xAC, stride-19 deinterleave, inverted data, CRC-10.
-
P25 End to End, Part 7: Phase 2 TDMA — Two Voices per Carrier
How P25 Phase 2 fits two voice calls in one 12.5 kHz channel — H-DQPSK at 6000 symbols per second, the 360 ms superframe and ISCH slot typing, MAC PDUs in place of TSBKs, and the FEC hand-off that silently defaulted to zero on one pipeline.
-
From Spec to Shipping, Part 7: Tests That Can Disagree With You
Four ways GopherTrunk engineers the self-consistent trap out of its test suites: fixture transmitters that behave like real radios, independent-path controls, fake servers that enforce the real protocol's strictness, and synthetic streams laid out on the real slot grid.
-
P25 End to End, Part 6: CQPSK & LSM — The Linear Twin Path
P25 Phase 1's second physical layer — the opt-in CQPSK/LSM demodulator GopherTrunk runs when an FM discriminator produces near-random dibits, its T/2 fractionally-spaced CMA equalizer and carrier-recovery chain, and the hard rule that simulcast does not imply LSM.
-
From Spec to Shipping, Part 6: When References Disagree, the Capture Referees
Two trusted decoders gave two different DMO burst geometries, and a single capture could not pin the colour-code bit offset at all. How GopherTrunk designs referee measurements where the right answer wins by a wide margin — and refuses to answer when it doesn't.
-
P25 End to End, Part 5: Channel Identifiers & Band Plans — From Channel Number to Hertz
How a P25 grant's 16-bit channel field becomes a frequency — the IDEN_UP band-plan broadcasts in their 0x3D, 0x34 and 0x33 dialects, the base-plus-spacing arithmetic in BandPlan.Frequency, and what happens when a grant arrives before the table that decodes it.
-
From Spec to Shipping, Part 5: Clean-Room Rules — Reading Without Copying
How a pure-Go, Apache-2.0 decoder uses GPL decoders as validation oracles without copying them: where the line sits between wire-format facts and copyrightable expression, the vocoder patent aisle, and the licensing hygiene that CI enforces on every build.
-
P25 End to End, Part 4: Multi-Block Trunking — The PDU That Isn't Noise
Why a PDU on a P25 control channel is Multi-Block Trunking, not garbage — the AMBT header and data blocks with their two different CRCs, the neighbour sites and WACN that only arrive this way, and why a "MBT data CRC failed" line next to a clean decode is two frames, not a contradiction.
-
From Spec to Shipping, Part 4: The Conformance Harness — Bit-Identical or Bust
Building a conformance harness against the ETSI EN 300 395-2 reference codec: one 137-bit bitstream into both decoders, zero PCM mismatches allowed, the LP64 Word32 gotcha that makes the reference itself lie, and why conformance at two layers brackets everything in between.
-
P25 End to End, Part 3: TSBKs — The Control Channel's Workhorse
Inside the P25 Trunking Signaling Block — 12 bytes through a 1/2-rate trellis and interleaver into 98 channel dibits, the CRC variant many references get wrong, the opcode families that run a trunked system, and the SCCB byte-offset bug a round-trip test blessed.
-
From Spec to Shipping, Part 3: Literal Vectors, Not Round-Trips
Why round-trip tests let parser bugs live — the P25 SCCB opcode that read channel B one byte early while its test stayed green — and the fix as method: pin every parser with literal byte vectors cross-checked against an independent decoder, kept as bytes in the test, never generated.
-
P25 End to End, Part 2: Frame Sync, the NID & What 'Locked' Means
How GopherTrunk turns a raw P25 dibit stream into frames — the 48-bit frame sync word, the BCH(63,16)-protected NID carrying NAC and DUID, the status symbols hiding every 36th dibit, and the tiered alignment search that decides when a control channel is really locked.
-
From Spec to Shipping, Part 2: Choosing Reference Implementations You Can Trust
The reference stable a protocol decoder is built against — OP25, trunk-recorder, SDRTrunk, osmo-tetra, the ETSI reference codec, mbelib — what each is authoritative for, why proven-on-air beats popular, and how to know what a reference does not decide.
-
P25 End to End, Part 1: C4FM & the Shape of a P25 Carrier
What a P25 Phase 1 carrier actually is — 4800 symbols per second of four-level FSK in a 12.5 kHz channel, dibits riding on ±600 and ±1800 Hz deviations, and the FM-discriminator → matched-filter → slicer chain GopherTrunk sizes around a 48 kHz channel rate.
-
From Spec to Shipping, Part 1: How to Read a Radio Standard
How ETSI EN and TIA-102 standards families are organized, the order a decoder author should read them — physical layer up to PDU layouts — and the constants to extract on day one: sync words, CRC definitions, and the exact bit offsets everything else leans on.
-
Weak-Signal Engineering, Part 14: The Weak-Signal Playbook
The finale — the whole method folded into one decision tree from symptom to lever: measure in-channel SNR and yield, classify the channel as ISI-limited, reliability-limited, fading-limited, or signal-limited, apply the matching lever, and hold every change to the failing-first, capture-A/B, yield-verdict discipline.
-
TETRA End to End, Part 14: Testing TETRA Without a Network — & What's Still Open
The finale — the four-layer test lattice that lets a TETRA stack be developed with no network in range, from synthetic round-trips through skip-guarded capture harnesses to full-daemon integration, and an unvarnished list of what is verified, what is staged, and what stays open until on-air evidence lands.
-
Weak-Signal Engineering, Part 13: The Odd Path Out — P25 Phase 1 C4FM
A diagnosis, not a fix — why P25 Phase 1 C4FM voice is the one decode path in GopherTrunk with neither an equalizer nor soft-decision FEC, what the Astro Spectra report showed, and the baseline harness waiting on the one thing that lets a fix land: a real weak-signal capture.
-
TETRA End to End, Part 13: DMO III — A Production Pipeline: Grid Votes, Grants & Noise
Wiring DMO into the daemon as a first-class protocol — streaming burst extraction, sticky locks, edge-triggered grants — and the first on-air run's humbling lesson: a loose correlator false-alarms eighteen times a second, and only a learned slot-grid vote separates a real transmission from an idle channel.
-
Weak-Signal Engineering, Part 12: Proving It's the Signal — Rate Invariance & Independent Resamplers
Experimental design as an engineering skill, with issue #764 as the worked example — how an independent 4:1 resampler proved a ten-dB decode deficit was baked into the captured samples, how clipping was ruled out, and how a unit test now pins the invariant the experiment relied on.
-
TETRA End to End, Part 12: DMO II — The 'Encrypted' Verdict That Was a Descramble Skip
The honest retelling of issue #1003: how a colour-0 descramble shortcut inherited from trunked mode made clear DMO voice look encrypted, why the synthetic round-trips couldn't catch it, and how the colour code the voice actually scrambles with turned out not to be the one the signalling advertises.
-
Weak-Signal Engineering, Part 11: Diversity II — Tracking Without Breaking the Differential
Why independent-PLL front ends make a frozen diversity calibration decay, and how TrackingCalibrator re-estimates the branch gain continuously yet stays safe ahead of a differential decoder — the anchored phase, the hold-don't-fallback rule, the step clamp, and the four-arm capture A/B.
-
TETRA End to End, Part 11: DMO I — Direct Mode & the DSB/DNB Geometry
TETRA without a network: how Direct Mode reuses the trunked physical layer with different burst layouts, how GopherTrunk derived the DSB and DNB block geometry from the spec's bit tables, and why one pair of offsets was confirmed by a sharp CRC optimum rather than trusted from another project.
-
Weak-Signal Engineering, Part 10: Diversity I — MRC & Coherence-Gated Calibration
Maximal-ratio combining from two receive branches, the wideband-scalar caveat that bounds what one complex gain can deliver, and the scale-invariant coherence gate — |rho| = γ/(1+γ), a noise floor you can compute, and the DC-removal detail that turns out to be load-bearing.
-
TETRA End to End, Part 10: The Control Channel Under Stress — Sync Loss & the CC Equalizer
Reading a one-hour field session's eleven hard control-channel sync losses: why the compute theory was wrong, how a signal-time resync budget survives CPU starvation, and the one TETRA control path that wasn't running the equalizer — lifting a marginal capture from twelve percent to full BSCH yield.
-
Weak-Signal Engineering, Part 9: Parallel Buffers — SymbolSink, SoftSink & Opt-In Soft Paths
The architecture pattern that let equalizers and soft-decision FEC land safely — LLR differentials and raw symbols carried strictly parallel to the hard dibit buffer, a stash bridge keyed by base index, and a byte-identical opt-out pinned by failing-first tests.
-
TETRA End to End, Part 9: The Equalizer on the Voice Path
How a blind CMA equalizer with frozen-snapshot taps, inserted between symbol timing and the differential decoder, roughly doubled CRC-valid TCH/S yield on real concurrent-load TETRA captures — and why the naive way of wiring it drives the decode to exactly zero.
-
Weak-Signal Engineering, Part 8: Soft Decisions — LLRs Through Depuncture & Viterbi
Why hard-slicing a marginal symbol throws away the information the FEC needs most, how GopherTrunk carries per-bit log-likelihood ratios through descramble, deinterleave, depuncture, and a correlation-metric Viterbi, and the measured TETRA outcome that justified the whole soft path.
-
TETRA End to End, Part 8: Going Soft — Soft-Decision TCH/S
Why hard-decision decoding threw away roughly seventy percent of a marginal same-carrier TETRA call's traffic bursts, and how the soft path — receiver differentials carried in lockstep through the traffic extractor into a soft Viterbi — recovers them without touching the hard contract.
-
Weak-Signal Engineering, Part 7: Trained Equalization — LMS on the Midamble
When the burst carries known symbols, stop guessing — how GopherTrunk trains a SnapshotLMS on each TETRA burst's midamble in the raw-symbol domain, freezes the taps, equalizes the payload with a FIR warm-up, and re-derives the soft LLRs, taking a synthetic multipath burst from 13% payload bit-error to zero while staying byte-identical when switched off.
-
TETRA End to End, Part 7: Conformance — Bit-Identical Against the ETSI Reference
How GopherTrunk proves its TETRA voice path — two independent conformance passes against the ETSI EN 300 395-2 reference codec, one demanding sample-for-sample PCM equality on a shared bitstream and one replaying real IQ through the whole chain, plus the 64-bit Word32 build trap that makes the reference tools lie.
-
Weak-Signal Engineering, Part 6: Normalisation & Divergence Guards
The unglamorous half of a working blind equalizer — why the CMA update's cubic scaling in input amplitude makes normalisation part of the algorithm, how an EMA tracking a TDMA downlink's slot-to-slot power swings turned the full equalizer win into CRC zero, and why the divergence guard that re-seeds blown-up taps deserves a test of its own.
-
TETRA End to End, Part 6: A Clean-Room ACELP Vocoder in Pure Go
Inside GopherTrunk's pure-Go TETRA speech decoder — how a 137-bit ACELP frame becomes 240 samples of 8 kHz audio through LSP dequantisation, adaptive and algebraic codebooks, and a bit-exact fixed-point synthesis filter, and what clean-room means when the target is sample-for-sample agreement with a reference codec.
-
Weak-Signal Engineering, Part 5: The Snapshot Trick — Frozen Taps & Differential Decoders
Why a continuously-adapting CMA in front of a differential decoder scores exactly zero CRC-valid frames, why permanently frozen taps only ever match the baseline, and how SnapshotCMA's adapt-continuously-apply-frozen design resolves the dilemma — doubling TETRA voice yield and lifting the thread capture from twelve percent to one hundred.
-
TETRA End to End, Part 5: TCH/S — From Traffic Burst to Speech Frame
Decoding TETRA's full-rate speech channel — two 137-bit frames per slot, the class-0/1/2 sensitivity split with its unequal error protection, the AACH usage marker that routes concurrent same-carrier calls, and the replay harness that correlated decoded timeslots against the control channel's grants on real air.
-
Weak-Signal Engineering, Part 4: Blind Equalization — CMA From First Principles
The Constant Modulus Algorithm built from its cost function up — how the Godard gradient becomes a five-line tap update in GopherTrunk's cma.go, why constant-envelope PSK is the perfect prey, why the algorithm needs a noise floor to be well-posed, and where the spurious minima behind Part 2's EVM trap come from.
-
TETRA End to End, Part 4: Scrambling & Colour Codes — Why Colour 0 Is Not a No-Op
Inside TETRA's scrambler — the 32-tap LFSR, the 30-bit extended colour code that seeds it, why colour 0 still produces a real scrambling sequence, and the two seed bugs that let a control channel lock perfectly while decoding nothing: a story that sets up the DMO 'encryption' misdiagnosis.
-
Weak-Signal Engineering, Part 3: ISI & the Linear Channel — What an Equalizer Can & Can't Fix
The convolution model y = h∗x + n that every equalizer in this series inverts — what multipath and band-edge group delay do to π/4-DQPSK and C4FM symbols, which impairments are linear and therefore recoverable, which are not, and the raw-symbol-domain fact that dictates the architecture of Parts 5 and 7.
-
TETRA End to End, Part 3: Channel Coding — RCPC, Viterbi & the CRC That Isn't an LFSR
Walking TETRA's type-5 bits back to information — two K=5 RCPC mother codes, depuncturing into a shared 16-state Viterbi, the block interleavers, and the class-2 speech CRC that is a fixed parity-check matrix, whose LFSR misimplementation silently dropped every on-air voice burst while synthetic round-trips stayed green.
-
Weak-Signal Engineering, Part 2: Metrics That Lie — EVM vs CRC Yield
Why error vector magnitude, wideband carrier SNR, and constellation beauty can all improve while a decoder recovers nothing — the equalizer whose EVM collapsed 34% to 8% with CRC stuck at zero, the
-
TETRA End to End, Part 2: The Burst Zoo & the Slot Grid
How GopherTrunk finds structure in the 18000-dibit-per-second TETRA firehose — the synchronisation and normal downlink bursts, the three training sequences that mark them, the 255-dibit slot grid, and the one-slot anchor shift that silently misfiles every traffic burst when you get it wrong.
-
Weak-Signal Engineering, Part 1: The Marginal Regime
Between full quieting and no signal lies the marginal regime — where a receiver locks but decodes only a fraction of what it hears — and this opener defines that regime with a real TETRA capture, previews the four levers that roughly doubled GopherTrunk's yield there, and sets the one verdict rule the whole series lives by.
-
TETRA End to End, Part 1: π/4-DQPSK & the Shape of a TETRA Carrier
Why TETRA is not "European P25" — a 25 kHz carrier at 18000 symbols per second, dibits riding in phase transitions instead of amplitudes, a four-slot TDMA downlink that never stops transmitting, and the 144 kHz channel rate GopherTrunk's whole TETRA path is sized around.
-
The Hunt, Part 14: The Hunt Cockpit & Testing Discovery Without Radios
The finale — how the interactive hunt cockpit drives the whole sweep-identify-map-export pipeline live over REST from a browser or terminal, and how the entire discovery path is tested end to end with synthesized IQ and canned sources so a find is proven reproducible with no radio attached.
-
Running It For Real, Part 14: Staying Up — Health, Watchdogs & the Ops Payoff
The finale — the health endpoint that tells a probe the daemon is doing work not just running, the heartbeat that makes a stop never silent, the soft memory limit that dodges the OOM killer, and the USB watchdog that reacquires a dropped dongle without a restart.
-
The Operator's Cockpit, Part 14: Testing Browser & Terminal UIs
The finale — how GopherTrunk tests a browser and a terminal operator UI without a live radio, standing the whole cockpit up in memory — httptest API contract tests, a byte-for-byte WAV header pinned on both the Go server and the TypeScript client, Bubbletea model tests that feed synthetic messages into the reducer, and pure SSE-parser and audio-framer unit tests.
-
The Hunt, Part 13: Exporting Your Finds — RadioReference, TrunkRecorder, SigMF
How GopherTrunk exports a discovered system in every format that matters — a round-tripping CSV import bundle, a TrunkRecorder JSON stanza, a RadioReference submission package with a live cross-reference diff, and a SigMF-tagged capture bundle — one DiscoveredSystem, one Write call, many destinations.
-
Running It For Real, Part 13: systemd Hardening & the Windows Installer
A hardened systemd unit that sandboxes the daemon down to one USB device and a state directory, and the Inno Setup Windows installer that separates program from data, seeds a config, bundles the Zadig driver, and preserves your captures on uninstall.
-
The Operator's Cockpit, Part 13: The Reflect-Driven Config Form
How one reflection walk over config.Config plus one shared field-metadata registry generates the whole config editor for both the terminal Bubbletea form and the web Config Builder — the TUI reading the registry directly, the web reading it over a fieldmeta endpoint, and two drift-guard tests keeping both editors honest to the struct.
-
The Hunt, Part 12: Alias Harvesting — Following Traffic for Talker Aliases
How GopherTrunk harvests P25 Phase 2 talker aliases off a traffic channel's signalling stream without recording voice — a signalling-only DDC tap per in-window grant, the shared MAC dispatcher the voice composer also uses, RS-parity gating, and harvesting the Motorola alias ciphertext for cryptanalysis.
-
Running It For Real, Part 12: Docker & RTL-SDR USB Pass-Through
Containerizing the pure-Go daemon and passing a real RTL-SDR through the container boundary — the multi-stage zero-CGO build, the three things a dongle needs inside a container (host udev, DVB blacklist, device mapping), and why least-privilege beats --privileged.
-
The Operator's Cockpit, Part 12: The TUI Cockpit — The Same API in Your Terminal
How GopherTrunk's Bubbletea TUI drives the identical REST + SSE API as the browser — an Elm-model root reducer fanning out polling commands plus a long-lived SSE pump into one SharedState, thirteen panels as pure renderers over it, and a tab strip that hides the same web.tabs panels the web SPA does.
-
The Hunt, Part 11: Naming the Unknown
How GopherTrunk turns a blind discovery's bare numbers into names — a synthesized system handle from the protocol and identity, a Name/Service/Purpose triple from the frequency allocation and reference catalog, and the honest line between what a decoder can name and what only an operator can.
-
Running It For Real, Part 11: Grant Webhooks & External Integrations
Firing an HTTP webhook the instant a control-channel grant is decoded — the push form of GET /api/v1/grants for home automation, dashboards, and alerting, with a bounded queue that drops before it ever stalls the decoder, a shared JSON schema, and per-grant retry/backoff.
-
Recording, Composition & Streaming, Part 14: The Aggregator Backends — Broadcastify, RdioScanner, OpenMHz, Icecast & Webhook
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.
-
The Operator's Cockpit, Part 11: The Mobile Shell, Command Palette & PWA
How one React shell serves a desktop sidebar and a phone's bottom nav from a single nav registry, adds a ⌘K command palette as the fastest path to any panel, and installs as an offline-first PWA on a phone — with a service-worker denylist so the console never shadows its sibling apps.
-
The Hunt, Part 10: Offline vs Live Surveys — Hunting a Recording
How GopherTrunk replays a recorded IQ capture through the exact same sweep, classify, identify, and accumulate pipeline it runs on the air — offline carrier detection with signed offsets, a shared per-candidate routing body, and why a find reproduces from a .cfile byte-for-byte.
-
Running It For Real, Part 10: Broadcast Backends II — RdioScanner, OpenMHz, Icecast
Operating the other three call destinations on a 24/7 daemon — RdioScanner and OpenMHz credentials and endpoints riding the shared retry loop, and why Icecast is the odd one out that drops instead of retrying, reconnects on its own, and needs a source password kept off disk.
-
Recording, Composition & Streaming, Part 13: Outbound Streaming — The Broadcast Manager
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.
-
The Operator's Cockpit, Part 10: Write Mode — Mutating Config Safely
How GopherTrunk lets a browser or terminal change a running daemon without lying about what took effect — the mutation-capability gate, pointer-field PATCH semantics that preserve leave-alone, the hot-reload vs restart-required split, an mtime conflict guard, and the validate-then-activate config swap.
-
The Hunt, Part 9: Wideband Multi-Site P25 — Watching Many Channels at Once
How GopherTrunk pins one wideband SDR to a band centre, splits its IQ into a narrowband stream per control channel with a tuner bank, and runs a per-channel P25 (or DMR) state machine on each — so a whole multi-site system's control channels are watched simultaneously off a single dongle.
-
Running It For Real, Part 9: Broadcast Backends I — Broadcastify
Operating a Broadcastify Calls feed on a 24/7 daemon — where the API key and system ID live, how the two-leg upload retries with exponential backoff, how a rejection surfaces, and the counters that tell you a feed went quiet before your listeners do.
-
Recording, Composition & Streaming, Part 12: Live Listening — The Local Audio Path
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.
-
The Operator's Cockpit, Part 9: The Map — Plotting Sites & Emitters
How GopherTrunk plots P25 sites and position-bearing emitters — APRS, AIS, ADS-B, DSC — on one shared Leaflet map, patching markers in place from the daemon's locations and sites REST endpoints as fixes arrive, with an auto-fitting camera and a merged discovered-plus-configured site table.
-
The Hunt, Part 8: DMR LCN Correlation — Rebuilding a Channel Map
How GopherTrunk learns a DMR Tier III band plan that is never broadcast — correlating granted logical channel numbers to the RF carriers that key up in response, confirming each pairing by decoding a DMR sync, and fitting a base-plus-spacing grid it hot-swaps into the live control channel.
-
Running It For Real, Part 8: The Opt-In Feature Matrix
How GopherTrunk decides what runs by default versus what stays off until you ask — the three kinds of feature gate, why headless-safe defaults matter, and the one config rule that keeps optional subsystems from surprising a 24/7 daemon.
-
Recording, Composition & Streaming, Part 11: Retention & Housekeeping
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.
-
The Operator's Cockpit, Part 8: Constellation, Eye Diagram & Symbol Scope
How one WebSocket symbol stream off the daemon feeds three browser DSP scopes — a complex constellation, a folded C4FM eye diagram, and a rolling symbol oscilloscope — with server-side offset tuning to dodge the DC spike and a soft-vs-dibit frame shape shared across all three canvases.
-
The Hunt, Part 7: Locking a P25 System — Candidate to Confirmed
How GopherTrunk turns a locked P25 control channel into a confirmed DiscoveredSystem — folding WACN and System ID into an identity, recovering the IDEN_UP band plan, resolving neighbour frequencies, and honestly reporting when the NSB that carries the WACN never decoded.
-
Running It For Real, Part 7: SDR Doctor & Preflight
How GopherTrunk catches a bad dongle, wrong USB driver, or broken config before it costs a call — the read-only sdr doctor that inspects per-device driver binding, the preflight that validates directories and TLS files before the listener binds, and the gain-sanity warnings.
-
Recording, Composition & Streaming, Part 10: The Call Log — SQLite Persistence & Never-Downgrade Updates
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.
-
The Operator's Cockpit, Part 7: Live Spectrum & Waterfall in the Browser
How GopherTrunk streams FFT frames from the SDR broker over a WebSocket and paints a scrolling waterfall plus analyzer trace on a canvas — a per-request FFT producer, a rate-clamped frame protocol, ImageData row rendering, and an FFT-shifted x-to-frequency mapping that powers hover readout and click-to-tune.
-
The Hunt, Part 6: Control-Channel Hunting — The Supervisor
How GopherTrunk's cchunt supervisor multiplexes per-system control-channel hunts over one shared SDR, dwelling on candidates, parking on locks, backing off failures exponentially, and publishing HuntProgress so the cockpit can watch every system at once.
-
Running It For Real, Part 6: The Diagnostics Reporter
How GopherTrunk prepends a host/SDR snapshot to every error surface — the boxed boot banner, the memoized Collector that enumerates dongles at most once, the cheap-versus-costly split in SysInfo, the full error-chain verbose trace, and the non-TTY-safe reporter.
-
Recording, Composition & Streaming, Part 9: CallComplete — The Seam to Everything Downstream
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.
-
The Operator's Cockpit, Part 6: Client-Side Resampling
How GopherTrunk plays 8 kHz voice on a 48 kHz browser AudioContext — a windowed-sinc polyphase resampler that carries a history tail and fractional cursor across network chunks, per-phase unity-gain normalized, band-limiting the images that linear interpolation leaks, with a split-feed-equals-whole-feed test as its keystone.
-
The Hunt, Part 5: Autogain & Autotune — Settling the Front End
How GopherTrunk sweeps front-end gain to minimize the decode error rate before committing to a control channel, and how a running-average autotune corrects each dongle's crystal offset so a decoder's carrier loop starts already near lock.
-
Running It For Real, Part 5: Structured Logs — Event, Message & Power
How GopherTrunk writes three purpose-built logs off one event bus — the JSONL event log for machines, the human-readable decoded-message log, and the decode-gated power log — plus the panic-recovery guard that turns a crashing goroutine into a logged, survivable event.
-
Recording, Composition & Streaming, Part 8: Loudness at the Output Stage
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.
-
The Operator's Cockpit, Part 5: The Live Audio Cockpit
How GopherTrunk streams live decoded voice to the browser — a publisher that fans PCM to bounded per-subscriber channels, a continuous open-ended WAV body served over fetch so the auth header rides along, and an AudioWorklet ring buffer that emits silence on underrun for gapless playback.
-
The Hunt, Part 4: Classifying a Signal — Analog, Digital, Encrypted
How GopherTrunk cheaply classifies a bare carrier into analog, digital, paging, or encrypted using envelope, discriminator, and cyclostationary features before spending an expensive protocol identify, and how the encryption triage tells structured traffic from random.
-
Running It For Real, Part 4: Metrics That Matter — Prometheus & SDR Tiles
How GopherTrunk's Prometheus registry is built — bus-driven counters versus scrape-time snapshot collectors, the SDR tiles worth alerting on (IQ power, clip ratio, gain, lock state), why calls_started beats calls_total, and the opt-in FEC histograms.
-
Recording, Composition & Streaming, Part 7: Correctness Guards — Cross-Call Fencing, Encryption Skip & Runtime Toggles
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.
-
The Operator's Cockpit, Part 4: The Event Stream — SSE to React
How GopherTrunk fans every internal bus event to clients as one JSON envelope — Server-Sent Events for the terminal and curl, a WebSocket twin for the browser that can't set an auth header — with jittered reconnect backoff, frame coalescing, and interval poll hooks that only re-render on real change.
-
The Hunt, Part 3: Peaks & Occupancy — Finding Carriers in the Noise
How GopherTrunk estimates a robust noise floor, picks true carrier peaks without triple-counting one emitter, snaps them onto the land-mobile channel raster, and separates narrow carriers from wide OFDM plateaus in one spectrum frame.
-
Running It For Real, Part 3: TLS & Sitting Behind a Reverse Proxy
How GopherTrunk terminates TLS on its HTTP and gRPC listeners, why plain TCP stays the default, the both-or-neither cert/key rule, the permissive-by-default CORS allow-list, and the loopback-upstream reverse-proxy pattern with nginx or Caddy.
-
Recording, Composition & Streaming, Part 6: Segmentation, Naming & Raw Sidecars
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.
-
The Operator's Cockpit, Part 3: The Connect Screen & Auth Handshake
How a browser pairs with a GopherTrunk daemon — the connect screen that probes health before saving, the three-mode bearer-token policy with loopback trust, the mutation gate, and the capability probe that lets a front-end grey out write controls before it ever tries them.
-
The Hunt, Part 2: The Wideband Sweep Engine
How GopherTrunk steps a wide SDR receiver across megahertz of spectrum, estimates a power spectrum from short IQ grabs, and stitches overlapping tiles into one occupancy picture without dropping a carrier at a step boundary.
-
Running It For Real, Part 2: Auth Posture — Closed-LAN, Auto, Required
How GopherTrunk's mutation-endpoint auth works — the disabled/auto/required modes, why the default flipped to disabled for closed-LAN deployments, the loopback and trusted-network bypasses, constant-time token comparison, and hot token rotation without a restart.
-
Recording, Composition & Streaming, Part 5: WAV on Disk — Crash-Safety, the Tolerant Reader & Clean Cuts
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.
-
The Operator's Cockpit, Part 2: A React SPA Inside a Go Binary
How GopherTrunk embeds its built Vite/React bundle into the daemon with go:embed all:dist, serves it at the root with a client-side-routing SPA fallback, degrades to a helpful message when the embed is empty, and stays a plain API client so browser and standalone deployments are identical.
-
The Hunt, Part 1: What Discovery Means — From a Blank Band to a Known System
How GopherTrunk turns an empty stretch of spectrum into a named, mapped trunked system — the sweep, identify, and map pipeline behind the hunt engine, and the one discovery contract that the CLI, the daemon, and the web cockpit all drive.
-
Running It For Real, Part 1: From a Laptop Demo to a 24/7 Service
What actually changes when GopherTrunk stops being a laptop demo and becomes an unattended 24/7 service — the daemon lifecycle, dependency-ordered construction and teardown, graceful shutdown, the supervised spawn model, and the operator mindset behind the whole series.
-
Recording, Composition & Streaming, Part 4: The Recording Session
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.
-
The Operator's Cockpit, Part 1: One Engine, Many Front-Ends
How one running GopherTrunk daemon exposes a single REST + Server-Sent-Events contract that both a React SPA in the browser and a Bubbletea terminal TUI render, kept honest by an api package that talks only to interfaces and never imports the SDR pool.
-
Recording, Composition & Streaming, Part 3: Assembling a Call
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.
-
Recording, Composition & Streaming, Part 2: The Composition Contract
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.
-
Recording, Composition & Streaming, Part 1: The Output Half
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.
-
Voice Coding, Part 12: Calibrating & Testing Vocoders Against References
How GopherTrunk proves its pure-Go IMBE and AMBE+2 decoders are correct — the voice-calibrate harness, golden-frame regression, streaming decode, and cross-correlation against DSD-FME and OP25.
-
Trunking Engine, Part 12: Control-Channel Hunting, the Call Watchdog & Testing With a Fake Bus
How GopherTrunk finds a control channel and backs off when it can't, how a 500 ms watchdog reaps calls that go silent, and how the whole engine is tested by publishing synthetic grants to a fake bus with no radio attached.
-
Protocol Decoders, Part 12: Testing Decoders Without Radios
How GopherTrunk tests every protocol decoder with no SDR and no live system — synthesized control channels modulated through the real TX chain, a decoder registry, golden fixtures, and the regression discipline that makes 'verified' mean something.
-
Voice Coding, Part 11: Recording & Encoding — WAV, MP3, Tone-Out & Hardware
How GopherTrunk writes crash-safe WAVs, encodes MP3 in pure Go with a Xing/LAME header, detects paging tones with Goertzel, and offers an optional DVSI hardware vocoder behind a build tag.
-
Trunking Engine, Part 11: Encrypted-Mode Handling — Follow, Metadata, or Ignore
How GopherTrunk decides what to do with an encrypted trunked call — hold the tuner, grab metadata and release, or ignore it — with a per-system policy, a configured-key exemption, and a metadata-follow window that frees scarce voice SDRs.
-
Protocol Decoders, Part 11: The Alias Hunt II — The Keystream, Dead Ends & Ethics
The clean-room cryptanalysis of the Motorola P25 talker-alias cipher — the per-character affine keystream, every ruled-out hypothesis, the low-byte observability floor that keeps it uncracked, and the ethics that gate it all.
-
Voice Coding, Part 10: Enhancement & Loudness — Making It Sound Right
The post-synthesis audio chain in GopherTrunk's MBE decoders — spectral enhancement, DC blocking, AGC, tone tilt, an opt-in voice enhancer, and per-call EBU R128 loudness normalization.
-
Trunking Engine, Part 10: Sites, Topology & Multi-Site Roaming
How GopherTrunk accumulates a P25 system's site map from control-channel status broadcasts, tracks a radio roaming between sites, and renders an SDRtrunk-style network configuration report — all derived from the event stream.
-
Protocol Decoders, Part 10: The Alias Hunt I — Framing the Unsolved Cipher
The Motorola P25 talker-alias field carries an encrypted display name. Part 1 of the capstone frames it correctly — the SUID header, the critical strip-the-CRC lesson, the verified per-byte decode model, and the recovered 256-entry LUT.
-
Voice Coding, Part 9: The Composer — Wiring Frames to the Right Vocoder
How GopherTrunk's composer turns a CallStart event into PCM — per-protocol demod chains, front-end decimation, boundary handling, and how the recorder maps each protocol to its vocoder.
-
Trunking Engine, Part 9: Patches, Supergroups & Physical-Channel RID Recovery
How GopherTrunk tracks P25 patches and dynamic-regroup supergroups, and how folding a source RID by physical channel recovers the transmitter and kills a phantom duplicate call from a mis-aliased compressed grant.
-
Protocol Decoders, Part 9: Conventional, Wideband & the Symbol Scope
How GopherTrunk decodes what isn't a trunked control channel — fixed-frequency conventional scanning with tone squelch, wideband multi-carrier Phase 2, DMR LCN band-plan learning, and the symbol-scope diagnostic.
-
Voice Coding, Part 8: AMBE+2 FEC & the Knox Path
Where AMBE+2 error correction actually lives, how GopherTrunk conceals bad frames with replay and adaptive smoothing, and what the vendor-specific knox tone path really is — and isn't.
-
Trunking Engine, Part 8: Affiliation Tracking — Building the Roster From the Event Stream
How GopherTrunk builds a live "which radio is on which talkgroup" roster entirely from the trunking event stream — grants, affiliation responses, and unit registrations — with no extra radio I/O.
-
Protocol Decoders, Part 8: The Legacy Family — EDACS, LTR & MPT-1327
How GopherTrunk follows the pre-digital trunking systems — EDACS 9600-baud CCWs, LTR's central-control-free per-repeater status words, and MPT-1327 FFSK codewords — and why they decode nothing like a modern digital control channel.
-
Voice Coding, Part 7: AMBE+2 — One Decoder, Two Rates
How GopherTrunk's pure-Go AMBE+2 decoder unpacks 49 information bits into shared MBE parameters, and why P25 Phase 2, DMR, and NXDN all run through one Go decoder with two rate tables.
-
Trunking Engine, Part 7: Recovering the Source Radio ID (Issue #915)
A current-work deep dive into how GopherTrunk backfills the source radio ID onto calls that bound from a source-less P25 Phase 2 grant, republishing it through the source-update path so nearly every call reports who keyed up.
-
Protocol Decoders, Part 7: TETRA — π/4-DQPSK, Channel Coding & the Sub-Target-Rate Fix
How GopherTrunk decodes TETRA — π/4-DQPSK framing, the type-1 to type-5 channel-coding chain, CMCE grant PDUs, and the recent fix that interpolates a narrowband capture up to the 144 kHz channel rate so the receiver locks.
-
Voice Coding, Part 6: The Call-Startup Acquisition Squelch
A real bug in GopherTrunk — fresh P25 recordings opened with a full-scale noise burst while the receiver acquired lock, because the FEC resolved marginal dibits to valid-but-wrong IMBE frames. Here's the heuristic that mutes it.
-
Trunking Engine, Part 6: Talkgroups, Aliases & Scan Modes
How GopherTrunk's talkgroup database gives numeric IDs names and policy, how hold and lockout gate a call, and how the engine flips between scan-all and scan-list at runtime from the cockpit without a restart.
-
Protocol Decoders, Part 6: NXDN & dPMR — The Narrowband FDMA Family
How GopherTrunk decodes NXDN and dPMR — the 6.25 kHz FDMA narrowband modes — their 80 ms frames, LICH and CAC signalling, CSBK trunking blocks, and convolutional FEC, and how they contrast with the TDMA modes.
-
Voice Coding, Part 5: IMBE FEC & De-Interleave — Recovering the Bit Soup
How GopherTrunk turns 144 noisy on-air IMBE channel bits into 88 clean information bits — the Golay and Hamming FEC on the eight u-vectors, the channel layout, and why de-interleave must run before descramble.
-
Trunking Engine, Part 5: Priority & Preemption When Calls Outnumber Radios
How GopherTrunk decides which call to drop when active calls exceed voice SDRs — the priority ranking, strict-higher preemption rule, emergency override, and the thrash and starvation it is tuned to avoid.
-
Protocol Decoders, Part 5: DMR Tier 2/3 — Bursts, EMB & FLC
How GopherTrunk decodes DMR's two-slot TDMA — the 132-dibit burst, the sync words closed under polarity flip, the slot-type Hamming code, embedded signalling and Full Link Control, and the reverse channel.
-
Voice Coding, Part 4: IMBE — Decoding P25 Phase 1 Voice
How GopherTrunk turns 88 IMBE information bits into MBE parameters — the b_0 pitch codeword, the PRBS scrambler that whitens the spectrum, and the table-driven PRBA and DCT quantization that reconstruct the spectral envelope.
-
Trunking Engine, Part 4: The Voice Pool — Allocating Scarce Radios
How GopherTrunk's voice pool allocates a handful of Voice-role SDRs across many talkgroups, binds a call, retunes for handoffs, and detects when a wideband rig's tuning window doesn't cover the granted repeater.
-
Protocol Decoders, Part 4: P25 Phase 2 TDMA — The MAC Layer
How GopherTrunk decodes P25 Phase 2's two-slot TDMA — the ISCH slot-type field, the 360 ms superframe, the MAC PDU layer, and the compressed source-less grant that forces a source-RID backfill downstream.
-
Voice Coding, Part 3: MBE Synthesis — Voiced Sinusoids + Unvoiced Noise
How GopherTrunk grows 160 samples of speech from an MBE parameter set — summed sinusoids for the voiced bands, FFT-shaped noise for the unvoiced bands, and the cross-frame phase and overlap-add smoothing that kills clicks.
-
Trunking Engine, Part 3: Grants — The Engine's Only Input
A field-by-field tour of GopherTrunk's protocol-agnostic Grant struct, source-bearing versus source-less grants, and the duplicate-grant guard that stops a repeated control-channel TSBK from binding a second radio.
-
Protocol Decoders, Part 3: P25 Phase 1 TSBKs & Link Control
How GopherTrunk parses P25 Phase 1 Trunking Signaling Blocks — the CRC-checked 12-byte TSBK, its OSP opcode taxonomy, vendor Motorola extensions, TDULC and Link Control — and turns a grant PDU into the Grant the trunking engine records.
-
Voice Coding, Part 2: The MBE Model — Pitch, Voicing & Spectral Envelope
The Multi-Band Excitation parameter set at the heart of IMBE and AMBE+2 — fundamental frequency, per-band voiced/unvoiced decisions, and spectral amplitudes, as the Go structs both codecs synthesize from.
-
Trunking Engine, Part 2: The Event Bus — Typed Pub/Sub That Decouples Everything
How GopherTrunk's in-process event bus fans typed events from the trunking engine to every subscriber, with async delivery and overflow protection so one slow consumer can never stall the decode path.
-
Protocol Decoders, Part 2: P25 Phase 1 — NID, Sync & the FEC That Gates Lock
A field-level tour of P25 Phase 1's physical layer in GopherTrunk — the 48-bit frame sync, the BCH-protected NID with its NAC and DUID, C4FM 4-level symbols, and the trellis plus Hamming FEC that decide whether a control channel locks.
-
Voice Coding, Part 1: What a Vocoder Is & the Pluggable Registry
How GopherTrunk turns 2400-bit-per-second radio data back into speech — what a vocoder actually models, and the Go Vocoder interface + registry that lets IMBE and AMBE+2 plug in behind one seam.
-
Trunking Engine, Part 1: From Grant to Recorded Call
A tour of GopherTrunk's trunking engine — the single-goroutine state machine that subscribes to control-channel grants, allocates a voice SDR, starts a call, and publishes events, without ever importing the code that records or displays them.
-
Protocol Decoders, Part 1: Anatomy of a Control-Channel Decoder
How every GopherTrunk control-channel decoder shares one shape — symbols to framing to FEC to PDU to opcode to bus event — behind a single pipeline interface and a protocol-keyed factory registry.
-
RF Front End, Part 14: Diagnostics, Metrics & the Pure-Go Payoff
The finale of RF Front End. How GopherTrunk makes the front end observable — sdr list --probe, the RTLSDR_DEBUG_USB control-transfer trace, and the iq_underruns_total metric that turned silent IQ loss into something an operator can see — and then the payoff the whole series argued for: one static binary, CGO_ENABLED=0, no librtlsdr, no libusb, cross-compiled everywhere.
-
RF Front End, Part 13: Testing Radios Without Radios
How GopherTrunk's USB driver code is tested in CI with no hardware attached — replaying captured control-transfer sequences through a scripted mock transport, pinning the pure-Go sample conversion bit-for-bit against librtlsdr with a golden-master test, and an opt-in real-hardware tier gated behind environment flags with bias-tee safety resets.
-
RF Front End, Part 12: The SDR Pool & USB Hotplug Watchdog
How GopherTrunk manages a fleet of opened dongles behind one pool — assigning control, voice, and wideband roles, running a USB watchdog that re-enumerates every 30 seconds to catch hotplug, and reacquiring a device after the kernel hands it a new bus address. Plus the re-open race that taught us to serialize retune behind stream teardown.
-
RF Front End, Part 11: HackRF One — Signed 8-Bit IQ End to End
The HackRF One driver end to end — the simplest IQ format of the three, signed 8-bit interleaved I,Q scaled to complex64; enumeration across One/Jawbreaker/Rad1o PIDs; and the open-time board-ID readback that fixed empty probe gains by establishing identity and the gain ladder before streaming.
-
RF Front End, Part 10: Airspy R2/Mini & HF+ — Real Samples to Complex Baseband
How GopherTrunk turns the Airspy R2/Mini's bare real ADC stream into complex baseband entirely on the host — a leaky DC blocker, a multiplier-free Fs/4 mix, and a stateful half-band Hilbert pair — then folds in the Airspy HF+, whose native int16 IQ needs none of it.
-
RF Front End, Part 9: RTL-SDR III — IQ Streaming & the GC-Churn Bug
Sustained IQ streaming from the RTL2832U in pure Go — 32 async USB buffers, a deep consumer channel, and a bit-identical U8-to-complex64 lookup table. Plus the headline bug: per-chunk allocations whose GC churn shed a quarter of the control channel's live IQ, and the zero-allocation reuse ring that fixed it.
-
RF Front End, Part 8: RTL-SDR II — The R82xx Tuner & the Blog V4 Deafness
GopherTrunk's pure-Go R820T/R828D tuner driver — PLL tuning, a shadow-register cache that makes read-modify-write free, and tuner auto-detection. Plus the headline bug: why the RTL-SDR Blog V4 came up deaf and mistuned by 1.8×, and how a manual override plus per-band input switching fixed it.
-
RF Front End, Part 7: RTL-SDR I — Bringing Up the RTL2832U
How GopherTrunk brings up the RTL2832U demodulator in pure Go — the order-sensitive init sequence captured verbatim from librtlsdr, the I2C bridge to the tuner, GPIO and bias-tee, and the USB register transport underneath it all.
-
RF Front End, Part 6: USB on macOS & Windows
How GopherTrunk reaches the same Transport contract through macOS IOKit (via purego, with OS-thread-pinned reader goroutines) and Windows WinUSB (with overlapped I/O), and why macOS forced us to own the calling thread.
-
RF Front End, Part 5: USB on Linux — USBDEVFS & Async URBs
How GopherTrunk's Linux USB backend hand-rolls USBDEVFS ioctl encoding, submits a ring of async URBs, and reaps them in a single goroutine for sustained IQ throughput — all in pure Go behind a safe Transport API.
-
RF Front End, Part 4: Talking to USB Without libusb
How GopherTrunk speaks to RTL-SDR hardware over raw kernel USB interfaces instead of linking libusb, and the single Transport contract that lets one driver run on three wildly different OS USB stacks.
-
Rebuilding GopherTrunk from Source: Update to the Latest Version with Git and GitHub
A copy-paste guide to rebuilding GopherTrunk from source with the Git CLI and GitHub — clone the repo, check out the latest version or a specific branch, build the pure-Go binary, verify it, and keep an existing clone up to date.
-
RF Front End, Part 3: The Driver Registry & Enumeration
How GopherTrunk's self-registering driver registry keeps the core hardware-agnostic — drivers register at init(), the binary's import set chooses the hardware, and EnumerateAll walks every backend to list attached dongles while staying resilient when one driver fails.
-
RF Front End, Part 2: The Device Contract
A method-by-method tour of GopherTrunk's Device interface — the one abstraction four very different radios hide behind — and the optional, type-asserted extension interfaces that expose RTL-SDR-only quirks without polluting the universal contract.
-
RF Front End, Part 1: Why Drive Radios in Pure Go?
The opening post of RF Front End, a 14-part deep dive into GopherTrunk's RF source layer — the pure-Go USB drivers that turn RTL-SDR, Airspy, and HackRF hardware into IQ samples without libusb, librtlsdr, or any C dependency.
-
SDR in Pure Go, Part 14: APIs, Testing & the Pure-Go Story
How GopherTrunk exposes the engine through gRPC, REST, WebSocket and a TUI, observes it with Prometheus and SQLite, and proves it works with synthesized-IQ integration tests — a ports-and-adapters finale.
-
SDR in Pure Go, Part 13: Recording, Composition & Streaming
How GopherTrunk records calls to WAV, drives a per-call demodulation chain with the composer, and streams audio to Broadcastify and RdioScanner — all from optional, config-driven subsystems.
-
SDR in Pure Go, Part 12: Voice Coding — IMBE, AMBE+2 & MBE
How GopherTrunk turns digital voice frames into audio with pure-Go IMBE and AMBE+2 vocoders sharing one MBE synthesis core, all behind a pluggable Vocoder registry.
-
SDR in Pure Go, Part 11: The Trunking Engine & Event Bus
How GopherTrunk's trunking engine turns channel grants into recorded calls, and how an in-process pub/sub event bus decouples the core from the API, storage, and streaming subsystems.
-
SDR in Pure Go, Part 10: Protocol Decoders as State Machines
How GopherTrunk decodes P25, DMR, NXDN, TETRA and a dozen more trunking protocols in pure Go — each a stateful receiver behind a uniform Grant contract, an adapter pattern that keeps the engine protocol-agnostic.
-
SDR in Pure Go, Part 9: Framing & Forward Error Correction
How GopherTrunk recovers clean data from noisy symbols using pure-Go forward error correction — Golay, BCH, Hamming, Reed-Solomon, BPTC, trellis, and Viterbi — built as deterministic, table-driven codecs.
-
SDR in Pure Go, Part 8: Equalization, Diversity & the FFT
How GopherTrunk fights simulcast distortion with adaptive equalizers, combines multiple receivers with diversity, and drives the live waterfall with a rate-limited FFT — all in pure Go.
-
SDR in Pure Go, Part 7: Symbol Timing & Sync Recovery
How GopherTrunk recovers the symbol clock from oversampled signals with a Mueller-Muller timing loop and finds frame boundaries with a sync correlator — feedback state machines implemented in pure Go.
-
SDR in Pure Go, Part 6: Demodulation — FM, C4FM, GFSK & More
How GopherTrunk turns baseband IQ into symbols with pure-Go demodulators for FM, C4FM, GFSK, FFSK, and π/4-DQPSK — each a focused, single-responsibility type composed from DSP primitives.
-
SDR in Pure Go, Part 5: Tuning & Channelization — DDC vs. Polyphase
How GopherTrunk extracts many narrowband channels from one wideband SDR capture using a digital down-converter or a polyphase channelizer, selected at runtime through a Go Strategy interface.
-
SDR in Pure Go, Part 4: DSP Foundations — Filters, NCO & AGC
The building blocks of GopherTrunk's signal chain in Go — FIR/CIC filters, a numerically-controlled oscillator, automatic gain control, and resamplers — and the zero-allocation streaming design behind them.
-
SDR in Pure Go, Part 3: The SDR Pool & Streaming Concurrency
How GopherTrunk streams IQ samples through Go channels and goroutines, manages a fleet of SDR dongles with a device pool, and fans one stream to many consumers without blocking the decode path.
-
SDR in Pure Go, Part 2: SDR Devices & the Driver Registry
How GopherTrunk talks to RTL-SDR, HackRF, and Airspy hardware in pure Go behind one Device interface, and how a self-registering driver registry keeps the core hardware-agnostic.
-
SDR in Pure Go, Part 1: What Is Software-Defined Radio?
A pure-Go tour of software-defined radio — what SDR is, the GopherTrunk signal pipeline from RF to audio, and the layered architecture behind a single static binary.