This is the opening post of SDR Internals, a 14-part series that walks the entire software-defined-radio pipeline behind GopherTrunk — one component per post — and explains the software-design principle behind each piece and how that principle shaped the Go code.
In this post
- What software-defined radio is and why it moves radio from soldered hardware into software you can change.
- The GopherTrunk pipeline: RF → IQ samples → DSP → symbols → protocol decode → events → audio.
- The design principle for the whole engine: a layered architecture with
a strict one-way dependency direction, built as a single pure-Go
(
CGO_ENABLED=0) static binary. - A map of the series so you can jump to the component you care about.
What is software-defined radio?
A traditional radio is a chain of fixed analog parts — a mixer here, a filter there, a demodulator soldered to a board. Each part does exactly one job at one frequency. Software-defined radio (SDR) replaces that fixed chain with one generic move: digitize the radio spectrum as early as possible, then do every remaining step — tuning, filtering, demodulation, decoding — in software.
An SDR dongle hands your computer a torrent of IQ samples: complex numbers that capture both the amplitude and the phase of the radio signal in a slice of spectrum. Once the signal is just numbers, “build a radio” becomes “write a program.” That is the whole premise of GopherTrunk: a P25, DMR, TETRA, NXDN and multi-protocol trunking scanner where every block — from the USB driver to the voice codec — is Go code.
New to the fundamentals? See the reference entries on software-defined radio and IQ data, or the learn-path lesson What is software-defined radio?.
The GopherTrunk pipeline
Every signal takes the same journey. Each stage is its own Go package, and each becomes its own post in this series:
RF → IQ samples → DSP → symbols → protocol → events → audio
(internal/sdr) (internal/dsp) (internal/radio) (trunking) (voice)
internal/sdr— pure-Go USB drivers for RTL-SDR, HackRF, and Airspy produce a stream of[]complex64IQ chunks.internal/dsp— filters, oscillators, channelizers, demodulators, and timing-recovery loops turn wideband IQ into clean symbol streams.internal/radio— per-protocol decoders (P25, DMR, NXDN, TETRA, …) turn symbols into framed, error-corrected control messages.internal/trunking+internal/events— an engine consumes those messages and publishes domain events (a call started, a channel was granted).internal/voice— vocoders and a recorder turn voice frames into WAV audio;internal/apiserves it to clients.
The design principle: a strict, layered architecture
The single most important architectural decision in GopherTrunk is that dependencies only ever point one way: the SDR layer knows nothing about DSP, DSP knows nothing about protocols, protocols know nothing about the trunking engine, and the engine knows nothing about the API, storage, or UI that consume its output.
How that principle shaped the Go code
That one-way rule shows up concretely in three Go idioms you’ll see throughout the series:
- Typed channels as the seams between layers. A device exposes
<-chan []complex64; a DSP stage consumes one channel and produces another. Layers are wired together by channels, not by calling into each other. - Interfaces owned by the consumer. Where one layer does need a capability from another, it declares a small interface describing only what it needs — so the dependency is on a contract, not a concrete type. This is classic Go “accept interfaces, return structs.”
- An event bus instead of back-references. The engine never calls the API,
storage, or broadcaster. It publishes events to
internal/events, and those subsystems subscribe. The core stays testable in isolation (more in Part 11).
And the principle that ties it all together: pure Go, no CGO. Every block —
USB transport, DSP, FEC math, even the voice codecs — is implemented in Go with
CGO_ENABLED=0. There is no librtlsdr, no libusb, no libmp3lame. The
payoff is one statically linked binary that cross-compiles to Linux, macOS, and
Windows with go build, and a codebase where you can read the radio from
antenna to audio without leaving the language.
The series map
| Part | Component | Design principle |
|---|---|---|
| 1 | What is SDR? (this post) | Layered architecture |
| 2 | SDR devices & the driver registry | Registry / dependency inversion |
| 3 | The SDR pool & streaming concurrency | Pipes-and-filters + CSP |
| 4 | DSP foundations: filters, NCO, AGC | Stateful streaming + zero-alloc reuse |
| 5 | Tuning & channelization | Strategy pattern |
| 6 | Demodulation (FM, C4FM, GFSK, …) | Single responsibility |
| 7 | Symbol timing & sync recovery | Feedback state machines |
| 8 | Equalization, diversity & FFT | Decorator + interface segregation |
| 9 | Framing & forward error correction | Pure functions & table-driven code |
| 10 | Protocol decoders as state machines | Adapter + uniform contract |
| 11 | Trunking engine & event bus | Observer / event-driven |
| 12 | Voice coding: IMBE, AMBE+2, MBE | Plugin registry + shared core |
| 13 | Recording, composition & streaming | Config-driven lazy init |
| 14 | APIs, testing & the pure-Go story | Ports & adapters + testability |
Each post is an overview — enough to understand the component, the Go that implements it, and the principle behind it. Where a topic deserves more, it will get its own dedicated deep-dive series later. You can always find every part on the SDR Internals series index.
FAQ
Is software-defined radio just a USB dongle? The dongle is only the digitizer. SDR is the idea that everything after sampling — tuning, filtering, demodulating, decoding — happens in software. The dongle produces IQ samples; the radio is the program that processes them.
Why build an SDR stack in Go instead of C or C++?
Go gives you memory safety, first-class concurrency (goroutines and channels map
beautifully onto a streaming DSP pipeline), and trivial cross-compilation. With
CGO_ENABLED=0 the whole thing ships as one static binary, with no shared
libraries to install on the target machine.
Do I have to read the series in order? No. Part 1 is the map; each later part stands alone. But the pipeline flows in order, so reading top-to-bottom mirrors the path a signal actually takes.
Series navigation
Part 1 of 14 · Next → Part 2: SDR devices & the driver registry