From 03f73591841de6e33de5f5d8ccf21a4270e682ec Mon Sep 17 00:00:00 2001 From: Uladzimir Karpenka Date: Mon, 8 Jun 2026 20:21:04 +0300 Subject: [PATCH] Phase 5: extract channel memory logic into ChannelController.pas MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Channels are recall-of-saved-config — a client of the radio core, not part of DSP/network. Move ApplyChannel/CheckChannelActive + active-channel state (FActiveChannelIdx, FPreChannelPower) out of MainForm into TChannelController, which orchestrates controller commands and emits OnActiveChanged for the Channel-button render. Removes the last UI-held state blocking headless mode. Also completes TRadioController.SetDrive (was a skeleton): clamp + CalcDriveByte + SetDriveLevel + PushNetworkState + Changed(rfDrive), with an rfDrive render case; routes slider/web/channel drive through it. Adds SetCurrentBandIdx for the lightweight HF band switch. Behavior note: channel recall now applies the mode-default filter via SetMode (was: kept current filter — a latent quirk that left FM on an SSB bandwidth). Co-Authored-By: Claude Opus 4.8 --- ChannelController.pas | 219 +++++++++++++++++++++++++++++++++++++++ MainForm.pas | 236 ++++++------------------------------------ RadioController.pas | 27 +++-- 3 files changed, 273 insertions(+), 209 deletions(-) create mode 100644 ChannelController.pas diff --git a/ChannelController.pas b/ChannelController.pas new file mode 100644 index 0000000..7defbd3 --- /dev/null +++ b/ChannelController.pas @@ -0,0 +1,219 @@ +unit ChannelController; + +// ----------------------------------------------------------------------------- +// TChannelController — адаптер каналов памяти поверх TRadioController. +// +// Каналы концептуально — recall сохранённого конфига (частота/режим/FM/мощность), +// т.е. клиент радио-ядра, а не часть DSP/сети. Поэтому логика применения каналов +// живёт здесь, отдельным юнитом, и оркеструет ПУБЛИЧНЫЕ команды контроллера +// (SetMode/SetFMCTCSS/SetFMRpt/SetVfoA/SetDrive/SaveCurrentBand/…). Каждая +// команда несёт DSP+сеть+состояние и эмитит Changed(field) — рендер делает UI +// через OnStateChanged. Юнит держит ТОЛЬКО канальное состояние (активный индекс +// + мощность «до канала») — это снимает последний UI-блокер для headless-режима. +// +// Наружу юнит эмитит собственное событие OnActiveChanged (рендер кнопки Channel). +// Две UI-композитные XVTR-операции (вход/выход трансвертера с wideband-видом и +// web-пушем) пока живут в MainForm-обёртках — юнит зовёт их через колбэки +// OnActivateXvtr/OnDeactivateXvtr (сократятся при переносе xvtr render-extras в +// OnControllerState). +// ----------------------------------------------------------------------------- + +{$mode delphi}{$H+} + +interface + +uses + Classes, SysUtils, Math, + RadioController, ChannelStore, Settings, WDSPEngine, FMRepeater, BoardUtils; + +type + // Рендер активного канала: Active=True → подсветка + Name; False → сброс. + TChannelActiveEvent = procedure(Idx: Integer; const Name: string; Active: Boolean) of object; + TChannelXvtrEvent = procedure(Idx: Integer) of object; + TChannelNotifyEvent = procedure of object; + + TChannelController = class + private + FRadio: TRadioController; + FActiveIdx: Integer; // -1 = нет активного канала + FPreChannelPower: Integer; // drive % до применения канала (-1 = не сохранён) + FOnActiveChanged: TChannelActiveEvent; + FOnActivateXvtr: TChannelXvtrEvent; + FOnDeactivateXvtr: TChannelNotifyEvent; + procedure EmitActive(Active: Boolean; const Name: string); + public + constructor Create(ARadio: TRadioController); + + procedure Select(Idx: Integer); // выбор канала из дропдауна + procedure Apply(const Ch: TChannel); // применить канал к радио + procedure CheckActive; // деактивировать при уходе с частоты + procedure OnVfoTuned; // канальная оркестрация после SetVfoA + procedure ClearPreChannelPower; // ручное изменение drive снимает память + + property ActiveIdx: Integer read FActiveIdx write FActiveIdx; + property OnActiveChanged: TChannelActiveEvent read FOnActiveChanged write FOnActiveChanged; + property OnActivateXvtr: TChannelXvtrEvent read FOnActivateXvtr write FOnActivateXvtr; + property OnDeactivateXvtr: TChannelNotifyEvent read FOnDeactivateXvtr write FOnDeactivateXvtr; + end; + +implementation + +constructor TChannelController.Create(ARadio: TRadioController); +begin + inherited Create; + FRadio := ARadio; + FActiveIdx := -1; + FPreChannelPower := -1; +end; + +procedure TChannelController.EmitActive(Active: Boolean; const Name: string); +begin + if Assigned(FOnActiveChanged) then FOnActiveChanged(FActiveIdx, Name, Active); +end; + +procedure TChannelController.ClearPreChannelPower; +begin + FPreChannelPower := -1; +end; + +procedure TChannelController.Select(Idx: Integer); +var Ch: TChannel; +begin + if (FRadio.FChannelStore = nil) or (Idx < 0) or (Idx >= FRadio.FChannelStore.Count) then Exit; + Ch := FRadio.FChannelStore.GetChannel(Idx); + FActiveIdx := Idx; + EmitActive(True, Ch.Name); // рендер кнопки Channel (подсветка + имя) + Apply(Ch); +end; + +procedure TChannelController.Apply(const Ch: TChannel); +var + i, NewBand, XvtrIdx: Integer; +begin + // Сохраняем текущее состояние до любых изменений + FRadio.SaveCurrentBand; + + // Ищем подходящий XVTR-слот по частоте канала + XvtrIdx := -1; + for i := 0 to CFG_XVTR_COUNT - 1 do + if FRadio.FXvtrSettings.Entries[i].Enabled and + (Ch.RXFreq >= FRadio.FXvtrSettings.Entries[i].FreqBegin) and + (Ch.RXFreq <= FRadio.FXvtrSettings.Entries[i].FreqEnd) then + begin + XvtrIdx := i; + Break; + end; + + NewBand := -1; + if XvtrIdx >= 0 then + begin + // Канал на трансвертерном диапазоне — активируем XVTR если не активен + if (FRadio.FCurrentXvtr <> XvtrIdx) and Assigned(FOnActivateXvtr) then + FOnActivateXvtr(XvtrIdx); + end + else + begin + // Канал на HF (или неизвестная частота). Если были на XVTR — выходим в HF, + // иначе SetVfoA заклэмпит частоту. + if (FRadio.FCurrentXvtr >= 0) and Assigned(FOnDeactivateXvtr) then + FOnDeactivateXvtr; + NewBand := FreqToBandIdx(Ch.RXFreq); + if (NewBand >= 0) and (NewBand <> FRadio.FCurrentBand) then + FRadio.SetCurrentBandIdx(NewBand); // band-индекс + drive-кал + рендер кнопок + end; + + // Режим (после XVTR/Band режим мог измениться — сверяем снова). SetMode несёт + // DSP + дефолтный фильтр под режим + рендер (FM-панели для FM). + if FRadio.FMode <> Ch.Mode then + FRadio.SetMode(Ch.Mode); + + // FM-параметры + if FRadio.FMode = MODE_FM then + begin + FRadio.FFMCTCSSAutoActive := Ch.CTCSSOn; // CTCSS из канала — авто, сбросится при уходе + FRadio.SetFMCTCSSTone(Ch.CTCSSToneIdx); + FRadio.SetFMCTCSS(Ch.CTCSSOn); + FRadio.FFMRptOffsetHz := Ch.RptOffsetHz; // зафиксировать до рендера в SetFMRpt + FRadio.SetFMRpt(Ch.RptDir); // dir + рендер кнопок/оффсета + FRadio.FFMRptAutoActive := Ch.RptDir <> RPT_NONE; // RPT из канала — авто + end; + + // Мощность из канала. Запоминаем drive «до канала» для восстановления при уходе + // (не перетираем при переходе канал→канал). + if Ch.Power >= 0 then + begin + if FPreChannelPower < 0 then + FPreChannelPower := FRadio.FDrivePercent; + FRadio.SetDrive(EnsureRange(Ch.Power, 0, 100)); + end; + + // Частота VFO A (XVTR-клэмп, CTUN, band-detect, сеть — всё в SetVfoA) + FRadio.SetVfoA(Round(Ch.RXFreq)); + + // Сохраняем итог в band/XVTR-кэш + if (XvtrIdx >= 0) or (NewBand >= 0) then + FRadio.SaveCurrentBand; +end; + +procedure TChannelController.CheckActive; +var Ch: TChannel; +begin + if FActiveIdx < 0 then Exit; + if (FRadio.FChannelStore = nil) or (FActiveIdx >= FRadio.FChannelStore.Count) then + begin + FActiveIdx := -1; + EmitActive(False, ''); + Exit; + end; + Ch := FRadio.FChannelStore.GetChannel(FActiveIdx); + if Round(FRadio.FVfoA) <> Round(Ch.RXFreq) then + begin + FActiveIdx := -1; + EmitActive(False, ''); + + // Сбрасываем CTCSS если он был включён каналом (не вручную) + if FRadio.FFMCTCSSAutoActive then + begin + FRadio.FFMCTCSSAutoActive := False; + FRadio.SetFMCTCSS(False); + end; + + // Сбрасываем RPT если он был включён каналом (не вручную), затем даём + // UpdateRptAutoState переоценить авто-MINUS для текущей частоты. + if FRadio.FFMRptAutoActive then + begin + FRadio.SetFMRpt(RPT_NONE); // dir NONE + рендер off (снимает авто-флаг) + FRadio.UpdateRptAutoState; + end; + + // Восстанавливаем мощность которая была до применения канала + if FPreChannelPower >= 0 then + begin + FRadio.SetDrive(FPreChannelPower); + FPreChannelPower := -1; + end; + end; +end; + +procedure TChannelController.OnVfoTuned; +// Вызывается из пути тюнинга (MainForm.ApplyVfoA) ПОСЛЕ FController.SetVfoA. +// Без активного канала: сброс авто-CTCSS + восстановление pre-channel drive. +// С активным каналом: CheckActive деактивирует при уходе с частоты канала. +begin + if FActiveIdx < 0 then + begin + if FRadio.FFMCTCSSAutoActive then + begin + FRadio.FFMCTCSSAutoActive := False; + FRadio.SetFMCTCSS(False); + end; + if FPreChannelPower >= 0 then + begin + FRadio.SetDrive(FPreChannelPower); // восстановить + push + рендер слайдера + FPreChannelPower := -1; + end; + end; + CheckActive; // no-op если FActiveIdx<0 +end; + +end. diff --git a/MainForm.pas b/MainForm.pas index a452104..b04cfa1 100644 --- a/MainForm.pas +++ b/MainForm.pas @@ -42,7 +42,7 @@ uses ChannelStore, ChannelsForm, PowerInhibit, DeviceStore, - RadioController; + RadioController, ChannelController; const CLR_BG = TColor($00101010); @@ -151,7 +151,6 @@ type FDisplayFPS: Integer; FWaterfallDirty: Boolean; FWidebandDirty: Boolean; - FPreChannelPower: Integer; // мощность до применения канала (-1 = не сохранена) // Display state (FShow*/FSpec*/FWf*/FTXSpec*/FController.FWidebandFill/FController.FSpectrumFill) // переехало в TRadioController (нужно headless-load в DoConnectDevice). // DSP data buffers (copy of last DSP output, for WebServer.PushSpectrum) @@ -171,6 +170,7 @@ type // --- Settings --- FWebServer: TWebServer; FWebAdapter: TWebAdapter; // мост web-сервер ↔ контроллер (вынесенные WebOnXxx) + FChannelController: TChannelController; // адаптер каналов памяти поверх контроллера FWebEnabled: Boolean; FWebPort: Integer; FWebBindAddr: string; @@ -284,7 +284,6 @@ type BtnChannels: TFlatButton; FChannelsDropDown: TFlatDropDown; FChannelsForm: TObject; // TChannelsForm (cast при использовании) - FActiveChannelIdx: Integer; // -1 = нет активного канала // ---- Right panel ---- PanelRight: TPanel; @@ -402,8 +401,7 @@ type procedure OnChannelsDropDownSelect(Sender: TObject; Idx: Integer); procedure RefreshChannelsDropDown; procedure OnChannelStoreChanged(Sender: TObject); - procedure ApplyChannel(const Ch: TChannel); - procedure CheckChannelActive; + procedure OnChannelActiveChanged(Idx: Integer; const Name: string; Active: Boolean); procedure PbSpectrumMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); procedure PbSpectrumMouseMove(Sender: TObject; Shift: TShiftState; @@ -847,6 +845,13 @@ begin FController := TRadioController.Create; FController.OnInvoke := DoInvoke; FController.OnStateChanged := OnControllerState; + // Адаптер каналов поверх контроллера: оркеструет команды, держит активный + // канал/pre-channel-drive. Рендер кнопки Channel — через OnActiveChanged; + // XVTR-вход/выход — через UI-обёртки (wideband/web живут в них). + FChannelController := TChannelController.Create(FController); + FChannelController.OnActiveChanged := OnChannelActiveChanged; + FChannelController.OnActivateXvtr := ActivateXvtrBand; + FChannelController.OnDeactivateXvtr := DeactivateXvtr; FController.FVfoA := 14200000; FController.FVfoB := 7100000; FController.FActiveVfo := 0; @@ -892,7 +897,6 @@ begin FController.FFMRptOffsetHz := RPT_DEFAULT_2M; FController.FFMRptAutoActive := False; FController.FFMCTCSSAutoActive := False; - FPreChannelPower := -1; FSpectrumBufCount := 1024; FWaterfallBufCount := 1024; FWaterfallFrameInterval := 2; @@ -908,7 +912,6 @@ begin FController.FTXSpecGridStep := 10.0; FSettingsForm := nil; FChannelsForm := nil; - FActiveChannelIdx := -1; FillChar(FController.FDevMAC, SizeOf(FController.FDevMAC), 0); // Инициализируем кэш диапазонов умолчаниями @@ -1173,6 +1176,7 @@ begin // Размер окна сохраняем всегда (не зависит от подключения) FController.FSettings.SaveStartupPreview(FController.FVfoA, FController.FVfoB, FController.FSampleRate); FController.FSettings.Save; + FreeAndNil(FChannelController); // адаптер каналов (не владеет store/контроллером) FreeAndNil(FController.FChannelStore); FWebServer.Stop; FreeAndNil(FWebAdapter); // адаптер не владеет сервером — освобождаем после Stop @@ -1346,6 +1350,8 @@ begin for i := 0 to 4 do StyleButton(BtnAGCMode[i], i = FController.FAGCMode); rfVolume: TrkVolume.Position := FController.FVolume; + rfDrive: + TrkDrive.Position := FController.FDrivePercent; // слайдер ← drive% rfAGCTop: begin TrkAGC.Position := FController.FAGCTop; @@ -3915,30 +3921,9 @@ begin // OnControllerState(rfVfoA/rfBand). FController.SetVfoA(NewFreq); - // Канальная оркестрация (UI). Только при отсутствии активного канала. - // Авто-RPT сбрасывает сам SetVfoA через UpdateRptAutoState; здесь — авто-CTCSS - // и восстановление pre-channel drive (виджет TrkDrive). - if FActiveChannelIdx < 0 then - begin - if FController.FFMCTCSSAutoActive then - begin - FController.FFMCTCSSAutoActive := False; - FController.FFMCTCSSOn := False; - if BtnFMCTCSS <> nil then StyleButton(BtnFMCTCSS, False); - if FController.FWDSPReady then FController.FDSPEngine.SetTXCTCSS(False, CTCSS_TONES[FController.FFMCTCSSToneIdx]); - end; - if FPreChannelPower >= 0 then - begin - TrkDrive.Position := FPreChannelPower; - FPreChannelPower := -1; - FController.FDriveLevel := CalcDriveByte; - if FController.FWDSPReady then - FController.FDSPEngine.SetDriveLevel(FController.FDrivePercent / 100.0); - FController.PushNetworkState; // протолкнуть восстановленный drive - end; - end; - - CheckChannelActive; + // Канальная оркестрация (авто-CTCSS/pre-channel-drive + деактивация при уходе + // с частоты канала) — в адаптере каналов. + FChannelController.OnVfoTuned; end; // --------------------------------------------------------------------------- @@ -4452,8 +4437,8 @@ begin for i := 0 to FController.FChannelStore.Count - 1 do names[i] := FController.FChannelStore.GetChannel(i).Name; FChannelsDropDown.SetItems(names); - if (FActiveChannelIdx >= 0) and (FActiveChannelIdx < FController.FChannelStore.Count) then - FChannelsDropDown.SetItemIndex(FActiveChannelIdx) + if (FChannelController.ActiveIdx >= 0) and (FChannelController.ActiveIdx < FController.FChannelStore.Count) then + FChannelsDropDown.SetItemIndex(FChannelController.ActiveIdx) else if BtnChannels <> nil then BtnChannels.Caption := 'Channel'; end; @@ -4497,168 +4482,20 @@ begin end; procedure TMainForm.OnChannelsDropDownSelect(Sender: TObject; Idx: Integer); -var Ch: TChannel; begin - 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; + // Логика применения канала (band/XVTR/mode/FM/drive/freq) — в адаптере каналов; + // рендер кнопки Channel — в OnChannelActiveChanged. + FChannelController.Select(Idx); +end; + +procedure TMainForm.OnChannelActiveChanged(Idx: Integer; const Name: string; Active: Boolean); +begin + // Рендер кнопки Channel по событию адаптера каналов (Active=True → подсветка+имя). + if BtnChannels = nil then Exit; + StyleButton(BtnChannels, Active); + if Active then BtnChannels.Caption := Name + else BtnChannels.Caption := 'Channel'; BtnChannels.Repaint; - ApplyChannel(Ch); -end; - -procedure TMainForm.ApplyChannel(const Ch: TChannel); -var - i, NewBand, XvtrIdx: Integer; -begin - // Сохраняем текущее состояние до любых изменений - SaveCurrentBand; - - // Ищем подходящий XVTR-слот по частоте канала - XvtrIdx := -1; - for i := 0 to CFG_XVTR_COUNT - 1 do - 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; - end; - - NewBand := -1; - - if XvtrIdx >= 0 then - begin - // Канал на трансвертерном диапазоне — активируем XVTR если не активен - if FController.FCurrentXvtr <> XvtrIdx then - ActivateXvtrBand(XvtrIdx); - end - else - begin - // Канал на HF (или неизвестная частота) - // Если были на XVTR — выходим из него, иначе ApplyVfoA заклэмпит частоту - if FController.FCurrentXvtr >= 0 then - DeactivateXvtr; - NewBand := FreqToBandIdx(Ch.RXFreq); - if (NewBand >= 0) and (NewBand <> FController.FCurrentBand) then - begin - StyleButton(BtnBand[FController.FCurrentBand], False); - FController.FCurrentBand := NewBand; - StyleButton(BtnBand[FController.FCurrentBand], True); - FController.FDriveLevel := CalcDriveByte; - end; - end; - - // Режим (после ActivateXvtrBand/DeactivateXvtr FController.FMode обновился — сравниваем снова) - if FController.FMode <> Ch.Mode then - begin - FController.FMode := Ch.Mode; - for i := 0 to MODE_COUNT - 1 do StyleButton(BtnMode[i], i = FController.FMode); - if FController.FWDSPReady then - begin - FController.FDSPEngine.SetMode(FController.FMode); - if FController.FTuning then FController.FDSPEngine.SetTXTone(True, FController.FTXSettings.TUNFreq, 1.0); - end; - UpdateFilterButtons; - FController.ApplyModeFilter; - SyncSpecViewFreq; - end; - - // FM-параметры - if FController.FMode = MODE_FM then - begin - FController.FFMCTCSSOn := Ch.CTCSSOn; - FController.FFMCTCSSAutoActive := Ch.CTCSSOn; // CTCSS из канала — авто, сбросится при уходе с частоты - SetFMCTCSSTone(Ch.CTCSSToneIdx); - if BtnFMCTCSS <> nil then - begin - StyleButton(BtnFMCTCSS, FController.FFMCTCSSOn); - BtnFMCTCSS.Repaint; - end; - 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; - - // Мощность из канала. - // TrkDrive.Position := ... синхронно вызывает TrkDriveChange → FPreChannelPower := -1, - // поэтому сохраняем оригинал в локальную переменную и восстанавливаем после. - if Ch.Power >= 0 then - begin - i := FController.FDrivePercent; // запомнить до срабатывания OnChange - TrkDrive.Position := EnsureRange(Ch.Power, 0, 100); // TrkDriveChange обновит FController.FDriveLevel и WDSP - if FPreChannelPower < 0 then - FPreChannelPower := i; // установить после (TrkDriveChange уже сбросил в -1) - end; - - // Частота VFO A - ApplyVfoA(Round(Ch.RXFreq)); - - // Сохраняем состояние - if XvtrIdx >= 0 then - SaveCurrentBand // сохраняем в XVTR-слот (CTCSS/RPT/Mode из канала) - else if NewBand >= 0 then - SaveCurrentBand; // сохраняем в HF band-кэш -end; - -procedure TMainForm.CheckChannelActive; -var Ch: TChannel; -begin - if FActiveChannelIdx < 0 then Exit; - if (FController.FChannelStore = nil) or (FActiveChannelIdx >= FController.FChannelStore.Count) then - begin - FActiveChannelIdx := -1; - Exit; - end; - Ch := FController.FChannelStore.GetChannel(FActiveChannelIdx); - if Round(FController.FVfoA) <> Round(Ch.RXFreq) then - begin - FActiveChannelIdx := -1; - StyleButton(BtnChannels, False); - BtnChannels.Caption := 'Channel'; - BtnChannels.Repaint; - - // Сбрасываем CTCSS если он был включён каналом (не вручную) - if FController.FFMCTCSSAutoActive then - begin - FController.FFMCTCSSAutoActive := False; - FController.FFMCTCSSOn := False; - if BtnFMCTCSS <> nil then - begin - StyleButton(BtnFMCTCSS, False); - BtnFMCTCSS.Repaint; - end; - if FController.FWDSPReady then - FController.FDSPEngine.SetTXCTCSS(False, CTCSS_TONES[FController.FFMCTCSSToneIdx]); - end; - - // Сбрасываем RPT если он был включён каналом (не вручную), затем - // даём UpdateRptAutoState переоценить авто-MINUS для текущей частоты - if FController.FFMRptAutoActive then - begin - FController.FFMRptDir := RPT_NONE; - FController.FFMRptAutoActive := False; - if BtnFMRptMinus <> nil then StyleButton(BtnFMRptMinus, False); - if BtnFMRptPlus <> nil then StyleButton(BtnFMRptPlus, False); - UpdateRptAutoState; - end; - - // Восстанавливаем мощность которая была до применения канала - if FPreChannelPower >= 0 then - begin - TrkDrive.Position := FPreChannelPower; - FPreChannelPower := -1; - FController.FDriveLevel := CalcDriveByte; - if FController.FWDSPReady then - FController.FDSPEngine.SetDriveLevel(FController.FDrivePercent / 100.0); - end; - end; end; procedure TMainForm.SetFMCTCSSTone(Idx: Integer); @@ -5253,16 +5090,9 @@ end; procedure TMainForm.TrkDriveChange(Sender: TObject); begin - FController.FDrivePercent := TrkDrive.Position; // слайдер — ввод; контроллер — истина - FPreChannelPower := -1; // ручное изменение снимает сохранённую мощность канала - FController.FDriveLevel := CalcDriveByte; - if FController.FWDSPReady then - FController.FDSPEngine.SetDriveLevel(FController.FDrivePercent / 100.0); - if FController.FNetwork.Connected and FController.FNetwork.Running then - begin - FController.FNetwork.UpdateState(XvtrTranslate(FController.FCenterFreq), XvtrTranslate(ActiveTXFreqHz), FController.FDriveLevel, FController.FTransmitting, True, True); - FController.FNetwork.SendFullHP; - end; + if FSyncingFromController then Exit; // программная установка из OnControllerState — не команда + FChannelController.ClearPreChannelPower; // ручное изменение снимает сохранённую мощность канала + FController.SetDrive(TrkDrive.Position); end; function TMainForm.CalcDriveByte: Byte; diff --git a/RadioController.pas b/RadioController.pas index 0758d00..377bcca 100644 --- a/RadioController.pas +++ b/RadioController.pas @@ -372,6 +372,7 @@ type procedure BandUp; procedure BandDown; procedure SetXvtrBand(Idx: Integer); // -1 = выход в HF + procedure SetCurrentBandIdx(NewBand: Integer); // лёгкая смена HF-диапазона (band+drive+рендер), без restore procedure SetSpan(Hz: Integer); procedure SetSampleRate(Hz: Integer); @@ -401,8 +402,8 @@ type procedure SetFMRptOffset(Hz: Double); // сдвиг репитера; применяет, если RPT вкл procedure UpdateRptAutoState; // авто-MINUS для 2 м под FVfoA - // Каналы - procedure ApplyChannel(Idx: Integer); + // Каналы: применение/деактивация каналов вынесено в TChannelController + // (отдельный юнит-адаптер поверх контроллера), оркеструет команды ниже. // Жизненный цикл движка / устройства procedure StartStop; @@ -1431,7 +1432,14 @@ procedure TRadioController.ToggleMute; begin SetMute(not FMuted); end; procedure TRadioController.SetDrive(V: Integer); -begin FDrivePercent := V; Changed(rfDrive); { TODO wiring: CalcDriveByte + WDSP } end; +begin + if V < 0 then V := 0 else if V > 100 then V := 100; + FDrivePercent := V; + FDriveLevel := CalcDriveByte; + if FWDSPReady and Assigned(FDSPEngine) then FDSPEngine.SetDriveLevel(FDrivePercent / 100.0); + PushNetworkState; // протолкнуть новый drive в радио (если подключено+Running) + Changed(rfDrive); +end; procedure TRadioController.DriveBy(Delta: Integer); begin SetDrive(FDrivePercent + Delta); end; @@ -1550,6 +1558,16 @@ begin else DeactivateXvtr; end; +procedure TRadioController.SetCurrentBandIdx(NewBand: Integer); +// Лёгкая смена HF-диапазона без полного restore: фиксируем индекс диапазона +// (для drive-кал и подсветки кнопок) и пересчитываем drive-байт. Используется +// канальным адаптером при переходе канала на другой HF-диапазон. +begin + FCurrentBand := NewBand; + FDriveLevel := CalcDriveByte; + Changed(rfBand); +end; + procedure TRadioController.SetSpan(Hz: Integer); begin FSpanHz := Hz; Changed(rfSpan); { TODO wiring } end; @@ -1946,9 +1964,6 @@ begin end; end; -procedure TRadioController.ApplyChannel(Idx: Integer); -begin Changed(rfChannel); { TODO wiring } end; - procedure TRadioController.StartStop; begin SetRun(not FRunning); end;