diff --git a/MainForm.pas b/MainForm.pas index 7f53c06..93f6a36 100644 --- a/MainForm.pas +++ b/MainForm.pas @@ -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 diff --git a/RadioController.pas b/RadioController.pas index 5f0cdf1..b542199 100644 --- a/RadioController.pas +++ b/RadioController.pas @@ -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).