mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 20:37:33 +00:00
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:
co-authored by
Claude Opus 4.8
parent
24102343ea
commit
e09bc21bba
+26
-5
@@ -878,9 +878,14 @@ begin
|
||||
FSpecView.ResetWfAvgBuf;
|
||||
// Состояние + DSP + события рендера — в контроллере.
|
||||
FController.RestoreBand(BandIdx);
|
||||
// Финальная перестройка VFO A с канальной оркестрацией (auto-CTCSS,
|
||||
// pre-channel drive) — через UI-путь ApplyVfoA.
|
||||
ApplyVfoA(Round(FController.FVfoA));
|
||||
// Финальная перестройка приёмника на АКТИВНЫЙ VFO. Если активен B — нельзя
|
||||
// центрироваться на VFO A (иначе движок слушает A, а маркер/мышь — на B).
|
||||
// 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;
|
||||
|
||||
procedure TMainForm.SaveAllAndExit;
|
||||
@@ -891,9 +896,12 @@ begin
|
||||
FController.FSettings.SaveGlobal(FController.FDevMAC, MakeGlobalSettings);
|
||||
if FController.FDevConnected then
|
||||
FController.FSettings.SaveTX(FController.FDevMAC, FController.FTXSettings);
|
||||
// Запомнить LastFreq XVTR для следующего запуска
|
||||
// Запомнить LastFreq A/B XVTR для следующего запуска
|
||||
if (FController.FCurrentXvtr >= 0) and (FController.FCurrentXvtr < CFG_XVTR_COUNT) then
|
||||
FController.FXvtrSettings.Entries[FController.FCurrentXvtr].LastFreq := FController.FVfoA;
|
||||
begin
|
||||
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.Save;
|
||||
end;
|
||||
@@ -4140,6 +4148,15 @@ begin
|
||||
and FController.FXvtrSettings.Entries[G_Settings.LastXvtr].Enabled then
|
||||
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(0, 'Board: ' + BoardTypeName(FController.FNetwork.Device.BoardType));
|
||||
|
||||
@@ -6562,6 +6579,9 @@ begin
|
||||
// CTUN
|
||||
FController.FCTun := E.LastCTun;
|
||||
StyleButton(BtnCTun, FController.FCTun);
|
||||
// VFO-B: своя сохранённая частота слота, клэмп в диапазон трансвертера.
|
||||
FController.FVfoB := EnsureRange(E.LastFreqB, E.FreqBegin, E.FreqEnd);
|
||||
FreqDispB.Frequency := Round(FController.FVfoB);
|
||||
// VFO на видимую частоту; FController.FCenterFreq тоже visible
|
||||
FController.FVfoA := Vis;
|
||||
FController.FCenterFreq := Vis;
|
||||
@@ -6600,6 +6620,7 @@ begin
|
||||
if FController.FCurrentXvtr < CFG_XVTR_COUNT then
|
||||
begin
|
||||
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].LastFilterIdx := FController.FFilter;
|
||||
FController.FXvtrSettings.Entries[FController.FCurrentXvtr].LastFMSQOn := FController.FFMSQOn;
|
||||
|
||||
+77
-33
@@ -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;
|
||||
|
||||
|
||||
+11
-2
@@ -161,7 +161,8 @@ type
|
||||
TXPower: Integer; // 0..100 % (drive level специально для XVTR)
|
||||
DisablePA: Boolean; // True — отключить внутренний PA при TX (XVTR имеет свой)
|
||||
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)
|
||||
LastFilterIdx: Integer; // FM: 0=NFM, 1=WFM; иначе 0..FILT_COUNT-1
|
||||
LastFMSQOn: Boolean;
|
||||
@@ -1205,6 +1206,7 @@ begin
|
||||
X.Entries[i].DisablePA := False;
|
||||
X.Entries[i].RXAntenna := 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].LastFilterIdx := 0; // NFM
|
||||
X.Entries[i].LastFMSQOn := False;
|
||||
@@ -1227,11 +1229,13 @@ begin
|
||||
X.Entries[0].FreqEnd := 148000000;
|
||||
X.Entries[0].LOOffset := 116000000;
|
||||
X.Entries[0].LastFreq := 144300000;
|
||||
X.Entries[0].LastFreqB := 145500000;
|
||||
X.Entries[1].ButtonText := '70cm';
|
||||
X.Entries[1].FreqBegin := 430000000;
|
||||
X.Entries[1].FreqEnd := 440000000;
|
||||
X.Entries[1].LOOffset := 404000000;
|
||||
X.Entries[1].LastFreq := 432200000;
|
||||
X.Entries[1].LastFreqB := 433000000;
|
||||
end;
|
||||
|
||||
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].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].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].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);
|
||||
@@ -1280,10 +1285,13 @@ begin
|
||||
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].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
|
||||
(X.Entries[i].LastFreq > X.Entries[i].FreqEnd) then
|
||||
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;
|
||||
|
||||
@@ -1310,6 +1318,7 @@ begin
|
||||
JW(EObj, 'dis_pa', X.Entries[i].DisablePA);
|
||||
JW(EObj, 'rx_ant', Integer(X.Entries[i].RXAntenna));
|
||||
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_filt', X.Entries[i].LastFilterIdx);
|
||||
JW(EObj, 'last_fmsq_on', X.Entries[i].LastFMSQOn);
|
||||
|
||||
Reference in New Issue
Block a user