fix(puresignal): цель feedback 22 для Orion MkII + outlier-фильтр calcc

Разбор «IMD3 хуже, чем в Thetis»: юзерская плата (Orion MkII/Anvelina
PRO3) при стоковой цели feedback 152 перегружает входные каскады
ADC2208+кодека — фидбек сам интермодулирует, PS корректирует по
искажённому наблюдению. Замеры eu2av (Thetis-Enhanced): оптимум FB≈22
при ATT≈10 дБ.

- PSFBTarget: цель уровня feedback, авто по плате (board 5 → 22, иначе
  152), настройка ps_fb_target (0 = авто)
- авто-ATT: пороги от цели (FB > 1.5×цели / FB < 0.7×цели при ATT>0),
  ddB = 20*log10(FB/цель). Заодно чинит «ATT всегда 0»: старый порог
  >181 на этой плате никогда не достигался
- PSOutlierSigmaEff → SetPSOutlierSigma (новый экспорт libwdsp): MAD-
  фильтр выбросов перед фитом calcc, авто σ=5.0 для Orion MkII,
  настройка ps_outlier_sigma (-1 = авто)
- лампа FB в поповере: зоны от цели (0.7–1.3× зелёная), подпись
  «FB: тек/цель»

Требует libwdsp с патчами eu2av (форк wdsp, коммит d27684c).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-17 23:01:08 +03:00
co-authored by Claude Fable 5
parent 6a9aebc23c
commit 7a999c95ee
5 changed files with 75 additions and 18 deletions
+14 -8
View File
@@ -97,8 +97,9 @@ const
CLR_INPUT = TColor($00FFFFFF);
CLR_INPUT_TEXT = TColor($00202020);
// Лампа feedback (пороги Thetis: >181 горячо, 129..181 оптимум, 91..128
// слабовато, <=90 нет сигнала). TColor = BGR.
// Лампа feedback пороги от цели PSFBTarget (Thetis-Enhanced eu2av):
// >1.5×цели горячо, 0.7..1.3×цели оптимум, 0.5×..0.7× слабовато, ниже — нет
// сигнала. TColor = BGR.
CLR_FB_HOT = TColor($00FF9030); // голубой: слишком горячо (атт. мал)
CLR_FB_OK = TColor($0000FF00); // зелёный: оптимум
CLR_FB_LOW = TColor($0000FFFF); // жёлтый: слабовато
@@ -382,7 +383,7 @@ end;
procedure TPureSignalPopup.RefreshStatus;
var
FB: Integer;
FB, FBTarget: Integer;
LampClr: TColor;
begin
if (FController = nil) or not Visible then Exit;
@@ -394,17 +395,22 @@ begin
FBtn2Ton.Active := FController.FTwoTone;
FB := FController.PSFeedbackLevel;
FBTarget := FController.PSFBTarget;
if FController.FPSFeedbackOn then
begin
if FB > 181 then LampClr := CLR_FB_HOT
else if FB > 128 then LampClr := CLR_FB_OK
else if FB > 90 then LampClr := CLR_FB_LOW
else LampClr := CLR_FB_NONE;
if FB > Round(FBTarget * 1.5) then LampClr := CLR_FB_HOT
else if FB >= Round(FBTarget * 0.7) then
begin
if FB <= Round(FBTarget * 1.3) then LampClr := CLR_FB_OK
else LampClr := CLR_FB_LOW // 1.3..1.5×: желтовато
end
else if FB >= Round(FBTarget * 0.5) then LampClr := CLR_FB_LOW
else LampClr := CLR_FB_NONE;
end
else
LampClr := CLR_LAMP_OFF;
if FFbLamp.Color <> LampClr then FFbLamp.Color := LampClr;
FLbFbVal.Caption := Format('FB: %d', [FB]);
FLbFbVal.Caption := Format('FB: %d/%d', [FB, FBTarget]);
if FController.PSCorrecting then
begin
+40 -8
View File
@@ -507,6 +507,7 @@ type
procedure ApplyNoiseFilterButtonsToDSP; // NR/NB/SNB/ANF → WDSP
procedure SendDUCSpecificFromSettings; // DUC Specific (mic/keyer/atten) в сеть
procedure ApplyTXSettingsToDSP; // TX-цепь (фильтр/gain/EQ/comp/...) → WDSP
function PSOutlierSigmaEff: Double; // σ outlier-фильтра calcc (авто по плате)
// ---- Управление диапазонами (состояние + DSP; рендер через события) ----
// MakeBandSettings — снимок текущего состояния в запись диапазона.
@@ -756,7 +757,11 @@ type
// Вооружение/снятие feedback-тракта (сеть + движок), идемпотентно.
procedure PSSetFeedbackPath(En: Boolean);
// Статус для UI (кэш последнего PureSignalTick)
function PSFeedbackLevel: Integer; // info[4], оптимум ~140-160
function PSFeedbackLevel: Integer; // info[4], оптимум ~PSFBTarget
// Эффективная цель уровня feedback: настройка ps_fb_target, либо авто по
// плате (Orion MkII/ANAN-7000/8000 → 22 — выше перегружается вход ADC,
// замеры eu2av на Anvelina PRO3; остальные → 152, классика Thetis).
function PSFBTarget: Integer;
function PSCalCount: Integer; // info[5]
function PSCorrecting: Boolean; // info[14]=1 — коррекция применяется
function PSMaxTXVal: Double; // GetPSMaxTX (пик TX для SetPk)
@@ -4412,6 +4417,30 @@ begin
Changed(rfPureSignal);
end;
function TRadioController.PSFBTarget: Integer;
begin
Result := FTXSettings.PSFBTarget;
if Result <= 0 then
begin
if FDevConnected and (FNetwork <> nil) and (FNetwork.Device.BoardType = 5) then
Result := 22 // Orion MkII: замеры eu2av — при 152 фидбек-АЦП сам даёт IMD
else
Result := 152; // классическая цель Thetis
end;
end;
function TRadioController.PSOutlierSigmaEff: Double;
begin
Result := FTXSettings.PSOutlierSigma;
if Result < 0 then
begin
if FDevConnected and (FNetwork <> nil) and (FNetwork.Device.BoardType = 5) then
Result := 5.0 // Orion MkII: дефолт Thetis-Enhanced eu2av
else
Result := 0.0; // выключен
end;
end;
procedure TRadioController.ApplyPSSettingsToDSP;
var
Ints, Spi: Integer;
@@ -4426,7 +4455,7 @@ begin
FTXSettings.PSTXDelayNs, FTXSettings.PSRelaxPtol,
FTXSettings.PSPin, FTXSettings.PSMap,
FTXSettings.PSStabilize, Ints, Spi,
FTXSettings.PSHWPeak);
FTXSettings.PSHWPeak, PSOutlierSigmaEff);
// Правка частот двухтональника при активном 2TON — применяем живьём.
if FTwoTone then
FDSPEngine.SetTXTwoTone(True, FTXSettings.PSTTFreq1, FTXSettings.PSTTFreq2);
@@ -4464,6 +4493,7 @@ procedure TRadioController.PureSignalTick;
// + auto-attenuate (timer2code): подстройка AttOnTX по уровню feedback.
var
NewAtt: Integer;
FBTarget: Integer;
ddB: Double;
CalAttemptsChanged: Boolean;
begin
@@ -4555,21 +4585,23 @@ begin
end;
end;
// --- Auto-attenuate (Thetis timer2code) ---
// --- Auto-attenuate (Thetis-Enhanced eu2av timer2code) ---
// По завершении очередной попытки калибровки: если feedback слишком горячий
// (>181) или слабый при ненулевом атт. (<=128), подстроить AttOnTX так,
// чтобы уровень лёг к оптимуму 152.
// (>1.5×цели) или слабый при ненулевом атт. (<0.7×цели), подстроить AttOnTX
// так, чтобы уровень лёг к цели PSFBTarget (22 на Orion MkII — выше вход
// фидбек-АЦП интермодулирует сам, и PS корректирует по искажённому сигналу).
FBTarget := PSFBTarget;
case FPSAAState of
0: begin
CalAttemptsChanged := FPSInfo[5] <> FPSInfoPrev[5];
if FTXSettings.PSAutoAtten and CalAttemptsChanged
and ((FPSInfo[4] > 181) or
((FPSInfo[4] <= 128) and (FTXSettings.AttOnTX > 0))) then
and ((FPSInfo[4] > Round(FBTarget * 1.5)) or
((FPSInfo[4] < Round(FBTarget * 0.7)) and (FTXSettings.AttOnTX > 0))) then
begin
FPSAAState := 1;
if (FPSInfo[4] > 0) and (FPSInfo[4] <= 256) then
begin
ddB := 20.0 * Log10(FPSInfo[4] / 152.293);
ddB := 20.0 * Log10(FPSInfo[4] / FBTarget);
if ddB < -100.0 then ddB := -100.0;
if ddB > +100.0 then ddB := +100.0;
end
+12
View File
@@ -234,6 +234,12 @@ type
PSHWPeak: Double; // SetPk (0.2899 для P2, 0.6121 Saturn)
PSTTFreq1: Integer; // Гц — тон 1 двухтональника (700)
PSTTFreq2: Integer; // Гц — тон 2 двухтональника (1900)
// Калибровка фидбек-тракта (Thetis-Enhanced eu2av): на Orion MkII цель 152
// перегружает входные каскады ADC → IMD-«юбка», оптимум FB≈22 при ATT≈10дБ.
PSFBTarget: Integer; // цель уровня feedback info[4]; 0 = авто по плате
// (Orion MkII → 22, иначе 152)
PSOutlierSigma: Double; // σ outlier-фильтра calcc; <0 = авто по плате
// (Orion MkII → 5.0, иначе выкл.), 0 = выкл.
end;
// Web-сервер — глобальные настройки (не привязаны к устройству, секция "web").
@@ -1214,6 +1220,8 @@ begin
T.PSHWPeak := 0.2899;
T.PSTTFreq1 := 700;
T.PSTTFreq2 := 1900;
T.PSFBTarget := 0; // авто по плате
T.PSOutlierSigma := -1.0; // авто по плате
end;
function TSettingsManager.LoadTX(const MAC: array of Byte;
@@ -1293,6 +1301,8 @@ begin
T.PSHWPeak := JD(O,'ps_hw_peak', T.PSHWPeak);
T.PSTTFreq1 := JI(O,'ps_tt_freq1', T.PSTTFreq1);
T.PSTTFreq2 := JI(O,'ps_tt_freq2', T.PSTTFreq2);
T.PSFBTarget := EnsureRange(JI(O,'ps_fb_target', T.PSFBTarget), 0, 256);
T.PSOutlierSigma := JD(O,'ps_outlier_sigma', T.PSOutlierSigma);
end;
procedure TSettingsManager.SaveTX(const MAC: array of Byte;
@@ -1364,6 +1374,8 @@ begin
JW(O,'ps_hw_peak', T.PSHWPeak);
JW(O,'ps_tt_freq1', T.PSTTFreq1);
JW(O,'ps_tt_freq2', T.PSTTFreq2);
JW(O,'ps_fb_target', T.PSFBTarget);
JW(O,'ps_outlier_sigma',T.PSOutlierSigma);
end;
class procedure TSettingsManager.DefaultAlex(out A: TAlexSettings);
+2
View File
@@ -281,6 +281,8 @@ procedure SetPSPinMode(channel: Integer; pin: Integer); cdecl; external WDSP_LIB
procedure SetPSMapMode(channel: Integer; map: Integer); cdecl; external WDSP_LIB;
procedure SetPSStabilize(channel: Integer; stbl: Integer); cdecl; external WDSP_LIB;
procedure SetPSIntsAndSpi(channel: Integer; ints: Integer; spi: Integer); cdecl; external WDSP_LIB;
// eu2av: медианный (MAD) outlier-фильтр перед кубическим фитом calcc; 0 = выкл.
procedure SetPSOutlierSigma(channel: Integer; sigma: Double); cdecl; external WDSP_LIB;
// ---------------------------------------------------------------------------
// cblock.c
+7 -2
View File
@@ -404,6 +404,7 @@ type
FPSInts: Integer;
FPSSpi: Integer;
FPSHWPeak: Double;
FPSOutlierSigma: Double; // eu2av MAD-фильтр перед фитом; 0 = выкл.
// Кэшированные TX-параметры — применяются после OpenChannel(TXA) и при изменении.
// Для Filter/Compressor/Leveler/ALC/PhaseRot/EQ/AM/FM/CTCSS.
@@ -668,7 +669,8 @@ type
function PSGetMaxTX: Double;
procedure PSSetParams(MoxDelay, LoopDelay: Double; TXDelayNs: Integer;
RelaxPtol: Boolean; Pin, Map, Stabilize: Boolean;
Ints, Spi: Integer; HWPeak: Double);
Ints, Spi: Integer; HWPeak: Double;
OutlierSigma: Double);
// Расширенное TX управление (применяются мгновенно если канал открыт)
procedure SetTXFilterFull(Low, High, NC, MP, Window: Integer);
@@ -1318,6 +1320,7 @@ begin
FPSInts := 16;
FPSSpi := 256;
FPSHWPeak := 0.2899; // протокол 2 (Saturn: 0.6121)
FPSOutlierSigma := 0.0; // выкл. до ApplyPSSettingsToDSP (авто по плате)
FMonitorRXDuringTX := False;
FPostTXMuteUntil := 0;
FActiveDisplayID := RX_DISP_ID;
@@ -3549,11 +3552,12 @@ begin
SetPSStabilize(TXA_CHAN, Ord(FPSStabilize));
SetPSIntsAndSpi(TXA_CHAN, FPSInts, FPSSpi);
SetPSHWPeak(TXA_CHAN, FPSHWPeak);
SetPSOutlierSigma(TXA_CHAN, FPSOutlierSigma);
end;
procedure TWDSPEngine.PSSetParams(MoxDelay, LoopDelay: Double; TXDelayNs: Integer;
RelaxPtol: Boolean; Pin, Map, Stabilize: Boolean; Ints, Spi: Integer;
HWPeak: Double);
HWPeak: Double; OutlierSigma: Double);
begin
FPSMoxDelay := MoxDelay;
FPSLoopDelay := LoopDelay;
@@ -3565,6 +3569,7 @@ begin
if Ints > 0 then FPSInts := Ints;
if Spi > 0 then FPSSpi := Spi;
if HWPeak > 0 then FPSHWPeak := HWPeak;
if OutlierSigma >= 0 then FPSOutlierSigma := OutlierSigma;
ApplyPSParams;
end;