mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 17:27:32 +00:00
feat(spectrum): zoom + pan для спектра и водопада (WDSP span-clip)
Зум через нативный span-clip WDSP (fscLin/fscHin в SetAnalyzer): clp=0, видимое окно сужается клипом, при зуме 0 полоса == sample rate (старое поведение сохранено). Конвенция как в Thetis: ZoomFactor 0=без зума..1=макс. - WDSPEngine: SetZoomPan + расчёт span-clip, ViewLowHz/ViewHighHz. - RadioController: GetViewWindow (единая истина видимого окна для оверлеев и мыши), ApplyZoom, ZoomIn/Out по лестнице кратностей, ZoomDefault. Якорь зума кнопками — на маркере активного VFO. rfZoom. - Per-band персист зума/пана (TBandSettings + Save/Load/Restore). - MainForm: маршрутизация оверлеев/кликов через GetViewWindow (покрывает CPU и OpenGL), резерв строки PANZOOM_H в layout + сплиттере, rfZoom-рендер. - WebAdapter: web-спектр/наведение на view-окно (делит те же WDSP-пиксели). - PanZoomBar (новый): нижняя полоса = линейка полного диапазона (частоты + деления), поверх окно-селектор «стекло» (полупрозрачное, объёмная фаска). Середина=пан, края=зум, клик мимо=центрировать, двойной клик=сброс. Сборка ewsdr (--ws=qt6) и ewsdrd — ОК. На железе не проверено. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+55
-2
@@ -204,6 +204,16 @@ type
|
||||
FWfAvgMode: Integer;
|
||||
FWfAvgTimeMS: Double;
|
||||
|
||||
// Zoom/pan анализатора (span-clip в SetAnalyzer). Конвенция WDSP/Thetis:
|
||||
// FZoomFactor 0.0..1.0, где 0.0 = весь span (без зума), 1.0 = максимум.
|
||||
// FPanSlider 0..1 (положение окна по span). FViewLowHz/FViewHighHz —
|
||||
// границы видимого окна в Гц относительно DDC-центра (low отрицательный),
|
||||
// пересчитываются в ApplyRXAnalyzerSettings.
|
||||
FZoomFactor: Double;
|
||||
FPanSlider: Double;
|
||||
FViewLowHz: Double;
|
||||
FViewHighHz: Double;
|
||||
|
||||
FOnAudio: TOnAudioReady;
|
||||
FOnSpectrum: TOnSpectrumReady;
|
||||
FOnWaterfall: TOnWaterfallReady;
|
||||
@@ -431,6 +441,14 @@ type
|
||||
// S-meter
|
||||
function GetSMeterDBm: Double;
|
||||
|
||||
// Zoom/pan: задать коэффициент зума и положение пана и переармировать
|
||||
// RX-анализатор. View* — границы видимого окна (Гц отн. DDC-центра).
|
||||
procedure SetZoomPan(AZoom, APan: Double);
|
||||
property ZoomFactor: Double read FZoomFactor;
|
||||
property PanSlider: Double read FPanSlider;
|
||||
property ViewLowHz: Double read FViewLowHz;
|
||||
property ViewHighHz: Double read FViewHighHz;
|
||||
|
||||
property Initialized: Boolean read FInitialized;
|
||||
property SampleRate: Integer read FSampleRate;
|
||||
property TXSampleRate: Integer read FTXSampleRate;
|
||||
@@ -768,6 +786,10 @@ begin
|
||||
FWfDetector := 0; // Peak
|
||||
FWfAvgMode := 3; // Log Recursive
|
||||
FWfAvgTimeMS := 120.0;
|
||||
FZoomFactor := 0.0; // 0.0 = весь span, без зума
|
||||
FPanSlider := 0.5; // окно по центру
|
||||
FViewLowHz := -FSampleRate / 2.0;
|
||||
FViewHighHz := FSampleRate / 2.0;
|
||||
|
||||
SetLength(FRXIn, FBufSize * 2); // in_size пар @ FSampleRate (4096*2)
|
||||
SetLength(FRXOut, FAudioBufSize * 2); // out_size пар @ FAudioRate (1024*2)
|
||||
@@ -906,7 +928,11 @@ procedure TWDSPEngine.ApplyRXAnalyzerSettings;
|
||||
var
|
||||
AnalyzerFFT, AvgCount, WfAvgCount, MaxW, Overlap, DisplayPixels: Integer;
|
||||
Backmult, WfBackmult: Double;
|
||||
const KEEP_TIME_SEC = 0.10;
|
||||
Bins, SpanClipL, SpanClipH, ZWidth: Integer;
|
||||
BinWidth, BW, ZoomSlider: Double;
|
||||
const
|
||||
KEEP_TIME_SEC = 0.10;
|
||||
ZOOM_LIMIT = 100.0; // максимальная кратность зума
|
||||
begin
|
||||
AnalyzerFFT := CalcDisplayFFTSize;
|
||||
FDispPos := 0;
|
||||
@@ -919,8 +945,25 @@ begin
|
||||
AverageTimeToParams(FSpecAvgMode, FSpecAvgTimeMS, AvgCount, Backmult);
|
||||
AverageTimeToParams(FWfAvgMode, FWfAvgTimeMS, WfAvgCount, WfBackmult);
|
||||
|
||||
// Zoom/pan: считаем span-clip для SetAnalyzer (fscLin/fscHin).
|
||||
// clp=0 — оставляем полный span FFT, чтобы при зуме 0.0 видимая полоса была
|
||||
// ровно == FSampleRate (остальной код это предполагает). Зум делает только
|
||||
// span-clip: FZoomFactor=0.0 → ZWidth=Bins → SpanClipL=SpanClipH=0.
|
||||
BinWidth := FSampleRate / AnalyzerFFT;
|
||||
Bins := AnalyzerFFT;
|
||||
BW := Bins * BinWidth;
|
||||
ZoomSlider := Log10(9.0 * EnsureRange(FZoomFactor, 0.0, 1.0) + 1.0);
|
||||
ZWidth := Round(Bins * (1.0 - (1.0 - 1.0 / ZOOM_LIMIT) * ZoomSlider));
|
||||
ZWidth := EnsureRange(ZWidth, 1, Bins);
|
||||
SpanClipL := Floor(EnsureRange(FPanSlider, 0.0, 1.0) * (Bins - ZWidth));
|
||||
SpanClipH := Bins - ZWidth - SpanClipL;
|
||||
// Видимое окно в Гц относительно DDC-центра (low отрицательный). Поправка
|
||||
// BinWidth/2 — у комплексного FFT на одну отрицательную ячейку больше.
|
||||
FViewLowHz := -(0.5 * BW - SpanClipL * BinWidth + BinWidth / 2.0);
|
||||
FViewHighHz := (0.5 * BW - SpanClipH * BinWidth - BinWidth / 2.0);
|
||||
|
||||
SetAnalyzer(RX_DISP_ID, 2, 1, 1, @FFlp[0], AnalyzerFFT, DISPLAY_BLOCK_SIZE,
|
||||
FWindowType, 14.0, Overlap, 0, 0.0, 0.0, DisplayPixels,
|
||||
FWindowType, 14.0, Overlap, 0, SpanClipL, SpanClipH, DisplayPixels,
|
||||
1, 0, 0.0, 0.0, MaxW);
|
||||
SetDisplayDetectorMode(RX_DISP_ID, 0, FSpecDetector);
|
||||
SetDisplayAverageMode (RX_DISP_ID, 0, FSpecAvgMode);
|
||||
@@ -978,6 +1021,16 @@ begin
|
||||
ApplyTXAnalyzerSettings;
|
||||
end;
|
||||
|
||||
procedure TWDSPEngine.SetZoomPan(AZoom, APan: Double);
|
||||
// Задаёт зум/пан и переармирует RX-анализатор (SetAnalyzer внутри WDSP
|
||||
// защищён собственной критической секцией, дисплей-поток не мешает).
|
||||
begin
|
||||
FZoomFactor := EnsureRange(AZoom, 0.0, 1.0);
|
||||
FPanSlider := EnsureRange(APan, 0.0, 1.0);
|
||||
if FAnalyzerOpen then
|
||||
ApplyRXAnalyzerSettings;
|
||||
end;
|
||||
|
||||
procedure TWDSPEngine.CloseAnalyzer;
|
||||
begin
|
||||
if not FAnalyzerOpen then Exit;
|
||||
|
||||
Reference in New Issue
Block a user