Files
ewsdr/RadioController.pas
T
Uladzimir KarpenkaandClaude Opus 4.8 2e4cd3cc65 Phase 3 (batch 7): route CTun toggle through controller command
Первая команда тяжёлого класса. TRadioController.SetCTun несёт логику
движка/сети (band cache, при выключении — центрирование на VFO + SetShift(0)
+ UpdateState/SendFullHP); все нужные поля и хелперы XvtrTranslate/
ActiveTXFreqHz уже в контроллере. BtnCTunClick — тонкая обёртка
(SetCTun(not FCTun)); рендер (StyleButton/SyncSpecViewFreq/перерисовка
спектра+водопада) ушёл в OnControllerState(rfCTun).

Прямые записи FController.FCTun в путях band restore / ActivateVfo
оставлены — мигрируют со своими командами (Band/VFO).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 15:49:18 +03:00

625 lines
24 KiB
ObjectPascal

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, FMRepeater;
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);
procedure ApplyAGCToEngine; // применяет FAGCMode к WDSP (единый маппинг)
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;
FDrivePercent: Integer; // драйв 0..100 % (UI-уровень, источник истины)
FDriveLevel: Byte; // вычисляемый hardware-байт (CalcDriveByte)
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;
// Частотные хелперы — чистые вычисления над состоянием, общие для всех
// фронтендов и сетевого слоя (Vfo/Mode/Band/CTun/TX/FM-Rpt).
// XvtrTranslate — переводит видимую частоту в частоту IF-тракта с учётом
// активного трансвертора (LOOffset/LOError).
// ActiveTXFreqHz — текущая TX-частота (split/active VFO + FM-репитер сдвиг).
function XvtrTranslate(VisibleHz: Double): Double;
function ActiveTXFreqHz: Double;
// ====================================================================
// КОМАНДЫ — единый 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; FDrivePercent := 50; 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 := FDrivePercent; 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;
function TRadioController.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;
// f_IF = f_visible - LOOffset + LOError
Result := VisibleHz
- FXvtrSettings.Entries[FCurrentXvtr].LOOffset
+ FXvtrSettings.Entries[FCurrentXvtr].LOError;
end;
function TRadioController.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;
end;
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.ApplyAGCToEngine;
const
// UI-индекс 0..4 (FAST/MED/SLOW/LONG/OFF) -> WDSP enum. Единый маппинг для
// всех фронтендов (раньше web/слайдер ошибочно кастовали индекс прямо в enum).
AGCModes: array[0..4] of TWDSPAGCMode = (agcFast, agcMedium, agcSlow, agcLong, agcOff);
begin
if FWDSPReady and Assigned(FDSPEngine) and (FAGCMode >= 0) and (FAGCMode <= 4) then
FDSPEngine.SetAGC(AGCModes[FAGCMode], 50.0);
end;
procedure TRadioController.SetAGCMode(M: Integer);
begin
if (M < 0) or (M > 4) then Exit;
FAGCMode := M;
ApplyAGCToEngine;
FBandCache[FCurrentBand].AGCMode := FAGCMode;
Changed(rfAGCMode);
end;
procedure TRadioController.SetAGCTop(DB: Integer);
begin
if DB < 20 then DB := 20 else if DB > 120 then DB := 120;
FAGCTop := DB;
if FWDSPReady and Assigned(FDSPEngine) then FDSPEngine.SetAGCTop(FAGCTop);
ApplyAGCToEngine; // ре-применяем режим с новым уровнем
FBandCache[FCurrentBand].AGCTop := FAGCTop;
Changed(rfAGCTop);
end;
procedure TRadioController.AGCTopBy(Delta: Integer);
begin SetAGCTop(FAGCTop + Delta); end;
procedure TRadioController.SetVolume(V: Integer);
begin
if V < 0 then V := 0 else if V > 100 then V := 100;
FVolume := V;
if FWDSPReady and Assigned(FDSPEngine) then FDSPEngine.SetVolume(FVolume / 100.0);
Changed(rfVolume);
end;
procedure TRadioController.VolumeBy(Delta: Integer);
begin SetVolume(FVolume + Delta); end;
procedure TRadioController.SetMute(On_: Boolean);
begin
FMuted := On_;
if FWDSPReady and Assigned(FDSPEngine) then FDSPEngine.SetMute(FMuted);
Changed(rfMute);
end;
procedure TRadioController.ToggleMute;
begin SetMute(not FMuted); end;
procedure TRadioController.SetDrive(V: Integer);
begin FDrivePercent := V; Changed(rfDrive); { TODO wiring: CalcDriveByte + WDSP } end;
procedure TRadioController.DriveBy(Delta: Integer);
begin SetDrive(FDrivePercent + Delta); end;
procedure TRadioController.SetAtten(Idx: Integer);
begin
if Idx < 0 then Idx := 0 else if Idx > 2 then Idx := 2;
FAtten := Idx;
if Assigned(FNetwork) then FNetwork.SetStepAtten(FAtten * 10);
Changed(rfAtten);
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_;
FBandCache[FCurrentBand].CTun := FCTun;
if not FCTun then
begin
// Выключили CTUN: центрируемся на VFO, shift=0. (Включение CTUN не трогает
// движок — FCenterFreq остаётся как есть, спектр не прыгает.)
FCenterFreq := FVfoA;
if FWDSPReady and Assigned(FDSPEngine) then FDSPEngine.SetShift(0.0);
if Assigned(FNetwork) and FNetwork.Connected and FNetwork.Running then
begin
FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslate(ActiveTXFreqHz),
FDriveLevel, FTransmitting, True, True);
FNetwork.SendFullHP;
end;
end;
Changed(rfCTun);
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
if Mode < 0 then Mode := 0 else if Mode > 4 then Mode := 4;
FNRMode := Mode;
if FWDSPReady and Assigned(FDSPEngine) then FDSPEngine.SetNRMode(FNRMode);
Changed(rfNR);
end;
procedure TRadioController.SetNB(Mode: Integer);
begin
if Mode < 0 then Mode := 0 else if Mode > 2 then Mode := 2;
FNBMode := Mode;
if FWDSPReady and Assigned(FDSPEngine) then FDSPEngine.SetNBMode(FNBMode);
Changed(rfNB);
end;
procedure TRadioController.SetSNB(On_: Boolean);
begin
FSNB := On_;
if FWDSPReady and Assigned(FDSPEngine) then FDSPEngine.SetSNB(FSNB);
Changed(rfSNB);
end;
procedure TRadioController.SetANF(On_: Boolean);
begin
FANF := On_;
if FWDSPReady and Assigned(FDSPEngine) then FDSPEngine.SetANF(FANF);
Changed(rfANF);
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
if Idx < 0 then Idx := 0 else if Idx > 3 then Idx := 3; // FM_STEP_COUNT = 4
FFMStepIdx := Idx;
Changed(rfFMStepIdx);
end;
procedure TRadioController.SetFMCTCSS(On_: Boolean);
begin
FFMCTCSSOn := On_;
if FWDSPReady and Assigned(FDSPEngine) then
FDSPEngine.SetTXCTCSS(FFMCTCSSOn, CTCSS_TONES[FFMCTCSSToneIdx]);
Changed(rfFMCTCSS);
end;
procedure TRadioController.SetFMCTCSSTone(Idx: Integer);
begin
if Idx < 0 then Idx := 0 else if Idx > CTCSS_COUNT - 1 then Idx := CTCSS_COUNT - 1;
FFMCTCSSToneIdx := Idx;
if FWDSPReady and Assigned(FDSPEngine) then
FDSPEngine.SetTXCTCSS(FFMCTCSSOn, CTCSS_TONES[FFMCTCSSToneIdx]);
Changed(rfFMCTCSSTone);
end;
procedure TRadioController.SetFMSquelch(On_: Boolean);
begin
FFMSQOn := On_;
if FWDSPReady and Assigned(FDSPEngine) then
FDSPEngine.SetFMSquelch(FFMSQOn and (FMode = MODE_FM), FFMSQLevel);
Changed(rfFMSQ);
end;
procedure TRadioController.SetFMSquelchLevel(V: Integer);
begin
FFMSQLevel := V;
if FWDSPReady and Assigned(FDSPEngine) then
FDSPEngine.SetFMSquelch(FFMSQOn and (FMode = MODE_FM), FFMSQLevel);
Changed(rfFMSQLevel);
end;
procedure TRadioController.SetFMRpt(Dir: Integer);
begin FFMRptDir := Dir; Changed(rfFMRpt); { TODO wiring } end;
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.