diff --git a/MainForm.pas b/MainForm.pas index bbfb1f1..66b26d6 100644 --- a/MainForm.pas +++ b/MainForm.pas @@ -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); diff --git a/WDSP.pas b/WDSP.pas index bf355cd..84a56ea 100644 --- a/WDSP.pas +++ b/WDSP.pas @@ -495,6 +495,20 @@ procedure SetRXAFMLimRun(channel: Integer; run: Integer); cdecl; external WDSP_L procedure SetRXAFMLimGain(channel: Integer; gaindB: Double); cdecl; external WDSP_LIB; procedure SetRXAFMAFFilter(channel: Integer; low: Double; high: Double); cdecl; external WDSP_LIB; +// --------------------------------------------------------------------------- +// wfm.c / rds.c (Broadcast WFM demodulator with stereo MPX + RDS) +// Requires channel dsp_rate >= ~192 kHz for RDS, >= ~120 kHz for stereo only. +// --------------------------------------------------------------------------- +procedure SetRXAWFMStereo(channel: Integer; stereo_run: Integer); cdecl; external WDSP_LIB; +procedure SetRXAWFMRDS(channel: Integer; rds_run: Integer); cdecl; external WDSP_LIB; +procedure SetRXAWFMDeemphTau(channel: Integer; tau: Double); cdecl; external WDSP_LIB; // 50e-6 EU, 75e-6 US +procedure SetRXAWFMDeviation(channel: Integer; deviation: Double); cdecl; external WDSP_LIB; +function GetRXAWFMStereoLocked(channel: Integer): Integer; cdecl; external WDSP_LIB; +function GetRXAWFMRDSSynced(channel: Integer): Integer; cdecl; external WDSP_LIB; +// offset_type: 0=A, 1=B, 2=C, 3=C', 4=D. errors: 0 if syndrome matched cleanly. +function GetRXAWFMRDSBlock(channel: Integer; data: PWord; offset_type: PByte; + errors: PByte): Integer; cdecl; external WDSP_LIB; + // --------------------------------------------------------------------------- // fmmod.c (FM Modulator) // --------------------------------------------------------------------------- diff --git a/WDSPEngine.pas b/WDSPEngine.pas index 0fc15a9..53d08b0 100644 --- a/WDSPEngine.pas +++ b/WDSPEngine.pas @@ -36,7 +36,8 @@ const WDSP_CWU = 4; WDSP_FM = 5; WDSP_AM = 6; - WDSP_SAM = 12; + WDSP_SAM = 10; + WDSP_WFM = 12; // Наши индексы режимов (совпадают с кнопками MainForm) MODE_LSB = 0; @@ -277,6 +278,8 @@ type FSMeter: Double; FFilterLow: Integer; FFilterHigh: Integer; + FWFMEnabled: Boolean; // переключатель: при MODE_FM использовать RXA_WFM вместо RXA_FM + FWFMDeviation: Double; // девиация для широкополосного WFM (например, 75 кГц) FAGCMode: TWDSPAGCMode; FAGCTop: Double; // gain (dB) = agc_gain в piHPSDR FAGCSlope: Integer; // наклон АРУ в дБ (default 0) @@ -361,6 +364,8 @@ type procedure SetSNB(Enable: Boolean); procedure SetANF(Enable: Boolean); procedure SetRXFMDeviation(Deviation: Double); + procedure SetWFMMode(Enabled: Boolean); + procedure SetRXWFMDeviation(Deviation: Double); procedure SetFMSquelch(Enable: Boolean; Level: Integer); procedure SetVolume(Vol: Double); procedure SetMute(Mute: Boolean); @@ -690,7 +695,7 @@ begin MODE_DSB: Result := WDSP_DSB; MODE_CWL: Result := WDSP_CWL; MODE_CWU: Result := WDSP_CWU; - MODE_FM: Result := WDSP_FM; + MODE_FM: if FWFMEnabled then Result := WDSP_WFM else Result := WDSP_FM; MODE_AM: Result := WDSP_AM; MODE_SAM: Result := WDSP_SAM; else Result := WDSP_USB; @@ -828,6 +833,8 @@ begin FTXEQFreqs[10] := 3500; FTXAMCarrier := 0.5; FTXFMDeviation := 5000.0; + FWFMEnabled := False; + FWFMDeviation := 75000.0; FTXFMLowCut := 300; FTXFMHighCut := 3000; FTXFMEmphPos := 1; @@ -1539,6 +1546,29 @@ begin SetRXAFMDeviation(RXA_CHAN, Deviation); end; +// Переключает RXA-демодулятор между узкополосным FM (RXA_FM) и широкополосным +// WFM (RXA_WFM, broadcast 88..108 МГц со стерео и RDS). Срабатывает только при +// FMode = MODE_FM; в других режимах флаг просто запоминается до перехода в FM. +procedure TWDSPEngine.SetWFMMode(Enabled: Boolean); +begin + if FWFMEnabled = Enabled then Exit; + FWFMEnabled := Enabled; + if not FInitialized then Exit; + if FMode = MODE_FM then + begin + SetRXAMode(RXA_CHAN, ModeToWDSP(FMode)); + if Enabled then + SetRXAWFMDeviation(RXA_CHAN, FWFMDeviation); + end; +end; + +procedure TWDSPEngine.SetRXWFMDeviation(Deviation: Double); +begin + FWFMDeviation := Deviation; + if not FInitialized then Exit; + SetRXAWFMDeviation(RXA_CHAN, Deviation); +end; + procedure TWDSPEngine.SetFMSquelch(Enable: Boolean; Level: Integer); var Threshold: Double; begin