feat(beacon): окно лупы переживает перезапуск

Ширина, положение и состояние FOLLOW уезжали в дефолт при каждом открытии
окна: настроил 5 кГц вокруг маяка, закрыл — в следующий раз снова 40 кГц.

Положение хранится СДВИГОМ от опорной частоты маяка, а не абсолютом. Абсолют
протух бы от любой правки опорной частоты, а главное — калибровка LOError
копится от сеанса к сеансу и двигает шкалу, так что сохранённая абсолютная
частота через неделю указывала бы уже не туда.

- Settings: BeaconZoomSpanHz/OffsetHz/Follow в TGlobalSettings (ключи
  beacon_zoom_span/offset/follow), дефолты и чтение/запись по общему пути.
- RadioController: FBcnZoom* с клампом при загрузке (span 600..200000 Гц,
  сдвиг ±500 кГц) — руками испорченный JSON окно не сломает.
- BeaconScopeForm: RestoreZoomState в DoShow, StoreZoomState каждый тик из
  PollZoom. Раз состояние пишется в одном месте, оно не разъедется, каким бы
  путём ни менялось — колесом, drag'ом, кнопками или автоподтяжкой FOLLOW.
  Наведение декодера за краем восстановленного окна подтянет сам FOLLOW на
  первом тике, отдельной логики не потребовалось.

Сборка ewsdr (--ws=qt6) и ewsdrd — ОК.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-14 12:03:13 +03:00
co-authored by Claude Opus 5
parent ac216fa332
commit e82f573313
3 changed files with 66 additions and 1 deletions
+15
View File
@@ -332,6 +332,12 @@ type
FBeaconDecHz: Double; // АБСОЛЮТНАЯ display-частота наведения декодера
// (узкий фильтр, ставится кликом); 0 = ещё не наведён
FBeaconDecInvert: Boolean; // зеркальный baseband (QO-100/LNB) — деф. True
// Лупа наведения (окно Beacon) — состояние окна, переживающее перезапуск.
// Центр хранится СДВИГОМ от FBeaconRefHz: так он не зависит ни от правки
// опорной частоты, ни от накопленной калибровки LOError.
FBcnZoomSpanHz: Double; // ширина окна лупы, Гц
FBcnZoomOffsetHz: Double; // центр = FBeaconRefHz + этот сдвиг
FBcnZoomFollow: Boolean; // держать наведение декодера в окне
// ---- DMR 4FSK receive decoder ----
FDMRDec: TDMRDecoder; // 48 kHz FM discriminator -> DMR worker
@@ -1317,6 +1323,9 @@ begin
// и prom 500-800 при invert=ON против шума при OFF, на наведении точно на маяк).
// Декодеру нужно сопряжение, чтобы наведённый offset доставлял маяк к DC.
FBeaconDecInvert := True;
FBcnZoomSpanHz := 40000.0;
FBcnZoomOffsetHz := 0.0;
FBcnZoomFollow := True;
FBeaconDec.SetInvert(FBeaconDecInvert);
FDSPEngine.SetBeaconDecoder(FBeaconDec);
@@ -3910,6 +3919,9 @@ begin
Result.SpecRefLevel := FSpecRefLevel;
Result.SpecRange := FSpecRange;
Result.SpecGridStep := FSpecGridStep;
Result.BeaconZoomSpanHz := FBcnZoomSpanHz;
Result.BeaconZoomOffsetHz := FBcnZoomOffsetHz;
Result.BeaconZoomFollow := FBcnZoomFollow;
Result.AudioSampleRate := FAudioOut.SampleRate;
Result.ShowSpectrum := FShowSpectrum;
Result.ShowWaterfall := FShowWaterfall;
@@ -7303,6 +7315,9 @@ begin
FDisplayPixels := EnsureRange(FLoadedGlobal.DisplayPixels, 64, 4096);
FWfSpeedDiv := EnsureRange(FLoadedGlobal.WfSpeedDiv, 1, 8);
FSpecMSAA := EnsureRange(FLoadedGlobal.SpecMSAA, 1, 8);
FBcnZoomSpanHz := EnsureRange(FLoadedGlobal.BeaconZoomSpanHz, 600.0, 200000.0);
FBcnZoomOffsetHz := EnsureRange(FLoadedGlobal.BeaconZoomOffsetHz, -500000.0, 500000.0);
FBcnZoomFollow := FLoadedGlobal.BeaconZoomFollow;
if FWDSPReady then FDSPEngine.SetDisplayResLimit(FDisplayPixels);
FShowSpectrum := FLoadedGlobal.ShowSpectrum;
FShowWaterfall := FLoadedGlobal.ShowWaterfall;