Fix active-VFO desync; add bandstack-on-VFO-switch and per-XVTR VFO-B

Active-VFO desync (batch 15 follow-up): DoConnectDevice loaded
FActiveVfo from settings but never re-rendered the highlight (it was
drawn once in FormCreate while the field was still 0) nor re-tuned the
receiver. Result on reconnect with B last-active: the engine (spectrum,
filter band, mouse tuning) ran on VFO B while the UI showed A active.
Now the connect path brings both the receiver and the highlight to the
actually-active VFO via SetActiveVfo, and RestoreBand's final retune
follows the active VFO (A -> ApplyVfoA, B -> SetVfoB) instead of always A.

Bandstack on VFO switch: switching the active VFO to one parked on a
different ham band now restores that band's DSP state (mode/filter/AGC/
CTUN/FM). Extracted ApplyBandDSP (the DSP half of RestoreBand, without
the VFO frequencies) and reused it from both RestoreBand and SetActiveVfo.
VFO frequencies stay independent (each VFO carries its own band);
continuous tuning across a band edge still does not recall (band button
only) as on real radios. SaveCurrentBand no longer writes a VFO frequency
into a band's memory unless that VFO is actually within the band, so a
40m VFO-A no longer pollutes 30m's stored VfoA.

Per-transverter VFO-B: TXvtrEntry gains LastFreqB (load/save/default +
range clamp). ActivateXvtrBand restores VFO B, Deactivate/SaveAllAndExit
persist it, and SetVfoB clamps to the transverter range like SetVfoA.
The connect-time guard that forced VFO A in XVTR is dropped since B is now
a valid, in-range frequency there too.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Uladzimir Karpenka
2026-06-05 12:45:11 +03:00
co-authored by Claude Opus 4.8
parent 24102343ea
commit e09bc21bba
3 changed files with 114 additions and 40 deletions
+25 -4
View File
@@ -878,9 +878,14 @@ begin
FSpecView.ResetWfAvgBuf; FSpecView.ResetWfAvgBuf;
// Состояние + DSP + события рендера — в контроллере. // Состояние + DSP + события рендера — в контроллере.
FController.RestoreBand(BandIdx); FController.RestoreBand(BandIdx);
// Финальная перестройка VFO A с канальной оркестрацией (auto-CTCSS, // Финальная перестройка приёмника на АКТИВНЫЙ VFO. Если активен B — нельзя
// pre-channel drive) — через UI-путь ApplyVfoA. // центрироваться на VFO A (иначе движок слушает A, а маркер/мышь — на B).
ApplyVfoA(Round(FController.FVfoA)); // VFO A идёт через ApplyVfoA (канальная оркестрация: auto-CTCSS, pre-channel
// drive); VFO B — через SetVfoB (перестройка + рендер rfVfoB).
if FController.FActiveVfo = 0 then
ApplyVfoA(Round(FController.FVfoA))
else
FController.SetVfoB(Round(FController.FVfoB));
end; end;
procedure TMainForm.SaveAllAndExit; procedure TMainForm.SaveAllAndExit;
@@ -891,9 +896,12 @@ begin
FController.FSettings.SaveGlobal(FController.FDevMAC, MakeGlobalSettings); FController.FSettings.SaveGlobal(FController.FDevMAC, MakeGlobalSettings);
if FController.FDevConnected then if FController.FDevConnected then
FController.FSettings.SaveTX(FController.FDevMAC, FController.FTXSettings); FController.FSettings.SaveTX(FController.FDevMAC, FController.FTXSettings);
// Запомнить LastFreq XVTR для следующего запуска // Запомнить LastFreq A/B XVTR для следующего запуска
if (FController.FCurrentXvtr >= 0) and (FController.FCurrentXvtr < CFG_XVTR_COUNT) then if (FController.FCurrentXvtr >= 0) and (FController.FCurrentXvtr < CFG_XVTR_COUNT) then
begin
FController.FXvtrSettings.Entries[FController.FCurrentXvtr].LastFreq := FController.FVfoA; FController.FXvtrSettings.Entries[FController.FCurrentXvtr].LastFreq := FController.FVfoA;
FController.FXvtrSettings.Entries[FController.FCurrentXvtr].LastFreqB := FController.FVfoB;
end;
FController.FSettings.SaveXvtr(FController.FDevMAC, FController.FXvtrSettings); FController.FSettings.SaveXvtr(FController.FDevMAC, FController.FXvtrSettings);
FController.FSettings.Save; FController.FSettings.Save;
end; end;
@@ -4140,6 +4148,15 @@ begin
and FController.FXvtrSettings.Entries[G_Settings.LastXvtr].Enabled then and FController.FXvtrSettings.Entries[G_Settings.LastXvtr].Enabled then
ActivateXvtrBand(G_Settings.LastXvtr); ActivateXvtrBand(G_Settings.LastXvtr);
// Активный VFO загружен из настроек (выше), но подсветку рисовали в FormCreate
// (тогда FActiveVfo ещё = 0 → A), а RestoreBand/ActivateXvtrBand перестраивают
// только VFO A. Приводим приёмник И подсветку к фактически активному VFO,
// иначе движок работает по B, а UI показывает активным A.
if FController.FActiveVfo = 1 then
FController.SetActiveVfo(1) // перестройка RX на VFO B + рендер (rfActiveVfo)
else
UpdateVfoDisplay;
SetStatusText(1, 'IP: ' + FController.FNetwork.Device.IPAddress); SetStatusText(1, 'IP: ' + FController.FNetwork.Device.IPAddress);
SetStatusText(0, 'Board: ' + BoardTypeName(FController.FNetwork.Device.BoardType)); SetStatusText(0, 'Board: ' + BoardTypeName(FController.FNetwork.Device.BoardType));
@@ -6562,6 +6579,9 @@ begin
// CTUN // CTUN
FController.FCTun := E.LastCTun; FController.FCTun := E.LastCTun;
StyleButton(BtnCTun, FController.FCTun); StyleButton(BtnCTun, FController.FCTun);
// VFO-B: своя сохранённая частота слота, клэмп в диапазон трансвертера.
FController.FVfoB := EnsureRange(E.LastFreqB, E.FreqBegin, E.FreqEnd);
FreqDispB.Frequency := Round(FController.FVfoB);
// VFO на видимую частоту; FController.FCenterFreq тоже visible // VFO на видимую частоту; FController.FCenterFreq тоже visible
FController.FVfoA := Vis; FController.FVfoA := Vis;
FController.FCenterFreq := Vis; FController.FCenterFreq := Vis;
@@ -6600,6 +6620,7 @@ begin
if FController.FCurrentXvtr < CFG_XVTR_COUNT then if FController.FCurrentXvtr < CFG_XVTR_COUNT then
begin begin
FController.FXvtrSettings.Entries[FController.FCurrentXvtr].LastFreq := FController.FVfoA; FController.FXvtrSettings.Entries[FController.FCurrentXvtr].LastFreq := FController.FVfoA;
FController.FXvtrSettings.Entries[FController.FCurrentXvtr].LastFreqB := FController.FVfoB;
FController.FXvtrSettings.Entries[FController.FCurrentXvtr].LastMode := FController.FMode; FController.FXvtrSettings.Entries[FController.FCurrentXvtr].LastMode := FController.FMode;
FController.FXvtrSettings.Entries[FController.FCurrentXvtr].LastFilterIdx := FController.FFilter; FController.FXvtrSettings.Entries[FController.FCurrentXvtr].LastFilterIdx := FController.FFilter;
FController.FXvtrSettings.Entries[FController.FCurrentXvtr].LastFMSQOn := FController.FFMSQOn; FController.FXvtrSettings.Entries[FController.FCurrentXvtr].LastFMSQOn := FController.FFMSQOn;
+77 -33
View File
@@ -231,6 +231,10 @@ type
function MakeBandSettings: TBandSettings; function MakeBandSettings: TBandSettings;
procedure SaveCurrentBand; procedure SaveCurrentBand;
procedure RestoreBand(BandIdx: Integer); procedure RestoreBand(BandIdx: Integer);
// Применяет DSP-настройки диапазона (mode/filter/AGC/CTUN/FM) + шлёт события
// рендера. НЕ трогает частоты VFO/центр — это переиспользуется как RestoreBand
// (с загрузкой VFO), так и SetActiveVfo (bandstack: смена бэнда без сброса VFO).
procedure ApplyBandDSP(const B: TBandSettings);
// Полный HP-кадр в радио (RX/TX freq через трансвертор + drive + MOX). // Полный HP-кадр в радио (RX/TX freq через трансвертор + drive + MOX).
// Вызывается командами смены частоты/центра и UI после правки drive. // Вызывается командами смены частоты/центра и UI после правки drive.
@@ -547,7 +551,9 @@ end;
procedure TRadioController.SaveCurrentBand; procedure TRadioController.SaveCurrentBand;
// Сохраняет текущее состояние диапазона. В XVTR-режиме пишем в XVTR-слот // Сохраняет текущее состояние диапазона. В XVTR-режиме пишем в XVTR-слот
// (HF band-cache не трогаем — там сохранён HF-диапазон до активации XVTR). // (HF band-cache не трогаем — там сохранён HF-диапазон до активации XVTR).
var i: Integer; var
i: Integer;
B: TBandSettings;
begin begin
if not FDevConnected then Exit; if not FDevConnected then Exit;
if FCurrentXvtr >= 0 then if FCurrentXvtr >= 0 then
@@ -556,6 +562,7 @@ begin
begin begin
i := FCurrentXvtr; i := FCurrentXvtr;
FXvtrSettings.Entries[i].LastFreq := FVfoA; FXvtrSettings.Entries[i].LastFreq := FVfoA;
FXvtrSettings.Entries[i].LastFreqB := FVfoB;
FXvtrSettings.Entries[i].LastMode := FMode; FXvtrSettings.Entries[i].LastMode := FMode;
FXvtrSettings.Entries[i].LastFilterIdx := FFilter; FXvtrSettings.Entries[i].LastFilterIdx := FFilter;
FXvtrSettings.Entries[i].LastFMSQOn := FFMSQOn; FXvtrSettings.Entries[i].LastFMSQOn := FFMSQOn;
@@ -578,20 +585,19 @@ begin
// Не сохраняем, если VFO ушёл в VHF/UHF (например, после канала на 2м без // Не сохраняем, если VFO ушёл в VHF/UHF (например, после канала на 2м без
// XVTR) — иначе HF-кэш диапазона перезапишется VHF-частотой. // XVTR) — иначе HF-кэш диапазона перезапишется VHF-частотой.
if FVfoA > 61000000 then Exit; if FVfoA > 61000000 then Exit;
FBandCache[FCurrentBand] := MakeBandSettings; B := MakeBandSettings;
// Bandstack: VFO A и B независимы и могут стоять на разных бэндах. В кэш
// ЭТОГО бэнда пишем только ту частоту VFO, что реально в нём — иначе чужая
// частота (напр. VFO A на 40м при сохранении 30м) загрязнит memory бэнда.
if FreqToBandIdx(FVfoA) <> FCurrentBand then B.VfoA := FBandCache[FCurrentBand].VfoA;
if FreqToBandIdx(FVfoB) <> FCurrentBand then B.VfoB := FBandCache[FCurrentBand].VfoB;
FBandCache[FCurrentBand] := B;
FSettings.SaveBand(FDevMAC, FCurrentBand, FBandCache[FCurrentBand]); FSettings.SaveBand(FDevMAC, FCurrentBand, FBandCache[FCurrentBand]);
end; end;
procedure TRadioController.RestoreBand(BandIdx: Integer); procedure TRadioController.ApplyBandDSP(const B: TBandSettings);
// Загружает состояние диапазона из кэша, применяет к DSP и шлёт события рендера. // DSP-настройки диапазона + события рендера. Частоты VFO/центр НЕ трогает.
// Финальную перестройку VFO A (с канальной оркестрацией) делает вызывающий
// UI-путь через ApplyVfoA(FVfoA); здесь FVfoA лишь выставляется.
var B: TBandSettings;
begin begin
if (BandIdx < 0) or (BandIdx >= CFG_BAND_COUNT) then Exit;
B := FBandCache[BandIdx];
FCurrentBand := BandIdx;
// --- Режим --- // --- Режим ---
FMode := B.Mode; FMode := B.Mode;
if FWDSPReady and Assigned(FDSPEngine) then FDSPEngine.SetMode(FMode); if FWDSPReady and Assigned(FDSPEngine) then FDSPEngine.SetMode(FMode);
@@ -631,12 +637,6 @@ begin
// --- Span (sample rate глобальный, не per-band — лишь ресинк) --- // --- Span (sample rate глобальный, не per-band — лишь ресинк) ---
FSpanHz := FSampleRate; FSpanHz := FSampleRate;
// --- VFO / drive ---
FCenterFreq := B.VfoA;
FVfoB := B.VfoB;
FVfoA := B.VfoA;
FDriveLevel := CalcDriveByte;
// --- Рендер: единичные события (UI рисует через OnControllerState) --- // --- Рендер: единичные события (UI рисует через OnControllerState) ---
Changed(rfBand); Changed(rfBand);
Changed(rfMode); // rfMode-рендер зовёт UpdateFilterButtons (FM-панели) Changed(rfMode); // rfMode-рендер зовёт UpdateFilterButtons (FM-панели)
@@ -651,6 +651,26 @@ begin
Changed(rfFMStepIdx); // дропдаун шага + web + grid Changed(rfFMStepIdx); // дропдаун шага + web + grid
Changed(rfFMRpt); Changed(rfFMRpt);
Changed(rfCTun); Changed(rfCTun);
end;
procedure TRadioController.RestoreBand(BandIdx: Integer);
// Загружает состояние диапазона из кэша, применяет к DSP и шлёт события рендера.
// Финальную перестройку VFO A (с канальной оркестрацией) делает вызывающий
// UI-путь через ApplyVfoA(FVfoA); здесь FVfoA лишь выставляется.
var B: TBandSettings;
begin
if (BandIdx < 0) or (BandIdx >= CFG_BAND_COUNT) then Exit;
B := FBandCache[BandIdx];
FCurrentBand := BandIdx;
ApplyBandDSP(B);
// --- VFO / drive (только RestoreBand — bandstack-путь VFO не трогает) ---
FCenterFreq := B.VfoA;
FVfoB := B.VfoB;
FVfoA := B.VfoA;
FDriveLevel := CalcDriveByte;
Changed(rfVfoB); Changed(rfVfoB);
// rfVfoA НЕ шлём: финальную перестройку делает ApplyVfoA в UI-обёртке. // rfVfoA НЕ шлём: финальную перестройку делает ApplyVfoA в UI-обёртке.
end; end;
@@ -743,9 +763,21 @@ procedure TRadioController.SetVfoB(Hz: Double);
// ApplyTuneCore (с учётом CTUN), детектим диапазон и толкаем в сеть. Если B // ApplyTuneCore (с учётом CTUN), детектим диапазон и толкаем в сеть. Если B
// неактивен (split, слушаем A) — только сохраняем частоту (для TX). // неактивен (split, слушаем A) — только сохраняем частоту (для TX).
// Рендер FreqDispB/спектра — в UI через OnControllerState(rfVfoB). // Рендер FreqDispB/спектра — в UI через OnControllerState(rfVfoB).
var BandIdx: Integer; var
BandIdx: Integer;
NewFreq: Int64;
begin begin
FVfoB := Hz; NewFreq := Round(Hz);
// Клэмп в пределах XVTR-band (как в SetVfoA) — VFO B тоже не уходит за край.
if (FCurrentXvtr >= 0) and (FCurrentXvtr < CFG_XVTR_COUNT) and
FXvtrSettings.Entries[FCurrentXvtr].Enabled then
begin
if NewFreq < FXvtrSettings.Entries[FCurrentXvtr].FreqBegin then
NewFreq := Round(FXvtrSettings.Entries[FCurrentXvtr].FreqBegin);
if NewFreq > FXvtrSettings.Entries[FCurrentXvtr].FreqEnd then
NewFreq := Round(FXvtrSettings.Entries[FCurrentXvtr].FreqEnd);
end;
FVfoB := NewFreq;
if FActiveVfo = 1 then if FActiveVfo = 1 then
begin begin
ApplyTuneCore(FVfoB); ApplyTuneCore(FVfoB);
@@ -766,17 +798,36 @@ end;
procedure TRadioController.SetActiveVfo(Idx: Integer); procedure TRadioController.SetActiveVfo(Idx: Integer);
// Переключение активного (RX) VFO с перестройкой приёмника на его частоту. // Переключение активного (RX) VFO с перестройкой приёмника на его частоту.
// CTUN: если частота нового VFO видна в текущем окне — только меняем shift // Bandstack: если активируемый VFO стоит на ДРУГОМ ham-бэнде — сохраняем
// покидаемый бэнд и восстанавливаем DSP-настройки нового (mode/filter/AGC/CTUN/
// FM). Частоты обоих VFO при этом НЕ трогаем — каждый VFO несёт свой диапазон.
// CTUN (того же бэнда): если частота нового VFO видна в окне — только shift
// (DDC/сеть не трогаем); иначе CTUN сбрасывается и центрируемся на частоте. // (DDC/сеть не трогаем); иначе CTUN сбрасывается и центрируемся на частоте.
// Рендер (дисплеи/спектр/band/CTUN-кнопка) — в UI через OnControllerState. // Рендер (дисплеи/спектр/band/CTUN-кнопка) — в UI через OnControllerState.
var var
ActiveFreq: Double; ActiveFreq: Double;
HalfSpan, Offset: Double; HalfSpan, Offset: Double;
FreqVisible: Boolean; FreqVisible, NeedPush: Boolean;
BandIdx: Integer; NewBand: Integer;
begin begin
FActiveVfo := Idx; FActiveVfo := Idx;
ActiveFreq := ActiveVfoHz; ActiveFreq := ActiveVfoHz;
NeedPush := False;
// --- Bandstack: активируемый VFO на другом ham-бэнде → восстановить бэнд ---
if FCurrentXvtr < 0 then
begin
NewBand := FreqToBandIdx(ActiveFreq);
if (NewBand >= 0) and (NewBand <> FCurrentBand) then
begin
SaveCurrentBand; // сохранить покидаемый бэнд
FCurrentBand := NewBand;
ApplyBandDSP(FBandCache[NewBand]); // mode/filter/AGC/CTUN/FM + рендер
FCenterFreq := ActiveFreq; // центр на активную (CTUN центрируется тут)
FDriveLevel := CalcDriveByte; // PA-калибровка нового бэнда
NeedPush := True; // DDC-центр сменился → толкнуть в сеть
end;
end;
if FCTun then if FCTun then
begin begin
@@ -785,7 +836,7 @@ begin
FreqVisible := (Offset > -HalfSpan) and (Offset < HalfSpan); FreqVisible := (Offset > -HalfSpan) and (Offset < HalfSpan);
if FreqVisible then if FreqVisible then
begin begin
// Частота видна — просто сдвигаем демодулятор, DDC/сеть не трогаем. // Частота видна — просто сдвигаем демодулятор.
if FWDSPReady and Assigned(FDSPEngine) then FDSPEngine.SetShift(Offset); if FWDSPReady and Assigned(FDSPEngine) then FDSPEngine.SetShift(Offset);
end end
else else
@@ -795,7 +846,7 @@ begin
FBandCache[FCurrentBand].CTun := False; FBandCache[FCurrentBand].CTun := False;
FCenterFreq := ActiveFreq; FCenterFreq := ActiveFreq;
if FWDSPReady and Assigned(FDSPEngine) then FDSPEngine.SetShift(0.0); if FWDSPReady and Assigned(FDSPEngine) then FDSPEngine.SetShift(0.0);
PushNetworkState; NeedPush := True;
Changed(rfCTun); Changed(rfCTun);
end; end;
end end
@@ -803,17 +854,10 @@ begin
begin begin
FCenterFreq := ActiveFreq; FCenterFreq := ActiveFreq;
if FWDSPReady and Assigned(FDSPEngine) then FDSPEngine.SetShift(0.0); if FWDSPReady and Assigned(FDSPEngine) then FDSPEngine.SetShift(0.0);
PushNetworkState; NeedPush := True;
end;
// Детект диапазона по новой частоте (без пересчёта drive — как исторически).
BandIdx := FreqToBandIdx(ActiveFreq);
if (BandIdx >= 0) and (BandIdx <> FCurrentBand) then
begin
FCurrentBand := BandIdx;
Changed(rfBand);
end; end;
if NeedPush then PushNetworkState;
Changed(rfActiveVfo); Changed(rfActiveVfo);
end; end;
+11 -2
View File
@@ -161,7 +161,8 @@ 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 частота VFO A (BandStack)
LastFreqB: Double; // Hz — последняя tuned visible частота VFO B
LastMode: Integer; // Mode (LSB=0,USB=1,…FM=5) LastMode: Integer; // Mode (LSB=0,USB=1,…FM=5)
LastFilterIdx: Integer; // FM: 0=NFM, 1=WFM; иначе 0..FILT_COUNT-1 LastFilterIdx: Integer; // FM: 0=NFM, 1=WFM; иначе 0..FILT_COUNT-1
LastFMSQOn: Boolean; LastFMSQOn: Boolean;
@@ -1205,6 +1206,7 @@ begin
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].LastFreqB := 0.0;
X.Entries[i].LastMode := 1; // USB — VHF SSB calling default X.Entries[i].LastMode := 1; // USB — VHF SSB calling default
X.Entries[i].LastFilterIdx := 0; // NFM X.Entries[i].LastFilterIdx := 0; // NFM
X.Entries[i].LastFMSQOn := False; X.Entries[i].LastFMSQOn := False;
@@ -1227,11 +1229,13 @@ begin
X.Entries[0].FreqEnd := 148000000; X.Entries[0].FreqEnd := 148000000;
X.Entries[0].LOOffset := 116000000; X.Entries[0].LOOffset := 116000000;
X.Entries[0].LastFreq := 144300000; X.Entries[0].LastFreq := 144300000;
X.Entries[0].LastFreqB := 145500000;
X.Entries[1].ButtonText := '70cm'; X.Entries[1].ButtonText := '70cm';
X.Entries[1].FreqBegin := 430000000; X.Entries[1].FreqBegin := 430000000;
X.Entries[1].FreqEnd := 440000000; X.Entries[1].FreqEnd := 440000000;
X.Entries[1].LOOffset := 404000000; X.Entries[1].LOOffset := 404000000;
X.Entries[1].LastFreq := 432200000; X.Entries[1].LastFreq := 432200000;
X.Entries[1].LastFreqB := 433000000;
end; end;
function TSettingsManager.LoadXvtr(const MAC: array of Byte; function TSettingsManager.LoadXvtr(const MAC: array of Byte;
@@ -1265,6 +1269,7 @@ begin
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].LastFreqB := JD(EObj, 'last_freq_b', X.Entries[i].LastFreqB);
X.Entries[i].LastMode := EnsureRange(JI(EObj, 'last_mode', X.Entries[i].LastMode), 0, 7); 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].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].LastFMSQOn := JB(EObj, 'last_fmsq_on', X.Entries[i].LastFMSQOn);
@@ -1280,10 +1285,13 @@ begin
X.Entries[i].LastFMRptOffsetHz := JD(EObj, 'last_fmrpt_offset', X.Entries[i].LastFMRptOffsetHz); X.Entries[i].LastFMRptOffsetHz := JD(EObj, 'last_fmrpt_offset', X.Entries[i].LastFMRptOffsetHz);
X.Entries[i].LastCTCSSAutoActive := JB(EObj, 'last_ctcss_auto', X.Entries[i].LastCTCSSAutoActive); X.Entries[i].LastCTCSSAutoActive := JB(EObj, 'last_ctcss_auto', X.Entries[i].LastCTCSSAutoActive);
X.Entries[i].LastFMRptAutoActive := JB(EObj, 'last_fmrpt_auto', X.Entries[i].LastFMRptAutoActive); X.Entries[i].LastFMRptAutoActive := JB(EObj, 'last_fmrpt_auto', X.Entries[i].LastFMRptAutoActive);
// Валидация: LastFreq должен быть в [FreqBegin..FreqEnd] // Валидация: Last freqs должны быть в [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
X.Entries[i].LastFreq := (X.Entries[i].FreqBegin + X.Entries[i].FreqEnd) / 2.0; X.Entries[i].LastFreq := (X.Entries[i].FreqBegin + X.Entries[i].FreqEnd) / 2.0;
if (X.Entries[i].LastFreqB < X.Entries[i].FreqBegin) or
(X.Entries[i].LastFreqB > X.Entries[i].FreqEnd) then
X.Entries[i].LastFreqB := (X.Entries[i].FreqBegin + X.Entries[i].FreqEnd) / 2.0;
end; end;
end; end;
@@ -1310,6 +1318,7 @@ begin
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_freq_b', X.Entries[i].LastFreqB);
JW(EObj, 'last_mode', X.Entries[i].LastMode); JW(EObj, 'last_mode', X.Entries[i].LastMode);
JW(EObj, 'last_filt', X.Entries[i].LastFilterIdx); JW(EObj, 'last_filt', X.Entries[i].LastFilterIdx);
JW(EObj, 'last_fmsq_on', X.Entries[i].LastFMSQOn); JW(EObj, 'last_fmsq_on', X.Entries[i].LastFMSQOn);