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
+4
View File
@@ -620,6 +620,7 @@ type
BeaconZoomSpanHz: Double; // ширина окна лупы, Гц
BeaconZoomOffsetHz: Double; // центр окна = BeaconRefHz + этот сдвиг
BeaconZoomFollow: Boolean; // держать наведение декодера в окне
BeaconZoomAuto: Boolean; // автопоиск маяка в окне лупы
// --- Audio settings ---
AudioOutDevice: string; // PortAudio output device name ('' = default)
AudioSampleRate: Integer; // PortAudio stream sample rate (e.g. 48000)
@@ -988,6 +989,7 @@ begin
G.BeaconZoomSpanHz := 40000.0;
G.BeaconZoomOffsetHz := 0.0;
G.BeaconZoomFollow := True;
G.BeaconZoomAuto := False;
G.AudioOutDevice := '';
G.AudioSampleRate := 48000;
G.AudioInDevice := '';
@@ -1241,6 +1243,7 @@ begin
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);
G.BeaconZoomAuto := JB(GObj,'beacon_zoom_auto',False);
// Audio settings
G.AudioOutDevice := JS(GObj,'audio_out_device','');
G.AudioSampleRate := JI(GObj,'audio_sample_rate',48000);
@@ -1391,6 +1394,7 @@ begin
JW(O,'beacon_zoom_span',G.BeaconZoomSpanHz);
JW(O,'beacon_zoom_offset',G.BeaconZoomOffsetHz);
JW(O,'beacon_zoom_follow',G.BeaconZoomFollow);
JW(O,'beacon_zoom_auto',G.BeaconZoomAuto);
// Audio settings
JWS(O,'audio_out_device',G.AudioOutDevice);
JW(O,'audio_sample_rate',G.AudioSampleRate);