diff --git a/DMRDecoder.pas b/DMRDecoder.pas index b73db2d..7899426 100644 --- a/DMRDecoder.pas +++ b/DMRDecoder.pas @@ -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 diff --git a/tests/dmr_frontend_test.pas b/tests/dmr_frontend_test.pas index 29eacf5..a43f1e3 100644 --- a/tests/dmr_frontend_test.pas +++ b/tests/dmr_frontend_test.pas @@ -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