mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 20:37:33 +00:00
feat(dsp): режимы WFM, DIGI-U и DIGI-L
WFM использует новые wfmd/wfmmod из нашего форка WDSP. Демодулятор —
квадратурный дискриминатор на dsp_rate, поэтому при штатных 48 кГц
девиация 75 кГц не помещается в полосу. В WFM цепь RXA поднимается до
WFMDSPRate (наибольший FAudioRate*2^k <= 192 кГц, делящий SampleRate
нацело), rsmpin децимирует вход сам. Выше 192 кГц смысла нет: энергия
демодулированного тона выходит на полку (33.5k при 192 кГц против 31.8k
при 576 кГц), а FFT дорожает.
Две ловушки WDSP, найденные по дороге:
* firmin.c: nfor = nc / size, idxmask = nfor - 1. Все fircore создаются
с nc = max(2048, dsp_size), поэтому смена dsp_size без RXASetNC даёт
nfor = 0 — пустой массив планов и падение в fftw_execute.
* fir.c/bandpass.c планируют с FFTW_PATIENT, а wisdom.c пишет в
wdspWisdom00 только степени двойки. При SampleRate 576 кГц наивный
dsp_size = in_size = 12288 требовал FFT на 24576 точек — FFTW мерил
план 25 секунд. Отсюда потолок 192 кГц и dsp_size = степень двойки.
RXA и TXA гейтятся раздельно: рейты TXA (48 кГц вход, 192 кГц выход) от
SampleRate не зависят, поэтому wfmmod уходит на широкий dsp_rate даже
когда приёмник остался на 48 кГц.
Заодно исправлен WDSP_SAM: было 12, а RXA_SAM = 10. Раньше 12 было вне
диапазона и WDSP молча игнорировал (SAM работал как DSB); после появления
RXA_WFM = 12 это включало бы WFM-демодулятор вместо SAM. txaMode
расходится с rxaMode после DRM (WFM: RXA 12, TXA 14) — отсюда ModeToWDSPTX.
DIGI-U/DIGI-L получили свою таблицу фильтров (3.0k…300) и полосу от нуля,
а не от среза на 100 Гц, как у SSB. В CAT они замаплены на коды Kenwood 6
(FSK) и 9 (FSK-R) — раньше эти коды сваливались в USB/LSB.
Новые индексы дописаны в конец (DIGU=8, DIGL=9, WFM=10): индекс режима
уходит в settings.json, band-cache, CAT и web-API. В VfoOverlay пришлось
развести хит-коды — режимы кодировались как -(I+1) и налезли на HIT_NR/
HIT_NB/HIT_SNB, а полосы кодировались значением BW, и WFM-овские
120000/160000/200000 налезли на HIT_AGC_PICK_BASE/HIT_DEV_BASE.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+268
-48
@@ -29,25 +29,55 @@ uses
|
||||
WDSP;
|
||||
|
||||
const
|
||||
// WDSP mode integers (нет именованных констант в WDSP.pas)
|
||||
WDSP_LSB = 0;
|
||||
WDSP_USB = 1;
|
||||
WDSP_DSB = 2;
|
||||
WDSP_CWL = 3;
|
||||
WDSP_CWU = 4;
|
||||
WDSP_FM = 5;
|
||||
WDSP_AM = 6;
|
||||
WDSP_SAM = 12;
|
||||
// WDSP mode integers (нет именованных констант в WDSP.pas).
|
||||
// Значения — enum rxaMode (RXA.h) / txaMode (TXA.h). До DRM включительно
|
||||
// нумерация совпадает, дальше расходится: TXA вставляет AM_LSB/AM_USB,
|
||||
// поэтому WFM у RXA = 12, а у TXA = 14. Отсюда две отдельные константы.
|
||||
WDSP_LSB = 0;
|
||||
WDSP_USB = 1;
|
||||
WDSP_DSB = 2;
|
||||
WDSP_CWL = 3;
|
||||
WDSP_CWU = 4;
|
||||
WDSP_FM = 5;
|
||||
WDSP_AM = 6;
|
||||
WDSP_DIGU = 7;
|
||||
WDSP_DIGL = 9;
|
||||
WDSP_SAM = 10;
|
||||
WDSP_RX_WFM = 12;
|
||||
WDSP_TX_WFM = 14;
|
||||
|
||||
// Наши индексы режимов (совпадают с кнопками MainForm)
|
||||
MODE_LSB = 0;
|
||||
MODE_USB = 1;
|
||||
MODE_DSB = 2;
|
||||
MODE_CWL = 3;
|
||||
MODE_CWU = 4;
|
||||
MODE_FM = 5;
|
||||
MODE_AM = 6;
|
||||
MODE_SAM = 7;
|
||||
// Наши индексы режимов (совпадают с кнопками MainForm).
|
||||
// Новые режимы дописываются В КОНЕЦ: индекс уходит в settings.json,
|
||||
// band-cache, CAT (ZZMD/ZZMV) и web-API.
|
||||
MODE_LSB = 0;
|
||||
MODE_USB = 1;
|
||||
MODE_DSB = 2;
|
||||
MODE_CWL = 3;
|
||||
MODE_CWU = 4;
|
||||
MODE_FM = 5;
|
||||
MODE_AM = 6;
|
||||
MODE_SAM = 7;
|
||||
MODE_DIGU = 8;
|
||||
MODE_DIGL = 9;
|
||||
MODE_WFM = 10;
|
||||
|
||||
// Минимальная длина FIR у WDSP: create_* поднимают fircore с
|
||||
// nc = max(2048, dsp_size). В firmin.c nfor = nc / size, idxmask = nfor - 1,
|
||||
// так что при nc < dsp_size получается nfor = 0 — пустой массив FFT-планов и
|
||||
// падение в fftw_execute. Любое изменение dsp_size обязано перевыставить nc.
|
||||
WDSP_MIN_NC = 2048;
|
||||
|
||||
// WFM: девиация и параметры де-эмфазиса вещательного FM.
|
||||
WFM_DEVIATION = 75000.0;
|
||||
WFM_AF_LOW = 20.0;
|
||||
WFM_AF_HIGH = 15000.0;
|
||||
WFM_TAU_DE_US = 50.0e-6; // Европа/Россия (Америка — 75 мкс)
|
||||
// Потолок dsp_rate для WFM. Вещательному каналу (200 кГц) хватает 192 кГц,
|
||||
// а гнать цепь на полном IQ-rate нельзя: при 576 кГц dsp_size стал бы 12288,
|
||||
// и fircore спланировал бы FFT на 2*12288 = 24576 точек. Это не степень
|
||||
// двойки — такого плана нет в wdspWisdom00, и FFTW_PATIENT меряет его ~25 с
|
||||
// при первом входе в режим. rsmpin децимирует in_rate → dsp_rate сам.
|
||||
WFM_DSP_RATE = 192000;
|
||||
|
||||
// AGC modes
|
||||
AGC_OFF = 0;
|
||||
@@ -182,6 +212,10 @@ type
|
||||
FAnalyzerOpen: Boolean;
|
||||
FSampleRate: Integer;
|
||||
FAudioRate: Integer;
|
||||
// Текущий dsp_rate канала RXA. Обычно = FAudioRate; в WFM поднимается
|
||||
// до FSampleRate, иначе девиация 75 кГц не помещается в полосу.
|
||||
FRXDSPRate: Integer;
|
||||
FTXDSPSize: Integer; // текущий dsp_size канала TXA (нижняя граница nc)
|
||||
FBufSize: Integer; // входной буфер @ FSampleRate
|
||||
FAudioBufSize: Integer; // выходной буфер @ FAudioRate (= FBufSize * FAudioRate / FSampleRate)
|
||||
FTXSampleRate: Integer; // фиксированный rate TX/DUC (TX_SAMPLE_RATE), не меняется при ChangeSampleRate
|
||||
@@ -356,7 +390,17 @@ type
|
||||
FBeaconDec: TBeaconDecoder; // не владеет; тап маяка в PushIQItemToDSP
|
||||
|
||||
function ModeToWDSP(Mode: Integer): Integer;
|
||||
function ModeToWDSPTX(Mode: Integer): Integer;
|
||||
procedure ApplyDefaultFilter;
|
||||
// WFM требует dsp_rate >= 192k (девиация 75 кГц не влезает в 48 кГц).
|
||||
function WantWideDSP(Mode: Integer): Boolean;
|
||||
function WFMDSPRate: Integer;
|
||||
function SliceDSPRate(Mode: Integer): Integer;
|
||||
function ClampToNyquist(Hz, Rate: Integer): Integer;
|
||||
function EffectiveTXNC: Integer;
|
||||
procedure ApplyDSPRate(Mode: Integer);
|
||||
procedure ApplyRXWFMToChan(Chan: Integer);
|
||||
procedure ApplyWFMSettings;
|
||||
// --- Мультислайсы ---
|
||||
function FindSliceIdx(Id: Integer): Integer; // -1 если нет
|
||||
procedure OpenSliceChannel(Idx: Integer); // OpenChannel + настройки
|
||||
@@ -770,29 +814,156 @@ end;
|
||||
function TWDSPEngine.ModeToWDSP(Mode: Integer): Integer;
|
||||
begin
|
||||
case Mode of
|
||||
MODE_LSB: Result := WDSP_LSB;
|
||||
MODE_USB: Result := WDSP_USB;
|
||||
MODE_DSB: Result := WDSP_DSB;
|
||||
MODE_CWL: Result := WDSP_CWL;
|
||||
MODE_CWU: Result := WDSP_CWU;
|
||||
MODE_FM: Result := WDSP_FM;
|
||||
MODE_AM: Result := WDSP_AM;
|
||||
MODE_SAM: Result := WDSP_SAM;
|
||||
else Result := WDSP_USB;
|
||||
MODE_LSB: Result := WDSP_LSB;
|
||||
MODE_USB: Result := WDSP_USB;
|
||||
MODE_DSB: Result := WDSP_DSB;
|
||||
MODE_CWL: Result := WDSP_CWL;
|
||||
MODE_CWU: Result := WDSP_CWU;
|
||||
MODE_FM: Result := WDSP_FM;
|
||||
MODE_AM: Result := WDSP_AM;
|
||||
MODE_SAM: Result := WDSP_SAM;
|
||||
MODE_DIGU: Result := WDSP_DIGU;
|
||||
MODE_DIGL: Result := WDSP_DIGL;
|
||||
MODE_WFM: Result := WDSP_RX_WFM;
|
||||
else Result := WDSP_USB;
|
||||
end;
|
||||
end;
|
||||
|
||||
function TWDSPEngine.ModeToWDSPTX(Mode: Integer): Integer;
|
||||
// txaMode расходится с rxaMode после DRM — отдельный маппинг только для WFM.
|
||||
begin
|
||||
if Mode = MODE_WFM then Result := WDSP_TX_WFM
|
||||
else Result := ModeToWDSP(Mode);
|
||||
end;
|
||||
|
||||
function TWDSPEngine.WantWideDSP(Mode: Integer): Boolean;
|
||||
// WFM демодулируется квадратурным дискриминатором на dsp_rate. При 48 кГц
|
||||
// полоса ±24 кГц — вещательный канал (±100 кГц) туда не влезает, поэтому в
|
||||
// WFM цепь RXA поднимается до WFMDSPRate. Если поднимать некуда — работаем
|
||||
// как есть (узкий WFM), но dsp_rate не трогаем.
|
||||
begin
|
||||
Result := (Mode = MODE_WFM) and (WFMDSPRate > FAudioRate);
|
||||
end;
|
||||
|
||||
function TWDSPEngine.WFMDSPRate: Integer;
|
||||
// Наибольший rate вида FAudioRate * 2^k, который не выше WFM_DSP_RATE и делит
|
||||
// FSampleRate нацело. Оба требования жёсткие:
|
||||
// * pre_main_build считает dsp_insize через ЦЕЛОЧИСЛЕННОЕ in_rate/dsp_rate,
|
||||
// поэтому отношение обязано быть точным;
|
||||
// * dsp_size = FAudioBufSize * (rate/FAudioRate) должен остаться степенью
|
||||
// двойки, иначе fircore планирует FFT нестандартного размера мимо wisdom.
|
||||
var
|
||||
R: Integer;
|
||||
begin
|
||||
R := WFM_DSP_RATE div FAudioRate; // 4 при 48 кГц аудио
|
||||
while R > 1 do
|
||||
begin
|
||||
if (FAudioRate * R <= FSampleRate) and (FSampleRate mod (FAudioRate * R) = 0) then
|
||||
Break;
|
||||
R := R div 2;
|
||||
end;
|
||||
Result := FAudioRate * R;
|
||||
end;
|
||||
|
||||
function TWDSPEngine.SliceDSPRate(Mode: Integer): Integer;
|
||||
begin
|
||||
if WantWideDSP(Mode) then Result := WFMDSPRate
|
||||
else Result := FAudioRate;
|
||||
end;
|
||||
|
||||
procedure TWDSPEngine.ApplyDSPRate(Mode: Integer);
|
||||
// Переводит RXA/TXA между «узким» (dsp_rate = FAudioRate) и «широким» режимом.
|
||||
// dsp_size всегда = FAudioBufSize * (dsp_rate / FAudioRate), поэтому dsp_outsize
|
||||
// остаётся FAudioBufSize и буферы fexchange0 (FRXIn/FRXOut/FTXIn/FTXOut)
|
||||
// переаллокации не требуют:
|
||||
// RXA wide: dsp_size=4096 @192k -> out=FAudioBufSize
|
||||
// RXA narw: dsp_size=FAudioBufSize @48k -> out=FAudioBufSize
|
||||
// TXA wide: dsp_size=FTXOutBufSize @192k -> in=FAudioBufSize, out=FTXOutBufSize
|
||||
// TXA narw: dsp_size=FAudioBufSize @48k -> in=FAudioBufSize, out=FTXOutBufSize
|
||||
// Зовётся и из Open — до FInitialized, но уже после OpenChannel обоих каналов.
|
||||
// RXA и TXA гейтятся раздельно: у TXA рейты свои (FAudioRate вход,
|
||||
// FTXSampleRate выход) и от FSampleRate не зависят, поэтому wfmmod уходит
|
||||
// на широкий dsp_rate даже когда приёмник остался на 48 кГц.
|
||||
var
|
||||
Wide: Boolean;
|
||||
RXRate, RXSize, TXRate, TXSize, Old: Integer;
|
||||
begin
|
||||
Wide := (Mode = MODE_WFM);
|
||||
|
||||
if Wide then RXRate := WFMDSPRate else RXRate := FAudioRate;
|
||||
RXSize := FAudioBufSize * (RXRate div FAudioRate);
|
||||
if RXRate <> FRXDSPRate then
|
||||
begin
|
||||
// rate, size и nc меняем под одной остановкой канала: между вызовами
|
||||
// dsp_insize не совпал бы с in_size, и fexchange0 прочитал бы мусор.
|
||||
// RXASetNC/TXASetNC обязательны — см. комментарий к WDSP_MIN_NC.
|
||||
Old := SetChannelState(RXA_CHAN, 0, 1);
|
||||
SetDSPSamplerate(RXA_CHAN, RXRate);
|
||||
SetDSPBuffsize (RXA_CHAN, RXSize);
|
||||
FRXDSPRate := RXRate;
|
||||
RXASetNC (RXA_CHAN, Max(WDSP_MIN_NC, RXSize));
|
||||
SetChannelState (RXA_CHAN, Old, 0);
|
||||
end;
|
||||
|
||||
if Wide then TXRate := FTXSampleRate else TXRate := FAudioRate;
|
||||
TXSize := FAudioBufSize * (TXRate div FAudioRate);
|
||||
if TXSize <> FTXDSPSize then
|
||||
begin
|
||||
Old := SetChannelState(TXA_CHAN, 0, 1);
|
||||
SetDSPSamplerate(TXA_CHAN, TXRate);
|
||||
SetDSPBuffsize (TXA_CHAN, TXSize);
|
||||
FTXDSPSize := TXSize;
|
||||
TXASetNC (TXA_CHAN, EffectiveTXNC);
|
||||
SetChannelState (TXA_CHAN, Old, 0);
|
||||
end;
|
||||
end;
|
||||
|
||||
function TWDSPEngine.EffectiveTXNC: Integer;
|
||||
// Пользовательский FilterNC (1024/2048/4096) не может быть короче dsp_size
|
||||
// канала: иначе nfor = nc / dsp_size = 0. В WFM dsp_size = FTXOutBufSize.
|
||||
begin
|
||||
Result := Max(FTXFilterNC, FTXDSPSize);
|
||||
end;
|
||||
|
||||
procedure TWDSPEngine.ApplyRXWFMToChan(Chan: Integer);
|
||||
// create_wfmd поднимается с американской постоянной 75 мкс.
|
||||
begin
|
||||
SetRXAWFMDeviation (Chan, WFM_DEVIATION);
|
||||
SetRXAWFMAFFilter (Chan, WFM_AF_LOW, WFM_AF_HIGH);
|
||||
SetRXAWFMDeemphRun (Chan, 1);
|
||||
SetRXAWFMDeemphTau (Chan, WFM_TAU_DE_US);
|
||||
end;
|
||||
|
||||
procedure TWDSPEngine.ApplyWFMSettings;
|
||||
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;
|
||||
|
||||
function TWDSPEngine.ClampToNyquist(Hz, Rate: Integer): Integer;
|
||||
var Limit: Integer;
|
||||
begin
|
||||
Limit := Rate div 2 - 1;
|
||||
Result := Max(-Limit, Min(Limit, Hz));
|
||||
end;
|
||||
|
||||
procedure TWDSPEngine.ApplyDefaultFilter;
|
||||
begin
|
||||
case FMode of
|
||||
MODE_LSB: SetFilter(-2400, -100);
|
||||
MODE_USB: SetFilter( 100, 2400);
|
||||
MODE_DSB: SetFilter(-2400, 2400);
|
||||
MODE_CWL: SetFilter( -800, -200);
|
||||
MODE_CWU: SetFilter( 200, 800);
|
||||
MODE_FM: SetFilter(-5000, 5000);
|
||||
MODE_AM: SetFilter(-4000, 4000);
|
||||
MODE_SAM: SetFilter(-4000, 4000);
|
||||
MODE_LSB: SetFilter(-2400, -100);
|
||||
MODE_USB: SetFilter( 100, 2400);
|
||||
MODE_DSB: SetFilter(-2400, 2400);
|
||||
MODE_CWL: SetFilter( -800, -200);
|
||||
MODE_CWU: SetFilter( 200, 800);
|
||||
MODE_FM: SetFilter(-5000, 5000);
|
||||
MODE_AM: SetFilter(-4000, 4000);
|
||||
MODE_SAM: SetFilter(-4000, 4000);
|
||||
MODE_DIGU: SetFilter( 0, 3000);
|
||||
MODE_DIGL: SetFilter(-3000, 0);
|
||||
MODE_WFM: SetFilter(-80000, 80000);
|
||||
end;
|
||||
end;
|
||||
|
||||
@@ -813,6 +984,8 @@ begin
|
||||
// TX rate фиксирован — не зависит от RX. FTXOutBufSize = out_size TXA @ FTXSampleRate.
|
||||
FTXSampleRate := TX_SAMPLE_RATE;
|
||||
FTXOutBufSize := BufSize * FTXSampleRate div AudioRate; // = 4096 @ 48k audio, 192k tx
|
||||
FRXDSPRate := AudioRate; // «узкий» режим; поднимается только в WFM
|
||||
FTXDSPSize := BufSize; // dsp_size TXA в «узком» режиме
|
||||
FInitialized := False;
|
||||
FAnalyzerOpen := False;
|
||||
FMode := MODE_USB;
|
||||
@@ -1157,6 +1330,11 @@ begin
|
||||
0
|
||||
);
|
||||
|
||||
// Каналы только что созданы с dsp_rate = FAudioRate; забываем «широкую»
|
||||
// геометрию прошлой сессии (Close/Open через ChangeSampleRate).
|
||||
FRXDSPRate := FAudioRate;
|
||||
FTXDSPSize := FAudioBufSize;
|
||||
|
||||
create_anbEXT(RXA_CHAN, 1, FBufSize, FSampleRate, 0.0001, 0.0001, 0.0001, 0.05, 20);
|
||||
create_nobEXT(RXA_CHAN, 1, 0, FBufSize, FSampleRate, 0.0001, 0.0001, 0.0001, 0.05, 20);
|
||||
|
||||
@@ -1189,12 +1367,21 @@ begin
|
||||
// изредка фазовый разрыв PostGen-тона (всплеск на водопаде + щелчок).
|
||||
);
|
||||
|
||||
SetTXAMode(TXA_CHAN, ModeToWDSP(FMode));
|
||||
SetTXAMode(TXA_CHAN, ModeToWDSPTX(FMode));
|
||||
SetTXAPanelRun(TXA_CHAN, 1);
|
||||
// PatchPanel: 1=Q, 2=I. Mic подаётся в I (FTXIn[i*2]), Q=0 — выбираем I.
|
||||
SetTXAPanelSelect(TXA_CHAN, 2);
|
||||
|
||||
FInitialized := True;
|
||||
|
||||
// Оба канала созданы — только теперь можно поднять dsp_rate под WFM.
|
||||
// SetMode переставит rate, режимы обоих каналов и параметры wfmd/wfmmod;
|
||||
// SetFilter пересчитает полосу уже под новый dsp_rate.
|
||||
if FMode = MODE_WFM then
|
||||
begin
|
||||
SetMode(FMode);
|
||||
SetFilter(FFilterLow, FFilterHigh);
|
||||
end;
|
||||
// Прокидываем все TX-параметры из FTX*-полей: фильтр, mic gain, EQ,
|
||||
// Leveler, ALC, Compressor, Phase Rotator, AM Carrier, FM, CTCSS.
|
||||
ApplyTXChainSettings;
|
||||
@@ -1547,21 +1734,27 @@ end;
|
||||
// Открывает WDSP-канал слайса с той же геометрией, что RXA_CHAN. Вызывать
|
||||
// под FSliceLock и только при FInitialized.
|
||||
procedure TWDSPEngine.OpenSliceChannel(Idx: Integer);
|
||||
var Chan: Integer;
|
||||
var Chan, DSPRate, DSPSize: Integer;
|
||||
begin
|
||||
if not FInitialized then Exit;
|
||||
if FSlices[Idx].Opened then Exit;
|
||||
Chan := FSlices[Idx].Chan;
|
||||
OpenChannel(Chan, FBufSize, FAudioBufSize, FSampleRate, FAudioRate, FAudioRate,
|
||||
// WFM-слайс, как и главный канал, живёт на WFMDSPRate: rsmpin децимирует
|
||||
// вход, rsmpout сводит к FAudioRate, так что out_size остаётся FAudioBufSize.
|
||||
DSPRate := SliceDSPRate(FSlices[Idx].Mode);
|
||||
DSPSize := FAudioBufSize * (DSPRate div FAudioRate);
|
||||
OpenChannel(Chan, FBufSize, DSPSize, FSampleRate, DSPRate, FAudioRate,
|
||||
0, 0, 0.010, 0.010, 0.010, 0.010, 0);
|
||||
// NB (ANB/NOB) требует создания EXT-блоков на канале, как для RXA_CHAN в Open.
|
||||
create_anbEXT(Chan, 1, FBufSize, FSampleRate, 0.0001, 0.0001, 0.0001, 0.05, 20);
|
||||
create_nobEXT(Chan, 1, 0, FBufSize, FSampleRate, 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);
|
||||
SetRXAShiftRun(Chan, 1);
|
||||
SetRXAShiftFreq(Chan, FSlices[Idx].ShiftHz);
|
||||
RXASetPassband(Chan, FSlices[Idx].FilterLo, FSlices[Idx].FilterHi);
|
||||
RXASetPassband(Chan, ClampToNyquist(FSlices[Idx].FilterLo, DSPRate),
|
||||
ClampToNyquist(FSlices[Idx].FilterHi, DSPRate));
|
||||
ApplyAGCToChan(Chan, FSlices[Idx].AGCMode, 50.0);
|
||||
// NR/NB/SNB/ANF из сохранённого состояния слайса.
|
||||
ApplyNRToChan(Chan, FSlices[Idx].NRMode);
|
||||
@@ -1686,27 +1879,45 @@ begin
|
||||
end;
|
||||
|
||||
procedure TWDSPEngine.SetSliceMode(Id, Mode: Integer);
|
||||
var idx: Integer;
|
||||
var idx: Integer; Rerate: Boolean;
|
||||
begin
|
||||
FSliceLock.Enter;
|
||||
try
|
||||
idx := FindSliceIdx(Id); if idx < 0 then Exit;
|
||||
// Уход в/из WFM меняет dsp_rate канала — проще пересоздать его целиком,
|
||||
// чем гонять SetDSPSamplerate: слайс всё равно перестраивает все фильтры.
|
||||
Rerate := FSlices[idx].Opened and
|
||||
(SliceDSPRate(Mode) <> SliceDSPRate(FSlices[idx].Mode));
|
||||
FSlices[idx].Mode := Mode;
|
||||
if FSlices[idx].Opened then SetRXAMode(FSlices[idx].Chan, ModeToWDSP(Mode));
|
||||
if Rerate then
|
||||
begin
|
||||
CloseSliceChannel(idx);
|
||||
OpenSliceChannel(idx);
|
||||
end
|
||||
else if FSlices[idx].Opened then
|
||||
begin
|
||||
SetRXAMode(FSlices[idx].Chan, ModeToWDSP(Mode));
|
||||
if Mode = MODE_WFM then ApplyRXWFMToChan(FSlices[idx].Chan);
|
||||
end;
|
||||
finally
|
||||
FSliceLock.Leave;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TWDSPEngine.SetSliceFilter(Id, Low, High: Integer);
|
||||
var idx: Integer;
|
||||
var idx, Rate: Integer;
|
||||
begin
|
||||
FSliceLock.Enter;
|
||||
try
|
||||
idx := FindSliceIdx(Id); if idx < 0 then Exit;
|
||||
FSlices[idx].FilterLo := Low;
|
||||
FSlices[idx].FilterHi := High;
|
||||
if FSlices[idx].Opened then RXASetPassband(FSlices[idx].Chan, Low, High);
|
||||
if FSlices[idx].Opened then
|
||||
begin
|
||||
Rate := SliceDSPRate(FSlices[idx].Mode);
|
||||
RXASetPassband(FSlices[idx].Chan, ClampToNyquist(Low, Rate),
|
||||
ClampToNyquist(High, Rate));
|
||||
end;
|
||||
finally
|
||||
FSliceLock.Leave;
|
||||
end;
|
||||
@@ -1865,8 +2076,12 @@ procedure TWDSPEngine.SetMode(Mode: Integer);
|
||||
begin
|
||||
FMode := Mode;
|
||||
if not FInitialized then Exit;
|
||||
// Rate — до режима: setDSPSamplerate_rxa перестраивает всю цепь (в т.ч. wfmd),
|
||||
// а SetRXAMode затем пересчитает bpsnba под уже актуальный dsp_rate.
|
||||
ApplyDSPRate(Mode);
|
||||
SetRXAMode(RXA_CHAN, ModeToWDSP(Mode));
|
||||
SetTXAMode(TXA_CHAN, ModeToWDSP(Mode));
|
||||
SetTXAMode(TXA_CHAN, ModeToWDSPTX(Mode));
|
||||
if Mode = MODE_WFM then ApplyWFMSettings;
|
||||
// НЕ вызываем ApplyDefaultFilter — фильтр устанавливается явно из UI
|
||||
end;
|
||||
|
||||
@@ -1875,8 +2090,13 @@ begin
|
||||
FFilterLow := Low;
|
||||
FFilterHigh := High;
|
||||
if not FInitialized then Exit;
|
||||
// Кромки не могут выходить за Найквист текущего dsp_rate: в WFM они доходят
|
||||
// до ±100 кГц, а dsp_rate зависит от SampleRate. Клампим только то, что
|
||||
// уходит в WDSP — запрошенные значения храним как есть, иначе понижение
|
||||
// SampleRate необратимо «съело» бы полосу.
|
||||
// RXASetPassband — правильный unified API, пересчитывает фильтр целиком
|
||||
RXASetPassband(RXA_CHAN, Low, High);
|
||||
RXASetPassband(RXA_CHAN, ClampToNyquist(Low, FRXDSPRate),
|
||||
ClampToNyquist(High, FRXDSPRate));
|
||||
end;
|
||||
|
||||
procedure TWDSPEngine.SetAGC(Mode: TWDSPAGCMode; FixedGain: Double);
|
||||
@@ -2135,7 +2355,7 @@ end;
|
||||
procedure TWDSPEngine.SetTXMode(Mode: Integer);
|
||||
begin
|
||||
if not FInitialized then Exit;
|
||||
SetTXAMode(TXA_CHAN, ModeToWDSP(Mode));
|
||||
SetTXAMode(TXA_CHAN, ModeToWDSPTX(Mode));
|
||||
end;
|
||||
|
||||
procedure TWDSPEngine.SetTXFilter(Low, High: Integer);
|
||||
@@ -2401,7 +2621,7 @@ begin
|
||||
if FTXFilterWindow >= 0 then
|
||||
SetTXABandpassWindow(TXA_CHAN, FTXFilterWindow);
|
||||
if FTXFilterNC > 0 then
|
||||
TXASetNC(TXA_CHAN, FTXFilterNC);
|
||||
TXASetNC(TXA_CHAN, EffectiveTXNC);
|
||||
if FTXFilterMP >= 0 then
|
||||
TXASetMP(TXA_CHAN, FTXFilterMP);
|
||||
|
||||
@@ -2463,7 +2683,7 @@ begin
|
||||
if not FInitialized then Exit;
|
||||
SetTXABandpassFreqs(TXA_CHAN, Low, High);
|
||||
SetTXABandpassWindow(TXA_CHAN, Window);
|
||||
if NC > 0 then TXASetNC(TXA_CHAN, NC);
|
||||
if NC > 0 then TXASetNC(TXA_CHAN, EffectiveTXNC);
|
||||
if MP >= 0 then TXASetMP(TXA_CHAN, MP);
|
||||
end;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user