Merge branch 'refactor/extract-modules'

Extract helpers out of MainForm into structured modules:
- BoardUtils: BoardTypeName, FreqToBandIdx (removes duplication with DeviceForm)
- WisdomBuilder: TWisdomBuildThread + TWisdomProgressDialog
- UISync: TDeviceFoundSync/TStatusUISync/TDDCSeqSync via callbacks
- PlatformUtils: HasOpenGLSpectrumSwitch, CurrentScreenDPI

Minor cleanup: unused local constants, stale comments in AudioOutput.
This commit is contained in:
2026-05-26 19:38:50 +03:00
8 changed files with 328 additions and 305 deletions
+2 -9
View File
@@ -186,10 +186,7 @@ const
PA_CONTINUE = LongInt(0);
PA_NO_DEV = TPaDeviceIndex(-1);
// ---------------------------------------------------------------------------
// Callback — точная копия pa_out_cb из оригинала
// ---------------------------------------------------------------------------
// Callback modeled after pa_out_cb from piHPSDR/portaudio.c
function PaOutCallback(inputBuffer, outputBuffer: Pointer;
framesPerBuffer: LongWord;
timeInfo: Pointer;
@@ -567,11 +564,7 @@ begin
end;
end;
// ---------------------------------------------------------------------------
// WriteDouble — точная копия audio_write() из оригинала
// ---------------------------------------------------------------------------
// WriteDouble — точная копия audio_write() из piHPSDR/portaudio.c
// WriteDouble — modeled after audio_write() from piHPSDR/portaudio.c
// Вызывается per-sample из Write. Мьютекс держится весь цикл в Write.
procedure TAudioOutput.WriteDouble(Left, Right: Double);
var
+50
View File
@@ -0,0 +1,50 @@
unit BoardUtils;
{$IFDEF FPC}
{$MODE Delphi}
{$ENDIF}
interface
function BoardTypeName(BoardType: Integer): string;
function FreqToBandIdx(Hz: Double): Integer;
implementation
uses SysUtils;
function BoardTypeName(BoardType: Integer): string;
begin
case BoardType of
1: Result := 'HERMES (ANAN-10/100)';
2: Result := 'HERMES-E (ANAN-10E/100B)';
3: Result := 'ANGELIA (ANAN-100D)';
4: Result := 'ORION (ANAN-200D)';
5: Result := 'ORION MkII (ANAN-7000/8000)';
6: Result := 'HERMES-LITE 2';
10: Result := 'SATURN (G2)';
else Result := Format('Unknown Board #%d', [BoardType]);
end;
end;
function FreqToBandIdx(Hz: Double): Integer;
const
BAND_LO: array[0..10] of Double = (
1800000, 3500000, 5330000, 7000000, 10100000,
14000000, 18068000, 21000000, 24890000, 28000000, 50000000);
BAND_HI: array[0..10] of Double = (
2000000, 4000000, 5410000, 7300000, 10150000,
14350000, 18168000, 21450000, 24990000, 29700000, 54000000);
var
i: Integer;
begin
Result := -1;
for i := 0 to 10 do
if (Hz >= BAND_LO[i]) and (Hz <= BAND_HI[i]) then
begin
Result := i;
Exit;
end;
end;
end.
+2 -18
View File
@@ -6,10 +6,8 @@ interface
uses
Classes, SysUtils, FlatButton, FlatEdit, FlatListBox, AppTheme, Forms, Controls, Graphics, Dialogs,
StdCtrls, ExtCtrls, ComCtrls, IniFiles;
// Декодирование типа платы (совпадает с MainForm.BoardTypeName)
function BoardTypeName(BoardType: Integer): string;
StdCtrls, ExtCtrls, ComCtrls, IniFiles,
BoardUtils;
const
DEVICE_CFG_FILE = 'hpsdr_devices.ini';
@@ -114,20 +112,6 @@ type
implementation
function BoardTypeName(BoardType: Integer): string;
begin
case BoardType of
1: Result := 'HERMES (ANAN-10/100)';
2: Result := 'HERMES-E (ANAN-10E/100B)';
3: Result := 'ANGELIA (ANAN-100D)';
4: Result := 'ORION (ANAN-200D)';
5: Result := 'ORION MkII (ANAN-7000/8000)';
6: Result := 'HERMES-LITE 2';
10: Result := 'SATURN (G2)';
else Result := Format('Unknown Board #%d', [BoardType]);
end;
end;
const
CLR_BG = TColor($00121212);
CLR_PANEL = TColor($001A1A1A);
+5 -277
View File
@@ -26,7 +26,6 @@ uses
FlatButton, FlatSlider, FlatDropDown, AppTheme, Forms, Controls, Graphics, Dialogs,
StdCtrls, ExtCtrls, Buttons, Menus, Math, Types,
OpenGLContext,
FlatProgressBar,
LCLIntf, LCLType, GraphType,
HPSDRProtocol, HPSDRNetwork,
WDSP, WDSPEngine, AudioOutput, AudioInput,
@@ -37,7 +36,8 @@ uses
WidebandView,
StatusBar,
PlatformUtils,
WinFirewall;
WinFirewall,
BoardUtils, WisdomBuilder, UISync;
const
CLR_BG = TColor($00101010);
@@ -138,45 +138,6 @@ type
Display: string;
end;
// Синхронизирующий объект для OnDeviceFound
TDeviceFoundSync = class
private
FForm: TObject; // TMainForm, через forward ref
FDev: THPSDRDevice;
FEntry: string;
public
constructor Create(AForm: TObject; const D: THPSDRDevice; const E: string);
procedure Execute;
end;
// Синхронизирующий объект для HP Status
TStatusUISync = class
private
FForm: TObject;
FFwdW: Double;
FSWRV: Double;
FSupplyV: Double;
FSupplyA: Double;
FPLLLock: Boolean;
FHWPTT: Boolean;
FADCOverload: Byte;
public
constructor Create(AForm: TObject; FW, SW, SV, SA: Double; PLL, HWPTT: Boolean;
ADCOverload: Byte);
procedure Execute;
end;
// Синхронизирующий объект для DDC IQ
TDDCSeqSync = class
private
FForm: TObject;
FDDCIdx: Integer;
FSeq: LongWord;
public
constructor Create(AForm: TObject; Idx: Integer; Seq: LongWord);
procedure Execute;
end;
{ TMainForm }
TMainForm = class(TForm)
private
@@ -750,30 +711,6 @@ 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: TFlatProgressBar;
FTimer: TTimer;
FThread: TWisdomBuildThread;
FPhase: Integer;
procedure TimerTick(Sender: TObject);
public
constructor Create(AOwner: TComponent; AThread: TWisdomBuildThread;
const ATheme: TAppTheme); reintroduce;
end;
var
MainForm: TMainForm;
@@ -783,20 +720,6 @@ uses SettingsForm;
{$R *.lfm}
function HasOpenGLSpectrumSwitch: Boolean;
var
I: Integer;
S: string;
begin
Result := False;
for I := 1 to ParamCount do
begin
S := LowerCase(ParamStr(I));
if (S = '--opengl') or (S = '-opengl') or (S = '/opengl') then
Exit(True);
end;
end;
function TMainForm.LeftPanelButtonWidth(PanelWidth, ColCount,
ColIndex: Integer): Integer;
var
@@ -822,179 +745,10 @@ begin
LEFT_PANEL_BTN_GAP);
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; const ATheme: TAppTheme);
begin
inherited CreateNew(AOwner);
FThread := AThread;
FPhase := 0;
Caption := 'WDSP Wisdom';
Width := 520;
Height := 140;
Position := poScreenCenter;
BorderStyle := bsDialog;
BorderIcons := [];
Color := ATheme.BG;
Font.Name := 'Courier New';
Font.Size := 9;
Font.Color := ATheme.Text;
FInfoLabel := TLabel.Create(Self);
FInfoLabel.Parent := Self;
FInfoLabel.SetBounds(16, 16, 480, 40);
FInfoLabel.AutoSize := False;
FInfoLabel.WordWrap := True;
FInfoLabel.Font.Color := ATheme.Text;
FInfoLabel.Caption :=
'Creating FFTW wisdom for WDSP. This is done once and may take a while on the first run.';
FProgress := TFlatProgressBar.Create(Self);
FProgress.Parent := Self;
FProgress.SetBounds(16, 72, 480, 22);
FProgress.Min := 0;
FProgress.Max := 100;
FProgress.Position := 0;
FProgress.SetAppTheme(ATheme);
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;
// ===========================================================================
// Общая функция декодирования типа платы — используется везде
// ===========================================================================
function BoardTypeName(BoardType: Integer): string;
begin
case BoardType of
1: Result := 'HERMES (ANAN-10/100)';
2: Result := 'HERMES-E (ANAN-10E/100B)';
3: Result := 'ANGELIA (ANAN-100D)';
4: Result := 'ORION (ANAN-200D)';
5: Result := 'ORION MkII (ANAN-7000/8000)';
6: Result := 'HERMES-LITE 2';
10: Result := 'SATURN (G2)';
else Result := Format('Unknown Board #%d', [BoardType]);
end;
end;
// ===========================================================================
// Sync helpers
// ===========================================================================
constructor TDeviceFoundSync.Create(AForm: TObject;
const D: THPSDRDevice; const E: string);
begin
inherited Create;
FForm := AForm;
FDev := D;
FEntry := E;
end;
procedure TDeviceFoundSync.Execute;
begin
TMainForm(FForm).DoAddDevice(FDev, FEntry);
end;
constructor TStatusUISync.Create(AForm: TObject;
FW, SW, SV, SA: Double; PLL, HWPTT: Boolean; ADCOverload: Byte);
begin
inherited Create;
FForm := AForm;
FFwdW := FW;
FSWRV := SW;
FSupplyV := SV;
FSupplyA := SA;
FPLLLock := PLL;
FHWPTT := HWPTT;
FADCOverload := ADCOverload;
end;
procedure TStatusUISync.Execute;
begin
TMainForm(FForm).DoUpdateStatus(FFwdW, FSWRV, FSupplyV, FSupplyA, FPLLLock, FHWPTT,
FADCOverload);
end;
constructor TDDCSeqSync.Create(AForm: TObject; Idx: Integer; Seq: LongWord);
begin
inherited Create;
FForm := AForm;
FDDCIdx := Idx;
FSeq := Seq;
end;
procedure TDDCSeqSync.Execute;
begin
TMainForm(FForm).DoUpdateDDCSeqOrNoDevice(FDDCIdx, FSeq);
end;
// ===========================================================================
// FormCreate / FormDestroy
// ===========================================================================
// ===========================================================================
// Settings helpers
// ===========================================================================
function CurrentScreenDPI: Integer;
begin
Result := Screen.PixelsPerInch;
if Result <= 0 then Result := 96;
end;
procedure TMainForm.RestoreWindowBounds;
var
L, T, Wd, Ht, SavedDPI, CurDPI: Integer;
@@ -1666,7 +1420,6 @@ const
LEFT_W = 232;
TOP_VFO_W = 244;
TOP_VFO_FREQ_H = 47;
TOP_VFO_BTN_H = 14;
var
i, X, Y, W: Integer;
B: TFlatButton;
@@ -2381,7 +2134,6 @@ const
MARGIN = 4;
TOP_OFFSET = 2;
TOP_VFO_FREQ_H = 47;
VFO_BTN_W = 20;
VFO_BTN_FREQ_GAP = 4;
VFO_FREQ_LEFT_PAD = 2;
VFO_FREQ_RIGHT_PAD = 8;
@@ -3394,7 +3146,7 @@ begin
Entry := Format('%s %s FW:%d DDC:%d',
[Dev.IPAddress, BoardName,
Dev.FirmwareVersion, Dev.NumDDCs]);
Sync := TDeviceFoundSync.Create(Self, Dev, Entry);
Sync := TDeviceFoundSync.Create(DoAddDevice, Dev, Entry);
try
M := Sync.Execute;
TThread.Synchronize(nil, M);
@@ -3483,7 +3235,7 @@ begin
end else
SWRV := 1.0;
if not IsTx then FwdW := 0;
Sync := TStatusUISync.Create(Self, FwdW, SWRV, SupplyV, SupplyA,
Sync := TStatusUISync.Create(DoUpdateStatus, FwdW, SWRV, SupplyV, SupplyA,
(Status.StatusBits and HPS_PLL_LOCKED) <> 0,
(Status.StatusBits and HPS_PTT) <> 0,
Status.ADCOverload);
@@ -3886,7 +3638,7 @@ begin
if Length(Devs) = 0 then
begin
// DDCIdx = -1 is the sentinel for "no device found"
Sync := TDDCSeqSync.Create(FForm, -1, 0);
Sync := TDDCSeqSync.Create(FForm.DoUpdateDDCSeqOrNoDevice, -1, 0);
try
M := Sync.Execute;
TThread.Synchronize(nil, M);
@@ -4393,30 +4145,6 @@ end;
// Idx=0 → VFO-A активен, Idx=1 → VFO-B активен.
// Перестройка всегда происходит на частоту активного VFO (не на оффсет).
// CTUN при этом сбрасывается чтобы не было путаницы с оффсетом.
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// FreqToBandIdx — возвращает индекс диапазона для частоты Hz, или -1
// ---------------------------------------------------------------------------
function FreqToBandIdx(Hz: Double): Integer;
const
// Границы диапазонов [Low, High] в Гц (приблизительные)
BAND_LO: array[0..10] of Double = (
1800000, 3500000, 5330000, 7000000, 10100000,
14000000, 18068000, 21000000, 24890000, 28000000, 50000000);
BAND_HI: array[0..10] of Double = (
2000000, 4000000, 5410000, 7300000, 10150000,
14350000, 18168000, 21450000, 24990000, 29700000, 54000000);
var
i: Integer;
begin
Result := -1;
for i := 0 to 10 do
if (Hz >= BAND_LO[i]) and (Hz <= BAND_HI[i]) then
begin
Result := i;
Exit;
end;
end;
// ---------------------------------------------------------------------------
// ActiveVfoFreq — частота активного VFO
+23 -1
View File
@@ -11,11 +11,13 @@ unit PlatformUtils;
interface
function QtPlatformAllowsOpenGLControls: Boolean;
function HasOpenGLSpectrumSwitch: Boolean;
function CurrentScreenDPI: Integer;
implementation
uses
SysUtils;
SysUtils, Forms;
function QtPlatformAllowsOpenGLControls: Boolean;
var
@@ -39,4 +41,24 @@ begin
Result := SessionType <> 'wayland';
end;
function HasOpenGLSpectrumSwitch: Boolean;
var
I: Integer;
S: string;
begin
Result := False;
for I := 1 to ParamCount do
begin
S := LowerCase(ParamStr(I));
if (S = '--opengl') or (S = '-opengl') or (S = '/opengl') then
Exit(True);
end;
end;
function CurrentScreenDPI: Integer;
begin
Result := Screen.PixelsPerInch;
if Result <= 0 then Result := 96;
end;
end.
+105
View File
@@ -0,0 +1,105 @@
unit UISync;
{$IFDEF FPC}
{$MODE Delphi}
{$ENDIF}
interface
uses
Classes, HPSDRNetwork;
type
TAddDeviceProc = procedure(const Dev: THPSDRDevice; const Entry: string) of object;
TUpdateStatusProc = procedure(FwdW, SWRV, SupplyV, SupplyA: Double;
PLLLock, HWPTT: Boolean; ADCOverload: Byte) of object;
TUpdateDDCSeqOrNoDeviceProc = procedure(DDCIdx: Integer; Seq: LongWord) of object;
TDeviceFoundSync = class
private
FCallback: TAddDeviceProc;
FDev: THPSDRDevice;
FEntry: string;
public
constructor Create(ACallback: TAddDeviceProc; const D: THPSDRDevice;
const E: string);
procedure Execute;
end;
TStatusUISync = class
private
FCallback: TUpdateStatusProc;
FFwdW: Double;
FSWRV: Double;
FSupplyV: Double;
FSupplyA: Double;
FPLLLock: Boolean;
FHWPTT: Boolean;
FADCOverload: Byte;
public
constructor Create(ACallback: TUpdateStatusProc;
FW, SW, SV, SA: Double; PLL, HWPTT: Boolean; ADCOverload: Byte);
procedure Execute;
end;
TDDCSeqSync = class
private
FCallback: TUpdateDDCSeqOrNoDeviceProc;
FDDCIdx: Integer;
FSeq: LongWord;
public
constructor Create(ACallback: TUpdateDDCSeqOrNoDeviceProc;
Idx: Integer; Seq: LongWord);
procedure Execute;
end;
implementation
constructor TDeviceFoundSync.Create(ACallback: TAddDeviceProc;
const D: THPSDRDevice; const E: string);
begin
inherited Create;
FCallback := ACallback;
FDev := D;
FEntry := E;
end;
procedure TDeviceFoundSync.Execute;
begin
FCallback(FDev, FEntry);
end;
constructor TStatusUISync.Create(ACallback: TUpdateStatusProc;
FW, SW, SV, SA: Double; PLL, HWPTT: Boolean; ADCOverload: Byte);
begin
inherited Create;
FCallback := ACallback;
FFwdW := FW;
FSWRV := SW;
FSupplyV := SV;
FSupplyA := SA;
FPLLLock := PLL;
FHWPTT := HWPTT;
FADCOverload := ADCOverload;
end;
procedure TStatusUISync.Execute;
begin
FCallback(FFwdW, FSWRV, FSupplyV, FSupplyA, FPLLLock, FHWPTT, FADCOverload);
end;
constructor TDDCSeqSync.Create(ACallback: TUpdateDDCSeqOrNoDeviceProc;
Idx: Integer; Seq: LongWord);
begin
inherited Create;
FCallback := ACallback;
FDDCIdx := Idx;
FSeq := Seq;
end;
procedure TDDCSeqSync.Execute;
begin
FCallback(FDDCIdx, FSeq);
end;
end.
+129
View File
@@ -0,0 +1,129 @@
unit WisdomBuilder;
{$IFDEF FPC}
{$MODE Delphi}
{$ENDIF}
interface
uses
Classes, SysUtils, Forms, Controls, ExtCtrls, StdCtrls,
WDSP, AppTheme, FlatProgressBar;
type
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: TFlatProgressBar;
FTimer: TTimer;
FThread: TWisdomBuildThread;
FPhase: Integer;
procedure TimerTick(Sender: TObject);
public
constructor Create(AOwner: TComponent; AThread: TWisdomBuildThread;
const ATheme: TAppTheme); reintroduce;
end;
implementation
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; const ATheme: TAppTheme);
begin
inherited CreateNew(AOwner);
FThread := AThread;
FPhase := 0;
Caption := 'WDSP Wisdom';
Width := 520;
Height := 140;
Position := poScreenCenter;
BorderStyle := bsDialog;
BorderIcons := [];
Color := ATheme.BG;
Font.Name := 'Courier New';
Font.Size := 9;
Font.Color := ATheme.Text;
FInfoLabel := TLabel.Create(Self);
FInfoLabel.Parent := Self;
FInfoLabel.SetBounds(16, 16, 480, 40);
FInfoLabel.AutoSize := False;
FInfoLabel.WordWrap := True;
FInfoLabel.Font.Color := ATheme.Text;
FInfoLabel.Caption :=
'Creating FFTW wisdom for WDSP. This is done once and may take a while on the first run.';
FProgress := TFlatProgressBar.Create(Self);
FProgress.Parent := Self;
FProgress.SetBounds(16, 72, 480, 22);
FProgress.Min := 0;
FProgress.Max := 100;
FProgress.Position := 0;
FProgress.SetAppTheme(ATheme);
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;
end.
+12
View File
@@ -214,6 +214,18 @@
<Filename Value="PlatformUtils.pas"/>
<IsPartOfProject Value="True"/>
</Unit>
<Unit>
<Filename Value="BoardUtils.pas"/>
<IsPartOfProject Value="True"/>
</Unit>
<Unit>
<Filename Value="WisdomBuilder.pas"/>
<IsPartOfProject Value="True"/>
</Unit>
<Unit>
<Filename Value="UISync.pas"/>
<IsPartOfProject Value="True"/>
</Unit>
<Unit>
<Filename Value="SpectrumViewOpengl.pas"/>
<IsPartOfProject Value="True"/>