DUP (display duplex) + ATT on TX

- DUP: локальный режим отображения, при TX данные RX1, поверх рисуется
  TX-фильтр. Кнопка на левой панели рядом с CTUN. Аппаратной командой
  не является. Отдельный SetDisplaySourceTX в WDSPEngine, разделение
  TXMode (data) и TXOverlay (filter band) в SpectrumView.
- ATT on TX: 0..31 dB, default 31 (Thetis-style). UI на вкладке Transmit
  в группе Microphone. Передаётся в DUC Specific bytes 57/58/59
  (StepAtten0/1/2). Перепосылается при изменении.
This commit is contained in:
2026-05-06 15:51:33 +03:00
parent 96d7e0c759
commit 88687b4e70
5 changed files with 112 additions and 6 deletions
+67 -4
View File
@@ -182,6 +182,9 @@ type
FRunning: Boolean;
FTransmitting: Boolean;
FSplitTxB: Boolean;
// DUP: при TX продолжаем показывать RX1-водопад/панораму, а TX-фильтр
// и TX-линии рисуем поверх. Аппаратной командой не является.
FDisplayDuplex: Boolean;
FHWPTTActive: Boolean; // True = hardware PTT нажата
FMuted: Boolean;
FVolume: Integer;
@@ -304,6 +307,7 @@ type
PanelFilter: TPanel;
BtnFilter: array[0..FILT_COUNT-1] of TFlatButton;
BtnCTun: TFlatButton;
BtnDUP: TFlatButton;
PanelRX: TPanel;
BtnAGCMode: array[0..4] of TFlatButton; // FAST MED SLOW LONG OFF
@@ -413,6 +417,8 @@ type
procedure BtnModeClick(Sender: TObject);
procedure BtnFilterClick(Sender: TObject);
procedure BtnCTunClick(Sender: TObject);
procedure BtnDUPClick(Sender: TObject);
procedure ApplyDUP(Active: Boolean);
procedure UpdateFilterButtons;
procedure PbSpectrumMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
@@ -863,6 +869,7 @@ begin
Result.AudioSampleRate := FAudioOut.SampleRate;
Result.ShowSpectrum := FShowSpectrum;
Result.ShowWaterfall := FShowWaterfall;
Result.DisplayDuplex := FDisplayDuplex;
Result.DisplayFPS := FDisplayFPS;
Result.LightTheme := FLightTheme;
// Audio device names
@@ -987,6 +994,7 @@ begin
FRunning := False;
FTransmitting := False;
FSplitTxB := False;
FDisplayDuplex := False;
FMuted := False;
FVolume := 70;
FCenterFreq := FVfoA;
@@ -1541,11 +1549,16 @@ begin
Inc(Y, 76);
// CTUN button
// CTUN / DUP buttons (display-related)
BtnCTun := MakeBtn(PanelLeft, 'CTUN', 2, Y, LEFT_W div 3 - 2, BTN_H, BtnCTunClick);
BtnCTun.Tag := 0;
StyleButton(BtnCTun, FCTun);
BtnDUP := MakeBtn(PanelLeft, 'DUP',
2 + (LEFT_W div 3) + 0, Y, LEFT_W div 3 - 2, BTN_H, BtnDUPClick);
BtnDUP.Tag := 0;
StyleButton(BtnDUP, FDisplayDuplex);
Inc(Y, BTN_H + 4);
// RX controls
@@ -2215,6 +2228,7 @@ begin
for i := 0 to FILT_COUNT-1 do StyleButton(BtnFilter[i], BtnFilter[i].Active);
for i := 0 to 4 do StyleButton(BtnAGCMode[i],BtnAGCMode[i].Active);
StyleButton(BtnCTun, BtnCTun.Active);
StyleButton(BtnDUP, BtnDUP.Active);
StyleButton(BtnNR, BtnNR.Active);
StyleButton(BtnNB, BtnNB.Active);
StyleButton(BtnSNB, BtnSNB.Active);
@@ -2910,6 +2924,12 @@ begin
DUCPkt.DUC0RateLo := Lo(RateKsps);
DUCPkt.DUC0Bits := 24;
DUCPkt.MicLineSelect := BuildMicLineSelectByte;
// ATT on TX — RX-ADC step attenuator во время передачи (Thetis-style protection).
// Дублируем значение во все три ADC-байта: на однопотоковых платах активен только
// ADC0, на Saturn/Hermes Lite 2 — могут использоваться ADC1/ADC2.
DUCPkt.StepAtten0 := Byte(EnsureRange(FTXSettings.AttOnTX, 0, 31));
DUCPkt.StepAtten1 := DUCPkt.StepAtten0;
DUCPkt.StepAtten2 := DUCPkt.StepAtten0;
FNetwork.SendDUCSpecific(DUCPkt);
end;
@@ -3352,6 +3372,8 @@ begin
FSpecView.ResetWfAvgBuf;
FShowSpectrum := G_Settings.ShowSpectrum;
FShowWaterfall := G_Settings.ShowWaterfall;
FDisplayDuplex := G_Settings.DisplayDuplex;
if BtnDUP <> nil then StyleButton(BtnDUP, FDisplayDuplex);
if G_Settings.DisplayFPS > 0 then
ApplyFPS(G_Settings.DisplayFPS);
// Настройки дисплея и сетки
@@ -3866,6 +3888,35 @@ begin
PbWaterfall.Invalidate;
end;
// ---------------------------------------------------------------------------
// DUP toggle (Thetis-style local display mode — без аппаратной команды)
// ---------------------------------------------------------------------------
procedure TMainForm.ApplyDUP(Active: Boolean);
begin
FDisplayDuplex := Active;
StyleButton(BtnDUP, FDisplayDuplex);
// Если сейчас идёт передача — переключаем источник пикселей и сбрасываем
// буферы, чтобы старые TX/RX bin'ы не оставались на следующем кадре.
if FTransmitting then
begin
if FWDSPReady then
FDSPEngine.SetDisplaySourceTX(not FDisplayDuplex);
FSpecView.TXMode := FTransmitting and not FDisplayDuplex;
FSpecView.ResetSpectrumBuf;
FSpecView.ResetWfAvgBuf;
ApplySpecViewGridFromState;
FSpecView.DrawSpectrum;
FSpecView.DrawWaterfall;
PbSpectrum.Invalidate;
PbWaterfall.Invalidate;
end;
end;
procedure TMainForm.BtnDUPClick(Sender: TObject);
begin
ApplyDUP(not FDisplayDuplex);
end;
// ---------------------------------------------------------------------------
// Клик/драг по спектру/водопаду
// ---------------------------------------------------------------------------
@@ -4261,11 +4312,20 @@ begin
FSpecView.TXFreq := ActiveTXFreqHz;
if FSplitTxB then FSpecView.TXVfoIndex := 1
else FSpecView.TXVfoIndex := FActiveVfo;
FSpecView.TXMode := FTransmitting;
// DUP: данные остаются от RX-анализатора (TXMode=False), но TX-полоса
// фильтра рисуется поверх через TXOverlay. Без DUP при TX оба флага True.
FSpecView.TXMode := FTransmitting and not FDisplayDuplex;
FSpecView.TXOverlay := FTransmitting;
// Перерисовываем сетку спектра под активный тракт (Thetis-style).
ApplySpecViewGridFromState;
if FWDSPReady then
begin
FDSPEngine.SetTXRun(FTransmitting);
// SetTXRun переключает FActiveDisplayID на TX_DISP_ID; при DUP возвращаем
// источник на RX, чтобы видеть приём во время передачи.
if FTransmitting and FDisplayDuplex then
FDSPEngine.SetDisplaySourceTX(False);
end;
if FNetwork.Connected and FNetwork.Running then
begin
FNetwork.UpdateState(FCenterFreq, ActiveTXFreqHz, FDriveLevel,
@@ -5029,9 +5089,11 @@ procedure TMainForm.OnTXSettingsChange(const T: TTXSettings);
// перешиваем WDSP TX-цепь и (если поменялись mic-биты) перепосылаем DUC Specific.
var
OldMicByte, NewMicByte: Byte;
OldAtt: Integer;
GridChanged: Boolean;
begin
OldMicByte := BuildMicLineSelectByte;
OldAtt := FTXSettings.AttOnTX;
GridChanged := (FTXSpecRefLevel <> T.TXSpecRefLevel)
or (FTXSpecRange <> T.TXSpecRange)
or (FTXSpecGridStep <> T.TXSpecGridStep);
@@ -5045,7 +5107,7 @@ begin
if GridChanged and FTransmitting then
ApplySpecViewGridFromState;
ApplyTXSettingsToDSP;
if OldMicByte <> NewMicByte then
if (OldMicByte <> NewMicByte) or (OldAtt <> FTXSettings.AttOnTX) then
SendDUCSpecificFromSettings;
// Сохраняем сразу — настройки per-device, попадают в hpsdr_settings.json
if FDevConnected then
@@ -5226,7 +5288,8 @@ end;
procedure TMainForm.ApplySpecViewGridFromState;
begin
if FTransmitting then
// При DUP во время TX данные приходят от RX-анализатора → используем RX-сетку.
if FTransmitting and not FDisplayDuplex then
begin
FSpecView.SpecRefLevel := FTXSpecRefLevel;
FSpecView.SpecRange := FTXSpecRange;