unit WDSPEngine; { WDSP DSP Engine for OpenHPSDR Transceiver Correct WDSP API usage based on actual WDSP.pas bindings: Spectrum pipeline: XCreateAnalyzer(disp, ...) — создать analyzer display SetDisplay*(disp, ...) — настроить detector/average/rate SetRXASpectrum(ch, 1, disp, 0, 0) — подключить RXA к display fexchange0() в цикле → WDSP внутри пишет данные в display буфер Spectrum0(1, disp, 0, 0, nil) — тригер snapshot GetPixels(disp, 0, pix, flag) — забрать пиксели OpenChannel сигнатура (13 параметров): channel, in_size, dsp_size, in_rate, dsp_rate, out_rate, atype, state, tdelayup, tslewup, tdelaydown, tslewdown, bfo } {$IFDEF FPC} {$MODE Delphi} {$ENDIF} interface uses Classes, SysUtils, Math, SyncObjs, WDSP; const // WDSP mode integers (нет именованных констант в WDSP.pas) WDSP_LSB = 0; WDSP_USB = 1; WDSP_DSB = 2; WDSP_CWL = 3; WDSP_CWU = 4; WDSP_FM = 5; WDSP_AM = 6; WDSP_SAM = 12; // Наши индексы режимов (совпадают с кнопками MainForm) MODE_LSB = 0; MODE_USB = 1; MODE_DSB = 2; MODE_CWL = 3; MODE_CWU = 4; MODE_FM = 5; MODE_AM = 6; MODE_SAM = 7; // AGC modes AGC_OFF = 0; AGC_LONG = 1; AGC_SLOW = 2; AGC_MEDIUM = 3; AGC_FAST = 4; // RX filter modes NR_MODE_OFF = 0; NR_MODE_1 = 1; NR_MODE_2 = 2; NR_MODE_3 = 3; NR_MODE_4 = 4; NB_MODE_OFF = 0; NB_MODE_1 = 1; NB_MODE_2 = 2; FILTER_NR_AGC_POSITION = 1; FILTER_NB_TAU = 0.00001; FILTER_NB_ADVTIME = 0.00001; FILTER_NB_HANGTIME = 0.00001; FILTER_NB_THRESHOLD = 4.95; FILTER_NB2_MODE = 0; FILTER_NR2_GAIN_METHOD = 2; FILTER_NR2_NPE_METHOD = 0; FILTER_NR2_POST_RUN = 0; FILTER_NR2_POST_TAPER = 12; FILTER_NR2_POST_NLEVEL = 15.0; FILTER_NR2_POST_FACTOR = 15.0; FILTER_NR2_POST_RATE = 5.0; FILTER_NR2_TRAINED_THRESHOLD = -0.5; FILTER_NR2_TRAINED_T2 = 0.2; FILTER_NR4_REDUCTION_AMOUNT = 10.0; FILTER_NR4_SMOOTHING_FACTOR = 20.0; FILTER_NR4_WHITENING_FACTOR = 0.0; FILTER_NR4_NOISE_RESCALE = 2.0; FILTER_NR4_POST_THRESHOLD = -3.0; FILTER_NR4_NOISE_SCALING_TYPE = 0; // S-meter types для GetRXAMeter RXA_S_PK = 0; // peak, dBm RXA_S_AV = 1; // average, dBm RXA_CHAN = 0; TXA_CHAN = 1; // используем разные каналы для RX и TX DISP_ID = 0; // ID для XCreateAnalyzer DSP_BUFSIZE = 1024; SPECTRUM_PIXELS = 1024; // Очередь IQ пакетов между сетевым и DSP потоком IQ_QUEUE_SIZE = 64; // кол-во слотов (степень двойки для AND-маски) IQ_PKT_MAXBYTES = 1428; // max байт IQ данных в пакете (238*6) type // Пакет в очереди между сетевым и DSP потоком TIQQueueItem = record Data: array[0..IQ_PKT_MAXBYTES - 1] of Byte; DataLen: Integer; // реальная длина данных IQPairs: Integer; // количество IQ пар end; TWDSPAGCMode = (agcOff = 0, agcLong = 1, agcSlow = 2, agcMedium = 3, agcFast = 4); TOnAudioReady = procedure(const Left, Right: array of Single; Count: Integer) of object; TOnSpectrumReady = procedure(const Pixels: array of Single; Count: Integer) of object; { TWDSPEngine } TWDSPEngine = class private FInitialized: Boolean; FAnalyzerOpen: Boolean; FSampleRate: Integer; FAudioRate: Integer; FBufSize: Integer; // входной буфер @ FSampleRate FAudioBufSize: Integer; // выходной буфер @ FAudioRate (= FBufSize * FAudioRate / FSampleRate) // Interleaved double I/Q буферы для fexchange0 FRXIn: array of Double; FRXOut: array of Double; FTXIn: array of Double; FTXOut: array of Double; // Накопитель входного буфера (DDC пакеты могут быть мельче FBufSize) FRXAccI: array of Double; FRXAccQ: array of Double; FRXAccPos: Integer; // Snapshot буфер для Spectrum0 FSnapBuf: array of Double; // Аудио выходные буферы — аллоцируем один раз FOutL: array of Single; FOutR: array of Single; // DSP поток + очередь пакетов FDSPThread: TThread; FQueue: array[0..IQ_QUEUE_SIZE - 1] of TIQQueueItem; FQueueHead: Integer; // пишет сетевой поток FQueueTail: Integer; // читает DSP поток FQueueSem: PRTLEvent; // сигнал: есть новые данные (RTLEvent) FDSPRunning: Boolean; // Double буфер для Spectrum0 (принимает PDouble, не PdINREAL) FSpecBuf: array of Double; // Double буфер для Spectrum0 (PDouble) FSpectrumPixels: array[0..SPECTRUM_PIXELS - 1] of Single; FFlp: array[0..0] of Integer; // for SetAnalyzer flp parameter FOnAudio: TOnAudioReady; FOnSpectrum: TOnSpectrumReady; FMode: Integer; FSMeter: Double; FFilterLow: Integer; FFilterHigh: Integer; FAGCMode: TWDSPAGCMode; FAGCTop: Double; // gain (dB) = agc_gain в piHPSDR FAGCSlope: Integer; // наклон АРУ в дБ (default 0) FAGCHangThreshold: Integer; // порог hang 0..100 FAGCHangLevel: Double; // читается из WDSP (GetRXAAGCHangLevel) FAGCThresh: Double; // читается из WDSP (GetRXAAGCThresh) FLastSpectrumW: Integer; // последняя известная ширина спектра (пикс) FShiftHz: Double; // NCO shift для CTUN FNRMode: Integer; FNBMode: Integer; FSNBEnabled: Boolean; FANFEnabled: Boolean; FMuted: Boolean; FVolume: Double; FLastError: string; function ModeToWDSP(Mode: Integer): Integer; procedure ApplyDefaultFilter; procedure ProcessRXBlock; procedure PushIQItemToDSP(const Item: TIQQueueItem); procedure OpenAnalyzer; procedure CloseAnalyzer; procedure ApplyNRState; procedure ApplyNBState; procedure ApplySNBState; procedure ApplyANFState; procedure ApplyNoiseFilterState; public constructor Create(SampleRate: Integer = 192000; AudioRate: Integer = 48000; BufSize: Integer = DSP_BUFSIZE); destructor Destroy; override; // Открыть DSP каналы function Open: Boolean; procedure Close; procedure ChangeSampleRate(NewRate: Integer); // Подача 24-bit big-endian IQ из DDC пакета // Buf — массив байт, DataOffset — смещение до IQ данных внутри Buf procedure PushDDCPacket(const Buf: array of Byte; DataOffset: Integer; IQPairs: Integer); // RX управление procedure SetMode(Mode: Integer); procedure SetFilter(Low, High: Integer); procedure SetAGC(Mode: TWDSPAGCMode; FixedGain: Double = 0.0); procedure SetAGCTop(TopDBm: Double); procedure SetAGCSlope(Slope: Integer); procedure SetAGCHangThreshold(Threshold: Integer); procedure UpdateAGCLines(SpectrumW: Integer); // читает hang/thresh из WDSP для линии на спектре procedure SetSpectrumWidth(W: Integer); // обновляет FLastSpectrumW для корректных AGC линий procedure SetShift(ShiftHz: Double); // CTUN NCO сдвиг procedure SetNRMode(Mode: Integer); procedure SetNR(Enable: Boolean); procedure SetNBMode(Mode: Integer); procedure SetNB(Enable: Boolean); procedure SetSNB(Enable: Boolean); procedure SetANF(Enable: Boolean); procedure SetVolume(Vol: Double); procedure SetMute(Mute: Boolean); // TX управление procedure SetTXMode(Mode: Integer); procedure SetTXFilter(Low, High: Integer); procedure SetDriveLevel(Level: Double); procedure SetMicGain(GainDB: Double); procedure SetTXRun(Run: Boolean); // Spectrum — вызывать из таймера (~20 fps) procedure UpdateSpectrum; procedure GetSpectrumData(var Pixels: array of Single; var Count: Integer); // S-meter function GetSMeterDBm: Double; property Initialized: Boolean read FInitialized; property SampleRate: Integer read FSampleRate; property AudioBufSize: Integer read FAudioBufSize; property AGCTop: Double read FAGCTop; property AGCSlope: Integer read FAGCSlope; property AGCHangThreshold: Integer read FAGCHangThreshold; property AGCHangLevel: Double read FAGCHangLevel; property AGCThresh: Double read FAGCThresh; property Mode: Integer read FMode; property FilterLow: Integer read FFilterLow; property FilterHigh: Integer read FFilterHigh; property NRMode: Integer read FNRMode; property NBMode: Integer read FNBMode; property SNBEnabled: Boolean read FSNBEnabled; property LastError: string read FLastError; property SMeter: Double read FSMeter; property OnAudio: TOnAudioReady read FOnAudio write FOnAudio; property OnSpectrum: TOnSpectrumReady read FOnSpectrum write FOnSpectrum; end; implementation // =========================================================================== // DSP поток — обрабатывает IQ пакеты из очереди // Сетевой поток только кладёт пакеты, этот поток занимается DSP // Аналог iq_thread в piHPSDR/new_protocol.c // =========================================================================== type TDSPThread = class(TThread) private FEngine: TWDSPEngine; protected procedure Execute; override; public constructor Create(AEngine: TWDSPEngine); end; constructor TDSPThread.Create(AEngine: TWDSPEngine); begin FEngine := AEngine; FreeOnTerminate := False; inherited Create(False); end; procedure TDSPThread.Execute; var Item: ^TIQQueueItem; begin while not Terminated do begin // Ждём сигнала от сетевого потока (как sem_wait в piHPSDR) RTLEventWaitFor(FEngine.FQueueSem, 100); if Terminated then Break; // Разбираем все накопившиеся пакеты while FEngine.FQueueTail <> FEngine.FQueueHead do begin Item := @FEngine.FQueue[FEngine.FQueueTail]; FEngine.PushIQItemToDSP(Item^); FEngine.FQueueTail := (FEngine.FQueueTail + 1) and (IQ_QUEUE_SIZE - 1); end; end; end; // --------------------------------------------------------------------------- function TWDSPEngine.ModeToWDSP(Mode: Integer): Integer; begin case Mode of MODE_LSB: Result := WDSP_LSB; MODE_USB: Result := WDSP_USB; MODE_DSB: Result := WDSP_DSB; MODE_CWL: Result := WDSP_CWL; MODE_CWU: Result := WDSP_CWU; MODE_FM: Result := WDSP_FM; MODE_AM: Result := WDSP_AM; MODE_SAM: Result := WDSP_SAM; else Result := WDSP_USB; end; end; procedure TWDSPEngine.ApplyDefaultFilter; begin case FMode of MODE_LSB: SetFilter(-2400, -100); MODE_USB: SetFilter( 100, 2400); MODE_DSB: SetFilter(-2400, 2400); MODE_CWL: SetFilter( -800, -200); MODE_CWU: SetFilter( 200, 800); MODE_FM: SetFilter(-5000, 5000); MODE_AM: SetFilter(-4000, 4000); MODE_SAM: SetFilter(-4000, 4000); end; end; // --------------------------------------------------------------------------- // Constructor / Destructor // --------------------------------------------------------------------------- constructor TWDSPEngine.Create(SampleRate, AudioRate, BufSize: Integer); begin inherited Create; FSampleRate := SampleRate; FAudioRate := AudioRate; // BufSize = dsp_size @ AudioRate (внутренний DSP буфер, piHPSDR buffer_size = 1024) // FBufSize = in_size @ SampleRate = BufSize * (SampleRate/AudioRate) // piHPSDR: in_size = buffer_size * sample_rate/48000 = 1024*4 = 4096 @ 192kHz FAudioBufSize := BufSize; // dsp_size = out_size @ AudioRate FBufSize := BufSize * SampleRate div AudioRate; // in_size @ SampleRate = 4096 FInitialized := False; FAnalyzerOpen := False; FMode := MODE_USB; FFilterLow := 100; FFilterHigh := 2400; FAGCMode := agcMedium; FShiftHz := 0.0; FAGCTop := -90.0; FVolume := 0.7; FSMeter := -130.0; FNRMode := NR_MODE_OFF; FNBMode := NB_MODE_OFF; FSNBEnabled := False; FANFEnabled := False; FRXAccPos := 0; SetLength(FRXIn, FBufSize * 2); // in_size пар @ FSampleRate (4096*2) SetLength(FRXOut, FAudioBufSize * 2); // out_size пар @ FAudioRate (1024*2) SetLength(FTXIn, FAudioBufSize * 2); SetLength(FTXOut, FBufSize * 2); SetLength(FRXAccI, FBufSize); SetLength(FRXAccQ, FBufSize); SetLength(FSnapBuf, FBufSize * 2); SetLength(FOutL, FAudioBufSize); SetLength(FOutR, FAudioBufSize); SetLength(FSpecBuf, FBufSize * 2); // Очередь и DSP поток FQueueHead := 0; FQueueTail := 0; FDSPRunning := False; FQueueSem := RTLEventCreate; FDSPThread := nil; end; destructor TWDSPEngine.Destroy; begin Close; RTLEventDestroy(FQueueSem); inherited; end; // --------------------------------------------------------------------------- // Analyzer display // --------------------------------------------------------------------------- procedure TWDSPEngine.OpenAnalyzer; var Success: Integer; MaxW: Integer; Ovrlp: Integer; FRAME_RATE: Integer; begin if FAnalyzerOpen then Exit; Success := -1; XCreateAnalyzer(DISP_ID, @Success, 16384, 1, 1, nil); if Success <> 0 then Exit; FRAME_RATE := 20; // max_w: WDSP аллоцирует ring-buffer и FFT планы на max_w сэмплов. // Формула: fft_size + min(0.1*samplerate, 0.1*fft_size*fps) // = 4096 + min(19200, 8192) = 12288 → 1700ms инициализации! // Реально нам нужен только один FFT-кадр без усреднения по времени. // MaxW = sz = 4096 — минимально допустимое значение (должно быть ≥ sz). // piHPSDR использует именно sz без добавки keep_time. MaxW := 4096; Ovrlp := 0; FFlp[0] := 0; SetAnalyzer(DISP_ID, 1, 1, 1, @FFlp[0], 4096, FAudioBufSize, 1, 14.0, Ovrlp, 0, 0.0, 0.0, SPECTRUM_PIXELS, 1, 0, 0.0, 0.0, MaxW); SetDisplayDetectorMode(DISP_ID, 0, DETECTOR_MODE_AVERAGE); SetDisplayAverageMode(DISP_ID, 0, AVERAGE_MODE_LOG_RECURSIVE); SetDisplayNumAverage(DISP_ID, 0, Max(2, Trunc(FRAME_RATE * 0.12))); SetDisplayAvBackmult(DISP_ID, 0, 0.45); SetDisplaySampleRate(DISP_ID, FSampleRate); SetDisplayNormOneHz(DISP_ID, 0, 0); FAnalyzerOpen := True; end; procedure TWDSPEngine.CloseAnalyzer; begin if not FAnalyzerOpen then Exit; DestroyAnalyzer(DISP_ID); FAnalyzerOpen := False; end; // --------------------------------------------------------------------------- // Open / Close // --------------------------------------------------------------------------- function TWDSPEngine.Open: Boolean; var DllPath: string; begin Result := False; FLastError := ''; if FInitialized then Exit; if not Assigned(@OpenChannel) then begin FInitialized := False; DllPath := IncludeTrailingPathDelimiter(ExtractFilePath(ParamStr(0))) + WDSP_LIB; if FileExists(DllPath) then FLastError := Format('Symbols from %s are not resolved. Check architecture (x64/x86) and dependencies.', [DllPath]) else FLastError := Format('WDSP library not found: %s', [DllPath]); Exit; end; try // Анализатор ПЕРВЫМ — до OpenChannel (порядок как в piHPSDR) OpenAnalyzer; if not FAnalyzerOpen then begin FLastError := 'XCreateAnalyzer failed (WDSP init failed).'; Exit; end; OpenChannel( RXA_CHAN, FBufSize, FAudioBufSize, FSampleRate, FAudioRate, FAudioRate, 0, 0, // state=0: создать но не запускать 0.010, 0.010, 0.010, 0.010, 0 ); create_anbEXT(RXA_CHAN, 1, FBufSize, FSampleRate, 0.0001, 0.0001, 0.0001, 0.05, 20); create_nobEXT(RXA_CHAN, 1, 0, FBufSize, FSampleRate, 0.0001, 0.0001, 0.0001, 0.05, 20); SetRXABandpassWindow(RXA_CHAN, 1); SetRXAMode(RXA_CHAN, ModeToWDSP(FMode)); SetRXAShiftRun(RXA_CHAN, 1); SetRXAShiftFreq(RXA_CHAN, 0.0); RXASetPassband(RXA_CHAN, FFilterLow, FFilterHigh); SetAGC(FAGCMode, 50.0); SetRXAPanelGain1(RXA_CHAN, FVolume); SetRXAPanelSelect(RXA_CHAN, 3); SetRXAPanelRun(RXA_CHAN, 1); SetChannelState(RXA_CHAN, 1, 0); OpenChannel( TXA_CHAN, FAudioBufSize, FAudioBufSize, FAudioRate, FAudioRate, FAudioRate, 1, 0, 0.010, 0.010, 0.010, 0.010, 0 ); SetTXAMode(TXA_CHAN, ModeToWDSP(FMode)); SetTXABandpassFreqs(TXA_CHAN, 100, 2800); SetTXABandpassWindow(TXA_CHAN, 1); SetTXALevelerSt(TXA_CHAN, 1); SetTXALevelerTop(TXA_CHAN, 5.0); SetTXAALCSt(TXA_CHAN, 1); SetTXAALCDecay(TXA_CHAN, 10); SetTXACompressorRun(TXA_CHAN, 0); SetTXAPanelGain1(TXA_CHAN, 1.0); SetTXAPanelRun(TXA_CHAN, 1); SetTXAPanelSelect(TXA_CHAN, 1); FInitialized := True; ApplyNoiseFilterState; Result := True; // Запускаем DSP поток после успешной инициализации WDSP // Аналог iq_thread_id = g_thread_new("iq thread", ...) в piHPSDR FDSPThread := TDSPThread.Create(Self); FDSPThread.Priority := tpHighest; // DSP критический поток except on E: Exception do begin FLastError := E.ClassName + ': ' + E.Message; FInitialized := False; Result := False; end; end; end; procedure TWDSPEngine.Close; begin if not FInitialized then Exit; // Останавливаем DSP поток перед закрытием WDSP каналов if Assigned(FDSPThread) then begin FDSPThread.Terminate; RTLEventSetEvent(FQueueSem); // разбудить чтобы увидел Terminated FDSPThread.WaitFor; FreeAndNil(FDSPThread); end; CloseAnalyzer; destroy_anbEXT(RXA_CHAN); destroy_nobEXT(RXA_CHAN); CloseChannel(RXA_CHAN); CloseChannel(TXA_CHAN); FInitialized := False; end; procedure TWDSPEngine.ChangeSampleRate(NewRate: Integer); // Меняет samplerate на лету: Close → сброс очереди → пересчёт буферов → Open. begin if NewRate = FSampleRate then Exit; // Останавливаем текущий канал Close; // Сбрасываем очередь IQ пакетов — старые пакеты не совместимы с новым rate. // Без этого DSP поток тратит секунды на разбор накопившихся пакетов. FQueueHead := 0; FQueueTail := 0; FRXAccPos := 0; // Обновляем параметры FSampleRate := NewRate; FBufSize := FAudioBufSize * NewRate div FAudioRate; // Перераспределяем буферы под новый размер SetLength(FRXIn, FBufSize * 2); SetLength(FRXOut, FBufSize * 2); SetLength(FSpecBuf, FBufSize * 2); SetLength(FRXAccI, FBufSize); SetLength(FRXAccQ, FBufSize); // Переинициализируем WDSP с новым rate Open; end; procedure TWDSPEngine.PushDDCPacket(const Buf: array of Byte; DataOffset: Integer; IQPairs: Integer); // Вызывается из СЕТЕВОГО потока — только кладём в очередь и возвращаемся немедленно // DSP поток разбудится семафором и займётся обработкой var NextHead: Integer; Item: ^TIQQueueItem; DataBytes: Integer; begin NextHead := (FQueueHead + 1) and (IQ_QUEUE_SIZE - 1); if NextHead = FQueueTail then Exit; // очередь полна — пропускаем пакет Item := @FQueue[FQueueHead]; DataBytes := IQPairs * 6; if DataBytes > IQ_PKT_MAXBYTES then DataBytes := IQ_PKT_MAXBYTES; Move(Buf[DataOffset], Item^.Data[0], DataBytes); Item^.DataLen := DataBytes; Item^.IQPairs := IQPairs; FQueueHead := NextHead; RTLEventSetEvent(FQueueSem); // будим DSP поток (как sem_post в piHPSDR) end; procedure TWDSPEngine.PushIQItemToDSP(const Item: TIQQueueItem); // Вызывается из DSP потока — декодирует IQ и накапливает до FBufSize var i, Pos: Integer; IR, QR: LongInt; const SCALE = 1.0 / 8388608.0; begin for i := 0 to Item.IQPairs - 1 do begin Pos := i * 6; if Pos + 5 >= Item.DataLen then Break; IR := (LongInt(Item.Data[Pos]) shl 16) or (LongInt(Item.Data[Pos+1]) shl 8) or LongInt(Item.Data[Pos+2]); QR := (LongInt(Item.Data[Pos+3]) shl 16) or (LongInt(Item.Data[Pos+4]) shl 8) or LongInt(Item.Data[Pos+5]); if (IR and $800000) <> 0 then IR := IR or LongInt($FF000000); if (QR and $800000) <> 0 then QR := QR or LongInt($FF000000); FRXAccI[FRXAccPos] := IR * SCALE; FRXAccQ[FRXAccPos] := QR * SCALE; Inc(FRXAccPos); if FRXAccPos >= FBufSize then ProcessRXBlock; end; end; procedure TWDSPEngine.ProcessRXBlock; var i: Integer; Err: Integer; // OutL/OutR теперь поля класса — не аллоцируем каждый раз begin FRXAccPos := 0; if not FInitialized then Exit; try // Упаковываем в interleaved буфер для fexchange0 for i := 0 to FBufSize - 1 do begin FRXIn[i * 2] := FRXAccI[i]; FRXIn[i * 2 + 1] := FRXAccQ[i]; end; // Копируем входные IQ ДО fexchange0 для Spectrum0 // fexchange0 in-place перезапишет FRXIn выходными данными @ 48kHz if FAnalyzerOpen then for i := 0 to FBufSize * 2 - 1 do FSpecBuf[i] := FRXIn[i]; // Single→Double, входные IQ @ 192kHz // DSP обработка Err := 0; fexchange0(RXA_CHAN, @FRXIn[0], @FRXOut[0], @Err); // Spectrum0 с входными данными (скопированными до fexchange0) if FAnalyzerOpen then Spectrum0(1, DISP_ID, 0, 0, @FSpecBuf[0]); // S-meter: читаем ОДИН раз в DSP-колбэке и кешируем в FSMeter. // GetRXAMeter сбрасывает аккумулятор после вызова, поэтому второй // вызов вернёт 0/-∞ — отсюда "падение до нуля". Только здесь! FSMeter := GetRXAMeter(RXA_CHAN, RXA_S_AV); // Аудио колбэк — FAudioBufSize сэмплов @ FAudioRate (после децимации 4:1) if Assigned(FOnAudio) and not FMuted then begin for i := 0 to FAudioBufSize - 1 do begin FOutL[i] := FRXOut[i * 2] * FVolume; FOutR[i] := FRXOut[i * 2 + 1] * FVolume; end; FOnAudio(FOutL, FOutR, FAudioBufSize); end; except // Защита от падения libwdsp в потоке — просто пропускаем блок end; end; // --------------------------------------------------------------------------- // RX управление // --------------------------------------------------------------------------- procedure TWDSPEngine.SetMode(Mode: Integer); begin FMode := Mode; if not FInitialized then Exit; SetRXAMode(RXA_CHAN, ModeToWDSP(Mode)); // НЕ вызываем ApplyDefaultFilter — фильтр устанавливается явно из UI end; procedure TWDSPEngine.SetFilter(Low, High: Integer); begin FFilterLow := Low; FFilterHigh := High; if not FInitialized then Exit; // RXASetPassband — правильный unified API, пересчитывает фильтр целиком RXASetPassband(RXA_CHAN, Low, High); end; procedure TWDSPEngine.SetAGC(Mode: TWDSPAGCMode; FixedGain: Double); begin FAGCMode := Mode; 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; // Читаем обратно hang level и thresh для линии на спектре UpdateAGCLines(FLastSpectrumW); end; procedure TWDSPEngine.SetShift(ShiftHz: Double); begin FShiftHz := ShiftHz; if not FInitialized then Exit; SetRXAShiftFreq(RXA_CHAN, ShiftHz); end; procedure TWDSPEngine.SetAGCTop(TopDBm: Double); begin FAGCTop := TopDBm; if not FInitialized then Exit; SetRXAAGCTop(RXA_CHAN, TopDBm); UpdateAGCLines(FLastSpectrumW); end; procedure TWDSPEngine.SetAGCSlope(Slope: Integer); begin FAGCSlope := Slope; if not FInitialized then Exit; SetRXAAGCSlope(RXA_CHAN, Slope); end; procedure TWDSPEngine.SetAGCHangThreshold(Threshold: Integer); begin FAGCHangThreshold := Threshold; if not FInitialized then Exit; SetRXAAGCHangThreshold(RXA_CHAN, Threshold); UpdateAGCLines(FLastSpectrumW); end; procedure TWDSPEngine.SetSpectrumWidth(W: Integer); begin if W > 0 then FLastSpectrumW := W; end; procedure TWDSPEngine.UpdateAGCLines(SpectrumW: Integer); begin if not FInitialized then Exit; if SpectrumW <= 0 then Exit; // окно ещё не показано — не читаем GetRXAAGCHangLevel(RXA_CHAN, @FAGCHangLevel); GetRXAAGCThresh(RXA_CHAN, @FAGCThresh, SpectrumW, // реальная ширина дисплея в пикселях FSampleRate); 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)); 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)); end; procedure TWDSPEngine.ApplySNBState; begin if not FInitialized then Exit; SetRXASNBARun(RXA_CHAN, Ord(FSNBEnabled)); end; procedure TWDSPEngine.ApplyANFState; begin if not FInitialized then Exit; SetRXAANFPosition(RXA_CHAN, FILTER_NR_AGC_POSITION); SetRXAANFRun(RXA_CHAN, Ord(FANFEnabled)); end; procedure TWDSPEngine.ApplyNoiseFilterState; begin ApplyNRState; ApplyNBState; ApplySNBState; ApplyANFState; end; procedure TWDSPEngine.SetNRMode(Mode: Integer); begin if Mode < NR_MODE_OFF then Mode := NR_MODE_OFF; if Mode > NR_MODE_4 then Mode := NR_MODE_4; FNRMode := Mode; ApplyNRState; end; procedure TWDSPEngine.SetNR(Enable: Boolean); begin if Enable then SetNRMode(NR_MODE_1) else SetNRMode(NR_MODE_OFF); end; procedure TWDSPEngine.SetNBMode(Mode: Integer); begin if Mode < NB_MODE_OFF then Mode := NB_MODE_OFF; if Mode > NB_MODE_2 then Mode := NB_MODE_2; FNBMode := Mode; ApplyNBState; end; procedure TWDSPEngine.SetNB(Enable: Boolean); begin if Enable then SetNBMode(NB_MODE_1) else SetNBMode(NB_MODE_OFF); end; procedure TWDSPEngine.SetSNB(Enable: Boolean); begin FSNBEnabled := Enable; ApplySNBState; end; procedure TWDSPEngine.SetANF(Enable: Boolean); begin FANFEnabled := Enable; ApplyANFState; end; procedure TWDSPEngine.SetVolume(Vol: Double); begin FVolume := Max(0.0, Min(1.0, Vol)); if not FInitialized then Exit; SetRXAPanelGain1(RXA_CHAN, FVolume); end; procedure TWDSPEngine.SetMute(Mute: Boolean); begin FMuted := Mute; if not FInitialized then Exit; SetRXAPanelRun(RXA_CHAN, Ord(not Mute)); end; // --------------------------------------------------------------------------- // TX управление // --------------------------------------------------------------------------- procedure TWDSPEngine.SetTXMode(Mode: Integer); begin if not FInitialized then Exit; SetTXAMode(TXA_CHAN, ModeToWDSP(Mode)); end; procedure TWDSPEngine.SetTXFilter(Low, High: Integer); begin if not FInitialized then Exit; SetTXABandpassFreqs(TXA_CHAN, Low, High); end; procedure TWDSPEngine.SetDriveLevel(Level: Double); begin if not FInitialized then Exit; SetTXAALCMaxGain(TXA_CHAN, Max(0.0, Min(1.0, Level))); end; procedure TWDSPEngine.SetMicGain(GainDB: Double); begin if not FInitialized then Exit; SetTXAPanelGain1(TXA_CHAN, Power(10.0, GainDB / 20.0)); end; procedure TWDSPEngine.SetTXRun(Run: Boolean); begin if not FInitialized then Exit; if Run then SetChannelState(TXA_CHAN, 1, 0) // run, no delay else SetChannelState(TXA_CHAN, 0, 1); // stop, with slew end; // --------------------------------------------------------------------------- // Spectrum // --------------------------------------------------------------------------- procedure TWDSPEngine.UpdateSpectrum; // Вызывается из таймера главного потока (~20fps) // Spectrum0 уже вызван в сетевом потоке после каждого fexchange0 // Здесь только читаем готовые пиксели var PixBuf: array[0..SPECTRUM_PIXELS - 1] of Single; Flag: Integer; i: Integer; begin if not FInitialized or not FAnalyzerOpen then Exit; Flag := 0; GetPixels(DISP_ID, 0, @PixBuf[0], @Flag); if Flag = 0 then Exit; // нет нового кадра — ждём следующего тика for i := 0 to SPECTRUM_PIXELS - 1 do FSpectrumPixels[i] := PixBuf[i]; if Assigned(FOnSpectrum) then FOnSpectrum(FSpectrumPixels, SPECTRUM_PIXELS); end; procedure TWDSPEngine.GetSpectrumData(var Pixels: array of Single; var Count: Integer); var N: Integer; begin N := Min(SPECTRUM_PIXELS, Length(Pixels)); if N > 0 then Move(FSpectrumPixels[0], Pixels[0], N * SizeOf(Single)); Count := N; end; function TWDSPEngine.GetSMeterDBm: Double; begin // НЕ вызываем GetRXAMeter здесь повторно — значение уже обновлено // в DSP-колбэке. Повторный вызов сбрасывает аккумулятор WDSP → 0. Result := FSMeter; end; end.