feat(tx): self-monitor RX-аудио на TX в любом DUP + меньше TX pre-roll

RX MUTE (self-monitor даунлинка на передаче): кнопка теперь доступна на
ВСЕХ бэкендах, а не только Pluto/QO-100. Активна только в DUP; без DUP
кнопка disabled и звук на TX не проходит (движок гейтит по
FKeepRXDuringTX=FDisplayDuplex — двойная защита: аудио-гейт + RXA не
кормится). Добавлен RadioController.DisplayDuplex. TFlatButton теперь
рисует disabled-состояние приглушённым (текст/рамка к фону).

TX pre-roll 8144->6096 (2 блока подушки вместо 3): обоснование «нужно 3
блока» опиралось на щелчок ~0.5с после старта, который на деле оказался
bfo-глитчом тона (устранён f2d0519), а не underrun'ом подушки. Латентность
старта TX ~-10мс; startup-щелчков под нагрузкой DUP не слышно (подтверждено).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-07 16:09:26 +03:00
co-authored by Claude Opus 4.8
parent f2d0519c9e
commit ff2082ab04
3 changed files with 49 additions and 21 deletions
+19 -1
View File
@@ -128,10 +128,28 @@ begin
R.Top + (R.Bottom - R.Top - TH) div 2, ACaption);
end;
function BlendCol(A, B: TColor; T: Double): TColor;
// Линейная смесь A→B на долю T (0..1); системные цвета резолвим через ColorToRGB.
var ca, cb: LongInt; r, g, bl: Integer;
begin
ca := ColorToRGB(A); cb := ColorToRGB(B);
r := Round((ca and $FF) + ((cb and $FF) - (ca and $FF)) * T);
g := Round(((ca shr 8) and $FF) + (((cb shr 8) and $FF) - ((ca shr 8) and $FF)) * T);
bl := Round(((ca shr 16) and $FF)+ (((cb shr 16) and $FF)- ((ca shr 16) and $FF))* T);
Result := TColor(r or (g shl 8) or (bl shl 16));
end;
procedure TFlatButton.Paint;
begin
if Enabled then
PaintFlatButton(Canvas, ClientRect, Caption, FActive, FHot,
FClrNorm, FClrActive, FClrHot, FClrBorder, FClrText, FClrTextAct);
FClrNorm, FClrActive, FClrHot, FClrBorder, FClrText, FClrTextAct)
else
// disabled: не active/не hot, текст и рамка приглушены к фону.
PaintFlatButton(Canvas, ClientRect, Caption, False, False,
FClrNorm, FClrActive, FClrHot,
BlendCol(FClrBorder, FClrNorm, 0.6),
BlendCol(FClrText, FClrNorm, 0.55), FClrTextAct);
end;
procedure TFlatButton.MouseEnter;
+12 -10
View File
@@ -1349,6 +1349,7 @@ begin
// через SetDuplex → rfDuplex). Раньше QO-100 включал DUP, но кнопку не
// подсвечивал → визуал расходился с состоянием.
if BtnDUP <> nil then StyleButton(BtnDUP, FController.FDisplayDuplex);
UpdateRxMuteButton; // активность RX MUTE зависит от DUP
// На передаче переключаем источник пикселей и сбрасываем буферы, чтобы
// старые TX/RX bin'ы не оставались на следующем кадре.
if FController.FTransmitting then
@@ -1791,14 +1792,14 @@ begin
BtnDUP.Tag := 0;
StyleButton(BtnDUP, FController.FDisplayDuplex);
// QO-100 self-monitor: рядом с DUP, col1. Виден только на Pluto в full-duplex
// транспондере. ON (подсвечена, умолч.) = RX-аудио глушится на TX; OFF = слышим
// свой downlink со спутника.
// Self-monitor RX-аудио на TX: рядом с DUP, col1. Видна на любом бэкенде,
// активна только в DUP (см. UpdateRxMuteButton). ON (подсвечена, умолч.) =
// RX-аудио глушится на TX; OFF = слышим свой downlink во время передачи.
BtnRxMute := MakeBtn(PanelTX, 'RX MUTE',
LeftPanelButtonLeft(LEFT_W, 4, 1), 44,
LeftPanelButtonWidth(LEFT_W, 4, 1), BTN_SM, BtnRxMuteClick);
BtnRxMute.Visible := False;
StyleButton(BtnRxMute, FController.FRxMuteOnTx);
UpdateRxMuteButton; // видимость/активность по DUP
LblDrv := TLabel.Create(Self);
LblDrv.Parent := PanelTX; LblDrv.Left := 4; LblDrv.Top := 77;
@@ -4203,14 +4204,15 @@ begin
end;
procedure TMainForm.UpdateRxMuteButton;
// Кнопка RX MUTE видна только на Pluto в full-duplex транспондере (QO-100).
// ON (подсвечена) = RX-аудио глушится на TX; OFF = self-monitor (слышим себя).
var Vis: Boolean;
// Кнопка RX MUTE видна на ЛЮБОМ бэкенде, но АКТИВНА только в DUP (self-monitor
// осмыслен лишь в full-duplex; без DUP звук на TX и так не проходит — движок
// гейтит по FKeepRXDuringTX=FDisplayDuplex). ON (подсвечена) = RX-аудио глушится
// на TX; OFF = слышим свой downlink во время передачи.
begin
if BtnRxMute = nil then Exit;
Vis := FController.InQO100;
BtnRxMute.Visible := Vis;
if Vis then StyleButton(BtnRxMute, FController.FRxMuteOnTx);
BtnRxMute.Visible := True;
BtnRxMute.Enabled := FController.DisplayDuplex;
StyleButton(BtnRxMute, FController.FRxMuteOnTx);
end;
procedure TMainForm.UpdateBandPlanOverlay;
+17 -9
View File
@@ -636,8 +636,11 @@ type
function BandPlan: TBandPlanArray;
// True если активный бэкенд — Pluto (VHF/UHF band-план вместо HF).
function IsPluto: Boolean;
// True — активен QO-100 (Pluto + full-duplex XVTR-слот); гейт UI beacon/RX MUTE.
// True — активен QO-100 (Pluto + full-duplex XVTR-слот); гейт UI beacon.
function InQO100: Boolean;
// True — включён режим DUP (дисплей+RX держатся на TX). Гейт активности
// кнопки RX MUTE (self-monitor) на ЛЮБОМ бэкенде — без DUP звук на TX не идёт.
function DisplayDuplex: Boolean;
// Человекочитаемое имя подключённой платы для статус-строки (UI/Web).
// HPSDR → тип платы; Pluto/LibreSDR → hw_model + серийник (если есть).
function BoardDisplayName: string;
@@ -3079,14 +3082,14 @@ begin
FDriveLevel, True, True, True);
FNetwork.SendFullHP; // 1й пакет: Alex bit27=1 (T/R relay), drive — без PTT
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);
// Pre-roll: THROTTLE (2000, порог FPGA — DAC не стартует с пустого буфера)
// + ДВА WDSP-блока (2×2048) нулевых пар подушкой в очередь. Раньше было 3
// блока: обоснование «2 не хватало — щелчок через ~0.5с» теперь под вопросом,
// т.к. тот щелчок по времени (0.5с, не ~21мс) похож не на underrun подушки, а
// на bfo-глитч тона (устранён коммитом f2d0519). Снижено до 2 блоков ради
// латентности старта TX (~10мс). Если под нагрузкой DUP / быстром пере-PTT
// вернётся щелчок РОВНО на старте — это реальный underrun, вернуть 8144.
FNetwork.PrimeDUCIQ(6096);
end;
if FWDSPReady and Assigned(FDSPEngine) then
begin
@@ -3791,6 +3794,11 @@ begin
and FXvtrSettings.Entries[FCurrentXvtr].FullDuplexTx;
end;
function TRadioController.DisplayDuplex: Boolean;
begin
Result := FDisplayDuplex;
end;
function TRadioController.BoardDisplayName: string;
var
Dev: THPSDRDevice;