From fd145b28f640740235eace80931ed2cd586f43cf Mon Sep 17 00:00:00 2001 From: Uladzimir Karpenka Date: Thu, 4 Jun 2026 15:15:46 +0300 Subject: [PATCH] Phase 3 (batch 6): route FM squelch + CTCSS through controller commands MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The user plans to expose FM over web/CAT later, so FM controls get the same controller treatment now — future web/CAT FM commands plug straight into these. - Relocate CTCSS_COUNT/CTCSS_TONES from MainForm to FMRepeater (a shared FM unit MainForm already uses) so TRadioController can drive CTCSS on the DSP. CTCSS_NAMES stays in MainForm (UI dropdown labels). MODE_FM is already visible to the controller via WDSPEngine. - SetFMSquelch/SetFMSquelchLevel apply FM squelch (mode-gated) to the DSP; SetFMCTCSS/SetFMCTCSSTone clamp and apply CTCSS. All fire OnStateChanged. - OnControllerState restyles the SQ/CTCSS buttons, syncs the squelch slider + label, and the CTCSS tone dropdown. Handlers/SetFMCTCSSTone call the commands (manual CTCSS still clears the auto-active flag in the handler). FM repeater stays for the heavy batch — it needs XvtrTranslate/ ActiveTXFreqHz moved into the controller. ApplyFMSquelch remains for band/mode re-apply paths (RestoreBand, XVTR). Builds clean (cocoa). Co-Authored-By: Claude Opus 4.8 --- FMRepeater.pas | 10 +++++++++ MainForm.pas | 49 +++++++++++++++++++++++++-------------------- RadioController.pas | 31 +++++++++++++++++++++++----- 3 files changed, 63 insertions(+), 27 deletions(-) diff --git a/FMRepeater.pas b/FMRepeater.pas index d86e7f4..9b28356 100644 --- a/FMRepeater.pas +++ b/FMRepeater.pas @@ -22,6 +22,16 @@ const RPT_AUTO_MINUS_LO = 145600000.0; // 145.600 MHz RPT_AUTO_MINUS_HI = 145787500.0; // 145.7875 MHz + // CTCSS tones (38 standard tones). Здесь (а не в MainForm), чтобы таблица + // была доступна TRadioController для FDSPEngine.SetTXCTCSS. Имена тонов + // (CTCSS_NAMES) остаются в MainForm — это UI-метки выпадающего списка. + CTCSS_COUNT = 38; + CTCSS_TONES: array[0..CTCSS_COUNT-1] of Double = ( + 67.0, 71.9, 74.4, 77.0, 79.7, 82.5, 85.4, 88.5, 91.5, 94.8, + 97.4, 100.0, 103.5, 107.2, 110.9, 114.8, 118.8, 123.0, 127.3, 131.8, + 136.5, 141.3, 146.2, 151.4, 156.7, 162.2, 167.9, 173.8, 179.9, 186.2, + 192.8, 203.5, 210.7, 218.1, 225.7, 233.6, 241.8, 250.3); + { Returns default offset (Hz) for the given visible frequency. 70 cm band → 7.600 MHz; everything else → 0.600 MHz. } function RptDefaultOffsetHz(FreqHz: Double): Double; diff --git a/MainForm.pas b/MainForm.pas index 43223a6..60b723f 100644 --- a/MainForm.pas +++ b/MainForm.pas @@ -101,12 +101,7 @@ const FILT_FM_DEF = 0; // NFM // CTCSS tones (38 standard tones, no None — toggle via CTCSS button) - CTCSS_COUNT = 38; - CTCSS_TONES: array[0..37] of Double = ( - 67.0, 71.9, 74.4, 77.0, 79.7, 82.5, 85.4, 88.5, 91.5, 94.8, - 97.4, 100.0, 103.5, 107.2, 110.9, 114.8, 118.8, 123.0, 127.3, 131.8, - 136.5, 141.3, 146.2, 151.4, 156.7, 162.2, 167.9, 173.8, 179.9, 186.2, - 192.8, 203.5, 210.7, 218.1, 225.7, 233.6, 241.8, 250.3); + // CTCSS_COUNT и CTCSS_TONES перенесены в FMRepeater (нужны TRadioController). CTCSS_NAMES: array[0..37] of string = ( '67.0', '71.9', '74.4', '77.0', '79.7', '82.5', '85.4', '88.5', '91.5', '94.8', '97.4', '100.0', '103.5', '107.2', '110.9', '114.8', @@ -1460,6 +1455,23 @@ begin if (FController.FMode = MODE_FM) and (FSpecView <> nil) then FSpecView.FMGridStepHz := FM_STEP_HZ[FController.FFMStepIdx]; end; + rfFMSQ: + begin + StyleButton(BtnFMSQ, FController.FFMSQOn); + BtnFMSQ.Repaint; + end; + rfFMSQLevel: + begin + TrkFMSQ.Position := FController.FFMSQLevel; + if LblFMSQ <> nil then LblFMSQ.Caption := IntToStr(FController.FFMSQLevel); + end; + rfFMCTCSS: + begin + StyleButton(BtnFMCTCSS, FController.FFMCTCSSOn); + BtnFMCTCSS.Repaint; + end; + rfFMCTCSSTone: + if FCTCSSDropDown <> nil then FCTCSSDropDown.SetItemIndex(FController.FFMCTCSSToneIdx); end; finally FSyncingFromController := False; @@ -5288,18 +5300,15 @@ end; procedure TMainForm.BtnFMSQClick(Sender: TObject); begin - FController.FFMSQOn := not FController.FFMSQOn; - StyleButton(BtnFMSQ, FController.FFMSQOn); - BtnFMSQ.Repaint; - ApplyFMSquelch; + if FSyncingFromController then Exit; + FController.SetFMSquelch(not FController.FFMSQOn); // DSP + OnControllerState SaveCurrentBand; end; procedure TMainForm.TrkFMSQChange(Sender: TObject); begin - FController.FFMSQLevel := TrkFMSQ.Position; - if LblFMSQ <> nil then LblFMSQ.Caption := IntToStr(FController.FFMSQLevel); - ApplyFMSquelch; + if FSyncingFromController then Exit; + FController.SetFMSquelchLevel(TrkFMSQ.Position); SaveCurrentBand; end; @@ -5658,20 +5667,16 @@ end; procedure TMainForm.SetFMCTCSSTone(Idx: Integer); begin - FController.FFMCTCSSToneIdx := Idx; - if FCTCSSDropDown <> nil then FCTCSSDropDown.SetItemIndex(Idx); - if FController.FWDSPReady then - FController.FDSPEngine.SetTXCTCSS(FController.FFMCTCSSOn, CTCSS_TONES[Idx]); + // Тонкая обёртка: команда применяет к DSP, OnControllerState(rfFMCTCSSTone) + // обновит выпадающий список. Сохранена для вызовов из restore/channel/XVTR. + FController.SetFMCTCSSTone(Idx); end; procedure TMainForm.BtnFMCTCSSClick(Sender: TObject); begin - FController.FFMCTCSSOn := not FController.FFMCTCSSOn; + if FSyncingFromController then Exit; FController.FFMCTCSSAutoActive := False; // ручное действие снимает авто-флаг - StyleButton(BtnFMCTCSS, FController.FFMCTCSSOn); - BtnFMCTCSS.Repaint; // Flush visual state before WDSP call - if FController.FWDSPReady then - FController.FDSPEngine.SetTXCTCSS(FController.FFMCTCSSOn, CTCSS_TONES[FController.FFMCTCSSToneIdx]); + FController.SetFMCTCSS(not FController.FFMCTCSSOn); end; procedure TMainForm.BtnFMCTCSSToneClick(Sender: TObject); diff --git a/RadioController.pas b/RadioController.pas index b408e43..1eebbb5 100644 --- a/RadioController.pas +++ b/RadioController.pas @@ -50,7 +50,7 @@ uses Classes, SysUtils, HPSDRProtocol, HPSDRNetwork, WDSPEngine, AudioOutput, AudioInput, - Settings, ChannelStore; + Settings, ChannelStore, FMRepeater; type // Поле, которое изменилось — для гранулярных уведомлений наружу. @@ -523,16 +523,37 @@ begin end; procedure TRadioController.SetFMCTCSS(On_: Boolean); -begin FFMCTCSSOn := On_; Changed(rfFMCTCSS); { TODO wiring } end; +begin + FFMCTCSSOn := On_; + if FWDSPReady and Assigned(FDSPEngine) then + FDSPEngine.SetTXCTCSS(FFMCTCSSOn, CTCSS_TONES[FFMCTCSSToneIdx]); + Changed(rfFMCTCSS); +end; procedure TRadioController.SetFMCTCSSTone(Idx: Integer); -begin FFMCTCSSToneIdx := Idx; Changed(rfFMCTCSSTone); { TODO wiring } end; +begin + if Idx < 0 then Idx := 0 else if Idx > CTCSS_COUNT - 1 then Idx := CTCSS_COUNT - 1; + FFMCTCSSToneIdx := Idx; + if FWDSPReady and Assigned(FDSPEngine) then + FDSPEngine.SetTXCTCSS(FFMCTCSSOn, CTCSS_TONES[FFMCTCSSToneIdx]); + Changed(rfFMCTCSSTone); +end; procedure TRadioController.SetFMSquelch(On_: Boolean); -begin FFMSQOn := On_; Changed(rfFMSQ); { TODO wiring } end; +begin + FFMSQOn := On_; + if FWDSPReady and Assigned(FDSPEngine) then + FDSPEngine.SetFMSquelch(FFMSQOn and (FMode = MODE_FM), FFMSQLevel); + Changed(rfFMSQ); +end; procedure TRadioController.SetFMSquelchLevel(V: Integer); -begin FFMSQLevel := V; Changed(rfFMSQLevel); { TODO wiring } end; +begin + FFMSQLevel := V; + if FWDSPReady and Assigned(FDSPEngine) then + FDSPEngine.SetFMSquelch(FFMSQOn and (FMode = MODE_FM), FFMSQLevel); + Changed(rfFMSQLevel); +end; procedure TRadioController.SetFMRpt(Dir: Integer); begin FFMRptDir := Dir; Changed(rfFMRpt); { TODO wiring } end;