fix fft, waterfall

This commit is contained in:
2026-04-22 22:09:42 +03:00
parent ae532a33cd
commit 77d380acfd
7 changed files with 1016 additions and 396 deletions
+332 -85
View File
@@ -98,6 +98,7 @@ const
DISP_ID = 0; // ID для XCreateAnalyzer
DSP_BUFSIZE = 1024;
SPECTRUM_PIXELS = 1024;
DISPLAY_BLOCK_SIZE = 64;
// Очередь IQ пакетов между сетевым и DSP потоком
IQ_QUEUE_SIZE = 64; // кол-во слотов (степень двойки для AND-маски)
@@ -150,11 +151,15 @@ type
// Копия входных IQ для Spectrum0 (до fexchange0, который данные in-place перезаписывает)
FSpecBuf: array of Double;
// Отдельный мелкий feed для display-анализатора, как в Thetis spec_blocksize.
FDispBuf: array of Double;
FDispPos: Integer;
// Аудио выходные буферы — аллоцируем один раз
FOutL: array of Single;
FOutR: array of Single;
// DSP поток + очередь пакетов
FDSPThread: TThread;
FDisplayThread:TThread;
FQueue: array[0..IQ_QUEUE_SIZE - 1] of TIQQueueItem;
FQueueHead: Integer; // пишет сетевой поток
FQueueTail: Integer; // читает DSP поток
@@ -168,14 +173,15 @@ type
// Настройки анализатора
FFFTSize: Integer;
FWindowType: Integer;
FDisplayFPS: Integer;
FEffectiveDisplayFPS: Double;
FDisplayPixelCount: Integer;
FSpecDetector: Integer;
FSpecAvgMode: Integer;
FSpecAvgCount: Integer;
FSpecBackmult: Double;
FSpecAvgTimeMS: Double;
FWfDetector: Integer;
FWfAvgMode: Integer;
FWfAvgCount: Integer;
FWfBackmult: Double;
FWfAvgTimeMS: Double;
FOnAudio: TOnAudioReady;
FOnSpectrum: TOnSpectrumReady;
@@ -213,11 +219,20 @@ type
function ModeToWDSP(Mode: Integer): Integer;
procedure ApplyDefaultFilter;
procedure FeedDisplaySample(const I, Q: Double);
procedure ProcessRXBlock;
procedure PushIQItemToDSP(const Item: TIQQueueItem);
procedure OpenAnalyzer;
procedure CloseAnalyzer;
procedure ApplyAnalyzerSettings;
function CalcDisplayFFTSize: Integer;
procedure CalcAnalyzerTiming(AnalyzerFFT: Integer; out Overlap: Integer;
out EffectiveFPS: Double);
function NormalizeFFTSize(FFTSize: Integer): Integer;
procedure NormalizeDisplayParams(var Detector, AvgMode: Integer;
var AvgTimeMS: Double; ForWaterfall: Boolean);
procedure AverageTimeToParams(AvgMode: Integer; AvgTimeMS: Double;
out AvgCount: Integer; out Backmult: Double);
procedure ApplyNRState;
procedure ApplyNBState;
procedure ApplySNBState;
@@ -274,8 +289,9 @@ type
// Настройки анализатора (применяются немедленно если открыт)
procedure SetFFTParams(FFTSize, WinType: Integer);
procedure SetSpectrumDisplay(Detector, AvgMode, AvgCount: Integer; Backmult: Double);
procedure SetWaterfallDisplay(Detector, AvgMode, AvgCount: Integer; Backmult: Double);
procedure SetDisplayFPS(FPS: Integer);
procedure SetSpectrumDisplay(Detector, AvgMode: Integer; AvgTimeMS: Double);
procedure SetWaterfallDisplay(Detector, AvgMode: Integer; AvgTimeMS: Double);
// Spectrum — вызывать из таймера (~20 fps)
procedure UpdateSpectrum;
@@ -304,12 +320,10 @@ type
property WindowType: Integer read FWindowType;
property SpecDetector: Integer read FSpecDetector;
property SpecAvgMode: Integer read FSpecAvgMode;
property SpecAvgCount: Integer read FSpecAvgCount;
property SpecBackmult: Double read FSpecBackmult;
property SpecAvgTimeMS: Double read FSpecAvgTimeMS;
property WfDetector: Integer read FWfDetector;
property WfAvgMode: Integer read FWfAvgMode;
property WfAvgCount: Integer read FWfAvgCount;
property WfBackmult: Double read FWfBackmult;
property WfAvgTimeMS: Double read FWfAvgTimeMS;
property OnAudio: TOnAudioReady read FOnAudio write FOnAudio;
property OnSpectrum: TOnSpectrumReady read FOnSpectrum write FOnSpectrum;
property OnWaterfall: TOnWaterfallReady read FOnWaterfall write FOnWaterfall;
@@ -325,6 +339,15 @@ implementation
// Аналог tx_thread в piHPSDR/transmitter.c
// ===========================================================================
type
TDisplayThread = class(TThread)
private
FEngine: TWDSPEngine;
protected
procedure Execute; override;
public
constructor Create(AEngine: TWDSPEngine);
end;
TTXDSPThread = class(TThread)
private
FEngine: TWDSPEngine;
@@ -334,6 +357,48 @@ type
constructor Create(AEngine: TWDSPEngine);
end;
function UsePanNormOneHz(Detector: Integer): Integer;
begin
// В Thetis нормализация к 1 Гц включается только для panadapter
// и только для detector modes 2/3/4.
if Detector in [2, 3, 4] then
Result := 1
else
Result := 0;
end;
constructor TDisplayThread.Create(AEngine: TWDSPEngine);
begin
FEngine := AEngine;
FreeOnTerminate := False;
inherited Create(False);
end;
procedure TDisplayThread.Execute;
var
NextTick: QWord;
NowTick: QWord;
DelayMS: Integer;
begin
NextTick := GetTickCount64;
while not Terminated do
begin
if FEngine.FInitialized and FEngine.FAnalyzerOpen then
FEngine.UpdateSpectrum;
Inc(NextTick, QWord(Max(1, Round(1000.0 / Max(1, FEngine.FDisplayFPS)))));
NowTick := GetTickCount64;
if NextTick > NowTick then
begin
DelayMS := Integer(NextTick - NowTick);
if DelayMS > 0 then
Sleep(DelayMS);
end
else
NextTick := NowTick;
end;
end;
constructor TTXDSPThread.Create(AEngine: TWDSPEngine);
begin
FEngine := AEngine;
@@ -342,24 +407,44 @@ begin
end;
procedure TTXDSPThread.Execute;
// Аналог tx_thread в piHPSDR/transmitter.c:
// Тикает с периодом одного mic-буфера (FAudioBufSize/FAudioRate).
// Если mic-данные накопились — обрабатывает их; если нет — обрабатывает тишину.
// Это обеспечивает непрерывный поток DUC IQ к железу и TX-сигнал на спектре/водопаде.
var
Avail: Integer;
Avail: Integer;
PeriodMs: Integer;
begin
// 512 сэмплов / 48000 Гц = ~10.67 мс → округляем вниз до 10 мс
PeriodMs := Max(1, FEngine.FAudioBufSize * 1000 div FEngine.FAudioRate);
while not Terminated do
begin
RTLEventWaitFor(FEngine.FTXMicSem, 50);
// Ждём сигнала от mic-данных ИЛИ истечения периода —
// в piHPSDR timing идёт от receive thread; здесь — от таймаута
RTLEventWaitFor(FEngine.FTXMicSem, PeriodMs);
if Terminated then Break;
if not FEngine.FTXActive then Continue;
// Обрабатываем все накопившиеся блоки
// Обрабатываем накопившиеся блоки с реальными mic-данными
repeat
Avail := (FEngine.FTXMicHead - FEngine.FTXMicTail + TX_MIC_RING)
and (TX_MIC_RING - 1);
if Avail >= FEngine.FAudioBufSize then
if (Avail >= FEngine.FAudioBufSize) and not Terminated then
FEngine.ProcessTXBlock
else
Break;
until Terminated;
until False;
if Terminated then Break;
// Если mic-данных не было — всё равно обрабатываем один блок тишины.
// Аналог piHPSDR: если mic_ring пуст, tx_add_mic_sample получает 0.0.
// Это держит DUC IQ поток и обновляет спектр/водопад TX-сигналом.
Avail := (FEngine.FTXMicHead - FEngine.FTXMicTail + TX_MIC_RING)
and (TX_MIC_RING - 1);
if Avail < FEngine.FAudioBufSize then
FEngine.ProcessTXBlock;
end;
end;
@@ -467,15 +552,16 @@ begin
FRXAccPos := 0;
// Analyzer defaults
FFFTSize := 4096;
FWindowType := 5; // Kaiser
FWindowType := 2; // Hann
FDisplayFPS := 20;
FEffectiveDisplayFPS := 20.0;
FDisplayPixelCount := SPECTRUM_PIXELS;
FSpecDetector := 0; // Peak
FSpecAvgMode := 3; // Log Recursive
FSpecAvgCount := 2;
FSpecBackmult := 0.45;
FWfDetector := 2; // Average
FSpecAvgTimeMS := 30.0;
FWfDetector := 0; // Peak
FWfAvgMode := 3; // Log Recursive
FWfAvgCount := 2;
FWfBackmult := 0.45;
FWfAvgTimeMS := 120.0;
SetLength(FRXIn, FBufSize * 2); // in_size пар @ FSampleRate (4096*2)
SetLength(FRXOut, FAudioBufSize * 2); // out_size пар @ FAudioRate (1024*2)
@@ -484,6 +570,8 @@ begin
SetLength(FRXAccI, FBufSize);
SetLength(FRXAccQ, FBufSize);
SetLength(FSpecBuf, FBufSize * 2);
SetLength(FDispBuf, DISPLAY_BLOCK_SIZE * 2);
FDispPos := 0;
SetLength(FOutL, FAudioBufSize);
SetLength(FOutR, FAudioBufSize);
@@ -493,6 +581,7 @@ begin
FDSPRunning := False;
FQueueSem := RTLEventCreate;
FDSPThread := nil;
FDisplayThread := nil;
// TX инициализация
FTXMicHead := 0;
@@ -523,47 +612,75 @@ begin
if FAnalyzerOpen then Exit;
Success := -1;
XCreateAnalyzer(DISP_ID, @Success, 16384, 1, 1, nil);
XCreateAnalyzer(DISP_ID, @Success, 262144, 1, 1, nil);
if Success <> 0 then Exit;
FFlp[0] := 0;
ApplyAnalyzerSettings;
FAnalyzerOpen := True;
if FDisplayThread = nil then
FDisplayThread := TDisplayThread.Create(Self);
end;
procedure TWDSPEngine.ApplyAnalyzerSettings;
// Вызывается из OpenAnalyzer и при изменении FFT/window/detector/avg параметров
var
AnalyzerFFT: Integer;
AvgCount: Integer;
Backmult: Double;
WaterfallAvgCount: Integer;
WaterfallBackmult: Double;
MaxW: Integer;
Overlap: Integer;
const
KEEP_TIME_SEC = 0.10;
var
DisplaySampleRate: Integer;
DisplayPixels: Integer;
begin
// MaxW = FFTSize — минимально допустимо (piHPSDR approach)
MaxW := FFFTSize;
// FFT Size пользователя управляет именно display-analyzer, как в Thetis,
// но визуальная нормализация должна идти к ширине видимого окна, как в piHPSDR.
AnalyzerFFT := CalcDisplayFFTSize;
FDispPos := 0;
MaxW := AnalyzerFFT + Round(Min(KEEP_TIME_SEC * FSampleRate,
KEEP_TIME_SEC * AnalyzerFFT * EnsureRange(FDisplayFPS, 5, 100)));
CalcAnalyzerTiming(AnalyzerFFT, Overlap, FEffectiveDisplayFPS);
DisplayPixels := EnsureRange(FLastSpectrumW, 64, SPECTRUM_PIXELS);
FDisplayPixelCount := DisplayPixels;
DisplaySampleRate := FSampleRate;
// n_pixout=2: pixout=0 — спектр, pixout=1 водопад
SetAnalyzer(DISP_ID, 2, 1, 1, @FFlp[0], FFFTSize, FAudioBufSize, FWindowType, 14.0,
0, 0, 0.0, 0.0, SPECTRUM_PIXELS, 1, 0, 0.0, 0.0, MaxW);
// Раздельные выходы display: pixout=0 панорама, pixout=1 водопад.
SetAnalyzer(DISP_ID, 2, 1, 1, @FFlp[0], AnalyzerFFT, DISPLAY_BLOCK_SIZE, FWindowType, 14.0,
Overlap, 0, 0.0, 0.0, DisplayPixels, 1, 0, 0.0, 0.0, MaxW);
// Спектр (pixout=0)
AverageTimeToParams(FSpecAvgMode, FSpecAvgTimeMS, AvgCount, Backmult);
AverageTimeToParams(FWfAvgMode, FWfAvgTimeMS, WaterfallAvgCount, WaterfallBackmult);
SetDisplayDetectorMode(DISP_ID, 0, FSpecDetector);
SetDisplayAverageMode(DISP_ID, 0, FSpecAvgMode);
SetDisplayNumAverage(DISP_ID, 0, FSpecAvgCount);
SetDisplayAvBackmult(DISP_ID, 0, FSpecBackmult);
SetDisplayNormOneHz(DISP_ID, 0, 0);
SetDisplayNumAverage(DISP_ID, 0, AvgCount);
SetDisplayAvBackmult(DISP_ID, 0, Backmult);
SetDisplayNormOneHz(DISP_ID, 0, UsePanNormOneHz(FSpecDetector));
// Водопад (pixout=1)
SetDisplayDetectorMode(DISP_ID, 1, FWfDetector);
SetDisplayAverageMode(DISP_ID, 1, FWfAvgMode);
SetDisplayNumAverage(DISP_ID, 1, FWfAvgCount);
SetDisplayAvBackmult(DISP_ID, 1, FWfBackmult);
SetDisplayNumAverage(DISP_ID, 1, WaterfallAvgCount);
SetDisplayAvBackmult(DISP_ID, 1, WaterfallBackmult);
SetDisplayNormOneHz(DISP_ID, 1, 0);
SetDisplaySampleRate(DISP_ID, FSampleRate);
// В Thetis сюда идёт реальный sample rate приёмника.
SetDisplaySampleRate(DISP_ID, DisplaySampleRate);
end;
procedure TWDSPEngine.CloseAnalyzer;
begin
if not FAnalyzerOpen then Exit;
if Assigned(FDisplayThread) then
begin
FDisplayThread.Terminate;
FDisplayThread.WaitFor;
FreeAndNil(FDisplayThread);
end;
DestroyAnalyzer(DISP_ID);
FAnalyzerOpen := False;
end;
@@ -719,6 +836,8 @@ begin
SetLength(FSpecBuf, FBufSize * 2);
SetLength(FRXAccI, FBufSize);
SetLength(FRXAccQ, FBufSize);
SetLength(FDispBuf, DISPLAY_BLOCK_SIZE * 2);
FDispPos := 0;
// Переинициализируем WDSP с новым rate
Open;
@@ -771,6 +890,8 @@ begin
if (IR and $800000) <> 0 then IR := IR or LongInt($FF000000);
if (QR and $800000) <> 0 then QR := QR or LongInt($FF000000);
FeedDisplaySample(IR * SCALE, QR * SCALE);
FRXAccI[FRXAccPos] := IR * SCALE;
FRXAccQ[FRXAccPos] := QR * SCALE;
Inc(FRXAccPos);
@@ -780,6 +901,22 @@ begin
end;
end;
procedure TWDSPEngine.FeedDisplaySample(const I, Q: Double);
begin
if not FAnalyzerOpen then Exit;
if FTXActive then Exit;
FDispBuf[FDispPos * 2] := I;
FDispBuf[FDispPos * 2 + 1] := Q;
Inc(FDispPos);
if FDispPos >= DISPLAY_BLOCK_SIZE then
begin
Spectrum0(1, DISP_ID, 0, 0, @FDispBuf[0]);
FDispPos := 0;
end;
end;
procedure TWDSPEngine.ProcessRXBlock;
var
i: Integer;
@@ -797,21 +934,10 @@ begin
FRXIn[i * 2 + 1] := FRXAccQ[i];
end;
// Копируем IQ в FSpecBuf ДО fexchange0: fexchange0 in-place перезаписывает FRXIn
if FAnalyzerOpen then
begin
Move(FRXIn[0], FSpecBuf[0], FBufSize * 2 * SizeOf(Double));
end;
// DSP обработка
Err := 0;
fexchange0(RXA_CHAN, @FRXIn[0], @FRXOut[0], @Err);
// Подаём RX IQ в анализатор только когда не передаём.
// Во время TX анализатор кормит ProcessTXBlock (TX сигнал на той же частоте).
if FAnalyzerOpen and not FTXActive then
Spectrum0(1, DISP_ID, 0, 0, @FSpecBuf[0]);
// S-meter: читаем ОДИН раз в DSP-колбэке и кешируем в FSMeter.
// GetRXAMeter сбрасывает аккумулятор после вызова, поэтому второй
// вызов вернёт 0/-∞ — отсюда "падение до нуля". Только здесь!
@@ -933,7 +1059,11 @@ end;
procedure TWDSPEngine.SetSpectrumWidth(W: Integer);
begin
if W > 0 then FLastSpectrumW := W;
if W <= 0 then Exit;
if FLastSpectrumW = W then Exit;
FLastSpectrumW := W;
if FAnalyzerOpen then
ApplyAnalyzerSettings;
end;
procedure TWDSPEngine.UpdateAGCLines(SpectrumW: Integer);
@@ -1104,6 +1234,7 @@ end;
procedure TWDSPEngine.SetTXRun(Run: Boolean);
begin
FTXActive := Run;
FDispPos := 0;
if Run then
begin
@@ -1135,22 +1266,35 @@ end;
procedure TWDSPEngine.ProcessTXBlock;
// Вызывается из TTXDSPThread — обрабатывает один блок FAudioBufSize mic сэмплов
// через WDSP TXA, выдаёт FBufSize IQ пар @ FSampleRate в FOnTXIQ callback
// через WDSP TXA, выдаёт FBufSize IQ пар @ FSampleRate в FOnTXIQ callback.
// Аналог tx_full_buffer() в piHPSDR/transmitter.c:
// если mic ring buffer содержит достаточно данных — берём их,
// иначе обрабатываем тишину (нули), как piHPSDR при mic_sample=0.0.
var
i, tail: Integer;
Err: Integer;
Avail: Integer;
begin
if not FInitialized then Exit;
// Читаем FAudioBufSize сэмплов из ring buffer → FTXIn
tail := FTXMicTail;
for i := 0 to FAudioBufSize - 1 do
Avail := (FTXMicHead - FTXMicTail + TX_MIC_RING) and (TX_MIC_RING - 1);
if Avail >= FAudioBufSize then
begin
FTXIn[i * 2] := FTXMicRing[tail];
FTXIn[i * 2 + 1] := 0.0; // Q = 0, mic — монофонический сигнал
tail := (tail + 1) and (TX_MIC_RING - 1);
// Читаем FAudioBufSize сэмплов из ring buffer → FTXIn
tail := FTXMicTail;
for i := 0 to FAudioBufSize - 1 do
begin
FTXIn[i * 2] := FTXMicRing[tail];
FTXIn[i * 2 + 1] := 0.0; // Q = 0, mic — монофонический сигнал
tail := (tail + 1) and (TX_MIC_RING - 1);
end;
FTXMicTail := tail; // Атомарно обновляем tail (один writer — TX thread)
end else
begin
// Нет mic-данных — тишина (аналог piHPSDR: tx->mic_input_buffer заполняется нулями)
FillChar(FTXIn[0], FAudioBufSize * 2 * SizeOf(Double), 0);
end;
FTXMicTail := tail; // Атомарно обновляем tail (один writer — TX thread)
Err := 0;
fexchange0(TXA_CHAN, @FTXIn[0], @FTXOut[0], @Err);
@@ -1205,30 +1349,29 @@ procedure TWDSPEngine.UpdateSpectrum;
var
PixBuf: array[0..SPECTRUM_PIXELS - 1] of Single;
Flag: Integer;
i: Integer;
i, N: Integer;
begin
if not FInitialized or not FAnalyzerOpen then Exit;
N := EnsureRange(FDisplayPixelCount, 1, SPECTRUM_PIXELS);
// Спектр (pixout=0)
Flag := 0;
GetPixels(DISP_ID, 0, @PixBuf[0], @Flag);
if Flag <> 0 then
begin
for i := 0 to SPECTRUM_PIXELS - 1 do
for i := 0 to N - 1 do
FSpectrumPixels[i] := PixBuf[i];
if Assigned(FOnSpectrum) then
FOnSpectrum(FSpectrumPixels, SPECTRUM_PIXELS);
FOnSpectrum(FSpectrumPixels, N);
end;
// Водопад (pixout=1)
Flag := 0;
GetPixels(DISP_ID, 1, @PixBuf[0], @Flag);
if Flag <> 0 then
begin
for i := 0 to SPECTRUM_PIXELS - 1 do
for i := 0 to N - 1 do
FWaterfallPixels[i] := PixBuf[i];
if Assigned(FOnWaterfall) then
FOnWaterfall(FWaterfallPixels, SPECTRUM_PIXELS);
FOnWaterfall(FWaterfallPixels, N);
end;
end;
@@ -1254,46 +1397,150 @@ end;
// Настройки анализатора (применяются немедленно)
// ---------------------------------------------------------------------------
procedure TWDSPEngine.SetFFTParams(FFTSize, WinType: Integer);
function TWDSPEngine.NormalizeFFTSize(FFTSize: Integer): Integer;
begin
// Ограничения: FFT size должен быть >= bf_sz (FAudioBufSize = 1024)
// и степенью двойки, не превышать m_size=16384 (из XCreateAnalyzer)
if FFTSize < FAudioBufSize then FFTSize := FAudioBufSize;
if FFTSize > 16384 then FFTSize := 16384;
// Thetis-style display FFT range.
if FFTSize < 4096 then FFTSize := 4096;
Result := 4096;
while Result < FFTSize do
Result := Result shl 1;
if Result > 262144 then
Result := 262144;
end;
function TWDSPEngine.CalcDisplayFFTSize: Integer;
begin
// FFT Size теперь снова управляет display-analyzer как в Thetis,
// но не влияет на аудио DSP цепочку.
Result := FFFTSize;
end;
procedure TWDSPEngine.CalcAnalyzerTiming(AnalyzerFFT: Integer; out Overlap: Integer;
out EffectiveFPS: Double);
var
TargetFPS: Double;
begin
TargetFPS := EnsureRange(FDisplayFPS, 5, 100);
// Thetis/piHPSDR-style timing:
// целимся в пользовательский FPS и разрешаем большой overlap даже на крупных FFT.
Overlap := Max(0, Ceil(AnalyzerFFT - (FSampleRate / TargetFPS)));
EffectiveFPS := TargetFPS;
end;
procedure TWDSPEngine.NormalizeDisplayParams(var Detector, AvgMode: Integer;
var AvgTimeMS: Double; ForWaterfall: Boolean);
begin
Detector := EnsureRange(Detector, 0, 4);
if ForWaterfall then
begin
// Peak hold на водопаде даёт грязную/залипающую картинку и не похож на Thetis.
if AvgMode < 0 then AvgMode := 1;
end
else
if AvgMode < -1 then AvgMode := -1;
if AvgMode > 3 then AvgMode := 3;
if AvgMode <= 0 then
AvgTimeMS := 0.0
else
AvgTimeMS := EnsureRange(AvgTimeMS, 10.0, 5000.0);
end;
procedure TWDSPEngine.AverageTimeToParams(AvgMode: Integer; AvgTimeMS: Double;
out AvgCount: Integer; out Backmult: Double);
var
TimeSec: Double;
Frames: Double;
FPSv: Double;
begin
if AvgMode <= 0 then
begin
AvgCount := 1;
Backmult := 0.0;
Exit;
end;
TimeSec := EnsureRange(AvgTimeMS, 10.0, 5000.0) * 0.001;
FPSv := Max(1.0, FEffectiveDisplayFPS);
Frames := FPSv * TimeSec;
AvgCount := EnsureRange(Round(Frames), 2, 60);
Backmult := Exp(-1.0 / Max(1E-6, FPSv * TimeSec));
Backmult := EnsureRange(Backmult, 0.0, 0.9999);
end;
procedure TWDSPEngine.SetFFTParams(FFTSize, WinType: Integer);
var
NewFFT: Integer;
begin
NewFFT := NormalizeFFTSize(FFTSize);
if WinType < 0 then WinType := 0;
if WinType > 6 then WinType := 6;
FFFTSize := FFTSize;
if (FFFTSize = NewFFT) and (FWindowType = WinType) then Exit;
FFFTSize := NewFFT;
FWindowType := WinType;
if FAnalyzerOpen then
ApplyAnalyzerSettings;
end;
procedure TWDSPEngine.SetSpectrumDisplay(Detector, AvgMode, AvgCount: Integer;
Backmult: Double);
procedure TWDSPEngine.SetDisplayFPS(FPS: Integer);
begin
FSpecDetector := Detector;
FSpecAvgMode := AvgMode;
FSpecAvgCount := AvgCount;
FSpecBackmult := Backmult;
if not FAnalyzerOpen then Exit;
SetDisplayDetectorMode(DISP_ID, 0, FSpecDetector);
SetDisplayAverageMode(DISP_ID, 0, FSpecAvgMode);
SetDisplayNumAverage(DISP_ID, 0, FSpecAvgCount);
SetDisplayAvBackmult(DISP_ID, 0, FSpecBackmult);
FPS := EnsureRange(FPS, 1, 100);
if FDisplayFPS = FPS then Exit;
FDisplayFPS := FPS;
if FAnalyzerOpen then
ApplyAnalyzerSettings;
end;
procedure TWDSPEngine.SetWaterfallDisplay(Detector, AvgMode, AvgCount: Integer;
Backmult: Double);
procedure TWDSPEngine.SetSpectrumDisplay(Detector, AvgMode: Integer;
AvgTimeMS: Double);
var
AvgCount: Integer;
Backmult: Double;
begin
NormalizeDisplayParams(Detector, AvgMode, AvgTimeMS, False);
if (FSpecDetector = Detector) and
(FSpecAvgMode = AvgMode) and
(Abs(FSpecAvgTimeMS - AvgTimeMS) < 1E-6) then Exit;
FSpecDetector := Detector;
FSpecAvgMode := AvgMode;
FSpecAvgTimeMS := AvgTimeMS;
if not FAnalyzerOpen then Exit;
AverageTimeToParams(FSpecAvgMode, FSpecAvgTimeMS, AvgCount, Backmult);
SetDisplayDetectorMode(DISP_ID, 0, FSpecDetector);
SetDisplayAverageMode(DISP_ID, 0, FSpecAvgMode);
SetDisplayNumAverage(DISP_ID, 0, AvgCount);
SetDisplayAvBackmult(DISP_ID, 0, Backmult);
SetDisplayNormOneHz(DISP_ID, 0, UsePanNormOneHz(FSpecDetector));
end;
procedure TWDSPEngine.SetWaterfallDisplay(Detector, AvgMode: Integer;
AvgTimeMS: Double);
var
AvgCount: Integer;
Backmult: Double;
begin
NormalizeDisplayParams(Detector, AvgMode, AvgTimeMS, True);
if (FWfDetector = Detector) and
(FWfAvgMode = AvgMode) and
(Abs(FWfAvgTimeMS - AvgTimeMS) < 1E-6) then Exit;
FWfDetector := Detector;
FWfAvgMode := AvgMode;
FWfAvgCount := AvgCount;
FWfBackmult := Backmult;
FWfAvgTimeMS := AvgTimeMS;
if not FAnalyzerOpen then Exit;
AverageTimeToParams(FWfAvgMode, FWfAvgTimeMS, AvgCount, Backmult);
SetDisplayDetectorMode(DISP_ID, 1, FWfDetector);
SetDisplayAverageMode(DISP_ID, 1, FWfAvgMode);
SetDisplayNumAverage(DISP_ID, 1, FWfAvgCount);
SetDisplayAvBackmult(DISP_ID, 1, FWfBackmult);
SetDisplayNumAverage(DISP_ID, 1, AvgCount);
SetDisplayAvBackmult(DISP_ID, 1, Backmult);
SetDisplayNormOneHz(DISP_ID, 1, 0);
end;
end.