feat: unify sample-rate presets across desktop + web UIs

Sample-rate (span) presets were duplicated in three places and none was
authoritative: HPSDR set hardcoded in the desktop overlay (SPAN_RATES),
Pluto set in backend caps, and a separate hardcoded list in the web JS.
The web showed HPSDR rates even on Pluto, and the web adapter silently
dropped any rate outside a hardcoded HPSDR whitelist (so Pluto-only rates
like 960k/2304k never switched).

Single source of truth = backend caps -> controller:
- HPSDRNetwork.Caps now fills RatePresets [48k..1536k] (was nil).
- RadioBackend: named type TBackendRateArray.
- TRadioController.SampleRatePresets returns BackendCaps.RatePresets.

Both UIs derive from it:
- Desktop: ApplyBackendCapsToUI always feeds the overlay from
  SampleRatePresets (overlay no longer owns the HPSDR list).
- Web: WebServer.SetRatePresets + rate_presets in state JSON; hosts
  (daemon OnState/startup, GUI ApplyBackendCapsToUI/startup) push
  SampleRatePresets; JS builds span buttons dynamically from it.
- WebAdapter.SyncSpan validates against SampleRatePresets instead of a
  hardcoded whitelist, so any backend rate is accepted.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-23 13:52:03 +03:00
co-authored by Claude Opus 4.8
parent d78d835485
commit 6b695ce756
8 changed files with 87 additions and 21 deletions
+30 -3
View File
@@ -297,6 +297,7 @@ type
FFreqMhzDigits: Integer; // 3=999MHz, 4=9.999GHz, 5=99.999GHz
FXvtrBands: TWebXvtrArray; // список enabled XVTR (для web-UI)
FDevices: TWebDeviceArray; // saved+discovered устройства (для web-overlay)
FRatePresets: array of Integer; // sample-rate пресеты текущего бэкенда (для span-кнопок)
FCurrentXvtr: Integer; // -1 = HF, иначе индекс активного XVTR
FOnXvtrBand: TWebCmdXvtrBand;
@@ -392,6 +393,7 @@ type
property OnXvtrBand: TWebCmdXvtrBand read FOnXvtrBand write FOnXvtrBand;
procedure SetXvtrBands(const ABands: TWebXvtrArray; ACurrent: Integer);
procedure SetDeviceList(const ADevices: TWebDeviceArray);
procedure SetRatePresets(const ARates: array of Integer);
property OnSpan: TWebCmdSpan read FOnSpan write FOnSpan;
property OnVolume: TWebCmdVolume read FOnVolume write FOnVolume;
property OnWfAGC: TWebCmdWfAGC read FOnWfAGC write FOnWfAGC;
@@ -535,6 +537,8 @@ begin
FCurrentXvtr := -1;
FFreqMhzDigits := 3;
SetLength(FXvtrBands, 0);
// Bootstrap-набор рейтов (до connect): хост перезапишет под бэкенд (HPSDR/Pluto).
SetRatePresets([48000, 96000, 192000, 384000, 768000, 1536000]);
end;
procedure TWebServer.SetXvtrBands(const ABands: TWebXvtrArray; ACurrent: Integer);
@@ -564,6 +568,21 @@ begin
end;
end;
procedure TWebServer.SetRatePresets(const ARates: array of Integer);
// Список пресетов sample-rate текущего бэкенда (из TRadioController). Web строит
// span-кнопки из него — HPSDR и Pluto имеют разные наборы.
var i: Integer;
begin
FStateLock.Enter;
try
SetLength(FRatePresets, Length(ARates));
for i := 0 to High(ARates) do
FRatePresets[i] := ARates[i];
finally
FStateLock.Leave;
end;
end;
destructor TWebServer.Destroy;
begin
Stop;
@@ -1542,13 +1561,21 @@ const
('LSB','USB','DSB','CWL','CWU','FM','AM','SAM');
var
FS: TFormatSettings;
XvtrJson, DevJson: string;
XvtrJson, DevJson, RateJson: string;
i: Integer;
begin
FS := DefaultFormatSettings;
FS.DecimalSeparator := '.';
FStateLock.Enter;
try
// Сборка массива rate_presets: [48000,96000,...] (span-кнопки web).
RateJson := '[';
for i := 0 to High(FRatePresets) do
begin
if i > 0 then RateJson := RateJson + ',';
RateJson := RateJson + IntToStr(FRatePresets[i]);
end;
RateJson := RateJson + ']';
// Сборка массива xvtr_bands: [{"idx":0,"name":"2m"},...]
XvtrJson := '[';
for i := 0 to High(FXvtrBands) do
@@ -1586,7 +1613,7 @@ begin
'"status_text":"%s","board_text":"%s","ip_text":"%s","supply_text":"%s",' +
'"pll_text":"%s","rx_text":"%s","tx_text":"%s","seq_text":"%s",' +
'"xvtr_current":%d,"xvtr_bands":%s,"freq_mhz_digits":%d,' +
'"fmstep_idx":%d,"devices":%s}',
'"fmstep_idx":%d,"devices":%s,"rate_presets":%s}',
[FFreq, FVfoB, FActiveVfo,
FMode, MODE_N[FMode mod 8],
FFilterIdx, FFilterBW,
@@ -1615,7 +1642,7 @@ begin
JsonEscape(FSupplyText), JsonEscape(FPLLText), JsonEscape(FRXText),
JsonEscape(FTXText), JsonEscape(FSeqText),
FCurrentXvtr, XvtrJson, FFreqMhzDigits,
FFMStepIdx, DevJson
FFMStepIdx, DevJson, RateJson
], FS);
finally
FStateLock.Leave;