fix(tx): боковая по режиму на всех путях + единая оркестрация TX/RX (эталон Thetis)

Боковая TX теперь строится ЕДИНЫМ конвертером знака TXSignedEdges (порт
Thetis UpdateTXLowHighFilterForMode): магнитуда TX-полосы → знаковые края bp0
по боковой режима. Через него идут ApplyTXChainSettings и SetTXFilterFull —
LSB/DIGL больше не уходят в USB, когда пуш оказывается последним (MOX).

Убраны оба SetTXABandpassRun(1): этот сеттер дёргает bp1 (aux), а не bp0.
При выкл. компрессоре bp1 держал стартовую полосу (−5000,−100) и глушил вход
модулятора — FM/WFM/FMRAW не заводились без «тычка» компрессором. bp1 отдан
компрессору через TXASetupBPFilters, как в Thetis.

Оркестрация сведена: ApplyTXMode (SetTXAMode+ApplyTXModeSettings) — единая
точка TX-перехода для Open/SetMode/SetTXMode (в т.ч. слайс-TX);
ApplyRXModeSettings (WFM/DMR/FMRAW) — для главного RXA и слайсов. Убраны
ручной DMR-инлайн в SetMode и двойной вызов WFM; ApplyWFMSettings→ApplyTXWFMSettings.

Дефолт TX-полосы 100/2800 → 200/3100 (Thetis-паритет).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-24 19:42:06 +03:00
co-authored by Claude Opus 4.8
parent b7e277e72b
commit 51e8fc4782
3 changed files with 85 additions and 35 deletions
+2 -2
View File
@@ -1253,8 +1253,8 @@ begin
T.TUNLevel := 10;
T.TUNFreq := 750;
// SSB-фильтр по умолчанию
T.FilterLow := 100;
T.FilterHigh := 2800;
T.FilterLow := 200;
T.FilterHigh := 3100;
T.FilterNC := 1024;
T.FilterMP := 1; // Low Latency (как в piHPSDR)
T.FilterWindow := 1; // BH4 / Blackman-Harris
+2 -2
View File
@@ -1390,9 +1390,9 @@ begin
Grp := MakeGroupPanel(FPageTransmit, 'TX Filter (bandpass)', MARGIN, Y, GRP_W, 220);
MakeLbl(Grp, 'Low cut (Hz)', PAD, R1 + 5, LW);
FEdTXFiltLow := MkSpin(Grp, CX, R1, 0, 8000, 100);
FEdTXFiltLow := MkSpin(Grp, CX, R1, 0, 8000, 200);
MakeLbl(Grp, 'High cut (Hz)', PAD + 320, R1 + 5, 90);
FEdTXFiltHigh := MkSpin(Grp, PAD + 320 + 100, R1, 100, 12000, 2800);
FEdTXFiltHigh := MkSpin(Grp, PAD + 320 + 100, R1, 100, 12000, 3100);
MakeLbl(Grp, 'FIR length', PAD, R1 + STEP + 5, LW);
FCmbTXFiltNC := MkCmb(Grp, CX, R1 + STEP, 140);
+81 -31
View File
@@ -493,10 +493,20 @@ type
procedure ApplyRXWFMToChan(Chan: Integer);
procedure ApplyRXDMRToChan(Chan: Integer);
procedure ApplyRXRawToChan(Chan: Integer);
procedure ApplyWFMSettings;
procedure ApplyTXWFMSettings;
// Единая RX-режимная надстройка канала (WFM/DMR/FM RAW). Одна точка для
// главного RXA и для слайсов — иначе дублирование по трём местам.
procedure ApplyRXModeSettings(Chan, Mode: Integer);
// Магнитуда TX-полосы (FTXFilter*) → знаковые края bp0 по боковой режима.
// Порт Thetis UpdateTXLowHighFilterForMode: единственный конвертер знака,
// через который обязан идти ЛЮБОЙ пуш TX-bandpass, иначе LSB уходит в USB.
procedure TXSignedEdges(Mode: Integer; out Lo, Hi: Double);
// Mode-dependent TX overlay. FM RAW uses the flat WFM modulator and
// bypasses every speech processor without overwriting the saved TX setup.
procedure ApplyTXModeSettings(Mode: Integer);
// ЕДИНАЯ точка перевода TX-тракта в режим (модулятор + режимная цепь).
// Через неё идут ВСЕ переходы: Open, SetMode, SetTXMode (в т.ч. слайс-TX).
procedure ApplyTXMode(Mode: Integer);
// --- Мультислайсы ---
function FindSliceIdx(Id: Integer): Integer; // -1 если нет
function SliceInRate(Idx: Integer): Integer; // входной rate слайса (его пан)
@@ -1159,15 +1169,50 @@ begin
SetRXAWFMLimRun (Chan, 0);
end;
procedure TWDSPEngine.ApplyWFMSettings;
procedure TWDSPEngine.ApplyTXWFMSettings;
// TX-часть широкого ЧМ. RX-сторона WFM ставится через ApplyRXModeSettings.
begin
ApplyRXWFMToChan(RXA_CHAN);
SetTXAWFMDeviation (TXA_CHAN, WFM_DEVIATION);
SetTXAWFMAFFreqs (TXA_CHAN, WFM_AF_LOW, WFM_AF_HIGH);
SetTXAWFMPreEmphRun (TXA_CHAN, 1);
SetTXAWFMPreEmphTau (TXA_CHAN, WFM_TAU_DE_US);
end;
procedure TWDSPEngine.ApplyRXModeSettings(Chan, Mode: Integer);
// Режимная RX-надстройка канала. Для «нормальных» режимов ничего не нужно —
// всё делает SetRXAMode; спец-обвязку требуют только FM-семейства.
begin
if Mode = MODE_WFM then ApplyRXWFMToChan(Chan)
else if Mode = MODE_DMR then ApplyRXDMRToChan(Chan)
else if Mode = MODE_FMRAW then ApplyRXRawToChan(Chan);
end;
procedure TWDSPEngine.TXSignedEdges(Mode: Integer; out Lo, Hi: Double);
// Порт Thetis UpdateTXLowHighFilterForMode. Вход — магнитуда TX-аудио-полосы
// (FTXFilterLow/High, всегда положительная); выход — знаковые края bp0 под
// боковую режима. Нижняя боковая зеркалит полосу в отрицательные частоты.
var
L, H, Half: Double;
begin
L := FTXFilterLow;
H := FTXFilterHigh;
case Mode of
MODE_LSB, MODE_CWL, MODE_DIGL:
begin Lo := -H; Hi := -L; end; // нижняя боковая
MODE_USB, MODE_CWU, MODE_DIGU:
begin Lo := L; Hi := H; end; // верхняя боковая
MODE_DSB, MODE_AM, MODE_SAM:
begin Lo := -H; Hi := H; end; // симметрично
MODE_FM:
begin // Карсон: ±(девиация + верхний срез)
Half := FTXFMDeviation + FTXFMHighCut;
Lo := -Half; Hi := Half;
end;
else
begin Lo := L; Hi := H; end; // прочее — как есть
end;
end;
procedure TWDSPEngine.ApplyTXModeSettings(Mode: Integer);
begin
if not FInitialized then Exit;
@@ -1180,7 +1225,6 @@ begin
SetTXAWFMAFFreqs (TXA_CHAN, 20.0, 7000.0);
SetTXAWFMPreEmphRun (TXA_CHAN, 0);
SetTXABandpassFreqs (TXA_CHAN, 20.0, 7000.0);
SetTXABandpassRun (TXA_CHAN, 1);
SetTXAPanelGain1 (TXA_CHAN, 1.0);
SetTXACompressorRun (TXA_CHAN, 0);
SetTXALevelerSt (TXA_CHAN, 0);
@@ -1192,12 +1236,22 @@ begin
else
begin
// Restore the user's normal voice/data chain after leaving FM RAW.
SetTXABandpassRun(TXA_CHAN, 1);
// bp1 (aux bandpass) НЕ трогаем вручную — им управляет компрессор через
// TXASetupBPFilters (как в Thetis). bp0 всегда активен сам.
ApplyTXChainSettings;
if Mode = MODE_WFM then ApplyWFMSettings;
if Mode = MODE_WFM then ApplyTXWFMSettings;
end;
end;
procedure TWDSPEngine.ApplyTXMode(Mode: Integer);
// ЕДИНАЯ точка перевода TX-тракта в режим. SetTXAMode пересобирает модулятор и
// bp0/bp1 под режим (TXASetupBPFilters), ApplyTXModeSettings кладёт режимную цепь.
begin
if not FInitialized then Exit;
SetTXAMode(TXA_CHAN, ModeToWDSPTX(Mode));
ApplyTXModeSettings(Mode);
end;
function TWDSPEngine.ClampToNyquist(Hz, Rate: Integer): Integer;
var Limit: Integer;
begin
@@ -1340,8 +1394,8 @@ begin
FillChar(FTXMicRing, SizeOf(FTXMicRing), 0);
// TX-цепь — дефолты, согласуются с Settings.DefaultTX
FTXFilterLow := 100;
FTXFilterHigh := 2800;
FTXFilterLow := 200;
FTXFilterHigh := 3100;
FTXFilterNC := 1024;
FTXFilterMP := 1;
FTXFilterWindow := 1;
@@ -1741,7 +1795,6 @@ begin
);
FTXMode := FMode;
SetTXAMode(TXA_CHAN, ModeToWDSPTX(FTXMode));
SetTXAPanelRun(TXA_CHAN, 1);
// PatchPanel: 1=Q, 2=I. Mic подаётся в I (FTXIn[i*2]), Q=0 — выбираем I.
SetTXAPanelSelect(TXA_CHAN, 2);
@@ -1753,12 +1806,14 @@ begin
// SetFilter пересчитает полосу уже под новый dsp_rate.
if FMode = MODE_WFM then
begin
SetMode(FMode);
SetMode(FMode); // внутри: ApplyRXModeSettings + ApplyTXMode
SetFilter(FFilterLow, FFilterHigh);
end;
// Прокидываем все TX-параметры из FTX*-полей: фильтр, mic gain, EQ,
// Leveler, ALC, Compressor, Phase Rotator, AM Carrier, FM, CTCSS.
ApplyTXChainSettings;
// Перевод TX-тракта в режим через единую точку (для WFM уже сделал SetMode).
if FMode <> MODE_WFM then ApplyTXMode(FTXMode);
ApplyNoiseFilterState;
// Переоткрываем WDSP-каналы активных слайсов (например после ChangeSampleRate,
@@ -2382,9 +2437,7 @@ begin
create_nobEXT(Chan, 1, 0, InSize, InRate, 0.0001, 0.0001, 0.0001, 0.05, 20);
SetRXABandpassWindow(Chan, 1);
SetRXAMode(Chan, ModeToWDSP(FSlices[Idx].Mode));
if FSlices[Idx].Mode = MODE_WFM then ApplyRXWFMToChan(Chan);
if FSlices[Idx].Mode = MODE_DMR then ApplyRXDMRToChan(Chan);
if FSlices[Idx].Mode = MODE_FMRAW then ApplyRXRawToChan(Chan);
ApplyRXModeSettings(Chan, FSlices[Idx].Mode);
SetRXAShiftRun(Chan, 1);
SetRXAShiftFreq(Chan, FSlices[Idx].ShiftHz);
RXASetPassband(Chan, ClampToNyquist(FSlices[Idx].FilterLo, DSPRate),
@@ -2688,9 +2741,7 @@ begin
else if FSlices[idx].Opened then
begin
SetRXAMode(FSlices[idx].Chan, ModeToWDSP(Mode));
if Mode = MODE_WFM then ApplyRXWFMToChan(FSlices[idx].Chan);
if Mode = MODE_DMR then ApplyRXDMRToChan(FSlices[idx].Chan);
if Mode = MODE_FMRAW then ApplyRXRawToChan(FSlices[idx].Chan);
ApplyRXModeSettings(FSlices[idx].Chan, Mode);
if Mode in [MODE_DMR, MODE_FMRAW] then
SetRXAPanelGain1(FSlices[idx].Chan, 1.0)
else
@@ -2895,16 +2946,8 @@ begin
// а SetRXAMode затем пересчитает bpsnba под уже актуальный dsp_rate.
ApplyDSPRate(Mode);
SetRXAMode(RXA_CHAN, ModeToWDSP(Mode));
SetTXAMode(TXA_CHAN, ModeToWDSPTX(Mode));
if Mode = MODE_WFM then ApplyWFMSettings
else if Mode = MODE_DMR then
begin
SetRXAWFMDeviation (RXA_CHAN, DMR_DEVIATION);
SetRXAWFMAFFilter (RXA_CHAN, 20.0, 7000.0);
SetRXAWFMDeemphRun (RXA_CHAN, 0);
end
else if Mode = MODE_FMRAW then ApplyRXRawToChan(RXA_CHAN);
ApplyTXModeSettings(FTXMode);
ApplyRXModeSettings(RXA_CHAN, Mode);
ApplyTXMode(FTXMode);
// НЕ вызываем ApplyDefaultFilter — фильтр устанавливается явно из UI
end;
@@ -3202,8 +3245,7 @@ procedure TWDSPEngine.SetTXMode(Mode: Integer);
begin
FTXMode := Mode;
if not FInitialized then Exit;
SetTXAMode(TXA_CHAN, ModeToWDSPTX(FTXMode));
ApplyTXModeSettings(FTXMode);
ApplyTXMode(FTXMode);
end;
procedure TWDSPEngine.SetTXFilter(Low, High: Integer);
@@ -3498,12 +3540,17 @@ procedure TWDSPEngine.ApplyTXChainSettings;
var
i: Integer;
Gains, Freqs, Qs: array[0..10] of Double;
BpLo, BpHi: Double;
begin
if not FInitialized then Exit;
// Bandpass
// Bandpass — магнитуду TX-полосы прогоняем через знак боковой режима
// (иначе LSB/DIGL уходят в USB, когда этот пуш оказывается последним, напр. MOX).
if (FTXFilterLow > 0) and (FTXFilterHigh > FTXFilterLow) then
SetTXABandpassFreqs(TXA_CHAN, FTXFilterLow, FTXFilterHigh);
begin
TXSignedEdges(FTXMode, BpLo, BpHi);
SetTXABandpassFreqs(TXA_CHAN, BpLo, BpHi);
end;
if FTXFilterWindow >= 0 then
SetTXABandpassWindow(TXA_CHAN, FTXFilterWindow);
if FTXFilterNC > 0 then
@@ -3672,6 +3719,7 @@ begin
end;
procedure TWDSPEngine.SetTXFilterFull(Low, High, NC, MP, Window: Integer);
var BpLo, BpHi: Double;
begin
FTXFilterLow := Low;
FTXFilterHigh := High;
@@ -3681,7 +3729,9 @@ begin
FFilterLow := Low;
FFilterHigh := High;
if not FInitialized then Exit;
SetTXABandpassFreqs(TXA_CHAN, Low, High);
// Магнитуда диалога → знаковые края под боковую режима (см. TXSignedEdges).
TXSignedEdges(FTXMode, BpLo, BpHi);
SetTXABandpassFreqs(TXA_CHAN, BpLo, BpHi);
SetTXABandpassWindow(TXA_CHAN, Window);
if NC > 0 then TXASetNC(TXA_CHAN, EffectiveTXNC);
if MP >= 0 then TXASetMP(TXA_CHAN, MP);