fix: persist CTun center + save VHF/UHF bands (Pluto); QO-100 transverter template

CTun position was lost on band restore / restart, and VHF/UHF bands were
never saved on Pluto.

- SaveCurrentBand: the >61 MHz guard (HPSDR HF-only) skipped saving native
  Pluto VHF/UHF bands → 2m/70cm reverted to defaults. Now HPSDR-only; Pluto
  saves VU-plan bands (FreqToBandIdx guards cross-band pollution).
- Persist DDC center for CTun: new TBandSettings.CenterHz (+ JSON center_hz)
  and TXvtrEntry.LastCenterHz (+ last_center_hz). RestoreBand / ActivateXvtr
  restore the saved center when CTun is on and the offset fits the span,
  instead of forcing center := VFO (which re-centered the receiver).
- UpdateFreqDisplayMax: freq-display upper bound from active transverter's
  FreqEnd (lets QO-100 show 10 GHz past the 6 GHz Pluto cap).
- QO-100 transverter template (slot 2, disabled): downlink 10489.5–10490.0,
  LO 9750. MigrateXvtrTemplate injects it into the first free slot for
  devices that already have a saved xvtr config (template was otherwise
  masked by the loaded slots).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-17 22:02:01 +03:00
co-authored by Claude Opus 4.8
parent 24f74357cd
commit 40dca01c40
3 changed files with 97 additions and 20 deletions
+28 -6
View File
@@ -1050,6 +1050,7 @@ begin
Result.AGCMode := FAGCMode;
Result.AGCTop := FAGCTop;
Result.CTun := FCTun;
Result.CenterHz := FCenterFreq; // DDC-центр → восстановим offset CTun при загрузке
Result.SpanHz := FSpanHz;
Result.FMSQOn := FFMSQOn;
Result.FMSQLevel := FFMSQLevel;
@@ -1088,6 +1089,7 @@ begin
FXvtrSettings.Entries[i].LastFMRptDir := FFMRptDir;
FXvtrSettings.Entries[i].LastFMRptOffsetHz := FFMRptOffsetHz;
FXvtrSettings.Entries[i].LastCTun := FCTun;
FXvtrSettings.Entries[i].LastCenterHz := FCenterFreq;
FXvtrSettings.Entries[i].LastAGCMode := FAGCMode;
FXvtrSettings.Entries[i].LastAGCTop := FAGCTop;
FXvtrSettings.Entries[i].LastCTCSSAutoActive := FFMCTCSSAutoActive;
@@ -1096,9 +1098,11 @@ begin
FSettings.SaveXvtr(FDevMAC, FXvtrSettings);
Exit;
end;
// Не сохраняем, если VFO ушёл в VHF/UHF (например, после канала на 2м без
// XVTR) иначе HF-кэш диапазона перезапишется VHF-частотой.
if FVfoA > 61000000 then Exit;
// HPSDR: приёмник только HF — если VFO ушёл в VHF/UHF (напр. канал на 2м без
// XVTR), не сохраняем, иначе HF-кэш диапазона перезапишется VHF-частотой.
// Pluto: VHF/UHF — нативные диапазоны (VU-план), сохранять обязательно;
// от перезаписи чужим диапазоном защищает FreqToBandIdx-проверка ниже.
if (not IsPluto) and (FVfoA > 61000000) then Exit;
B := MakeBandSettings;
// Bandstack: VFO A и B независимы и могут стоять на разных бэндах. В кэш
// ЭТОГО бэнда пишем только ту частоту VFO, что реально в нём — иначе чужая
@@ -1262,7 +1266,14 @@ begin
ApplyBandDSP(B);
// --- VFO / drive (только RestoreBand — bandstack-путь VFO не трогает) ---
FCenterFreq := B.VfoA;
// При CTun восстанавливаем сохранённый DDC-центр, чтобы VFO остался на своём
// месте в полосе (а не прыгал в центр). Берём только если offset влезает в
// текущий span — иначе (сменился sample rate / битый конфиг) центрируем на VFO.
// ApplyTuneCore в SetVfoA при необходимости сам прокрутит центр к краю.
if FCTun and (B.CenterHz > 0) and (Abs(B.VfoA - B.CenterHz) < FSpanHz / 2) then
FCenterFreq := B.CenterHz
else
FCenterFreq := B.VfoA;
FVfoB := B.VfoB;
FVfoA := B.VfoA;
FDriveLevel := CalcDriveByte;
@@ -1808,8 +1819,19 @@ begin
// 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);
// При CTun восстанавливаем сохранённый DDC-центр, чтобы VFO остался на своём
// месте в полосе (а не прыгал в центр). Берём только если offset влезает в
// текущий span — иначе центрируемся на видимой частоте.
if FCTun and (E.LastCenterHz > 0) and (Abs(Vis - E.LastCenterHz) < FSpanHz / 2) then
begin
FCenterFreq := E.LastCenterHz;
if FWDSPReady and Assigned(FDSPEngine) then FDSPEngine.SetShift(Vis - FCenterFreq);
end
else
begin
FCenterFreq := Vis;
if FWDSPReady and Assigned(FDSPEngine) then FDSPEngine.SetShift(0.0);
end;
FDriveLevel := CalcDriveByte;
PushNetworkState;