TUN button — Thetis-style тон через WDSP PostGen

- BtnTUN рядом с MOX. Включает SetTXAPostGenRun(TXA, 1) с тоном
  (mode=0, mag=1.0, freq из настроек) и активирует MOX. PTT/HW
  игнорируются во время tune.
- Drive в режиме TUN использует FTXSettings.TUNLevel вместо слайдера,
  per-band калибровка FPABandCal применяется одинаково.
- Settings → Transmit → Tune (TUN): TUNLevel 0..100% (def 10),
  TUNFreq 100..5000 Hz (def 750). Live-обновление во время tune.
This commit is contained in:
2026-05-06 19:16:27 +03:00
parent 88687b4e70
commit 006cf772b8
4 changed files with 145 additions and 11 deletions
+100 -10
View File
@@ -185,6 +185,9 @@ type
// DUP: при TX продолжаем показывать RX1-водопад/панораму, а TX-фильтр
// и TX-линии рисуем поверх. Аппаратной командой не является.
FDisplayDuplex: Boolean;
// TUN: режим тоновой подстройки. WDSP TXA PostGen генерирует чистый
// тон, Drive подменяется на FTXSettings.TUNLevel (через Cal[band]).
FTuning: Boolean;
FHWPTTActive: Boolean; // True = hardware PTT нажата
FMuted: Boolean;
FVolume: Integer;
@@ -329,6 +332,7 @@ type
LblDrv: TLabel;
TrkDrive: TFlatSlider;
BtnMOX: TFlatButton;
BtnTUN: TFlatButton;
PbFwdPower: TPaintBox;
PbSWR: TPaintBox;
LblFwdPwr: TLabel;
@@ -419,6 +423,8 @@ type
procedure BtnCTunClick(Sender: TObject);
procedure BtnDUPClick(Sender: TObject);
procedure ApplyDUP(Active: Boolean);
procedure BtnTUNClick(Sender: TObject);
procedure ApplyTUN(Active: Boolean);
procedure UpdateFilterButtons;
procedure PbSpectrumMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
@@ -995,6 +1001,7 @@ begin
FTransmitting := False;
FSplitTxB := False;
FDisplayDuplex := False;
FTuning := False;
FMuted := False;
FVolume := 70;
FCenterFreq := FVfoA;
@@ -1637,16 +1644,19 @@ begin
TrkDrive.Min := 0; TrkDrive.Max := 100; TrkDrive.Position := 50;
TrkDrive.OnChange := TrkDriveChange;
BtnMOX := MakeBtn(PanelTX, 'MOX', 2, 38, 70, 32, BtnMOXClick);
BtnMOX := MakeBtn(PanelTX, 'MOX', 2, 38, 50, 32, BtnMOXClick);
BtnMOX.Font.Size := 12; BtnMOX.Font.Bold := True;
BtnTUN := MakeBtn(PanelTX, 'TUN', 56, 38, 50, 32, BtnTUNClick);
BtnTUN.Font.Size := 12; BtnTUN.Font.Bold := True;
LblFwdPwr := TLabel.Create(Self);
LblFwdPwr.Parent := PanelTX; LblFwdPwr.Left := 80; LblFwdPwr.Top := 42;
LblFwdPwr.Parent := PanelTX; LblFwdPwr.Left := 112; LblFwdPwr.Top := 42;
LblFwdPwr.Caption := '0W'; LblFwdPwr.Font.Color := CLR_METER_ON;
LblFwdPwr.Font.Name := 'Courier New'; LblFwdPwr.Font.Size := 8;
LblSWRVal := TLabel.Create(Self);
LblSWRVal.Parent := PanelTX; LblSWRVal.Left := 140; LblSWRVal.Top := 42;
LblSWRVal.Parent := PanelTX; LblSWRVal.Left := 156; LblSWRVal.Top := 42;
LblSWRVal.Caption := 'SWR:1.0'; LblSWRVal.Font.Color := CLR_TEXTDIM;
LblSWRVal.Font.Name := 'Courier New'; LblSWRVal.Font.Size := 8;
@@ -2237,6 +2247,7 @@ begin
StyleButton(BtnWfAGC, BtnWfAGC.Active);
StyleButton(BtnWfNF, BtnWfNF.Active);
StyleButton(BtnMOX, BtnMOX.Active);
StyleButton(BtnTUN, BtnTUN.Active);
// Span / VFO-copy / HidePanel
StyleSpanButton(BtnSpan48k, BtnSpan48k.Active);
@@ -2839,8 +2850,9 @@ begin
if HWPTT <> FHWPTTActive then
begin
FHWPTTActive := HWPTT;
// Hardware PTT включает/выключает передачу только если BtnMOX не нажата
if not BtnMOX.Active then
// Hardware PTT игнорируется во время TUN; иначе включает/выключает передачу
// только если кнопка MOX не нажата (приоритет за UI).
if (not FTuning) and (not BtnMOX.Active) then
ApplyMOX(HWPTT);
end;
end;
@@ -3917,6 +3929,50 @@ begin
ApplyDUP(not FDisplayDuplex);
end;
// ---------------------------------------------------------------------------
// TUN — Thetis-style тон: WDSP TXA PostGen + MOX с отдельным уровнем Drive
// ---------------------------------------------------------------------------
procedure TMainForm.ApplyTUN(Active: Boolean);
begin
if Active = FTuning then Exit;
if Active then
begin
// Включаем тон ДО запуска TX-аудио, чтобы первые сэмплы уже шли с тоном
if FWDSPReady then
FDSPEngine.SetTXTone(True, FTXSettings.TUNFreq, 1.0);
FTuning := True;
StyleButton(BtnTUN, True);
// Drive байт пересчитываем (CalcDriveByte увидит FTuning=True)
FDriveLevel := CalcDriveByte;
if FWDSPReady then
FDSPEngine.SetDriveLevel(FTXSettings.TUNLevel / 100.0);
// Активируем TX (то же что MOX — PTT, run TXA-канал)
if not FTransmitting then ApplyMOX(True);
end else
begin
// Выключаем TX, потом тон, потом восстанавливаем drive
if FTransmitting then ApplyMOX(False);
if FWDSPReady then
FDSPEngine.SetTXTone(False, 0, 0);
FTuning := False;
StyleButton(BtnTUN, False);
FDriveLevel := CalcDriveByte;
if FWDSPReady then
FDSPEngine.SetDriveLevel(TrkDrive.Position / 100.0);
if FNetwork.Connected and FNetwork.Running then
begin
FNetwork.UpdateState(FCenterFreq, ActiveTXFreqHz, FDriveLevel,
FTransmitting, True, True);
FNetwork.SendFullHP;
end;
end;
end;
procedure TMainForm.BtnTUNClick(Sender: TObject);
begin
ApplyTUN(not FTuning);
end;
// ---------------------------------------------------------------------------
// Клик/драг по спектру/водопаду
// ---------------------------------------------------------------------------
@@ -4336,6 +4392,12 @@ end;
procedure TMainForm.BtnMOXClick(Sender: TObject);
begin
// Если идёт tune — клик MOX просто выходит из tune (без перехода на голос).
if FTuning then
begin
ApplyTUN(False);
Exit;
end;
ApplyMOX(not FTransmitting);
end;
@@ -5034,13 +5096,21 @@ begin
end;
function TMainForm.CalcDriveByte: Byte;
var Cal: Double;
var
Cal: Double;
Pos: Integer;
begin
if (FCurrentBand >= 0) and (FCurrentBand < BAND_COUNT) and (FPABandCal[FCurrentBand] > 0.0) then
Cal := FPABandCal[FCurrentBand]
else
Cal := 100.0;
Result := Round(TrkDrive.Position / 100.0 * Cal / 100.0 * 255.0);
// В режиме TUN используем отдельный per-device уровень (см. TTXSettings.TUNLevel),
// чтобы tune не зависел от Drive-слайдера. Per-band калибровка — общая для обоих.
if FTuning then
Pos := EnsureRange(FTXSettings.TUNLevel, 0, 100)
else
Pos := TrkDrive.Position;
Result := Round(Pos / 100.0 * Cal / 100.0 * 255.0);
end;
procedure TMainForm.OnTXIQReady(const Buf: array of Double; Count: Integer);
@@ -5089,11 +5159,13 @@ procedure TMainForm.OnTXSettingsChange(const T: TTXSettings);
// перешиваем WDSP TX-цепь и (если поменялись mic-биты) перепосылаем DUC Specific.
var
OldMicByte, NewMicByte: Byte;
OldAtt: Integer;
OldAtt, OldTUNLevel, OldTUNFreq: Integer;
GridChanged: Boolean;
begin
OldMicByte := BuildMicLineSelectByte;
OldAtt := FTXSettings.AttOnTX;
OldMicByte := BuildMicLineSelectByte;
OldAtt := FTXSettings.AttOnTX;
OldTUNLevel := FTXSettings.TUNLevel;
OldTUNFreq := FTXSettings.TUNFreq;
GridChanged := (FTXSpecRefLevel <> T.TXSpecRefLevel)
or (FTXSpecRange <> T.TXSpecRange)
or (FTXSpecGridStep <> T.TXSpecGridStep);
@@ -5109,6 +5181,24 @@ begin
ApplyTXSettingsToDSP;
if (OldMicByte <> NewMicByte) or (OldAtt <> FTXSettings.AttOnTX) then
SendDUCSpecificFromSettings;
// TUN-параметры live: если идёт tune — обновить тон/drive, иначе просто запомнить.
if FTuning then
begin
if (OldTUNFreq <> FTXSettings.TUNFreq) and FWDSPReady then
FDSPEngine.SetTXTone(True, FTXSettings.TUNFreq, 1.0);
if OldTUNLevel <> FTXSettings.TUNLevel then
begin
FDriveLevel := CalcDriveByte;
if FWDSPReady then
FDSPEngine.SetDriveLevel(FTXSettings.TUNLevel / 100.0);
if FNetwork.Connected and FNetwork.Running then
begin
FNetwork.UpdateState(FCenterFreq, ActiveTXFreqHz, FDriveLevel,
FTransmitting, True, True);
FNetwork.SendFullHP;
end;
end;
end;
// Сохраняем сразу — настройки per-device, попадают в hpsdr_settings.json
if FDevConnected then
begin
+12
View File
@@ -47,6 +47,11 @@ type
// ATT on TX — ослабление RX-ADC во время передачи (защита от утечки TX).
// DUC Specific bytes 57/58/59 (StepAtten 0/1/2). Thetis default: 31 dB.
AttOnTX: Integer; // 0..31 dB
// Tune (кнопка TUN) — генерация тонального сигнала через WDSP PostGen.
// Уровень — отдельный от слайдера Drive, проходит через ту же
// per-band калибровку. Thetis defaults: level=10 %, freq=750 Hz.
TUNLevel: Integer; // 0..100 (% от полной шкалы Drive)
TUNFreq: Integer; // Hz, обычно 600..1500 для SSB tune
// TX bandpass filter
FilterLow: Integer; // Hz, для SSB обычно 100
FilterHigh: Integer; // Hz, для SSB обычно 2800
@@ -730,6 +735,9 @@ begin
T.MicGainDB := 10.0;
// ATT on TX — Thetis default 31 dB (полная защита RX-ADC во время передачи)
T.AttOnTX := 31;
// Tune — Thetis defaults
T.TUNLevel := 10;
T.TUNFreq := 750;
// SSB-фильтр по умолчанию
T.FilterLow := 100;
T.FilterHigh := 2800;
@@ -804,6 +812,8 @@ begin
T.MicPTTEnabled := JB(O,'mic_ptt_enabled', T.MicPTTEnabled);
T.MicGainDB := JD(O,'mic_gain_db', T.MicGainDB);
T.AttOnTX := EnsureRange(JI(O,'att_on_tx', T.AttOnTX), 0, 31);
T.TUNLevel := EnsureRange(JI(O,'tun_level', T.TUNLevel), 0, 100);
T.TUNFreq := EnsureRange(JI(O,'tun_freq', T.TUNFreq), 100, 5000);
T.FilterLow := JI(O,'filter_low', T.FilterLow);
T.FilterHigh := JI(O,'filter_high', T.FilterHigh);
T.FilterNC := JI(O,'filter_nc', T.FilterNC);
@@ -859,6 +869,8 @@ begin
JW(O,'mic_ptt_enabled', T.MicPTTEnabled);
JW(O,'mic_gain_db', T.MicGainDB);
JW(O,'att_on_tx', T.AttOnTX);
JW(O,'tun_level', T.TUNLevel);
JW(O,'tun_freq', T.TUNFreq);
JW(O,'filter_low', T.FilterLow);
JW(O,'filter_high', T.FilterHigh);
JW(O,'filter_nc', T.FilterNC);
+17 -1
View File
@@ -123,6 +123,9 @@ type
FChkTXMicPTT: TCheckBox;
FEdTXMicGain: TFloatSpinEdit;
FEdTXAttOnTX: TSpinEdit;
// Tune (TUN button)
FEdTUNLevel: TSpinEdit;
FEdTUNFreq: TSpinEdit;
// Filter
FEdTXFiltLow: TSpinEdit;
FEdTXFiltHigh: TSpinEdit;
@@ -958,8 +961,17 @@ begin
MakeLbl(Grp, 'ATT on TX (dB)', PAD + 320, R1 + 3 * STEP + 5, 110);
FEdTXAttOnTX := MkSpin(Grp, PAD + 320 + 120, R1 + 3 * STEP, 0, 31, 31);
// ---- TX bandpass filter ----
// ---- Tune (TUN button) ----
Inc(Y, 220 + 12);
Grp := MakeGroupPanel(FPageTransmit, 'Tune (TUN)', MARGIN, Y, 700, 100);
MakeLbl(Grp, 'Tune level (%)', PAD, R1 + 5, LW);
FEdTUNLevel := MkSpin(Grp, CX, R1, 0, 100, 10);
MakeLbl(Grp, 'Tone freq (Hz)', PAD + 320, R1 + 5, 110);
FEdTUNFreq := MkSpin(Grp, PAD + 320 + 120, R1, 100, 5000, 750);
// ---- TX bandpass filter ----
Inc(Y, 100 + 12);
Grp := MakeGroupPanel(FPageTransmit, 'TX Filter (bandpass)', MARGIN, Y, 700, 200);
MakeLbl(Grp, 'Low cut (Hz)', PAD, R1 + 5, LW);
@@ -1138,6 +1150,8 @@ begin
if FChkTXMicPTT <> nil then FTX.MicPTTEnabled := FChkTXMicPTT.Checked;
if FEdTXMicGain <> nil then FTX.MicGainDB := FEdTXMicGain.Value;
if FEdTXAttOnTX <> nil then FTX.AttOnTX := FEdTXAttOnTX.Value;
if FEdTUNLevel <> nil then FTX.TUNLevel := FEdTUNLevel.Value;
if FEdTUNFreq <> nil then FTX.TUNFreq := FEdTUNFreq.Value;
if FEdTXFiltLow <> nil then FTX.FilterLow := FEdTXFiltLow.Value;
if FEdTXFiltHigh <> nil then FTX.FilterHigh := FEdTXFiltHigh.Value;
if FCmbTXFiltNC <> nil then
@@ -1223,6 +1237,8 @@ begin
if FChkTXMicPTT <> nil then FChkTXMicPTT.Checked := T.MicPTTEnabled;
if FEdTXMicGain <> nil then FEdTXMicGain.Value := T.MicGainDB;
if FEdTXAttOnTX <> nil then FEdTXAttOnTX.Value := EnsureRange(T.AttOnTX, 0, 31);
if FEdTUNLevel <> nil then FEdTUNLevel.Value := EnsureRange(T.TUNLevel, 0, 100);
if FEdTUNFreq <> nil then FEdTUNFreq.Value := EnsureRange(T.TUNFreq, 100, 5000);
if FEdTXFiltLow <> nil then FEdTXFiltLow.Value := T.FilterLow;
if FEdTXFiltHigh <> nil then FEdTXFiltHigh.Value := T.FilterHigh;
if FCmbTXFiltNC <> nil then FCmbTXFiltNC.Text := IntToStr(T.FilterNC);
+16
View File
@@ -354,6 +354,9 @@ type
// (SetTXRun меняет и аудио, и источник; этот метод позволяет вернуть
// RX-источник пока TX-аудио продолжает работать).
procedure SetDisplaySourceTX(UseTX: Boolean);
// TUN: WDSP TXA PostGen — гонит чистый тон в DSP-цепь после всей
// обработки. Mag — амплитуда в [0..1], Freq — Hz (audio).
procedure SetTXTone(Enabled: Boolean; FreqHz, Mag: Double);
// Расширенное TX управление (применяются мгновенно если канал открыт)
procedure SetTXFilterFull(Low, High, NC, MP, Window: Integer);
@@ -1506,6 +1509,19 @@ begin
end;
end;
procedure TWDSPEngine.SetTXTone(Enabled: Boolean; FreqHz, Mag: Double);
begin
if not FInitialized then Exit;
if Enabled then
begin
SetTXAPostGenMode(TXA_CHAN, 0); // 0 = tone
SetTXAPostGenToneFreq(TXA_CHAN, FreqHz);
SetTXAPostGenToneMag(TXA_CHAN, Max(0.0, Min(1.0, Mag)));
SetTXAPostGenRun(TXA_CHAN, 1);
end else
SetTXAPostGenRun(TXA_CHAN, 0);
end;
procedure TWDSPEngine.SetDisplaySourceTX(UseTX: Boolean);
begin
// Тот же сброс позиций, что и в SetTXRun — analyzer-буферы у обоих DispID