Phase 5 (batch 26): move OnTXIQ + DUC queue into the controller

The TX IQ handler (batch WDSP TXA output into 240-sample DUC packets,
24-bit clamp, FNetwork.SendDUCIQ) moves into TRadioController.OnTXIQ,
along with the FDUCPendingI/Q/Count queue fields. The DUC-queue reset on
a TX-state change moves from the rfTransmitting render into SetMOX itself
(the render does not run headless). Both DSP-callback wiring sites
(FormCreate and RecreateDSPEngine) now point at FController.OnTXIQ.
Backend-agnostic: a Pluto backend converts 24-bit->int16 in SendDUCIQ.

Pre-existing gap noted: RecreateDSPEngine never re-wires OnPullMicSamples,
so sound-card mic is lost after a sample-rate change (unchanged here).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Uladzimir Karpenka
2026-06-05 23:42:22 +03:00
co-authored by Claude Opus 4.8
parent c1da506f73
commit 3a387e06ce
2 changed files with 40 additions and 45 deletions
+36
View File
@@ -215,6 +215,10 @@ type
FLastSeqErrorDelta: Int64;
FSeqOkStreak: Integer;
FADCOverloadActive: Boolean; // ADC overload (рендерится по rfADCOverload)
// ---- TX DUC-очередь (накопление IQ от WDSP в пакеты по 240, OnTXIQ) ----
FDUCPendingI: array[0..239] of Integer;
FDUCPendingQ: array[0..239] of Integer;
FDUCPendingCount: Integer;
// Pending-значения декода HP-статуса (сетевой поток → ApplyHPStatus через
// Invoke; Synchronize блокирующий и последовательный, гонок нет).
FHPpFwdW, FHPpSWR, FHPpSupplyV, FHPpSupplyA: Double;
@@ -239,6 +243,7 @@ type
procedure OnDDCIQ(DDCIndex: Integer; const Data: TDDCIQPacket);
procedure OnHPStatus(const Status: THighPriorityStatus);
procedure ApplyHPStatus; // маршалится из OnHPStatus (сглаживание/overload/HWPTT)
procedure OnTXIQ(const Buf: array of Double; Count: Integer);
// Маршалинг команды в поток контроллера (GUI: TThread.Synchronize).
procedure Invoke(M: TThreadMethod);
@@ -508,6 +513,36 @@ begin
FLastDDCIndex := DDCIndex;
end;
procedure TRadioController.OnTXIQ(const Buf: array of Double; Count: Integer);
// WDSP TXA отдаёт IQ-пары @ TX rate. Копим хвост между вызовами и шлём только
// полные DUC-пакеты по 240 (зануление хвоста давало 47 Гц гул). Клампим в
// 24-bit, чтобы пики компрессора/EQ (чуть >1.0) не оборачивались через wrap.
// Backend-агностично: Pluto-backend в SendDUCIQ переведёт 24-bit→int16.
function ScaleIQ24(V: Double): Integer;
var S: Double;
begin
S := V * 8388607.0;
if S > 8388607.0 then Result := 8388607
else if S < -8388608.0 then Result := -8388608
else Result := Round(S);
end;
var
i: Integer;
begin
if not (Assigned(FNetwork) and FNetwork.Connected) then Exit;
for i := 0 to Count - 1 do
begin
FDUCPendingI[FDUCPendingCount] := ScaleIQ24(Buf[i * 2]);
FDUCPendingQ[FDUCPendingCount] := ScaleIQ24(Buf[i * 2 + 1]);
Inc(FDUCPendingCount);
if FDUCPendingCount >= 240 then
begin
FNetwork.SendDUCIQ(FDUCPendingI, FDUCPendingQ);
FDUCPendingCount := 0;
end;
end;
end;
procedure TRadioController.OnHPStatus(const Status: THighPriorityStatus);
// Сетевой поток: декод HP-статуса (board-specific supply/power/SWR) → pending →
// маршалинг в ApplyHPStatus (сглаживание + overload + HWPTT уже на потоке UI/ядра).
@@ -1597,6 +1632,7 @@ begin
WasTransmitting := FTransmitting;
FTransmitting := On_;
if On_ then FDriveLevel := CalcDriveByte;
FDUCPendingCount := 0; // сброс TX DUC-очереди на смене TX-состояния
// Сетевая PTT-последовательность TX-on: сначала реле (Alex bit27=1, drive) без
// PTT, затем PTT=1 — переключаем реле раньше передачи.
if FTransmitting and Assigned(FNetwork) and FNetwork.Connected and FNetwork.Running then