diff --git a/FlatButton.pas b/FlatButton.pas index 6f675aa..d8f94e1 100644 --- a/FlatButton.pas +++ b/FlatButton.pas @@ -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; diff --git a/MainForm.pas b/MainForm.pas index 513e05f..9435fbd 100644 --- a/MainForm.pas +++ b/MainForm.pas @@ -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; diff --git a/RadioController.pas b/RadioController.pas index cdb1624..7815a87 100644 --- a/RadioController.pas +++ b/RadioController.pas @@ -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;