fix: four logic bugs in RadioController

1. StartRunning applied AGC mode via a raw enum cast (TWDSPAGCMode(FAGCMode))
   instead of the index→enum table, inverting Fast↔Off on every connect/go-live
   (GUI and daemon). Now uses ApplyAGCToEngine like the rest of the unit.
2. SetFilterIdx indexed the per-mode BW/deviation tables with an unchecked Idx
   (FM tables hold only 2 entries) — out-of-bounds when driven from CAT/web.
   Now clamps Idx per mode (EnsureRange).
3. ConnectDevice loaded FActiveVfo / FCurrentBand straight from config without
   range validation; a corrupt/old blob could leave FCurrentBand invalid and
   make later SetMode/SetFilterIdx write past FBandCache. Both now clamped.
4. VfoSwap did not run the bandstack DSP restore that SetActiveVfo does, so a
   swap to a VFO on another band kept the old mode/filter/AGC while the band
   button highlighted the new band. Now mirrors SetActiveVfo.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-15 20:02:17 +03:00
co-authored by Claude Opus 4.8
parent 1cbc5a2307
commit 341bb249fd
+25 -10
View File
@@ -1472,18 +1472,27 @@ procedure TRadioController.VfoSwap;
var var
T: Double; T: Double;
ActiveFreq: Double; ActiveFreq: Double;
BandIdx: Integer; NewBand: Integer;
begin begin
T := FVfoA; FVfoA := FVfoB; FVfoB := T; T := FVfoA; FVfoA := FVfoB; FVfoB := T;
ActiveFreq := ActiveVfoHz; ActiveFreq := ActiveVfoHz;
// Bandstack: если активный после свопа VFO на другом ham-бэнде — сохраняем
// покидаемый бэнд и восстанавливаем DSP нового (mode/filter/AGC/CTUN/FM), как
// SetActiveVfo. Иначе своп на другой диапазон оставлял бы старые mode/фильтр/AGC
// при подсвеченной кнопке нового бэнда (рассинхрон).
if FCurrentXvtr < 0 then
begin
NewBand := FreqToBandIdx(ActiveFreq);
if (NewBand >= 0) and (NewBand <> FCurrentBand) then
begin
SaveCurrentBand;
FCurrentBand := NewBand;
ApplyBandDSP(FBandCache[NewBand]); // несёт Changed(rfBand) + рендер DSP-полей
FDriveLevel := CalcDriveByte; // PA-калибровка нового бэнда
end;
end;
FCenterFreq := ActiveFreq; FCenterFreq := ActiveFreq;
if FWDSPReady and Assigned(FDSPEngine) then FDSPEngine.SetShift(0.0); if FWDSPReady and Assigned(FDSPEngine) then FDSPEngine.SetShift(0.0);
BandIdx := FreqToBandIdx(ActiveFreq);
if (BandIdx >= 0) and (BandIdx <> FCurrentBand) then
begin
FCurrentBand := BandIdx;
Changed(rfBand);
end;
PushNetworkState; PushNetworkState;
Changed(rfVfoA); Changed(rfVfoA);
Changed(rfVfoB); Changed(rfVfoB);
@@ -1518,6 +1527,10 @@ end;
procedure TRadioController.SetFilterIdx(Idx: Integer); procedure TRadioController.SetFilterIdx(Idx: Integer);
begin begin
// Клампим индекс под текущий режим (как FilterBWFor) — публичная команда,
// Idx может прийти из CAT/web без проверки; FM-таблицы всего на 2 элемента.
if FMode = MODE_FM then Idx := EnsureRange(Idx, 0, FILT_FM_COUNT - 1)
else Idx := EnsureRange(Idx, 0, FILT_COUNT - 1);
FFilter := Idx; FFilter := Idx;
case FMode of case FMode of
0, 1: FFilterBW := FILT_SSB_BW[Idx]; 0, 1: FFilterBW := FILT_SSB_BW[Idx];
@@ -2225,7 +2238,7 @@ begin
ApplyNoiseFilterButtonsToDSP; ApplyNoiseFilterButtonsToDSP;
// ChangeSampleRate пересоздаёт WDSP-канал — восстанавливаем все настройки // ChangeSampleRate пересоздаёт WDSP-канал — восстанавливаем все настройки
FDSPEngine.SetAGCTop(FAGCTop); FDSPEngine.SetAGCTop(FAGCTop);
FDSPEngine.SetAGC(TWDSPAGCMode(FAGCMode), 50.0); ApplyAGCToEngine; // единый маппинг UI-индекс→enum (не прямой каст)
end; end;
// Сначала отправляем Run=1 — эмулятор/железо создают ddc_specific_thread // Сначала отправляем Run=1 — эмулятор/железо создают ddc_specific_thread
@@ -2309,7 +2322,7 @@ begin
if FTXSettings.TXSpecGridStep > 0 then if FTXSettings.TXSpecGridStep > 0 then
FTXSpecGridStep := FTXSettings.TXSpecGridStep; FTXSpecGridStep := FTXSettings.TXSpecGridStep;
FVolume := FLoadedGlobal.Volume; FVolume := FLoadedGlobal.Volume;
FActiveVfo := FLoadedGlobal.ActiveVfo; FActiveVfo := EnsureRange(FLoadedGlobal.ActiveVfo, 0, 1); // битый конфиг не должен дать невалидный VFO
FPAMaxPower := FLoadedGlobal.PAMaxPower; FPAMaxPower := FLoadedGlobal.PAMaxPower;
for i := 0 to CFG_BAND_COUNT - 1 do for i := 0 to CFG_BAND_COUNT - 1 do
FPABandCal[i] := FLoadedGlobal.PABandCal[i]; FPABandCal[i] := FLoadedGlobal.PABandCal[i];
@@ -2318,7 +2331,9 @@ begin
// drive% (0..100) — раньше грузился через TrkDrive.Position; теперь напрямую, // drive% (0..100) — раньше грузился через TrkDrive.Position; теперь напрямую,
// слайдер выставит rfDevice-рендер. Калиброванный байт считаем после FCurrentBand. // слайдер выставит rfDevice-рендер. Калиброванный байт считаем после FCurrentBand.
FDrivePercent := EnsureRange(FLoadedGlobal.DriveLevel, 0, 100); FDrivePercent := EnsureRange(FLoadedGlobal.DriveLevel, 0, 100);
FCurrentBand := FLoadedGlobal.LastBand; // Клампим LastBand — иначе битый/старый конфиг → RestoreBand выйдет по гарду,
// а последующие SetMode/SetFilterIdx запишут в FBandCache[невалид] за массив.
FCurrentBand := EnsureRange(FLoadedGlobal.LastBand, 0, CFG_BAND_COUNT - 1);
FDriveLevel := CalcDriveByte; FDriveLevel := CalcDriveByte;
// SampleRate — глобальный, до RestoreBand // SampleRate — глобальный, до RestoreBand
if FLoadedGlobal.SampleRate > 0 then if FLoadedGlobal.SampleRate > 0 then