Add flat FM RAW transmit path

This commit is contained in:
2026-07-16 20:33:39 +03:00
parent cd816c54d6
commit 6197ed0aee
3 changed files with 86 additions and 21 deletions
+8 -5
View File
@@ -3595,13 +3595,16 @@ begin
end; end;
procedure TMainForm.UpdateTXControlAvailability; procedure TMainForm.UpdateTXControlAvailability;
var Allowed: Boolean; var MOXAllowed, TuneAllowed: Boolean;
begin begin
if (BtnMOX = nil) or (BtnTUN = nil) or (FController = nil) then Exit; if (BtnMOX = nil) or (BtnTUN = nil) or (FController = nil) then Exit;
Allowed := FController.FRunning and MOXAllowed := FController.FRunning and
not (FController.ActiveTXMode in [MODE_DMR, MODE_FMRAW]); (FController.ActiveTXMode <> MODE_DMR);
BtnMOX.Enabled := Allowed; // FM RAW transmits external PCM only; the speech-path tune generator is not
BtnTUN.Enabled := Allowed; // useful there and remains guarded in the controller as well.
TuneAllowed := MOXAllowed and (FController.ActiveTXMode <> MODE_FMRAW);
BtnMOX.Enabled := MOXAllowed;
BtnTUN.Enabled := TuneAllowed;
UpdatePSButtons; UpdatePSButtons;
end; end;
+28 -12
View File
@@ -1923,8 +1923,8 @@ begin
FSlices[idx].Mode := Mode; FSlices[idx].Mode := Mode;
if Assigned(FDSPEngine) then FDSPEngine.SetSliceMode(Id, 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 // 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. // to the main receiver as soon as it enters DMR. FM RAW supports flat TX.
if (Mode in [MODE_DMR, MODE_FMRAW]) and (FTxSliceId = Id) then SetTxSlice(0); if (Mode = MODE_DMR) and (FTxSliceId = Id) then SetTxSlice(0);
if (OldMode = MODE_DMR) and (Mode <> MODE_DMR) then if (OldMode = MODE_DMR) and (Mode <> MODE_DMR) then
FreeAndNil(FSlices[idx].DMR); FreeAndNil(FSlices[idx].DMR);
end; end;
@@ -2233,14 +2233,13 @@ begin
if SliceId > 0 then if SliceId > 0 then
begin begin
if not GetSlice(SliceId, S) then Exit; if not GetSlice(SliceId, S) then Exit;
// EWSDR has no DMR transmitter/AMBE encoder. Do not accidentally use the // EWSDR has no DMR transmitter/AMBE encoder.
// analog WFM transmitter for a receive-only DMR slice. if S.Mode = MODE_DMR then Exit;
if S.Mode in [MODE_DMR, MODE_FMRAW] then Exit;
end; end;
if FTxSliceId = SliceId then Exit; if FTxSliceId = SliceId then Exit;
// Selecting main A while it is in DMR is allowed as an RX selection, but it // 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. // 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 begin
if FTuning then SetTune(False) if FTuning then SetTune(False)
else if FTransmitting then SetMOX(False); else if FTransmitting then SetMOX(False);
@@ -3271,7 +3270,7 @@ begin
end; end;
// Entering receive-only DMR on the selected main TX source immediately // Entering receive-only DMR on the selected main TX source immediately
// releases an existing carrier. SetTune(False) also releases MOX. // releases an existing carrier. SetTune(False) also releases MOX.
if ActiveTXMode in [MODE_DMR, MODE_FMRAW] then if ActiveTXMode = MODE_DMR then
begin begin
if FTuning then SetTune(False) if FTuning then SetTune(False)
else if FTransmitting then SetMOX(False); else if FTransmitting then SetMOX(False);
@@ -3282,6 +3281,17 @@ begin
if FWDSPReady and Assigned(FDSPEngine) then if FWDSPReady and Assigned(FDSPEngine) then
begin begin
FDSPEngine.SetMode(FMode); 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 активен — пересчитываем знак частоты под новый режим // Если TUN активен — пересчитываем знак частоты под новый режим
if FTuning then FDSPEngine.SetTXTone(True, FTXSettings.TUNFreq, TUN_TONE_MAG); if FTuning then FDSPEngine.SetTXTone(True, FTXSettings.TUNFreq, TUN_TONE_MAG);
end; end;
@@ -4165,6 +4175,9 @@ begin
FDSPEngine.SetTXWaterfallDisplay(FTXSettings.TXWfDetector, FDSPEngine.SetTXWaterfallDisplay(FTXSettings.TXWfDetector,
FTXSettings.TXWfAvgMode, FTXSettings.TXWfAvgMode,
FTXSettings.TXWfAvgTimeMS); 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) // PureSignal-параметры calcc (mox/loop/tx delay, ptol, pin/map/stbl, SetPk)
ApplyPSSettingsToDSP; ApplyPSSettingsToDSP;
end; end;
@@ -4174,8 +4187,8 @@ var
WasTransmitting: Boolean; WasTransmitting: Boolean;
begin begin
// DMR support is receive-only. This guard also covers CAT, web and hardware // 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. // PTT. FM RAW is a flat external-modem transmit path.
if On_ and (ActiveTXMode in [MODE_DMR, MODE_FMRAW]) then Exit; if On_ and (ActiveTXMode = MODE_DMR) then Exit;
// Safety: блокируем TX на бэндах с DoNotTx (Alex) и на RX-only трансвертере. // Safety: блокируем TX на бэндах с DoNotTx (Alex) и на RX-only трансвертере.
if On_ and FDevConnected and (FCurrentXvtr < 0) if On_ and FDevConnected and (FCurrentXvtr < 0)
and FAlexSettings.DoNotTx[EnsureRange(TxBandIdx, 0, 10)] then and FAlexSettings.DoNotTx[EnsureRange(TxBandIdx, 0, 10)] then
@@ -4222,11 +4235,14 @@ begin
end; end;
if FWDSPReady and Assigned(FDSPEngine) then if FWDSPReady and Assigned(FDSPEngine) then
begin begin
// Mic source автоматически по источнику TX: // Mic source автоматически по источнику TX. FM RAW always uses the
// Web TX → txmsWeb; HW PTT → txmsRadio; MOX/CAT → DefaultMicSource. // configured default input (normally a virtual PortAudio cable), even if
// a web client is connected or hardware PTT keyed the transmitter.
if FTransmitting then if FTransmitting then
begin begin
if FWebClientActive then if ActiveTXMode = MODE_FMRAW then
FDSPEngine.SetTXMicSource(DefaultMicSource)
else if FWebClientActive then
begin begin
FWebMicActive := True; FWebMicActive := True;
FDSPEngine.SetTXMicSource(txmsWeb); FDSPEngine.SetTXMicSource(txmsWeb);
+50 -4
View File
@@ -447,6 +447,7 @@ type
FTXWfAvgTimeMS: Double; FTXWfAvgTimeMS: Double;
FMode: Integer; FMode: Integer;
FTXMode: Integer; // may differ from RX when a slice is TX source
FRawFMDeviation: Double; FRawFMDeviation: Double;
FSMeter: Double; FSMeter: Double;
FFilterLow: Integer; FFilterLow: Integer;
@@ -486,6 +487,9 @@ type
procedure ApplyRXDMRToChan(Chan: Integer); procedure ApplyRXDMRToChan(Chan: Integer);
procedure ApplyRXRawToChan(Chan: Integer); procedure ApplyRXRawToChan(Chan: Integer);
procedure ApplyWFMSettings; 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 FindSliceIdx(Id: Integer): Integer; // -1 если нет
function SliceInRate(Idx: Integer): Integer; // входной rate слайса (его пан) function SliceInRate(Idx: Integer): Integer; // входной rate слайса (его пан)
@@ -1006,8 +1010,10 @@ end;
function TWDSPEngine.ModeToWDSPTX(Mode: Integer): Integer; function TWDSPEngine.ModeToWDSPTX(Mode: Integer): Integer;
// txaMode расходится с rxaMode после DRM — отдельный маппинг только для WFM. // txaMode расходится с rxaMode после DRM — отдельный маппинг только для WFM.
begin begin
if Mode = MODE_WFM then Result := WDSP_TX_WFM // The narrow FM modulator has mandatory speech pre-emphasis. FM RAW uses
else if Mode in [MODE_DMR, MODE_FMRAW] then Result := WDSP_FM // 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); else Result := ModeToWDSP(Mode);
end; end;
@@ -1148,6 +1154,36 @@ begin
SetTXAWFMPreEmphTau (TXA_CHAN, WFM_TAU_DE_US); SetTXAWFMPreEmphTau (TXA_CHAN, WFM_TAU_DE_US);
end; 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; function TWDSPEngine.ClampToNyquist(Hz, Rate: Integer): Integer;
var Limit: Integer; var Limit: Integer;
begin begin
@@ -1196,6 +1232,7 @@ begin
FInitialized := False; FInitialized := False;
FAnalyzerOpen := False; FAnalyzerOpen := False;
FMode := MODE_USB; FMode := MODE_USB;
FTXMode := MODE_USB;
FFilterLow := 100; FFilterLow := 100;
FFilterHigh := 2400; FFilterHigh := 2400;
FAGCMode := agcMedium; FAGCMode := agcMedium;
@@ -1688,7 +1725,8 @@ begin
// изредка фазовый разрыв PostGen-тона (всплеск на водопаде + щелчок). // изредка фазовый разрыв PostGen-тона (всплеск на водопаде + щелчок).
); );
SetTXAMode(TXA_CHAN, ModeToWDSPTX(FMode)); FTXMode := FMode;
SetTXAMode(TXA_CHAN, ModeToWDSPTX(FTXMode));
SetTXAPanelRun(TXA_CHAN, 1); SetTXAPanelRun(TXA_CHAN, 1);
// PatchPanel: 1=Q, 2=I. Mic подаётся в I (FTXIn[i*2]), Q=0 — выбираем I. // PatchPanel: 1=Q, 2=I. Mic подаётся в I (FTXIn[i*2]), Q=0 — выбираем I.
SetTXAPanelSelect(TXA_CHAN, 2); SetTXAPanelSelect(TXA_CHAN, 2);
@@ -2819,6 +2857,7 @@ end;
procedure TWDSPEngine.SetMode(Mode: Integer); procedure TWDSPEngine.SetMode(Mode: Integer);
begin begin
FMode := Mode; FMode := Mode;
FTXMode := Mode;
if not FInitialized then Exit; if not FInitialized then Exit;
// Rate — до режима: setDSPSamplerate_rxa перестраивает всю цепь (в т.ч. wfmd), // Rate — до режима: setDSPSamplerate_rxa перестраивает всю цепь (в т.ч. wfmd),
// а SetRXAMode затем пересчитает bpsnba под уже актуальный dsp_rate. // а SetRXAMode затем пересчитает bpsnba под уже актуальный dsp_rate.
@@ -2833,6 +2872,7 @@ begin
SetRXAWFMDeemphRun (RXA_CHAN, 0); SetRXAWFMDeemphRun (RXA_CHAN, 0);
end end
else if Mode = MODE_FMRAW then ApplyRXRawToChan(RXA_CHAN); else if Mode = MODE_FMRAW then ApplyRXRawToChan(RXA_CHAN);
ApplyTXModeSettings(FTXMode);
// НЕ вызываем ApplyDefaultFilter — фильтр устанавливается явно из UI // НЕ вызываем ApplyDefaultFilter — фильтр устанавливается явно из UI
end; end;
@@ -3081,6 +3121,7 @@ begin
FRawFMDeviation := EnsureRange(Deviation, 500.0, 10000.0); FRawFMDeviation := EnsureRange(Deviation, 500.0, 10000.0);
if not FInitialized then Exit; if not FInitialized then Exit;
if FMode = MODE_FMRAW then SetRXAWFMDeviation(RXA_CHAN, FRawFMDeviation); 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. // One calibration value applies to every active FM RAW decoder output.
FSliceLock.Enter; FSliceLock.Enter;
try try
@@ -3127,13 +3168,18 @@ end;
procedure TWDSPEngine.SetTXMode(Mode: Integer); procedure TWDSPEngine.SetTXMode(Mode: Integer);
begin begin
FTXMode := Mode;
if not FInitialized then Exit; if not FInitialized then Exit;
SetTXAMode(TXA_CHAN, ModeToWDSPTX(Mode)); SetTXAMode(TXA_CHAN, ModeToWDSPTX(FTXMode));
ApplyTXModeSettings(FTXMode);
end; end;
procedure TWDSPEngine.SetTXFilter(Low, High: Integer); procedure TWDSPEngine.SetTXFilter(Low, High: Integer);
begin begin
if not FInitialized then Exit; 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); SetTXABandpassFreqs(TXA_CHAN, Low, High);
end; end;