feat(dmr): decode independent receiver slices

This commit is contained in:
2026-07-14 20:08:49 +03:00
parent 9e2f8c7743
commit bac0b7612e
5 changed files with 208 additions and 17 deletions
+13 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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;
+5
View File
@@ -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.