This commit is contained in:
2026-03-08 22:34:18 +03:00
parent 811c96b36c
commit d347a26d6d
5 changed files with 519 additions and 546 deletions
+53 -86
View File
@@ -145,6 +145,7 @@ type
FANFEnabled: Boolean;
FMuted: Boolean;
FVolume: Double;
FLastError: string;
function ModeToWDSP(Mode: Integer): Integer;
procedure ApplyDefaultFilter;
@@ -211,6 +212,7 @@ type
property Mode: Integer read FMode;
property FilterLow: Integer read FFilterLow;
property FilterHigh: Integer read FFilterHigh;
property LastError: string read FLastError;
property SMeter: Double read FSMeter;
property OnAudio: TOnAudioReady read FOnAudio write FOnAudio;
property OnSpectrum: TOnSpectrumReady read FOnSpectrum write FOnSpectrum;
@@ -349,69 +351,31 @@ end;
procedure TWDSPEngine.OpenAnalyzer;
var
Success: Integer;
MaxW: Integer;
Ovrlp: Integer;
Success: Integer;
MaxW: Integer;
Ovrlp: Integer;
FRAME_RATE: Integer;
begin
if FAnalyzerOpen then Exit;
// ---- Шаг 1: создать анализатор ----
// success = 0 означает УСПЕХ (WDSP Guide p.119)
// m_size = максимальный FFT размер (должен быть степенью 2, ≤ 262144)
// ОПТИМИЗАЦИЯ: 262144 → 16384. WDSP аллоцирует и инициализирует twiddle-таблицы
// для ВСЕХ степеней 2 вплоть до m_size при каждом вызове XCreateAnalyzer.
// Реальный рабочий FFT = 4096, запас ×4 = 16384 достаточен для будущих апгрейдов.
// Эффект: время открытия WDSP ~10 сек → ~1 сек.
// m_LO = 1 (нет spur elimination)
// m_stitch = 1 (один sub-span)
Success := -1;
XCreateAnalyzer(DISP_ID, @Success, 16384, 1, 1, nil);
if Success <> 0 then
begin
// Анализатор не создан — работаем без спектра, краша не будет
Exit;
end;
if Success <> 0 then Exit;
// ---- Шаг 2: SetAnalyzer ----
// Рассчитываем параметры по документации (WDSP Guide p.120-121)
FRAME_RATE := 20; // 20 fps
// max_w = fft_size + min(KEEP_TIME*sample_rate, KEEP_TIME*fft_size*frame_rate)
// KEEP_TIME = 0.1
// min(0.1*192000, 0.1*4096*20) = min(19200, 8192) = 8192
MaxW := 4096 + 8192; // = 12288
// ovrlp = max(0, ceil(fft_size - sample_rate/frame_rate))
// = max(0, ceil(4096 - 192000/20)) = max(0, 4096-9600) = 0
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; // LO на нижней стороне, нет spur elimination
SetAnalyzer(
DISP_ID,
1, // n_pixout: 1 (один выход — панадаптер и водопад одинаковые)
1, // n_fft: 1 (без spur elimination)
1, // typ: 1 = COMPLEX (I+Q)
@FFlp[0], // flp: [0] — LO ниже сигнала
4096, // sz: размер FFT
FAudioBufSize, // bf_sz: блок данных @ AudioRate = dsp_size (1024 @ 48kHz)
1, // win_type: 1 = 4-term Blackman-Harris
14.0, // pi: Kaiser beta (не используется при BH)
Ovrlp, // ovrlp: перекрытие = 0
0, // clp: обрезка краёв = 0
0.0, // fscLin: pan left = 0 (полный диапазон)
0.0, // fscHin: pan right = 0 (полный диапазон)
SPECTRUM_PIXELS, // n_pix: пикселей на выходе GetPixels
1, // n_stch: 1 sub-span (нет stitching)
0, // calset: нет калибровки
0.0, // fmin
0.0, // fmax
MaxW // max_w: рассчитан выше = 12288
);
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);
// ---- Шаг 3: настройка детектора и усреднения ----
// DETECTOR_MODE_AVERAGE + LOG_RECURSIVE — плавный, как в piHPSDR/Thetis
// backmult ~0.45 = инерция ~2.2 кадра → плавно, без смазывания быстрых сигналов
// NumAverage = FRAME_RATE * 0.12 ≈ 2..3 при 20fps — достаточно для LOG_RECURSIVE
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)));
@@ -434,66 +398,65 @@ end;
// ---------------------------------------------------------------------------
function TWDSPEngine.Open: Boolean;
var
DllPath: string;
begin
Result := False;
FLastError := '';
if FInitialized then Exit;
// Проверяем что libwdsp загружена (WEAKEXTERNALSYMBOLS: nil если нет DLL)
if not Assigned(@OpenChannel) then
begin
// wdsp.dll / libwdsp.so не найдена — демо-режим
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 и гарантирует что DISP_ID готов
// до того как DSP поток начнёт вызывать Spectrum0.
// Анализатор ПЕРВЫМ — до OpenChannel (порядок как в piHPSDR)
OpenAnalyzer;
// НЕ используем SetRXASpectrum — кормим анализатор вручную
// через Spectrum0 с Double буфером, чтобы избежать SIGSEGV
// (fexchange0 работает с Single, а внутри WDSP Spectrum2 ждёт Double)
if not FAnalyzerOpen then
begin
FLastError := 'XCreateAnalyzer failed (WDSP init failed).';
Exit;
end;
// ---- RXA channel 0 ----
// ОПТИМИЗАЦИЯ: state=0 (не запускать сразу) — настраиваем все параметры
// вхолостую, только потом стартуем через SetChannelState.
// Канал не тратит CPU на обработку "мусорных" данных в процессе настройки.
OpenChannel(
RXA_CHAN,
FBufSize, // in_size @ FSampleRate (4096 @ 192kHz)
FAudioBufSize, // dsp_size @ AudioRate (1024 @ 48kHz) — как piHPSDR buffer_size
FBufSize,
FAudioBufSize,
FSampleRate,
FAudioRate, // dsp_rate = 48kHz
FAudioRate, // out_rate = 48kHz
0, // atype: 0=RX
0, // state: 0=hold (запустим после настройки всех параметров)
FAudioRate,
FAudioRate,
0,
0, // state=0: создать но не запускать
0.010, 0.010, 0.010, 0.010,
0
);
SetRXAMode(RXA_CHAN, ModeToWDSP(FMode));
SetRXAShiftRun(RXA_CHAN, 1); // включаем NCO шифтер (нужен для CTUN)
SetRXAShiftFreq(RXA_CHAN, 0.0); // по умолчанию сдвига нет
SetRXAShiftRun(RXA_CHAN, 1);
SetRXAShiftFreq(RXA_CHAN, 0.0);
RXASetPassband(RXA_CHAN, FFilterLow, FFilterHigh);
SetAGC(FAGCMode, 50.0); // настраиваем AGC с полными параметрами piHPSDR
SetAGC(FAGCMode, 50.0);
SetRXAPanelGain1(RXA_CHAN, FVolume);
SetRXAPanelSelect(RXA_CHAN, 3);
SetRXAPanelRun(RXA_CHAN, 1);
SetChannelState(RXA_CHAN, 1, 0);
// Запускаем RXA канал только после полной настройки
SetChannelState(RXA_CHAN, 1, 0); // run, no delay-wait
// ---- TXA channel 1 ----
OpenChannel(
TXA_CHAN,
FBufSize,
FBufSize,
FAudioBufSize,
FAudioBufSize,
FAudioRate,
48000,
FSampleRate,
1, // atype: 1=TX
0, // state: 0=hold
FAudioRate,
FAudioRate,
1,
0,
0.010, 0.010, 0.010, 0.010,
0
);
@@ -519,8 +482,12 @@ begin
FDSPThread.Priority := tpHighest; // DSP критический поток
except
FInitialized := False;
Result := False;
on E: Exception do
begin
FLastError := E.ClassName + ': ' + E.Message;
FInitialized := False;
Result := False;
end;
end;
end;