mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 18:43:51 +00:00
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:
+194
-5
@@ -47,6 +47,13 @@ const
|
||||
BCN_CLR_REF = TColor($0000FF00); // clLime — где маяк ДОЛЖЕН быть (пунктир)
|
||||
BCN_CLR_TRK = TColor($000AA5FF); // отслеживаемый центроид (оранж)
|
||||
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
|
||||
TBeaconScopeForm = class(TForm)
|
||||
@@ -78,7 +85,7 @@ type
|
||||
FWfBox: TPaintBox; // мини-водопад лупы
|
||||
FWf: TWaterfallView;
|
||||
FStrip: TBitmap; // офскрин спектра (без него мигает)
|
||||
FBtnZoomIn, FBtnZoomOut, FBtnReset, FBtnFollow: TFlatButton;
|
||||
FBtnZoomIn, FBtnZoomOut, FBtnReset, FBtnFollow, FBtnAuto: TFlatButton;
|
||||
|
||||
FCenterHz: Double; // центр окна лупы (display Hz), 0 = ещё не задан
|
||||
FSpanHz: Double; // ширина окна лупы
|
||||
@@ -100,6 +107,14 @@ type
|
||||
// лезть за копией TBeaconScope на каждую отрисовку незачем.
|
||||
FState: TBeaconState;
|
||||
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 = курсор вне лупы
|
||||
FDragging: Boolean; // ЛКМ зажата в лупе
|
||||
FDragMoved: Boolean; // увели мышь → это панорамирование, не клик
|
||||
@@ -145,6 +160,10 @@ type
|
||||
procedure BtnZoomOutClick(Sender: TObject);
|
||||
procedure BtnResetClick(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;
|
||||
protected
|
||||
procedure DoShow; override;
|
||||
@@ -311,7 +330,8 @@ begin
|
||||
|
||||
FTuneHint := TLabel.Create(Self);
|
||||
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.SetBounds(DpiScale(120), DpiScale(11), DpiScale(360), DpiScale(16));
|
||||
FTuneHint.Font.Size := 8;
|
||||
@@ -319,6 +339,8 @@ begin
|
||||
|
||||
FBtnFollow := MakeFlatBtn(FTune, 'FOLLOW', 0, 0,
|
||||
DpiScale(66), DpiScale(22), @BtnFollowClick);
|
||||
FBtnAuto := MakeFlatBtn(FTune, 'AUTO', 0, 0,
|
||||
DpiScale(52), DpiScale(22), @BtnAutoClick);
|
||||
FBtnReset := MakeFlatBtn(FTune, 'RESET', 0, 0,
|
||||
DpiScale(58), DpiScale(22), @BtnResetClick);
|
||||
FBtnZoomOut := MakeFlatBtn(FTune, '−', 0, 0,
|
||||
@@ -384,7 +406,9 @@ begin
|
||||
Style(FBtnZoomOut);
|
||||
Style(FBtnReset);
|
||||
Style(FBtnFollow);
|
||||
Style(FBtnAuto);
|
||||
if FBtnFollow <> nil then FBtnFollow.Active := FFollow;
|
||||
if FBtnAuto <> nil then FBtnAuto.Active := FAuto;
|
||||
end;
|
||||
|
||||
procedure TBeaconScopeForm.LayoutTunePanel;
|
||||
@@ -399,6 +423,8 @@ begin
|
||||
Y := DpiScale(8);
|
||||
X := W - Pad;
|
||||
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);
|
||||
FBtnReset.SetBounds(X, Y, FBtnReset.Width, FBtnReset.Height);
|
||||
Dec(X, DpiScale(10) + FBtnZoomIn.Width);
|
||||
@@ -517,6 +543,150 @@ begin
|
||||
FCenterHz := EnsureRange(FCenterHz, CapLo + Half, CapHi - Half);
|
||||
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;
|
||||
// Окно лупы переживает перезапуск: ширина, положение и FOLLOW лежат в
|
||||
// глобальных настройках (персист — общий SaveGlobalSettings при выходе).
|
||||
@@ -526,11 +696,14 @@ begin
|
||||
if FCtl = nil then Exit;
|
||||
FSpanHz := EnsureRange(FCtl.FBcnZoomSpanHz, BCN_SPAN_MIN, BCN_SPAN_MAX);
|
||||
FFollow := FCtl.FBcnZoomFollow;
|
||||
FAuto := FCtl.FBcnZoomAuto;
|
||||
if FCtl.BeaconRefHz > 0 then
|
||||
FCenterHz := FCtl.BeaconRefHz + FCtl.FBcnZoomOffsetHz
|
||||
else
|
||||
FCenterHz := DefaultCenterHz;
|
||||
if FBtnFollow <> nil then FBtnFollow.Active := FFollow;
|
||||
if FBtnAuto <> nil then FBtnAuto.Active := FAuto;
|
||||
AutoReset;
|
||||
ClampWindow;
|
||||
// Наведение декодера могло остаться за краем восстановленного окна — его
|
||||
// подтянет FOLLOW на первом же тике (PollZoom), отдельной логики не нужно.
|
||||
@@ -545,6 +718,7 @@ begin
|
||||
if FCtl = nil then Exit;
|
||||
FCtl.FBcnZoomSpanHz := FSpanHz;
|
||||
FCtl.FBcnZoomFollow := FFollow;
|
||||
FCtl.FBcnZoomAuto := FAuto;
|
||||
if FCtl.BeaconRefHz > 0 then
|
||||
FCtl.FBcnZoomOffsetHz := FCenterHz - FCtl.BeaconRefHz;
|
||||
end;
|
||||
@@ -555,7 +729,7 @@ procedure TBeaconScopeForm.PollZoom;
|
||||
var
|
||||
Cnt: Integer;
|
||||
Lo, Hi, Dec_: Double;
|
||||
GotWf: Boolean;
|
||||
GotWf, Fresh: Boolean;
|
||||
begin
|
||||
if FCtl = nil then Exit;
|
||||
|
||||
@@ -583,8 +757,8 @@ begin
|
||||
// Возврат GetBeaconZoomSpectrum = «в кэше был НЕпрочитанный кадр»; это и есть
|
||||
// главный триггер перерисовки трассы. Границы окна тут не помощник: пока
|
||||
// никто не панорамирует, они не меняются, а пиксели — каждый кадр.
|
||||
if FCtl.GetBeaconZoomSpectrum(FPix, Cnt, Lo, Hi) then
|
||||
FZoomDirty := True;
|
||||
Fresh := FCtl.GetBeaconZoomSpectrum(FPix, Cnt, Lo, Hi);
|
||||
if Fresh then FZoomDirty := True;
|
||||
if Cnt > 0 then
|
||||
begin
|
||||
if (Cnt <> FPixCount) or (Abs(Lo - FViewLo) > 0.01)
|
||||
@@ -613,6 +787,8 @@ begin
|
||||
FLastTrkHz := FCtl.BeaconTrackedFreqHz;
|
||||
FLastDecHz := FCtl.BeaconDecodeFreqHz;
|
||||
|
||||
AutoService(Fresh);
|
||||
|
||||
GotWf := FCtl.GetBeaconZoomWaterfall(FWfPix, Cnt);
|
||||
if GotWf and (Cnt > 0) and (FWf <> nil) then
|
||||
begin
|
||||
@@ -806,6 +982,15 @@ begin
|
||||
if FBtnFollow <> nil then FBtnFollow.Active := FFollow;
|
||||
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;
|
||||
begin
|
||||
inherited DoShow;
|
||||
@@ -1146,6 +1331,10 @@ begin
|
||||
end;
|
||||
if (FState <> bsIdle) and (FSNRdB > -50) then
|
||||
Txt := Txt + Format(' · %.1f dB', [FSNRdB]);
|
||||
// Автопоиск помечаем только пока он реально работает: на локе он молчит, и
|
||||
// светить «AUTO» там значило бы обещать действие, которого не происходит.
|
||||
if FAuto and (FState <> bsLock) then
|
||||
Txt := Txt + ' · AUTO';
|
||||
C.TextOut(DpiScale(6), DpiScale(21), Txt);
|
||||
a := DpiScale(6) + C.TextWidth(Txt) + DpiScale(10);
|
||||
|
||||
|
||||
@@ -339,6 +339,7 @@ type
|
||||
FBcnZoomSpanHz: Double; // ширина окна лупы, Гц
|
||||
FBcnZoomOffsetHz: Double; // центр = FBeaconRefHz + этот сдвиг
|
||||
FBcnZoomFollow: Boolean; // держать наведение декодера в окне
|
||||
FBcnZoomAuto: Boolean; // автопоиск маяка в окне лупы
|
||||
|
||||
// ---- DMR 4FSK receive decoder ----
|
||||
FDMRDec: TDMRDecoder; // 48 kHz FM discriminator -> DMR worker
|
||||
@@ -1327,6 +1328,7 @@ begin
|
||||
FBcnZoomSpanHz := 40000.0;
|
||||
FBcnZoomOffsetHz := 0.0;
|
||||
FBcnZoomFollow := True;
|
||||
FBcnZoomAuto := False;
|
||||
FBeaconDec.SetInvert(FBeaconDecInvert);
|
||||
FDSPEngine.SetBeaconDecoder(FBeaconDec);
|
||||
|
||||
@@ -3959,6 +3961,7 @@ begin
|
||||
Result.BeaconZoomSpanHz := FBcnZoomSpanHz;
|
||||
Result.BeaconZoomOffsetHz := FBcnZoomOffsetHz;
|
||||
Result.BeaconZoomFollow := FBcnZoomFollow;
|
||||
Result.BeaconZoomAuto := FBcnZoomAuto;
|
||||
Result.AudioSampleRate := FAudioOut.SampleRate;
|
||||
Result.ShowSpectrum := FShowSpectrum;
|
||||
Result.ShowWaterfall := FShowWaterfall;
|
||||
@@ -7355,6 +7358,7 @@ begin
|
||||
FBcnZoomSpanHz := EnsureRange(FLoadedGlobal.BeaconZoomSpanHz, 600.0, 200000.0);
|
||||
FBcnZoomOffsetHz := EnsureRange(FLoadedGlobal.BeaconZoomOffsetHz, -500000.0, 500000.0);
|
||||
FBcnZoomFollow := FLoadedGlobal.BeaconZoomFollow;
|
||||
FBcnZoomAuto := FLoadedGlobal.BeaconZoomAuto;
|
||||
if FWDSPReady then FDSPEngine.SetDisplayResLimit(FDisplayPixels);
|
||||
FShowSpectrum := FLoadedGlobal.ShowSpectrum;
|
||||
FShowWaterfall := FLoadedGlobal.ShowWaterfall;
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user