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
+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).