From 6197ed0aee74b6246dd4213232249773903e01dd Mon Sep 17 00:00:00 2001 From: Vladimir Date: Thu, 16 Jul 2026 20:33:39 +0300 Subject: [PATCH] Add flat FM RAW transmit path --- MainForm.pas | 13 ++++++----- RadioController.pas | 40 +++++++++++++++++++++++---------- WDSPEngine.pas | 54 +++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 86 insertions(+), 21 deletions(-) diff --git a/MainForm.pas b/MainForm.pas index fac98c9..ea5de28 100644 --- a/MainForm.pas +++ b/MainForm.pas @@ -3595,13 +3595,16 @@ begin end; procedure TMainForm.UpdateTXControlAvailability; -var Allowed: Boolean; +var MOXAllowed, TuneAllowed: Boolean; begin if (BtnMOX = nil) or (BtnTUN = nil) or (FController = nil) then Exit; - Allowed := FController.FRunning and - not (FController.ActiveTXMode in [MODE_DMR, MODE_FMRAW]); - BtnMOX.Enabled := Allowed; - BtnTUN.Enabled := Allowed; + MOXAllowed := FController.FRunning and + (FController.ActiveTXMode <> MODE_DMR); + // FM RAW transmits external PCM only; the speech-path tune generator is not + // useful there and remains guarded in the controller as well. + TuneAllowed := MOXAllowed and (FController.ActiveTXMode <> MODE_FMRAW); + BtnMOX.Enabled := MOXAllowed; + BtnTUN.Enabled := TuneAllowed; UpdatePSButtons; end; diff --git a/RadioController.pas b/RadioController.pas index 191c014..4fe380e 100644 --- a/RadioController.pas +++ b/RadioController.pas @@ -1923,8 +1923,8 @@ begin FSlices[idx].Mode := Mode; if Assigned(FDSPEngine) then FDSPEngine.SetSliceMode(Id, Mode); // DMR is receive-only. If this slice used to be the TX source, hand TX back - // to the main receiver as soon as it enters DMR. - if (Mode in [MODE_DMR, MODE_FMRAW]) and (FTxSliceId = Id) then SetTxSlice(0); + // to the main receiver as soon as it enters DMR. FM RAW supports flat TX. + if (Mode = MODE_DMR) and (FTxSliceId = Id) then SetTxSlice(0); if (OldMode = MODE_DMR) and (Mode <> MODE_DMR) then FreeAndNil(FSlices[idx].DMR); end; @@ -2233,14 +2233,13 @@ begin if SliceId > 0 then begin if not GetSlice(SliceId, S) then Exit; - // EWSDR has no DMR transmitter/AMBE encoder. Do not accidentally use the - // analog WFM transmitter for a receive-only DMR slice. - if S.Mode in [MODE_DMR, MODE_FMRAW] then Exit; + // EWSDR has no DMR transmitter/AMBE encoder. + if S.Mode = MODE_DMR then Exit; end; if FTxSliceId = SliceId then Exit; // Selecting main A while it is in DMR is allowed as an RX selection, but it // cannot inherit an active carrier from a previously selected TX slice. - if (SliceId = 0) and (FMode in [MODE_DMR, MODE_FMRAW]) then + if (SliceId = 0) and (FMode = MODE_DMR) then begin if FTuning then SetTune(False) else if FTransmitting then SetMOX(False); @@ -3271,7 +3270,7 @@ begin end; // Entering receive-only DMR on the selected main TX source immediately // releases an existing carrier. SetTune(False) also releases MOX. - if ActiveTXMode in [MODE_DMR, MODE_FMRAW] then + if ActiveTXMode = MODE_DMR then begin if FTuning then SetTune(False) else if FTransmitting then SetMOX(False); @@ -3282,6 +3281,17 @@ begin if FWDSPReady and Assigned(FDSPEngine) then begin FDSPEngine.SetMode(FMode); + // SetMode configures the main RX/TX pair; a selected slice may still be + // the actual TX source and therefore owns the transmitter mode. + FDSPEngine.SetTXMode(ActiveTXMode); + // A live mode change must also switch away from web/HW microphone when + // entering FM RAW; normally the modem is connected through PortAudio. + if FTransmitting and (ActiveTXMode = MODE_FMRAW) then + begin + FWebMicActive := False; + FDSPEngine.SetTXMicSource(DefaultMicSource); + if Assigned(FAudioIn) and FAudioIn.IsOpen then FAudioIn.Flush; + end; // Если TUN активен — пересчитываем знак частоты под новый режим if FTuning then FDSPEngine.SetTXTone(True, FTXSettings.TUNFreq, TUN_TONE_MAG); end; @@ -4165,6 +4175,9 @@ begin FDSPEngine.SetTXWaterfallDisplay(FTXSettings.TXWfDetector, FTXSettings.TXWfAvgMode, FTXSettings.TXWfAvgTimeMS); + // Reapply the mode overlay last: FM RAW must stay flat even when the user + // edits and saves ordinary voice-chain settings while this mode is active. + FDSPEngine.SetTXMode(ActiveTXMode); // PureSignal-параметры calcc (mox/loop/tx delay, ptol, pin/map/stbl, SetPk) ApplyPSSettingsToDSP; end; @@ -4174,8 +4187,8 @@ var WasTransmitting: Boolean; begin // DMR support is receive-only. This guard also covers CAT, web and hardware - // PTT. A selected non-DMR TX slice remains valid while main A receives DMR. - if On_ and (ActiveTXMode in [MODE_DMR, MODE_FMRAW]) then Exit; + // PTT. FM RAW is a flat external-modem transmit path. + if On_ and (ActiveTXMode = MODE_DMR) then Exit; // Safety: блокируем TX на бэндах с DoNotTx (Alex) и на RX-only трансвертере. if On_ and FDevConnected and (FCurrentXvtr < 0) and FAlexSettings.DoNotTx[EnsureRange(TxBandIdx, 0, 10)] then @@ -4222,11 +4235,14 @@ begin end; if FWDSPReady and Assigned(FDSPEngine) then begin - // Mic source автоматически по источнику TX: - // Web TX → txmsWeb; HW PTT → txmsRadio; MOX/CAT → DefaultMicSource. + // Mic source автоматически по источнику TX. FM RAW always uses the + // configured default input (normally a virtual PortAudio cable), even if + // a web client is connected or hardware PTT keyed the transmitter. if FTransmitting then begin - if FWebClientActive then + if ActiveTXMode = MODE_FMRAW then + FDSPEngine.SetTXMicSource(DefaultMicSource) + else if FWebClientActive then begin FWebMicActive := True; FDSPEngine.SetTXMicSource(txmsWeb); diff --git a/WDSPEngine.pas b/WDSPEngine.pas index db66e52..9040539 100644 --- a/WDSPEngine.pas +++ b/WDSPEngine.pas @@ -447,6 +447,7 @@ type FTXWfAvgTimeMS: Double; FMode: Integer; + FTXMode: Integer; // may differ from RX when a slice is TX source FRawFMDeviation: Double; FSMeter: Double; FFilterLow: Integer; @@ -486,6 +487,9 @@ type procedure ApplyRXDMRToChan(Chan: Integer); procedure ApplyRXRawToChan(Chan: Integer); procedure ApplyWFMSettings; + // Mode-dependent TX overlay. FM RAW uses the flat WFM modulator and + // bypasses every speech processor without overwriting the saved TX setup. + procedure ApplyTXModeSettings(Mode: Integer); // --- Мультислайсы --- function FindSliceIdx(Id: Integer): Integer; // -1 если нет function SliceInRate(Idx: Integer): Integer; // входной rate слайса (его пан) @@ -1006,8 +1010,10 @@ end; function TWDSPEngine.ModeToWDSPTX(Mode: Integer): Integer; // txaMode расходится с rxaMode после DRM — отдельный маппинг только для WFM. begin - if Mode = MODE_WFM then Result := WDSP_TX_WFM - else if Mode in [MODE_DMR, MODE_FMRAW] then Result := WDSP_FM + // The narrow FM modulator has mandatory speech pre-emphasis. FM RAW uses + // wfmmod even at NFM deviation because that path has an explicit flat mode. + if Mode in [MODE_WFM, MODE_FMRAW] then Result := WDSP_TX_WFM + else if Mode = MODE_DMR then Result := WDSP_FM else Result := ModeToWDSP(Mode); end; @@ -1148,6 +1154,36 @@ begin SetTXAWFMPreEmphTau (TXA_CHAN, WFM_TAU_DE_US); end; +procedure TWDSPEngine.ApplyTXModeSettings(Mode: Integer); +begin + if not FInitialized then Exit; + if Mode = MODE_FMRAW then + begin + // Decoder/modem-grade FM input. A full-scale PCM peak produces the + // selected peak deviation. Keep only a broad linear-phase AF bandpass; + // APRS and other external modems must not see voice EQ, dynamics or emphasis. + SetTXAWFMDeviation (TXA_CHAN, FRawFMDeviation); + SetTXAWFMAFFreqs (TXA_CHAN, 20.0, 7000.0); + SetTXAWFMPreEmphRun (TXA_CHAN, 0); + SetTXABandpassFreqs (TXA_CHAN, 20.0, 7000.0); + SetTXABandpassRun (TXA_CHAN, 1); + SetTXAPanelGain1 (TXA_CHAN, 1.0); + SetTXACompressorRun (TXA_CHAN, 0); + SetTXALevelerSt (TXA_CHAN, 0); + SetTXAALCSt (TXA_CHAN, 0); + SetTXAPHROTRun (TXA_CHAN, 0); + SetTXAEQRun (TXA_CHAN, 0); + SetTXACTCSSRun (TXA_CHAN, 0); + end + else + begin + // Restore the user's normal voice/data chain after leaving FM RAW. + SetTXABandpassRun(TXA_CHAN, 1); + ApplyTXChainSettings; + if Mode = MODE_WFM then ApplyWFMSettings; + end; +end; + function TWDSPEngine.ClampToNyquist(Hz, Rate: Integer): Integer; var Limit: Integer; begin @@ -1196,6 +1232,7 @@ begin FInitialized := False; FAnalyzerOpen := False; FMode := MODE_USB; + FTXMode := MODE_USB; FFilterLow := 100; FFilterHigh := 2400; FAGCMode := agcMedium; @@ -1688,7 +1725,8 @@ begin // изредка фазовый разрыв PostGen-тона (всплеск на водопаде + щелчок). ); - SetTXAMode(TXA_CHAN, ModeToWDSPTX(FMode)); + FTXMode := FMode; + SetTXAMode(TXA_CHAN, ModeToWDSPTX(FTXMode)); SetTXAPanelRun(TXA_CHAN, 1); // PatchPanel: 1=Q, 2=I. Mic подаётся в I (FTXIn[i*2]), Q=0 — выбираем I. SetTXAPanelSelect(TXA_CHAN, 2); @@ -2819,6 +2857,7 @@ end; procedure TWDSPEngine.SetMode(Mode: Integer); begin FMode := Mode; + FTXMode := Mode; if not FInitialized then Exit; // Rate — до режима: setDSPSamplerate_rxa перестраивает всю цепь (в т.ч. wfmd), // а SetRXAMode затем пересчитает bpsnba под уже актуальный dsp_rate. @@ -2833,6 +2872,7 @@ begin SetRXAWFMDeemphRun (RXA_CHAN, 0); end else if Mode = MODE_FMRAW then ApplyRXRawToChan(RXA_CHAN); + ApplyTXModeSettings(FTXMode); // НЕ вызываем ApplyDefaultFilter — фильтр устанавливается явно из UI end; @@ -3081,6 +3121,7 @@ begin FRawFMDeviation := EnsureRange(Deviation, 500.0, 10000.0); if not FInitialized then Exit; if FMode = MODE_FMRAW then SetRXAWFMDeviation(RXA_CHAN, FRawFMDeviation); + if FTXMode = MODE_FMRAW then SetTXAWFMDeviation(TXA_CHAN, FRawFMDeviation); // One calibration value applies to every active FM RAW decoder output. FSliceLock.Enter; try @@ -3127,13 +3168,18 @@ end; procedure TWDSPEngine.SetTXMode(Mode: Integer); begin + FTXMode := Mode; if not FInitialized then Exit; - SetTXAMode(TXA_CHAN, ModeToWDSPTX(Mode)); + SetTXAMode(TXA_CHAN, ModeToWDSPTX(FTXMode)); + ApplyTXModeSettings(FTXMode); end; procedure TWDSPEngine.SetTXFilter(Low, High: Integer); begin if not FInitialized then Exit; + // In FM RAW this is an RF-channel-width update mirrored from the RX UI, + // not an audio passband request. The flat modem AF passband is fixed above. + if FTXMode = MODE_FMRAW then Exit; SetTXABandpassFreqs(TXA_CHAN, Low, High); end;