mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 20:37:33 +00:00
181 lines
4.6 KiB
ObjectPascal
181 lines
4.6 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);
|
|
const
|
|
DC_OFFSET = 0.12;
|
|
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 + 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 + DC_OFFSET;
|
|
1: V := 0.70 + DC_OFFSET;
|
|
2: V := -0.23 + DC_OFFSET;
|
|
else
|
|
V := -0.70 + DC_OFFSET;
|
|
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.
|