mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 20:37:33 +00:00
Phase 5 (D1): move RX audio routing into the controller
OnAudioReady moves from MainForm into TRadioController (radio-speaker push + local AudioOut). The web steal becomes a route hook: controller fires OnAudioConsume; TWebAdapter.AudioConsume mixes to mono and pushes to the web client, returning True to skip local AudioOut. Behavior-preserving; removes the last audio-path coupling to MainForm. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
682025e99b
commit
7c8fd298ae
+4
-22
@@ -336,7 +336,6 @@ type
|
|||||||
procedure DoStopAndDisconnect;
|
procedure DoStopAndDisconnect;
|
||||||
|
|
||||||
// DSP/Audio callbacks (вызываются из рабочих потоков)
|
// DSP/Audio callbacks (вызываются из рабочих потоков)
|
||||||
procedure OnAudioReady(const Left, Right: array of Single; Count: Integer);
|
|
||||||
procedure OnSpectrumReady(const Pixels: array of Single; Count: Integer);
|
procedure OnSpectrumReady(const Pixels: array of Single; Count: Integer);
|
||||||
procedure OnWaterfallReady(const Pixels: array of Single; Count: Integer);
|
procedure OnWaterfallReady(const Pixels: array of Single; Count: Integer);
|
||||||
|
|
||||||
@@ -982,10 +981,12 @@ begin
|
|||||||
|
|
||||||
// DSP Engine.Open (загрузка libwdsp, ~1-2 сек) вызывается при нажатии START.
|
// DSP Engine.Open (загрузка libwdsp, ~1-2 сек) вызывается при нажатии START.
|
||||||
FController.FWDSPReady := False;
|
FController.FWDSPReady := False;
|
||||||
FController.FDSPEngine.OnAudio := OnAudioReady;
|
FController.FDSPEngine.OnAudio := FController.OnAudioReady;
|
||||||
FController.FDSPEngine.OnSpectrum := OnSpectrumReady;
|
FController.FDSPEngine.OnSpectrum := OnSpectrumReady;
|
||||||
FController.FDSPEngine.OnWaterfall := OnWaterfallReady;
|
FController.FDSPEngine.OnWaterfall := OnWaterfallReady;
|
||||||
FController.FDSPEngine.OnTXIQ := FController.OnTXIQ;
|
FController.FDSPEngine.OnTXIQ := FController.OnTXIQ;
|
||||||
|
// Web-клиент перехватывает RX-аудио (моно-микс) у контроллера, если подключён.
|
||||||
|
FController.OnAudioConsume := FWebAdapter.AudioConsume;
|
||||||
// Sound-card mic путь: WDSP TX-thread дёргает этот колбэк перед каждым
|
// Sound-card mic путь: WDSP TX-thread дёргает этот колбэк перед каждым
|
||||||
// тиком, если выбран источник SoundCard. Для Radio колбэк не вызывается.
|
// тиком, если выбран источник SoundCard. Для Radio колбэк не вызывается.
|
||||||
FController.FDSPEngine.OnPullMicSamples := FController.PullSoundCardMic;
|
FController.FDSPEngine.OnPullMicSamples := FController.PullSoundCardMic;
|
||||||
@@ -3206,7 +3207,7 @@ begin
|
|||||||
end;
|
end;
|
||||||
FController.FWDSPReady := False;
|
FController.FWDSPReady := False;
|
||||||
FController.FDSPEngine := TWDSPEngine.Create(ASampleRate, 48000, 512);
|
FController.FDSPEngine := TWDSPEngine.Create(ASampleRate, 48000, 512);
|
||||||
FController.FDSPEngine.OnAudio := OnAudioReady;
|
FController.FDSPEngine.OnAudio := FController.OnAudioReady;
|
||||||
FController.FDSPEngine.OnSpectrum := OnSpectrumReady;
|
FController.FDSPEngine.OnSpectrum := OnSpectrumReady;
|
||||||
FController.FDSPEngine.OnWaterfall := OnWaterfallReady;
|
FController.FDSPEngine.OnWaterfall := OnWaterfallReady;
|
||||||
FController.FDSPEngine.OnTXIQ := FController.OnTXIQ;
|
FController.FDSPEngine.OnTXIQ := FController.OnTXIQ;
|
||||||
@@ -5179,25 +5180,6 @@ end;
|
|||||||
// DSP/Audio callbacks — вызываются из рабочих потоков
|
// DSP/Audio callbacks — вызываются из рабочих потоков
|
||||||
// ===========================================================================
|
// ===========================================================================
|
||||||
|
|
||||||
procedure TMainForm.OnAudioReady(const Left, Right: array of Single;
|
|
||||||
Count: Integer);
|
|
||||||
var
|
|
||||||
MonoBuf: array[0..1023] of Single;
|
|
||||||
i: Integer;
|
|
||||||
begin
|
|
||||||
// Дублируем RX-аудио во встроенный динамик трансивера (DDC Audio, port 1028).
|
|
||||||
if FController.FSendAudioToRadio then
|
|
||||||
FController.FNetwork.SendSpeakerAudio(Left, Right, Count);
|
|
||||||
if Assigned(FWebServer) and FWebServer.WebClientActive then
|
|
||||||
begin
|
|
||||||
for i := 0 to Count - 1 do
|
|
||||||
MonoBuf[i] := (Left[i] + Right[i]) * 0.5;
|
|
||||||
FWebServer.PushAudio(@MonoBuf[0], Count);
|
|
||||||
Exit;
|
|
||||||
end;
|
|
||||||
FController.FAudioOut.Write(Left, Right, Count);
|
|
||||||
end;
|
|
||||||
|
|
||||||
procedure TMainForm.OnSpectrumReady(const Pixels: array of Single;
|
procedure TMainForm.OnSpectrumReady(const Pixels: array of Single;
|
||||||
Count: Integer);
|
Count: Integer);
|
||||||
var
|
var
|
||||||
|
|||||||
@@ -79,6 +79,12 @@ type
|
|||||||
// Маршалинг произвольного метода в поток контроллера (GUI: TThread.Synchronize).
|
// Маршалинг произвольного метода в поток контроллера (GUI: TThread.Synchronize).
|
||||||
TUIInvokeEvent = procedure(M: TThreadMethod) of object;
|
TUIInvokeEvent = procedure(M: TThreadMethod) of object;
|
||||||
|
|
||||||
|
// Перехват RX-аудио потребителем (web-адаптер): вернуть True, если аудио
|
||||||
|
// отправлено клиенту и НЕ должно идти в локальный AudioOut. Вызывается из
|
||||||
|
// DSP-потока (как сам OnAudioReady) — реализация должна быть thread-safe.
|
||||||
|
TAudioConsumeEvent = function(const Left, Right: array of Single;
|
||||||
|
Count: Integer): Boolean of object;
|
||||||
|
|
||||||
// Снимок состояния для синхронизации нового фронтенда (Arduino/Web на connect).
|
// Снимок состояния для синхронизации нового фронтенда (Arduino/Web на connect).
|
||||||
TRadioSnapshot = record
|
TRadioSnapshot = record
|
||||||
VfoA, VfoB, CenterFreq, SpanHz: Double;
|
VfoA, VfoB, CenterFreq, SpanHz: Double;
|
||||||
@@ -110,6 +116,7 @@ type
|
|||||||
FOnInvoke: TUIInvokeEvent;
|
FOnInvoke: TUIInvokeEvent;
|
||||||
FOnStateChanged: TRadioStateEvent;
|
FOnStateChanged: TRadioStateEvent;
|
||||||
FOnAfterTune: TThreadMethod; // пост-тюн хук: канал-оркестрация после SetVfoA
|
FOnAfterTune: TThreadMethod; // пост-тюн хук: канал-оркестрация после SetVfoA
|
||||||
|
FOnAudioConsume: TAudioConsumeEvent; // web-перехват RX-аудио (если активен клиент)
|
||||||
procedure Changed(Field: TRadioField);
|
procedure Changed(Field: TRadioField);
|
||||||
procedure ApplyAGCToEngine; // применяет FAGCMode к WDSP (единый маппинг)
|
procedure ApplyAGCToEngine; // применяет FAGCMode к WDSP (единый маппинг)
|
||||||
procedure ApplyModeDefaults; // дефолтный фильтр/девиация под текущий FMode
|
procedure ApplyModeDefaults; // дефолтный фильтр/девиация под текущий FMode
|
||||||
@@ -265,6 +272,7 @@ type
|
|||||||
// ---- Колбэки движков (вызываются из рабочих потоков сети/DSP) ----
|
// ---- Колбэки движков (вызываются из рабочих потоков сети/DSP) ----
|
||||||
// Чистая обработка данных без UI; навешиваются на FNetwork/FDSPEngine
|
// Чистая обработка данных без UI; навешиваются на FNetwork/FDSPEngine
|
||||||
// вызывающим (GUI или демон) после CreateEngines.
|
// вызывающим (GUI или демон) после CreateEngines.
|
||||||
|
procedure OnAudioReady(const Left, Right: array of Single; Count: Integer);
|
||||||
procedure OnMicPacket(const Data: TMicDataPacket);
|
procedure OnMicPacket(const Data: TMicDataPacket);
|
||||||
function PullSoundCardMic(MaxN: Integer): Integer;
|
function PullSoundCardMic(MaxN: Integer): Integer;
|
||||||
procedure OnDDCIQ(DDCIndex: Integer; const Data: TDDCIQPacket);
|
procedure OnDDCIQ(DDCIndex: Integer; const Data: TDDCIQPacket);
|
||||||
@@ -429,6 +437,7 @@ type
|
|||||||
// рендера) — сюда вешается канальная оркестрация (ChannelController.OnVfoTuned),
|
// рендера) — сюда вешается канальная оркестрация (ChannelController.OnVfoTuned),
|
||||||
// чтобы любой путь тюнинга (desktop/web/CAT/энкодер) был канал-aware headless.
|
// чтобы любой путь тюнинга (desktop/web/CAT/энкодер) был канал-aware headless.
|
||||||
property OnAfterTune: TThreadMethod read FOnAfterTune write FOnAfterTune;
|
property OnAfterTune: TThreadMethod read FOnAfterTune write FOnAfterTune;
|
||||||
|
property OnAudioConsume: TAudioConsumeEvent read FOnAudioConsume write FOnAudioConsume;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
implementation
|
implementation
|
||||||
@@ -732,6 +741,19 @@ begin
|
|||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
procedure TRadioController.OnAudioReady(const Left, Right: array of Single;
|
||||||
|
Count: Integer);
|
||||||
|
// DSP-поток: маршрут RX-аудио. Встроенный динамик трансивера (DDC Audio,
|
||||||
|
// port 1028) — независимый канал. Затем web-адаптер может перехватить аудио
|
||||||
|
// (активный клиент); если перехвачено — в локальный AudioOut не пишем.
|
||||||
|
begin
|
||||||
|
if FSendAudioToRadio then
|
||||||
|
FNetwork.SendSpeakerAudio(Left, Right, Count);
|
||||||
|
if Assigned(FOnAudioConsume) and FOnAudioConsume(Left, Right, Count) then
|
||||||
|
Exit;
|
||||||
|
FAudioOut.Write(Left, Right, Count);
|
||||||
|
end;
|
||||||
|
|
||||||
procedure TRadioController.OnMicPacket(const Data: TMicDataPacket);
|
procedure TRadioController.OnMicPacket(const Data: TMicDataPacket);
|
||||||
// HW mic-стрим уходит в WDSP только если выбран источник Radio; иначе сэмплы
|
// HW mic-стрим уходит в WDSP только если выбран источник Radio; иначе сэмплы
|
||||||
// игнорируются (mic берётся со звуковой карты).
|
// игнорируются (mic берётся со звуковой карты).
|
||||||
|
|||||||
@@ -102,6 +102,12 @@ type
|
|||||||
// подача в движок thread-safe (как OnMicPacket/OnDDCIQ контроллера).
|
// подача в движок thread-safe (как OnMicPacket/OnDDCIQ контроллера).
|
||||||
procedure OnMic(Samples: PSingle; Count: Integer);
|
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.
|
// Исходящее зеркало: периодический снимок состояния + спектр/водопад в web.
|
||||||
// Зовётся хозяином из GUI-таймера (буферы пикселей — у хозяина, остальное из
|
// Зовётся хозяином из GUI-таймера (буферы пикселей — у хозяина, остальное из
|
||||||
// FController). Сервер сам гейтит по FRunning и шлёт активным клиентам.
|
// FController). Сервер сам гейтит по FRunning и шлёт активным клиентам.
|
||||||
@@ -360,6 +366,22 @@ begin
|
|||||||
FController.FDSPEngine.PushTXMicSamplesD(Buf, n);
|
FController.FDSPEngine.PushTXMicSamplesD(Buf, n);
|
||||||
end;
|
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;
|
||||||
|
|
||||||
// ── Исходящее зеркало состояния ──────────────────────────────────────────────
|
// ── Исходящее зеркало состояния ──────────────────────────────────────────────
|
||||||
|
|
||||||
procedure TWebAdapter.PushState(const ASpec, AWf: array of Single);
|
procedure TWebAdapter.PushState(const ASpec, AWf: array of Single);
|
||||||
|
|||||||
Reference in New Issue
Block a user