feat(pans): выбор аппаратного DDC для панадаптера

Клик по «NNN kHz · DDCn» в шапке пана — кроме сетки rate теперь есть пункты
«→ DDC N»: пан переезжает на другой аппаратный DDC (MovePanDDC), при этом
движок, анализатор и слайсы не пересоздаются — перепрошивается только
hw-индекс. Пригодилось при отладке: конкретный DDC платы может вести себя
хуже соседей, и пан можно просто увести на другой.

Диапазон ограничен реальными DDC железа: MaxDDCIndex = 7 (DDC8+ openHPSDR
enable-бит принимает, но поток не отдаёт). Ограничение действует и в меню,
и в DDCFree, и в самом MovePanDDC.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-14 12:52:31 +03:00
co-authored by Claude Opus 4.8
parent e040c1b24e
commit f33a1fbd0a
3 changed files with 78 additions and 4 deletions
+54
View File
@@ -596,6 +596,13 @@ type
// Смена rate живого пана: движок переоткрывает аккумулятор/анализатор/слайсы,
// DDC реконфигурируется; слайсы вне новой полосы клампятся к её краям.
function SetPanDDCRate(PanId: Integer; RateKHz: Word): Boolean;
// Переезд пана на другой аппаратный DDC (пан/слайсы/движок не пересоздаются,
// меняется только hw-индекс): бывает, что конкретный DDC платы ведёт себя
// хуже соседей — тогда пан можно просто увести на другой.
function MovePanDDC(PanId, NewDDC: Integer): Boolean;
function PanDDCIndex(PanId: Integer): Integer; // hw-индекс DDC пана
function MaxDDCIndex: Integer; // последний DDC железа
function DDCFree(DDCIdx: Integer): Boolean; // индекс свободен?
function PanDDCActive(PanId: Integer): Boolean;
function PanDDCFreq(PanId: Integer): Double;
function PanDDCRateKHz(PanId: Integer): Word;
@@ -1311,6 +1318,53 @@ begin
Result := True;
end;
function TRadioController.MovePanDDC(PanId, NewDDC: Integer): Boolean;
// Тот же пан (движок/анализатор/слайсы не трогаем) — только другой аппаратный
// DDC: старый гасим, новый включаем с теми же freq/rate/ADC. Короткий разрыв IQ
// на время реконфига — норма.
begin
Result := False;
if not PanDDCActive(PanId) then Exit;
if not Assigned(FNetwork) then Exit;
if (NewDDC <= FActiveDDC) or (NewDDC > MaxDDCIndex) then Exit;
if FPanDDCIdx[PanId] = NewDDC then Exit(True);
if FDDCPanMap[NewDDC] >= 0 then Exit; // занят другим паном
FNetwork.SetPanDDC(FPanDDCIdx[PanId], False, 0, 0, 0);
FDDCPanMap[FPanDDCIdx[PanId]] := -1;
// Seq-трекинг обоих индексов начинаем заново (новый поток — новый счётчик).
if FPanDDCIdx[PanId] <= High(FDDCSeqValid) then
FDDCSeqValid[FPanDDCIdx[PanId]] := False;
if NewDDC <= High(FDDCSeqValid) then
FDDCSeqValid[NewDDC] := False;
FPanDDCIdx[PanId] := NewDDC;
FDDCPanMap[NewDDC] := PanId;
FNetwork.SetPanDDC(NewDDC, True, XvtrTranslate(FPanFreqHz[PanId]),
FPanRateKHz[PanId], FPanADC[PanId]);
Result := True;
end;
function TRadioController.PanDDCIndex(PanId: Integer): Integer;
begin
if (PanId >= 1) and (PanId < MAX_PANS) then Result := FPanDDCIdx[PanId]
else if PanId = 0 then Result := FActiveDDC
else Result := -1;
end;
function TRadioController.MaxDDCIndex: Integer;
// Железо openHPSDR (Orion/Saturn) поднимает DDC0..DDC7 — DDC8 и выше молчат
// (проверено на плате: enable-бит ставится, поток не идёт).
begin
Result := 7;
end;
function TRadioController.DDCFree(DDCIdx: Integer): Boolean;
begin
Result := (DDCIdx > FActiveDDC) and (DDCIdx <= MaxDDCIndex)
and (FDDCPanMap[DDCIdx] < 0);
end;
procedure TRadioController.RemovePanDDC(PanId: Integer);
var ddc, i: Integer;
begin