mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 17:27:32 +00:00
feat(dmr): add receive frontend and AMBE adapter
This commit is contained in:
+57
-3
@@ -49,7 +49,7 @@ interface
|
||||
uses
|
||||
Classes, SysUtils, Math,
|
||||
HPSDRProtocol, HPSDRNetwork, RadioBackend, PlutoBackend, IIOBindings,
|
||||
WDSPEngine, AudioOutput, AudioInput, BeaconDecoder, BeaconFEC,
|
||||
WDSPEngine, AudioOutput, AudioInput, BeaconDecoder, BeaconFEC, DMRDecoder,
|
||||
Settings, ChannelStore, FMRepeater, BoardUtils, DeviceStore;
|
||||
|
||||
// Панадаптеры (этап 3): потолок MAX_PANS живёт в WDSPEngine (общий для
|
||||
@@ -249,6 +249,9 @@ type
|
||||
// (узкий фильтр, ставится кликом); 0 = ещё не наведён
|
||||
FBeaconDecInvert: Boolean; // зеркальный baseband (QO-100/LNB) — деф. True
|
||||
|
||||
// ---- DMR 4FSK receive decoder ----
|
||||
FDMRDec: TDMRDecoder; // 48 kHz FM discriminator -> DMR worker
|
||||
|
||||
// ---- Аудио / TX ----
|
||||
FVolume: Integer; // громкость RX
|
||||
FTxMonVolume: Integer; // громкость self-monitor RX-аудио на TX (RX MUTE off, DUP)
|
||||
@@ -401,6 +404,7 @@ type
|
||||
// Чистая обработка данных без UI; навешиваются на FNetwork/FDSPEngine
|
||||
// вызывающим (GUI или демон) после CreateEngines.
|
||||
procedure OnAudioReady(const Left, Right: array of Single; Count: Integer);
|
||||
procedure OnDemodAudioReady(const Left, Right: array of Single; Count: Integer);
|
||||
procedure OnSpectrumReady(const Pixels: array of Single; Count: Integer);
|
||||
procedure OnWaterfallReady(const Pixels: array of Single; Count: Integer);
|
||||
procedure OnMicPacket(const Data: TMicDataPacket);
|
||||
@@ -670,6 +674,7 @@ type
|
||||
function BeaconDecodeInvert: Boolean;
|
||||
function GetBeaconScope(out S: TBeaconScope): Boolean;
|
||||
function GetBeaconFrame(out Frame: TBeaconFrame; out RSErrors: Integer): Boolean;
|
||||
function GetDMRStatus(out S: TDMRStatus): Boolean;
|
||||
|
||||
// Отображение / приём
|
||||
procedure SetCTun(On_: Boolean);
|
||||
@@ -827,6 +832,7 @@ begin
|
||||
// Роутинг аудио слайсов на их звуковухи — внутренний, вешаем сразу (не
|
||||
// зависит от фронтенда, в отличие от OnAudio/OnSpectrum).
|
||||
FDSPEngine.OnSliceAudio := OnSliceAudioReady;
|
||||
FDSPEngine.OnDemodAudio := OnDemodAudioReady;
|
||||
FDSPEngine.SetDisplayResLimit(FDisplayPixels);
|
||||
|
||||
// QO-100 beacon lock: эталон частоты = декодер (см. ServiceBeaconLock).
|
||||
@@ -843,6 +849,9 @@ begin
|
||||
FBeaconDec.SetInvert(FBeaconDecInvert);
|
||||
FDSPEngine.SetBeaconDecoder(FBeaconDec);
|
||||
|
||||
// DMR front-end owns a worker thread. It stays idle until MODE_DMR is active.
|
||||
FDMRDec := TDMRDecoder.Create;
|
||||
|
||||
// Audio out/in — объекты создаём сейчас, Open вызывается позже (после показа
|
||||
// формы / при подключении устройства).
|
||||
FAudioOut := TAudioOutput.Create(48000);
|
||||
@@ -874,6 +883,7 @@ begin
|
||||
FDSPEngine.Close;
|
||||
FreeAndNil(FDSPEngine);
|
||||
end;
|
||||
if Assigned(FDMRDec) then FreeAndNil(FDMRDec);
|
||||
if Assigned(FBeaconDec) then FreeAndNil(FBeaconDec);
|
||||
end;
|
||||
|
||||
@@ -1148,6 +1158,10 @@ procedure TRadioController.OnAudioReady(const Left, Right: array of Single;
|
||||
// port 1028) — независимый канал. Затем web-адаптер может перехватить аудио
|
||||
// (активный клиент); если перехвачено — в локальный AudioOut не пишем.
|
||||
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 FSendAudioToRadio then
|
||||
FNetwork.SendSpeakerAudio(Left, Right, Count);
|
||||
if Assigned(FOnAudioConsume) and FOnAudioConsume(Left, Right, Count) then
|
||||
@@ -1158,6 +1172,20 @@ begin
|
||||
FAudioOut.Write(Left, Right, Count);
|
||||
end;
|
||||
|
||||
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 FDMRDec.Enabled then FDMRDec.SetEnabled(True);
|
||||
FDMRDec.FeedAudio(Left, Right, Count);
|
||||
end
|
||||
else if FDMRDec.Enabled then
|
||||
FDMRDec.SetEnabled(False);
|
||||
end;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Мультислайсы (софт-fan-out)
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -2157,6 +2185,13 @@ begin
|
||||
Result := Assigned(FBeaconDec) and FBeaconDec.GetLastFrame(Frame, RSErrors);
|
||||
end;
|
||||
|
||||
function TRadioController.GetDMRStatus(out S: TDMRStatus): Boolean;
|
||||
begin
|
||||
Result := Assigned(FDMRDec);
|
||||
if Result then FDMRDec.GetStatus(S)
|
||||
else FillChar(S, SizeOf(S), 0);
|
||||
end;
|
||||
|
||||
// Кромки полосы под режим/полосу (единый расчёт для главного канала и слайсов).
|
||||
class procedure TRadioController.FilterEdgesFor(Mode, BW: Integer; out Lo, Hi: Integer);
|
||||
var Half: Integer;
|
||||
@@ -2190,12 +2225,13 @@ begin
|
||||
begin
|
||||
FDSPEngine.SetFilter(Lo, Hi);
|
||||
FDSPEngine.SetTXFilter(Lo, Hi);
|
||||
if FMode = MODE_FM then
|
||||
if (FMode = MODE_FM) or (FMode = MODE_DMR) then
|
||||
begin
|
||||
FDSPEngine.SetRXFMDeviation(FFMDeviation);
|
||||
FDSPEngine.SetTXFMParams(FFMDeviation, FTXSettings.FMLowCut,
|
||||
FTXSettings.FMHighCut, FTXSettings.FMEmphPosition);
|
||||
FDSPEngine.SetFMSquelch(FFMSQOn, FFMSQLevel);
|
||||
// A squelch would remove sync preambles; DMR always receives open.
|
||||
FDSPEngine.SetFMSquelch((FMode = MODE_FM) and FFMSQOn, FFMSQLevel);
|
||||
end
|
||||
else
|
||||
FDSPEngine.SetFMSquelch(False, 0);
|
||||
@@ -2219,6 +2255,12 @@ begin
|
||||
if FFilter > FILT_WFM_COUNT - 1 then FFilter := FILT_WFM_DEF;
|
||||
FFilterBW := FILT_WFM_BW[FFilter];
|
||||
end;
|
||||
MODE_DMR:
|
||||
begin
|
||||
FFilter := 0;
|
||||
FFilterBW := FILT_DMR_BW;
|
||||
FFMDeviation := DMR_FM_DEV;
|
||||
end;
|
||||
MODE_LSB, MODE_USB:
|
||||
begin FFilter := FILT_SSB_DEF; FFilterBW := FILT_SSB_BW[FFilter]; end;
|
||||
MODE_DIGU, MODE_DIGL:
|
||||
@@ -2238,6 +2280,7 @@ begin
|
||||
case M of
|
||||
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_LSB, MODE_USB:
|
||||
Result := FILT_SSB_BW[EnsureRange(Idx, 0, FILT_COUNT - 1)];
|
||||
MODE_DIGU, MODE_DIGL:
|
||||
@@ -2310,6 +2353,12 @@ begin
|
||||
FFilterBW := FILT_FM_BW[FFilter];
|
||||
FFMDeviation := FILT_FM_DEV[FFilter];
|
||||
end;
|
||||
if FMode = MODE_DMR then
|
||||
begin
|
||||
FFilter := 0;
|
||||
FFilterBW := FILT_DMR_BW;
|
||||
FFMDeviation := DMR_FM_DEV;
|
||||
end;
|
||||
end;
|
||||
|
||||
function TRadioController.MakeBandSettings: TBandSettings;
|
||||
@@ -2480,6 +2529,7 @@ procedure TRadioController.ApplyBandDSP(const B: TBandSettings);
|
||||
begin
|
||||
// --- Режим ---
|
||||
FMode := B.Mode;
|
||||
if Assigned(FDMRDec) then FDMRDec.SetEnabled(FMode = MODE_DMR);
|
||||
if FWDSPReady and Assigned(FDSPEngine) then FDSPEngine.SetMode(FMode);
|
||||
|
||||
// --- Фильтр (санитизация FM до применения к DSP — как в оригинале) ---
|
||||
@@ -2870,6 +2920,7 @@ end;
|
||||
procedure TRadioController.SetMode(M: Integer);
|
||||
begin
|
||||
FMode := M;
|
||||
if Assigned(FDMRDec) then FDMRDec.SetEnabled(FMode = MODE_DMR);
|
||||
ApplyModeDefaults; // дефолтный фильтр/девиация под режим
|
||||
FBandCache[FCurrentBand].Mode := FMode;
|
||||
if FWDSPReady and Assigned(FDSPEngine) then
|
||||
@@ -2889,6 +2940,7 @@ begin
|
||||
case FMode of
|
||||
MODE_FM: Idx := EnsureRange(Idx, 0, FILT_FM_COUNT - 1);
|
||||
MODE_WFM: Idx := EnsureRange(Idx, 0, FILT_WFM_COUNT - 1);
|
||||
MODE_DMR: Idx := 0;
|
||||
else Idx := EnsureRange(Idx, 0, FILT_COUNT - 1);
|
||||
end;
|
||||
FFilter := Idx;
|
||||
@@ -2898,6 +2950,7 @@ begin
|
||||
MODE_DSB: FFilterBW := FILT_DSB_BW[Idx];
|
||||
MODE_CWL, MODE_CWU: FFilterBW := FILT_CW_BW[Idx];
|
||||
MODE_WFM: FFilterBW := FILT_WFM_BW[Idx];
|
||||
MODE_DMR: FFilterBW := FILT_DMR_BW;
|
||||
MODE_FM:
|
||||
begin
|
||||
FFilterBW := FILT_FM_BW[Idx];
|
||||
@@ -2929,6 +2982,7 @@ begin
|
||||
for i := 0 to FILT_COUNT-1 do BW_arr[i] := FILT_CW_BW[i];
|
||||
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;
|
||||
else for i := 0 to FILT_COUNT-1 do BW_arr[i] := FILT_AM_BW[i];
|
||||
end;
|
||||
best := 0; dist := MaxInt;
|
||||
|
||||
Reference in New Issue
Block a user