fix(tx): чистый openHPSDR TX-тракт — задержка DUP, всплески TUN, фриз PTT-off

Диагностика временным TXLog (снят) вскрыла цепочку проблем TX-тракта:

- Задержка ~200мс своего сигнала в DUP: за время приёма ринг PortAudio-
  микрофона наполнялся до упора (9600 сэмплов) и на старте TX бёрстом
  оседал стоячим запасом в DUC-очереди. Фикс: TAudioInput.Flush на RX→TX
  (заодно убрана передача в эфир аудио, записанного до нажатия PTT).

- Всплески на TUN: (1) старт — радио начинало с пустого DUC-FIFO; (2) по
  ходу — пейсинг со строгой каденцией держал запас FIFO у нуля, джиттер
  3-6мс давал underrun; (3) клип: голос из бэклога складывался с PostGen-
  тоном (стоит после ALC) поверх шкалы, а сам тон при mag=1.0 давал
  выброс Гиббса x1.18 на старте. Фиксы: pre-roll PrimeDUCIQ (THROTTLE +
  3 блока подушки в очереди — sender метрит непрерывно, FIFO радио прижат
  2000..2400), анти-старв доливка нулями до REANCHOR при просадке,
  TUN_TONE_MAG=0.83, мьют mic-входа TXA при активном тоне.

- Огрызок TUN-тона в эфир на первом блоке следующего TX: стоп канала
  dmode=1 ждал фейд, который некому прокрутить (продюсер уже остановлен) —
  канал стопился по таймауту Sleep(1)x100 с недобранным сигналом в
  буферах. Это же было и «остаточным фризом ~106мс» GUI на отпускании
  PTT: чистый таймаут, фейд никогда не исполнялся. Фикс: dmode=0 +
  синхронный слив/продувка канала 4 нулевыми fexchange0 на стопе и
  старте (<1мс, штатные down-slew и flush).

- Дисплей: ResetPixelBuffers(TX_DISP_ID) на старте TX — усредняющая
  история TX-анализатора показывала спектр тона прошлой сессии.

- StopRunning при активном TUN не чистил DUC-очередь и не сбрасывал
  FTuning/тон — закрыто.

Латентность тракта mic→радио теперь ~43мс (WDSP 11 + подушка 21 + FIFO
10.4). Проверено на эфире: q=0..30, fill/drops/underrun=0, clip=0,
клоки радио/ПК без дрейфа (micRx 48000/с).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-05 22:46:19 +03:00
co-authored by Claude Fable 5
parent 98493f08b3
commit 577b8add9d
6 changed files with 185 additions and 7 deletions
+13
View File
@@ -135,6 +135,11 @@ type
// Сколько сэмплов реально лежит в ring буфере (0..MY_MIC_RING_SIZE-1). // Сколько сэмплов реально лежит в ring буфере (0..MY_MIC_RING_SIZE-1).
// Использовать для чтения только доступных данных без пуш-тишины. // Использовать для чтения только доступных данных без пуш-тишины.
function Available: Integer; function Available: Integer;
// Сбросить накопленный бэклог (читатель догоняет писателя). Зовётся на
// старте TX: пока идёт приём, ринг наполняется до упора (~200мс), и без
// сброса весь этот «старый» звук бёрстом уходит в TX-тракт — стоячая
// задержка в DUC-очереди + передача аудио, записанного до нажатия PTT.
function Flush: Integer;
property IsOpen: Boolean read FOpen; property IsOpen: Boolean read FOpen;
property LastError: string read GetLastError; property LastError: string read GetLastError;
@@ -559,4 +564,12 @@ begin
Result := d; Result := d;
end; end;
function TAudioInput.Flush: Integer;
begin
// Lock-free, как ReadSample: одно присваивание Integer. Гонка с callback
// безобидна — в худшем случае останется/пропадёт несколько свежих сэмплов.
Result := Available;
FOutPt := FInPt;
end;
end. end.
+62 -2
View File
@@ -245,6 +245,7 @@ type
procedure SetSpeakerAudio(AEnabled: Boolean); override; procedure SetSpeakerAudio(AEnabled: Boolean); override;
procedure SendDUCIQ(const IData, QData: array of Integer); override; procedure SendDUCIQ(const IData, QData: array of Integer); override;
procedure ClearDUCIQQueue; override; procedure ClearDUCIQQueue; override;
procedure PrimeDUCIQ(Pairs: Integer); override;
// LocalPort — HPSDR-специфично (локальный UDP-порт), не часть базы. // LocalPort — HPSDR-специфично (локальный UDP-порт), не часть базы.
property LocalPort: Word read FLocalPort; property LocalPort: Word read FLocalPort;
@@ -419,7 +420,22 @@ procedure TDUCIQSenderThread.Execute;
const const
DUC_IQ_PAIRS_PER_PACKET = 240; DUC_IQ_PAIRS_PER_PACKET = 240;
DUC_SAMPLE_RATE = 192000; // IQ pairs per second DUC_SAMPLE_RATE = 192000; // IQ pairs per second
DUC_FIFO_THROTTLE = 1250; // piHPSDR-style virtual radio FIFO limit // Пейсинг DUC-FIFO радио (виртуальная оценка уровня, Linux-ветка).
// Режим «подушка в очереди»: pre-roll на старте TX (~THROTTLE+блоки нулей)
// наполняет FIFO радио до THROTTLE, а остаток (~3 WDSP-блока) остаётся лежать
// в FDUCIQQueue. Дальше очередь на каждом приходе блока непуста → sender
// непрерывно метрит пакеты у порога THROTTLE, уровень FIFO радио прижат к
// THROTTLE..THROTTLE+240 (а не ходит пилой до дна). Опоздание WDSP-блока
// (замерено до 25мс на старте под нагрузкой DUP) съедает подушку очереди,
// но FIFO радио не проседает — нулевые вставки (LOW) не нужны.
// THROTTLE=2000: FIFO радио ~2000..2400 — запас и от пустоты (2000), и от
// переполнения (4096−2400) под дрейф клоков радио/ПК.
DUC_FIFO_THROTTLE = 2000;
DUC_FIFO_LOW = 1000; // анти-старв (аварийный): ниже — шлём нули
// Доливка при срабатывании анти-старва: не один пакет (якорь у LOW+240,
// запас 1.25мс — следующее опоздание блока снова даст вставку-щелчок),
// а до REANCHOR. Излишек осядет подушкой в очереди через троттл THROTTLE.
DUC_FIFO_REANCHOR = 1700;
DUC_PACKET_INTERVAL_US = 1250; // 240 / 192000 seconds DUC_PACKET_INTERVAL_US = 1250; // 240 / 192000 seconds
DUC_WIN_SPIN_US = 350; // final part of 1.25 ms interval: avoid Sleep jitter DUC_WIN_SPIN_US = 350; // final part of 1.25 ms interval: avoid Sleep jitter
var var
@@ -435,6 +451,8 @@ var
LastTick: QWord; LastTick: QWord;
NowTick: QWord; NowTick: QWord;
ElapsedMs: QWord; ElapsedMs: QWord;
ZeroPkt: TDUCIQPacket; // анти-старв филлер (нулевые IQ)
WaitMs: Integer;
{$ENDIF} {$ENDIF}
{$IFDEF WINDOWS} {$IFDEF WINDOWS}
@@ -469,6 +487,7 @@ var
LastTick := NowTick; LastTick := NowTick;
end; end;
end; end;
{$ENDIF} {$ENDIF}
begin begin
@@ -505,13 +524,35 @@ begin
{$ELSE} {$ELSE}
VirtualSamples := 0; VirtualSamples := 0;
LastTick := GetTickCount64; LastTick := GetTickCount64;
FillChar(ZeroPkt, SizeOf(ZeroPkt), 0);
while not Terminated do while not Terminated do
begin begin
if not FNet.DequeueDUCIQ(Pkt) then if not FNet.DequeueDUCIQ(Pkt) then
begin begin
DrainVirtualFIFO; DrainVirtualFIFO;
RTLEventWaitFor(FNet.FDUCIQSem, 20); // Анти-старв: на передаче данных нет, а уровень FIFO радио проседает —
// шлём нулевые пакеты (тишина вместо underrun-мусора DAC). В устоявшемся
// режиме сюда не заходим (подушка очереди держит уровень у THROTTLE);
// срабатывает на старте TX (первый WDSP-блок через 10-25мс), в окне
// WDSP-фейда после PTT-off (продюсер уже молчит, PTT ещё поднят) и
// после стайлов планировщика. Доливаем сразу до REANCHOR: излишек
// осядет обратно подушкой в очереди, и якорь не остаётся у LOW.
if FNet.FIsTransmitting and (VirtualSamples < DUC_FIFO_LOW) then
begin
while (not Terminated) and (VirtualSamples < DUC_FIFO_REANCHOR) do
begin
FNet.PackSeqBytes(ZeroPkt.Seq, FNet.NextSeq(FNet.FSeqDUCIQ));
FNet.DoSendTo(FNet.FSocket, ZeroPkt, SizeOf(ZeroPkt),
FNet.FDevice.IPAddress, FNet.FPortDUCIQ);
Inc(VirtualSamples, DUC_IQ_PAIRS_PER_PACKET);
end;
Continue; // сразу пробуем данные — вдруг уже пришли
end;
// На передаче ждём коротко: за 20мс FIFO радио сливает 3840 сэмплов —
// анти-старв выше должен успевать подхватывать просадку.
if FNet.FIsTransmitting then WaitMs := 1 else WaitMs := 20;
RTLEventWaitFor(FNet.FDUCIQSem, WaitMs);
DrainVirtualFIFO; DrainVirtualFIFO;
Continue; Continue;
end; end;
@@ -1714,6 +1755,25 @@ begin
end; end;
end; end;
procedure THPSDRNetwork.PrimeDUCIQ(Pairs: Integer);
// Pre-roll на старте TX: Pairs нулевых IQ-пар (пакетами по 240) в очередь.
// Sender вышлет их немедленно (VirtualSamples после RX-паузы = 0, троттл
// пропустит бёрст) — DUC-FIFO радио наполняется нулями, пока реле/WDSP
// раскачиваются, и DAC не стартует с пустого буфера (underrun-всплеск).
var
Pkt: TDUCIQPacket;
n: Integer;
begin
if not FConnected then Exit;
FillChar(Pkt, SizeOf(Pkt), 0);
n := (Pairs + 239) div 240;
while n > 0 do
begin
EnqueueDUCIQ(Pkt);
Dec(n);
end;
end;
procedure THPSDRNetwork.SendDUCIQ(const IData, QData: array of Integer); procedure THPSDRNetwork.SendDUCIQ(const IData, QData: array of Integer);
var var
Pkt: TDUCIQPacket; Pkt: TDUCIQPacket;
+9
View File
@@ -129,6 +129,11 @@ type
// ---- TX IQ ---- // ---- TX IQ ----
procedure SendDUCIQ(const IData, QData: array of Integer); virtual; procedure SendDUCIQ(const IData, QData: array of Integer); virtual;
procedure ClearDUCIQQueue; virtual; procedure ClearDUCIQQueue; virtual;
// Pre-roll: закачать Pairs нулевых IQ-пар в TX-очередь на старте передачи —
// радио начинает с наполненным DUC-FIFO (подавление underrun-всплеска
// первых мс, как pre-roll 1024 в piHPSDR). База/Pluto — no-op: у Pluto
// TX-стрим непрерывный, его FIFO при недоборе сам добивается нулями.
procedure PrimeDUCIQ(Pairs: Integer); virtual;
// ---- Опциональные (HPSDR-специфичные): no-op по умолчанию ---- // ---- Опциональные (HPSDR-специфичные): no-op по умолчанию ----
procedure SendGeneralPacket(const Pkt: TGeneralPacket); virtual; procedure SendGeneralPacket(const Pkt: TGeneralPacket); virtual;
@@ -197,6 +202,10 @@ procedure TRadioBackend.ClearDUCIQQueue;
begin begin
end; end;
procedure TRadioBackend.PrimeDUCIQ(Pairs: Integer);
begin
end;
procedure TRadioBackend.SendGeneralPacket(const Pkt: TGeneralPacket); procedure TRadioBackend.SendGeneralPacket(const Pkt: TGeneralPacket);
begin begin
end; end;
+36 -2
View File
@@ -672,6 +672,14 @@ type
implementation implementation
const
// Амплитуда TUN-тона (WDSP PostGen). НЕ 1.0: на резком старте тона фильтры
// TXA-тракта дают переходный выброс ~×1.18 (замерено: peak=1.176 при
// mag=1.0) поверх полной шкалы → жёсткий кламп 24-бит → сплэттер на старте
// TUN. 0.83×1.18≈0.98 — выброс остаётся в шкале. Мощность TUN задаёт drive
// (TUNLevel), падение уровня IQ ~1.6дБ компенсируется калибровкой TUNLevel.
TUN_TONE_MAG = 0.83;
constructor TRadioController.Create; constructor TRadioController.Create;
begin begin
inherited Create; inherited Create;
@@ -2335,7 +2343,7 @@ begin
begin begin
FDSPEngine.SetMode(FMode); FDSPEngine.SetMode(FMode);
// Если TUN активен — пересчитываем знак частоты под новый режим // Если TUN активен — пересчитываем знак частоты под новый режим
if FTuning then FDSPEngine.SetTXTone(True, FTXSettings.TUNFreq, 1.0); if FTuning then FDSPEngine.SetTXTone(True, FTXSettings.TUNFreq, TUN_TONE_MAG);
end; end;
ApplyModeFilter; ApplyModeFilter;
Changed(rfMode); Changed(rfMode);
@@ -3071,6 +3079,14 @@ begin
FDriveLevel, True, True, True); FDriveLevel, True, True, True);
FNetwork.SendFullHP; // 1й пакет: Alex bit27=1 (T/R relay), drive — без PTT FNetwork.SendFullHP; // 1й пакет: Alex bit27=1 (T/R relay), drive — без PTT
FNetwork.SendPTT(True); // 2й пакет: PTT=1 после переключения реле FNetwork.SendPTT(True); // 2й пакет: PTT=1 после переключения реле
// Pre-roll: THROTTLE (2000) + ТРИ WDSP-блока (3×2048) нулевых пар. Первая
// часть улетает сразу и наполняет DUC-FIFO радио до порога THROTTLE (DAC
// не стартует с пустого буфера), остаток ~3 блока (~32мс) ложится ПОДУШКОЙ
// в очередь — дальше очередь на приходе каждого WDSP-блока непуста, sender
// метрит непрерывно, и опоздание блока не роняет FIFO радио. Замеры: под
// нагрузкой DUP и при быстром пере-PTT первый WDSP-блок опаздывает до
// 25мс — двух блоков подушки не хватало (щелчок через ~0.5с после старта).
FNetwork.PrimeDUCIQ(8144);
end; end;
if FWDSPReady and Assigned(FDSPEngine) then if FWDSPReady and Assigned(FDSPEngine) then
begin begin
@@ -3093,6 +3109,14 @@ begin
FWebMicActive := False; FWebMicActive := False;
FDSPEngine.SetTXMicSource(DefaultMicSource); FDSPEngine.SetTXMicSource(DefaultMicSource);
end; end;
// Сброс бэклога sound-card микрофона на RX→TX: пока шёл приём, ринг
// PortAudio наполнился до упора (~200мс). Без сброса весь этот «старый»
// звук бёрстом уходит в тракт: стоячая задержка ~190мс в DUC-очереди
// + в эфир передаётся аудио, записанное ДО нажатия PTT (на TUN оно
// складывается с тоном поверх полной шкалы → клип-всплеск).
if FTransmitting and (not WasTransmitting) and Assigned(FAudioIn)
and FAudioIn.IsOpen then
FAudioIn.Flush;
// В non-DUP RX-IQ пакеты дропаются на входе DSP-потока во время TX, чтобы // В non-DUP RX-IQ пакеты дропаются на входе DSP-потока во время TX, чтобы
// TX leakage не накапливался в RXA pipeline / FFT-истории анализатора. // TX leakage не накапливался в RXA pipeline / FFT-истории анализатора.
FDSPEngine.KeepRXDuringTX := FDisplayDuplex; FDSPEngine.KeepRXDuringTX := FDisplayDuplex;
@@ -3139,7 +3163,7 @@ begin
begin begin
// Тон включаем ДО запуска TX-аудио, чтобы первые сэмплы уже шли с тоном. // Тон включаем ДО запуска TX-аудио, чтобы первые сэмплы уже шли с тоном.
if FWDSPReady and Assigned(FDSPEngine) then if FWDSPReady and Assigned(FDSPEngine) then
FDSPEngine.SetTXTone(True, FTXSettings.TUNFreq, 1.0); FDSPEngine.SetTXTone(True, FTXSettings.TUNFreq, TUN_TONE_MAG);
FTuning := True; FTuning := True;
FDriveLevel := CalcDriveByte; // CalcDriveByte увидит FTuning=True (TUN-уровень) FDriveLevel := CalcDriveByte; // CalcDriveByte увидит FTuning=True (TUN-уровень)
ApplyWDSPDrive(FTXSettings.TUNLevel); ApplyWDSPDrive(FTXSettings.TUNLevel);
@@ -3370,6 +3394,16 @@ begin
FNetwork.SetRunAndFreq(False, XvtrTranslate(FCenterFreq), XvtrTranslate(FCenterFreq), 0); FNetwork.SetRunAndFreq(False, XvtrTranslate(FCenterFreq), XvtrTranslate(FCenterFreq), 0);
FRunning := False; FRunning := False;
FTransmitting := False; FTransmitting := False;
// Стоп при активном TX/TUN: хвост IQ в DUC-очереди не должен пережить
// остановку (иначе при следующем старте уйдёт в эфир старый тон/голос),
// а TUN-состояние (PostGen-тон) — вернуться в выключенное.
FNetwork.ClearDUCIQQueue;
if FTuning then
begin
FTuning := False;
if FWDSPReady then FDSPEngine.SetTXTone(False, 0, 0);
Changed(rfTuning); // UI: TUN-кнопка off
end;
if FWDSPReady then FDSPEngine.SetTXRun(False); if FWDSPReady then FDSPEngine.SetTXRun(False);
Changed(rfTransmitting); // MOX-кнопка off Changed(rfTransmitting); // MOX-кнопка off
end; end;
+53 -1
View File
@@ -285,6 +285,10 @@ type
FActiveDisplayID: Integer; // RX_DISP_ID или TX_DISP_ID FActiveDisplayID: Integer; // RX_DISP_ID или TX_DISP_ID
FTXMicSource: TTXMicSource; FTXMicSource: TTXMicSource;
FOnPullMic: TPullMicSamplesFunc; FOnPullMic: TPullMicSamplesFunc;
// TUN активен (PostGen-тон): mic-вход TXA зануляется в ProcessTXBlock.
// PostGen стоит ПОСЛЕ ALC — голос из микрофона складывался бы с тоном
// поверх полной шкалы (тон ~0.98) → клип 24-бит → сплэттер на спектре.
FTXToneOn: Boolean;
// Кэшированные TX-параметры — применяются после OpenChannel(TXA) и при изменении. // Кэшированные TX-параметры — применяются после OpenChannel(TXA) и при изменении.
// Для Filter/Compressor/Leveler/ALC/PhaseRot/EQ/AM/FM/CTCSS. // Для Filter/Compressor/Leveler/ALC/PhaseRot/EQ/AM/FM/CTCSS.
@@ -897,6 +901,7 @@ begin
FTXMicHead := 0; FTXMicHead := 0;
FTXMicTail := 0; FTXMicTail := 0;
FTXActive := False; FTXActive := False;
FTXToneOn := False;
FKeepRXDuringTX := False; FKeepRXDuringTX := False;
FMonitorRXDuringTX := False; FMonitorRXDuringTX := False;
FPostTXMuteUntil := 0; FPostTXMuteUntil := 0;
@@ -2140,6 +2145,8 @@ begin
end; end;
procedure TWDSPEngine.SetTXRun(Run: Boolean); procedure TWDSPEngine.SetTXRun(Run: Boolean);
var
k, Err: Integer;
begin begin
FDispPos := 0; FDispPos := 0;
FTXDispPos := 0; FTXDispPos := 0;
@@ -2150,7 +2157,30 @@ begin
FTXMicHead := 0; FTXMicHead := 0;
FTXMicTail := 0; FTXMicTail := 0;
if FInitialized then if FInitialized then
begin
SetChannelState(TXA_CHAN, 1, 0); // run, no delay SetChannelState(TXA_CHAN, 1, 0); // run, no delay
// Продувка канала: страховка от остатка прошлой TX-сессии в буферах/
// фильтрах TXA (без неё огрызок — после TUN это полноразмахный тон —
// выплёвывался в эфир первым же блоком нового TX). Основной слив теперь
// на стопе (ниже), но нештатные пути останова канал не чистят. 4 нулевых
// блока через fexchange0, выход в мусор (не в FOnTXIQ/display); поток
// ещё idle (FTXActive=False) — гонки нет.
FillChar(FTXIn[0], FAudioBufSize * 2 * SizeOf(Double), 0);
for k := 1 to 4 do
begin
Err := 0;
fexchange0(TXA_CHAN, @FTXIn[0], @FTXOut[0], @Err);
end;
// Сброс TX-анализатора: его усредняющая история хранит спектр ПРОШЛОЙ
// сессии (после TUN — тон), и при переключении на TX-дисплей первые
// кадры показывали «всплеск» старого тона (виден в non-DUP, где на
// экране TX-спектр; эфир при этом чист — в DUP старт чистый).
// ResetPixelBuffers — лёгкое зануление pixel/average буферов (MW0LGE);
// НЕ ApplyTXAnalyzerSettings: тот зовёт SetAnalyzer (перепланирование
// FFT в UI-потоке) — давал видимую задержку отрисовки на старте TX.
if FAnalyzerOpen then
ResetPixelBuffers(TX_DISP_ID);
end;
// TX-DSP поток создаётся ОДИН раз и живёт весь сеанс. НЕ пересоздаём на каждом // TX-DSP поток создаётся ОДИН раз и живёт весь сеанс. НЕ пересоздаём на каждом
// T/R: join (WaitFor) давал фиксированные ~100 мс фриза GUI на отпускании PTT. // T/R: join (WaitFor) давал фиксированные ~100 мс фриза GUI на отпускании PTT.
// При FTXActive=False поток простаивает (см. TTXDSPThread.Execute: Continue). // При FTXActive=False поток простаивает (см. TTXDSPThread.Execute: Continue).
@@ -2164,7 +2194,24 @@ begin
FTXActive := False; // поток уходит в простой (не трогает канал/ring) FTXActive := False; // поток уходит в простой (не трогает канал/ring)
FActiveDisplayID := RX_DISP_ID; FActiveDisplayID := RX_DISP_ID;
if FInitialized then if FInitialized then
SetChannelState(TXA_CHAN, 0, 1); // stop, with slew (поток уже idle) begin
// Мгновенный стоп + синхронный слив. Раньше здесь был dmode=1: он
// спинует Sleep(1)×100 в ожидании фейда, который должен прокрутить
// fexchange — а продюсер уже остановлен, fexchange никто не зовёт →
// чистый таймаут ~106мс (остаточный фриз GUI на отпускании PTT) и
// канал, брошенный с недобранным сигналом в буферах. dmode=0 лишь
// взводит downflag/flushflag и возвращается сразу; down-slew (фейд
// 10мс) и штатный flush буферов исполняют наши же fexchange0 с нулевым
// входом — суммарно <1мс, выход в мусор (в эфир не идёт: DUC-очередь
// чистится контроллером на T/R).
SetChannelState(TXA_CHAN, 0, 0); // stop, без ожидания
FillChar(FTXIn[0], FAudioBufSize * 2 * SizeOf(Double), 0);
for k := 1 to 4 do
begin
Err := 0;
fexchange0(TXA_CHAN, @FTXIn[0], @FTXOut[0], @Err);
end;
end;
FTXMicHead := 0; FTXMicHead := 0;
FTXMicTail := 0; FTXMicTail := 0;
end; end;
@@ -2175,6 +2222,7 @@ var
SignedFreq: Double; SignedFreq: Double;
begin begin
if not FInitialized then Exit; if not FInitialized then Exit;
FTXToneOn := Enabled; // мьют mic-входа на TUN (см. ProcessTXBlock)
if Enabled then if Enabled then
begin begin
// gen1 (PostGen) стоит после всех модуляторов и бандпасса. // gen1 (PostGen) стоит после всех модуляторов и бандпасса.
@@ -2232,6 +2280,10 @@ begin
tail := (tail + 1) and (TX_MIC_RING - 1); tail := (tail + 1) and (TX_MIC_RING - 1);
end; end;
FTXMicTail := tail; // Атомарно обновляем tail (один writer — TX thread) FTXMicTail := tail; // Атомарно обновляем tail (один writer — TX thread)
// TUN: ринг потребляем (иначе поток зациклится на «есть полный блок»),
// но в TXA подаём тишину — тон PostGen должен идти без голоса поверх.
if FTXToneOn then
FillChar(FTXIn[0], FAudioBufSize * 2 * SizeOf(Double), 0);
end else end else
begin begin
// Нет mic-данных — тишина (аналог piHPSDR: tx->mic_input_buffer заполняется нулями) // Нет mic-данных — тишина (аналог piHPSDR: tx->mic_input_buffer заполняется нулями)
+12 -2
View File
@@ -17,9 +17,9 @@
<UseVersionInfo Value="True"/> <UseVersionInfo Value="True"/>
<AutoIncrementBuild Value="True"/> <AutoIncrementBuild Value="True"/>
<MinorVersionNr Value="9"/> <MinorVersionNr Value="9"/>
<BuildNr Value="246"/> <BuildNr Value="256"/>
</VersionInfo> </VersionInfo>
<MacroValues Count="64"> <MacroValues Count="74">
<Macro1 Name="LCLWidgetType" Value="qt6"/> <Macro1 Name="LCLWidgetType" Value="qt6"/>
<Macro2 Name="LCLWidgetType" Value="qt6"/> <Macro2 Name="LCLWidgetType" Value="qt6"/>
<Macro3 Name="LCLWidgetType" Value="qt6"/> <Macro3 Name="LCLWidgetType" Value="qt6"/>
@@ -84,6 +84,16 @@
<Macro62 Name="LCLWidgetType" Value="qt6"/> <Macro62 Name="LCLWidgetType" Value="qt6"/>
<Macro63 Name="LCLWidgetType" Value="qt6"/> <Macro63 Name="LCLWidgetType" Value="qt6"/>
<Macro64 Name="LCLWidgetType" Value="qt6"/> <Macro64 Name="LCLWidgetType" Value="qt6"/>
<Macro65 Name="LCLWidgetType" Value="qt6"/>
<Macro66 Name="LCLWidgetType" Value="qt6"/>
<Macro67 Name="LCLWidgetType" Value="qt6"/>
<Macro68 Name="LCLWidgetType" Value="qt6"/>
<Macro69 Name="LCLWidgetType" Value="qt6"/>
<Macro70 Name="LCLWidgetType" Value="qt6"/>
<Macro71 Name="LCLWidgetType" Value="qt6"/>
<Macro72 Name="LCLWidgetType" Value="qt6"/>
<Macro73 Name="LCLWidgetType" Value="qt6"/>
<Macro74 Name="LCLWidgetType" Value="qt6"/>
</MacroValues> </MacroValues>
<BuildModes> <BuildModes>
<Item Name="Debug" Default="True"/> <Item Name="Debug" Default="True"/>