feat(fm): add universal FM RAW decoder output

This commit is contained in:
2026-07-16 18:58:28 +03:00
parent 3acd569da5
commit cd816c54d6
16 changed files with 656 additions and 148 deletions
+63 -22
View File
@@ -25,7 +25,7 @@ interface
uses
Classes, SysUtils, Math, SyncObjs,
BeaconDecoder,
BeaconDecoder, RadioModes,
WDSP;
const
@@ -49,18 +49,22 @@ const
// Наши индексы режимов (совпадают с кнопками MainForm).
// Новые режимы дописываются В КОНЕЦ: индекс уходит в settings.json,
// band-cache, CAT (ZZMD/ZZMV) и web-API.
MODE_LSB = 0;
MODE_USB = 1;
MODE_DSB = 2;
MODE_CWL = 3;
MODE_CWU = 4;
MODE_FM = 5;
MODE_AM = 6;
MODE_SAM = 7;
MODE_DIGU = 8;
MODE_DIGL = 9;
MODE_WFM = 10;
MODE_DMR = 11; // DMR receive: flat WDSP discriminator -> DMRDecoder
MODE_LSB = RadioModes.MODE_LSB;
MODE_USB = RadioModes.MODE_USB;
MODE_DSB = RadioModes.MODE_DSB;
MODE_CWL = RadioModes.MODE_CWL;
MODE_CWU = RadioModes.MODE_CWU;
MODE_FM = RadioModes.MODE_FM;
MODE_AM = RadioModes.MODE_AM;
MODE_SAM = RadioModes.MODE_SAM;
MODE_DIGU = RadioModes.MODE_DIGU;
MODE_DIGL = RadioModes.MODE_DIGL;
MODE_WFM = RadioModes.MODE_WFM;
MODE_DMR = RadioModes.MODE_DMR;
MODE_FMRAW = RadioModes.MODE_FMRAW;
MODE_MIN = RadioModes.MODE_MIN;
MODE_MAX = RadioModes.MODE_MAX;
MODE_COUNT = RadioModes.MODE_COUNT;
// Минимальная длина FIR у WDSP: create_* поднимают fircore с
// nc = max(2048, dsp_size). В firmin.c nfor = nc / size, idxmask = nfor - 1,
@@ -443,6 +447,7 @@ type
FTXWfAvgTimeMS: Double;
FMode: Integer;
FRawFMDeviation: Double;
FSMeter: Double;
FFilterLow: Integer;
FFilterHigh: Integer;
@@ -479,6 +484,7 @@ type
procedure ApplyDSPRate(Mode: Integer);
procedure ApplyRXWFMToChan(Chan: Integer);
procedure ApplyRXDMRToChan(Chan: Integer);
procedure ApplyRXRawToChan(Chan: Integer);
procedure ApplyWFMSettings;
// --- Мультислайсы ---
function FindSliceIdx(Id: Integer): Integer; // -1 если нет
@@ -615,6 +621,7 @@ type
procedure SetSNB(Enable: Boolean);
procedure SetANF(Enable: Boolean);
procedure SetRXFMDeviation(Deviation: Double);
procedure SetRawFMDeviation(Deviation: Double);
procedure SetFMSquelch(Enable: Boolean; Level: Integer);
procedure SetVolume(Vol: Double);
procedure SetMute(Mute: Boolean);
@@ -991,6 +998,7 @@ begin
// de-emphasis. Those filters collapse the four DMR discriminator levels.
// WFMD is a plain quadrature discriminator and permits flat data audio.
MODE_DMR: Result := WDSP_RX_WFM;
MODE_FMRAW: Result := WDSP_RX_WFM;
else Result := WDSP_USB;
end;
end;
@@ -999,7 +1007,7 @@ function TWDSPEngine.ModeToWDSPTX(Mode: Integer): Integer;
// txaMode расходится с rxaMode после DRM — отдельный маппинг только для WFM.
begin
if Mode = MODE_WFM then Result := WDSP_TX_WFM
else if Mode = MODE_DMR then Result := WDSP_FM
else if Mode in [MODE_DMR, MODE_FMRAW] then Result := WDSP_FM
else Result := ModeToWDSP(Mode);
end;
@@ -1122,6 +1130,15 @@ begin
SetRXAWFMDeemphRun (Chan, 0);
end;
procedure TWDSPEngine.ApplyRXRawToChan(Chan: Integer);
begin
// Decoder-grade discriminator: no broadcast de-emphasis or limiter.
SetRXAWFMDeviation (Chan, FRawFMDeviation);
SetRXAWFMAFFilter (Chan, 20.0, 7000.0);
SetRXAWFMDeemphRun (Chan, 0);
SetRXAWFMLimRun (Chan, 0);
end;
procedure TWDSPEngine.ApplyWFMSettings;
begin
ApplyRXWFMToChan(RXA_CHAN);
@@ -1153,6 +1170,7 @@ begin
MODE_DIGL: SetFilter(-3000, 0);
MODE_WFM: SetFilter(-80000, 80000);
MODE_DMR: SetFilter(-6250, 6250);
MODE_FMRAW: SetFilter(-6250, 6250);
end;
end;
@@ -1306,6 +1324,7 @@ begin
FTXEQFreqs[10] := 3500;
FTXAMCarrier := 0.5;
FTXFMDeviation := 5000.0;
FRawFMDeviation := 2500.0;
FTXFMLowCut := 300;
FTXFMHighCut := 3000;
FTXFMEmphPos := 1;
@@ -2312,6 +2331,7 @@ begin
SetRXAMode(Chan, ModeToWDSP(FSlices[Idx].Mode));
if FSlices[Idx].Mode = MODE_WFM then ApplyRXWFMToChan(Chan);
if FSlices[Idx].Mode = MODE_DMR then ApplyRXDMRToChan(Chan);
if FSlices[Idx].Mode = MODE_FMRAW then ApplyRXRawToChan(Chan);
SetRXAShiftRun(Chan, 1);
SetRXAShiftFreq(Chan, FSlices[Idx].ShiftHz);
RXASetPassband(Chan, ClampToNyquist(FSlices[Idx].FilterLo, DSPRate),
@@ -2324,7 +2344,7 @@ begin
ApplyANFToChan(Chan, FSlices[Idx].ANFOn);
ApplyFMSQToChan(Chan, FSlices[Idx].FMSQOn, FSlices[Idx].FMSQLevel);
// Decoder input must not disappear or change scale with speaker volume.
if FSlices[Idx].Mode = MODE_DMR then
if FSlices[Idx].Mode in [MODE_DMR, MODE_FMRAW] then
SetRXAPanelGain1(Chan, 1.0)
else
SetRXAPanelGain1(Chan, FSlices[Idx].Volume);
@@ -2374,7 +2394,7 @@ begin
if not (FSlices[s].Active and FSlices[s].Opened) then Continue;
if FSlices[s].PanId <> PanId then Continue;
// Muting a DMR speaker must not make its decoder lose synchronization.
if FSlices[s].Muted and (FSlices[s].Mode <> MODE_DMR) then Continue;
if FSlices[s].Muted and not (FSlices[s].Mode in [MODE_DMR, MODE_FMRAW]) then Continue;
// Свежая копия входного IQ — fexchange0 портит вход in-place.
for i := 0 to N - 1 do
begin
@@ -2390,12 +2410,12 @@ begin
FSliceOutL[k] := FSliceOut[k * 2];
FSliceOutR[k] := FSliceOut[k * 2 + 1];
end;
if FSlices[s].Mode = MODE_DMR then
if FSlices[s].Mode in [MODE_DMR, MODE_FMRAW] then
begin
if Assigned(FOnSliceDemodAudio) then
FOnSliceDemodAudio(FSlices[s].Id, FSliceOutL, FSliceOutR,
FAudioBufSize);
Continue; // never route the raw 4FSK discriminator to the speaker
Continue; // decoder-grade signal uses the independent pre-volume route
end;
if Assigned(FOnSliceAudio) then
begin
@@ -2616,7 +2636,8 @@ begin
SetRXAMode(FSlices[idx].Chan, ModeToWDSP(Mode));
if Mode = MODE_WFM then ApplyRXWFMToChan(FSlices[idx].Chan);
if Mode = MODE_DMR then ApplyRXDMRToChan(FSlices[idx].Chan);
if Mode = MODE_DMR then
if Mode = MODE_FMRAW then ApplyRXRawToChan(FSlices[idx].Chan);
if Mode in [MODE_DMR, MODE_FMRAW] then
SetRXAPanelGain1(FSlices[idx].Chan, 1.0)
else
SetRXAPanelGain1(FSlices[idx].Chan, FSlices[idx].Volume);
@@ -2665,7 +2686,7 @@ begin
try
idx := FindSliceIdx(Id); if idx < 0 then Exit;
FSlices[idx].Volume := Vol;
if FSlices[idx].Opened and (FSlices[idx].Mode <> MODE_DMR) then
if FSlices[idx].Opened and not (FSlices[idx].Mode in [MODE_DMR, MODE_FMRAW]) then
SetRXAPanelGain1(FSlices[idx].Chan, Vol);
finally
FSliceLock.Leave;
@@ -2810,7 +2831,8 @@ begin
SetRXAWFMDeviation (RXA_CHAN, DMR_DEVIATION);
SetRXAWFMAFFilter (RXA_CHAN, 20.0, 7000.0);
SetRXAWFMDeemphRun (RXA_CHAN, 0);
end;
end
else if Mode = MODE_FMRAW then ApplyRXRawToChan(RXA_CHAN);
// НЕ вызываем ApplyDefaultFilter — фильтр устанавливается явно из UI
end;
@@ -3046,12 +3068,31 @@ end;
procedure TWDSPEngine.SetRXFMDeviation(Deviation: Double);
begin
if not FInitialized then Exit;
if FMode = MODE_DMR then
if FMode = MODE_FMRAW then SetRawFMDeviation(Deviation)
else if FMode = MODE_DMR then
SetRXAWFMDeviation(RXA_CHAN, Deviation)
else
SetRXAFMDeviation(RXA_CHAN, Deviation);
end;
procedure TWDSPEngine.SetRawFMDeviation(Deviation: Double);
var i: Integer;
begin
FRawFMDeviation := EnsureRange(Deviation, 500.0, 10000.0);
if not FInitialized then Exit;
if FMode = MODE_FMRAW then SetRXAWFMDeviation(RXA_CHAN, FRawFMDeviation);
// One calibration value applies to every active FM RAW decoder output.
FSliceLock.Enter;
try
for i := 0 to MAX_SLICES - 1 do
if FSlices[i].Active and FSlices[i].Opened and
(FSlices[i].Mode = MODE_FMRAW) then
SetRXAWFMDeviation(FSlices[i].Chan, FRawFMDeviation);
finally
FSliceLock.Leave;
end;
end;
procedure TWDSPEngine.SetFMSquelch(Enable: Boolean; Level: Integer);
var Threshold: Double;
begin