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:
+19
-1
@@ -128,10 +128,28 @@ begin
|
|||||||
R.Top + (R.Bottom - R.Top - TH) div 2, ACaption);
|
R.Top + (R.Bottom - R.Top - TH) div 2, ACaption);
|
||||||
end;
|
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;
|
procedure TFlatButton.Paint;
|
||||||
begin
|
begin
|
||||||
|
if Enabled then
|
||||||
PaintFlatButton(Canvas, ClientRect, Caption, FActive, FHot,
|
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;
|
end;
|
||||||
|
|
||||||
procedure TFlatButton.MouseEnter;
|
procedure TFlatButton.MouseEnter;
|
||||||
|
|||||||
+12
-10
@@ -1349,6 +1349,7 @@ begin
|
|||||||
// через SetDuplex → rfDuplex). Раньше QO-100 включал DUP, но кнопку не
|
// через SetDuplex → rfDuplex). Раньше QO-100 включал DUP, но кнопку не
|
||||||
// подсвечивал → визуал расходился с состоянием.
|
// подсвечивал → визуал расходился с состоянием.
|
||||||
if BtnDUP <> nil then StyleButton(BtnDUP, FController.FDisplayDuplex);
|
if BtnDUP <> nil then StyleButton(BtnDUP, FController.FDisplayDuplex);
|
||||||
|
UpdateRxMuteButton; // активность RX MUTE зависит от DUP
|
||||||
// На передаче переключаем источник пикселей и сбрасываем буферы, чтобы
|
// На передаче переключаем источник пикселей и сбрасываем буферы, чтобы
|
||||||
// старые TX/RX bin'ы не оставались на следующем кадре.
|
// старые TX/RX bin'ы не оставались на следующем кадре.
|
||||||
if FController.FTransmitting then
|
if FController.FTransmitting then
|
||||||
@@ -1791,14 +1792,14 @@ begin
|
|||||||
BtnDUP.Tag := 0;
|
BtnDUP.Tag := 0;
|
||||||
StyleButton(BtnDUP, FController.FDisplayDuplex);
|
StyleButton(BtnDUP, FController.FDisplayDuplex);
|
||||||
|
|
||||||
// QO-100 self-monitor: рядом с DUP, col1. Виден только на Pluto в full-duplex
|
// Self-monitor RX-аудио на TX: рядом с DUP, col1. Видна на любом бэкенде,
|
||||||
// транспондере. ON (подсвечена, умолч.) = RX-аудио глушится на TX; OFF = слышим
|
// активна только в DUP (см. UpdateRxMuteButton). ON (подсвечена, умолч.) =
|
||||||
// свой downlink со спутника.
|
// RX-аудио глушится на TX; OFF = слышим свой downlink во время передачи.
|
||||||
BtnRxMute := MakeBtn(PanelTX, 'RX MUTE',
|
BtnRxMute := MakeBtn(PanelTX, 'RX MUTE',
|
||||||
LeftPanelButtonLeft(LEFT_W, 4, 1), 44,
|
LeftPanelButtonLeft(LEFT_W, 4, 1), 44,
|
||||||
LeftPanelButtonWidth(LEFT_W, 4, 1), BTN_SM, BtnRxMuteClick);
|
LeftPanelButtonWidth(LEFT_W, 4, 1), BTN_SM, BtnRxMuteClick);
|
||||||
BtnRxMute.Visible := False;
|
|
||||||
StyleButton(BtnRxMute, FController.FRxMuteOnTx);
|
StyleButton(BtnRxMute, FController.FRxMuteOnTx);
|
||||||
|
UpdateRxMuteButton; // видимость/активность по DUP
|
||||||
|
|
||||||
LblDrv := TLabel.Create(Self);
|
LblDrv := TLabel.Create(Self);
|
||||||
LblDrv.Parent := PanelTX; LblDrv.Left := 4; LblDrv.Top := 77;
|
LblDrv.Parent := PanelTX; LblDrv.Left := 4; LblDrv.Top := 77;
|
||||||
@@ -4203,14 +4204,15 @@ begin
|
|||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TMainForm.UpdateRxMuteButton;
|
procedure TMainForm.UpdateRxMuteButton;
|
||||||
// Кнопка RX MUTE видна только на Pluto в full-duplex транспондере (QO-100).
|
// Кнопка RX MUTE видна на ЛЮБОМ бэкенде, но АКТИВНА только в DUP (self-monitor
|
||||||
// ON (подсвечена) = RX-аудио глушится на TX; OFF = self-monitor (слышим себя).
|
// осмыслен лишь в full-duplex; без DUP звук на TX и так не проходит — движок
|
||||||
var Vis: Boolean;
|
// гейтит по FKeepRXDuringTX=FDisplayDuplex). ON (подсвечена) = RX-аудио глушится
|
||||||
|
// на TX; OFF = слышим свой downlink во время передачи.
|
||||||
begin
|
begin
|
||||||
if BtnRxMute = nil then Exit;
|
if BtnRxMute = nil then Exit;
|
||||||
Vis := FController.InQO100;
|
BtnRxMute.Visible := True;
|
||||||
BtnRxMute.Visible := Vis;
|
BtnRxMute.Enabled := FController.DisplayDuplex;
|
||||||
if Vis then StyleButton(BtnRxMute, FController.FRxMuteOnTx);
|
StyleButton(BtnRxMute, FController.FRxMuteOnTx);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TMainForm.UpdateBandPlanOverlay;
|
procedure TMainForm.UpdateBandPlanOverlay;
|
||||||
|
|||||||
+17
-9
@@ -636,8 +636,11 @@ type
|
|||||||
function BandPlan: TBandPlanArray;
|
function BandPlan: TBandPlanArray;
|
||||||
// True если активный бэкенд — Pluto (VHF/UHF band-план вместо HF).
|
// True если активный бэкенд — Pluto (VHF/UHF band-план вместо HF).
|
||||||
function IsPluto: Boolean;
|
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;
|
function InQO100: Boolean;
|
||||||
|
// True — включён режим DUP (дисплей+RX держатся на TX). Гейт активности
|
||||||
|
// кнопки RX MUTE (self-monitor) на ЛЮБОМ бэкенде — без DUP звук на TX не идёт.
|
||||||
|
function DisplayDuplex: Boolean;
|
||||||
// Человекочитаемое имя подключённой платы для статус-строки (UI/Web).
|
// Человекочитаемое имя подключённой платы для статус-строки (UI/Web).
|
||||||
// HPSDR → тип платы; Pluto/LibreSDR → hw_model + серийник (если есть).
|
// HPSDR → тип платы; Pluto/LibreSDR → hw_model + серийник (если есть).
|
||||||
function BoardDisplayName: string;
|
function BoardDisplayName: string;
|
||||||
@@ -3079,14 +3082,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) нулевых пар. Первая
|
// Pre-roll: THROTTLE (2000, порог FPGA — DAC не стартует с пустого буфера)
|
||||||
// часть улетает сразу и наполняет DUC-FIFO радио до порога THROTTLE (DAC
|
// + ДВА WDSP-блока (2×2048) нулевых пар подушкой в очередь. Раньше было 3
|
||||||
// не стартует с пустого буфера), остаток ~3 блока (~32мс) ложится ПОДУШКОЙ
|
// блока: обоснование «2 не хватало — щелчок через ~0.5с» теперь под вопросом,
|
||||||
// в очередь — дальше очередь на приходе каждого WDSP-блока непуста, sender
|
// т.к. тот щелчок по времени (0.5с, не ~21мс) похож не на underrun подушки, а
|
||||||
// метрит непрерывно, и опоздание блока не роняет FIFO радио. Замеры: под
|
// на bfo-глитч тона (устранён коммитом f2d0519). Снижено до 2 блоков ради
|
||||||
// нагрузкой DUP и при быстром пере-PTT первый WDSP-блок опаздывает до
|
// латентности старта TX (~10мс). Если под нагрузкой DUP / быстром пере-PTT
|
||||||
// 25мс — двух блоков подушки не хватало (щелчок через ~0.5с после старта).
|
// вернётся щелчок РОВНО на старте — это реальный underrun, вернуть 8144.
|
||||||
FNetwork.PrimeDUCIQ(8144);
|
FNetwork.PrimeDUCIQ(6096);
|
||||||
end;
|
end;
|
||||||
if FWDSPReady and Assigned(FDSPEngine) then
|
if FWDSPReady and Assigned(FDSPEngine) then
|
||||||
begin
|
begin
|
||||||
@@ -3791,6 +3794,11 @@ begin
|
|||||||
and FXvtrSettings.Entries[FCurrentXvtr].FullDuplexTx;
|
and FXvtrSettings.Entries[FCurrentXvtr].FullDuplexTx;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
function TRadioController.DisplayDuplex: Boolean;
|
||||||
|
begin
|
||||||
|
Result := FDisplayDuplex;
|
||||||
|
end;
|
||||||
|
|
||||||
function TRadioController.BoardDisplayName: string;
|
function TRadioController.BoardDisplayName: string;
|
||||||
var
|
var
|
||||||
Dev: THPSDRDevice;
|
Dev: THPSDRDevice;
|
||||||
|
|||||||
Reference in New Issue
Block a user