mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 20:37:33 +00:00
feat(dmr): decode independent receiver slices
This commit is contained in:
+13
-1
@@ -50,6 +50,7 @@ var
|
|||||||
GMbeDestroy: TMbeDestroy = nil;
|
GMbeDestroy: TMbeDestroy = nil;
|
||||||
GMbeReset: TMbeReset = nil;
|
GMbeReset: TMbeReset = nil;
|
||||||
GMbeDecode: TMbeDecode = nil;
|
GMbeDecode: TMbeDecode = nil;
|
||||||
|
GLoadRefs: Integer = 0;
|
||||||
|
|
||||||
function Symbol(const Name: PChar): Pointer;
|
function Symbol(const Name: PChar): Pointer;
|
||||||
begin
|
begin
|
||||||
@@ -73,7 +74,11 @@ var
|
|||||||
i: Integer;
|
i: Integer;
|
||||||
LocalName, BuildName: string;
|
LocalName, BuildName: string;
|
||||||
begin
|
begin
|
||||||
if GLib <> dynlibs.NilHandle then Exit(True);
|
if GLib <> dynlibs.NilHandle then
|
||||||
|
begin
|
||||||
|
Inc(GLoadRefs);
|
||||||
|
Exit(True);
|
||||||
|
end;
|
||||||
GLastError := '';
|
GLastError := '';
|
||||||
for i := Low(Names) to High(Names) do
|
for i := Low(Names) to High(Names) do
|
||||||
begin
|
begin
|
||||||
@@ -115,11 +120,18 @@ begin
|
|||||||
DMRUnload;
|
DMRUnload;
|
||||||
Exit(False);
|
Exit(False);
|
||||||
end;
|
end;
|
||||||
|
GLoadRefs := 1;
|
||||||
Result := True;
|
Result := True;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure DMRUnload;
|
procedure DMRUnload;
|
||||||
begin
|
begin
|
||||||
|
if GLoadRefs > 1 then
|
||||||
|
begin
|
||||||
|
Dec(GLoadRefs);
|
||||||
|
Exit;
|
||||||
|
end;
|
||||||
|
GLoadRefs := 0;
|
||||||
GAbiVersion := nil;
|
GAbiVersion := nil;
|
||||||
GMbeCreate := nil;
|
GMbeCreate := nil;
|
||||||
GMbeDestroy := nil;
|
GMbeDestroy := nil;
|
||||||
|
|||||||
+7
-1
@@ -97,6 +97,7 @@ type
|
|||||||
FWake: PRTLEvent;
|
FWake: PRTLEvent;
|
||||||
FThread: TDMRDecoderThread;
|
FThread: TDMRDecoderThread;
|
||||||
FMbe: Pointer;
|
FMbe: Pointer;
|
||||||
|
FBindingsLoaded: Boolean;
|
||||||
FRing: array[0..DMR_RING_SIZE - 1] of Single;
|
FRing: array[0..DMR_RING_SIZE - 1] of Single;
|
||||||
FReadPos, FWritePos: Integer;
|
FReadPos, FWritePos: Integer;
|
||||||
FDropped: QWord;
|
FDropped: QWord;
|
||||||
@@ -272,7 +273,8 @@ begin
|
|||||||
FStatusLock := TCriticalSection.Create;
|
FStatusLock := TCriticalSection.Create;
|
||||||
FWake := RTLEventCreate;
|
FWake := RTLEventCreate;
|
||||||
FThread := TDMRDecoderThread.Create(Self);
|
FThread := TDMRDecoderThread.Create(Self);
|
||||||
if DMRLoad then FMbe := DMRMbeCreate(3);
|
FBindingsLoaded := DMRLoad;
|
||||||
|
if FBindingsLoaded then FMbe := DMRMbeCreate(3);
|
||||||
ResetDSP;
|
ResetDSP;
|
||||||
FThread.Start;
|
FThread.Start;
|
||||||
end;
|
end;
|
||||||
@@ -288,7 +290,11 @@ begin
|
|||||||
end;
|
end;
|
||||||
DMRMbeDestroy(FMbe);
|
DMRMbeDestroy(FMbe);
|
||||||
FMbe := nil;
|
FMbe := nil;
|
||||||
|
if FBindingsLoaded then
|
||||||
|
begin
|
||||||
DMRUnload;
|
DMRUnload;
|
||||||
|
FBindingsLoaded := False;
|
||||||
|
end;
|
||||||
RTLEventDestroy(FWake);
|
RTLEventDestroy(FWake);
|
||||||
FStatusLock.Free;
|
FStatusLock.Free;
|
||||||
FDSPLock.Free;
|
FDSPLock.Free;
|
||||||
|
|||||||
+131
-3
@@ -128,6 +128,22 @@ type
|
|||||||
PLLLock: Boolean;
|
PLLLock: Boolean;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
TRadioController = class;
|
||||||
|
|
||||||
|
// Event adapter: every DMR slice owns a decoder, while its asynchronous
|
||||||
|
// audio callback still has to retain the originating logical slice id.
|
||||||
|
TDMRSliceDecoder = class
|
||||||
|
private
|
||||||
|
FOwner: TRadioController;
|
||||||
|
FSliceId: Integer;
|
||||||
|
FDecoder: TDMRDecoder;
|
||||||
|
procedure AudioReady(const PCM: array of Single; Count: Integer);
|
||||||
|
public
|
||||||
|
constructor Create(AOwner: TRadioController; ASliceId: Integer);
|
||||||
|
destructor Destroy; override;
|
||||||
|
procedure FeedAudio(const Left, Right: array of Single; Count: Integer);
|
||||||
|
end;
|
||||||
|
|
||||||
// Слайс уровня контроллера: логическая обёртка над WDSP-слайс-каналом
|
// Слайс уровня контроллера: логическая обёртка над WDSP-слайс-каналом
|
||||||
// (в TWDSPEngine) + собственный аудио-выход со своей звуковухой.
|
// (в TWDSPEngine) + собственный аудио-выход со своей звуковухой.
|
||||||
TCtrlSlice = record
|
TCtrlSlice = record
|
||||||
@@ -147,6 +163,7 @@ type
|
|||||||
DeviceIndex: Integer; // PortAudio-индекс (-1 = default)
|
DeviceIndex: Integer; // PortAudio-индекс (-1 = default)
|
||||||
DevName: string;
|
DevName: string;
|
||||||
Audio: TAudioOutput; // владеет; свой поток вывода
|
Audio: TAudioOutput; // владеет; свой поток вывода
|
||||||
|
DMR: TDMRSliceDecoder; // владеет; nil для не-DMR слайса
|
||||||
end;
|
end;
|
||||||
|
|
||||||
{ TRadioController }
|
{ TRadioController }
|
||||||
@@ -166,6 +183,10 @@ type
|
|||||||
function FindSliceIndex(Id: Integer): Integer; // -1 если нет
|
function FindSliceIndex(Id: Integer): Integer; // -1 если нет
|
||||||
procedure OnSliceAudioReady(SliceId: Integer;
|
procedure OnSliceAudioReady(SliceId: Integer;
|
||||||
const Left, Right: array of Single; Count: Integer);
|
const Left, Right: array of Single; Count: Integer);
|
||||||
|
procedure OnSliceDemodAudioReady(SliceId: Integer;
|
||||||
|
const Left, Right: array of Single; Count: Integer);
|
||||||
|
procedure OnSliceDMRAudioReady(SliceId: Integer;
|
||||||
|
const PCM: array of Single; Count: Integer);
|
||||||
procedure RefreshSliceShifts; // пересчёт ShiftHz слайсов пана 0 при смене центра
|
procedure RefreshSliceShifts; // пересчёт ShiftHz слайсов пана 0 при смене центра
|
||||||
// --- Панадаптеры на аппаратных DDC (этап 3.1/3.2) ---
|
// --- Панадаптеры на аппаратных DDC (этап 3.1/3.2) ---
|
||||||
procedure ResetPanDDCMaps; // все пан-слоты свободны, DDC→Pan пуст
|
procedure ResetPanDDCMaps; // все пан-слоты свободны, DDC→Pan пуст
|
||||||
@@ -794,6 +815,43 @@ const
|
|||||||
// (TUNLevel), падение уровня IQ ~1.6дБ компенсируется калибровкой TUNLevel.
|
// (TUNLevel), падение уровня IQ ~1.6дБ компенсируется калибровкой TUNLevel.
|
||||||
TUN_TONE_MAG = 0.83;
|
TUN_TONE_MAG = 0.83;
|
||||||
|
|
||||||
|
constructor TDMRSliceDecoder.Create(AOwner: TRadioController;
|
||||||
|
ASliceId: Integer);
|
||||||
|
begin
|
||||||
|
inherited Create;
|
||||||
|
FOwner := AOwner;
|
||||||
|
FSliceId := ASliceId;
|
||||||
|
FDecoder := TDMRDecoder.Create;
|
||||||
|
FDecoder.SetInverted(True);
|
||||||
|
FDecoder.OnAudio := AudioReady;
|
||||||
|
FDecoder.SetEnabled(True);
|
||||||
|
end;
|
||||||
|
|
||||||
|
destructor TDMRSliceDecoder.Destroy;
|
||||||
|
begin
|
||||||
|
if Assigned(FDecoder) then
|
||||||
|
begin
|
||||||
|
FDecoder.SetEnabled(False);
|
||||||
|
FDecoder.OnAudio := nil;
|
||||||
|
FDecoder.OnBurst := nil;
|
||||||
|
FreeAndNil(FDecoder);
|
||||||
|
end;
|
||||||
|
FOwner := nil;
|
||||||
|
inherited Destroy;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TDMRSliceDecoder.FeedAudio(const Left, Right: array of Single;
|
||||||
|
Count: Integer);
|
||||||
|
begin
|
||||||
|
if Assigned(FDecoder) then FDecoder.FeedAudio(Left, Right, Count);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TDMRSliceDecoder.AudioReady(const PCM: array of Single;
|
||||||
|
Count: Integer);
|
||||||
|
begin
|
||||||
|
if Assigned(FOwner) then FOwner.OnSliceDMRAudioReady(FSliceId, PCM, Count);
|
||||||
|
end;
|
||||||
|
|
||||||
constructor TRadioController.Create;
|
constructor TRadioController.Create;
|
||||||
begin
|
begin
|
||||||
inherited Create;
|
inherited Create;
|
||||||
@@ -839,6 +897,7 @@ begin
|
|||||||
// Роутинг аудио слайсов на их звуковухи — внутренний, вешаем сразу (не
|
// Роутинг аудио слайсов на их звуковухи — внутренний, вешаем сразу (не
|
||||||
// зависит от фронтенда, в отличие от OnAudio/OnSpectrum).
|
// зависит от фронтенда, в отличие от OnAudio/OnSpectrum).
|
||||||
FDSPEngine.OnSliceAudio := OnSliceAudioReady;
|
FDSPEngine.OnSliceAudio := OnSliceAudioReady;
|
||||||
|
FDSPEngine.OnSliceDemodAudio := OnSliceDemodAudioReady;
|
||||||
FDSPEngine.OnDemodAudio := OnDemodAudioReady;
|
FDSPEngine.OnDemodAudio := OnDemodAudioReady;
|
||||||
FDSPEngine.SetDisplayResLimit(FDisplayPixels);
|
FDSPEngine.SetDisplayResLimit(FDisplayPixels);
|
||||||
|
|
||||||
@@ -876,6 +935,12 @@ end;
|
|||||||
procedure TRadioController.FreeEngines;
|
procedure TRadioController.FreeEngines;
|
||||||
var i: Integer;
|
var i: Integer;
|
||||||
begin
|
begin
|
||||||
|
// Stop new slice callbacks before their decoder/audio destinations vanish.
|
||||||
|
if Assigned(FDSPEngine) then
|
||||||
|
begin
|
||||||
|
FDSPEngine.OnSliceAudio := nil;
|
||||||
|
FDSPEngine.OnSliceDemodAudio := nil;
|
||||||
|
end;
|
||||||
// Stop the DMR worker before destroying any of its audio destinations.
|
// Stop the DMR worker before destroying any of its audio destinations.
|
||||||
if Assigned(FDMRDec) then
|
if Assigned(FDMRDec) then
|
||||||
begin
|
begin
|
||||||
@@ -890,12 +955,21 @@ begin
|
|||||||
if FNetwork.Connected then FNetwork.Disconnect;
|
if FNetwork.Connected then FNetwork.Disconnect;
|
||||||
FreeAndNil(FNetwork);
|
FreeAndNil(FNetwork);
|
||||||
end;
|
end;
|
||||||
// Слайсы: закрываем их аудио-выходы (WDSP-каналы закроет FDSPEngine.Close ниже).
|
// RemoveSlice takes the DSP slice lock. Besides closing the WDSP channel it
|
||||||
|
// waits out a callback that might already have entered before we detached it.
|
||||||
|
if Assigned(FDSPEngine) then
|
||||||
for i := 0 to MAX_SLICES - 1 do
|
for i := 0 to MAX_SLICES - 1 do
|
||||||
if FSlices[i].Active and (FSlices[i].Audio <> nil) then
|
if FSlices[i].Active then FDSPEngine.RemoveSlice(FSlices[i].Id);
|
||||||
|
// Слайсы: каналы уже закрыты, останавливаем декодеры и аудио.
|
||||||
|
for i := 0 to MAX_SLICES - 1 do
|
||||||
|
if FSlices[i].Active then
|
||||||
|
begin
|
||||||
|
FreeAndNil(FSlices[i].DMR);
|
||||||
|
if FSlices[i].Audio <> nil then
|
||||||
begin
|
begin
|
||||||
FSlices[i].Audio.Close;
|
FSlices[i].Audio.Close;
|
||||||
FreeAndNil(FSlices[i].Audio);
|
FreeAndNil(FSlices[i].Audio);
|
||||||
|
end;
|
||||||
FSlices[i].Active := False;
|
FSlices[i].Active := False;
|
||||||
end;
|
end;
|
||||||
if Assigned(FAudioOut) then begin FAudioOut.Close; FreeAndNil(FAudioOut); end;
|
if Assigned(FAudioOut) then begin FAudioOut.Close; FreeAndNil(FAudioOut); end;
|
||||||
@@ -1274,10 +1348,55 @@ begin
|
|||||||
if not FLocalAudio then Exit;
|
if not FLocalAudio then Exit;
|
||||||
idx := FindSliceIndex(SliceId);
|
idx := FindSliceIndex(SliceId);
|
||||||
if idx < 0 then Exit;
|
if idx < 0 then Exit;
|
||||||
|
if FSlices[idx].Mode = MODE_DMR then Exit;
|
||||||
if FSlices[idx].Audio <> nil then
|
if FSlices[idx].Audio <> nil then
|
||||||
FSlices[idx].Audio.Write(Left, Right, Count);
|
FSlices[idx].Audio.Write(Left, Right, Count);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
// DSP thread: flat, volume-independent discriminator of the corresponding
|
||||||
|
// WDSP slice channel. FeedAudio only copies it into that decoder's ring.
|
||||||
|
procedure TRadioController.OnSliceDemodAudioReady(SliceId: Integer;
|
||||||
|
const Left, Right: array of Single; Count: Integer);
|
||||||
|
var idx: Integer;
|
||||||
|
begin
|
||||||
|
idx := FindSliceIndex(SliceId);
|
||||||
|
if (idx < 0) or (FSlices[idx].Mode <> MODE_DMR) then Exit;
|
||||||
|
if Assigned(FSlices[idx].DMR) then
|
||||||
|
FSlices[idx].DMR.FeedAudio(Left, Right, Count);
|
||||||
|
end;
|
||||||
|
|
||||||
|
// DMR worker thread: decoded 8 kHz mono -> this slice's 48 kHz local output.
|
||||||
|
procedure TRadioController.OnSliceDMRAudioReady(SliceId: Integer;
|
||||||
|
const PCM: array of Single; Count: Integer);
|
||||||
|
var
|
||||||
|
Left, Right: array of Single;
|
||||||
|
idx, i, Phase, OutPos, N: Integer;
|
||||||
|
A, B, V, Gain: Single;
|
||||||
|
begin
|
||||||
|
if not FLocalAudio or (Count <= 0) then Exit;
|
||||||
|
idx := FindSliceIndex(SliceId);
|
||||||
|
if (idx < 0) or (FSlices[idx].Mode <> MODE_DMR) or
|
||||||
|
FSlices[idx].Muted or not Assigned(FSlices[idx].Audio) then Exit;
|
||||||
|
N := Min(Count, Length(PCM));
|
||||||
|
SetLength(Left, N * 6);
|
||||||
|
SetLength(Right, N * 6);
|
||||||
|
Gain := FSlices[idx].Volume;
|
||||||
|
OutPos := 0;
|
||||||
|
for i := 0 to N - 1 do
|
||||||
|
begin
|
||||||
|
A := PCM[i];
|
||||||
|
if i + 1 < N then B := PCM[i + 1] else B := A;
|
||||||
|
for Phase := 0 to 5 do
|
||||||
|
begin
|
||||||
|
V := (A + (B - A) * (Phase / 6.0)) * Gain;
|
||||||
|
Left[OutPos] := V;
|
||||||
|
Right[OutPos] := V;
|
||||||
|
Inc(OutPos);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
FSlices[idx].Audio.Write(Left, Right, OutPos);
|
||||||
|
end;
|
||||||
|
|
||||||
procedure TRadioController.RefreshSliceShifts;
|
procedure TRadioController.RefreshSliceShifts;
|
||||||
// Смена центра ГЛАВНОГО захвата: пересчитываем только слайсы пана 0 —
|
// Смена центра ГЛАВНОГО захвата: пересчитываем только слайсы пана 0 —
|
||||||
// слайсы доп. панов привязаны к центрам своих DDC (SetPanDDCFreq).
|
// слайсы доп. панов привязаны к центрам своих DDC (SetPanDDCFreq).
|
||||||
@@ -1684,6 +1803,9 @@ begin
|
|||||||
FSlices[slot].DeviceIndex := DevIndex;
|
FSlices[slot].DeviceIndex := DevIndex;
|
||||||
FSlices[slot].DevName := DevName;
|
FSlices[slot].DevName := DevName;
|
||||||
FSlices[slot].Audio := AO;
|
FSlices[slot].Audio := AO;
|
||||||
|
FSlices[slot].DMR := nil;
|
||||||
|
if Mode = MODE_DMR then
|
||||||
|
FSlices[slot].DMR := TDMRSliceDecoder.Create(Self, Id);
|
||||||
|
|
||||||
Result := Id;
|
Result := Id;
|
||||||
Changed(rfDevice);
|
Changed(rfDevice);
|
||||||
@@ -1697,6 +1819,7 @@ begin
|
|||||||
// Удаляемый слайс был источником TX — вернуть передачу на главный VFO.
|
// Удаляемый слайс был источником TX — вернуть передачу на главный VFO.
|
||||||
if FTxSliceId = Id then SetTxSlice(0);
|
if FTxSliceId = Id then SetTxSlice(0);
|
||||||
if Assigned(FDSPEngine) then FDSPEngine.RemoveSlice(Id);
|
if Assigned(FDSPEngine) then FDSPEngine.RemoveSlice(Id);
|
||||||
|
FreeAndNil(FSlices[idx].DMR);
|
||||||
if FSlices[idx].Audio <> nil then
|
if FSlices[idx].Audio <> nil then
|
||||||
begin
|
begin
|
||||||
FSlices[idx].Audio.Close;
|
FSlices[idx].Audio.Close;
|
||||||
@@ -1717,12 +1840,17 @@ begin
|
|||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TRadioController.SetSliceMode(Id, Mode: Integer);
|
procedure TRadioController.SetSliceMode(Id, Mode: Integer);
|
||||||
var idx: Integer;
|
var idx, OldMode: Integer;
|
||||||
begin
|
begin
|
||||||
idx := FindSliceIndex(Id);
|
idx := FindSliceIndex(Id);
|
||||||
if idx < 0 then Exit;
|
if idx < 0 then Exit;
|
||||||
|
OldMode := FSlices[idx].Mode;
|
||||||
|
if (Mode = MODE_DMR) and not Assigned(FSlices[idx].DMR) then
|
||||||
|
FSlices[idx].DMR := TDMRSliceDecoder.Create(Self, Id);
|
||||||
FSlices[idx].Mode := Mode;
|
FSlices[idx].Mode := Mode;
|
||||||
if Assigned(FDSPEngine) then FDSPEngine.SetSliceMode(Id, Mode);
|
if Assigned(FDSPEngine) then FDSPEngine.SetSliceMode(Id, Mode);
|
||||||
|
if (OldMode = MODE_DMR) and (Mode <> MODE_DMR) then
|
||||||
|
FreeAndNil(FSlices[idx].DMR);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TRadioController.SetSliceFilter(Id, Low, High: Integer);
|
procedure TRadioController.SetSliceFilter(Id, Low, High: Integer);
|
||||||
|
|||||||
+46
-6
@@ -337,6 +337,7 @@ type
|
|||||||
FSliceOutL: array of Single; // деинтерливленный L для callback
|
FSliceOutL: array of Single; // деинтерливленный L для callback
|
||||||
FSliceOutR: array of Single; // R
|
FSliceOutR: array of Single; // R
|
||||||
FOnSliceAudio: TOnSliceAudio;
|
FOnSliceAudio: TOnSliceAudio;
|
||||||
|
FOnSliceDemodAudio: TOnSliceAudio; // pre-volume/mute tap for digital slices
|
||||||
|
|
||||||
// --- Панадаптеры на аппаратных DDC (этап 3.2) ---
|
// --- Панадаптеры на аппаратных DDC (этап 3.2) ---
|
||||||
FPans: array[1..MAX_PANS-1] of TPanChan;
|
FPans: array[1..MAX_PANS-1] of TPanChan;
|
||||||
@@ -452,6 +453,7 @@ type
|
|||||||
function EffectiveTXNC: Integer;
|
function EffectiveTXNC: Integer;
|
||||||
procedure ApplyDSPRate(Mode: Integer);
|
procedure ApplyDSPRate(Mode: Integer);
|
||||||
procedure ApplyRXWFMToChan(Chan: Integer);
|
procedure ApplyRXWFMToChan(Chan: Integer);
|
||||||
|
procedure ApplyRXDMRToChan(Chan: Integer);
|
||||||
procedure ApplyWFMSettings;
|
procedure ApplyWFMSettings;
|
||||||
// --- Мультислайсы ---
|
// --- Мультислайсы ---
|
||||||
function FindSliceIdx(Id: Integer): Integer; // -1 если нет
|
function FindSliceIdx(Id: Integer): Integer; // -1 если нет
|
||||||
@@ -687,6 +689,8 @@ type
|
|||||||
property OnAudio: TOnAudioReady read FOnAudio write FOnAudio;
|
property OnAudio: TOnAudioReady read FOnAudio write FOnAudio;
|
||||||
property OnDemodAudio: TOnAudioReady read FOnDemodAudio write FOnDemodAudio;
|
property OnDemodAudio: TOnAudioReady read FOnDemodAudio write FOnDemodAudio;
|
||||||
property OnSliceAudio: TOnSliceAudio read FOnSliceAudio write FOnSliceAudio;
|
property OnSliceAudio: TOnSliceAudio read FOnSliceAudio write FOnSliceAudio;
|
||||||
|
property OnSliceDemodAudio: TOnSliceAudio
|
||||||
|
read FOnSliceDemodAudio write FOnSliceDemodAudio;
|
||||||
property OnSpectrum: TOnSpectrumReady read FOnSpectrum write FOnSpectrum;
|
property OnSpectrum: TOnSpectrumReady read FOnSpectrum write FOnSpectrum;
|
||||||
property OnWaterfall: TOnWaterfallReady read FOnWaterfall write FOnWaterfall;
|
property OnWaterfall: TOnWaterfallReady read FOnWaterfall write FOnWaterfall;
|
||||||
// Пиксели доп. панов (display-поток). UI вешает при появлении панов (3.3).
|
// Пиксели доп. панов (display-поток). UI вешает при появлении панов (3.3).
|
||||||
@@ -1061,6 +1065,14 @@ begin
|
|||||||
SetRXAWFMDeemphTau (Chan, WFM_TAU_DE_US);
|
SetRXAWFMDeemphTau (Chan, WFM_TAU_DE_US);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
procedure TWDSPEngine.ApplyRXDMRToChan(Chan: Integer);
|
||||||
|
begin
|
||||||
|
// DMR needs the flat 4FSK discriminator, not broadcast-FM deemphasis.
|
||||||
|
SetRXAWFMDeviation (Chan, DMR_DEVIATION);
|
||||||
|
SetRXAWFMAFFilter (Chan, 20.0, 7000.0);
|
||||||
|
SetRXAWFMDeemphRun (Chan, 0);
|
||||||
|
end;
|
||||||
|
|
||||||
procedure TWDSPEngine.ApplyWFMSettings;
|
procedure TWDSPEngine.ApplyWFMSettings;
|
||||||
begin
|
begin
|
||||||
ApplyRXWFMToChan(RXA_CHAN);
|
ApplyRXWFMToChan(RXA_CHAN);
|
||||||
@@ -1177,6 +1189,7 @@ begin
|
|||||||
FSliceLock := TCriticalSection.Create;
|
FSliceLock := TCriticalSection.Create;
|
||||||
FillChar(FSlices, SizeOf(FSlices), 0);
|
FillChar(FSlices, SizeOf(FSlices), 0);
|
||||||
FOnSliceAudio := nil;
|
FOnSliceAudio := nil;
|
||||||
|
FOnSliceDemodAudio := nil;
|
||||||
AllocSliceBuffers;
|
AllocSliceBuffers;
|
||||||
|
|
||||||
// TX инициализация
|
// TX инициализация
|
||||||
@@ -1945,7 +1958,7 @@ begin
|
|||||||
if Pan^.AccPos >= Pan^.BufSize then
|
if Pan^.AccPos >= Pan^.BufSize then
|
||||||
begin
|
begin
|
||||||
Pan^.AccPos := 0;
|
Pan^.AccPos := 0;
|
||||||
if Assigned(FOnSliceAudio) then
|
if Assigned(FOnSliceAudio) or Assigned(FOnSliceDemodAudio) then
|
||||||
try
|
try
|
||||||
ProcessSlicesFor(PanId, Pan^.AccI, Pan^.AccQ, Pan^.BufSize);
|
ProcessSlicesFor(PanId, Pan^.AccI, Pan^.AccQ, Pan^.BufSize);
|
||||||
except
|
except
|
||||||
@@ -2232,6 +2245,7 @@ begin
|
|||||||
SetRXABandpassWindow(Chan, 1);
|
SetRXABandpassWindow(Chan, 1);
|
||||||
SetRXAMode(Chan, ModeToWDSP(FSlices[Idx].Mode));
|
SetRXAMode(Chan, ModeToWDSP(FSlices[Idx].Mode));
|
||||||
if FSlices[Idx].Mode = MODE_WFM then ApplyRXWFMToChan(Chan);
|
if FSlices[Idx].Mode = MODE_WFM then ApplyRXWFMToChan(Chan);
|
||||||
|
if FSlices[Idx].Mode = MODE_DMR then ApplyRXDMRToChan(Chan);
|
||||||
SetRXAShiftRun(Chan, 1);
|
SetRXAShiftRun(Chan, 1);
|
||||||
SetRXAShiftFreq(Chan, FSlices[Idx].ShiftHz);
|
SetRXAShiftFreq(Chan, FSlices[Idx].ShiftHz);
|
||||||
RXASetPassband(Chan, ClampToNyquist(FSlices[Idx].FilterLo, DSPRate),
|
RXASetPassband(Chan, ClampToNyquist(FSlices[Idx].FilterLo, DSPRate),
|
||||||
@@ -2243,6 +2257,10 @@ begin
|
|||||||
ApplySNBToChan(Chan, FSlices[Idx].SNBOn);
|
ApplySNBToChan(Chan, FSlices[Idx].SNBOn);
|
||||||
ApplyANFToChan(Chan, FSlices[Idx].ANFOn);
|
ApplyANFToChan(Chan, FSlices[Idx].ANFOn);
|
||||||
ApplyFMSQToChan(Chan, FSlices[Idx].FMSQOn, FSlices[Idx].FMSQLevel);
|
ApplyFMSQToChan(Chan, FSlices[Idx].FMSQOn, FSlices[Idx].FMSQLevel);
|
||||||
|
// Decoder input must not disappear or change scale with speaker volume.
|
||||||
|
if FSlices[Idx].Mode = MODE_DMR then
|
||||||
|
SetRXAPanelGain1(Chan, 1.0)
|
||||||
|
else
|
||||||
SetRXAPanelGain1(Chan, FSlices[Idx].Volume);
|
SetRXAPanelGain1(Chan, FSlices[Idx].Volume);
|
||||||
SetRXAPanelSelect(Chan, 3);
|
SetRXAPanelSelect(Chan, 3);
|
||||||
SetRXAPanelRun(Chan, 1);
|
SetRXAPanelRun(Chan, 1);
|
||||||
@@ -2268,7 +2286,7 @@ end;
|
|||||||
// после основного канала.
|
// после основного канала.
|
||||||
procedure TWDSPEngine.ProcessSlices;
|
procedure TWDSPEngine.ProcessSlices;
|
||||||
begin
|
begin
|
||||||
if not Assigned(FOnSliceAudio) then Exit;
|
if not Assigned(FOnSliceAudio) and not Assigned(FOnSliceDemodAudio) then Exit;
|
||||||
FSliceLock.Enter;
|
FSliceLock.Enter;
|
||||||
try
|
try
|
||||||
ProcessSlicesFor(0, FRXAccI, FRXAccQ, FBufSize);
|
ProcessSlicesFor(0, FRXAccI, FRXAccQ, FBufSize);
|
||||||
@@ -2289,7 +2307,8 @@ begin
|
|||||||
begin
|
begin
|
||||||
if not (FSlices[s].Active and FSlices[s].Opened) then Continue;
|
if not (FSlices[s].Active and FSlices[s].Opened) then Continue;
|
||||||
if FSlices[s].PanId <> PanId then Continue;
|
if FSlices[s].PanId <> PanId then Continue;
|
||||||
if FSlices[s].Muted then Continue;
|
// Muting a DMR speaker must not make its decoder lose synchronization.
|
||||||
|
if FSlices[s].Muted and (FSlices[s].Mode <> MODE_DMR) then Continue;
|
||||||
// Свежая копия входного IQ — fexchange0 портит вход in-place.
|
// Свежая копия входного IQ — fexchange0 портит вход in-place.
|
||||||
for i := 0 to N - 1 do
|
for i := 0 to N - 1 do
|
||||||
begin
|
begin
|
||||||
@@ -2300,14 +2319,29 @@ begin
|
|||||||
fexchange0(FSlices[s].Chan, @FSliceIn[0], @FSliceOut[0], @Err);
|
fexchange0(FSlices[s].Chan, @FSliceIn[0], @FSliceOut[0], @Err);
|
||||||
// S-метр канала читаем здесь (DSP-поток) — GetRXAMeter сбрасывает аккумулятор.
|
// S-метр канала читаем здесь (DSP-поток) — GetRXAMeter сбрасывает аккумулятор.
|
||||||
FSlices[s].SMeter := GetRXAMeter(FSlices[s].Chan, RXA_S_AV);
|
FSlices[s].SMeter := GetRXAMeter(FSlices[s].Chan, RXA_S_AV);
|
||||||
|
for k := 0 to FAudioBufSize - 1 do
|
||||||
|
begin
|
||||||
|
FSliceOutL[k] := FSliceOut[k * 2];
|
||||||
|
FSliceOutR[k] := FSliceOut[k * 2 + 1];
|
||||||
|
end;
|
||||||
|
if FSlices[s].Mode = MODE_DMR then
|
||||||
|
begin
|
||||||
|
if Assigned(FOnSliceDemodAudio) then
|
||||||
|
FOnSliceDemodAudio(FSlices[s].Id, FSliceOutL, FSliceOutR,
|
||||||
|
FAudioBufSize);
|
||||||
|
Continue; // never route the raw 4FSK discriminator to the speaker
|
||||||
|
end;
|
||||||
|
if Assigned(FOnSliceAudio) then
|
||||||
|
begin
|
||||||
V := FSlices[s].Volume;
|
V := FSlices[s].Volume;
|
||||||
for k := 0 to FAudioBufSize - 1 do
|
for k := 0 to FAudioBufSize - 1 do
|
||||||
begin
|
begin
|
||||||
FSliceOutL[k] := FSliceOut[k * 2] * V;
|
FSliceOutL[k] := FSliceOutL[k] * V;
|
||||||
FSliceOutR[k] := FSliceOut[k * 2 + 1] * V;
|
FSliceOutR[k] := FSliceOutR[k] * V;
|
||||||
end;
|
end;
|
||||||
FOnSliceAudio(FSlices[s].Id, FSliceOutL, FSliceOutR, FAudioBufSize);
|
FOnSliceAudio(FSlices[s].Id, FSliceOutL, FSliceOutR, FAudioBufSize);
|
||||||
end;
|
end;
|
||||||
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -2515,6 +2549,11 @@ begin
|
|||||||
begin
|
begin
|
||||||
SetRXAMode(FSlices[idx].Chan, ModeToWDSP(Mode));
|
SetRXAMode(FSlices[idx].Chan, ModeToWDSP(Mode));
|
||||||
if Mode = MODE_WFM then ApplyRXWFMToChan(FSlices[idx].Chan);
|
if Mode = MODE_WFM then ApplyRXWFMToChan(FSlices[idx].Chan);
|
||||||
|
if Mode = MODE_DMR then ApplyRXDMRToChan(FSlices[idx].Chan);
|
||||||
|
if Mode = MODE_DMR then
|
||||||
|
SetRXAPanelGain1(FSlices[idx].Chan, 1.0)
|
||||||
|
else
|
||||||
|
SetRXAPanelGain1(FSlices[idx].Chan, FSlices[idx].Volume);
|
||||||
end;
|
end;
|
||||||
finally
|
finally
|
||||||
FSliceLock.Leave;
|
FSliceLock.Leave;
|
||||||
@@ -2560,7 +2599,8 @@ begin
|
|||||||
try
|
try
|
||||||
idx := FindSliceIdx(Id); if idx < 0 then Exit;
|
idx := FindSliceIdx(Id); if idx < 0 then Exit;
|
||||||
FSlices[idx].Volume := Vol;
|
FSlices[idx].Volume := Vol;
|
||||||
if FSlices[idx].Opened then SetRXAPanelGain1(FSlices[idx].Chan, Vol);
|
if FSlices[idx].Opened and (FSlices[idx].Mode <> MODE_DMR) then
|
||||||
|
SetRXAPanelGain1(FSlices[idx].Chan, Vol);
|
||||||
finally
|
finally
|
||||||
FSliceLock.Leave;
|
FSliceLock.Leave;
|
||||||
end;
|
end;
|
||||||
|
|||||||
@@ -16,6 +16,10 @@ begin
|
|||||||
WriteLn(DMRLastError);
|
WriteLn(DMRLastError);
|
||||||
Halt(1);
|
Halt(1);
|
||||||
end;
|
end;
|
||||||
|
// Several simultaneous slice decoders share one dynamically loaded adapter.
|
||||||
|
if not DMRLoad then Halt(4);
|
||||||
|
DMRUnload;
|
||||||
|
if not DMRLoaded then Halt(5);
|
||||||
Decoder := DMRMbeCreate(3);
|
Decoder := DMRMbeCreate(3);
|
||||||
if Decoder = nil then Halt(2);
|
if Decoder = nil then Halt(2);
|
||||||
try
|
try
|
||||||
@@ -27,5 +31,6 @@ begin
|
|||||||
DMRMbeDestroy(Decoder);
|
DMRMbeDestroy(Decoder);
|
||||||
DMRUnload;
|
DMRUnload;
|
||||||
end;
|
end;
|
||||||
|
if DMRLoaded then Halt(6);
|
||||||
WriteLn('DMR native bindings tests passed');
|
WriteLn('DMR native bindings tests passed');
|
||||||
end.
|
end.
|
||||||
|
|||||||
Reference in New Issue
Block a user