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
@@ -614,6 +614,12 @@ type
SpecRefLevel: Double; // dBm reference (top of scale), e.g. -20
SpecRange: Double; // dB range, e.g. 110 (bottom = ref - range)
SpecGridStep: Double; // grid step in dB, e.g. 10
// --- Лупа наведения на маяк QO-100 (карточка BEACON TUNING) ---
// Центр хранится СДВИГОМ от опорной частоты маяка, а не абсолютом: так
// окно переживает и правку опорной частоты, и калибровку LOError.
BeaconZoomSpanHz: Double; // ширина окна лупы, Гц
BeaconZoomOffsetHz: Double; // центр окна = BeaconRefHz + этот сдвиг
BeaconZoomFollow: Boolean; // держать наведение декодера в окне
// --- Audio settings ---
AudioOutDevice: string; // PortAudio output device name ('' = default)
AudioSampleRate: Integer; // PortAudio stream sample rate (e.g. 48000)
@@ -979,6 +985,9 @@ begin
G.SpecRefLevel := -20.0;
G.SpecRange := 110.0;
G.SpecGridStep := 10.0;
G.BeaconZoomSpanHz := 40000.0;
G.BeaconZoomOffsetHz := 0.0;
G.BeaconZoomFollow := True;
G.AudioOutDevice := '';
G.AudioSampleRate := 48000;
G.AudioInDevice := '';
@@ -1229,6 +1238,9 @@ begin
G.SpecRefLevel := JD(GObj,'spec_ref_level',-20.0);
G.SpecRange := JD(GObj,'spec_range',110.0);
G.SpecGridStep := JD(GObj,'spec_grid_step',10.0);
G.BeaconZoomSpanHz := JD(GObj,'beacon_zoom_span',40000.0);
G.BeaconZoomOffsetHz := JD(GObj,'beacon_zoom_offset',0.0);
G.BeaconZoomFollow := JB(GObj,'beacon_zoom_follow',True);
// Audio settings
G.AudioOutDevice := JS(GObj,'audio_out_device','');
G.AudioSampleRate := JI(GObj,'audio_sample_rate',48000);
@@ -1376,6 +1388,9 @@ begin
JW(O,'spec_ref_level',G.SpecRefLevel);
JW(O,'spec_range',G.SpecRange);
JW(O,'spec_grid_step',G.SpecGridStep);
JW(O,'beacon_zoom_span',G.BeaconZoomSpanHz);
JW(O,'beacon_zoom_offset',G.BeaconZoomOffsetHz);
JW(O,'beacon_zoom_follow',G.BeaconZoomFollow);
// Audio settings
JWS(O,'audio_out_device',G.AudioOutDevice);
JW(O,'audio_sample_rate',G.AudioSampleRate);