A software-defined ATSC 1.0 television decoder

Television,
recovered from noise.

Off the air comes a faint smear of radio — eight overlapping amplitude levels, buried under static and ghost reflections. This is how a stack of pure math turns that smear back into a clean 19.39 Mbps broadcast, on any antenna, in any market.

8-VSB
Modulation
10.76 MS/s
Symbol rate
8
Amplitude levels
15.2 dB
The cliff (MER)
02 / 13
What's in the air

One dimension, eight levels, a pilot

ATSC 1.0 doesn't use a two-dimensional constellation like Wi-Fi or QAM cable. It stuffs all its data into the amplitude of a single carrier — eight discrete levels, ±1, ±3, ±5, ±7. A steady pilot tone sits at the band edge so the receiver has something unmistakable to lock onto.

The transmitter frames this into 313 segments per field, prepends a known field-sync training sequence, and protects everything with a trellis code and Reed–Solomon parity. Then it all gets smeared by the channel: thermal noise, multipath ghosts from buildings, impulse bursts. The decoder's job is to undo every layer, in order.

Why it's hard: at the receiver those eight crisp levels arrive as overlapping fuzzy clouds. Push the noise a little further and the clouds merge — there is no "slightly worse picture," only a hard cliff.
8-VSB: eight real amplitude levels with a pilot at the band edge
Real capture. Eight amplitude clusters on one axis; the pilot rides the −3 MHz band edge.
03 / 13
The pipeline

Fourteen stages, IQ samples → clean video

Raw complex samples enter from the SDR at 8 MS/s. Each block below fixes one specific corruption the channel introduced, and hands the result to the next. The equalizer is the heart — it also doubles as the tuner's live quality meter.

RF
SDR IQ
STAGE 5
FPLL
STAGE 7
Timing
STAGE 8
Field Sync
STAGE 9
Equalizer
STAGE 10
Viterbi
STAGE 12
Reed–Solomon
OUT
MPEG-TS

Simplified. Between these sit the matched filter, DC-block & AGC, the convolutional deinterleaver, the PN derandomizer, and optional front-end cleaners (noise blanker, spectral smoother, adaptive notch) that switch in only when the channel needs them.

04 / 13
Locking onto the signal

Before you can decode, you have to lock

Four loops recover what the airwaves scrambled — carrier frequency, symbol clock, and frame alignment — each a small feedback controller chasing an error toward zero.

Stage 05 · CarrierFPLL

Chase the pilot

A frequency/phase-locked loop de-rotates the carrier until the pilot sits still, then keeps the real VSB signal.

phase error x = atan2(Im, Re)
φ ← φ + α·x   f ← f + β·x
with β = α²/4
Stage 04 · FilterRRC

Matched filter

A root-raised-cosine filter maximises signal-to-noise at the sampling instant and rejects out-of-band energy.

h(t) = root-raised-cosine
excess bandwidth α = 0.1152
resample → 1.5 samples / symbol
Stage 07 · ClockTIMING

Find the symbol instant

A fractional interpolator slides its sampling phase μ to land dead-centre on each symbol, locked to the 4-symbol segment sync.

μ ← μ + g·ε  (timing error ε)
→ exactly 1 sample per symbol
Stage 08 · FrameFIELD SYNC

Where does the field start?

Correlate every segment against the known PN511 + 3×PN63 training words; a low mismatch count declares the field boundary.

errors = Σ (bits ⊕ PN)
accept if errors ≤ limit  → 313 seg / field
05 / 13
Where the filters live · the z-plane

Every filter is a few points on one circle

A digital filter's whole personality is a small set of points on the z-plane — the complex plane where the unit circle is the frequency axis. Drop a zero on the circle and that frequency disappears; put a pole beside it and the rest survives. Three of this decoder's filters, drawn exactly that way:

z-plane pole-zero plots for the DC blocker, RRC matched filter, and adaptive notch
Real filter math. The notch's pole/zero is taken straight from the code — H(z) = (1 − c·z⁻¹)/(1 − r·c·z⁻¹), c = e.
High-passDC BLOCKER

A zero nailed to DC

Zero at z = 1, a pole just inside it. Erases the tuner's DC offset and passes everything else untouched.

Matched filterRRC

All zeros, no poles

An FIR filter — 24 zeros ringing the circle carve the root-raised-cosine skirt (α = 0.1152) that maximises SNR.

InterferenceADAPTIVE NOTCH ★

A complex, single-sided bite

It drops a pole/zero pair onto one interferer — r = 0.985, a ~30 kHz notch — and, being one-sided, leaves the pilot alone.

How to read it: zeros carve frequencies away, poles hold them up, and how close a pole sits to the circle sets how sharp the effect — the whole language of these filters, in one picture.
06 / 13
The heart · Adaptive equalizer

256 taps that learn the room

Buildings and terrain reflect the broadcast, so the antenna hears the same symbol several times, delayed and faded — multipath. The equalizer is a 256-tap adaptive filter that models those echoes and subtracts them.

It trains on the field-sync sequence, whose symbols are known exactly (mapped to ±5). Every field it computes its output, measures the error against that known target, and nudges each tap down the gradient — textbook LMS. Between fields it keeps adapting blindly, slicing to the nearest legal level and trusting its own decisions.

The free gift: the training error it already computes is a measurement of signal quality. No extra hardware — the equalizer hands you a live meter.
// filter output
y = Σ tapk·xk

// error vs known ±5 field sync
e = y − d

// gradient step μ = 5·10⁻⁵
tap ← tap − μ·e·x

// between fields: normalised LMS
μeff = DD_MU / (ε + ‖x‖²)
Diverging taps (|tap|>50) trip a bail-out; a last-known-good snapshot restores lock after a hit.
07 / 13
The instrument · MER & the cliff

A free meter, and the edge it watches

Take the equalizer's training-error energy, fs_err_rms, and one line of math turns it into MER — Modulation Error Ratio, the industry's signal-quality number — updated about 41 times a second:

MER = 20·log10( 5 / fs_err_rms )  dB

Plotted against real packet loss, the reason digital TV feels "all or nothing" becomes obvious. Above ~16 dB the picture is flawless. Below ~15.2 dB loss rockets vertically — the cliff. The entire tuner is built to sit a couple of dB above it and stay there.

Packet loss vs MER, showing a sharp cliff near 15.2 dB
1,933 live measurements. Teal = median trend. The fall is nearly vertical at the cliff.
08 / 13
Fixing what's left

Two error-correcting codes, in series

Even after equalization some symbols are wrong. Two classical codes clean up the remainder — and this decoder feeds soft confidence from the first into the second.

Stage 10 · Inner codeVITERBI

Rate-2/3 trellis

Twelve interleaved trellis decoders find the most likely bit sequence. The branch metric is L1 distance to each legal level — chosen because it locked more reliably on real RF than the textbook squared distance.

metrici = |input − leveli|
levels ±1 ±3 ±5 ±7 · 12 coders
SOVA → per-bit confidence
Stage 12 · Outer codeREED–SOLOMON

RS(207,187) over GF(256)

Twenty parity bytes correct up to 10 byte errors per packet. When a hard decode fails, a Forney/GMD ladder marks the Viterbi's weakest bytes as erasures and retries — fixing up to 20.

20 roots → correct ≤ 10 errors
           or ≤ 20 erasures
erasures ranked by SOVA weakness
Then: the derandomizer XORs out the transmitter's whitening sequence, and what falls out the end is a standard 188-byte MPEG-TS packet stream — playable video.
09 / 13
Beyond the math · It learns

The best channel is a function of time

A correct decoder isn't enough. Reception changes through the day — the ionosphere, the weather, and the neighbourhood's electrical noise all drift. A channel that's crystal-clear at 3 a.m. can dip under the cliff by evening.

So the tuner keeps an hour-by-hour history of every channel's MER and quietly learns each curve. When you ask for a network, it already knows which physical channel — and which antenna — is the best bet right now.

Median MER by hour of day for several channels
11 days of telemetry. Dots mark hours with real decodes; dashed line is the cliff.
10 / 13
Beyond the math · Any antenna

No single antenna wins everywhere

Point five different antennas at the same market and you get five different maps of who's strong. A discone owns the low VHF channels; a directional shed antenna dominates one tower and ignores the rest; rabbit ears surprise you on a handful.

The tuner fingerprints each antenna by its frequency response — even recognising the same antenna moved to a different port — and remembers which channels it owns. Universal means no antenna-specific code: the antennas are data the software learns, not constants baked in.

Heatmap of pilot SNR per RF channel across five antennas
Five antennas, one market. Brighter = stronger pilot. The territories barely overlap.
11 / 13
Because it measures · it improves

Every knob is settled by an A/B, not a hunch

A/B bar charts: packet loss down and MER up for the tuned CLIFF profile
RF9 controlled test. Same signal, better math: loss down, MER up.
Distribution of MER readings with the cliff marked
Where the signal lives. Red bars fall below the cliff — the engineering target.

Because MER is measured live, every proposed change — a longer equalizer, a different Viterbi metric, an erasure ladder — is decided by a real overflow-gated A/B on captured signal, never by feel. Improvements that don't clear the bar get reverted.

12 / 13
It predicts

It knows the answer before it tunes

All that history rolls up into an oracle: given a channel, an antenna, and the current hour, it predicts the MER you'll get — before spending a second of tuning time on it.

Plotted against what actually happened, the predictions hug the diagonal within about a dB. That's what lets the tuner skip the losers, race the likely winners, and find a locked picture in seconds instead of scanning blind.

Predicted vs actual MER scatter hugging the diagonal
Predicted vs. actual. The dashed line is a perfect call; points cluster on it.
13 / 13
The whole point

One decoder. Any antenna. Any market. And it keeps getting better.

From a smear of eight-level radio buried under noise, a chain of well-understood math — carrier recovery, matched filtering, adaptive equalization, trellis and Reed–Solomon coding — reconstructs broadcast television bit-for-bit. On top sits a layer that measures itself, learns the day and the antennas, and tunes accordingly.

Grounded

Classical DSP

Every stage is a named, testable algorithm with a measurable job — no black boxes.

Honest

Measured, not guessed

MER is computed live; changes ship only when a real A/B proves them.

Universal

Learns, doesn't assume

Antennas, hours and markets are data the tuner learns — nothing hard-coded.

Runs on WindowsRuns on LinuxRuns on Raspberry PiBit-comparable across all three