mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 18:43:51 +00:00
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:
+36
-1
@@ -119,6 +119,8 @@ type
|
||||
procedure LayoutTunePanel;
|
||||
procedure PollZoom; // забрать кадр лупы у контроллера
|
||||
procedure ClampWindow; // окно не вылезает из полосы захвата
|
||||
procedure RestoreZoomState; // окно лупы из настроек
|
||||
procedure StoreZoomState; // окно лупы в настройки
|
||||
function DefaultCenterHz: Double; // куда смотреть, пока не выбрали
|
||||
function XToFreq(X, W: Integer): Double;
|
||||
function FreqToX(F: Double; W: Integer): Integer;
|
||||
@@ -511,6 +513,38 @@ begin
|
||||
FCenterHz := EnsureRange(FCenterHz, CapLo + Half, CapHi - Half);
|
||||
end;
|
||||
|
||||
procedure TBeaconScopeForm.RestoreZoomState;
|
||||
// Окно лупы переживает перезапуск: ширина, положение и FOLLOW лежат в
|
||||
// глобальных настройках (персист — общий SaveGlobalSettings при выходе).
|
||||
// Положение хранится сдвигом от опорной частоты маяка, поэтому восстановление
|
||||
// не зависит от накопленной калибровки LOError.
|
||||
begin
|
||||
if FCtl = nil then Exit;
|
||||
FSpanHz := EnsureRange(FCtl.FBcnZoomSpanHz, BCN_SPAN_MIN, BCN_SPAN_MAX);
|
||||
FFollow := FCtl.FBcnZoomFollow;
|
||||
if FCtl.BeaconRefHz > 0 then
|
||||
FCenterHz := FCtl.BeaconRefHz + FCtl.FBcnZoomOffsetHz
|
||||
else
|
||||
FCenterHz := DefaultCenterHz;
|
||||
if FBtnFollow <> nil then FBtnFollow.Active := FFollow;
|
||||
ClampWindow;
|
||||
// Наведение декодера могло остаться за краем восстановленного окна — его
|
||||
// подтянет FOLLOW на первом же тике (PollZoom), отдельной логики не нужно.
|
||||
FZoomDirty := True;
|
||||
FStripValid := False;
|
||||
end;
|
||||
|
||||
procedure TBeaconScopeForm.StoreZoomState;
|
||||
// Зовётся каждый тик: так последнее состояние окна попадает в настройки, каким
|
||||
// бы путём оно ни поменялось (колесо, drag, кнопки, авто-подтяжка FOLLOW).
|
||||
begin
|
||||
if FCtl = nil then Exit;
|
||||
FCtl.FBcnZoomSpanHz := FSpanHz;
|
||||
FCtl.FBcnZoomFollow := FFollow;
|
||||
if FCtl.BeaconRefHz > 0 then
|
||||
FCtl.FBcnZoomOffsetHz := FCenterHz - FCtl.BeaconRefHz;
|
||||
end;
|
||||
|
||||
procedure TBeaconScopeForm.PollZoom;
|
||||
// Раз/тик: держим окно на месте (или тянем за декодером), просим у контроллера
|
||||
// свежий кадр лупы и отдаём водопаду.
|
||||
@@ -534,6 +568,7 @@ begin
|
||||
end;
|
||||
end;
|
||||
ClampWindow;
|
||||
StoreZoomState;
|
||||
|
||||
FCtl.SetBeaconZoomWindow(True, FCenterHz, FSpanHz);
|
||||
|
||||
@@ -762,7 +797,7 @@ end;
|
||||
procedure TBeaconScopeForm.DoShow;
|
||||
begin
|
||||
inherited DoShow;
|
||||
if FCenterHz <= 0 then FCenterHz := DefaultCenterHz;
|
||||
RestoreZoomState;
|
||||
// Палитра/гамма водопада — те же, что выбраны для главного (настройки живут
|
||||
// в контроллере), иначе лупа выглядела бы чужой панелью.
|
||||
if (FWf <> nil) and (FCtl <> nil) then
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user