diff --git a/MainForm.pas b/MainForm.pas index 6deb519..8db3f15 100644 --- a/MainForm.pas +++ b/MainForm.pas @@ -139,12 +139,7 @@ type FSpecDragX0: Integer; // X при нажатии FSpecDragFreq: Double; // FCenterFreq при нажатии FSpectrumDirty: Boolean; // таймер должен перерисовать спектр/водопад - // DUC IQ packetizer: TXA отдаёт блок FTXOutBufSize пар, который - // не обязан быть кратен 240 (DUC packet). Хвост переносим в следующий - // вызов OnTXIQReady, иначе зануление давало 47 Гц гул. - FDUCPendingI: array[0..239] of Integer; - FDUCPendingQ: array[0..239] of Integer; - FDUCPendingCount: Integer; + // TX DUC-очередь (FDUCPending*) переехала в TRadioController (OnTXIQ). FSMeterPeak: Double; // верхняя граница светлой зоны FSMeterMin: Double; // нижняя граница светлой зоны FSMeterAvg: Double; // сглаженное среднее (EMA) @@ -577,7 +572,6 @@ type function ActiveTXFreqHz: Double; function DefaultMicSource: TTXMicSource; procedure ApplyMOX(Active: Boolean); - procedure OnTXIQReady(const Buf: array of Double; Count: Integer); procedure OnPASettingsChange(MaxPower: Double; const BandCal: array of Double); procedure OnVHFCalSettingsChange(const VHFCal: array of Double); procedure OnTXSettingsChange(const T: TTXSettings); @@ -914,7 +908,6 @@ begin FController.FRunning := False; FController.FTransmitting := False; FController.FSplitTxB := False; - FDUCPendingCount := 0; FController.FDisplayDuplex := False; FController.FTuning := False; FController.FMuted := False; @@ -1157,7 +1150,7 @@ begin FController.FDSPEngine.OnAudio := OnAudioReady; FController.FDSPEngine.OnSpectrum := OnSpectrumReady; FController.FDSPEngine.OnWaterfall := OnWaterfallReady; - FController.FDSPEngine.OnTXIQ := OnTXIQReady; + FController.FDSPEngine.OnTXIQ := FController.OnTXIQ; // Sound-card mic путь: WDSP TX-thread дёргает этот колбэк перед каждым // тиком, если выбран источник SoundCard. Для Radio колбэк не вызывается. FController.FDSPEngine.OnPullMicSamples := FController.PullSoundCardMic; @@ -1310,8 +1303,7 @@ begin FSpecView.TXMode := FController.FTransmitting and not FController.FDisplayDuplex; FSpecView.TXOverlay := FController.FTransmitting; ApplySpecViewGridFromState; // сетка спектра под активный тракт - FDUCPendingCount := 0; - end; + end; // (DUC-очередь сбрасывает SetMOX) rfTuning: StyleButton(BtnTUN, FController.FTuning); rfADCOverload: @@ -3542,7 +3534,7 @@ begin FController.FDSPEngine.OnAudio := OnAudioReady; FController.FDSPEngine.OnSpectrum := OnSpectrumReady; FController.FDSPEngine.OnWaterfall := OnWaterfallReady; - FController.FDSPEngine.OnTXIQ := OnTXIQReady; + FController.FDSPEngine.OnTXIQ := FController.OnTXIQ; end; function TMainForm.EnsureWDSPWisdom: Boolean; @@ -5758,39 +5750,6 @@ begin Result := FController.CalcDriveByte; end; -procedure TMainForm.OnTXIQReady(const Buf: array of Double; Count: Integer); -// FTXOutBufSize не обязан быть кратен 240, поэтому копим хвост между -// вызовами и шлём только полные DUC-пакеты. Иначе зануление хвоста -// последнего пакета давало периодический провал на каждом TX-блоке -// (≈47 Гц гул при 192k IQ rate). Также клампим в 24-bit чтобы пики -// от компрессора/EQ (значение чуть >1.0) не оборачивались через wrap. - 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 FController.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 - FController.FNetwork.SendDUCIQ(FDUCPendingI, FDUCPendingQ); - FDUCPendingCount := 0; - end; - end; -end; - procedure TMainForm.OnPASettingsChange(MaxPower: Double; const BandCal: array of Double); var i: Integer; diff --git a/RadioController.pas b/RadioController.pas index 75ab676..a17713b 100644 --- a/RadioController.pas +++ b/RadioController.pas @@ -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