diff --git a/MainForm.pas b/MainForm.pas index be212bf..fb06d91 100644 --- a/MainForm.pas +++ b/MainForm.pas @@ -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); diff --git a/RadioController.pas b/RadioController.pas index 3403e38..6c51e2a 100644 --- a/RadioController.pas +++ b/RadioController.pas @@ -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 тянется 200–500мс). + 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;