fix(dmr): center discriminator eye per sync

This commit is contained in:
2026-07-14 19:47:55 +03:00
parent a633bf57cd
commit 22505d4df7
2 changed files with 61 additions and 17 deletions
+54 -12
View File
@@ -24,6 +24,7 @@ const
DMR_INPUT_RATE = 48000;
DMR_SYMBOL_RATE = 4800;
DMR_SAMPLES_PER_SYM = DMR_INPUT_RATE div DMR_SYMBOL_RATE;
DMR_INTEGRATOR_SAMPLES = 8;
DMR_RING_SIZE = 65536; // >1.3 s at 48 kHz, power of two
DMR_BURST_DIBITS = 144;
DMR_SYNC_END_DIBIT = 89; // sync occupies burst dibits 66..89
@@ -100,7 +101,7 @@ type
FReadPos, FWritePos: Integer;
FDropped: QWord;
FSampleWindow: array[0..DMR_SAMPLES_PER_SYM - 1] of Double;
FSampleWindow: array[0..DMR_INTEGRATOR_SAMPLES - 1] of Double;
FWindowPos, FWindowCount: Integer;
FWindowSum: Double;
FSampleNo: QWord;
@@ -117,6 +118,9 @@ type
FMagSum: array[0..DMR_SAMPLES_PER_SYM - 1] of Double;
FBestSyncQuality: Double;
FFixedOuterLevel: Double;
FFixedPositiveOuter: Double;
FFixedNegativeOuter: Double;
FFixedCenter: Double;
FTrackPhase: Integer;
FCurrentBurst: TDMRDibitBurst;
FCurrentBurstPos: Integer;
@@ -155,6 +159,7 @@ type
function Digitize(Phase: Integer; Symbol: Double): Byte;
function SliceSymbol(Symbol, OuterLevel: Double): Byte;
function SyncEyeOpening(Phase: Integer): Double;
procedure CalibrateSyncLevels(Phase: Integer);
procedure StartBurstTracking(Phase: Integer);
procedure CompleteBurst;
procedure DecodeVoiceBurst;
@@ -315,6 +320,9 @@ begin
FillChar(FMagSum, SizeOf(FMagSum), 0);
FBestSyncQuality := 0;
FFixedOuterLevel := 0;
FFixedPositiveOuter := 0;
FFixedNegativeOuter := 0;
FFixedCenter := 0;
FillChar(FCurrentBurst, SizeOf(FCurrentBurst), 0);
FillChar(FLastBurst, SizeOf(FLastBurst), 0);
FillChar(FLastBurstInfo, SizeOf(FLastBurstInfo), 0);
@@ -427,15 +435,15 @@ begin
FSignalMean := FSignalMean + 0.001 * (S - FSignalMean);
FWindowSum := FWindowSum - FSampleWindow[FWindowPos] + S;
FSampleWindow[FWindowPos] := S;
FWindowPos := (FWindowPos + 1) mod DMR_SAMPLES_PER_SYM;
if FWindowCount < DMR_SAMPLES_PER_SYM then Inc(FWindowCount);
FWindowPos := (FWindowPos + 1) mod DMR_INTEGRATOR_SAMPLES;
if FWindowCount < DMR_INTEGRATOR_SAMPLES then Inc(FWindowCount);
Inc(FSampleNo);
if FWindowCount < DMR_SAMPLES_PER_SYM then Exit;
if FWindowCount < DMR_INTEGRATOR_SAMPLES then Exit;
// Ten interleaved timing hypotheses. Each receives one boxcar-integrated
// symbol every ten input samples; the correct phase produces exact sync.
Phase := Integer(FSampleNo mod DMR_SAMPLES_PER_SYM);
Sym := FWindowSum / DMR_SAMPLES_PER_SYM;
Sym := FWindowSum / DMR_INTEGRATOR_SAMPLES;
ProcessSymbol(Phase, Sym);
end;
@@ -580,6 +588,9 @@ begin
FTrackPhase := -1;
FCurrentBurstPos := 0;
FFixedOuterLevel := 0;
FFixedPositiveOuter := 0;
FFixedNegativeOuter := 0;
FFixedCenter := 0;
FMobileActiveBurst := True;
DMRMbeReset(FMbe);
end;
@@ -589,7 +600,7 @@ var
A: Double;
begin
if (Phase = FTrackPhase) and (FFixedOuterLevel > 1.0e-9) then
Exit(SliceSymbol(Symbol, FFixedOuterLevel));
Exit(SliceSymbol(Symbol - FFixedCenter, FFixedOuterLevel));
A := Abs(Symbol);
if FOuterLevel[Phase] <= 1.0e-9 then FOuterLevel[Phase] := A
@@ -605,7 +616,7 @@ var
A, Threshold: Double;
begin
A := Abs(Symbol);
Threshold := (2.0 / 3.0) * OuterLevel;
Threshold := 0.645 * OuterLevel;
if Symbol >= 0 then
begin
if A >= Threshold then Result := 1 else Result := 0; // +3 / +1
@@ -625,6 +636,39 @@ begin
Result := Min(Result, FMagHistory[Phase, i]);
end;
procedure TDMRDecoder.CalibrateSyncLevels(Phase: Integer);
var
i, P, PositiveCount, NegativeCount: Integer;
V, PositiveSum, NegativeSum: Double;
begin
PositiveSum := 0;
NegativeSum := 0;
PositiveCount := 0;
NegativeCount := 0;
P := (FHistoryPos[Phase] + DMR_SYNC_END_DIBIT + 1 - 24) mod
(DMR_SYNC_END_DIBIT + 1);
for i := 0 to 23 do
begin
V := FSymbolHistory[Phase, P];
if V >= 0 then
begin
PositiveSum := PositiveSum + V;
Inc(PositiveCount);
end
else
begin
NegativeSum := NegativeSum - V;
Inc(NegativeCount);
end;
P := (P + 1) mod (DMR_SYNC_END_DIBIT + 1);
end;
if PositiveCount > 0 then FFixedPositiveOuter := PositiveSum / PositiveCount;
if NegativeCount > 0 then FFixedNegativeOuter := NegativeSum / NegativeCount;
FFixedCenter := 0.5 * (FFixedPositiveOuter - FFixedNegativeOuter);
FFixedOuterLevel := 0.5 * (FFixedPositiveOuter + FFixedNegativeOuter);
FOuterLevel[Phase] := FFixedOuterLevel;
end;
procedure TDMRDecoder.StartBurstTracking(Phase: Integer);
var
i, P: Integer;
@@ -636,7 +680,7 @@ begin
begin
// All 24 DMR sync dibits are known outer levels. Once they calibrate the
// eye, re-slice the already buffered pre-sync payload with the same level.
FCurrentBurst[i] := SliceSymbol(FSymbolHistory[Phase, P],
FCurrentBurst[i] := SliceSymbol(FSymbolHistory[Phase, P] - FFixedCenter,
FFixedOuterLevel);
P := (P + 1) mod (DMR_SYNC_END_DIBIT + 1);
end;
@@ -755,8 +799,7 @@ begin
FBestSyncQuality := SyncEyeOpening(Phase);
FSyncKind := Kind;
FInverted := Inverted;
FFixedOuterLevel := FMagSum[Phase] / 24.0;
FOuterLevel[Phase] := FFixedOuterLevel;
CalibrateSyncLevels(Phase);
StartBurstTracking(Phase);
end;
Exit;
@@ -767,8 +810,7 @@ begin
FLastSyncTick := NowTick;
FLastSyncSample := FSampleNo;
FBestSyncQuality := SyncEyeOpening(Phase);
FFixedOuterLevel := FMagSum[Phase] / 24.0;
FOuterLevel[Phase] := FFixedOuterLevel;
CalibrateSyncLevels(Phase);
if Kind in [dskMSData, dskMSVoice] then FMobileActiveBurst := True;
StartBurstTracking(Phase);
finally
+7 -5
View File
@@ -67,6 +67,8 @@ begin
end;
procedure TestBurst(D: TDMRDecoder);
const
DC_OFFSET = 0.12;
var
Want, Got: array[0..DMR_BURST_DIBITS - 1] of Byte;
L, R: array of Single;
@@ -84,17 +86,17 @@ begin
SetLength(R, Length(L));
// One complete preceding symbol mirrors a continuous stream and fills the
// 90-dibit history even for the latest timing hypothesis.
V := 0.70; // Want[0] = 1
V := 0.70 + DC_OFFSET; // Want[0] = 1
p := 3 + DMR_SAMPLES_PER_SYM;
for j := 0 to p - 1 do begin L[j] := V; R[j] := V; end;
for i := 0 to High(Want) do
begin
case Want[i] of
0: V := 0.23;
1: V := 0.70;
2: V := -0.23;
0: V := 0.23 + DC_OFFSET;
1: V := 0.70 + DC_OFFSET;
2: V := -0.23 + DC_OFFSET;
else
V := -0.70;
V := -0.70 + DC_OFFSET;
end;
for j := 0 to DMR_SAMPLES_PER_SYM - 1 do
begin