Fix: dragging/wheeling the spectrum in the web UI clamped frequency to a
hardcoded HF ceiling (vfoMax=60 MHz), so on Pluto VHF/UHF it snapped down
to 60 MHz and couldn't pan up (only the VFO wheel worked, via a different
limit). Push the backend's tuning range (BackendCaps.MinFreqHz/MaxFreqHz)
to the web alongside the band plan; JS vfoMin/vfoMax use freq_min/freq_max
from state. The transverter branch (XVTR_CUR>=0) is unchanged — it keeps
the wide-open range since the displayed RF freq is translated to IF and
clamped server-side.
Dead-code cleanup (audit of this session's work):
- SampleRateMode/TSampleRateMode: orphaned when the sample-rate
unification replaced the srmContinuous branch in ApplyBackendCapsToUI;
no readers left. Removed (+ now-unused PLUTO_MAX_SR).
- Pre-existing write-only/unused: TBackendCaps.MaxSampleRate,
SampleRateOverlay.SPAN_NAMES, MainForm.BAND_FREQ.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add ADALM-Pluto / AD9361 support as a second hardware backend alongside
openHPSDR, sharing the WDSP DSP pipeline and the existing controller API
(UI stays decoupled from logic).
- RadioBackend.pas: abstract TRadioBackend + TBackendCaps + TRadioDevice
(Kind/URI/Serial). THPSDRNetwork now derives from it (state via virtual
getters); TRadioController.FNetwork is the base type.
- IIOBindings.pas: dynamic libiio loader (runs without libiio present).
- PlutoBackend.pas: scan/probe-by-URI, connect, LO/rate/bandwidth/gain
control, RX streaming thread (int16->24bit BE -> OnDDCIQ), Q conjugated
to match WDSP IQ convention. Verified on LibreSDR (AD9361) over network.
- Unified discovery: TDiscoverThread scans both backends; network Plutos
found via direct ProbeURI (no mDNS needed). ConnectDevice dispatches by
Dev.Kind (EnsureBackend swaps backend, preserving callbacks).
- DeviceStore/DeviceForm: persist Kind/URI/Serial; save Pluto via
AddSavedPluto so saved/autostart devices reconnect across restarts.
- VHF/UHF band plan (BoardUtils, kind-aware): 6m..ADS-B for Pluto; fixes
HF clamps (band detect, mouse-wheel 60 MHz cap, freq-display max).
- SampleRateOverlay: configurable presets (Pluto 576k..5760k, >520 ksps),
auto-width to fit.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The span-selector panel was rendered directly onto the spectrum canvas
every frame via DrawSelf, doing ~30 TextWidth + ~11 TextOut Canvas calls.
On Cocoa these text ops are very expensive (~10.6 ms/frame, ~30% of a
core) and dominated DrawSpectrum.
Render the panel once into a per-pixel-alpha cache bitmap and only
composite it over the spectrum each frame; rebuild only when state
changes (rate, hover, hidden, bounds). The cache is built with the
two-background method (render over black and white, derive straight
alpha + color) so layered semi-transparent button fills and antialiased
text composite over the live spectrum exactly as before.
Cuts the overlay phase from ~10.6 ms to ~0.2 ms per frame; DrawSpectrum
drops ~13.8 ms -> ~2.7 ms. Mirrors the caching VfoOverlay already uses.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
On macOS Cocoa, TBitmap.pf32bit stores pixels in ARGB order
(byte0=A, byte1=R, byte2=G, byte3=B) rather than BGRA used on
Linux/Windows. All raw ScanLine pixel blending and channel mapping
is now guarded with { DARWIN} to use the correct offsets.
Also fix PortAudio library discovery on macOS: probe .dylib names
(including Homebrew paths) before falling through to .so/.dll.
- Add SampleRateOverlay.pas: TCustomControl overlay (top-left of spectrum)
with hide-panel button and 6 span buttons (48k–1536k), same hit-test
pattern as VfoOverlay
- Consolidate toolbar: move PanelTopVfoGroup and PanelSMeterRight into
PanelToolbar (height 72) instead of floating on the form
- Remove PanelSpanButtons (alTop spacer) that caused the ~1cm black strip
at the top of the spectrum
- Add separator line inside PanelToolbar anchored to its bottom edge,
with 16px left/right margins
- Fix left panel blocks starting Y offset (was 38, now 0)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>