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;
|
||||
FStripValid: Boolean; // в FStrip лежит кадр под текущий размер
|
||||
FLastRefHz, FLastTrkHz, FLastDecHz: Double; // детект сдвига маркеров
|
||||
// Состояние захвата, снятое в PollZoom: рисовать его надо в кадре лупы, а
|
||||
// лезть за копией TBeaconScope на каждую отрисовку незачем.
|
||||
FState: TBeaconState;
|
||||
FSNRdB: Double;
|
||||
FCursorX: Integer; // -1 = курсор вне лупы
|
||||
FDragging: Boolean; // ЛКМ зажата в лупе
|
||||
FDragMoved: Boolean; // увели мышь → это панорамирование, не клик
|
||||
@@ -594,6 +598,14 @@ begin
|
||||
else
|
||||
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.BeaconTrackedFreqHz <> FLastTrkHz then FZoomDirty := True;
|
||||
if FCtl.BeaconDecodeFreqHz <> FLastDecHz then FZoomDirty := True;
|
||||
@@ -905,6 +917,8 @@ var
|
||||
Sorted: array of Double;
|
||||
TmpD: Double;
|
||||
a, b: Integer;
|
||||
Locked: Boolean;
|
||||
Sign: string;
|
||||
|
||||
procedure SetFont(ASize: Integer; AStyle: TFontStyles = []);
|
||||
begin
|
||||
@@ -1057,9 +1071,27 @@ begin
|
||||
if TrkHz > 0 then VLine(TrkHz, BCN_CLR_TRK, False);
|
||||
if DecHz > 0 then
|
||||
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;
|
||||
|
||||
// --- Линейка частот ---
|
||||
@@ -1100,6 +1132,37 @@ begin
|
||||
Txt := Format('span %.1f kHz', [Span / 1000]);
|
||||
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.Pen.Style := psSolid;
|
||||
|
||||
Reference in New Issue
Block a user