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
+430 -108
View File
@@ -26,7 +26,7 @@ uses
StdCtrls, ExtCtrls, ComCtrls, Buttons, Menus, Math, Types,
LCLIntf, LCLType, GraphType,
HPSDRProtocol, HPSDRNetwork,
WDSPEngine, AudioOutput, AudioInput,
WDSP, WDSPEngine, AudioOutput, AudioInput,
IntfGraphics, FPImage,
Settings,
WebServer;
@@ -45,6 +45,7 @@ const
CLR_METER_ON = TColor($0000CC44);
CLR_METER_OVR= TColor($000000CC);
CLR_SPECTRUM = TColor($0044FF44);
WDSP_WISDOM_FILE = 'wdspWisdom00';
BAND_COUNT = 11;
MODE_COUNT = 8;
@@ -229,11 +230,15 @@ type
// ---- Spectrum ----
FSpectrumBuf: array[0..1023] of Single;
FWfAvgBuf: array[0..1023] of Single;
FWaterfallBuf: array[0..1023] of Single; // WDSP waterfall pixout=1 data
FWaterfallBuf: array[0..1023] of Single; // текущая строка водопада после локального сглаживания
FSpectrumBufCount: Integer;
FWaterfallBufCount: Integer;
FSpectrumBitmap: TBitmap;
FWfAGCEnabled: Boolean;
FWfNFEnabled: Boolean;
FWfManualHigh: Double;
FWfManualLow: Double;
FWfAGCOffset: Double;
FWfHigh: Double;
FWfLow: Double;
// Spectrum grid settings
@@ -242,13 +247,16 @@ type
FSpecGridStep: Double; // grid step in dB (e.g. 10)
FWaterfallBitmap:TBitmap;
FWaterfallTemp: TBitmap;
FWaterfallDirty: Boolean;
FWaterfallFrameInterval: Integer;
FWaterfallFrameCounter: Integer;
FSpectrumWidth: Integer;
FSpectrumHeight: Integer;
FWaterfallHeight:Integer;
// ---- Visibility settings ----
FShowSpectrum: Boolean;
FShowWaterfall: Boolean;
FDisplayFPS: Integer; // таймер спектра: кадров в секунду (5..30)
FDisplayFPS: Integer; // таймер спектра: кадров в секунду (1..100)
// ---- Splitter между спектром и водопадом ----
FSplitter: TPanel; // визуальная полоса-разделитель
FSplitterDrag: Boolean; // True = идёт перетаскивание
@@ -403,6 +411,7 @@ type
procedure ResetSpectrumBuf;
procedure ResizeSpectrumPanels;
procedure RecreateDSPEngine(ASampleRate: Integer);
function EnsureWDSPWisdom: Boolean;
// Network callbacks
procedure OnDeviceFound(const Dev: THPSDRDevice);
@@ -498,6 +507,8 @@ type
procedure WebOnFreqB(Hz: Double);
procedure WebOnActiveVfo(Idx: Integer);
procedure WebOnCenter(Hz: Double);
procedure WebOnMOX(On_: Boolean);
procedure WebOnDrive(V: Integer);
// Synchronize-обёртки (выполняются в UI-потоке)
procedure SyncWebFreq;
procedure SyncWebMode;
@@ -519,6 +530,8 @@ type
procedure SyncWebFreqB;
procedure SyncWebActiveVfo;
procedure SyncWebCenter;
procedure SyncWebMOX;
procedure SyncWebDrive;
procedure BtnWfAGCClick(Sender: TObject);
procedure BtnWfNFClick(Sender: TObject);
procedure BtnHidePanelClick(Sender: TObject);
@@ -555,10 +568,10 @@ type
public
// Методы для SettingsForm — немедленное применение настроек
procedure ApplyDisplayParams(FFTSize, WinType, SpecDet, SpecAvgMode,
SpecAvgCount: Integer; SpecBackmult: Double);
procedure ApplyWaterfallParams(WfDet, WfAvgMode, WfAvgCount: Integer;
WfBackmult: Double);
procedure ApplyDisplayParams(FFTSize, WinType, SpecDet, SpecAvgMode: Integer;
SpecAvgTimeMS: Double);
procedure ApplyWaterfallParams(WfDet, WfAvgMode: Integer;
WfAvgTimeMS, WfHigh, WfLow, WfAGCOffset: Double);
procedure ApplyGridParams(RefLevel, Range, GridStep: Double);
procedure ApplyAudioDevice(DevIndex: Integer; const DevName: string);
procedure ApplyAudioInputDevice(DevIndex: Integer; const DevName: string);
@@ -575,6 +588,29 @@ type
var Handled: Boolean);
end;
TWisdomBuildThread = class(TThread)
private
FDirectory: string;
FError: string;
protected
procedure Execute; override;
public
constructor Create(const ADirectory: string);
property ErrorText: string read FError;
end;
TWisdomProgressDialog = class(TForm)
private
FInfoLabel: TLabel;
FProgress: TProgressBar;
FTimer: TTimer;
FThread: TWisdomBuildThread;
FPhase: Integer;
procedure TimerTick(Sender: TObject);
public
constructor Create(AOwner: TComponent; AThread: TWisdomBuildThread); reintroduce;
end;
var
MainForm: TMainForm;
@@ -584,6 +620,123 @@ uses SettingsForm;
{$R *.lfm}
function WaterfallColorThetis(Level: Integer): LongWord;
const
STOPS: array[0..5] of LongWord = (
$000000, // black
$0A1A48, // deep blue
$0088CC, // cyan
$E0C020, // yellow
$D84A12, // orange-red
$FFFFFF // white
);
var
Seg, Base: Integer;
T: Double;
C0, C1: LongWord;
R, G, B: Integer;
begin
Level := EnsureRange(Level, 0, 255);
Seg := Min(4, Level div 51);
Base := Seg * 51;
T := (Level - Base) / 51.0;
C0 := STOPS[Seg];
C1 := STOPS[Seg + 1];
R := Round(((C0 shr 16) and $FF) * (1.0 - T) + ((C1 shr 16) and $FF) * T);
G := Round(((C0 shr 8) and $FF) * (1.0 - T) + ((C1 shr 8) and $FF) * T);
B := Round((C0 and $FF) * (1.0 - T) + (C1 and $FF) * T);
Result := (R shl 16) or (G shl 8) or B;
end;
constructor TWisdomBuildThread.Create(const ADirectory: string);
begin
inherited Create(False);
FreeOnTerminate := False;
FDirectory := ADirectory;
FError := '';
end;
procedure TWisdomBuildThread.Execute;
var
DirA: AnsiString;
begin
try
if not Assigned(@WDSPwisdom) then Exit;
DirA := AnsiString(FDirectory);
WDSPwisdom(PAnsiChar(DirA));
except
on E: Exception do
FError := E.ClassName + ': ' + E.Message;
end;
end;
constructor TWisdomProgressDialog.Create(AOwner: TComponent;
AThread: TWisdomBuildThread);
begin
inherited CreateNew(AOwner);
FThread := AThread;
FPhase := 0;
Caption := 'WDSP Wisdom';
Width := 520;
Height := 140;
Position := poScreenCenter;
BorderStyle := bsDialog;
BorderIcons := [];
Color := CLR_BG;
Font.Name := 'Courier New';
Font.Size := 9;
Font.Color := CLR_TEXT;
FInfoLabel := TLabel.Create(Self);
FInfoLabel.Parent := Self;
FInfoLabel.SetBounds(16, 16, 480, 40);
FInfoLabel.AutoSize := False;
FInfoLabel.WordWrap := True;
FInfoLabel.Font.Color := CLR_TEXT;
FInfoLabel.Caption :=
'Creating FFTW wisdom for WDSP. This is done once and may take a while on the first run.';
FProgress := TProgressBar.Create(Self);
FProgress.Parent := Self;
FProgress.SetBounds(16, 72, 480, 22);
FProgress.Min := 0;
FProgress.Max := 100;
FProgress.Position := 0;
FTimer := TTimer.Create(Self);
FTimer.Interval := 250;
FTimer.OnTimer := TimerTick;
FTimer.Enabled := True;
end;
procedure TWisdomProgressDialog.TimerTick(Sender: TObject);
var
P: PAnsiChar;
S: string;
begin
FPhase := (FPhase + 7) mod 101;
FProgress.Position := FPhase;
if Assigned(@wisdom_get_status) then
begin
P := wisdom_get_status;
if P <> nil then
begin
S := Trim(string(AnsiString(P)));
if S <> '' then
FInfoLabel.Caption := S;
end;
end;
if Assigned(FThread) and FThread.Finished then
begin
FTimer.Enabled := False;
ModalResult := mrOk;
end;
end;
// ===========================================================================
// Общая функция декодирования типа платы — используется везде
// ===========================================================================
@@ -713,26 +866,25 @@ begin
Result.WindowType := FDSPEngine.WindowType;
Result.SpecDetector := FDSPEngine.SpecDetector;
Result.SpecAvgMode := FDSPEngine.SpecAvgMode;
Result.SpecAvgCount := FDSPEngine.SpecAvgCount;
Result.SpecBackmult := FDSPEngine.SpecBackmult;
Result.SpecAvgTimeMS := FDSPEngine.SpecAvgTimeMS;
Result.WfDetector := FDSPEngine.WfDetector;
Result.WfAvgMode := FDSPEngine.WfAvgMode;
Result.WfAvgCount := FDSPEngine.WfAvgCount;
Result.WfBackmult := FDSPEngine.WfBackmult;
Result.WfAvgTimeMS := FDSPEngine.WfAvgTimeMS;
end
else
begin
Result.FFTSize := 4096;
Result.WindowType := 5;
Result.WindowType := 2;
Result.SpecDetector := 0;
Result.SpecAvgMode := 3;
Result.SpecAvgCount := 2;
Result.SpecBackmult := 0.45;
Result.WfDetector := 2;
Result.SpecAvgTimeMS := 30.0;
Result.WfDetector := 0;
Result.WfAvgMode := 3;
Result.WfAvgCount := 2;
Result.WfBackmult := 0.45;
Result.WfAvgTimeMS := 120.0;
end;
Result.WfManualHigh := FWfManualHigh;
Result.WfManualLow := FWfManualLow;
Result.WfAGCOffset := FWfAGCOffset;
Result.SpecRefLevel := FSpecRefLevel;
Result.SpecRange := FSpecRange;
Result.SpecGridStep := FSpecGridStep;
@@ -763,9 +915,9 @@ begin
// --- Кнопки диапазонов ---
for i := 0 to BAND_COUNT - 1 do
StyleButton(BtnBand[i], i = BandIdx);
// Сбрасываем waterfall AGC/NF при смене диапазона
FWfHigh := -50.0;
FWfLow := -120.0;
// Сбрасываем waterfall thresholds при смене диапазона
FWfHigh := FWfManualHigh;
FWfLow := FWfManualLow;
// Сбрасываем EMA буфер — иначе старый спектр "просочится" в новый диапазон
ResetWfAvgBuf;
@@ -859,12 +1011,17 @@ begin
FSampleRate := 192000;
FCurrentBand := 5; // 20m по умолчанию
// Waterfall normalization
FWfHigh := -50.0; // начальная верхняя граница dBm
// WF avg buffer — инициализируем значением шумового пола
// FWfAvgBuf инициализируется в ResetSpectrumBuf
FWfLow := -120.0; // начальная нижняя граница dBm
FWfAGCEnabled := False;
FWfManualHigh := -80.0;
FWfManualLow := -130.0;
FWfAGCOffset := 0.0;
FWfHigh := FWfManualHigh; // начальная верхняя граница dBm
FWfLow := FWfManualLow; // начальная нижняя граница dBm
FWfAGCEnabled := True;
FWfNFEnabled := False;
FSpectrumBufCount := 1024;
FWaterfallBufCount := 1024;
FWaterfallFrameInterval := 2;
FWaterfallFrameCounter := 0;
FDevConnected := False;
// Spectrum grid settings defaults
FSpecRefLevel := -20.0;
@@ -920,6 +1077,8 @@ begin
FWebServer.OnFreqB := WebOnFreqB;
FWebServer.OnActiveVfo := WebOnActiveVfo;
FWebServer.OnCenter := WebOnCenter;
FWebServer.OnMOX := WebOnMOX;
FWebServer.OnDrive := WebOnDrive;
FWebServer.Start;
FSMeterPeak := -130;
FSMeterMin := -130;
@@ -940,6 +1099,7 @@ begin
FShowSpectrum := True;
FShowWaterfall := True;
FDisplayFPS := 20;
FWaterfallDirty := True;
FSplitterRatio := 0.40; // 40% спектр, 60% водопад по умолчанию
FSplitterDrag := False;
@@ -2020,21 +2180,20 @@ begin
end;
procedure TMainForm.ResetWfAvgBuf;
var i: Integer;
begin
for i := 0 to 1023 do FWfAvgBuf[i] := -120.0;
FWfHigh := -50.0;
FWfLow := -120.0;
FWfHigh := FWfManualHigh;
FWfLow := FWfManualLow;
FWaterfallDirty := True;
end;
procedure TMainForm.ResetSpectrumBuf;
var i: Integer;
begin
for i := 0 to 1023 do FSpectrumBuf[i] := -130.0;
for i := 0 to 1023 do FWfAvgBuf[i] := -120.0;
for i := 0 to 1023 do FWaterfallBuf[i] := -120.0;
FWfHigh := -50.0;
FWfLow := -120.0;
for i := 0 to 1023 do FWaterfallBuf[i] := -130.0;
FWfHigh := FWfManualHigh;
FWfLow := FWfManualLow;
FWaterfallDirty := True;
end;
procedure TMainForm.InvalidateGridCache;
@@ -2253,6 +2412,7 @@ var
S0, S1: Integer;
InvRange: Double;
LabelBandW: Integer;
SrcCount: Integer;
begin
if FSpectrumBitmap = nil then Exit;
W := FSpectrumBitmap.Width;
@@ -2359,11 +2519,12 @@ begin
SetLength(FSpPts, W + 2);
FSpPtsLen := W + 2;
end;
SrcCount := EnsureRange(FSpectrumBufCount, 2, 1024);
for i := 0 to W - 1 do
begin
SrcF := i * 1023.0 / Max(1, W - 1);
S0 := Min(Trunc(SrcF), 1023);
S1 := Min(S0 + 1, 1023);
SrcF := i * (SrcCount - 1.0) / Max(1, W - 1);
S0 := Min(Trunc(SrcF), SrcCount - 1);
S1 := Min(S0 + 1, SrcCount - 1);
Frac := SrcF - S0;
dBv := FSpectrumBuf[S0] * (1.0 - Frac) + FSpectrumBuf[S1] * Frac;
FSpPts[i] := Point(i, Max(0, Min(H-1, Round((DBmax - dBv) * InvRange * H))));
@@ -2409,10 +2570,17 @@ var
dB, frac: Double;
WatSrcF: Double;
WatS0, WatS1, V: Integer;
AvgDB: Double;
PeakDB: Double;
NFSum: Double;
NFCount: Integer;
TargetLow: Double;
TargetHigh: Double;
HistMinDB: Double;
HistMaxDB: Double;
HistStepDB: Double;
NoiseFloorDB: Double;
SignalTopDB: Double;
CumCount: Integer;
LowTargetCount: Integer;
HighTargetCount: Integer;
HistIdx: Integer;
WfHigh, WfLow, InvRange: Double;
Step: Double;
Px: PLongWord;
@@ -2422,53 +2590,78 @@ var
Src: PLongWord;
BPPi: Integer;
Pal: LongWord;
Hist: array[0..191] of Integer;
SrcCount: Integer;
const
ALPHA_SPEC = 0.25;
ALPHA_HIGH = 0.02;
ALPHA_LOW = 0.005;
ALPHA_HIGH = 0.10;
ALPHA_LOW = 0.08;
WF_AUTO_OFFSET = -4.0;
WF_MIN_RANGE = 48.0;
WF_MAX_RANGE = 62.0;
begin
if FWaterfallBitmap = nil then Exit;
W := FWaterfallBitmap.Width;
H := FWaterfallBitmap.Height;
if (W <= 0) or (H <= 0) then Exit;
SrcCount := EnsureRange(FWaterfallBufCount, 2, 1024);
// ── Источник данных водопада ─────────────────────────────────────────────
// При работе WDSP: FWaterfallBuf заполнен из pixout=1 (уже усреднённые WDSP данные)
// Без WDSP (demo): делаем EMA FSpectrumBuf → FWfAvgBuf
PeakDB := -200.0; NFSum := 0.0; NFCount := 0;
if FWDSPReady then
FillChar(Hist, SizeOf(Hist), 0);
HistMinDB := -170.0;
HistMaxDB := 22.0;
HistStepDB := (HistMaxDB - HistMinDB) / Length(Hist);
for X := 0 to SrcCount - 1 do
begin
// Используем WDSP waterfall данные напрямую
for X := 0 to 1023 do
HistIdx := EnsureRange(Trunc((FWaterfallBuf[X] - HistMinDB) / HistStepDB), 0, High(Hist));
Inc(Hist[HistIdx]);
end;
LowTargetCount := Round(SrcCount * 0.30);
HighTargetCount := Round(SrcCount * 0.98);
CumCount := 0;
NoiseFloorDB := FWfLow;
SignalTopDB := FWfHigh;
for HistIdx := 0 to High(Hist) do
begin
CumCount := CumCount + Hist[HistIdx];
if CumCount >= LowTargetCount then
begin
AvgDB := FWaterfallBuf[X];
FWfAvgBuf[X] := AvgDB; // синхронизируем для AGC логики
if AvgDB > PeakDB then PeakDB := AvgDB;
if AvgDB < FWfLow + 15.0 then begin NFSum := NFSum + AvgDB; Inc(NFCount); end;
NoiseFloorDB := HistMinDB + (HistIdx + 0.5) * HistStepDB;
Break;
end;
end
else
end;
CumCount := 0;
for HistIdx := 0 to High(Hist) do
begin
// Fallback: Pascal EMA от демо-спектра
for X := 0 to 1023 do
CumCount := CumCount + Hist[HistIdx];
if CumCount >= HighTargetCount then
begin
AvgDB := FWfAvgBuf[X] * (1.0 - ALPHA_SPEC) + FSpectrumBuf[X] * ALPHA_SPEC;
FWfAvgBuf[X] := AvgDB;
if AvgDB > PeakDB then PeakDB := AvgDB;
if AvgDB < FWfLow + 15.0 then begin NFSum := NFSum + AvgDB; Inc(NFCount); end;
SignalTopDB := HistMinDB + (HistIdx + 0.5) * HistStepDB;
Break;
end;
end;
// ── AGC High/Low ────────────────────────────────────────────────────────
if FWfAGCEnabled then
begin
if PeakDB > FWfHigh then FWfHigh := PeakDB + 5.0
else FWfHigh := FWfHigh + ALPHA_HIGH * ((PeakDB + 5.0) - FWfHigh);
TargetLow := NoiseFloorDB + WF_AUTO_OFFSET + FWfAGCOffset;
if FWfNFEnabled then
TargetHigh := Max(TargetLow + WF_MIN_RANGE, SignalTopDB + 6.0)
else
TargetHigh := TargetLow + 52.0;
if TargetHigh > TargetLow + WF_MAX_RANGE then
TargetHigh := TargetLow + WF_MAX_RANGE;
FWfLow := FWfLow + ALPHA_LOW * (TargetLow - FWfLow);
FWfHigh := FWfHigh + ALPHA_HIGH * (TargetHigh - FWfHigh);
end;
if FWfNFEnabled and (NFCount > 10) then
FWfLow := FWfLow + ALPHA_LOW * ((NFSum / NFCount - 5.0) - FWfLow);
WfHigh := FWfHigh; WfLow := FWfLow;
if WfHigh < WfLow + 15.0 then WfHigh := WfLow + 15.0;
WfHigh := FWfHigh;
WfLow := FWfLow;
if not FWfAGCEnabled then
begin
WfHigh := FWfManualHigh;
WfLow := FWfManualLow;
end;
if WfHigh < WfLow + 40.0 then WfHigh := WfLow + 40.0;
if WfHigh > 0.0 then WfHigh := 0.0;
if WfLow < -160 then WfLow := -160;
InvRange := 255.0 / (WfHigh - WfLow);
@@ -2489,21 +2682,21 @@ begin
Move(FWfPixels[0], FWfPixels[W], (H - 1) * SizeOf(LongWord) * W);
// ── Новая строка 0 — инкрементный указатель, без умножения X*BPP ────────
Step := 1023.0 / Max(1, W - 1);
Step := (SrcCount - 1.0) / Max(1, W - 1);
WatSrcF := 0.0;
Px := @FWfPixels[0];
for X := 0 to W - 1 do
begin
WatS0 := Trunc(WatSrcF);
if WatS0 > 1022 then WatS0 := 1022;
if WatS0 > SrcCount - 2 then WatS0 := SrcCount - 2;
WatS1 := WatS0 + 1;
frac := WatSrcF - WatS0;
dB := FWfAvgBuf[WatS0] * (1.0 - frac) + FWfAvgBuf[WatS1] * frac;
dB := FWaterfallBuf[WatS0] * (1.0 - frac) + FWaterfallBuf[WatS1] * frac;
V := Trunc((dB - WfLow) * InvRange);
if V < 0 then V := 0;
if V > 255 then V := 255;
// Палитра задана как RGB, а в буфере нужен BGRA.
// Меняем местами R/B и ставим alpha=255, чтобы совпадало с web/Thetis-градиентом.
// WFALL_PALETTE задана в RGB-логике, а в буфере нужен BGRA.
// Поэтому меняем R/B местами и добавляем alpha=255.
Pal := WFALL_PALETTE[V];
Px^ := ((Pal and $000000FF) shl 16) or
(Pal and $0000FF00) or
@@ -3084,7 +3277,6 @@ begin
if FWDSPReady then
begin
FDSPEngine.UpdateSpectrum;
FLastSMeter := FDSPEngine.GetSMeterDBm;
FDSPEngine.SetSpectrumWidth(FSpectrumWidth); // актуальная ширина для SetAGC/SetAGCTop
FDSPEngine.UpdateAGCLines(FSpectrumWidth);
@@ -3096,22 +3288,24 @@ begin
FVfoOverlay.UpdateVfo(FVfoA);
PositionVfoOverlay;
end;
// Рисуем только при новых спектральных данных/изменении UI (FSpectrumDirty).
// Это резко снижает CPU в холостом режиме.
// Панорама и водопад теперь рисуются независимо по факту новых кадров.
// Это лучше совпадает с Thetis: маленький FFT даёт новые строки чаще.
if FSpectrumDirty then
begin
DrawSpectrum;
PbSpectrum.Invalidate;
// Водопад рисуем на каждом новом спектральном кадре для плавности.
DrawWaterfall;
PbWaterfall.Invalidate;
if (Abs(FCenterFreq - FRulerLastFreq) >= 0.5) or
(Abs(FVfoA - FRulerLastVfo) >= 0.5) then
PbRuler.Invalidate;
FSpectrumDirty := False;
end;
if FWaterfallDirty then
begin
DrawWaterfall;
PbWaterfall.Invalidate;
FWaterfallDirty := False;
end;
end
else
begin
@@ -3139,15 +3333,20 @@ begin
end;
// Если не в режиме RUN, но есть "грязный" кадр (drag/marker/UI), перерисуем.
if FSpectrumDirty and (not FRunning) then
if (FSpectrumDirty or FWaterfallDirty) and (not FRunning) then
begin
DrawSpectrum;
DrawWaterfall;
PbSpectrum.Invalidate;
if FSpectrumDirty then
DrawSpectrum;
if FWaterfallDirty then
DrawWaterfall;
if FSpectrumDirty then
PbSpectrum.Invalidate;
if (Abs(FCenterFreq - FRulerLastFreq) >= 0.5) or
(Abs(FVfoA - FRulerLastVfo) >= 0.5) then
PbRuler.Invalidate;
PbWaterfall.Invalidate;
if FWaterfallDirty then
PbWaterfall.Invalidate;
FWaterfallDirty := False;
FSpectrumDirty := False;
end;
@@ -3155,7 +3354,7 @@ begin
if Assigned(FWebServer) then
FWebServer.PushSpectrum(
FSpectrumBuf, 1024,
FWfAvgBuf,
FWaterfallBuf,
FLastSMeter,
FVfoA, FMode, FFilterBW, FAGCMode, FAGCTop,
FSpanHz, FVolume,
@@ -3165,7 +3364,8 @@ begin
FRunning, FMuted, FCTun,
BtnNR.Tag, BtnNB.Tag, BtnSNB.Tag <> 0, BtnANF.Tag <> 0,
FCenterFreq, FFilter,
FVfoB, FActiveVfo);
FVfoB, FActiveVfo,
FTransmitting, TrkDrive.Position);
end;
// ===========================================================================
@@ -3412,6 +3612,67 @@ begin
FDSPEngine.OnTXIQ := OnTXIQReady;
end;
function TMainForm.EnsureWDSPWisdom: Boolean;
var
WisdomDir: string;
WisdomPath: string;
DirA: AnsiString;
BuildThread: TWisdomBuildThread;
Dlg: TWisdomProgressDialog;
begin
Result := True;
if not Assigned(@WDSPwisdom) then Exit;
WisdomDir := IncludeTrailingPathDelimiter(ExtractFilePath(ParamStr(0)));
WisdomPath := WisdomDir + WDSP_WISDOM_FILE;
if FileExists(WisdomPath) then
begin
// По рекомендации WDSP вызываем функцию на каждом старте:
// если файл уже есть, библиотека просто импортирует wisdom.
DirA := AnsiString(WisdomDir);
WDSPwisdom(PAnsiChar(DirA));
Exit;
end;
BtnStartStop.Enabled := False;
StatusBar1.Panels[3].Text := 'Creating WDSP wisdom...';
BuildThread := TWisdomBuildThread.Create(WisdomDir);
try
Dlg := TWisdomProgressDialog.Create(Self, BuildThread);
try
Dlg.ShowModal;
finally
Dlg.Free;
end;
BuildThread.WaitFor;
if BuildThread.ErrorText <> '' then
begin
ShowMessage('Failed to create WDSP wisdom.' + LineEnding +
BuildThread.ErrorText);
Result := False;
end
else if not FileExists(WisdomPath) then
begin
ShowMessage('WDSP wisdom was not created.' + LineEnding +
'Expected file: ' + WisdomPath);
Result := False;
end;
finally
BuildThread.Free;
BtnStartStop.Enabled := True;
end;
if Result then
StatusBar1.Panels[3].Text := 'WDSP wisdom ready'
else
StatusBar1.Panels[3].Text := 'WDSP wisdom missing';
end;
procedure TMainForm.BtnDiscoverClick(Sender: TObject);
begin
// Открываем диалог выбора устройства
@@ -3595,6 +3856,11 @@ begin
BtnANF.Tag := Ord(G_Settings.ANFEnabled); UpdateANFButton;
FWfAGCEnabled := G_Settings.WfAGCEnabled; StyleButton(BtnWfAGC, FWfAGCEnabled);
FWfNFEnabled := G_Settings.WfNFEnabled; StyleButton(BtnWfNF, FWfNFEnabled);
FWfManualHigh := G_Settings.WfManualHigh;
FWfManualLow := G_Settings.WfManualLow;
FWfAGCOffset := G_Settings.WfAGCOffset;
FWfHigh := FWfManualHigh;
FWfLow := FWfManualLow;
FShowSpectrum := G_Settings.ShowSpectrum;
FShowWaterfall := G_Settings.ShowWaterfall;
if G_Settings.DisplayFPS > 0 then
@@ -3610,9 +3876,9 @@ begin
begin
FDSPEngine.SetFFTParams(G_Settings.FFTSize, G_Settings.WindowType);
FDSPEngine.SetSpectrumDisplay(G_Settings.SpecDetector, G_Settings.SpecAvgMode,
G_Settings.SpecAvgCount, G_Settings.SpecBackmult);
G_Settings.SpecAvgTimeMS);
FDSPEngine.SetWaterfallDisplay(G_Settings.WfDetector, G_Settings.WfAvgMode,
G_Settings.WfAvgCount, G_Settings.WfBackmult);
G_Settings.WfAvgTimeMS);
end;
// Аудио устройство вывода
FAudioOut.Close;
@@ -4555,7 +4821,7 @@ begin
FWfAGCEnabled := not FWfAGCEnabled;
StyleButton(BtnWfAGC, FWfAGCEnabled);
if not FWfAGCEnabled then
FWfHigh := -50.0; // сбрасываем к умолчанию при выключении
FWfHigh := FWfManualHigh; // сбрасываем к ручному уровню при выключении
end;
procedure TMainForm.BtnWfNFClick(Sender: TObject);
@@ -4563,7 +4829,7 @@ begin
FWfNFEnabled := not FWfNFEnabled;
StyleButton(BtnWfNF, FWfNFEnabled);
if not FWfNFEnabled then
FWfLow := -120.0;
FWfLow := FWfManualLow;
end;
procedure TMainForm.BtnHidePanelClick(Sender: TObject);
@@ -5114,11 +5380,41 @@ begin
FSpectrumDirty := True;
end;
procedure TMainForm.WebOnMOX(On_: Boolean);
begin
FWebSyncBool := On_;
FWebSyncM := SyncWebMOX;
TThread.Synchronize(nil, FWebSyncM);
end;
procedure TMainForm.SyncWebMOX;
begin
ApplyMOX(FWebSyncBool);
end;
procedure TMainForm.WebOnDrive(V: Integer);
begin
FWebSyncInt := V;
FWebSyncM := SyncWebDrive;
TThread.Synchronize(nil, FWebSyncM);
end;
procedure TMainForm.SyncWebDrive;
begin
// Установка Position вызывает TrkDriveChange через OnChange автоматически
TrkDrive.Position := FWebSyncInt;
end;
procedure TMainForm.TrkDriveChange(Sender: TObject);
begin
FDriveLevel := CalcDriveByte;
if FWDSPReady then
FDSPEngine.SetDriveLevel(TrkDrive.Position / 100.0);
if FNetwork.Connected and FNetwork.Running then
begin
FNetwork.UpdateState(FCenterFreq, ActiveTXFreqHz, FDriveLevel, FTransmitting, True, True);
FNetwork.SendFullHP;
end;
end;
function TMainForm.CalcDriveByte: Byte;
@@ -5283,6 +5579,7 @@ begin
N := Min(Count, 1024);
for i := 0 to N - 1 do
FSpectrumBuf[i] := Pixels[i];
FSpectrumBufCount := N;
FSpectrumDirty := True;
end;
@@ -5291,31 +5588,47 @@ procedure TMainForm.OnWaterfallReady(const Pixels: array of Single;
var
i, N: Integer;
begin
// Заполняем буфер водопада из WDSP pixout=1.
// Вызывается из DSP-потока — Single-запись атомарна.
N := Min(Count, 1024);
for i := 0 to N - 1 do
FWaterfallBuf[i] := Pixels[i];
FWaterfallBufCount := N;
Inc(FWaterfallFrameCounter);
if FWaterfallFrameCounter >= Max(1, FWaterfallFrameInterval) then
begin
FWaterfallFrameCounter := 0;
FWaterfallDirty := True;
end;
end;
// ===========================================================================
// Settings apply methods (вызываются из TSettingsForm)
// ===========================================================================
procedure TMainForm.ApplyDisplayParams(FFTSize, WinType, SpecDet, SpecAvgMode,
SpecAvgCount: Integer; SpecBackmult: Double);
procedure TMainForm.ApplyDisplayParams(FFTSize, WinType, SpecDet, SpecAvgMode: Integer;
SpecAvgTimeMS: Double);
begin
if FWDSPReady then
FDSPEngine.SetSpectrumDisplay(SpecDet, SpecAvgMode, SpecAvgTimeMS);
if FWDSPReady then
FDSPEngine.SetFFTParams(FFTSize, WinType);
if FWDSPReady then
FDSPEngine.SetSpectrumDisplay(SpecDet, SpecAvgMode, SpecAvgCount, SpecBackmult);
FSpectrumDirty := True;
end;
procedure TMainForm.ApplyWaterfallParams(WfDet, WfAvgMode, WfAvgCount: Integer;
WfBackmult: Double);
procedure TMainForm.ApplyWaterfallParams(WfDet, WfAvgMode: Integer;
WfAvgTimeMS, WfHigh, WfLow, WfAGCOffset: Double);
begin
if FWDSPReady then
FDSPEngine.SetWaterfallDisplay(WfDet, WfAvgMode, WfAvgCount, WfBackmult);
FDSPEngine.SetWaterfallDisplay(WfDet, WfAvgMode, WfAvgTimeMS);
FWfManualHigh := WfHigh;
FWfManualLow := WfLow;
FWfAGCOffset := WfAGCOffset;
if not FWfAGCEnabled then
begin
FWfHigh := FWfManualHigh;
FWfLow := FWfManualLow;
end;
ResetWfAvgBuf;
FSpectrumDirty := True;
end;
procedure TMainForm.ApplyGridParams(RefLevel, Range, GridStep: Double);
@@ -5375,9 +5688,15 @@ end;
procedure TMainForm.ApplyFPS(FPS: Integer);
begin
if FPS < 1 then FPS := 1;
if FPS > 60 then FPS := 60;
if FPS > 100 then FPS := 100;
FDisplayFPS := FPS;
FSpectrumTimer.Interval := 1000 div FPS;
// Thetis по умолчанию обновляет водопад через один display frame.
FWaterfallFrameInterval := 2;
FWaterfallFrameCounter := 0;
FWaterfallDirty := True;
if FWDSPReady then
FDSPEngine.SetDisplayFPS(FPS);
end;
procedure TMainForm.BtnSettingsClick(Sender: TObject);
@@ -5410,18 +5729,19 @@ begin
FDSPEngine.WindowType,
FDSPEngine.SpecDetector,
FDSPEngine.SpecAvgMode,
FDSPEngine.SpecAvgCount,
FDSPEngine.SpecBackmult,
FDSPEngine.SpecAvgTimeMS,
FDSPEngine.WfDetector,
FDSPEngine.WfAvgMode,
FDSPEngine.WfAvgCount,
FDSPEngine.WfBackmult,
FDSPEngine.WfAvgTimeMS,
FWfManualHigh,
FWfManualLow,
FWfAGCOffset,
FSpecRefLevel, FSpecRange, FSpecGridStep,
FAudioOutDevName, FAudioInDevName)
else
SF.LoadValues(
4096, 5, 0, 3, 2, 0.45,
2, 3, 2, 0.45,
4096, 2, 0, 3, 30.0,
0, 3, 120.0, FWfManualHigh, FWfManualLow, FWfAGCOffset,
FSpecRefLevel, FSpecRange, FSpecGridStep,
FAudioOutDevName, FAudioInDevName);
SF.Show;
@@ -5442,6 +5762,8 @@ begin
on E: Exception do
StatusBar1.Panels[3].Text := 'Audio: ' + E.Message;
end;
EnsureWDSPWisdom;
end;