Files
ewsdr/tests/dmr_frontend_test.pas
T

179 lines
4.5 KiB
ObjectPascal

program dmr_frontend_test;
{$MODE Delphi}
uses
{$IFDEF UNIX}cthreads,{$ENDIF}
Classes, SysUtils, DMRDecoder;
const
BS_VOICE_SYNC = '131111333113313313113313';
DMR_TEST_SUPERFRAME_SAMPLES = DMR_INPUT_RATE * 360 div 1000;
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 FeedConfirmedSync(D: TDMRDecoder; const Pattern: string;
Invert: Boolean);
var
L, R: array of Single;
GapSamples, RepeatIndex: Integer;
begin
GapSamples := DMR_TEST_SUPERFRAME_SAMPLES -
(3 + Length(Pattern) * DMR_SAMPLES_PER_SYM + 20);
SetLength(L, GapSamples);
SetLength(R, GapSamples);
for RepeatIndex := 1 to 3 do
begin
FeedSync(D, Pattern, Invert);
if RepeatIndex < 3 then D.FeedAudio(L, R, GapSamples);
end;
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);
FeedConfirmedSync(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);
FeedConfirmedSync(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);
FeedConfirmedSync(D, BS_VOICE_SYNC, False);
Sleep(50);
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.