fix(slices): XVTR-трансляция частот панов N

Частота пана хранится ВИДИМОЙ (как FVfoA/FCenterFreq) и транслируется в
IF через XvtrTranslate только на границе с железом (AddPanDDC,
SetPanDDCFreq, SetPanDDCRate, SetPanADC) — раньше DDC пана получал
видимую частоту сырьём, а drag водопада пана N клампился пределами АЦП
(~61 МГц) вместо диапазона трансвертера.

Новое: controller.VisibleFreqBounds (диапазон активного XVTR | пределы
железа) для UI-клампа драга; RefreshPanDDCs — на входе/выходе XVTR и
правке LO/LOError клампит видимые центры живых панов, перешивает
freq-слова DDC и прижимает выпавшие слайсы к краям полосы захвата.

Подтверждено юзером на трансвертере.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-12 16:02:54 +03:00
co-authored by Claude Fable 5
parent eac66100a8
commit b3edf3a4ad
2 changed files with 78 additions and 12 deletions
+6 -2
View File
@@ -6154,8 +6154,10 @@ begin
end; end;
PanViewWindow(P, VC, VS); PanViewWindow(P, VC, VS);
NewHz := FPanCenterDragF0 - (X - FPanCenterDragX0) * VS / W; NewHz := FPanCenterDragF0 - (X - FPanCenterDragX0) * VS / W;
NewHz := Max(FController.BackendCaps.MinFreqHz, // Кламп в ВИДИМЫХ частотах: при активном XVTR — его диапазон (иначе
Min(FController.BackendCaps.MaxFreqHz, NewHz)); // 1296 МГц «падала» на MaxFreqHz железа ~61 МГц), без XVTR — железо.
FController.VisibleFreqBounds(LoHz, HiHz);
NewHz := Max(LoHz, Min(HiHz, NewHz));
FController.SetPanDDCFreq(P.PanId, NewHz); FController.SetPanDDCFreq(P.PanId, NewHz);
SyncPanZoomUI(P); // окно вьюхи с учётом зума + freq зум-бара SyncPanZoomUI(P); // окно вьюхи с учётом зума + freq зум-бара
P.LayoutFlags; P.LayoutFlags;
@@ -6885,6 +6887,8 @@ begin
DeactivateXvtr; DeactivateXvtr;
// Перепрошить XVTR в сеть (если параметры текущего изменились) // Перепрошить XVTR в сеть (если параметры текущего изменились)
ApplyXvtrToNetwork; ApplyXvtrToNetwork;
// Правка LO/LOError при живых панах: freq-слова их DDC — через свежую трансляцию.
FController.RefreshPanDDCs;
UpdateWidebandFrequencyView; UpdateWidebandFrequencyView;
if PbWideband <> nil then PbWideband.Invalidate; if PbWideband <> nil then PbWideband.Invalidate;
if PbWidebandRuler <> nil then PbWidebandRuler.Invalidate; if PbWidebandRuler <> nil then PbWidebandRuler.Invalidate;
+72 -10
View File
@@ -565,13 +565,21 @@ type
function SliceFitsCapture(TargetHz: Double; PanId: Integer = 0): Boolean; function SliceFitsCapture(TargetHz: Double; PanId: Integer = 0): Boolean;
// ---- Панадаптеры на аппаратных DDC (этап 3.1; UI/движок — 3.2/3.3) ---- // ---- Панадаптеры на аппаратных DDC (этап 3.1; UI/движок — 3.2/3.3) ----
// Создаёт пан PanId (1..MAX_PANS-1) на своём DDC: FreqHz = центр (сырые Гц // Создаёт пан PanId (1..MAX_PANS-1) на своём DDC: FreqHz = центр в ВИДИМЫХ
// железа, XVTR-трансляции у панов нет), RateKHz ∈ PanRateAllowed (иначе 384), // Гц (как FVfoA/FCenterFreq: при активном XVTR транслируется в IF только
// ADCSource — АЦП платы. False = нет слота/бэкенд не умеет/не Running. // на границе с железом, XvtrTranslate), RateKHz ∈ PanRateAllowed (иначе
// 384), ADCSource — АЦП платы. False = нет слота/бэкенд не умеет/не Running.
function AddPanDDC(PanId: Integer; FreqHz: Double; RateKHz: Word = 384; function AddPanDDC(PanId: Integer; FreqHz: Double; RateKHz: Word = 384;
ADCSource: Byte = 0): Boolean; ADCSource: Byte = 0): Boolean;
procedure RemovePanDDC(PanId: Integer); procedure RemovePanDDC(PanId: Integer);
procedure SetPanDDCFreq(PanId: Integer; FreqHz: Double); // ретюн (HP-пакет) procedure SetPanDDCFreq(PanId: Integer; FreqHz: Double); // ретюн (HP-пакет)
// Допустимое окно ВИДИМОЙ частоты пана: активный XVTR — его диапазон,
// иначе пределы железа (UI-кламп drag/тюна панов N).
procedure VisibleFreqBounds(out LoHz, HiHz: Double);
// Вход/выход/перекалибровка XVTR при живых панах: клампит видимые частоты
// панов в новое окно, перешивает freq-слова DDC (XvtrTranslate), слайсы
// панов — к краям их полос захвата.
procedure RefreshPanDDCs;
// Rate ∈ сетке бэкенда (Caps.RatePresets) и кратен 48 кГц (движок). // Rate ∈ сетке бэкенда (Caps.RatePresets) и кратен 48 кГц (движок).
function PanRateAllowed(RateKHz: Word): Boolean; function PanRateAllowed(RateKHz: Word): Boolean;
// Смена rate живого пана: движок переоткрывает аккумулятор/анализатор/слайсы, // Смена rate живого пана: движок переоткрывает аккумулятор/анализатор/слайсы,
@@ -1252,7 +1260,8 @@ begin
FPanZoomF[PanId] := 0.0; FPanZoomF[PanId] := 0.0;
FPanPanSl[PanId] := 0.5; FPanPanSl[PanId] := 0.5;
FDDCPanMap[ddc] := PanId; FDDCPanMap[ddc] := PanId;
FNetwork.SetPanDDC(ddc, True, FreqHz, RateKHz, ADCSource); // В железо — IF-частота: FPanFreqHz хранит видимую (как FCenterFreq).
FNetwork.SetPanDDC(ddc, True, XvtrTranslate(FreqHz), RateKHz, ADCSource);
Result := True; Result := True;
end; end;
@@ -1270,10 +1279,10 @@ begin
if FWDSPReady and not FDSPEngine.SetPanRate(PanId, Integer(RateKHz) * 1000) then if FWDSPReady and not FDSPEngine.SetPanRate(PanId, Integer(RateKHz) * 1000) then
Exit; Exit;
FPanRateKHz[PanId] := RateKHz; FPanRateKHz[PanId] := RateKHz;
// DDC железа: реконфиг rate (RebuildDDCSpecific + freq-слово). // DDC железа: реконфиг rate (RebuildDDCSpecific + freq-слово, IF при XVTR).
if Assigned(FNetwork) then if Assigned(FNetwork) then
FNetwork.SetPanDDC(FPanDDCIdx[PanId], True, FPanFreqHz[PanId], RateKHz, FNetwork.SetPanDDC(FPanDDCIdx[PanId], True, XvtrTranslate(FPanFreqHz[PanId]),
FPanADC[PanId]); RateKHz, FPanADC[PanId]);
// Слайсы, выпавшие из сузившейся полосы, — к её краям (абс. TargetHz + NCO). // Слайсы, выпавшие из сузившейся полосы, — к её краям (абс. TargetHz + NCO).
CapturedSpan(LoHz, HiHz, PanId); CapturedSpan(LoHz, HiHz, PanId);
for i := 0 to MAX_SLICES - 1 do for i := 0 to MAX_SLICES - 1 do
@@ -1312,9 +1321,9 @@ var i: Integer;
begin begin
if (PanId < 1) or (PanId >= MAX_PANS) then Exit; if (PanId < 1) or (PanId >= MAX_PANS) then Exit;
if FPanDDCIdx[PanId] < 0 then Exit; if FPanDDCIdx[PanId] < 0 then Exit;
FPanFreqHz[PanId] := FreqHz; FPanFreqHz[PanId] := FreqHz; // видимая; в железо — IF при активном XVTR
if Assigned(FNetwork) then if Assigned(FNetwork) then
FNetwork.SetPanDDCFreq(FPanDDCIdx[PanId], FreqHz); FNetwork.SetPanDDCFreq(FPanDDCIdx[PanId], XvtrTranslate(FreqHz));
// Слайсы пана держат абсолютный TargetHz — обновляем их NCO-сдвиги // Слайсы пана держат абсолютный TargetHz — обновляем их NCO-сдвиги
// относительно нового центра (аналог RefreshSliceShifts главного). // относительно нового центра (аналог RefreshSliceShifts главного).
if Assigned(FDSPEngine) then if Assigned(FDSPEngine) then
@@ -1323,6 +1332,57 @@ begin
FDSPEngine.SetSliceShift(FSlices[i].Id, FSlices[i].TargetHz - FreqHz); FDSPEngine.SetSliceShift(FSlices[i].Id, FSlices[i].TargetHz - FreqHz);
end; end;
procedure TRadioController.VisibleFreqBounds(out LoHz, HiHz: Double);
begin
if (FCurrentXvtr >= 0) and (FCurrentXvtr < CFG_XVTR_COUNT) and
FXvtrSettings.Entries[FCurrentXvtr].Enabled then
begin
// Активный XVTR: видимые частоты живут в его диапазоне, в железо уходит
// IF (XvtrTranslate) — клампить видимую частоту пределами АЦП нельзя.
LoHz := FXvtrSettings.Entries[FCurrentXvtr].FreqBegin;
HiHz := FXvtrSettings.Entries[FCurrentXvtr].FreqEnd;
end
else if Assigned(FNetwork) then
begin
LoHz := FNetwork.Caps.MinFreqHz;
HiHz := FNetwork.Caps.MaxFreqHz;
end
else
begin
LoHz := 0;
HiHz := 61440000;
end;
end;
procedure TRadioController.RefreshPanDDCs;
var
p, i: Integer;
LoV, HiV, LoHz, HiHz: Double;
begin
VisibleFreqBounds(LoV, HiV);
for p := 1 to MAX_PANS - 1 do
if PanDDCActive(p) then
begin
// Видимый центр пана — в новое допустимое окно, freq-слово DDC — через
// свежую трансляцию (вход/выход XVTR, правка LO/LOError).
FPanFreqHz[p] := EnsureRange(FPanFreqHz[p], LoV, HiV);
if Assigned(FNetwork) then
FNetwork.SetPanDDCFreq(FPanDDCIdx[p], XvtrTranslate(FPanFreqHz[p]));
// Слайсы держат абсолютный видимый TargetHz: выпавшие из полосы захвата
// пана — к её краям (SetSliceTarget обновит NCO), остальные — свежий NCO.
CapturedSpan(LoHz, HiHz, p);
for i := 0 to MAX_SLICES - 1 do
if FSlices[i].Active and (FSlices[i].PanId = p) then
begin
if FSlices[i].TargetHz < LoHz then SetSliceTarget(FSlices[i].Id, LoHz)
else if FSlices[i].TargetHz > HiHz then SetSliceTarget(FSlices[i].Id, HiHz)
else if Assigned(FDSPEngine) then
FDSPEngine.SetSliceShift(FSlices[i].Id,
FSlices[i].TargetHz - FPanFreqHz[p]);
end;
end;
end;
function TRadioController.PanDDCActive(PanId: Integer): Boolean; function TRadioController.PanDDCActive(PanId: Integer): Boolean;
begin begin
Result := (PanId >= 1) and (PanId < MAX_PANS) and (FPanDDCIdx[PanId] >= 0); Result := (PanId >= 1) and (PanId < MAX_PANS) and (FPanDDCIdx[PanId] >= 0);
@@ -1364,7 +1424,7 @@ begin
if not PanDDCActive(PanId) then Exit; if not PanDDCActive(PanId) then Exit;
if FPanADC[PanId] = ADC then Exit(True); if FPanADC[PanId] = ADC then Exit(True);
FPanADC[PanId] := ADC; FPanADC[PanId] := ADC;
FNetwork.SetPanDDC(FPanDDCIdx[PanId], True, FPanFreqHz[PanId], FNetwork.SetPanDDC(FPanDDCIdx[PanId], True, XvtrTranslate(FPanFreqHz[PanId]),
FPanRateKHz[PanId], ADC); FPanRateKHz[PanId], ADC);
Result := True; Result := True;
end; end;
@@ -3007,6 +3067,7 @@ begin
// QO-100 / split-LO: авто display-duplex (мониторинг своего downlink при TX). // QO-100 / split-LO: авто display-duplex (мониторинг своего downlink при TX).
if E.FullDuplexTx and not FDisplayDuplex then SetDuplex(True); if E.FullDuplexTx and not FDisplayDuplex then SetDuplex(True);
PushNetworkState; PushNetworkState;
RefreshPanDDCs; // живые паны — в диапазон XVTR, DDC — на IF
Changed(rfXvtr); Changed(rfXvtr);
Changed(rfActiveVfo); Changed(rfActiveVfo);
@@ -3023,6 +3084,7 @@ begin
if FDevConnected then FSettings.Save; if FDevConnected then FSettings.Save;
FCurrentXvtr := -1; FCurrentXvtr := -1;
ApplyXvtrToNetwork; // снять XVTR enable bit ApplyXvtrToNetwork; // снять XVTR enable bit
RefreshPanDDCs; // живые паны — обратно в пределы железа
if (FCurrentBand >= 0) and (FCurrentBand < CFG_BAND_COUNT) then if (FCurrentBand >= 0) and (FCurrentBand < CFG_BAND_COUNT) then
RestoreBand(FCurrentBand); // HF-возврат (состояние/DSP/retune) RestoreBand(FCurrentBand); // HF-возврат (состояние/DSP/retune)
Changed(rfXvtr); // погасить XVTR-кнопки + wideband/web (rfXvtr-рендер) Changed(rfXvtr); // погасить XVTR-кнопки + wideband/web (rfXvtr-рендер)