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:
2026-07-01 23:46:30 +03:00
co-authored by Claude Opus 4.8
parent a04c592f1d
commit 1527b0f849
8 changed files with 1915 additions and 139 deletions
+517 -81
View File
@@ -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;