feat(dmr): add diagnostic capture probes

This commit is contained in:
2026-07-14 19:09:35 +03:00
parent 70486d901b
commit 39b4627c2b
9 changed files with 662 additions and 3 deletions
+39
View File
@@ -50,6 +50,7 @@ uses
Classes, SysUtils, Math,
HPSDRProtocol, HPSDRNetwork, RadioBackend, PlutoBackend, IIOBindings,
WDSPEngine, AudioOutput, AudioInput, BeaconDecoder, BeaconFEC, DMRDecoder,
DMRDiagnostics,
Settings, ChannelStore, FMRepeater, BoardUtils, DeviceStore;
// Панадаптеры (этап 3): потолок MAX_PANS живёт в WDSPEngine (общий для
@@ -251,6 +252,7 @@ type
// ---- DMR 4FSK receive decoder ----
FDMRDec: TDMRDecoder; // 48 kHz FM discriminator -> DMR worker
FDMRDiag: TDMRDiagnosticCapture; // bounded async IQ/demod/burst recorder
// ---- Аудио / TX ----
FVolume: Integer; // громкость RX
@@ -406,6 +408,7 @@ type
procedure OnAudioReady(const Left, Right: array of Single; Count: Integer);
procedure OnDemodAudioReady(const Left, Right: array of Single; Count: Integer);
procedure OnDMRAudioReady(const PCM: array of Single; Count: Integer);
procedure OnDMRBurstReady(const Dibits: array of Byte; 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);
@@ -676,6 +679,9 @@ type
function GetBeaconScope(out S: TBeaconScope): Boolean;
function GetBeaconFrame(out Frame: TBeaconFrame; out RSErrors: Integer): Boolean;
function GetDMRStatus(out S: TDMRStatus): Boolean;
function StartDMRDiagnostics(DurationSeconds: Integer = 15): string;
procedure StopDMRDiagnostics;
function DMRDiagnosticsActive: Boolean;
// Отображение / приём
procedure SetCTun(On_: Boolean);
@@ -851,8 +857,10 @@ begin
FDSPEngine.SetBeaconDecoder(FBeaconDec);
// DMR front-end owns a worker thread. It stays idle until MODE_DMR is active.
FDMRDiag := TDMRDiagnosticCapture.Create;
FDMRDec := TDMRDecoder.Create;
FDMRDec.OnAudio := OnDMRAudioReady;
FDMRDec.OnBurst := OnDMRBurstReady;
// Audio out/in — объекты создаём сейчас, Open вызывается позже (после показа
// формы / при подключении устройства).
@@ -869,8 +877,10 @@ begin
begin
FDMRDec.SetEnabled(False);
FDMRDec.OnAudio := nil;
FDMRDec.OnBurst := nil;
FreeAndNil(FDMRDec);
end;
FreeAndNil(FDMRDiag);
if Assigned(FNetwork) then
begin
if FNetwork.Connected then FNetwork.Disconnect;
@@ -982,6 +992,9 @@ begin
SamplesPerFrame := (Integer(Data.SamplesPerFrame[0]) shl 8) or
Integer(Data.SamplesPerFrame[1]);
if SamplesPerFrame <= 0 then SamplesPerFrame := 238; // fallback
if (DDCIndex = FActiveDDC) and (FMode = MODE_DMR) and
Assigned(FDMRDiag) and FDMRDiag.Active then
FDMRDiag.FeedIQ24BE(Data.IQData, SamplesPerFrame * 6);
if DDCIndex = FActiveDDC then
FDSPEngine.PushDDCPacket(Data.IQData, 0, SamplesPerFrame)
else if (DDCIndex >= 0) and (DDCIndex < MAX_DDCS)
@@ -1187,6 +1200,8 @@ begin
if not Assigned(FDMRDec) then Exit;
if FMode = MODE_DMR then
begin
if Assigned(FDMRDiag) and FDMRDiag.Active then
FDMRDiag.FeedDemod(Left, Right, Count);
if not FDMRDec.Enabled then FDMRDec.SetEnabled(True);
FDMRDec.FeedAudio(Left, Right, Count);
end
@@ -1194,6 +1209,13 @@ begin
FDMRDec.SetEnabled(False);
end;
procedure TRadioController.OnDMRBurstReady(const Dibits: array of Byte;
Count: Integer);
begin
if Assigned(FDMRDiag) and FDMRDiag.Active then
FDMRDiag.FeedBurst(Dibits, Count);
end;
procedure TRadioController.OnDMRAudioReady(const PCM: array of Single;
Count: Integer);
// DMR worker thread, 8 kHz mono from mbelib. Linear 6x interpolation brings
@@ -2233,6 +2255,23 @@ begin
else FillChar(S, SizeOf(S), 0);
end;
function TRadioController.StartDMRDiagnostics(DurationSeconds: Integer): string;
begin
if not Assigned(FDMRDiag) then Exit('');
Result := FDMRDiag.Start(DurationSeconds, FSampleRate, FCenterFreq,
ActiveVfoHz);
end;
procedure TRadioController.StopDMRDiagnostics;
begin
if Assigned(FDMRDiag) then FDMRDiag.Stop;
end;
function TRadioController.DMRDiagnosticsActive: Boolean;
begin
Result := Assigned(FDMRDiag) and FDMRDiag.Active;
end;
// Кромки полосы под режим/полосу (единый расчёт для главного канала и слайсов).
class procedure TRadioController.FilterEdgesFor(Mode, BW: Integer; out Lo, Hi: Integer);
var Half: Integer;