mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 20:37:33 +00:00
Align spectrum grid and ruler to FM tuning step
When FM mode is active, vertical grid lines and frequency labels snap to FM channel boundaries (FM_STEP_HZ) instead of the fixed 8-division layout. Grid and label density auto-adapt: lines thinner than 4px are thinned, labels are spaced so they never overlap (short tick shown between labeled lines). Grid cache invalidates on center-freq, step, or span changes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -2797,6 +2797,10 @@ begin
|
|||||||
FSpecView.SpanHz := FSpanHz;
|
FSpecView.SpanHz := FSpanHz;
|
||||||
FSpecView.Mode := FMode;
|
FSpecView.Mode := FMode;
|
||||||
FSpecView.FilterBW := FFilterBW;
|
FSpecView.FilterBW := FFilterBW;
|
||||||
|
if FMode = MODE_FM then
|
||||||
|
FSpecView.FMGridStepHz := FM_STEP_HZ[FFMStepIdx]
|
||||||
|
else
|
||||||
|
FSpecView.FMGridStepHz := 0;
|
||||||
// TX overlay следует за активной TX-частотой (учитывает split). При CTUN
|
// TX overlay следует за активной TX-частотой (учитывает split). При CTUN
|
||||||
// спектр TX будет отрисован на VFO, а не в центре дисплея.
|
// спектр TX будет отрисован на VFO, а не в центре дисплея.
|
||||||
FSpecView.TXFreq := ActiveTXFreqHz;
|
FSpecView.TXFreq := ActiveTXFreqHz;
|
||||||
@@ -4805,6 +4809,8 @@ begin
|
|||||||
if FBtnFMSteps[Idx] <> nil then
|
if FBtnFMSteps[Idx] <> nil then
|
||||||
FBtnFMSteps[Idx].Active := True;
|
FBtnFMSteps[Idx].Active := True;
|
||||||
if FWebServer <> nil then FWebServer.FMStepIdx := Idx;
|
if FWebServer <> nil then FWebServer.FMStepIdx := Idx;
|
||||||
|
if (FMode = MODE_FM) and (FSpecView <> nil) then
|
||||||
|
FSpecView.FMGridStepHz := FM_STEP_HZ[Idx];
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TMainForm.BtnFMStepClick(Sender: TObject);
|
procedure TMainForm.BtnFMStepClick(Sender: TObject);
|
||||||
|
|||||||
+93
-2
@@ -38,6 +38,9 @@ type
|
|||||||
FGridBitmap: TBitmap;
|
FGridBitmap: TBitmap;
|
||||||
FGridBitmapW: Integer;
|
FGridBitmapW: Integer;
|
||||||
FGridBitmapH: Integer;
|
FGridBitmapH: Integer;
|
||||||
|
FFMGridLastCenter: Double;
|
||||||
|
FFMGridLastStepHz: Double;
|
||||||
|
FFMGridLastSpan: Double;
|
||||||
FRulerBitmap: TBitmap;
|
FRulerBitmap: TBitmap;
|
||||||
FSpecGradImg: TLazIntfImage;
|
FSpecGradImg: TLazIntfImage;
|
||||||
FSpecGradBmp: TBitmap;
|
FSpecGradBmp: TBitmap;
|
||||||
@@ -79,6 +82,7 @@ type
|
|||||||
FSpecRefLevel: Double;
|
FSpecRefLevel: Double;
|
||||||
FSpecRange: Double;
|
FSpecRange: Double;
|
||||||
FSpecGridStep: Double;
|
FSpecGridStep: Double;
|
||||||
|
FFMGridStepHz: Double;
|
||||||
|
|
||||||
// ── TX overlay (Thetis-style: при TX данные в FSpectrumBuf приходят
|
// ── TX overlay (Thetis-style: при TX данные в FSpectrumBuf приходят
|
||||||
// от TX-анализатора @ FTXSpanHz Гц вокруг FTXFreq, и должны быть
|
// от TX-анализатора @ FTXSpanHz Гц вокруг FTXFreq, и должны быть
|
||||||
@@ -128,6 +132,7 @@ type
|
|||||||
// ── Приватные методы рендеринга ───────────────────────────────────────────
|
// ── Приватные методы рендеринга ───────────────────────────────────────────
|
||||||
function ActiveVfoFreq: Double;
|
function ActiveVfoFreq: Double;
|
||||||
function ScaleX(X, Total, Width: Integer): Integer;
|
function ScaleX(X, Total, Width: Integer): Integer;
|
||||||
|
procedure SetFMGridStepHz(V: Double);
|
||||||
procedure DrawSpectrumGradient(const SpPts: array of TPoint; W, H: Integer);
|
procedure DrawSpectrumGradient(const SpPts: array of TPoint; W, H: Integer);
|
||||||
procedure DrawMarkerLine(C: TCanvas; W, H: Integer);
|
procedure DrawMarkerLine(C: TCanvas; W, H: Integer);
|
||||||
// Альфа-blending вертикальной полосы поверх FSpectrumBitmap (pf32bit, BGRA).
|
// Альфа-blending вертикальной полосы поверх FSpectrumBitmap (pf32bit, BGRA).
|
||||||
@@ -167,6 +172,7 @@ type
|
|||||||
property SpecRefLevel: Double read FSpecRefLevel write FSpecRefLevel;
|
property SpecRefLevel: Double read FSpecRefLevel write FSpecRefLevel;
|
||||||
property SpecRange: Double read FSpecRange write FSpecRange;
|
property SpecRange: Double read FSpecRange write FSpecRange;
|
||||||
property SpecGridStep: Double read FSpecGridStep write FSpecGridStep;
|
property SpecGridStep: Double read FSpecGridStep write FSpecGridStep;
|
||||||
|
property FMGridStepHz: Double read FFMGridStepHz write SetFMGridStepHz;
|
||||||
|
|
||||||
// ── TX overlay ────────────────────────────────────────────────────────────
|
// ── TX overlay ────────────────────────────────────────────────────────────
|
||||||
property TXMode: Boolean read FTXMode write FTXMode;
|
property TXMode: Boolean read FTXMode write FTXMode;
|
||||||
@@ -404,6 +410,8 @@ begin
|
|||||||
FWfIntfImg := nil;
|
FWfIntfImg := nil;
|
||||||
FWfBitmapW := 0; FWfBitmapH := 0;
|
FWfBitmapW := 0; FWfBitmapH := 0;
|
||||||
FGridBitmapW := 0; FGridBitmapH := 0;
|
FGridBitmapW := 0; FGridBitmapH := 0;
|
||||||
|
FFMGridLastCenter := -1.0; FFMGridLastStepHz := -1.0; FFMGridLastSpan := -1.0;
|
||||||
|
FFMGridStepHz := 0.0;
|
||||||
FSpPtsLen := 0;
|
FSpPtsLen := 0;
|
||||||
FRulerLastFreq := -1.0; FRulerLastVfo := -1.0;
|
FRulerLastFreq := -1.0; FRulerLastVfo := -1.0;
|
||||||
// Водопад AGC defaults
|
// Водопад AGC defaults
|
||||||
@@ -467,6 +475,16 @@ begin
|
|||||||
else Result := Round(X / Total * Width);
|
else Result := Round(X / Total * Width);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
procedure TSpectrumView.SetFMGridStepHz(V: Double);
|
||||||
|
begin
|
||||||
|
if Abs(FFMGridStepHz - V) > 0.5 then
|
||||||
|
begin
|
||||||
|
FFMGridStepHz := V;
|
||||||
|
InvalidateGridCache;
|
||||||
|
InvalidateRulerCache;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
procedure TSpectrumView.DrawMarkerLine(C: TCanvas; W, H: Integer);
|
procedure TSpectrumView.DrawMarkerLine(C: TCanvas; W, H: Integer);
|
||||||
var MX: Integer; MarkerFreq: Double; MarkerLbl: string;
|
var MX: Integer; MarkerFreq: Double; MarkerLbl: string;
|
||||||
begin
|
begin
|
||||||
@@ -734,6 +752,8 @@ var
|
|||||||
S0, S1: Integer;
|
S0, S1: Integer;
|
||||||
InvRange: Double;
|
InvRange: Double;
|
||||||
LabelBandW, SrcCount: Integer;
|
LabelBandW, SrcCount: Integer;
|
||||||
|
GridFreqS, GridLine, pixPerStep: Double;
|
||||||
|
N, gridMult: Integer;
|
||||||
begin
|
begin
|
||||||
if FSpectrumBitmap = nil then Exit;
|
if FSpectrumBitmap = nil then Exit;
|
||||||
W := FSpectrumBitmap.Width; H := FSpectrumBitmap.Height;
|
W := FSpectrumBitmap.Width; H := FSpectrumBitmap.Height;
|
||||||
@@ -744,9 +764,16 @@ begin
|
|||||||
InvRange := 1.0 / (DBmax - DBmin);
|
InvRange := 1.0 / (DBmax - DBmin);
|
||||||
|
|
||||||
// ── 1. Фон+сетка из кэша ──────────────────────────────────────────────────
|
// ── 1. Фон+сетка из кэша ──────────────────────────────────────────────────
|
||||||
if (FGridBitmapW <> W) or (FGridBitmapH <> H) then
|
if (FGridBitmapW <> W) or (FGridBitmapH <> H) or
|
||||||
|
((FFMGridStepHz > 0) and
|
||||||
|
((Abs(FCenterFreq - FFMGridLastCenter) > 0.5) or
|
||||||
|
(FFMGridStepHz <> FFMGridLastStepHz) or
|
||||||
|
(Abs(FSpanHz - FFMGridLastSpan) > 1.0))) then
|
||||||
begin
|
begin
|
||||||
FGridBitmapW := W; FGridBitmapH := H;
|
FGridBitmapW := W; FGridBitmapH := H;
|
||||||
|
FFMGridLastCenter := FCenterFreq;
|
||||||
|
FFMGridLastStepHz := FFMGridStepHz;
|
||||||
|
FFMGridLastSpan := FSpanHz;
|
||||||
FGridBitmap.SetSize(W, H);
|
FGridBitmap.SetSize(W, H);
|
||||||
GC := FGridBitmap.Canvas;
|
GC := FGridBitmap.Canvas;
|
||||||
PaintVerticalGradient(GC, W, H, FTheme.SpecGradTop, FTheme.SpecGradBot);
|
PaintVerticalGradient(GC, W, H, FTheme.SpecGradTop, FTheme.SpecGradBot);
|
||||||
@@ -767,6 +794,35 @@ begin
|
|||||||
GC.TextOut(1, Yp - 9, Format('%4.0f', [dB]));
|
GC.TextOut(1, Yp - 9, Format('%4.0f', [dB]));
|
||||||
dB := dB - FSpecGridStep;
|
dB := dB - FSpecGridStep;
|
||||||
end;
|
end;
|
||||||
|
if FFMGridStepHz > 0 then
|
||||||
|
begin
|
||||||
|
GridFreqS := FCenterFreq - FSpanHz / 2;
|
||||||
|
pixPerStep := W * FFMGridStepHz / FSpanHz;
|
||||||
|
if pixPerStep >= 1.0 then
|
||||||
|
gridMult := Max(1, Ceil(4.0 / pixPerStep))
|
||||||
|
else
|
||||||
|
gridMult := 0;
|
||||||
|
if gridMult > 0 then
|
||||||
|
begin
|
||||||
|
N := Ceil(GridFreqS / FFMGridStepHz);
|
||||||
|
GridLine := N * FFMGridStepHz;
|
||||||
|
while GridLine <= FCenterFreq + FSpanHz / 2 + 0.5 do
|
||||||
|
begin
|
||||||
|
if (N mod gridMult) = 0 then
|
||||||
|
begin
|
||||||
|
GX := Round((GridLine - GridFreqS) / FSpanHz * W);
|
||||||
|
if (GX >= 0) and (GX < W) then
|
||||||
|
begin
|
||||||
|
GC.Pen.Color := FTheme.SpecGrid;
|
||||||
|
GC.MoveTo(GX, 0); GC.LineTo(GX, H);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
Inc(N);
|
||||||
|
GridLine := GridLine + FFMGridStepHz;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
end else
|
||||||
|
begin
|
||||||
for i := 0 to 8 do
|
for i := 0 to 8 do
|
||||||
begin
|
begin
|
||||||
GX := ScaleX(i, 8, W);
|
GX := ScaleX(i, 8, W);
|
||||||
@@ -774,6 +830,7 @@ begin
|
|||||||
GC.MoveTo(GX, 0); GC.LineTo(GX, H);
|
GC.MoveTo(GX, 0); GC.LineTo(GX, H);
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
end;
|
||||||
LCLIntf.BitBlt(C.Handle, 0, 0, W, H, FGridBitmap.Canvas.Handle, 0, 0, $CC0020);
|
LCLIntf.BitBlt(C.Handle, 0, 0, W, H, FGridBitmap.Canvas.Handle, 0, 0, $CC0020);
|
||||||
|
|
||||||
// ── 2. Полоса фильтра ─────────────────────────────────────────────────────
|
// ── 2. Полоса фильтра ─────────────────────────────────────────────────────
|
||||||
@@ -1074,8 +1131,9 @@ end;
|
|||||||
procedure TSpectrumView.DrawRuler;
|
procedure TSpectrumView.DrawRuler;
|
||||||
var
|
var
|
||||||
C: TCanvas; W, H, i, X: Integer;
|
C: TCanvas; W, H, i, X: Integer;
|
||||||
FreqStart, FreqHz: Double; VfoX: Integer;
|
FreqStart, FreqHz, GridLine, pixPerStep: Double; VfoX: Integer;
|
||||||
Lbl: string; TW: Integer;
|
Lbl: string; TW: Integer;
|
||||||
|
N, labelMult: Integer;
|
||||||
begin
|
begin
|
||||||
if FPbRuler = nil then Exit;
|
if FPbRuler = nil then Exit;
|
||||||
W := FPbRuler.Width; H := FPbRuler.Height;
|
W := FPbRuler.Width; H := FPbRuler.Height;
|
||||||
@@ -1094,6 +1152,38 @@ begin
|
|||||||
C.Font.Name := 'Courier New'; C.Font.Size := 7;
|
C.Font.Name := 'Courier New'; C.Font.Size := 7;
|
||||||
C.Brush.Style := bsClear;
|
C.Brush.Style := bsClear;
|
||||||
FreqStart := FCenterFreq - FSpanHz / 2;
|
FreqStart := FCenterFreq - FSpanHz / 2;
|
||||||
|
if FFMGridStepHz > 0 then
|
||||||
|
begin
|
||||||
|
pixPerStep := W * FFMGridStepHz / FSpanHz;
|
||||||
|
if pixPerStep >= 1.0 then
|
||||||
|
labelMult := Max(1, Ceil((C.TextWidth('000.000') + 6) / pixPerStep))
|
||||||
|
else
|
||||||
|
labelMult := MaxInt;
|
||||||
|
N := Ceil(FreqStart / FFMGridStepHz);
|
||||||
|
GridLine := N * FFMGridStepHz;
|
||||||
|
while GridLine <= FCenterFreq + FSpanHz / 2 + 0.5 do
|
||||||
|
begin
|
||||||
|
X := Round((GridLine - FreqStart) / FSpanHz * W);
|
||||||
|
if (X >= 0) and (X < W) then
|
||||||
|
begin
|
||||||
|
C.Pen.Color := FTheme.RulerBorder;
|
||||||
|
if (N mod labelMult) = 0 then
|
||||||
|
begin
|
||||||
|
C.MoveTo(X, 0); C.LineTo(X, H div 2);
|
||||||
|
Lbl := Format('%.3f', [GridLine / 1e6]);
|
||||||
|
TW := C.TextWidth(Lbl);
|
||||||
|
C.Font.Color := FTheme.RulerText;
|
||||||
|
C.TextOut(X - TW div 2, H div 2 - 1, Lbl);
|
||||||
|
end else
|
||||||
|
begin
|
||||||
|
C.MoveTo(X, 0); C.LineTo(X, H div 4);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
Inc(N);
|
||||||
|
GridLine := GridLine + FFMGridStepHz;
|
||||||
|
end;
|
||||||
|
end else
|
||||||
|
begin
|
||||||
for i := 0 to 8 do
|
for i := 0 to 8 do
|
||||||
begin
|
begin
|
||||||
X := ScaleX(i, 8, W);
|
X := ScaleX(i, 8, W);
|
||||||
@@ -1105,6 +1195,7 @@ begin
|
|||||||
C.Font.Color := FTheme.RulerText;
|
C.Font.Color := FTheme.RulerText;
|
||||||
C.TextOut(X - TW div 2, H div 2 - 1, Lbl);
|
C.TextOut(X - TW div 2, H div 2 - 1, Lbl);
|
||||||
end;
|
end;
|
||||||
|
end;
|
||||||
VfoX := Round((ActiveVfoFreq - FCenterFreq + FSpanHz/2) / FSpanHz * W);
|
VfoX := Round((ActiveVfoFreq - FCenterFreq + FSpanHz/2) / FSpanHz * W);
|
||||||
if (VfoX >= 0) and (VfoX < W) then
|
if (VfoX >= 0) and (VfoX < W) then
|
||||||
begin
|
begin
|
||||||
|
|||||||
Reference in New Issue
Block a user