mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 20:37:33 +00:00
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:
+20
-2
@@ -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
|
||||
PaintFlatButton(Canvas, ClientRect, Caption, FActive, FHot,
|
||||
FClrNorm, FClrActive, FClrHot, FClrBorder, FClrText, FClrTextAct);
|
||||
if Enabled then
|
||||
PaintFlatButton(Canvas, ClientRect, Caption, FActive, FHot,
|
||||
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
@@ -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
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user