Phase 5 (D2): move spectrum/waterfall raw frames into the controller

OnSpectrumReady/OnWaterfallReady move into TRadioController. The controller now
owns the last spectrum/waterfall frame (FSpectrumBuf/FWaterfallBuf) for the web
mirror and emits raw pixels via OnSpectrumData/OnWaterfallData. MainForm
subscribes for FSpecView rendering (+ waterfall scroll decimation, a UI concern).
TWebAdapter.PushState now reads the frame from the controller (no buffer params).
Behavior-preserving; removes the last spectrum/waterfall data-path coupling.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Uladzimir Karpenka
2026-06-09 14:46:39 +03:00
co-authored by Claude Opus 4.8
parent 7c8fd298ae
commit f122079b83
6 changed files with 620 additions and 36 deletions
+16 -26
View File
@@ -153,11 +153,8 @@ type
FWidebandDirty: Boolean;
// 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)
FSpectrumBuf: array[0..1023] of Single;
FWaterfallBuf: array[0..1023] of Single;
FSpectrumBufCount: Integer;
FWaterfallBufCount:Integer;
// Снимок последнего кадра спектра/водопада для web переехал в TRadioController
// (FSpectrumBuf/FWaterfallBuf) — нужен headless-демону.
// Waterfall frame timing
FWaterfallFrameInterval: Integer;
FWaterfallFrameCounter: Integer;
@@ -759,8 +756,6 @@ begin
FController.FFMRptOffsetHz := RPT_DEFAULT_2M;
FController.FFMRptAutoActive := False;
FController.FFMCTCSSAutoActive := False;
FSpectrumBufCount := 1024;
FWaterfallBufCount := 1024;
FWaterfallFrameInterval := 2;
FWaterfallFrameCounter := 0;
FController.FDevConnected := False;
@@ -982,11 +977,14 @@ begin
// DSP Engine.Open (загрузка libwdsp, ~1-2 сек) вызывается при нажатии START.
FController.FWDSPReady := False;
FController.FDSPEngine.OnAudio := FController.OnAudioReady;
FController.FDSPEngine.OnSpectrum := OnSpectrumReady;
FController.FDSPEngine.OnWaterfall := OnWaterfallReady;
FController.FDSPEngine.OnSpectrum := FController.OnSpectrumReady;
FController.FDSPEngine.OnWaterfall := FController.OnWaterfallReady;
FController.FDSPEngine.OnTXIQ := FController.OnTXIQ;
// Web-клиент перехватывает RX-аудио (моно-микс) у контроллера, если подключён.
FController.OnAudioConsume := FWebAdapter.AudioConsume;
// Сырьё спектра/водопада из контроллера → рендер FSpecView (UI-подписчик).
FController.OnSpectrumData := OnSpectrumReady;
FController.OnWaterfallData := OnWaterfallReady;
// Sound-card mic путь: WDSP TX-thread дёргает этот колбэк перед каждым
// тиком, если выбран источник SoundCard. Для Radio колбэк не вызывается.
FController.FDSPEngine.OnPullMicSamples := FController.PullSoundCardMic;
@@ -3072,10 +3070,10 @@ begin
FSpectrumDirty := False;
end;
// Исходящее зеркало состояния + спектр/водопад в web — в адаптере (читает
// FController; буферы пикселейу нас). Сервер сам гейтит по FRunning.
// Исходящее зеркало состояния + спектр/водопад в web — в адаптере (всё из
// FController, включая последний кадр пикселей). Сервер сам гейтит по FRunning.
if Assigned(FWebAdapter) then
FWebAdapter.PushState(FSpectrumBuf, FWaterfallBuf);
FWebAdapter.PushState;
end;
// ===========================================================================
@@ -3208,8 +3206,8 @@ begin
FController.FWDSPReady := False;
FController.FDSPEngine := TWDSPEngine.Create(ASampleRate, 48000, 512);
FController.FDSPEngine.OnAudio := FController.OnAudioReady;
FController.FDSPEngine.OnSpectrum := OnSpectrumReady;
FController.FDSPEngine.OnWaterfall := OnWaterfallReady;
FController.FDSPEngine.OnSpectrum := FController.OnSpectrumReady;
FController.FDSPEngine.OnWaterfall := FController.OnWaterfallReady;
FController.FDSPEngine.OnTXIQ := FController.OnTXIQ;
// Иначе sound-card mic путь теряется после смены sample rate (колбэк
// навешивается в FormCreate, но новый движок про него не знал).
@@ -5182,26 +5180,18 @@ end;
procedure TMainForm.OnSpectrumReady(const Pixels: array of Single;
Count: Integer);
var
i, N: Integer;
// Подписчик на FController.OnSpectrumData (сырьё из DSP-потока) — только рендер;
// web-снимок держит контроллер (FSpectrumBuf).
begin
N := Min(Count, 1024);
for i := 0 to N - 1 do
FSpectrumBuf[i] := Pixels[i]; // local copy for WebServer
FSpectrumBufCount := N;
FSpecView.SetSpectrumData(Pixels, Count);
FSpectrumDirty := True;
end;
procedure TMainForm.OnWaterfallReady(const Pixels: array of Single;
Count: Integer);
var
i, N: Integer;
// Подписчик на FController.OnWaterfallData — рендер с децимацией скорости
// прокрутки (FWaterfallFrameInterval). Web получает каждый кадр (в контроллере).
begin
N := Min(Count, 1024);
for i := 0 to N - 1 do
FWaterfallBuf[i] := Pixels[i]; // local copy for WebServer
FWaterfallBufCount := N;
Inc(FWaterfallFrameCounter);
if FWaterfallFrameCounter >= Max(1, FWaterfallFrameInterval) then
begin