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
+49
View File
@@ -85,6 +85,9 @@ type
TAudioConsumeEvent = function(const Left, Right: array of Single;
Count: Integer): Boolean of object;
// Сырьё спектра/водопада наружу (фронтенды рисуют). Вызывается из DSP-потока.
TPixelDataEvent = procedure(const Pixels: array of Single; Count: Integer) of object;
// Снимок состояния для синхронизации нового фронтенда (Arduino/Web на connect).
TRadioSnapshot = record
VfoA, VfoB, CenterFreq, SpanHz: Double;
@@ -117,6 +120,8 @@ type
FOnStateChanged: TRadioStateEvent;
FOnAfterTune: TThreadMethod; // пост-тюн хук: канал-оркестрация после SetVfoA
FOnAudioConsume: TAudioConsumeEvent; // web-перехват RX-аудио (если активен клиент)
FOnSpectrumData: TPixelDataEvent; // сырьё спектра наружу (UI рисует FSpecView)
FOnWaterfallData: TPixelDataEvent; // сырьё водопада наружу (UI: децимация+рендер)
procedure Changed(Field: TRadioField);
procedure ApplyAGCToEngine; // применяет FAGCMode к WDSP (единый маппинг)
procedure ApplyModeDefaults; // дефолтный фильтр/девиация под текущий FMode
@@ -130,6 +135,15 @@ type
// OnStateChanged. Прямая запись извне — временная мера на время Фазы 1.
// ============================================================
// ---- Последний кадр спектра/водопада (пиксели от WDSP) ----
// Снимок для исходящего web-зеркала (PushState читает напрямую). Пишется из
// DSP-потока в OnSpectrumReady/OnWaterfallReady; читается из GUI-таймера —
// та же толерантная гонка, что и раньше (косметика пикселей).
FSpectrumBuf: array[0..1023] of Single;
FWaterfallBuf: array[0..1023] of Single;
FSpectrumBufCount: Integer;
FWaterfallBufCount: Integer;
// ---- Движки (владение: см. шапку юнита) ----
FNetwork: THPSDRNetwork;
FDSPEngine: TWDSPEngine;
@@ -273,6 +287,8 @@ type
// Чистая обработка данных без UI; навешиваются на FNetwork/FDSPEngine
// вызывающим (GUI или демон) после CreateEngines.
procedure OnAudioReady(const Left, Right: array of Single; Count: Integer);
procedure OnSpectrumReady(const Pixels: array of Single; Count: Integer);
procedure OnWaterfallReady(const Pixels: array of Single; Count: Integer);
procedure OnMicPacket(const Data: TMicDataPacket);
function PullSoundCardMic(MaxN: Integer): Integer;
procedure OnDDCIQ(DDCIndex: Integer; const Data: TDDCIQPacket);
@@ -438,6 +454,8 @@ type
// чтобы любой путь тюнинга (desktop/web/CAT/энкодер) был канал-aware headless.
property OnAfterTune: TThreadMethod read FOnAfterTune write FOnAfterTune;
property OnAudioConsume: TAudioConsumeEvent read FOnAudioConsume write FOnAudioConsume;
property OnSpectrumData: TPixelDataEvent read FOnSpectrumData write FOnSpectrumData;
property OnWaterfallData: TPixelDataEvent read FOnWaterfallData write FOnWaterfallData;
end;
implementation
@@ -454,6 +472,7 @@ begin
FVolume := 70; FDrivePercent := 50; FDriveLevel := 0; FAtten := 0;
FCenterFreq := FVfoA; FSpanHz := 192000; FSampleRate := 192000;
FCurrentBand := 5; FCurrentXvtr := -1;
FSpectrumBufCount := 1024; FWaterfallBufCount := 1024;
FPAMaxPower := 100.0;
FWfAGCEnabled := True; FDitherEnabled := True; FRandomEnabled := True;
FFMStepOn := True;
@@ -754,6 +773,36 @@ begin
FAudioOut.Write(Left, Right, Count);
end;
procedure TRadioController.OnSpectrumReady(const Pixels: array of Single;
Count: Integer);
// DSP-поток: сохраняем последний кадр для web-зеркала и отдаём сырьё наружу
// (UI рисует FSpecView). Децимации у спектра нет.
var
i, N: Integer;
begin
N := Count;
if N > Length(FSpectrumBuf) then N := Length(FSpectrumBuf);
for i := 0 to N - 1 do
FSpectrumBuf[i] := Pixels[i];
FSpectrumBufCount := N;
if Assigned(FOnSpectrumData) then FOnSpectrumData(Pixels, Count);
end;
procedure TRadioController.OnWaterfallReady(const Pixels: array of Single;
Count: Integer);
// DSP-поток: web-зеркало получает КАЖДЫЙ кадр; децимация рендера водопада —
// забота фронтенда (OnWaterfallData).
var
i, N: Integer;
begin
N := Count;
if N > Length(FWaterfallBuf) then N := Length(FWaterfallBuf);
for i := 0 to N - 1 do
FWaterfallBuf[i] := Pixels[i];
FWaterfallBufCount := N;
if Assigned(FOnWaterfallData) then FOnWaterfallData(Pixels, Count);
end;
procedure TRadioController.OnMicPacket(const Data: TMicDataPacket);
// HW mic-стрим уходит в WDSP только если выбран источник Radio; иначе сэмплы
// игнорируются (mic берётся со звуковой карты).