From 3d2d76452c3148f57da3cd4d0e3b978f8dc462bc Mon Sep 17 00:00:00 2001 From: Uladzimir Karpenka Date: Fri, 5 Jun 2026 17:33:11 +0300 Subject: [PATCH] Phase 5 (batch 23): move TX mic callbacks into the controller OnMicPacket (HW mic packet -> WDSP, only when TX mic source is Radio) and PullSoundCardMic (sound-card input -> WDSP, when source is SoundCard) are pure data-path handlers with no UI/MainForm state, so they move verbatim into TRadioController. FormCreate now wires FNetwork.OnMicPacket and FDSPEngine.OnPullMicSamples to the controller's own methods. These are backend-agnostic consumers (they operate on the common interchange format), so they belong in the core regardless of HPSDR vs future Pluto backends. Behaviour unchanged. Co-Authored-By: Claude Opus 4.8 --- MainForm.pas | 37 ++----------------------------------- RadioController.pas | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 35 deletions(-) diff --git a/MainForm.pas b/MainForm.pas index 3e1ed67..02b3d45 100644 --- a/MainForm.pas +++ b/MainForm.pas @@ -375,7 +375,6 @@ type Count: Integer); procedure OnWDSPOpenDone(Success: Boolean); procedure DoConnectDevice(const Dev: THPSDRDevice); - procedure OnMicPacketCB(const Data: TMicDataPacket); // DSP/Audio callbacks (вызываются из рабочих потоков) procedure OnAudioReady(const Left, Right: array of Single; Count: Integer); @@ -640,7 +639,6 @@ type // (mic-биты Boost/Bias/LineIn/PTT попадают в byte 50 DUCSpecific). procedure ApplyTXSettingsToDSP; procedure SendDUCSpecificFromSettings; - function PullSoundCardMic(MaxN: Integer): Integer; function BuildMicLineSelectByte: Byte; procedure ApplyVisibility(ShowSpectrum, ShowWaterfall, ShowWideband, WidebandFill, SpectrumFill: Boolean); @@ -1158,7 +1156,7 @@ begin FController.FNetwork.OnDeviceFound := OnDeviceFound; FController.FNetwork.OnHPStatus := OnHPStatusCB; FController.FNetwork.OnDDCIQ := OnDDCIQCB; - FController.FNetwork.OnMicPacket := OnMicPacketCB; + FController.FNetwork.OnMicPacket := FController.OnMicPacket; FController.FNetwork.OnWideband := OnWidebandCB; // Дефолты TX, Alex и XVTR (на случай если устройство ещё не выбрано). TSettingsManager.DefaultTX(FController.FTXSettings); @@ -1180,7 +1178,7 @@ begin FController.FDSPEngine.OnTXIQ := OnTXIQReady; // Sound-card mic путь: WDSP TX-thread дёргает этот колбэк перед каждым // тиком, если выбран источник SoundCard. Для Radio колбэк не вызывается. - FController.FDSPEngine.OnPullMicSamples := PullSoundCardMic; + FController.FDSPEngine.OnPullMicSamples := FController.PullSoundCardMic; FMeterTimer := TTimer.Create(Self); FMeterTimer.Interval := 100; @@ -3565,15 +3563,6 @@ begin SetStatusText(4, 'RX running'); end; -procedure TMainForm.OnMicPacketCB(const Data: TMicDataPacket); -begin - // HW mic-стрим уходит в WDSP только если выбран источник Radio. - // Иначе сэмплы игнорируются (mic берётся со звуковой карты). - if FController.FTransmitting and FController.FWDSPReady and - (FController.FDSPEngine.TXMicSource = txmsRadio) then - FController.FDSPEngine.PushTXMicSamples16(Data.Samples, 64); -end; - // --------------------------------------------------------------------------- // TX settings — применение к WDSP, сборка DUC Specific, sound-card pull // --------------------------------------------------------------------------- @@ -3670,28 +3659,6 @@ begin FController.FTXSettings.TXWfAvgTimeMS); end; -function TMainForm.PullSoundCardMic(MaxN: Integer): Integer; -// Колбэк, который TTXDSPThread зовёт при TXMicSource=txmsSoundCard. -// Тянем только реально доступные сэмплы из FController.FAudioIn — иначе мы пушили бы -// нули и mic-цепь в WDSP получала бы тишину вместо реального сигнала. -// Читаем пачкой в локальный буфер и пушим одним вызовом — экономим -// будилки семафора и предотвращаем подмену TX-блока тишиной. -var - i, n: Integer; - Buf: array[0..2047] of Double; -begin - Result := 0; - if (FController.FAudioIn = nil) or (not FController.FAudioIn.IsOpen) then Exit; - n := FController.FAudioIn.Available; - if n > MaxN then n := MaxN; - if n > Length(Buf) then n := Length(Buf); - for i := 0 to n - 1 do - Buf[i] := FController.FAudioIn.ReadSample; - if n > 0 then - FController.FDSPEngine.PushTXMicSamplesD(Buf, n); - Result := n; -end; - // =========================================================================== // Button handlers // =========================================================================== diff --git a/RadioController.pas b/RadioController.pas index f86385c..ef455a8 100644 --- a/RadioController.pas +++ b/RadioController.pas @@ -210,6 +210,12 @@ type procedure CreateEngines(ASampleRate: Integer); procedure FreeEngines; + // ---- Колбэки движков (вызываются из рабочих потоков сети/DSP) ---- + // Чистая обработка данных без UI; навешиваются на FNetwork/FDSPEngine + // вызывающим (GUI или демон) после CreateEngines. + procedure OnMicPacket(const Data: TMicDataPacket); + function PullSoundCardMic(MaxN: Integer): Integer; + // Маршалинг команды в поток контроллера (GUI: TThread.Synchronize). procedure Invoke(M: TThreadMethod); @@ -418,6 +424,35 @@ begin end; end; +procedure TRadioController.OnMicPacket(const Data: TMicDataPacket); +// HW mic-стрим уходит в WDSP только если выбран источник Radio; иначе сэмплы +// игнорируются (mic берётся со звуковой карты). +begin + if FTransmitting and FWDSPReady and + (FDSPEngine.TXMicSource = txmsRadio) then + FDSPEngine.PushTXMicSamples16(Data.Samples, 64); +end; + +function TRadioController.PullSoundCardMic(MaxN: Integer): Integer; +// Колбэк TTXDSPThread при TXMicSource=txmsSoundCard. Тянем только реально +// доступные сэмплы из FAudioIn (иначе пушили бы тишину в mic-цепь WDSP), +// пачкой в локальный буфер — экономим будилки семафора. +var + i, n: Integer; + Buf: array[0..2047] of Double; +begin + Result := 0; + if (FAudioIn = nil) or (not FAudioIn.IsOpen) then Exit; + n := FAudioIn.Available; + if n > MaxN then n := MaxN; + if n > Length(Buf) then n := Length(Buf); + for i := 0 to n - 1 do + Buf[i] := FAudioIn.ReadSample; + if n > 0 then + FDSPEngine.PushTXMicSamplesD(Buf, n); + Result := n; +end; + procedure TRadioController.Invoke(M: TThreadMethod); begin if Assigned(FOnInvoke) then FOnInvoke(M)