Phase 5 (batch 29): move Run=1 go-live (StartRunning) into the controller

Move the device bring-up tail of DoConnectDevice into TRadioController,
symmetric with StopRunning:

- Relocate four pure DSP/network helpers into the controller
  (BuildMicLineSelectByte, ApplyNoiseFilterButtonsToDSP,
  SendDUCSpecificFromSettings, ApplyTXSettingsToDSP); external callers
  (OnTXSettingsChange) now call FController.*.
- New StartRunning(SpectrumWidth): General packet, wideband/DDC config,
  WDSP sample-rate sync, DSP restore (mode/volume/filter/NR/AGC), Run=1
  sequence, seq reset, DDC/DUC Specific + TX chain, AGC lines. Emits
  Changed(rfRunning) so the run button / MOX enable / sleep inhibit render
  through OnControllerState.

DoConnectDevice's ~80-line Phase C collapses to one call; only UI bits
(FSpecView.AGCTop, status text, ResizeSpectrumPanels) stay in the wrapper.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Uladzimir Karpenka
2026-06-08 11:31:04 +03:00
co-authored by Claude Opus 4.8
parent 1fdbe27b96
commit 04f621bf3d
2 changed files with 197 additions and 178 deletions
+13 -178
View File
@@ -430,7 +430,6 @@ type
procedure UpdateNBButton;
procedure UpdateSNBButton;
procedure UpdateANFButton;
procedure ApplyNoiseFilterButtonsToDSP;
procedure BtnNRClick(Sender: TObject);
procedure BtnNBClick(Sender: TObject);
procedure BtnSNBClick(Sender: TObject);
@@ -593,11 +592,8 @@ type
procedure ApplyAudioInputDevice(DevIndex: Integer; const DevName: string);
procedure ApplyAudioBufferSize(BufferSize: Integer);
procedure ApplySendAudioToRadio(Enabled: Boolean);
// TX settings: применить FTXSettings к WDSP и переслать DUC Specific
// (mic-биты Boost/Bias/LineIn/PTT попадают в byte 50 DUCSpecific).
procedure ApplyTXSettingsToDSP;
procedure SendDUCSpecificFromSettings;
function BuildMicLineSelectByte: Byte;
// TX settings (ApplyTXSettingsToDSP/SendDUCSpecificFromSettings/
// BuildMicLineSelectByte) переехали в TRadioController.
procedure ApplyVisibility(ShowSpectrum, ShowWaterfall, ShowWideband,
WidebandFill, SpectrumFill: Boolean);
procedure ApplyFPS(FPS: Integer);
@@ -3324,55 +3320,8 @@ end;
// TX settings — применение к WDSP, сборка DUC Specific, sound-card pull
// ---------------------------------------------------------------------------
function TMainForm.BuildMicLineSelectByte: Byte;
// openHPSDR Ethernet Protocol v4.3, DUC Specific byte 50:
// bit 0 — Line In (1 = line-in вместо mic-in)
// bit 1 — Mic Boost (+20 dB); не ставим когда Line In
// bit 2 — 0 = Orion mic PTT enabled, 1 = disabled (инверсия!)
// bit 3 — Tip/Ring (0=Tip, 1=Ring; Orion/OrionMkII)
// bit 4 — Orion mic bias (1 = enable +5V bias)
// bit 5 — Saturn XLR/balanced (не используем)
begin
Result := 0;
if FController.FTXSettings.MicLineIn then
Result := Result or $01 // bit 0: Line In
else if FController.FTXSettings.MicBoost then
Result := Result or $02; // bit 1: Boost только при Mic In
if not FController.FTXSettings.MicPTTEnabled then Result := Result or $04; // bit 2: инверсия
if FController.FTXSettings.MicTipRing then Result := Result or $08; // bit 3: Ring
if FController.FTXSettings.MicBias then Result := Result or $10; // bit 4: bias
end;
procedure TMainForm.SendDUCSpecificFromSettings;
var
DUCPkt: TDUCSpecificPacket;
RateKsps: Word;
begin
if not (FController.FNetwork.Connected and FController.FNetwork.Running) then Exit;
FillChar(DUCPkt, SizeOf(DUCPkt), 0);
// DUC0 rate в ksps берём из WDSP-движка (фиксированный TX rate, не RX).
RateKsps := FController.FDSPEngine.TXSampleRate div 1000;
DUCPkt.NumDACs := 1;
DUCPkt.SidetoneLevel := 50;
DUCPkt.SidetoneFreqHi := Hi(600);
DUCPkt.SidetoneFreqLo := Lo(600);
DUCPkt.KeyerSpeed := 20;
DUCPkt.KeyerWeight := 50;
DUCPkt.DUC0RateHi := Hi(RateKsps);
DUCPkt.DUC0RateLo := Lo(RateKsps);
DUCPkt.DUC0Bits := 24;
DUCPkt.MicLineSelect := BuildMicLineSelectByte;
// Line In Gain (byte 51): 0=-34.5dB .. 31=+12dB (шаг 1.5 dB, формула Thetis).
// При Mic In значение игнорируется железом, но шлём корректное для Line In.
DUCPkt.LineInGain := Byte(EnsureRange(Round((FController.FTXSettings.LineInGainDB + 34.5) / 1.5), 0, 31));
// ATT on TX — RX-ADC step attenuator во время передачи (Thetis-style protection).
// Дублируем значение во все три ADC-байта: на однопотоковых платах активен только
// ADC0, на Saturn/Hermes Lite 2 — могут использоваться ADC1/ADC2.
DUCPkt.StepAtten0 := Byte(EnsureRange(FController.FTXSettings.AttOnTX, 0, 31));
DUCPkt.StepAtten1 := DUCPkt.StepAtten0;
DUCPkt.StepAtten2 := DUCPkt.StepAtten0;
FController.FNetwork.SendDUCSpecific(DUCPkt);
end;
// BuildMicLineSelectByte / SendDUCSpecificFromSettings переехали в
// TRadioController (чистые над FTXSettings/движком; нужны headless StartRunning).
function TMainForm.DefaultMicSource: TTXMicSource;
// Mic source для MOX-кнопки и Web-TX: если настроена звуковая карта — берём с неё,
@@ -3383,38 +3332,7 @@ begin
else Result := txmsRadio;
end;
procedure TMainForm.ApplyTXSettingsToDSP;
begin
if not FController.FWDSPReady then Exit;
// Mic source — выставляем значение по умолчанию для MOX-кнопки;
// при реальном PTT ApplyMOX скорректирует источник.
FController.FDSPEngine.SetTXMicSource(DefaultMicSource);
FController.FDSPEngine.SetMicGain(FController.FTXSettings.MicGainDB);
FController.FDSPEngine.SetTXFilterFull(FController.FTXSettings.FilterLow, FController.FTXSettings.FilterHigh,
FController.FTXSettings.FilterNC, FController.FTXSettings.FilterMP,
FController.FTXSettings.FilterWindow);
FController.FDSPEngine.SetTXCompressor(FController.FTXSettings.CompressorOn, FController.FTXSettings.CompressorGain);
FController.FDSPEngine.SetTXLeveler(FController.FTXSettings.LevelerOn, FController.FTXSettings.LevelerTop,
FController.FTXSettings.LevelerDecay);
FController.FDSPEngine.SetTXALC(FController.FTXSettings.ALCOn, FController.FTXSettings.ALCMaxGain,
FController.FTXSettings.ALCDecay);
FController.FDSPEngine.SetTXPhaseRot(FController.FTXSettings.PhaseRotOn, FController.FTXSettings.PhaseRotStages,
FController.FTXSettings.PhaseRotFreq);
FController.FDSPEngine.SetTXEQ(FController.FTXSettings.EQOn, FController.FTXSettings.EQNumBands,
FController.FTXSettings.EQGains, FController.FTXSettings.EQFreqs);
FController.FDSPEngine.SetTXAMCarrierLevel(FController.FTXSettings.AMCarrierLevel);
FController.FDSPEngine.SetTXFMParams(FController.FTXSettings.FMDeviation, FController.FTXSettings.FMLowCut,
FController.FTXSettings.FMHighCut, FController.FTXSettings.FMEmphPosition);
FController.FDSPEngine.SetTXCTCSS(FController.FTXSettings.CTCSSOn, FController.FTXSettings.CTCSSFreq);
// TX display
FController.FDSPEngine.SetTXFFTParams(FController.FTXSettings.TXFFTSize, FController.FTXSettings.TXWindowType);
FController.FDSPEngine.SetTXSpectrumDisplay(FController.FTXSettings.TXSpecDetector,
FController.FTXSettings.TXSpecAvgMode,
FController.FTXSettings.TXSpecAvgTimeMS);
FController.FDSPEngine.SetTXWaterfallDisplay(FController.FTXSettings.TXWfDetector,
FController.FTXSettings.TXWfAvgMode,
FController.FTXSettings.TXWfAvgTimeMS);
end;
// ApplyTXSettingsToDSP переехал в TRadioController (вся TX-цепь над FTXSettings).
// ===========================================================================
// Button handlers
@@ -3761,7 +3679,6 @@ end;
procedure TMainForm.DoConnectDevice(const Dev: THPSDRDevice);
var
GenPkt: TGeneralPacket;
G_Settings: TGlobalSettings;
i: Integer;
begin
@@ -3915,87 +3832,12 @@ begin
SetStatusText(1, 'IP: ' + FController.FNetwork.Device.IPAddress);
SetStatusText(0, 'Board: ' + BoardTypeName(FController.FNetwork.Device.BoardType));
// General packet
FillChar(GenPkt, SizeOf(GenPkt), 0);
GenPkt.Command := CMD_GENERAL;
GenPkt.Flags37 := $08;
GenPkt.Flags38 := $01;
GenPkt.PAConfig := $01;
GenPkt.WBPort[0] := (PORT_WIDEBAND_ADC0 shr 8) and $FF;
GenPkt.WBPort[1] := PORT_WIDEBAND_ADC0 and $FF;
GenPkt.WBEnable := IfThen(FController.FShowWideband, 1, 0);
GenPkt.WBSamplesPerPkt[0] := 512 shr 8;
GenPkt.WBSamplesPerPkt[1] := 512 and $FF;
GenPkt.WBSampleSize := 16;
GenPkt.WBUpdateRate := 70;
GenPkt.WBPacketsPerFrame := 32;
if FController.FNetwork.Device.BoardType = 5 then
GenPkt.AlexEnable := $03
else
GenPkt.AlexEnable := $01;
FController.FNetwork.SendGeneralPacket(GenPkt);
FController.FNetwork.ConfigureWideband(0, FController.FShowWideband, 512, 16, 70, 32);
if FController.FNetwork.Device.BoardType in [3, 4, 5] then
FController.FActiveDDC := 2
else
FController.FActiveDDC := 0;
// Синхронизируем samplerate WDSP если нужно
if FController.FWDSPReady and (FController.FDSPEngine.SampleRate <> FController.FSampleRate) then
begin
FController.FWDSPReady := False;
FController.FDSPEngine.ChangeSampleRate(FController.FSampleRate);
FController.FWDSPReady := FController.FDSPEngine.Initialized;
end;
if FController.FWDSPReady then
begin
FController.FDSPEngine.SetMode(FController.FMode);
FController.FDSPEngine.SetVolume(FController.FVolume / 100.0);
FController.ApplyModeFilter;
ApplyNoiseFilterButtonsToDSP;
// ChangeSampleRate пересоздаёт WDSP-канал — восстанавливаем все настройки
FController.FDSPEngine.SetAGCTop(FController.FAGCTop);
FController.FDSPEngine.SetAGC(TWDSPAGCMode(FController.FAGCMode), 50.0);
end;
// «Go-live» Run=1 (General/DDC/DUC + DSP restore) — в контроллере; рендер
// run-кнопки/MOX/sleep идёт через OnControllerState(rfRunning).
FController.StartRunning(FSpectrumWidth);
FSpecView.AGCTop := FController.FAGCTop;
// Сначала отправляем Run=1 — эмулятор/железо создают ddc_specific_thread
// (порт 1025) и rx_thread (порт 1035+) только после получения HP Run=1.
// DDC Specific и DUC Specific отправляем ПОСЛЕ, иначе порты ещё не открыты.
FController.FNetwork.UpdateState(XvtrTranslate(FController.FCenterFreq), XvtrTranslate(ActiveTXFreqHz), FController.FDriveLevel, False, True, True);
FController.FNetwork.SetRunAndFreq(True, XvtrTranslate(FController.FCenterFreq), XvtrTranslate(FController.FCenterFreq), FController.FDriveLevel);
FController.FRunning := True;
InhibitSleep;
FController.FRXStartTime := GetTickCount64;
FController.FRXLastPktTime := 0;
FController.FRXPacketCount := 0;
FillChar(FController.FDDCSeqValid, SizeOf(FController.FDDCSeqValid), 0);
FController.FSeqErrorCount := 0;
FController.FLastSeqErrorDDC := -1;
FController.FLastSeqErrorDelta := 0;
FController.FSeqOkStreak := 0;
SetStatusText(6, 'SEQ OK');
// DDC и DUC Specific — теперь потоки эмулятора слушают на своих портах.
// DUC Specific содержит mic-конфигурацию (boost/bias/line/PTT) из FController.FTXSettings.
FController.FNetwork.ConfigureDDCs(1, FController.FSampleRate div 1000, 0, FController.FDitherEnabled, FController.FRandomEnabled);
// Применяем сохранённое состояние динамика/отправки аудио (мьют byte 1400 bit1).
FController.FNetwork.SetSpeakerAudio(FController.FSendAudioToRadio);
SendDUCSpecificFromSettings;
// Применяем TX-цепь (фильтр, mic gain, EQ, leveler, ALC, comp, ...).
ApplyTXSettingsToDSP;
FController.ApplyModeFilter;
// Принудительно обновляем AGC линии — гарантированно после всех SetAGC вызовов
if FController.FWDSPReady then
FController.FDSPEngine.UpdateAGCLines(FSpectrumWidth); // ещё не получили ни одного пакета
BtnStartStop.Caption := 'STOP';
StyleButton(BtnStartStop, True);
SetStatusText(2, 'Running');
BtnMOX.Enabled := True;
ResizeSpectrumPanels;
end;
@@ -5091,14 +4933,7 @@ begin
BtnANF.Invalidate;
end;
procedure TMainForm.ApplyNoiseFilterButtonsToDSP;
begin
if not FController.FWDSPReady then Exit;
FController.FDSPEngine.SetNRMode(FController.FNRMode);
FController.FDSPEngine.SetNBMode(FController.FNBMode);
FController.FDSPEngine.SetSNB(FController.FSNB);
FController.FDSPEngine.SetANF(FController.FANF);
end;
// ApplyNoiseFilterButtonsToDSP переехал в TRadioController (NR/NB/SNB/ANF → WDSP).
procedure TMainForm.BtnNRClick(Sender: TObject);
begin
@@ -5779,7 +5614,7 @@ var
OldAtt, OldTUNLevel, OldTUNFreq: Integer;
GridChanged: Boolean;
begin
OldMicByte := BuildMicLineSelectByte;
OldMicByte := FController.BuildMicLineSelectByte;
OldAtt := FController.FTXSettings.AttOnTX;
OldTUNLevel := FController.FTXSettings.TUNLevel;
OldTUNFreq := FController.FTXSettings.TUNFreq;
@@ -5787,7 +5622,7 @@ begin
or (FController.FTXSpecRange <> T.TXSpecRange)
or (FController.FTXSpecGridStep <> T.TXSpecGridStep);
FController.FTXSettings := T;
NewMicByte := BuildMicLineSelectByte;
NewMicByte := FController.BuildMicLineSelectByte;
// TX-grid в момент правки — обновляем рабочие поля и (если на передаче) пушим в FSpecView
FController.FTXSpecRefLevel := FController.FTXSettings.TXSpecRefLevel;
FController.FTXSpecRange := FController.FTXSettings.TXSpecRange;
@@ -5795,9 +5630,9 @@ begin
FController.FTXSpecGridStep := FController.FTXSettings.TXSpecGridStep;
if GridChanged and FController.FTransmitting then
ApplySpecViewGridFromState;
ApplyTXSettingsToDSP;
FController.ApplyTXSettingsToDSP;
if (OldMicByte <> NewMicByte) or (OldAtt <> FController.FTXSettings.AttOnTX) then
SendDUCSpecificFromSettings;
FController.SendDUCSpecificFromSettings;
// TUN-параметры live: если идёт tune — обновить тон/drive, иначе просто запомнить.
if FController.FTuning then
begin
+184
View File
@@ -287,6 +287,12 @@ type
// если настроена, иначе HW mic-стрим трансивера. HW PTT → txmsRadio в SetMOX.
function DefaultMicSource: TTXMicSource;
// ---- TX/DSP-цепь (чистые над состоянием: читают F*/FTXSettings, пишут в движок) ----
function BuildMicLineSelectByte: Byte; // DUC Specific byte 50 (mic/line биты)
procedure ApplyNoiseFilterButtonsToDSP; // NR/NB/SNB/ANF → WDSP
procedure SendDUCSpecificFromSettings; // DUC Specific (mic/keyer/atten) в сеть
procedure ApplyTXSettingsToDSP; // TX-цепь (фильтр/gain/EQ/comp/...) → WDSP
// ---- Управление диапазонами (состояние + DSP; рендер через события) ----
// MakeBandSettings — снимок текущего состояния в запись диапазона.
// SaveCurrentBand — сохраняет текущее состояние в кэш+конфиг (или в XVTR-слот).
@@ -388,6 +394,7 @@ type
// Жизненный цикл движка / устройства
procedure StartStop;
procedure SetRun(On_: Boolean);
procedure StartRunning(SpectrumWidth: Integer); // Run=1 bring-up: General/DDC/DUC + DSP restore
procedure StopRunning; // Run=0: graceful-стоп RX-потока + сброс TX/телеметрии
procedure ConnectDevice(const Dev: THPSDRDevice);
procedure Discover;
@@ -1635,6 +1642,98 @@ begin
else Result := txmsRadio;
end;
function TRadioController.BuildMicLineSelectByte: Byte;
// openHPSDR Ethernet Protocol v4.3, DUC Specific byte 50:
// bit 0 — Line In (1 = line-in вместо mic-in)
// bit 1 — Mic Boost (+20 dB); не ставим когда Line In
// bit 2 — 0 = Orion mic PTT enabled, 1 = disabled (инверсия!)
// bit 3 — Tip/Ring (0=Tip, 1=Ring; Orion/OrionMkII)
// bit 4 — Orion mic bias (1 = enable +5V bias)
// bit 5 — Saturn XLR/balanced (не используем)
begin
Result := 0;
if FTXSettings.MicLineIn then
Result := Result or $01 // bit 0: Line In
else if FTXSettings.MicBoost then
Result := Result or $02; // bit 1: Boost только при Mic In
if not FTXSettings.MicPTTEnabled then Result := Result or $04; // bit 2: инверсия
if FTXSettings.MicTipRing then Result := Result or $08; // bit 3: Ring
if FTXSettings.MicBias then Result := Result or $10; // bit 4: bias
end;
procedure TRadioController.ApplyNoiseFilterButtonsToDSP;
begin
if not FWDSPReady then Exit;
FDSPEngine.SetNRMode(FNRMode);
FDSPEngine.SetNBMode(FNBMode);
FDSPEngine.SetSNB(FSNB);
FDSPEngine.SetANF(FANF);
end;
procedure TRadioController.SendDUCSpecificFromSettings;
var
DUCPkt: TDUCSpecificPacket;
RateKsps: Word;
begin
if not (FNetwork.Connected and FNetwork.Running) then Exit;
FillChar(DUCPkt, SizeOf(DUCPkt), 0);
// DUC0 rate в ksps берём из WDSP-движка (фиксированный TX rate, не RX).
RateKsps := FDSPEngine.TXSampleRate div 1000;
DUCPkt.NumDACs := 1;
DUCPkt.SidetoneLevel := 50;
DUCPkt.SidetoneFreqHi := Hi(600);
DUCPkt.SidetoneFreqLo := Lo(600);
DUCPkt.KeyerSpeed := 20;
DUCPkt.KeyerWeight := 50;
DUCPkt.DUC0RateHi := Hi(RateKsps);
DUCPkt.DUC0RateLo := Lo(RateKsps);
DUCPkt.DUC0Bits := 24;
DUCPkt.MicLineSelect := BuildMicLineSelectByte;
// Line In Gain (byte 51): 0=-34.5dB .. 31=+12dB (шаг 1.5 dB, формула Thetis).
// При Mic In значение игнорируется железом, но шлём корректное для Line In.
DUCPkt.LineInGain := Byte(EnsureRange(Round((FTXSettings.LineInGainDB + 34.5) / 1.5), 0, 31));
// 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;
procedure TRadioController.ApplyTXSettingsToDSP;
begin
if not FWDSPReady then Exit;
// Mic source — выставляем значение по умолчанию для MOX-кнопки;
// при реальном PTT SetMOX скорректирует источник.
FDSPEngine.SetTXMicSource(DefaultMicSource);
FDSPEngine.SetMicGain(FTXSettings.MicGainDB);
FDSPEngine.SetTXFilterFull(FTXSettings.FilterLow, FTXSettings.FilterHigh,
FTXSettings.FilterNC, FTXSettings.FilterMP,
FTXSettings.FilterWindow);
FDSPEngine.SetTXCompressor(FTXSettings.CompressorOn, FTXSettings.CompressorGain);
FDSPEngine.SetTXLeveler(FTXSettings.LevelerOn, FTXSettings.LevelerTop,
FTXSettings.LevelerDecay);
FDSPEngine.SetTXALC(FTXSettings.ALCOn, FTXSettings.ALCMaxGain,
FTXSettings.ALCDecay);
FDSPEngine.SetTXPhaseRot(FTXSettings.PhaseRotOn, FTXSettings.PhaseRotStages,
FTXSettings.PhaseRotFreq);
FDSPEngine.SetTXEQ(FTXSettings.EQOn, FTXSettings.EQNumBands,
FTXSettings.EQGains, FTXSettings.EQFreqs);
FDSPEngine.SetTXAMCarrierLevel(FTXSettings.AMCarrierLevel);
FDSPEngine.SetTXFMParams(FTXSettings.FMDeviation, FTXSettings.FMLowCut,
FTXSettings.FMHighCut, FTXSettings.FMEmphPosition);
FDSPEngine.SetTXCTCSS(FTXSettings.CTCSSOn, FTXSettings.CTCSSFreq);
// TX display
FDSPEngine.SetTXFFTParams(FTXSettings.TXFFTSize, FTXSettings.TXWindowType);
FDSPEngine.SetTXSpectrumDisplay(FTXSettings.TXSpecDetector,
FTXSettings.TXSpecAvgMode,
FTXSettings.TXSpecAvgTimeMS);
FDSPEngine.SetTXWaterfallDisplay(FTXSettings.TXWfDetector,
FTXSettings.TXWfAvgMode,
FTXSettings.TXWfAvgTimeMS);
end;
procedure TRadioController.SetMOX(On_: Boolean);
var
WasTransmitting: Boolean;
@@ -1841,6 +1940,91 @@ begin SetRun(not FRunning); end;
procedure TRadioController.SetRun(On_: Boolean);
begin FRunning := On_; Changed(rfRunning); { TODO wiring: логика BtnStartStopClick } end;
procedure TRadioController.StartRunning(SpectrumWidth: Integer);
// «Go-live» последовательность Run=1: General packet, wideband/DDC-конфиг,
// синхронизация WDSP sample rate, восстановление DSP (mode/volume/filter/NR/AGC),
// Run=1 → seq-сброс → DDC/DUC Specific + TX-цепь. Рендер run-кнопки/sleep — UI
// через OnControllerState(rfRunning); статус-бар/FSpecView.AGCTop/resize — обёртка.
var
GenPkt: TGeneralPacket;
begin
// General packet
FillChar(GenPkt, SizeOf(GenPkt), 0);
GenPkt.Command := CMD_GENERAL;
GenPkt.Flags37 := $08;
GenPkt.Flags38 := $01;
GenPkt.PAConfig := $01;
GenPkt.WBPort[0] := (PORT_WIDEBAND_ADC0 shr 8) and $FF;
GenPkt.WBPort[1] := PORT_WIDEBAND_ADC0 and $FF;
GenPkt.WBEnable := IfThen(FShowWideband, 1, 0);
GenPkt.WBSamplesPerPkt[0] := 512 shr 8;
GenPkt.WBSamplesPerPkt[1] := 512 and $FF;
GenPkt.WBSampleSize := 16;
GenPkt.WBUpdateRate := 70;
GenPkt.WBPacketsPerFrame := 32;
if FNetwork.Device.BoardType = 5 then
GenPkt.AlexEnable := $03
else
GenPkt.AlexEnable := $01;
FNetwork.SendGeneralPacket(GenPkt);
FNetwork.ConfigureWideband(0, FShowWideband, 512, 16, 70, 32);
if FNetwork.Device.BoardType in [3, 4, 5] then
FActiveDDC := 2
else
FActiveDDC := 0;
// Синхронизируем samplerate WDSP если нужно
if FWDSPReady and (FDSPEngine.SampleRate <> FSampleRate) then
begin
FWDSPReady := False;
FDSPEngine.ChangeSampleRate(FSampleRate);
FWDSPReady := FDSPEngine.Initialized;
end;
if FWDSPReady then
begin
FDSPEngine.SetMode(FMode);
FDSPEngine.SetVolume(FVolume / 100.0);
ApplyModeFilter;
ApplyNoiseFilterButtonsToDSP;
// ChangeSampleRate пересоздаёт WDSP-канал — восстанавливаем все настройки
FDSPEngine.SetAGCTop(FAGCTop);
FDSPEngine.SetAGC(TWDSPAGCMode(FAGCMode), 50.0);
end;
// Сначала отправляем Run=1 — эмулятор/железо создают ddc_specific_thread
// (порт 1025) и rx_thread (порт 1035+) только после получения HP Run=1.
// DDC Specific и DUC Specific отправляем ПОСЛЕ, иначе порты ещё не открыты.
FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslate(ActiveTXFreqHz), FDriveLevel, False, True, True);
FNetwork.SetRunAndFreq(True, XvtrTranslate(FCenterFreq), XvtrTranslate(FCenterFreq), FDriveLevel);
FRunning := True;
FRXStartTime := GetTickCount64;
FRXLastPktTime := 0;
FRXPacketCount := 0;
FillChar(FDDCSeqValid, SizeOf(FDDCSeqValid), 0);
FSeqErrorCount := 0;
FLastSeqErrorDDC := -1;
FLastSeqErrorDelta := 0;
FSeqOkStreak := 0;
// DDC и DUC Specific — теперь потоки эмулятора слушают на своих портах.
// DUC Specific содержит mic-конфигурацию (boost/bias/line/PTT) из FTXSettings.
FNetwork.ConfigureDDCs(1, FSampleRate div 1000, 0, FDitherEnabled, FRandomEnabled);
// Применяем сохранённое состояние динамика/отправки аудио (мьют byte 1400 bit1).
FNetwork.SetSpeakerAudio(FSendAudioToRadio);
SendDUCSpecificFromSettings;
// Применяем TX-цепь (фильтр, mic gain, EQ, leveler, ALC, comp, ...).
ApplyTXSettingsToDSP;
ApplyModeFilter;
// Принудительно обновляем AGC линии — гарантированно после всех SetAGC вызовов
if FWDSPReady then
FDSPEngine.UpdateAGCLines(SpectrumWidth); // ещё не получили ни одного пакета
Changed(rfRunning); // UI: caption STOP / MOX.Enabled / InhibitSleep
end;
procedure TRadioController.StopRunning;
// Graceful-останов RX-потока (Run=0) + сброс TX и RX-телеметрии. Сеть остаётся
// Connected — Disconnect отдельно (UI-обёртка делает это после persistence).