mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 20:37:33 +00:00
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:
+180
-14
@@ -104,6 +104,12 @@ const
|
|||||||
'156.7', '162.2', '167.9', '173.8', '179.9', '186.2', '192.8', '203.5',
|
'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');
|
'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 = (
|
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 = (
|
BAND_FREQ: array[0..BAND_COUNT-1] of Double = (
|
||||||
@@ -258,6 +264,8 @@ type
|
|||||||
FFMCTCSSToneIdx: Integer;
|
FFMCTCSSToneIdx: Integer;
|
||||||
FFMSQOn: Boolean;
|
FFMSQOn: Boolean;
|
||||||
FFMSQLevel: Integer;
|
FFMSQLevel: Integer;
|
||||||
|
FFMStepOn: Boolean;
|
||||||
|
FFMStepIdx: Integer;
|
||||||
FWfManualHigh: Double;
|
FWfManualHigh: Double;
|
||||||
FWfManualLow: Double;
|
FWfManualLow: Double;
|
||||||
FWfAGCOffset: Double;
|
FWfAGCOffset: Double;
|
||||||
@@ -373,6 +381,11 @@ type
|
|||||||
BtnFMCTCSSTone: TFlatButton;
|
BtnFMCTCSSTone: TFlatButton;
|
||||||
PanelFMCTCSSPop: TPanel;
|
PanelFMCTCSSPop: TPanel;
|
||||||
FBtnCTCSSTones: array[0..CTCSS_COUNT - 1] of TFlatButton;
|
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;
|
PanelRX: TPanel;
|
||||||
BtnAGCMode: array[0..4] of TFlatButton; // FAST MED SLOW LONG OFF
|
BtnAGCMode: array[0..4] of TFlatButton; // FAST MED SLOW LONG OFF
|
||||||
@@ -491,6 +504,11 @@ type
|
|||||||
procedure BtnFMToneSelectClick(Sender: TObject);
|
procedure BtnFMToneSelectClick(Sender: TObject);
|
||||||
procedure SetFMCTCSSTone(Idx: Integer);
|
procedure SetFMCTCSSTone(Idx: Integer);
|
||||||
procedure CloseCTCSSPopup;
|
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;
|
procedure PbSpectrumMouseDown(Sender: TObject; Button: TMouseButton;
|
||||||
Shift: TShiftState; X, Y: Integer);
|
Shift: TShiftState; X, Y: Integer);
|
||||||
procedure PbSpectrumMouseMove(Sender: TObject; Shift: TShiftState;
|
procedure PbSpectrumMouseMove(Sender: TObject; Shift: TShiftState;
|
||||||
@@ -910,8 +928,12 @@ begin
|
|||||||
Result.AGCTop := FAGCTop;
|
Result.AGCTop := FAGCTop;
|
||||||
Result.CTun := FCTun;
|
Result.CTun := FCTun;
|
||||||
Result.SpanHz := FSpanHz;
|
Result.SpanHz := FSpanHz;
|
||||||
Result.FMSQOn := FFMSQOn;
|
Result.FMSQOn := FFMSQOn;
|
||||||
Result.FMSQLevel := FFMSQLevel;
|
Result.FMSQLevel := FFMSQLevel;
|
||||||
|
Result.CTCSSOn := FFMCTCSSOn;
|
||||||
|
Result.CTCSSToneIdx := FFMCTCSSToneIdx;
|
||||||
|
Result.FMStepOn := FFMStepOn;
|
||||||
|
Result.FMStepIdx := FFMStepIdx;
|
||||||
// Waterfall AGC/NF — глобальные (не диапазонные)
|
// Waterfall AGC/NF — глобальные (не диапазонные)
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -998,7 +1020,17 @@ begin
|
|||||||
if FCurrentXvtr >= 0 then
|
if FCurrentXvtr >= 0 then
|
||||||
begin
|
begin
|
||||||
if (FCurrentXvtr < CFG_XVTR_COUNT) then
|
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);
|
FSettings.SaveXvtr(FDevMAC, FXvtrSettings);
|
||||||
Exit;
|
Exit;
|
||||||
end;
|
end;
|
||||||
@@ -1047,10 +1079,17 @@ begin
|
|||||||
end;
|
end;
|
||||||
FSpecView.AGCTop := FAGCTop;
|
FSpecView.AGCTop := FAGCTop;
|
||||||
|
|
||||||
// --- FM Squelch ---
|
// --- FM Squelch + CTCSS ---
|
||||||
FFMSQOn := B.FMSQOn;
|
FFMSQOn := B.FMSQOn;
|
||||||
FFMSQLevel := B.FMSQLevel;
|
FFMSQLevel := B.FMSQLevel;
|
||||||
// UI update happens inside UpdateFilterButtons (called above) when mode=FM
|
// 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 ---
|
// --- CTUN ---
|
||||||
FCTun := B.CTun;
|
FCTun := B.CTun;
|
||||||
@@ -1139,6 +1178,8 @@ begin
|
|||||||
FFMCTCSSToneIdx := 0;
|
FFMCTCSSToneIdx := 0;
|
||||||
FFMSQOn := False;
|
FFMSQOn := False;
|
||||||
FFMSQLevel := 30;
|
FFMSQLevel := 30;
|
||||||
|
FFMStepOn := True;
|
||||||
|
FFMStepIdx := FM_STEP_DEF;
|
||||||
FSpectrumBufCount := 1024;
|
FSpectrumBufCount := 1024;
|
||||||
FWaterfallBufCount := 1024;
|
FWaterfallBufCount := 1024;
|
||||||
FWaterfallFrameInterval := 2;
|
FWaterfallFrameInterval := 2;
|
||||||
@@ -1793,6 +1834,37 @@ begin
|
|||||||
FBtnCTCSSTones[i] := B;
|
FBtnCTCSSTones[i] := B;
|
||||||
end;
|
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)
|
// CTUN / DUP buttons (display-related)
|
||||||
BtnCTun := MakeBtn(PanelLeft, 'CTUN', 2, Y, LEFT_W div 3 - 2, BTN_H, BtnCTunClick);
|
BtnCTun := MakeBtn(PanelLeft, 'CTUN', 2, Y, LEFT_W div 3 - 2, BTN_H, BtnCTunClick);
|
||||||
BtnCTun.Tag := 0;
|
BtnCTun.Tag := 0;
|
||||||
@@ -2394,6 +2466,8 @@ begin
|
|||||||
if PanelFMSQ <> nil then DP(PanelFMSQ);
|
if PanelFMSQ <> nil then DP(PanelFMSQ);
|
||||||
if PanelFMCTCSS <> nil then DP(PanelFMCTCSS);
|
if PanelFMCTCSS <> nil then DP(PanelFMCTCSS);
|
||||||
if PanelFMCTCSSPop <> nil then DP(PanelFMCTCSSPop);
|
if PanelFMCTCSSPop <> nil then DP(PanelFMCTCSSPop);
|
||||||
|
if PanelFMStep <> nil then DP(PanelFMStep);
|
||||||
|
if PanelFMStepPop <> nil then DP(PanelFMStepPop);
|
||||||
DP(PanelRX); DP(PanelTX);
|
DP(PanelRX); DP(PanelTX);
|
||||||
DP(PanelRight); DP(PanelSpanButtons);
|
DP(PanelRight); DP(PanelSpanButtons);
|
||||||
|
|
||||||
@@ -4037,9 +4111,9 @@ begin
|
|||||||
begin
|
begin
|
||||||
// FM: show only NFM and WFM buttons, resize them to fill the row
|
// FM: show only NFM and WFM buttons, resize them to fill the row
|
||||||
CloseCTCSSPopup;
|
CloseCTCSSPopup;
|
||||||
FFilter := FILT_FM_DEF;
|
if FFilter > 1 then FFilter := FILT_FM_DEF; // sanitize, keep NFM/WFM as-is
|
||||||
FFilterBW := FILT_FM_BW[FILT_FM_DEF];
|
FFilterBW := FILT_FM_BW[FFilter];
|
||||||
FFMDeviation := FILT_FM_DEV[FILT_FM_DEF];
|
FFMDeviation := FILT_FM_DEV[FFilter];
|
||||||
W := (PanelFilter.Width - 6) div 2;
|
W := (PanelFilter.Width - 6) div 2;
|
||||||
BtnFilter[0].Caption := FILT_FM_NAMES[0];
|
BtnFilter[0].Caption := FILT_FM_NAMES[0];
|
||||||
BtnFilter[0].Left := 2;
|
BtnFilter[0].Left := 2;
|
||||||
@@ -4064,6 +4138,12 @@ begin
|
|||||||
LblFMSQ.Caption := IntToStr(FFMSQLevel);
|
LblFMSQ.Caption := IntToStr(FFMSQLevel);
|
||||||
end;
|
end;
|
||||||
if PanelFMCTCSS <> nil then PanelFMCTCSS.Visible := True;
|
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;
|
RelayoutBelowBands;
|
||||||
Exit;
|
Exit;
|
||||||
end;
|
end;
|
||||||
@@ -4072,6 +4152,7 @@ begin
|
|||||||
PanelFilter.Height := 74;
|
PanelFilter.Height := 74;
|
||||||
if PanelFMSQ <> nil then PanelFMSQ.Visible := False;
|
if PanelFMSQ <> nil then PanelFMSQ.Visible := False;
|
||||||
if PanelFMCTCSS <> nil then PanelFMCTCSS.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;
|
W := (PanelFilter.Width - 6) div 5;
|
||||||
for i := 0 to FILT_COUNT - 1 do
|
for i := 0 to FILT_COUNT - 1 do
|
||||||
begin
|
begin
|
||||||
@@ -4697,6 +4778,63 @@ procedure TMainForm.CloseCTCSSPopup;
|
|||||||
begin
|
begin
|
||||||
if (PanelFMCTCSSPop <> nil) and PanelFMCTCSSPop.Visible then
|
if (PanelFMCTCSSPop <> nil) and PanelFMCTCSSPop.Visible then
|
||||||
PanelFMCTCSSPop.Visible := False;
|
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;
|
end;
|
||||||
|
|
||||||
procedure TMainForm.SetFMCTCSSTone(Idx: Integer);
|
procedure TMainForm.SetFMCTCSSTone(Idx: Integer);
|
||||||
@@ -5967,6 +6105,10 @@ begin
|
|||||||
Y := PanelBands.Top + PanelBands.Height + 2;
|
Y := PanelBands.Top + PanelBands.Height + 2;
|
||||||
PanelMode.Top := Y; Y := Y + PanelMode.Height + 2;
|
PanelMode.Top := Y; Y := Y + PanelMode.Height + 2;
|
||||||
PanelFilter.Top := Y; Y := Y + PanelFilter.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
|
if (PanelFMSQ <> nil) and PanelFMSQ.Visible then
|
||||||
begin
|
begin
|
||||||
PanelFMSQ.Top := Y; Y := Y + PanelFMSQ.Height + 2;
|
PanelFMSQ.Top := Y; Y := Y + PanelFMSQ.Height + 2;
|
||||||
@@ -6066,16 +6208,16 @@ procedure TMainForm.ActivateXvtrBand(Idx: Integer);
|
|||||||
var
|
var
|
||||||
Vis: Double;
|
Vis: Double;
|
||||||
i: Integer;
|
i: Integer;
|
||||||
|
E: TXvtrEntry;
|
||||||
begin
|
begin
|
||||||
if (Idx < 0) or (Idx >= CFG_XVTR_COUNT) then Exit;
|
if (Idx < 0) or (Idx >= CFG_XVTR_COUNT) then Exit;
|
||||||
if not FXvtrSettings.Entries[Idx].Enabled then Exit;
|
if not FXvtrSettings.Entries[Idx].Enabled then Exit;
|
||||||
FCurrentXvtr := Idx;
|
FCurrentXvtr := Idx;
|
||||||
|
E := FXvtrSettings.Entries[Idx];
|
||||||
// Выбор начальной частоты: LastFreq если в диапазоне, иначе середина
|
// Выбор начальной частоты: LastFreq если в диапазоне, иначе середина
|
||||||
Vis := FXvtrSettings.Entries[Idx].LastFreq;
|
Vis := E.LastFreq;
|
||||||
if (Vis < FXvtrSettings.Entries[Idx].FreqBegin) or
|
if (Vis < E.FreqBegin) or (Vis > E.FreqEnd) then
|
||||||
(Vis > FXvtrSettings.Entries[Idx].FreqEnd) then
|
Vis := (E.FreqBegin + E.FreqEnd) / 2.0;
|
||||||
Vis := (FXvtrSettings.Entries[Idx].FreqBegin +
|
|
||||||
FXvtrSettings.Entries[Idx].FreqEnd) / 2.0;
|
|
||||||
// Отключаем кнопки HF band
|
// Отключаем кнопки HF band
|
||||||
for i := 0 to BAND_COUNT - 1 do
|
for i := 0 to BAND_COUNT - 1 do
|
||||||
StyleButton(BtnBand[i], False);
|
StyleButton(BtnBand[i], False);
|
||||||
@@ -6085,6 +6227,22 @@ begin
|
|||||||
StyleButton(BtnXvtrBand[i], i = Idx);
|
StyleButton(BtnXvtrBand[i], i = Idx);
|
||||||
// Применяем XVTR режим к сети (XVTR enable bit + DDC IN + DisablePA + RX ant)
|
// Применяем XVTR режим к сети (XVTR enable bit + DDC IN + DisablePA + RX ant)
|
||||||
ApplyXvtrToNetwork;
|
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 переход
|
// VFO на видимую частоту; FCenterFreq тоже visible (как и для HF — DDC переход
|
||||||
// в IF происходит только в момент UpdateState/SetRunAndFreq через XvtrTranslate).
|
// в IF происходит только в момент UpdateState/SetRunAndFreq через XvtrTranslate).
|
||||||
FVfoA := Vis;
|
FVfoA := Vis;
|
||||||
@@ -6210,7 +6368,10 @@ var
|
|||||||
Base: Int64;
|
Base: Int64;
|
||||||
NewFreq: Int64;
|
NewFreq: Int64;
|
||||||
begin
|
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 ssCtrl in Shift then Step := 1000
|
||||||
else if ssShift in Shift then Step := 10
|
else if ssShift in Shift then Step := 10
|
||||||
else Step := 100;
|
else Step := 100;
|
||||||
@@ -6230,7 +6391,12 @@ begin
|
|||||||
NewFreq := Base; // снэп к нижней границе
|
NewFreq := Base; // снэп к нижней границе
|
||||||
end;
|
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);
|
ApplyVfoA(NewFreq);
|
||||||
|
|
||||||
Handled := True;
|
Handled := True;
|
||||||
|
|||||||
+56
-10
@@ -29,8 +29,12 @@ type
|
|||||||
AGCTop: Integer;
|
AGCTop: Integer;
|
||||||
CTun: Boolean;
|
CTun: Boolean;
|
||||||
SpanHz: Double;
|
SpanHz: Double;
|
||||||
FMSQOn: Boolean;
|
FMSQOn: Boolean;
|
||||||
FMSQLevel: Integer; // 0..100 → WDSP threshold 0.0..0.5
|
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;
|
end;
|
||||||
|
|
||||||
// TX-настройки per-device. Хранятся в JSON-секции "tx" под MAC,
|
// TX-настройки per-device. Хранятся в JSON-секции "tx" под MAC,
|
||||||
@@ -131,7 +135,15 @@ type
|
|||||||
TXPower: Integer; // 0..100 % (drive level специально для XVTR)
|
TXPower: Integer; // 0..100 % (drive level специально для XVTR)
|
||||||
DisablePA: Boolean; // True — отключить внутренний PA при TX (XVTR имеет свой)
|
DisablePA: Boolean; // True — отключить внутренний PA при TX (XVTR имеет свой)
|
||||||
RXAntenna: Byte; // 0=по-умолчанию(основной band), 1=ANT1, 2=ANT2, 3=ANT3
|
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;
|
end;
|
||||||
|
|
||||||
TXvtrSettings = record
|
TXvtrSettings = record
|
||||||
@@ -327,8 +339,12 @@ begin
|
|||||||
B.AGCTop := 90;
|
B.AGCTop := 90;
|
||||||
B.CTun := False;
|
B.CTun := False;
|
||||||
B.SpanHz := 192000;
|
B.SpanHz := 192000;
|
||||||
B.FMSQOn := False;
|
B.FMSQOn := False;
|
||||||
B.FMSQLevel := 30;
|
B.FMSQLevel := 30;
|
||||||
|
B.CTCSSOn := False;
|
||||||
|
B.CTCSSToneIdx := 0;
|
||||||
|
B.FMStepOn := True;
|
||||||
|
B.FMStepIdx := 3; // 25 kHz
|
||||||
end;
|
end;
|
||||||
|
|
||||||
class procedure TSettingsManager.DefaultGlobal(out G: TGlobalSettings);
|
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,'agc_mode',B.AGCMode); JW(O,'agc_top',B.AGCTop);
|
||||||
JW(O,'ctun',B.CTun); JW(O,'span_hz',B.SpanHz);
|
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,'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;
|
end;
|
||||||
|
|
||||||
function TSettingsManager.LoadBand(const MAC: array of Byte; BandIdx: Integer;
|
function TSettingsManager.LoadBand(const MAC: array of Byte; BandIdx: Integer;
|
||||||
@@ -717,8 +735,12 @@ begin
|
|||||||
B.AGCTop := JI(O,'agc_top', B.AGCTop);
|
B.AGCTop := JI(O,'agc_top', B.AGCTop);
|
||||||
B.CTun := JB(O,'ctun', B.CTun);
|
B.CTun := JB(O,'ctun', B.CTun);
|
||||||
B.SpanHz := JD(O,'span_hz', B.SpanHz);
|
B.SpanHz := JD(O,'span_hz', B.SpanHz);
|
||||||
B.FMSQOn := JB(O,'fmsq_on', B.FMSQOn);
|
B.FMSQOn := JB(O,'fmsq_on', B.FMSQOn);
|
||||||
B.FMSQLevel := JI(O,'fmsq_level', B.FMSQLevel);
|
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;
|
end;
|
||||||
|
|
||||||
procedure TSettingsManager.SaveWindowBounds(L, T, W, H: Integer);
|
procedure TSettingsManager.SaveWindowBounds(L, T, W, H: Integer);
|
||||||
@@ -1090,7 +1112,15 @@ begin
|
|||||||
X.Entries[i].TXPower := 100;
|
X.Entries[i].TXPower := 100;
|
||||||
X.Entries[i].DisablePA := False;
|
X.Entries[i].DisablePA := False;
|
||||||
X.Entries[i].RXAntenna := 0;
|
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;
|
end;
|
||||||
// Шаблоны (пользователь должен сам Enable, чтобы трансверторы появились)
|
// Шаблоны (пользователь должен сам Enable, чтобы трансверторы появились)
|
||||||
X.Entries[0].ButtonText := '2m';
|
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].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].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].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]
|
// Валидация: LastFreq должен быть в [FreqBegin..FreqEnd]
|
||||||
if (X.Entries[i].LastFreq < X.Entries[i].FreqBegin) or
|
if (X.Entries[i].LastFreq < X.Entries[i].FreqBegin) or
|
||||||
(X.Entries[i].LastFreq > X.Entries[i].FreqEnd) then
|
(X.Entries[i].LastFreq > X.Entries[i].FreqEnd) then
|
||||||
@@ -1165,7 +1203,15 @@ begin
|
|||||||
JW(EObj, 'tx_pwr', X.Entries[i].TXPower);
|
JW(EObj, 'tx_pwr', X.Entries[i].TXPower);
|
||||||
JW(EObj, 'dis_pa', X.Entries[i].DisablePA);
|
JW(EObj, 'dis_pa', X.Entries[i].DisablePA);
|
||||||
JW(EObj, 'rx_ant', Integer(X.Entries[i].RXAntenna));
|
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;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
|||||||
+5
-3
@@ -646,8 +646,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;}' +
|
||||||
|
'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;' +
|
'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();' +
|
'setActiveVfoHz(nf);wheelActiveTs=Date.now();' +
|
||||||
'if(activeVfo==="A"){cur.vfo_a_hz=nf;}else{cur.vfo_b_hz=nf;}' +
|
'if(activeVfo==="A"){cur.vfo_a_hz=nf;}else{cur.vfo_b_hz=nf;}' +
|
||||||
'renderVfos();pend("freq");ws2({cmd:"freq",hz:nf});}' +
|
'renderVfos();pend("freq");ws2({cmd:"freq",hz:nf});}' +
|
||||||
@@ -674,7 +676,7 @@ begin
|
|||||||
'localCenter=Math.round(dragCen0-dHz);' +
|
'localCenter=Math.round(dragCen0-dHz);' +
|
||||||
'ws2({cmd:"set_center",hz:localCenter});' +
|
'ws2({cmd:"set_center",hz:localCenter});' +
|
||||||
'}else{' +
|
'}else{' +
|
||||||
'const nf=cl(Math.round(dragVfo0-dHz),30000,60000000);' +
|
'const nf=cl(Math.round(dragVfo0-dHz),vfoMin(),vfoMax());' +
|
||||||
'setActiveVfoHz(nf);localCenter=nf;' +
|
'setActiveVfoHz(nf);localCenter=nf;' +
|
||||||
'if(activeVfo==="A")cur.vfo_a_hz=nf;else cur.vfo_b_hz=nf;' +
|
'if(activeVfo==="A")cur.vfo_a_hz=nf;else cur.vfo_b_hz=nf;' +
|
||||||
'renderVfos();pend("freq");ws2({cmd:"freq",hz:nf});' +
|
'renderVfos();pend("freq");ws2({cmd:"freq",hz:nf});' +
|
||||||
@@ -686,7 +688,7 @@ begin
|
|||||||
'if(dragging&&Math.abs(e.offsetX-dragX0)<4){' +
|
'if(dragging&&Math.abs(e.offsetX-dragX0)<4){' +
|
||||||
// клик — не трогаем localCenter: фильтр встаёт точно туда куда кликнули.
|
// клик — не трогаем localCenter: фильтр встаёт точно туда куда кликнули.
|
||||||
// Спектр остаётся на месте, сервер пришлёт center_hz через ~200ms и всё перецентрируется.
|
// Спектр остаётся на месте, сервер пришлёт 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);' +
|
'setActiveVfoHz(nf);' +
|
||||||
'if(activeVfo==="A")cur.vfo_a_hz=nf;else cur.vfo_b_hz=nf;' +
|
'if(activeVfo==="A")cur.vfo_a_hz=nf;else cur.vfo_b_hz=nf;' +
|
||||||
'renderVfos();pend("freq");ws2({cmd:"freq",hz:nf});' +
|
'renderVfos();pend("freq");ws2({cmd:"freq",hz:nf});' +
|
||||||
|
|||||||
Reference in New Issue
Block a user