Phase 5: extract channel memory logic into ChannelController.pas

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 <noreply@anthropic.com>
This commit is contained in:
Uladzimir Karpenka
2026-06-08 20:21:04 +03:00
co-authored by Claude Opus 4.8
parent 06fd730ff4
commit 03f7359184
3 changed files with 273 additions and 209 deletions
+219
View File
@@ -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.
+33 -203
View File
@@ -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;
+21 -6
View File
@@ -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;