diff --git a/MainForm.pas b/MainForm.pas index 81f508c..c7169da 100644 --- a/MainForm.pas +++ b/MainForm.pas @@ -40,7 +40,8 @@ uses BoardUtils, WisdomBuilder, UISync, FlatEdit, FMRepeater, ChannelStore, ChannelsForm, - PowerInhibit; + PowerInhibit, + RadioController; const CLR_BG = TColor($00101010); @@ -144,8 +145,12 @@ type { TMainForm } TMainForm = class(TForm) private + // ---- Headless ядро (состояние + движки). Фаза 1 миграции: см. + // RadioController.pas. Поля состояния/движков теперь живут в + // FController; обращения идут как FController.FXxx. ---- + FController: TRadioController; + // ---- Network ---- - FNetwork: THPSDRNetwork; FDevices: array of TDeviceItem; FDeviceCount: Integer; FDeviceDialog: TDeviceDialog; @@ -156,60 +161,17 @@ type FLeftPanelW: Integer; // ширина панели до скрытия (DPI-safe restore) FPendingBoardType: Integer; // BoardType устройства для подключения - // ---- DSP + Audio ---- - FDSPEngine: TWDSPEngine; - FAudioOut: TAudioOutput; - FAudioIn: TAudioInput; - FAudioOutDevName: string; // текущее имя выходного устройства - FAudioInDevName: string; // текущее имя входного устройства (TX mic) - // Отправка RX-аудио в трансивер (встроенный динамик радио, DDC Audio port 1028). - // Само накопление/конвертация живёт в THPSDRNetwork.SendSpeakerAudio. - FSendAudioToRadio: Boolean; - FTXSettings: TTXSettings; // полный TX-конфиг (загружается per-MAC) - FAlexSettings: TAlexSettings; // per-band antenna/routing config (Alex board) - FXvtrSettings: TXvtrSettings; // transverter slots (per-device) - FCurrentXvtr: Integer; // -1 = HF, 0..CFG_XVTR_COUNT-1 = active XVTR - FWDSPReady: Boolean; // True когда WDSP открыт и работает - - // ---- State ---- - FVfoA: Double; - FVfoB: Double; - FActiveVfo: Integer; - FMode: Integer; - FFilter: Integer; - FAGCMode: Integer; // 0=FAST 1=MED 2=SLOW 3=LONG 4=OFF - FAGCTop: Integer; // AGC level dBm, −20..−120 - FCTun: Boolean; // Center Tune: True=спектр стоит, маркер двигается - FFilterBW: Integer; // текущая полоса фильтра в Гц - // мышь на спектре/водопаде + // ---- UI-local state (мышь на спектре/водопаде) ---- FSpecDrag: Boolean; FSpecDragX0: Integer; // X при нажатии FSpecDragFreq: Double; // FCenterFreq при нажатии FSpectrumDirty: Boolean; // таймер должен перерисовать спектр/водопад - FDriveLevel: Byte; - FPAMaxPower: Double; - FPABandCal: array[0..BAND_COUNT-1] of Double; // калибровка 38.8..100.0 на диапазон - FVHFBandCal: array[0..CFG_XVTR_COUNT-1] of Double; // калибровка VHF/XVTR слотов - FRunning: Boolean; - FTransmitting: Boolean; - FSplitTxB: Boolean; // DUC IQ packetizer: TXA отдаёт блок FTXOutBufSize пар, который // не обязан быть кратен 240 (DUC packet). Хвост переносим в следующий // вызов OnTXIQReady, иначе зануление давало 47 Гц гул. FDUCPendingI: array[0..239] of Integer; FDUCPendingQ: array[0..239] of Integer; FDUCPendingCount: Integer; - // DUP: при TX продолжаем показывать RX1-водопад/панораму, а TX-фильтр - // и TX-линии рисуем поверх. Аппаратной командой не является. - FDisplayDuplex: Boolean; - // TUN: режим тоновой подстройки. WDSP TXA PostGen генерирует чистый - // тон, Drive подменяется на FTXSettings.TUNLevel (через Cal[band]). - FTuning: Boolean; - FHWPTTActive: Boolean; // True = hardware PTT нажата - FHWPTTStartedTX: Boolean; // True = TX был включён именно через HWPTT - FMuted: Boolean; - FVolume: Integer; - FAtten: Integer; // RX step attenuator index: 0=0dB 1=10dB 2=20dB FLastSMeter: Double; FSMeterPeak: Double; // верхняя граница светлой зоны FSMeterMin: Double; // нижняя граница светлой зоны @@ -249,22 +211,6 @@ type FDisplayFPS: Integer; FWaterfallDirty: Boolean; FWidebandDirty: Boolean; - // Waterfall settings (kept here for MakeGlobalSettings, buttons, settings dialog) - FWfAGCEnabled: Boolean; - FWfNFEnabled: Boolean; - FDitherEnabled: Boolean; - FRandomEnabled: Boolean; - FFMDeviation: Double; - FFMCTCSSOn: Boolean; - FFMCTCSSToneIdx: Integer; - FFMSQOn: Boolean; - FFMSQLevel: Integer; - FFMStepOn: Boolean; - FFMStepIdx: Integer; - FFMRptDir: Integer; // RPT_NONE / RPT_MINUS / RPT_PLUS - FFMRptOffsetHz: Double; - FFMRptAutoActive: Boolean; // True = RPT включён автоматически (авто или из канала) - FFMCTCSSAutoActive: Boolean; // True = CTCSS включён из канала (не вручную) FPreChannelPower: Integer; // мощность до применения канала (-1 = не сохранена) FWfManualHigh: Double; FWfManualLow: Double; @@ -293,7 +239,6 @@ type FSplitterSH0: Integer; FSplitterRatio: Double; // --- Settings --- - FSettings: TSettingsManager; FWebServer: TWebServer; FWebEnabled: Boolean; FWebPort: Integer; @@ -312,17 +257,10 @@ type FWebSyncInt: Integer; FWebSyncBool: Boolean; FWebSyncM: TThreadMethod; - FCurrentBand: Integer; // текущий активный диапазон 0..10 FSettingsForm: TObject; // TSettingsForm (cast при использовании) - FDevMAC: array[0..5] of Byte; // MAC подключённого трансивера FPendingDev: THPSDRDevice; // устройство ожидающее открытия WDSP - FBandCache: array[0..CFG_BAND_COUNT-1] of TBandSettings; // кэш диапазонов - FDevConnected: Boolean; // True после первого подключения FLightTheme: Boolean; // True = светлая тема FFreqMhzDigits: Integer; // 3=999MHz, 4=9.999GHz, 5=99.999GHz - FCenterFreq: Double; // центр спектра = LO (DDC). При CTUN ON не меняется при кручении VFO - FSpanHz: Double; - FSampleRate: Integer; // текущий DDC sample rate (Hz) // ---- Timers ---- FMeterTimer: TTimer; @@ -413,7 +351,6 @@ type PanelRXBlock: TPanel; BtnChannels: TFlatButton; FChannelsDropDown: TFlatDropDown; - FChannelStore: TChannelStore; FChannelsForm: TObject; // TChannelsForm (cast при использовании) FActiveChannelIdx: Integer; // -1 = нет активного канала @@ -430,6 +367,7 @@ type StatusPanel: TMainStatusBar; // ---- Helpers ---- + procedure DoInvoke(M: TThreadMethod); // маршалинг команд контроллера в UI-поток procedure BuildUI; function LeftPanelButtonLeft(PanelWidth, ColCount, ColIndex: Integer): Integer; function LeftPanelButtonWidth(PanelWidth, ColCount, ColIndex: Integer): Integer; @@ -794,7 +732,7 @@ var R, WA: TRect; Mon: TMonitor; begin - FSettings.LoadWindowBounds(L, T, Wd, Ht, SavedDPI, Maximized); + FController.FSettings.LoadWindowBounds(L, T, Wd, Ht, SavedDPI, Maximized); if (Wd > 400) and (Ht > 300) then begin CurDPI := CurrentScreenDPI; @@ -874,66 +812,66 @@ begin if (L < -32000) or (T < -32000) then begin L := 80; T := 80; end; if (Wd <= 400) or (Ht <= 300) then Exit; DPI := CurrentScreenDPI; - FSettings.SaveWindowBounds(L, T, Wd, Ht, DPI, Maximized); + FController.FSettings.SaveWindowBounds(L, T, Wd, Ht, DPI, Maximized); end; function TMainForm.MakeBandSettings: TBandSettings; begin - Result.VfoA := FVfoA; - Result.VfoB := FVfoB; - Result.Mode := FMode; - Result.FilterIdx := FFilter; - Result.FilterBW := FFilterBW; - Result.AGCMode := FAGCMode; - Result.AGCTop := FAGCTop; - Result.CTun := FCTun; - Result.SpanHz := FSpanHz; - Result.FMSQOn := FFMSQOn; - Result.FMSQLevel := FFMSQLevel; - Result.CTCSSOn := FFMCTCSSOn; - Result.CTCSSToneIdx := FFMCTCSSToneIdx; - Result.FMStepOn := FFMStepOn; - Result.FMStepIdx := FFMStepIdx; - Result.FMRptDir := FFMRptDir; - Result.FMRptOffsetHz := FFMRptOffsetHz; + Result.VfoA := FController.FVfoA; + Result.VfoB := FController.FVfoB; + Result.Mode := FController.FMode; + Result.FilterIdx := FController.FFilter; + Result.FilterBW := FController.FFilterBW; + Result.AGCMode := FController.FAGCMode; + Result.AGCTop := FController.FAGCTop; + Result.CTun := FController.FCTun; + Result.SpanHz := FController.FSpanHz; + Result.FMSQOn := FController.FFMSQOn; + Result.FMSQLevel := FController.FFMSQLevel; + Result.CTCSSOn := FController.FFMCTCSSOn; + Result.CTCSSToneIdx := FController.FFMCTCSSToneIdx; + Result.FMStepOn := FController.FFMStepOn; + Result.FMStepIdx := FController.FFMStepIdx; + Result.FMRptDir := FController.FFMRptDir; + Result.FMRptOffsetHz := FController.FFMRptOffsetHz; // Waterfall AGC/NF — глобальные (не диапазонные) end; function TMainForm.MakeGlobalSettings: TGlobalSettings; var i: Integer; begin - Result.Volume := FVolume; + Result.Volume := FController.FVolume; Result.DriveLevel := TrkDrive.Position; - Result.PAMaxPower := FPAMaxPower; + Result.PAMaxPower := FController.FPAMaxPower; for i := 0 to BAND_COUNT - 1 do - Result.PABandCal[i] := FPABandCal[i]; + Result.PABandCal[i] := FController.FPABandCal[i]; for i := 0 to CFG_XVTR_COUNT - 1 do - Result.VHFBandCal[i] := FVHFBandCal[i]; - Result.ActiveVfo := FActiveVfo; + Result.VHFBandCal[i] := FController.FVHFBandCal[i]; + Result.ActiveVfo := FController.FActiveVfo; Result.NRMode := BtnNR.Tag; Result.NBMode := BtnNB.Tag; Result.SNBEnabled := BtnSNB.Tag <> 0; Result.ANFEnabled := BtnANF.Tag <> 0; Result.AGCSlope := 0; Result.AGCHangThreshold := 100; - Result.WfAGCEnabled := FWfAGCEnabled; - Result.WfNFEnabled := FWfNFEnabled; - Result.DitherEnabled := FDitherEnabled; - Result.RandomEnabled := FRandomEnabled; - Result.LastBand := FCurrentBand; - Result.LastXvtr := FCurrentXvtr; - Result.SampleRate := FSampleRate; + Result.WfAGCEnabled := FController.FWfAGCEnabled; + Result.WfNFEnabled := FController.FWfNFEnabled; + Result.DitherEnabled := FController.FDitherEnabled; + Result.RandomEnabled := FController.FRandomEnabled; + Result.LastBand := FController.FCurrentBand; + Result.LastXvtr := FController.FCurrentXvtr; + Result.SampleRate := FController.FSampleRate; // Display settings - if FWDSPReady then + if FController.FWDSPReady then begin - Result.FFTSize := FDSPEngine.FFTSize; - Result.WindowType := FDSPEngine.WindowType; - Result.SpecDetector := FDSPEngine.SpecDetector; - Result.SpecAvgMode := FDSPEngine.SpecAvgMode; - Result.SpecAvgTimeMS := FDSPEngine.SpecAvgTimeMS; - Result.WfDetector := FDSPEngine.WfDetector; - Result.WfAvgMode := FDSPEngine.WfAvgMode; - Result.WfAvgTimeMS := FDSPEngine.WfAvgTimeMS; + Result.FFTSize := FController.FDSPEngine.FFTSize; + Result.WindowType := FController.FDSPEngine.WindowType; + Result.SpecDetector := FController.FDSPEngine.SpecDetector; + Result.SpecAvgMode := FController.FDSPEngine.SpecAvgMode; + Result.SpecAvgTimeMS := FController.FDSPEngine.SpecAvgTimeMS; + Result.WfDetector := FController.FDSPEngine.WfDetector; + Result.WfAvgMode := FController.FDSPEngine.WfAvgMode; + Result.WfAvgTimeMS := FController.FDSPEngine.WfAvgTimeMS; end else begin @@ -952,20 +890,20 @@ begin Result.SpecRefLevel := FSpecRefLevel; Result.SpecRange := FSpecRange; Result.SpecGridStep := FSpecGridStep; - Result.AudioSampleRate := FAudioOut.SampleRate; + Result.AudioSampleRate := FController.FAudioOut.SampleRate; Result.ShowSpectrum := FShowSpectrum; Result.ShowWaterfall := FShowWaterfall; Result.ShowWideband := FShowWideband; Result.WidebandFill := FWidebandFill; Result.SpectrumFill := FSpectrumFill; - Result.DisplayDuplex := FDisplayDuplex; + Result.DisplayDuplex := FController.FDisplayDuplex; Result.DisplayFPS := FDisplayFPS; Result.LightTheme := FLightTheme; Result.FreqMhzDigits := FFreqMhzDigits; // Audio device names - Result.AudioOutDevice := FAudioOutDevName; - Result.AudioInDevice := FAudioInDevName; - Result.SendAudioToRadio := FSendAudioToRadio; + Result.AudioOutDevice := FController.FAudioOutDevName; + Result.AudioInDevice := FController.FAudioInDevName; + Result.SendAudioToRadio := FController.FSendAudioToRadio; // CAT settings — carried from the last loaded/saved device config Result.CATSerialEnabled := FCATLastGlobal.CATSerialEnabled; Result.CATSerialPort := FCATLastGlobal.CATSerialPort; @@ -979,41 +917,41 @@ end; procedure TMainForm.SaveCurrentBand; begin - if not FDevConnected then Exit; - // В XVTR-режиме НЕ трогаем HF band-cache: FBandCache[FCurrentBand] + if not FController.FDevConnected then Exit; + // В XVTR-режиме НЕ трогаем HF band-cache: FController.FBandCache[FController.FCurrentBand] // относится к HF-диапазону, на котором пользователь был ДО активации - // трансвертера, и FVfoA сейчас на XVTR-частоте (144.x, 432.x и т.д.). - // Запись в FBandCache[FCurrentBand] стерла бы корректное HF-состояние. + // трансвертера, и FController.FVfoA сейчас на XVTR-частоте (144.x, 432.x и т.д.). + // Запись в FController.FBandCache[FController.FCurrentBand] стерла бы корректное HF-состояние. // Вместо этого сохраняем LastFreq в XVTR-настройках. - if FCurrentXvtr >= 0 then + if FController.FCurrentXvtr >= 0 then begin - if (FCurrentXvtr < CFG_XVTR_COUNT) then + if (FController.FCurrentXvtr < CFG_XVTR_COUNT) then begin - FXvtrSettings.Entries[FCurrentXvtr].LastFreq := FVfoA; - FXvtrSettings.Entries[FCurrentXvtr].LastMode := FMode; - FXvtrSettings.Entries[FCurrentXvtr].LastFilterIdx := FFilter; - FXvtrSettings.Entries[FCurrentXvtr].LastFMSQOn := FFMSQOn; - FXvtrSettings.Entries[FCurrentXvtr].LastFMSQLevel := FFMSQLevel; - FXvtrSettings.Entries[FCurrentXvtr].LastCTCSSOn := FFMCTCSSOn; - FXvtrSettings.Entries[FCurrentXvtr].LastCTCSSToneIdx := FFMCTCSSToneIdx; - FXvtrSettings.Entries[FCurrentXvtr].LastFMStepOn := FFMStepOn; - FXvtrSettings.Entries[FCurrentXvtr].LastFMStepIdx := FFMStepIdx; - FXvtrSettings.Entries[FCurrentXvtr].LastFMRptDir := FFMRptDir; - FXvtrSettings.Entries[FCurrentXvtr].LastFMRptOffsetHz := FFMRptOffsetHz; - FXvtrSettings.Entries[FCurrentXvtr].LastCTun := FCTun; - FXvtrSettings.Entries[FCurrentXvtr].LastAGCMode := FAGCMode; - FXvtrSettings.Entries[FCurrentXvtr].LastAGCTop := FAGCTop; - FXvtrSettings.Entries[FCurrentXvtr].LastCTCSSAutoActive := FFMCTCSSAutoActive; - FXvtrSettings.Entries[FCurrentXvtr].LastFMRptAutoActive := FFMRptAutoActive; + FController.FXvtrSettings.Entries[FController.FCurrentXvtr].LastFreq := FController.FVfoA; + FController.FXvtrSettings.Entries[FController.FCurrentXvtr].LastMode := FController.FMode; + FController.FXvtrSettings.Entries[FController.FCurrentXvtr].LastFilterIdx := FController.FFilter; + FController.FXvtrSettings.Entries[FController.FCurrentXvtr].LastFMSQOn := FController.FFMSQOn; + FController.FXvtrSettings.Entries[FController.FCurrentXvtr].LastFMSQLevel := FController.FFMSQLevel; + FController.FXvtrSettings.Entries[FController.FCurrentXvtr].LastCTCSSOn := FController.FFMCTCSSOn; + FController.FXvtrSettings.Entries[FController.FCurrentXvtr].LastCTCSSToneIdx := FController.FFMCTCSSToneIdx; + FController.FXvtrSettings.Entries[FController.FCurrentXvtr].LastFMStepOn := FController.FFMStepOn; + FController.FXvtrSettings.Entries[FController.FCurrentXvtr].LastFMStepIdx := FController.FFMStepIdx; + FController.FXvtrSettings.Entries[FController.FCurrentXvtr].LastFMRptDir := FController.FFMRptDir; + FController.FXvtrSettings.Entries[FController.FCurrentXvtr].LastFMRptOffsetHz := FController.FFMRptOffsetHz; + FController.FXvtrSettings.Entries[FController.FCurrentXvtr].LastCTun := FController.FCTun; + FController.FXvtrSettings.Entries[FController.FCurrentXvtr].LastAGCMode := FController.FAGCMode; + FController.FXvtrSettings.Entries[FController.FCurrentXvtr].LastAGCTop := FController.FAGCTop; + FController.FXvtrSettings.Entries[FController.FCurrentXvtr].LastCTCSSAutoActive := FController.FFMCTCSSAutoActive; + FController.FXvtrSettings.Entries[FController.FCurrentXvtr].LastFMRptAutoActive := FController.FFMRptAutoActive; end; - FSettings.SaveXvtr(FDevMAC, FXvtrSettings); + FController.FSettings.SaveXvtr(FController.FDevMAC, FController.FXvtrSettings); Exit; end; // Не сохраняем, если VFO ушёл в VHF/UHF область (например, после применения // канала на 2м без XVTR) — иначе HF-кэш диапазона будет перезаписан VHF-частотой. - if FVfoA > 61000000 then Exit; - FBandCache[FCurrentBand] := MakeBandSettings; - FSettings.SaveBand(FDevMAC, FCurrentBand, FBandCache[FCurrentBand]); + if FController.FVfoA > 61000000 then Exit; + FController.FBandCache[FController.FCurrentBand] := MakeBandSettings; + FController.FSettings.SaveBand(FController.FDevMAC, FController.FCurrentBand, FController.FBandCache[FController.FCurrentBand]); end; procedure TMainForm.RestoreBand(BandIdx: Integer); @@ -1022,7 +960,7 @@ var i: Integer; begin if (BandIdx < 0) or (BandIdx >= CFG_BAND_COUNT) then Exit; - B := FBandCache[BandIdx]; + B := FController.FBandCache[BandIdx]; // --- Кнопки диапазонов --- for i := 0 to BAND_COUNT - 1 do @@ -1030,88 +968,88 @@ begin // Сбрасываем waterfall thresholds при смене диапазона FSpecView.ResetWfAvgBuf; - // --- Режим: сначала устанавливаем FMode, потом кнопки и WDSP --- - FMode := B.Mode; + // --- Режим: сначала устанавливаем FController.FMode, потом кнопки и WDSP --- + FController.FMode := B.Mode; for i := 0 to MODE_COUNT - 1 do - StyleButton(BtnMode[i], i = FMode); - if FWDSPReady then - FDSPEngine.SetMode(FMode); + StyleButton(BtnMode[i], i = FController.FMode); + if FController.FWDSPReady then + FController.FDSPEngine.SetMode(FController.FMode); // --- Фильтр --- - FFilter := B.FilterIdx; - FFilterBW := B.FilterBW; + FController.FFilter := B.FilterIdx; + FController.FFilterBW := B.FilterBW; UpdateFilterButtons; // обновляет кнопки фильтра - ApplyModeFilter; // применяет Lo/Hi в WDSP с учётом нового FMode + ApplyModeFilter; // применяет Lo/Hi в WDSP с учётом нового FController.FMode // --- AGC --- - FAGCMode := B.AGCMode; - FAGCTop := B.AGCTop; + FController.FAGCMode := B.AGCMode; + FController.FAGCTop := B.AGCTop; for i := 0 to 4 do - StyleButton(BtnAGCMode[i], i = FAGCMode); - TrkAGC.Position := FAGCTop; - LblAGCTop.Caption := Format('%ddB', [FAGCTop]); - if FWDSPReady then + StyleButton(BtnAGCMode[i], i = FController.FAGCMode); + TrkAGC.Position := FController.FAGCTop; + LblAGCTop.Caption := Format('%ddB', [FController.FAGCTop]); + if FController.FWDSPReady then begin - FDSPEngine.SetAGCTop(FAGCTop); - FDSPEngine.SetAGC(TWDSPAGCMode(FAGCMode), 50.0); + FController.FDSPEngine.SetAGCTop(FController.FAGCTop); + FController.FDSPEngine.SetAGC(TWDSPAGCMode(FController.FAGCMode), 50.0); end; - FSpecView.AGCTop := FAGCTop; + FSpecView.AGCTop := FController.FAGCTop; // --- FM Squelch + CTCSS --- - FFMSQOn := B.FMSQOn; - FFMSQLevel := B.FMSQLevel; + FController.FFMSQOn := B.FMSQOn; + FController.FFMSQLevel := B.FMSQLevel; // UI update happens inside UpdateFilterButtons (called above) when mode=FM - FFMCTCSSOn := B.CTCSSOn; - if BtnFMCTCSS <> nil then StyleButton(BtnFMCTCSS, FFMCTCSSOn); + FController.FFMCTCSSOn := B.CTCSSOn; + if BtnFMCTCSS <> nil then StyleButton(BtnFMCTCSS, FController.FFMCTCSSOn); SetFMCTCSSTone(B.CTCSSToneIdx); ApplyFMSquelch; - FFMStepOn := B.FMStepOn; + FController.FFMStepOn := B.FMStepOn; SetFMStep(B.FMStepIdx); - if BtnFMStep <> nil then StyleButton(BtnFMStep, FFMStepOn); - FFMRptDir := B.FMRptDir; - FFMRptOffsetHz := B.FMRptOffsetHz; - if FFMRptOffsetHz <= 0 then - FFMRptOffsetHz := RptDefaultOffsetHz(B.VfoA); - if BtnFMRptMinus <> nil then StyleButton(BtnFMRptMinus, FFMRptDir = RPT_MINUS); - if BtnFMRptPlus <> nil then StyleButton(BtnFMRptPlus, FFMRptDir = RPT_PLUS); - if EdFMRptOffset <> nil then EdFMRptOffset.Text := RptFormatOffset(FFMRptOffsetHz); + if BtnFMStep <> nil then StyleButton(BtnFMStep, FController.FFMStepOn); + FController.FFMRptDir := B.FMRptDir; + FController.FFMRptOffsetHz := B.FMRptOffsetHz; + if FController.FFMRptOffsetHz <= 0 then + FController.FFMRptOffsetHz := RptDefaultOffsetHz(B.VfoA); + if BtnFMRptMinus <> nil then StyleButton(BtnFMRptMinus, FController.FFMRptDir = RPT_MINUS); + if BtnFMRptPlus <> nil then StyleButton(BtnFMRptPlus, FController.FFMRptDir = RPT_PLUS); + if EdFMRptOffset <> nil then EdFMRptOffset.Text := RptFormatOffset(FController.FFMRptOffsetHz); // --- CTUN --- - FCTun := B.CTun; - StyleButton(BtnCTun, FCTun); - if FWDSPReady and not FCTun then - FDSPEngine.SetShift(0.0); + FController.FCTun := B.CTun; + StyleButton(BtnCTun, FController.FCTun); + if FController.FWDSPReady and not FController.FCTun then + FController.FDSPEngine.SetShift(0.0); // --- Sample Rate / Span --- // SampleRate — глобальный, не per-band. Не восстанавливаем из диапазона. - FSpanHz := FSampleRate; + FController.FSpanHz := FController.FSampleRate; if Assigned(FSampleRateOverlay) then - FSampleRateOverlay.SetCurrentRate(FSampleRate); + FSampleRateOverlay.SetCurrentRate(FController.FSampleRate); // --- VFO A: центрируем на новой частоте, сбрасываем shift --- - FCenterFreq := B.VfoA; // DDC = центр диапазона - FVfoB := B.VfoB; - FreqDispB.Frequency := Round(FVfoB); + FController.FCenterFreq := B.VfoA; // DDC = центр диапазона + FController.FVfoB := B.VfoB; + FreqDispB.Frequency := Round(FController.FVfoB); // Пересчитываем drive byte для нового диапазона перед отправкой в сеть - FDriveLevel := CalcDriveByte; + FController.FDriveLevel := CalcDriveByte; // ApplyVfoA обновит сеть, FreqDisp, shift, перерисует ApplyVfoA(Round(B.VfoA)); end; procedure TMainForm.SaveAllAndExit; begin - if FDevConnected then + if FController.FDevConnected then begin SaveCurrentBand; - FSettings.SaveGlobal(FDevMAC, MakeGlobalSettings); - if FDevConnected then - FSettings.SaveTX(FDevMAC, FTXSettings); + FController.FSettings.SaveGlobal(FController.FDevMAC, MakeGlobalSettings); + if FController.FDevConnected then + FController.FSettings.SaveTX(FController.FDevMAC, FController.FTXSettings); // Запомнить LastFreq XVTR для следующего запуска - if (FCurrentXvtr >= 0) and (FCurrentXvtr < CFG_XVTR_COUNT) then - FXvtrSettings.Entries[FCurrentXvtr].LastFreq := FVfoA; - FSettings.SaveXvtr(FDevMAC, FXvtrSettings); - FSettings.Save; + if (FController.FCurrentXvtr >= 0) and (FController.FCurrentXvtr < CFG_XVTR_COUNT) then + FController.FXvtrSettings.Entries[FController.FCurrentXvtr].LastFreq := FController.FVfoA; + FController.FSettings.SaveXvtr(FController.FDevMAC, FController.FXvtrSettings); + FController.FSettings.Save; end; end; @@ -1122,58 +1060,63 @@ var StartupVfoA, StartupVfoB: Double; begin SetExceptionMask(GetExceptionMask + [exZeroDivide, exInvalidOp]); - FVfoA := 14200000; - FVfoB := 7100000; - FActiveVfo := 0; - FMode := 1; - FFilter := 5; - FAGCMode := 1; // MEDIUM - FAGCTop := 90; // −90 dBm + // Headless ядро: владеет состоянием и движками. Создаём первым — все + // присваивания FController.FXxx ниже зависят от него. Движки создаются/ + // освобождаются здесь же (в полях FController) — Фаза 1 миграции. + FController := TRadioController.Create; + FController.OnInvoke := DoInvoke; + FController.FVfoA := 14200000; + FController.FVfoB := 7100000; + FController.FActiveVfo := 0; + FController.FMode := 1; + FController.FFilter := 5; + FController.FAGCMode := 1; // MEDIUM + FController.FAGCTop := 90; // −90 dBm // default: зависит от режима, будет сброшен в UpdateFilterButtons - FCTun := False; - FFilterBW := 2700; + FController.FCTun := False; + FController.FFilterBW := 2700; FSpecDrag := False; - FDriveLevel := 0; - FPAMaxPower := 100.0; - for i := 0 to BAND_COUNT - 1 do FPABandCal[i] := CFG_PA_BAND_CAL_DEFAULT[i]; - for i := 0 to CFG_XVTR_COUNT - 1 do FVHFBandCal[i] := CFG_VHF_CAL_DEFAULT; - FRunning := False; - FTransmitting := False; - FSplitTxB := False; + FController.FDriveLevel := 0; + FController.FPAMaxPower := 100.0; + for i := 0 to BAND_COUNT - 1 do FController.FPABandCal[i] := CFG_PA_BAND_CAL_DEFAULT[i]; + for i := 0 to CFG_XVTR_COUNT - 1 do FController.FVHFBandCal[i] := CFG_VHF_CAL_DEFAULT; + FController.FRunning := False; + FController.FTransmitting := False; + FController.FSplitTxB := False; FDUCPendingCount := 0; - FDisplayDuplex := False; - FTuning := False; - FMuted := False; - FVolume := 70; - FCenterFreq := FVfoA; - FSpanHz := 192000; - FSampleRate := 192000; - FCurrentBand := 5; // 20m по умолчанию + FController.FDisplayDuplex := False; + FController.FTuning := False; + FController.FMuted := False; + FController.FVolume := 70; + FController.FCenterFreq := FController.FVfoA; + FController.FSpanHz := 192000; + FController.FSampleRate := 192000; + FController.FCurrentBand := 5; // 20m по умолчанию // Waterfall normalization FWfManualHigh := -80.0; FWfManualLow := -130.0; FWfAGCOffset := 0.0; - FWfAGCEnabled := True; - FWfNFEnabled := False; - FDitherEnabled := True; - FRandomEnabled := True; - FFMDeviation := FILT_FM_DEV[FILT_FM_DEF]; - FFMCTCSSOn := False; - FFMCTCSSToneIdx := 0; - FFMSQOn := False; - FFMSQLevel := 30; - FFMStepOn := True; - FFMStepIdx := FM_STEP_DEF; - FFMRptDir := RPT_NONE; - FFMRptOffsetHz := RPT_DEFAULT_2M; - FFMRptAutoActive := False; - FFMCTCSSAutoActive := False; + FController.FWfAGCEnabled := True; + FController.FWfNFEnabled := False; + FController.FDitherEnabled := True; + FController.FRandomEnabled := True; + FController.FFMDeviation := FILT_FM_DEV[FILT_FM_DEF]; + FController.FFMCTCSSOn := False; + FController.FFMCTCSSToneIdx := 0; + FController.FFMSQOn := False; + FController.FFMSQLevel := 30; + FController.FFMStepOn := True; + FController.FFMStepIdx := FM_STEP_DEF; + FController.FFMRptDir := RPT_NONE; + FController.FFMRptOffsetHz := RPT_DEFAULT_2M; + FController.FFMRptAutoActive := False; + FController.FFMCTCSSAutoActive := False; FPreChannelPower := -1; FSpectrumBufCount := 1024; FWaterfallBufCount := 1024; FWaterfallFrameInterval := 2; FWaterfallFrameCounter := 0; - FDevConnected := False; + FController.FDevConnected := False; // Spectrum grid settings defaults FSpecRefLevel := -20.0; FSpecRange := 110.0; @@ -1185,29 +1128,29 @@ begin FSettingsForm := nil; FChannelsForm := nil; FActiveChannelIdx := -1; - FillChar(FDevMAC, SizeOf(FDevMAC), 0); + FillChar(FController.FDevMAC, SizeOf(FController.FDevMAC), 0); // Инициализируем кэш диапазонов умолчаниями for bi_ := 0 to CFG_BAND_COUNT - 1 do - TSettingsManager.DefaultBand(bi_, FBandCache[bi_]); - FCurrentBand := 5; + TSettingsManager.DefaultBand(bi_, FController.FBandCache[bi_]); + FController.FCurrentBand := 5; // Загружаем JSON настройки - FSettings := TSettingsManager.Create; - FSettings.Load; + FController.FSettings := TSettingsManager.Create; + FController.FSettings.Load; // Загружаем список каналов - FChannelStore := TChannelStore.Create(CHANNELS_FILE); - FChannelStore.Load; - if FSettings.LoadStartupPreview(StartupVfoA, StartupVfoB, StartupRate) then + FController.FChannelStore := TChannelStore.Create(CHANNELS_FILE); + FController.FChannelStore.Load; + if FController.FSettings.LoadStartupPreview(StartupVfoA, StartupVfoB, StartupRate) then begin - FVfoA := StartupVfoA; - FVfoB := StartupVfoB; - FCenterFreq := FVfoA; + FController.FVfoA := StartupVfoA; + FController.FVfoB := StartupVfoB; + FController.FCenterFreq := FController.FVfoA; if StartupRate > 0 then begin - FSampleRate := StartupRate; - FSpanHz := StartupRate; + FController.FSampleRate := StartupRate; + FController.FSpanHz := StartupRate; end; end; // Восстанавливаем размер/позицию окна при старте (до подключения устройства) @@ -1217,7 +1160,7 @@ begin FLastSMeter := -130; // Загружаем настройки веб-сервера из JSON и создаём сервер - FSettings.LoadWebSettings(WebCfg); + FController.FSettings.LoadWebSettings(WebCfg); FWebEnabled := WebCfg.Enabled; FWebPort := WebCfg.Port; FWebBindAddr := WebCfg.BindAddr; @@ -1267,8 +1210,8 @@ begin FCATLastGlobal.CATSerialPort[i] := '/dev/ttyS' + IntToStr(i); {$ENDIF} end; - FSettings.LoadCATSettings(FCATLastGlobal); - FLightTheme := FSettings.LoadTheme; + FController.FSettings.LoadCATSettings(FCATLastGlobal); + FLightTheme := FController.FSettings.LoadTheme; InitCATEngine; CATApplySettings(FCATLastGlobal); FSMeterPeak := -130; @@ -1333,16 +1276,16 @@ begin else FSpecView := TSpectrumView.Create; FSpecView.VfoOverlay := FVfoOverlay; - FSpecView.VfoA := FVfoA; - FSpecView.VfoB := FVfoB; - FSpecView.ActiveVfo := FActiveVfo; - FSpecView.CenterFreq := FCenterFreq; - FSpecView.SpanHz := FSpanHz; - FSpecView.Mode := FMode; - FSpecView.FilterBW := FFilterBW; - FSpecView.AGCTop := FAGCTop; - FSpecView.WfAGCEnabled := FWfAGCEnabled; - FSpecView.WfNFEnabled := FWfNFEnabled; + FSpecView.VfoA := FController.FVfoA; + FSpecView.VfoB := FController.FVfoB; + FSpecView.ActiveVfo := FController.FActiveVfo; + FSpecView.CenterFreq := FController.FCenterFreq; + FSpecView.SpanHz := FController.FSpanHz; + FSpecView.Mode := FController.FMode; + FSpecView.FilterBW := FController.FFilterBW; + FSpecView.AGCTop := FController.FAGCTop; + FSpecView.WfAGCEnabled := FController.FWfAGCEnabled; + FSpecView.WfNFEnabled := FController.FWfNFEnabled; FSpecView.WfManualHigh := FWfManualHigh; FSpecView.WfManualLow := FWfManualLow; FSpecView.WfAGCOffset := FWfAGCOffset; @@ -1352,20 +1295,20 @@ begin // TX overlay: ширина окна = TX_SAMPLE_RATE из движка (фикс 192k) FSpecView.TXSpanHz := WDSPEngine.TX_SAMPLE_RATE; FSpecView.WfFrameInterval := FWaterfallFrameInterval; - FSpecView.PAMaxPower := FPAMaxPower; + FSpecView.PAMaxPower := FController.FPAMaxPower; FWidebandView := TWidebandView.Create; - FNetwork := THPSDRNetwork.Create; - FNetwork.OnDeviceFound := OnDeviceFound; - FNetwork.OnHPStatus := OnHPStatusCB; - FNetwork.OnDDCIQ := OnDDCIQCB; - FNetwork.OnMicPacket := OnMicPacketCB; - FNetwork.OnWideband := OnWidebandCB; + FController.FNetwork := THPSDRNetwork.Create; + FController.FNetwork.OnDeviceFound := OnDeviceFound; + FController.FNetwork.OnHPStatus := OnHPStatusCB; + FController.FNetwork.OnDDCIQ := OnDDCIQCB; + FController.FNetwork.OnMicPacket := OnMicPacketCB; + FController.FNetwork.OnWideband := OnWidebandCB; // Дефолты TX, Alex и XVTR (на случай если устройство ещё не выбрано). - TSettingsManager.DefaultTX(FTXSettings); - TSettingsManager.DefaultAlex(FAlexSettings); - TSettingsManager.DefaultXvtr(FXvtrSettings); - FCurrentXvtr := -1; + TSettingsManager.DefaultTX(FController.FTXSettings); + TSettingsManager.DefaultAlex(FController.FAlexSettings); + TSettingsManager.DefaultXvtr(FController.FXvtrSettings); + FController.FCurrentXvtr := -1; BuildUI; ResizeSMeter; @@ -1374,25 +1317,25 @@ begin UpdateFilterButtons; // DSP Engine — создаём объект, Open вызовется при нажатии START - // (FDSPEngine.Open загружает libwdsp и занимает ~1-2 сек) - FWDSPReady := False; + // (FController.FDSPEngine.Open загружает libwdsp и занимает ~1-2 сек) + FController.FWDSPReady := False; // BufSize=512: FBufSize=2048 @ 192kHz, FAudioBufSize=512 @ 48kHz. // Уменьшение с 1024 вдвое сокращает время построения downsampler в OpenChannel RXA // (~1300ms → ~650ms). Латентность: 512/48000 ≈ 10.7ms — допустимо для SDR. - FDSPEngine := TWDSPEngine.Create(FSampleRate, 48000, 512); - FDSPEngine.OnAudio := OnAudioReady; - FDSPEngine.OnSpectrum := OnSpectrumReady; - FDSPEngine.OnWaterfall := OnWaterfallReady; - FDSPEngine.OnTXIQ := OnTXIQReady; + FController.FDSPEngine := TWDSPEngine.Create(FController.FSampleRate, 48000, 512); + FController.FDSPEngine.OnAudio := OnAudioReady; + FController.FDSPEngine.OnSpectrum := OnSpectrumReady; + FController.FDSPEngine.OnWaterfall := OnWaterfallReady; + FController.FDSPEngine.OnTXIQ := OnTXIQReady; // Sound-card mic путь: WDSP TX-thread дёргает этот колбэк перед каждым // тиком, если выбран источник SoundCard. Для Radio колбэк не вызывается. - FDSPEngine.OnPullMicSamples := PullSoundCardMic; + FController.FDSPEngine.OnPullMicSamples := PullSoundCardMic; // Audio output/input — создаём объекты сейчас, открываем после показа формы // (Pa_Initialize на Linux пишет в stderr до перехвата сигналов FPC) - FAudioOut := TAudioOutput.Create(48000); - FAudioOut.OutputBufferSize := FSettings.LoadAudioBufferSize; - FAudioIn := TAudioInput.Create(48000); + FController.FAudioOut := TAudioOutput.Create(48000); + FController.FAudioOut.OutputBufferSize := FController.FSettings.LoadAudioBufferSize; + FController.FAudioIn := TAudioInput.Create(48000); FMeterTimer := TTimer.Create(Self); FMeterTimer.Interval := 100; @@ -1424,37 +1367,47 @@ procedure TMainForm.FormDestroy(Sender: TObject); begin FMeterTimer.Enabled := False; FSpectrumTimer.Enabled := False; - if FNetwork.Running then - FNetwork.SetRunAndFreq(False, XvtrTranslate(FCenterFreq), XvtrTranslate(FCenterFreq), 0); - FNetwork.Disconnect; - FNetwork.Free; - FAudioOut.Close; - FAudioOut.Free; - FAudioIn.Close; - FAudioIn.Free; - FDSPEngine.Close; - FDSPEngine.Free; + if FController.FNetwork.Running then + FController.FNetwork.SetRunAndFreq(False, XvtrTranslate(FController.FCenterFreq), XvtrTranslate(FController.FCenterFreq), 0); + FController.FNetwork.Disconnect; + FController.FNetwork.Free; + FController.FAudioOut.Close; + FController.FAudioOut.Free; + FController.FAudioIn.Close; + FController.FAudioIn.Free; + FController.FDSPEngine.Close; + FController.FDSPEngine.Free; FreeAndNil(FSpecView); FreeAndNil(FWidebandView); // Сохраняем настройки при закрытии - if FDevConnected then + if FController.FDevConnected then begin SaveCurrentBand; - FSettings.SaveGlobal(FDevMAC, MakeGlobalSettings); - if FDevConnected then - FSettings.SaveTX(FDevMAC, FTXSettings); + FController.FSettings.SaveGlobal(FController.FDevMAC, MakeGlobalSettings); + if FController.FDevConnected then + FController.FSettings.SaveTX(FController.FDevMAC, FController.FTXSettings); end; // Размер окна сохраняем всегда (не зависит от подключения) - FSettings.SaveStartupPreview(FVfoA, FVfoB, FSampleRate); - FSettings.Save; - FSettings.Free; - FreeAndNil(FChannelStore); + FController.FSettings.SaveStartupPreview(FController.FVfoA, FController.FVfoB, FController.FSampleRate); + FController.FSettings.Save; + FController.FSettings.Free; + FreeAndNil(FController.FChannelStore); FWebServer.Stop; FWebServer.Free; if Assigned(FCATTcp) then begin FCATTcp.Stop; FreeAndNil(FCATTcp); end; if Assigned(FCATSerial) then begin FCATSerial.StopAll; FreeAndNil(FCATSerial); end; FreeAndNil(FCATEngine); + // Движки (поля FController) уже освобождены выше. + // Освобождаем само ядро последним. + FreeAndNil(FController); +end; + +// Маршалинг команд контроллера в UI-поток (фронтенды зовут FController.Invoke). +// В режиме демона хозяин подставит другой механизм (очередь/прямой вызов). +procedure TMainForm.DoInvoke(M: TThreadMethod); +begin + TThread.Synchronize(nil, M); end; procedure TMainForm.FormClose(Sender: TObject; var CloseAction: TCloseAction); @@ -1463,18 +1416,18 @@ begin // В FormDestroy Qt-виджет может быть уже в нестабильном состоянии. SaveWindowBounds; // Останавливаем трансивер как при нажатии STOP - if FNetwork.Connected then + if FController.FNetwork.Connected then begin - if FRunning then + if FController.FRunning then begin - FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslate(ActiveTXFreqHz), 0, False, True, True); - FNetwork.SetRunAndFreq(False, XvtrTranslate(FCenterFreq), XvtrTranslate(FCenterFreq), 0); - FRunning := False; + FController.FNetwork.UpdateState(XvtrTranslate(FController.FCenterFreq), XvtrTranslate(ActiveTXFreqHz), 0, False, True, True); + FController.FNetwork.SetRunAndFreq(False, XvtrTranslate(FController.FCenterFreq), XvtrTranslate(FController.FCenterFreq), 0); + FController.FRunning := False; UninhibitSleep; - FTransmitting := False; - if FWDSPReady then FDSPEngine.SetTXRun(False); + FController.FTransmitting := False; + if FController.FWDSPReady then FController.FDSPEngine.SetTXRun(False); end; - FNetwork.Disconnect; + FController.FNetwork.Disconnect; end; CloseAction := caFree; end; @@ -1625,7 +1578,7 @@ begin FreqDispA.SetBounds(20, -2, TOP_VFO_W - 20, TOP_VFO_FREQ_H); FreqDispA.FontSize := TOP_VFO_FONT; FreqDispA.FontName := 'Courier New'; - FreqDispA.Frequency := Round(FVfoA); + FreqDispA.Frequency := Round(FController.FVfoA); FreqDispA.ColorNormal := CLR_FREQ; FreqDispA.ColorHover := TColor($0040DDFF); FreqDispA.ColorDim := CLR_TEXTDIM; @@ -1682,7 +1635,7 @@ begin FreqDispB.SetBounds(20, -2, TOP_VFO_W - 20, TOP_VFO_FREQ_H); FreqDispB.FontSize := TOP_VFO_FONT; FreqDispB.FontName := 'Courier New'; - FreqDispB.Frequency := Round(FVfoB); + FreqDispB.Frequency := Round(FController.FVfoB); FreqDispB.ColorNormal := CLR_FREQ_DIM; FreqDispB.ColorHover := TColor($0040DDFF); FreqDispB.ColorDim := CLR_TEXTDIM; @@ -1730,7 +1683,7 @@ begin LeftPanelButtonLeft(LEFT_W, 4, 0), 44, LeftPanelButtonWidth(LEFT_W, 4, 0), BTN_SM, BtnDUPClick); BtnDUP.Tag := 0; - StyleButton(BtnDUP, FDisplayDuplex); + StyleButton(BtnDUP, FController.FDisplayDuplex); LblDrv := TLabel.Create(Self); LblDrv.Parent := PanelTX; LblDrv.Left := 4; LblDrv.Top := 77; @@ -1757,7 +1710,7 @@ begin LeftPanelButtonWidth(LEFT_W, 3, 0), BTN_H, BtnCTunClick); BtnCTun.Tag := 0; - StyleButton(BtnCTun, FCTun); + StyleButton(BtnCTun, FController.FCTun); BtnChannels := MakeBtn(PanelRXBlock, 'Channel', LeftPanelButtonLeft(LEFT_W, 3, 1), 16, @@ -1775,7 +1728,7 @@ begin TrkVolume.Parent := PanelRXBlock; TrkVolume.Left := 34; TrkVolume.Top := 46; TrkVolume.Width := LEFT_W - 38; TrkVolume.Height := 16; TrkVolume.Min := 0; TrkVolume.Max := 100; - TrkVolume.Position := FVolume; + TrkVolume.Position := FController.FVolume; TrkVolume.OnChange := TrkVolumeChange; BtnNR := MakeBtn(PanelRXBlock, 'NR', @@ -1829,7 +1782,7 @@ begin LeftPanelButtonWidth(LEFT_W, 5, 4), BTN_SM, BtnAGCModeClick); BtnAGCMode[4].Tag := 4; - for i := 0 to 4 do StyleButton(BtnAGCMode[i], i = FAGCMode); + for i := 0 to 4 do StyleButton(BtnAGCMode[i], i = FController.FAGCMode); // AGC level slider MakeLbl(PanelAGC, 'THRESH', 4, 44); @@ -1837,14 +1790,14 @@ begin TrkAGC.Parent := PanelAGC; TrkAGC.Left := 56; TrkAGC.Top := 40; TrkAGC.Width := LEFT_W - 96; TrkAGC.Height := 16; TrkAGC.Min := 20; TrkAGC.Max := 120; // 20..120 → −20..-120 dBm - TrkAGC.Position := FAGCTop; + TrkAGC.Position := FController.FAGCTop; TrkAGC.Reversed := True; TrkAGC.OnChange := TrkAGCChange; LblAGCTop := TLabel.Create(Self); LblAGCTop.Parent := PanelAGC; LblAGCTop.Left := LEFT_W - 38; LblAGCTop.Top := 44; - LblAGCTop.Caption := Format('-%ddB', [FAGCTop]); + LblAGCTop.Caption := Format('-%ddB', [FController.FAGCTop]); LblAGCTop.Font.Color := CLR_TEXT; LblAGCTop.Font.Name := 'Courier New'; LblAGCTop.Font.Size := 7; @@ -1889,7 +1842,7 @@ begin BTN_SM, BtnModeClick); B.Tag := i; BtnMode[i] := B; - StyleButton(B, i = FMode); + StyleButton(B, i = FController.FMode); end; Inc(Y, 76); @@ -1934,7 +1887,7 @@ begin TrkFMSQ.Width := LEFT_W - TrkFMSQ.Left - 30 - 4; // leave 30px for label + margins TrkFMSQ.Height := 14; TrkFMSQ.Min := 0; TrkFMSQ.Max := 100; - TrkFMSQ.Position := FFMSQLevel; + TrkFMSQ.Position := FController.FFMSQLevel; TrkFMSQ.OnChange := TrkFMSQChange; // Numeric value label to the right of the slider LblFMSQ := TLabel.Create(Self); @@ -1942,7 +1895,7 @@ begin LblFMSQ.Left := TrkFMSQ.Left + TrkFMSQ.Width + 3; LblFMSQ.Top := 21; LblFMSQ.Width := 26; - LblFMSQ.Caption := IntToStr(FFMSQLevel); + LblFMSQ.Caption := IntToStr(FController.FFMSQLevel); LblFMSQ.Font.Color := CLR_TEXT; LblFMSQ.Font.Name := 'Courier New'; LblFMSQ.Font.Size := 8; @@ -1968,7 +1921,7 @@ begin FCTCSSDropDown := TFlatDropDown.Create(BtnFMCTCSSTone, PanelLeft, 4, BTN_SM); FCTCSSDropDown.SetItems(CTCSS_NAMES); - FCTCSSDropDown.SetItemIndex(FFMCTCSSToneIdx); + FCTCSSDropDown.SetItemIndex(FController.FFMCTCSSToneIdx); FCTCSSDropDown.OnSelect := OnCTCSSDropDownSelect; // FM Step panel (visible only in FM mode, between Filter and SQL) @@ -1982,8 +1935,8 @@ begin LeftPanelButtonLeft(LEFT_W, 3, 0), 16, LeftPanelButtonWidth(LEFT_W, 3, 0), BTN_H, BtnFMStepClick); - StyleButton(BtnFMStep, FFMStepOn); - BtnFMStepSel := MakeBtn(PanelFMStep, FM_STEP_NAMES[FFMStepIdx], + StyleButton(BtnFMStep, FController.FFMStepOn); + BtnFMStepSel := MakeBtn(PanelFMStep, FM_STEP_NAMES[FController.FFMStepIdx], LeftPanelButtonLeft(LEFT_W, 3, 1), 16, LEFT_W - LEFT_PANEL_BTN_PAD - LeftPanelButtonLeft(LEFT_W, 3, 1), @@ -1992,7 +1945,7 @@ begin FStepDropDown := TFlatDropDown.Create(BtnFMStepSel, PanelLeft, FM_STEP_COUNT, BTN_SM); FStepDropDown.SetItems(FM_STEP_NAMES); - FStepDropDown.SetItemIndex(FFMStepIdx); + FStepDropDown.SetItemIndex(FController.FFMStepIdx); FStepDropDown.OnSelect := OnStepDropDownSelect; // FM Repeater offset panel (visible only in FM mode, below CTCSS) @@ -2017,7 +1970,7 @@ begin EdFMRptOffset.Top := 18; EdFMRptOffset.Width := LeftPanelButtonLeft(LEFT_W, 5, 4) - W - LEFT_PANEL_BTN_GAP; EdFMRptOffset.Height := BTN_H - 4; - EdFMRptOffset.Text := RptFormatOffset(FFMRptOffsetHz); + EdFMRptOffset.Text := RptFormatOffset(FController.FFMRptOffsetHz); EdFMRptOffset.OnChange := EdFMRptOffsetChange; // ---- Right panel ---- @@ -2032,7 +1985,7 @@ begin FSampleRateOverlay.OnSpanSelect := OnSampleRateSelect; FSampleRateOverlay.OnHidePanel := OnSampleRateHidePanel; FSampleRateOverlay.OnInvalidate := OnSampleRateOverlayInvalidate; - FSampleRateOverlay.SetCurrentRate(FSampleRate); + FSampleRateOverlay.SetCurrentRate(FController.FSampleRate); FSpecView.SampleRateOverlay := FSampleRateOverlay; // S-метр — внутри PanelToolbar, справа @@ -2392,14 +2345,14 @@ var begin Result := False; if FWidebandView = nil then Exit; - if FActiveVfo = 0 then - MarkerChanged := FWidebandView.SetMarkerHz(FVfoA) + if FController.FActiveVfo = 0 then + MarkerChanged := FWidebandView.SetMarkerHz(FController.FVfoA) else - MarkerChanged := FWidebandView.SetMarkerHz(FVfoB); - if (FCurrentXvtr >= 0) and (FCurrentXvtr < CFG_XVTR_COUNT) and - FXvtrSettings.Entries[FCurrentXvtr].Enabled then + MarkerChanged := FWidebandView.SetMarkerHz(FController.FVfoB); + if (FController.FCurrentXvtr >= 0) and (FController.FCurrentXvtr < CFG_XVTR_COUNT) and + FController.FXvtrSettings.Entries[FController.FCurrentXvtr].Enabled then begin - E := FXvtrSettings.Entries[FCurrentXvtr]; + E := FController.FXvtrSettings.Entries[FController.FCurrentXvtr]; SrcStart := E.FreqBegin - E.LOOffset + E.LOError; SrcEnd := E.FreqEnd - E.LOOffset + E.LOError; ViewChanged := FWidebandView.SetFrequencyView(E.FreqBegin, E.FreqEnd, @@ -2717,8 +2670,8 @@ begin if FLightTheme = V then Exit; FLightTheme := V; ApplyDarkTheme; - FSettings.SaveTheme(V); - FSettings.Save; + FController.FSettings.SaveTheme(V); + FController.FSettings.Save; if (FSettingsForm <> nil) and TSettingsForm(FSettingsForm).Visible then TSettingsForm(FSettingsForm).LoadLightTheme(V); end; @@ -2800,8 +2753,8 @@ var T: TAppTheme; begin if FLightTheme then T := LightTheme else T := DarkTheme; - FreqDispA.Frequency := Round(FVfoA); - FreqDispB.Frequency := Round(FVfoB); + FreqDispA.Frequency := Round(FController.FVfoA); + FreqDispB.Frequency := Round(FController.FVfoB); FreqDispA.FontSize := TOP_VFO_FONT; FreqDispB.FontSize := TOP_VFO_FONT; FreqDispA.Height := TOP_VFO_FREQ_H; @@ -2814,7 +2767,7 @@ begin FreqDispB.ColorHoverBG := T.FreqHoverBG; FreqDispB.ColorDim := T.FreqDimDot; - if FActiveVfo = 0 then + if FController.FActiveVfo = 0 then begin FreqDispA.ColorNormal := T.FreqActive; FreqDispB.ColorNormal := T.FreqDim; @@ -2868,7 +2821,7 @@ begin end; if BtnTopVfoATX <> nil then begin - if not FSplitTxB then + if not FController.FSplitTxB then begin BtnTopVfoATX.ClrNorm := T.VfoTxActNorm; BtnTopVfoATX.ClrHot := T.VfoTxActNorm; @@ -2892,7 +2845,7 @@ begin end; if BtnTopVfoBTX <> nil then begin - if FSplitTxB then + if FController.FSplitTxB then begin BtnTopVfoBTX.ClrNorm := T.VfoTxActNorm; BtnTopVfoBTX.ClrHot := T.VfoTxActNorm; @@ -2931,23 +2884,23 @@ end; procedure TMainForm.SyncSpecViewFreq; begin - FSpecView.VfoA := FVfoA; - FSpecView.VfoB := FVfoB; - FSpecView.ActiveVfo := FActiveVfo; - FSpecView.CenterFreq := FCenterFreq; - FSpecView.SpanHz := FSpanHz; - FSpecView.Mode := FMode; - FSpecView.FilterBW := FFilterBW; - if FMode = MODE_FM then - FSpecView.FMGridStepHz := FM_STEP_HZ[FFMStepIdx] + FSpecView.VfoA := FController.FVfoA; + FSpecView.VfoB := FController.FVfoB; + FSpecView.ActiveVfo := FController.FActiveVfo; + FSpecView.CenterFreq := FController.FCenterFreq; + FSpecView.SpanHz := FController.FSpanHz; + FSpecView.Mode := FController.FMode; + FSpecView.FilterBW := FController.FFilterBW; + if FController.FMode = MODE_FM then + FSpecView.FMGridStepHz := FM_STEP_HZ[FController.FFMStepIdx] else FSpecView.FMGridStepHz := 0; // TX overlay следует за активной TX-частотой (учитывает split). При CTUN // спектр TX будет отрисован на VFO, а не в центре дисплея. FSpecView.TXFreq := ActiveTXFreqHz; - // Какой VFO привязан к TX (для split-полос фильтра): 1=B при FSplitTxB, иначе FActiveVfo. - if FSplitTxB then FSpecView.TXVfoIndex := 1 - else FSpecView.TXVfoIndex := FActiveVfo; + // Какой VFO привязан к TX (для split-полос фильтра): 1=B при FController.FSplitTxB, иначе FController.FActiveVfo. + if FController.FSplitTxB then FSpecView.TXVfoIndex := 1 + else FSpecView.TXVfoIndex := FController.FActiveVfo; if UpdateWidebandFrequencyView and FShowWideband and (PbWideband <> nil) then PbWideband.Invalidate; end; @@ -2971,7 +2924,7 @@ var PeakDiff, MinDiff: Double; PeakAlpha, MinAlpha: Double; begin - if not FRunning then + if not FController.FRunning then begin if PbSMeterRight <> nil then PbSMeterRight.Invalidate; Exit; @@ -2999,18 +2952,18 @@ begin FSpecView.SMeterMin := FSMeterMin; FSpecView.LastFwdW := FLastFwdW; FSpecView.LastSWR := FLastSWR; - FSpecView.Transmitting := FTransmitting; + FSpecView.Transmitting := FController.FTransmitting; if PbSMeterRight <> nil then PbSMeterRight.Invalidate; // PWR/SWR метки и бары обновляем здесь (10 Гц), а не в DoUpdateStatus — // HP Status пакеты идут ~150 раз/с, при таком rate цифры дёргаются. UpdateTXMeters; - if FWDSPReady then + if FController.FWDSPReady then begin - if FTransmitting then + if FController.FTransmitting then begin - if FDSPEngine.TXMicSource = txmsSoundCard then + if FController.FDSPEngine.TXMicSource = txmsSoundCard then SetStatusText(5, 'TX sound card') else SetStatusText(5, 'TX radio mic'); @@ -3043,7 +2996,7 @@ begin SyncSpecViewFreq; // Проверка таймаута — трансивер не отвечает - if FRunning and FNetwork.Running then + if FController.FRunning and FController.FNetwork.Running then begin NowTick := GetTickCount64; ElapsedTick := NowTick - FRXStartTime; @@ -3052,9 +3005,9 @@ begin begin BtnStartStop.Caption := 'START'; StyleButton(BtnStartStop, False); - FRunning := False; + FController.FRunning := False; UninhibitSleep; - FNetwork.SetRunAndFreq(False, XvtrTranslate(FCenterFreq), XvtrTranslate(FCenterFreq), 0); + FController.FNetwork.SetRunAndFreq(False, XvtrTranslate(FController.FCenterFreq), XvtrTranslate(FController.FCenterFreq), 0); SetRadioOfflineStatus('Hardware timeout'); BtnMOX.Enabled := False; Exit; @@ -3064,16 +3017,16 @@ begin begin BtnStartStop.Caption := 'START'; StyleButton(BtnStartStop, False); - FRunning := False; + FController.FRunning := False; UninhibitSleep; - FNetwork.SetRunAndFreq(False, XvtrTranslate(FCenterFreq), XvtrTranslate(FCenterFreq), 0); + FController.FNetwork.SetRunAndFreq(False, XvtrTranslate(FController.FCenterFreq), XvtrTranslate(FController.FCenterFreq), 0); SetRadioOfflineStatus('Connection lost'); BtnMOX.Enabled := False; Exit; end; end; - if FRunning then + if FController.FRunning then begin // Обновляем статус из таймера — без Synchronize в сетевом потоке. SetStatusText(4, 'RX running'); @@ -3082,25 +3035,25 @@ begin else SetStatusText(6, 'SEQ OK'); - if FWDSPReady then + if FController.FWDSPReady then begin - FLastSMeter := FDSPEngine.GetSMeterDBm; - FDSPEngine.SetSpectrumWidth(FSpectrumWidth); + FLastSMeter := FController.FDSPEngine.GetSMeterDBm; + FController.FDSPEngine.SetSpectrumWidth(FSpectrumWidth); Inc(FAgcLineCounter); if FAgcLineCounter >= 6 then begin - FDSPEngine.UpdateAGCLines(FSpectrumWidth); - FSpecView.AGCThresh := FDSPEngine.AGCThresh; - FSpecView.AGCHangLevel := FDSPEngine.AGCHangLevel; + FController.FDSPEngine.UpdateAGCLines(FSpectrumWidth); + FSpecView.AGCThresh := FController.FDSPEngine.AGCThresh; + FSpecView.AGCHangLevel := FController.FDSPEngine.AGCHangLevel; FAgcLineCounter := 0; end; end; - FSpecView.WDSPReady := FWDSPReady; + FSpecView.WDSPReady := FController.FWDSPReady; // Обновляем оверлей если видим if Assigned(FVfoOverlay) and FVfoOverlay.Visible then begin FVfoOverlay.UpdateSMeter(FLastSMeter); - FVfoOverlay.UpdateVfo(FVfoA); + FVfoOverlay.UpdateVfo(FController.FVfoA); PositionVfoOverlay; end; // Панорама и водопад теперь рисуются независимо по факту новых кадров. @@ -3128,7 +3081,7 @@ begin else begin // Не подключены — показываем чистый экран (без анимации) - if not FNetwork.Connected then + if not FController.FNetwork.Connected then begin // При первом старте показываем демо-спектр if FSpecView.SpectrumBitmapWidth <> FSpectrumWidth then @@ -3149,7 +3102,7 @@ begin end; // Если не в режиме RUN, но есть "грязный" кадр (drag/marker/UI), перерисуем. - if (FSpectrumDirty or FWaterfallDirty) and (not FRunning) then + if (FSpectrumDirty or FWaterfallDirty) and (not FController.FRunning) then begin if FSpectrumDirty then FSpecView.DrawSpectrum; @@ -3168,14 +3121,14 @@ begin // Пушим текущее состояние в веб-клиенты (если есть) if Assigned(FWebServer) then begin - if FNetwork.Connected then + if FController.FNetwork.Connected then begin - if FRunning then + if FController.FRunning then WebStatusText := 'Running' else WebStatusText := 'Connected'; - WebBoardText := 'Board: ' + BoardTypeName(FNetwork.Device.BoardType); - WebIPText := 'IP: ' + FNetwork.Device.IPAddress; + WebBoardText := 'Board: ' + BoardTypeName(FController.FNetwork.Device.BoardType); + WebIPText := 'IP: ' + FController.FNetwork.Device.IPAddress; end else begin @@ -3184,7 +3137,7 @@ begin WebIPText := 'IP --'; end; - if not FRunning then + if not FController.FRunning then begin WebSupplyText := 'Supply --'; WebPLLText := 'PLL --'; @@ -3196,7 +3149,7 @@ begin else WebPLLText := 'PLL?'; - if not BoardSupportsSupplyVoltage(FNetwork.Device.BoardType) then + if not BoardSupportsSupplyVoltage(FController.FNetwork.Device.BoardType) then WebSupplyText := 'Supply n/a' else if FLastSupplyV >= 0 then begin @@ -3209,21 +3162,21 @@ begin WebSupplyText := 'Supply --'; end; - if FRunning then + if FController.FRunning then WebRXText := 'RX running' else WebRXText := 'RX idle'; - if FTuning then + if FController.FTuning then WebTXText := 'TX tune' - else if FTransmitting then + else if FController.FTransmitting then WebTXText := 'TX active' else WebTXText := 'TX idle'; if FSeqErrorCount > 0 then WebSeqText := Format('SEQ ERR DDC%d', [FLastSeqErrorDDC]) - else if FRunning then + else if FController.FRunning then WebSeqText := 'SEQ OK' else WebSeqText := 'SEQ --'; @@ -3232,18 +3185,18 @@ begin FSpectrumBuf, 1024, FWaterfallBuf, FLastSMeter, - FVfoA, FMode, FFilterBW, FAGCMode, FAGCTop, - FSpanHz, FVolume, - FWfAGCEnabled, FWfNFEnabled, - FCurrentBand, - FRunning and FNetwork.Connected, - FRunning, FMuted, FCTun, + FController.FVfoA, FController.FMode, FController.FFilterBW, FController.FAGCMode, FController.FAGCTop, + FController.FSpanHz, FController.FVolume, + FController.FWfAGCEnabled, FController.FWfNFEnabled, + FController.FCurrentBand, + FController.FRunning and FController.FNetwork.Connected, + FController.FRunning, FController.FMuted, FController.FCTun, BtnNR.Tag, BtnNB.Tag, BtnSNB.Tag <> 0, BtnANF.Tag <> 0, - FCenterFreq, FFilter, - FVfoB, FActiveVfo, - FTransmitting, TrkDrive.Position, - FAtten, FTuning, FDisplayDuplex, - FLastFwdW, FLastSWR, FPAMaxPower, + FController.FCenterFreq, FController.FFilter, + FController.FVfoB, FController.FActiveVfo, + FController.FTransmitting, TrkDrive.Position, + FController.FAtten, FController.FTuning, FController.FDisplayDuplex, + FLastFwdW, FLastSWR, FController.FPAMaxPower, WebStatusText, WebBoardText, WebIPText, WebSupplyText, WebPLLText, WebRXText, WebTXText, WebSeqText); end; @@ -3310,29 +3263,29 @@ begin // Board 5 (Orion MkII): voltage on UserADC0 (bytes 57-58), // current on UserADC1 (bytes 55-56) — matching Thetis behaviour. // Other supported boards report voltage in SupplyVolts bytes and no current. - if BoardSupportsSupplyVoltage(FNetwork.Device.BoardType) then + if BoardSupportsSupplyVoltage(FController.FNetwork.Device.BoardType) then begin - if FNetwork.Device.BoardType = 5 then + if FController.FNetwork.Device.BoardType = 5 then SupplyV := ADCToSupplyVolts( (Status.UserADC0Hi shl 8) or Status.UserADC0Lo, - FNetwork.Device.BoardType) + FController.FNetwork.Device.BoardType) else SupplyV := ADCToSupplyVolts( (Status.SupplyVoltsHi shl 8) or Status.SupplyVoltsLo, - FNetwork.Device.BoardType); + FController.FNetwork.Device.BoardType); - if BoardSupportsSupplyCurrent(FNetwork.Device.BoardType) then + if BoardSupportsSupplyCurrent(FController.FNetwork.Device.BoardType) then SupplyA := ADCToSupplyCurrent( (Status.UserADC1Hi shl 8) or Status.UserADC1Lo, - FNetwork.Device.BoardType); + FController.FNetwork.Device.BoardType); end; // FLastSMeter обновляется только из WDSP (GetSMeterDBm) в SpectrumTimerTick — // ExciterPwr это мощность TX, не уровень принятого сигнала. FwdW := ADCToWatts100(FwdPwr); - // FTransmitting — наш программный флаг (MOX/TUN/HW-PTT через ApplyMOX). + // FController.FTransmitting — наш программный флаг (MOX/TUN/HW-PTT через ApplyMOX). // HPS_PTT-бит в StatusBits — это вход аппаратного PTT (footswitch/mic), // а не «трансивер сейчас передаёт», поэтому только на него полагаться нельзя. - IsTx := FTransmitting or ((Status.StatusBits and HPS_PTT) <> 0); + IsTx := FController.FTransmitting or ((Status.StatusBits and HPS_PTT) <> 0); // SWR: правильная формула (1+ρ)/(1-ρ), считаем только когда есть значимая // прямая мощность — иначе ADC-шум RevPwr даёт случайные «прыжки» SWR. // Не на передаче — SWR не показываем (фиксируем 1.0), Fwd принудительно 0. @@ -3420,10 +3373,10 @@ begin // Hardware PTT (foot switch / mic PTT) — обнаружение фронта (нужно делать // на rate HP Status, а не таймера, чтобы не пропустить короткое нажатие). - if HWPTT <> FHWPTTActive then + if HWPTT <> FController.FHWPTTActive then begin - FHWPTTActive := HWPTT; - if not FTuning then + FController.FHWPTTActive := HWPTT; + if not FController.FTuning then begin if HWPTT then begin @@ -3431,15 +3384,15 @@ begin if not BtnMOX.Active then begin ApplyMOX(True); - FHWPTTStartedTX := True; + FController.FHWPTTStartedTX := True; end; end else begin // Release: выключаем TX только если он был включён через HWPTT. // Нельзя проверять BtnMOX.Active — ApplyMOX(True) сам его выставляет. - if FHWPTTStartedTX then + if FController.FHWPTTStartedTX then begin - FHWPTTStartedTX := False; + FController.FHWPTTStartedTX := False; ApplyMOX(False); end; end; @@ -3452,7 +3405,7 @@ procedure TMainForm.UpdateTXMeters; // FLastSupplyV/FLastSupplyA обновляются на rate HP Status (~150/с) // в DoUpdateStatus. begin - if not FRunning then + if not FController.FRunning then begin SetStatusText(3, 'Supply --'); SetStatusText(7, 'PLL --'); @@ -3464,7 +3417,7 @@ begin else SetStatusText(7, 'PLL?'); - if not BoardSupportsSupplyVoltage(FNetwork.Device.BoardType) then + if not BoardSupportsSupplyVoltage(FController.FNetwork.Device.BoardType) then begin SetStatusText(3, 'Supply n/a'); Exit; @@ -3529,12 +3482,12 @@ begin end; // 1. Подаём IQ данные в DSP — только с активного DDC - if (DDCIndex = FActiveDDC) and FWDSPReady then + if (DDCIndex = FActiveDDC) and FController.FWDSPReady then begin SamplesPerFrame := (Integer(Data.SamplesPerFrame[0]) shl 8) or Integer(Data.SamplesPerFrame[1]); if SamplesPerFrame <= 0 then SamplesPerFrame := 238; // fallback - FDSPEngine.PushDDCPacket(Data.IQData, 0, SamplesPerFrame); + FController.FDSPEngine.PushDDCPacket(Data.IQData, 0, SamplesPerFrame); end; // 2. Запоминаем последний seq — статус обновит таймер (без Synchronize в горячем пути) @@ -3559,9 +3512,9 @@ procedure TMainForm.OnMicPacketCB(const Data: TMicDataPacket); begin // HW mic-стрим уходит в WDSP только если выбран источник Radio. // Иначе сэмплы игнорируются (mic берётся со звуковой карты). - if FTransmitting and FWDSPReady and - (FDSPEngine.TXMicSource = txmsRadio) then - FDSPEngine.PushTXMicSamples16(Data.Samples, 64); + if FController.FTransmitting and FController.FWDSPReady and + (FController.FDSPEngine.TXMicSource = txmsRadio) then + FController.FDSPEngine.PushTXMicSamples16(Data.Samples, 64); end; // --------------------------------------------------------------------------- @@ -3578,13 +3531,13 @@ function TMainForm.BuildMicLineSelectByte: Byte; // bit 5 — Saturn XLR/balanced (не используем) begin Result := 0; - if FTXSettings.MicLineIn then + if FController.FTXSettings.MicLineIn then Result := Result or $01 // bit 0: Line In - else if FTXSettings.MicBoost then + else if FController.FTXSettings.MicBoost then Result := Result or $02; // bit 1: Boost только при Mic In - if not FTXSettings.MicPTTEnabled then Result := Result or $04; // bit 2: инверсия - if FTXSettings.MicTipRing then Result := Result or $08; // bit 3: Ring - if FTXSettings.MicBias then Result := Result or $10; // bit 4: bias + if not FController.FTXSettings.MicPTTEnabled then Result := Result or $04; // bit 2: инверсия + if FController.FTXSettings.MicTipRing then Result := Result or $08; // bit 3: Ring + if FController.FTXSettings.MicBias then Result := Result or $10; // bit 4: bias end; procedure TMainForm.SendDUCSpecificFromSettings; @@ -3592,10 +3545,10 @@ var DUCPkt: TDUCSpecificPacket; RateKsps: Word; begin - if not (FNetwork.Connected and FNetwork.Running) then Exit; + if not (FController.FNetwork.Connected and FController.FNetwork.Running) then Exit; FillChar(DUCPkt, SizeOf(DUCPkt), 0); // DUC0 rate в ksps берём из WDSP-движка (фиксированный TX rate, не RX). - RateKsps := FDSPEngine.TXSampleRate div 1000; + RateKsps := FController.FDSPEngine.TXSampleRate div 1000; DUCPkt.NumDACs := 1; DUCPkt.SidetoneLevel := 50; DUCPkt.SidetoneFreqHi := Hi(600); @@ -3608,14 +3561,14 @@ begin DUCPkt.MicLineSelect := BuildMicLineSelectByte; // Line In Gain (byte 51): 0=-34.5dB .. 31=+12dB (шаг 1.5 dB, формула Thetis). // При Mic In значение игнорируется железом, но шлём корректное для Line In. - DUCPkt.LineInGain := Byte(EnsureRange(Round((FTXSettings.LineInGainDB + 34.5) / 1.5), 0, 31)); + DUCPkt.LineInGain := Byte(EnsureRange(Round((FController.FTXSettings.LineInGainDB + 34.5) / 1.5), 0, 31)); // ATT on TX — RX-ADC step attenuator во время передачи (Thetis-style protection). // Дублируем значение во все три ADC-байта: на однопотоковых платах активен только // ADC0, на Saturn/Hermes Lite 2 — могут использоваться ADC1/ADC2. - DUCPkt.StepAtten0 := Byte(EnsureRange(FTXSettings.AttOnTX, 0, 31)); + DUCPkt.StepAtten0 := Byte(EnsureRange(FController.FTXSettings.AttOnTX, 0, 31)); DUCPkt.StepAtten1 := DUCPkt.StepAtten0; DUCPkt.StepAtten2 := DUCPkt.StepAtten0; - FNetwork.SendDUCSpecific(DUCPkt); + FController.FNetwork.SendDUCSpecific(DUCPkt); end; function TMainForm.DefaultMicSource: TTXMicSource; @@ -3623,46 +3576,46 @@ function TMainForm.DefaultMicSource: TTXMicSource; // иначе используем HW mic-стрим от трансивера. // HW PTT всегда переключает на txmsRadio непосредственно в ApplyMOX. begin - if FAudioInDevName <> '' then Result := txmsSoundCard + if FController.FAudioInDevName <> '' then Result := txmsSoundCard else Result := txmsRadio; end; procedure TMainForm.ApplyTXSettingsToDSP; begin - if not FWDSPReady then Exit; + if not FController.FWDSPReady then Exit; // Mic source — выставляем значение по умолчанию для MOX-кнопки; // при реальном PTT ApplyMOX скорректирует источник. - FDSPEngine.SetTXMicSource(DefaultMicSource); - FDSPEngine.SetMicGain(FTXSettings.MicGainDB); - FDSPEngine.SetTXFilterFull(FTXSettings.FilterLow, FTXSettings.FilterHigh, - FTXSettings.FilterNC, FTXSettings.FilterMP, - FTXSettings.FilterWindow); - FDSPEngine.SetTXCompressor(FTXSettings.CompressorOn, FTXSettings.CompressorGain); - FDSPEngine.SetTXLeveler(FTXSettings.LevelerOn, FTXSettings.LevelerTop, - FTXSettings.LevelerDecay); - FDSPEngine.SetTXALC(FTXSettings.ALCOn, FTXSettings.ALCMaxGain, - FTXSettings.ALCDecay); - FDSPEngine.SetTXPhaseRot(FTXSettings.PhaseRotOn, FTXSettings.PhaseRotStages, - FTXSettings.PhaseRotFreq); - FDSPEngine.SetTXEQ(FTXSettings.EQOn, FTXSettings.EQNumBands, - FTXSettings.EQGains, FTXSettings.EQFreqs); - FDSPEngine.SetTXAMCarrierLevel(FTXSettings.AMCarrierLevel); - FDSPEngine.SetTXFMParams(FTXSettings.FMDeviation, FTXSettings.FMLowCut, - FTXSettings.FMHighCut, FTXSettings.FMEmphPosition); - FDSPEngine.SetTXCTCSS(FTXSettings.CTCSSOn, FTXSettings.CTCSSFreq); + FController.FDSPEngine.SetTXMicSource(DefaultMicSource); + FController.FDSPEngine.SetMicGain(FController.FTXSettings.MicGainDB); + FController.FDSPEngine.SetTXFilterFull(FController.FTXSettings.FilterLow, FController.FTXSettings.FilterHigh, + FController.FTXSettings.FilterNC, FController.FTXSettings.FilterMP, + FController.FTXSettings.FilterWindow); + FController.FDSPEngine.SetTXCompressor(FController.FTXSettings.CompressorOn, FController.FTXSettings.CompressorGain); + FController.FDSPEngine.SetTXLeveler(FController.FTXSettings.LevelerOn, FController.FTXSettings.LevelerTop, + FController.FTXSettings.LevelerDecay); + FController.FDSPEngine.SetTXALC(FController.FTXSettings.ALCOn, FController.FTXSettings.ALCMaxGain, + FController.FTXSettings.ALCDecay); + FController.FDSPEngine.SetTXPhaseRot(FController.FTXSettings.PhaseRotOn, FController.FTXSettings.PhaseRotStages, + FController.FTXSettings.PhaseRotFreq); + FController.FDSPEngine.SetTXEQ(FController.FTXSettings.EQOn, FController.FTXSettings.EQNumBands, + FController.FTXSettings.EQGains, FController.FTXSettings.EQFreqs); + FController.FDSPEngine.SetTXAMCarrierLevel(FController.FTXSettings.AMCarrierLevel); + FController.FDSPEngine.SetTXFMParams(FController.FTXSettings.FMDeviation, FController.FTXSettings.FMLowCut, + FController.FTXSettings.FMHighCut, FController.FTXSettings.FMEmphPosition); + FController.FDSPEngine.SetTXCTCSS(FController.FTXSettings.CTCSSOn, FController.FTXSettings.CTCSSFreq); // TX display - FDSPEngine.SetTXFFTParams(FTXSettings.TXFFTSize, FTXSettings.TXWindowType); - FDSPEngine.SetTXSpectrumDisplay(FTXSettings.TXSpecDetector, - FTXSettings.TXSpecAvgMode, - FTXSettings.TXSpecAvgTimeMS); - FDSPEngine.SetTXWaterfallDisplay(FTXSettings.TXWfDetector, - FTXSettings.TXWfAvgMode, - FTXSettings.TXWfAvgTimeMS); + FController.FDSPEngine.SetTXFFTParams(FController.FTXSettings.TXFFTSize, FController.FTXSettings.TXWindowType); + FController.FDSPEngine.SetTXSpectrumDisplay(FController.FTXSettings.TXSpecDetector, + FController.FTXSettings.TXSpecAvgMode, + FController.FTXSettings.TXSpecAvgTimeMS); + FController.FDSPEngine.SetTXWaterfallDisplay(FController.FTXSettings.TXWfDetector, + FController.FTXSettings.TXWfAvgMode, + FController.FTXSettings.TXWfAvgTimeMS); end; function TMainForm.PullSoundCardMic(MaxN: Integer): Integer; // Колбэк, который TTXDSPThread зовёт при TXMicSource=txmsSoundCard. -// Тянем только реально доступные сэмплы из FAudioIn — иначе мы пушили бы +// Тянем только реально доступные сэмплы из FController.FAudioIn — иначе мы пушили бы // нули и mic-цепь в WDSP получала бы тишину вместо реального сигнала. // Читаем пачкой в локальный буфер и пушим одним вызовом — экономим // будилки семафора и предотвращаем подмену TX-блока тишиной. @@ -3671,14 +3624,14 @@ var Buf: array[0..2047] of Double; begin Result := 0; - if (FAudioIn = nil) or (not FAudioIn.IsOpen) then Exit; - n := FAudioIn.Available; + if (FController.FAudioIn = nil) or (not FController.FAudioIn.IsOpen) then Exit; + n := FController.FAudioIn.Available; if n > MaxN then n := MaxN; if n > Length(Buf) then n := Length(Buf); for i := 0 to n - 1 do - Buf[i] := FAudioIn.ReadSample; + Buf[i] := FController.FAudioIn.ReadSample; if n > 0 then - FDSPEngine.PushTXMicSamplesD(Buf, n); + FController.FDSPEngine.PushTXMicSamplesD(Buf, n); Result := n; end; @@ -3713,7 +3666,7 @@ var M: TThreadMethod; begin try - FResult := FForm.FDSPEngine.Open; + FResult := FForm.FController.FDSPEngine.Open; except FResult := False; end; @@ -3785,17 +3738,17 @@ end; procedure TMainForm.RecreateDSPEngine(ASampleRate: Integer); begin if ASampleRate <= 0 then Exit; - if Assigned(FDSPEngine) then + if Assigned(FController.FDSPEngine) then begin - FDSPEngine.Close; - FreeAndNil(FDSPEngine); + FController.FDSPEngine.Close; + FreeAndNil(FController.FDSPEngine); end; - FWDSPReady := False; - FDSPEngine := TWDSPEngine.Create(ASampleRate, 48000, 512); - FDSPEngine.OnAudio := OnAudioReady; - FDSPEngine.OnSpectrum := OnSpectrumReady; - FDSPEngine.OnWaterfall := OnWaterfallReady; - FDSPEngine.OnTXIQ := OnTXIQReady; + FController.FWDSPReady := False; + FController.FDSPEngine := TWDSPEngine.Create(ASampleRate, 48000, 512); + FController.FDSPEngine.OnAudio := OnAudioReady; + FController.FDSPEngine.OnSpectrum := OnSpectrumReady; + FController.FDSPEngine.OnWaterfall := OnWaterfallReady; + FController.FDSPEngine.OnTXIQ := OnTXIQReady; end; function TMainForm.EnsureWDSPWisdom: Boolean; @@ -3882,8 +3835,8 @@ begin SetLength(FDevices, 0); FDeviceCount := 0; SetStatusText(2, 'Discovering...'); - FNetwork.DirectIP := ''; - TDiscoverThread.Create(FNetwork, Self); + FController.FNetwork.DirectIP := ''; + TDiscoverThread.Create(FController.FNetwork, Self); end; procedure TMainForm.BtnStartStopClick(Sender: TObject); @@ -3898,38 +3851,38 @@ var FoundByIP: Boolean; AutoIP: string; begin - if FNetwork.Connected then + if FController.FNetwork.Connected then begin // --- STOP --- - if FRunning then + if FController.FRunning then begin - FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslate(ActiveTXFreqHz), 0, False, True, True); - FNetwork.SetRunAndFreq(False, XvtrTranslate(FCenterFreq), XvtrTranslate(FCenterFreq), 0); - FRunning := False; + FController.FNetwork.UpdateState(XvtrTranslate(FController.FCenterFreq), XvtrTranslate(ActiveTXFreqHz), 0, False, True, True); + FController.FNetwork.SetRunAndFreq(False, XvtrTranslate(FController.FCenterFreq), XvtrTranslate(FController.FCenterFreq), 0); + FController.FRunning := False; UninhibitSleep; - FTransmitting := False; - if FWDSPReady then FDSPEngine.SetTXRun(False); + FController.FTransmitting := False; + if FController.FWDSPReady then FController.FDSPEngine.SetTXRun(False); StyleButton(BtnMOX, False); end; FRXPacketCount := 0; FActiveDDC := 0; // Сохраняем текущую частоту/настройки чтобы следующий START восстановил их - if FDevConnected then + if FController.FDevConnected then begin SaveCurrentBand; - FSettings.SaveGlobal(FDevMAC, MakeGlobalSettings); - FSettings.SaveAlex(FDevMAC, FAlexSettings); + FController.FSettings.SaveGlobal(FController.FDevMAC, MakeGlobalSettings); + FController.FSettings.SaveAlex(FController.FDevMAC, FController.FAlexSettings); // XVTR LastFreq → актуальная видимая частота в момент остановки - if (FCurrentXvtr >= 0) and (FCurrentXvtr < CFG_XVTR_COUNT) then - FXvtrSettings.Entries[FCurrentXvtr].LastFreq := FVfoA; - FSettings.SaveXvtr(FDevMAC, FXvtrSettings); + if (FController.FCurrentXvtr >= 0) and (FController.FCurrentXvtr < CFG_XVTR_COUNT) then + FController.FXvtrSettings.Entries[FController.FCurrentXvtr].LastFreq := FController.FVfoA; + FController.FSettings.SaveXvtr(FController.FDevMAC, FController.FXvtrSettings); end; - FSettings.SaveStartupPreview(FVfoA, FVfoB, FSampleRate); - FSettings.Save; + FController.FSettings.SaveStartupPreview(FController.FVfoA, FController.FVfoB, FController.FSampleRate); + FController.FSettings.Save; FSpecView.ResetSpectrumBuf; FSpecView.DrawSpectrum; PbSpectrum.Invalidate; - FNetwork.Disconnect; + FController.FNetwork.Disconnect; BtnStartStop.Caption := 'START'; StyleButton(BtnStartStop, False); SetRadioOfflineStatus('Disconnected', True); @@ -3984,21 +3937,21 @@ begin // 1) Open на 192k, 2) сразу ChangeSampleRate+Open ещё раз. // Если устройство уже было обнаружено (есть MAC) и есть сохранённый профиль, // создаём WDSPEngine сразу с нужной частотой. - PreloadRate := FSampleRate; + PreloadRate := FController.FSampleRate; FoundByIP := False; for i := 0 to High(FDevices) do if SameText(FDevices[i].Dev.IPAddress, Dev.IPAddress) then begin FoundByIP := True; - if FSettings.LoadDevice(FDevices[i].Dev.MAC, PreG, PreBands) and (PreG.SampleRate > 0) then + if FController.FSettings.LoadDevice(FDevices[i].Dev.MAC, PreG, PreBands) and (PreG.SampleRate > 0) then PreloadRate := PreG.SampleRate; Break; end; if FoundByIP and (PreloadRate > 0) then begin - FSampleRate := PreloadRate; - FSpanHz := PreloadRate; - if (not FWDSPReady) and (Assigned(FDSPEngine)) and (FDSPEngine.SampleRate <> PreloadRate) then + FController.FSampleRate := PreloadRate; + FController.FSpanHz := PreloadRate; + if (not FController.FWDSPReady) and (Assigned(FController.FDSPEngine)) and (FController.FDSPEngine.SampleRate <> PreloadRate) then RecreateDSPEngine(PreloadRate); end; @@ -4006,7 +3959,7 @@ begin FPendingDev := Dev; BtnStartStop.Enabled := False; SetStatusText(2, 'Opening DSP...'); - if FWDSPReady then + if FController.FWDSPReady then // WDSP уже открыт — сразу подключаемся DoConnectDevice(Dev) else @@ -4017,7 +3970,7 @@ end; procedure TMainForm.OnWDSPOpenDone(Success: Boolean); begin // Вызывается из TWDSPOpenThread.SyncDone — уже в главном потоке - FWDSPReady := Success; + FController.FWDSPReady := Success; if not Success then begin SetStatusText(2, 'DSP failed'); @@ -4025,7 +3978,7 @@ begin BtnStartStop.Caption := 'START'; StyleButton(BtnStartStop, False); ShowMessage('WDSP not loaded.' + LineEnding + - IfThen(FDSPEngine.LastError <> '', FDSPEngine.LastError, 'Unknown WDSP init error.') + LineEnding + + IfThen(FController.FDSPEngine.LastError <> '', FController.FDSPEngine.LastError, 'Unknown WDSP init error.') + LineEnding + 'Put wdsp.dll рядом с ewsdr.exe (той же разрядности, что и приложение).'); Exit; end; @@ -4040,68 +3993,68 @@ var begin BtnStartStop.Enabled := True; - if not FNetwork.Connect(Dev) then + if not FController.FNetwork.Connect(Dev) then begin ShowMessage('Failed to connect to ' + Dev.IPAddress + - IfThen(FNetwork.LastError <> '', ': ' + FNetwork.LastError, '')); + IfThen(FController.FNetwork.LastError <> '', ': ' + FController.FNetwork.LastError, '')); Exit; end; // --- Загружаем настройки устройства по MAC --- - Move(FNetwork.Device.MAC[0], FDevMAC[0], 6); - FDevConnected := True; - FSettings.LoadDevice(FDevMAC, G_Settings, FBandCache); + Move(FController.FNetwork.Device.MAC[0], FController.FDevMAC[0], 6); + FController.FDevConnected := True; + FController.FSettings.LoadDevice(FController.FDevMAC, G_Settings, FController.FBandCache); FCATLastGlobal := G_Settings; CATApplySettings(G_Settings); // TX-настройки per-device — отдельная JSON-секция "tx" - FSettings.LoadTX(FDevMAC, FTXSettings); + FController.FSettings.LoadTX(FController.FDevMAC, FController.FTXSettings); // Alex-настройки per-device (антенны/маршрутизация) - FSettings.LoadAlex(FDevMAC, FAlexSettings); - FNetwork.SetAlexConfig(FAlexSettings); + FController.FSettings.LoadAlex(FController.FDevMAC, FController.FAlexSettings); + FController.FNetwork.SetAlexConfig(FController.FAlexSettings); // XVTR-настройки per-device (трансвертеры) - FSettings.LoadXvtr(FDevMAC, FXvtrSettings); - FCurrentXvtr := -1; + FController.FSettings.LoadXvtr(FController.FDevMAC, FController.FXvtrSettings); + FController.FCurrentXvtr := -1; RebuildXvtrButtons; ApplyXvtrToNetwork; PushXvtrToWeb; - FTXSpecRefLevel := FTXSettings.TXSpecRefLevel; - FTXSpecRange := FTXSettings.TXSpecRange; - if FTXSettings.TXSpecGridStep > 0 then - FTXSpecGridStep := FTXSettings.TXSpecGridStep; - FVolume := G_Settings.Volume; - FActiveVfo := G_Settings.ActiveVfo; + FTXSpecRefLevel := FController.FTXSettings.TXSpecRefLevel; + FTXSpecRange := FController.FTXSettings.TXSpecRange; + if FController.FTXSettings.TXSpecGridStep > 0 then + FTXSpecGridStep := FController.FTXSettings.TXSpecGridStep; + FController.FVolume := G_Settings.Volume; + FController.FActiveVfo := G_Settings.ActiveVfo; // PA settings - FPAMaxPower := G_Settings.PAMaxPower; + FController.FPAMaxPower := G_Settings.PAMaxPower; for i := 0 to BAND_COUNT - 1 do - FPABandCal[i] := G_Settings.PABandCal[i]; + FController.FPABandCal[i] := G_Settings.PABandCal[i]; for i := 0 to CFG_XVTR_COUNT - 1 do - FVHFBandCal[i] := G_Settings.VHFBandCal[i]; + FController.FVHFBandCal[i] := G_Settings.VHFBandCal[i]; // Slider position (0..100) and calibrated drive byte TrkDrive.Position := EnsureRange(G_Settings.DriveLevel, 0, 100); - FCurrentBand := G_Settings.LastBand; - FDriveLevel := CalcDriveByte; + FController.FCurrentBand := G_Settings.LastBand; + FController.FDriveLevel := CalcDriveByte; // SampleRate — глобальный, загружаем до RestoreBand if G_Settings.SampleRate > 0 then begin - FSampleRate := G_Settings.SampleRate; - FSpanHz := FSampleRate; + FController.FSampleRate := G_Settings.SampleRate; + FController.FSpanHz := FController.FSampleRate; if Assigned(FSampleRateOverlay) then - FSampleRateOverlay.SetCurrentRate(FSampleRate); + FSampleRateOverlay.SetCurrentRate(FController.FSampleRate); end; BtnNR.Tag := EnsureRange(G_Settings.NRMode, 0, 4); UpdateNRButton; BtnNB.Tag := EnsureRange(G_Settings.NBMode, 0, 2); UpdateNBButton; BtnSNB.Tag := Ord(G_Settings.SNBEnabled); UpdateSNBButton; BtnANF.Tag := Ord(G_Settings.ANFEnabled); UpdateANFButton; - FWfAGCEnabled := G_Settings.WfAGCEnabled; - FWfNFEnabled := G_Settings.WfNFEnabled; - FDitherEnabled := G_Settings.DitherEnabled; - FRandomEnabled := G_Settings.RandomEnabled; - FSendAudioToRadio := G_Settings.SendAudioToRadio; + FController.FWfAGCEnabled := G_Settings.WfAGCEnabled; + FController.FWfNFEnabled := G_Settings.WfNFEnabled; + FController.FDitherEnabled := G_Settings.DitherEnabled; + FController.FRandomEnabled := G_Settings.RandomEnabled; + FController.FSendAudioToRadio := G_Settings.SendAudioToRadio; FWfManualHigh := G_Settings.WfManualHigh; FWfManualLow := G_Settings.WfManualLow; FWfAGCOffset := G_Settings.WfAGCOffset; - FSpecView.WfAGCEnabled := FWfAGCEnabled; - FSpecView.WfNFEnabled := FWfNFEnabled; + FSpecView.WfAGCEnabled := FController.FWfAGCEnabled; + FSpecView.WfNFEnabled := FController.FWfNFEnabled; FSpecView.WfManualHigh := FWfManualHigh; FSpecView.WfManualLow := FWfManualLow; FSpecView.WfAGCOffset := FWfAGCOffset; @@ -4114,8 +4067,8 @@ begin FWidebandView.SetFillSpectrum(FWidebandFill); FSpectrumFill := G_Settings.SpectrumFill; FSpecView.FillSpectrum := FSpectrumFill; - FDisplayDuplex := G_Settings.DisplayDuplex; - if BtnDUP <> nil then StyleButton(BtnDUP, FDisplayDuplex); + FController.FDisplayDuplex := G_Settings.DisplayDuplex; + if BtnDUP <> nil then StyleButton(BtnDUP, FController.FDisplayDuplex); if G_Settings.DisplayFPS > 0 then ApplyFPS(G_Settings.DisplayFPS); ApplyFreqMhzDigits(EnsureRange(G_Settings.FreqMhzDigits, 3, 5)); @@ -4129,55 +4082,55 @@ begin FSpecView.SpecRange := FSpecRange; FSpecView.SpecGridStep := FSpecGridStep; InvalidateGridCache; - if FWDSPReady then + if FController.FWDSPReady then begin - FDSPEngine.SetFFTParams(G_Settings.FFTSize, G_Settings.WindowType); - FDSPEngine.SetSpectrumDisplay(G_Settings.SpecDetector, G_Settings.SpecAvgMode, + FController.FDSPEngine.SetFFTParams(G_Settings.FFTSize, G_Settings.WindowType); + FController.FDSPEngine.SetSpectrumDisplay(G_Settings.SpecDetector, G_Settings.SpecAvgMode, G_Settings.SpecAvgTimeMS); - FDSPEngine.SetWaterfallDisplay(G_Settings.WfDetector, G_Settings.WfAvgMode, + FController.FDSPEngine.SetWaterfallDisplay(G_Settings.WfDetector, G_Settings.WfAvgMode, G_Settings.WfAvgTimeMS); end; // Аудио устройство вывода - FAudioOut.Close; + FController.FAudioOut.Close; if G_Settings.AudioSampleRate > 0 then - FAudioOut.SampleRate := G_Settings.AudioSampleRate; + FController.FAudioOut.SampleRate := G_Settings.AudioSampleRate; if G_Settings.AudioOutDevice <> '' then begin - FAudioOut.DeviceIndex := FAudioOut.FindDeviceByName(G_Settings.AudioOutDevice); - FAudioOutDevName := G_Settings.AudioOutDevice; + FController.FAudioOut.DeviceIndex := FController.FAudioOut.FindDeviceByName(G_Settings.AudioOutDevice); + FController.FAudioOutDevName := G_Settings.AudioOutDevice; end else begin - FAudioOut.DeviceIndex := -1; - FAudioOutDevName := ''; + FController.FAudioOut.DeviceIndex := -1; + FController.FAudioOutDevName := ''; end; - FAudioOut.Open; + FController.FAudioOut.Open; // Аудио устройство ввода (TX mic). MicSource (Radio/SoundCard) определяется // автоматически в ApplyMOX по источнику PTT — здесь только открываем устройство. if G_Settings.AudioInDevice <> '' then begin - FAudioIn.DeviceIndex := FAudioIn.FindDeviceByName(G_Settings.AudioInDevice); - FAudioInDevName := G_Settings.AudioInDevice; - FAudioIn.Open; + FController.FAudioIn.DeviceIndex := FController.FAudioIn.FindDeviceByName(G_Settings.AudioInDevice); + FController.FAudioInDevName := G_Settings.AudioInDevice; + FController.FAudioIn.Open; end else begin - FAudioIn.DeviceIndex := -1; - FAudioInDevName := ''; + FController.FAudioIn.DeviceIndex := -1; + FController.FAudioInDevName := ''; end; end; - RestoreBand(FCurrentBand); + RestoreBand(FController.FCurrentBand); // Восстанавливаем XVTR-режим, если он был активен при выходе. - // FCurrentBand уже загружен (HF-диапазон, на который пользователь зашёл - // до XVTR), его FBandCache не пострадает — ActivateXvtrBand только - // выставит FVfoA на XVTR-частоту и FCurrentXvtr. + // FController.FCurrentBand уже загружен (HF-диапазон, на который пользователь зашёл + // до XVTR), его FController.FBandCache не пострадает — ActivateXvtrBand только + // выставит FController.FVfoA на XVTR-частоту и FController.FCurrentXvtr. if (G_Settings.LastXvtr >= 0) and (G_Settings.LastXvtr < CFG_XVTR_COUNT) - and FXvtrSettings.Entries[G_Settings.LastXvtr].Enabled then + and FController.FXvtrSettings.Entries[G_Settings.LastXvtr].Enabled then ActivateXvtrBand(G_Settings.LastXvtr); - SetStatusText(1, 'IP: ' + FNetwork.Device.IPAddress); - SetStatusText(0, 'Board: ' + BoardTypeName(FNetwork.Device.BoardType)); + SetStatusText(1, 'IP: ' + FController.FNetwork.Device.IPAddress); + SetStatusText(0, 'Board: ' + BoardTypeName(FController.FNetwork.Device.BoardType)); // General packet FillChar(GenPkt, SizeOf(GenPkt), 0); @@ -4193,44 +4146,44 @@ begin GenPkt.WBSampleSize := 16; GenPkt.WBUpdateRate := 70; GenPkt.WBPacketsPerFrame := 32; - if FNetwork.Device.BoardType = 5 then + if FController.FNetwork.Device.BoardType = 5 then GenPkt.AlexEnable := $03 else GenPkt.AlexEnable := $01; - FNetwork.SendGeneralPacket(GenPkt); - FNetwork.ConfigureWideband(0, FShowWideband, 512, 16, 70, 32); + FController.FNetwork.SendGeneralPacket(GenPkt); + FController.FNetwork.ConfigureWideband(0, FShowWideband, 512, 16, 70, 32); - if FNetwork.Device.BoardType in [3, 4, 5] then + if FController.FNetwork.Device.BoardType in [3, 4, 5] then FActiveDDC := 2 else FActiveDDC := 0; // Синхронизируем samplerate WDSP если нужно - if FWDSPReady and (FDSPEngine.SampleRate <> FSampleRate) then + if FController.FWDSPReady and (FController.FDSPEngine.SampleRate <> FController.FSampleRate) then begin - FWDSPReady := False; - FDSPEngine.ChangeSampleRate(FSampleRate); - FWDSPReady := FDSPEngine.Initialized; + FController.FWDSPReady := False; + FController.FDSPEngine.ChangeSampleRate(FController.FSampleRate); + FController.FWDSPReady := FController.FDSPEngine.Initialized; end; - if FWDSPReady then + if FController.FWDSPReady then begin - FDSPEngine.SetMode(FMode); - FDSPEngine.SetVolume(FVolume / 100.0); + FController.FDSPEngine.SetMode(FController.FMode); + FController.FDSPEngine.SetVolume(FController.FVolume / 100.0); ApplyModeFilter; ApplyNoiseFilterButtonsToDSP; // ChangeSampleRate пересоздаёт WDSP-канал — восстанавливаем все настройки - FDSPEngine.SetAGCTop(FAGCTop); - FDSPEngine.SetAGC(TWDSPAGCMode(FAGCMode), 50.0); + FController.FDSPEngine.SetAGCTop(FController.FAGCTop); + FController.FDSPEngine.SetAGC(TWDSPAGCMode(FController.FAGCMode), 50.0); end; - FSpecView.AGCTop := FAGCTop; + FSpecView.AGCTop := FController.FAGCTop; // Сначала отправляем Run=1 — эмулятор/железо создают ddc_specific_thread // (порт 1025) и rx_thread (порт 1035+) только после получения HP Run=1. // DDC Specific и DUC Specific отправляем ПОСЛЕ, иначе порты ещё не открыты. - FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslate(ActiveTXFreqHz), FDriveLevel, False, True, True); - FNetwork.SetRunAndFreq(True, XvtrTranslate(FCenterFreq), XvtrTranslate(FCenterFreq), FDriveLevel); - FRunning := True; + FController.FNetwork.UpdateState(XvtrTranslate(FController.FCenterFreq), XvtrTranslate(ActiveTXFreqHz), FController.FDriveLevel, False, True, True); + FController.FNetwork.SetRunAndFreq(True, XvtrTranslate(FController.FCenterFreq), XvtrTranslate(FController.FCenterFreq), FController.FDriveLevel); + FController.FRunning := True; InhibitSleep; FRXStartTime := GetTickCount64; FRXLastPktTime := 0; @@ -4243,18 +4196,18 @@ begin SetStatusText(6, 'SEQ OK'); // DDC и DUC Specific — теперь потоки эмулятора слушают на своих портах. - // DUC Specific содержит mic-конфигурацию (boost/bias/line/PTT) из FTXSettings. - FNetwork.ConfigureDDCs(1, FSampleRate div 1000, 0, FDitherEnabled, FRandomEnabled); + // DUC Specific содержит mic-конфигурацию (boost/bias/line/PTT) из FController.FTXSettings. + FController.FNetwork.ConfigureDDCs(1, FController.FSampleRate div 1000, 0, FController.FDitherEnabled, FController.FRandomEnabled); // Применяем сохранённое состояние динамика/отправки аудио (мьют byte 1400 bit1). - FNetwork.SetSpeakerAudio(FSendAudioToRadio); + FController.FNetwork.SetSpeakerAudio(FController.FSendAudioToRadio); SendDUCSpecificFromSettings; // Применяем TX-цепь (фильтр, mic gain, EQ, leveler, ALC, comp, ...). ApplyTXSettingsToDSP; ApplyModeFilter; // Принудительно обновляем AGC линии — гарантированно после всех SetAGC вызовов - if FWDSPReady then - FDSPEngine.UpdateAGCLines(FSpectrumWidth); // ещё не получили ни одного пакета + if FController.FWDSPReady then + FController.FDSPEngine.UpdateAGCLines(FSpectrumWidth); // ещё не получили ни одного пакета BtnStartStop.Caption := 'STOP'; StyleButton(BtnStartStop, True); @@ -4275,8 +4228,8 @@ end; // --------------------------------------------------------------------------- function TMainForm.ActiveVfoFreq: Int64; begin - if FActiveVfo = 0 then Result := Round(FVfoA) - else Result := Round(FVfoB); + if FController.FActiveVfo = 0 then Result := Round(FController.FVfoA) + else Result := Round(FController.FVfoB); end; // --------------------------------------------------------------------------- @@ -4292,64 +4245,64 @@ var HalfSpan: Double; FreqVisible: Boolean; begin - FActiveVfo := Idx; + FController.FActiveVfo := Idx; // Частота нового активного VFO - if FActiveVfo = 0 then - ActiveFreq := Round(FVfoA) + if FController.FActiveVfo = 0 then + ActiveFreq := Round(FController.FVfoA) else - ActiveFreq := Round(FVfoB); + ActiveFreq := Round(FController.FVfoB); // Обновляем визуальный дисплей UpdateVfoDisplay; - if FCTun then + if FController.FCTun then begin // CTUN включён: проверяем, видна ли новая частота на текущем спектре - HalfSpan := FSpanHz / 2; - Offset := ActiveFreq - FCenterFreq; + HalfSpan := FController.FSpanHz / 2; + Offset := ActiveFreq - FController.FCenterFreq; FreqVisible := (Offset > -HalfSpan) and (Offset < HalfSpan); if FreqVisible then begin // Частота видна в окне — просто меняем shift, DDC не трогаем - if FWDSPReady then FDSPEngine.SetShift(Offset); - // Сеть не трогаем — DDC (FCenterFreq) остаётся прежним + if FController.FWDSPReady then FController.FDSPEngine.SetShift(Offset); + // Сеть не трогаем — DDC (FController.FCenterFreq) остаётся прежним end else begin // Частота за пределами спектра — центрируем окно и сбрасываем CTUN - FCTun := False; + FController.FCTun := False; StyleButton(BtnCTun, False); - FBandCache[FCurrentBand].CTun := False; - FCenterFreq := ActiveFreq; - if FWDSPReady then FDSPEngine.SetShift(0.0); - if FNetwork.Connected and FNetwork.Running then + FController.FBandCache[FController.FCurrentBand].CTun := False; + FController.FCenterFreq := ActiveFreq; + if FController.FWDSPReady then FController.FDSPEngine.SetShift(0.0); + if FController.FNetwork.Connected and FController.FNetwork.Running then begin - FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslate(ActiveTXFreqHz), FDriveLevel, FTransmitting, True, True); - FNetwork.SendFullHP; + FController.FNetwork.UpdateState(XvtrTranslate(FController.FCenterFreq), XvtrTranslate(ActiveTXFreqHz), FController.FDriveLevel, FController.FTransmitting, True, True); + FController.FNetwork.SendFullHP; end; end; end else begin // CTUN выключен: перестраиваем DDC на новую частоту - FCenterFreq := ActiveFreq; - if FWDSPReady then FDSPEngine.SetShift(0.0); - if FNetwork.Connected and FNetwork.Running then + FController.FCenterFreq := ActiveFreq; + if FController.FWDSPReady then FController.FDSPEngine.SetShift(0.0); + if FController.FNetwork.Connected and FController.FNetwork.Running then begin - FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslate(ActiveTXFreqHz), FDriveLevel, FTransmitting, True, True); - FNetwork.SendFullHP; + FController.FNetwork.UpdateState(XvtrTranslate(FController.FCenterFreq), XvtrTranslate(ActiveTXFreqHz), FController.FDriveLevel, FController.FTransmitting, True, True); + FController.FNetwork.SendFullHP; end; end; // Переключаем кнопку диапазона по новой частоте NewBand := FreqToBandIdx(ActiveFreq); - if (NewBand >= 0) and (NewBand <> FCurrentBand) then + if (NewBand >= 0) and (NewBand <> FController.FCurrentBand) then begin - StyleButton(BtnBand[FCurrentBand], False); - FCurrentBand := NewBand; - StyleButton(BtnBand[FCurrentBand], True); + StyleButton(BtnBand[FController.FCurrentBand], False); + FController.FCurrentBand := NewBand; + StyleButton(BtnBand[FController.FCurrentBand], True); end; FSpecView.InvalidateRulerCache; @@ -4363,90 +4316,90 @@ end; procedure TMainForm.FreqDispAClick(Sender: TObject); begin - // Клик на VFO-A → активируем A (перестройка на FVfoA) - if FActiveVfo <> 0 then + // Клик на VFO-A → активируем A (перестройка на FController.FVfoA) + if FController.FActiveVfo <> 0 then ActivateVfo(0); end; procedure TMainForm.FreqDispBClick(Sender: TObject); begin - // Клик на VFO-B → активируем B (перестройка на FVfoB) - if FActiveVfo <> 1 then + // Клик на VFO-B → активируем B (перестройка на FController.FVfoB) + if FController.FActiveVfo <> 1 then ActivateVfo(1); end; procedure TMainForm.BtnTopVfoASelectClick(Sender: TObject); begin - if FActiveVfo <> 0 then + if FController.FActiveVfo <> 0 then ActivateVfo(0); end; procedure TMainForm.BtnTopVfoATXClick(Sender: TObject); begin - FSplitTxB := False; + FController.FSplitTxB := False; UpdateVfoDisplay; - if FNetwork.Connected and FNetwork.Running then + if FController.FNetwork.Connected and FController.FNetwork.Running then begin - FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslate(ActiveTXFreqHz), FDriveLevel, - FTransmitting, True, True); - FNetwork.SendFullHP; + FController.FNetwork.UpdateState(XvtrTranslate(FController.FCenterFreq), XvtrTranslate(ActiveTXFreqHz), FController.FDriveLevel, + FController.FTransmitting, True, True); + FController.FNetwork.SendFullHP; end; end; procedure TMainForm.BtnTopVfoBSelectClick(Sender: TObject); begin - if FActiveVfo <> 1 then + if FController.FActiveVfo <> 1 then ActivateVfo(1); end; procedure TMainForm.BtnTopVfoBTXClick(Sender: TObject); begin - FSplitTxB := True; - if FSplitTxB and (FActiveVfo <> 0) then + FController.FSplitTxB := True; + if FController.FSplitTxB and (FController.FActiveVfo <> 0) then ActivateVfo(0) else UpdateVfoDisplay; - if FNetwork.Connected and FNetwork.Running then + if FController.FNetwork.Connected and FController.FNetwork.Running then begin - FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslate(ActiveTXFreqHz), FDriveLevel, - FTransmitting, True, True); - FNetwork.SendFullHP; + FController.FNetwork.UpdateState(XvtrTranslate(FController.FCenterFreq), XvtrTranslate(ActiveTXFreqHz), FController.FDriveLevel, + FController.FTransmitting, True, True); + FController.FNetwork.SendFullHP; end; end; procedure TMainForm.FreqDispAChanged(Sender: TObject; NewFreq: Int64); begin - if FActiveVfo = 0 then + if FController.FActiveVfo = 0 then ApplyVfoA(NewFreq) else - FVfoA := NewFreq; // просто сохраняем, приёмник не перестраиваем + FController.FVfoA := NewFreq; // просто сохраняем, приёмник не перестраиваем end; procedure TMainForm.FreqDispBChanged(Sender: TObject; NewFreq: Int64); var BandIdx: Integer; begin - FVfoB := NewFreq; - if FActiveVfo = 1 then + FController.FVfoB := NewFreq; + if FController.FActiveVfo = 1 then begin // VFO-B активен: перестраиваем приёмник - FCenterFreq := FVfoB; - if FWDSPReady then FDSPEngine.SetShift(0.0); - if FNetwork.Connected and FNetwork.Running then + FController.FCenterFreq := FController.FVfoB; + if FController.FWDSPReady then FController.FDSPEngine.SetShift(0.0); + if FController.FNetwork.Connected and FController.FNetwork.Running then begin - FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslate(ActiveTXFreqHz), FDriveLevel, FTransmitting, True, True); - FNetwork.SendFullHP; + FController.FNetwork.UpdateState(XvtrTranslate(FController.FCenterFreq), XvtrTranslate(ActiveTXFreqHz), FController.FDriveLevel, FController.FTransmitting, True, True); + FController.FNetwork.SendFullHP; end; // Обновляем диапазон - BandIdx := FreqToBandIdx(FVfoB); - if (BandIdx >= 0) and (BandIdx <> FCurrentBand) then + BandIdx := FreqToBandIdx(FController.FVfoB); + if (BandIdx >= 0) and (BandIdx <> FController.FCurrentBand) then begin - StyleButton(BtnBand[FCurrentBand], False); - FCurrentBand := BandIdx; - StyleButton(BtnBand[FCurrentBand], True); - FDriveLevel := CalcDriveByte; + StyleButton(BtnBand[FController.FCurrentBand], False); + FController.FCurrentBand := BandIdx; + StyleButton(BtnBand[FController.FCurrentBand], True); + FController.FDriveLevel := CalcDriveByte; end; FSpecView.InvalidateRulerCache; SyncSpecViewFreq; @@ -4465,47 +4418,47 @@ var Names: array[0..FILT_COUNT-1] of string; DefIdx: Integer; begin - if FMode = MODE_FM then + if FController.FMode = MODE_FM then begin // FM: show only NFM and WFM buttons, resize them to fill the row CloseCTCSSPopup; - if FFilter > 1 then FFilter := FILT_FM_DEF; // sanitize, keep NFM/WFM as-is - FFilterBW := FILT_FM_BW[FFilter]; - FFMDeviation := FILT_FM_DEV[FFilter]; + if FController.FFilter > 1 then FController.FFilter := FILT_FM_DEF; // sanitize, keep NFM/WFM as-is + FController.FFilterBW := FILT_FM_BW[FController.FFilter]; + FController.FFMDeviation := FILT_FM_DEV[FController.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); + StyleButton(BtnFilter[0], FController.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); + StyleButton(BtnFilter[1], FController.FFilter = 1); for i := 2 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 begin PanelFMSQ.Visible := True; - StyleButton(BtnFMSQ, FFMSQOn); - TrkFMSQ.Position := FFMSQLevel; - LblFMSQ.Caption := IntToStr(FFMSQLevel); + StyleButton(BtnFMSQ, FController.FFMSQOn); + TrkFMSQ.Position := FController.FFMSQLevel; + LblFMSQ.Caption := IntToStr(FController.FFMSQLevel); end; if PanelFMCTCSS <> nil then PanelFMCTCSS.Visible := True; if PanelFMStep <> nil then begin - StyleButton(BtnFMStep, FFMStepOn); - if FStepDropDown <> nil then FStepDropDown.SetItemIndex(FFMStepIdx); + StyleButton(BtnFMStep, FController.FFMStepOn); + if FStepDropDown <> nil then FStepDropDown.SetItemIndex(FController.FFMStepIdx); PanelFMStep.Visible := True; end; if PanelFMRpt <> nil then begin - StyleButton(BtnFMRptMinus, FFMRptDir = RPT_MINUS); - StyleButton(BtnFMRptPlus, FFMRptDir = RPT_PLUS); - EdFMRptOffset.Text := RptFormatOffset(FFMRptOffsetHz); + StyleButton(BtnFMRptMinus, FController.FFMRptDir = RPT_MINUS); + StyleButton(BtnFMRptPlus, FController.FFMRptDir = RPT_PLUS); + EdFMRptOffset.Text := RptFormatOffset(FController.FFMRptOffsetHz); PanelFMRpt.Visible := True; end; RelayoutBelowBands; @@ -4526,7 +4479,7 @@ begin BtnFilter[i].Visible := True; end; - case FMode of + case FController.FMode of 0, 1: begin for i := 0 to FILT_COUNT-1 do Names[i] := FILT_SSB_NAMES[i]; DefIdx := FILT_SSB_DEF; @@ -4545,18 +4498,18 @@ begin end; end; - FFilter := DefIdx; - case FMode of - 0, 1: FFilterBW := FILT_SSB_BW[DefIdx]; - 2: FFilterBW := FILT_DSB_BW[DefIdx]; - 3, 4: FFilterBW := FILT_CW_BW[DefIdx]; - else FFilterBW := FILT_AM_BW[DefIdx]; + FController.FFilter := DefIdx; + case FController.FMode of + 0, 1: FController.FFilterBW := FILT_SSB_BW[DefIdx]; + 2: FController.FFilterBW := FILT_DSB_BW[DefIdx]; + 3, 4: FController.FFilterBW := FILT_CW_BW[DefIdx]; + else FController.FFilterBW := FILT_AM_BW[DefIdx]; end; for i := 0 to FILT_COUNT-1 do begin BtnFilter[i].Caption := Names[i]; - StyleButton(BtnFilter[i], i = FFilter); + StyleButton(BtnFilter[i], i = FController.FFilter); end; RelayoutBelowBands; end; @@ -4565,11 +4518,11 @@ end; // ApplyVfoA — единая точка смены частоты VFO A // // CTUN ВЫКЛ (классический режим): -// FCenterFreq = FVfoA — спектр центрирован на VFO, маркер всегда в центре +// FController.FCenterFreq = FController.FVfoA — спектр центрирован на VFO, маркер всегда в центре // // CTUN ВКЛ (piHPSDR/Thetis режим): -// FCenterFreq статичен — спектр/водопад не двигаются -// FVfoA движется по дисплею (маркер гуляет) +// FController.FCenterFreq статичен — спектр/водопад не двигаются +// FController.FVfoA движется по дисплею (маркер гуляет) // Перецентрирование — только когда КРАЙ ПОЛОСЫ ФИЛЬТРА выходит за край дисплея // (по образцу OpenHPSDR/PowerSDR: "re-centering occurs as the edge of the passband // hits the edge of the display") @@ -4584,74 +4537,74 @@ var BandIdx: Integer; begin // Клэмп в пределах XVTR-band — не даём VFO уйти за FreqBegin/FreqEnd - if (FCurrentXvtr >= 0) and (FCurrentXvtr < CFG_XVTR_COUNT) and - FXvtrSettings.Entries[FCurrentXvtr].Enabled then + if (FController.FCurrentXvtr >= 0) and (FController.FCurrentXvtr < CFG_XVTR_COUNT) and + FController.FXvtrSettings.Entries[FController.FCurrentXvtr].Enabled then begin - if NewFreq < FXvtrSettings.Entries[FCurrentXvtr].FreqBegin then - NewFreq := Round(FXvtrSettings.Entries[FCurrentXvtr].FreqBegin); - if NewFreq > FXvtrSettings.Entries[FCurrentXvtr].FreqEnd then - NewFreq := Round(FXvtrSettings.Entries[FCurrentXvtr].FreqEnd); + if NewFreq < FController.FXvtrSettings.Entries[FController.FCurrentXvtr].FreqBegin then + NewFreq := Round(FController.FXvtrSettings.Entries[FController.FCurrentXvtr].FreqBegin); + if NewFreq > FController.FXvtrSettings.Entries[FController.FCurrentXvtr].FreqEnd then + NewFreq := Round(FController.FXvtrSettings.Entries[FController.FCurrentXvtr].FreqEnd); end; - FVfoA := NewFreq; + FController.FVfoA := NewFreq; Scrolled := False; - if not FCTun then + if not FController.FCTun then begin // ---- CTUN OFF ---- // DDC = VFO, сдвига нет - FCenterFreq := FVfoA; - if FWDSPReady then - FDSPEngine.SetShift(0.0); + FController.FCenterFreq := FController.FVfoA; + if FController.FWDSPReady then + FController.FDSPEngine.SetShift(0.0); end else begin // ---- CTUN ON ---- - // DDC (FCenterFreq) стоит на месте. + // DDC (FController.FCenterFreq) стоит на месте. // SetRXAShiftFreq сдвигает спектр внутри WDSP так чтобы - // демодулятор принимал сигнал на FVfoA, а не на FCenterFreq. - // Shift = FVfoA - FCenterFreq (в Гц) - Offset := FVfoA - FCenterFreq; - if FWDSPReady then - FDSPEngine.SetShift(Offset); + // демодулятор принимал сигнал на FController.FVfoA, а не на FController.FCenterFreq. + // Shift = FController.FVfoA - FController.FCenterFreq (в Гц) + Offset := FController.FVfoA - FController.FCenterFreq; + if FController.FWDSPReady then + FController.FDSPEngine.SetShift(Offset); // Проверяем не вышла ли полоса фильтра за край дисплея - HalfSpan := FSpanHz / 2; - case FMode of - 0: begin FiltLo := Offset - FFilterBW; FiltHi := Offset - 100; end; - 1: begin FiltLo := Offset + 100; FiltHi := Offset + FFilterBW; end; - else begin FiltLo := Offset - FFilterBW/2; FiltHi := Offset + FFilterBW/2; end; + HalfSpan := FController.FSpanHz / 2; + case FController.FMode of + 0: begin FiltLo := Offset - FController.FFilterBW; FiltHi := Offset - 100; end; + 1: begin FiltLo := Offset + 100; FiltHi := Offset + FController.FFilterBW; end; + else begin FiltLo := Offset - FController.FFilterBW/2; FiltHi := Offset + FController.FFilterBW/2; end; end; if (FiltHi > HalfSpan) or (FiltLo < -HalfSpan) then begin // Полоса вышла за край → прокручиваем центр if FiltHi > HalfSpan then - FCenterFreq := FVfoA - HalfSpan * 0.5 + FController.FCenterFreq := FController.FVfoA - HalfSpan * 0.5 else - FCenterFreq := FVfoA + HalfSpan * 0.5; + FController.FCenterFreq := FController.FVfoA + HalfSpan * 0.5; // Пересчитываем сдвиг после прокрутки - if FWDSPReady then - FDSPEngine.SetShift(FVfoA - FCenterFreq); - // DDC перестраивается на новый FCenterFreq + if FController.FWDSPReady then + FController.FDSPEngine.SetShift(FController.FVfoA - FController.FCenterFreq); + // DDC перестраивается на новый FController.FCenterFreq Scrolled := True; end; end; // Обновляем VFO дисплей - FreqDispA.Frequency := Round(FVfoA); + FreqDispA.Frequency := Round(FController.FVfoA); // Обновляем кнопку диапазона если частота перешла в другой диапазон. // В XVTR-режиме HF band-кнопки не подсвечиваем (мы клэмпим VFO внутри // XVTR-диапазона — FreqToBandIdx всегда вернёт -1 для VHF/UHF). - if FCurrentXvtr < 0 then + if FController.FCurrentXvtr < 0 then begin - BandIdx := FreqToBandIdx(FVfoA); - if (BandIdx >= 0) and (BandIdx <> FCurrentBand) then + BandIdx := FreqToBandIdx(FController.FVfoA); + if (BandIdx >= 0) and (BandIdx <> FController.FCurrentBand) then begin - StyleButton(BtnBand[FCurrentBand], False); - FCurrentBand := BandIdx; - StyleButton(BtnBand[FCurrentBand], True); - FDriveLevel := CalcDriveByte; + StyleButton(BtnBand[FController.FCurrentBand], False); + FController.FCurrentBand := BandIdx; + StyleButton(BtnBand[FController.FCurrentBand], True); + FController.FDriveLevel := CalcDriveByte; end; end; @@ -4659,17 +4612,17 @@ begin // сбрасываем их при первом же изменении частоты if FActiveChannelIdx < 0 then begin - if FFMCTCSSAutoActive then + if FController.FFMCTCSSAutoActive then begin - FFMCTCSSAutoActive := False; - FFMCTCSSOn := False; + FController.FFMCTCSSAutoActive := False; + FController.FFMCTCSSOn := False; if BtnFMCTCSS <> nil then StyleButton(BtnFMCTCSS, False); - if FWDSPReady then FDSPEngine.SetTXCTCSS(False, CTCSS_TONES[FFMCTCSSToneIdx]); + if FController.FWDSPReady then FController.FDSPEngine.SetTXCTCSS(False, CTCSS_TONES[FController.FFMCTCSSToneIdx]); end; - if FFMRptAutoActive then + if FController.FFMRptAutoActive then begin - FFMRptDir := RPT_NONE; - FFMRptAutoActive := False; + FController.FFMRptDir := RPT_NONE; + FController.FFMRptAutoActive := False; if BtnFMRptMinus <> nil then StyleButton(BtnFMRptMinus, False); if BtnFMRptPlus <> nil then StyleButton(BtnFMRptPlus, False); end; @@ -4677,20 +4630,20 @@ begin begin TrkDrive.Position := FPreChannelPower; FPreChannelPower := -1; - FDriveLevel := CalcDriveByte; - if FWDSPReady then - FDSPEngine.SetDriveLevel(TrkDrive.Position / 100.0); + FController.FDriveLevel := CalcDriveByte; + if FController.FWDSPReady then + FController.FDSPEngine.SetDriveLevel(TrkDrive.Position / 100.0); end; end; // Auto-enable repeater minus for 2 m repeater input segment - if FMode = MODE_FM then UpdateRptAutoState; + if FController.FMode = MODE_FM then UpdateRptAutoState; - // DDC: передаём FCenterFreq (при CTUN OFF = FVfoA, при CTUN ON = фиксирован) - if FNetwork.Connected and FNetwork.Running then + // DDC: передаём FController.FCenterFreq (при CTUN OFF = FController.FVfoA, при CTUN ON = фиксирован) + if FController.FNetwork.Connected and FController.FNetwork.Running then begin - FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslate(ActiveTXFreqHz), FDriveLevel, FTransmitting, True, True); - FNetwork.SendFullHP; + FController.FNetwork.UpdateState(XvtrTranslate(FController.FCenterFreq), XvtrTranslate(ActiveTXFreqHz), FController.FDriveLevel, FController.FTransmitting, True, True); + FController.FNetwork.SendFullHP; end; // Не рисуем синхронно из wheel/drag path: частые события мыши иначе @@ -4712,25 +4665,25 @@ end; // --------------------------------------------------------------------------- procedure TMainForm.BtnCTunClick(Sender: TObject); begin - FCTun := not FCTun; - StyleButton(BtnCTun, FCTun); - FBandCache[FCurrentBand].CTun := FCTun; - if not FCTun then + FController.FCTun := not FController.FCTun; + StyleButton(BtnCTun, FController.FCTun); + FController.FBandCache[FController.FCurrentBand].CTun := FController.FCTun; + if not FController.FCTun then begin // Выключили CTUN: центрируемся на VFO, shift=0 - FCenterFreq := FVfoA; - if FWDSPReady then - FDSPEngine.SetShift(0.0); - if FNetwork.Connected and FNetwork.Running then + FController.FCenterFreq := FController.FVfoA; + if FController.FWDSPReady then + FController.FDSPEngine.SetShift(0.0); + if FController.FNetwork.Connected and FController.FNetwork.Running then begin - FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslate(ActiveTXFreqHz), FDriveLevel, FTransmitting, True, True); - FNetwork.SendFullHP; + FController.FNetwork.UpdateState(XvtrTranslate(FController.FCenterFreq), XvtrTranslate(ActiveTXFreqHz), FController.FDriveLevel, FController.FTransmitting, True, True); + FController.FNetwork.SendFullHP; end; end else begin // Включили CTUN: фиксируем текущее положение дисплея - // FCenterFreq остаётся как есть — спектр не прыгает + // FController.FCenterFreq остаётся как есть — спектр не прыгает end; SyncSpecViewFreq; FSpecView.DrawSpectrum; @@ -4744,15 +4697,15 @@ end; // --------------------------------------------------------------------------- procedure TMainForm.ApplyDUP(Active: Boolean); begin - FDisplayDuplex := Active; - StyleButton(BtnDUP, FDisplayDuplex); + FController.FDisplayDuplex := Active; + StyleButton(BtnDUP, FController.FDisplayDuplex); // Если сейчас идёт передача — переключаем источник пикселей и сбрасываем // буферы, чтобы старые TX/RX bin'ы не оставались на следующем кадре. - if FTransmitting then + if FController.FTransmitting then begin - if FWDSPReady then - FDSPEngine.SetDisplaySourceTX(not FDisplayDuplex); - FSpecView.TXMode := FTransmitting and not FDisplayDuplex; + if FController.FWDSPReady then + FController.FDSPEngine.SetDisplaySourceTX(not FController.FDisplayDuplex); + FSpecView.TXMode := FController.FTransmitting and not FController.FDisplayDuplex; FSpecView.ResetSpectrumBuf; FSpecView.ResetWfAvgBuf; ApplySpecViewGridFromState; @@ -4765,7 +4718,7 @@ end; procedure TMainForm.BtnDUPClick(Sender: TObject); begin - ApplyDUP(not FDisplayDuplex); + ApplyDUP(not FController.FDisplayDuplex); end; // --------------------------------------------------------------------------- @@ -4773,51 +4726,51 @@ end; // --------------------------------------------------------------------------- procedure TMainForm.ApplyTUN(Active: Boolean); begin - if Active = FTuning then Exit; + if Active = FController.FTuning then Exit; // Safety: те же блокировки что в ApplyMOX. TUN — это TX через WDSP PostGen, // он тоже должен молчать на бэндах с DoNotTx или на RX-only XVTR. - if Active and FDevConnected and (FCurrentXvtr < 0) - and FAlexSettings.DoNotTx[EnsureRange(FCurrentBand, 0, 10)] then + if Active and FController.FDevConnected and (FController.FCurrentXvtr < 0) + and FController.FAlexSettings.DoNotTx[EnsureRange(FController.FCurrentBand, 0, 10)] then Exit; - if Active and (FCurrentXvtr >= 0) and (FCurrentXvtr < CFG_XVTR_COUNT) - and FXvtrSettings.Entries[FCurrentXvtr].RXOnly then + if Active and (FController.FCurrentXvtr >= 0) and (FController.FCurrentXvtr < CFG_XVTR_COUNT) + and FController.FXvtrSettings.Entries[FController.FCurrentXvtr].RXOnly then Exit; if Active then begin // Включаем тон ДО запуска TX-аудио, чтобы первые сэмплы уже шли с тоном - if FWDSPReady then - FDSPEngine.SetTXTone(True, FTXSettings.TUNFreq, 1.0); - FTuning := True; + if FController.FWDSPReady then + FController.FDSPEngine.SetTXTone(True, FController.FTXSettings.TUNFreq, 1.0); + FController.FTuning := True; StyleButton(BtnTUN, True); - // Drive байт пересчитываем (CalcDriveByte увидит FTuning=True) - FDriveLevel := CalcDriveByte; - if FWDSPReady then - FDSPEngine.SetDriveLevel(FTXSettings.TUNLevel / 100.0); + // Drive байт пересчитываем (CalcDriveByte увидит FController.FTuning=True) + FController.FDriveLevel := CalcDriveByte; + if FController.FWDSPReady then + FController.FDSPEngine.SetDriveLevel(FController.FTXSettings.TUNLevel / 100.0); // Активируем TX (то же что MOX — PTT, run TXA-канал) - if not FTransmitting then ApplyMOX(True); + if not FController.FTransmitting then ApplyMOX(True); end else begin // Выключаем TX, потом тон, потом восстанавливаем drive - if FTransmitting then ApplyMOX(False); - if FWDSPReady then - FDSPEngine.SetTXTone(False, 0, 0); - FTuning := False; + if FController.FTransmitting then ApplyMOX(False); + if FController.FWDSPReady then + FController.FDSPEngine.SetTXTone(False, 0, 0); + FController.FTuning := False; StyleButton(BtnTUN, False); - FDriveLevel := CalcDriveByte; - if FWDSPReady then - FDSPEngine.SetDriveLevel(TrkDrive.Position / 100.0); - if FNetwork.Connected and FNetwork.Running then + FController.FDriveLevel := CalcDriveByte; + if FController.FWDSPReady then + FController.FDSPEngine.SetDriveLevel(TrkDrive.Position / 100.0); + if FController.FNetwork.Connected and FController.FNetwork.Running then begin - FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslate(ActiveTXFreqHz), FDriveLevel, - FTransmitting, True, True); - FNetwork.SendFullHP; + FController.FNetwork.UpdateState(XvtrTranslate(FController.FCenterFreq), XvtrTranslate(ActiveTXFreqHz), FController.FDriveLevel, + FController.FTransmitting, True, True); + FController.FNetwork.SendFullHP; end; end; end; procedure TMainForm.BtnTUNClick(Sender: TObject); begin - ApplyTUN(not FTuning); + ApplyTUN(not FController.FTuning); end; // --------------------------------------------------------------------------- @@ -4838,31 +4791,31 @@ var BandIdx: Integer; begin if PanelWidth <= 0 then Exit; - if (FMode = MODE_FM) and FFMStepOn then - StepHz := FM_STEP_HZ[FFMStepIdx] + if (FController.FMode = MODE_FM) and FController.FFMStepOn then + StepHz := FM_STEP_HZ[FController.FFMStepIdx] else StepHz := 100; - ClickFreq := Round(FCenterFreq + (PixelX / PanelWidth - 0.5) * FSpanHz); + ClickFreq := Round(FController.FCenterFreq + (PixelX / PanelWidth - 0.5) * FController.FSpanHz); ClickFreq := ((ClickFreq + StepHz div 2) div StepHz) * StepHz; - if FActiveVfo = 0 then + if FController.FActiveVfo = 0 then ApplyVfoA(ClickFreq) else begin - FVfoB := ClickFreq; - FreqDispB.Frequency := Round(FVfoB); - FCenterFreq := ClickFreq; - if FWDSPReady then FDSPEngine.SetShift(0.0); - if FNetwork.Connected and FNetwork.Running then + FController.FVfoB := ClickFreq; + FreqDispB.Frequency := Round(FController.FVfoB); + FController.FCenterFreq := ClickFreq; + if FController.FWDSPReady then FController.FDSPEngine.SetShift(0.0); + if FController.FNetwork.Connected and FController.FNetwork.Running then begin - FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslate(ActiveTXFreqHz), FDriveLevel, FTransmitting, True, True); - FNetwork.SendFullHP; + FController.FNetwork.UpdateState(XvtrTranslate(FController.FCenterFreq), XvtrTranslate(ActiveTXFreqHz), FController.FDriveLevel, FController.FTransmitting, True, True); + FController.FNetwork.SendFullHP; end; - BandIdx := FreqToBandIdx(FVfoB); - if (BandIdx >= 0) and (BandIdx <> FCurrentBand) then + BandIdx := FreqToBandIdx(FController.FVfoB); + if (BandIdx >= 0) and (BandIdx <> FController.FCurrentBand) then begin - StyleButton(BtnBand[FCurrentBand], False); - FCurrentBand := BandIdx; - StyleButton(BtnBand[FCurrentBand], True); + StyleButton(BtnBand[FController.FCurrentBand], False); + FController.FCurrentBand := BandIdx; + StyleButton(BtnBand[FController.FCurrentBand], True); end; FSpecView.InvalidateRulerCache; SyncSpecViewFreq; @@ -4883,67 +4836,67 @@ begin if (FWidebandView = nil) or (not FWidebandView.TryPixelToFrequency(PixelX, PanelWidth, FreqHz)) then Exit; - if (FMode = MODE_FM) and FFMStepOn then - StepHz := FM_STEP_HZ[FFMStepIdx] + if (FController.FMode = MODE_FM) and FController.FFMStepOn then + StepHz := FM_STEP_HZ[FController.FFMStepIdx] else StepHz := 100; ClickFreq := Round(FreqHz); ClickFreq := ((ClickFreq + StepHz div 2) div StepHz) * StepHz; - if (FCurrentXvtr >= 0) and (FCurrentXvtr < CFG_XVTR_COUNT) and - FXvtrSettings.Entries[FCurrentXvtr].Enabled then + if (FController.FCurrentXvtr >= 0) and (FController.FCurrentXvtr < CFG_XVTR_COUNT) and + FController.FXvtrSettings.Entries[FController.FCurrentXvtr].Enabled then begin - if ClickFreq < FXvtrSettings.Entries[FCurrentXvtr].FreqBegin then - ClickFreq := Round(FXvtrSettings.Entries[FCurrentXvtr].FreqBegin); - if ClickFreq > FXvtrSettings.Entries[FCurrentXvtr].FreqEnd then - ClickFreq := Round(FXvtrSettings.Entries[FCurrentXvtr].FreqEnd); + if ClickFreq < FController.FXvtrSettings.Entries[FController.FCurrentXvtr].FreqBegin then + ClickFreq := Round(FController.FXvtrSettings.Entries[FController.FCurrentXvtr].FreqBegin); + if ClickFreq > FController.FXvtrSettings.Entries[FController.FCurrentXvtr].FreqEnd then + ClickFreq := Round(FController.FXvtrSettings.Entries[FController.FCurrentXvtr].FreqEnd); end; - if FCurrentXvtr < 0 then + if FController.FCurrentXvtr < 0 then begin BandIdx := FreqToBandIdx(ClickFreq); - if (BandIdx >= 0) and (BandIdx <> FCurrentBand) then + if (BandIdx >= 0) and (BandIdx <> FController.FCurrentBand) then begin - if FDevConnected then + if FController.FDevConnected then begin SaveCurrentBand; - FSettings.Save; + FController.FSettings.Save; end; - FCurrentBand := BandIdx; - RestoreBand(FCurrentBand); + FController.FCurrentBand := BandIdx; + RestoreBand(FController.FCurrentBand); end; end; - if FActiveVfo = 0 then + if FController.FActiveVfo = 0 then begin - WasCTun := FCTun; - FCTun := False; + WasCTun := FController.FCTun; + FController.FCTun := False; ApplyVfoA(ClickFreq); - FCTun := WasCTun; - if WasCTun and FWDSPReady then - FDSPEngine.SetShift(0.0); + FController.FCTun := WasCTun; + if WasCTun and FController.FWDSPReady then + FController.FDSPEngine.SetShift(0.0); end else begin - FVfoB := ClickFreq; - FreqDispB.Frequency := Round(FVfoB); - FCenterFreq := ClickFreq; - if FWDSPReady then FDSPEngine.SetShift(0.0); - if FNetwork.Connected and FNetwork.Running then + FController.FVfoB := ClickFreq; + FreqDispB.Frequency := Round(FController.FVfoB); + FController.FCenterFreq := ClickFreq; + if FController.FWDSPReady then FController.FDSPEngine.SetShift(0.0); + if FController.FNetwork.Connected and FController.FNetwork.Running then begin - FNetwork.UpdateState(XvtrTranslate(FCenterFreq), - XvtrTranslate(ActiveTXFreqHz), FDriveLevel, FTransmitting, True, True); - FNetwork.SendFullHP; + FController.FNetwork.UpdateState(XvtrTranslate(FController.FCenterFreq), + XvtrTranslate(ActiveTXFreqHz), FController.FDriveLevel, FController.FTransmitting, True, True); + FController.FNetwork.SendFullHP; end; - if FCurrentXvtr < 0 then + if FController.FCurrentXvtr < 0 then begin - BandIdx := FreqToBandIdx(FVfoB); - if (BandIdx >= 0) and (BandIdx <> FCurrentBand) then + BandIdx := FreqToBandIdx(FController.FVfoB); + if (BandIdx >= 0) and (BandIdx <> FController.FCurrentBand) then begin - if FCurrentBand >= 0 then - StyleButton(BtnBand[FCurrentBand], False); - FCurrentBand := BandIdx; - StyleButton(BtnBand[FCurrentBand], True); + if FController.FCurrentBand >= 0 then + StyleButton(BtnBand[FController.FCurrentBand], False); + FController.FCurrentBand := BandIdx; + StyleButton(BtnBand[FController.FCurrentBand], True); end; end; UpdateVfoDisplay; @@ -4967,41 +4920,41 @@ var begin if PanelWidth <= 0 then Exit; dPix := PixelX - FSpecDragX0; - dFreq := dPix / PanelWidth * FSpanHz; - if FCTun then + dFreq := dPix / PanelWidth * FController.FSpanHz; + if FController.FCTun then begin - // CTUN ON: drag двигает окно просмотра (FCenterFreq/DDC) - // VFO остаётся, shift обновляется = VFO - новый FCenterFreq - FCenterFreq := FSpecDragFreq - dFreq; - if FWDSPReady then + // CTUN ON: drag двигает окно просмотра (FController.FCenterFreq/DDC) + // VFO остаётся, shift обновляется = VFO - новый FController.FCenterFreq + FController.FCenterFreq := FSpecDragFreq - dFreq; + if FController.FWDSPReady then begin - if FActiveVfo = 0 then - FDSPEngine.SetShift(FVfoA - FCenterFreq) + if FController.FActiveVfo = 0 then + FController.FDSPEngine.SetShift(FController.FVfoA - FController.FCenterFreq) else - FDSPEngine.SetShift(FVfoB - FCenterFreq); + FController.FDSPEngine.SetShift(FController.FVfoB - FController.FCenterFreq); end; - if FNetwork.Connected and FNetwork.Running then + if FController.FNetwork.Connected and FController.FNetwork.Running then begin - FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslate(ActiveTXFreqHz), FDriveLevel, FTransmitting, True, True); - FNetwork.SendFullHP; + FController.FNetwork.UpdateState(XvtrTranslate(FController.FCenterFreq), XvtrTranslate(ActiveTXFreqHz), FController.FDriveLevel, FController.FTransmitting, True, True); + FController.FNetwork.SendFullHP; end; FSpectrumDirty := True; end else begin - // CTUN OFF: drag двигает VFO, FCenterFreq следует через Apply - if FActiveVfo = 0 then + // CTUN OFF: drag двигает VFO, FController.FCenterFreq следует через Apply + if FController.FActiveVfo = 0 then ApplyVfoA(Round(FSpecDragFreq - dFreq)) else begin - FVfoB := FSpecDragFreq - dFreq; - FreqDispB.Frequency := Round(FVfoB); - FCenterFreq := FVfoB; - if FWDSPReady then FDSPEngine.SetShift(0.0); - if FNetwork.Connected and FNetwork.Running then + FController.FVfoB := FSpecDragFreq - dFreq; + FreqDispB.Frequency := Round(FController.FVfoB); + FController.FCenterFreq := FController.FVfoB; + if FController.FWDSPReady then FController.FDSPEngine.SetShift(0.0); + if FController.FNetwork.Connected and FController.FNetwork.Running then begin - FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslate(ActiveTXFreqHz), FDriveLevel, FTransmitting, True, True); - FNetwork.SendFullHP; + FController.FNetwork.UpdateState(XvtrTranslate(FController.FCenterFreq), XvtrTranslate(ActiveTXFreqHz), FController.FDriveLevel, FController.FTransmitting, True, True); + FController.FNetwork.SendFullHP; end; FSpectrumDirty := True; end; @@ -5028,10 +4981,10 @@ begin end; FSpecDrag := True; FSpecDragX0 := X; - if FCTun then FSpecDragFreq := FCenterFreq + if FController.FCTun then FSpecDragFreq := FController.FCenterFreq else begin - if FActiveVfo = 0 then FSpecDragFreq := FVfoA - else FSpecDragFreq := FVfoB; + if FController.FActiveVfo = 0 then FSpecDragFreq := FController.FVfoA + else FSpecDragFreq := FController.FVfoB; end; PbSpectrum.Cursor := crSizeWE; end @@ -5124,10 +5077,10 @@ begin end; FSpecDrag := True; FSpecDragX0 := X; - if FCTun then FSpecDragFreq := FCenterFreq + if FController.FCTun then FSpecDragFreq := FController.FCenterFreq else begin - if FActiveVfo = 0 then FSpecDragFreq := FVfoA - else FSpecDragFreq := FVfoB; + if FController.FActiveVfo = 0 then FSpecDragFreq := FController.FVfoA + else FSpecDragFreq := FController.FVfoB; end; PbWaterfall.Cursor := crSizeWE; end @@ -5176,33 +5129,33 @@ begin NewBand := (Sender as TFlatButton).Tag; if (NewBand < 0) or (NewBand >= BAND_COUNT) then Exit; // Если в XVTR-режиме — DeactivateXvtr вернёт HF-состояние (RestoreBand - // на сохранённом FCurrentBand). Если NewBand совпадает с восстановленным - // FCurrentBand, дальше делать ничего не нужно. - if FCurrentXvtr >= 0 then + // на сохранённом FController.FCurrentBand). Если NewBand совпадает с восстановленным + // FController.FCurrentBand, дальше делать ничего не нужно. + if FController.FCurrentXvtr >= 0 then begin DeactivateXvtr; - if NewBand = FCurrentBand then Exit; + if NewBand = FController.FCurrentBand then Exit; end - else if NewBand = FCurrentBand then + else if NewBand = FController.FCurrentBand then Exit; - if FDevConnected then + if FController.FDevConnected then begin SaveCurrentBand; - FSettings.Save; + FController.FSettings.Save; end; - FCurrentBand := NewBand; - RestoreBand(FCurrentBand); + FController.FCurrentBand := NewBand; + RestoreBand(FController.FCurrentBand); end; procedure TMainForm.ApplyModeFilter; var Lo, Hi, Half: Integer; begin - Half := FFilterBW div 2; - case FMode of - 0: begin Lo := -FFilterBW; Hi := -100; end; // LSB - 1: begin Lo := 100; Hi := FFilterBW; end; // USB + Half := FController.FFilterBW div 2; + case FController.FMode of + 0: begin Lo := -FController.FFilterBW; Hi := -100; end; // LSB + 1: begin Lo := 100; Hi := FController.FFilterBW; end; // USB 2: begin Lo := -Half; Hi := Half; end; // DSB 3: begin Lo := -Half; Hi := Half; end; // CWL 4: begin Lo := -Half; Hi := Half; end; // CWU @@ -5211,19 +5164,19 @@ begin 7: begin Lo := -Half; Hi := Half; end; // SAM else Lo := -Half; Hi := Half; end; - if FWDSPReady then + if FController.FWDSPReady then begin - FDSPEngine.SetFilter(Lo, Hi); - FDSPEngine.SetTXFilter(Lo, Hi); - if FMode = MODE_FM then + FController.FDSPEngine.SetFilter(Lo, Hi); + FController.FDSPEngine.SetTXFilter(Lo, Hi); + if FController.FMode = MODE_FM then begin - FDSPEngine.SetRXFMDeviation(FFMDeviation); - FDSPEngine.SetTXFMParams(FFMDeviation, FTXSettings.FMLowCut, - FTXSettings.FMHighCut, FTXSettings.FMEmphPosition); - FDSPEngine.SetFMSquelch(FFMSQOn, FFMSQLevel); + FController.FDSPEngine.SetRXFMDeviation(FController.FFMDeviation); + FController.FDSPEngine.SetTXFMParams(FController.FFMDeviation, FController.FTXSettings.FMLowCut, + FController.FTXSettings.FMHighCut, FController.FTXSettings.FMEmphPosition); + FController.FDSPEngine.SetFMSquelch(FController.FFMSQOn, FController.FFMSQLevel); end else - FDSPEngine.SetFMSquelch(False, 0); + FController.FDSPEngine.SetFMSquelch(False, 0); end; end; @@ -5232,14 +5185,14 @@ var i, N: Integer; begin N := (Sender as TFlatButton).Tag; - FMode := N; - for i := 0 to MODE_COUNT - 1 do StyleButton(BtnMode[i], i = FMode); - if FWDSPReady then + FController.FMode := N; + for i := 0 to MODE_COUNT - 1 do StyleButton(BtnMode[i], i = FController.FMode); + if FController.FWDSPReady then begin - FDSPEngine.SetMode(N); + FController.FDSPEngine.SetMode(N); // Если TUN активен — пересчитываем знак частоты под новый режим - if FTuning then - FDSPEngine.SetTXTone(True, FTXSettings.TUNFreq, 1.0); + if FController.FTuning then + FController.FDSPEngine.SetTXTone(True, FController.FTXSettings.TUNFreq, 1.0); end; UpdateFilterButtons; // обновляем подписи и дефолт фильтра под новый режим ApplyModeFilter; @@ -5251,18 +5204,18 @@ var i, N: Integer; begin N := (Sender as TFlatButton).Tag; - FFilter := N; - case FMode of - 0, 1: FFilterBW := FILT_SSB_BW[N]; - 2: FFilterBW := FILT_DSB_BW[N]; - 3, 4: FFilterBW := FILT_CW_BW[N]; + FController.FFilter := N; + case FController.FMode of + 0, 1: FController.FFilterBW := FILT_SSB_BW[N]; + 2: FController.FFilterBW := FILT_DSB_BW[N]; + 3, 4: FController.FFilterBW := FILT_CW_BW[N]; 5: begin - FFilterBW := FILT_FM_BW[N]; - FFMDeviation := FILT_FM_DEV[N]; + FController.FFilterBW := FILT_FM_BW[N]; + FController.FFMDeviation := FILT_FM_DEV[N]; end; - else FFilterBW := FILT_AM_BW[N]; + else FController.FFilterBW := FILT_AM_BW[N]; end; - for i := 0 to FILT_COUNT - 1 do StyleButton(BtnFilter[i], i = FFilter); + for i := 0 to FILT_COUNT - 1 do StyleButton(BtnFilter[i], i = FController.FFilter); ApplyModeFilter; SyncSpecViewFreq; end; @@ -5273,14 +5226,14 @@ end; procedure TMainForm.ApplyFMSquelch; begin - if FWDSPReady then - FDSPEngine.SetFMSquelch(FFMSQOn and (FMode = MODE_FM), FFMSQLevel); + if FController.FWDSPReady then + FController.FDSPEngine.SetFMSquelch(FController.FFMSQOn and (FController.FMode = MODE_FM), FController.FFMSQLevel); end; procedure TMainForm.BtnFMSQClick(Sender: TObject); begin - FFMSQOn := not FFMSQOn; - StyleButton(BtnFMSQ, FFMSQOn); + FController.FFMSQOn := not FController.FFMSQOn; + StyleButton(BtnFMSQ, FController.FFMSQOn); BtnFMSQ.Repaint; ApplyFMSquelch; SaveCurrentBand; @@ -5288,8 +5241,8 @@ end; procedure TMainForm.TrkFMSQChange(Sender: TObject); begin - FFMSQLevel := TrkFMSQ.Position; - if LblFMSQ <> nil then LblFMSQ.Caption := IntToStr(FFMSQLevel); + FController.FFMSQLevel := TrkFMSQ.Position; + if LblFMSQ <> nil then LblFMSQ.Caption := IntToStr(FController.FFMSQLevel); ApplyFMSquelch; SaveCurrentBand; end; @@ -5303,13 +5256,13 @@ end; procedure TMainForm.BtnFMRptMinusClick(Sender: TObject); begin - if FFMRptDir = RPT_MINUS then - FFMRptDir := RPT_NONE + if FController.FFMRptDir = RPT_MINUS then + FController.FFMRptDir := RPT_NONE else - FFMRptDir := RPT_MINUS; - FFMRptAutoActive := False; // ручное действие снимает авто-флаг - StyleButton(BtnFMRptMinus, FFMRptDir = RPT_MINUS); - StyleButton(BtnFMRptPlus, FFMRptDir = RPT_PLUS); + FController.FFMRptDir := RPT_MINUS; + FController.FFMRptAutoActive := False; // ручное действие снимает авто-флаг + StyleButton(BtnFMRptMinus, FController.FFMRptDir = RPT_MINUS); + StyleButton(BtnFMRptPlus, FController.FFMRptDir = RPT_PLUS); BtnFMRptMinus.Repaint; BtnFMRptPlus.Repaint; ApplyFMRpt; @@ -5318,13 +5271,13 @@ end; procedure TMainForm.BtnFMRptPlusClick(Sender: TObject); begin - if FFMRptDir = RPT_PLUS then - FFMRptDir := RPT_NONE + if FController.FFMRptDir = RPT_PLUS then + FController.FFMRptDir := RPT_NONE else - FFMRptDir := RPT_PLUS; - FFMRptAutoActive := False; // ручное действие снимает авто-флаг - StyleButton(BtnFMRptMinus, FFMRptDir = RPT_MINUS); - StyleButton(BtnFMRptPlus, FFMRptDir = RPT_PLUS); + FController.FFMRptDir := RPT_PLUS; + FController.FFMRptAutoActive := False; // ручное действие снимает авто-флаг + StyleButton(BtnFMRptMinus, FController.FFMRptDir = RPT_MINUS); + StyleButton(BtnFMRptPlus, FController.FFMRptDir = RPT_PLUS); BtnFMRptMinus.Repaint; BtnFMRptPlus.Repaint; ApplyFMRpt; @@ -5337,8 +5290,8 @@ begin Hz := RptParseOffset(EdFMRptOffset.Text); if Hz > 0.0 then begin - FFMRptOffsetHz := Hz; - if FFMRptDir <> RPT_NONE then + FController.FFMRptOffsetHz := Hz; + if FController.FFMRptDir <> RPT_NONE then begin ApplyFMRpt; SaveCurrentBand; @@ -5348,25 +5301,25 @@ end; procedure TMainForm.ApplyFMRpt; begin - if FNetwork.Connected and FNetwork.Running then + if FController.FNetwork.Connected and FController.FNetwork.Running then begin - FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslate(ActiveTXFreqHz), - FDriveLevel, FTransmitting, True, True); - FNetwork.SendFullHP; + FController.FNetwork.UpdateState(XvtrTranslate(FController.FCenterFreq), XvtrTranslate(ActiveTXFreqHz), + FController.FDriveLevel, FController.FTransmitting, True, True); + FController.FNetwork.SendFullHP; end; end; procedure TMainForm.UpdateRptAutoState; var InRange: Boolean; begin - InRange := RptAutoDir(FVfoA) = RPT_MINUS; + InRange := RptAutoDir(FController.FVfoA) = RPT_MINUS; if InRange then begin // Auto-enable minus only when currently off. - if FFMRptDir = RPT_NONE then + if FController.FFMRptDir = RPT_NONE then begin - FFMRptDir := RPT_MINUS; - FFMRptAutoActive := True; + FController.FFMRptDir := RPT_MINUS; + FController.FFMRptAutoActive := True; if BtnFMRptMinus <> nil then begin StyleButton(BtnFMRptMinus, True); @@ -5377,10 +5330,10 @@ begin else begin // Auto-disable only if this minus was set automatically. - if (FFMRptDir = RPT_MINUS) and FFMRptAutoActive then + if (FController.FFMRptDir = RPT_MINUS) and FController.FFMRptAutoActive then begin - FFMRptDir := RPT_NONE; - FFMRptAutoActive := False; + FController.FFMRptDir := RPT_NONE; + FController.FFMRptAutoActive := False; if BtnFMRptMinus <> nil then begin StyleButton(BtnFMRptMinus, False); @@ -5398,17 +5351,17 @@ end; procedure TMainForm.SetFMStep(Idx: Integer); begin - FFMStepIdx := Idx; + FController.FFMStepIdx := Idx; if FStepDropDown <> nil then FStepDropDown.SetItemIndex(Idx); if FWebServer <> nil then FWebServer.FMStepIdx := Idx; - if (FMode = MODE_FM) and (FSpecView <> nil) then + if (FController.FMode = MODE_FM) and (FSpecView <> nil) then FSpecView.FMGridStepHz := FM_STEP_HZ[Idx]; end; procedure TMainForm.BtnFMStepClick(Sender: TObject); begin - FFMStepOn := not FFMStepOn; - StyleButton(BtnFMStep, FFMStepOn); + FController.FFMStepOn := not FController.FFMStepOn; + StyleButton(BtnFMStep, FController.FFMStepOn); BtnFMStep.Repaint; SaveCurrentBand; end; @@ -5434,12 +5387,12 @@ var names: array of string; i: Integer; begin - if (FChannelStore = nil) or (FChannelsDropDown = nil) then Exit; - SetLength(names, FChannelStore.Count); - for i := 0 to FChannelStore.Count - 1 do - names[i] := FChannelStore.GetChannel(i).Name; + if (FController.FChannelStore = nil) or (FChannelsDropDown = nil) then Exit; + SetLength(names, FController.FChannelStore.Count); + for i := 0 to FController.FChannelStore.Count - 1 do + names[i] := FController.FChannelStore.GetChannel(i).Name; FChannelsDropDown.SetItems(names); - if (FActiveChannelIdx >= 0) and (FActiveChannelIdx < FChannelStore.Count) then + if (FActiveChannelIdx >= 0) and (FActiveChannelIdx < FController.FChannelStore.Count) then FChannelsDropDown.SetItemIndex(FActiveChannelIdx) else if BtnChannels <> nil then BtnChannels.Caption := 'Channel'; @@ -5452,7 +5405,7 @@ end; procedure TMainForm.BtnChannelsClick(Sender: TObject); begin - if FChannelStore.Count = 0 then Exit; + if FController.FChannelStore.Count = 0 then Exit; FCTCSSDropDown.ClosePopup; FStepDropDown.ClosePopup; FChannelsDropDown.Toggle; @@ -5466,16 +5419,16 @@ begin if Button <> mbRight then Exit; FChannelsDropDown.ClosePopup; if FChannelsForm = nil then - FChannelsForm := TChannelsForm.Create(Self, FChannelStore, OnChannelStoreChanged); + FChannelsForm := TChannelsForm.Create(Self, FController.FChannelStore, OnChannelStoreChanged); // Передаём текущее состояние радио как значения по умолчанию для нового канала TChannelStore.DefaultChannel(Def); - Def.RXFreq := FVfoA; + Def.RXFreq := FController.FVfoA; Def.TXFreq := ActiveTXFreqHz; - Def.Mode := FMode; - Def.RptDir := FFMRptDir; - Def.RptOffsetHz := FFMRptOffsetHz; - Def.CTCSSOn := FFMCTCSSOn; - Def.CTCSSToneIdx := FFMCTCSSToneIdx; + Def.Mode := FController.FMode; + Def.RptDir := FController.FFMRptDir; + Def.RptOffsetHz := FController.FFMRptOffsetHz; + Def.CTCSSOn := FController.FFMCTCSSOn; + Def.CTCSSToneIdx := FController.FFMCTCSSToneIdx; Def.Power := 100; Def.Name := ''; TChannelsForm(FChannelsForm).SetCurrentDefaults(Def); @@ -5486,8 +5439,8 @@ end; procedure TMainForm.OnChannelsDropDownSelect(Sender: TObject; Idx: Integer); var Ch: TChannel; begin - if (FChannelStore = nil) or (Idx < 0) or (Idx >= FChannelStore.Count) then Exit; - Ch := FChannelStore.GetChannel(Idx); + if (FController.FChannelStore = nil) or (Idx < 0) or (Idx >= FController.FChannelStore.Count) then Exit; + Ch := FController.FChannelStore.GetChannel(Idx); FActiveChannelIdx := Idx; StyleButton(BtnChannels, True); BtnChannels.Caption := Ch.Name; @@ -5505,9 +5458,9 @@ begin // Ищем подходящий XVTR-слот по частоте канала XvtrIdx := -1; for i := 0 to CFG_XVTR_COUNT - 1 do - if FXvtrSettings.Entries[i].Enabled and - (Ch.RXFreq >= FXvtrSettings.Entries[i].FreqBegin) and - (Ch.RXFreq <= FXvtrSettings.Entries[i].FreqEnd) then + if FController.FXvtrSettings.Entries[i].Enabled and + (Ch.RXFreq >= FController.FXvtrSettings.Entries[i].FreqBegin) and + (Ch.RXFreq <= FController.FXvtrSettings.Entries[i].FreqEnd) then begin XvtrIdx := i; Break; @@ -5518,34 +5471,34 @@ begin if XvtrIdx >= 0 then begin // Канал на трансвертерном диапазоне — активируем XVTR если не активен - if FCurrentXvtr <> XvtrIdx then + if FController.FCurrentXvtr <> XvtrIdx then ActivateXvtrBand(XvtrIdx); end else begin // Канал на HF (или неизвестная частота) // Если были на XVTR — выходим из него, иначе ApplyVfoA заклэмпит частоту - if FCurrentXvtr >= 0 then + if FController.FCurrentXvtr >= 0 then DeactivateXvtr; NewBand := FreqToBandIdx(Ch.RXFreq); - if (NewBand >= 0) and (NewBand <> FCurrentBand) then + if (NewBand >= 0) and (NewBand <> FController.FCurrentBand) then begin - StyleButton(BtnBand[FCurrentBand], False); - FCurrentBand := NewBand; - StyleButton(BtnBand[FCurrentBand], True); - FDriveLevel := CalcDriveByte; + StyleButton(BtnBand[FController.FCurrentBand], False); + FController.FCurrentBand := NewBand; + StyleButton(BtnBand[FController.FCurrentBand], True); + FController.FDriveLevel := CalcDriveByte; end; end; - // Режим (после ActivateXvtrBand/DeactivateXvtr FMode обновился — сравниваем снова) - if FMode <> Ch.Mode then + // Режим (после ActivateXvtrBand/DeactivateXvtr FController.FMode обновился — сравниваем снова) + if FController.FMode <> Ch.Mode then begin - FMode := Ch.Mode; - for i := 0 to MODE_COUNT - 1 do StyleButton(BtnMode[i], i = FMode); - if FWDSPReady then + FController.FMode := Ch.Mode; + for i := 0 to MODE_COUNT - 1 do StyleButton(BtnMode[i], i = FController.FMode); + if FController.FWDSPReady then begin - FDSPEngine.SetMode(FMode); - if FTuning then FDSPEngine.SetTXTone(True, FTXSettings.TUNFreq, 1.0); + FController.FDSPEngine.SetMode(FController.FMode); + if FController.FTuning then FController.FDSPEngine.SetTXTone(True, FController.FTXSettings.TUNFreq, 1.0); end; UpdateFilterButtons; ApplyModeFilter; @@ -5553,24 +5506,24 @@ begin end; // FM-параметры - if FMode = MODE_FM then + if FController.FMode = MODE_FM then begin - FFMCTCSSOn := Ch.CTCSSOn; - FFMCTCSSAutoActive := Ch.CTCSSOn; // CTCSS из канала — авто, сбросится при уходе с частоты + FController.FFMCTCSSOn := Ch.CTCSSOn; + FController.FFMCTCSSAutoActive := Ch.CTCSSOn; // CTCSS из канала — авто, сбросится при уходе с частоты SetFMCTCSSTone(Ch.CTCSSToneIdx); if BtnFMCTCSS <> nil then begin - StyleButton(BtnFMCTCSS, FFMCTCSSOn); + StyleButton(BtnFMCTCSS, FController.FFMCTCSSOn); BtnFMCTCSS.Repaint; end; - if FWDSPReady then - FDSPEngine.SetTXCTCSS(FFMCTCSSOn, CTCSS_TONES[FFMCTCSSToneIdx]); - FFMRptDir := Ch.RptDir; - FFMRptOffsetHz := Ch.RptOffsetHz; - FFMRptAutoActive := Ch.RptDir <> RPT_NONE; // RPT из канала — авто, сбросится при уходе - if BtnFMRptMinus <> nil then StyleButton(BtnFMRptMinus, FFMRptDir = RPT_MINUS); - if BtnFMRptPlus <> nil then StyleButton(BtnFMRptPlus, FFMRptDir = RPT_PLUS); - if EdFMRptOffset <> nil then EdFMRptOffset.Text := RptFormatOffset(FFMRptOffsetHz); + if FController.FWDSPReady then + FController.FDSPEngine.SetTXCTCSS(FController.FFMCTCSSOn, CTCSS_TONES[FController.FFMCTCSSToneIdx]); + FController.FFMRptDir := Ch.RptDir; + FController.FFMRptOffsetHz := Ch.RptOffsetHz; + FController.FFMRptAutoActive := Ch.RptDir <> RPT_NONE; // RPT из канала — авто, сбросится при уходе + if BtnFMRptMinus <> nil then StyleButton(BtnFMRptMinus, FController.FFMRptDir = RPT_MINUS); + if BtnFMRptPlus <> nil then StyleButton(BtnFMRptPlus, FController.FFMRptDir = RPT_PLUS); + if EdFMRptOffset <> nil then EdFMRptOffset.Text := RptFormatOffset(FController.FFMRptOffsetHz); end; // Мощность из канала. @@ -5579,7 +5532,7 @@ begin if Ch.Power >= 0 then begin i := TrkDrive.Position; // запомнить до срабатывания OnChange - TrkDrive.Position := EnsureRange(Ch.Power, 0, 100); // TrkDriveChange обновит FDriveLevel и WDSP + TrkDrive.Position := EnsureRange(Ch.Power, 0, 100); // TrkDriveChange обновит FController.FDriveLevel и WDSP if FPreChannelPower < 0 then FPreChannelPower := i; // установить после (TrkDriveChange уже сбросил в -1) end; @@ -5598,13 +5551,13 @@ procedure TMainForm.CheckChannelActive; var Ch: TChannel; begin if FActiveChannelIdx < 0 then Exit; - if (FChannelStore = nil) or (FActiveChannelIdx >= FChannelStore.Count) then + if (FController.FChannelStore = nil) or (FActiveChannelIdx >= FController.FChannelStore.Count) then begin FActiveChannelIdx := -1; Exit; end; - Ch := FChannelStore.GetChannel(FActiveChannelIdx); - if Round(FVfoA) <> Round(Ch.RXFreq) then + Ch := FController.FChannelStore.GetChannel(FActiveChannelIdx); + if Round(FController.FVfoA) <> Round(Ch.RXFreq) then begin FActiveChannelIdx := -1; StyleButton(BtnChannels, False); @@ -5612,25 +5565,25 @@ begin BtnChannels.Repaint; // Сбрасываем CTCSS если он был включён каналом (не вручную) - if FFMCTCSSAutoActive then + if FController.FFMCTCSSAutoActive then begin - FFMCTCSSAutoActive := False; - FFMCTCSSOn := False; + FController.FFMCTCSSAutoActive := False; + FController.FFMCTCSSOn := False; if BtnFMCTCSS <> nil then begin StyleButton(BtnFMCTCSS, False); BtnFMCTCSS.Repaint; end; - if FWDSPReady then - FDSPEngine.SetTXCTCSS(False, CTCSS_TONES[FFMCTCSSToneIdx]); + if FController.FWDSPReady then + FController.FDSPEngine.SetTXCTCSS(False, CTCSS_TONES[FController.FFMCTCSSToneIdx]); end; // Сбрасываем RPT если он был включён каналом (не вручную), затем // даём UpdateRptAutoState переоценить авто-MINUS для текущей частоты - if FFMRptAutoActive then + if FController.FFMRptAutoActive then begin - FFMRptDir := RPT_NONE; - FFMRptAutoActive := False; + FController.FFMRptDir := RPT_NONE; + FController.FFMRptAutoActive := False; if BtnFMRptMinus <> nil then StyleButton(BtnFMRptMinus, False); if BtnFMRptPlus <> nil then StyleButton(BtnFMRptPlus, False); UpdateRptAutoState; @@ -5641,29 +5594,29 @@ begin begin TrkDrive.Position := FPreChannelPower; FPreChannelPower := -1; - FDriveLevel := CalcDriveByte; - if FWDSPReady then - FDSPEngine.SetDriveLevel(TrkDrive.Position / 100.0); + FController.FDriveLevel := CalcDriveByte; + if FController.FWDSPReady then + FController.FDSPEngine.SetDriveLevel(TrkDrive.Position / 100.0); end; end; end; procedure TMainForm.SetFMCTCSSTone(Idx: Integer); begin - FFMCTCSSToneIdx := Idx; + FController.FFMCTCSSToneIdx := Idx; if FCTCSSDropDown <> nil then FCTCSSDropDown.SetItemIndex(Idx); - if FWDSPReady then - FDSPEngine.SetTXCTCSS(FFMCTCSSOn, CTCSS_TONES[Idx]); + if FController.FWDSPReady then + FController.FDSPEngine.SetTXCTCSS(FController.FFMCTCSSOn, CTCSS_TONES[Idx]); end; procedure TMainForm.BtnFMCTCSSClick(Sender: TObject); begin - FFMCTCSSOn := not FFMCTCSSOn; - FFMCTCSSAutoActive := False; // ручное действие снимает авто-флаг - StyleButton(BtnFMCTCSS, FFMCTCSSOn); + FController.FFMCTCSSOn := not FController.FFMCTCSSOn; + FController.FFMCTCSSAutoActive := False; // ручное действие снимает авто-флаг + StyleButton(BtnFMCTCSS, FController.FFMCTCSSOn); BtnFMCTCSS.Repaint; // Flush visual state before WDSP call - if FWDSPReady then - FDSPEngine.SetTXCTCSS(FFMCTCSSOn, CTCSS_TONES[FFMCTCSSToneIdx]); + if FController.FWDSPReady then + FController.FDSPEngine.SetTXCTCSS(FController.FFMCTCSSOn, CTCSS_TONES[FController.FFMCTCSSToneIdx]); end; procedure TMainForm.BtnFMCTCSSToneClick(Sender: TObject); @@ -5684,28 +5637,28 @@ var ActiveFreq: Int64; begin // Меняем частоты местами - Tmp := FVfoA; FVfoA := FVfoB; FVfoB := Tmp; + Tmp := FController.FVfoA; FController.FVfoA := FController.FVfoB; FController.FVfoB := Tmp; // Определяем новую частоту активного VFO - if FActiveVfo = 0 then ActiveFreq := Round(FVfoA) - else ActiveFreq := Round(FVfoB); + if FController.FActiveVfo = 0 then ActiveFreq := Round(FController.FVfoA) + else ActiveFreq := Round(FController.FVfoB); // Перестраиваем приёмник - FCenterFreq := ActiveFreq; - if FWDSPReady then FDSPEngine.SetShift(0.0); - if FNetwork.Connected and FNetwork.Running then + FController.FCenterFreq := ActiveFreq; + if FController.FWDSPReady then FController.FDSPEngine.SetShift(0.0); + if FController.FNetwork.Connected and FController.FNetwork.Running then begin - FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslate(ActiveTXFreqHz), FDriveLevel, FTransmitting, True, True); - FNetwork.SendFullHP; + FController.FNetwork.UpdateState(XvtrTranslate(FController.FCenterFreq), XvtrTranslate(ActiveTXFreqHz), FController.FDriveLevel, FController.FTransmitting, True, True); + FController.FNetwork.SendFullHP; end; // Обновляем кнопку диапазона BandIdx := FreqToBandIdx(ActiveFreq); - if (BandIdx >= 0) and (BandIdx <> FCurrentBand) then + if (BandIdx >= 0) and (BandIdx <> FController.FCurrentBand) then begin - StyleButton(BtnBand[FCurrentBand], False); - FCurrentBand := BandIdx; - StyleButton(BtnBand[FCurrentBand], True); + StyleButton(BtnBand[FController.FCurrentBand], False); + FController.FCurrentBand := BandIdx; + StyleButton(BtnBand[FController.FCurrentBand], True); end; FSpecView.InvalidateRulerCache; @@ -5719,48 +5672,48 @@ end; procedure TMainForm.BtnVfoACopyBClick(Sender: TObject); begin // A>B: копируем частоту A в B (VFO-B получает частоту VFO-A) - FVfoB := FVfoA; - if FActiveVfo = 1 then + FController.FVfoB := FController.FVfoA; + if FController.FActiveVfo = 1 then begin // B активен — перестраиваем приёмник на новую частоту B - FCenterFreq := Round(FVfoB); - if FWDSPReady then FDSPEngine.SetShift(0.0); - if FNetwork.Connected and FNetwork.Running then + FController.FCenterFreq := Round(FController.FVfoB); + if FController.FWDSPReady then FController.FDSPEngine.SetShift(0.0); + if FController.FNetwork.Connected and FController.FNetwork.Running then begin - FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslate(ActiveTXFreqHz), FDriveLevel, FTransmitting, True, True); - FNetwork.SendFullHP; + FController.FNetwork.UpdateState(XvtrTranslate(FController.FCenterFreq), XvtrTranslate(ActiveTXFreqHz), FController.FDriveLevel, FController.FTransmitting, True, True); + FController.FNetwork.SendFullHP; end; FSpecView.InvalidateRulerCache; SyncSpecViewFreq; FSpecView.DrawSpectrum; PbSpectrum.Invalidate; if PbRuler <> nil then PbRuler.Invalidate; end; - FreqDispB.Frequency := Round(FVfoB); + FreqDispB.Frequency := Round(FController.FVfoB); UpdateVfoDisplay; end; procedure TMainForm.BtnVfoBCopyAClick(Sender: TObject); begin // B>A: копируем частоту B в A (VFO-A получает частоту VFO-B) - FVfoA := FVfoB; - if FActiveVfo = 0 then - ApplyVfoA(Round(FVfoA)) // A активен — перестраиваем приёмник + FController.FVfoA := FController.FVfoB; + if FController.FActiveVfo = 0 then + ApplyVfoA(Round(FController.FVfoA)) // A активен — перестраиваем приёмник else begin - FreqDispA.Frequency := Round(FVfoA); + FreqDispA.Frequency := Round(FController.FVfoA); UpdateVfoDisplay; end; end; function TMainForm.ActiveTXFreqHz: Double; begin - if FSplitTxB then Result := FVfoB - else if FActiveVfo = 0 then Result := FVfoA - else Result := FVfoB; - if FMode = MODE_FM then - case FFMRptDir of - RPT_MINUS: Result := Result - FFMRptOffsetHz; - RPT_PLUS: Result := Result + FFMRptOffsetHz; + if FController.FSplitTxB then Result := FController.FVfoB + else if FController.FActiveVfo = 0 then Result := FController.FVfoA + else Result := FController.FVfoB; + if FController.FMode = MODE_FM then + case FController.FFMRptDir of + RPT_MINUS: Result := Result - FController.FFMRptOffsetHz; + RPT_PLUS: Result := Result + FController.FFMRptOffsetHz; end; end; @@ -5769,18 +5722,18 @@ var WasTransmitting: Boolean; begin // Safety: block TX on bands marked DoNotTx in Alex config - if Active and FDevConnected and (FCurrentXvtr < 0) - and FAlexSettings.DoNotTx[EnsureRange(FCurrentBand, 0, 10)] then + if Active and FController.FDevConnected and (FController.FCurrentXvtr < 0) + and FController.FAlexSettings.DoNotTx[EnsureRange(FController.FCurrentBand, 0, 10)] then Exit; // Safety: block TX on RX-only transverter - if Active and (FCurrentXvtr >= 0) and (FCurrentXvtr < CFG_XVTR_COUNT) - and FXvtrSettings.Entries[FCurrentXvtr].RXOnly then + if Active and (FController.FCurrentXvtr >= 0) and (FController.FCurrentXvtr < CFG_XVTR_COUNT) + and FController.FXvtrSettings.Entries[FController.FCurrentXvtr].RXOnly then Exit; - WasTransmitting := FTransmitting; - FTransmitting := Active; - if Active then FDriveLevel := CalcDriveByte; + WasTransmitting := FController.FTransmitting; + FController.FTransmitting := Active; + if Active then FController.FDriveLevel := CalcDriveByte; FDUCPendingCount := 0; - if FTransmitting then + if FController.FTransmitting then begin BtnMOX.ClrNorm := TColor($00000044); BtnMOX.ClrText := TColor($000000FF); @@ -5790,55 +5743,55 @@ begin StyleButton(BtnMOX, False); // TX-overlay в SpectrumView: фиксируем актуальную TX-частоту/VFO-индекс и режим FSpecView.TXFreq := ActiveTXFreqHz; - if FSplitTxB then FSpecView.TXVfoIndex := 1 - else FSpecView.TXVfoIndex := FActiveVfo; + if FController.FSplitTxB then FSpecView.TXVfoIndex := 1 + else FSpecView.TXVfoIndex := FController.FActiveVfo; // DUP: данные остаются от RX-анализатора (TXMode=False), но TX-полоса // фильтра рисуется поверх через TXOverlay. Без DUP при TX оба флага True. - FSpecView.TXMode := FTransmitting and not FDisplayDuplex; - FSpecView.TXOverlay := FTransmitting; + FSpecView.TXMode := FController.FTransmitting and not FController.FDisplayDuplex; + FSpecView.TXOverlay := FController.FTransmitting; // Перерисовываем сетку спектра под активный тракт (Thetis-style). ApplySpecViewGridFromState; - if FTransmitting and FNetwork.Connected and FNetwork.Running then + if FController.FTransmitting and FController.FNetwork.Connected and FController.FNetwork.Running then begin - FNetwork.ClearDUCIQQueue; - FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslate(ActiveTXFreqHz), FDriveLevel, + FController.FNetwork.ClearDUCIQQueue; + FController.FNetwork.UpdateState(XvtrTranslate(FController.FCenterFreq), XvtrTranslate(ActiveTXFreqHz), FController.FDriveLevel, True, True, True); - FNetwork.SendFullHP; // 1й пакет: Alex bit27=1 (T/R relay), drive — без PTT - FNetwork.SendPTT(True); // 2й пакет: PTT=1 после переключения реле + FController.FNetwork.SendFullHP; // 1й пакет: Alex bit27=1 (T/R relay), drive — без PTT + FController.FNetwork.SendPTT(True); // 2й пакет: PTT=1 после переключения реле end; - if FWDSPReady then + if FController.FWDSPReady then begin // Mic source — определяем автоматически по источнику TX: // Web TX → txmsWeb - // HW PTT (FHWPTTActive при входе в ApplyMOX уже True) → txmsRadio + // HW PTT (FController.FHWPTTActive при входе в ApplyMOX уже True) → txmsRadio // MOX-кнопка/CAT → sound card если настроена, иначе txmsRadio - if FTransmitting then + if FController.FTransmitting then begin if Assigned(FWebServer) and FWebServer.WebClientActive then begin FWebMicActive := True; - FDSPEngine.SetTXMicSource(txmsWeb); + FController.FDSPEngine.SetTXMicSource(txmsWeb); end - else if FHWPTTActive then - FDSPEngine.SetTXMicSource(txmsRadio) + else if FController.FHWPTTActive then + FController.FDSPEngine.SetTXMicSource(txmsRadio) else - FDSPEngine.SetTXMicSource(DefaultMicSource); + FController.FDSPEngine.SetTXMicSource(DefaultMicSource); end else if FWebMicActive then begin FWebMicActive := False; - FDSPEngine.SetTXMicSource(DefaultMicSource); + FController.FDSPEngine.SetTXMicSource(DefaultMicSource); end; // В non-DUP RX-IQ пакеты дропаются на входе DSP-потока во время TX, // чтобы TX leakage не накапливался в RXA pipeline и FFT-истории // RX-анализатора. В DUP RX-тракт работает как обычно. - FDSPEngine.KeepRXDuringTX := FDisplayDuplex; - FDSPEngine.SetTXRun(FTransmitting); - if WasTransmitting and (not FTransmitting) and (not FDisplayDuplex) then + FController.FDSPEngine.KeepRXDuringTX := FController.FDisplayDuplex; + FController.FDSPEngine.SetTXRun(FController.FTransmitting); + if WasTransmitting and (not FController.FTransmitting) and (not FController.FDisplayDuplex) then begin - FDSPEngine.FlushRX; - if Assigned(FAudioOut) then - FAudioOut.Clear; + FController.FDSPEngine.FlushRX; + if Assigned(FController.FAudioOut) then + FController.FAudioOut.Clear; {$IFDEF WINDOWS} // Windows: на TX→RX дропаем И RX-IQ пакеты, И аудио-выход на это окно. // Источник «хвоста» — radio FPGA TX-buffer + PA slew-down: после MOX-off @@ -5846,42 +5799,42 @@ begin // отдаёт это и в водопад, и в звук. На Linux наблюдается окно <30мс, // на Windows может тянуться 200–500мс. Подобрать значение можно здесь // без перекомпиляции engine. На Linux код-путь не активируется. - FDSPEngine.BeginPostTXMute(250); + FController.FDSPEngine.BeginPostTXMute(250); {$ENDIF} end; // SetTXRun переключает FActiveDisplayID на TX_DISP_ID; при DUP возвращаем // источник на RX, чтобы видеть приём во время передачи. - if FTransmitting and FDisplayDuplex then - FDSPEngine.SetDisplaySourceTX(False); + if FController.FTransmitting and FController.FDisplayDuplex then + FController.FDSPEngine.SetDisplaySourceTX(False); end; - if (not FTransmitting) and FNetwork.Connected and FNetwork.Running then + if (not FController.FTransmitting) and FController.FNetwork.Connected and FController.FNetwork.Running then begin - FNetwork.SendPTT(False); // 1й пакет: PTT=0 — сначала снимаем PTT - FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslate(ActiveTXFreqHz), FDriveLevel, + FController.FNetwork.SendPTT(False); // 1й пакет: PTT=0 — сначала снимаем PTT + FController.FNetwork.UpdateState(XvtrTranslate(FController.FCenterFreq), XvtrTranslate(ActiveTXFreqHz), FController.FDriveLevel, False, True, True); - FNetwork.ClearDUCIQQueue; - FNetwork.SendFullHP; // 2й пакет: Alex bit27=0 (T/R relay RX), drive=0 + FController.FNetwork.ClearDUCIQQueue; + FController.FNetwork.SendFullHP; // 2й пакет: Alex bit27=0 (T/R relay RX), drive=0 end; end; procedure TMainForm.BtnMOXClick(Sender: TObject); begin // Если идёт tune — клик MOX просто выходит из tune (без перехода на голос). - if FTuning then + if FController.FTuning then begin ApplyTUN(False); Exit; end; - ApplyMOX(not FTransmitting); + ApplyMOX(not FController.FTransmitting); end; procedure TMainForm.BtnMuteClick(Sender: TObject); begin - FMuted := not FMuted; - StyleButton(BtnMute, FMuted); - if FMuted then BtnMute.Caption := 'UNMUTE' + FController.FMuted := not FController.FMuted; + StyleButton(BtnMute, FController.FMuted); + if FController.FMuted then BtnMute.Caption := 'UNMUTE' else BtnMute.Caption := 'MUTE'; - if FWDSPReady then FDSPEngine.SetMute(FMuted); + if FController.FWDSPReady then FController.FDSPEngine.SetMute(FController.FMuted); end; procedure TMainForm.UpdateNRButton; @@ -5926,58 +5879,58 @@ end; procedure TMainForm.ApplyNoiseFilterButtonsToDSP; begin - if not FWDSPReady then Exit; - FDSPEngine.SetNRMode(BtnNR.Tag); - FDSPEngine.SetNBMode(BtnNB.Tag); - FDSPEngine.SetSNB(BtnSNB.Tag <> 0); - FDSPEngine.SetANF(BtnANF.Tag <> 0); + if not FController.FWDSPReady then Exit; + FController.FDSPEngine.SetNRMode(BtnNR.Tag); + FController.FDSPEngine.SetNBMode(BtnNB.Tag); + FController.FDSPEngine.SetSNB(BtnSNB.Tag <> 0); + FController.FDSPEngine.SetANF(BtnANF.Tag <> 0); end; procedure TMainForm.BtnNRClick(Sender: TObject); begin BtnNR.Tag := (BtnNR.Tag + 1) mod 5; UpdateNRButton; - if FWDSPReady then FDSPEngine.SetNRMode(BtnNR.Tag); + if FController.FWDSPReady then FController.FDSPEngine.SetNRMode(BtnNR.Tag); end; procedure TMainForm.BtnNBClick(Sender: TObject); begin BtnNB.Tag := (BtnNB.Tag + 1) mod 3; UpdateNBButton; - if FWDSPReady then FDSPEngine.SetNBMode(BtnNB.Tag); + if FController.FWDSPReady then FController.FDSPEngine.SetNBMode(BtnNB.Tag); end; procedure TMainForm.BtnSNBClick(Sender: TObject); begin BtnSNB.Tag := 1 - BtnSNB.Tag; UpdateSNBButton; - if FWDSPReady then FDSPEngine.SetSNB(BtnSNB.Tag <> 0); + if FController.FWDSPReady then FController.FDSPEngine.SetSNB(BtnSNB.Tag <> 0); end; procedure TMainForm.BtnANFClick(Sender: TObject); begin BtnANF.Tag := 1 - BtnANF.Tag; UpdateANFButton; - if FWDSPReady then FDSPEngine.SetANF(BtnANF.Tag <> 0); + if FController.FWDSPReady then FController.FDSPEngine.SetANF(BtnANF.Tag <> 0); end; procedure TMainForm.ApplyWfAGCNF(WfAGC, WfNF: Boolean); begin - FWfAGCEnabled := WfAGC; - FWfNFEnabled := WfNF; - FSpecView.WfAGCEnabled := FWfAGCEnabled; - FSpecView.WfNFEnabled := FWfNFEnabled; + FController.FWfAGCEnabled := WfAGC; + FController.FWfNFEnabled := WfNF; + FSpecView.WfAGCEnabled := FController.FWfAGCEnabled; + FSpecView.WfNFEnabled := FController.FWfNFEnabled; end; procedure TMainForm.ApplyADCSettings(Dither, Random: Boolean); begin - FDitherEnabled := Dither; - FRandomEnabled := Random; - if FNetwork.Connected and FRunning then - FNetwork.ConfigureDDCs(1, FSampleRate div 1000, 0, FDitherEnabled, FRandomEnabled); - if FDevConnected then - FSettings.SaveGlobal(FDevMAC, MakeGlobalSettings); + FController.FDitherEnabled := Dither; + FController.FRandomEnabled := Random; + if FController.FNetwork.Connected and FController.FRunning then + FController.FNetwork.ConfigureDDCs(1, FController.FSampleRate div 1000, 0, FController.FDitherEnabled, FController.FRandomEnabled); + if FController.FDevConnected then + FController.FSettings.SaveGlobal(FController.FDevMAC, MakeGlobalSettings); end; procedure TMainForm.ApplyWebSettings(Enabled: Boolean; Port: Integer; @@ -5996,7 +5949,7 @@ begin W.BindAddr := BindAddr; W.User := User; W.Pass := Pass; - FSettings.SaveWebSettings(W); + FController.FSettings.SaveWebSettings(W); end; procedure TMainForm.OnSampleRateHidePanel; @@ -6008,8 +5961,8 @@ begin PanelLeft.Width := 0; FVfoOverlay.Width := 260; FVfoOverlay.Height := 136; // OVL_H_NORM — расширяется сам при открытии AGC-пикера - FVfoOverlay.SetState(FMode, FFilterBW, FVfoA, FLastSMeter); - FVfoOverlay.SetDSPState(BtnNR.Tag, BtnNB.Tag, BtnSNB.Tag <> 0, BtnANF.Tag <> 0, FAGCMode); + FVfoOverlay.SetState(FController.FMode, FController.FFilterBW, FController.FVfoA, FLastSMeter); + FVfoOverlay.SetDSPState(BtnNR.Tag, BtnNB.Tag, BtnSNB.Tag <> 0, BtnANF.Tag <> 0, FController.FAGCMode); FVfoOverlay.Visible := True; PositionVfoOverlay; end @@ -6033,21 +5986,21 @@ begin if not Assigned(FVfoOverlay) or not FVfoOverlay.Visible then Exit; OW := FVfoOverlay.Width; - if (PbSpectrum.Width > 0) and (FSpanHz > 0) then + if (PbSpectrum.Width > 0) and (FController.FSpanHz > 0) then begin - VfoX := Round((FVfoA - FCenterFreq + FSpanHz / 2) / FSpanHz * PbSpectrum.Width) + VfoX := Round((FController.FVfoA - FController.FCenterFreq + FController.FSpanHz / 2) / FController.FSpanHz * PbSpectrum.Width) end else VfoX := PbSpectrum.Width div 2; - case FMode of + case FController.FMode of MODE_LSB: HiHz := -100; - MODE_USB: HiHz := FFilterBW; + MODE_USB: HiHz := FController.FFilterBW; else - HiHz := FFilterBW / 2; + HiHz := FController.FFilterBW / 2; end; - if (PbSpectrum.Width > 0) and (FSpanHz > 0) then - FilterEndX := VfoX + Round(HiHz / FSpanHz * PbSpectrum.Width) + if (PbSpectrum.Width > 0) and (FController.FSpanHz > 0) then + FilterEndX := VfoX + Round(HiHz / FController.FSpanHz * PbSpectrum.Width) else FilterEndX := VfoX; FilterEndX := Max(0, Min(PbSpectrum.Width - 1, FilterEndX)); @@ -6072,29 +6025,29 @@ procedure TMainForm.OnModeFilterSelect(Mode: Integer; FilterBW: Integer); var Idx: Integer; begin - FMode := Mode; - FFilterBW := FilterBW; + FController.FMode := Mode; + FController.FFilterBW := FilterBW; if Mode = MODE_FM then begin - FFilter := FILT_FM_DEF; + FController.FFilter := FILT_FM_DEF; for Idx := 0 to FILT_FM_COUNT - 1 do if FILT_FM_BW[Idx] = FilterBW then begin - FFilter := Idx; - FFMDeviation := FILT_FM_DEV[Idx]; + FController.FFilter := Idx; + FController.FFMDeviation := FILT_FM_DEV[Idx]; Break; end; end; - if FWDSPReady then + if FController.FWDSPReady then begin - FDSPEngine.SetMode(FMode); + FController.FDSPEngine.SetMode(FController.FMode); ApplyModeFilter; end; SyncSpecViewFreq; if Assigned(FVfoOverlay) and FVfoOverlay.Visible then begin - FVfoOverlay.SetState(FMode, FFilterBW, FVfoA, FLastSMeter); - FVfoOverlay.SetDSPState(BtnNR.Tag, BtnNB.Tag, BtnSNB.Tag <> 0, BtnANF.Tag <> 0, FAGCMode); + FVfoOverlay.SetState(FController.FMode, FController.FFilterBW, FController.FVfoA, FLastSMeter); + FVfoOverlay.SetDSPState(BtnNR.Tag, BtnNB.Tag, BtnSNB.Tag <> 0, BtnANF.Tag <> 0, FController.FAGCMode); PositionVfoOverlay; // перепозиционируем при смене LSB↔USB end; end; @@ -6114,11 +6067,11 @@ const var I: Integer; begin - FAGCMode := AGCMode; + FController.FAGCMode := AGCMode; for I := 0 to 4 do StyleButton(BtnAGCMode[I], I = AGCMode); - if FWDSPReady then - FDSPEngine.SetAGC(AGCModes[AGCMode], 50.0); - FBandCache[FCurrentBand].AGCMode := FAGCMode; + if FController.FWDSPReady then + FController.FDSPEngine.SetAGC(AGCModes[AGCMode], 50.0); + FController.FBandCache[FController.FCurrentBand].AGCMode := FController.FAGCMode; end; procedure TMainForm.OnVfoOverlayInvalidate(Sender: TObject); @@ -6138,47 +6091,47 @@ var DDCRate: Word; begin NewRate := SampleRate; - if NewRate = FSampleRate then Exit; + if NewRate = FController.FSampleRate then Exit; - FSampleRate := NewRate; - FSpanHz := NewRate; + FController.FSampleRate := NewRate; + FController.FSpanHz := NewRate; DDCRate := NewRate div 1000; if Assigned(FSampleRateOverlay) then FSampleRateOverlay.SetCurrentRate(NewRate); Application.ProcessMessages; - FBandCache[FCurrentBand].SpanHz := FSpanHz; // для обратной совместимости + FController.FBandCache[FController.FCurrentBand].SpanHz := FController.FSpanHz; // для обратной совместимости // SampleRate — глобальный: сохраняем в global settings - if FDevConnected then - FSettings.SaveGlobal(FDevMAC, MakeGlobalSettings); + if FController.FDevConnected then + FController.FSettings.SaveGlobal(FController.FDevMAC, MakeGlobalSettings); // 1. Останавливаем WDSP и сбрасываем очередь старых пакетов - if FWDSPReady then + if FController.FWDSPReady then begin - FWDSPReady := False; - FDSPEngine.ChangeSampleRate(NewRate); // Close + FlushQueue + Open + FController.FWDSPReady := False; + FController.FDSPEngine.ChangeSampleRate(NewRate); // Close + FlushQueue + Open end; // 2. Отправляем новый rate трансиверу // (после остановки DSP — новые пакеты сразу идут с правильным rate) - if FNetwork.Connected then - FNetwork.ConfigureDDCs(1, DDCRate, 0, FDitherEnabled, FRandomEnabled); + if FController.FNetwork.Connected then + FController.FNetwork.ConfigureDDCs(1, DDCRate, 0, FController.FDitherEnabled, FController.FRandomEnabled); // 3. Восстанавливаем настройки DSP - FWDSPReady := FDSPEngine.Initialized; - if FWDSPReady then + FController.FWDSPReady := FController.FDSPEngine.Initialized; + if FController.FWDSPReady then begin - FDSPEngine.SetMode(FMode); - FDSPEngine.SetVolume(FVolume / 100.0); + FController.FDSPEngine.SetMode(FController.FMode); + FController.FDSPEngine.SetVolume(FController.FVolume / 100.0); ApplyModeFilter; ApplyNoiseFilterButtonsToDSP; // ChangeSampleRate пересоздаёт WDSP-канал — восстанавливаем AGC - FDSPEngine.SetAGCTop(FAGCTop); - FDSPEngine.SetAGC(TWDSPAGCMode(FAGCMode), 50.0); - FDSPEngine.UpdateAGCLines(FSpectrumWidth); - if FNetwork.Connected then - FDSPEngine.SetShift(FVfoA - FCenterFreq); + FController.FDSPEngine.SetAGCTop(FController.FAGCTop); + FController.FDSPEngine.SetAGC(TWDSPAGCMode(FController.FAGCMode), 50.0); + FController.FDSPEngine.UpdateAGCLines(FSpectrumWidth); + if FController.FNetwork.Connected then + FController.FDSPEngine.SetShift(FController.FVfoA - FController.FCenterFreq); end; end; @@ -6199,22 +6152,22 @@ procedure TMainForm.SyncWebFreq; var BandIdx: Integer; begin // Веб шлёт "freq" для активного VFO - if FActiveVfo = 0 then + if FController.FActiveVfo = 0 then ApplyVfoA(Round(FWebSyncFreq)) else begin - FVfoB := FWebSyncFreq; - FreqDispB.Frequency := Round(FVfoB); - FCenterFreq := FVfoB; - if FWDSPReady then FDSPEngine.SetShift(0.0); - if FRunning then - FNetwork.SetRunAndFreq(True, XvtrTranslate(FCenterFreq), XvtrTranslate(FCenterFreq), FDriveLevel); - BandIdx := FreqToBandIdx(FVfoB); - if (BandIdx >= 0) and (BandIdx <> FCurrentBand) then + FController.FVfoB := FWebSyncFreq; + FreqDispB.Frequency := Round(FController.FVfoB); + FController.FCenterFreq := FController.FVfoB; + if FController.FWDSPReady then FController.FDSPEngine.SetShift(0.0); + if FController.FRunning then + FController.FNetwork.SetRunAndFreq(True, XvtrTranslate(FController.FCenterFreq), XvtrTranslate(FController.FCenterFreq), FController.FDriveLevel); + BandIdx := FreqToBandIdx(FController.FVfoB); + if (BandIdx >= 0) and (BandIdx <> FController.FCurrentBand) then begin - StyleButton(BtnBand[FCurrentBand], False); - FCurrentBand := BandIdx; - StyleButton(BtnBand[FCurrentBand], True); + StyleButton(BtnBand[FController.FCurrentBand], False); + FController.FCurrentBand := BandIdx; + StyleButton(BtnBand[FController.FCurrentBand], True); end; FSpecView.InvalidateRulerCache; SyncSpecViewFreq; @@ -6235,13 +6188,13 @@ procedure TMainForm.SyncWebMode; var i: Integer; begin if (FWebSyncInt < 0) or (FWebSyncInt >= MODE_COUNT) then Exit; - FMode := FWebSyncInt; - for i := 0 to MODE_COUNT - 1 do StyleButton(BtnMode[i], i = FMode); - if FWDSPReady then FDSPEngine.SetMode(FMode); + FController.FMode := FWebSyncInt; + for i := 0 to MODE_COUNT - 1 do StyleButton(BtnMode[i], i = FController.FMode); + if FController.FWDSPReady then FController.FDSPEngine.SetMode(FController.FMode); UpdateFilterButtons; ApplyModeFilter; SyncSpecViewFreq; - FBandCache[FCurrentBand].Mode := FMode; + FController.FBandCache[FController.FCurrentBand].Mode := FController.FMode; end; procedure TMainForm.WebOnFilter(BW: Integer); @@ -6263,29 +6216,29 @@ begin Idx := (-FWebSyncInt) - 1; if (Idx >= 0) and (Idx < FILT_COUNT) then begin - FFilter := Idx; - case FMode of - 0,1: FFilterBW := FILT_SSB_BW[Idx]; - 2: FFilterBW := FILT_DSB_BW[Idx]; - 3,4: FFilterBW := FILT_CW_BW[Idx]; + FController.FFilter := Idx; + case FController.FMode of + 0,1: FController.FFilterBW := FILT_SSB_BW[Idx]; + 2: FController.FFilterBW := FILT_DSB_BW[Idx]; + 3,4: FController.FFilterBW := FILT_CW_BW[Idx]; 5: if Idx < FILT_FM_COUNT then begin - FFilterBW := FILT_FM_BW[Idx]; - FFMDeviation := FILT_FM_DEV[Idx]; + FController.FFilterBW := FILT_FM_BW[Idx]; + FController.FFMDeviation := FILT_FM_DEV[Idx]; end; - else FFilterBW := FILT_AM_BW[Idx]; + else FController.FFilterBW := FILT_AM_BW[Idx]; end; - for j := 0 to FILT_COUNT-1 do StyleButton(BtnFilter[j], j = FFilter); + for j := 0 to FILT_COUNT-1 do StyleButton(BtnFilter[j], j = FController.FFilter); ApplyModeFilter; SyncSpecViewFreq; - FBandCache[FCurrentBand].FilterBW := FFilterBW; + FController.FBandCache[FController.FCurrentBand].FilterBW := FController.FFilterBW; end; Exit; end; // Positive value = bandwidth in Hz, find closest filter - FFilterBW := FWebSyncInt; + FController.FFilterBW := FWebSyncInt; for i := 0 to FILT_COUNT-1 do BW_arr[i] := MaxInt; - case FMode of + case FController.FMode of 0,1: for i := 0 to FILT_COUNT-1 do BW_arr[i] := FILT_SSB_BW[i]; 2: for i := 0 to FILT_COUNT-1 do BW_arr[i] := FILT_DSB_BW[i]; 3,4: for i := 0 to FILT_COUNT-1 do BW_arr[i] := FILT_CW_BW[i]; @@ -6299,12 +6252,12 @@ begin dist := Abs(BW_arr[i] - FWebSyncInt); best := i; end; - FFilter := best; - if FMode = 5 then FFMDeviation := FILT_FM_DEV[best]; - for j := 0 to FILT_COUNT-1 do StyleButton(BtnFilter[j], j = FFilter); + FController.FFilter := best; + if FController.FMode = 5 then FController.FFMDeviation := FILT_FM_DEV[best]; + for j := 0 to FILT_COUNT-1 do StyleButton(BtnFilter[j], j = FController.FFilter); ApplyModeFilter; SyncSpecViewFreq; - FBandCache[FCurrentBand].FilterBW := FFilterBW; + FController.FBandCache[FController.FCurrentBand].FilterBW := FController.FFilterBW; end; procedure TMainForm.WebOnAGC(Mode: Integer); @@ -6318,11 +6271,11 @@ procedure TMainForm.SyncWebAGC; var i: Integer; begin if (FWebSyncInt < 0) or (FWebSyncInt > 4) then Exit; - FAGCMode := FWebSyncInt; - for i := 0 to 4 do StyleButton(BtnAGCMode[i], i = FAGCMode); - if FWDSPReady then - FDSPEngine.SetAGC(TWDSPAGCMode(FAGCMode), 50.0); - FBandCache[FCurrentBand].AGCMode := FAGCMode; + FController.FAGCMode := FWebSyncInt; + for i := 0 to 4 do StyleButton(BtnAGCMode[i], i = FController.FAGCMode); + if FController.FWDSPReady then + FController.FDSPEngine.SetAGC(TWDSPAGCMode(FController.FAGCMode), 50.0); + FController.FBandCache[FController.FCurrentBand].AGCMode := FController.FAGCMode; end; procedure TMainForm.WebOnAGCTop(DB: Integer); @@ -6334,15 +6287,15 @@ end; procedure TMainForm.SyncWebAGCTop; begin - FAGCTop := Max(20, Min(120, FWebSyncInt)); - TrkAGC.Position := FAGCTop; - LblAGCTop.Caption := Format('%ddB', [FAGCTop]); - if FWDSPReady then + FController.FAGCTop := Max(20, Min(120, FWebSyncInt)); + TrkAGC.Position := FController.FAGCTop; + LblAGCTop.Caption := Format('%ddB', [FController.FAGCTop]); + if FController.FWDSPReady then begin - FDSPEngine.SetAGCTop(FAGCTop); - FDSPEngine.SetAGC(TWDSPAGCMode(FAGCMode), 50.0); + FController.FDSPEngine.SetAGCTop(FController.FAGCTop); + FController.FDSPEngine.SetAGC(TWDSPAGCMode(FController.FAGCMode), 50.0); end; - FBandCache[FCurrentBand].AGCTop := FAGCTop; + FController.FBandCache[FController.FCurrentBand].AGCTop := FController.FAGCTop; end; procedure TMainForm.WebOnBand(Idx: Integer); @@ -6356,16 +6309,16 @@ procedure TMainForm.SyncWebBand; begin if (FWebSyncInt < 0) or (FWebSyncInt >= BAND_COUNT) then Exit; // Выйти из XVTR при переходе на HF из web. DeactivateXvtr вернёт HF-состояние. - if FCurrentXvtr >= 0 then + if FController.FCurrentXvtr >= 0 then begin DeactivateXvtr; - if FWebSyncInt = FCurrentBand then Exit; + if FWebSyncInt = FController.FCurrentBand then Exit; end - else if FWebSyncInt = FCurrentBand then + else if FWebSyncInt = FController.FCurrentBand then Exit; - if FDevConnected then begin SaveCurrentBand; FSettings.Save; end; - FCurrentBand := FWebSyncInt; - RestoreBand(FCurrentBand); + if FController.FDevConnected then begin SaveCurrentBand; FController.FSettings.Save; end; + FController.FCurrentBand := FWebSyncInt; + RestoreBand(FController.FCurrentBand); end; procedure TMainForm.WebOnXvtrBand(Idx: Integer); @@ -6383,9 +6336,9 @@ begin Exit; end; if (FWebSyncInt >= CFG_XVTR_COUNT) then Exit; - if not FXvtrSettings.Entries[FWebSyncInt].Enabled then Exit; - if FWebSyncInt = FCurrentXvtr then Exit; - if FDevConnected then begin SaveCurrentBand; FSettings.Save; end; + if not FController.FXvtrSettings.Entries[FWebSyncInt].Enabled then Exit; + if FWebSyncInt = FController.FCurrentXvtr then Exit; + if FController.FDevConnected then begin SaveCurrentBand; FController.FSettings.Save; end; ActivateXvtrBand(FWebSyncInt); end; @@ -6419,9 +6372,9 @@ end; procedure TMainForm.SyncWebVolume; begin - FVolume := Max(0, Min(100, FWebSyncInt)); - TrkVolume.Position := FVolume; - if FWDSPReady then FDSPEngine.SetVolume(FVolume / 100.0); + FController.FVolume := Max(0, Min(100, FWebSyncInt)); + TrkVolume.Position := FController.FVolume; + if FController.FWDSPReady then FController.FDSPEngine.SetVolume(FController.FVolume / 100.0); end; procedure TMainForm.WebOnWfAGC(On_: Boolean); @@ -6433,8 +6386,8 @@ end; procedure TMainForm.SyncWebWfAGC; begin - FWfAGCEnabled := FWebSyncBool; - FSpecView.WfAGCEnabled := FWfAGCEnabled; + FController.FWfAGCEnabled := FWebSyncBool; + FSpecView.WfAGCEnabled := FController.FWfAGCEnabled; end; procedure TMainForm.WebOnWfNF(On_: Boolean); @@ -6446,8 +6399,8 @@ end; procedure TMainForm.SyncWebWfNF; begin - FWfNFEnabled := FWebSyncBool; - FSpecView.WfNFEnabled := FWfNFEnabled; + FController.FWfNFEnabled := FWebSyncBool; + FSpecView.WfNFEnabled := FController.FWfNFEnabled; end; // ── Run ──────────────────────────────────────────────────────────────────── @@ -6462,7 +6415,7 @@ end; procedure TMainForm.SyncWebRun; begin // Toggle only if state differs - if FWebSyncBool <> FRunning then + if FWebSyncBool <> FController.FRunning then BtnStartStopClick(nil); end; @@ -6477,7 +6430,7 @@ end; procedure TMainForm.SyncWebMute; begin - if FWebSyncBool <> FMuted then + if FWebSyncBool <> FController.FMuted then BtnMuteClick(nil); end; @@ -6492,7 +6445,7 @@ end; procedure TMainForm.SyncWebCtun; begin - if FWebSyncBool <> FCTun then + if FWebSyncBool <> FController.FCTun then BtnCTunClick(nil); end; @@ -6513,7 +6466,7 @@ begin begin BtnNR.Tag := FWebSyncInt; UpdateNRButton; - if FWDSPReady then FDSPEngine.SetNRMode(BtnNR.Tag); + if FController.FWDSPReady then FController.FDSPEngine.SetNRMode(BtnNR.Tag); end; end; @@ -6534,7 +6487,7 @@ begin begin BtnNB.Tag := FWebSyncInt; UpdateNBButton; - if FWDSPReady then FDSPEngine.SetNBMode(BtnNB.Tag); + if FController.FWDSPReady then FController.FDSPEngine.SetNBMode(BtnNB.Tag); end; end; @@ -6578,21 +6531,21 @@ end; procedure TMainForm.SyncWebFreqB; var BandIdx: Integer; begin - FVfoB := FWebSyncFreq; - FreqDispB.Frequency := Round(FVfoB); - if FActiveVfo = 1 then + FController.FVfoB := FWebSyncFreq; + FreqDispB.Frequency := Round(FController.FVfoB); + if FController.FActiveVfo = 1 then begin - FCenterFreq := FVfoB; - if not FCTun then - if FWDSPReady then FDSPEngine.SetShift(0.0); - if FRunning then - FNetwork.SetRunAndFreq(True, XvtrTranslate(FCenterFreq), XvtrTranslate(FCenterFreq), FDriveLevel); - BandIdx := FreqToBandIdx(FVfoB); - if (BandIdx >= 0) and (BandIdx <> FCurrentBand) then + FController.FCenterFreq := FController.FVfoB; + if not FController.FCTun then + if FController.FWDSPReady then FController.FDSPEngine.SetShift(0.0); + if FController.FRunning then + FController.FNetwork.SetRunAndFreq(True, XvtrTranslate(FController.FCenterFreq), XvtrTranslate(FController.FCenterFreq), FController.FDriveLevel); + BandIdx := FreqToBandIdx(FController.FVfoB); + if (BandIdx >= 0) and (BandIdx <> FController.FCurrentBand) then begin - StyleButton(BtnBand[FCurrentBand], False); - FCurrentBand := BandIdx; - StyleButton(BtnBand[FCurrentBand], True); + StyleButton(BtnBand[FController.FCurrentBand], False); + FController.FCurrentBand := BandIdx; + StyleButton(BtnBand[FController.FCurrentBand], True); end; FSpecView.InvalidateRulerCache; SyncSpecViewFreq; @@ -6614,7 +6567,7 @@ end; procedure TMainForm.SyncWebActiveVfo; begin if (FWebSyncInt < 0) or (FWebSyncInt > 1) then Exit; - if FWebSyncInt = FActiveVfo then Exit; + if FWebSyncInt = FController.FActiveVfo then Exit; ActivateVfo(FWebSyncInt); end; @@ -6630,14 +6583,14 @@ end; procedure TMainForm.SyncWebCenter; begin // Сдвигаем DDC-центр без смены VFO (аналог CTUN-drag в десктопе) - FCenterFreq := FWebSyncFreq; - if FWDSPReady then + FController.FCenterFreq := FWebSyncFreq; + if FController.FWDSPReady then begin - if FActiveVfo = 0 then FDSPEngine.SetShift(FVfoA - FCenterFreq) - else FDSPEngine.SetShift(FVfoB - FCenterFreq); + if FController.FActiveVfo = 0 then FController.FDSPEngine.SetShift(FController.FVfoA - FController.FCenterFreq) + else FController.FDSPEngine.SetShift(FController.FVfoB - FController.FCenterFreq); end; - if FNetwork.Connected and FNetwork.Running then - FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslate(ActiveTXFreqHz), FDriveLevel, FTransmitting, True, True); + if FController.FNetwork.Connected and FController.FNetwork.Running then + FController.FNetwork.UpdateState(XvtrTranslate(FController.FCenterFreq), XvtrTranslate(ActiveTXFreqHz), FController.FDriveLevel, FController.FTransmitting, True, True); FSpecView.InvalidateRulerCache; FSpectrumDirty := True; end; @@ -6688,8 +6641,8 @@ end; procedure TMainForm.SyncWebAttn; begin - FAtten := Max(0, Min(2, FWebSyncInt)); - FNetwork.SetStepAtten(FAtten * 10); + FController.FAtten := Max(0, Min(2, FWebSyncInt)); + FController.FNetwork.SetStepAtten(FController.FAtten * 10); end; procedure TMainForm.WebOnFMStep(Idx: Integer); @@ -6723,7 +6676,7 @@ var SP: PSingle; i, n: Integer; begin - if not FWDSPReady then Exit; + if not FController.FWDSPReady then Exit; n := Count; if n > Length(Buf) then n := Length(Buf); SP := Samples; @@ -6732,19 +6685,19 @@ begin Buf[i] := SP^; Inc(SP); end; - FDSPEngine.PushTXMicSamplesD(Buf, n); + FController.FDSPEngine.PushTXMicSamplesD(Buf, n); end; procedure TMainForm.TrkDriveChange(Sender: TObject); begin FPreChannelPower := -1; // ручное изменение снимает сохранённую мощность канала - FDriveLevel := CalcDriveByte; - if FWDSPReady then - FDSPEngine.SetDriveLevel(TrkDrive.Position / 100.0); - if FNetwork.Connected and FNetwork.Running then + FController.FDriveLevel := CalcDriveByte; + if FController.FWDSPReady then + FController.FDSPEngine.SetDriveLevel(TrkDrive.Position / 100.0); + if FController.FNetwork.Connected and FController.FNetwork.Running then begin - FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslate(ActiveTXFreqHz), FDriveLevel, FTransmitting, True, True); - FNetwork.SendFullHP; + FController.FNetwork.UpdateState(XvtrTranslate(FController.FCenterFreq), XvtrTranslate(ActiveTXFreqHz), FController.FDriveLevel, FController.FTransmitting, True, True); + FController.FNetwork.SendFullHP; end; end; @@ -6754,7 +6707,7 @@ function TMainForm.CalcDriveByte: Byte; // where: // target_volts = sqrt( 10^((target_dBm)/10) * 0.05 ) [E=sqrt(P*R), R=50, P in mW] // target_dBm = 10*log10(pwr_W * 1000) - Cal -// pwr_W = Pos/100 * FPAMaxPower +// pwr_W = Pos/100 * FController.FPAMaxPower // Cal = per-band PA gain in dB (HF: ~38.8-41.3, VHF default: ~56.2) var Cal: Double; @@ -6765,35 +6718,35 @@ var target_volts: Double; audio_vol: Double; begin - XvtrActive := (FCurrentXvtr >= 0) and (FCurrentXvtr < CFG_XVTR_COUNT) and - FXvtrSettings.Entries[FCurrentXvtr].Enabled; + XvtrActive := (FController.FCurrentXvtr >= 0) and (FController.FCurrentXvtr < CFG_XVTR_COUNT) and + FController.FXvtrSettings.Entries[FController.FCurrentXvtr].Enabled; if XvtrActive then - Cal := FVHFBandCal[FCurrentXvtr] - else if (FCurrentBand >= 0) and (FCurrentBand < BAND_COUNT) then - Cal := FPABandCal[FCurrentBand] + Cal := FController.FVHFBandCal[FController.FCurrentXvtr] + else if (FController.FCurrentBand >= 0) and (FController.FCurrentBand < BAND_COUNT) then + Cal := FController.FPABandCal[FController.FCurrentBand] else Cal := 38.8; // Pos — итоговый процент драйва 0..100 - if FTuning then + if FController.FTuning then begin - if XvtrActive and FXvtrSettings.UseXVTRTunePower then - Pos := EnsureRange(FXvtrSettings.Entries[FCurrentXvtr].TXPower, 0, 100) + if XvtrActive and FController.FXvtrSettings.UseXVTRTunePower then + Pos := EnsureRange(FController.FXvtrSettings.Entries[FController.FCurrentXvtr].TXPower, 0, 100) else - Pos := EnsureRange(FTXSettings.TUNLevel, 0, 100); + Pos := EnsureRange(FController.FTXSettings.TUNLevel, 0, 100); end else begin if XvtrActive then - Pos := Round(TrkDrive.Position * FXvtrSettings.Entries[FCurrentXvtr].TXPower / 100.0) + Pos := Round(TrkDrive.Position * FController.FXvtrSettings.Entries[FController.FCurrentXvtr].TXPower / 100.0) else Pos := TrkDrive.Position; end; if Pos <= 0 then begin Result := 0; Exit; end; - pwr_W := Pos / 100.0 * FPAMaxPower; + pwr_W := Pos / 100.0 * FController.FPAMaxPower; target_dBm := 10.0 * Log10(pwr_W * 1000.0) - Cal; target_volts := Sqrt(Power(10.0, target_dBm * 0.1) * 0.05); audio_vol := Min(target_volts / 0.8, 1.0); @@ -6817,7 +6770,7 @@ procedure TMainForm.OnTXIQReady(const Buf: array of Double; Count: Integer); var i: Integer; begin - if not FNetwork.Connected then Exit; + if not FController.FNetwork.Connected then Exit; for i := 0 to Count - 1 do begin @@ -6827,7 +6780,7 @@ begin if FDUCPendingCount >= 240 then begin - FNetwork.SendDUCIQ(FDUCPendingI, FDUCPendingQ); + FController.FNetwork.SendDUCIQ(FDUCPendingI, FDUCPendingQ); FDUCPendingCount := 0; end; end; @@ -6837,18 +6790,18 @@ procedure TMainForm.OnPASettingsChange(MaxPower: Double; const BandCal: array of Double); var i: Integer; begin - FPAMaxPower := MaxPower; + FController.FPAMaxPower := MaxPower; if FSpecView <> nil then FSpecView.PAMaxPower := MaxPower; for i := 0 to BAND_COUNT - 1 do begin if i <= High(BandCal) then - FPABandCal[i] := BandCal[i] + FController.FPABandCal[i] := BandCal[i] else - FPABandCal[i] := 100.0; + FController.FPABandCal[i] := 100.0; end; - FDriveLevel := CalcDriveByte; - if FNetwork.Connected and FNetwork.Running then - FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslate(ActiveTXFreqHz), FDriveLevel, FTransmitting, True, True); + FController.FDriveLevel := CalcDriveByte; + if FController.FNetwork.Connected and FController.FNetwork.Running then + FController.FNetwork.UpdateState(XvtrTranslate(FController.FCenterFreq), XvtrTranslate(ActiveTXFreqHz), FController.FDriveLevel, FController.FTransmitting, True, True); end; procedure TMainForm.OnVHFCalSettingsChange(const VHFCal: array of Double); @@ -6857,17 +6810,17 @@ begin for i := 0 to CFG_XVTR_COUNT - 1 do begin if i <= High(VHFCal) then - FVHFBandCal[i] := VHFCal[i] + FController.FVHFBandCal[i] := VHFCal[i] else - FVHFBandCal[i] := CFG_VHF_CAL_DEFAULT; + FController.FVHFBandCal[i] := CFG_VHF_CAL_DEFAULT; end; - FDriveLevel := CalcDriveByte; - if FNetwork.Connected and FNetwork.Running then - FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslate(ActiveTXFreqHz), FDriveLevel, FTransmitting, True, True); + FController.FDriveLevel := CalcDriveByte; + if FController.FNetwork.Connected and FController.FNetwork.Running then + FController.FNetwork.UpdateState(XvtrTranslate(FController.FCenterFreq), XvtrTranslate(ActiveTXFreqHz), FController.FDriveLevel, FController.FTransmitting, True, True); end; procedure TMainForm.OnTXSettingsChange(const T: TTXSettings); -// Любая правка в Settings→Transmit летит сюда: обновляем FTXSettings, +// Любая правка в Settings→Transmit летит сюда: обновляем FController.FTXSettings, // перешиваем WDSP TX-цепь и (если поменялись mic-биты) перепосылаем DUC Specific. var OldMicByte, NewMicByte: Byte; @@ -6875,59 +6828,59 @@ var GridChanged: Boolean; begin OldMicByte := BuildMicLineSelectByte; - OldAtt := FTXSettings.AttOnTX; - OldTUNLevel := FTXSettings.TUNLevel; - OldTUNFreq := FTXSettings.TUNFreq; + OldAtt := FController.FTXSettings.AttOnTX; + OldTUNLevel := FController.FTXSettings.TUNLevel; + OldTUNFreq := FController.FTXSettings.TUNFreq; GridChanged := (FTXSpecRefLevel <> T.TXSpecRefLevel) or (FTXSpecRange <> T.TXSpecRange) or (FTXSpecGridStep <> T.TXSpecGridStep); - FTXSettings := T; + FController.FTXSettings := T; NewMicByte := BuildMicLineSelectByte; // TX-grid в момент правки — обновляем рабочие поля и (если на передаче) пушим в FSpecView - FTXSpecRefLevel := FTXSettings.TXSpecRefLevel; - FTXSpecRange := FTXSettings.TXSpecRange; - if FTXSettings.TXSpecGridStep > 0 then - FTXSpecGridStep := FTXSettings.TXSpecGridStep; - if GridChanged and FTransmitting then + FTXSpecRefLevel := FController.FTXSettings.TXSpecRefLevel; + FTXSpecRange := FController.FTXSettings.TXSpecRange; + if FController.FTXSettings.TXSpecGridStep > 0 then + FTXSpecGridStep := FController.FTXSettings.TXSpecGridStep; + if GridChanged and FController.FTransmitting then ApplySpecViewGridFromState; ApplyTXSettingsToDSP; - if (OldMicByte <> NewMicByte) or (OldAtt <> FTXSettings.AttOnTX) then + if (OldMicByte <> NewMicByte) or (OldAtt <> FController.FTXSettings.AttOnTX) then SendDUCSpecificFromSettings; // TUN-параметры live: если идёт tune — обновить тон/drive, иначе просто запомнить. - if FTuning then + if FController.FTuning then begin - if (OldTUNFreq <> FTXSettings.TUNFreq) and FWDSPReady then - FDSPEngine.SetTXTone(True, FTXSettings.TUNFreq, 1.0); - if OldTUNLevel <> FTXSettings.TUNLevel then + if (OldTUNFreq <> FController.FTXSettings.TUNFreq) and FController.FWDSPReady then + FController.FDSPEngine.SetTXTone(True, FController.FTXSettings.TUNFreq, 1.0); + if OldTUNLevel <> FController.FTXSettings.TUNLevel then begin - FDriveLevel := CalcDriveByte; - if FWDSPReady then - FDSPEngine.SetDriveLevel(FTXSettings.TUNLevel / 100.0); - if FNetwork.Connected and FNetwork.Running then + FController.FDriveLevel := CalcDriveByte; + if FController.FWDSPReady then + FController.FDSPEngine.SetDriveLevel(FController.FTXSettings.TUNLevel / 100.0); + if FController.FNetwork.Connected and FController.FNetwork.Running then begin - FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslate(ActiveTXFreqHz), FDriveLevel, - FTransmitting, True, True); - FNetwork.SendFullHP; + FController.FNetwork.UpdateState(XvtrTranslate(FController.FCenterFreq), XvtrTranslate(ActiveTXFreqHz), FController.FDriveLevel, + FController.FTransmitting, True, True); + FController.FNetwork.SendFullHP; end; end; end; // Сохраняем сразу — настройки per-device, попадают в hpsdr_settings.json - if FDevConnected then + if FController.FDevConnected then begin - FSettings.SaveTX(FDevMAC, FTXSettings); - FSettings.Save; + FController.FSettings.SaveTX(FController.FDevMAC, FController.FTXSettings); + FController.FSettings.Save; end; end; procedure TMainForm.OnAlexSettingsChange(const A: TAlexSettings); begin - FAlexSettings := A; - if FNetwork.Connected then - FNetwork.SetAlexConfig(A); - if FDevConnected then + FController.FAlexSettings := A; + if FController.FNetwork.Connected then + FController.FNetwork.SetAlexConfig(A); + if FController.FDevConnected then begin - FSettings.SaveAlex(FDevMAC, A); - FSettings.Save; + FController.FSettings.SaveAlex(FController.FDevMAC, A); + FController.FSettings.Save; end; end; @@ -6942,7 +6895,7 @@ var i: Integer; begin Result := -1; for i := 0 to CFG_XVTR_COUNT - 1 do - with FXvtrSettings.Entries[i] do + with FController.FXvtrSettings.Entries[i] do if Enabled and (FreqHz >= FreqBegin) and (FreqHz <= FreqEnd) then Exit(i); end; @@ -6952,12 +6905,12 @@ end; function TMainForm.XvtrTranslate(VisibleHz: Double): Double; begin Result := VisibleHz; - if (FCurrentXvtr < 0) or (FCurrentXvtr >= CFG_XVTR_COUNT) then Exit; - if not FXvtrSettings.Entries[FCurrentXvtr].Enabled then Exit; + if (FController.FCurrentXvtr < 0) or (FController.FCurrentXvtr >= CFG_XVTR_COUNT) then Exit; + if not FController.FXvtrSettings.Entries[FController.FCurrentXvtr].Enabled then Exit; // f_IF = f_visible - LOOffset + LOError Result := VisibleHz - - FXvtrSettings.Entries[FCurrentXvtr].LOOffset - + FXvtrSettings.Entries[FCurrentXvtr].LOError; + - FController.FXvtrSettings.Entries[FController.FCurrentXvtr].LOOffset + + FController.FXvtrSettings.Entries[FController.FCurrentXvtr].LOError; end; // Передаёт XVTR-режим в сетевой слой. Вызывается при смене XVTR или его @@ -6970,19 +6923,19 @@ var DisablePA: Boolean; RxAnt: Byte; begin - if (FCurrentXvtr >= 0) and (FCurrentXvtr < CFG_XVTR_COUNT) and - FXvtrSettings.Entries[FCurrentXvtr].Enabled then + if (FController.FCurrentXvtr >= 0) and (FController.FCurrentXvtr < CFG_XVTR_COUNT) and + FController.FXvtrSettings.Entries[FController.FCurrentXvtr].Enabled then begin En := True; - DisablePA := FXvtrSettings.Entries[FCurrentXvtr].DisablePA; - RxAnt := FXvtrSettings.Entries[FCurrentXvtr].RXAntenna; + DisablePA := FController.FXvtrSettings.Entries[FController.FCurrentXvtr].DisablePA; + RxAnt := FController.FXvtrSettings.Entries[FController.FCurrentXvtr].RXAntenna; end else begin En := False; DisablePA := False; RxAnt := 0; end; - if FNetwork.Connected then - FNetwork.SetXvtrMode(En, DisablePA, RxAnt); + if FController.FNetwork.Connected then + FController.FNetwork.SetXvtrMode(En, DisablePA, RxAnt); end; // Перестраивает динамические XVTR-кнопки в PanelBands. Удаляет старые, @@ -7045,10 +6998,10 @@ begin Pos := 0; // позиция среди enabled слотов for i := 0 to CFG_XVTR_COUNT - 1 do - if FXvtrSettings.Entries[i].Enabled then + if FController.FXvtrSettings.Entries[i].Enabled then begin if Pos >= 12 then Break; // 2 ряда по 6 кнопок - Cap := FXvtrSettings.Entries[i].ButtonText; + Cap := FController.FXvtrSettings.Entries[i].ButtonText; if Cap = '' then Cap := 'X' + IntToStr(i + 1); B := TFlatButton.Create(Self); B.Parent := PanelBands; @@ -7060,7 +7013,7 @@ begin B.OnClick := BtnXvtrBandClick; B.Tag := -(i + 1); // отрицательное значение = XVTR индекс BtnXvtrBand[i] := B; - StyleButton(B, i = FCurrentXvtr); + StyleButton(B, i = FController.FCurrentXvtr); Inc(Pos); end; // Динамически подгоняем высоту PanelBands под реальное содержимое @@ -7082,13 +7035,13 @@ begin TagVal := (Sender as TFlatButton).Tag; Idx := -TagVal - 1; if (Idx < 0) or (Idx >= CFG_XVTR_COUNT) then Exit; - if not FXvtrSettings.Entries[Idx].Enabled then Exit; - if FCurrentXvtr = Idx then Exit; // уже на этом XVTR + if not FController.FXvtrSettings.Entries[Idx].Enabled then Exit; + if FController.FCurrentXvtr = Idx then Exit; // уже на этом XVTR // Сохраняем текущий band перед сменой (как BtnBandClick) - if FDevConnected then + if FController.FDevConnected then begin SaveCurrentBand; - FSettings.Save; + FController.FSettings.Save; end; ActivateXvtrBand(Idx); end; @@ -7102,9 +7055,9 @@ var E: TXvtrEntry; begin if (Idx < 0) or (Idx >= CFG_XVTR_COUNT) then Exit; - if not FXvtrSettings.Entries[Idx].Enabled then Exit; - FCurrentXvtr := Idx; - E := FXvtrSettings.Entries[Idx]; + if not FController.FXvtrSettings.Entries[Idx].Enabled then Exit; + FController.FCurrentXvtr := Idx; + E := FController.FXvtrSettings.Entries[Idx]; // Выбор начальной частоты: LastFreq если в диапазоне, иначе середина Vis := E.LastFreq; if (Vis < E.FreqBegin) or (Vis > E.FreqEnd) then @@ -7119,57 +7072,57 @@ begin // Применяем XVTR режим к сети (XVTR enable bit + DDC IN + DisablePA + RX ant) ApplyXvtrToNetwork; // Восстанавливаем Mode + FM Squelch + CTCSS для этого XVTR-слота - FFMSQOn := E.LastFMSQOn; - FFMSQLevel := E.LastFMSQLevel; - FMode := E.LastMode; - FFilter := E.LastFilterIdx; - for i := 0 to MODE_COUNT - 1 do StyleButton(BtnMode[i], i = FMode); - if FWDSPReady then FDSPEngine.SetMode(FMode); + FController.FFMSQOn := E.LastFMSQOn; + FController.FFMSQLevel := E.LastFMSQLevel; + FController.FMode := E.LastMode; + FController.FFilter := E.LastFilterIdx; + for i := 0 to MODE_COUNT - 1 do StyleButton(BtnMode[i], i = FController.FMode); + if FController.FWDSPReady then FController.FDSPEngine.SetMode(FController.FMode); UpdateFilterButtons; // показывает/скрывает FM-панели, обновляет SQL UI ApplyModeFilter; - FFMCTCSSOn := E.LastCTCSSOn; - FFMCTCSSAutoActive := E.LastCTCSSAutoActive; - if BtnFMCTCSS <> nil then StyleButton(BtnFMCTCSS, FFMCTCSSOn); + FController.FFMCTCSSOn := E.LastCTCSSOn; + FController.FFMCTCSSAutoActive := E.LastCTCSSAutoActive; + if BtnFMCTCSS <> nil then StyleButton(BtnFMCTCSS, FController.FFMCTCSSOn); SetFMCTCSSTone(E.LastCTCSSToneIdx); ApplyFMSquelch; - FFMStepOn := E.LastFMStepOn; + FController.FFMStepOn := E.LastFMStepOn; SetFMStep(E.LastFMStepIdx); - if BtnFMStep <> nil then StyleButton(BtnFMStep, FFMStepOn); - FFMRptDir := E.LastFMRptDir; - FFMRptAutoActive := E.LastFMRptAutoActive; - FFMRptOffsetHz := E.LastFMRptOffsetHz; - if FFMRptOffsetHz <= 0 then - FFMRptOffsetHz := RptDefaultOffsetHz(E.FreqBegin); - if BtnFMRptMinus <> nil then StyleButton(BtnFMRptMinus, FFMRptDir = RPT_MINUS); - if BtnFMRptPlus <> nil then StyleButton(BtnFMRptPlus, FFMRptDir = RPT_PLUS); - if EdFMRptOffset <> nil then EdFMRptOffset.Text := RptFormatOffset(FFMRptOffsetHz); + if BtnFMStep <> nil then StyleButton(BtnFMStep, FController.FFMStepOn); + FController.FFMRptDir := E.LastFMRptDir; + FController.FFMRptAutoActive := E.LastFMRptAutoActive; + FController.FFMRptOffsetHz := E.LastFMRptOffsetHz; + if FController.FFMRptOffsetHz <= 0 then + FController.FFMRptOffsetHz := RptDefaultOffsetHz(E.FreqBegin); + if BtnFMRptMinus <> nil then StyleButton(BtnFMRptMinus, FController.FFMRptDir = RPT_MINUS); + if BtnFMRptPlus <> nil then StyleButton(BtnFMRptPlus, FController.FFMRptDir = RPT_PLUS); + if EdFMRptOffset <> nil then EdFMRptOffset.Text := RptFormatOffset(FController.FFMRptOffsetHz); // AGC - FAGCMode := E.LastAGCMode; - FAGCTop := E.LastAGCTop; - for i := 0 to 4 do StyleButton(BtnAGCMode[i], i = FAGCMode); - TrkAGC.Position := FAGCTop; - LblAGCTop.Caption := Format('%ddB', [FAGCTop]); - if FWDSPReady then + FController.FAGCMode := E.LastAGCMode; + FController.FAGCTop := E.LastAGCTop; + for i := 0 to 4 do StyleButton(BtnAGCMode[i], i = FController.FAGCMode); + TrkAGC.Position := FController.FAGCTop; + LblAGCTop.Caption := Format('%ddB', [FController.FAGCTop]); + if FController.FWDSPReady then begin - FDSPEngine.SetAGCTop(FAGCTop); - FDSPEngine.SetAGC(TWDSPAGCMode(FAGCMode), 50.0); + FController.FDSPEngine.SetAGCTop(FController.FAGCTop); + FController.FDSPEngine.SetAGC(TWDSPAGCMode(FController.FAGCMode), 50.0); end; - FSpecView.AGCTop := FAGCTop; + FSpecView.AGCTop := FController.FAGCTop; // CTUN - FCTun := E.LastCTun; - StyleButton(BtnCTun, FCTun); - // VFO на видимую частоту; FCenterFreq тоже visible - FVfoA := Vis; - FCenterFreq := Vis; - FreqDispA.Frequency := Round(FVfoA); - if FWDSPReady then FDSPEngine.SetShift(0.0); + FController.FCTun := E.LastCTun; + StyleButton(BtnCTun, FController.FCTun); + // VFO на видимую частоту; FController.FCenterFreq тоже visible + FController.FVfoA := Vis; + FController.FCenterFreq := Vis; + FreqDispA.Frequency := Round(FController.FVfoA); + if FController.FWDSPReady then FController.FDSPEngine.SetShift(0.0); // Пересчитываем drive byte для нового XVTR слота и отправляем в сеть - FDriveLevel := CalcDriveByte; - if FNetwork.Connected and FNetwork.Running then + FController.FDriveLevel := CalcDriveByte; + if FController.FNetwork.Connected and FController.FNetwork.Running then begin - FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslate(ActiveTXFreqHz), - FDriveLevel, FTransmitting, True, True); - FNetwork.SendFullHP; + FController.FNetwork.UpdateState(XvtrTranslate(FController.FCenterFreq), XvtrTranslate(ActiveTXFreqHz), + FController.FDriveLevel, FController.FTransmitting, True, True); + FController.FNetwork.SendFullHP; end; SyncSpecViewFreq; FSpecView.DrawSpectrum; @@ -7184,40 +7137,40 @@ begin end; // Деактивирует XVTR — возвращает к HF-режиму. -// FCurrentBand был сохранён неизменным во время XVTR (см. ActivateXvtrBand, +// FController.FCurrentBand был сохранён неизменным во время XVTR (см. ActivateXvtrBand, // ApplyVfoA, SaveCurrentBand) — он указывает на HF-диапазон, с которого // пользователь зашёл в трансвертер. RestoreBand вернёт VFO/Mode/Filter/AGC // /CTUN/Span этого диапазона и обновит подсветку HF band-кнопки. procedure TMainForm.DeactivateXvtr; var i: Integer; begin - if FCurrentXvtr < 0 then Exit; + if FController.FCurrentXvtr < 0 then Exit; // Сохраняем полное состояние XVTR-слота (раньше сохранялся только LastFreq) - if FCurrentXvtr < CFG_XVTR_COUNT then + if FController.FCurrentXvtr < CFG_XVTR_COUNT then begin - FXvtrSettings.Entries[FCurrentXvtr].LastFreq := FVfoA; - FXvtrSettings.Entries[FCurrentXvtr].LastMode := FMode; - FXvtrSettings.Entries[FCurrentXvtr].LastFilterIdx := FFilter; - FXvtrSettings.Entries[FCurrentXvtr].LastFMSQOn := FFMSQOn; - FXvtrSettings.Entries[FCurrentXvtr].LastFMSQLevel := FFMSQLevel; - FXvtrSettings.Entries[FCurrentXvtr].LastCTCSSOn := FFMCTCSSOn; - FXvtrSettings.Entries[FCurrentXvtr].LastCTCSSToneIdx := FFMCTCSSToneIdx; - FXvtrSettings.Entries[FCurrentXvtr].LastFMStepOn := FFMStepOn; - FXvtrSettings.Entries[FCurrentXvtr].LastFMStepIdx := FFMStepIdx; - FXvtrSettings.Entries[FCurrentXvtr].LastFMRptDir := FFMRptDir; - FXvtrSettings.Entries[FCurrentXvtr].LastFMRptOffsetHz := FFMRptOffsetHz; - FXvtrSettings.Entries[FCurrentXvtr].LastCTun := FCTun; - FXvtrSettings.Entries[FCurrentXvtr].LastAGCMode := FAGCMode; - FXvtrSettings.Entries[FCurrentXvtr].LastAGCTop := FAGCTop; - FXvtrSettings.Entries[FCurrentXvtr].LastCTCSSAutoActive := FFMCTCSSAutoActive; - FXvtrSettings.Entries[FCurrentXvtr].LastFMRptAutoActive := FFMRptAutoActive; - if FDevConnected then + FController.FXvtrSettings.Entries[FController.FCurrentXvtr].LastFreq := FController.FVfoA; + FController.FXvtrSettings.Entries[FController.FCurrentXvtr].LastMode := FController.FMode; + FController.FXvtrSettings.Entries[FController.FCurrentXvtr].LastFilterIdx := FController.FFilter; + FController.FXvtrSettings.Entries[FController.FCurrentXvtr].LastFMSQOn := FController.FFMSQOn; + FController.FXvtrSettings.Entries[FController.FCurrentXvtr].LastFMSQLevel := FController.FFMSQLevel; + FController.FXvtrSettings.Entries[FController.FCurrentXvtr].LastCTCSSOn := FController.FFMCTCSSOn; + FController.FXvtrSettings.Entries[FController.FCurrentXvtr].LastCTCSSToneIdx := FController.FFMCTCSSToneIdx; + FController.FXvtrSettings.Entries[FController.FCurrentXvtr].LastFMStepOn := FController.FFMStepOn; + FController.FXvtrSettings.Entries[FController.FCurrentXvtr].LastFMStepIdx := FController.FFMStepIdx; + FController.FXvtrSettings.Entries[FController.FCurrentXvtr].LastFMRptDir := FController.FFMRptDir; + FController.FXvtrSettings.Entries[FController.FCurrentXvtr].LastFMRptOffsetHz := FController.FFMRptOffsetHz; + FController.FXvtrSettings.Entries[FController.FCurrentXvtr].LastCTun := FController.FCTun; + FController.FXvtrSettings.Entries[FController.FCurrentXvtr].LastAGCMode := FController.FAGCMode; + FController.FXvtrSettings.Entries[FController.FCurrentXvtr].LastAGCTop := FController.FAGCTop; + FController.FXvtrSettings.Entries[FController.FCurrentXvtr].LastCTCSSAutoActive := FController.FFMCTCSSAutoActive; + FController.FXvtrSettings.Entries[FController.FCurrentXvtr].LastFMRptAutoActive := FController.FFMRptAutoActive; + if FController.FDevConnected then begin - FSettings.SaveXvtr(FDevMAC, FXvtrSettings); - FSettings.Save; + FController.FSettings.SaveXvtr(FController.FDevMAC, FController.FXvtrSettings); + FController.FSettings.Save; end; end; - FCurrentXvtr := -1; + FController.FCurrentXvtr := -1; // Сбрасываем подсветку XVTR-кнопок for i := 0 to CFG_XVTR_COUNT - 1 do if BtnXvtrBand[i] <> nil then @@ -7225,8 +7178,8 @@ begin ApplyXvtrToNetwork; // Восстанавливаем HF band: VFO/Mode/Filter/AGC/CTUN/Span из кэша. // RestoreBand сам обновит подсветку HF band-кнопки и сетевое состояние. - if (FCurrentBand >= 0) and (FCurrentBand < CFG_BAND_COUNT) then - RestoreBand(FCurrentBand); + if (FController.FCurrentBand >= 0) and (FController.FCurrentBand < CFG_BAND_COUNT) then + RestoreBand(FController.FCurrentBand); UpdateWidebandFrequencyView; if PbWideband <> nil then PbWideband.Invalidate; if PbWidebandRuler <> nil then PbWidebandRuler.Invalidate; @@ -7235,12 +7188,12 @@ end; procedure TMainForm.OnXvtrSettingsChange(const X: TXvtrSettings); begin - FXvtrSettings := X; + FController.FXvtrSettings := X; RebuildXvtrButtons; // Если активный XVTR стал disabled — выйти из XVTR режима - if (FCurrentXvtr >= 0) and - ((FCurrentXvtr >= CFG_XVTR_COUNT) or - (not FXvtrSettings.Entries[FCurrentXvtr].Enabled)) then + if (FController.FCurrentXvtr >= 0) and + ((FController.FCurrentXvtr >= CFG_XVTR_COUNT) or + (not FController.FXvtrSettings.Entries[FController.FCurrentXvtr].Enabled)) then DeactivateXvtr; // Перепрошить XVTR в сеть (если параметры текущего изменились) ApplyXvtrToNetwork; @@ -7248,10 +7201,10 @@ begin if PbWideband <> nil then PbWideband.Invalidate; if PbWidebandRuler <> nil then PbWidebandRuler.Invalidate; PushXvtrToWeb; - if FDevConnected then + if FController.FDevConnected then begin - FSettings.SaveXvtr(FDevMAC, FXvtrSettings); - FSettings.Save; + FController.FSettings.SaveXvtr(FController.FDevMAC, FController.FXvtrSettings); + FController.FSettings.Save; end; end; @@ -7263,24 +7216,24 @@ begin if FWebServer = nil then Exit; Cnt := 0; for i := 0 to CFG_XVTR_COUNT - 1 do - if FXvtrSettings.Entries[i].Enabled then Inc(Cnt); + if FController.FXvtrSettings.Entries[i].Enabled then Inc(Cnt); SetLength(Arr, Cnt); Cnt := 0; for i := 0 to CFG_XVTR_COUNT - 1 do - if FXvtrSettings.Entries[i].Enabled then + if FController.FXvtrSettings.Entries[i].Enabled then begin Arr[Cnt].Idx := i; - Arr[Cnt].Name := FXvtrSettings.Entries[i].ButtonText; + Arr[Cnt].Name := FController.FXvtrSettings.Entries[i].ButtonText; Inc(Cnt); end; - FWebServer.SetXvtrBands(Arr, FCurrentXvtr); + FWebServer.SetXvtrBands(Arr, FController.FCurrentXvtr); end; procedure TMainForm.TrkVolumeChange(Sender: TObject); begin - FVolume := TrkVolume.Position; - if FWDSPReady then - FDSPEngine.SetVolume(FVolume / 100.0); + FController.FVolume := TrkVolume.Position; + if FController.FWDSPReady then + FController.FDSPEngine.SetVolume(FController.FVolume / 100.0); end; // =========================================================================== @@ -7299,9 +7252,9 @@ var Base: Int64; NewFreq: Int64; begin - if (FMode = MODE_FM) and FFMStepOn and + if (FController.FMode = MODE_FM) and FController.FFMStepOn and not ((ssCtrl in Shift) or (ssShift in Shift)) then - Step := FM_STEP_HZ[FFMStepIdx] + Step := FM_STEP_HZ[FController.FFMStepIdx] else if (ssCtrl in Shift) and (ssShift in Shift) then Step := 1000000 else if ssCtrl in Shift then Step := 1000 else if ssShift in Shift then Step := 10 @@ -7311,20 +7264,20 @@ begin if WheelDelta < 0 then Delta := -1; // Квантование (та же логика что в FreqDisplay.ChangeByDigit) - Base := (Round(FVfoA) div Step) * Step; + Base := (Round(FController.FVfoA) div Step) * Step; if Delta > 0 then NewFreq := Base + Step else begin - if Round(FVfoA) = Base then + if Round(FController.FVfoA) = Base then NewFreq := Base - Step else NewFreq := Base; // снэп к нижней границе end; // В XVTR-режиме HF-клэмп не применяем: ApplyVfoA сам ограничит по FreqBegin/FreqEnd - if (FCurrentXvtr >= 0) and (FCurrentXvtr < CFG_XVTR_COUNT) and - FXvtrSettings.Entries[FCurrentXvtr].Enabled then + if (FController.FCurrentXvtr >= 0) and (FController.FCurrentXvtr < CFG_XVTR_COUNT) and + FController.FXvtrSettings.Entries[FController.FCurrentXvtr].Enabled then NewFreq := Max(0, NewFreq) else NewFreq := Max(30000, Min(60000000, NewFreq)); @@ -7342,24 +7295,24 @@ var i, N: Integer; begin N := (Sender as TFlatButton).Tag; - FAGCMode := N; + FController.FAGCMode := N; for i := 0 to 4 do StyleButton(BtnAGCMode[i], i = N); - if FWDSPReady then - FDSPEngine.SetAGC(AGCModes[N], 50.0); - FBandCache[FCurrentBand].AGCMode := FAGCMode; + if FController.FWDSPReady then + FController.FDSPEngine.SetAGC(AGCModes[N], 50.0); + FController.FBandCache[FController.FCurrentBand].AGCMode := FController.FAGCMode; end; procedure TMainForm.TrkAGCChange(Sender: TObject); begin - FAGCTop := TrkAGC.Position; - LblAGCTop.Caption := Format('%ddB', [FAGCTop]); - if FWDSPReady then + FController.FAGCTop := TrkAGC.Position; + LblAGCTop.Caption := Format('%ddB', [FController.FAGCTop]); + if FController.FWDSPReady then begin - FDSPEngine.SetAGCTop(FAGCTop); - FDSPEngine.SetAGC(TWDSPAGCMode(FAGCMode), 50.0); + FController.FDSPEngine.SetAGCTop(FController.FAGCTop); + FController.FDSPEngine.SetAGC(TWDSPAGCMode(FController.FAGCMode), 50.0); end; - FBandCache[FCurrentBand].AGCTop := FAGCTop; - FSpecView.AGCTop := FAGCTop; + FController.FBandCache[FController.FCurrentBand].AGCTop := FController.FAGCTop; + FSpecView.AGCTop := FController.FAGCTop; FSpecView.DrawSpectrum; PbSpectrum.Invalidate; end; @@ -7375,8 +7328,8 @@ var i: Integer; begin // Дублируем RX-аудио во встроенный динамик трансивера (DDC Audio, port 1028). - if FSendAudioToRadio then - FNetwork.SendSpeakerAudio(Left, Right, Count); + if FController.FSendAudioToRadio then + FController.FNetwork.SendSpeakerAudio(Left, Right, Count); if Assigned(FWebServer) and FWebServer.WebClientActive then begin for i := 0 to Count - 1 do @@ -7384,7 +7337,7 @@ begin FWebServer.PushAudio(@MonoBuf[0], Count); Exit; end; - FAudioOut.Write(Left, Right, Count); + FController.FAudioOut.Write(Left, Right, Count); end; procedure TMainForm.OnSpectrumReady(const Pixels: array of Single; @@ -7425,18 +7378,18 @@ end; procedure TMainForm.ApplyDisplayParams(FFTSize, WinType, SpecDet, SpecAvgMode: Integer; SpecAvgTimeMS: Double); begin - if FWDSPReady then - FDSPEngine.SetSpectrumDisplay(SpecDet, SpecAvgMode, SpecAvgTimeMS); - if FWDSPReady then - FDSPEngine.SetFFTParams(FFTSize, WinType); + if FController.FWDSPReady then + FController.FDSPEngine.SetSpectrumDisplay(SpecDet, SpecAvgMode, SpecAvgTimeMS); + if FController.FWDSPReady then + FController.FDSPEngine.SetFFTParams(FFTSize, WinType); FSpectrumDirty := True; end; procedure TMainForm.ApplyWaterfallParams(WfDet, WfAvgMode: Integer; WfAvgTimeMS, WfHigh, WfLow, WfAGCOffset: Double); begin - if FWDSPReady then - FDSPEngine.SetWaterfallDisplay(WfDet, WfAvgMode, WfAvgTimeMS); + if FController.FWDSPReady then + FController.FDSPEngine.SetWaterfallDisplay(WfDet, WfAvgMode, WfAvgTimeMS); FWfManualHigh := WfHigh; FWfManualLow := WfLow; FWfAGCOffset := WfAGCOffset; @@ -7453,13 +7406,13 @@ begin FSpecRange := Range; if GridStep > 0 then FSpecGridStep := GridStep; // На передаче FSpecView показывает TX-сетку — RX-правка не должна её перетирать. - if not FTransmitting then ApplySpecViewGridFromState; + if not FController.FTransmitting then ApplySpecViewGridFromState; end; procedure TMainForm.ApplySpecViewGridFromState; begin // При DUP во время TX данные приходят от RX-анализатора → используем RX-сетку. - if FTransmitting and not FDisplayDuplex then + if FController.FTransmitting and not FController.FDisplayDuplex then begin FSpecView.SpecRefLevel := FTXSpecRefLevel; FSpecView.SpecRange := FTXSpecRange; @@ -7476,26 +7429,26 @@ end; procedure TMainForm.ApplyAudioDevice(DevIndex: Integer; const DevName: string); begin - if FAudioOut.IsOpen then - FAudioOut.Close; - FAudioOut.DeviceIndex := DevIndex; - FAudioOutDevName := DevName; + if FController.FAudioOut.IsOpen then + FController.FAudioOut.Close; + FController.FAudioOut.DeviceIndex := DevIndex; + FController.FAudioOutDevName := DevName; try - FAudioOut.Open; + FController.FAudioOut.Open; except end; end; procedure TMainForm.ApplyAudioInputDevice(DevIndex: Integer; const DevName: string); begin - if FAudioIn.IsOpen then - FAudioIn.Close; - FAudioIn.DeviceIndex := DevIndex; - FAudioInDevName := DevName; + if FController.FAudioIn.IsOpen then + FController.FAudioIn.Close; + FController.FAudioIn.DeviceIndex := DevIndex; + FController.FAudioInDevName := DevName; if DevIndex >= 0 then begin try - FAudioIn.Open; + FController.FAudioIn.Open; except end; end; @@ -7508,30 +7461,30 @@ begin if BufferSize <= 128 then BufferSize := 128 else if BufferSize <= 256 then BufferSize := 256 else BufferSize := 512; - if FAudioOut.OutputBufferSize = BufferSize then Exit; + if FController.FAudioOut.OutputBufferSize = BufferSize then Exit; - WasOpen := FAudioOut.IsOpen; - if WasOpen then FAudioOut.Close; - FAudioOut.OutputBufferSize := BufferSize; + WasOpen := FController.FAudioOut.IsOpen; + if WasOpen then FController.FAudioOut.Close; + FController.FAudioOut.OutputBufferSize := BufferSize; if WasOpen then begin try - FAudioOut.Open; + FController.FAudioOut.Open; except end; end; - FSettings.SaveAudioBufferSize(BufferSize); - FSettings.Save; + FController.FSettings.SaveAudioBufferSize(BufferSize); + FController.FSettings.Save; end; procedure TMainForm.ApplySendAudioToRadio(Enabled: Boolean); begin - FSendAudioToRadio := Enabled; + FController.FSendAudioToRadio := Enabled; // Один переключатель: вкл = слать поток + снять мьют динамика, // выкл = замьютить динамик (byte 1400 bit1) и не слать поток. - FNetwork.SetSpeakerAudio(Enabled); - if FDevConnected then - FSettings.SaveGlobal(FDevMAC, MakeGlobalSettings); + FController.FNetwork.SetSpeakerAudio(Enabled); + if FController.FDevConnected then + FController.FSettings.SaveGlobal(FController.FDevMAC, MakeGlobalSettings); end; procedure TMainForm.ApplyVisibility(ShowSpectrum, ShowWaterfall, @@ -7545,8 +7498,8 @@ begin if Assigned(FWidebandView) then FWidebandView.SetFillSpectrum(FWidebandFill); FSpecView.FillSpectrum := FSpectrumFill; - if Assigned(FNetwork) and FNetwork.Connected then - FNetwork.ConfigureWideband(0, FShowWideband, 512, 16, 70, 32); + if Assigned(FController.FNetwork) and FController.FNetwork.Connected then + FController.FNetwork.ConfigureWideband(0, FShowWideband, 512, 16, 70, 32); ResizeSpectrumPanels; end; @@ -7565,8 +7518,8 @@ begin FWaterfallFrameCounter := 0; FWaterfallDirty := True; FSpecView.WfFrameInterval := FWaterfallFrameInterval; - if FWDSPReady then - FDSPEngine.SetDisplayFPS(FPS); + if FController.FWDSPReady then + FController.FDSPEngine.SetDisplayFPS(FPS); end; procedure TMainForm.ApplyFreqMhzDigits(Digits: Integer); @@ -7614,13 +7567,13 @@ begin SF.OnWebSettingsChange := ApplyWebSettings; end; SF := TSettingsForm(FSettingsForm); - SF.LoadTXSettings(FTXSettings, - IfThen(FNetwork.Connected, FNetwork.Device.BoardType, FPendingBoardType)); - SF.LoadAlexSettings(FAlexSettings, - IfThen(FNetwork.Connected, FNetwork.Device.BoardType, FPendingBoardType)); - SF.LoadXvtrSettings(FXvtrSettings); - SF.LoadPASettings(FPAMaxPower, FPABandCal); - SF.LoadVHFCalSettings(FVHFBandCal); + SF.LoadTXSettings(FController.FTXSettings, + IfThen(FController.FNetwork.Connected, FController.FNetwork.Device.BoardType, FPendingBoardType)); + SF.LoadAlexSettings(FController.FAlexSettings, + IfThen(FController.FNetwork.Connected, FController.FNetwork.Device.BoardType, FPendingBoardType)); + SF.LoadXvtrSettings(FController.FXvtrSettings); + SF.LoadPASettings(FController.FPAMaxPower, FController.FPABandCal); + SF.LoadVHFCalSettings(FController.FVHFBandCal); SF.LoadCATSettings( FCATLastGlobal.CATSerialEnabled, FCATLastGlobal.CATSerialPort, @@ -7631,39 +7584,39 @@ begin FCATLastGlobal.CATTcpEnabled, FCATLastGlobal.CATTcpPort); // Перечисляем PA устройства - SF.RefreshAudioDevices(FAudioOut, FAudioIn); + SF.RefreshAudioDevices(FController.FAudioOut, FController.FAudioIn); SF.LoadVisibility(FShowSpectrum, FShowWaterfall, FShowWideband, FWidebandFill, FSpectrumFill); - SF.LoadWfAGCNF(FWfAGCEnabled, FWfNFEnabled); - SF.LoadADCSettings(FDitherEnabled, FRandomEnabled); + SF.LoadWfAGCNF(FController.FWfAGCEnabled, FController.FWfNFEnabled); + SF.LoadADCSettings(FController.FDitherEnabled, FController.FRandomEnabled); SF.LoadWebSettings(FWebEnabled, FWebPort, FWebBindAddr, FWebUser, FWebPass); SF.LoadFPS(FDisplayFPS); SF.LoadLightTheme(FLightTheme); SF.LoadFreqMhzDigits(FFreqMhzDigits); - SF.LoadAudioBufferSize(FAudioOut.OutputBufferSize); - SF.LoadSendAudioToRadio(FSendAudioToRadio); + SF.LoadAudioBufferSize(FController.FAudioOut.OutputBufferSize); + SF.LoadSendAudioToRadio(FController.FSendAudioToRadio); // Загружаем текущие значения - if FWDSPReady then + if FController.FWDSPReady then SF.LoadValues( - FDSPEngine.FFTSize, - FDSPEngine.WindowType, - FDSPEngine.SpecDetector, - FDSPEngine.SpecAvgMode, - FDSPEngine.SpecAvgTimeMS, - FDSPEngine.WfDetector, - FDSPEngine.WfAvgMode, - FDSPEngine.WfAvgTimeMS, + FController.FDSPEngine.FFTSize, + FController.FDSPEngine.WindowType, + FController.FDSPEngine.SpecDetector, + FController.FDSPEngine.SpecAvgMode, + FController.FDSPEngine.SpecAvgTimeMS, + FController.FDSPEngine.WfDetector, + FController.FDSPEngine.WfAvgMode, + FController.FDSPEngine.WfAvgTimeMS, FWfManualHigh, FWfManualLow, FWfAGCOffset, FSpecRefLevel, FSpecRange, FSpecGridStep, - FAudioOutDevName, FAudioInDevName) + FController.FAudioOutDevName, FController.FAudioInDevName) else SF.LoadValues( 131072, 2, 0, 3, 30.0, 0, 3, 120.0, FWfManualHigh, FWfManualLow, FWfAGCOffset, FSpecRefLevel, FSpecRange, FSpecGridStep, - FAudioOutDevName, FAudioInDevName); + FController.FAudioOutDevName, FController.FAudioInDevName); SF.Show; end; @@ -7681,7 +7634,7 @@ begin end; try - FAudioOut.Open; + FController.FAudioOut.Open; except end; @@ -7783,31 +7736,31 @@ begin FCATLastGlobal.CATTcpEnabled := TcpEnabled; FCATLastGlobal.CATTcpPort := TcpPort; CATApplySettings(FCATLastGlobal); - FSettings.SaveCATSettings(FCATLastGlobal); - if FDevConnected then - FSettings.SaveGlobal(FDevMAC, MakeGlobalSettings); - FSettings.Save; + FController.FSettings.SaveCATSettings(FCATLastGlobal); + if FController.FDevConnected then + FController.FSettings.SaveGlobal(FController.FDevMAC, MakeGlobalSettings); + FController.FSettings.Save; end; // --- Getters (called from CAT thread — read only, no sync needed) ----------- -function TMainForm.CATGetVfoA: Double; begin Result := FVfoA; end; -function TMainForm.CATGetVfoB: Double; begin Result := FVfoB; end; -function TMainForm.CATGetMode: Integer; begin Result := FMode; end; -function TMainForm.CATGetActiveVfo: Integer; begin Result := FActiveVfo; end; -function TMainForm.CATGetAGCMode: Integer; begin Result := FAGCMode; end; -function TMainForm.CATGetVolume: Integer; begin Result := FVolume; end; +function TMainForm.CATGetVfoA: Double; begin Result := FController.FVfoA; end; +function TMainForm.CATGetVfoB: Double; begin Result := FController.FVfoB; end; +function TMainForm.CATGetMode: Integer; begin Result := FController.FMode; end; +function TMainForm.CATGetActiveVfo: Integer; begin Result := FController.FActiveVfo; end; +function TMainForm.CATGetAGCMode: Integer; begin Result := FController.FAGCMode; end; +function TMainForm.CATGetVolume: Integer; begin Result := FController.FVolume; end; function TMainForm.CATGetDriveLevel: Integer;begin Result := TrkDrive.Position; end; -function TMainForm.CATGetFilterIdx: Integer; begin Result := FFilter; end; -function TMainForm.CATGetFilterBW: Integer; begin Result := FFilterBW; end; +function TMainForm.CATGetFilterIdx: Integer; begin Result := FController.FFilter; end; +function TMainForm.CATGetFilterBW: Integer; begin Result := FController.FFilterBW; end; function TMainForm.CATGetNRMode: Integer; begin Result := BtnNR.Tag; end; function TMainForm.CATGetNBMode: Integer; begin Result := BtnNB.Tag; end; function TMainForm.CATGetSNB: Boolean; begin Result := BtnSNB.Tag <> 0; end; function TMainForm.CATGetANF: Boolean; begin Result := BtnANF.Tag <> 0; end; -function TMainForm.CATGetTX: Boolean; begin Result := FTransmitting; end; -function TMainForm.CATGetRunning: Boolean; begin Result := FRunning; end; +function TMainForm.CATGetTX: Boolean; begin Result := FController.FTransmitting; end; +function TMainForm.CATGetRunning: Boolean; begin Result := FController.FRunning; end; function TMainForm.CATGetSMeter: Double; begin Result := FLastSMeter; end; -function TMainForm.CATGetBand: Integer; begin Result := FCurrentBand; end; +function TMainForm.CATGetBand: Integer; begin Result := FController.FCurrentBand; end; // --- Setters ---------------------------------------------------------------- @@ -7846,21 +7799,21 @@ begin TThread.Synchronize(nil, SyncCATTuneDown); end; procedure TMainForm.SyncCATVfoA; var BandIdx: Integer; begin - FVfoA := FCATSyncFreq; - if FActiveVfo = 0 then + FController.FVfoA := FCATSyncFreq; + if FController.FActiveVfo = 0 then begin - ApplyVfoA(Round(FVfoA)); + ApplyVfoA(Round(FController.FVfoA)); end else begin // VFO A is not active — just update display and band indicator - FreqDispA.Frequency := Round(FVfoA); - BandIdx := FreqToBandIdx(FVfoA); - if (BandIdx >= 0) and (BandIdx <> FCurrentBand) then + FreqDispA.Frequency := Round(FController.FVfoA); + BandIdx := FreqToBandIdx(FController.FVfoA); + if (BandIdx >= 0) and (BandIdx <> FController.FCurrentBand) then begin - StyleButton(BtnBand[FCurrentBand], False); - FCurrentBand := BandIdx; - StyleButton(BtnBand[FCurrentBand], True); + StyleButton(BtnBand[FController.FCurrentBand], False); + FController.FCurrentBand := BandIdx; + StyleButton(BtnBand[FController.FCurrentBand], True); end; end; end; @@ -7868,20 +7821,20 @@ end; procedure TMainForm.SyncCATVfoB; var BandIdx: Integer; begin - FVfoB := FCATSyncFreq; - FreqDispB.Frequency := Round(FVfoB); - if FActiveVfo = 1 then + FController.FVfoB := FCATSyncFreq; + FreqDispB.Frequency := Round(FController.FVfoB); + if FController.FActiveVfo = 1 then begin - FCenterFreq := FVfoB; - if FWDSPReady then FDSPEngine.SetShift(0.0); - if FRunning then - FNetwork.SetRunAndFreq(True, XvtrTranslate(FCenterFreq), XvtrTranslate(FCenterFreq), FDriveLevel); - BandIdx := FreqToBandIdx(FVfoB); - if (BandIdx >= 0) and (BandIdx <> FCurrentBand) then + FController.FCenterFreq := FController.FVfoB; + if FController.FWDSPReady then FController.FDSPEngine.SetShift(0.0); + if FController.FRunning then + FController.FNetwork.SetRunAndFreq(True, XvtrTranslate(FController.FCenterFreq), XvtrTranslate(FController.FCenterFreq), FController.FDriveLevel); + BandIdx := FreqToBandIdx(FController.FVfoB); + if (BandIdx >= 0) and (BandIdx <> FController.FCurrentBand) then begin - StyleButton(BtnBand[FCurrentBand], False); - FCurrentBand := BandIdx; - StyleButton(BtnBand[FCurrentBand], True); + StyleButton(BtnBand[FController.FCurrentBand], False); + FController.FCurrentBand := BandIdx; + StyleButton(BtnBand[FController.FCurrentBand], True); end; FSpecView.InvalidateRulerCache; SyncSpecViewFreq; @@ -7893,34 +7846,34 @@ end; procedure TMainForm.SyncCATBandUp; begin - if FCurrentBand < BAND_COUNT - 1 then - WebOnBand(FCurrentBand + 1); + if FController.FCurrentBand < BAND_COUNT - 1 then + WebOnBand(FController.FCurrentBand + 1); end; procedure TMainForm.SyncCATBandDown; begin - if FCurrentBand > 0 then - WebOnBand(FCurrentBand - 1); + if FController.FCurrentBand > 0 then + WebOnBand(FController.FCurrentBand - 1); end; procedure TMainForm.SyncCATTuneUp; begin - if FActiveVfo = 0 then - ApplyVfoA(Round(FVfoA) + 10) + if FController.FActiveVfo = 0 then + ApplyVfoA(Round(FController.FVfoA) + 10) else begin - FCATSyncFreq := FVfoB + 10; + FCATSyncFreq := FController.FVfoB + 10; SyncCATVfoB; end; end; procedure TMainForm.SyncCATTuneDown; begin - if FActiveVfo = 0 then - ApplyVfoA(Round(FVfoA) - 10) + if FController.FActiveVfo = 0 then + ApplyVfoA(Round(FController.FVfoA) - 10) else begin - FCATSyncFreq := FVfoB - 10; + FCATSyncFreq := FController.FVfoB - 10; SyncCATVfoB; end; end; diff --git a/RadioController.pas b/RadioController.pas new file mode 100644 index 0000000..a6dcf03 --- /dev/null +++ b/RadioController.pas @@ -0,0 +1,494 @@ +unit RadioController; + +{ + EWSDR — Radio Controller (headless ядро) + + Назначение + ---------- + TRadioController — «мозг» трансивера БЕЗ привязки к UI/LCL-формам. Владеет + состоянием радио и движками (сеть HPSDR, WDSP, аудио, настройки, каналы) и + предоставляет единый интерфейс управления для ЛЮБОГО фронтенда: GUI, Web, CAT, + аппаратная панель Arduino. + + • Команды (SetMode, SetVfoA, TuneActiveBy, StartStop, …) — единая точка входа. + Абсолютные (Set*) удобны UI/Web; относительные (*By/Up/Down) — энкодерам. + • GetSnapshot → TRadioSnapshot — весь стейт одной записью (синхронизация + нового фронтенда при подключении: LED/дисплеи панели, веб-клиент). + • OnStateChanged(Field) — гранулярное уведомление об изменении поля; все + фронтенды остаются синхронны, кто бы ни инициировал изменение. + + Маршалинг потоков + ----------------- + Команды из сетевых/serial/web-потоков идут через OnInvoke (GUI = обёртка над + TThread.Synchronize; демон = своя очередь/прямой вызов), чтобы вызовы движка + шли из одного потока без зависимости от GUI-цикла LCL. + + СТАТУС МИГРАЦИИ (Фаза 1) + ------------------------ + Состояние и движки вынесены сюда как ПУБЛИЧНЫЕ ПОЛЯ с теми же именами, что были + в TMainForm (FVfoA, FNetwork, FDSPEngine, …), чтобы проводка MainForm свелась к + механической замене FXxx -> FController.FXxx без изменения поведения. Логика + операций (ApplyModeFilter, BtnStartStopClick, ApplyVfoA, …) пока остаётся в + MainForm; команды контроллера обновляют поле + шлют событие и будут наполнены + логикой на следующих фазах (помечено TODO-wiring). Поля станут приватными, + когда MainForm перестанет писать в них напрямую. + + ВЛАДЕНИЕ ДВИЖКАМИ (Фаза 1): объекты создаёт и освобождает MainForm (в + FormCreate/FormDestroy), сохраняя в поля контроллера. Перенос создания в + Create/Destroy контроллера — отдельная фаза (нужен для режима демона). + + FPC 3.2+ / режим Delphi. Без inline var и анонимных процедур. +} + +{$IFDEF FPC} + {$MODE Delphi} +{$ENDIF} + +interface + +uses + Classes, SysUtils, + HPSDRProtocol, HPSDRNetwork, + WDSPEngine, AudioOutput, AudioInput, + Settings, ChannelStore; + +type + // Поле, которое изменилось — для гранулярных уведомлений наружу. + TRadioField = ( + rfVfoA, rfVfoB, rfActiveVfo, + rfMode, rfFilter, rfFilterBW, + rfAGCMode, rfAGCTop, + rfVolume, rfMute, rfDrive, rfAtten, + rfBand, rfXvtr, + rfCenterFreq, rfSpan, rfSampleRate, + rfCTun, rfDuplex, + rfRunning, rfConnected, rfTransmitting, rfTuning, + rfNR, rfNB, rfSNB, rfANF, + rfWfAGC, rfWfNF, rfDither, rfRandom, + rfFMDeviation, rfFMCTCSS, rfFMCTCSSTone, rfFMSQ, rfFMSQLevel, + rfFMStep, rfFMStepIdx, rfFMRpt, + rfChannel, + rfSMeter, rfPower, rfSWR, rfSupply, rfPLL, // телеметрия + rfDevice // подключённое устройство сменилось + ); + + TRadioStateEvent = procedure(Sender: TObject; Field: TRadioField) of object; + + // Маршалинг произвольного метода в поток контроллера (GUI: TThread.Synchronize). + TUIInvokeEvent = procedure(M: TThreadMethod) of object; + + // Снимок состояния для синхронизации нового фронтенда (Arduino/Web на connect). + TRadioSnapshot = record + VfoA, VfoB, CenterFreq, SpanHz: Double; + SampleRate: Integer; + ActiveVfo: Integer; + Mode, Filter, FilterBW: Integer; + AGCMode, AGCTop: Integer; + Volume, Drive, Atten: Integer; + Band, Xvtr: Integer; + Muted, CTun, Duplex: Boolean; + Running, Connected, Transmitting, Tuning: Boolean; + NRMode, NBMode: Integer; + SNB, ANF: Boolean; + WfAGC, WfNF: Boolean; + FMStepOn: Boolean; + FMStepIdx: Integer; + FMCTCSSOn: Boolean; + FMCTCSSToneIdx: Integer; + FMSQOn: Boolean; + FMSQLevel: Integer; + FMRptDir: Integer; + SMeter, FwdW, SWR, SupplyV, SupplyA: Double; + PLLLock: Boolean; + end; + + { TRadioController } + TRadioController = class + private + FOnInvoke: TUIInvokeEvent; + FOnStateChanged: TRadioStateEvent; + procedure Changed(Field: TRadioField); + public + // ============================================================ + // ПУБЛИЧНЫЕ ПОЛЯ (миграционные — имена совпадают с TMainForm). + // Внешний чистый доступ для адаптеров — через команды / GetSnapshot / + // OnStateChanged. Прямая запись извне — временная мера на время Фазы 1. + // ============================================================ + + // ---- Движки (владение: см. шапку юнита) ---- + FNetwork: THPSDRNetwork; + FDSPEngine: TWDSPEngine; + FAudioOut: TAudioOutput; + FAudioIn: TAudioInput; + FSettings: TSettingsManager; + FChannelStore: TChannelStore; + FWDSPReady: Boolean; + + // ---- Частоты / режим / фильтр / AGC ---- + FVfoA, FVfoB: Double; + FActiveVfo: Integer; + FMode: Integer; + FFilter: Integer; + FFilterBW: Integer; + FAGCMode: Integer; + FAGCTop: Integer; + FCTun: Boolean; + FCenterFreq: Double; + FSpanHz: Double; + FSampleRate: Integer; + FCurrentBand: Integer; + FCurrentXvtr: Integer; + + // ---- Аудио / TX ---- + FVolume: Integer; + FMuted: Boolean; + FDriveLevel: Byte; + FAtten: Integer; + FPAMaxPower: Double; + FRunning: Boolean; + FTransmitting: Boolean; + FTuning: Boolean; + FSplitTxB: Boolean; + FDisplayDuplex: Boolean; + FHWPTTActive: Boolean; + FHWPTTStartedTX: Boolean; + FSendAudioToRadio: Boolean; + FAudioOutDevName: string; + FAudioInDevName: string; + FDevConnected: Boolean; + + // ---- DSP-фильтры шума / водопад ---- + FNRMode, FNBMode: Integer; // источник истины (раньше Btn*.Tag) + FSNB, FANF: Boolean; // источник истины (раньше Btn*.Tag) + FWfAGCEnabled: Boolean; + FWfNFEnabled: Boolean; + FDitherEnabled: Boolean; + FRandomEnabled: Boolean; + + // ---- FM ---- + FFMDeviation: Double; + FFMCTCSSOn: Boolean; + FFMCTCSSToneIdx: Integer; + FFMSQOn: Boolean; + FFMSQLevel: Integer; + FFMStepOn: Boolean; + FFMStepIdx: Integer; + FFMRptDir: Integer; + FFMRptOffsetHz: Double; + FFMRptAutoActive: Boolean; + FFMCTCSSAutoActive: Boolean; + + // ---- Конфиги / устройство (per-MAC) ---- + FTXSettings: TTXSettings; + FAlexSettings: TAlexSettings; + FXvtrSettings: TXvtrSettings; + FBandCache: array[0..CFG_BAND_COUNT-1] of TBandSettings; + FPABandCal: array[0..CFG_BAND_COUNT-1] of Double; + FVHFBandCal: array[0..CFG_XVTR_COUNT-1] of Double; + FDevMAC: array[0..5] of Byte; + + // ---- Телеметрия (наполняется в Фазе 3/4 вместе с сетевыми колбэками) ---- + FLastSMeter, FLastFwdW, FLastSWR, FLastSupplyV, FLastSupplyA: Double; + FLastPLLLock: Boolean; + + constructor Create; + destructor Destroy; override; + + // Маршалинг команды в поток контроллера (GUI: TThread.Synchronize). + procedure Invoke(M: TThreadMethod); + + // Снимок всего состояния (для синхронизации нового фронтенда). + function GetSnapshot: TRadioSnapshot; + + // ==================================================================== + // КОМАНДЫ — единый API для GUI / Web / CAT / Arduino. + // Фаза 1: обновляют поле + шлют событие; логика движка — TODO-wiring. + // ==================================================================== + + // Частоты / VFO + procedure SetVfoA(Hz: Double); + procedure SetVfoB(Hz: Double); + procedure SetActiveVfo(Idx: Integer); + procedure TuneActiveBy(DeltaHz: Double); // энкодер VFO + procedure SetCenter(Hz: Double); + procedure VfoSwap; + procedure VfoACopyB; + procedure VfoBCopyA; + + // Режим / фильтр / AGC + procedure SetMode(M: Integer); + procedure SetFilterIdx(Idx: Integer); + procedure SetFilterBW(BWHz: Integer); + procedure SetAGCMode(M: Integer); + procedure SetAGCTop(DB: Integer); + procedure AGCTopBy(Delta: Integer); + + // Громкость / mute / drive / аттенюатор + procedure SetVolume(V: Integer); + procedure VolumeBy(Delta: Integer); + procedure SetMute(On_: Boolean); + procedure ToggleMute; + procedure SetDrive(V: Integer); + procedure DriveBy(Delta: Integer); + procedure SetAtten(Idx: Integer); + + // Диапазоны / трансвертеры / спан + procedure SetBand(Idx: Integer); + procedure BandUp; + procedure BandDown; + procedure SetXvtrBand(Idx: Integer); // -1 = выход в HF + procedure SetSpan(Hz: Integer); + procedure SetSampleRate(Hz: Integer); + + // Отображение / приём + procedure SetCTun(On_: Boolean); + procedure SetDuplex(On_: Boolean); + procedure SetWfAGC(On_: Boolean); + procedure SetWfNF(On_: Boolean); + + // DSP-фильтры шума + procedure SetNR(Mode: Integer); + procedure SetNB(Mode: Integer); + procedure SetSNB(On_: Boolean); + procedure SetANF(On_: Boolean); + + // Передача + procedure SetMOX(On_: Boolean); + procedure SetTune(On_: Boolean); // TUN + + // FM + procedure SetFMStep(Idx: Integer); + procedure SetFMCTCSS(On_: Boolean); + procedure SetFMCTCSSTone(Idx: Integer); + procedure SetFMSquelch(On_: Boolean); + procedure SetFMSquelchLevel(V: Integer); + procedure SetFMRpt(Dir: Integer); + + // Каналы + procedure ApplyChannel(Idx: Integer); + + // Жизненный цикл движка / устройства + procedure StartStop; + procedure SetRun(On_: Boolean); + procedure ConnectDevice(const Dev: THPSDRDevice); + procedure Discover; + + // ---- События наружу ---- + property OnInvoke: TUIInvokeEvent read FOnInvoke write FOnInvoke; + property OnStateChanged: TRadioStateEvent read FOnStateChanged write FOnStateChanged; + end; + +implementation + +constructor TRadioController.Create; +begin + inherited Create; + // Дефолты (дублируют TMainForm.FormCreate; в GUI перезапишутся, в демоне нужны). + FVfoA := 14200000; FVfoB := 7100000; FActiveVfo := 0; + FMode := 1; FFilter := 5; FFilterBW := 2700; + FAGCMode := 1; FAGCTop := 90; + FVolume := 70; FDriveLevel := 0; FAtten := 0; + FCenterFreq := FVfoA; FSpanHz := 192000; FSampleRate := 192000; + FCurrentBand := 5; FCurrentXvtr := -1; + FPAMaxPower := 100.0; + FWfAGCEnabled := True; FDitherEnabled := True; FRandomEnabled := True; + FFMStepOn := True; + FLastSMeter := -130; FLastSWR := 1; FLastSupplyV := -1; FLastSupplyA := -1; + FillChar(FDevMAC, SizeOf(FDevMAC), 0); +end; + +destructor TRadioController.Destroy; +begin + // Фаза 1: движки освобождает MainForm. Здесь ничего не освобождаем. + inherited Destroy; +end; + +procedure TRadioController.Changed(Field: TRadioField); +begin + if Assigned(FOnStateChanged) then FOnStateChanged(Self, Field); +end; + +procedure TRadioController.Invoke(M: TThreadMethod); +begin + if Assigned(FOnInvoke) then FOnInvoke(M) + else if Assigned(M) then M(); // нет хозяина-маршалера — прямой вызов +end; + +function TRadioController.GetSnapshot: TRadioSnapshot; +begin + FillChar(Result, SizeOf(Result), 0); + Result.VfoA := FVfoA; Result.VfoB := FVfoB; + Result.CenterFreq := FCenterFreq; Result.SpanHz := FSpanHz; + Result.SampleRate := FSampleRate; Result.ActiveVfo := FActiveVfo; + Result.Mode := FMode; Result.Filter := FFilter; Result.FilterBW := FFilterBW; + Result.AGCMode := FAGCMode; Result.AGCTop := FAGCTop; + Result.Volume := FVolume; Result.Drive := FDriveLevel; Result.Atten := FAtten; + Result.Band := FCurrentBand; Result.Xvtr := FCurrentXvtr; + Result.Muted := FMuted; Result.CTun := FCTun; Result.Duplex := FDisplayDuplex; + Result.Running := FRunning; Result.Connected := FDevConnected; + Result.Transmitting := FTransmitting; Result.Tuning := FTuning; + Result.NRMode := FNRMode; Result.NBMode := FNBMode; + Result.SNB := FSNB; Result.ANF := FANF; + Result.WfAGC := FWfAGCEnabled; Result.WfNF := FWfNFEnabled; + Result.FMStepOn := FFMStepOn; Result.FMStepIdx := FFMStepIdx; + Result.FMCTCSSOn := FFMCTCSSOn; Result.FMCTCSSToneIdx := FFMCTCSSToneIdx; + Result.FMSQOn := FFMSQOn; Result.FMSQLevel := FFMSQLevel; + Result.FMRptDir := FFMRptDir; + Result.SMeter := FLastSMeter; Result.FwdW := FLastFwdW; Result.SWR := FLastSWR; + Result.SupplyV := FLastSupplyV; Result.SupplyA := FLastSupplyA; + Result.PLLLock := FLastPLLLock; +end; + +// ===================================================================== +// Команды (Фаза 1 — каркас). Полная логика переносится из TMainForm. +// ===================================================================== + +procedure TRadioController.SetVfoA(Hz: Double); +begin FVfoA := Hz; Changed(rfVfoA); { TODO wiring: ApplyVfoA } end; + +procedure TRadioController.SetVfoB(Hz: Double); +begin FVfoB := Hz; Changed(rfVfoB); { TODO wiring } end; + +procedure TRadioController.SetActiveVfo(Idx: Integer); +begin FActiveVfo := Idx; Changed(rfActiveVfo); { TODO wiring: ActivateVfo } end; + +procedure TRadioController.TuneActiveBy(DeltaHz: Double); +begin + if FActiveVfo = 0 then SetVfoA(FVfoA + DeltaHz) + else SetVfoB(FVfoB + DeltaHz); +end; + +procedure TRadioController.SetCenter(Hz: Double); +begin FCenterFreq := Hz; Changed(rfCenterFreq); { TODO wiring } end; + +procedure TRadioController.VfoSwap; +var T: Double; +begin T := FVfoA; FVfoA := FVfoB; FVfoB := T; Changed(rfVfoA); Changed(rfVfoB); { TODO wiring } end; + +procedure TRadioController.VfoACopyB; +begin FVfoA := FVfoB; Changed(rfVfoA); { TODO wiring } end; + +procedure TRadioController.VfoBCopyA; +begin FVfoB := FVfoA; Changed(rfVfoB); { TODO wiring } end; + +procedure TRadioController.SetMode(M: Integer); +begin FMode := M; Changed(rfMode); { TODO wiring: ApplyModeFilter } end; + +procedure TRadioController.SetFilterIdx(Idx: Integer); +begin FFilter := Idx; Changed(rfFilter); { TODO wiring } end; + +procedure TRadioController.SetFilterBW(BWHz: Integer); +begin FFilterBW := BWHz; Changed(rfFilterBW); { TODO wiring } end; + +procedure TRadioController.SetAGCMode(M: Integer); +begin FAGCMode := M; Changed(rfAGCMode); { TODO wiring: FDSPEngine.SetAGC } end; + +procedure TRadioController.SetAGCTop(DB: Integer); +begin FAGCTop := DB; Changed(rfAGCTop); { TODO wiring } end; + +procedure TRadioController.AGCTopBy(Delta: Integer); +begin SetAGCTop(FAGCTop + Delta); end; + +procedure TRadioController.SetVolume(V: Integer); +begin FVolume := V; Changed(rfVolume); { TODO wiring: FDSPEngine.SetVolume } end; + +procedure TRadioController.VolumeBy(Delta: Integer); +begin SetVolume(FVolume + Delta); end; + +procedure TRadioController.SetMute(On_: Boolean); +begin FMuted := On_; Changed(rfMute); { TODO wiring } end; + +procedure TRadioController.ToggleMute; +begin SetMute(not FMuted); end; + +procedure TRadioController.SetDrive(V: Integer); +begin FDriveLevel := Byte(V); Changed(rfDrive); { TODO wiring } end; + +procedure TRadioController.DriveBy(Delta: Integer); +begin SetDrive(FDriveLevel + Delta); end; + +procedure TRadioController.SetAtten(Idx: Integer); +begin FAtten := Idx; Changed(rfAtten); { TODO wiring } end; + +procedure TRadioController.SetBand(Idx: Integer); +begin FCurrentBand := Idx; Changed(rfBand); { TODO wiring: RestoreBand } end; + +procedure TRadioController.BandUp; +begin if FCurrentBand < CFG_BAND_COUNT - 1 then SetBand(FCurrentBand + 1); end; + +procedure TRadioController.BandDown; +begin if FCurrentBand > 0 then SetBand(FCurrentBand - 1); end; + +procedure TRadioController.SetXvtrBand(Idx: Integer); +begin FCurrentXvtr := Idx; Changed(rfXvtr); { TODO wiring } end; + +procedure TRadioController.SetSpan(Hz: Integer); +begin FSpanHz := Hz; Changed(rfSpan); { TODO wiring } end; + +procedure TRadioController.SetSampleRate(Hz: Integer); +begin FSampleRate := Hz; Changed(rfSampleRate); { TODO wiring: RecreateDSPEngine } end; + +procedure TRadioController.SetCTun(On_: Boolean); +begin FCTun := On_; Changed(rfCTun); { TODO wiring } end; + +procedure TRadioController.SetDuplex(On_: Boolean); +begin FDisplayDuplex := On_; Changed(rfDuplex); { TODO wiring } end; + +procedure TRadioController.SetWfAGC(On_: Boolean); +begin FWfAGCEnabled := On_; Changed(rfWfAGC); end; + +procedure TRadioController.SetWfNF(On_: Boolean); +begin FWfNFEnabled := On_; Changed(rfWfNF); end; + +procedure TRadioController.SetNR(Mode: Integer); +begin FNRMode := Mode; Changed(rfNR); { TODO wiring: FDSPEngine.SetNRMode } end; + +procedure TRadioController.SetNB(Mode: Integer); +begin FNBMode := Mode; Changed(rfNB); { TODO wiring } end; + +procedure TRadioController.SetSNB(On_: Boolean); +begin FSNB := On_; Changed(rfSNB); { TODO wiring } end; + +procedure TRadioController.SetANF(On_: Boolean); +begin FANF := On_; Changed(rfANF); { TODO wiring } end; + +procedure TRadioController.SetMOX(On_: Boolean); +begin FTransmitting := On_; Changed(rfTransmitting); { TODO wiring: ApplyMOX } end; + +procedure TRadioController.SetTune(On_: Boolean); +begin FTuning := On_; Changed(rfTuning); { TODO wiring: ApplyTUN } end; + +procedure TRadioController.SetFMStep(Idx: Integer); +begin FFMStepIdx := Idx; Changed(rfFMStepIdx); { TODO wiring } end; + +procedure TRadioController.SetFMCTCSS(On_: Boolean); +begin FFMCTCSSOn := On_; Changed(rfFMCTCSS); { TODO wiring } end; + +procedure TRadioController.SetFMCTCSSTone(Idx: Integer); +begin FFMCTCSSToneIdx := Idx; Changed(rfFMCTCSSTone); { TODO wiring } end; + +procedure TRadioController.SetFMSquelch(On_: Boolean); +begin FFMSQOn := On_; Changed(rfFMSQ); { TODO wiring } end; + +procedure TRadioController.SetFMSquelchLevel(V: Integer); +begin FFMSQLevel := V; Changed(rfFMSQLevel); { TODO wiring } end; + +procedure TRadioController.SetFMRpt(Dir: Integer); +begin FFMRptDir := Dir; Changed(rfFMRpt); { TODO wiring } end; + +procedure TRadioController.ApplyChannel(Idx: Integer); +begin Changed(rfChannel); { TODO wiring } end; + +procedure TRadioController.StartStop; +begin SetRun(not FRunning); end; + +procedure TRadioController.SetRun(On_: Boolean); +begin FRunning := On_; Changed(rfRunning); { TODO wiring: логика BtnStartStopClick } end; + +procedure TRadioController.ConnectDevice(const Dev: THPSDRDevice); +begin { TODO wiring: DoConnectDevice } Changed(rfDevice); end; + +procedure TRadioController.Discover; +begin { TODO wiring: FNetwork.Discover } end; + +end. diff --git a/ewsdr.lpr b/ewsdr.lpr index 845b11e..fe2c468 100644 --- a/ewsdr.lpr +++ b/ewsdr.lpr @@ -18,7 +18,7 @@ uses Forms, LazOpenGLContext, MainForm, AudioOutput, DeviceForm, FlatButton, FreqDisplay, VfoOverlay, WDSP, WDSPEngine, WebPageHtml, WebServer, WebUtils, WinFirewall, WsClient, - Settings; + Settings, RadioController; {$R *.res}