mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 17:27:32 +00:00
feat(slices): multi-slice receivers (FlexRadio-style) — soft fan-out + per-slice flag/audio
Independent RX slices sharing one panadapter: each has its own freq/mode/filter/AGC/NR-NB-SNB-ANF, S-meter, volume and sound card. VFO A = main receiver; B+ are soft fan-out WDSP channels within the captured span (ideal for Pluto/QO-100; HW DDC path is Stage 3). - WDSPEngine: soft fan-out (TSliceChan, ProcessSlices in DSP thread, per-slice NCO/mode/filter/AGC/NR/NB/SNB/ANF + S-meter, OnSliceAudio), FSliceLock serialises add/remove/set vs DSP; reopened on rate change. - RadioController: TCtrlSlice with its own TAudioOutput/device, Add/Remove/SetSlice*, RefreshSliceShifts keeps slices on target Hz across centre changes. Slice letters start at 'B' (A = main receiver). - UI: per-slice flags (VfoOverlay) with letter badge/close, filter-band markers in BOTH GL and CPU renderers, drag-retune, wheel; flags placed beside the filter band with flip, like the main flag. Ctrl+click adds. - Main flag (slice A) visible whenever the left panel is hidden OR any slice exists; synced with panel-driven DSP/mode/AGC changes. - Perf: S-meter decoupled from flag chrome cache (FMeterDirty + meter-only RefreshMeter via EnsureRendered); InvalidateVfoOverlay avoids global overlay-cache churn on meter ticks. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+517
-81
@@ -96,6 +96,10 @@ const
|
||||
RXA_CHAN = 0;
|
||||
TXA_CHAN = 1; // используем разные каналы для RX и TX
|
||||
|
||||
// --- Мультислайсы (софт-fan-out) ---
|
||||
MAX_SLICES = 6; // потолок одновременных софтовых слайсов
|
||||
SLICE_CHAN_BASE = 2; // WDSP-каналы слайсов: 0=RXA, 1=TXA, слайсы с 2+
|
||||
|
||||
RX_DISP_ID = 0; // analyzer для RX-спектра/водопада
|
||||
TX_DISP_ID = 1; // analyzer для TX-спектра/водопада
|
||||
DSP_BUFSIZE = 1024;
|
||||
@@ -135,6 +139,10 @@ type
|
||||
TOnTXIQReady = procedure(const Buf: array of Double; Count: Integer) of object;
|
||||
TOnWaterfallReady = procedure(const Pixels: array of Single;
|
||||
Count: Integer) of object;
|
||||
// Аудио готового слайса — вызывается из DSP-потока по каждому активному слайсу.
|
||||
// SliceId — логический id (из RadioController), не WDSP-канал.
|
||||
TOnSliceAudio = procedure(SliceId: Integer; const Left, Right: array of Single;
|
||||
Count: Integer) of object;
|
||||
// Sound-card mic source: TTXDSPThread дёргает этот колбэк перед каждым тиком,
|
||||
// чтобы вытянуть накопленные сэмплы из внешнего буфера (например PortAudio).
|
||||
// Реализация должна вернуть кол-во отправленных в WDSP сэмплов.
|
||||
@@ -143,6 +151,28 @@ type
|
||||
// TX mic source: откуда получаем сэмплы для модулятора.
|
||||
TTXMicSource = (txmsRadio, txmsSoundCard, txmsWeb);
|
||||
|
||||
// Софтовый слайс: отдельный WDSP-канал, кормится тем же IQ-блоком, что и
|
||||
// RXA_CHAN, но со своим NCO-сдвигом/режимом/фильтром/AGC/громкостью и своим
|
||||
// аудио-callback'ом. Геометрия канала = как у RXA_CHAN.
|
||||
TSliceChan = record
|
||||
Active: Boolean; // слот занят
|
||||
Id: Integer; // логический id слайса
|
||||
Chan: Integer; // WDSP-канал (SLICE_CHAN_BASE + слот)
|
||||
ShiftHz: Double; // NCO-сдвиг = TargetHz - CapturedCenterHz
|
||||
Mode: Integer; // индекс режима (MODE_*)
|
||||
FilterLo: Integer;
|
||||
FilterHi: Integer;
|
||||
AGCMode: TWDSPAGCMode;
|
||||
Volume: Double; // 0..1
|
||||
Muted: Boolean;
|
||||
NRMode: Integer; // 0=off,1..4
|
||||
NBMode: Integer; // 0=off,1=NB,2=NB2
|
||||
SNBOn: Boolean;
|
||||
ANFOn: Boolean;
|
||||
SMeter: Double; // последнее показание S-метра канала (dBm), пишет DSP-поток
|
||||
Opened: Boolean; // WDSP-канал реально открыт (после Open движка)
|
||||
end;
|
||||
|
||||
{ TWDSPEngine }
|
||||
TWDSPEngine = class
|
||||
private
|
||||
@@ -219,6 +249,15 @@ type
|
||||
FOnWaterfall: TOnWaterfallReady;
|
||||
FOnTXIQ: TOnTXIQReady;
|
||||
|
||||
// --- Мультислайсы ---
|
||||
FSlices: array[0..MAX_SLICES-1] of TSliceChan;
|
||||
FSliceLock: TCriticalSection; // сериализует add/remove/set vs DSP-обработку
|
||||
FSliceIn: array of Double; // interleaved вход fexchange0 (переиспользуем)
|
||||
FSliceOut: array of Double; // interleaved выход fexchange0
|
||||
FSliceOutL: array of Single; // деинтерливленный L для callback
|
||||
FSliceOutR: array of Single; // R
|
||||
FOnSliceAudio: TOnSliceAudio;
|
||||
|
||||
// TX mic ring buffer (lock-free: один writer — receive thread,
|
||||
// один reader — TX DSP thread)
|
||||
FTXMicRing: array[0..TX_MIC_RING-1] of Double;
|
||||
@@ -311,6 +350,13 @@ type
|
||||
|
||||
function ModeToWDSP(Mode: Integer): Integer;
|
||||
procedure ApplyDefaultFilter;
|
||||
// --- Мультислайсы ---
|
||||
function FindSliceIdx(Id: Integer): Integer; // -1 если нет
|
||||
procedure OpenSliceChannel(Idx: Integer); // OpenChannel + настройки
|
||||
procedure CloseSliceChannel(Idx: Integer); // CloseChannel (запись хранит)
|
||||
procedure ApplyAGCToChan(Chan: Integer; Mode: TWDSPAGCMode; FixedGain: Double);
|
||||
procedure ProcessSlices; // fan-out в ProcessRXBlock
|
||||
procedure AllocSliceBuffers; // (пере)аллокация буферов
|
||||
procedure FeedDisplaySample(const I, Q: Double);
|
||||
procedure ProcessRXBlock;
|
||||
procedure PushIQItemToDSP(const Item: TIQQueueItem);
|
||||
@@ -329,6 +375,10 @@ type
|
||||
procedure ApplyNBState;
|
||||
procedure ApplySNBState;
|
||||
procedure ApplyANFState;
|
||||
procedure ApplyNRToChan(Chan, NRMode: Integer);
|
||||
procedure ApplyNBToChan(Chan, NBMode: Integer);
|
||||
procedure ApplySNBToChan(Chan: Integer; On_: Boolean);
|
||||
procedure ApplyANFToChan(Chan: Integer; On_: Boolean);
|
||||
procedure ApplyNoiseFilterState;
|
||||
procedure ProcessTXBlock; // вызывается из TTXDSPThread
|
||||
procedure FeedTXDisplaySample(const I, Q: Double);
|
||||
@@ -350,6 +400,26 @@ type
|
||||
// QO-100 beacon-декодер: тап RX-IQ для демодуляции маяка (не владеет).
|
||||
procedure SetBeaconDecoder(D: TBeaconDecoder);
|
||||
|
||||
// --- Мультислайсы (софт-fan-out) ---
|
||||
// Добавляет слайс с логическим Id (уникальным). ShiftHz = TargetHz - центр
|
||||
// захвата. Возвращает False если нет свободных слотов или Id уже есть.
|
||||
function AddSlice(Id, Mode, FilterLo, FilterHi: Integer;
|
||||
ShiftHz, Volume: Double; AGC: TWDSPAGCMode): Boolean;
|
||||
procedure RemoveSlice(Id: Integer);
|
||||
procedure SetSliceShift(Id: Integer; ShiftHz: Double);
|
||||
procedure SetSliceMode(Id, Mode: Integer);
|
||||
procedure SetSliceFilter(Id, Low, High: Integer);
|
||||
procedure SetSliceAGC(Id: Integer; Mode: TWDSPAGCMode);
|
||||
procedure SetSliceVolume(Id: Integer; Vol: Double);
|
||||
procedure SetSliceMute(Id: Integer; Mute: Boolean);
|
||||
procedure SetSliceNRMode(Id, Mode: Integer);
|
||||
procedure SetSliceNBMode(Id, Mode: Integer);
|
||||
procedure SetSliceSNB(Id: Integer; On_: Boolean);
|
||||
procedure SetSliceANF(Id: Integer; On_: Boolean);
|
||||
function GetSliceSMeter(Id: Integer): Double;
|
||||
function SliceCount: Integer;
|
||||
function HasSlice(Id: Integer): Boolean;
|
||||
|
||||
// Подача 24-bit big-endian IQ из DDC пакета
|
||||
// Buf — массив байт, DataOffset — смещение до IQ данных внутри Buf
|
||||
procedure PushDDCPacket(const Buf: array of Byte;
|
||||
@@ -475,6 +545,7 @@ type
|
||||
property WfAvgMode: Integer read FWfAvgMode;
|
||||
property WfAvgTimeMS: Double read FWfAvgTimeMS;
|
||||
property OnAudio: TOnAudioReady read FOnAudio write FOnAudio;
|
||||
property OnSliceAudio: TOnSliceAudio read FOnSliceAudio write FOnSliceAudio;
|
||||
property OnSpectrum: TOnSpectrumReady read FOnSpectrum write FOnSpectrum;
|
||||
property OnWaterfall: TOnWaterfallReady read FOnWaterfall write FOnWaterfall;
|
||||
property OnTXIQ: TOnTXIQReady read FOnTXIQ write FOnTXIQ;
|
||||
@@ -813,6 +884,12 @@ begin
|
||||
FDSPThread := nil;
|
||||
FDisplayThread := nil;
|
||||
|
||||
// Мультислайсы
|
||||
FSliceLock := TCriticalSection.Create;
|
||||
FillChar(FSlices, SizeOf(FSlices), 0);
|
||||
FOnSliceAudio := nil;
|
||||
AllocSliceBuffers;
|
||||
|
||||
// TX инициализация
|
||||
FTXMicHead := 0;
|
||||
FTXMicTail := 0;
|
||||
@@ -887,6 +964,7 @@ begin
|
||||
Close;
|
||||
RTLEventDestroy(FQueueSem);
|
||||
RTLEventDestroy(FTXMicSem);
|
||||
FSliceLock.Free;
|
||||
inherited;
|
||||
end;
|
||||
|
||||
@@ -1052,6 +1130,7 @@ end;
|
||||
function TWDSPEngine.Open: Boolean;
|
||||
var
|
||||
DllPath: string;
|
||||
i: Integer;
|
||||
begin
|
||||
Result := False;
|
||||
FLastError := '';
|
||||
@@ -1131,6 +1210,18 @@ begin
|
||||
// Leveler, ALC, Compressor, Phase Rotator, AM Carrier, FM, CTCSS.
|
||||
ApplyTXChainSettings;
|
||||
ApplyNoiseFilterState;
|
||||
|
||||
// Переоткрываем WDSP-каналы активных слайсов (например после ChangeSampleRate,
|
||||
// где Close закрыл их, но записи FSlices сохранились).
|
||||
FSliceLock.Enter;
|
||||
try
|
||||
for i := 0 to MAX_SLICES - 1 do
|
||||
if FSlices[i].Active and not FSlices[i].Opened then
|
||||
OpenSliceChannel(i);
|
||||
finally
|
||||
FSliceLock.Leave;
|
||||
end;
|
||||
|
||||
Result := True;
|
||||
|
||||
// Запускаем DSP поток после успешной инициализации WDSP
|
||||
@@ -1149,6 +1240,8 @@ begin
|
||||
end;
|
||||
|
||||
procedure TWDSPEngine.Close;
|
||||
var
|
||||
i: Integer;
|
||||
begin
|
||||
if not FInitialized then Exit;
|
||||
|
||||
@@ -1161,6 +1254,15 @@ begin
|
||||
FreeAndNil(FDSPThread);
|
||||
end;
|
||||
|
||||
// Закрываем WDSP-каналы слайсов (записи FSlices сохраняем — переоткроются в Open).
|
||||
FSliceLock.Enter;
|
||||
try
|
||||
for i := 0 to MAX_SLICES - 1 do
|
||||
if FSlices[i].Opened then CloseSliceChannel(i);
|
||||
finally
|
||||
FSliceLock.Leave;
|
||||
end;
|
||||
|
||||
CloseAnalyzer;
|
||||
destroy_anbEXT(RXA_CHAN);
|
||||
destroy_nobEXT(RXA_CHAN);
|
||||
@@ -1195,6 +1297,7 @@ begin
|
||||
SetLength(FRXAccQ, FBufSize);
|
||||
SetLength(FDispBuf, DISPLAY_BLOCK_SIZE * 2);
|
||||
FDispPos := 0;
|
||||
AllocSliceBuffers;
|
||||
|
||||
// Переинициализируем WDSP с новым rate
|
||||
Open;
|
||||
@@ -1379,11 +1482,360 @@ begin
|
||||
FOnAudio(FOutL, FOutR, FAudioBufSize);
|
||||
end;
|
||||
|
||||
// Софтовые слайсы: fan-out того же IQ-блока на доп. WDSP-каналы.
|
||||
ProcessSlices;
|
||||
|
||||
except
|
||||
// Защита от падения libwdsp в потоке — просто пропускаем блок
|
||||
end;
|
||||
end;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Мультислайсы (софт-fan-out)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
procedure TWDSPEngine.AllocSliceBuffers;
|
||||
begin
|
||||
SetLength(FSliceIn, FBufSize * 2);
|
||||
SetLength(FSliceOut, FBufSize * 2); // с запасом (>= FAudioBufSize*2)
|
||||
SetLength(FSliceOutL, FAudioBufSize);
|
||||
SetLength(FSliceOutR, FAudioBufSize);
|
||||
end;
|
||||
|
||||
function TWDSPEngine.FindSliceIdx(Id: Integer): Integer;
|
||||
var i: Integer;
|
||||
begin
|
||||
Result := -1;
|
||||
for i := 0 to MAX_SLICES - 1 do
|
||||
if FSlices[i].Active and (FSlices[i].Id = Id) then Exit(i);
|
||||
end;
|
||||
|
||||
// Применяет AGC к произвольному каналу — вынесено из SetAGC чтобы делить с слайсами.
|
||||
procedure TWDSPEngine.ApplyAGCToChan(Chan: Integer; Mode: TWDSPAGCMode;
|
||||
FixedGain: Double);
|
||||
begin
|
||||
SetRXAAGCMode(Chan, Ord(Mode));
|
||||
SetRXAAGCSlope(Chan, FAGCSlope);
|
||||
SetRXAAGCTop(Chan, FAGCTop);
|
||||
case Mode of
|
||||
agcOff:
|
||||
SetRXAAGCFixed(Chan, FixedGain);
|
||||
agcLong: begin
|
||||
SetRXAAGCAttack(Chan, 2); SetRXAAGCHang(Chan, 2000);
|
||||
SetRXAAGCDecay(Chan, 2000); SetRXAAGCHangThreshold(Chan, FAGCHangThreshold);
|
||||
end;
|
||||
agcSlow: begin
|
||||
SetRXAAGCAttack(Chan, 2); SetRXAAGCHang(Chan, 1000);
|
||||
SetRXAAGCDecay(Chan, 500); SetRXAAGCHangThreshold(Chan, FAGCHangThreshold);
|
||||
end;
|
||||
agcMedium: begin
|
||||
SetRXAAGCAttack(Chan, 2); SetRXAAGCHang(Chan, 0);
|
||||
SetRXAAGCDecay(Chan, 250); SetRXAAGCHangThreshold(Chan, 100);
|
||||
end;
|
||||
agcFast: begin
|
||||
SetRXAAGCAttack(Chan, 2); SetRXAAGCHang(Chan, 0);
|
||||
SetRXAAGCDecay(Chan, 50); SetRXAAGCHangThreshold(Chan, 100);
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
// Открывает WDSP-канал слайса с той же геометрией, что RXA_CHAN. Вызывать
|
||||
// под FSliceLock и только при FInitialized.
|
||||
procedure TWDSPEngine.OpenSliceChannel(Idx: Integer);
|
||||
var Chan: Integer;
|
||||
begin
|
||||
if not FInitialized then Exit;
|
||||
if FSlices[Idx].Opened then Exit;
|
||||
Chan := FSlices[Idx].Chan;
|
||||
OpenChannel(Chan, FBufSize, FAudioBufSize, FSampleRate, FAudioRate, FAudioRate,
|
||||
0, 0, 0.010, 0.010, 0.010, 0.010, 0);
|
||||
// NB (ANB/NOB) требует создания EXT-блоков на канале, как для RXA_CHAN в Open.
|
||||
create_anbEXT(Chan, 1, FBufSize, FSampleRate, 0.0001, 0.0001, 0.0001, 0.05, 20);
|
||||
create_nobEXT(Chan, 1, 0, FBufSize, FSampleRate, 0.0001, 0.0001, 0.0001, 0.05, 20);
|
||||
SetRXABandpassWindow(Chan, 1);
|
||||
SetRXAMode(Chan, ModeToWDSP(FSlices[Idx].Mode));
|
||||
SetRXAShiftRun(Chan, 1);
|
||||
SetRXAShiftFreq(Chan, FSlices[Idx].ShiftHz);
|
||||
RXASetPassband(Chan, FSlices[Idx].FilterLo, FSlices[Idx].FilterHi);
|
||||
ApplyAGCToChan(Chan, FSlices[Idx].AGCMode, 50.0);
|
||||
// NR/NB/SNB/ANF из сохранённого состояния слайса.
|
||||
ApplyNRToChan(Chan, FSlices[Idx].NRMode);
|
||||
ApplyNBToChan(Chan, FSlices[Idx].NBMode);
|
||||
ApplySNBToChan(Chan, FSlices[Idx].SNBOn);
|
||||
ApplyANFToChan(Chan, FSlices[Idx].ANFOn);
|
||||
SetRXAPanelGain1(Chan, FSlices[Idx].Volume);
|
||||
SetRXAPanelSelect(Chan, 3);
|
||||
SetRXAPanelRun(Chan, 1);
|
||||
SetChannelState(Chan, 1, 0);
|
||||
FSlices[Idx].Opened := True;
|
||||
end;
|
||||
|
||||
procedure TWDSPEngine.CloseSliceChannel(Idx: Integer);
|
||||
begin
|
||||
if not FSlices[Idx].Opened then Exit;
|
||||
SetChannelState(FSlices[Idx].Chan, 0, 1);
|
||||
destroy_anbEXT(FSlices[Idx].Chan);
|
||||
destroy_nobEXT(FSlices[Idx].Chan);
|
||||
CloseChannel(FSlices[Idx].Chan);
|
||||
FSlices[Idx].Opened := False;
|
||||
end;
|
||||
|
||||
// Fan-out: прогоняет тот же входной IQ-блок через каждый активный слайс-канал.
|
||||
// Вызывается из ProcessRXBlock (DSP-поток) после основного канала.
|
||||
procedure TWDSPEngine.ProcessSlices;
|
||||
var
|
||||
s, i, k, Err: Integer;
|
||||
V: Double;
|
||||
begin
|
||||
if not Assigned(FOnSliceAudio) then Exit;
|
||||
FSliceLock.Enter;
|
||||
try
|
||||
for s := 0 to MAX_SLICES - 1 do
|
||||
begin
|
||||
if not (FSlices[s].Active and FSlices[s].Opened) then Continue;
|
||||
if FSlices[s].Muted then Continue;
|
||||
// Свежая копия входного IQ — fexchange0 портит вход in-place.
|
||||
for i := 0 to FBufSize - 1 do
|
||||
begin
|
||||
FSliceIn[i * 2] := FRXAccI[i];
|
||||
FSliceIn[i * 2 + 1] := FRXAccQ[i];
|
||||
end;
|
||||
Err := 0;
|
||||
fexchange0(FSlices[s].Chan, @FSliceIn[0], @FSliceOut[0], @Err);
|
||||
// S-метр канала читаем здесь (DSP-поток) — GetRXAMeter сбрасывает аккумулятор.
|
||||
FSlices[s].SMeter := GetRXAMeter(FSlices[s].Chan, RXA_S_AV);
|
||||
V := FSlices[s].Volume;
|
||||
for k := 0 to FAudioBufSize - 1 do
|
||||
begin
|
||||
FSliceOutL[k] := FSliceOut[k * 2] * V;
|
||||
FSliceOutR[k] := FSliceOut[k * 2 + 1] * V;
|
||||
end;
|
||||
FOnSliceAudio(FSlices[s].Id, FSliceOutL, FSliceOutR, FAudioBufSize);
|
||||
end;
|
||||
finally
|
||||
FSliceLock.Leave;
|
||||
end;
|
||||
end;
|
||||
|
||||
function TWDSPEngine.AddSlice(Id, Mode, FilterLo, FilterHi: Integer;
|
||||
ShiftHz, Volume: Double; AGC: TWDSPAGCMode): Boolean;
|
||||
var i, slot: Integer;
|
||||
begin
|
||||
Result := False;
|
||||
FSliceLock.Enter;
|
||||
try
|
||||
if FindSliceIdx(Id) >= 0 then Exit; // уже существует
|
||||
slot := -1;
|
||||
for i := 0 to MAX_SLICES - 1 do
|
||||
if not FSlices[i].Active then begin slot := i; Break; end;
|
||||
if slot < 0 then Exit; // нет свободных слотов
|
||||
FSlices[slot].Active := True;
|
||||
FSlices[slot].Id := Id;
|
||||
FSlices[slot].Chan := SLICE_CHAN_BASE + slot;
|
||||
FSlices[slot].Mode := Mode;
|
||||
FSlices[slot].FilterLo := FilterLo;
|
||||
FSlices[slot].FilterHi := FilterHi;
|
||||
FSlices[slot].ShiftHz := ShiftHz;
|
||||
FSlices[slot].Volume := Volume;
|
||||
FSlices[slot].AGCMode := AGC;
|
||||
FSlices[slot].Muted := False;
|
||||
FSlices[slot].NRMode := NR_MODE_OFF;
|
||||
FSlices[slot].NBMode := NB_MODE_OFF;
|
||||
FSlices[slot].SNBOn := False;
|
||||
FSlices[slot].ANFOn := False;
|
||||
FSlices[slot].SMeter := -130.0;
|
||||
FSlices[slot].Opened := False;
|
||||
if FInitialized then OpenSliceChannel(slot);
|
||||
Result := True;
|
||||
finally
|
||||
FSliceLock.Leave;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TWDSPEngine.RemoveSlice(Id: Integer);
|
||||
var idx: Integer;
|
||||
begin
|
||||
FSliceLock.Enter;
|
||||
try
|
||||
idx := FindSliceIdx(Id);
|
||||
if idx < 0 then Exit;
|
||||
CloseSliceChannel(idx);
|
||||
FSlices[idx].Active := False;
|
||||
finally
|
||||
FSliceLock.Leave;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TWDSPEngine.SetSliceShift(Id: Integer; ShiftHz: Double);
|
||||
var idx: Integer;
|
||||
begin
|
||||
FSliceLock.Enter;
|
||||
try
|
||||
idx := FindSliceIdx(Id); if idx < 0 then Exit;
|
||||
FSlices[idx].ShiftHz := ShiftHz;
|
||||
if FSlices[idx].Opened then SetRXAShiftFreq(FSlices[idx].Chan, ShiftHz);
|
||||
finally
|
||||
FSliceLock.Leave;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TWDSPEngine.SetSliceMode(Id, Mode: Integer);
|
||||
var idx: Integer;
|
||||
begin
|
||||
FSliceLock.Enter;
|
||||
try
|
||||
idx := FindSliceIdx(Id); if idx < 0 then Exit;
|
||||
FSlices[idx].Mode := Mode;
|
||||
if FSlices[idx].Opened then SetRXAMode(FSlices[idx].Chan, ModeToWDSP(Mode));
|
||||
finally
|
||||
FSliceLock.Leave;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TWDSPEngine.SetSliceFilter(Id, Low, High: Integer);
|
||||
var idx: Integer;
|
||||
begin
|
||||
FSliceLock.Enter;
|
||||
try
|
||||
idx := FindSliceIdx(Id); if idx < 0 then Exit;
|
||||
FSlices[idx].FilterLo := Low;
|
||||
FSlices[idx].FilterHi := High;
|
||||
if FSlices[idx].Opened then RXASetPassband(FSlices[idx].Chan, Low, High);
|
||||
finally
|
||||
FSliceLock.Leave;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TWDSPEngine.SetSliceAGC(Id: Integer; Mode: TWDSPAGCMode);
|
||||
var idx: Integer;
|
||||
begin
|
||||
FSliceLock.Enter;
|
||||
try
|
||||
idx := FindSliceIdx(Id); if idx < 0 then Exit;
|
||||
FSlices[idx].AGCMode := Mode;
|
||||
if FSlices[idx].Opened then ApplyAGCToChan(FSlices[idx].Chan, Mode, 50.0);
|
||||
finally
|
||||
FSliceLock.Leave;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TWDSPEngine.SetSliceVolume(Id: Integer; Vol: Double);
|
||||
var idx: Integer;
|
||||
begin
|
||||
FSliceLock.Enter;
|
||||
try
|
||||
idx := FindSliceIdx(Id); if idx < 0 then Exit;
|
||||
FSlices[idx].Volume := Vol;
|
||||
if FSlices[idx].Opened then SetRXAPanelGain1(FSlices[idx].Chan, Vol);
|
||||
finally
|
||||
FSliceLock.Leave;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TWDSPEngine.SetSliceMute(Id: Integer; Mute: Boolean);
|
||||
var idx: Integer;
|
||||
begin
|
||||
FSliceLock.Enter;
|
||||
try
|
||||
idx := FindSliceIdx(Id); if idx < 0 then Exit;
|
||||
FSlices[idx].Muted := Mute;
|
||||
finally
|
||||
FSliceLock.Leave;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TWDSPEngine.SetSliceNRMode(Id, Mode: Integer);
|
||||
var idx: Integer;
|
||||
begin
|
||||
if Mode < NR_MODE_OFF then Mode := NR_MODE_OFF;
|
||||
if Mode > NR_MODE_4 then Mode := NR_MODE_4;
|
||||
FSliceLock.Enter;
|
||||
try
|
||||
idx := FindSliceIdx(Id); if idx < 0 then Exit;
|
||||
FSlices[idx].NRMode := Mode;
|
||||
if FSlices[idx].Opened then ApplyNRToChan(FSlices[idx].Chan, Mode);
|
||||
finally
|
||||
FSliceLock.Leave;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TWDSPEngine.SetSliceNBMode(Id, Mode: Integer);
|
||||
var idx: Integer;
|
||||
begin
|
||||
if Mode < NB_MODE_OFF then Mode := NB_MODE_OFF;
|
||||
if Mode > NB_MODE_2 then Mode := NB_MODE_2;
|
||||
FSliceLock.Enter;
|
||||
try
|
||||
idx := FindSliceIdx(Id); if idx < 0 then Exit;
|
||||
FSlices[idx].NBMode := Mode;
|
||||
if FSlices[idx].Opened then ApplyNBToChan(FSlices[idx].Chan, Mode);
|
||||
finally
|
||||
FSliceLock.Leave;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TWDSPEngine.SetSliceSNB(Id: Integer; On_: Boolean);
|
||||
var idx: Integer;
|
||||
begin
|
||||
FSliceLock.Enter;
|
||||
try
|
||||
idx := FindSliceIdx(Id); if idx < 0 then Exit;
|
||||
FSlices[idx].SNBOn := On_;
|
||||
if FSlices[idx].Opened then ApplySNBToChan(FSlices[idx].Chan, On_);
|
||||
finally
|
||||
FSliceLock.Leave;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TWDSPEngine.SetSliceANF(Id: Integer; On_: Boolean);
|
||||
var idx: Integer;
|
||||
begin
|
||||
FSliceLock.Enter;
|
||||
try
|
||||
idx := FindSliceIdx(Id); if idx < 0 then Exit;
|
||||
FSlices[idx].ANFOn := On_;
|
||||
if FSlices[idx].Opened then ApplyANFToChan(FSlices[idx].Chan, On_);
|
||||
finally
|
||||
FSliceLock.Leave;
|
||||
end;
|
||||
end;
|
||||
|
||||
function TWDSPEngine.GetSliceSMeter(Id: Integer): Double;
|
||||
var idx: Integer;
|
||||
begin
|
||||
Result := -130.0;
|
||||
FSliceLock.Enter;
|
||||
try
|
||||
idx := FindSliceIdx(Id);
|
||||
if idx >= 0 then Result := FSlices[idx].SMeter;
|
||||
finally
|
||||
FSliceLock.Leave;
|
||||
end;
|
||||
end;
|
||||
|
||||
function TWDSPEngine.SliceCount: Integer;
|
||||
var i: Integer;
|
||||
begin
|
||||
Result := 0;
|
||||
FSliceLock.Enter;
|
||||
try
|
||||
for i := 0 to MAX_SLICES - 1 do
|
||||
if FSlices[i].Active then Inc(Result);
|
||||
finally
|
||||
FSliceLock.Leave;
|
||||
end;
|
||||
end;
|
||||
|
||||
function TWDSPEngine.HasSlice(Id: Integer): Boolean;
|
||||
begin
|
||||
FSliceLock.Enter;
|
||||
try
|
||||
Result := FindSliceIdx(Id) >= 0;
|
||||
finally
|
||||
FSliceLock.Leave;
|
||||
end;
|
||||
end;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// RX управление
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -1412,42 +1864,7 @@ begin
|
||||
if not FInitialized then Exit;
|
||||
|
||||
// Точно по piHPSDR receiver.c: set_agc()
|
||||
SetRXAAGCMode(RXA_CHAN, Ord(Mode));
|
||||
SetRXAAGCSlope(RXA_CHAN, FAGCSlope);
|
||||
SetRXAAGCTop(RXA_CHAN, FAGCTop);
|
||||
|
||||
case Mode of
|
||||
agcOff:
|
||||
SetRXAAGCFixed(RXA_CHAN, FixedGain);
|
||||
|
||||
agcLong: begin
|
||||
SetRXAAGCAttack(RXA_CHAN, 2);
|
||||
SetRXAAGCHang(RXA_CHAN, 2000);
|
||||
SetRXAAGCDecay(RXA_CHAN, 2000);
|
||||
SetRXAAGCHangThreshold(RXA_CHAN, FAGCHangThreshold);
|
||||
end;
|
||||
|
||||
agcSlow: begin
|
||||
SetRXAAGCAttack(RXA_CHAN, 2);
|
||||
SetRXAAGCHang(RXA_CHAN, 1000);
|
||||
SetRXAAGCDecay(RXA_CHAN, 500);
|
||||
SetRXAAGCHangThreshold(RXA_CHAN, FAGCHangThreshold);
|
||||
end;
|
||||
|
||||
agcMedium: begin
|
||||
SetRXAAGCAttack(RXA_CHAN, 2);
|
||||
SetRXAAGCHang(RXA_CHAN, 0);
|
||||
SetRXAAGCDecay(RXA_CHAN, 250);
|
||||
SetRXAAGCHangThreshold(RXA_CHAN, 100);
|
||||
end;
|
||||
|
||||
agcFast: begin
|
||||
SetRXAAGCAttack(RXA_CHAN, 2);
|
||||
SetRXAAGCHang(RXA_CHAN, 0);
|
||||
SetRXAAGCDecay(RXA_CHAN, 50);
|
||||
SetRXAAGCHangThreshold(RXA_CHAN, 100);
|
||||
end;
|
||||
end;
|
||||
ApplyAGCToChan(RXA_CHAN, Mode, FixedGain);
|
||||
|
||||
// Читаем обратно hang level и thresh для линии на спектре
|
||||
UpdateAGCLines(FLastSpectrumW);
|
||||
@@ -1502,69 +1919,88 @@ begin
|
||||
FSampleRate);
|
||||
end;
|
||||
|
||||
// Параметризованные по каналу версии — делятся между главным каналом и слайсами.
|
||||
procedure TWDSPEngine.ApplyNRToChan(Chan, NRMode: Integer);
|
||||
begin
|
||||
SetRXAANRVals(Chan, 64, 16, 16e-4, 10e-7);
|
||||
SetRXAANRPosition(Chan, FILTER_NR_AGC_POSITION);
|
||||
SetRXAANRRun(Chan, Ord(NRMode = NR_MODE_1));
|
||||
|
||||
SetRXAEMNRPosition(Chan, FILTER_NR_AGC_POSITION);
|
||||
SetRXAEMNRgainMethod(Chan, FILTER_NR2_GAIN_METHOD);
|
||||
SetRXAEMNRnpeMethod(Chan, FILTER_NR2_NPE_METHOD);
|
||||
SetRXAEMNRtrainZetaThresh(Chan, FILTER_NR2_TRAINED_THRESHOLD);
|
||||
SetRXAEMNRtrainT2(Chan, FILTER_NR2_TRAINED_T2);
|
||||
SetRXAEMNRpost2Taper(Chan, FILTER_NR2_POST_TAPER);
|
||||
SetRXAEMNRpost2Nlevel(Chan, FILTER_NR2_POST_NLEVEL);
|
||||
SetRXAEMNRpost2Factor(Chan, FILTER_NR2_POST_FACTOR);
|
||||
SetRXAEMNRpost2Rate(Chan, FILTER_NR2_POST_RATE);
|
||||
SetRXAEMNRaeRun(Chan, 1);
|
||||
SetRXAEMNRpost2Run(Chan, FILTER_NR2_POST_RUN);
|
||||
SetRXAEMNRRun(Chan, Ord(NRMode = NR_MODE_2));
|
||||
|
||||
SetRXARNNRPosition(Chan, FILTER_NR_AGC_POSITION);
|
||||
SetRXARNNRRun(Chan, Ord(NRMode = NR_MODE_3));
|
||||
|
||||
SetRXASBNRreductionAmount(Chan, FILTER_NR4_REDUCTION_AMOUNT);
|
||||
SetRXASBNRsmoothingFactor(Chan, FILTER_NR4_SMOOTHING_FACTOR);
|
||||
SetRXASBNRwhiteningFactor(Chan, FILTER_NR4_WHITENING_FACTOR);
|
||||
SetRXASBNRnoiseRescale(Chan, FILTER_NR4_NOISE_RESCALE);
|
||||
SetRXASBNRpostFilterThreshold(Chan, FILTER_NR4_POST_THRESHOLD);
|
||||
SetRXASBNRnoiseScalingType(Chan, FILTER_NR4_NOISE_SCALING_TYPE);
|
||||
SetRXASBNRPosition(Chan, FILTER_NR_AGC_POSITION);
|
||||
SetRXASBNRRun(Chan, Ord(NRMode = NR_MODE_4));
|
||||
end;
|
||||
|
||||
procedure TWDSPEngine.ApplyNBToChan(Chan, NBMode: Integer);
|
||||
begin
|
||||
SetEXTANBTau(Chan, FILTER_NB_TAU);
|
||||
SetEXTANBHangtime(Chan, FILTER_NB_HANGTIME);
|
||||
SetEXTANBAdvtime(Chan, FILTER_NB_ADVTIME);
|
||||
SetEXTANBThreshold(Chan, FILTER_NB_THRESHOLD);
|
||||
SetEXTANBRun(Chan, Ord(NBMode = NB_MODE_1));
|
||||
|
||||
SetEXTNOBMode(Chan, FILTER_NB2_MODE);
|
||||
SetEXTNOBTau(Chan, FILTER_NB_TAU);
|
||||
SetEXTNOBHangtime(Chan, FILTER_NB_HANGTIME);
|
||||
SetEXTNOBAdvtime(Chan, FILTER_NB_ADVTIME);
|
||||
SetEXTNOBThreshold(Chan, FILTER_NB_THRESHOLD);
|
||||
SetEXTNOBRun(Chan, Ord(NBMode = NB_MODE_2));
|
||||
end;
|
||||
|
||||
procedure TWDSPEngine.ApplySNBToChan(Chan: Integer; On_: Boolean);
|
||||
begin
|
||||
SetRXASNBARun(Chan, Ord(On_));
|
||||
end;
|
||||
|
||||
procedure TWDSPEngine.ApplyANFToChan(Chan: Integer; On_: Boolean);
|
||||
begin
|
||||
SetRXAANFPosition(Chan, FILTER_NR_AGC_POSITION);
|
||||
SetRXAANFRun(Chan, Ord(On_));
|
||||
end;
|
||||
|
||||
procedure TWDSPEngine.ApplyNRState;
|
||||
begin
|
||||
if not FInitialized then Exit;
|
||||
|
||||
SetRXAANRVals(RXA_CHAN, 64, 16, 16e-4, 10e-7);
|
||||
SetRXAANRPosition(RXA_CHAN, FILTER_NR_AGC_POSITION);
|
||||
SetRXAANRRun(RXA_CHAN, Ord(FNRMode = NR_MODE_1));
|
||||
|
||||
SetRXAEMNRPosition(RXA_CHAN, FILTER_NR_AGC_POSITION);
|
||||
SetRXAEMNRgainMethod(RXA_CHAN, FILTER_NR2_GAIN_METHOD);
|
||||
SetRXAEMNRnpeMethod(RXA_CHAN, FILTER_NR2_NPE_METHOD);
|
||||
SetRXAEMNRtrainZetaThresh(RXA_CHAN, FILTER_NR2_TRAINED_THRESHOLD);
|
||||
SetRXAEMNRtrainT2(RXA_CHAN, FILTER_NR2_TRAINED_T2);
|
||||
SetRXAEMNRpost2Taper(RXA_CHAN, FILTER_NR2_POST_TAPER);
|
||||
SetRXAEMNRpost2Nlevel(RXA_CHAN, FILTER_NR2_POST_NLEVEL);
|
||||
SetRXAEMNRpost2Factor(RXA_CHAN, FILTER_NR2_POST_FACTOR);
|
||||
SetRXAEMNRpost2Rate(RXA_CHAN, FILTER_NR2_POST_RATE);
|
||||
SetRXAEMNRaeRun(RXA_CHAN, 1);
|
||||
SetRXAEMNRpost2Run(RXA_CHAN, FILTER_NR2_POST_RUN);
|
||||
SetRXAEMNRRun(RXA_CHAN, Ord(FNRMode = NR_MODE_2));
|
||||
|
||||
SetRXARNNRPosition(RXA_CHAN, FILTER_NR_AGC_POSITION);
|
||||
SetRXARNNRRun(RXA_CHAN, Ord(FNRMode = NR_MODE_3));
|
||||
|
||||
SetRXASBNRreductionAmount(RXA_CHAN, FILTER_NR4_REDUCTION_AMOUNT);
|
||||
SetRXASBNRsmoothingFactor(RXA_CHAN, FILTER_NR4_SMOOTHING_FACTOR);
|
||||
SetRXASBNRwhiteningFactor(RXA_CHAN, FILTER_NR4_WHITENING_FACTOR);
|
||||
SetRXASBNRnoiseRescale(RXA_CHAN, FILTER_NR4_NOISE_RESCALE);
|
||||
SetRXASBNRpostFilterThreshold(RXA_CHAN, FILTER_NR4_POST_THRESHOLD);
|
||||
SetRXASBNRnoiseScalingType(RXA_CHAN, FILTER_NR4_NOISE_SCALING_TYPE);
|
||||
SetRXASBNRPosition(RXA_CHAN, FILTER_NR_AGC_POSITION);
|
||||
SetRXASBNRRun(RXA_CHAN, Ord(FNRMode = NR_MODE_4));
|
||||
ApplyNRToChan(RXA_CHAN, FNRMode);
|
||||
end;
|
||||
|
||||
procedure TWDSPEngine.ApplyNBState;
|
||||
begin
|
||||
if not FInitialized then Exit;
|
||||
|
||||
SetEXTANBTau(RXA_CHAN, FILTER_NB_TAU);
|
||||
SetEXTANBHangtime(RXA_CHAN, FILTER_NB_HANGTIME);
|
||||
SetEXTANBAdvtime(RXA_CHAN, FILTER_NB_ADVTIME);
|
||||
SetEXTANBThreshold(RXA_CHAN, FILTER_NB_THRESHOLD);
|
||||
SetEXTANBRun(RXA_CHAN, Ord(FNBMode = NB_MODE_1));
|
||||
|
||||
SetEXTNOBMode(RXA_CHAN, FILTER_NB2_MODE);
|
||||
SetEXTNOBTau(RXA_CHAN, FILTER_NB_TAU);
|
||||
SetEXTNOBHangtime(RXA_CHAN, FILTER_NB_HANGTIME);
|
||||
SetEXTNOBAdvtime(RXA_CHAN, FILTER_NB_ADVTIME);
|
||||
SetEXTNOBThreshold(RXA_CHAN, FILTER_NB_THRESHOLD);
|
||||
SetEXTNOBRun(RXA_CHAN, Ord(FNBMode = NB_MODE_2));
|
||||
ApplyNBToChan(RXA_CHAN, FNBMode);
|
||||
end;
|
||||
|
||||
procedure TWDSPEngine.ApplySNBState;
|
||||
begin
|
||||
if not FInitialized then Exit;
|
||||
SetRXASNBARun(RXA_CHAN, Ord(FSNBEnabled));
|
||||
ApplySNBToChan(RXA_CHAN, FSNBEnabled);
|
||||
end;
|
||||
|
||||
procedure TWDSPEngine.ApplyANFState;
|
||||
begin
|
||||
if not FInitialized then Exit;
|
||||
SetRXAANFPosition(RXA_CHAN, FILTER_NR_AGC_POSITION);
|
||||
SetRXAANFRun(RXA_CHAN, Ord(FANFEnabled));
|
||||
ApplyANFToChan(RXA_CHAN, FANFEnabled);
|
||||
end;
|
||||
|
||||
procedure TWDSPEngine.ApplyNoiseFilterState;
|
||||
|
||||
Reference in New Issue
Block a user