fix: web spectrum tuning range from backend caps; remove dead code

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>
This commit is contained in:
2026-06-23 14:35:56 +03:00
co-authored by Claude Opus 4.8
parent 4749cf0cbc
commit 3cedc6225d
8 changed files with 29 additions and 16 deletions
-2
View File
@@ -631,8 +631,6 @@ begin
Result.HasRFBandwidth := False; Result.HasRFBandwidth := False;
Result.HasFullDuplex := True; Result.HasFullDuplex := True;
Result.MinSampleRate := 48000; Result.MinSampleRate := 48000;
Result.MaxSampleRate := 1536000;
Result.SampleRateMode := srmDiscrete;
// Бэкенд — единственный источник своих рейтов (общий для desktop/web UI). // Бэкенд — единственный источник своих рейтов (общий для desktop/web UI).
SetLength(Result.RatePresets, 6); SetLength(Result.RatePresets, 6);
Result.RatePresets[0] := 48000; Result.RatePresets[0] := 48000;
+1 -3
View File
@@ -94,9 +94,6 @@ const
BAND_NAMES: array[0..BAND_COUNT-1] of string = ( BAND_NAMES: array[0..BAND_COUNT-1] of string = (
'160m','80m','60m','40m','30m','20m','17m','15m','12m','10m','6m'); '160m','80m','60m','40m','30m','20m','17m','15m','12m','10m','6m');
BAND_FREQ: array[0..BAND_COUNT-1] of Double = (
1900000, 3750000, 5357000, 7100000, 10125000,
14200000, 18120000, 21200000, 24940000, 28500000, 50150000);
MODE_NAMES: array[0..MODE_COUNT-1] of string = ( MODE_NAMES: array[0..MODE_COUNT-1] of string = (
'LSB','USB','DSB','CWL','CWU','FM','AM','SAM'); 'LSB','USB','DSB','CWL','CWU','FM','AM','SAM');
@@ -5397,6 +5394,7 @@ begin
Freqs[i] := Plan[i].FreqHz; Freqs[i] := Plan[i].FreqHz;
end; end;
FWebServer.SetBands(Names, Freqs); FWebServer.SetBands(Names, Freqs);
with FController.BackendCaps do FWebServer.SetFreqRange(MinFreqHz, MaxFreqHz);
end; end;
procedure TMainForm.PushXvtrToWeb; procedure TMainForm.PushXvtrToWeb;
-3
View File
@@ -139,7 +139,6 @@ implementation
const const
PLUTO_MIN_SR = 521000; // нижний предел AD936x sampling_frequency PLUTO_MIN_SR = 521000; // нижний предел AD936x sampling_frequency
PLUTO_MAX_SR = 61440000;
PLUTO_MIN_HZ = 46875000; // AD9361 нативно (LibreSDR/AntSDR); AD9363 — 325 МГц PLUTO_MIN_HZ = 46875000; // AD9361 нативно (LibreSDR/AntSDR); AD9363 — 325 МГц
PLUTO_MAX_HZ = 6000000000.0; PLUTO_MAX_HZ = 6000000000.0;
PLUTO_DEF_SR = 1536000; // дефолтный rate если контроллер задал невалидный PLUTO_DEF_SR = 1536000; // дефолтный rate если контроллер задал невалидный
@@ -389,8 +388,6 @@ begin
Result.HasRFBandwidth := True; Result.HasRFBandwidth := True;
Result.HasFullDuplex := True; Result.HasFullDuplex := True;
Result.MinSampleRate := PLUTO_MIN_SR; Result.MinSampleRate := PLUTO_MIN_SR;
Result.MaxSampleRate := PLUTO_MAX_SR;
Result.SampleRateMode := srmContinuous;
// Пресеты для SampleRateOverlay (все > 520 ksps, минимум AD936x). // Пресеты для SampleRateOverlay (все > 520 ksps, минимум AD936x).
SetLength(Result.RatePresets, 8); SetLength(Result.RatePresets, 8);
Result.RatePresets[0] := 576000; Result.RatePresets[0] := 576000;
-3
View File
@@ -28,7 +28,6 @@ uses
type type
TBackendKind = (bkHPSDR, bkPluto); TBackendKind = (bkHPSDR, bkPluto);
TSampleRateMode = (srmDiscrete, srmContinuous);
TBackendRateArray = array of Integer; // пресеты sample-rate (общий тип UI) TBackendRateArray = array of Integer; // пресеты sample-rate (общий тип UI)
// Описание возможностей бэкенда. Контроллер гейтит UI/поведение по этим полям. // Описание возможностей бэкенда. Контроллер гейтит UI/поведение по этим полям.
@@ -45,8 +44,6 @@ type
HasRFBandwidth: Boolean; // настройка аналоговой RF-полосы (Pluto) HasRFBandwidth: Boolean; // настройка аналоговой RF-полосы (Pluto)
HasFullDuplex: Boolean; // одновременный RX+TX (Pluto / QO-100) HasFullDuplex: Boolean; // одновременный RX+TX (Pluto / QO-100)
MinSampleRate: Integer; MinSampleRate: Integer;
MaxSampleRate: Integer;
SampleRateMode: TSampleRateMode;
RatePresets: TBackendRateArray; // пресеты для SampleRateOverlay RatePresets: TBackendRateArray; // пресеты для SampleRateOverlay
MinFreqHz: Double; MinFreqHz: Double;
MaxFreqHz: Double; MaxFreqHz: Double;
-1
View File
@@ -90,7 +90,6 @@ implementation
const const
SPAN_RATES: array[0..5] of Integer = (48000, 96000, 192000, 384000, 768000, 1536000); SPAN_RATES: array[0..5] of Integer = (48000, 96000, 192000, 384000, 768000, 1536000);
SPAN_NAMES: array[0..5] of string = ('48k', '96k', '192k', '384k', '768k', '1536k');
HIDE_W = 22; HIDE_W = 22;
SPAN_H = 18; SPAN_H = 18;
+6 -2
View File
@@ -455,6 +455,7 @@ begin
'let stA=0,stAmem=100,stB=0,stBmem=100;' + 'let stA=0,stAmem=100,stB=0,stBmem=100;' +
'let freqMhzDigits=3,freqMaxHz=999999999;' + 'let freqMhzDigits=3,freqMaxHz=999999999;' +
'let freqMin=30000,freqMax=60000000;' + // диапазон бэкенда (из state.freq_min/max)
'function fmtVfo(hz,step){' + 'function fmtVfo(hz,step){' +
'const N=freqMhzDigits+6;' + 'const N=freqMhzDigits+6;' +
'const s=String(Math.max(0,Math.round(hz||0))).padStart(N,"0");' + 'const s=String(Math.max(0,Math.round(hz||0))).padStart(N,"0");' +
@@ -695,6 +696,7 @@ begin
'if(msg.bands!=null&&Array.isArray(msg.bands)&&msg.bands.length){' + 'if(msg.bands!=null&&Array.isArray(msg.bands)&&msg.bands.length){' +
'const nn=msg.bands.map(function(b){return b.name;});' + 'const nn=msg.bands.map(function(b){return b.name;});' +
'if(JSON.stringify(nn)!==JSON.stringify(BAND_N)){BAND_N=nn;BAND_F=msg.bands.map(function(b){return b.freq;});rebuildBandSel();}}' + 'if(JSON.stringify(nn)!==JSON.stringify(BAND_N)){BAND_N=nn;BAND_F=msg.bands.map(function(b){return b.freq;});rebuildBandSel();}}' +
'if(msg.freq_min!=null)freqMin=msg.freq_min;if(msg.freq_max!=null)freqMax=msg.freq_max;' +
// Обновляем localCenter/localSpan из сервера только когда пользователь не тянет // Обновляем localCenter/localSpan из сервера только когда пользователь не тянет
'if(!dragging&&Date.now()-wheelActiveTs>300){' + 'if(!dragging&&Date.now()-wheelActiveTs>300){' +
// В CTUN без DUP: TX-спектр WDSP центрирован по vfo_a_hz, а не по DDC LO. // В CTUN без DUP: TX-спектр WDSP центрирован по vfo_a_hz, а не по DDC LO.
@@ -813,8 +815,10 @@ begin
'function activeVfoHz(){return activeVfo==="A"?vfoA:vfoB;}' + 'function activeVfoHz(){return activeVfo==="A"?vfoA:vfoB;}' +
'function setActiveVfoHz(f){if(activeVfo==="A")vfoA=f;else vfoB=f;}' + 'function setActiveVfoHz(f){if(activeVfo==="A")vfoA=f;else vfoB=f;}' +
'function sendActiveFreq(f){if(activeVfo==="A")ws2({cmd:"freq",hz:f});else ws2({cmd:"freq",hz:f});}' + 'function sendActiveFreq(f){if(activeVfo==="A")ws2({cmd:"freq",hz:f});else ws2({cmd:"freq",hz:f});}' +
'function vfoMin(){return XVTR_CUR>=0?0:30000;}' + // Границы перестройки берём из бэкенда (freqMin/freqMax из state), а не
'function vfoMax(){return XVTR_CUR>=0?999999999999:60000000;}' + // хардкодим HF — иначе на Pluto (VHF/UHF) drag по спектру клампило в 60 МГц.
'function vfoMin(){return XVTR_CUR>=0?0:Math.max(30000,freqMin);}' +
'function vfoMax(){return XVTR_CUR>=0?999999999999:freqMax;}' +
'function doWheel(e){e.preventDefault();e.stopPropagation();audioKick();const dir=e.deltaY<0?1:-1,st=tuneStep(e),base=Math.floor(activeVfoHz()/st)*st;' + 'function doWheel(e){e.preventDefault();e.stopPropagation();audioKick();const dir=e.deltaY<0?1:-1,st=tuneStep(e),base=Math.floor(activeVfoHz()/st)*st;' +
'const nf=cl(dir>0?base+st:(Math.round(activeVfoHz())===base?base-st:base),vfoMin(),vfoMax());' + 'const nf=cl(dir>0?base+st:(Math.round(activeVfoHz())===base?base-st:base),vfoMin(),vfoMax());' +
'setActiveVfoHz(nf);wheelActiveTs=Date.now();' + 'setActiveVfoHz(nf);wheelActiveTs=Date.now();' +
+21 -2
View File
@@ -300,6 +300,8 @@ type
FRatePresets: array of Integer; // sample-rate пресеты текущего бэкенда (для span-кнопок) FRatePresets: array of Integer; // sample-rate пресеты текущего бэкенда (для span-кнопок)
FBandNames: array of string; // band-план текущего бэкенда (имена кнопок) FBandNames: array of string; // band-план текущего бэкенда (имена кнопок)
FBandFreqs: array of Double; // band-план: дефолтные частоты (для подсветки selector) FBandFreqs: array of Double; // band-план: дефолтные частоты (для подсветки selector)
FFreqMin: Double; // диапазон перестройки бэкенда (клампинг drag/wheel)
FFreqMax: Double;
FCurrentXvtr: Integer; // -1 = HF, иначе индекс активного XVTR FCurrentXvtr: Integer; // -1 = HF, иначе индекс активного XVTR
FOnXvtrBand: TWebCmdXvtrBand; FOnXvtrBand: TWebCmdXvtrBand;
@@ -397,6 +399,7 @@ type
procedure SetDeviceList(const ADevices: TWebDeviceArray); procedure SetDeviceList(const ADevices: TWebDeviceArray);
procedure SetRatePresets(const ARates: array of Integer); procedure SetRatePresets(const ARates: array of Integer);
procedure SetBands(const ANames: array of string; const AFreqs: array of Double); procedure SetBands(const ANames: array of string; const AFreqs: array of Double);
procedure SetFreqRange(AMin, AMax: Double);
property OnSpan: TWebCmdSpan read FOnSpan write FOnSpan; property OnSpan: TWebCmdSpan read FOnSpan write FOnSpan;
property OnVolume: TWebCmdVolume read FOnVolume write FOnVolume; property OnVolume: TWebCmdVolume read FOnVolume write FOnVolume;
property OnWfAGC: TWebCmdWfAGC read FOnWfAGC write FOnWfAGC; property OnWfAGC: TWebCmdWfAGC read FOnWfAGC write FOnWfAGC;
@@ -546,6 +549,7 @@ begin
SetBands(['160m','80m','60m','40m','30m','20m','17m','15m','12m','10m','6m'], SetBands(['160m','80m','60m','40m','30m','20m','17m','15m','12m','10m','6m'],
[1840000,3700000,5357000,7100000,10120000,14200000, [1840000,3700000,5357000,7100000,10120000,14200000,
18130000,21300000,24940000,28400000,50150000]); 18130000,21300000,24940000,28400000,50150000]);
SetFreqRange(0, 61440000); // HPSDR bootstrap; хост перезапишет под бэкенд
end; end;
procedure TWebServer.SetXvtrBands(const ABands: TWebXvtrArray; ACurrent: Integer); procedure TWebServer.SetXvtrBands(const ABands: TWebXvtrArray; ACurrent: Integer);
@@ -606,6 +610,19 @@ begin
end; end;
end; end;
procedure TWebServer.SetFreqRange(AMin, AMax: Double);
// Диапазон перестройки текущего бэкенда (caps): web клампит drag/wheel/click по
// спектру в эти границы. HPSDR 0..61.44МГц / Pluto 46.875МГц..6ГГц.
begin
FStateLock.Enter;
try
FFreqMin := AMin;
FFreqMax := AMax;
finally
FStateLock.Leave;
end;
end;
destructor TWebServer.Destroy; destructor TWebServer.Destroy;
begin begin
Stop; Stop;
@@ -1646,7 +1663,8 @@ begin
'"status_text":"%s","board_text":"%s","ip_text":"%s","supply_text":"%s",' + '"status_text":"%s","board_text":"%s","ip_text":"%s","supply_text":"%s",' +
'"pll_text":"%s","rx_text":"%s","tx_text":"%s","seq_text":"%s",' + '"pll_text":"%s","rx_text":"%s","tx_text":"%s","seq_text":"%s",' +
'"xvtr_current":%d,"xvtr_bands":%s,"freq_mhz_digits":%d,' + '"xvtr_current":%d,"xvtr_bands":%s,"freq_mhz_digits":%d,' +
'"fmstep_idx":%d,"devices":%s,"rate_presets":%s,"bands":%s}', '"fmstep_idx":%d,"devices":%s,"rate_presets":%s,"bands":%s,' +
'"freq_min":%.0f,"freq_max":%.0f}',
[FFreq, FVfoB, FActiveVfo, [FFreq, FVfoB, FActiveVfo,
FMode, MODE_N[FMode mod 8], FMode, MODE_N[FMode mod 8],
FFilterIdx, FFilterBW, FFilterIdx, FFilterBW,
@@ -1675,7 +1693,8 @@ begin
JsonEscape(FSupplyText), JsonEscape(FPLLText), JsonEscape(FRXText), JsonEscape(FSupplyText), JsonEscape(FPLLText), JsonEscape(FRXText),
JsonEscape(FTXText), JsonEscape(FSeqText), JsonEscape(FTXText), JsonEscape(FSeqText),
FCurrentXvtr, XvtrJson, FFreqMhzDigits, FCurrentXvtr, XvtrJson, FFreqMhzDigits,
FFMStepIdx, DevJson, RateJson, BandJson FFMStepIdx, DevJson, RateJson, BandJson,
FFreqMin, FFreqMax
], FS); ], FS);
finally finally
FStateLock.Leave; FStateLock.Leave;
+1
View File
@@ -314,6 +314,7 @@ begin
Freqs[i] := Plan[i].FreqHz; Freqs[i] := Plan[i].FreqHz;
end; end;
FWeb.SetBands(Names, Freqs); FWeb.SetBands(Names, Freqs);
with FCtrl.BackendCaps do FWeb.SetFreqRange(MinFreqHz, MaxFreqHz);
end; end;
// --- Входящие web-команды (WS-поток) → маршалинг в главный поток --- // --- Входящие web-команды (WS-поток) → маршалинг в главный поток ---