feat(trx-client,http-frontend): spectrum waveform with frequency picker

Poll GetSpectrum every 200 ms in remote_client via a dedicated timer that
bypasses the main state-watch channel (no SSE noise). The resulting
SpectrumData is stored in FrontendRuntimeContext::spectrum and served by
a new GET /spectrum endpoint (JSON or 204 when unavailable).

HTTP frontend shows a spectrum panel (canvas + frequency axis) only when
the rig reports filter_controls=true (i.e. SoapySDR). The canvas renders:
- dark background with dBFS grid lines
- green FFT spectrum line with semi-transparent fill
- red dashed vertical marker at the currently tuned frequency
- frequency axis labels (MHz/kHz) below the canvas

Clicking the canvas tunes the rig to the clicked frequency.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Stan Grams <sjg@haxx.space>
This commit is contained in:
2026-02-27 21:36:05 +01:00
parent 76969b5499
commit 952961b9fd
9 changed files with 314 additions and 2 deletions
@@ -583,3 +583,31 @@ button:focus-visible, input:focus-visible, select:focus-visible {
.vfo-picker button { border-right: none; border-bottom: 1px solid var(--border-light); }
.vfo-picker button:last-child { border-bottom: none; }
}
/* ── Spectrum display ─────────────────────────────────────────────────── */
.spectrum-wrap {
position: relative;
width: 100%;
}
#spectrum-canvas {
display: block;
width: 100%;
height: 120px;
background: #0a0f18;
border-radius: 4px;
cursor: crosshair;
}
#spectrum-freq-axis {
position: relative;
height: 18px;
width: 100%;
font-size: 0.7rem;
color: var(--text-muted);
user-select: none;
}
#spectrum-freq-axis span {
position: absolute;
transform: translateX(-50%);
white-space: nowrap;
}