diff --git a/MainForm.pas b/MainForm.pas index 37ddad8..3e1ed67 100644 --- a/MainForm.pas +++ b/MainForm.pas @@ -989,8 +989,7 @@ begin TSettingsManager.DefaultBand(bi_, FController.FBandCache[bi_]); FController.FCurrentBand := 5; - // Загружаем JSON настройки - FController.FSettings := TSettingsManager.Create; + // Загружаем JSON настройки (FSettings создаётся в TRadioController.Create) FController.FSettings.Load; // Загружаем список каналов @@ -1152,7 +1151,10 @@ begin FSpecView.PAMaxPower := FController.FPAMaxPower; FWidebandView := TWidebandView.Create; - FController.FNetwork := THPSDRNetwork.Create; + // Движки создаёт контроллер (владелец lifecycle — нужно для демона); здесь + // лишь навешиваем колбэки на методы MainForm. FSampleRate уже загружен из + // startup-preview выше, поэтому FDSPEngine рождается с нужной частотой. + FController.CreateEngines(FController.FSampleRate); FController.FNetwork.OnDeviceFound := OnDeviceFound; FController.FNetwork.OnHPStatus := OnHPStatusCB; FController.FNetwork.OnDDCIQ := OnDDCIQCB; @@ -1170,13 +1172,8 @@ begin UpdateVfoDisplay; UpdateFilterButtons; - // DSP Engine — создаём объект, Open вызовется при нажатии START - // (FController.FDSPEngine.Open загружает libwdsp и занимает ~1-2 сек) + // DSP Engine.Open (загрузка libwdsp, ~1-2 сек) вызывается при нажатии START. FController.FWDSPReady := False; - // BufSize=512: FBufSize=2048 @ 192kHz, FAudioBufSize=512 @ 48kHz. - // Уменьшение с 1024 вдвое сокращает время построения downsampler в OpenChannel RXA - // (~1300ms → ~650ms). Латентность: 512/48000 ≈ 10.7ms — допустимо для SDR. - FController.FDSPEngine := TWDSPEngine.Create(FController.FSampleRate, 48000, 512); FController.FDSPEngine.OnAudio := OnAudioReady; FController.FDSPEngine.OnSpectrum := OnSpectrumReady; FController.FDSPEngine.OnWaterfall := OnWaterfallReady; @@ -1185,12 +1182,6 @@ begin // тиком, если выбран источник SoundCard. Для Radio колбэк не вызывается. FController.FDSPEngine.OnPullMicSamples := PullSoundCardMic; - // Audio output/input — создаём объекты сейчас, открываем после показа формы - // (Pa_Initialize на Linux пишет в stderr до перехвата сигналов FPC) - FController.FAudioOut := TAudioOutput.Create(48000); - FController.FAudioOut.OutputBufferSize := FController.FSettings.LoadAudioBufferSize; - FController.FAudioIn := TAudioInput.Create(48000); - FMeterTimer := TTimer.Create(Self); FMeterTimer.Interval := 100; FMeterTimer.OnTimer := MeterTimerTick; @@ -1221,16 +1212,11 @@ procedure TMainForm.FormDestroy(Sender: TObject); begin FMeterTimer.Enabled := False; FSpectrumTimer.Enabled := False; + // Грациозный стоп радио (Run=0) до освобождения; сами движки закрывает и + // освобождает TRadioController.Destroy (FreeEngines) ниже. if FController.FNetwork.Running then FController.FNetwork.SetRunAndFreq(False, XvtrTranslate(FController.FCenterFreq), XvtrTranslate(FController.FCenterFreq), 0); FController.FNetwork.Disconnect; - FController.FNetwork.Free; - FController.FAudioOut.Close; - FController.FAudioOut.Free; - FController.FAudioIn.Close; - FController.FAudioIn.Free; - FController.FDSPEngine.Close; - FController.FDSPEngine.Free; FreeAndNil(FSpecView); FreeAndNil(FWidebandView); @@ -1245,15 +1231,14 @@ begin // Размер окна сохраняем всегда (не зависит от подключения) FController.FSettings.SaveStartupPreview(FController.FVfoA, FController.FVfoB, FController.FSampleRate); FController.FSettings.Save; - FController.FSettings.Free; FreeAndNil(FController.FChannelStore); FWebServer.Stop; FWebServer.Free; if Assigned(FCATTcp) then begin FCATTcp.Stop; FreeAndNil(FCATTcp); end; if Assigned(FCATSerial) then begin FCATSerial.StopAll; FreeAndNil(FCATSerial); end; FreeAndNil(FCATEngine); - // Движки (поля FController) уже освобождены выше. - // Освобождаем само ядро последним. + // Ядро освобождаем последним — его Destroy закрывает и освобождает движки + // (FreeEngines) и FSettings. FreeAndNil(FController); end; diff --git a/RadioController.pas b/RadioController.pas index 843823a..f86385c 100644 --- a/RadioController.pas +++ b/RadioController.pas @@ -203,6 +203,13 @@ type constructor Create; destructor Destroy; override; + // Lifecycle движков: создаёт FNetwork/FDSPEngine/FAudioOut/FAudioIn с + // заданным sample rate (вызывающий уже определил его из настроек). Колбэки + // движков навешивает вызывающий (GUI: методы MainForm; демон: свои). Парный + // FreeEngines закрывает и освобождает их; вызывается из Destroy. + procedure CreateEngines(ASampleRate: Integer); + procedure FreeEngines; + // Маршалинг команды в поток контроллера (GUI: TThread.Synchronize). procedure Invoke(M: TThreadMethod); @@ -343,6 +350,7 @@ implementation constructor TRadioController.Create; begin inherited Create; + FSettings := TSettingsManager.Create; // владелец настроек (GUI и демон) // Дефолты (дублируют TMainForm.FormCreate; в GUI перезапишутся, в демоне нужны). FVfoA := 14200000; FVfoB := 7100000; FActiveVfo := 0; FMode := 1; FFilter := 5; FFilterBW := 2700; @@ -357,9 +365,39 @@ begin FillChar(FDevMAC, SizeOf(FDevMAC), 0); end; +procedure TRadioController.CreateEngines(ASampleRate: Integer); +// Создаёт движки в полях контроллера. Колбэки (OnAudio/OnSpectrum/OnHPStatus…) +// навешивает вызывающий после возврата — они указывают на UI/демон. +begin + FNetwork := THPSDRNetwork.Create; + + // BufSize=512: латентность 512/48000 ≈ 10.7мс — допустимо для SDR; меньший + // буфер вдвое сокращает построение downsampler в OpenChannel RXA. + FDSPEngine := TWDSPEngine.Create(ASampleRate, 48000, 512); + + // Audio out/in — объекты создаём сейчас, Open вызывается позже (после показа + // формы / при подключении устройства). + FAudioOut := TAudioOutput.Create(48000); + FAudioOut.OutputBufferSize := FSettings.LoadAudioBufferSize; + FAudioIn := TAudioInput.Create(48000); +end; + +procedure TRadioController.FreeEngines; +begin + if Assigned(FNetwork) then + begin + if FNetwork.Connected then FNetwork.Disconnect; + FreeAndNil(FNetwork); + end; + if Assigned(FAudioOut) then begin FAudioOut.Close; FreeAndNil(FAudioOut); end; + if Assigned(FAudioIn) then begin FAudioIn.Close; FreeAndNil(FAudioIn); end; + if Assigned(FDSPEngine) then begin FDSPEngine.Close; FreeAndNil(FDSPEngine); end; +end; + destructor TRadioController.Destroy; begin - // Фаза 1: движки освобождает MainForm. Здесь ничего не освобождаем. + FreeEngines; + FreeAndNil(FSettings); inherited Destroy; end;