Phase 3 (batch 16): move XVTR activate/deactivate into the controller

ActivateXvtrBand/DeactivateXvtr followed the Band pattern: the state +
DSP + network now live on TRadioController; the UI renders via events.

- ApplyXvtrToNetwork moved to the controller (XVTR enable bit / DisablePA
  / RX antenna). MainForm.ApplyXvtrToNetwork is a thin delegate.
- ActivateXvtr builds a TBandSettings from the XVTR slot and runs it
  through the shared ApplyBandDSP (mode/filter/AGC/CTUN/FM), then sets
  VFO B (range-clamped) / VFO A / center / drive and pushes to the radio.
  Render is driven by Changed(rfXvtr) + Changed(rfActiveVfo). The slot has
  no stored filter bandwidth, so FilterBWFor(mode, idx) derives it from
  the per-mode table without changing the index.
- DeactivateXvtr saves the slot via SaveCurrentBand (its XVTR branch
  already writes the whole slot) and exits; the HF return (RestoreBand +
  final active-VFO retune) stays in the UI wrapper.
- SetXvtrBand is now a real dispatcher (>=0 activate, <0 deactivate).
- rfBand render is XVTR-aware (clears HF band buttons while in XVTR);
  added an rfXvtr render case for the XVTR button highlight.
- The thin UI wrappers keep only wideband-view + web push. ~150 lines of
  inline logic removed from MainForm; all callers (web/channel/band-click/
  startup) are unchanged and go through the wrappers.

Also fixes a latent filter-restore bug surfaced by the XVTR round-trip
(transverter -> HF -> transverter): the rfMode render calls
UpdateFilterButtons, which resets the non-FM filter to the mode default.
That reset belongs to a mode change (the controller already applies it via
ApplyModeDefaults), not to a passive render, so it clobbered the filter
restored from the band/XVTR cache. rfMode now preserves FFilter/FFilterBW
around UpdateFilterButtons. This also fixes the same loss on plain HF band
switches for non-FM modes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Uladzimir Karpenka
2026-06-05 13:49:31 +03:00
co-authored by Claude Opus 4.8
parent e09bc21bba
commit 831e5c3a31
2 changed files with 152 additions and 143 deletions
+120 -1
View File
@@ -235,6 +235,18 @@ type
// рендера. НЕ трогает частоты VFO/центр — это переиспользуется как RestoreBand
// (с загрузкой VFO), так и SetActiveVfo (bandstack: смена бэнда без сброса VFO).
procedure ApplyBandDSP(const B: TBandSettings);
// Полоса фильтра для (режим, индекс фильтра) — выбор per-mode таблицы.
function FilterBWFor(M, Idx: Integer): Integer;
// ---- XVTR (трансвертер) ----
// ApplyXvtrToNetwork — XVTR enable bit + DisablePA + RX ant в сеть.
// ActivateXvtr — вход в слот: state+DSP (через ApplyBandDSP) + VFO/центр + сеть;
// рендер через rfXvtr/rfBand(XVTR-aware)/rfActiveVfo. UI: wideband+web.
// DeactivateXvtr — сохраняет слот (SaveCurrentBand) и выходит; HF-возврат
// (RestoreBand + финальная перестройка VFO) делает UI-обёртка.
procedure ApplyXvtrToNetwork;
procedure ActivateXvtr(Idx: Integer);
procedure DeactivateXvtr;
// Полный HP-кадр в радио (RX/TX freq через трансвертор + drive + MOX).
// Вызывается командами смены частоты/центра и UI после правки drive.
@@ -463,6 +475,19 @@ begin
end;
end;
function TRadioController.FilterBWFor(M, Idx: Integer): Integer;
// Полоса выбранного фильтра под режим. Зеркалит выбор таблицы в UpdateFilterButtons,
// но НЕ меняет индекс (в отличие от ApplyModeDefaults) — нужно для XVTR-restore.
begin
case M of
MODE_FM: Result := FILT_FM_BW[EnsureRange(Idx, 0, 1)];
0, 1: Result := FILT_SSB_BW[EnsureRange(Idx, 0, FILT_COUNT - 1)];
2: Result := FILT_DSB_BW[EnsureRange(Idx, 0, FILT_COUNT - 1)];
3, 4: Result := FILT_CW_BW[EnsureRange(Idx, 0, FILT_COUNT - 1)];
else Result := FILT_AM_BW[EnsureRange(Idx, 0, FILT_COUNT - 1)];
end;
end;
// =====================================================================
// Команды (Фаза 1 — каркас). Полная логика переносится из TMainForm.
// =====================================================================
@@ -1018,8 +1043,102 @@ begin if FCurrentBand < CFG_BAND_COUNT - 1 then SetBand(FCurrentBand + 1); end;
procedure TRadioController.BandDown;
begin if FCurrentBand > 0 then SetBand(FCurrentBand - 1); end;
procedure TRadioController.ApplyXvtrToNetwork;
// Передаёт XVTR-режим в сеть (enable bit + T/R relay suppression + RX ant).
var
En, DisablePA: Boolean;
RxAnt: Byte;
begin
if (FCurrentXvtr >= 0) and (FCurrentXvtr < CFG_XVTR_COUNT) and
FXvtrSettings.Entries[FCurrentXvtr].Enabled then
begin
En := True;
DisablePA := FXvtrSettings.Entries[FCurrentXvtr].DisablePA;
RxAnt := FXvtrSettings.Entries[FCurrentXvtr].RXAntenna;
end
else
begin
En := False; DisablePA := False; RxAnt := 0;
end;
if Assigned(FNetwork) and FNetwork.Connected then
FNetwork.SetXvtrMode(En, DisablePA, RxAnt);
end;
procedure TRadioController.ActivateXvtr(Idx: Integer);
// Вход в XVTR-слот: state+DSP через общий ApplyBandDSP + VFO/центр/сеть.
// Рендер: rfXvtr (XVTR-кнопки), rfBand (XVTR-aware гасит HF), rfActiveVfo
// (дисплеи A/B + полный перерисов спектра). UI-обёртка добавляет wideband+web.
var
E: TXvtrEntry;
B: TBandSettings;
Vis: Double;
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 := E.LastFreq;
if (Vis < E.FreqBegin) or (Vis > E.FreqEnd) then
Vis := (E.FreqBegin + E.FreqEnd) / 2.0;
ApplyXvtrToNetwork; // XVTR enable bit + DisablePA + RX ant
// Авто-флаги канала (не входят в TBandSettings/ApplyBandDSP).
FFMCTCSSAutoActive := E.LastCTCSSAutoActive;
FFMRptAutoActive := E.LastFMRptAutoActive;
// DSP-настройки слота — через общий путь Band (mode/filter/AGC/CTUN/FM + рендер).
B.Mode := E.LastMode;
B.FilterIdx := E.LastFilterIdx;
B.FilterBW := FilterBWFor(E.LastMode, E.LastFilterIdx);
B.AGCMode := E.LastAGCMode;
B.AGCTop := E.LastAGCTop;
B.CTun := E.LastCTun;
B.SpanHz := FSampleRate;
B.FMSQOn := E.LastFMSQOn;
B.FMSQLevel := E.LastFMSQLevel;
B.CTCSSOn := E.LastCTCSSOn;
B.CTCSSToneIdx := E.LastCTCSSToneIdx;
B.FMStepOn := E.LastFMStepOn;
B.FMStepIdx := E.LastFMStepIdx;
B.FMRptDir := E.LastFMRptDir;
B.FMRptOffsetHz := E.LastFMRptOffsetHz;
B.VfoA := Vis;
B.VfoB := FVfoB;
ApplyBandDSP(B);
// VFO: B — своя сохранённая частота слота (клэмп в диапазон); A = visible.
FVfoB := EnsureRange(E.LastFreqB, E.FreqBegin, E.FreqEnd);
FVfoA := Vis;
FCenterFreq := Vis;
if FWDSPReady and Assigned(FDSPEngine) then FDSPEngine.SetShift(0.0);
FDriveLevel := CalcDriveByte;
PushNetworkState;
Changed(rfXvtr);
Changed(rfActiveVfo);
end;
procedure TRadioController.DeactivateXvtr;
// Сохраняет текущий XVTR-слот и выходит в HF. Сам HF-возврат (RestoreBand +
// финальная перестройка активного VFO) делает UI-обёртка — там же ResetWfAvgBuf.
begin
if FCurrentXvtr < 0 then Exit;
SaveCurrentBand; // XVTR-ветка: пишет весь слот + SaveXvtr
if FDevConnected then FSettings.Save;
FCurrentXvtr := -1;
ApplyXvtrToNetwork; // снять XVTR enable bit
Changed(rfXvtr); // погасить XVTR-кнопки
end;
procedure TRadioController.SetXvtrBand(Idx: Integer);
begin FCurrentXvtr := Idx; Changed(rfXvtr); { TODO wiring } end;
// Единая точка для GUI/Web/CAT: Idx>=0 — войти в слот, Idx<0 — выйти в HF.
// ВНИМАНИЕ: HF-возврат при выходе делает UI-обёртка DeactivateXvtr (RestoreBand).
begin
if Idx >= 0 then ActivateXvtr(Idx)
else DeactivateXvtr;
end;
procedure TRadioController.SetSpan(Hz: Integer);
begin FSpanHz := Hz; Changed(rfSpan); { TODO wiring } end;