mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-27 07:17:32 +00:00
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:
@@ -0,0 +1,310 @@
|
|||||||
|
unit BandPlanOverlay;
|
||||||
|
|
||||||
|
{
|
||||||
|
Оверлей бэндплана QO-100 (узкополосный транспондер) — тонкая цветная полоска
|
||||||
|
вдоль оси частот ВНИЗУ спектра (над ruler) с подписями сегментов.
|
||||||
|
|
||||||
|
Производительность — модель VfoOverlay: контент рендерится в кэш-битмап ОДИН раз
|
||||||
|
и пересобирается только при смене вида (center/span/размер/тема/enabled), а каждый
|
||||||
|
кадр идёт лишь композит полоски (W × STRIP_H) с постоянной альфой. Дорогих
|
||||||
|
попиксельных проходов на кадр нет. GL-путь берёт тот же битмап текстурой
|
||||||
|
(DrawOverlayBitmap) и заливает её только при dirty — одинаковый вид в CPU и GL,
|
||||||
|
т.к. GL-UploadBitmap поддерживает color-key + постоянную альфу (как у VfoOverlay).
|
||||||
|
|
||||||
|
Сегменты заданы таблицей в DOWNLINK-Гц (QO100_NB). Границы — по плану AMSAT-DL
|
||||||
|
NB-транспондера; правятся в одном месте.
|
||||||
|
}
|
||||||
|
|
||||||
|
{$mode objfpc}{$H+}
|
||||||
|
|
||||||
|
interface
|
||||||
|
|
||||||
|
uses
|
||||||
|
Classes, SysUtils, Controls, Graphics, Types, Math, Forms;
|
||||||
|
|
||||||
|
const
|
||||||
|
BANDPLAN_STRIP_BASE_H = 20; // базовая высота полоски @96dpi (чуть больше ruler=18);
|
||||||
|
// фактическая FStripH масштабируется по DPI экрана
|
||||||
|
BANDPLAN_ALPHA = 188; // глобальная альфа композита (0..255)
|
||||||
|
|
||||||
|
type
|
||||||
|
// Тип сегмента → цвет. Правится таблицей SEG_COLOR ниже.
|
||||||
|
TSegKind = (skBeacon, skCW, skSSB, skDigi, skNBDigi, skDV, skMixed);
|
||||||
|
|
||||||
|
TBandSeg = record
|
||||||
|
Lo, Hi: Double; // downlink-Гц
|
||||||
|
Name: string;
|
||||||
|
Kind: TSegKind;
|
||||||
|
end;
|
||||||
|
|
||||||
|
TBandPlanOverlay = class(TComponent)
|
||||||
|
private
|
||||||
|
FCenterFreq: Double;
|
||||||
|
FSpanHz: Double;
|
||||||
|
FEnabled: Boolean;
|
||||||
|
|
||||||
|
FCache: TBitmap; // W × STRIP_H, непрозрачный; альфа применяется при композите
|
||||||
|
FCacheW: Integer;
|
||||||
|
FCacheDirty: Boolean;
|
||||||
|
// ключ кэша — пересборка только при изменении
|
||||||
|
FKeyCenter: Double;
|
||||||
|
FKeySpan: Double;
|
||||||
|
FKeyW: Integer;
|
||||||
|
FKeyEnabled: Boolean;
|
||||||
|
FStripH: Integer; // фактическая высота полоски (px), масштаб по DPI
|
||||||
|
|
||||||
|
function FreqToX(FreqHz: Double; W: Integer): Integer;
|
||||||
|
procedure DrawSelf(C: TCanvas; W: Integer);
|
||||||
|
procedure RebuildCache(W: Integer);
|
||||||
|
function NeedsRebuild(W: Integer): Boolean;
|
||||||
|
procedure CompositeStrip(Target: TBitmap; DstY: Integer);
|
||||||
|
public
|
||||||
|
constructor Create(AOwner: TComponent); override;
|
||||||
|
destructor Destroy; override;
|
||||||
|
// Текущий вид спектра (downlink-домен). Дёргается при тюнинге/смене span.
|
||||||
|
procedure SetView(ACenterHz, ASpanHz: Double);
|
||||||
|
procedure SetEnabled(En: Boolean);
|
||||||
|
function Active: Boolean;
|
||||||
|
// CPU-путь: композит полоски внизу Target (над ruler).
|
||||||
|
procedure DrawOverlay(Target: TBitmap; W, H: Integer);
|
||||||
|
// GL-путь: рендер полоски в Target-битмап (W × STRIP_H) для заливки текстурой.
|
||||||
|
procedure DrawOverlayBitmap(Target: TBitmap; W: Integer);
|
||||||
|
property Enabled: Boolean read FEnabled;
|
||||||
|
property CenterFreq: Double read FCenterFreq; // для GL-инвалидизации текстуры
|
||||||
|
property SpanHz: Double read FSpanHz;
|
||||||
|
end;
|
||||||
|
|
||||||
|
implementation
|
||||||
|
|
||||||
|
const
|
||||||
|
// Палитра по типу сегмента (TColor = $00BBGGRR).
|
||||||
|
SEG_COLOR: array[TSegKind] of TColor = (
|
||||||
|
$005258C8, // skBeacon — приглушённый красный (с прохладным оттенком палитры)
|
||||||
|
$00C07820, // skCW — синий
|
||||||
|
$002F9A3A, // skSSB — зелёный
|
||||||
|
$00B0479E, // skDigi — фиолетовый (яркий)
|
||||||
|
$00794A6E, // skNBDigi — приглушённый фиолетовый (NB DIGI, тусклее DIGI)
|
||||||
|
$009A9A20, // skDV — бирюзовый
|
||||||
|
$00606060); // skMixed — серый
|
||||||
|
|
||||||
|
CLR_STRIP_BG = TColor($00181818); // фон полоски вне транспондера
|
||||||
|
CLR_STRIP_TOP = TColor($00585858); // верхняя кромка (отделить от спектра)
|
||||||
|
CLR_SEP = TColor($000C0C0C); // разделители сегментов
|
||||||
|
CLR_LBL = TColor($00F0F0F0); // подписи
|
||||||
|
|
||||||
|
// ── Бэндплан QO-100 NB-транспондера (DOWNLINK Гц) ────────────────────────────
|
||||||
|
// Транспондер 10489.500–10490.000 (500 кГц). Mode-заливки между маяками; маяки —
|
||||||
|
// отдельными ярко-янтарными ячейками на своих диапазонах. Границы правятся ЗДЕСЬ.
|
||||||
|
QO100_NB: array[0..5] of TBandSeg = (
|
||||||
|
(Lo: 10489505000.0; Hi: 10489540000.0; Name: 'CW ONLY'; Kind: skCW),
|
||||||
|
(Lo: 10489540000.0; Hi: 10489580000.0; Name: 'NB DIGI'; Kind: skNBDigi),
|
||||||
|
(Lo: 10489580000.0; Hi: 10489650000.0; Name: 'DIGI'; Kind: skDigi),
|
||||||
|
(Lo: 10489650000.0; Hi: 10489745000.0; Name: 'SSB'; Kind: skSSB),
|
||||||
|
(Lo: 10489755000.0; Hi: 10489850000.0; Name: 'SSB'; Kind: skSSB),
|
||||||
|
(Lo: 10489850000.0; Hi: 10489990000.0; Name: 'MIXED MODES'; Kind: skDV));
|
||||||
|
|
||||||
|
// Маяки QO-100 (диапазоны): нижний CW, средний PSK, верхний. Ярко-янтарные ячейки.
|
||||||
|
QO100_BEACONS: array[0..2] of TBandSeg = (
|
||||||
|
(Lo: 10489500000.0; Hi: 10489505000.0; Name: 'CW'; Kind: skBeacon), // нижний CW-маяк
|
||||||
|
(Lo: 10489745000.0; Hi: 10489755000.0; Name: 'PSK'; Kind: skBeacon), // средний PSK-маяк
|
||||||
|
(Lo: 10489990000.0; Hi: 10490000000.0; Name: 'EXP'; Kind: skBeacon)); // верхний эксп. маяк
|
||||||
|
|
||||||
|
function Darken(C: TColor; Num, Den: Integer): TColor;
|
||||||
|
var r, g, b: Integer;
|
||||||
|
begin
|
||||||
|
r := (C and $FF) * Num div Den;
|
||||||
|
g := ((C shr 8) and $FF) * Num div Den;
|
||||||
|
b := ((C shr 16) and $FF) * Num div Den;
|
||||||
|
Result := TColor((b shl 16) or (g shl 8) or r);
|
||||||
|
end;
|
||||||
|
|
||||||
|
constructor TBandPlanOverlay.Create(AOwner: TComponent);
|
||||||
|
begin
|
||||||
|
inherited Create(AOwner);
|
||||||
|
// Высота полоски масштабируется по DPI экрана (как RULER_H в MainForm), иначе на
|
||||||
|
// Windows-масштабе 125/150/200% полоска осталась бы фиксированно тонкой.
|
||||||
|
FStripH := Round(BANDPLAN_STRIP_BASE_H * Screen.PixelsPerInch / 96);
|
||||||
|
if FStripH < BANDPLAN_STRIP_BASE_H then FStripH := BANDPLAN_STRIP_BASE_H;
|
||||||
|
FCenterFreq := 10489750000.0;
|
||||||
|
FSpanHz := 500000.0;
|
||||||
|
FEnabled := False;
|
||||||
|
FCache := TBitmap.Create;
|
||||||
|
FCache.PixelFormat := pf32bit;
|
||||||
|
FCacheW := 0;
|
||||||
|
FCacheDirty := True;
|
||||||
|
FKeyCenter := -1; FKeySpan := -1; FKeyW := -1; FKeyEnabled := False;
|
||||||
|
end;
|
||||||
|
|
||||||
|
destructor TBandPlanOverlay.Destroy;
|
||||||
|
begin
|
||||||
|
FCache.Free;
|
||||||
|
inherited Destroy;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TBandPlanOverlay.SetView(ACenterHz, ASpanHz: Double);
|
||||||
|
begin
|
||||||
|
// Квантуем сравнение, чтобы микродрейф не вызывал постоянную пересборку.
|
||||||
|
if (Abs(ACenterHz - FCenterFreq) < 1.0) and (Abs(ASpanHz - FSpanHz) < 1.0) then Exit;
|
||||||
|
FCenterFreq := ACenterHz;
|
||||||
|
FSpanHz := ASpanHz;
|
||||||
|
FCacheDirty := True;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TBandPlanOverlay.SetEnabled(En: Boolean);
|
||||||
|
begin
|
||||||
|
if FEnabled = En then Exit;
|
||||||
|
FEnabled := En;
|
||||||
|
FCacheDirty := True;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TBandPlanOverlay.Active: Boolean;
|
||||||
|
begin
|
||||||
|
Result := FEnabled;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TBandPlanOverlay.FreqToX(FreqHz: Double; W: Integer): Integer;
|
||||||
|
begin
|
||||||
|
if FSpanHz <= 0 then Result := -1
|
||||||
|
else Result := Round((FreqHz - (FCenterFreq - FSpanHz / 2)) / FSpanHz * W);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TBandPlanOverlay.DrawSelf(C: TCanvas; W: Integer);
|
||||||
|
// Рендерит полоску в канвас битмапа W × FStripH, начало (0,0), НЕПРОЗРАЧНО.
|
||||||
|
// Прозрачность накладывается позже (CompositeStrip / GL FixedAlpha).
|
||||||
|
var
|
||||||
|
i, ty, th, SH: Integer;
|
||||||
|
|
||||||
|
// Единый стиль для всех ячеек (mode-сегменты И маяки): приглушённая заливка +
|
||||||
|
// яркая кромка цвета сверху + разделитель слева + подпись по центру если влезает.
|
||||||
|
procedure DrawSeg(const seg: TBandSeg);
|
||||||
|
var x1, x2, cx, tw: Integer; col: TColor;
|
||||||
|
begin
|
||||||
|
x1 := FreqToX(seg.Lo, W);
|
||||||
|
x2 := FreqToX(seg.Hi, W);
|
||||||
|
if (x2 <= 0) or (x1 >= W) then Exit; // вне экрана
|
||||||
|
if x1 < 0 then x1 := 0;
|
||||||
|
if x2 > W then x2 := W;
|
||||||
|
if x2 <= x1 then x2 := x1 + 1; // минимум 1px видимости (узкие маяки)
|
||||||
|
|
||||||
|
col := SEG_COLOR[seg.Kind];
|
||||||
|
C.Brush.Color := Darken(col, 3, 5); C.Brush.Style := bsSolid; C.Pen.Style := psClear;
|
||||||
|
C.FillRect(Rect(x1, 1, x2, SH));
|
||||||
|
C.Brush.Color := col; // яркая кромка-«легенда» сверху
|
||||||
|
C.FillRect(Rect(x1, 1, x2, 3));
|
||||||
|
|
||||||
|
C.Pen.Style := psSolid; C.Pen.Color := CLR_SEP; C.Pen.Width := 1;
|
||||||
|
C.MoveTo(x1, 1); C.LineTo(x1, SH);
|
||||||
|
|
||||||
|
tw := C.TextWidth(seg.Name);
|
||||||
|
if (x2 - x1) >= tw + 6 then
|
||||||
|
begin
|
||||||
|
cx := x1 + ((x2 - x1) - tw) div 2;
|
||||||
|
C.Brush.Style := bsClear; C.Font.Color := CLR_LBL;
|
||||||
|
C.TextOut(cx, ty, seg.Name);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
begin
|
||||||
|
SH := FStripH;
|
||||||
|
|
||||||
|
// Фон всей полоски (зона вне транспондера остаётся этим тёмным фоном).
|
||||||
|
C.Brush.Color := CLR_STRIP_BG; C.Brush.Style := bsSolid; C.Pen.Style := psClear;
|
||||||
|
C.FillRect(Rect(0, 0, W, SH));
|
||||||
|
|
||||||
|
// Высота шрифта привязана к высоте полоски (а не point-size), поэтому текст
|
||||||
|
// всегда влезает при любом DPI-масштабе Windows. Отриц. Height = glyph-px.
|
||||||
|
C.Font.Name := 'Courier New'; C.Font.Style := [];
|
||||||
|
C.Font.Height := -Max(8, Round(SH * 0.5));
|
||||||
|
th := C.TextHeight('BCN');
|
||||||
|
// Вертикальный центр под caps: +1 компенсирует пустой descent у заглавных.
|
||||||
|
ty := (SH - th) div 2 + 1;
|
||||||
|
if ty < 1 then ty := 1;
|
||||||
|
|
||||||
|
// Mode-сегменты, затем маяки (поверх) — одинаковый стиль.
|
||||||
|
for i := Low(QO100_NB) to High(QO100_NB) do DrawSeg(QO100_NB[i]);
|
||||||
|
for i := Low(QO100_BEACONS) to High(QO100_BEACONS) do DrawSeg(QO100_BEACONS[i]);
|
||||||
|
|
||||||
|
// Верхняя кромка полоски — отделить от спектра.
|
||||||
|
C.Pen.Style := psSolid; C.Pen.Color := CLR_STRIP_TOP; C.Pen.Width := 1;
|
||||||
|
C.MoveTo(0, 0); C.LineTo(W, 0);
|
||||||
|
C.Pen.Style := psSolid;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TBandPlanOverlay.NeedsRebuild(W: Integer): Boolean;
|
||||||
|
begin
|
||||||
|
Result := FCacheDirty or (FCacheW <> W)
|
||||||
|
or (FKeyW <> W) or (FKeyEnabled <> FEnabled)
|
||||||
|
or (Abs(FKeyCenter - FCenterFreq) >= 1.0)
|
||||||
|
or (Abs(FKeySpan - FSpanHz) >= 1.0);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TBandPlanOverlay.RebuildCache(W: Integer);
|
||||||
|
begin
|
||||||
|
if W <= 0 then begin FCacheW := 0; FCacheDirty := False; Exit; end;
|
||||||
|
if (FCache.Width <> W) or (FCache.Height <> FStripH) then
|
||||||
|
FCache.SetSize(W, FStripH);
|
||||||
|
DrawSelf(FCache.Canvas, W);
|
||||||
|
FCacheW := W;
|
||||||
|
FKeyCenter := FCenterFreq; FKeySpan := FSpanHz; FKeyW := W;
|
||||||
|
FKeyEnabled := FEnabled;
|
||||||
|
FCacheDirty := False;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TBandPlanOverlay.CompositeStrip(Target: TBitmap; DstY: Integer);
|
||||||
|
// Альфа-композит FCache поверх Target в (0, DstY) с постоянной BANDPLAN_ALPHA.
|
||||||
|
// Один проход по W × STRIP_H пикселей за кадр.
|
||||||
|
var
|
||||||
|
X, Y, ty, InvA: Integer;
|
||||||
|
RS, RD: PByte;
|
||||||
|
const
|
||||||
|
A = BANDPLAN_ALPHA;
|
||||||
|
begin
|
||||||
|
if (Target = nil) or (FCacheW <= 0) then Exit;
|
||||||
|
InvA := 255 - A;
|
||||||
|
Target.BeginUpdate(False);
|
||||||
|
FCache.BeginUpdate(False);
|
||||||
|
try
|
||||||
|
for Y := 0 to FStripH - 1 do
|
||||||
|
begin
|
||||||
|
ty := DstY + Y;
|
||||||
|
if (ty < 0) or (ty >= Target.Height) then Continue;
|
||||||
|
RS := PByte(FCache.ScanLine[Y]);
|
||||||
|
RD := PByte(Target.ScanLine[ty]);
|
||||||
|
if (RS = nil) or (RD = nil) then Continue;
|
||||||
|
for X := 0 to FCacheW - 1 do
|
||||||
|
begin
|
||||||
|
if X >= Target.Width then Break;
|
||||||
|
{$IFDEF DARWIN}
|
||||||
|
RD[1] := Byte((A * RS[1] + InvA * RD[1]) div 255);
|
||||||
|
RD[2] := Byte((A * RS[2] + InvA * RD[2]) div 255);
|
||||||
|
RD[3] := Byte((A * RS[3] + InvA * RD[3]) div 255);
|
||||||
|
{$ELSE}
|
||||||
|
RD[0] := Byte((A * RS[0] + InvA * RD[0]) div 255);
|
||||||
|
RD[1] := Byte((A * RS[1] + InvA * RD[1]) div 255);
|
||||||
|
RD[2] := Byte((A * RS[2] + InvA * RD[2]) div 255);
|
||||||
|
{$ENDIF}
|
||||||
|
Inc(RS, 4); Inc(RD, 4);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
finally
|
||||||
|
FCache.EndUpdate(False);
|
||||||
|
Target.EndUpdate(False);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TBandPlanOverlay.DrawOverlay(Target: TBitmap; W, H: Integer);
|
||||||
|
begin
|
||||||
|
if (not FEnabled) or (Target = nil) or (W <= 0) or (H <= FStripH) then Exit;
|
||||||
|
if NeedsRebuild(W) then RebuildCache(W);
|
||||||
|
CompositeStrip(Target, H - FStripH);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TBandPlanOverlay.DrawOverlayBitmap(Target: TBitmap; W: Integer);
|
||||||
|
begin
|
||||||
|
if (Target = nil) or (W <= 0) then Exit;
|
||||||
|
Target.PixelFormat := pf32bit;
|
||||||
|
Target.SetSize(W, FStripH);
|
||||||
|
DrawSelf(Target.Canvas, W);
|
||||||
|
end;
|
||||||
|
|
||||||
|
end.
|
||||||
+22
-1
@@ -22,7 +22,7 @@ unit MainForm;
|
|||||||
interface
|
interface
|
||||||
|
|
||||||
uses
|
uses
|
||||||
Classes, SysUtils, StrUtils, FreqDisplay, DeviceForm, VfoOverlay, SampleRateOverlay,
|
Classes, SysUtils, StrUtils, FreqDisplay, DeviceForm, VfoOverlay, SampleRateOverlay, BandPlanOverlay,
|
||||||
FlatButton, FlatSlider, FlatDropDown, AppTheme, Forms, Controls, Graphics, Dialogs,
|
FlatButton, FlatSlider, FlatDropDown, AppTheme, Forms, Controls, Graphics, Dialogs,
|
||||||
StdCtrls, ExtCtrls, Buttons, Menus, Math, Types,
|
StdCtrls, ExtCtrls, Buttons, Menus, Math, Types,
|
||||||
OpenGLContext,
|
OpenGLContext,
|
||||||
@@ -123,6 +123,7 @@ type
|
|||||||
FPendingIP: string; // IP устройства для подключения
|
FPendingIP: string; // IP устройства для подключения
|
||||||
FVfoOverlay: TVfoOverlay; // накладка SmartSDR-стиль на спектре
|
FVfoOverlay: TVfoOverlay; // накладка SmartSDR-стиль на спектре
|
||||||
FSampleRateOverlay: TSampleRateOverlay; // оверлей span/hide слева вверху спектра
|
FSampleRateOverlay: TSampleRateOverlay; // оверлей span/hide слева вверху спектра
|
||||||
|
FBandPlanOverlay: TBandPlanOverlay; // полоска бэндплана QO-100 внизу спектра
|
||||||
FPanelHidden: Boolean; // True = левая панель скрыта
|
FPanelHidden: Boolean; // True = левая панель скрыта
|
||||||
FLeftPanelW: Integer; // ширина панели до скрытия (DPI-safe restore)
|
FLeftPanelW: Integer; // ширина панели до скрытия (DPI-safe restore)
|
||||||
FPendingBoardType: Integer; // BoardType устройства для подключения
|
FPendingBoardType: Integer; // BoardType устройства для подключения
|
||||||
@@ -390,6 +391,7 @@ type
|
|||||||
procedure ApplyDUP(Active: Boolean);
|
procedure ApplyDUP(Active: Boolean);
|
||||||
procedure BtnRxMuteClick(Sender: TObject);
|
procedure BtnRxMuteClick(Sender: TObject);
|
||||||
procedure UpdateRxMuteButton; // видимость (Pluto+full-duplex) + стиль кнопки RX MUTE
|
procedure UpdateRxMuteButton; // видимость (Pluto+full-duplex) + стиль кнопки RX MUTE
|
||||||
|
procedure UpdateBandPlanOverlay; // вкл/выкл полоски бэндплана QO-100 по InQO100
|
||||||
procedure BtnTUNClick(Sender: TObject);
|
procedure BtnTUNClick(Sender: TObject);
|
||||||
procedure ApplyTUN(Active: Boolean);
|
procedure ApplyTUN(Active: Boolean);
|
||||||
procedure UpdateFilterButtons;
|
procedure UpdateFilterButtons;
|
||||||
@@ -1221,6 +1223,7 @@ begin
|
|||||||
UpdateBeaconButton;
|
UpdateBeaconButton;
|
||||||
end;
|
end;
|
||||||
UpdateRxMuteButton; // RX MUTE — только Pluto + full-duplex (QO-100)
|
UpdateRxMuteButton; // RX MUTE — только Pluto + full-duplex (QO-100)
|
||||||
|
UpdateBandPlanOverlay; // полоска бэндплана QO-100
|
||||||
// Wideband-вид + web-зеркало xvtr (ранее в обёртках Activate/Deactivate).
|
// Wideband-вид + web-зеркало xvtr (ранее в обёртках Activate/Deactivate).
|
||||||
// rfXvtr эмитят ТОЛЬКО ActivateXvtr/DeactivateXvtr — состояние финальное.
|
// rfXvtr эмитят ТОЛЬКО ActivateXvtr/DeactivateXvtr — состояние финальное.
|
||||||
UpdateWidebandFrequencyView;
|
UpdateWidebandFrequencyView;
|
||||||
@@ -2085,6 +2088,11 @@ begin
|
|||||||
FSampleRateOverlay.SetCurrentRate(FController.FSampleRate);
|
FSampleRateOverlay.SetCurrentRate(FController.FSampleRate);
|
||||||
FSpecView.SampleRateOverlay := FSampleRateOverlay;
|
FSpecView.SampleRateOverlay := FSampleRateOverlay;
|
||||||
|
|
||||||
|
// Бэндплан QO-100 — полоска внизу спектра; видимость по InQO100.
|
||||||
|
FBandPlanOverlay := TBandPlanOverlay.Create(Self);
|
||||||
|
FBandPlanOverlay.SetView(FController.FCenterFreq, FController.FSpanHz);
|
||||||
|
FSpecView.BandPlanOverlay := FBandPlanOverlay;
|
||||||
|
|
||||||
// S-метр — внутри PanelToolbar, справа
|
// S-метр — внутри PanelToolbar, справа
|
||||||
PanelSMeterRight := TPanel.Create(Self);
|
PanelSMeterRight := TPanel.Create(Self);
|
||||||
PanelSMeterRight.Parent := PanelToolbar;
|
PanelSMeterRight.Parent := PanelToolbar;
|
||||||
@@ -3002,6 +3010,10 @@ begin
|
|||||||
// FSplitTxB (TX-A/TX-B), независимо от активного RX-VFO.
|
// FSplitTxB (TX-A/TX-B), независимо от активного RX-VFO.
|
||||||
if FController.FSplitTxB then FSpecView.TXVfoIndex := 1
|
if FController.FSplitTxB then FSpecView.TXVfoIndex := 1
|
||||||
else FSpecView.TXVfoIndex := 0;
|
else FSpecView.TXVfoIndex := 0;
|
||||||
|
// Бэндплан QO-100 следует за видом спектра (downlink-домен). SetView сам
|
||||||
|
// отсекает неизменное → пересборки кэша на каждый вызов нет.
|
||||||
|
if Assigned(FBandPlanOverlay) then
|
||||||
|
FBandPlanOverlay.SetView(FController.FCenterFreq, FController.FSpanHz);
|
||||||
if UpdateWidebandFrequencyView and FController.FShowWideband and (PbWideband <> nil) then
|
if UpdateWidebandFrequencyView and FController.FShowWideband and (PbWideband <> nil) then
|
||||||
PbWideband.Invalidate;
|
PbWideband.Invalidate;
|
||||||
end;
|
end;
|
||||||
@@ -3715,6 +3727,7 @@ begin
|
|||||||
LayoutLeftPanel(Caps.HasHWGain);
|
LayoutLeftPanel(Caps.HasHWGain);
|
||||||
if Caps.HasHWGain then UpdateRxGainUI;
|
if Caps.HasHWGain then UpdateRxGainUI;
|
||||||
UpdateRxMuteButton; // RX MUTE — видимость по Pluto + full-duplex (если QO-100 уже активен)
|
UpdateRxMuteButton; // RX MUTE — видимость по Pluto + full-duplex (если QO-100 уже активен)
|
||||||
|
UpdateBandPlanOverlay; // полоска бэндплана QO-100
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TMainForm.DoConnectDevice(const Dev: THPSDRDevice);
|
procedure TMainForm.DoConnectDevice(const Dev: THPSDRDevice);
|
||||||
@@ -4066,6 +4079,14 @@ begin
|
|||||||
if Vis then StyleButton(BtnRxMute, FController.FRxMuteOnTx);
|
if Vis then StyleButton(BtnRxMute, FController.FRxMuteOnTx);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
procedure TMainForm.UpdateBandPlanOverlay;
|
||||||
|
// Полоска бэндплана видна только в QO-100 (Pluto + full-duplex транспондер).
|
||||||
|
begin
|
||||||
|
if FBandPlanOverlay = nil then Exit;
|
||||||
|
FBandPlanOverlay.SetEnabled(FController.InQO100);
|
||||||
|
FSpectrumDirty := True; // моментальная перерисовка (не ждём таймер)
|
||||||
|
end;
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// TUN — Thetis-style тон: WDSP TXA PostGen + MOX с отдельным уровнем Drive
|
// TUN — Thetis-style тон: WDSP TXA PostGen + MOX с отдельным уровнем Drive
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|||||||
+6
-1
@@ -20,7 +20,7 @@ interface
|
|||||||
uses
|
uses
|
||||||
Classes, SysUtils, Graphics, ExtCtrls, Controls, Math,
|
Classes, SysUtils, Graphics, ExtCtrls, Controls, Math,
|
||||||
AppTheme,
|
AppTheme,
|
||||||
AlertOverlay, SampleRateOverlay, VfoOverlay,
|
AlertOverlay, SampleRateOverlay, VfoOverlay, BandPlanOverlay,
|
||||||
WaterfallView, SMeterView, RulerView;
|
WaterfallView, SMeterView, RulerView;
|
||||||
|
|
||||||
const
|
const
|
||||||
@@ -70,6 +70,7 @@ type
|
|||||||
FADCOverloadVisible: Boolean;
|
FADCOverloadVisible: Boolean;
|
||||||
FSampleRateOverlay: TSampleRateOverlay;
|
FSampleRateOverlay: TSampleRateOverlay;
|
||||||
FVfoOverlay: TVfoOverlay;
|
FVfoOverlay: TVfoOverlay;
|
||||||
|
FBandPlanOverlay: TBandPlanOverlay;
|
||||||
// ── Marker ────────────────────────────────────────────────────────────────
|
// ── Marker ────────────────────────────────────────────────────────────────
|
||||||
FMarkerActive: Boolean;
|
FMarkerActive: Boolean;
|
||||||
FMarkerX: Integer;
|
FMarkerX: Integer;
|
||||||
@@ -217,6 +218,7 @@ type
|
|||||||
property PbSMeterRight: TPaintBox write SetPbSMeterRight;
|
property PbSMeterRight: TPaintBox write SetPbSMeterRight;
|
||||||
property SampleRateOverlay: TSampleRateOverlay read FSampleRateOverlay write FSampleRateOverlay;
|
property SampleRateOverlay: TSampleRateOverlay read FSampleRateOverlay write FSampleRateOverlay;
|
||||||
property VfoOverlay: TVfoOverlay read FVfoOverlay write FVfoOverlay;
|
property VfoOverlay: TVfoOverlay read FVfoOverlay write FVfoOverlay;
|
||||||
|
property BandPlanOverlay: TBandPlanOverlay read FBandPlanOverlay write FBandPlanOverlay;
|
||||||
|
|
||||||
// ── Данные от DSP ─────────────────────────────────────────────────────────
|
// ── Данные от DSP ─────────────────────────────────────────────────────────
|
||||||
procedure SetSpectrumData(const Pixels: array of Single; Count: Integer); virtual;
|
procedure SetSpectrumData(const Pixels: array of Single; Count: Integer); virtual;
|
||||||
@@ -1000,6 +1002,9 @@ begin
|
|||||||
if FMarkerActive then DrawMarkerLine(C, W, H);
|
if FMarkerActive then DrawMarkerLine(C, W, H);
|
||||||
if FBeaconMarkActive or FBeaconDecActive then DrawBeaconMarkers(C, W, H);
|
if FBeaconMarkActive or FBeaconDecActive then DrawBeaconMarkers(C, W, H);
|
||||||
DrawADCOverloadOverlay(C, W, H);
|
DrawADCOverloadOverlay(C, W, H);
|
||||||
|
// Бэндплан QO-100 — полоска внизу спектра (над ruler), под панелями оверлеев.
|
||||||
|
if Assigned(FBandPlanOverlay) then
|
||||||
|
FBandPlanOverlay.DrawOverlay(FSpectrumBitmap, W, H);
|
||||||
if Assigned(FSampleRateOverlay) then
|
if Assigned(FSampleRateOverlay) then
|
||||||
FSampleRateOverlay.DrawOverlay(FSpectrumBitmap, C, W, H);
|
FSampleRateOverlay.DrawOverlay(FSpectrumBitmap, C, W, H);
|
||||||
if Assigned(FVfoOverlay) then
|
if Assigned(FVfoOverlay) then
|
||||||
|
|||||||
+33
-1
@@ -17,7 +17,7 @@ interface
|
|||||||
uses
|
uses
|
||||||
Classes, SysUtils, Graphics, Controls, Math, Types,
|
Classes, SysUtils, Graphics, Controls, Math, Types,
|
||||||
OpenGLContext, GL,
|
OpenGLContext, GL,
|
||||||
AppTheme, SpectrumView, VfoOverlay, WaterfallViewOpengl;
|
AppTheme, SpectrumView, VfoOverlay, BandPlanOverlay, WaterfallViewOpengl;
|
||||||
|
|
||||||
type
|
type
|
||||||
TGLTextureCache = record
|
TGLTextureCache = record
|
||||||
@@ -34,6 +34,7 @@ type
|
|||||||
FOverlayDirty: Boolean;
|
FOverlayDirty: Boolean;
|
||||||
FSampleOverlayDirty: Boolean;
|
FSampleOverlayDirty: Boolean;
|
||||||
FVfoOverlayDirty: Boolean;
|
FVfoOverlayDirty: Boolean;
|
||||||
|
FBandOverlayDirty: Boolean;
|
||||||
FGridLabelTex: TGLTextureCache;
|
FGridLabelTex: TGLTextureCache;
|
||||||
FAGCLabelTex: TGLTextureCache;
|
FAGCLabelTex: TGLTextureCache;
|
||||||
FAGCHangLabelTex: TGLTextureCache;
|
FAGCHangLabelTex: TGLTextureCache;
|
||||||
@@ -41,6 +42,7 @@ type
|
|||||||
FADCOverlayTex: TGLTextureCache;
|
FADCOverlayTex: TGLTextureCache;
|
||||||
FSampleOverlayTex: TGLTextureCache;
|
FSampleOverlayTex: TGLTextureCache;
|
||||||
FVfoOverlayTex: TGLTextureCache;
|
FVfoOverlayTex: TGLTextureCache;
|
||||||
|
FBandOverlayTex: TGLTextureCache;
|
||||||
FLastAGCText: string;
|
FLastAGCText: string;
|
||||||
FLastAGCY: Integer;
|
FLastAGCY: Integer;
|
||||||
FLastAGCHangY: Integer;
|
FLastAGCHangY: Integer;
|
||||||
@@ -55,6 +57,9 @@ type
|
|||||||
FLastVfoY: Integer;
|
FLastVfoY: Integer;
|
||||||
FLastVfoW: Integer;
|
FLastVfoW: Integer;
|
||||||
FLastVfoH: Integer;
|
FLastVfoH: Integer;
|
||||||
|
FLastBandW: Integer;
|
||||||
|
FLastBandCenter: Double;
|
||||||
|
FLastBandSpan: Double;
|
||||||
FGLSpectrumW: Integer;
|
FGLSpectrumW: Integer;
|
||||||
FGLSpectrumH: Integer;
|
FGLSpectrumH: Integer;
|
||||||
FSpY: array of Integer;
|
FSpY: array of Integer;
|
||||||
@@ -123,6 +128,9 @@ begin
|
|||||||
FOverlayDirty := True;
|
FOverlayDirty := True;
|
||||||
FSampleOverlayDirty := True;
|
FSampleOverlayDirty := True;
|
||||||
FVfoOverlayDirty := True;
|
FVfoOverlayDirty := True;
|
||||||
|
FBandOverlayDirty := True;
|
||||||
|
FLastBandW := -MaxInt;
|
||||||
|
FLastBandCenter := -1; FLastBandSpan := -1;
|
||||||
FLastAGCY := -MaxInt;
|
FLastAGCY := -MaxInt;
|
||||||
FLastAGCHangY := -MaxInt;
|
FLastAGCHangY := -MaxInt;
|
||||||
FLastMarkerX := -MaxInt;
|
FLastMarkerX := -MaxInt;
|
||||||
@@ -140,6 +148,7 @@ begin
|
|||||||
DeleteTexture(FADCOverlayTex);
|
DeleteTexture(FADCOverlayTex);
|
||||||
DeleteTexture(FSampleOverlayTex);
|
DeleteTexture(FSampleOverlayTex);
|
||||||
DeleteTexture(FVfoOverlayTex);
|
DeleteTexture(FVfoOverlayTex);
|
||||||
|
DeleteTexture(FBandOverlayTex);
|
||||||
end;
|
end;
|
||||||
inherited Destroy;
|
inherited Destroy;
|
||||||
end;
|
end;
|
||||||
@@ -720,6 +729,29 @@ var
|
|||||||
B: TBitmap;
|
B: TBitmap;
|
||||||
OW, OH: Integer;
|
OW, OH: Integer;
|
||||||
begin
|
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
|
if Assigned(FSampleRateOverlay) then
|
||||||
begin
|
begin
|
||||||
OW := Min(FSampleRateOverlay.Width, W - FSampleRateOverlay.Left);
|
OW := Min(FSampleRateOverlay.Width, W - FSampleRateOverlay.Left);
|
||||||
|
|||||||
@@ -343,9 +343,17 @@ end;
|
|||||||
downlink через транспондер, ~250 мс). Реализация: гейт `WDSPEngine` (`not FTXActive
|
downlink через транспондер, ~250 мс). Реализация: гейт `WDSPEngine` (`not FTXActive
|
||||||
or (FKeepRXDuringTX and FMonitorRXDuringTX)`) + флаг `FRxMuteOnTx` в контроллере,
|
or (FKeepRXDuringTX and FMonitorRXDuringTX)`) + флаг `FRxMuteOnTx` в контроллере,
|
||||||
персист в глобале (`rx_mute_on_tx`). Web — не сделано (пока вне области).
|
персист в глобале (`rx_mute_on_tx`). Web — не сделано (пока вне области).
|
||||||
- TODO: проверка на железе; RIT/XIT-клариффайер (опц., на геостационаре редко нужен);
|
- ✅ **Bandplan-оверлей NB-транспондера** (`BandPlanOverlay.pas`): полоска внизу
|
||||||
TX-маркер на RX-спектре в DUP (визуальный self-monitor — `FSpecView.TXMode` сейчас
|
спектра (над ruler) с сегментами CW ONLY / NB DIGI / DIGI / SSB / MIXED MODES +
|
||||||
в DUP выключен).
|
маяки CW/PSK/EXP. Модель `VfoOverlay` (кэш-битмап + const-alpha), CPU и GL, виден
|
||||||
|
по `InQO100`. DPI-устойчив (высота и шрифт по `Screen.PixelsPerInch`).
|
||||||
|
- TODO (визуальный self-monitor / навигация по транспондеру):
|
||||||
|
* **Индикатор «не громче маяка»** — сравнение уровня своего downlink с маяком
|
||||||
|
(этикет QO-100); амплитуда маяка уже известна декодеру.
|
||||||
|
* RIT/XIT-клариффайер — опц., на геостационаре нужен редко.
|
||||||
|
- TODO: проверка на железе.
|
||||||
|
- ОТВЕРГНУТО: TX-маркер на RX-спектре в DUP — в обычной работе RX=TX (один VFO),
|
||||||
|
маркер ложится на RX-курсор, информации не даёт; полезен только в split (редко).
|
||||||
|
|
||||||
**Фаза 6 — Полировка** ✅ (в основном)
|
**Фаза 6 — Полировка** ✅ (в основном)
|
||||||
- ✅ Web/CAT-фронтенды (команды уже агностичны) + гейтинг полей; демон `ewsdrd.lpr`
|
- ✅ Web/CAT-фронтенды (команды уже агностичны) + гейтинг полей; демон `ewsdrd.lpr`
|
||||||
|
|||||||
Reference in New Issue
Block a user