Symbol scope panel
The Symbol scope (web /symbols) renders a live oscilloscope of
the demodulated symbol stream coming off whichever SDR you pick — the
“are the symbols clean?” view, complementing the Constellation panel’s
IQ scatter. It is GopherTrunk’s take on OP25’s Symbol plot.
What you see
The X axis is symbol index (time, left→right); the Y axis is the symbol level. Two render modes are chosen automatically:
- P25 C4FM streams the pre-slicer soft waveform — the FM-discriminator + matched-filter + clock-recovered samples, just before slicing. A healthy 4-level channel reads as ~4 noisy horizontal bands; faint rails mark the mean of each decided level.
- P25 CQPSK has no soft tap yet, so it streams the sliced dibit decisions only, drawn as discrete rows.
Brighter (additively-blended) regions are denser symbol populations. The trace is GopherTrunk’s sky-blue accent, matching the Constellation and Spectrum panels.
Getting a usable picture (offset / Hold / follow)
Like the Constellation panel, the scope taps the SDR’s wideband IQ, so a channel sitting on the SDR centre is buried under the DC spike. The same controls work around it:
- Mode — the receiver / demod path (P25 C4FM or P25 CQPSK).
- Offset / Freq — tunes a channel sitting at this offset (kHz, relative to the SDR centre) down to baseband before the receiver channelizes it, lifting an off-centre control/voice channel clear of the DC spike. Type the kHz offset (1 Hz resolution, so 6.25 / 12.5 kHz grids land exactly) or the channel’s absolute MHz frequency — the two stay in sync. The tuned frequency is shown as soon as an SDR is selected, before any symbols decode.
- Hold — when off, the Offset automatically follows the newest active call on the selected SDR (the “last locked channel”), and rests on the system’s control channel (resolved from config) when no call is up — so the scope defaults onto a decodable channel off the centre DC spike and returns there when a call ends (#557). Turn Hold on (or edit the Offset / press Centre) to pin it. Devices with no control channel in their passband keep the old centre default.
How it works
- The panel opens a WebSocket to
WS /api/v1/diag/symbols?device=...&proto=...&offset=.... - The daemon attaches a parallel decode to the SDR’s iqtap broker —
the same broker the Constellation and Spectrum panels use — so it
never disturbs the production control-channel decoder. The parallel
decode reuses the production down-converter
(
ccdecoder.Downconverter, channelizing to 48 kHz for the C4FM family) and the production P25 Phase 1 receiver. - The receiver’s
SoftSink(pre-slicer waveform) andDibitSink(sliced decisions) feedinternal/scanner/symbolscope, which batches ~256 symbols per frame and ships them over the WS. Soft and dibit are aligned index-for-index on the C4FM path.
Offline (SigLab)
The offline SigLab analyzer has the same view. Run a capture
with collect IQ diag + capture IQ enabled and the Result carries
an aligned symbol series (IQTaps.symbol_dibits / symbol_soft); the
SigLab Results page renders it as a Symbol scope card next to the eye
diagram. The live panel and the offline viz share the same props
contract, so they read identically.
Limitations
- P25 Phase 1 only, today. C4FM gets the soft waveform + dibits; CQPSK gets dibits only. TETRA and the rest of the C4FM family (DMR / NXDN / YSF / D-STAR) — and a soft waveform for them — land as the per-receiver soft taps ship.
- Not a demodulator UI. This is a diagnostic view; to actually
decode, configure the channel as a
trunking.systemsentry and let the production pipeline take over. - One engine per panel. Each open scope runs a full down-converter + receiver on its own broker subscription; CPU scales with the number of open scopes (the broker fan-out never back-pressures the production decoder).
Implementation
| Path | Role |
|---|---|
internal/scanner/symbolscope/scope.go |
Engine — DDC + P25 receiver + soft/dibit taps → Frames |
internal/api/symbols.go |
SymbolProvider interface + WS /api/v1/diag/symbols handler |
cmd/gophertrunk/symbol_provider.go |
Daemon provider — wires the engine to the iqtap broker per subscriber |
web/src/api/symbols.ts |
Typed client with auto-reconnect / backoff |
web/src/components/SymbolScopeChart.tsx |
Canvas scope renderer (shared contract with the SigLab viz) |
web/src/components/TuningControls.tsx |
Shared offset / frequency / Hold / Centre controls (also used by the Constellation) |
web/src/panels/SymbolScope.tsx |
Panel: SDR + mode + offset/Hold/follow controls |