Fix XVTR/FM frequency, mode and step persistence

- Fix web spectrum scroll resetting freq to XVTR minimum (same root
  cause as desktop: hardcoded 60 MHz clamp; now delegates to server)
- Save/restore FM mode, NFM/WFM filter, squelch, CTCSS per XVTR slot
  and per HF band (CTCSSOn/ToneIdx added to TBandSettings; full state
  added to TXvtrEntry; ActivateXvtrBand restores all on slot switch)
- Fix UpdateFilterButtons always resetting NFM/WFM to default on
  mode restore; now preserves saved filter index
- Add FM Step tuning: panel with STEP toggle + 6.25/12.5/20/25 kHz
  selector (popup); mouse wheel uses selected step in FM mode;
  state saved per band and XVTR slot

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-18 22:08:11 +03:00
co-authored by Claude Sonnet 4.6
parent 87f449c737
commit c4cd8afb06
3 changed files with 241 additions and 27 deletions
+180 -14
View File
@@ -104,6 +104,12 @@ const
'156.7', '162.2', '167.9', '173.8', '179.9', '186.2', '192.8', '203.5',
'210.7', '218.1', '225.7', '233.6', '241.8', '250.3');
// FM Step tuning
FM_STEP_COUNT = 4;
FM_STEP_HZ: array[0..3] of Integer = (6250, 12500, 20000, 25000);
FM_STEP_NAMES: array[0..3] of string = ('6.25k', '12.5k', '20k', '25k');
FM_STEP_DEF = 3; // 25 kHz
BAND_NAMES: array[0..BAND_COUNT-1] of string = (
'160m','80m','60m','40m','30m','20m','17m','15m','12m','10m','6m');
BAND_FREQ: array[0..BAND_COUNT-1] of Double = (
@@ -258,6 +264,8 @@ type
FFMCTCSSToneIdx: Integer;
FFMSQOn: Boolean;
FFMSQLevel: Integer;
FFMStepOn: Boolean;
FFMStepIdx: Integer;
FWfManualHigh: Double;
FWfManualLow: Double;
FWfAGCOffset: Double;
@@ -373,6 +381,11 @@ type
BtnFMCTCSSTone: TFlatButton;
PanelFMCTCSSPop: TPanel;
FBtnCTCSSTones: array[0..CTCSS_COUNT - 1] of TFlatButton;
PanelFMStep: TPanel;
BtnFMStep: TFlatButton;
BtnFMStepSel: TFlatButton;
PanelFMStepPop: TPanel;
FBtnFMSteps: array[0..FM_STEP_COUNT - 1] of TFlatButton;
PanelRX: TPanel;
BtnAGCMode: array[0..4] of TFlatButton; // FAST MED SLOW LONG OFF
@@ -491,6 +504,11 @@ type
procedure BtnFMToneSelectClick(Sender: TObject);
procedure SetFMCTCSSTone(Idx: Integer);
procedure CloseCTCSSPopup;
procedure SetFMStep(Idx: Integer);
procedure CloseFMStepPopup;
procedure BtnFMStepClick(Sender: TObject);
procedure BtnFMStepSelClick(Sender: TObject);
procedure BtnFMStepSelectClick(Sender: TObject);
procedure PbSpectrumMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
procedure PbSpectrumMouseMove(Sender: TObject; Shift: TShiftState;
@@ -910,8 +928,12 @@ begin
Result.AGCTop := FAGCTop;
Result.CTun := FCTun;
Result.SpanHz := FSpanHz;
Result.FMSQOn := FFMSQOn;
Result.FMSQLevel := FFMSQLevel;
Result.FMSQOn := FFMSQOn;
Result.FMSQLevel := FFMSQLevel;
Result.CTCSSOn := FFMCTCSSOn;
Result.CTCSSToneIdx := FFMCTCSSToneIdx;
Result.FMStepOn := FFMStepOn;
Result.FMStepIdx := FFMStepIdx;
// Waterfall AGC/NF — глобальные (не диапазонные)
end;
@@ -998,7 +1020,17 @@ begin
if FCurrentXvtr >= 0 then
begin
if (FCurrentXvtr < CFG_XVTR_COUNT) then
FXvtrSettings.Entries[FCurrentXvtr].LastFreq := FVfoA;
begin
FXvtrSettings.Entries[FCurrentXvtr].LastFreq := FVfoA;
FXvtrSettings.Entries[FCurrentXvtr].LastMode := FMode;
FXvtrSettings.Entries[FCurrentXvtr].LastFilterIdx := FFilter;
FXvtrSettings.Entries[FCurrentXvtr].LastFMSQOn := FFMSQOn;
FXvtrSettings.Entries[FCurrentXvtr].LastFMSQLevel := FFMSQLevel;
FXvtrSettings.Entries[FCurrentXvtr].LastCTCSSOn := FFMCTCSSOn;
FXvtrSettings.Entries[FCurrentXvtr].LastCTCSSToneIdx := FFMCTCSSToneIdx;
FXvtrSettings.Entries[FCurrentXvtr].LastFMStepOn := FFMStepOn;
FXvtrSettings.Entries[FCurrentXvtr].LastFMStepIdx := FFMStepIdx;
end;
FSettings.SaveXvtr(FDevMAC, FXvtrSettings);
Exit;
end;
@@ -1047,10 +1079,17 @@ begin
end;
FSpecView.AGCTop := FAGCTop;
// --- FM Squelch ---
// --- FM Squelch + CTCSS ---
FFMSQOn := B.FMSQOn;
FFMSQLevel := B.FMSQLevel;
// UI update happens inside UpdateFilterButtons (called above) when mode=FM
FFMCTCSSOn := B.CTCSSOn;
if BtnFMCTCSS <> nil then StyleButton(BtnFMCTCSS, FFMCTCSSOn);
SetFMCTCSSTone(B.CTCSSToneIdx);
ApplyFMSquelch;
FFMStepOn := B.FMStepOn;
SetFMStep(B.FMStepIdx);
if BtnFMStep <> nil then StyleButton(BtnFMStep, FFMStepOn);
// --- CTUN ---
FCTun := B.CTun;
@@ -1139,6 +1178,8 @@ begin
FFMCTCSSToneIdx := 0;
FFMSQOn := False;
FFMSQLevel := 30;
FFMStepOn := True;
FFMStepIdx := FM_STEP_DEF;
FSpectrumBufCount := 1024;
FWaterfallBufCount := 1024;
FWaterfallFrameInterval := 2;
@@ -1793,6 +1834,37 @@ begin
FBtnCTCSSTones[i] := B;
end;
// FM Step panel (visible only in FM mode, between Filter and SQL)
PanelFMStep := TPanel.Create(Self);
PanelFMStep.Parent := PanelLeft;
PanelFMStep.SetBounds(0, Y, LEFT_W, 50);
PanelFMStep.BevelOuter := bvNone;
PanelFMStep.Visible := False;
MakeLbl(PanelFMStep, 'STEP', 4, 4);
W := LEFT_W div 3 - 2;
BtnFMStep := MakeBtn(PanelFMStep, 'STEP', 2, 20, W, BTN_H, BtnFMStepClick);
StyleButton(BtnFMStep, FFMStepOn);
BtnFMStepSel := MakeBtn(PanelFMStep, FM_STEP_NAMES[FFMStepIdx],
W + 4, 20, LEFT_W - W - 8, BTN_H, BtnFMStepSelClick);
StyleButton(BtnFMStepSel, False);
// Step picker popup (child of PanelLeft)
PanelFMStepPop := TPanel.Create(Self);
PanelFMStepPop.Parent := PanelLeft;
PanelFMStepPop.BevelOuter := bvNone;
PanelFMStepPop.Color := CLR_PANEL;
PanelFMStepPop.Visible := False;
W := (LEFT_W - 4) div FM_STEP_COUNT;
PanelFMStepPop.SetBounds(0, 0, LEFT_W, BTN_SM);
for i := 0 to FM_STEP_COUNT - 1 do
begin
B := MakeBtn(PanelFMStepPop, FM_STEP_NAMES[i],
2 + i * W, 0, W - 2, BTN_SM, BtnFMStepSelectClick);
B.Tag := i;
StyleButton(B, i = FFMStepIdx);
FBtnFMSteps[i] := B;
end;
// CTUN / DUP buttons (display-related)
BtnCTun := MakeBtn(PanelLeft, 'CTUN', 2, Y, LEFT_W div 3 - 2, BTN_H, BtnCTunClick);
BtnCTun.Tag := 0;
@@ -2394,6 +2466,8 @@ begin
if PanelFMSQ <> nil then DP(PanelFMSQ);
if PanelFMCTCSS <> nil then DP(PanelFMCTCSS);
if PanelFMCTCSSPop <> nil then DP(PanelFMCTCSSPop);
if PanelFMStep <> nil then DP(PanelFMStep);
if PanelFMStepPop <> nil then DP(PanelFMStepPop);
DP(PanelRX); DP(PanelTX);
DP(PanelRight); DP(PanelSpanButtons);
@@ -4037,9 +4111,9 @@ begin
begin
// FM: show only NFM and WFM buttons, resize them to fill the row
CloseCTCSSPopup;
FFilter := FILT_FM_DEF;
FFilterBW := FILT_FM_BW[FILT_FM_DEF];
FFMDeviation := FILT_FM_DEV[FILT_FM_DEF];
if FFilter > 1 then FFilter := FILT_FM_DEF; // sanitize, keep NFM/WFM as-is
FFilterBW := FILT_FM_BW[FFilter];
FFMDeviation := FILT_FM_DEV[FFilter];
W := (PanelFilter.Width - 6) div 2;
BtnFilter[0].Caption := FILT_FM_NAMES[0];
BtnFilter[0].Left := 2;
@@ -4064,6 +4138,12 @@ begin
LblFMSQ.Caption := IntToStr(FFMSQLevel);
end;
if PanelFMCTCSS <> nil then PanelFMCTCSS.Visible := True;
if PanelFMStep <> nil then
begin
StyleButton(BtnFMStep, FFMStepOn);
BtnFMStepSel.Caption := FM_STEP_NAMES[FFMStepIdx];
PanelFMStep.Visible := True;
end;
RelayoutBelowBands;
Exit;
end;
@@ -4072,6 +4152,7 @@ begin
PanelFilter.Height := 74;
if PanelFMSQ <> nil then PanelFMSQ.Visible := False;
if PanelFMCTCSS <> nil then PanelFMCTCSS.Visible := False;
if PanelFMStep <> nil then begin CloseFMStepPopup; PanelFMStep.Visible := False; end;
W := (PanelFilter.Width - 6) div 5;
for i := 0 to FILT_COUNT - 1 do
begin
@@ -4697,6 +4778,63 @@ procedure TMainForm.CloseCTCSSPopup;
begin
if (PanelFMCTCSSPop <> nil) and PanelFMCTCSSPop.Visible then
PanelFMCTCSSPop.Visible := False;
CloseFMStepPopup;
end;
procedure TMainForm.CloseFMStepPopup;
begin
if (PanelFMStepPop <> nil) and PanelFMStepPop.Visible then
PanelFMStepPop.Visible := False;
end;
procedure TMainForm.SetFMStep(Idx: Integer);
var OldIdx: Integer;
begin
OldIdx := FFMStepIdx;
FFMStepIdx := Idx;
if BtnFMStepSel <> nil then
begin
BtnFMStepSel.Caption := FM_STEP_NAMES[Idx];
BtnFMStepSel.Invalidate;
end;
if (OldIdx <> Idx) and (FBtnFMSteps[OldIdx] <> nil) then
FBtnFMSteps[OldIdx].Active := False;
if FBtnFMSteps[Idx] <> nil then
FBtnFMSteps[Idx].Active := True;
end;
procedure TMainForm.BtnFMStepClick(Sender: TObject);
begin
FFMStepOn := not FFMStepOn;
StyleButton(BtnFMStep, FFMStepOn);
BtnFMStep.Repaint;
SaveCurrentBand;
end;
procedure TMainForm.BtnFMStepSelClick(Sender: TObject);
var Y0: Integer;
begin
if PanelFMStepPop.Visible then
begin
CloseFMStepPopup;
Exit;
end;
CloseCTCSSPopup;
Y0 := PanelFMStep.Top + PanelFMStep.Height + 2;
if Y0 + PanelFMStepPop.Height > PanelLeft.ClientHeight then
Y0 := PanelFMStep.Top - PanelFMStepPop.Height - 2;
if Y0 < 0 then Y0 := 0;
PanelFMStepPop.Top := Y0;
PanelFMStepPop.Left := 0;
PanelFMStepPop.BringToFront;
PanelFMStepPop.Visible := True;
end;
procedure TMainForm.BtnFMStepSelectClick(Sender: TObject);
begin
CloseFMStepPopup;
SetFMStep((Sender as TFlatButton).Tag);
SaveCurrentBand;
end;
procedure TMainForm.SetFMCTCSSTone(Idx: Integer);
@@ -5967,6 +6105,10 @@ begin
Y := PanelBands.Top + PanelBands.Height + 2;
PanelMode.Top := Y; Y := Y + PanelMode.Height + 2;
PanelFilter.Top := Y; Y := Y + PanelFilter.Height + 2;
if (PanelFMStep <> nil) and PanelFMStep.Visible then
begin
PanelFMStep.Top := Y; Y := Y + PanelFMStep.Height + 2;
end;
if (PanelFMSQ <> nil) and PanelFMSQ.Visible then
begin
PanelFMSQ.Top := Y; Y := Y + PanelFMSQ.Height + 2;
@@ -6066,16 +6208,16 @@ procedure TMainForm.ActivateXvtrBand(Idx: Integer);
var
Vis: Double;
i: Integer;
E: TXvtrEntry;
begin
if (Idx < 0) or (Idx >= CFG_XVTR_COUNT) then Exit;
if not FXvtrSettings.Entries[Idx].Enabled then Exit;
FCurrentXvtr := Idx;
E := FXvtrSettings.Entries[Idx];
// Выбор начальной частоты: LastFreq если в диапазоне, иначе середина
Vis := FXvtrSettings.Entries[Idx].LastFreq;
if (Vis < FXvtrSettings.Entries[Idx].FreqBegin) or
(Vis > FXvtrSettings.Entries[Idx].FreqEnd) then
Vis := (FXvtrSettings.Entries[Idx].FreqBegin +
FXvtrSettings.Entries[Idx].FreqEnd) / 2.0;
Vis := E.LastFreq;
if (Vis < E.FreqBegin) or (Vis > E.FreqEnd) then
Vis := (E.FreqBegin + E.FreqEnd) / 2.0;
// Отключаем кнопки HF band
for i := 0 to BAND_COUNT - 1 do
StyleButton(BtnBand[i], False);
@@ -6085,6 +6227,22 @@ begin
StyleButton(BtnXvtrBand[i], i = Idx);
// Применяем XVTR режим к сети (XVTR enable bit + DDC IN + DisablePA + RX ant)
ApplyXvtrToNetwork;
// Восстанавливаем Mode + FM Squelch + CTCSS для этого XVTR-слота
FFMSQOn := E.LastFMSQOn;
FFMSQLevel := E.LastFMSQLevel;
FMode := E.LastMode;
FFilter := E.LastFilterIdx;
for i := 0 to MODE_COUNT - 1 do StyleButton(BtnMode[i], i = FMode);
if FWDSPReady then FDSPEngine.SetMode(FMode);
UpdateFilterButtons; // показывает/скрывает FM-панели, обновляет SQL UI
ApplyModeFilter;
FFMCTCSSOn := E.LastCTCSSOn;
if BtnFMCTCSS <> nil then StyleButton(BtnFMCTCSS, FFMCTCSSOn);
SetFMCTCSSTone(E.LastCTCSSToneIdx);
ApplyFMSquelch;
FFMStepOn := E.LastFMStepOn;
SetFMStep(E.LastFMStepIdx);
if BtnFMStep <> nil then StyleButton(BtnFMStep, FFMStepOn);
// VFO на видимую частоту; FCenterFreq тоже visible (как и для HF — DDC переход
// в IF происходит только в момент UpdateState/SetRunAndFreq через XvtrTranslate).
FVfoA := Vis;
@@ -6210,7 +6368,10 @@ var
Base: Int64;
NewFreq: Int64;
begin
if (ssCtrl in Shift) and (ssShift in Shift) then Step := 1000000
if (FMode = MODE_FM) and FFMStepOn and
not ((ssCtrl in Shift) or (ssShift in Shift)) then
Step := FM_STEP_HZ[FFMStepIdx]
else if (ssCtrl in Shift) and (ssShift in Shift) then Step := 1000000
else if ssCtrl in Shift then Step := 1000
else if ssShift in Shift then Step := 10
else Step := 100;
@@ -6230,7 +6391,12 @@ begin
NewFreq := Base; // снэп к нижней границе
end;
NewFreq := Max(30000, Min(60000000, NewFreq));
// В XVTR-режиме HF-клэмп не применяем: ApplyVfoA сам ограничит по FreqBegin/FreqEnd
if (FCurrentXvtr >= 0) and (FCurrentXvtr < CFG_XVTR_COUNT) and
FXvtrSettings.Entries[FCurrentXvtr].Enabled then
NewFreq := Max(0, NewFreq)
else
NewFreq := Max(30000, Min(60000000, NewFreq));
ApplyVfoA(NewFreq);
Handled := True;
+56 -10
View File
@@ -29,8 +29,12 @@ type
AGCTop: Integer;
CTun: Boolean;
SpanHz: Double;
FMSQOn: Boolean;
FMSQLevel: Integer; // 0..100 → WDSP threshold 0.0..0.5
FMSQOn: Boolean;
FMSQLevel: Integer; // 0..100 → WDSP threshold 0.0..0.5
CTCSSOn: Boolean;
CTCSSToneIdx: Integer; // 0..37
FMStepOn: Boolean;
FMStepIdx: Integer; // 0..3 (6.25/12.5/20/25 kHz)
end;
// TX-настройки per-device. Хранятся в JSON-секции "tx" под MAC,
@@ -131,7 +135,15 @@ type
TXPower: Integer; // 0..100 % (drive level специально для XVTR)
DisablePA: Boolean; // True — отключить внутренний PA при TX (XVTR имеет свой)
RXAntenna: Byte; // 0=по-умолчанию(основной band), 1=ANT1, 2=ANT2, 3=ANT3
LastFreq: Double; // Hz — последняя tuned visible частота (BandStack)
LastFreq: Double; // Hz — последняя tuned visible частота (BandStack)
LastMode: Integer; // Mode (LSB=0,USB=1,…FM=5)
LastFilterIdx: Integer; // FM: 0=NFM, 1=WFM; иначе 0..FILT_COUNT-1
LastFMSQOn: Boolean;
LastFMSQLevel: Integer;
LastCTCSSOn: Boolean;
LastCTCSSToneIdx: Integer;
LastFMStepOn: Boolean;
LastFMStepIdx: Integer;
end;
TXvtrSettings = record
@@ -327,8 +339,12 @@ begin
B.AGCTop := 90;
B.CTun := False;
B.SpanHz := 192000;
B.FMSQOn := False;
B.FMSQLevel := 30;
B.FMSQOn := False;
B.FMSQLevel := 30;
B.CTCSSOn := False;
B.CTCSSToneIdx := 0;
B.FMStepOn := True;
B.FMStepIdx := 3; // 25 kHz
end;
class procedure TSettingsManager.DefaultGlobal(out G: TGlobalSettings);
@@ -697,6 +713,8 @@ begin
JW(O,'agc_mode',B.AGCMode); JW(O,'agc_top',B.AGCTop);
JW(O,'ctun',B.CTun); JW(O,'span_hz',B.SpanHz);
JW(O,'fmsq_on',B.FMSQOn); JW(O,'fmsq_level',B.FMSQLevel);
JW(O,'ctcss_on',B.CTCSSOn); JW(O,'ctcss_idx',B.CTCSSToneIdx);
JW(O,'fmstep_on',B.FMStepOn); JW(O,'fmstep_idx',B.FMStepIdx);
end;
function TSettingsManager.LoadBand(const MAC: array of Byte; BandIdx: Integer;
@@ -717,8 +735,12 @@ begin
B.AGCTop := JI(O,'agc_top', B.AGCTop);
B.CTun := JB(O,'ctun', B.CTun);
B.SpanHz := JD(O,'span_hz', B.SpanHz);
B.FMSQOn := JB(O,'fmsq_on', B.FMSQOn);
B.FMSQLevel := JI(O,'fmsq_level', B.FMSQLevel);
B.FMSQOn := JB(O,'fmsq_on', B.FMSQOn);
B.FMSQLevel := JI(O,'fmsq_level', B.FMSQLevel);
B.CTCSSOn := JB(O,'ctcss_on', B.CTCSSOn);
B.CTCSSToneIdx := EnsureRange(JI(O,'ctcss_idx', B.CTCSSToneIdx), 0, 37);
B.FMStepOn := JB(O,'fmstep_on', B.FMStepOn);
B.FMStepIdx := EnsureRange(JI(O,'fmstep_idx', B.FMStepIdx), 0, 3);
end;
procedure TSettingsManager.SaveWindowBounds(L, T, W, H: Integer);
@@ -1090,7 +1112,15 @@ begin
X.Entries[i].TXPower := 100;
X.Entries[i].DisablePA := False;
X.Entries[i].RXAntenna := 0;
X.Entries[i].LastFreq := 0.0;
X.Entries[i].LastFreq := 0.0;
X.Entries[i].LastMode := 1; // USB — VHF SSB calling default
X.Entries[i].LastFilterIdx := 0; // NFM
X.Entries[i].LastFMSQOn := False;
X.Entries[i].LastFMSQLevel := 30;
X.Entries[i].LastCTCSSOn := False;
X.Entries[i].LastCTCSSToneIdx := 0;
X.Entries[i].LastFMStepOn := True;
X.Entries[i].LastFMStepIdx := 3; // 25 kHz
end;
// Шаблоны (пользователь должен сам Enable, чтобы трансверторы появились)
X.Entries[0].ButtonText := '2m';
@@ -1135,7 +1165,15 @@ begin
X.Entries[i].TXPower := EnsureRange(JI(EObj, 'tx_pwr', X.Entries[i].TXPower), 0, 100);
X.Entries[i].DisablePA := JB(EObj, 'dis_pa', X.Entries[i].DisablePA);
X.Entries[i].RXAntenna := EnsureRange(JI(EObj, 'rx_ant', X.Entries[i].RXAntenna), 0, 3);
X.Entries[i].LastFreq := JD(EObj, 'last_freq', X.Entries[i].LastFreq);
X.Entries[i].LastFreq := JD(EObj, 'last_freq', X.Entries[i].LastFreq);
X.Entries[i].LastMode := EnsureRange(JI(EObj, 'last_mode', X.Entries[i].LastMode), 0, 7);
X.Entries[i].LastFilterIdx := EnsureRange(JI(EObj, 'last_filt', X.Entries[i].LastFilterIdx), 0, 9);
X.Entries[i].LastFMSQOn := JB(EObj, 'last_fmsq_on', X.Entries[i].LastFMSQOn);
X.Entries[i].LastFMSQLevel := EnsureRange(JI(EObj, 'last_fmsq_lvl', X.Entries[i].LastFMSQLevel), 0, 100);
X.Entries[i].LastCTCSSOn := JB(EObj, 'last_ctcss_on', X.Entries[i].LastCTCSSOn);
X.Entries[i].LastCTCSSToneIdx := EnsureRange(JI(EObj, 'last_ctcss_idx', X.Entries[i].LastCTCSSToneIdx), 0, 37);
X.Entries[i].LastFMStepOn := JB(EObj, 'last_fmstep_on', X.Entries[i].LastFMStepOn);
X.Entries[i].LastFMStepIdx := EnsureRange(JI(EObj, 'last_fmstep_idx', X.Entries[i].LastFMStepIdx), 0, 3);
// Валидация: LastFreq должен быть в [FreqBegin..FreqEnd]
if (X.Entries[i].LastFreq < X.Entries[i].FreqBegin) or
(X.Entries[i].LastFreq > X.Entries[i].FreqEnd) then
@@ -1165,7 +1203,15 @@ begin
JW(EObj, 'tx_pwr', X.Entries[i].TXPower);
JW(EObj, 'dis_pa', X.Entries[i].DisablePA);
JW(EObj, 'rx_ant', Integer(X.Entries[i].RXAntenna));
JW(EObj, 'last_freq', X.Entries[i].LastFreq);
JW(EObj, 'last_freq', X.Entries[i].LastFreq);
JW(EObj, 'last_mode', X.Entries[i].LastMode);
JW(EObj, 'last_filt', X.Entries[i].LastFilterIdx);
JW(EObj, 'last_fmsq_on', X.Entries[i].LastFMSQOn);
JW(EObj, 'last_fmsq_lvl', X.Entries[i].LastFMSQLevel);
JW(EObj, 'last_ctcss_on', X.Entries[i].LastCTCSSOn);
JW(EObj, 'last_ctcss_idx',X.Entries[i].LastCTCSSToneIdx);
JW(EObj, 'last_fmstep_on', X.Entries[i].LastFMStepOn);
JW(EObj, 'last_fmstep_idx',X.Entries[i].LastFMStepIdx);
end;
end;
+5 -3
View File
@@ -646,8 +646,10 @@ begin
'function activeVfoHz(){return activeVfo==="A"?vfoA:vfoB;}' +
'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 vfoMin(){return XVTR_CUR>=0?0:30000;}' +
'function vfoMax(){return XVTR_CUR>=0?999999999999:60000000;}' +
'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),30000,60000000);' +
'const nf=cl(dir>0?base+st:(Math.round(activeVfoHz())===base?base-st:base),vfoMin(),vfoMax());' +
'setActiveVfoHz(nf);wheelActiveTs=Date.now();' +
'if(activeVfo==="A"){cur.vfo_a_hz=nf;}else{cur.vfo_b_hz=nf;}' +
'renderVfos();pend("freq");ws2({cmd:"freq",hz:nf});}' +
@@ -674,7 +676,7 @@ begin
'localCenter=Math.round(dragCen0-dHz);' +
'ws2({cmd:"set_center",hz:localCenter});' +
'}else{' +
'const nf=cl(Math.round(dragVfo0-dHz),30000,60000000);' +
'const nf=cl(Math.round(dragVfo0-dHz),vfoMin(),vfoMax());' +
'setActiveVfoHz(nf);localCenter=nf;' +
'if(activeVfo==="A")cur.vfo_a_hz=nf;else cur.vfo_b_hz=nf;' +
'renderVfos();pend("freq");ws2({cmd:"freq",hz:nf});' +
@@ -686,7 +688,7 @@ begin
'if(dragging&&Math.abs(e.offsetX-dragX0)<4){' +
// клик — не трогаем localCenter: фильтр встаёт точно туда куда кликнули.
// Спектр остаётся на месте, сервер пришлёт center_hz через ~200ms и всё перецентрируется.
'const nf=cl(Math.round(xToHz(e.offsetX,cv.clientWidth)/100)*100,30000,60000000);' +
'const nf=cl(Math.round(xToHz(e.offsetX,cv.clientWidth)/100)*100,vfoMin(),vfoMax());' +
'setActiveVfoHz(nf);' +
'if(activeVfo==="A")cur.vfo_a_hz=nf;else cur.vfo_b_hz=nf;' +
'renderVfos();pend("freq");ws2({cmd:"freq",hz:nf});' +