diff --git a/BeaconScopeForm.pas b/BeaconScopeForm.pas index 6e9365b..7208760 100644 --- a/BeaconScopeForm.pas +++ b/BeaconScopeForm.pas @@ -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;