{
  Copyright (C)
  2026 - Uladzimir Karpenka, EW8BAK

  This program is free software; you can redistribute it and/or
  modify it under the terms of the GNU General Public License
  as published by the Free Software Foundation; either version 2
  of the License, or (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
}

unit WebAdapter;

{$mode objfpc}{$H+}

// Адаптер веб-интерфейса: мост между TWebServer (входящие команды из WS-потока)
// и TRadioController (ядро). Раньше эти WebOnXxx/SyncWebXxx жили в TMainForm —
// вынесены сюда, чтобы развязать UI от веба и подготовить headless-демон.
//
// Маршалинг: входящие команды приходят из потока веб-сервера; выполняем их в
// потоке контроллера через FController.Invoke (GUI = TThread.Synchronize; демон
// подставит свою очередь). Synchronize блокирующий и последовательный — поля
// FSync* для передачи параметров безопасны (гонок нет).
//
// Часть команд (тюнинг VFO/диапазон/MOX/...) идёт через UI-обёртки MainForm,
// которые ещё не headless — для них есть IWebHost (его реализует хозяин). По мере
// переноса оркестрации в контроллер интерфейс будет сужаться.

interface

uses
  Classes, SysUtils,
  RadioController, WebServer, WDSPEngine, HPSDRNetwork, HPSDRProtocol, RadioBackend, BoardUtils, Settings;

type
  // UI/оркестрационные операции, которые адаптер дёргает у хозяина (пока не
  // headless — десктоп: методы MainForm; демон: своя реализация). Наполняется
  // в WA2 по мере переноса host-зависимых хендлеров.
  IWebHost = interface
    ['{B3A1F7C2-9D54-4E18-9C6B-2E5A7F0D1A33}']
  end;

  { TWebAdapter }
  TWebAdapter = class
  private
    FController: TRadioController;
    FServer:     TWebServer;
    FHost:       IWebHost;
    // Поля для передачи параметров команды в Sync-метод (через Invoke).
    FSyncInt:    Integer;
    FSyncBool:   Boolean;
    FSyncFreq:   Double;
    // Sync-методы (выполняются в потоке контроллера через Invoke).
    procedure SyncMode;
    procedure SyncFilter;
    procedure SyncAGC;
    procedure SyncAGCTop;
    procedure SyncVolume;
    procedure SyncDrive;
    procedure SyncSpan;
    procedure SyncBand;
    procedure SyncXvtrBand;
    procedure SyncWfAGC;
    procedure SyncWfNF;
    procedure SyncMute;
    procedure SyncNR;
    procedure SyncNB;
    procedure SyncSNB;
    procedure SyncANF;
    procedure SyncFreq;
    procedure SyncFreqA;
    procedure SyncFreqB;
    procedure SyncCenter;
    procedure SyncAttn;
    procedure SyncActiveVfo;
    procedure SyncCtun;
    procedure SyncMOX;
    procedure SyncTun;
    procedure SyncFMStep;
    procedure SyncBeacon;
    procedure SyncBeaconSeed;
    procedure SyncRxGainMode;
    procedure SyncRxGain;
    procedure SyncRxMute;
    function  BeaconStatusText: string;   // компактный статус (как десктоп поле 7)
  public
    constructor Create(AController: TRadioController; AServer: TWebServer;
                       AHost: IWebHost);

    // Колбэки веб-сервера (навешиваются хозяином на FServer.OnXxx).
    procedure OnMode(Mode: Integer);
    procedure OnFilter(BW: Integer);
    procedure OnAGC(Mode: Integer);
    procedure OnAGCTop(DB: Integer);
    procedure OnVolume(V: Integer);
    procedure OnDrive(V: Integer);
    procedure OnSpan(Hz: Integer);
    procedure OnBand(Idx: Integer);
    procedure OnXvtrBand(Idx: Integer);
    procedure OnWfAGC(On_: Boolean);
    procedure OnWfNF(On_: Boolean);
    procedure OnMute(On_: Boolean);
    procedure OnNR(Mode: Integer);
    procedure OnNB(Mode: Integer);
    procedure OnSNB(On_: Boolean);
    procedure OnANF(On_: Boolean);
    procedure OnFreq(Hz: Double);
    procedure OnFreqA(Hz: Double);
    procedure OnFreqB(Hz: Double);
    procedure OnCenter(Hz: Double);
    procedure OnAttn(Idx: Integer);
    procedure OnActiveVfo(Idx: Integer);
    procedure OnCtun(On_: Boolean);
    procedure OnMOX(On_: Boolean);
    procedure OnTun(On_: Boolean);
    procedure OnFMStep(Idx: Integer);
    // QO-100 beacon: вкл/выкл лок и наведение по клику в спектре (frac 0..1).
    procedure OnBeacon(On_: Boolean);
    procedure OnBeaconSeed(Frac: Double);
    procedure OnRxGainMode(Mode: Integer);
    procedure OnRxGain(Db: Integer);
    procedure OnRxMute(On_: Boolean);
    procedure OnClientActiveChanged(Active: Boolean);
    // TX-mic от web-клиента → WDSP. Вызывается из WS-потока; без маршалинга —
    // подача в движок thread-safe (как OnMicPacket/OnDDCIQ контроллера).
    procedure OnMic(Samples: PSingle; Count: Integer);

    // RX-аудио-перехват: навешивается на FController.OnAudioConsume. Если есть
    // активный web-клиент — сводим в моно и шлём ему, возвращаем True (локальный
    // AudioOut пропускается контроллером). Вызывается из DSP-потока.
    function AudioConsume(const Left, Right: array of Single;
                          Count: Integer): Boolean;

    // Исходящее зеркало: периодический снимок состояния + спектр/водопад в web.
    // Зовётся хозяином из GUI-таймера; всё (включая последний кадр спектра/
    // водопада) читается из FController. Сервер сам гейтит по FRunning и шлёт
    // активным клиентам.
    procedure PushState;
  end;

implementation

constructor TWebAdapter.Create(AController: TRadioController;
  AServer: TWebServer; AHost: IWebHost);
begin
  inherited Create;
  FController := AController;
  FServer     := AServer;
  FHost       := AHost;
end;

// ── Входящие команды: запоминаем параметр, выполняем в потоке контроллера ─────

procedure TWebAdapter.OnMode(Mode: Integer);
begin FSyncInt := Mode; FController.Invoke(@SyncMode); end;

procedure TWebAdapter.SyncMode;
begin
  if (FSyncInt < MODE_MIN) or (FSyncInt > MODE_MAX) then Exit;
  FController.SetMode(FSyncInt);   // дефолт фильтра + DSP + cache; рендер через rfMode
end;

procedure TWebAdapter.OnFilter(BW: Integer);
begin FSyncInt := BW; FController.Invoke(@SyncFilter); end;

procedure TWebAdapter.SyncFilter;
begin
  // Web-клиент шлёт полосу в Гц (кнопки панели фильтров = пресетные значения);
  // команда применяет её и подсвечивает ближайший пресет (рендер — rfFilter).
  FController.SetFilterBW(FSyncInt);
end;

procedure TWebAdapter.OnAGC(Mode: Integer);
begin FSyncInt := Mode; FController.Invoke(@SyncAGC); end;

procedure TWebAdapter.SyncAGC;
begin
  FController.SetAGCMode(FSyncInt);   // единый маппинг (исправляет старый web-баг)
end;

procedure TWebAdapter.OnAGCTop(DB: Integer);
begin FSyncInt := DB; FController.Invoke(@SyncAGCTop); end;

procedure TWebAdapter.SyncAGCTop;
begin
  FController.SetAGCTop(FSyncInt);   // клампит, применяет, OnControllerState обновит UI
end;

procedure TWebAdapter.OnVolume(V: Integer);
begin FSyncInt := V; FController.Invoke(@SyncVolume); end;

procedure TWebAdapter.SyncVolume;
begin FController.SetVolume(FSyncInt); end;

procedure TWebAdapter.OnDrive(V: Integer);
begin FSyncInt := V; FController.Invoke(@SyncDrive); end;

procedure TWebAdapter.SyncDrive;
begin FController.SetDrive(FSyncInt); end;   // rfDrive снимет память канала (ChannelController)

procedure TWebAdapter.OnBand(Idx: Integer);
begin FSyncInt := Idx; FController.Invoke(@SyncBand); end;

procedure TWebAdapter.SyncBand;
begin FController.SetBand(FSyncInt); end;   // XVTR-выход + persist + restore — в команде

procedure TWebAdapter.OnXvtrBand(Idx: Integer);
begin FSyncInt := Idx; FController.Invoke(@SyncXvtrBand); end;

procedure TWebAdapter.SyncXvtrBand;
begin
  if FSyncInt < 0 then begin FController.SetXvtrBand(-1); Exit; end;
  if FSyncInt >= CFG_XVTR_COUNT then Exit;
  if not FController.FXvtrSettings.Entries[FSyncInt].Enabled then Exit;
  if FSyncInt = FController.FCurrentXvtr then Exit;
  if FController.FDevConnected then
  begin FController.SaveCurrentBand; FController.FSettings.Save; end;
  FController.SetXvtrBand(FSyncInt);   // ActivateXvtr (рендер/wideband/web — rfXvtr)
end;

procedure TWebAdapter.OnSpan(Hz: Integer);
begin FSyncInt := Hz; FController.Invoke(@SyncSpan); end;

procedure TWebAdapter.SyncSpan;
// span ≡ sample rate. Валидируем по пресетам текущего бэкенда (единый источник,
// как десктоп-оверлей) — иначе Pluto-рейты (960k/2304k/…) отбрасывались бы.
// SetSampleRate сам пересоздаёт WDSP-канал + персистит (idempotent при том же rate).
var
  Presets: TBackendRateArray;
  i: Integer;
begin
  Presets := FController.SampleRatePresets;
  for i := 0 to High(Presets) do
    if Presets[i] = FSyncInt then
    begin
      FController.SetSampleRate(FSyncInt);
      Exit;
    end;
end;

procedure TWebAdapter.OnWfAGC(On_: Boolean);
begin FSyncBool := On_; FController.Invoke(@SyncWfAGC); end;

procedure TWebAdapter.SyncWfAGC;
begin FController.SetWfAGC(FSyncBool); end;

procedure TWebAdapter.OnWfNF(On_: Boolean);
begin FSyncBool := On_; FController.Invoke(@SyncWfNF); end;

procedure TWebAdapter.SyncWfNF;
begin FController.SetWfNF(FSyncBool); end;

procedure TWebAdapter.OnMute(On_: Boolean);
begin FSyncBool := On_; FController.Invoke(@SyncMute); end;

procedure TWebAdapter.SyncMute;
begin FController.SetMute(FSyncBool); end;   // идемпотентно; OnControllerState обновит UI

procedure TWebAdapter.OnNR(Mode: Integer);
begin FSyncInt := Mode; FController.Invoke(@SyncNR); end;

procedure TWebAdapter.SyncNR;
begin FController.SetNR(FSyncInt); end;   // клампит и применяет; рендер через rfNR

procedure TWebAdapter.OnNB(Mode: Integer);
begin FSyncInt := Mode; FController.Invoke(@SyncNB); end;

procedure TWebAdapter.SyncNB;
begin FController.SetNB(FSyncInt); end;

procedure TWebAdapter.OnSNB(On_: Boolean);
begin FSyncBool := On_; FController.Invoke(@SyncSNB); end;

procedure TWebAdapter.SyncSNB;
begin FController.SetSNB(FSyncBool); end;

procedure TWebAdapter.OnANF(On_: Boolean);
begin FSyncBool := On_; FController.Invoke(@SyncANF); end;

procedure TWebAdapter.SyncANF;
begin FController.SetANF(FSyncBool); end;

procedure TWebAdapter.OnFreq(Hz: Double);
begin FSyncFreq := Hz; FController.Invoke(@SyncFreq); end;

procedure TWebAdapter.SyncFreq;
begin
  // "freq" для АКТИВНОГО VFO. SetVfoA/SetVfoB несут CTUN/band-detect/сеть +
  // канальную оркестрацию (через OnAfterTune-хук); рендер — rfVfoA/rfVfoB.
  if FController.FActiveVfo = 0 then FController.SetVfoA(FSyncFreq)
  else                              FController.SetVfoB(FSyncFreq);
end;

procedure TWebAdapter.OnFreqA(Hz: Double);
begin FSyncFreq := Hz; FController.Invoke(@SyncFreqA); end;

procedure TWebAdapter.SyncFreqA;
begin FController.SetVfoA(FSyncFreq); end;

procedure TWebAdapter.OnFreqB(Hz: Double);
begin FSyncFreq := Hz; FController.Invoke(@SyncFreqB); end;

procedure TWebAdapter.SyncFreqB;
begin
  // Унифицировано с десктопом: команда хранит частоту, при активном B
  // перестраивает приёмник (CTUN-aware) + сеть; рендер — через rfVfoB/rfBand.
  FController.SetVfoB(FSyncFreq);
end;

procedure TWebAdapter.OnCenter(Hz: Double);
begin FSyncFreq := Hz; FController.Invoke(@SyncCenter); end;

procedure TWebAdapter.SyncCenter;
begin
  // Сдвигаем DDC-центр без смены VFO (аналог CTUN-drag в десктопе).
  FController.SetCenter(FSyncFreq);
end;

procedure TWebAdapter.OnAttn(Idx: Integer);
begin FSyncInt := Idx; FController.Invoke(@SyncAttn); end;

procedure TWebAdapter.SyncAttn;
begin FController.SetAtten(FSyncInt); end;

procedure TWebAdapter.OnActiveVfo(Idx: Integer);
begin FSyncInt := Idx; FController.Invoke(@SyncActiveVfo); end;

procedure TWebAdapter.SyncActiveVfo;
begin
  if (FSyncInt < 0) or (FSyncInt > 1) then Exit;
  if FSyncInt = FController.FActiveVfo then Exit;
  FController.SetActiveVfo(FSyncInt);   // перестройка RX-VFO; рендер rfActiveVfo
end;

procedure TWebAdapter.OnCtun(On_: Boolean);
begin FSyncBool := On_; FController.Invoke(@SyncCtun); end;

procedure TWebAdapter.SyncCtun;
begin
  if FSyncBool <> FController.FCTun then
    FController.SetCTun(FSyncBool);   // band cache + center/shift + сеть; рендер rfCTun
end;

procedure TWebAdapter.OnMOX(On_: Boolean);
begin FSyncBool := On_; FController.Invoke(@SyncMOX); end;

procedure TWebAdapter.SyncMOX;
begin
  // FWebClientActive держится актуальным OnClientActiveChanged — выбор mic-source.
  FController.SetMOX(FSyncBool);   // ядро TX (safety/PTT/DSP); рендер rfTransmitting
end;

procedure TWebAdapter.OnTun(On_: Boolean);
begin FSyncBool := On_; FController.Invoke(@SyncTun); end;

procedure TWebAdapter.SyncTun;
begin
  FController.SetTune(FSyncBool);   // ядро TUN (safety/тон/drive/MOX); рендер rfTuning
end;

procedure TWebAdapter.OnFMStep(Idx: Integer);
begin FSyncInt := Idx; FController.Invoke(@SyncFMStep); end;

procedure TWebAdapter.SyncFMStep;
begin
  FController.SetFMStep(FSyncInt);   // клампит 0..3 внутри; рендер rfFMStepIdx
  FController.SaveCurrentBand;
end;

procedure TWebAdapter.OnBeacon(On_: Boolean);
begin FSyncBool := On_; FController.Invoke(@SyncBeacon); end;

procedure TWebAdapter.SyncBeacon;
begin
  // Вкл/выкл лок маяка (= запуск декодера + трим LOError). Идемпотентно.
  FController.SetBeaconLock(FSyncBool);
end;

procedure TWebAdapter.OnBeaconSeed(Frac: Double);
begin FSyncFreq := Frac; FController.Invoke(@SyncBeaconSeed); end;

procedure TWebAdapter.SyncBeaconSeed;
var Hz, VC, VS: Double;
begin
  // Frac (доля 0..1 по ширине спектра) → абс. display-Hz от ЖИВОГО видимого окна
  // контроллера (зеркалит десктоп PixelToFreq с учётом зума). Так наведение
  // точно, даже когда LO ретюнится локом и клиентский center отстаёт.
  if (FSyncFreq < 0) or (FSyncFreq > 1) then Exit;
  FController.GetViewWindow(VC, VS);
  Hz := VC + (FSyncFreq - 0.5) * VS;
  FController.BeaconSeedAtHz(Hz);
end;

procedure TWebAdapter.OnRxGainMode(Mode: Integer);
begin FSyncInt := Mode; FController.Invoke(@SyncRxGainMode); end;

procedure TWebAdapter.SyncRxGainMode;
begin
  // Pluto hw-AGC: 0=manual,1=fast,2=slow,3=hybrid (рендер через rfRxGain).
  FController.SetRxGainMode(FSyncInt);
end;

procedure TWebAdapter.OnRxGain(Db: Integer);
begin FSyncInt := Db; FController.Invoke(@SyncRxGain); end;

procedure TWebAdapter.SyncRxGain;
begin
  // Ручной hw-gain (0..73 дБ); SetRxGain переводит режим в manual.
  FController.SetRxGain(FSyncInt);
end;

procedure TWebAdapter.OnRxMute(On_: Boolean);
begin FSyncBool := On_; FController.Invoke(@SyncRxMute); end;

procedure TWebAdapter.SyncRxMute;
begin
  // QO-100 self-monitor: RX-аудио на TX (toggle, рендер через rfRxMuteOnTx).
  FController.SetRxMuteOnTx(FSyncBool);
end;

procedure TWebAdapter.OnClientActiveChanged(Active: Boolean);
// Из потока веб-сервера при connect/disconnect клиента. Простая запись флага —
// ядро (SetMOX, в т.ч. HWPTT-путь) выбирает по нему mic-source. Без маршалинга.
begin
  FController.FWebClientActive := Active;
end;

procedure TWebAdapter.OnMic(Samples: PSingle; Count: Integer);
var
  Buf: array[0..5759] of Double;
  SP:  PSingle;
  i, n: Integer;
begin
  if not FController.FWDSPReady then Exit;
  n := Count;
  if n > Length(Buf) then n := Length(Buf);
  SP := Samples;
  for i := 0 to n - 1 do
  begin
    Buf[i] := SP^;
    Inc(SP);
  end;
  FController.FDSPEngine.PushTXMicSamplesD(Buf, n);
end;

function TWebAdapter.AudioConsume(const Left, Right: array of Single;
  Count: Integer): Boolean;
var
  MonoBuf: array[0..1023] of Single;
  i, n: Integer;
begin
  Result := False;
  if not (Assigned(FServer) and FServer.WebClientActive) then Exit;
  n := Count;
  if n > Length(MonoBuf) then n := Length(MonoBuf);
  for i := 0 to n - 1 do
    MonoBuf[i] := (Left[i] + Right[i]) * 0.5;
  FServer.PushAudio(@MonoBuf[0], n);
  Result := True;
end;

// ── Исходящее зеркало состояния ──────────────────────────────────────────────

function TWebAdapter.BeaconStatusText: string;
// Зеркалит TMainForm.BeaconStatusText (поле 7 десктопа). Pluto в трансвертере.
begin
  if not FController.BeaconLockEnabled then Exit('BCN: off');
  if FController.BeaconDecodeFreqHz <= 0 then Exit('BCN: click beacon');
  case FController.BeaconState of
    bsLock:   Result := Format('BCN LOCK %.0fHz /%.0fdB',
                               [FController.BeaconCorrectionHz, FController.BeaconSNRdB]);
    bsSearch: Result := Format('BCN sync /%.0fdB', [FController.BeaconSNRdB]);
  else        Result := 'BCN --';
  end;
end;

procedure TWebAdapter.PushState;
var
  StatusText, BoardText, IPText, SupplyText: string;
  PLLText, RXText, TXText, SeqText: string;
  ViewCenter, ViewSpan: Double;
  WfNew: Boolean;
begin
  // Пришла ли от DSP новая строка водопада с прошлого пуша. Снимаем ДО пуша:
  // без этого сервер повторно отправлял бы прежнюю строку, когда кадров нет
  // (RX остановлен) или когда пуш случился чаще кадров — водопад двоил.
  WfNew := FController.FWfAccumFresh;
  // Видимое окно (центр+span) с учётом зума: пиксели спектра уже зумлены
  // анализатором, web-метки/маппинг должны соответствовать.
  FController.GetViewWindow(ViewCenter, ViewSpan);
  if FController.FNetwork.Connected then
  begin
    if FController.FRunning then StatusText := 'Running'
    else                        StatusText := 'Connected';
    BoardText := 'Board: ' + FController.BoardDisplayName;
    IPText    := 'IP: ' + FController.FNetwork.Device.IPAddress;
  end
  else
  begin
    StatusText := 'Disconnected';
    BoardText  := 'Board --';
    IPText     := 'IP --';
  end;

  if not FController.FRunning then
  begin
    SupplyText := 'Supply --';
    PLLText    := 'PLL --';
  end
  else
  begin
    if FController.FLastPLLLock then PLLText := 'PLL OK'
    else                            PLLText := 'PLL?';

    if FController.IsPluto then
    begin
      // Pluto: нет PA/supply — температуры (трансивер/SoC) + RSSI (telemetry-поле).
      // Temp X/YC: X — трансивер AD9361, Y — Zynq SoC/FPGA (xadc).
      SupplyText := '';
      if (FController.FLastPlutoTempC > TELEMETRY_NONE)
         and (FController.FLastPlutoSocTempC > TELEMETRY_NONE) then
        SupplyText := Format('Temp %.0f/%.0fC', [FController.FLastPlutoTempC, FController.FLastPlutoSocTempC])
      else if FController.FLastPlutoTempC > TELEMETRY_NONE then
        SupplyText := Format('Temp %.0fC', [FController.FLastPlutoTempC])
      else if FController.FLastPlutoSocTempC > TELEMETRY_NONE then
        SupplyText := Format('Temp %.0fC', [FController.FLastPlutoSocTempC]);
      if FController.FLastPlutoRSSIdB > TELEMETRY_NONE then
      begin
        if SupplyText <> '' then SupplyText := SupplyText + '  ';
        SupplyText := SupplyText + Format('RSSI %.0fdB', [FController.FLastPlutoRSSIdB]);
      end;
      if SupplyText = '' then SupplyText := 'Pluto --';
    end
    else if not BoardSupportsSupplyVoltage(FController.FNetwork.Device.BoardType) then
      SupplyText := 'Supply n/a'
    else if FController.FLastSupplyV >= 0 then
    begin
      if FController.FLastSupplyA >= 0 then
        SupplyText := Format('Supply %.1fV %.1fA', [FController.FLastSupplyV, FController.FLastSupplyA])
      else
        SupplyText := Format('Supply %.1fV', [FController.FLastSupplyV]);
    end
    else
      SupplyText := 'Supply --';
  end;

  if FController.FRunning then RXText := 'RX running'
  else                        RXText := 'RX idle';

  if FController.FTuning then TXText := 'TX tune'
  else if FController.FTransmitting then TXText := 'TX active'
  else                        TXText := 'TX idle';

  if FController.FSeqErrorCount > 0 then
    SeqText := Format('SEQ ERR DDC%d', [FController.FLastSeqErrorDDC])
  else if FController.FRunning then
    SeqText := 'SEQ OK'
  else
    SeqText := 'SEQ --';

  // QO-100 beacon lock — отдельный канал состояния (видим только на Pluto в XVTR).
  FServer.SetBeaconStatus(
    FController.IsPluto and (FController.FCurrentXvtr >= 0),
    FController.BeaconLockEnabled,
    BeaconStatusText,
    FController.BeaconRefHz,
    FController.BeaconTrackedFreqHz);

  // Pluto RF-AGC (виден при HasHWGain = Pluto подключён) + QO-100 RX MUTE (InQO100).
  FServer.SetRxGainStatus(
    FController.BackendCaps.HasHWGain,
    FController.FRxGainMode, FController.FRxGainDb);
  FServer.SetRxMuteStatus(
    FController.InQO100,
    FController.FRxMuteOnTx);

  // Счётчики — РЕАЛЬНЫЕ: кадр короче потолка (web.spec_pixels), когда
  // разрешение анализатора (= ширина панадаптера) меньше него — узкая панель,
  // грид из двух колонок. Константа здесь отправляла хвост буфера — нули
  // (0 dBm) поверх шкалы и сжатый по частоте спектр.
  FServer.PushSpectrum(
    FController.FSpectrumBuf, FController.FSpectrumBufCount,
    FController.FWaterfallBuf, FController.FWaterfallBufCount, WfNew,
    FController.FLastSMeter,
    FController.FVfoA, FController.FMode, FController.FFilterBW, FController.FAGCMode, FController.FAGCTop,
    ViewSpan, FController.ActiveVolume,
    FController.FWfAGCEnabled, FController.FWfNFEnabled,
    FController.FCurrentBand,
    FController.FRunning and FController.FNetwork.Connected,
    FController.FRunning, FController.FMuted, FController.FCTun,
    FController.FNRMode, FController.FNBMode, FController.FSNB, FController.FANF,
    ViewCenter, FController.FFilter,
    FController.FVfoB, FController.FActiveVfo,
    FController.FTransmitting, FController.FDrivePercent,
    FController.FAtten, FController.FTuning, FController.FDisplayDuplex,
    FController.FLastFwdW, FController.FLastSWR, FController.FPAMaxPower,
    StatusText, BoardText, IPText, SupplyText,
    PLLText, RXText, TXText, SeqText);

  // Строка забрана — следующий кадр DSP начинает новое накопление.
  if WfNew then FController.FWfAccumFresh := False;
end;

end.
