Add WFM (broadcast FM) demodulator support

Wires the new wdsp WFM API into the engine and adds a WFM preset to the
FM filter row (200 kHz BW / 75 kHz deviation). Also fixes WDSP_SAM
constant which was 12 — that slot is now RXA_WFM in the updated enum.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-31 22:40:24 +03:00
co-authored by Claude Opus 4.7
parent 83ec28f997
commit e52e32dc20
3 changed files with 82 additions and 25 deletions
+36 -23
View File
@@ -92,11 +92,17 @@ const
(20000, 18000, 16000, 12000, 10000, 9000, 8000, 7000, 6000, 5000);
FILT_AM_DEF = 4; // 10k
// FM: NFM (2.5 kHz deviation / 11 kHz BW) and FM (5.0 kHz deviation / 16 kHz BW)
FILT_FM_COUNT = 2;
FILT_FM_NAMES: array[0..1] of string = ('NFM', 'FM');
FILT_FM_BW: array[0..1] of Integer = (11000, 16000);
FILT_FM_DEV: array[0..1] of Double = (2500.0, 5000.0);
// FM filter presets:
// NFM — 2.5 kHz deviation / 11 kHz BW (узкополосный FM, любительские диапазоны)
// FM — 5.0 kHz deviation / 16 kHz BW (расширенный NFM)
// WFM — 75 kHz deviation / 200 kHz BW (broadcast FM 88..108 МГц).
// Требует dsp_rate (ширину канала) >= ~120 кГц для моно, >= ~192 кГц
// для стерео+RDS. На малой ширине демодулятор WDSP всё равно
// отработает, но качество пострадает.
FILT_FM_COUNT = 3;
FILT_FM_NAMES: array[0..2] of string = ('NFM', 'FM', 'WFM');
FILT_FM_BW: array[0..2] of Integer = (11000, 16000, 200000);
FILT_FM_DEV: array[0..2] of Double = (2500.0, 5000.0, 75000.0);
FILT_FM_DEF = 0; // NFM
// CTCSS tones (38 standard tones, no None — toggle via CTCSS button)
@@ -4459,24 +4465,21 @@ var
begin
if FMode = MODE_FM then
begin
// FM: show only NFM and WFM buttons, resize them to fill the row
// FM: рисуем ровно FILT_FM_COUNT кнопок (NFM / FM / WFM), растягивая их на всю строку
CloseCTCSSPopup;
if FFilter > 1 then FFilter := FILT_FM_DEF; // sanitize, keep NFM/WFM as-is
if (FFilter < 0) or (FFilter >= FILT_FM_COUNT) then FFilter := FILT_FM_DEF;
FFilterBW := FILT_FM_BW[FFilter];
FFMDeviation := FILT_FM_DEV[FFilter];
BtnFilter[0].Caption := FILT_FM_NAMES[0];
BtnFilter[0].Left := LeftPanelButtonLeft(PanelFilter.Width, 2, 0);
BtnFilter[0].Width := LeftPanelButtonWidth(PanelFilter.Width, 2, 0);
BtnFilter[0].Top := 16;
BtnFilter[0].Visible := True;
StyleButton(BtnFilter[0], FFilter = 0);
BtnFilter[1].Caption := FILT_FM_NAMES[1];
BtnFilter[1].Left := LeftPanelButtonLeft(PanelFilter.Width, 2, 1);
BtnFilter[1].Width := LeftPanelButtonWidth(PanelFilter.Width, 2, 1);
BtnFilter[1].Top := 16;
BtnFilter[1].Visible := True;
StyleButton(BtnFilter[1], FFilter = 1);
for i := 2 to FILT_COUNT - 1 do BtnFilter[i].Visible := False;
for i := 0 to FILT_FM_COUNT - 1 do
begin
BtnFilter[i].Caption := FILT_FM_NAMES[i];
BtnFilter[i].Left := LeftPanelButtonLeft(PanelFilter.Width, FILT_FM_COUNT, i);
BtnFilter[i].Width := LeftPanelButtonWidth(PanelFilter.Width, FILT_FM_COUNT, i);
BtnFilter[i].Top := 16;
BtnFilter[i].Visible := True;
StyleButton(BtnFilter[i], i = FFilter);
end;
for i := FILT_FM_COUNT to FILT_COUNT - 1 do BtnFilter[i].Visible := False;
// Shrink panel to fit label + 1 button row so SQL/CTCSS sit right below
PanelFilter.Height := BtnFilter[0].Top + BtnFilter[0].Height + 4;
if PanelFMSQ <> nil then
@@ -5205,14 +5208,22 @@ begin
end;
if FWDSPReady then
begin
// WFM-флаг ставим ДО SetFilter и до отправки девиации, чтобы движок
// переключил RXA в RXA_WFM и SetRXAWFMDeviation попал на активный демодулятор.
FDSPEngine.SetWFMMode((FMode = MODE_FM) and (FFilter = 2));
FDSPEngine.SetFilter(Lo, Hi);
FDSPEngine.SetTXFilter(Lo, Hi);
if FMode = MODE_FM then
begin
FDSPEngine.SetRXFMDeviation(FFMDeviation);
if FFilter = 2 then
FDSPEngine.SetRXWFMDeviation(FFMDeviation)
else
FDSPEngine.SetRXFMDeviation(FFMDeviation);
FDSPEngine.SetTXFMParams(FFMDeviation, FTXSettings.FMLowCut,
FTXSettings.FMHighCut, FTXSettings.FMEmphPosition);
FDSPEngine.SetFMSquelch(FFMSQOn, FFMSQLevel);
// FM-сквелч узкополосного демодулятора (RXA_FM) на WFM смысла не имеет,
// и WDSP в RXA_WFM просто проигнорирует флаг — отключаем UI-эффектом.
FDSPEngine.SetFMSquelch(FFMSQOn and (FFilter <> 2), FFMSQLevel);
end
else
FDSPEngine.SetFMSquelch(False, 0);
@@ -5266,7 +5277,9 @@ end;
procedure TMainForm.ApplyFMSquelch;
begin
if FWDSPReady then
FDSPEngine.SetFMSquelch(FFMSQOn and (FMode = MODE_FM), FFMSQLevel);
// SQL относится к узкополосному FM-демодулятору; для WFM (FFilter = 2)
// отключаем, иначе кнопка SQL не имела бы эффекта в WDSP.
FDSPEngine.SetFMSquelch(FFMSQOn and (FMode = MODE_FM) and (FFilter <> 2), FFMSQLevel);
end;
procedure TMainForm.BtnFMSQClick(Sender: TObject);