From 006cf772b845a8f52d4a7992db5980a9c7d0e00b Mon Sep 17 00:00:00 2001 From: Uladzimir Karpenka Date: Wed, 6 May 2026 19:16:27 +0300 Subject: [PATCH] =?UTF-8?q?TUN=20button=20=E2=80=94=20Thetis-style=20?= =?UTF-8?q?=D1=82=D0=BE=D0=BD=20=D1=87=D0=B5=D1=80=D0=B5=D0=B7=20WDSP=20Po?= =?UTF-8?q?stGen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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. --- MainForm.pas | 110 ++++++++++++++++++++++++++++++++++++++++++----- Settings.pas | 12 ++++++ SettingsForm.pas | 18 +++++++- WDSPEngine.pas | 16 +++++++ 4 files changed, 145 insertions(+), 11 deletions(-) diff --git a/MainForm.pas b/MainForm.pas index be79697..066d49a 100644 --- a/MainForm.pas +++ b/MainForm.pas @@ -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 diff --git a/Settings.pas b/Settings.pas index 96508f6..16b862e 100644 --- a/Settings.pas +++ b/Settings.pas @@ -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); diff --git a/SettingsForm.pas b/SettingsForm.pas index 07e23cd..b74641e 100644 --- a/SettingsForm.pas +++ b/SettingsForm.pas @@ -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); diff --git a/WDSPEngine.pas b/WDSPEngine.pas index dfedfc4..1e4c4af 100644 --- a/WDSPEngine.pas +++ b/WDSPEngine.pas @@ -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