From 9e2f8c7743ac05cdb09fcfc26633ca8acda64e34 Mon Sep 17 00:00:00 2001 From: Vladimir Date: Tue, 14 Jul 2026 19:56:36 +0300 Subject: [PATCH] fix(dmr): choose timing phase by sync variance --- DMRDecoder.pas | 56 +++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 46 insertions(+), 10 deletions(-) diff --git a/DMRDecoder.pas b/DMRDecoder.pas index 7899426..3d8dc19 100644 --- a/DMRDecoder.pas +++ b/DMRDecoder.pas @@ -158,7 +158,7 @@ type procedure LoseSync; function Digitize(Phase: Integer; Symbol: Double): Byte; function SliceSymbol(Symbol, OuterLevel: Double): Byte; - function SyncEyeOpening(Phase: Integer): Double; + function SyncPhaseQuality(Phase: Integer): Double; procedure CalibrateSyncLevels(Phase: Integer); procedure StartBurstTracking(Phase: Integer); procedure CompleteBurst; @@ -627,13 +627,49 @@ begin end; end; -function TDMRDecoder.SyncEyeOpening(Phase: Integer): Double; +function TDMRDecoder.SyncPhaseQuality(Phase: Integer): Double; var - i: Integer; + i, P, PositiveCount, NegativeCount: Integer; + V, PositiveMean, NegativeMean, ErrorSum, Scale: Double; begin - Result := 1.0e300; + PositiveMean := 0; + NegativeMean := 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 - Result := Min(Result, FMagHistory[Phase, i]); + begin + V := FSymbolHistory[Phase, P]; + if V >= 0 then + begin + PositiveMean := PositiveMean + V; + Inc(PositiveCount); + end + else + begin + NegativeMean := NegativeMean - V; + Inc(NegativeCount); + end; + P := (P + 1) mod (DMR_SYNC_END_DIBIT + 1); + end; + if (PositiveCount = 0) or (NegativeCount = 0) then Exit(-1.0e300); + PositiveMean := PositiveMean / PositiveCount; + NegativeMean := NegativeMean / NegativeCount; + ErrorSum := 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 ErrorSum := ErrorSum + Sqr(V - PositiveMean) + else ErrorSum := ErrorSum + Sqr((-V) - NegativeMean); + P := (P + 1) mod (DMR_SYNC_END_DIBIT + 1); + end; + Scale := Sqr(0.5 * (PositiveMean + NegativeMean)); + if Scale <= 1.0e-18 then Exit(-1.0e300); + // Higher is better: zero denotes perfectly compact outer-level clusters. + Result := -ErrorSum / (24.0 * Scale); end; procedure TDMRDecoder.CalibrateSyncLevels(Phase: Integer); @@ -789,14 +825,14 @@ begin FStatusLock.Enter; try // Adjacent timing hypotheses recognize the same physical burst within one - // sample period. Keep the phase with the largest 24-symbol eye opening, - // not merely the first phase that happened to match the sign pattern. + // sample period. Keep the phase whose known positive/negative sync levels + // form the tightest clusters, not merely the first sign-pattern match. if (FLastSyncSample <> 0) and (FSampleNo - FLastSyncSample <= DMR_SAMPLES_PER_SYM * 2) then begin - if SyncEyeOpening(Phase) > FBestSyncQuality then + if SyncPhaseQuality(Phase) > FBestSyncQuality then begin - FBestSyncQuality := SyncEyeOpening(Phase); + FBestSyncQuality := SyncPhaseQuality(Phase); FSyncKind := Kind; FInverted := Inverted; CalibrateSyncLevels(Phase); @@ -809,7 +845,7 @@ begin Inc(FSyncCount); FLastSyncTick := NowTick; FLastSyncSample := FSampleNo; - FBestSyncQuality := SyncEyeOpening(Phase); + FBestSyncQuality := SyncPhaseQuality(Phase); CalibrateSyncLevels(Phase); if Kind in [dskMSData, dskMSVoice] then FMobileActiveBurst := True; StartBurstTracking(Phase);