feat: QO-100 NB transponder bandplan overlay on spectrum

Add a thin colored strip along the bottom of the spectrum (above the
ruler) showing the QO-100 narrow-band transponder bandplan: mode
segments (CW ONLY / NB DIGI / DIGI / SSB / MIXED MODES) plus the three
beacons (CW / PSK / EXP) as muted-red cells at their ranges.

- New unit BandPlanOverlay.pas (TBandPlanOverlay), modelled on
  VfoOverlay: content rendered once into a cache bitmap, per-frame cost
  is just a constant-alpha composite (CPU) / one textured quad (GL).
  Cache invalidates only on view change (center/span/width) so it idles
  while the QO-100 downlink display is static.
- Wired into both render paths: SpectrumView (CPU composite) and
  SpectrumViewOpengl (texture, re-uploaded only when dirty).
- MainForm: SetView fed from SyncSpecViewFreq; visibility gated by
  InQO100 (Pluto + full-duplex transponder), like RX MUTE / BEACON.
- DPI-robust: strip height scales with Screen.PixelsPerInch and the font
  height is tied to the strip height (not point size), so labels always
  fit at any Windows scaling.
- Segment/beacon frequencies and colors are a single editable table.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-23 21:02:50 +03:00
co-authored by Claude Opus 4.8
parent 3bd37b956e
commit 432c0285f0
5 changed files with 382 additions and 6 deletions
+33 -1
View File
@@ -17,7 +17,7 @@ interface
uses
Classes, SysUtils, Graphics, Controls, Math, Types,
OpenGLContext, GL,
AppTheme, SpectrumView, VfoOverlay, WaterfallViewOpengl;
AppTheme, SpectrumView, VfoOverlay, BandPlanOverlay, WaterfallViewOpengl;
type
TGLTextureCache = record
@@ -34,6 +34,7 @@ type
FOverlayDirty: Boolean;
FSampleOverlayDirty: Boolean;
FVfoOverlayDirty: Boolean;
FBandOverlayDirty: Boolean;
FGridLabelTex: TGLTextureCache;
FAGCLabelTex: TGLTextureCache;
FAGCHangLabelTex: TGLTextureCache;
@@ -41,6 +42,7 @@ type
FADCOverlayTex: TGLTextureCache;
FSampleOverlayTex: TGLTextureCache;
FVfoOverlayTex: TGLTextureCache;
FBandOverlayTex: TGLTextureCache;
FLastAGCText: string;
FLastAGCY: Integer;
FLastAGCHangY: Integer;
@@ -55,6 +57,9 @@ type
FLastVfoY: Integer;
FLastVfoW: Integer;
FLastVfoH: Integer;
FLastBandW: Integer;
FLastBandCenter: Double;
FLastBandSpan: Double;
FGLSpectrumW: Integer;
FGLSpectrumH: Integer;
FSpY: array of Integer;
@@ -123,6 +128,9 @@ begin
FOverlayDirty := True;
FSampleOverlayDirty := True;
FVfoOverlayDirty := True;
FBandOverlayDirty := True;
FLastBandW := -MaxInt;
FLastBandCenter := -1; FLastBandSpan := -1;
FLastAGCY := -MaxInt;
FLastAGCHangY := -MaxInt;
FLastMarkerX := -MaxInt;
@@ -140,6 +148,7 @@ begin
DeleteTexture(FADCOverlayTex);
DeleteTexture(FSampleOverlayTex);
DeleteTexture(FVfoOverlayTex);
DeleteTexture(FBandOverlayTex);
end;
inherited Destroy;
end;
@@ -720,6 +729,29 @@ var
B: TBitmap;
OW, OH: Integer;
begin
// Бэндплан QO-100 — полоска внизу спектра (над ruler), под панелями оверлеев.
if Assigned(BandPlanOverlay) and BandPlanOverlay.Active then
begin
if FOverlayDirty or FBandOverlayDirty or FBandOverlayTex.Dirty or
(FLastBandW <> W) or
(Abs(FLastBandCenter - BandPlanOverlay.CenterFreq) >= 1.0) or
(Abs(FLastBandSpan - BandPlanOverlay.SpanHz) >= 1.0) then
begin
B := TBitmap.Create;
try
BandPlanOverlay.DrawOverlayBitmap(B, W);
UploadBitmap(FBandOverlayTex, B, False, BANDPLAN_ALPHA);
finally
B.Free;
end;
FLastBandW := W;
FLastBandCenter := BandPlanOverlay.CenterFreq;
FLastBandSpan := BandPlanOverlay.SpanHz;
FBandOverlayDirty := False;
end;
DrawTexture(FBandOverlayTex, 0, H - FBandOverlayTex.H);
end;
if Assigned(FSampleRateOverlay) then
begin
OW := Min(FSampleRateOverlay.Width, W - FSampleRateOverlay.Left);