Add wideband display pane

This commit is contained in:
2026-05-26 16:24:07 +03:00
parent 762fad420e
commit e0d49c7bfa
5 changed files with 1139 additions and 12 deletions
+236 -5
View File
@@ -34,6 +34,7 @@ uses
WebServer,
CATEngine, CATSerial, CATTcp,
SpectrumView, SpectrumViewOpengl,
WidebandView,
StatusBar,
PlatformUtils,
WinFirewall;
@@ -267,14 +268,17 @@ type
// ---- Spectrum / Waterfall view ----
FSpecView: TSpectrumView;
FWidebandView: TWidebandView;
FUseOpenGLSpectrum: Boolean;
FSpectrumWidth: Integer; // актуальная ширина для FDSPEngine и resize-детектора
FSpectrumHeight: Integer;
FWaterfallHeight:Integer;
FShowSpectrum: Boolean;
FShowWaterfall: Boolean;
FShowWideband: Boolean;
FDisplayFPS: Integer;
FWaterfallDirty: Boolean;
FWidebandDirty: Boolean;
// Waterfall settings (kept here for MakeGlobalSettings, buttons, settings dialog)
FWfAGCEnabled: Boolean;
FWfNFEnabled: Boolean;
@@ -430,6 +434,8 @@ type
// ---- Right panel ----
PanelRight: TPanel;
PbSpectrum: TControl;
PbWideband: TControl;
PbWidebandRuler: TPaintBox;
PbRuler: TPaintBox; // полоса частотных меток между спектром и водопадом
PanelSplitter: TPanel; // перетаскиваемый разделитель спектр/водопад
PbWaterfall: TControl;
@@ -458,6 +464,7 @@ type
procedure ResizeSMeter;
procedure ResizeSpectrumPanels;
function UpdateWidebandFrequencyView: Boolean;
procedure InvalidateGridCache;
procedure SyncSpecViewFreq;
procedure RecreateDSPEngine(ASampleRate: Integer);
@@ -467,6 +474,8 @@ type
procedure OnDeviceFound(const Dev: THPSDRDevice);
procedure OnHPStatusCB(const Status: THighPriorityStatus);
procedure OnDDCIQCB(DDCIndex: Integer; const Data: TDDCIQPacket);
procedure OnWidebandCB(ADCIndex: Integer; const Samples: array of SmallInt;
Count: Integer);
procedure OnWDSPOpenDone(Success: Boolean);
procedure DoConnectDevice(const Dev: THPSDRDevice);
procedure OnMicPacketCB(const Data: TMicDataPacket);
@@ -537,6 +546,8 @@ type
procedure PbSpectrumMouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
procedure PbSpectrumMouseLeave(Sender: TObject);
procedure PbWidebandMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
procedure PbWaterfallMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
procedure PbWaterfallMouseMove(Sender: TObject; Shift: TShiftState;
@@ -545,6 +556,7 @@ type
Shift: TShiftState; X, Y: Integer);
procedure DoSpectrumClick(PixelX: Integer; PanelWidth: Integer);
procedure DoSpectrumDrag(PixelX: Integer; PanelWidth: Integer);
procedure DoWidebandClick(PixelX: Integer; PanelWidth: Integer);
procedure BtnVfoSwapClick(Sender: TObject);
procedure BtnVfoACopyBClick(Sender: TObject);
procedure BtnVfoBCopyAClick(Sender: TObject);
@@ -722,7 +734,7 @@ type
procedure SendDUCSpecificFromSettings;
function PullSoundCardMic(MaxN: Integer): Integer;
function BuildMicLineSelectByte: Byte;
procedure ApplyVisibility(ShowSpectrum, ShowWaterfall: Boolean);
procedure ApplyVisibility(ShowSpectrum, ShowWaterfall, ShowWideband: Boolean);
procedure ApplyFPS(FPS: Integer);
procedure ApplyFreqMhzDigits(Digits: Integer);
@@ -1147,6 +1159,7 @@ begin
Result.AudioSampleRate := FAudioOut.SampleRate;
Result.ShowSpectrum := FShowSpectrum;
Result.ShowWaterfall := FShowWaterfall;
Result.ShowWideband := FShowWideband;
Result.DisplayDuplex := FDisplayDuplex;
Result.DisplayFPS := FDisplayFPS;
Result.LightTheme := FLightTheme;
@@ -1464,6 +1477,7 @@ begin
FPanelHidden := False;
FShowSpectrum := True;
FShowWaterfall := True;
FShowWideband := False;
FDisplayFPS := 60;
FFreqMhzDigits := 3;
FSplitterRatio := 0.40;
@@ -1513,12 +1527,14 @@ begin
FSpecView.TXSpanHz := WDSPEngine.TX_SAMPLE_RATE;
FSpecView.WfFrameInterval := FWaterfallFrameInterval;
FSpecView.PAMaxPower := FPAMaxPower;
FWidebandView := TWidebandView.Create;
FNetwork := THPSDRNetwork.Create;
FNetwork.OnDeviceFound := OnDeviceFound;
FNetwork.OnHPStatus := OnHPStatusCB;
FNetwork.OnDDCIQ := OnDDCIQCB;
FNetwork.OnMicPacket := OnMicPacketCB;
FNetwork.OnWideband := OnWidebandCB;
// Дефолты TX, Alex и XVTR (на случай если устройство ещё не выбрано).
TSettingsManager.DefaultTX(FTXSettings);
TSettingsManager.DefaultAlex(FAlexSettings);
@@ -1593,6 +1609,7 @@ begin
FDSPEngine.Close;
FDSPEngine.Free;
FreeAndNil(FSpecView);
FreeAndNil(FWidebandView);
// Сохраняем настройки при закрытии
if FDevConnected then
@@ -2163,6 +2180,29 @@ begin
PbSMeterRight.OnPaint := FSpecView.PaintSMeterRight;
PbSMeterRight.Color := CLR_PANEL;
if FUseOpenGLSpectrum then
begin
PbWideband := TOpenGLControl.Create(Self);
TOpenGLControl(PbWideband).AutoResizeViewport := False;
TOpenGLControl(PbWideband).DoubleBuffered := True;
TOpenGLControl(PbWideband).OnPaint := FWidebandView.Paint;
TOpenGLControl(PbWideband).OnMouseDown := PbWidebandMouseDown;
end
else
begin
PbWideband := TPaintBox.Create(Self);
TPaintBox(PbWideband).OnPaint := FWidebandView.Paint;
TPaintBox(PbWideband).OnMouseDown := PbWidebandMouseDown;
end;
PbWideband.Parent := PanelRight;
PbWideband.Visible := False;
PbWidebandRuler := TPaintBox.Create(Self);
PbWidebandRuler.Parent := PanelRight;
PbWidebandRuler.OnPaint := FWidebandView.PaintRuler;
PbWidebandRuler.Cursor := crDefault;
PbWidebandRuler.Visible := False;
if FUseOpenGLSpectrum then
begin
PbSpectrum := TOpenGLControl.Create(Self);
@@ -2479,13 +2519,41 @@ begin
PanelSMeterRight.SetBounds(SMLeft, SMTop, SMW, SMBot - SMTop);
end;
function TMainForm.UpdateWidebandFrequencyView: Boolean;
var
E: TXvtrEntry;
SrcStart, SrcEnd: Double;
MarkerChanged, ViewChanged: Boolean;
begin
Result := False;
if FWidebandView = nil then Exit;
if FActiveVfo = 0 then
MarkerChanged := FWidebandView.SetMarkerHz(FVfoA)
else
MarkerChanged := FWidebandView.SetMarkerHz(FVfoB);
if (FCurrentXvtr >= 0) and (FCurrentXvtr < CFG_XVTR_COUNT) and
FXvtrSettings.Entries[FCurrentXvtr].Enabled then
begin
E := FXvtrSettings.Entries[FCurrentXvtr];
SrcStart := E.FreqBegin - E.LOOffset + E.LOError;
SrcEnd := E.FreqEnd - E.LOOffset + E.LOError;
ViewChanged := FWidebandView.SetFrequencyView(E.FreqBegin, E.FreqEnd,
SrcStart, SrcEnd);
end
else
ViewChanged := FWidebandView.SetFrequencyView(0.0, 61440000.0, 0.0,
61440000.0);
Result := MarkerChanged or ViewChanged;
end;
procedure TMainForm.ResizeSpectrumPanels;
const
SPLITTER_H = 5;
WB_H = 118;
MIN_SH = 60; // минимальная высота спектра
MIN_WH = 40; // минимальная высота водопада
var
RW, RH, SH, WH, TopOff: Integer;
RW, RH, SH, WH, TopOff, WideH, WideSpecH: Integer;
AvailH: Integer;
RULER_H: Integer;
begin
@@ -2494,7 +2562,34 @@ begin
if Assigned(FSpecView) then SyncSpecViewFreq;
RW := PanelRight.ClientWidth;
RH := PanelRight.ClientHeight;
TopOff := 0;
UpdateWidebandFrequencyView;
WideH := 0;
WideSpecH := 0;
if FShowWideband then
WideH := Min(MulDiv(WB_H, Screen.PixelsPerInch, 96), Max(40, RH div 2));
if WideH > 0 then
WideSpecH := Max(1, WideH - RULER_H);
TopOff := WideH;
if PbWideband <> nil then
begin
PbWideband.SetBounds(0, 0, RW, WideSpecH);
PbWideband.Visible := FShowWideband and (WideSpecH > 0);
if FShowWideband and (RW > 0) and (WideSpecH > 0) then
begin
FWidebandView.SetBitmapSize(RW, WideSpecH);
PbWideband.Invalidate;
end;
end;
if PbWidebandRuler <> nil then
begin
PbWidebandRuler.SetBounds(0, WideSpecH, RW, IfThen(FShowWideband and (WideH > 0), RULER_H, 0));
PbWidebandRuler.Visible := FShowWideband and (WideH > 0);
if FShowWideband and (RW > 0) and (WideH > 0) then
begin
FWidebandView.SetRulerSize(RW, RULER_H);
PbWidebandRuler.Invalidate;
end;
end;
// S-метр позиционируется отдельно в ResizeSMeter
ResizeSMeter;
@@ -2704,6 +2799,7 @@ begin
// Спектр
FSpecView.SetTheme(T);
if Assigned(FWidebandView) then FWidebandView.SetTheme(T);
// VFO display — вызывает UpdateVfoDisplay, который использует тему
UpdateVfoDisplay;
@@ -2981,6 +3077,8 @@ begin
// Какой VFO привязан к TX (для split-полос фильтра): 1=B при FSplitTxB, иначе FActiveVfo.
if FSplitTxB then FSpecView.TXVfoIndex := 1
else FSpecView.TXVfoIndex := FActiveVfo;
if UpdateWidebandFrequencyView and FShowWideband and (PbWideband <> nil) then
PbWideband.Invalidate;
end;
@@ -3063,6 +3161,7 @@ begin
// PbSpectrum.Height может быть ненулевым при FSpectrumHeight=0,
// что вызвало бы сброс буфера водопада на каждом тике.
if (FSpectrumWidth = 0) or
(FShowWideband and (PbWideband.Width <> FSpectrumWidth)) or
(FShowSpectrum and ((PbSpectrum.Width <> FSpectrumWidth) or
(PbSpectrum.Height <> FSpectrumHeight))) or
(FShowWaterfall and not FShowSpectrum and
@@ -3147,6 +3246,11 @@ begin
PbWaterfall.Invalidate;
FWaterfallDirty := False;
end;
if FWidebandDirty and FShowWideband then
begin
PbWideband.Invalidate;
FWidebandDirty := False;
end;
end
else
begin
@@ -3565,6 +3669,14 @@ begin
FLastDDCIndex := DDCIndex;
end;
procedure TMainForm.OnWidebandCB(ADCIndex: Integer;
const Samples: array of SmallInt; Count: Integer);
begin
if (not FShowWideband) or (FWidebandView = nil) then Exit;
FWidebandView.SetSamples(Samples, Count);
FWidebandDirty := True;
end;
procedure TMainForm.DoUpdateDDCSeq(DDCIdx: Integer; Seq: LongWord);
begin
SetStatusText(4, 'RX running');
@@ -4121,6 +4233,7 @@ begin
FSpecView.ResetWfAvgBuf;
FShowSpectrum := G_Settings.ShowSpectrum;
FShowWaterfall := G_Settings.ShowWaterfall;
FShowWideband := G_Settings.ShowWideband;
FDisplayDuplex := G_Settings.DisplayDuplex;
if BtnDUP <> nil then StyleButton(BtnDUP, FDisplayDuplex);
if G_Settings.DisplayFPS > 0 then
@@ -4192,11 +4305,20 @@ begin
GenPkt.Flags37 := $08;
GenPkt.Flags38 := $01;
GenPkt.PAConfig := $01;
GenPkt.WBPort[0] := (PORT_WIDEBAND_ADC0 shr 8) and $FF;
GenPkt.WBPort[1] := PORT_WIDEBAND_ADC0 and $FF;
GenPkt.WBEnable := IfThen(FShowWideband, 1, 0);
GenPkt.WBSamplesPerPkt[0] := 512 shr 8;
GenPkt.WBSamplesPerPkt[1] := 512 and $FF;
GenPkt.WBSampleSize := 16;
GenPkt.WBUpdateRate := 70;
GenPkt.WBPacketsPerFrame := 32;
if FNetwork.Device.BoardType = 5 then
GenPkt.AlexEnable := $03
else
GenPkt.AlexEnable := $01;
FNetwork.SendGeneralPacket(GenPkt);
FNetwork.ConfigureWideband(0, FShowWideband, 512, 16, 70, 32);
if FNetwork.Device.BoardType in [3, 4, 5] then
FActiveDDC := 2
@@ -4255,6 +4377,7 @@ begin
StyleButton(BtnStartStop, True);
SetStatusText(2, 'Running');
BtnMOX.Enabled := True;
ResizeSpectrumPanels;
end;
@@ -4849,6 +4972,94 @@ begin
end;
end;
procedure TMainForm.DoWidebandClick(PixelX: Integer; PanelWidth: Integer);
var
FreqHz: Double;
ClickFreq: Int64;
StepHz: Int64;
BandIdx: Integer;
WasCTun: Boolean;
begin
if (FWidebandView = nil) or
(not FWidebandView.TryPixelToFrequency(PixelX, PanelWidth, FreqHz)) then Exit;
if (FMode = MODE_FM) and FFMStepOn then
StepHz := FM_STEP_HZ[FFMStepIdx]
else
StepHz := 100;
ClickFreq := Round(FreqHz);
ClickFreq := (ClickFreq div StepHz) * StepHz;
if (FCurrentXvtr >= 0) and (FCurrentXvtr < CFG_XVTR_COUNT) and
FXvtrSettings.Entries[FCurrentXvtr].Enabled then
begin
if ClickFreq < FXvtrSettings.Entries[FCurrentXvtr].FreqBegin then
ClickFreq := Round(FXvtrSettings.Entries[FCurrentXvtr].FreqBegin);
if ClickFreq > FXvtrSettings.Entries[FCurrentXvtr].FreqEnd then
ClickFreq := Round(FXvtrSettings.Entries[FCurrentXvtr].FreqEnd);
end;
if FCurrentXvtr < 0 then
begin
BandIdx := FreqToBandIdx(ClickFreq);
if (BandIdx >= 0) and (BandIdx <> FCurrentBand) then
begin
if FDevConnected then
begin
SaveCurrentBand;
FSettings.Save;
end;
FCurrentBand := BandIdx;
RestoreBand(FCurrentBand);
end;
end;
if FActiveVfo = 0 then
begin
WasCTun := FCTun;
FCTun := False;
ApplyVfoA(ClickFreq);
FCTun := WasCTun;
if WasCTun and FWDSPReady then
FDSPEngine.SetShift(0.0);
end
else
begin
FVfoB := ClickFreq;
FreqDispB.Frequency := Round(FVfoB);
FCenterFreq := ClickFreq;
if FWDSPReady then FDSPEngine.SetShift(0.0);
if FNetwork.Connected and FNetwork.Running then
begin
FNetwork.UpdateState(XvtrTranslate(FCenterFreq),
XvtrTranslate(ActiveTXFreqHz), FDriveLevel, FTransmitting, True, True);
FNetwork.SendFullHP;
end;
if FCurrentXvtr < 0 then
begin
BandIdx := FreqToBandIdx(FVfoB);
if (BandIdx >= 0) and (BandIdx <> FCurrentBand) then
begin
if FCurrentBand >= 0 then
StyleButton(BtnBand[FCurrentBand], False);
FCurrentBand := BandIdx;
StyleButton(BtnBand[FCurrentBand], True);
end;
end;
UpdateVfoDisplay;
SyncSpecViewFreq;
FSpectrumDirty := True;
PbSpectrum.Invalidate;
end;
FSpecView.InvalidateRulerCache;
if PbRuler <> nil then PbRuler.Invalidate;
FWaterfallDirty := True;
if PbWaterfall <> nil then PbWaterfall.Invalidate;
if UpdateWidebandFrequencyView and (PbWideband <> nil) then
PbWideband.Invalidate;
end;
procedure TMainForm.DoSpectrumDrag(PixelX: Integer; PanelWidth: Integer);
var
dPix: Integer;
@@ -4943,6 +5154,13 @@ begin
end;
end;
procedure TMainForm.PbWidebandMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
if Button = mbLeft then
DoWidebandClick(X, TControl(Sender).Width);
end;
procedure TMainForm.PbSpectrumMouseMove(Sender: TObject; Shift: TShiftState;
X, Y: Integer);
begin
@@ -6726,6 +6944,9 @@ begin
FSpecView.DrawWaterfall;
PbWaterfall.Invalidate;
if PbRuler <> nil then PbRuler.Invalidate;
UpdateWidebandFrequencyView;
if PbWideband <> nil then PbWideband.Invalidate;
if PbWidebandRuler <> nil then PbWidebandRuler.Invalidate;
PushXvtrToWeb;
end;
@@ -6758,6 +6979,9 @@ begin
// RestoreBand сам обновит подсветку HF band-кнопки и сетевое состояние.
if (FCurrentBand >= 0) and (FCurrentBand < CFG_BAND_COUNT) then
RestoreBand(FCurrentBand);
UpdateWidebandFrequencyView;
if PbWideband <> nil then PbWideband.Invalidate;
if PbWidebandRuler <> nil then PbWidebandRuler.Invalidate;
PushXvtrToWeb;
end;
@@ -6772,6 +6996,9 @@ begin
DeactivateXvtr;
// Перепрошить XVTR в сеть (если параметры текущего изменились)
ApplyXvtrToNetwork;
UpdateWidebandFrequencyView;
if PbWideband <> nil then PbWideband.Invalidate;
if PbWidebandRuler <> nil then PbWidebandRuler.Invalidate;
PushXvtrToWeb;
if FDevConnected then
begin
@@ -7046,10 +7273,14 @@ begin
FSettings.Save;
end;
procedure TMainForm.ApplyVisibility(ShowSpectrum, ShowWaterfall: Boolean);
procedure TMainForm.ApplyVisibility(ShowSpectrum, ShowWaterfall,
ShowWideband: Boolean);
begin
FShowSpectrum := ShowSpectrum;
FShowWaterfall := ShowWaterfall;
FShowWideband := ShowWideband;
if Assigned(FNetwork) and FNetwork.Connected then
FNetwork.ConfigureWideband(0, FShowWideband, 512, 16, 70, 32);
ResizeSpectrumPanels;
end;
@@ -7134,7 +7365,7 @@ begin
FCATLastGlobal.CATTcpPort);
// Перечисляем PA устройства
SF.RefreshAudioDevices(FAudioOut, FAudioIn);
SF.LoadVisibility(FShowSpectrum, FShowWaterfall);
SF.LoadVisibility(FShowSpectrum, FShowWaterfall, FShowWideband);
SF.LoadWfAGCNF(FWfAGCEnabled, FWfNFEnabled);
SF.LoadADCSettings(FDitherEnabled, FRandomEnabled);
SF.LoadWebSettings(FWebEnabled, FWebPort, FWebBindAddr, FWebUser, FWebPass);