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:
+27
-19
@@ -31,10 +31,12 @@ const
|
|||||||
CAT_EXEC_ERROR = 'E;';
|
CAT_EXEC_ERROR = 'E;';
|
||||||
CAT_ID_NUM = '019'; // TS-2000 compatible
|
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_LSB = 0; MODE_USB = 1; MODE_DSB = 2;
|
||||||
MODE_CWL = 3; MODE_CWU = 4; MODE_FM = 5;
|
MODE_CWL = 3; MODE_CWU = 4; MODE_FM = 5;
|
||||||
MODE_AM = 6; MODE_SAM = 7;
|
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_* (значения должны
|
// FM repeater offset directions — зеркало FMRepeater.RPT_* (значения должны
|
||||||
// совпадать: контроллер хранит FFMRptDir в этих же кодах).
|
// совпадать: контроллер хранит FFMRptDir в этих же кодах).
|
||||||
@@ -383,15 +385,18 @@ end;
|
|||||||
function TCATEngine.ModeToK(M: Integer): Integer;
|
function TCATEngine.ModeToK(M: Integer): Integer;
|
||||||
begin
|
begin
|
||||||
case M of
|
case M of
|
||||||
MODE_LSB: Result := 1;
|
MODE_LSB: Result := 1;
|
||||||
MODE_USB: Result := 2;
|
MODE_USB: Result := 2;
|
||||||
MODE_DSB: Result := 2; // treated as USB for CAT compatibility
|
MODE_DSB: Result := 2; // treated as USB for CAT compatibility
|
||||||
MODE_CWU: Result := 3;
|
MODE_CWU: Result := 3;
|
||||||
MODE_FM: Result := 4;
|
MODE_FM: Result := 4;
|
||||||
MODE_AM: Result := 5;
|
MODE_WFM: Result := 4; // treated as FM
|
||||||
MODE_SAM: Result := 5; // treated as AM
|
MODE_AM: Result := 5;
|
||||||
MODE_CWL: Result := 7;
|
MODE_SAM: Result := 5; // treated as AM
|
||||||
else Result := 2;
|
MODE_DIGU: Result := 6; // Kenwood FSK
|
||||||
|
MODE_CWL: Result := 7;
|
||||||
|
MODE_DIGL: Result := 9; // Kenwood FSK-R
|
||||||
|
else Result := 2;
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -403,9 +408,9 @@ begin
|
|||||||
3: Result := MODE_CWU;
|
3: Result := MODE_CWU;
|
||||||
4: Result := MODE_FM;
|
4: Result := MODE_FM;
|
||||||
5: Result := MODE_AM;
|
5: Result := MODE_AM;
|
||||||
6: Result := MODE_USB; // FSK/RTTY → USB
|
6: Result := MODE_DIGU; // FSK/RTTY
|
||||||
7: Result := MODE_CWL;
|
7: Result := MODE_CWL;
|
||||||
9: Result := MODE_LSB; // FSK-R → LSB
|
9: Result := MODE_DIGL; // FSK-R
|
||||||
else Result := MODE_USB;
|
else Result := MODE_USB;
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
@@ -1787,17 +1792,20 @@ end;
|
|||||||
|
|
||||||
function TCATEngine.ZZML: string;
|
function TCATEngine.ZZML: string;
|
||||||
// ZZML: mode list (returns all mode names)
|
// 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;
|
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;
|
var k: Integer;
|
||||||
names: array[0..7] of string;
|
names: array[0..CAT_MODE_COUNT-1] of string;
|
||||||
begin
|
begin
|
||||||
names[0]:='LSB'; names[1]:='USB'; names[2]:='DSB'; names[3]:='CWL';
|
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[4]:='CWU'; names[5]:='FM'; names[6]:='AM'; names[7]:='SAM';
|
||||||
if Length(s) = 1 then begin
|
names[8]:='DIGU'; names[9]:='DIGL'; names[10]:='WFM';
|
||||||
k := EnsureRange(StrToIntDef(s,0),0,7);
|
if (Length(s) = 1) or (Length(s) = 2) then begin
|
||||||
|
k := EnsureRange(StrToIntDef(s,0),0,CAT_MODE_COUNT-1);
|
||||||
Result := names[k];
|
Result := names[k];
|
||||||
end else Result := CAT_ERROR;
|
end else Result := CAT_ERROR;
|
||||||
end;
|
end;
|
||||||
|
|||||||
+2
-2
@@ -18,9 +18,9 @@ uses
|
|||||||
|
|
||||||
const
|
const
|
||||||
// Копии из MainForm: нужны для выпадающих списков
|
// Копии из MainForm: нужны для выпадающих списков
|
||||||
CH_MODE_COUNT = 8;
|
CH_MODE_COUNT = 11;
|
||||||
CH_MODE_NAMES: array[0..CH_MODE_COUNT-1] of string =
|
CH_MODE_NAMES: array[0..CH_MODE_COUNT-1] of string =
|
||||||
('LSB','USB','DSB','CWL','CWU','FM','AM','SAM');
|
('LSB','USB','DSB','CWL','CWU','FM','AM','SAM','DIGU','DIGL','WFM');
|
||||||
|
|
||||||
CH_CTCSS_COUNT = 38;
|
CH_CTCSS_COUNT = 38;
|
||||||
CH_CTCSS_NAMES: array[0..CH_CTCSS_COUNT-1] of string = (
|
CH_CTCSS_NAMES: array[0..CH_CTCSS_COUNT-1] of string = (
|
||||||
|
|||||||
+79
-24
@@ -64,7 +64,7 @@ const
|
|||||||
WDSP_WISDOM_FILE = 'wdspWisdom00';
|
WDSP_WISDOM_FILE = 'wdspWisdom00';
|
||||||
|
|
||||||
BAND_COUNT = 11;
|
BAND_COUNT = 11;
|
||||||
MODE_COUNT = 8;
|
MODE_COUNT = 11;
|
||||||
// Полосы/дефолты фильтров (FILT_COUNT, FILT_*_BW, FILT_*_DEF, FILT_FM_*)
|
// Полосы/дефолты фильтров (FILT_COUNT, FILT_*_BW, FILT_*_DEF, FILT_FM_*)
|
||||||
// перенесены в Settings.pas — нужны TRadioController. Здесь остаются только
|
// перенесены в Settings.pas — нужны TRadioController. Здесь остаются только
|
||||||
// UI-подписи кнопок (NAMES).
|
// UI-подписи кнопок (NAMES).
|
||||||
@@ -76,7 +76,10 @@ const
|
|||||||
('16k','12k','10k','8.0k','6.6k','5.2k','4.0k','3.1k','2.9k','2.4k');
|
('16k','12k','10k','8.0k','6.6k','5.2k','4.0k','3.1k','2.9k','2.4k');
|
||||||
FILT_AM_NAMES: array[0..FILT_COUNT-1] of string =
|
FILT_AM_NAMES: array[0..FILT_COUNT-1] of string =
|
||||||
('20k','18k','16k','12k','10k','9.0k','8.0k','7.0k','6.0k','5.0k');
|
('20k','18k','16k','12k','10k','9.0k','8.0k','7.0k','6.0k','5.0k');
|
||||||
|
FILT_DIGI_NAMES: array[0..FILT_COUNT-1] of string =
|
||||||
|
('3.0k','2.7k','2.4k','2.1k','1.8k','1.5k','1.2k','900','600','300');
|
||||||
FILT_FM_NAMES: array[0..1] of string = ('NFM', 'FM');
|
FILT_FM_NAMES: array[0..1] of string = ('NFM', 'FM');
|
||||||
|
FILT_WFM_NAMES: array[0..FILT_WFM_COUNT-1] of string = ('200k', '160k', '120k');
|
||||||
|
|
||||||
// CTCSS tones (38 standard tones, no None — toggle via CTCSS button)
|
// CTCSS tones (38 standard tones, no None — toggle via CTCSS button)
|
||||||
// CTCSS_COUNT и CTCSS_TONES перенесены в FMRepeater (нужны TRadioController).
|
// CTCSS_COUNT и CTCSS_TONES перенесены в FMRepeater (нужны TRadioController).
|
||||||
@@ -97,7 +100,8 @@ const
|
|||||||
'160m','80m','60m','40m','30m','20m','17m','15m','12m','10m','6m');
|
'160m','80m','60m','40m','30m','20m','17m','15m','12m','10m','6m');
|
||||||
|
|
||||||
MODE_NAMES: array[0..MODE_COUNT-1] of string = (
|
MODE_NAMES: array[0..MODE_COUNT-1] of string = (
|
||||||
'LSB','USB','DSB','CWL','CWU','FM','AM','SAM');
|
'LSB','USB','DSB','CWL','CWU','FM','AM','SAM','DIGU','DIGL','WFM');
|
||||||
|
MODE_ROWS = 3; // 4 колонки; последний ряд неполный
|
||||||
|
|
||||||
SMETER_WIDTH_RATIO = 0.32;
|
SMETER_WIDTH_RATIO = 0.32;
|
||||||
SMETER_MARGIN = 3;
|
SMETER_MARGIN = 3;
|
||||||
@@ -1551,6 +1555,8 @@ const
|
|||||||
BTN_H = 26;
|
BTN_H = 26;
|
||||||
BTN_SM = 24;
|
BTN_SM = 24;
|
||||||
LEFT_W = 232;
|
LEFT_W = 232;
|
||||||
|
// Заголовок (16) + MODE_ROWS рядов с шагом 27 + нижний отступ.
|
||||||
|
MODE_PANEL_H = 16 + (MODE_ROWS - 1) * 27 + BTN_SM + 7;
|
||||||
PLUTO_AGC_H = 64; // высота отдельного Pluto RF-AGC блока (под AGC)
|
PLUTO_AGC_H = 64; // высота отдельного Pluto RF-AGC блока (под AGC)
|
||||||
TOP_VFO_W = 244;
|
TOP_VFO_W = 244;
|
||||||
TOP_VFO_FREQ_H = 47;
|
TOP_VFO_FREQ_H = 47;
|
||||||
@@ -2005,10 +2011,10 @@ begin
|
|||||||
|
|
||||||
Inc(Y, 76);
|
Inc(Y, 76);
|
||||||
|
|
||||||
// Mode selector
|
// Mode selector — MODE_ROWS рядов по 4 кнопки (последний неполный).
|
||||||
PanelMode := TPanel.Create(Self);
|
PanelMode := TPanel.Create(Self);
|
||||||
PanelMode.Parent := PanelLeft;
|
PanelMode.Parent := PanelLeft;
|
||||||
PanelMode.SetBounds(0, Y, LEFT_W, 74);
|
PanelMode.SetBounds(0, Y, LEFT_W, MODE_PANEL_H);
|
||||||
PanelMode.BevelOuter := bvNone;
|
PanelMode.BevelOuter := bvNone;
|
||||||
MakeLbl(PanelMode, 'MODE', 4, 2);
|
MakeLbl(PanelMode, 'MODE', 4, 2);
|
||||||
for i := 0 to MODE_COUNT - 1 do
|
for i := 0 to MODE_COUNT - 1 do
|
||||||
@@ -2023,7 +2029,7 @@ begin
|
|||||||
StyleButton(B, i = FController.FMode);
|
StyleButton(B, i = FController.FMode);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
Inc(Y, 76);
|
Inc(Y, MODE_PANEL_H + 2);
|
||||||
|
|
||||||
// Filter selector (10 кнопок в 2 ряда по 5)
|
// Filter selector (10 кнопок в 2 ряда по 5)
|
||||||
PanelFilter := TPanel.Create(Self);
|
PanelFilter := TPanel.Create(Self);
|
||||||
@@ -4057,6 +4063,28 @@ begin
|
|||||||
if PanelFMCTCSS <> nil then PanelFMCTCSS.Visible := False;
|
if PanelFMCTCSS <> nil then PanelFMCTCSS.Visible := False;
|
||||||
if PanelFMStep <> nil then begin CloseFMStepPopup; PanelFMStep.Visible := False; end;
|
if PanelFMStep <> nil then begin CloseFMStepPopup; PanelFMStep.Visible := False; end;
|
||||||
if PanelFMRpt <> nil then PanelFMRpt.Visible := False;
|
if PanelFMRpt <> nil then PanelFMRpt.Visible := False;
|
||||||
|
|
||||||
|
if FController.FMode = MODE_WFM then
|
||||||
|
begin
|
||||||
|
// WFM: полосы вещательного канала — три кнопки в один ряд.
|
||||||
|
CloseCTCSSPopup;
|
||||||
|
if FController.FFilter > FILT_WFM_COUNT - 1 then FController.FFilter := FILT_WFM_DEF;
|
||||||
|
FController.FFilterBW := FILT_WFM_BW[FController.FFilter];
|
||||||
|
for i := 0 to FILT_WFM_COUNT - 1 do
|
||||||
|
begin
|
||||||
|
BtnFilter[i].Caption := FILT_WFM_NAMES[i];
|
||||||
|
BtnFilter[i].Left := LeftPanelButtonLeft(PanelFilter.Width, FILT_WFM_COUNT, i);
|
||||||
|
BtnFilter[i].Width := LeftPanelButtonWidth(PanelFilter.Width, FILT_WFM_COUNT, i);
|
||||||
|
BtnFilter[i].Top := 16;
|
||||||
|
BtnFilter[i].Visible := True;
|
||||||
|
StyleButton(BtnFilter[i], i = FController.FFilter);
|
||||||
|
end;
|
||||||
|
for i := FILT_WFM_COUNT to FILT_COUNT - 1 do BtnFilter[i].Visible := False;
|
||||||
|
PanelFilter.Height := BtnFilter[0].Top + BtnFilter[0].Height + 4;
|
||||||
|
RelayoutBelowBands;
|
||||||
|
Exit;
|
||||||
|
end;
|
||||||
|
|
||||||
for i := 0 to FILT_COUNT - 1 do
|
for i := 0 to FILT_COUNT - 1 do
|
||||||
begin
|
begin
|
||||||
BtnFilter[i].Left := LeftPanelButtonLeft(PanelFilter.Width, 5, i mod 5);
|
BtnFilter[i].Left := LeftPanelButtonLeft(PanelFilter.Width, 5, i mod 5);
|
||||||
@@ -4066,15 +4094,23 @@ begin
|
|||||||
end;
|
end;
|
||||||
|
|
||||||
case FController.FMode of
|
case FController.FMode of
|
||||||
0, 1: begin
|
MODE_LSB, MODE_USB:
|
||||||
|
begin
|
||||||
for i := 0 to FILT_COUNT-1 do Names[i] := FILT_SSB_NAMES[i];
|
for i := 0 to FILT_COUNT-1 do Names[i] := FILT_SSB_NAMES[i];
|
||||||
DefIdx := FILT_SSB_DEF;
|
DefIdx := FILT_SSB_DEF;
|
||||||
end;
|
end;
|
||||||
2: begin
|
MODE_DIGU, MODE_DIGL:
|
||||||
|
begin
|
||||||
|
for i := 0 to FILT_COUNT-1 do Names[i] := FILT_DIGI_NAMES[i];
|
||||||
|
DefIdx := FILT_DIGI_DEF;
|
||||||
|
end;
|
||||||
|
MODE_DSB:
|
||||||
|
begin
|
||||||
for i := 0 to FILT_COUNT-1 do Names[i] := FILT_DSB_NAMES[i];
|
for i := 0 to FILT_COUNT-1 do Names[i] := FILT_DSB_NAMES[i];
|
||||||
DefIdx := FILT_DSB_DEF;
|
DefIdx := FILT_DSB_DEF;
|
||||||
end;
|
end;
|
||||||
3, 4: begin
|
MODE_CWL, MODE_CWU:
|
||||||
|
begin
|
||||||
for i := 0 to FILT_COUNT-1 do Names[i] := FILT_CW_NAMES[i];
|
for i := 0 to FILT_COUNT-1 do Names[i] := FILT_CW_NAMES[i];
|
||||||
DefIdx := FILT_CW_DEF;
|
DefIdx := FILT_CW_DEF;
|
||||||
end;
|
end;
|
||||||
@@ -4086,9 +4122,14 @@ begin
|
|||||||
|
|
||||||
FController.FFilter := DefIdx;
|
FController.FFilter := DefIdx;
|
||||||
case FController.FMode of
|
case FController.FMode of
|
||||||
0, 1: FController.FFilterBW := FILT_SSB_BW[DefIdx];
|
MODE_LSB, MODE_USB:
|
||||||
2: FController.FFilterBW := FILT_DSB_BW[DefIdx];
|
FController.FFilterBW := FILT_SSB_BW[DefIdx];
|
||||||
3, 4: FController.FFilterBW := FILT_CW_BW[DefIdx];
|
MODE_DIGU, MODE_DIGL:
|
||||||
|
FController.FFilterBW := FILT_DIGI_BW[DefIdx];
|
||||||
|
MODE_DSB:
|
||||||
|
FController.FFilterBW := FILT_DSB_BW[DefIdx];
|
||||||
|
MODE_CWL, MODE_CWU:
|
||||||
|
FController.FFilterBW := FILT_CW_BW[DefIdx];
|
||||||
else FController.FFilterBW := FILT_AM_BW[DefIdx];
|
else FController.FFilterBW := FILT_AM_BW[DefIdx];
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -5190,8 +5231,10 @@ begin
|
|||||||
if Assigned(FVfoOverlay) and FVfoOverlay.Visible then
|
if Assigned(FVfoOverlay) and FVfoOverlay.Visible then
|
||||||
begin
|
begin
|
||||||
case FController.FMode of
|
case FController.FMode of
|
||||||
MODE_LSB: begin LoHz := -FController.FFilterBW; HiHz := -100; end;
|
MODE_LSB: begin LoHz := -FController.FFilterBW; HiHz := -100; end;
|
||||||
MODE_USB: begin LoHz := 100; HiHz := FController.FFilterBW; end;
|
MODE_USB: begin LoHz := 100; HiHz := FController.FFilterBW; end;
|
||||||
|
MODE_DIGL: begin LoHz := -FController.FFilterBW; HiHz := 0; end;
|
||||||
|
MODE_DIGU: begin LoHz := 0; HiHz := FController.FFilterBW; end;
|
||||||
else begin LoHz := -FController.FFilterBW / 2; HiHz := FController.FFilterBW / 2; end;
|
else begin LoHz := -FController.FFilterBW / 2; HiHz := FController.FFilterBW / 2; end;
|
||||||
end;
|
end;
|
||||||
Ovls[n] := FVfoOverlay;
|
Ovls[n] := FVfoOverlay;
|
||||||
@@ -5275,15 +5318,27 @@ begin
|
|||||||
end;
|
end;
|
||||||
FController.FMode := Mode;
|
FController.FMode := Mode;
|
||||||
FController.FFilterBW := FilterBW;
|
FController.FFilterBW := FilterBW;
|
||||||
if Mode = MODE_FM then
|
case Mode of
|
||||||
begin
|
MODE_FM:
|
||||||
FController.FFilter := FILT_FM_DEF;
|
|
||||||
for Idx := 0 to FILT_FM_COUNT - 1 do
|
|
||||||
if FILT_FM_BW[Idx] = FilterBW then
|
|
||||||
begin
|
begin
|
||||||
FController.FFilter := Idx;
|
FController.FFilter := FILT_FM_DEF;
|
||||||
FController.FFMDeviation := FILT_FM_DEV[Idx];
|
for Idx := 0 to FILT_FM_COUNT - 1 do
|
||||||
Break;
|
if FILT_FM_BW[Idx] = FilterBW then
|
||||||
|
begin
|
||||||
|
FController.FFilter := Idx;
|
||||||
|
FController.FFMDeviation := FILT_FM_DEV[Idx];
|
||||||
|
Break;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
MODE_WFM:
|
||||||
|
begin
|
||||||
|
FController.FFilter := FILT_WFM_DEF;
|
||||||
|
for Idx := 0 to FILT_WFM_COUNT - 1 do
|
||||||
|
if FILT_WFM_BW[Idx] = FilterBW then
|
||||||
|
begin
|
||||||
|
FController.FFilter := Idx;
|
||||||
|
Break;
|
||||||
|
end;
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
if FController.FWDSPReady then
|
if FController.FWDSPReady then
|
||||||
@@ -5576,9 +5631,9 @@ begin
|
|||||||
O := FindSliceFlag(SliceId);
|
O := FindSliceFlag(SliceId);
|
||||||
if (O = nil) or not FController.GetSlice(SliceId, S) then Exit;
|
if (O = nil) or not FController.GetSlice(SliceId, S) then Exit;
|
||||||
case S.Mode of
|
case S.Mode of
|
||||||
MODE_LSB: BW := -S.FilterLo;
|
MODE_LSB, MODE_DIGL: BW := -S.FilterLo;
|
||||||
MODE_USB: BW := S.FilterHi;
|
MODE_USB, MODE_DIGU: BW := S.FilterHi;
|
||||||
else BW := S.FilterHi - S.FilterLo;
|
else BW := S.FilterHi - S.FilterLo;
|
||||||
end;
|
end;
|
||||||
O.SetState(S.Mode, BW, S.TargetHz, FController.SliceSMeter(SliceId));
|
O.SetState(S.Mode, BW, S.TargetHz, FController.SliceSMeter(SliceId));
|
||||||
O.SetDSPState(0, 0, False, False, FController.FAGCMode);
|
O.SetDSPState(0, 0, False, False, FController.FAGCMode);
|
||||||
|
|||||||
+52
-23
@@ -1692,9 +1692,13 @@ var Half: Integer;
|
|||||||
begin
|
begin
|
||||||
Half := BW div 2;
|
Half := BW div 2;
|
||||||
case Mode of
|
case Mode of
|
||||||
0: begin Lo := -BW; Hi := -100; end; // LSB
|
MODE_LSB: begin Lo := -BW; Hi := -100; end;
|
||||||
1: begin Lo := 100; Hi := BW; end; // USB
|
MODE_USB: begin Lo := 100; Hi := BW; end;
|
||||||
else Lo := -Half; Hi := Half; // DSB/CW/FM/AM/SAM
|
// DIGI: полоса от нуля — поднесущие цифровых видов начинаются низко,
|
||||||
|
// срез на 100 Гц как в SSB съел бы их.
|
||||||
|
MODE_DIGL: begin Lo := -BW; Hi := 0; end;
|
||||||
|
MODE_DIGU: begin Lo := 0; Hi := BW; end;
|
||||||
|
else Lo := -Half; Hi := Half; // DSB/CW/FM/AM/SAM/WFM
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -1739,10 +1743,20 @@ begin
|
|||||||
FFilterBW := FILT_FM_BW[FFilter];
|
FFilterBW := FILT_FM_BW[FFilter];
|
||||||
FFMDeviation := FILT_FM_DEV[FFilter];
|
FFMDeviation := FILT_FM_DEV[FFilter];
|
||||||
end;
|
end;
|
||||||
0, 1: begin FFilter := FILT_SSB_DEF; FFilterBW := FILT_SSB_BW[FFilter]; end;
|
MODE_WFM:
|
||||||
2: begin FFilter := FILT_DSB_DEF; FFilterBW := FILT_DSB_BW[FFilter]; end;
|
begin
|
||||||
3, 4: begin FFilter := FILT_CW_DEF; FFilterBW := FILT_CW_BW[FFilter]; end;
|
if FFilter > FILT_WFM_COUNT - 1 then FFilter := FILT_WFM_DEF;
|
||||||
else begin FFilter := FILT_AM_DEF; FFilterBW := FILT_AM_BW[FFilter]; end;
|
FFilterBW := FILT_WFM_BW[FFilter];
|
||||||
|
end;
|
||||||
|
MODE_LSB, MODE_USB:
|
||||||
|
begin FFilter := FILT_SSB_DEF; FFilterBW := FILT_SSB_BW[FFilter]; end;
|
||||||
|
MODE_DIGU, MODE_DIGL:
|
||||||
|
begin FFilter := FILT_DIGI_DEF; FFilterBW := FILT_DIGI_BW[FFilter]; end;
|
||||||
|
MODE_DSB:
|
||||||
|
begin FFilter := FILT_DSB_DEF; FFilterBW := FILT_DSB_BW[FFilter]; end;
|
||||||
|
MODE_CWL, MODE_CWU:
|
||||||
|
begin FFilter := FILT_CW_DEF; FFilterBW := FILT_CW_BW[FFilter]; end;
|
||||||
|
else begin FFilter := FILT_AM_DEF; FFilterBW := FILT_AM_BW[FFilter]; end;
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -1751,11 +1765,16 @@ function TRadioController.FilterBWFor(M, Idx: Integer): Integer;
|
|||||||
// но НЕ меняет индекс (в отличие от ApplyModeDefaults) — нужно для XVTR-restore.
|
// но НЕ меняет индекс (в отличие от ApplyModeDefaults) — нужно для XVTR-restore.
|
||||||
begin
|
begin
|
||||||
case M of
|
case M of
|
||||||
MODE_FM: Result := FILT_FM_BW[EnsureRange(Idx, 0, 1)];
|
MODE_FM: Result := FILT_FM_BW[EnsureRange(Idx, 0, FILT_FM_COUNT - 1)];
|
||||||
0, 1: Result := FILT_SSB_BW[EnsureRange(Idx, 0, FILT_COUNT - 1)];
|
MODE_WFM: Result := FILT_WFM_BW[EnsureRange(Idx, 0, FILT_WFM_COUNT - 1)];
|
||||||
2: Result := FILT_DSB_BW[EnsureRange(Idx, 0, FILT_COUNT - 1)];
|
MODE_LSB, MODE_USB:
|
||||||
3, 4: Result := FILT_CW_BW[EnsureRange(Idx, 0, FILT_COUNT - 1)];
|
Result := FILT_SSB_BW[EnsureRange(Idx, 0, FILT_COUNT - 1)];
|
||||||
else Result := FILT_AM_BW[EnsureRange(Idx, 0, FILT_COUNT - 1)];
|
MODE_DIGU, MODE_DIGL:
|
||||||
|
Result := FILT_DIGI_BW[EnsureRange(Idx, 0, FILT_COUNT - 1)];
|
||||||
|
MODE_DSB: Result := FILT_DSB_BW[EnsureRange(Idx, 0, FILT_COUNT - 1)];
|
||||||
|
MODE_CWL, MODE_CWU:
|
||||||
|
Result := FILT_CW_BW[EnsureRange(Idx, 0, FILT_COUNT - 1)];
|
||||||
|
else Result := FILT_AM_BW[EnsureRange(Idx, 0, FILT_COUNT - 1)];
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -2382,14 +2401,19 @@ end;
|
|||||||
procedure TRadioController.SetFilterIdx(Idx: Integer);
|
procedure TRadioController.SetFilterIdx(Idx: Integer);
|
||||||
begin
|
begin
|
||||||
// Клампим индекс под текущий режим (как FilterBWFor) — публичная команда,
|
// Клампим индекс под текущий режим (как FilterBWFor) — публичная команда,
|
||||||
// Idx может прийти из CAT/web без проверки; FM-таблицы всего на 2 элемента.
|
// Idx может прийти из CAT/web без проверки; FM/WFM-таблицы короче остальных.
|
||||||
if FMode = MODE_FM then Idx := EnsureRange(Idx, 0, FILT_FM_COUNT - 1)
|
case FMode of
|
||||||
else Idx := EnsureRange(Idx, 0, FILT_COUNT - 1);
|
MODE_FM: Idx := EnsureRange(Idx, 0, FILT_FM_COUNT - 1);
|
||||||
|
MODE_WFM: Idx := EnsureRange(Idx, 0, FILT_WFM_COUNT - 1);
|
||||||
|
else Idx := EnsureRange(Idx, 0, FILT_COUNT - 1);
|
||||||
|
end;
|
||||||
FFilter := Idx;
|
FFilter := Idx;
|
||||||
case FMode of
|
case FMode of
|
||||||
0, 1: FFilterBW := FILT_SSB_BW[Idx];
|
MODE_LSB, MODE_USB: FFilterBW := FILT_SSB_BW[Idx];
|
||||||
2: FFilterBW := FILT_DSB_BW[Idx];
|
MODE_DIGU, MODE_DIGL: FFilterBW := FILT_DIGI_BW[Idx];
|
||||||
3, 4: FFilterBW := FILT_CW_BW[Idx];
|
MODE_DSB: FFilterBW := FILT_DSB_BW[Idx];
|
||||||
|
MODE_CWL, MODE_CWU: FFilterBW := FILT_CW_BW[Idx];
|
||||||
|
MODE_WFM: FFilterBW := FILT_WFM_BW[Idx];
|
||||||
MODE_FM:
|
MODE_FM:
|
||||||
begin
|
begin
|
||||||
FFilterBW := FILT_FM_BW[Idx];
|
FFilterBW := FILT_FM_BW[Idx];
|
||||||
@@ -2412,11 +2436,16 @@ begin
|
|||||||
FFilterBW := BWHz;
|
FFilterBW := BWHz;
|
||||||
for i := 0 to FILT_COUNT-1 do BW_arr[i] := MaxInt;
|
for i := 0 to FILT_COUNT-1 do BW_arr[i] := MaxInt;
|
||||||
case FMode of
|
case FMode of
|
||||||
0, 1: for i := 0 to FILT_COUNT-1 do BW_arr[i] := FILT_SSB_BW[i];
|
MODE_LSB, MODE_USB:
|
||||||
2: for i := 0 to FILT_COUNT-1 do BW_arr[i] := FILT_DSB_BW[i];
|
for i := 0 to FILT_COUNT-1 do BW_arr[i] := FILT_SSB_BW[i];
|
||||||
3, 4: for i := 0 to FILT_COUNT-1 do BW_arr[i] := FILT_CW_BW[i];
|
MODE_DIGU, MODE_DIGL:
|
||||||
MODE_FM: for i := 0 to FILT_FM_COUNT-1 do BW_arr[i] := FILT_FM_BW[i];
|
for i := 0 to FILT_COUNT-1 do BW_arr[i] := FILT_DIGI_BW[i];
|
||||||
else for i := 0 to FILT_COUNT-1 do BW_arr[i] := FILT_AM_BW[i];
|
MODE_DSB: for i := 0 to FILT_COUNT-1 do BW_arr[i] := FILT_DSB_BW[i];
|
||||||
|
MODE_CWL, MODE_CWU:
|
||||||
|
for i := 0 to FILT_COUNT-1 do BW_arr[i] := FILT_CW_BW[i];
|
||||||
|
MODE_FM: for i := 0 to FILT_FM_COUNT-1 do BW_arr[i] := FILT_FM_BW[i];
|
||||||
|
MODE_WFM: for i := 0 to FILT_WFM_COUNT-1 do BW_arr[i] := FILT_WFM_BW[i];
|
||||||
|
else for i := 0 to FILT_COUNT-1 do BW_arr[i] := FILT_AM_BW[i];
|
||||||
end;
|
end;
|
||||||
best := 0; dist := MaxInt;
|
best := 0; dist := MaxInt;
|
||||||
for i := 0 to FILT_COUNT-1 do
|
for i := 0 to FILT_COUNT-1 do
|
||||||
|
|||||||
@@ -42,10 +42,20 @@ const
|
|||||||
FILT_AM_BW: array[0..FILT_COUNT-1] of Integer =
|
FILT_AM_BW: array[0..FILT_COUNT-1] of Integer =
|
||||||
(20000, 18000, 16000, 12000, 10000, 9000, 8000, 7000, 6000, 5000);
|
(20000, 18000, 16000, 12000, 10000, 9000, 8000, 7000, 6000, 5000);
|
||||||
FILT_AM_DEF = 4; // 10k
|
FILT_AM_DEF = 4; // 10k
|
||||||
|
// DIGI: полоса считается от нуля (кромки 0..BW), поэтому таблица своя —
|
||||||
|
// SSB-полосы отмерены от среза на 100 Гц и здесь не подходят.
|
||||||
|
FILT_DIGI_BW: array[0..FILT_COUNT-1] of Integer =
|
||||||
|
(3000, 2700, 2400, 2100, 1800, 1500, 1200, 900, 600, 300);
|
||||||
|
FILT_DIGI_DEF = 0; // 3.0k
|
||||||
FILT_FM_COUNT = 2;
|
FILT_FM_COUNT = 2;
|
||||||
FILT_FM_BW: array[0..1] of Integer = (11000, 16000);
|
FILT_FM_BW: array[0..1] of Integer = (11000, 16000);
|
||||||
FILT_FM_DEV: array[0..1] of Double = (2500.0, 5000.0);
|
FILT_FM_DEV: array[0..1] of Double = (2500.0, 5000.0);
|
||||||
FILT_FM_DEF = 0; // NFM
|
FILT_FM_DEF = 0; // NFM
|
||||||
|
// WFM: вещательный канал. 200 кГц требует SampleRate 384k — при 192k
|
||||||
|
// WDSPEngine.SetFilter сам зажмёт кромки по Найквисту dsp_rate.
|
||||||
|
FILT_WFM_COUNT = 3;
|
||||||
|
FILT_WFM_BW: array[0..2] of Integer = (200000, 160000, 120000);
|
||||||
|
FILT_WFM_DEF = 1; // 160k — влезает и в 192k
|
||||||
|
|
||||||
type
|
type
|
||||||
TBandSettings = record
|
TBandSettings = record
|
||||||
|
|||||||
+4
-2
@@ -1088,8 +1088,10 @@ begin
|
|||||||
Half := BW / 2;
|
Half := BW / 2;
|
||||||
VfoX := Round((VfoFreq - FCenterFreq + FSpanHz / 2) / FSpanHz * W);
|
VfoX := Round((VfoFreq - FCenterFreq + FSpanHz / 2) / FSpanHz * W);
|
||||||
case Mode of
|
case Mode of
|
||||||
0: begin Lo_Hz := -BW; Hi_Hz := -100; end;
|
0: begin Lo_Hz := -BW; Hi_Hz := -100; end; // LSB
|
||||||
1: begin Lo_Hz := 100; Hi_Hz := BW; end;
|
1: begin Lo_Hz := 100; Hi_Hz := BW; end; // USB
|
||||||
|
9: begin Lo_Hz := -BW; Hi_Hz := 0; end; // DIGL — полоса от нуля
|
||||||
|
8: begin Lo_Hz := 0; Hi_Hz := BW; end; // DIGU
|
||||||
else begin Lo_Hz := -Half; Hi_Hz := Half; end;
|
else begin Lo_Hz := -Half; Hi_Hz := Half; end;
|
||||||
end;
|
end;
|
||||||
X1 := Max(0, Min(W - 1, VfoX + Round(Lo_Hz / FSpanHz * W)));
|
X1 := Max(0, Min(W - 1, VfoX + Round(Lo_Hz / FSpanHz * W)));
|
||||||
|
|||||||
+54
-33
@@ -23,6 +23,13 @@ const
|
|||||||
VFO_OVERLAY_ALPHA = 210;
|
VFO_OVERLAY_ALPHA = 210;
|
||||||
OVL_W = 280; // ширина карточки
|
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): одна на букву — общий цвет для
|
// Палитра слайсов (TColor = $00BBGGRR): одна на букву — общий цвет для
|
||||||
// бейджа-карточки во флаге и для буквы на полосе фильтра в спектре.
|
// бейджа-карточки во флаге и для буквы на полосе фильтра в спектре.
|
||||||
SLICE_COLORS: array[0..7] of TColor = (
|
SLICE_COLORS: array[0..7] of TColor = (
|
||||||
@@ -54,7 +61,7 @@ type
|
|||||||
FFilterBW: Integer;
|
FFilterBW: Integer;
|
||||||
FVfoHz: Double;
|
FVfoHz: Double;
|
||||||
FSMeterDB: Double;
|
FSMeterDB: Double;
|
||||||
FModeFilter: array[0..7] of Integer;
|
FModeFilter: array[0..OVL_MODE_COUNT-1] of Integer;
|
||||||
|
|
||||||
FNRMode: Integer; // 0=off, 1..4
|
FNRMode: Integer; // 0=off, 1..4
|
||||||
FNBMode: Integer; // 0=off, 1=NB, 2=NB2
|
FNBMode: Integer; // 0=off, 1=NB, 2=NB2
|
||||||
@@ -220,8 +227,9 @@ const
|
|||||||
KEY_COLOR = TColor($00FF00FF);
|
KEY_COLOR = TColor($00FF00FF);
|
||||||
|
|
||||||
// Значения хит-тестов
|
// Значения хит-тестов
|
||||||
// режимы: -(I+1) = -1..-8
|
// режимы: HIT_MODE_BASE-I
|
||||||
// фильтры: BW в Гц (>0)
|
// фильтры: HIT_FILT_BASE+I (индекс, не BW: полосы WFM доходят до 200000
|
||||||
|
// и налезали бы на HIT_AGC_PICK_BASE/HIT_DEV_BASE)
|
||||||
// AGC: HIT_AGC_PICK_BASE+I
|
// AGC: HIT_AGC_PICK_BASE+I
|
||||||
// устройства: HIT_DEV_BASE+I
|
// устройства: HIT_DEV_BASE+I
|
||||||
HIT_NR = -9;
|
HIT_NR = -9;
|
||||||
@@ -243,6 +251,9 @@ const
|
|||||||
HIT_SQL_SLIDER = -32; // трек порога FM-squelch
|
HIT_SQL_SLIDER = -32; // трек порога FM-squelch
|
||||||
HIT_AGC_PICK_BASE = 100000;
|
HIT_AGC_PICK_BASE = 100000;
|
||||||
HIT_DEV_BASE = 200000;
|
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');
|
AGC_NAMES: array[0..4] of string = ('FAST', 'MED', 'SLOW', 'LONG', 'OFF');
|
||||||
|
|
||||||
@@ -268,10 +279,6 @@ const
|
|||||||
CLR_SPLIT_ON = TColor($0020C0FF); // оранжевый SPLIT
|
CLR_SPLIT_ON = TColor($0020C0FF); // оранжевый SPLIT
|
||||||
CLR_BADGE_TXT = TColor($00181818); // тёмный текст поверх ярких бейджей
|
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_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');
|
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);
|
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');
|
FM_LBL: array[0..1] of string = ('NFM', 'FM');
|
||||||
AM_BW: array[0..5] of Integer = (20000,16000,12000,10000,8000,6000);
|
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');
|
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);
|
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;
|
function TVfoOverlay.GetFilterBWForMode(ModeIdx, FilterIdx: Integer): Integer;
|
||||||
begin
|
begin
|
||||||
case ModeIdx of
|
case ModeIdx of
|
||||||
0,1: Result := SSB_BW[FilterIdx];
|
0,1: Result := SSB_BW[FilterIdx];
|
||||||
2: Result := DSB_BW[FilterIdx];
|
2: Result := DSB_BW[FilterIdx];
|
||||||
3,4: Result := CW_BW[FilterIdx];
|
3,4: Result := CW_BW[FilterIdx];
|
||||||
5: Result := FM_BW[FilterIdx];
|
5: Result := FM_BW[FilterIdx];
|
||||||
else Result := AM_BW[FilterIdx];
|
8,9: Result := DIGI_BW[FilterIdx]; // DIGU/DIGL — полоса от нуля
|
||||||
|
10: Result := WFM_BW[FilterIdx];
|
||||||
|
else Result := AM_BW[FilterIdx];
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TVfoOverlay.GetFilterLblForMode(ModeIdx, FilterIdx: Integer): string;
|
function TVfoOverlay.GetFilterLblForMode(ModeIdx, FilterIdx: Integer): string;
|
||||||
begin
|
begin
|
||||||
case ModeIdx of
|
case ModeIdx of
|
||||||
0,1: Result := SSB_LBL[FilterIdx];
|
0,1: Result := SSB_LBL[FilterIdx];
|
||||||
2: Result := DSB_LBL[FilterIdx];
|
2: Result := DSB_LBL[FilterIdx];
|
||||||
3,4: Result := CW_LBL[FilterIdx];
|
3,4: Result := CW_LBL[FilterIdx];
|
||||||
5: Result := FM_LBL[FilterIdx];
|
5: Result := FM_LBL[FilterIdx];
|
||||||
else Result := AM_LBL[FilterIdx];
|
8,9: Result := DIGI_LBL[FilterIdx];
|
||||||
|
10: Result := WFM_LBL[FilterIdx];
|
||||||
|
else Result := AM_LBL[FilterIdx];
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
function TVfoOverlay.GetFilterCountForMode(ModeIdx: Integer): Integer;
|
function TVfoOverlay.GetFilterCountForMode(ModeIdx: Integer): Integer;
|
||||||
begin
|
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;
|
end;
|
||||||
|
|
||||||
{ ---- Lifecycle ---- }
|
{ ---- Lifecycle ---- }
|
||||||
@@ -413,7 +432,8 @@ begin
|
|||||||
FHotIdx := -1;
|
FHotIdx := -1;
|
||||||
FModeFilter[0] := 2700; FModeFilter[1] := 2700; FModeFilter[2] := 8000;
|
FModeFilter[0] := 2700; FModeFilter[1] := 2700; FModeFilter[2] := 8000;
|
||||||
FModeFilter[3] := 500; FModeFilter[4] := 500; FModeFilter[5] := 11000;
|
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;
|
FNRMode := 0; FNBMode := 0; FSNBOn := False; FANFOn := False; FAGCMode := 0;
|
||||||
FSQLOn := False; FSQLLevel := 40; FSqlDrag := False;
|
FSQLOn := False; FSQLLevel := 40; FSqlDrag := False;
|
||||||
FVolume := 70; FMuted := False; FSplit := False; FTx := False;
|
FVolume := 70; FMuted := False; FSplit := False; FTx := False;
|
||||||
@@ -554,7 +574,7 @@ end;
|
|||||||
function TVfoOverlay.PanelContentH: Integer;
|
function TVfoOverlay.PanelContentH: Integer;
|
||||||
begin
|
begin
|
||||||
case FPanel of
|
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
|
fpDsp: begin
|
||||||
Result := ROW_H + IGAP + ROW_H; // toggles + AGC
|
Result := ROW_H + IGAP + ROW_H; // toggles + AGC
|
||||||
if IsFM then Inc(Result, IGAP + ROW_H); // + ряд SQL (только FM)
|
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;
|
I, Col, Row, BX, BY, BW, FiltN, FBX, FBRight, BV: Integer;
|
||||||
HR: TRect;
|
HR: TRect;
|
||||||
begin
|
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
|
for I := 0 to OVL_MODE_COUNT - 1 do
|
||||||
begin
|
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);
|
BX := X + Col * (BW + 3);
|
||||||
BY := Y + Row * (ROW_H + IGAP);
|
BY := Y + Row * (ROW_H + IGAP);
|
||||||
HR := Rect(BX, BY, BX + BW, BY + ROW_H);
|
HR := Rect(BX, BY, BX + BW, BY + ROW_H);
|
||||||
DrawButton(C, HR, OVL_MODE_NAMES[I], I = FMode,
|
DrawButton(C, HR, OVL_MODE_NAMES[I], I = FMode,
|
||||||
(FHotIdx >= 0) and (FHitActions[FHotIdx] = -(I+1)), 7);
|
(FHotIdx >= 0) and (FHitActions[FHotIdx] = HIT_MODE_BASE - I), 7);
|
||||||
RegisterHit(HR, -(I+1));
|
RegisterHit(HR, HIT_MODE_BASE - I);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// Ряд фильтров текущего режима
|
// Ряд фильтров текущего режима
|
||||||
FiltN := GetFilterCountForMode(FMode);
|
FiltN := GetFilterCountForMode(FMode);
|
||||||
BY := Y + 2*(ROW_H + IGAP);
|
BY := Y + OVL_MODE_ROWS*(ROW_H + IGAP);
|
||||||
FBX := X;
|
FBX := X;
|
||||||
for I := 0 to FiltN - 1 do
|
for I := 0 to FiltN - 1 do
|
||||||
begin
|
begin
|
||||||
@@ -770,8 +790,8 @@ begin
|
|||||||
BV := GetFilterBWForMode(FMode, I);
|
BV := GetFilterBWForMode(FMode, I);
|
||||||
HR := Rect(FBX, BY, FBRight - 2, BY + ROW_H);
|
HR := Rect(FBX, BY, FBRight - 2, BY + ROW_H);
|
||||||
DrawButton(C, HR, GetFilterLblForMode(FMode, I), BV = FFilterBW,
|
DrawButton(C, HR, GetFilterLblForMode(FMode, I), BV = FFilterBW,
|
||||||
(FHotIdx >= 0) and (FHitActions[FHotIdx] = BV), 7);
|
(FHotIdx >= 0) and (FHitActions[FHotIdx] = HIT_FILT_BASE + I), 7);
|
||||||
RegisterHit(HR, BV);
|
RegisterHit(HR, HIT_FILT_BASE + I);
|
||||||
FBX := FBRight;
|
FBX := FBRight;
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
@@ -1184,25 +1204,26 @@ begin
|
|||||||
end;
|
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
|
begin
|
||||||
NewMode := (-HV) - 1;
|
NewMode := HIT_MODE_BASE - HV;
|
||||||
if NewMode <> FMode then
|
if NewMode <> FMode then
|
||||||
begin
|
begin
|
||||||
FModeFilter[FMode] := FFilterBW;
|
FModeFilter[FMode] := FFilterBW;
|
||||||
FMode := NewMode;
|
FMode := NewMode;
|
||||||
FFilterBW := FModeFilter[FMode];
|
FFilterBW := FModeFilter[FMode];
|
||||||
Height := ComputeHeight; // число фильтров могло смениться (FM=2)
|
Height := ComputeHeight; // число фильтров могло смениться (FM=2, WFM=3)
|
||||||
RequestInvalidate;
|
RequestInvalidate;
|
||||||
if Assigned(FOnSelect) then FOnSelect(FMode, FFilterBW);
|
if Assigned(FOnSelect) then FOnSelect(FMode, FFilterBW);
|
||||||
end;
|
end;
|
||||||
Exit;
|
Exit;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// Фильтр (BW>0)
|
// Фильтр текущего режима (по индексу кнопки, не по BW)
|
||||||
if HV > 0 then
|
if (HV >= HIT_FILT_BASE) and
|
||||||
|
(HV < HIT_FILT_BASE + GetFilterCountForMode(FMode)) then
|
||||||
begin
|
begin
|
||||||
NewBW := HV;
|
NewBW := GetFilterBWForMode(FMode, HV - HIT_FILT_BASE);
|
||||||
if NewBW <> FFilterBW then
|
if NewBW <> FFilterBW then
|
||||||
begin
|
begin
|
||||||
FFilterBW := NewBW;
|
FFilterBW := NewBW;
|
||||||
|
|||||||
@@ -505,6 +505,28 @@ procedure SetTXAFMNC(channel: Integer; nc: Integer); cdecl; external WDSP_LIB;
|
|||||||
procedure SetTXAFMMP(channel: Integer; mp: Integer); cdecl; external WDSP_LIB;
|
procedure SetTXAFMMP(channel: Integer; mp: Integer); cdecl; external WDSP_LIB;
|
||||||
procedure SetTXAFMAFFreqs(channel: Integer; low: Double; high: Double); cdecl; external WDSP_LIB;
|
procedure SetTXAFMAFFreqs(channel: Integer; low: Double; high: Double); cdecl; external WDSP_LIB;
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// wfmd.c (Wideband FM Demodulator)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
procedure SetRXAWFMDeviation(channel: Integer; deviation: Double); cdecl; external WDSP_LIB;
|
||||||
|
procedure SetRXAWFMNCaud(channel: Integer; nc: Integer); cdecl; external WDSP_LIB;
|
||||||
|
procedure SetRXAWFMMPaud(channel: Integer; mp: Integer); cdecl; external WDSP_LIB;
|
||||||
|
procedure SetRXAWFMAFFilter(channel: Integer; low: Double; high: Double); cdecl; external WDSP_LIB;
|
||||||
|
procedure SetRXAWFMDeemphRun(channel: Integer; run: Integer); cdecl; external WDSP_LIB;
|
||||||
|
procedure SetRXAWFMDeemphTau(channel: Integer; tau: Double); cdecl; external WDSP_LIB;
|
||||||
|
procedure SetRXAWFMLimRun(channel: Integer; run: Integer); cdecl; external WDSP_LIB;
|
||||||
|
procedure SetRXAWFMLimGain(channel: Integer; gaindB: Double); cdecl; external WDSP_LIB;
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// wfmmod.c (Wideband FM Modulator)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
procedure SetTXAWFMDeviation(channel: Integer; deviation: Double); cdecl; external WDSP_LIB;
|
||||||
|
procedure SetTXAWFMNC(channel: Integer; nc: Integer); cdecl; external WDSP_LIB;
|
||||||
|
procedure SetTXAWFMMP(channel: Integer; mp: Integer); cdecl; external WDSP_LIB;
|
||||||
|
procedure SetTXAWFMAFFreqs(channel: Integer; low: Double; high: Double); cdecl; external WDSP_LIB;
|
||||||
|
procedure SetTXAWFMPreEmphRun(channel: Integer; run: Integer); cdecl; external WDSP_LIB;
|
||||||
|
procedure SetTXAWFMPreEmphTau(channel: Integer; tau: Double); cdecl; external WDSP_LIB;
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// fmsq.c (FM Squelch)
|
// fmsq.c (FM Squelch)
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|||||||
+268
-48
@@ -29,25 +29,55 @@ uses
|
|||||||
WDSP;
|
WDSP;
|
||||||
|
|
||||||
const
|
const
|
||||||
// WDSP mode integers (нет именованных констант в WDSP.pas)
|
// WDSP mode integers (нет именованных констант в WDSP.pas).
|
||||||
WDSP_LSB = 0;
|
// Значения — enum rxaMode (RXA.h) / txaMode (TXA.h). До DRM включительно
|
||||||
WDSP_USB = 1;
|
// нумерация совпадает, дальше расходится: TXA вставляет AM_LSB/AM_USB,
|
||||||
WDSP_DSB = 2;
|
// поэтому WFM у RXA = 12, а у TXA = 14. Отсюда две отдельные константы.
|
||||||
WDSP_CWL = 3;
|
WDSP_LSB = 0;
|
||||||
WDSP_CWU = 4;
|
WDSP_USB = 1;
|
||||||
WDSP_FM = 5;
|
WDSP_DSB = 2;
|
||||||
WDSP_AM = 6;
|
WDSP_CWL = 3;
|
||||||
WDSP_SAM = 12;
|
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)
|
// Наши индексы режимов (совпадают с кнопками MainForm).
|
||||||
MODE_LSB = 0;
|
// Новые режимы дописываются В КОНЕЦ: индекс уходит в settings.json,
|
||||||
MODE_USB = 1;
|
// band-cache, CAT (ZZMD/ZZMV) и web-API.
|
||||||
MODE_DSB = 2;
|
MODE_LSB = 0;
|
||||||
MODE_CWL = 3;
|
MODE_USB = 1;
|
||||||
MODE_CWU = 4;
|
MODE_DSB = 2;
|
||||||
MODE_FM = 5;
|
MODE_CWL = 3;
|
||||||
MODE_AM = 6;
|
MODE_CWU = 4;
|
||||||
MODE_SAM = 7;
|
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 modes
|
||||||
AGC_OFF = 0;
|
AGC_OFF = 0;
|
||||||
@@ -182,6 +212,10 @@ type
|
|||||||
FAnalyzerOpen: Boolean;
|
FAnalyzerOpen: Boolean;
|
||||||
FSampleRate: Integer;
|
FSampleRate: Integer;
|
||||||
FAudioRate: Integer;
|
FAudioRate: Integer;
|
||||||
|
// Текущий dsp_rate канала RXA. Обычно = FAudioRate; в WFM поднимается
|
||||||
|
// до FSampleRate, иначе девиация 75 кГц не помещается в полосу.
|
||||||
|
FRXDSPRate: Integer;
|
||||||
|
FTXDSPSize: Integer; // текущий dsp_size канала TXA (нижняя граница nc)
|
||||||
FBufSize: Integer; // входной буфер @ FSampleRate
|
FBufSize: Integer; // входной буфер @ FSampleRate
|
||||||
FAudioBufSize: Integer; // выходной буфер @ FAudioRate (= FBufSize * FAudioRate / FSampleRate)
|
FAudioBufSize: Integer; // выходной буфер @ FAudioRate (= FBufSize * FAudioRate / FSampleRate)
|
||||||
FTXSampleRate: Integer; // фиксированный rate TX/DUC (TX_SAMPLE_RATE), не меняется при ChangeSampleRate
|
FTXSampleRate: Integer; // фиксированный rate TX/DUC (TX_SAMPLE_RATE), не меняется при ChangeSampleRate
|
||||||
@@ -356,7 +390,17 @@ type
|
|||||||
FBeaconDec: TBeaconDecoder; // не владеет; тап маяка в PushIQItemToDSP
|
FBeaconDec: TBeaconDecoder; // не владеет; тап маяка в PushIQItemToDSP
|
||||||
|
|
||||||
function ModeToWDSP(Mode: Integer): Integer;
|
function ModeToWDSP(Mode: Integer): Integer;
|
||||||
|
function ModeToWDSPTX(Mode: Integer): Integer;
|
||||||
procedure ApplyDefaultFilter;
|
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 если нет
|
function FindSliceIdx(Id: Integer): Integer; // -1 если нет
|
||||||
procedure OpenSliceChannel(Idx: Integer); // OpenChannel + настройки
|
procedure OpenSliceChannel(Idx: Integer); // OpenChannel + настройки
|
||||||
@@ -770,29 +814,156 @@ end;
|
|||||||
function TWDSPEngine.ModeToWDSP(Mode: Integer): Integer;
|
function TWDSPEngine.ModeToWDSP(Mode: Integer): Integer;
|
||||||
begin
|
begin
|
||||||
case Mode of
|
case Mode of
|
||||||
MODE_LSB: Result := WDSP_LSB;
|
MODE_LSB: Result := WDSP_LSB;
|
||||||
MODE_USB: Result := WDSP_USB;
|
MODE_USB: Result := WDSP_USB;
|
||||||
MODE_DSB: Result := WDSP_DSB;
|
MODE_DSB: Result := WDSP_DSB;
|
||||||
MODE_CWL: Result := WDSP_CWL;
|
MODE_CWL: Result := WDSP_CWL;
|
||||||
MODE_CWU: Result := WDSP_CWU;
|
MODE_CWU: Result := WDSP_CWU;
|
||||||
MODE_FM: Result := WDSP_FM;
|
MODE_FM: Result := WDSP_FM;
|
||||||
MODE_AM: Result := WDSP_AM;
|
MODE_AM: Result := WDSP_AM;
|
||||||
MODE_SAM: Result := WDSP_SAM;
|
MODE_SAM: Result := WDSP_SAM;
|
||||||
else Result := WDSP_USB;
|
MODE_DIGU: Result := WDSP_DIGU;
|
||||||
|
MODE_DIGL: Result := WDSP_DIGL;
|
||||||
|
MODE_WFM: Result := WDSP_RX_WFM;
|
||||||
|
else Result := WDSP_USB;
|
||||||
end;
|
end;
|
||||||
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;
|
procedure TWDSPEngine.ApplyDefaultFilter;
|
||||||
begin
|
begin
|
||||||
case FMode of
|
case FMode of
|
||||||
MODE_LSB: SetFilter(-2400, -100);
|
MODE_LSB: SetFilter(-2400, -100);
|
||||||
MODE_USB: SetFilter( 100, 2400);
|
MODE_USB: SetFilter( 100, 2400);
|
||||||
MODE_DSB: SetFilter(-2400, 2400);
|
MODE_DSB: SetFilter(-2400, 2400);
|
||||||
MODE_CWL: SetFilter( -800, -200);
|
MODE_CWL: SetFilter( -800, -200);
|
||||||
MODE_CWU: SetFilter( 200, 800);
|
MODE_CWU: SetFilter( 200, 800);
|
||||||
MODE_FM: SetFilter(-5000, 5000);
|
MODE_FM: SetFilter(-5000, 5000);
|
||||||
MODE_AM: SetFilter(-4000, 4000);
|
MODE_AM: SetFilter(-4000, 4000);
|
||||||
MODE_SAM: 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;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -813,6 +984,8 @@ begin
|
|||||||
// TX rate фиксирован — не зависит от RX. FTXOutBufSize = out_size TXA @ FTXSampleRate.
|
// TX rate фиксирован — не зависит от RX. FTXOutBufSize = out_size TXA @ FTXSampleRate.
|
||||||
FTXSampleRate := TX_SAMPLE_RATE;
|
FTXSampleRate := TX_SAMPLE_RATE;
|
||||||
FTXOutBufSize := BufSize * FTXSampleRate div AudioRate; // = 4096 @ 48k audio, 192k tx
|
FTXOutBufSize := BufSize * FTXSampleRate div AudioRate; // = 4096 @ 48k audio, 192k tx
|
||||||
|
FRXDSPRate := AudioRate; // «узкий» режим; поднимается только в WFM
|
||||||
|
FTXDSPSize := BufSize; // dsp_size TXA в «узком» режиме
|
||||||
FInitialized := False;
|
FInitialized := False;
|
||||||
FAnalyzerOpen := False;
|
FAnalyzerOpen := False;
|
||||||
FMode := MODE_USB;
|
FMode := MODE_USB;
|
||||||
@@ -1157,6 +1330,11 @@ begin
|
|||||||
0
|
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_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);
|
create_nobEXT(RXA_CHAN, 1, 0, FBufSize, FSampleRate, 0.0001, 0.0001, 0.0001, 0.05, 20);
|
||||||
|
|
||||||
@@ -1189,12 +1367,21 @@ begin
|
|||||||
// изредка фазовый разрыв PostGen-тона (всплеск на водопаде + щелчок).
|
// изредка фазовый разрыв PostGen-тона (всплеск на водопаде + щелчок).
|
||||||
);
|
);
|
||||||
|
|
||||||
SetTXAMode(TXA_CHAN, ModeToWDSP(FMode));
|
SetTXAMode(TXA_CHAN, ModeToWDSPTX(FMode));
|
||||||
SetTXAPanelRun(TXA_CHAN, 1);
|
SetTXAPanelRun(TXA_CHAN, 1);
|
||||||
// PatchPanel: 1=Q, 2=I. Mic подаётся в I (FTXIn[i*2]), Q=0 — выбираем I.
|
// PatchPanel: 1=Q, 2=I. Mic подаётся в I (FTXIn[i*2]), Q=0 — выбираем I.
|
||||||
SetTXAPanelSelect(TXA_CHAN, 2);
|
SetTXAPanelSelect(TXA_CHAN, 2);
|
||||||
|
|
||||||
FInitialized := True;
|
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,
|
// Прокидываем все TX-параметры из FTX*-полей: фильтр, mic gain, EQ,
|
||||||
// Leveler, ALC, Compressor, Phase Rotator, AM Carrier, FM, CTCSS.
|
// Leveler, ALC, Compressor, Phase Rotator, AM Carrier, FM, CTCSS.
|
||||||
ApplyTXChainSettings;
|
ApplyTXChainSettings;
|
||||||
@@ -1547,21 +1734,27 @@ end;
|
|||||||
// Открывает WDSP-канал слайса с той же геометрией, что RXA_CHAN. Вызывать
|
// Открывает WDSP-канал слайса с той же геометрией, что RXA_CHAN. Вызывать
|
||||||
// под FSliceLock и только при FInitialized.
|
// под FSliceLock и только при FInitialized.
|
||||||
procedure TWDSPEngine.OpenSliceChannel(Idx: Integer);
|
procedure TWDSPEngine.OpenSliceChannel(Idx: Integer);
|
||||||
var Chan: Integer;
|
var Chan, DSPRate, DSPSize: Integer;
|
||||||
begin
|
begin
|
||||||
if not FInitialized then Exit;
|
if not FInitialized then Exit;
|
||||||
if FSlices[Idx].Opened then Exit;
|
if FSlices[Idx].Opened then Exit;
|
||||||
Chan := FSlices[Idx].Chan;
|
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);
|
0, 0, 0.010, 0.010, 0.010, 0.010, 0);
|
||||||
// NB (ANB/NOB) требует создания EXT-блоков на канале, как для RXA_CHAN в Open.
|
// NB (ANB/NOB) требует создания EXT-блоков на канале, как для RXA_CHAN в Open.
|
||||||
create_anbEXT(Chan, 1, FBufSize, FSampleRate, 0.0001, 0.0001, 0.0001, 0.05, 20);
|
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);
|
create_nobEXT(Chan, 1, 0, FBufSize, FSampleRate, 0.0001, 0.0001, 0.0001, 0.05, 20);
|
||||||
SetRXABandpassWindow(Chan, 1);
|
SetRXABandpassWindow(Chan, 1);
|
||||||
SetRXAMode(Chan, ModeToWDSP(FSlices[Idx].Mode));
|
SetRXAMode(Chan, ModeToWDSP(FSlices[Idx].Mode));
|
||||||
|
if FSlices[Idx].Mode = MODE_WFM then ApplyRXWFMToChan(Chan);
|
||||||
SetRXAShiftRun(Chan, 1);
|
SetRXAShiftRun(Chan, 1);
|
||||||
SetRXAShiftFreq(Chan, FSlices[Idx].ShiftHz);
|
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);
|
ApplyAGCToChan(Chan, FSlices[Idx].AGCMode, 50.0);
|
||||||
// NR/NB/SNB/ANF из сохранённого состояния слайса.
|
// NR/NB/SNB/ANF из сохранённого состояния слайса.
|
||||||
ApplyNRToChan(Chan, FSlices[Idx].NRMode);
|
ApplyNRToChan(Chan, FSlices[Idx].NRMode);
|
||||||
@@ -1686,27 +1879,45 @@ begin
|
|||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TWDSPEngine.SetSliceMode(Id, Mode: Integer);
|
procedure TWDSPEngine.SetSliceMode(Id, Mode: Integer);
|
||||||
var idx: Integer;
|
var idx: Integer; Rerate: Boolean;
|
||||||
begin
|
begin
|
||||||
FSliceLock.Enter;
|
FSliceLock.Enter;
|
||||||
try
|
try
|
||||||
idx := FindSliceIdx(Id); if idx < 0 then Exit;
|
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;
|
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
|
finally
|
||||||
FSliceLock.Leave;
|
FSliceLock.Leave;
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TWDSPEngine.SetSliceFilter(Id, Low, High: Integer);
|
procedure TWDSPEngine.SetSliceFilter(Id, Low, High: Integer);
|
||||||
var idx: Integer;
|
var idx, Rate: Integer;
|
||||||
begin
|
begin
|
||||||
FSliceLock.Enter;
|
FSliceLock.Enter;
|
||||||
try
|
try
|
||||||
idx := FindSliceIdx(Id); if idx < 0 then Exit;
|
idx := FindSliceIdx(Id); if idx < 0 then Exit;
|
||||||
FSlices[idx].FilterLo := Low;
|
FSlices[idx].FilterLo := Low;
|
||||||
FSlices[idx].FilterHi := High;
|
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
|
finally
|
||||||
FSliceLock.Leave;
|
FSliceLock.Leave;
|
||||||
end;
|
end;
|
||||||
@@ -1865,8 +2076,12 @@ procedure TWDSPEngine.SetMode(Mode: Integer);
|
|||||||
begin
|
begin
|
||||||
FMode := Mode;
|
FMode := Mode;
|
||||||
if not FInitialized then Exit;
|
if not FInitialized then Exit;
|
||||||
|
// Rate — до режима: setDSPSamplerate_rxa перестраивает всю цепь (в т.ч. wfmd),
|
||||||
|
// а SetRXAMode затем пересчитает bpsnba под уже актуальный dsp_rate.
|
||||||
|
ApplyDSPRate(Mode);
|
||||||
SetRXAMode(RXA_CHAN, ModeToWDSP(Mode));
|
SetRXAMode(RXA_CHAN, ModeToWDSP(Mode));
|
||||||
SetTXAMode(TXA_CHAN, ModeToWDSP(Mode));
|
SetTXAMode(TXA_CHAN, ModeToWDSPTX(Mode));
|
||||||
|
if Mode = MODE_WFM then ApplyWFMSettings;
|
||||||
// НЕ вызываем ApplyDefaultFilter — фильтр устанавливается явно из UI
|
// НЕ вызываем ApplyDefaultFilter — фильтр устанавливается явно из UI
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -1875,8 +2090,13 @@ begin
|
|||||||
FFilterLow := Low;
|
FFilterLow := Low;
|
||||||
FFilterHigh := High;
|
FFilterHigh := High;
|
||||||
if not FInitialized then Exit;
|
if not FInitialized then Exit;
|
||||||
|
// Кромки не могут выходить за Найквист текущего dsp_rate: в WFM они доходят
|
||||||
|
// до ±100 кГц, а dsp_rate зависит от SampleRate. Клампим только то, что
|
||||||
|
// уходит в WDSP — запрошенные значения храним как есть, иначе понижение
|
||||||
|
// SampleRate необратимо «съело» бы полосу.
|
||||||
// RXASetPassband — правильный unified API, пересчитывает фильтр целиком
|
// RXASetPassband — правильный unified API, пересчитывает фильтр целиком
|
||||||
RXASetPassband(RXA_CHAN, Low, High);
|
RXASetPassband(RXA_CHAN, ClampToNyquist(Low, FRXDSPRate),
|
||||||
|
ClampToNyquist(High, FRXDSPRate));
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TWDSPEngine.SetAGC(Mode: TWDSPAGCMode; FixedGain: Double);
|
procedure TWDSPEngine.SetAGC(Mode: TWDSPAGCMode; FixedGain: Double);
|
||||||
@@ -2135,7 +2355,7 @@ end;
|
|||||||
procedure TWDSPEngine.SetTXMode(Mode: Integer);
|
procedure TWDSPEngine.SetTXMode(Mode: Integer);
|
||||||
begin
|
begin
|
||||||
if not FInitialized then Exit;
|
if not FInitialized then Exit;
|
||||||
SetTXAMode(TXA_CHAN, ModeToWDSP(Mode));
|
SetTXAMode(TXA_CHAN, ModeToWDSPTX(Mode));
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TWDSPEngine.SetTXFilter(Low, High: Integer);
|
procedure TWDSPEngine.SetTXFilter(Low, High: Integer);
|
||||||
@@ -2401,7 +2621,7 @@ begin
|
|||||||
if FTXFilterWindow >= 0 then
|
if FTXFilterWindow >= 0 then
|
||||||
SetTXABandpassWindow(TXA_CHAN, FTXFilterWindow);
|
SetTXABandpassWindow(TXA_CHAN, FTXFilterWindow);
|
||||||
if FTXFilterNC > 0 then
|
if FTXFilterNC > 0 then
|
||||||
TXASetNC(TXA_CHAN, FTXFilterNC);
|
TXASetNC(TXA_CHAN, EffectiveTXNC);
|
||||||
if FTXFilterMP >= 0 then
|
if FTXFilterMP >= 0 then
|
||||||
TXASetMP(TXA_CHAN, FTXFilterMP);
|
TXASetMP(TXA_CHAN, FTXFilterMP);
|
||||||
|
|
||||||
@@ -2463,7 +2683,7 @@ begin
|
|||||||
if not FInitialized then Exit;
|
if not FInitialized then Exit;
|
||||||
SetTXABandpassFreqs(TXA_CHAN, Low, High);
|
SetTXABandpassFreqs(TXA_CHAN, Low, High);
|
||||||
SetTXABandpassWindow(TXA_CHAN, Window);
|
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);
|
if MP >= 0 then TXASetMP(TXA_CHAN, MP);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
|||||||
+11
-6
@@ -193,6 +193,7 @@ begin
|
|||||||
'<select id="modGrpSel">' +
|
'<select id="modGrpSel">' +
|
||||||
'<option value="ssb" selected>SSB</option><option value="cw">CW</option>' +
|
'<option value="ssb" selected>SSB</option><option value="cw">CW</option>' +
|
||||||
'<option value="fm">FM</option><option value="am">AM</option>' +
|
'<option value="fm">FM</option><option value="am">AM</option>' +
|
||||||
|
'<option value="digi">DIGI</option>' +
|
||||||
'</select>' +
|
'</select>' +
|
||||||
'<select id="stepSel">' +
|
'<select id="stepSel">' +
|
||||||
'<option value="10">10 Hz</option><option value="100" selected>100 Hz</option>' +
|
'<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();}' +
|
'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 MODE_N=["LSB","USB","DSB","CWL","CWU","FM","AM","SAM","DIGU","DIGL","WFM"];' +
|
||||||
'const GRP_MAP={ssb:[0,1],cw:[3,4],fm:[5,5],am:[6,7]};' +
|
'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"];' +
|
'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 modGrpFromMode(m){return MODE_TO_GRP[m]||"ssb";}' +
|
||||||
'function modGrp(){return q("modGrpSel").value||"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];}' +
|
'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"],' +
|
'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"],' +
|
'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"],' +
|
'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],' +
|
'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],' +
|
'dsb:[16000,12000,10000,8000,6600,5200,4000,3100,2900,2400],' +
|
||||||
'cw:[1000,800,600,500,400,250,150,100,50,25],' +
|
'cw:[1000,800,600,500,400,250,150,100,50,25],' +
|
||||||
'am:[20000,18000,16000,12000,10000,9000,8000,7000,6000,5000],' +
|
'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;' +
|
'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 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 fnpOutside(e){if(fnpOv&&!fnpOv.contains(e.target)&&e.target!==q("fnpBtn"))closeFnp();}' +
|
||||||
'function showFnp(){' +
|
'function showFnp(){' +
|
||||||
|
|||||||
+3
-3
@@ -1717,8 +1717,8 @@ end;
|
|||||||
|
|
||||||
function TWebServer.BuildStateJson: string;
|
function TWebServer.BuildStateJson: string;
|
||||||
const
|
const
|
||||||
MODE_N: array[0..7] of string =
|
MODE_N: array[0..10] of string =
|
||||||
('LSB','USB','DSB','CWL','CWU','FM','AM','SAM');
|
('LSB','USB','DSB','CWL','CWU','FM','AM','SAM','DIGU','DIGL','WFM');
|
||||||
var
|
var
|
||||||
FS: TFormatSettings;
|
FS: TFormatSettings;
|
||||||
XvtrJson, DevJson, RateJson, BandJson: string;
|
XvtrJson, DevJson, RateJson, BandJson: string;
|
||||||
@@ -1790,7 +1790,7 @@ begin
|
|||||||
'"rf_agc_visible":%s,"rx_gain_mode":%d,"rx_gain_db":%d,' +
|
'"rf_agc_visible":%s,"rx_gain_mode":%d,"rx_gain_db":%d,' +
|
||||||
'"rx_mute_visible":%s,"rx_mute_on":%s}',
|
'"rx_mute_visible":%s,"rx_mute_on":%s}',
|
||||||
[FFreq, FVfoB, FActiveVfo,
|
[FFreq, FVfoB, FActiveVfo,
|
||||||
FMode, MODE_N[FMode mod 8],
|
FMode, MODE_N[FMode mod Length(MODE_N)],
|
||||||
FFilterIdx, FFilterBW,
|
FFilterIdx, FFilterBW,
|
||||||
FAGCMode, FAGCTop,
|
FAGCMode, FAGCTop,
|
||||||
FSpanHz, FCenterHz, FVolume,
|
FSpanHz, FCenterHz, FVolume,
|
||||||
|
|||||||
Reference in New Issue
Block a user