mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 18:43:51 +00:00
feat(fm): add universal FM RAW decoder output
This commit is contained in:
+74
-24
@@ -354,6 +354,7 @@ type
|
||||
|
||||
// ---- FM ----
|
||||
FFMDeviation: Double;
|
||||
FRawFMDeviation: Double;
|
||||
FFMCTCSSOn: Boolean;
|
||||
FFMCTCSSToneIdx: Integer;
|
||||
FFMSQOn: Boolean;
|
||||
@@ -563,6 +564,7 @@ type
|
||||
procedure SetMode(M: Integer);
|
||||
procedure SetFilterIdx(Idx: Integer);
|
||||
procedure SetFilterBW(BWHz: Integer);
|
||||
procedure SetRawFMDeviation(Hz: Integer);
|
||||
procedure SetAGCMode(M: Integer);
|
||||
procedure SetAGCTop(DB: Integer);
|
||||
procedure AGCTopBy(Delta: Integer);
|
||||
@@ -916,7 +918,7 @@ begin
|
||||
FDeviceStore := TDeviceStore.Create; // общий список устройств (desktop+web)
|
||||
// Дефолты (дублируют TMainForm.FormCreate; в GUI перезапишутся, в демоне нужны).
|
||||
FVfoA := 14200000; FVfoB := 7100000; FActiveVfo := 0;
|
||||
FMode := 1; FFilter := 5; FFilterBW := 2700;
|
||||
FMode := MODE_USB; FFilter := 5; FFilterBW := 2700;
|
||||
FAGCMode := 1; FAGCTop := 90;
|
||||
FRxGainMode := 2; FRxGainDb := 40; // Pluto: slow_attack по умолчанию
|
||||
FRxMuteOnTx := True; // QO-100 self-monitor выкл по умолчанию (RX заглушён на TX)
|
||||
@@ -934,6 +936,7 @@ begin
|
||||
FWfPalette := 0; FWfGamma := 1.0; FDisplayPixels := 4096;
|
||||
FWfSpeedDiv := 1; FSpecMSAA := 4;
|
||||
FFMStepOn := True;
|
||||
FRawFMDeviation := RAW_FM_DEV_DEFAULT;
|
||||
FLocalAudio := True; // GUI: звук на локальную карту; демон выключит явно
|
||||
FLastSMeter := -130; FLastSWR := 1; FLastSupplyV := -1; FLastSupplyA := -1;
|
||||
FLastPlutoTempC := TELEMETRY_NONE; FLastPlutoSocTempC := TELEMETRY_NONE;
|
||||
@@ -1316,7 +1319,7 @@ procedure TRadioController.OnAudioReady(const Left, Right: array of Single;
|
||||
begin
|
||||
// Decoded DMR PCM will use this route later. Until then suppress raw 4FSK;
|
||||
// the independent pre-volume tap is handled by OnDemodAudioReady.
|
||||
if FMode = MODE_DMR then Exit;
|
||||
if FMode in [MODE_DMR, MODE_FMRAW] then Exit;
|
||||
|
||||
if FSendAudioToRadio then
|
||||
FNetwork.SendSpeakerAudio(Left, Right, Count);
|
||||
@@ -1332,13 +1335,19 @@ procedure TRadioController.OnDemodAudioReady(const Left, Right: array of Single;
|
||||
Count: Integer);
|
||||
// DSP thread, pre-volume/pre-mute. FeedAudio only copies into a bounded ring.
|
||||
begin
|
||||
if not Assigned(FDMRDec) then Exit;
|
||||
if FMode = MODE_DMR then
|
||||
begin
|
||||
if not Assigned(FDMRDec) then Exit;
|
||||
if not FDMRDec.Enabled then FDMRDec.SetEnabled(True);
|
||||
FDMRDec.FeedAudio(Left, Right, Count);
|
||||
end
|
||||
else if FDMRDec.Enabled then
|
||||
else if FMode = MODE_FMRAW then
|
||||
begin
|
||||
// FM RAW decoder audio deliberately bypasses speaker volume and mute.
|
||||
if FLocalAudio and Assigned(FAudioOut) then
|
||||
FAudioOut.Write(Left, Right, Count);
|
||||
end
|
||||
else if Assigned(FDMRDec) and FDMRDec.Enabled then
|
||||
FDMRDec.SetEnabled(False);
|
||||
end;
|
||||
|
||||
@@ -1396,7 +1405,7 @@ begin
|
||||
if not FLocalAudio then Exit;
|
||||
idx := FindSliceIndex(SliceId);
|
||||
if idx < 0 then Exit;
|
||||
if FSlices[idx].Mode = MODE_DMR then Exit;
|
||||
if FSlices[idx].Mode in [MODE_DMR, MODE_FMRAW] then Exit;
|
||||
if SliceMutedByTx(idx) then Exit;
|
||||
if FSlices[idx].Audio <> nil then
|
||||
FSlices[idx].Audio.Write(Left, Right, Count);
|
||||
@@ -1417,9 +1426,12 @@ procedure TRadioController.OnSliceDemodAudioReady(SliceId: Integer;
|
||||
var idx: Integer;
|
||||
begin
|
||||
idx := FindSliceIndex(SliceId);
|
||||
if (idx < 0) or (FSlices[idx].Mode <> MODE_DMR) then Exit;
|
||||
if Assigned(FSlices[idx].DMR) then
|
||||
if idx < 0 then Exit;
|
||||
if (FSlices[idx].Mode = MODE_DMR) and Assigned(FSlices[idx].DMR) then
|
||||
FSlices[idx].DMR.FeedAudio(Left, Right, Count);
|
||||
if (FSlices[idx].Mode = MODE_FMRAW) and FLocalAudio and
|
||||
Assigned(FSlices[idx].Audio) then
|
||||
FSlices[idx].Audio.Write(Left, Right, Count);
|
||||
end;
|
||||
|
||||
// DMR worker thread: decoded 8 kHz mono -> this slice's 48 kHz local output.
|
||||
@@ -1821,6 +1833,7 @@ var
|
||||
AO: TAudioOutput;
|
||||
begin
|
||||
Result := -1;
|
||||
Mode := EnsureRange(Mode, MODE_MIN, MODE_MAX);
|
||||
if not Assigned(FDSPEngine) then Exit;
|
||||
if (APanId < 0) or (APanId >= MAX_PANS) then Exit;
|
||||
if (APanId > 0) and (FPanDDCIdx[APanId] < 0) then Exit; // пан не создан
|
||||
@@ -1901,6 +1914,7 @@ end;
|
||||
procedure TRadioController.SetSliceMode(Id, Mode: Integer);
|
||||
var idx, OldMode: Integer;
|
||||
begin
|
||||
Mode := EnsureRange(Mode, MODE_MIN, MODE_MAX);
|
||||
idx := FindSliceIndex(Id);
|
||||
if idx < 0 then Exit;
|
||||
OldMode := FSlices[idx].Mode;
|
||||
@@ -1910,7 +1924,7 @@ begin
|
||||
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 = MODE_DMR) and (FTxSliceId = Id) then SetTxSlice(0);
|
||||
if (Mode in [MODE_DMR, MODE_FMRAW]) and (FTxSliceId = Id) then SetTxSlice(0);
|
||||
if (OldMode = MODE_DMR) and (Mode <> MODE_DMR) then
|
||||
FreeAndNil(FSlices[idx].DMR);
|
||||
end;
|
||||
@@ -2221,12 +2235,12 @@ 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 = MODE_DMR then Exit;
|
||||
if S.Mode in [MODE_DMR, MODE_FMRAW] 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 = MODE_DMR) then
|
||||
if (SliceId = 0) and (FMode in [MODE_DMR, MODE_FMRAW]) then
|
||||
begin
|
||||
if FTuning then SetTune(False)
|
||||
else if FTransmitting then SetMOX(False);
|
||||
@@ -2529,11 +2543,15 @@ begin
|
||||
begin
|
||||
FDSPEngine.SetFilter(Lo, Hi);
|
||||
FDSPEngine.SetTXFilter(Lo, Hi);
|
||||
if (FMode = MODE_FM) or (FMode = MODE_DMR) then
|
||||
if FMode in [MODE_FM, MODE_DMR, MODE_FMRAW] then
|
||||
begin
|
||||
FDSPEngine.SetRXFMDeviation(FFMDeviation);
|
||||
FDSPEngine.SetTXFMParams(FFMDeviation, FTXSettings.FMLowCut,
|
||||
FTXSettings.FMHighCut, FTXSettings.FMEmphPosition);
|
||||
if FMode = MODE_FMRAW then
|
||||
FDSPEngine.SetRawFMDeviation(FRawFMDeviation)
|
||||
else
|
||||
FDSPEngine.SetRXFMDeviation(FFMDeviation);
|
||||
if FMode <> MODE_FMRAW then
|
||||
FDSPEngine.SetTXFMParams(FFMDeviation, FTXSettings.FMLowCut,
|
||||
FTXSettings.FMHighCut, FTXSettings.FMEmphPosition);
|
||||
// A squelch would remove sync preambles; DMR always receives open.
|
||||
FDSPEngine.SetFMSquelch((FMode = MODE_FM) and FFMSQOn, FFMSQLevel);
|
||||
end
|
||||
@@ -2565,6 +2583,11 @@ begin
|
||||
FFilterBW := FILT_DMR_BW;
|
||||
FFMDeviation := DMR_FM_DEV;
|
||||
end;
|
||||
MODE_FMRAW:
|
||||
begin
|
||||
if FFilter > FILT_RAW_COUNT - 1 then FFilter := FILT_RAW_DEF;
|
||||
FFilterBW := FILT_RAW_BW[FFilter];
|
||||
end;
|
||||
MODE_LSB, MODE_USB:
|
||||
begin FFilter := FILT_SSB_DEF; FFilterBW := FILT_SSB_BW[FFilter]; end;
|
||||
MODE_DIGU, MODE_DIGL:
|
||||
@@ -2585,6 +2608,7 @@ begin
|
||||
MODE_FM: Result := FILT_FM_BW[EnsureRange(Idx, 0, FILT_FM_COUNT - 1)];
|
||||
MODE_WFM: Result := FILT_WFM_BW[EnsureRange(Idx, 0, FILT_WFM_COUNT - 1)];
|
||||
MODE_DMR: Result := FILT_DMR_BW;
|
||||
MODE_FMRAW: Result := FILT_RAW_BW[EnsureRange(Idx, 0, FILT_RAW_COUNT - 1)];
|
||||
MODE_LSB, MODE_USB:
|
||||
Result := FILT_SSB_BW[EnsureRange(Idx, 0, FILT_COUNT - 1)];
|
||||
MODE_DIGU, MODE_DIGL:
|
||||
@@ -2663,6 +2687,11 @@ begin
|
||||
FFilterBW := FILT_DMR_BW;
|
||||
FFMDeviation := DMR_FM_DEV;
|
||||
end;
|
||||
if FMode = MODE_FMRAW then
|
||||
begin
|
||||
if FFilter > FILT_RAW_COUNT - 1 then FFilter := FILT_RAW_DEF;
|
||||
FFilterBW := FILT_RAW_BW[FFilter];
|
||||
end;
|
||||
end;
|
||||
|
||||
function TRadioController.MakeBandSettings: TBandSettings;
|
||||
@@ -2821,6 +2850,7 @@ begin
|
||||
Result.AudioOutDevice := FAudioOutDevName;
|
||||
Result.AudioInDevice := FAudioInDevName;
|
||||
Result.SendAudioToRadio := FSendAudioToRadio;
|
||||
Result.RawFMDeviation := Round(FRawFMDeviation);
|
||||
end;
|
||||
|
||||
procedure TRadioController.SaveGlobalSettings;
|
||||
@@ -2832,7 +2862,7 @@ procedure TRadioController.ApplyBandDSP(const B: TBandSettings);
|
||||
// DSP-настройки диапазона + события рендера. Частоты VFO/центр НЕ трогает.
|
||||
begin
|
||||
// --- Режим ---
|
||||
FMode := B.Mode;
|
||||
FMode := EnsureRange(B.Mode, MODE_MIN, MODE_MAX);
|
||||
if Assigned(FDMRDec) then FDMRDec.SetEnabled(FMode = MODE_DMR);
|
||||
if FWDSPReady and Assigned(FDSPEngine) then FDSPEngine.SetMode(FMode);
|
||||
|
||||
@@ -2867,7 +2897,7 @@ begin
|
||||
end;
|
||||
FFMStepOn := B.FMStepOn;
|
||||
FFMStepIdx := EnsureRange(B.FMStepIdx, 0, 3);
|
||||
if FMode = MODE_DMR then
|
||||
if FMode in [MODE_DMR, MODE_FMRAW] then
|
||||
begin
|
||||
FFMStepOn := True;
|
||||
FFMStepIdx := 1; // DMR default raster: 12.5 kHz
|
||||
@@ -2974,8 +3004,8 @@ begin
|
||||
if FWDSPReady and Assigned(FDSPEngine) then FDSPEngine.SetShift(Offset);
|
||||
HalfSpan := FSpanHz / 2;
|
||||
case FMode of
|
||||
0: begin FiltLo := Offset - FFilterBW; FiltHi := Offset - 100; end;
|
||||
1: begin FiltLo := Offset + 100; FiltHi := Offset + FFilterBW; end;
|
||||
MODE_LSB: begin FiltLo := Offset - FFilterBW; FiltHi := Offset - 100; end;
|
||||
MODE_USB: begin FiltLo := Offset + 100; FiltHi := Offset + FFilterBW; end;
|
||||
else begin FiltLo := Offset - FFilterBW/2; FiltHi := Offset + FFilterBW/2; end;
|
||||
end;
|
||||
if (FiltHi > HalfSpan) or (FiltLo < -HalfSpan) then
|
||||
@@ -3228,19 +3258,20 @@ end;
|
||||
|
||||
procedure TRadioController.SetMode(M: Integer);
|
||||
begin
|
||||
M := EnsureRange(M, MODE_MIN, MODE_MAX);
|
||||
FMode := M;
|
||||
// DMR channels use a 12.5 kHz raster by default. Keep the ordinary STEP
|
||||
// controls enabled so the operator can select another raster afterwards.
|
||||
if FMode = MODE_DMR then
|
||||
if FMode in [MODE_DMR, MODE_FMRAW] then
|
||||
begin
|
||||
FFMStepOn := True;
|
||||
FFMStepIdx := 1; // 12.5 kHz
|
||||
if FMode = MODE_DMR then FFMStepIdx := 1; // 12.5 kHz
|
||||
Changed(rfFMStep);
|
||||
Changed(rfFMStepIdx);
|
||||
end;
|
||||
// Entering receive-only DMR on the selected main TX source immediately
|
||||
// releases an existing carrier. SetTune(False) also releases MOX.
|
||||
if ActiveTXMode = MODE_DMR then
|
||||
if ActiveTXMode in [MODE_DMR, MODE_FMRAW] then
|
||||
begin
|
||||
if FTuning then SetTune(False)
|
||||
else if FTransmitting then SetMOX(False);
|
||||
@@ -3266,6 +3297,7 @@ begin
|
||||
MODE_FM: Idx := EnsureRange(Idx, 0, FILT_FM_COUNT - 1);
|
||||
MODE_WFM: Idx := EnsureRange(Idx, 0, FILT_WFM_COUNT - 1);
|
||||
MODE_DMR: Idx := 0;
|
||||
MODE_FMRAW: Idx := EnsureRange(Idx, 0, FILT_RAW_COUNT - 1);
|
||||
else Idx := EnsureRange(Idx, 0, FILT_COUNT - 1);
|
||||
end;
|
||||
FFilter := Idx;
|
||||
@@ -3276,6 +3308,7 @@ begin
|
||||
MODE_CWL, MODE_CWU: FFilterBW := FILT_CW_BW[Idx];
|
||||
MODE_WFM: FFilterBW := FILT_WFM_BW[Idx];
|
||||
MODE_DMR: FFilterBW := FILT_DMR_BW;
|
||||
MODE_FMRAW: FFilterBW := FILT_RAW_BW[Idx];
|
||||
MODE_FM:
|
||||
begin
|
||||
FFilterBW := FILT_FM_BW[Idx];
|
||||
@@ -3309,6 +3342,7 @@ begin
|
||||
MODE_FM: for i := 0 to FILT_FM_COUNT-1 do BW_arr[i] := FILT_FM_BW[i];
|
||||
MODE_WFM: for i := 0 to FILT_WFM_COUNT-1 do BW_arr[i] := FILT_WFM_BW[i];
|
||||
MODE_DMR: BW_arr[0] := FILT_DMR_BW;
|
||||
MODE_FMRAW: for i := 0 to FILT_RAW_COUNT-1 do BW_arr[i] := FILT_RAW_BW[i];
|
||||
else for i := 0 to FILT_COUNT-1 do BW_arr[i] := FILT_AM_BW[i];
|
||||
end;
|
||||
best := 0; dist := MaxInt;
|
||||
@@ -3325,6 +3359,15 @@ begin
|
||||
Changed(rfFilter);
|
||||
end;
|
||||
|
||||
procedure TRadioController.SetRawFMDeviation(Hz: Integer);
|
||||
begin
|
||||
FRawFMDeviation := EnsureRange(Hz, RAW_FM_DEV_MIN, RAW_FM_DEV_MAX);
|
||||
if FWDSPReady and Assigned(FDSPEngine) then
|
||||
FDSPEngine.SetRawFMDeviation(FRawFMDeviation);
|
||||
SaveGlobalSettings;
|
||||
Changed(rfFMDeviation);
|
||||
end;
|
||||
|
||||
// UI-индекс 0..4 (FAST/MED/SLOW/LONG/OFF) -> WDSP enum. Единый маппинг для всех
|
||||
// фронтендов и слайсов (раньше web/слайдер ошибочно кастовали индекс прямо в enum).
|
||||
class function TRadioController.AGCModeFromUI(M: Integer): TWDSPAGCMode;
|
||||
@@ -4132,7 +4175,7 @@ var
|
||||
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 = MODE_DMR) then Exit;
|
||||
if On_ and (ActiveTXMode in [MODE_DMR, MODE_FMRAW]) then Exit;
|
||||
// Safety: блокируем TX на бэндах с DoNotTx (Alex) и на RX-only трансвертере.
|
||||
if On_ and FDevConnected and (FCurrentXvtr < 0)
|
||||
and FAlexSettings.DoNotTx[EnsureRange(TxBandIdx, 0, 10)] then
|
||||
@@ -4250,7 +4293,7 @@ end;
|
||||
procedure TRadioController.SetTune(On_: Boolean);
|
||||
begin
|
||||
if On_ = FTuning then Exit;
|
||||
if On_ and (ActiveTXMode = MODE_DMR) then Exit;
|
||||
if On_ and (ActiveTXMode in [MODE_DMR, MODE_FMRAW]) then Exit;
|
||||
// Safety: те же блокировки, что и в SetMOX — TUN это TX через WDSP PostGen-тон,
|
||||
// он тоже должен молчать на бэндах с DoNotTx и на RX-only XVTR.
|
||||
if On_ and FDevConnected and (FCurrentXvtr < 0)
|
||||
@@ -4364,6 +4407,9 @@ begin
|
||||
FTXSettings.PSPin, FTXSettings.PSMap,
|
||||
FTXSettings.PSStabilize, Ints, Spi,
|
||||
FTXSettings.PSHWPeak);
|
||||
// Правка частот двухтональника при активном 2TON — применяем живьём.
|
||||
if FTwoTone then
|
||||
FDSPEngine.SetTXTwoTone(True, FTXSettings.PSTTFreq1, FTXSettings.PSTTFreq2);
|
||||
end;
|
||||
|
||||
function TRadioController.PSFeedbackLevel: Integer;
|
||||
@@ -4541,7 +4587,7 @@ procedure TRadioController.SetTwoTone(On_: Boolean);
|
||||
// Drive; тона PSTTFreq1/2 (700/1900 по умолчанию).
|
||||
begin
|
||||
if On_ = FTwoTone then Exit;
|
||||
if On_ and (ActiveTXMode = MODE_DMR) then Exit;
|
||||
if On_ and (ActiveTXMode in [MODE_DMR, MODE_FMRAW]) then Exit;
|
||||
if On_ and FTuning then Exit; // не поверх TUN-тона
|
||||
// Safety: те же блокировки, что SetMOX/SetTune.
|
||||
if On_ and FDevConnected and (FCurrentXvtr < 0)
|
||||
@@ -4952,6 +4998,10 @@ begin
|
||||
FDitherEnabled := FLoadedGlobal.DitherEnabled;
|
||||
FRandomEnabled := FLoadedGlobal.RandomEnabled;
|
||||
FSendAudioToRadio := FLoadedGlobal.SendAudioToRadio;
|
||||
FRawFMDeviation := EnsureRange(FLoadedGlobal.RawFMDeviation,
|
||||
RAW_FM_DEV_MIN, RAW_FM_DEV_MAX);
|
||||
if FWDSPReady and Assigned(FDSPEngine) then
|
||||
FDSPEngine.SetRawFMDeviation(FRawFMDeviation);
|
||||
FWfManualHigh := FLoadedGlobal.WfManualHigh;
|
||||
FWfManualLow := FLoadedGlobal.WfManualLow;
|
||||
FWfAGCOffset := FLoadedGlobal.WfAGCOffset;
|
||||
|
||||
Reference in New Issue
Block a user