Phase 3 (batch 18): move MOX (TX core) into the controller

SetMOX now carries the TX engine/network logic: DoNotTx/RXOnly safety
gates, drive recalc, the PTT relay sequence (relay-before-PTT on TX-on,
PTT-before-relay on TX-off), mic-source selection (web/HW-PTT/default),
KeepRXDuringTX, SetTXRun, and the non-DUP TX->RX FlushRX + post-TX mute.
New controller fields FWebClientActive (mirrored from the web server in
the thin ApplyMOX wrapper) and FWebMicActive (moved from MainForm), plus
DefaultMicSource. UI render (BtnMOX style, FSpecView TX overlay/mode,
grid, FDUCPendingCount reset) moved to OnControllerState(rfTransmitting).

Also fix a pre-existing status-bar bug: web TX was labelled 'TX radio
mic' (the old if/else folded any non-soundcard source into radio); now
a case shows 'TX web mic' for txmsWeb.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Uladzimir Karpenka
2026-06-05 16:35:54 +03:00
co-authored by Claude Opus 4.8
parent c249b46b17
commit 603e0850cf
2 changed files with 117 additions and 101 deletions
+30 -100
View File
@@ -218,7 +218,6 @@ type
FWebBindAddr: string;
FWebUser: string;
FWebPass: string;
FWebMicActive: Boolean; // True если текущий TX идёт через txmsWeb
// --- CAT ---
FCATEngine: TCATEngine;
FCATSerial: TCATSerialManager;
@@ -1326,6 +1325,28 @@ begin
if PbRuler <> nil then PbRuler.Invalidate;
end;
end;
rfTransmitting:
begin
if FController.FTransmitting then
begin
BtnMOX.ClrNorm := TColor($00000044);
BtnMOX.ClrText := TColor($000000FF);
BtnMOX.ClrTextAct := TColor($000000FF);
BtnMOX.Active := True;
end else
StyleButton(BtnMOX, False);
// TX-overlay в SpectrumView: TX-частота/VFO-индекс (по селектору
// FSplitTxB, независимо от активного RX-VFO) и режим.
FSpecView.TXFreq := ActiveTXFreqHz;
if FController.FSplitTxB then FSpecView.TXVfoIndex := 1
else FSpecView.TXVfoIndex := 0;
// DUP: данные от RX-анализатора (TXMode=False), TX-полоса рисуется
// поверх через TXOverlay. Без DUP при TX оба флага True.
FSpecView.TXMode := FController.FTransmitting and not FController.FDisplayDuplex;
FSpecView.TXOverlay := FController.FTransmitting;
ApplySpecViewGridFromState; // сетка спектра под активный тракт
FDUCPendingCount := 0;
end;
rfBand:
// В XVTR-режиме HF-кнопки гасим (активна XVTR-кнопка, см. rfXvtr).
for i := 0 to BAND_COUNT - 1 do
@@ -3010,10 +3031,12 @@ begin
begin
if FController.FTransmitting then
begin
if FController.FDSPEngine.TXMicSource = txmsSoundCard then
SetStatusText(5, 'TX sound card')
case FController.FDSPEngine.TXMicSource of
txmsSoundCard: SetStatusText(5, 'TX sound card');
txmsWeb: SetStatusText(5, 'TX web mic');
else
SetStatusText(5, 'TX radio mic');
end;
end
else
SetStatusText(5, 'TX idle');
@@ -5339,104 +5362,11 @@ begin
end;
procedure TMainForm.ApplyMOX(Active: Boolean);
var
WasTransmitting: Boolean;
begin
// Safety: block TX on bands marked DoNotTx in Alex config
if Active and FController.FDevConnected and (FController.FCurrentXvtr < 0)
and FController.FAlexSettings.DoNotTx[EnsureRange(FController.FCurrentBand, 0, 10)] then
Exit;
// Safety: block TX on RX-only transverter
if Active and (FController.FCurrentXvtr >= 0) and (FController.FCurrentXvtr < CFG_XVTR_COUNT)
and FController.FXvtrSettings.Entries[FController.FCurrentXvtr].RXOnly then
Exit;
WasTransmitting := FController.FTransmitting;
FController.FTransmitting := Active;
if Active then FController.FDriveLevel := CalcDriveByte;
FDUCPendingCount := 0;
if FController.FTransmitting then
begin
BtnMOX.ClrNorm := TColor($00000044);
BtnMOX.ClrText := TColor($000000FF);
BtnMOX.ClrTextAct := TColor($000000FF);
BtnMOX.Active := True;
end else
StyleButton(BtnMOX, False);
// TX-overlay в SpectrumView: фиксируем актуальную TX-частоту/VFO-индекс и режим
FSpecView.TXFreq := ActiveTXFreqHz;
// TX-VFO задаётся селектором FSplitTxB, независимо от активного RX-VFO.
if FController.FSplitTxB then FSpecView.TXVfoIndex := 1
else FSpecView.TXVfoIndex := 0;
// DUP: данные остаются от RX-анализатора (TXMode=False), но TX-полоса
// фильтра рисуется поверх через TXOverlay. Без DUP при TX оба флага True.
FSpecView.TXMode := FController.FTransmitting and not FController.FDisplayDuplex;
FSpecView.TXOverlay := FController.FTransmitting;
// Перерисовываем сетку спектра под активный тракт (Thetis-style).
ApplySpecViewGridFromState;
if FController.FTransmitting and FController.FNetwork.Connected and FController.FNetwork.Running then
begin
FController.FNetwork.ClearDUCIQQueue;
FController.FNetwork.UpdateState(XvtrTranslate(FController.FCenterFreq), XvtrTranslate(ActiveTXFreqHz), FController.FDriveLevel,
True, True, True);
FController.FNetwork.SendFullHP; // 1й пакет: Alex bit27=1 (T/R relay), drive — без PTT
FController.FNetwork.SendPTT(True); // 2й пакет: PTT=1 после переключения реле
end;
if FController.FWDSPReady then
begin
// Mic source — определяем автоматически по источнику TX:
// Web TX → txmsWeb
// HW PTT (FController.FHWPTTActive при входе в ApplyMOX уже True) → txmsRadio
// MOX-кнопка/CAT → sound card если настроена, иначе txmsRadio
if FController.FTransmitting then
begin
if Assigned(FWebServer) and FWebServer.WebClientActive then
begin
FWebMicActive := True;
FController.FDSPEngine.SetTXMicSource(txmsWeb);
end
else if FController.FHWPTTActive then
FController.FDSPEngine.SetTXMicSource(txmsRadio)
else
FController.FDSPEngine.SetTXMicSource(DefaultMicSource);
end
else if FWebMicActive then
begin
FWebMicActive := False;
FController.FDSPEngine.SetTXMicSource(DefaultMicSource);
end;
// В non-DUP RX-IQ пакеты дропаются на входе DSP-потока во время TX,
// чтобы TX leakage не накапливался в RXA pipeline и FFT-истории
// RX-анализатора. В DUP RX-тракт работает как обычно.
FController.FDSPEngine.KeepRXDuringTX := FController.FDisplayDuplex;
FController.FDSPEngine.SetTXRun(FController.FTransmitting);
if WasTransmitting and (not FController.FTransmitting) and (not FController.FDisplayDuplex) then
begin
FController.FDSPEngine.FlushRX;
if Assigned(FController.FAudioOut) then
FController.FAudioOut.Clear;
{$IFDEF WINDOWS}
// Windows: на TX→RX дропаем И RX-IQ пакеты, И аудио-выход на это окно.
// Источник «хвоста» — radio FPGA TX-buffer + PA slew-down: после MOX-off
// радио дотравливает буфер передатчика, RX1 ловит это как leak, и WDSP
// отдаёт это и в водопад, и в звук. На Linux наблюдается окно <30мс,
// на Windows может тянуться 200–500мс. Подобрать значение можно здесь
// без перекомпиляции engine. На Linux код-путь не активируется.
FController.FDSPEngine.BeginPostTXMute(250);
{$ENDIF}
end;
// SetTXRun переключает FActiveDisplayID на TX_DISP_ID; при DUP возвращаем
// источник на RX, чтобы видеть приём во время передачи.
if FController.FTransmitting and FController.FDisplayDuplex then
FController.FDSPEngine.SetDisplaySourceTX(False);
end;
if (not FController.FTransmitting) and FController.FNetwork.Connected and FController.FNetwork.Running then
begin
FController.FNetwork.SendPTT(False); // 1й пакет: PTT=0 — сначала снимаем PTT
FController.FNetwork.UpdateState(XvtrTranslate(FController.FCenterFreq), XvtrTranslate(ActiveTXFreqHz), FController.FDriveLevel,
False, True, True);
FController.FNetwork.ClearDUCIQQueue;
FController.FNetwork.SendFullHP; // 2й пакет: Alex bit27=0 (T/R relay RX), drive=0
end;
// Web-слой пока не владеет своим флагом — зеркалим текущее состояние клиента
// в контроллер перед командой (определяет mic-source при TX-on).
FController.FWebClientActive := Assigned(FWebServer) and FWebServer.WebClientActive;
FController.SetMOX(Active); // ядро TX (safety/PTT/DSP) + рендер через rfTransmitting
end;
procedure TMainForm.BtnMOXClick(Sender: TObject);
+87 -1
View File
@@ -159,6 +159,8 @@ type
FDisplayDuplex: Boolean;
FHWPTTActive: Boolean;
FHWPTTStartedTX: Boolean;
FWebClientActive: Boolean; // выставляет web-слой (connect/disconnect клиента)
FWebMicActive: Boolean; // True если текущий TX идёт через txmsWeb
FSendAudioToRadio: Boolean;
FAudioOutDevName: string;
FAudioInDevName: string;
@@ -223,6 +225,10 @@ type
// (band/xvtr cal, TUN/drive%, PA max). Чистая функция над состоянием.
function CalcDriveByte: Byte;
// Источник микрофона по умолчанию для MOX-кнопки/Web-TX: звуковая карта
// если настроена, иначе HW mic-стрим трансивера. HW PTT → txmsRadio в SetMOX.
function DefaultMicSource: TTXMicSource;
// ---- Управление диапазонами (состояние + DSP; рендер через события) ----
// MakeBandSettings — снимок текущего состояния в запись диапазона.
// SaveCurrentBand — сохраняет текущее состояние в кэш+конфиг (или в XVTR-слот).
@@ -1251,8 +1257,88 @@ begin
Changed(rfANF);
end;
function TRadioController.DefaultMicSource: TTXMicSource;
begin
if FAudioInDevName <> '' then Result := txmsSoundCard
else Result := txmsRadio;
end;
procedure TRadioController.SetMOX(On_: Boolean);
begin FTransmitting := On_; Changed(rfTransmitting); { TODO wiring: ApplyMOX } end;
var
WasTransmitting: Boolean;
begin
// Safety: блокируем TX на бэндах с DoNotTx (Alex) и на RX-only трансвертере.
if On_ and FDevConnected and (FCurrentXvtr < 0)
and FAlexSettings.DoNotTx[EnsureRange(FCurrentBand, 0, 10)] then
Exit;
if On_ and (FCurrentXvtr >= 0) and (FCurrentXvtr < CFG_XVTR_COUNT)
and FXvtrSettings.Entries[FCurrentXvtr].RXOnly then
Exit;
WasTransmitting := FTransmitting;
FTransmitting := On_;
if On_ then FDriveLevel := CalcDriveByte;
// Сетевая PTT-последовательность TX-on: сначала реле (Alex bit27=1, drive) без
// PTT, затем PTT=1 — переключаем реле раньше передачи.
if FTransmitting and Assigned(FNetwork) and FNetwork.Connected and FNetwork.Running then
begin
FNetwork.ClearDUCIQQueue;
FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslate(ActiveTXFreqHz),
FDriveLevel, True, True, True);
FNetwork.SendFullHP; // 1й пакет: Alex bit27=1 (T/R relay), drive — без PTT
FNetwork.SendPTT(True); // 2й пакет: PTT=1 после переключения реле
end;
if FWDSPReady and Assigned(FDSPEngine) then
begin
// Mic source автоматически по источнику TX:
// Web TX → txmsWeb; HW PTT → txmsRadio; MOX/CAT → DefaultMicSource.
if FTransmitting then
begin
if FWebClientActive then
begin
FWebMicActive := True;
FDSPEngine.SetTXMicSource(txmsWeb);
end
else if FHWPTTActive then
FDSPEngine.SetTXMicSource(txmsRadio)
else
FDSPEngine.SetTXMicSource(DefaultMicSource);
end
else if FWebMicActive then
begin
FWebMicActive := False;
FDSPEngine.SetTXMicSource(DefaultMicSource);
end;
// В non-DUP RX-IQ пакеты дропаются на входе DSP-потока во время TX, чтобы
// TX leakage не накапливался в RXA pipeline / FFT-истории анализатора.
FDSPEngine.KeepRXDuringTX := FDisplayDuplex;
FDSPEngine.SetTXRun(FTransmitting);
if WasTransmitting and (not FTransmitting) and (not FDisplayDuplex) then
begin
FDSPEngine.FlushRX;
if Assigned(FAudioOut) then
FAudioOut.Clear;
{$IFDEF WINDOWS}
// Windows: на TX→RX дропаем И RX-IQ, И аудио-выход — radio FPGA TX-buffer +
// PA slew-down дотравливают «хвост» (на Windows тянется 200500мс).
FDSPEngine.BeginPostTXMute(250);
{$ENDIF}
end;
// SetTXRun переключает FActiveDisplayID на TX_DISP_ID; при DUP возвращаем
// источник на RX, чтобы видеть приём во время передачи.
if FTransmitting and FDisplayDuplex then
FDSPEngine.SetDisplaySourceTX(False);
end;
// Сетевая PTT-последовательность TX-off: сначала снимаем PTT, затем реле RX.
if (not FTransmitting) and Assigned(FNetwork) and FNetwork.Connected and FNetwork.Running then
begin
FNetwork.SendPTT(False); // 1й пакет: PTT=0 — сначала снимаем PTT
FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslate(ActiveTXFreqHz),
FDriveLevel, False, True, True);
FNetwork.ClearDUCIQQueue;
FNetwork.SendFullHP; // 2й пакет: Alex bit27=0 (T/R relay RX)
end;
Changed(rfTransmitting);
end;
procedure TRadioController.SetTune(On_: Boolean);
begin FTuning := On_; Changed(rfTuning); { TODO wiring: ApplyTUN } end;