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
+77 -33
View File
@@ -231,6 +231,10 @@ type
function MakeBandSettings: TBandSettings;
procedure SaveCurrentBand;
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).
// Вызывается командами смены частоты/центра и UI после правки drive.
@@ -547,7 +551,9 @@ end;
procedure TRadioController.SaveCurrentBand;
// Сохраняет текущее состояние диапазона. В XVTR-режиме пишем в XVTR-слот
// (HF band-cache не трогаем — там сохранён HF-диапазон до активации XVTR).
var i: Integer;
var
i: Integer;
B: TBandSettings;
begin
if not FDevConnected then Exit;
if FCurrentXvtr >= 0 then
@@ -556,6 +562,7 @@ begin
begin
i := FCurrentXvtr;
FXvtrSettings.Entries[i].LastFreq := FVfoA;
FXvtrSettings.Entries[i].LastFreqB := FVfoB;
FXvtrSettings.Entries[i].LastMode := FMode;
FXvtrSettings.Entries[i].LastFilterIdx := FFilter;
FXvtrSettings.Entries[i].LastFMSQOn := FFMSQOn;
@@ -578,20 +585,19 @@ begin
// Не сохраняем, если VFO ушёл в VHF/UHF (например, после канала на 2м без
// XVTR) — иначе HF-кэш диапазона перезапишется VHF-частотой.
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]);
end;
procedure TRadioController.RestoreBand(BandIdx: Integer);
// Загружает состояние диапазона из кэша, применяет к DSP и шлёт события рендера.
// Финальную перестройку VFO A (с канальной оркестрацией) делает вызывающий
// UI-путь через ApplyVfoA(FVfoA); здесь FVfoA лишь выставляется.
var B: TBandSettings;
procedure TRadioController.ApplyBandDSP(const B: TBandSettings);
// DSP-настройки диапазона + события рендера. Частоты VFO/центр НЕ трогает.
begin
if (BandIdx < 0) or (BandIdx >= CFG_BAND_COUNT) then Exit;
B := FBandCache[BandIdx];
FCurrentBand := BandIdx;
// --- Режим ---
FMode := B.Mode;
if FWDSPReady and Assigned(FDSPEngine) then FDSPEngine.SetMode(FMode);
@@ -631,12 +637,6 @@ begin
// --- Span (sample rate глобальный, не per-band — лишь ресинк) ---
FSpanHz := FSampleRate;
// --- VFO / drive ---
FCenterFreq := B.VfoA;
FVfoB := B.VfoB;
FVfoA := B.VfoA;
FDriveLevel := CalcDriveByte;
// --- Рендер: единичные события (UI рисует через OnControllerState) ---
Changed(rfBand);
Changed(rfMode); // rfMode-рендер зовёт UpdateFilterButtons (FM-панели)
@@ -651,6 +651,26 @@ begin
Changed(rfFMStepIdx); // дропдаун шага + web + grid
Changed(rfFMRpt);
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);
// rfVfoA НЕ шлём: финальную перестройку делает ApplyVfoA в UI-обёртке.
end;
@@ -743,9 +763,21 @@ procedure TRadioController.SetVfoB(Hz: Double);
// ApplyTuneCore (с учётом CTUN), детектим диапазон и толкаем в сеть. Если B
// неактивен (split, слушаем A) — только сохраняем частоту (для TX).
// Рендер FreqDispB/спектра — в UI через OnControllerState(rfVfoB).
var BandIdx: Integer;
var
BandIdx: Integer;
NewFreq: Int64;
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
begin
ApplyTuneCore(FVfoB);
@@ -766,17 +798,36 @@ end;
procedure TRadioController.SetActiveVfo(Idx: Integer);
// Переключение активного (RX) VFO с перестройкой приёмника на его частоту.
// CTUN: если частота нового VFO видна в текущем окне — только меняем shift
// Bandstack: если активируемый VFO стоит на ДРУГОМ ham-бэнде — сохраняем
// покидаемый бэнд и восстанавливаем DSP-настройки нового (mode/filter/AGC/CTUN/
// FM). Частоты обоих VFO при этом НЕ трогаем — каждый VFO несёт свой диапазон.
// CTUN (того же бэнда): если частота нового VFO видна в окне — только shift
// (DDC/сеть не трогаем); иначе CTUN сбрасывается и центрируемся на частоте.
// Рендер (дисплеи/спектр/band/CTUN-кнопка) — в UI через OnControllerState.
var
ActiveFreq: Double;
HalfSpan, Offset: Double;
FreqVisible: Boolean;
BandIdx: Integer;
FreqVisible, NeedPush: Boolean;
NewBand: Integer;
begin
FActiveVfo := Idx;
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
begin
@@ -785,7 +836,7 @@ begin
FreqVisible := (Offset > -HalfSpan) and (Offset < HalfSpan);
if FreqVisible then
begin
// Частота видна — просто сдвигаем демодулятор, DDC/сеть не трогаем.
// Частота видна — просто сдвигаем демодулятор.
if FWDSPReady and Assigned(FDSPEngine) then FDSPEngine.SetShift(Offset);
end
else
@@ -795,7 +846,7 @@ begin
FBandCache[FCurrentBand].CTun := False;
FCenterFreq := ActiveFreq;
if FWDSPReady and Assigned(FDSPEngine) then FDSPEngine.SetShift(0.0);
PushNetworkState;
NeedPush := True;
Changed(rfCTun);
end;
end
@@ -803,17 +854,10 @@ begin
begin
FCenterFreq := ActiveFreq;
if FWDSPReady and Assigned(FDSPEngine) then FDSPEngine.SetShift(0.0);
PushNetworkState;
end;
// Детект диапазона по новой частоте (без пересчёта drive — как исторически).
BandIdx := FreqToBandIdx(ActiveFreq);
if (BandIdx >= 0) and (BandIdx <> FCurrentBand) then
begin
FCurrentBand := BandIdx;
Changed(rfBand);
NeedPush := True;
end;
if NeedPush then PushNetworkState;
Changed(rfActiveVfo);
end;