mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 19:45:09 +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:
+54
-33
@@ -23,6 +23,13 @@ const
|
||||
VFO_OVERLAY_ALPHA = 210;
|
||||
OVL_W = 280; // ширина карточки
|
||||
|
||||
// Индексы совпадают с MODE_* контроллера (WDSPEngine.pas).
|
||||
OVL_MODE_COUNT = 11;
|
||||
OVL_MODE_COLS = 4;
|
||||
OVL_MODE_ROWS = (OVL_MODE_COUNT + OVL_MODE_COLS - 1) div OVL_MODE_COLS;
|
||||
OVL_MODE_NAMES: array[0..OVL_MODE_COUNT-1] of string = (
|
||||
'LSB','USB','DSB','CWL','CWU','FM','AM','SAM','DIGU','DIGL','WFM');
|
||||
|
||||
// Палитра слайсов (TColor = $00BBGGRR): одна на букву — общий цвет для
|
||||
// бейджа-карточки во флаге и для буквы на полосе фильтра в спектре.
|
||||
SLICE_COLORS: array[0..7] of TColor = (
|
||||
@@ -54,7 +61,7 @@ type
|
||||
FFilterBW: Integer;
|
||||
FVfoHz: Double;
|
||||
FSMeterDB: Double;
|
||||
FModeFilter: array[0..7] of Integer;
|
||||
FModeFilter: array[0..OVL_MODE_COUNT-1] of Integer;
|
||||
|
||||
FNRMode: Integer; // 0=off, 1..4
|
||||
FNBMode: Integer; // 0=off, 1=NB, 2=NB2
|
||||
@@ -220,8 +227,9 @@ const
|
||||
KEY_COLOR = TColor($00FF00FF);
|
||||
|
||||
// Значения хит-тестов
|
||||
// режимы: -(I+1) = -1..-8
|
||||
// фильтры: BW в Гц (>0)
|
||||
// режимы: HIT_MODE_BASE-I
|
||||
// фильтры: HIT_FILT_BASE+I (индекс, не BW: полосы WFM доходят до 200000
|
||||
// и налезали бы на HIT_AGC_PICK_BASE/HIT_DEV_BASE)
|
||||
// AGC: HIT_AGC_PICK_BASE+I
|
||||
// устройства: HIT_DEV_BASE+I
|
||||
HIT_NR = -9;
|
||||
@@ -243,6 +251,9 @@ const
|
||||
HIT_SQL_SLIDER = -32; // трек порога FM-squelch
|
||||
HIT_AGC_PICK_BASE = 100000;
|
||||
HIT_DEV_BASE = 200000;
|
||||
// Свои диапазоны, чтобы не пересекаться с тумблерами (-9..-32) и с базами выше.
|
||||
HIT_MODE_BASE = -1000; // -1000 .. -1000-(OVL_MODE_COUNT-1)
|
||||
HIT_FILT_BASE = 1000; // 1000 + индекс кнопки в ряду фильтров
|
||||
|
||||
AGC_NAMES: array[0..4] of string = ('FAST', 'MED', 'SLOW', 'LONG', 'OFF');
|
||||
|
||||
@@ -268,10 +279,6 @@ const
|
||||
CLR_SPLIT_ON = TColor($0020C0FF); // оранжевый SPLIT
|
||||
CLR_BADGE_TXT = TColor($00181818); // тёмный текст поверх ярких бейджей
|
||||
|
||||
OVL_MODE_COUNT = 8;
|
||||
OVL_MODE_NAMES: array[0..7] of string = (
|
||||
'LSB','USB','DSB','CWL','CWU','FM','AM','SAM');
|
||||
|
||||
SSB_BW: array[0..5] of Integer = (1800,2100,2400,2700,3300,3800);
|
||||
SSB_LBL: array[0..5] of string = ('1.8K','2.1K','2.4K','2.7K','3.3K','3.8K');
|
||||
DSB_BW: array[0..5] of Integer = (16000,12000,10000,8000,5200,4000);
|
||||
@@ -282,6 +289,10 @@ const
|
||||
FM_LBL: array[0..1] of string = ('NFM', 'FM');
|
||||
AM_BW: array[0..5] of Integer = (20000,16000,12000,10000,8000,6000);
|
||||
AM_LBL: array[0..5] of string = ('20K','16K','12K','10K','8K','6K');
|
||||
WFM_BW: array[0..2] of Integer = (200000, 160000, 120000);
|
||||
WFM_LBL: array[0..2] of string = ('200K', '160K', '120K');
|
||||
DIGI_BW: array[0..5] of Integer = (300, 600, 1200, 1800, 2400, 3000);
|
||||
DIGI_LBL: array[0..5] of string = ('300','600','1.2K','1.8K','2.4K','3.0K');
|
||||
|
||||
DB_S: array[1..9] of Double = (-121,-115,-109,-103,-97,-91,-85,-79,-73);
|
||||
|
||||
@@ -375,28 +386,36 @@ end;
|
||||
function TVfoOverlay.GetFilterBWForMode(ModeIdx, FilterIdx: Integer): Integer;
|
||||
begin
|
||||
case ModeIdx of
|
||||
0,1: Result := SSB_BW[FilterIdx];
|
||||
2: Result := DSB_BW[FilterIdx];
|
||||
3,4: Result := CW_BW[FilterIdx];
|
||||
5: Result := FM_BW[FilterIdx];
|
||||
else Result := AM_BW[FilterIdx];
|
||||
0,1: Result := SSB_BW[FilterIdx];
|
||||
2: Result := DSB_BW[FilterIdx];
|
||||
3,4: Result := CW_BW[FilterIdx];
|
||||
5: Result := FM_BW[FilterIdx];
|
||||
8,9: Result := DIGI_BW[FilterIdx]; // DIGU/DIGL — полоса от нуля
|
||||
10: Result := WFM_BW[FilterIdx];
|
||||
else Result := AM_BW[FilterIdx];
|
||||
end;
|
||||
end;
|
||||
|
||||
function TVfoOverlay.GetFilterLblForMode(ModeIdx, FilterIdx: Integer): string;
|
||||
begin
|
||||
case ModeIdx of
|
||||
0,1: Result := SSB_LBL[FilterIdx];
|
||||
2: Result := DSB_LBL[FilterIdx];
|
||||
3,4: Result := CW_LBL[FilterIdx];
|
||||
5: Result := FM_LBL[FilterIdx];
|
||||
else Result := AM_LBL[FilterIdx];
|
||||
0,1: Result := SSB_LBL[FilterIdx];
|
||||
2: Result := DSB_LBL[FilterIdx];
|
||||
3,4: Result := CW_LBL[FilterIdx];
|
||||
5: Result := FM_LBL[FilterIdx];
|
||||
8,9: Result := DIGI_LBL[FilterIdx];
|
||||
10: Result := WFM_LBL[FilterIdx];
|
||||
else Result := AM_LBL[FilterIdx];
|
||||
end;
|
||||
end;
|
||||
|
||||
function TVfoOverlay.GetFilterCountForMode(ModeIdx: Integer): Integer;
|
||||
begin
|
||||
if ModeIdx = 5 then Result := 2 else Result := 6;
|
||||
case ModeIdx of
|
||||
5: Result := 2; // FM: NFM/FM
|
||||
10: Result := 3; // WFM: 200K/160K/120K
|
||||
else Result := 6;
|
||||
end;
|
||||
end;
|
||||
|
||||
{ ---- Lifecycle ---- }
|
||||
@@ -413,7 +432,8 @@ begin
|
||||
FHotIdx := -1;
|
||||
FModeFilter[0] := 2700; FModeFilter[1] := 2700; FModeFilter[2] := 8000;
|
||||
FModeFilter[3] := 500; FModeFilter[4] := 500; FModeFilter[5] := 11000;
|
||||
FModeFilter[6] := 8000; FModeFilter[7] := 8000;
|
||||
FModeFilter[6] := 8000; FModeFilter[7] := 8000; FModeFilter[8] := 3000;
|
||||
FModeFilter[9] := 3000; FModeFilter[10] := 160000;
|
||||
FNRMode := 0; FNBMode := 0; FSNBOn := False; FANFOn := False; FAGCMode := 0;
|
||||
FSQLOn := False; FSQLLevel := 40; FSqlDrag := False;
|
||||
FVolume := 70; FMuted := False; FSplit := False; FTx := False;
|
||||
@@ -554,7 +574,7 @@ end;
|
||||
function TVfoOverlay.PanelContentH: Integer;
|
||||
begin
|
||||
case FPanel of
|
||||
fpMode: Result := ROW_H*2 + IGAP + ROW_H + IGAP; // 2 ряда режимов + ряд фильтров
|
||||
fpMode: Result := OVL_MODE_ROWS*(ROW_H + IGAP) + ROW_H; // ряды режимов + ряд фильтров
|
||||
fpDsp: begin
|
||||
Result := ROW_H + IGAP + ROW_H; // toggles + AGC
|
||||
if IsFM then Inc(Result, IGAP + ROW_H); // + ряд SQL (только FM)
|
||||
@@ -748,21 +768,21 @@ var
|
||||
I, Col, Row, BX, BY, BW, FiltN, FBX, FBRight, BV: Integer;
|
||||
HR: TRect;
|
||||
begin
|
||||
BW := (RowW - 3*3) div 4; // 4 колонки, зазор 3
|
||||
BW := (RowW - (OVL_MODE_COLS - 1)*3) div OVL_MODE_COLS; // зазор 3
|
||||
for I := 0 to OVL_MODE_COUNT - 1 do
|
||||
begin
|
||||
Col := I mod 4; Row := I div 4;
|
||||
Col := I mod OVL_MODE_COLS; Row := I div OVL_MODE_COLS;
|
||||
BX := X + Col * (BW + 3);
|
||||
BY := Y + Row * (ROW_H + IGAP);
|
||||
HR := Rect(BX, BY, BX + BW, BY + ROW_H);
|
||||
DrawButton(C, HR, OVL_MODE_NAMES[I], I = FMode,
|
||||
(FHotIdx >= 0) and (FHitActions[FHotIdx] = -(I+1)), 7);
|
||||
RegisterHit(HR, -(I+1));
|
||||
(FHotIdx >= 0) and (FHitActions[FHotIdx] = HIT_MODE_BASE - I), 7);
|
||||
RegisterHit(HR, HIT_MODE_BASE - I);
|
||||
end;
|
||||
|
||||
// Ряд фильтров текущего режима
|
||||
FiltN := GetFilterCountForMode(FMode);
|
||||
BY := Y + 2*(ROW_H + IGAP);
|
||||
BY := Y + OVL_MODE_ROWS*(ROW_H + IGAP);
|
||||
FBX := X;
|
||||
for I := 0 to FiltN - 1 do
|
||||
begin
|
||||
@@ -770,8 +790,8 @@ begin
|
||||
BV := GetFilterBWForMode(FMode, I);
|
||||
HR := Rect(FBX, BY, FBRight - 2, BY + ROW_H);
|
||||
DrawButton(C, HR, GetFilterLblForMode(FMode, I), BV = FFilterBW,
|
||||
(FHotIdx >= 0) and (FHitActions[FHotIdx] = BV), 7);
|
||||
RegisterHit(HR, BV);
|
||||
(FHotIdx >= 0) and (FHitActions[FHotIdx] = HIT_FILT_BASE + I), 7);
|
||||
RegisterHit(HR, HIT_FILT_BASE + I);
|
||||
FBX := FBRight;
|
||||
end;
|
||||
end;
|
||||
@@ -1184,25 +1204,26 @@ begin
|
||||
end;
|
||||
|
||||
// Режим
|
||||
if (HV < 0) and (HV >= -OVL_MODE_COUNT) then
|
||||
if (HV <= HIT_MODE_BASE) and (HV > HIT_MODE_BASE - OVL_MODE_COUNT) then
|
||||
begin
|
||||
NewMode := (-HV) - 1;
|
||||
NewMode := HIT_MODE_BASE - HV;
|
||||
if NewMode <> FMode then
|
||||
begin
|
||||
FModeFilter[FMode] := FFilterBW;
|
||||
FMode := NewMode;
|
||||
FFilterBW := FModeFilter[FMode];
|
||||
Height := ComputeHeight; // число фильтров могло смениться (FM=2)
|
||||
Height := ComputeHeight; // число фильтров могло смениться (FM=2, WFM=3)
|
||||
RequestInvalidate;
|
||||
if Assigned(FOnSelect) then FOnSelect(FMode, FFilterBW);
|
||||
end;
|
||||
Exit;
|
||||
end;
|
||||
|
||||
// Фильтр (BW>0)
|
||||
if HV > 0 then
|
||||
// Фильтр текущего режима (по индексу кнопки, не по BW)
|
||||
if (HV >= HIT_FILT_BASE) and
|
||||
(HV < HIT_FILT_BASE + GetFilterCountForMode(FMode)) then
|
||||
begin
|
||||
NewBW := HV;
|
||||
NewBW := GetFilterBWForMode(FMode, HV - HIT_FILT_BASE);
|
||||
if NewBW <> FFilterBW then
|
||||
begin
|
||||
FFilterBW := NewBW;
|
||||
|
||||
Reference in New Issue
Block a user