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:
2026-07-10 16:59:27 +03:00
co-authored by Claude Opus 4.8
parent 388687d684
commit ba2a75bb04
11 changed files with 532 additions and 160 deletions
+27 -19
View File
@@ -31,10 +31,12 @@ const
CAT_EXEC_ERROR = 'E;';
CAT_ID_NUM = '019'; // TS-2000 compatible
// Mode mapping ewsdr → CAT
// Mode mapping ewsdr → CAT (зеркало MODE_* из WDSPEngine)
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;
CAT_MODE_COUNT = 11;
// FM repeater offset directions — зеркало FMRepeater.RPT_* (значения должны
// совпадать: контроллер хранит FFMRptDir в этих же кодах).
@@ -383,15 +385,18 @@ end;
function TCATEngine.ModeToK(M: Integer): Integer;
begin
case M of
MODE_LSB: Result := 1;
MODE_USB: Result := 2;
MODE_DSB: Result := 2; // treated as USB for CAT compatibility
MODE_CWU: Result := 3;
MODE_FM: Result := 4;
MODE_AM: Result := 5;
MODE_SAM: Result := 5; // treated as AM
MODE_CWL: Result := 7;
else Result := 2;
MODE_LSB: Result := 1;
MODE_USB: Result := 2;
MODE_DSB: Result := 2; // treated as USB for CAT compatibility
MODE_CWU: Result := 3;
MODE_FM: Result := 4;
MODE_WFM: Result := 4; // treated as FM
MODE_AM: Result := 5;
MODE_SAM: Result := 5; // treated as AM
MODE_DIGU: Result := 6; // Kenwood FSK
MODE_CWL: Result := 7;
MODE_DIGL: Result := 9; // Kenwood FSK-R
else Result := 2;
end;
end;
@@ -403,9 +408,9 @@ begin
3: Result := MODE_CWU;
4: Result := MODE_FM;
5: Result := MODE_AM;
6: Result := MODE_USB; // FSK/RTTY → USB
6: Result := MODE_DIGU; // FSK/RTTY
7: Result := MODE_CWL;
9: Result := MODE_LSB; // FSK-R → LSB
9: Result := MODE_DIGL; // FSK-R
else Result := MODE_USB;
end;
end;
@@ -1787,17 +1792,20 @@ end;
function TCATEngine.ZZML: string;
// ZZML: mode list (returns all mode names)
begin Result := 'LSB USB DSB CWL CWU FM AM SAM '; end;
begin Result := 'LSB USB DSB CWL CWU FM AM SAM DIGUDIGLWFM '; end;
function TCATEngine.ZZMN(const s: string): string;
// ZZMN: mode name — returns mode name for given mode number
// ZZMN: mode name — returns mode name for given mode number.
// Индекс режима теперь двузначный (0..10), но односимвольный запрос
// продолжаем принимать — старые клиенты шлют одну цифру.
var k: Integer;
names: array[0..7] of string;
names: array[0..CAT_MODE_COUNT-1] of string;
begin
names[0]:='LSB'; names[1]:='USB'; names[2]:='DSB'; names[3]:='CWL';
names[4]:='CWU'; names[5]:='FM'; names[6]:='AM'; names[7]:='SAM';
if Length(s) = 1 then begin
k := EnsureRange(StrToIntDef(s,0),0,7);
names[0]:='LSB'; names[1]:='USB'; names[2]:='DSB'; names[3]:='CWL';
names[4]:='CWU'; names[5]:='FM'; names[6]:='AM'; names[7]:='SAM';
names[8]:='DIGU'; names[9]:='DIGL'; names[10]:='WFM';
if (Length(s) = 1) or (Length(s) = 2) then begin
k := EnsureRange(StrToIntDef(s,0),0,CAT_MODE_COUNT-1);
Result := names[k];
end else Result := CAT_ERROR;
end;