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
+72 -10
View File
@@ -565,13 +565,21 @@ type
function SliceFitsCapture(TargetHz: Double; PanId: Integer = 0): Boolean;
// ---- Панадаптеры на аппаратных DDC (этап 3.1; UI/движок — 3.2/3.3) ----
// Создаёт пан PanId (1..MAX_PANS-1) на своём DDC: FreqHz = центр (сырые Гц
// железа, XVTR-трансляции у панов нет), RateKHz ∈ PanRateAllowed (иначе 384),
// ADCSource — АЦП платы. False = нет слота/бэкенд не умеет/не Running.
// Создаёт пан PanId (1..MAX_PANS-1) на своём DDC: FreqHz = центр в ВИДИМЫХ
// Гц (как FVfoA/FCenterFreq: при активном XVTR транслируется в IF только
// на границе с железом, XvtrTranslate), RateKHz ∈ PanRateAllowed (иначе
// 384), ADCSource — АЦП платы. False = нет слота/бэкенд не умеет/не Running.
function AddPanDDC(PanId: Integer; FreqHz: Double; RateKHz: Word = 384;
ADCSource: Byte = 0): Boolean;
procedure RemovePanDDC(PanId: Integer);
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 кГц (движок).
function PanRateAllowed(RateKHz: Word): Boolean;
// Смена rate живого пана: движок переоткрывает аккумулятор/анализатор/слайсы,
@@ -1252,7 +1260,8 @@ begin
FPanZoomF[PanId] := 0.0;
FPanPanSl[PanId] := 0.5;
FDDCPanMap[ddc] := PanId;
FNetwork.SetPanDDC(ddc, True, FreqHz, RateKHz, ADCSource);
// В железо — IF-частота: FPanFreqHz хранит видимую (как FCenterFreq).
FNetwork.SetPanDDC(ddc, True, XvtrTranslate(FreqHz), RateKHz, ADCSource);
Result := True;
end;
@@ -1270,10 +1279,10 @@ begin
if FWDSPReady and not FDSPEngine.SetPanRate(PanId, Integer(RateKHz) * 1000) then
Exit;
FPanRateKHz[PanId] := RateKHz;
// DDC железа: реконфиг rate (RebuildDDCSpecific + freq-слово).
// DDC железа: реконфиг rate (RebuildDDCSpecific + freq-слово, IF при XVTR).
if Assigned(FNetwork) then
FNetwork.SetPanDDC(FPanDDCIdx[PanId], True, FPanFreqHz[PanId], RateKHz,
FPanADC[PanId]);
FNetwork.SetPanDDC(FPanDDCIdx[PanId], True, XvtrTranslate(FPanFreqHz[PanId]),
RateKHz, FPanADC[PanId]);
// Слайсы, выпавшие из сузившейся полосы, — к её краям (абс. TargetHz + NCO).
CapturedSpan(LoHz, HiHz, PanId);
for i := 0 to MAX_SLICES - 1 do
@@ -1312,9 +1321,9 @@ var i: Integer;
begin
if (PanId < 1) or (PanId >= MAX_PANS) then Exit;
if FPanDDCIdx[PanId] < 0 then Exit;
FPanFreqHz[PanId] := FreqHz;
FPanFreqHz[PanId] := FreqHz; // видимая; в железо — IF при активном XVTR
if Assigned(FNetwork) then
FNetwork.SetPanDDCFreq(FPanDDCIdx[PanId], FreqHz);
FNetwork.SetPanDDCFreq(FPanDDCIdx[PanId], XvtrTranslate(FreqHz));
// Слайсы пана держат абсолютный TargetHz — обновляем их NCO-сдвиги
// относительно нового центра (аналог RefreshSliceShifts главного).
if Assigned(FDSPEngine) then
@@ -1323,6 +1332,57 @@ begin
FDSPEngine.SetSliceShift(FSlices[i].Id, FSlices[i].TargetHz - FreqHz);
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;
begin
Result := (PanId >= 1) and (PanId < MAX_PANS) and (FPanDDCIdx[PanId] >= 0);
@@ -1364,7 +1424,7 @@ begin
if not PanDDCActive(PanId) then Exit;
if FPanADC[PanId] = ADC then Exit(True);
FPanADC[PanId] := ADC;
FNetwork.SetPanDDC(FPanDDCIdx[PanId], True, FPanFreqHz[PanId],
FNetwork.SetPanDDC(FPanDDCIdx[PanId], True, XvtrTranslate(FPanFreqHz[PanId]),
FPanRateKHz[PanId], ADC);
Result := True;
end;
@@ -3007,6 +3067,7 @@ begin
// QO-100 / split-LO: авто display-duplex (мониторинг своего downlink при TX).
if E.FullDuplexTx and not FDisplayDuplex then SetDuplex(True);
PushNetworkState;
RefreshPanDDCs; // живые паны — в диапазон XVTR, DDC — на IF
Changed(rfXvtr);
Changed(rfActiveVfo);
@@ -3023,6 +3084,7 @@ begin
if FDevConnected then FSettings.Save;
FCurrentXvtr := -1;
ApplyXvtrToNetwork; // снять XVTR enable bit
RefreshPanDDCs; // живые паны — обратно в пределы железа
if (FCurrentBand >= 0) and (FCurrentBand < CFG_BAND_COUNT) then
RestoreBand(FCurrentBand); // HF-возврат (состояние/DSP/retune)
Changed(rfXvtr); // погасить XVTR-кнопки + wideband/web (rfXvtr-рендер)