program dmr_frontend_test; {$MODE Delphi} uses {$IFDEF UNIX}cthreads,{$ENDIF} Classes, SysUtils, DMRDecoder; const BS_VOICE_SYNC = '131111333113313313113313'; type TAudioSink = class public CallCount: Integer; SampleCount: Integer; procedure AudioReady(const PCM: array of Single; Count: Integer); end; procedure TAudioSink.AudioReady(const PCM: array of Single; Count: Integer); begin Inc(CallCount); Inc(SampleCount, Count); end; procedure FeedSync(D: TDMRDecoder; const Pattern: string; Invert: Boolean); var L, R: array of Single; i, j, p: Integer; V: Single; begin // Three leading samples ensure the detector really searches all ten timing // hypotheses instead of relying on an aligned first sample. SetLength(L, 3 + Length(Pattern) * DMR_SAMPLES_PER_SYM + 20); SetLength(R, Length(L)); p := 3; for i := 1 to Length(Pattern) do begin if Pattern[i] = '1' then V := 0.7 else V := -0.7; if Invert then V := -V; for j := 0 to DMR_SAMPLES_PER_SYM - 1 do begin L[p] := V; R[p] := V; Inc(p); end; end; D.FeedAudio(L, R, Length(L)); end; procedure TestBurst(D: TDMRDecoder); var Want, Got: array[0..DMR_BURST_DIBITS - 1] of Byte; L, R: array of Single; i, j, p: Integer; V: Single; Seq: QWord; Status: TDMRStatus; begin for i := 0 to High(Want) do Want[i] := Byte((i * 3 + 1) and 3); for i := 1 to Length(BS_VOICE_SYNC) do if BS_VOICE_SYNC[i] = '1' then Want[65 + i] := 1 else Want[65 + i] := 3; SetLength(L, 3 + (DMR_BURST_DIBITS + 2) * DMR_SAMPLES_PER_SYM); 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 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; else V := -0.70; end; for j := 0 to DMR_SAMPLES_PER_SYM - 1 do begin L[p] := V; R[p] := V; Inc(p); end; end; // Hold the last level for one symbol so every one of the ten timing // hypotheses has enough tail samples to close dibit 143. for j := 0 to DMR_SAMPLES_PER_SYM - 1 do begin L[p] := V; R[p] := V; Inc(p); end; D.FeedAudio(L, R, Length(L)); Sleep(80); if not D.GetLastBurst(Got, Seq) then begin D.GetStatus(Status); WriteLn('burst was not assembled; sync=', Status.SyncCount, ' bursts=', Status.BurstCount, ' pos=', Status.BurstDibits); Halt(3); end; for i := 0 to High(Want) do if Got[i] <> Want[i] then begin WriteLn('burst dibit mismatch at ', i, ': ', Got[i], ' <> ', Want[i]); Halt(4); end; end; var D: TDMRDecoder; S: TDMRStatus; Sink: TAudioSink; begin Sink := TAudioSink.Create; D := TDMRDecoder.Create; try D.OnAudio := Sink.AudioReady; D.SetEnabled(True); FeedSync(D, BS_VOICE_SYNC, False); Sleep(50); D.GetStatus(S); if (not S.Synced) or (S.SyncKind <> dskBSVoice) or S.Inverted then begin WriteLn('normal sync failed'); Halt(1); end; D.SetEnabled(False); D.SetInverted(True); D.SetEnabled(True); FeedSync(D, BS_VOICE_SYNC, True); Sleep(50); D.GetStatus(S); if (not S.Synced) or (S.SyncKind <> dskBSVoice) or (not S.Inverted) then begin WriteLn('inverted sync failed'); Halt(2); end; D.SetEnabled(False); D.SetInverted(False); D.SetEnabled(True); TestBurst(D); D.GetStatus(S); if S.VocoderAvailable and ((Sink.CallCount <> 3) or (Sink.SampleCount <> 3 * DMR_VOICE_FRAME_SAMPLES)) then begin WriteLn('AMBE callback failed: calls=', Sink.CallCount, ' samples=', Sink.SampleCount); Halt(5); end; WriteLn('DMR front-end sync tests passed'); finally D.Free; Sink.Free; end; end.