add trancverter

This commit is contained in:
2026-05-14 20:24:50 +03:00
parent aea9f02df8
commit 020b9422e0
6 changed files with 1083 additions and 66 deletions
+448 -55
View File
@@ -160,6 +160,8 @@ type
FAudioInDevName: string; // текущее имя входного устройства (TX mic)
FTXSettings: TTXSettings; // полный TX-конфиг (загружается per-MAC)
FAlexSettings: TAlexSettings; // per-band antenna/routing config (Alex board)
FXvtrSettings: TXvtrSettings; // transverter slots (per-device)
FCurrentXvtr: Integer; // -1 = HF, 0..CFG_XVTR_COUNT-1 = active XVTR
FWDSPReady: Boolean; // True когда WDSP открыт и работает
// ---- State ----
@@ -317,6 +319,9 @@ type
PanelBands: TPanel;
BtnBand: array[0..BAND_COUNT-1] of TFlatButton;
// Динамические кнопки XVTR (создаются/удаляются по мере того,
// как пользователь enabled/disabled слоты в Settings → Transverter).
BtnXvtrBand: array[0..CFG_XVTR_COUNT-1] of TFlatButton;
PanelMode: TPanel;
BtnMode: array[0..MODE_COUNT-1] of TFlatButton;
@@ -497,6 +502,7 @@ type
procedure WebOnAttn(Idx: Integer);
procedure WebOnTun(On_: Boolean);
procedure WebOnMic(Samples: PSingle; Count: Integer);
procedure WebOnXvtrBand(Idx: Integer);
// Synchronize-обёртки (выполняются в UI-потоке)
procedure SyncWebFreq;
procedure SyncWebMode;
@@ -504,6 +510,7 @@ type
procedure SyncWebAGC;
procedure SyncWebAGCTop;
procedure SyncWebBand;
procedure SyncWebXvtrBand;
procedure SyncWebSpan;
procedure SyncWebVolume;
procedure SyncWebWfAGC;
@@ -562,6 +569,16 @@ type
const SerBaud, SerDataBits, SerStopBits, SerParity: array of Integer;
TcpEnabled: Boolean; TcpPort: Integer);
procedure OnAlexSettingsChange(const A: TAlexSettings);
procedure OnXvtrSettingsChange(const X: TXvtrSettings);
procedure PushXvtrToWeb;
// XVTR helpers
function FindXvtrIdxForFreq(FreqHz: Double): Integer;
function XvtrTranslate(VisibleHz: Double): Double;
procedure ApplyXvtrToNetwork;
procedure RebuildXvtrButtons;
procedure BtnXvtrBandClick(Sender: TObject);
procedure ActivateXvtrBand(Idx: Integer);
procedure DeactivateXvtr;
// -------------------------------------------------------------------------
procedure BtnWfAGCClick(Sender: TObject);
procedure BtnWfNFClick(Sender: TObject);
@@ -865,6 +882,7 @@ begin
Result.WfAGCEnabled := FWfAGCEnabled;
Result.WfNFEnabled := FWfNFEnabled;
Result.LastBand := FCurrentBand;
Result.LastXvtr := FCurrentXvtr;
Result.SampleRate := FSampleRate;
// Display settings
if FWDSPReady then
@@ -918,6 +936,18 @@ end;
procedure TMainForm.SaveCurrentBand;
begin
if not FDevConnected then Exit;
// В XVTR-режиме НЕ трогаем HF band-cache: FBandCache[FCurrentBand]
// относится к HF-диапазону, на котором пользователь был ДО активации
// трансвертера, и FVfoA сейчас на XVTR-частоте (144.x, 432.x и т.д.).
// Запись в FBandCache[FCurrentBand] стерла бы корректное HF-состояние.
// Вместо этого сохраняем LastFreq в XVTR-настройках.
if FCurrentXvtr >= 0 then
begin
if (FCurrentXvtr < CFG_XVTR_COUNT) then
FXvtrSettings.Entries[FCurrentXvtr].LastFreq := FVfoA;
FSettings.SaveXvtr(FDevMAC, FXvtrSettings);
Exit;
end;
FBandCache[FCurrentBand] := MakeBandSettings;
FSettings.SaveBand(FDevMAC, FCurrentBand, FBandCache[FCurrentBand]);
end;
@@ -997,6 +1027,10 @@ begin
FSettings.SaveGlobal(FDevMAC, MakeGlobalSettings);
if FDevConnected then
FSettings.SaveTX(FDevMAC, FTXSettings);
// Запомнить LastFreq XVTR для следующего запуска
if (FCurrentXvtr >= 0) and (FCurrentXvtr < CFG_XVTR_COUNT) then
FXvtrSettings.Entries[FCurrentXvtr].LastFreq := FVfoA;
FSettings.SaveXvtr(FDevMAC, FXvtrSettings);
FSettings.Save;
end;
end;
@@ -1107,7 +1141,9 @@ begin
FWebServer.OnAttn := WebOnAttn;
FWebServer.OnTun := WebOnTun;
FWebServer.OnWebMic := WebOnMic;
FWebServer.OnXvtrBand := WebOnXvtrBand;
FWebServer.Start;
PushXvtrToWeb;
FillChar(FCATLastGlobal, SizeOf(FCATLastGlobal), 0);
FCATLastGlobal.CATTcpPort := 19090;
for i := 0 to 3 do
@@ -1175,9 +1211,11 @@ begin
FNetwork.OnHPStatus := OnHPStatusCB;
FNetwork.OnDDCIQ := OnDDCIQCB;
FNetwork.OnMicPacket := OnMicPacketCB;
// Дефолты TX и Alex (на случай если устройство ещё не выбрано).
// Дефолты TX, Alex и XVTR (на случай если устройство ещё не выбрано).
TSettingsManager.DefaultTX(FTXSettings);
TSettingsManager.DefaultAlex(FAlexSettings);
TSettingsManager.DefaultXvtr(FXvtrSettings);
FCurrentXvtr := -1;
BuildUI;
StyleSpanButton(BtnSpan48k, FSampleRate = 48000);
@@ -1242,7 +1280,7 @@ begin
FMeterTimer.Enabled := False;
FSpectrumTimer.Enabled := False;
if FNetwork.Running then
FNetwork.SetRunAndFreq(False, FCenterFreq, FCenterFreq, 0);
FNetwork.SetRunAndFreq(False, XvtrTranslate(FCenterFreq), XvtrTranslate(FCenterFreq), 0);
FNetwork.Disconnect;
FNetwork.Free;
FAudioOut.Close;
@@ -1280,8 +1318,8 @@ begin
begin
if FRunning then
begin
FNetwork.UpdateState(FCenterFreq, ActiveTXFreqHz, 0, False, True, True);
FNetwork.SetRunAndFreq(False, FCenterFreq, FCenterFreq, 0);
FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslate(ActiveTXFreqHz), 0, False, True, True);
FNetwork.SetRunAndFreq(False, XvtrTranslate(FCenterFreq), XvtrTranslate(FCenterFreq), 0);
FRunning := False;
FTransmitting := False;
if FWDSPReady then FDSPEngine.SetTXRun(False);
@@ -1529,10 +1567,12 @@ begin
LayoutTopVfoBlock;
// Band selector
// Band selector. Высота резервирует 2 дополнительных ряда под XVTR-кнопки
// (создаются динамически в RebuildXvtrButtons). В каждом ряду до 6 кнопок,
// итого до 12 XVTR-кнопок (наш CFG_XVTR_COUNT = 8 умещается с запасом).
PanelBands := TPanel.Create(Self);
PanelBands.Parent := PanelLeft;
PanelBands.SetBounds(0, Y, LEFT_W, 74);
PanelBands.SetBounds(0, Y, LEFT_W, 74 + 54);
PanelBands.BevelOuter := bvNone;
MakeLbl(PanelBands, 'BAND', 4, 2);
W := (LEFT_W - 6) div 6;
@@ -1545,8 +1585,11 @@ begin
B.Tag := i;
BtnBand[i] := B;
end;
// XVTR кнопки создаются позже (в RebuildXvtrButtons), их Tag = -(idx+1)
for i := 0 to CFG_XVTR_COUNT - 1 do
BtnXvtrBand[i] := nil;
Inc(Y, 76);
Inc(Y, 76 + 54);
// Mode selector
PanelMode := TPanel.Create(Self);
@@ -2669,7 +2712,7 @@ begin
BtnStartStop.Caption := 'START';
StyleButton(BtnStartStop, False);
FRunning := False;
FNetwork.SetRunAndFreq(False, FCenterFreq, FCenterFreq, 0);
FNetwork.SetRunAndFreq(False, XvtrTranslate(FCenterFreq), XvtrTranslate(FCenterFreq), 0);
StatusBar1.Panels[4].Text := 'ТРАНСИВЕР НЕДОСТУПЕН';
BtnMOX.Enabled := False;
Exit;
@@ -2680,7 +2723,7 @@ begin
BtnStartStop.Caption := 'START';
StyleButton(BtnStartStop, False);
FRunning := False;
FNetwork.SetRunAndFreq(False, FCenterFreq, FCenterFreq, 0);
FNetwork.SetRunAndFreq(False, XvtrTranslate(FCenterFreq), XvtrTranslate(FCenterFreq), 0);
StatusBar1.Panels[4].Text := 'ПОТЕРЯ СВЯЗИ С ТРАНСИВЕРОМ';
BtnMOX.Enabled := False;
Exit;
@@ -3321,8 +3364,8 @@ begin
// --- STOP ---
if FRunning then
begin
FNetwork.UpdateState(FCenterFreq, ActiveTXFreqHz, 0, False, True, True);
FNetwork.SetRunAndFreq(False, FCenterFreq, FCenterFreq, 0);
FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslate(ActiveTXFreqHz), 0, False, True, True);
FNetwork.SetRunAndFreq(False, XvtrTranslate(FCenterFreq), XvtrTranslate(FCenterFreq), 0);
FRunning := False;
FTransmitting := False;
if FWDSPReady then FDSPEngine.SetTXRun(False);
@@ -3337,6 +3380,10 @@ begin
SaveCurrentBand;
FSettings.SaveGlobal(FDevMAC, MakeGlobalSettings);
FSettings.SaveAlex(FDevMAC, FAlexSettings);
// XVTR LastFreq → актуальная видимая частота в момент остановки
if (FCurrentXvtr >= 0) and (FCurrentXvtr < CFG_XVTR_COUNT) then
FXvtrSettings.Entries[FCurrentXvtr].LastFreq := FVfoA;
FSettings.SaveXvtr(FDevMAC, FXvtrSettings);
end;
FSettings.SaveStartupPreview(FVfoA, FVfoB, FSampleRate);
FSettings.Save;
@@ -3474,6 +3521,12 @@ begin
// Alex-настройки per-device (антенны/маршрутизация)
FSettings.LoadAlex(FDevMAC, FAlexSettings);
FNetwork.SetAlexConfig(FAlexSettings);
// XVTR-настройки per-device (трансвертеры)
FSettings.LoadXvtr(FDevMAC, FXvtrSettings);
FCurrentXvtr := -1;
RebuildXvtrButtons;
ApplyXvtrToNetwork;
PushXvtrToWeb;
FTXSpecRefLevel := FTXSettings.TXSpecRefLevel;
FTXSpecRange := FTXSettings.TXSpecRange;
if FTXSettings.TXSpecGridStep > 0 then
@@ -3566,6 +3619,14 @@ begin
end;
RestoreBand(FCurrentBand);
// Восстанавливаем XVTR-режим, если он был активен при выходе.
// FCurrentBand уже загружен (HF-диапазон, на который пользователь зашёл
// до XVTR), его FBandCache не пострадает — ActivateXvtrBand только
// выставит FVfoA на XVTR-частоту и FCurrentXvtr.
if (G_Settings.LastXvtr >= 0) and (G_Settings.LastXvtr < CFG_XVTR_COUNT)
and FXvtrSettings.Entries[G_Settings.LastXvtr].Enabled then
ActivateXvtrBand(G_Settings.LastXvtr);
StatusBar1.Panels[0].Text := 'IP: ' + FNetwork.Device.IPAddress;
StatusBar1.Panels[1].Text := 'Board: ' + BoardTypeName(FNetwork.Device.BoardType);
@@ -3609,8 +3670,8 @@ begin
// Сначала отправляем Run=1 — эмулятор/железо создают ddc_specific_thread
// (порт 1025) и rx_thread (порт 1035+) только после получения HP Run=1.
// DDC Specific и DUC Specific отправляем ПОСЛЕ, иначе порты ещё не открыты.
FNetwork.UpdateState(FCenterFreq, ActiveTXFreqHz, FDriveLevel, False, True, True);
FNetwork.SetRunAndFreq(True, FCenterFreq, FCenterFreq, FDriveLevel);
FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslate(ActiveTXFreqHz), FDriveLevel, False, True, True);
FNetwork.SetRunAndFreq(True, XvtrTranslate(FCenterFreq), XvtrTranslate(FCenterFreq), FDriveLevel);
FRunning := True;
FRXStartTime := GetTickCount64;
FRXLastPktTime := 0;
@@ -3720,7 +3781,7 @@ begin
if FWDSPReady then FDSPEngine.SetShift(0.0);
if FNetwork.Connected and FNetwork.Running then
begin
FNetwork.UpdateState(FCenterFreq, ActiveTXFreqHz, FDriveLevel, FTransmitting, True, True);
FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslate(ActiveTXFreqHz), FDriveLevel, FTransmitting, True, True);
FNetwork.SendFullHP;
end;
end;
@@ -3732,7 +3793,7 @@ begin
if FWDSPReady then FDSPEngine.SetShift(0.0);
if FNetwork.Connected and FNetwork.Running then
begin
FNetwork.UpdateState(FCenterFreq, ActiveTXFreqHz, FDriveLevel, FTransmitting, True, True);
FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslate(ActiveTXFreqHz), FDriveLevel, FTransmitting, True, True);
FNetwork.SendFullHP;
end;
end;
@@ -3782,7 +3843,7 @@ begin
if FNetwork.Connected and FNetwork.Running then
begin
FNetwork.UpdateState(FCenterFreq, ActiveTXFreqHz, FDriveLevel,
FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslate(ActiveTXFreqHz), FDriveLevel,
FTransmitting, True, True);
FNetwork.SendFullHP;
end;
@@ -3804,7 +3865,7 @@ begin
if FNetwork.Connected and FNetwork.Running then
begin
FNetwork.UpdateState(FCenterFreq, ActiveTXFreqHz, FDriveLevel,
FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslate(ActiveTXFreqHz), FDriveLevel,
FTransmitting, True, True);
FNetwork.SendFullHP;
end;
@@ -3830,7 +3891,7 @@ begin
if FWDSPReady then FDSPEngine.SetShift(0.0);
if FNetwork.Connected and FNetwork.Running then
begin
FNetwork.UpdateState(FCenterFreq, ActiveTXFreqHz, FDriveLevel, FTransmitting, True, True);
FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslate(ActiveTXFreqHz), FDriveLevel, FTransmitting, True, True);
FNetwork.SendFullHP;
end;
// Обновляем диапазон
@@ -3914,6 +3975,15 @@ var
Scrolled: Boolean;
BandIdx: Integer;
begin
// Клэмп в пределах XVTR-band — не даём VFO уйти за FreqBegin/FreqEnd
if (FCurrentXvtr >= 0) and (FCurrentXvtr < CFG_XVTR_COUNT) and
FXvtrSettings.Entries[FCurrentXvtr].Enabled then
begin
if NewFreq < FXvtrSettings.Entries[FCurrentXvtr].FreqBegin then
NewFreq := Round(FXvtrSettings.Entries[FCurrentXvtr].FreqBegin);
if NewFreq > FXvtrSettings.Entries[FCurrentXvtr].FreqEnd then
NewFreq := Round(FXvtrSettings.Entries[FCurrentXvtr].FreqEnd);
end;
FVfoA := NewFreq;
Scrolled := False;
@@ -3962,19 +4032,24 @@ begin
// Обновляем VFO дисплей
FreqDispA.Frequency := Round(FVfoA);
// Обновляем кнопку диапазона если частота перешла в другой диапазон
BandIdx := FreqToBandIdx(FVfoA);
if (BandIdx >= 0) and (BandIdx <> FCurrentBand) then
// Обновляем кнопку диапазона если частота перешла в другой диапазон.
// В XVTR-режиме HF band-кнопки не подсвечиваем (мы клэмпим VFO внутри
// XVTR-диапазона — FreqToBandIdx всегда вернёт -1 для VHF/UHF).
if FCurrentXvtr < 0 then
begin
StyleButton(BtnBand[FCurrentBand], False);
FCurrentBand := BandIdx;
StyleButton(BtnBand[FCurrentBand], True);
BandIdx := FreqToBandIdx(FVfoA);
if (BandIdx >= 0) and (BandIdx <> FCurrentBand) then
begin
StyleButton(BtnBand[FCurrentBand], False);
FCurrentBand := BandIdx;
StyleButton(BtnBand[FCurrentBand], True);
end;
end;
// DDC: передаём FCenterFreq (при CTUN OFF = FVfoA, при CTUN ON = фиксирован)
if FNetwork.Connected and FNetwork.Running then
begin
FNetwork.UpdateState(FCenterFreq, ActiveTXFreqHz, FDriveLevel, FTransmitting, True, True);
FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslate(ActiveTXFreqHz), FDriveLevel, FTransmitting, True, True);
FNetwork.SendFullHP;
end;
@@ -4011,7 +4086,7 @@ begin
FDSPEngine.SetShift(0.0);
if FNetwork.Connected and FNetwork.Running then
begin
FNetwork.UpdateState(FCenterFreq, ActiveTXFreqHz, FDriveLevel, FTransmitting, True, True);
FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslate(ActiveTXFreqHz), FDriveLevel, FTransmitting, True, True);
FNetwork.SendFullHP;
end;
end
@@ -4062,6 +4137,14 @@ end;
procedure TMainForm.ApplyTUN(Active: Boolean);
begin
if Active = FTuning then Exit;
// Safety: те же блокировки что в ApplyMOX. TUN — это TX через WDSP PostGen,
// он тоже должен молчать на бэндах с DoNotTx или на RX-only XVTR.
if Active and FDevConnected and (FCurrentXvtr < 0)
and FAlexSettings.DoNotTx[EnsureRange(FCurrentBand, 0, 10)] then
Exit;
if Active and (FCurrentXvtr >= 0) and (FCurrentXvtr < CFG_XVTR_COUNT)
and FXvtrSettings.Entries[FCurrentXvtr].RXOnly then
Exit;
if Active then
begin
// Включаем тон ДО запуска TX-аудио, чтобы первые сэмплы уже шли с тоном
@@ -4088,7 +4171,7 @@ begin
FDSPEngine.SetDriveLevel(TrkDrive.Position / 100.0);
if FNetwork.Connected and FNetwork.Running then
begin
FNetwork.UpdateState(FCenterFreq, ActiveTXFreqHz, FDriveLevel,
FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslate(ActiveTXFreqHz), FDriveLevel,
FTransmitting, True, True);
FNetwork.SendFullHP;
end;
@@ -4131,7 +4214,7 @@ begin
if FWDSPReady then FDSPEngine.SetShift(0.0);
if FNetwork.Connected and FNetwork.Running then
begin
FNetwork.UpdateState(FCenterFreq, ActiveTXFreqHz, FDriveLevel, FTransmitting, True, True);
FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslate(ActiveTXFreqHz), FDriveLevel, FTransmitting, True, True);
FNetwork.SendFullHP;
end;
BandIdx := FreqToBandIdx(FVfoB);
@@ -4171,7 +4254,7 @@ begin
end;
if FNetwork.Connected and FNetwork.Running then
begin
FNetwork.UpdateState(FCenterFreq, ActiveTXFreqHz, FDriveLevel, FTransmitting, True, True);
FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslate(ActiveTXFreqHz), FDriveLevel, FTransmitting, True, True);
FNetwork.SendFullHP;
end;
FSpectrumDirty := True;
@@ -4189,7 +4272,7 @@ begin
if FWDSPReady then FDSPEngine.SetShift(0.0);
if FNetwork.Connected and FNetwork.Running then
begin
FNetwork.UpdateState(FCenterFreq, ActiveTXFreqHz, FDriveLevel, FTransmitting, True, True);
FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslate(ActiveTXFreqHz), FDriveLevel, FTransmitting, True, True);
FNetwork.SendFullHP;
end;
FSpectrumDirty := True;
@@ -4328,19 +4411,23 @@ var
begin
NewBand := (Sender as TFlatButton).Tag;
if (NewBand < 0) or (NewBand >= BAND_COUNT) then Exit;
if NewBand = FCurrentBand then Exit; // уже на этом диапазоне
// Если в XVTR-режиме — DeactivateXvtr вернёт HF-состояние (RestoreBand
// на сохранённом FCurrentBand). Если NewBand совпадает с восстановленным
// FCurrentBand, дальше делать ничего не нужно.
if FCurrentXvtr >= 0 then
begin
DeactivateXvtr;
if NewBand = FCurrentBand then Exit;
end
else if NewBand = FCurrentBand then
Exit;
// Сохраняем настройки текущего диапазона перед сменой
if FDevConnected then
begin
SaveCurrentBand;
FSettings.Save;
end;
// Переключаем диапазон
FCurrentBand := NewBand;
// Восстанавливаем настройки нового диапазона
RestoreBand(FCurrentBand);
end;
@@ -4421,7 +4508,7 @@ begin
if FWDSPReady then FDSPEngine.SetShift(0.0);
if FNetwork.Connected and FNetwork.Running then
begin
FNetwork.UpdateState(FCenterFreq, ActiveTXFreqHz, FDriveLevel, FTransmitting, True, True);
FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslate(ActiveTXFreqHz), FDriveLevel, FTransmitting, True, True);
FNetwork.SendFullHP;
end;
@@ -4453,7 +4540,7 @@ begin
if FWDSPReady then FDSPEngine.SetShift(0.0);
if FNetwork.Connected and FNetwork.Running then
begin
FNetwork.UpdateState(FCenterFreq, ActiveTXFreqHz, FDriveLevel, FTransmitting, True, True);
FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslate(ActiveTXFreqHz), FDriveLevel, FTransmitting, True, True);
FNetwork.SendFullHP;
end;
FSpecView.InvalidateRulerCache;
@@ -4490,9 +4577,13 @@ var
WasTransmitting: Boolean;
begin
// Safety: block TX on bands marked DoNotTx in Alex config
if Active and FDevConnected
if Active and FDevConnected and (FCurrentXvtr < 0)
and FAlexSettings.DoNotTx[EnsureRange(FCurrentBand, 0, 10)] then
Exit;
// Safety: block TX on RX-only transverter
if Active and (FCurrentXvtr >= 0) and (FCurrentXvtr < CFG_XVTR_COUNT)
and FXvtrSettings.Entries[FCurrentXvtr].RXOnly then
Exit;
WasTransmitting := FTransmitting;
FTransmitting := Active;
FDUCPendingCount := 0;
@@ -4517,7 +4608,7 @@ begin
if FTransmitting and FNetwork.Connected and FNetwork.Running then
begin
FNetwork.ClearDUCIQQueue;
FNetwork.UpdateState(FCenterFreq, ActiveTXFreqHz, FDriveLevel,
FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslate(ActiveTXFreqHz), FDriveLevel,
True, True, True);
FNetwork.SendFullHP;
end;
@@ -4563,7 +4654,7 @@ begin
end;
if (not FTransmitting) and FNetwork.Connected and FNetwork.Running then
begin
FNetwork.UpdateState(FCenterFreq, ActiveTXFreqHz, FDriveLevel,
FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslate(ActiveTXFreqHz), FDriveLevel,
FTransmitting, True, True);
FNetwork.ClearDUCIQQueue;
FNetwork.SendFullHP;
@@ -4828,7 +4919,7 @@ begin
FCenterFreq := FVfoB;
if FWDSPReady then FDSPEngine.SetShift(0.0);
if FRunning then
FNetwork.SetRunAndFreq(True, FCenterFreq, FCenterFreq, FDriveLevel);
FNetwork.SetRunAndFreq(True, XvtrTranslate(FCenterFreq), XvtrTranslate(FCenterFreq), FDriveLevel);
BandIdx := FreqToBandIdx(FVfoB);
if (BandIdx >= 0) and (BandIdx <> FCurrentBand) then
begin
@@ -4967,12 +5058,40 @@ end;
procedure TMainForm.SyncWebBand;
begin
if (FWebSyncInt < 0) or (FWebSyncInt >= BAND_COUNT) then Exit;
if FWebSyncInt = FCurrentBand then Exit;
// Выйти из XVTR при переходе на HF из web. DeactivateXvtr вернёт HF-состояние.
if FCurrentXvtr >= 0 then
begin
DeactivateXvtr;
if FWebSyncInt = FCurrentBand then Exit;
end
else if FWebSyncInt = FCurrentBand then
Exit;
if FDevConnected then begin SaveCurrentBand; FSettings.Save; end;
FCurrentBand := FWebSyncInt;
RestoreBand(FCurrentBand);
end;
procedure TMainForm.WebOnXvtrBand(Idx: Integer);
begin
FWebSyncInt := Idx;
FWebSyncM := SyncWebXvtrBand;
TThread.Synchronize(nil, FWebSyncM);
end;
procedure TMainForm.SyncWebXvtrBand;
begin
if FWebSyncInt < 0 then
begin
DeactivateXvtr;
Exit;
end;
if (FWebSyncInt >= CFG_XVTR_COUNT) then Exit;
if not FXvtrSettings.Entries[FWebSyncInt].Enabled then Exit;
if FWebSyncInt = FCurrentXvtr then Exit;
if FDevConnected then begin SaveCurrentBand; FSettings.Save; end;
ActivateXvtrBand(FWebSyncInt);
end;
procedure TMainForm.WebOnSpan(Hz: Integer);
begin
FWebSyncInt := Hz;
@@ -5182,7 +5301,7 @@ begin
if not FCTun then
if FWDSPReady then FDSPEngine.SetShift(0.0);
if FRunning then
FNetwork.SetRunAndFreq(True, FCenterFreq, FCenterFreq, FDriveLevel);
FNetwork.SetRunAndFreq(True, XvtrTranslate(FCenterFreq), XvtrTranslate(FCenterFreq), FDriveLevel);
BandIdx := FreqToBandIdx(FVfoB);
if (BandIdx >= 0) and (BandIdx <> FCurrentBand) then
begin
@@ -5233,7 +5352,7 @@ begin
else FDSPEngine.SetShift(FVfoB - FCenterFreq);
end;
if FNetwork.Connected and FNetwork.Running then
FNetwork.UpdateState(FCenterFreq, ActiveTXFreqHz, FDriveLevel, FTransmitting, True, True);
FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslate(ActiveTXFreqHz), FDriveLevel, FTransmitting, True, True);
FSpecView.InvalidateRulerCache;
FSpectrumDirty := True;
end;
@@ -5322,26 +5441,53 @@ begin
FDSPEngine.SetDriveLevel(TrkDrive.Position / 100.0);
if FNetwork.Connected and FNetwork.Running then
begin
FNetwork.UpdateState(FCenterFreq, ActiveTXFreqHz, FDriveLevel, FTransmitting, True, True);
FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslate(ActiveTXFreqHz), FDriveLevel, FTransmitting, True, True);
FNetwork.SendFullHP;
end;
end;
function TMainForm.CalcDriveByte: Byte;
var
Cal: Double;
Pos: Integer;
Cal: Double;
Pos: Integer;
XvtrActive: Boolean;
begin
if (FCurrentBand >= 0) and (FCurrentBand < BAND_COUNT) and (FPABandCal[FCurrentBand] > 0.0) then
Cal := FPABandCal[FCurrentBand]
else
Cal := 100.0;
// В режиме TUN используем отдельный per-device уровень (см. TTXSettings.TUNLevel),
// чтобы tune не зависел от Drive-слайдера. Per-band калибровка — общая для обоих.
XvtrActive := (FCurrentXvtr >= 0) and (FCurrentXvtr < CFG_XVTR_COUNT) and
FXvtrSettings.Entries[FCurrentXvtr].Enabled;
// Pos — итоговый процент драйва 0..100 ДО per-band калибровки.
//
// Логика (как в Thetis):
// • Регулярный TX:
// HF → Pos = TrkDrive.Position
// XVTR → Pos = TrkDrive.Position × (XVTR.TXPower / 100)
// (TXPower работает как верхний предел драйва на трансверторе)
// • TUN (кнопка):
// HF → Pos = TUNLevel (TTXSettings.TUNLevel)
// XVTR, UseXVTRTunePower=False → Pos = TUNLevel (поведение как HF)
// XVTR, UseXVTRTunePower=True → Pos = XVTR.TXPower
// (галка ЗАМЕНЯЕТ TUNLevel на XVTR power — Thetis-семантика)
if FTuning then
Pos := EnsureRange(FTXSettings.TUNLevel, 0, 100)
begin
if XvtrActive and FXvtrSettings.UseXVTRTunePower then
Pos := EnsureRange(FXvtrSettings.Entries[FCurrentXvtr].TXPower, 0, 100)
else
Pos := EnsureRange(FTXSettings.TUNLevel, 0, 100);
end
else
Pos := TrkDrive.Position;
begin
if XvtrActive then
Pos := Round(TrkDrive.Position
* FXvtrSettings.Entries[FCurrentXvtr].TXPower / 100.0)
else
Pos := TrkDrive.Position;
end;
Result := Round(Pos / 100.0 * Cal / 100.0 * 255.0);
end;
@@ -5392,7 +5538,7 @@ begin
end;
FDriveLevel := CalcDriveByte;
if FNetwork.Connected and FNetwork.Running then
FNetwork.UpdateState(FCenterFreq, ActiveTXFreqHz, FDriveLevel, FTransmitting, True, True);
FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslate(ActiveTXFreqHz), FDriveLevel, FTransmitting, True, True);
end;
procedure TMainForm.OnTXSettingsChange(const T: TTXSettings);
@@ -5434,7 +5580,7 @@ begin
FDSPEngine.SetDriveLevel(FTXSettings.TUNLevel / 100.0);
if FNetwork.Connected and FNetwork.Running then
begin
FNetwork.UpdateState(FCenterFreq, ActiveTXFreqHz, FDriveLevel,
FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslate(ActiveTXFreqHz), FDriveLevel,
FTransmitting, True, True);
FNetwork.SendFullHP;
end;
@@ -5460,6 +5606,251 @@ begin
end;
end;
// ===========================================================================
// XVTR (Transverter) integration
// ===========================================================================
// Ищет XVTR слот, в чьё [FreqBegin..FreqEnd] попадает данная видимая частота.
// Возвращает индекс 0..CFG_XVTR_COUNT-1 или -1.
function TMainForm.FindXvtrIdxForFreq(FreqHz: Double): Integer;
var i: Integer;
begin
Result := -1;
for i := 0 to CFG_XVTR_COUNT - 1 do
with FXvtrSettings.Entries[i] do
if Enabled and (FreqHz >= FreqBegin) and (FreqHz <= FreqEnd) then
Exit(i);
end;
// Транслирует видимую частоту в IF (то, что слышит/передаёт трансивер).
// Если активный XVTR не задан — возвращает входную частоту без изменений.
function TMainForm.XvtrTranslate(VisibleHz: Double): Double;
begin
Result := VisibleHz;
if (FCurrentXvtr < 0) or (FCurrentXvtr >= CFG_XVTR_COUNT) then Exit;
if not FXvtrSettings.Entries[FCurrentXvtr].Enabled then Exit;
// f_IF = f_visible - LOOffset + LOError
Result := VisibleHz
- FXvtrSettings.Entries[FCurrentXvtr].LOOffset
+ FXvtrSettings.Entries[FCurrentXvtr].LOError;
end;
// Передаёт XVTR-режим в сетевой слой. Вызывается при смене XVTR или его
// настроек. Управляет XVTR Enable bit, DDC IN routing, T/R relay suppression.
procedure TMainForm.ApplyXvtrToNetwork;
var
En: Boolean;
UseDDCIn: Boolean;
DisablePA: Boolean;
RxAnt: Byte;
begin
if (FCurrentXvtr >= 0) and (FCurrentXvtr < CFG_XVTR_COUNT) and
FXvtrSettings.Entries[FCurrentXvtr].Enabled then
begin
En := True;
// XVTRRF=True означает что трансвертер использует HF тракт — не нужно
// переключаться на отдельный XVTR DDC IN на Alex. Default False →
// подключаем XVTR-IF к выделенному пину XVTR DDC IN.
UseDDCIn := not FXvtrSettings.Entries[FCurrentXvtr].XVTRRF;
DisablePA := FXvtrSettings.Entries[FCurrentXvtr].DisablePA;
RxAnt := FXvtrSettings.Entries[FCurrentXvtr].RXAntenna;
end
else
begin
En := False; UseDDCIn := False; DisablePA := False; RxAnt := 0;
end;
if FNetwork.Connected then
FNetwork.SetXvtrMode(En, UseDDCIn, DisablePA, RxAnt);
end;
// Перестраивает динамические XVTR-кнопки в PanelBands. Удаляет старые,
// создаёт новые для enabled слотов в 3-м и 4-м ряду PanelBands.
procedure TMainForm.RebuildXvtrButtons;
const
BTN_H_LOC = 24;
var
i, Pos: Integer;
W: Integer;
B: TFlatButton;
Cap: string;
begin
// Уничтожаем все старые
for i := 0 to CFG_XVTR_COUNT - 1 do
if BtnXvtrBand[i] <> nil then
begin
BtnXvtrBand[i].Free;
BtnXvtrBand[i] := nil;
end;
if PanelBands = nil then Exit;
W := (PanelBands.Width - 6) div 6;
Pos := 0; // позиция среди enabled слотов
for i := 0 to CFG_XVTR_COUNT - 1 do
if FXvtrSettings.Entries[i].Enabled then
begin
if Pos >= 12 then Break; // 2 ряда по 6 кнопок
Cap := FXvtrSettings.Entries[i].ButtonText;
if Cap = '' then Cap := 'X' + IntToStr(i + 1);
B := TFlatButton.Create(Self);
B.Parent := PanelBands;
B.Caption := Cap;
B.Left := 2 + (Pos mod 6) * W;
B.Top := 16 + (2 + (Pos div 6)) * 27;
B.Width := W - 2;
B.Height := BTN_H_LOC;
B.OnClick := BtnXvtrBandClick;
B.Tag := -(i + 1); // отрицательное значение = XVTR индекс
BtnXvtrBand[i] := B;
StyleButton(B, i = FCurrentXvtr);
Inc(Pos);
end;
end;
// Обработчик клика по XVTR-кнопке. Tag = -(idx+1).
procedure TMainForm.BtnXvtrBandClick(Sender: TObject);
var
TagVal, Idx: Integer;
begin
TagVal := (Sender as TFlatButton).Tag;
Idx := -TagVal - 1;
if (Idx < 0) or (Idx >= CFG_XVTR_COUNT) then Exit;
if not FXvtrSettings.Entries[Idx].Enabled then Exit;
if FCurrentXvtr = Idx then Exit; // уже на этом XVTR
// Сохраняем текущий band перед сменой (как BtnBandClick)
if FDevConnected then
begin
SaveCurrentBand;
FSettings.Save;
end;
ActivateXvtrBand(Idx);
end;
// Активирует XVTR-band: устанавливает видимую частоту по LastFreq или
// середине [FreqBegin..FreqEnd], применяет network XVTR-режим.
procedure TMainForm.ActivateXvtrBand(Idx: Integer);
var
Vis: Double;
i: Integer;
begin
if (Idx < 0) or (Idx >= CFG_XVTR_COUNT) then Exit;
if not FXvtrSettings.Entries[Idx].Enabled then Exit;
FCurrentXvtr := Idx;
// Выбор начальной частоты: LastFreq если в диапазоне, иначе середина
Vis := FXvtrSettings.Entries[Idx].LastFreq;
if (Vis < FXvtrSettings.Entries[Idx].FreqBegin) or
(Vis > FXvtrSettings.Entries[Idx].FreqEnd) then
Vis := (FXvtrSettings.Entries[Idx].FreqBegin +
FXvtrSettings.Entries[Idx].FreqEnd) / 2.0;
// Отключаем кнопки HF band
for i := 0 to BAND_COUNT - 1 do
StyleButton(BtnBand[i], False);
// Подсвечиваем активную XVTR-кнопку
for i := 0 to CFG_XVTR_COUNT - 1 do
if BtnXvtrBand[i] <> nil then
StyleButton(BtnXvtrBand[i], i = Idx);
// Применяем XVTR режим к сети (XVTR enable bit + DDC IN + DisablePA + RX ant)
ApplyXvtrToNetwork;
// VFO на видимую частоту; FCenterFreq тоже visible (как и для HF — DDC переход
// в IF происходит только в момент UpdateState/SetRunAndFreq через XvtrTranslate).
FVfoA := Vis;
FCenterFreq := Vis;
FreqDispA.Frequency := Round(FVfoA);
// Сбросить CTUN — не имеет смысла в начальном состоянии XVTR-band
if FCTun then
begin
FCTun := False;
StyleButton(BtnCTun, False);
end;
if FWDSPReady then FDSPEngine.SetShift(0.0);
// Применяем драйв (если есть кастомный TX power для этого XVTR)
// Сделано на стороне ActiveTXFreqHz / TX path — здесь только обновляем дисплей.
// Отправляем в сеть с трансляцией
if FNetwork.Connected and FNetwork.Running then
begin
FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslate(ActiveTXFreqHz),
FDriveLevel, FTransmitting, True, True);
FNetwork.SendFullHP;
end;
SyncSpecViewFreq;
FSpecView.DrawSpectrum;
PbSpectrum.Invalidate;
FSpecView.DrawWaterfall;
PbWaterfall.Invalidate;
if PbRuler <> nil then PbRuler.Invalidate;
PushXvtrToWeb;
end;
// Деактивирует XVTR — возвращает к HF-режиму.
// FCurrentBand был сохранён неизменным во время XVTR (см. ActivateXvtrBand,
// ApplyVfoA, SaveCurrentBand) — он указывает на HF-диапазон, с которого
// пользователь зашёл в трансвертер. RestoreBand вернёт VFO/Mode/Filter/AGC
// /CTUN/Span этого диапазона и обновит подсветку HF band-кнопки.
procedure TMainForm.DeactivateXvtr;
var i: Integer;
begin
if FCurrentXvtr < 0 then Exit;
// Запоминаем LastFreq для bandstack
if FCurrentXvtr < CFG_XVTR_COUNT then
begin
FXvtrSettings.Entries[FCurrentXvtr].LastFreq := FVfoA;
if FDevConnected then
begin
FSettings.SaveXvtr(FDevMAC, FXvtrSettings);
FSettings.Save;
end;
end;
FCurrentXvtr := -1;
// Сбрасываем подсветку XVTR-кнопок
for i := 0 to CFG_XVTR_COUNT - 1 do
if BtnXvtrBand[i] <> nil then
StyleButton(BtnXvtrBand[i], False);
ApplyXvtrToNetwork;
// Восстанавливаем HF band: VFO/Mode/Filter/AGC/CTUN/Span из кэша.
// RestoreBand сам обновит подсветку HF band-кнопки и сетевое состояние.
if (FCurrentBand >= 0) and (FCurrentBand < CFG_BAND_COUNT) then
RestoreBand(FCurrentBand);
PushXvtrToWeb;
end;
procedure TMainForm.OnXvtrSettingsChange(const X: TXvtrSettings);
begin
FXvtrSettings := X;
RebuildXvtrButtons;
// Если активный XVTR стал disabled — выйти из XVTR режима
if (FCurrentXvtr >= 0) and
((FCurrentXvtr >= CFG_XVTR_COUNT) or
(not FXvtrSettings.Entries[FCurrentXvtr].Enabled)) then
DeactivateXvtr;
// Перепрошить XVTR в сеть (если параметры текущего изменились)
ApplyXvtrToNetwork;
PushXvtrToWeb;
if FDevConnected then
begin
FSettings.SaveXvtr(FDevMAC, FXvtrSettings);
FSettings.Save;
end;
end;
procedure TMainForm.PushXvtrToWeb;
var
Arr: TWebXvtrArray;
i, Cnt: Integer;
begin
if FWebServer = nil then Exit;
Cnt := 0;
for i := 0 to CFG_XVTR_COUNT - 1 do
if FXvtrSettings.Entries[i].Enabled then Inc(Cnt);
SetLength(Arr, Cnt);
Cnt := 0;
for i := 0 to CFG_XVTR_COUNT - 1 do
if FXvtrSettings.Entries[i].Enabled then
begin
Arr[Cnt].Idx := i;
Arr[Cnt].Name := FXvtrSettings.Entries[i].ButtonText;
Inc(Cnt);
end;
FWebServer.SetXvtrBands(Arr, FCurrentXvtr);
end;
procedure TMainForm.TrkVolumeChange(Sender: TObject);
begin
FVolume := TrkVolume.Position;
@@ -5745,11 +6136,13 @@ begin
SF.OnThemeChange := SetLightTheme;
SF.OnTXChange := OnTXSettingsChange;
SF.OnAlexChange := OnAlexSettingsChange;
SF.OnXvtrChange := OnXvtrSettingsChange;
end;
SF := TSettingsForm(FSettingsForm);
SF.LoadTXSettings(FTXSettings);
SF.LoadAlexSettings(FAlexSettings,
IfThen(FNetwork.Connected, FNetwork.Device.BoardType, FPendingBoardType));
SF.LoadXvtrSettings(FXvtrSettings);
SF.LoadPASettings(FPAMaxPower, FPABandCal);
SF.LoadCATSettings(
FCATLastGlobal.CATSerialEnabled,
@@ -5996,7 +6389,7 @@ begin
FCenterFreq := FVfoB;
if FWDSPReady then FDSPEngine.SetShift(0.0);
if FRunning then
FNetwork.SetRunAndFreq(True, FCenterFreq, FCenterFreq, FDriveLevel);
FNetwork.SetRunAndFreq(True, XvtrTranslate(FCenterFreq), XvtrTranslate(FCenterFreq), FDriveLevel);
BandIdx := FreqToBandIdx(FVfoB);
if (BandIdx >= 0) and (BandIdx <> FCurrentBand) then
begin