mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 20:37:33 +00:00
feat(beacon): состояние захвата видно прямо в лупе
Чтобы понять «попал или нет», приходилось переводить взгляд на плитки метрик под графиком. Теперь это читается в самой лупе: - Полоса приёма показывает захват начертанием: в поиске три линии пунктирные, на локе — сплошные и стянуты сверху скобой. Новых цветов не добавлено, та же семантика $0020D0FF, что и на главном спектре. - Строка захвата под частотой центра: LOCK · 14.2 dB (зелёная) / SEARCH (янтарь) / BEACON OFF. Состояние берётся из BeaconState — это гистерезисный FBeaconLocked, которому доверяет сам контур коррекции, а не мгновенный CarrierLock: тот мерцал бы на границе. - Δ от опорной рядом: расхождение измеренной частоты маяка с опорной, то есть расстояние между оранжевым и зелёным маркерами числом. Это невязка, которую лок стекает в LOError; в пределах дедбэнда (20 Гц) — зелёная, иначе янтарная. Знак у Δ ставится вручную: флага '+' в Format Object Pascal нет (это printf'изм), '%+.0f' молча съедается и в строку попадает голый хвост спецификатора — на экране было «Δ .0f Hz» вместо значения. Состояние и SNR снимаются раз в тик в PollZoom и кладутся в поля: копия TBeaconScope в отрисовку не лезет, а SNR сравнивается с допуском 0.2 дБ — иначе дрожание в сотых долях дБ пересобирало бы кадр каждый тик и dirty-флаг потерял бы смысл. Сборка ewsdr (--ws=qt6) — ОК. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+66
-3
@@ -96,6 +96,10 @@ type
|
|||||||
FZoomDirty: Boolean;
|
FZoomDirty: Boolean;
|
||||||
FStripValid: Boolean; // в FStrip лежит кадр под текущий размер
|
FStripValid: Boolean; // в FStrip лежит кадр под текущий размер
|
||||||
FLastRefHz, FLastTrkHz, FLastDecHz: Double; // детект сдвига маркеров
|
FLastRefHz, FLastTrkHz, FLastDecHz: Double; // детект сдвига маркеров
|
||||||
|
// Состояние захвата, снятое в PollZoom: рисовать его надо в кадре лупы, а
|
||||||
|
// лезть за копией TBeaconScope на каждую отрисовку незачем.
|
||||||
|
FState: TBeaconState;
|
||||||
|
FSNRdB: Double;
|
||||||
FCursorX: Integer; // -1 = курсор вне лупы
|
FCursorX: Integer; // -1 = курсор вне лупы
|
||||||
FDragging: Boolean; // ЛКМ зажата в лупе
|
FDragging: Boolean; // ЛКМ зажата в лупе
|
||||||
FDragMoved: Boolean; // увели мышь → это панорамирование, не клик
|
FDragMoved: Boolean; // увели мышь → это панорамирование, не клик
|
||||||
@@ -594,6 +598,14 @@ begin
|
|||||||
else
|
else
|
||||||
FPixCount := 0;
|
FPixCount := 0;
|
||||||
|
|
||||||
|
// Лок и SNR — часть картинки: по ним видно, попал ли клик в маяк. SNR
|
||||||
|
// сравниваем с допуском, иначе дрожание в сотых долях дБ пересобирало бы
|
||||||
|
// кадр на каждом тике.
|
||||||
|
if FCtl.BeaconState <> FState then FZoomDirty := True;
|
||||||
|
if Abs(FCtl.BeaconSNRdB - FSNRdB) > 0.2 then FZoomDirty := True;
|
||||||
|
FState := FCtl.BeaconState;
|
||||||
|
FSNRdB := FCtl.BeaconSNRdB;
|
||||||
|
|
||||||
if FCtl.BeaconRefHz <> FLastRefHz then FZoomDirty := True;
|
if FCtl.BeaconRefHz <> FLastRefHz then FZoomDirty := True;
|
||||||
if FCtl.BeaconTrackedFreqHz <> FLastTrkHz then FZoomDirty := True;
|
if FCtl.BeaconTrackedFreqHz <> FLastTrkHz then FZoomDirty := True;
|
||||||
if FCtl.BeaconDecodeFreqHz <> FLastDecHz then FZoomDirty := True;
|
if FCtl.BeaconDecodeFreqHz <> FLastDecHz then FZoomDirty := True;
|
||||||
@@ -905,6 +917,8 @@ var
|
|||||||
Sorted: array of Double;
|
Sorted: array of Double;
|
||||||
TmpD: Double;
|
TmpD: Double;
|
||||||
a, b: Integer;
|
a, b: Integer;
|
||||||
|
Locked: Boolean;
|
||||||
|
Sign: string;
|
||||||
|
|
||||||
procedure SetFont(ASize: Integer; AStyle: TFontStyles = []);
|
procedure SetFont(ASize: Integer; AStyle: TFontStyles = []);
|
||||||
begin
|
begin
|
||||||
@@ -1057,9 +1071,27 @@ begin
|
|||||||
if TrkHz > 0 then VLine(TrkHz, BCN_CLR_TRK, False);
|
if TrkHz > 0 then VLine(TrkHz, BCN_CLR_TRK, False);
|
||||||
if DecHz > 0 then
|
if DecHz > 0 then
|
||||||
begin
|
begin
|
||||||
VLine(DecHz - BCN_DEC_BW_HZ, BCN_CLR_DEC, False);
|
// Полоса приёма показывает захват сама: в поиске грани пунктирные, на локе
|
||||||
VLine(DecHz + BCN_DEC_BW_HZ, BCN_CLR_DEC, False);
|
// — сплошные и стянуты скобой. Так «попал/не попал» читается прямо здесь,
|
||||||
VLine(DecHz, BCN_CLR_DEC, False);
|
// не переводя взгляд на плитки метрик под графиком.
|
||||||
|
Locked := FState = bsLock;
|
||||||
|
VLine(DecHz - BCN_DEC_BW_HZ, BCN_CLR_DEC, not Locked);
|
||||||
|
VLine(DecHz + BCN_DEC_BW_HZ, BCN_CLR_DEC, not Locked);
|
||||||
|
VLine(DecHz, BCN_CLR_DEC, not Locked);
|
||||||
|
if Locked then
|
||||||
|
begin
|
||||||
|
a := EnsureRange(FreqToX(DecHz - BCN_DEC_BW_HZ, W), 0, W - 1);
|
||||||
|
b := EnsureRange(FreqToX(DecHz + BCN_DEC_BW_HZ, W), 0, W - 1);
|
||||||
|
if b > a then
|
||||||
|
begin
|
||||||
|
Y := DpiScale(40); // ниже строки захвата, чтобы не наехать на текст
|
||||||
|
C.Pen.Color := BCN_CLR_DEC;
|
||||||
|
C.Pen.Style := psSolid;
|
||||||
|
C.Line(a, Y, b, Y);
|
||||||
|
C.Line(a, Y, a, Y + DpiScale(4));
|
||||||
|
C.Line(b, Y, b, Y + DpiScale(4));
|
||||||
|
end;
|
||||||
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// --- Линейка частот ---
|
// --- Линейка частот ---
|
||||||
@@ -1100,6 +1132,37 @@ begin
|
|||||||
Txt := Format('span %.1f kHz', [Span / 1000]);
|
Txt := Format('span %.1f kHz', [Span / 1000]);
|
||||||
C.TextOut(W - DpiScale(6) - C.TextWidth(Txt), DpiScale(5), Txt);
|
C.TextOut(W - DpiScale(6) - C.TextWidth(Txt), DpiScale(5), Txt);
|
||||||
|
|
||||||
|
// --- Строка захвата: состояние + SNR + расхождение с опорной ---
|
||||||
|
// Δ = измеренная частота маяка минус опорная, то есть ровно расстояние между
|
||||||
|
// оранжевым и зелёным маркерами. Это невязка, которую контур стекает в
|
||||||
|
// LOError: пока она больше дедбэнда, лок ещё подкручивает LO.
|
||||||
|
SetFont(7);
|
||||||
|
case FState of
|
||||||
|
bsLock: begin Txt := 'LOCK'; C.Font.Color := FTheme.MeterOn; end;
|
||||||
|
bsSearch: begin Txt := 'SEARCH'; C.Font.Color := FTheme.Amber; end;
|
||||||
|
else
|
||||||
|
Txt := 'BEACON OFF';
|
||||||
|
C.Font.Color := FTheme.TextDim;
|
||||||
|
end;
|
||||||
|
if (FState <> bsIdle) and (FSNRdB > -50) then
|
||||||
|
Txt := Txt + Format(' · %.1f dB', [FSNRdB]);
|
||||||
|
C.TextOut(DpiScale(6), DpiScale(21), Txt);
|
||||||
|
a := DpiScale(6) + C.TextWidth(Txt) + DpiScale(10);
|
||||||
|
|
||||||
|
if (FState <> bsIdle) and (TrkHz > 0) and (RefHz > 0) then
|
||||||
|
begin
|
||||||
|
T := TrkHz - RefHz;
|
||||||
|
// Знак ставим руками: флага '+' в Format Object Pascal нет (это printf'изм),
|
||||||
|
// '%+.0f' молча съедается и в строку попадает голый остаток спецификатора.
|
||||||
|
Sign := IfThen(T >= 0, '+', '');
|
||||||
|
if Abs(T) < 1000 then Txt := Format('Δ %s%.0f Hz', [Sign, T])
|
||||||
|
else Txt := Format('Δ %s%.2f kHz', [Sign, T / 1000]);
|
||||||
|
// Внутри дедбэнда контура (20 Гц) маяк считается стоящим на месте.
|
||||||
|
if Abs(T) <= 20.0 then C.Font.Color := FTheme.MeterOn
|
||||||
|
else C.Font.Color := FTheme.Amber;
|
||||||
|
C.TextOut(a, DpiScale(21), Txt);
|
||||||
|
end;
|
||||||
|
|
||||||
// Рамка карточки поверх всего
|
// Рамка карточки поверх всего
|
||||||
C.Brush.Style := bsClear;
|
C.Brush.Style := bsClear;
|
||||||
C.Pen.Style := psSolid;
|
C.Pen.Style := psSolid;
|
||||||
|
|||||||
Reference in New Issue
Block a user