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
+11 -6
View File
@@ -193,6 +193,7 @@ begin
'<select id="modGrpSel">' +
'<option value="ssb" selected>SSB</option><option value="cw">CW</option>' +
'<option value="fm">FM</option><option value="am">AM</option>' +
'<option value="digi">DIGI</option>' +
'</select>' +
'<select id="stepSel">' +
'<option value="10">10 Hz</option><option value="100" selected>100 Hz</option>' +
@@ -298,9 +299,9 @@ begin
'function updRfAgc(){const b=q("rfagcBtn");if(!b)return;b.style.display=cur.rf_agc_visible?"":"none";b.textContent="RF AGC "+rfagcModeName(cur.rx_gain_mode);if(rfagcEls)rfagcRefresh();}' +
'const MODE_N=["LSB","USB","DSB","CWL","CWU","FM","AM","SAM"];' +
'const GRP_MAP={ssb:[0,1],cw:[3,4],fm:[5,5],am:[6,7]};' +
'const MODE_TO_GRP=["ssb","ssb","ssb","cw","cw","fm","am","am"];' +
'const MODE_N=["LSB","USB","DSB","CWL","CWU","FM","AM","SAM","DIGU","DIGL","WFM"];' +
'const GRP_MAP={ssb:[0,1],cw:[3,4],fm:[5,10],am:[6,7],digi:[8,9]};' +
'const MODE_TO_GRP=["ssb","ssb","ssb","cw","cw","fm","am","am","digi","digi","fm"];' +
'function modGrpFromMode(m){return MODE_TO_GRP[m]||"ssb";}' +
'function modGrp(){return q("modGrpSel").value||"ssb";}' +
'function modTglTarget(){const grp=GRP_MAP[modGrp()];const m=cur.mode||0;return(m===grp[0])?grp[1]:grp[0];}' +
@@ -316,14 +317,18 @@ begin
'dsb:["16k","12k","10k","8.0k","6.6k","5.2k","4.0k","3.1k","2.9k","2.4k"],' +
'cw:["1.0k","800","600","500","400","250","150","100","50","25"],' +
'am:["20k","18k","16k","12k","10k","9.0k","8.0k","7.0k","6.0k","5.0k"],' +
'fm:["NFM","FM"]};' +
'fm:["NFM","FM"],' +
'wfm:["200k","160k","120k"],' +
'digi:["3.0k","2.7k","2.4k","2.1k","1.8k","1.5k","1.2k","900","600","300"]};' +
'var FNP_BW={ssb:[5000,4400,3800,3300,2900,2700,2400,2100,1800,1000],' +
'dsb:[16000,12000,10000,8000,6600,5200,4000,3100,2900,2400],' +
'cw:[1000,800,600,500,400,250,150,100,50,25],' +
'am:[20000,18000,16000,12000,10000,9000,8000,7000,6000,5000],' +
'fm:[11000,16000]};' +
'fm:[11000,16000],' +
'wfm:[200000,160000,120000],' +
'digi:[3000,2700,2400,2100,1800,1500,1200,900,600,300]};' +
'var fnpOv=null;' +
'function fnpGrp(){var m=cur.mode|0;return(m===3||m===4)?"cw":(m===5)?"fm":(m===2)?"dsb":(m===6||m===7)?"am":"ssb";}' +
'function fnpGrp(){var m=cur.mode|0;return(m===3||m===4)?"cw":(m===5)?"fm":(m===10)?"wfm":(m===8||m===9)?"digi":(m===2)?"dsb":(m===6||m===7)?"am":"ssb";}' +
'function closeFnp(){if(fnpOv){fnpOv.remove();fnpOv=null;}document.removeEventListener("click",fnpOutside);}' +
'function fnpOutside(e){if(fnpOv&&!fnpOv.contains(e.target)&&e.target!==q("fnpBtn"))closeFnp();}' +
'function showFnp(){' +