feat(slices): мультислайс-TX — выбор TX-слайса, кросс-банд (Flex-паритет)

- RadioController: FTxSliceId (0=главный VFO, >0=слайс), SetTxSlice
  эксклюзивно; ActiveTXFreqHz/ActiveTXMode с TX-слайса, режим применяется
  в SetMOX(True) перед SetTXRun; TxBandIdx для PA-кал и safety DoNotTx
  при кросс-банде; RemoveSlice сбрасывает TX-слайс.
- HPSDRNetwork: TX-антенна выбиралась по RX-бэнду → по TX-частоте
  (TxAnt[FreqToBandIndex(TXFreqHz)]).
- MainForm/PanafallPanel: клик TX на флаге слайса → SetTxSlice, TX-бейджи
  (янтарь=выбран, красный=передаёт), RefreshTxIndicators по rfTransmitting.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-13 18:15:52 +03:00
co-authored by Claude Opus 4.8
parent 9bb86b59b8
commit 8d65f4d6e8
5 changed files with 86 additions and 14 deletions
+57 -4
View File
@@ -263,6 +263,7 @@ type
FTransmitting: Boolean;
FTuning: Boolean;
FSplitTxB: Boolean;
FTxSliceId: Integer; // 0 = главный VFO (A/B split); >0 = слайс-источник TX
FVfoLock: Boolean; // блокировка перестройки VFO (Andromeda Lock)
FDisplayDuplex: Boolean;
// QO-100 full-duplex self-monitor: True — RX-аудио глушится на TX (по умолч.),
@@ -427,6 +428,13 @@ type
// uplink-LO не подвержен дрейфу LNB).
function XvtrTranslateTX(VisibleHz: Double): Double;
function ActiveTXFreqHz: Double;
// Режим передающего тракта: слайс-источник TX (FTxSliceId) либо главный FMode.
function ActiveTXMode: Integer;
// Диапазон для калибровки драйва/антенны TX (банд TX-слайса при кросс-банде).
function TxBandIdx: Integer;
// Выбор слайса-источника передачи (0 = главный VFO). Эксклюзивно.
procedure SetTxSlice(SliceId: Integer);
property TxSliceId: Integer read FTxSliceId;
// Складывает коррекцию маяка dLock в LOError активного трансвертера (живая
// калибровка LNB), ретюнит радио и троттлингом сохраняет на диск.
procedure ApplyBeaconLOError(dLock: Double);
@@ -1489,6 +1497,8 @@ var idx: Integer;
begin
idx := FindSliceIndex(Id);
if idx < 0 then Exit;
// Удаляемый слайс был источником TX — вернуть передачу на главный VFO.
if FTxSliceId = Id then SetTxSlice(0);
if Assigned(FDSPEngine) then FDSPEngine.RemoveSlice(Id);
if FSlices[idx].Audio <> nil then
begin
@@ -1757,7 +1767,12 @@ begin
end;
function TRadioController.ActiveTXFreqHz: Double;
var S: TCtrlSlice;
begin
// Мультислайс-TX: если выбран слайс-источник — передаём на его частоте
// (слайсы не несут repeater-конфиг, FM-сдвиг не применяем).
if (FTxSliceId > 0) and GetSlice(FTxSliceId, S) then
Exit(S.TargetHz);
// TX-VFO задаётся ТОЛЬКО селектором FSplitTxB (UI: кнопки TX-A/TX-B),
// независимо от активного RX-VFO. Раньше при не-split TX ошибочно следовал
// за активным RX (TX-A горит, но передача шла на B при активном B).
@@ -1770,6 +1785,41 @@ begin
end;
end;
function TRadioController.ActiveTXMode: Integer;
var S: TCtrlSlice;
begin
if (FTxSliceId > 0) and GetSlice(FTxSliceId, S) then Result := S.Mode
else Result := FMode;
end;
function TRadioController.TxBandIdx: Integer;
begin
// При TX на слайсе другого диапазона (только КВ) калибровка/антенна — по
// банду TX-частоты, иначе главный банд.
Result := FCurrentBand;
if (FTxSliceId > 0) and (FCurrentXvtr < 0) then
begin
Result := FreqToBandIdx(ActiveTXFreqHz, IsPluto);
if (Result < 0) or (Result >= CFG_BAND_COUNT) then Result := FCurrentBand;
end;
end;
procedure TRadioController.SetTxSlice(SliceId: Integer);
var S: TCtrlSlice;
begin
if SliceId < 0 then SliceId := 0;
// Слайс-источник должен существовать (0 = главный всегда валиден).
if (SliceId > 0) and not GetSlice(SliceId, S) then Exit;
if FTxSliceId = SliceId then Exit;
FTxSliceId := SliceId;
// Передающий тракт под режим нового источника (idle-канал, применится к PTT).
if FWDSPReady and Assigned(FDSPEngine) then
FDSPEngine.SetTXMode(ActiveTXMode);
FDriveLevel := CalcDriveByte; // калибровка драйва банда TX-слайса
PushNetworkState; // TX-частота/Alex через ActiveTXFreqHz/TxBandIdx
Changed(rfTransmitting); // UI: перерисовать TX-бейджи флагов/оверлей
end;
// ---------------------------------------------------------------------------
// QO-100 beacon lock
// ---------------------------------------------------------------------------
@@ -2118,8 +2168,8 @@ begin
FXvtrSettings.Entries[FCurrentXvtr].Enabled;
if XvtrActive then
Cal := FVHFBandCal[FCurrentXvtr]
else if (FCurrentBand >= 0) and (FCurrentBand < CFG_BAND_COUNT) then
Cal := FPABandCal[FCurrentBand]
else if (TxBandIdx >= 0) and (TxBandIdx < CFG_BAND_COUNT) then
Cal := FPABandCal[TxBandIdx] // банд TX-слайса при кросс-банд TX
else
Cal := 38.8;
@@ -3581,7 +3631,7 @@ var
begin
// Safety: блокируем TX на бэндах с DoNotTx (Alex) и на RX-only трансвертере.
if On_ and FDevConnected and (FCurrentXvtr < 0)
and FAlexSettings.DoNotTx[EnsureRange(FCurrentBand, 0, 10)] then
and FAlexSettings.DoNotTx[EnsureRange(TxBandIdx, 0, 10)] then
Exit;
if On_ and (FCurrentXvtr >= 0) and (FCurrentXvtr < CFG_XVTR_COUNT)
and FXvtrSettings.Entries[FCurrentXvtr].RXOnly then
@@ -3640,6 +3690,9 @@ begin
// В non-DUP RX-IQ пакеты дропаются на входе DSP-потока во время TX, чтобы
// TX leakage не накапливался в RXA pipeline / FFT-истории анализатора.
FDSPEngine.KeepRXDuringTX := FDisplayDuplex;
// Мультислайс-TX: передающий тракт под режим слайса-источника (SetMode мог
// оставить главный режим). Ставим перед стартом TX-потока.
if FTransmitting then FDSPEngine.SetTXMode(ActiveTXMode);
FDSPEngine.SetTXRun(FTransmitting);
if WasTransmitting and (not FTransmitting) and (not FDisplayDuplex) then
begin
@@ -3677,7 +3730,7 @@ begin
// Safety: те же блокировки, что и в SetMOX — TUN это TX через WDSP PostGen-тон,
// он тоже должен молчать на бэндах с DoNotTx и на RX-only XVTR.
if On_ and FDevConnected and (FCurrentXvtr < 0)
and FAlexSettings.DoNotTx[EnsureRange(FCurrentBand, 0, 10)] then Exit;
and FAlexSettings.DoNotTx[EnsureRange(TxBandIdx, 0, 10)] then Exit;
if On_ and (FCurrentXvtr >= 0) and (FCurrentXvtr < CFG_XVTR_COUNT)
and FXvtrSettings.Entries[FCurrentXvtr].RXOnly then Exit;
if On_ then