feat(beacon): автопоиск маяка в окне лупы (кнопка AUTO)

Лупа сделала прицеливание возможным, но целиться всё равно надо руками. AUTO
находит маяк сам, в текущем окне лупы: что видно, то и обыскивается — заодно
зумом можно сузить область поиска.

Критерий — не «самый громкий», иначе поймается первая же SSB-станция. Копим
min-hold по кадрам лупы (~1.6 с): у маяка несущая непрерывна и минимум остаётся
высоким, а речь, телеграф и цифра в паузах проваливаются к шуму и срезаются
минимумом. Это тот самый «непрерывный след», который видно на водопаде, только
числом.

По накопленному: шумовой пол = медиана, кандидат = максимум скользящего среднего
шириной с полосу приёма (±450 Гц) — среднее по полосе, а не отдельный бин, потому
что маяк это плато ~900 Гц, и по такой мере он выигрывает у узкой пораженки с той
же высотой пика. Порог 6 дБ над полом; центр уточняется центроидом превышения,
чтобы наведение село на середину плато.

- Тумблер рядом с FOLLOW, состояние в настройках (beacon_zoom_auto).
- На локе автопоиск молчит: в контур не лезем.
- После наведения выдержка ~3 с — дать декодеру попробовать захватиться.
- Нашли там же, где уже стоит наведение (ближе 150 Гц) — не трогаем.
  BeaconSeedAtHz дёргает Reseed, и без этой проверки при слабом сигнале
  автопоиск сбрасывал бы захват по кругу, мешая декодеру сойтись.
- В накопление идут только свежие кадры: таймер формы (25 Гц) быстрее
  анализатора (15 к/с), и повторный учёт кадра сокращал бы окно наблюдения,
  ради которого min-hold и заведён.

В строке захвата появляется пометка AUTO — и только пока автопоиск реально
работает: на локе она гаснет, чтобы не обещать действие, которого нет.

Сборка ewsdr (--ws=qt6) и ewsdrd — ОК. На эфире не проверено.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-14 13:01:26 +03:00
co-authored by Claude Opus 5
parent 51599b2457
commit 8569236214
3 changed files with 202 additions and 5 deletions
+194 -5
View File
@@ -47,6 +47,13 @@ const
BCN_CLR_REF = TColor($0000FF00); // clLime — где маяк ДОЛЖЕН быть (пунктир) BCN_CLR_REF = TColor($0000FF00); // clLime — где маяк ДОЛЖЕН быть (пунктир)
BCN_CLR_TRK = TColor($000AA5FF); // отслеживаемый центроид (оранж) BCN_CLR_TRK = TColor($000AA5FF); // отслеживаемый центроид (оранж)
BCN_CLR_DEC = TColor($0020D0FF); // грани полосы приёма + центр наведения BCN_CLR_DEC = TColor($0020D0FF); // грани полосы приёма + центр наведения
// Автопоиск маяка. Копим min-hold по кадрам лупы: у маяка несущая непрерывна,
// и минимум за пару секунд остаётся высоким, а любая SSB/CW-станция в паузах
// проваливается к шуму — это и отсеивает «громко, но не маяк».
BCN_AUTO_FRAMES = 24; // кадров в min-hold (~1.6 с при 15 к/с анализатора)
BCN_AUTO_COOLDOWN = 75; // тиков паузы после наведения (~3 с при 25 Гц)
BCN_AUTO_MIN_DB = 6.0; // насколько кандидат должен возвышаться над полом
BCN_AUTO_MIN_MOVE = 150.0; // ближе этого к текущему наведению не пере-наводим
type type
TBeaconScopeForm = class(TForm) TBeaconScopeForm = class(TForm)
@@ -78,7 +85,7 @@ type
FWfBox: TPaintBox; // мини-водопад лупы FWfBox: TPaintBox; // мини-водопад лупы
FWf: TWaterfallView; FWf: TWaterfallView;
FStrip: TBitmap; // офскрин спектра (без него мигает) FStrip: TBitmap; // офскрин спектра (без него мигает)
FBtnZoomIn, FBtnZoomOut, FBtnReset, FBtnFollow: TFlatButton; FBtnZoomIn, FBtnZoomOut, FBtnReset, FBtnFollow, FBtnAuto: TFlatButton;
FCenterHz: Double; // центр окна лупы (display Hz), 0 = ещё не задан FCenterHz: Double; // центр окна лупы (display Hz), 0 = ещё не задан
FSpanHz: Double; // ширина окна лупы FSpanHz: Double; // ширина окна лупы
@@ -100,6 +107,14 @@ type
// лезть за копией TBeaconScope на каждую отрисовку незачем. // лезть за копией TBeaconScope на каждую отрисовку незачем.
FState: TBeaconState; FState: TBeaconState;
FSNRdB: Double; FSNRdB: Double;
// --- Автопоиск ---
FAuto: Boolean; // кнопка AUTO взведена
FAutoMin: array[0..BCN_MAX_PIX - 1] of Single; // min-hold кадров
FAutoFrames: Integer; // сколько кадров уже в min-hold
FAutoCnt: Integer; // точек в кадре, под который копится min-hold
FAutoLo, FAutoHi: Double; // окно, под которое копится (сменилось — сброс)
FAutoCooldown: Integer; // тиков паузы после наведения
FCursorX: Integer; // -1 = курсор вне лупы FCursorX: Integer; // -1 = курсор вне лупы
FDragging: Boolean; // ЛКМ зажата в лупе FDragging: Boolean; // ЛКМ зажата в лупе
FDragMoved: Boolean; // увели мышь → это панорамирование, не клик FDragMoved: Boolean; // увели мышь → это панорамирование, не клик
@@ -145,6 +160,10 @@ type
procedure BtnZoomOutClick(Sender: TObject); procedure BtnZoomOutClick(Sender: TObject);
procedure BtnResetClick(Sender: TObject); procedure BtnResetClick(Sender: TObject);
procedure BtnFollowClick(Sender: TObject); procedure BtnFollowClick(Sender: TObject);
procedure BtnAutoClick(Sender: TObject);
procedure AutoReset; // сбросить накопление min-hold
procedure AutoService(NewFrame: Boolean); // одна итерация автопоиска
function AutoFindBeacon(out FreqHz: Double): Boolean;
procedure StyleTuneButtons; procedure StyleTuneButtons;
protected protected
procedure DoShow; override; procedure DoShow; override;
@@ -311,7 +330,8 @@ begin
FTuneHint := TLabel.Create(Self); FTuneHint := TLabel.Create(Self);
FTuneHint.Parent := FTune; FTuneHint.Parent := FTune;
FTuneHint.Caption := 'click the beacon to point the decoder · wheel zooms · drag pans'; FTuneHint.Caption :=
'click the beacon to point the decoder · AUTO finds it in this window';
FTuneHint.AutoSize := False; FTuneHint.AutoSize := False;
FTuneHint.SetBounds(DpiScale(120), DpiScale(11), DpiScale(360), DpiScale(16)); FTuneHint.SetBounds(DpiScale(120), DpiScale(11), DpiScale(360), DpiScale(16));
FTuneHint.Font.Size := 8; FTuneHint.Font.Size := 8;
@@ -319,6 +339,8 @@ begin
FBtnFollow := MakeFlatBtn(FTune, 'FOLLOW', 0, 0, FBtnFollow := MakeFlatBtn(FTune, 'FOLLOW', 0, 0,
DpiScale(66), DpiScale(22), @BtnFollowClick); DpiScale(66), DpiScale(22), @BtnFollowClick);
FBtnAuto := MakeFlatBtn(FTune, 'AUTO', 0, 0,
DpiScale(52), DpiScale(22), @BtnAutoClick);
FBtnReset := MakeFlatBtn(FTune, 'RESET', 0, 0, FBtnReset := MakeFlatBtn(FTune, 'RESET', 0, 0,
DpiScale(58), DpiScale(22), @BtnResetClick); DpiScale(58), DpiScale(22), @BtnResetClick);
FBtnZoomOut := MakeFlatBtn(FTune, '', 0, 0, FBtnZoomOut := MakeFlatBtn(FTune, '', 0, 0,
@@ -384,7 +406,9 @@ begin
Style(FBtnZoomOut); Style(FBtnZoomOut);
Style(FBtnReset); Style(FBtnReset);
Style(FBtnFollow); Style(FBtnFollow);
Style(FBtnAuto);
if FBtnFollow <> nil then FBtnFollow.Active := FFollow; if FBtnFollow <> nil then FBtnFollow.Active := FFollow;
if FBtnAuto <> nil then FBtnAuto.Active := FAuto;
end; end;
procedure TBeaconScopeForm.LayoutTunePanel; procedure TBeaconScopeForm.LayoutTunePanel;
@@ -399,6 +423,8 @@ begin
Y := DpiScale(8); Y := DpiScale(8);
X := W - Pad; X := W - Pad;
Dec(X, FBtnFollow.Width); FBtnFollow.SetBounds(X, Y, FBtnFollow.Width, FBtnFollow.Height); Dec(X, FBtnFollow.Width); FBtnFollow.SetBounds(X, Y, FBtnFollow.Width, FBtnFollow.Height);
Dec(X, DpiScale(6) + FBtnAuto.Width);
FBtnAuto.SetBounds(X, Y, FBtnAuto.Width, FBtnAuto.Height);
Dec(X, DpiScale(6) + FBtnReset.Width); Dec(X, DpiScale(6) + FBtnReset.Width);
FBtnReset.SetBounds(X, Y, FBtnReset.Width, FBtnReset.Height); FBtnReset.SetBounds(X, Y, FBtnReset.Width, FBtnReset.Height);
Dec(X, DpiScale(10) + FBtnZoomIn.Width); Dec(X, DpiScale(10) + FBtnZoomIn.Width);
@@ -517,6 +543,150 @@ begin
FCenterHz := EnsureRange(FCenterHz, CapLo + Half, CapHi - Half); FCenterHz := EnsureRange(FCenterHz, CapLo + Half, CapHi - Half);
end; end;
// ===========================================================================
// Автопоиск маяка
// ===========================================================================
// Ищем в ТЕКУЩЕМ окне лупы — что видно, то и обыскивается. Это и предсказуемо,
// и даёт управление: зумом пользователь сам сужает область поиска.
procedure TBeaconScopeForm.AutoReset;
begin
FAutoFrames := 0;
FAutoCnt := 0;
end;
function TBeaconScopeForm.AutoFindBeacon(out FreqHz: Double): Boolean;
// По накопленному min-hold: шумовой пол = медиана, кандидат = максимум
// скользящего среднего шириной с полосу приёма декодера. Именно среднее по
// полосе, а не отдельный бин: маяк — плато ~900 Гц, и по нему он выигрывает у
// узкой пораженки/бирди с той же высотой пика.
var
N, i, Half, i1, i2, Best: Integer;
BinHz, FloorDb, Sum, BestVal, V, WSum, FSum, Wt: Double;
Sorted: array of Double;
TmpD: Double;
a, b: Integer;
begin
Result := False;
FreqHz := 0;
N := FAutoCnt;
if (N < 16) or (FAutoFrames < BCN_AUTO_FRAMES) then Exit;
BinHz := (FAutoHi - FAutoLo) / (N - 1);
if BinHz <= 0 then Exit;
// Шумовой пол — медиана по прореженной выборке (как в шкале дБ кадра).
SetLength(Sorted, (N + 7) div 8);
for i := 0 to High(Sorted) do
Sorted[i] := FAutoMin[Min(i * 8, N - 1)];
for a := 1 to High(Sorted) do
begin
TmpD := Sorted[a];
b := a - 1;
while (b >= 0) and (Sorted[b] > TmpD) do
begin
Sorted[b + 1] := Sorted[b];
Dec(b);
end;
Sorted[b + 1] := TmpD;
end;
FloorDb := Sorted[Length(Sorted) div 2];
Half := Max(1, Round(BCN_DEC_BW_HZ / BinHz));
if 2 * Half + 1 >= N then Exit; // окно уже полосы приёма — не ищем
// Скользящее среднее одним проходом: сумма правится краями окна.
Sum := 0;
for i := 0 to 2 * Half do Sum := Sum + FAutoMin[i];
Best := Half;
BestVal := Sum / (2 * Half + 1);
for i := Half + 1 to N - Half - 1 do
begin
Sum := Sum - FAutoMin[i - Half - 1] + FAutoMin[i + Half];
V := Sum / (2 * Half + 1);
if V > BestVal then
begin
BestVal := V;
Best := i;
end;
end;
if BestVal - FloorDb < BCN_AUTO_MIN_DB then Exit; // ничего похожего на маяк
// Уточнение центра: центроид превышения над полом внутри полосы. Декодер
// дотянет и с ошибкой в сотню герц (у него свой acquisition), но так наведение
// сразу садится на середину плато.
i1 := Max(0, Best - Half);
i2 := Min(N - 1, Best + Half);
WSum := 0;
FSum := 0;
for i := i1 to i2 do
begin
Wt := FAutoMin[i] - FloorDb;
if Wt <= 0 then Continue;
WSum := WSum + Wt;
FSum := FSum + Wt * i;
end;
if WSum <= 0 then Exit;
FreqHz := FAutoLo + (FSum / WSum) * BinHz;
Result := True;
end;
procedure TBeaconScopeForm.AutoService(NewFrame: Boolean);
// Раз в тик, при взведённой AUTO. Пока декодер солидно залочен — не мешаем
// контуру; потерял лок (или его и не было) — копим min-hold и наводимся сами.
// В накопление идут только СВЕЖИЕ кадры: таймер формы быстрее анализатора, и
// повторный учёт одного и того же кадра сокращал бы окно наблюдения, ради
// которого min-hold и заведён.
var
i: Integer;
F: Double;
begin
if not FAuto then Exit;
if FCtl = nil then Exit;
if FState = bsLock then
begin
AutoReset; // лок держится — искать нечего
Exit;
end;
if FAutoCooldown > 0 then
begin
Dec(FAutoCooldown); // ждём, пока декодер попробует захватиться
AutoReset;
Exit;
end;
if (not NewFrame) or (FPixCount < 16) then Exit;
// Окно переехало (зум/пан/смена rate) — накопленное больше не про эти бины.
if (FPixCount <> FAutoCnt) or (Abs(FViewLo - FAutoLo) > 0.5)
or (Abs(FViewHi - FAutoHi) > 0.5) then
begin
FAutoCnt := FPixCount;
FAutoLo := FViewLo;
FAutoHi := FViewHi;
FAutoFrames := 0;
end;
if FAutoFrames = 0 then
for i := 0 to FAutoCnt - 1 do FAutoMin[i] := FPix[i]
else
for i := 0 to FAutoCnt - 1 do
if FPix[i] < FAutoMin[i] then FAutoMin[i] := FPix[i];
Inc(FAutoFrames);
if FAutoFrames < BCN_AUTO_FRAMES then Exit;
// Наводимся, только если нашли ДРУГОЕ место. Если декодер и так смотрит сюда,
// а лока нет (слабый сигнал) — пусть работает: BeaconSeedAtHz дёргает Reseed,
// и повторные наведения на ту же частоту сбрасывали бы захват по кругу.
if AutoFindBeacon(F) then
if (FCtl.BeaconDecodeFreqHz <= 0)
or (Abs(F - FCtl.BeaconDecodeFreqHz) > BCN_AUTO_MIN_MOVE) then
begin
FCtl.BeaconSeedAtHz(F); // тот же путь, что клик мышью
FAutoCooldown := BCN_AUTO_COOLDOWN;
end;
AutoReset; // не нашли (или не двигались) — копим заново
end;
procedure TBeaconScopeForm.RestoreZoomState; procedure TBeaconScopeForm.RestoreZoomState;
// Окно лупы переживает перезапуск: ширина, положение и FOLLOW лежат в // Окно лупы переживает перезапуск: ширина, положение и FOLLOW лежат в
// глобальных настройках (персист — общий SaveGlobalSettings при выходе). // глобальных настройках (персист — общий SaveGlobalSettings при выходе).
@@ -526,11 +696,14 @@ begin
if FCtl = nil then Exit; if FCtl = nil then Exit;
FSpanHz := EnsureRange(FCtl.FBcnZoomSpanHz, BCN_SPAN_MIN, BCN_SPAN_MAX); FSpanHz := EnsureRange(FCtl.FBcnZoomSpanHz, BCN_SPAN_MIN, BCN_SPAN_MAX);
FFollow := FCtl.FBcnZoomFollow; FFollow := FCtl.FBcnZoomFollow;
FAuto := FCtl.FBcnZoomAuto;
if FCtl.BeaconRefHz > 0 then if FCtl.BeaconRefHz > 0 then
FCenterHz := FCtl.BeaconRefHz + FCtl.FBcnZoomOffsetHz FCenterHz := FCtl.BeaconRefHz + FCtl.FBcnZoomOffsetHz
else else
FCenterHz := DefaultCenterHz; FCenterHz := DefaultCenterHz;
if FBtnFollow <> nil then FBtnFollow.Active := FFollow; if FBtnFollow <> nil then FBtnFollow.Active := FFollow;
if FBtnAuto <> nil then FBtnAuto.Active := FAuto;
AutoReset;
ClampWindow; ClampWindow;
// Наведение декодера могло остаться за краем восстановленного окна — его // Наведение декодера могло остаться за краем восстановленного окна — его
// подтянет FOLLOW на первом же тике (PollZoom), отдельной логики не нужно. // подтянет FOLLOW на первом же тике (PollZoom), отдельной логики не нужно.
@@ -545,6 +718,7 @@ begin
if FCtl = nil then Exit; if FCtl = nil then Exit;
FCtl.FBcnZoomSpanHz := FSpanHz; FCtl.FBcnZoomSpanHz := FSpanHz;
FCtl.FBcnZoomFollow := FFollow; FCtl.FBcnZoomFollow := FFollow;
FCtl.FBcnZoomAuto := FAuto;
if FCtl.BeaconRefHz > 0 then if FCtl.BeaconRefHz > 0 then
FCtl.FBcnZoomOffsetHz := FCenterHz - FCtl.BeaconRefHz; FCtl.FBcnZoomOffsetHz := FCenterHz - FCtl.BeaconRefHz;
end; end;
@@ -555,7 +729,7 @@ procedure TBeaconScopeForm.PollZoom;
var var
Cnt: Integer; Cnt: Integer;
Lo, Hi, Dec_: Double; Lo, Hi, Dec_: Double;
GotWf: Boolean; GotWf, Fresh: Boolean;
begin begin
if FCtl = nil then Exit; if FCtl = nil then Exit;
@@ -583,8 +757,8 @@ begin
// Возврат GetBeaconZoomSpectrum = «в кэше был НЕпрочитанный кадр»; это и есть // Возврат GetBeaconZoomSpectrum = «в кэше был НЕпрочитанный кадр»; это и есть
// главный триггер перерисовки трассы. Границы окна тут не помощник: пока // главный триггер перерисовки трассы. Границы окна тут не помощник: пока
// никто не панорамирует, они не меняются, а пиксели — каждый кадр. // никто не панорамирует, они не меняются, а пиксели — каждый кадр.
if FCtl.GetBeaconZoomSpectrum(FPix, Cnt, Lo, Hi) then Fresh := FCtl.GetBeaconZoomSpectrum(FPix, Cnt, Lo, Hi);
FZoomDirty := True; if Fresh then FZoomDirty := True;
if Cnt > 0 then if Cnt > 0 then
begin begin
if (Cnt <> FPixCount) or (Abs(Lo - FViewLo) > 0.01) if (Cnt <> FPixCount) or (Abs(Lo - FViewLo) > 0.01)
@@ -613,6 +787,8 @@ begin
FLastTrkHz := FCtl.BeaconTrackedFreqHz; FLastTrkHz := FCtl.BeaconTrackedFreqHz;
FLastDecHz := FCtl.BeaconDecodeFreqHz; FLastDecHz := FCtl.BeaconDecodeFreqHz;
AutoService(Fresh);
GotWf := FCtl.GetBeaconZoomWaterfall(FWfPix, Cnt); GotWf := FCtl.GetBeaconZoomWaterfall(FWfPix, Cnt);
if GotWf and (Cnt > 0) and (FWf <> nil) then if GotWf and (Cnt > 0) and (FWf <> nil) then
begin begin
@@ -806,6 +982,15 @@ begin
if FBtnFollow <> nil then FBtnFollow.Active := FFollow; if FBtnFollow <> nil then FBtnFollow.Active := FFollow;
end; end;
procedure TBeaconScopeForm.BtnAutoClick(Sender: TObject);
begin
FAuto := not FAuto;
if FBtnAuto <> nil then FBtnAuto.Active := FAuto;
AutoReset;
FAutoCooldown := 0; // включили руками — ищем сразу, без выдержки
FZoomDirty := True; // в строке захвата появляется/пропадает пометка AUTO
end;
procedure TBeaconScopeForm.DoShow; procedure TBeaconScopeForm.DoShow;
begin begin
inherited DoShow; inherited DoShow;
@@ -1146,6 +1331,10 @@ begin
end; end;
if (FState <> bsIdle) and (FSNRdB > -50) then if (FState <> bsIdle) and (FSNRdB > -50) then
Txt := Txt + Format(' · %.1f dB', [FSNRdB]); Txt := Txt + Format(' · %.1f dB', [FSNRdB]);
// Автопоиск помечаем только пока он реально работает: на локе он молчит, и
// светить «AUTO» там значило бы обещать действие, которого не происходит.
if FAuto and (FState <> bsLock) then
Txt := Txt + ' · AUTO';
C.TextOut(DpiScale(6), DpiScale(21), Txt); C.TextOut(DpiScale(6), DpiScale(21), Txt);
a := DpiScale(6) + C.TextWidth(Txt) + DpiScale(10); a := DpiScale(6) + C.TextWidth(Txt) + DpiScale(10);
+4
View File
@@ -339,6 +339,7 @@ type
FBcnZoomSpanHz: Double; // ширина окна лупы, Гц FBcnZoomSpanHz: Double; // ширина окна лупы, Гц
FBcnZoomOffsetHz: Double; // центр = FBeaconRefHz + этот сдвиг FBcnZoomOffsetHz: Double; // центр = FBeaconRefHz + этот сдвиг
FBcnZoomFollow: Boolean; // держать наведение декодера в окне FBcnZoomFollow: Boolean; // держать наведение декодера в окне
FBcnZoomAuto: Boolean; // автопоиск маяка в окне лупы
// ---- DMR 4FSK receive decoder ---- // ---- DMR 4FSK receive decoder ----
FDMRDec: TDMRDecoder; // 48 kHz FM discriminator -> DMR worker FDMRDec: TDMRDecoder; // 48 kHz FM discriminator -> DMR worker
@@ -1327,6 +1328,7 @@ begin
FBcnZoomSpanHz := 40000.0; FBcnZoomSpanHz := 40000.0;
FBcnZoomOffsetHz := 0.0; FBcnZoomOffsetHz := 0.0;
FBcnZoomFollow := True; FBcnZoomFollow := True;
FBcnZoomAuto := False;
FBeaconDec.SetInvert(FBeaconDecInvert); FBeaconDec.SetInvert(FBeaconDecInvert);
FDSPEngine.SetBeaconDecoder(FBeaconDec); FDSPEngine.SetBeaconDecoder(FBeaconDec);
@@ -3959,6 +3961,7 @@ begin
Result.BeaconZoomSpanHz := FBcnZoomSpanHz; Result.BeaconZoomSpanHz := FBcnZoomSpanHz;
Result.BeaconZoomOffsetHz := FBcnZoomOffsetHz; Result.BeaconZoomOffsetHz := FBcnZoomOffsetHz;
Result.BeaconZoomFollow := FBcnZoomFollow; Result.BeaconZoomFollow := FBcnZoomFollow;
Result.BeaconZoomAuto := FBcnZoomAuto;
Result.AudioSampleRate := FAudioOut.SampleRate; Result.AudioSampleRate := FAudioOut.SampleRate;
Result.ShowSpectrum := FShowSpectrum; Result.ShowSpectrum := FShowSpectrum;
Result.ShowWaterfall := FShowWaterfall; Result.ShowWaterfall := FShowWaterfall;
@@ -7355,6 +7358,7 @@ begin
FBcnZoomSpanHz := EnsureRange(FLoadedGlobal.BeaconZoomSpanHz, 600.0, 200000.0); FBcnZoomSpanHz := EnsureRange(FLoadedGlobal.BeaconZoomSpanHz, 600.0, 200000.0);
FBcnZoomOffsetHz := EnsureRange(FLoadedGlobal.BeaconZoomOffsetHz, -500000.0, 500000.0); FBcnZoomOffsetHz := EnsureRange(FLoadedGlobal.BeaconZoomOffsetHz, -500000.0, 500000.0);
FBcnZoomFollow := FLoadedGlobal.BeaconZoomFollow; FBcnZoomFollow := FLoadedGlobal.BeaconZoomFollow;
FBcnZoomAuto := FLoadedGlobal.BeaconZoomAuto;
if FWDSPReady then FDSPEngine.SetDisplayResLimit(FDisplayPixels); if FWDSPReady then FDSPEngine.SetDisplayResLimit(FDisplayPixels);
FShowSpectrum := FLoadedGlobal.ShowSpectrum; FShowSpectrum := FLoadedGlobal.ShowSpectrum;
FShowWaterfall := FLoadedGlobal.ShowWaterfall; FShowWaterfall := FLoadedGlobal.ShowWaterfall;
+4
View File
@@ -620,6 +620,7 @@ type
BeaconZoomSpanHz: Double; // ширина окна лупы, Гц BeaconZoomSpanHz: Double; // ширина окна лупы, Гц
BeaconZoomOffsetHz: Double; // центр окна = BeaconRefHz + этот сдвиг BeaconZoomOffsetHz: Double; // центр окна = BeaconRefHz + этот сдвиг
BeaconZoomFollow: Boolean; // держать наведение декодера в окне BeaconZoomFollow: Boolean; // держать наведение декодера в окне
BeaconZoomAuto: Boolean; // автопоиск маяка в окне лупы
// --- Audio settings --- // --- Audio settings ---
AudioOutDevice: string; // PortAudio output device name ('' = default) AudioOutDevice: string; // PortAudio output device name ('' = default)
AudioSampleRate: Integer; // PortAudio stream sample rate (e.g. 48000) AudioSampleRate: Integer; // PortAudio stream sample rate (e.g. 48000)
@@ -988,6 +989,7 @@ begin
G.BeaconZoomSpanHz := 40000.0; G.BeaconZoomSpanHz := 40000.0;
G.BeaconZoomOffsetHz := 0.0; G.BeaconZoomOffsetHz := 0.0;
G.BeaconZoomFollow := True; G.BeaconZoomFollow := True;
G.BeaconZoomAuto := False;
G.AudioOutDevice := ''; G.AudioOutDevice := '';
G.AudioSampleRate := 48000; G.AudioSampleRate := 48000;
G.AudioInDevice := ''; G.AudioInDevice := '';
@@ -1241,6 +1243,7 @@ begin
G.BeaconZoomSpanHz := JD(GObj,'beacon_zoom_span',40000.0); G.BeaconZoomSpanHz := JD(GObj,'beacon_zoom_span',40000.0);
G.BeaconZoomOffsetHz := JD(GObj,'beacon_zoom_offset',0.0); G.BeaconZoomOffsetHz := JD(GObj,'beacon_zoom_offset',0.0);
G.BeaconZoomFollow := JB(GObj,'beacon_zoom_follow',True); G.BeaconZoomFollow := JB(GObj,'beacon_zoom_follow',True);
G.BeaconZoomAuto := JB(GObj,'beacon_zoom_auto',False);
// Audio settings // Audio settings
G.AudioOutDevice := JS(GObj,'audio_out_device',''); G.AudioOutDevice := JS(GObj,'audio_out_device','');
G.AudioSampleRate := JI(GObj,'audio_sample_rate',48000); G.AudioSampleRate := JI(GObj,'audio_sample_rate',48000);
@@ -1391,6 +1394,7 @@ begin
JW(O,'beacon_zoom_span',G.BeaconZoomSpanHz); JW(O,'beacon_zoom_span',G.BeaconZoomSpanHz);
JW(O,'beacon_zoom_offset',G.BeaconZoomOffsetHz); JW(O,'beacon_zoom_offset',G.BeaconZoomOffsetHz);
JW(O,'beacon_zoom_follow',G.BeaconZoomFollow); JW(O,'beacon_zoom_follow',G.BeaconZoomFollow);
JW(O,'beacon_zoom_auto',G.BeaconZoomAuto);
// Audio settings // Audio settings
JWS(O,'audio_out_device',G.AudioOutDevice); JWS(O,'audio_out_device',G.AudioOutDevice);
JW(O,'audio_sample_rate',G.AudioSampleRate); JW(O,'audio_sample_rate',G.AudioSampleRate);