diff --git a/MainForm.pas b/MainForm.pas index d934297..cf29837 100644 --- a/MainForm.pas +++ b/MainForm.pas @@ -163,9 +163,6 @@ type // переехало в TRadioController (нужно headless-load в DoConnectDevice). // Снимок последнего кадра спектра/водопада для web переехал в TRadioController // (FSpectrumBuf/FWaterfallBuf) — нужен headless-демону. - // Waterfall frame timing - FWaterfallFrameInterval: Integer; - FWaterfallFrameCounter: Integer; FAgcLineCounter: Integer; // ---- Splitter ---- FSplitterDrag: Boolean; @@ -588,7 +585,9 @@ type procedure ApplyWaterfallParams(WfDet, WfAvgMode: Integer; WfAvgTimeMS, WfHigh, WfLow, WfAGCOffset: Double); procedure ApplyWfRenderParams(Palette: Integer; Gamma: Double; - ResLimit: Integer); + ResLimit, SpeedDiv: Integer); + procedure ApplySpecMSAA(Samples: Integer); + function EffectiveWfInterval: Integer; procedure ApplyWfAGCNF(WfAGC, WfNF: Boolean); procedure ApplyADCSettings(Dither, Random: Boolean); procedure ApplyWebSettings(Enabled: Boolean; Port: Integer; @@ -845,8 +844,6 @@ begin FController.FFMRptOffsetHz := RPT_DEFAULT_2M; FController.FFMRptAutoActive := False; FController.FFMCTCSSAutoActive := False; - FWaterfallFrameInterval := 2; - FWaterfallFrameCounter := 0; FController.FDevConnected := False; // Spectrum grid settings defaults FController.FSpecRefLevel := -20.0; @@ -1010,8 +1007,6 @@ begin // OpenGLContextEx (QOpenGLWidget-бэкенд) работает и в нативном Wayland, // старая X11-заглушка больше не нужна FUseOpenGLSpectrum := HasOpenGLSpectrumSwitch; - if FUseOpenGLSpectrum then - FWaterfallFrameInterval := 1; if FUseOpenGLSpectrum then FSpecView := TSpectrumViewOpenGL.Create else @@ -1037,7 +1032,7 @@ begin FSpecView.SpecGridStep := FController.FSpecGridStep; // TX overlay: ширина окна = TX_SAMPLE_RATE из движка (фикс 192k) FSpecView.TXSpanHz := WDSPEngine.TX_SAMPLE_RATE; - FSpecView.WfFrameInterval := FWaterfallFrameInterval; + FSpecView.WfFrameInterval := EffectiveWfInterval; FSpecView.PAMaxPower := FController.FPAMaxPower; FWidebandView := TWidebandView.Create; @@ -1475,6 +1470,8 @@ begin FSpecView.WfAGCOffset := FController.FWfAGCOffset; FSpecView.WfPalette := FController.FWfPalette; FSpecView.WfGamma := FController.FWfGamma; + FSpecView.WfFrameInterval := EffectiveWfInterval; + ApplySpecMSAA(FController.FSpecMSAA); FSpecView.ResetWfAvgBuf; if Assigned(FWidebandView) then FWidebandView.SetFillSpectrum(FController.FWidebandFill); @@ -2211,6 +2208,7 @@ begin PbSpectrum := TOpenGLControl.Create(Self); TOpenGLControl(PbSpectrum).AutoResizeViewport := False; TOpenGLControl(PbSpectrum).DoubleBuffered := True; + TOpenGLControl(PbSpectrum).MultiSampling := Max(1, FController.FSpecMSAA); TOpenGLControl(PbSpectrum).OnPaint := FSpecView.PaintSpectrum; TOpenGLControl(PbSpectrum).OnMouseDown := PbSpectrumMouseDown; TOpenGLControl(PbSpectrum).OnDblClick := PbSpectrumDblClick; @@ -6400,16 +6398,12 @@ end; procedure TMainForm.OnWaterfallReady(const Pixels: array of Single; Count: Integer); -// Подписчик на FController.OnWaterfallData — рендер с децимацией скорости -// прокрутки (FWaterfallFrameInterval). Web получает каждый кадр (в контроллере). +// Подписчик на FController.OnWaterfallData. Каждый кадр уходит во view — +// замедление/накопление (max-hold) делает сам TWaterfallView по +// WfFrameInterval; рендер дёргаем только когда view собрал строку. begin - Inc(FWaterfallFrameCounter); - if FWaterfallFrameCounter >= Max(1, FWaterfallFrameInterval) then - begin - FWaterfallFrameCounter := 0; - FSpecView.SetWaterfallData(Pixels, Count); - FWaterfallDirty := True; - end; + FSpecView.SetWaterfallData(Pixels, Count); + if FSpecView.WaterfallDirty then FWaterfallDirty := True; end; // =========================================================================== @@ -6442,19 +6436,37 @@ begin end; procedure TMainForm.ApplyWfRenderParams(Palette: Integer; Gamma: Double; - ResLimit: Integer); + ResLimit, SpeedDiv: Integer); begin FController.FWfPalette := Palette; FController.FWfGamma := Gamma; FController.FDisplayPixels := ResLimit; + FController.FWfSpeedDiv := SpeedDiv; FSpecView.WfPalette := Palette; FSpecView.WfGamma := Gamma; + FSpecView.WfFrameInterval := EffectiveWfInterval; if FController.FWDSPReady then FController.FDSPEngine.SetDisplayResLimit(ResLimit); FWaterfallDirty := True; FSpectrumDirty := True; end; +procedure TMainForm.ApplySpecMSAA(Samples: Integer); +begin + Samples := EnsureRange(Samples, 1, 8); + FController.FSpecMSAA := Samples; + if FUseOpenGLSpectrum and (PbSpectrum is TOpenGLControl) and + (TOpenGLControl(PbSpectrum).MultiSampling <> Cardinal(Samples)) then + begin + // Смена MSAA пересоздаёт хэндл → старый GL-контекст (и все текстуры) + // умирает; кэши view нужно обнулить БЕЗ glDelete. + TOpenGLControl(PbSpectrum).MultiSampling := Samples; + if FSpecView is TSpectrumViewOpenGL then + TSpectrumViewOpenGL(FSpecView).ResetGLCache; + FSpectrumDirty := True; + end; +end; + procedure TMainForm.ApplyGridParams(RefLevel, Range, GridStep: Double); begin FController.FSpecRefLevel := RefLevel; @@ -6565,19 +6577,21 @@ begin FDisplayFPS := FPS; FController.FLoadedGlobal.DisplayFPS := FPS; // зеркало в persist-буфер контроллера FSpectrumTimer.Interval := 1000 div FPS; - // CPU path keeps the historical every-other-frame waterfall update. - // OpenGL uploads only one texture row per frame, so it can match display FPS. - if FUseOpenGLSpectrum then - FWaterfallFrameInterval := 1 - else - FWaterfallFrameInterval := 2; - FWaterfallFrameCounter := 0; FWaterfallDirty := True; - FSpecView.WfFrameInterval := FWaterfallFrameInterval; + FSpecView.WfFrameInterval := EffectiveWfInterval; if FController.FWDSPReady then FController.FDSPEngine.SetDisplayFPS(FPS); end; +function TMainForm.EffectiveWfInterval: Integer; +// Делитель скорости водопада: пользовательский Speed (max-hold накопление +// в TWaterfallView); CPU-путь дополнительно не быстрее полукадровой частоты +// (исторически — экономия на полной перерисовке bitmap). +begin + Result := Max(1, FController.FWfSpeedDiv); + if not FUseOpenGLSpectrum then Result := Max(Result, 2); +end; + procedure TMainForm.ApplyFreqMhzDigits(Digits: Integer); begin Digits := EnsureRange(Digits, 3, 5); @@ -6623,6 +6637,7 @@ begin SF.OnDisplayChange := ApplyDisplayParams; SF.OnWaterfallChange := ApplyWaterfallParams; SF.OnWfRenderChange := ApplyWfRenderParams; + SF.OnSpecMSAAChange := ApplySpecMSAA; SF.OnGridChange := ApplyGridParams; SF.OnAudioDevChange := ApplyAudioDevice; SF.OnAudioInDevChange := ApplyAudioInputDevice; @@ -6668,7 +6683,8 @@ begin FController.FSpectrumFill); SF.LoadWfAGCNF(FController.FWfAGCEnabled, FController.FWfNFEnabled); SF.LoadWfRender(FController.FWfPalette, FController.FWfGamma, - FController.FDisplayPixels); + FController.FDisplayPixels, FController.FWfSpeedDiv); + SF.LoadSpecMSAA(FController.FSpecMSAA); SF.LoadADCSettings(FController.FDitherEnabled, FController.FRandomEnabled); SF.LoadWebSettings(FWebEnabled, FWebPort, FWebBindAddr, FWebUser, FWebPass); SF.LoadFPS(FDisplayFPS); diff --git a/RadioController.pas b/RadioController.pas index 7fedfbb..55f58a0 100644 --- a/RadioController.pas +++ b/RadioController.pas @@ -284,6 +284,8 @@ type FWfPalette: Integer; // 0=Classic, 1=Inferno, 2=Turbo FWfGamma: Double; // контраст палитры водопада FDisplayPixels: Integer; // потолок точек анализатора + FWfSpeedDiv: Integer; // делитель скорости водопада (1/2/4/8, max-hold) + FSpecMSAA: Integer; // MSAA GL-спектра (1=off, 2, 4, 8) FSpecRefLevel: Double; FSpecRange: Double; FSpecGridStep: Double; @@ -692,6 +694,7 @@ begin FPlutoTxMaxAttDb := -10.0; // ADI-дефолт: линейный режим выходных каскадов FWfAGCEnabled := True; FDitherEnabled := True; FRandomEnabled := True; FWfPalette := 0; FWfGamma := 1.0; FDisplayPixels := 4096; + FWfSpeedDiv := 1; FSpecMSAA := 4; FFMStepOn := True; FLocalAudio := True; // GUI: звук на локальную карту; демон выключит явно FLastSMeter := -130; FLastSWR := 1; FLastSupplyV := -1; FLastSupplyA := -1; @@ -1916,6 +1919,8 @@ begin Result.WfPalette := FWfPalette; Result.WfGamma := FWfGamma; Result.DisplayPixels := FDisplayPixels; + Result.WfSpeedDiv := FWfSpeedDiv; + Result.SpecMSAA := FSpecMSAA; Result.SpecRefLevel := FSpecRefLevel; Result.SpecRange := FSpecRange; Result.SpecGridStep := FSpecGridStep; @@ -3505,6 +3510,8 @@ begin FWfPalette := EnsureRange(FLoadedGlobal.WfPalette, 0, 2); FWfGamma := EnsureRange(FLoadedGlobal.WfGamma, 0.3, 3.0); FDisplayPixels := EnsureRange(FLoadedGlobal.DisplayPixels, 64, 4096); + FWfSpeedDiv := EnsureRange(FLoadedGlobal.WfSpeedDiv, 1, 8); + FSpecMSAA := EnsureRange(FLoadedGlobal.SpecMSAA, 1, 8); if FWDSPReady then FDSPEngine.SetDisplayResLimit(FDisplayPixels); FShowSpectrum := FLoadedGlobal.ShowSpectrum; FShowWaterfall := FLoadedGlobal.ShowWaterfall; diff --git a/Settings.pas b/Settings.pas index 98ebd86..19378cf 100644 --- a/Settings.pas +++ b/Settings.pas @@ -253,6 +253,8 @@ type WfPalette: Integer; // 0=Classic, 1=Inferno, 2=Turbo WfGamma: Double; // контраст палитры (0.3..3.0, 1.0 = линейно) DisplayPixels: Integer; // потолок точек анализатора (1024/2048/4096) + WfSpeedDiv: Integer; // делитель скорости водопада (1/2/4/8, max-hold) + SpecMSAA: Integer; // MSAA GL-спектра (1=off, 2, 4, 8) SpecRefLevel: Double; // dBm reference (top of scale), e.g. -20 SpecRange: Double; // dB range, e.g. 110 (bottom = ref - range) SpecGridStep: Double; // grid step in dB, e.g. 10 @@ -449,6 +451,8 @@ begin G.WfPalette := 0; // Classic G.WfGamma := 1.0; G.DisplayPixels := 4096; // 1:1 до 4K-ширины + G.WfSpeedDiv := 1; // полная скорость + G.SpecMSAA := 4; // сглаживание кривой спектра G.SpecRefLevel := -20.0; G.SpecRange := 110.0; G.SpecGridStep := 10.0; @@ -675,6 +679,8 @@ begin G.WfPalette := JI(GObj,'wf_palette',0); G.WfGamma := JD(GObj,'wf_gamma',1.0); G.DisplayPixels := JI(GObj,'display_pixels',4096); + G.WfSpeedDiv := JI(GObj,'wf_speed_div',1); + G.SpecMSAA := JI(GObj,'spec_msaa',4); G.SpecRefLevel := JD(GObj,'spec_ref_level',-20.0); G.SpecRange := JD(GObj,'spec_range',110.0); G.SpecGridStep := JD(GObj,'spec_grid_step',10.0); @@ -783,6 +789,8 @@ begin JW(O,'wf_palette',G.WfPalette); JW(O,'wf_gamma',G.WfGamma); JW(O,'display_pixels',G.DisplayPixels); + JW(O,'wf_speed_div',G.WfSpeedDiv); + JW(O,'spec_msaa',G.SpecMSAA); JW(O,'spec_ref_level',G.SpecRefLevel); JW(O,'spec_range',G.SpecRange); JW(O,'spec_grid_step',G.SpecGridStep); diff --git a/SettingsForm.pas b/SettingsForm.pas index 97126da..b8b8f71 100644 --- a/SettingsForm.pas +++ b/SettingsForm.pas @@ -48,7 +48,8 @@ type TOnWaterfallParamChange = procedure(WfDetector, WfAvgMode: Integer; WfAvgTimeMS, WfHigh, WfLow, WfAGCOffset: Double) of object; TOnWfRenderChange = procedure(Palette: Integer; Gamma: Double; - ResLimit: Integer) of object; + ResLimit, SpeedDiv: Integer) of object; + TOnSpecMSAAChange = procedure(Samples: Integer) of object; TOnGridParamChange = procedure(RefLevel, Range, GridStep: Double) of object; TOnAudioDevChange = procedure(DevIndex: Integer; const DevName: string) of object; TOnAudioInDevChange = procedure(DevIndex: Integer; const DevName: string) of object; @@ -124,6 +125,8 @@ type FCmbWfPalette: TFlatComboBox; FWfGamma: TFlatFloatSpinEdit; FCmbWfRes: TFlatComboBox; + FCmbWfSpeed: TFlatComboBox; + FCmbSpecMSAA: TFlatComboBox; FEdRefLevel: TFlatEdit; FEdRange: TFlatEdit; FCmbGridStep: TFlatComboBox; @@ -295,6 +298,7 @@ type FOnDisplayChange: TOnDisplayParamChange; FOnWaterfallChange: TOnWaterfallParamChange; FOnWfRenderChange: TOnWfRenderChange; + FOnSpecMSAAChange: TOnSpecMSAAChange; FOnGridChange: TOnGridParamChange; FOnAudioDevChange: TOnAudioDevChange; FOnAudioInDevChange: TOnAudioInDevChange; @@ -386,6 +390,7 @@ type procedure FireWaterfallChange; procedure FireWfRenderChange; procedure OnWfRenderCtlChange(Sender: TObject); + procedure OnSpecMSAACtlChange(Sender: TObject); procedure FireGridChange; function GetSpecAvgTimeMS: Double; @@ -431,7 +436,9 @@ type procedure LoadAlexSettings(const A: TAlexSettings; BoardType: Integer); procedure LoadXvtrSettings(const X: TXvtrSettings); procedure LoadWfAGCNF(AGC, NF: Boolean); - procedure LoadWfRender(Palette: Integer; Gamma: Double; ResLimit: Integer); + procedure LoadWfRender(Palette: Integer; Gamma: Double; + ResLimit, SpeedDiv: Integer); + procedure LoadSpecMSAA(Samples: Integer); procedure LoadADCSettings(Dither, Random: Boolean); procedure LoadWebSettings(Enabled: Boolean; Port: Integer; const BindAddr, User, Pass: string); @@ -445,6 +452,7 @@ type property OnDisplayChange: TOnDisplayParamChange read FOnDisplayChange write FOnDisplayChange; property OnWaterfallChange: TOnWaterfallParamChange read FOnWaterfallChange write FOnWaterfallChange; property OnWfRenderChange: TOnWfRenderChange read FOnWfRenderChange write FOnWfRenderChange; + property OnSpecMSAAChange: TOnSpecMSAAChange read FOnSpecMSAAChange write FOnSpecMSAAChange; property OnGridChange: TOnGridParamChange read FOnGridChange write FOnGridChange; property OnAudioDevChange: TOnAudioDevChange read FOnAudioDevChange write FOnAudioDevChange; property OnAudioInDevChange: TOnAudioInDevChange read FOnAudioInDevChange write FOnAudioInDevChange; @@ -505,6 +513,10 @@ const WF_PALETTE_NAMES: array[0..2] of string = ('Classic', 'Inferno', 'Turbo'); DISPLAY_RES_VALUES: array[0..2] of Integer = (1024, 2048, 4096); DISPLAY_RES_NAMES: array[0..2] of string = ('1024 px', '2048 px', '4096 px'); + WF_SPEED_VALUES: array[0..3] of Integer = (1, 2, 4, 8); + WF_SPEED_NAMES: array[0..3] of string = ('Full rate', '1/2', '1/4', '1/8'); + MSAA_VALUES: array[0..3] of Integer = (1, 2, 4, 8); + MSAA_NAMES: array[0..3] of string = ('Off', '2x', '4x', '8x'); FPS_VALUES: array[0..12] of Integer = (5, 10, 15, 20, 25, 30, 40, 50, 60, 75, 80, 90, 100); FPS_NAMES: array[0..12] of string = ('5 fps','10 fps','15 fps','20 fps','25 fps','30 fps', @@ -1039,6 +1051,13 @@ begin MakeLbl(Grp, 'Avg Time (ms):', PAD, R1 + 2 * STEP + 5, LW); FSpecAvgTime := MkFloat(CX, R1 + 2 * STEP, SPW, 30.0, 0.0, 5000.0, 10.0, OnSpecAvgTimeChange); + Grp := MakeGroupPanel(FPageSpectrum, 'Rendering', MARGIN, 602, 650, 128); + MakeLbl(Grp, 'Anti-aliasing:', PAD, R1 + 5, LW); + FCmbSpecMSAA := MakeCombo(Grp, CX, R1, 130, OnSpecMSAACtlChange); + for i := 0 to High(MSAA_NAMES) do FCmbSpecMSAA.Items.Add(MSAA_NAMES[i]); + FCmbSpecMSAA.ItemIndex := 2; // 4x + MakeLbl(Grp, '(GL spectrum curve smoothing, MSAA)', CX + 140, R1 + 5, 300); + Grp := MakeGroupPanel(FPageSpectrum, 'Grid', MARGIN, 416, 650, 170); MakeLbl(Grp, 'Reference level:', PAD, R1 + 5, LW); FEdRefLevel := MkEdit(CX, R1, EDT_W, '-20', OnRefLevelChange); @@ -1108,7 +1127,7 @@ begin FChkWfNF.Font.Size := 9; FChkWfNF.OnChange := OnWfAGCNFChkChange; - Grp := MakeGroupPanel(FPageWaterfall, 'Rendering', MARGIN, 614, 650, 170); + Grp := MakeGroupPanel(FPageWaterfall, 'Rendering', MARGIN, 614, 650, 212); MakeLbl(Grp, 'Palette:', PAD, R1 + 5, LW); FCmbWfPalette := MakeCombo(Grp, CX, R1, CMB_W, OnWfRenderCtlChange); for i := 0 to High(WF_PALETTE_NAMES) do FCmbWfPalette.Items.Add(WF_PALETTE_NAMES[i]); @@ -1120,6 +1139,10 @@ begin FCmbWfRes := MakeCombo(Grp, CX, R1 + 2 * STEP, 130, OnWfRenderCtlChange); for i := 0 to High(DISPLAY_RES_NAMES) do FCmbWfRes.Items.Add(DISPLAY_RES_NAMES[i]); FCmbWfRes.ItemIndex := High(DISPLAY_RES_NAMES); + MakeLbl(Grp, 'Speed:', PAD, R1 + 3 * STEP + 5, LW); + FCmbWfSpeed := MakeCombo(Grp, CX, R1 + 3 * STEP, 130, OnWfRenderCtlChange); + for i := 0 to High(WF_SPEED_NAMES) do FCmbWfSpeed.Items.Add(WF_SPEED_NAMES[i]); + FCmbWfSpeed.ItemIndex := 0; end; // --------------------------------------------------------------------------- @@ -2339,7 +2362,7 @@ end; procedure TSettingsForm.FireWfRenderChange; var - ResV: Integer; + ResV, SpeedV: Integer; begin if FLoading then Exit; if not Assigned(FOnWfRenderChange) then Exit; @@ -2347,13 +2370,26 @@ begin ResV := DISPLAY_RES_VALUES[FCmbWfRes.ItemIndex] else ResV := DISPLAY_RES_VALUES[High(DISPLAY_RES_VALUES)]; - FOnWfRenderChange(Max(0, FCmbWfPalette.ItemIndex), FWfGamma.Value, ResV); + if (FCmbWfSpeed.ItemIndex >= 0) and (FCmbWfSpeed.ItemIndex <= High(WF_SPEED_VALUES)) then + SpeedV := WF_SPEED_VALUES[FCmbWfSpeed.ItemIndex] + else + SpeedV := 1; + FOnWfRenderChange(Max(0, FCmbWfPalette.ItemIndex), FWfGamma.Value, ResV, SpeedV); end; procedure TSettingsForm.OnWfRenderCtlChange(Sender: TObject); begin FireWfRenderChange; end; -procedure TSettingsForm.LoadWfRender(Palette: Integer; Gamma: Double; ResLimit: Integer); +procedure TSettingsForm.OnSpecMSAACtlChange(Sender: TObject); +begin + if FLoading then Exit; + if not Assigned(FOnSpecMSAAChange) then Exit; + if (FCmbSpecMSAA.ItemIndex >= 0) and (FCmbSpecMSAA.ItemIndex <= High(MSAA_VALUES)) then + FOnSpecMSAAChange(MSAA_VALUES[FCmbSpecMSAA.ItemIndex]); +end; + +procedure TSettingsForm.LoadWfRender(Palette: Integer; Gamma: Double; + ResLimit, SpeedDiv: Integer); var i: Integer; begin FLoading := True; @@ -2367,6 +2403,24 @@ begin for i := 0 to High(DISPLAY_RES_VALUES) do if DISPLAY_RES_VALUES[i] = ResLimit then begin FCmbWfRes.ItemIndex := i; Break; end; + FCmbWfSpeed.ItemIndex := 0; + for i := 0 to High(WF_SPEED_VALUES) do + if WF_SPEED_VALUES[i] = SpeedDiv then + begin FCmbWfSpeed.ItemIndex := i; Break; end; + finally + FLoading := False; + end; +end; + +procedure TSettingsForm.LoadSpecMSAA(Samples: Integer); +var i: Integer; +begin + FLoading := True; + try + FCmbSpecMSAA.ItemIndex := 0; + for i := 0 to High(MSAA_VALUES) do + if MSAA_VALUES[i] = Samples then + begin FCmbSpecMSAA.ItemIndex := i; Break; end; finally FLoading := False; end; diff --git a/SpectrumViewOpengl.pas b/SpectrumViewOpengl.pas index 70b449b..cb90a53 100644 --- a/SpectrumViewOpengl.pas +++ b/SpectrumViewOpengl.pas @@ -110,6 +110,7 @@ type procedure InvalidateGridCache; override; procedure InvalidateOverlayCache; override; procedure InvalidateVfoOverlay; override; + procedure ResetGLCache; procedure SetTheme(const T: TAppTheme); override; end; @@ -424,6 +425,40 @@ begin FOverlayDirty := True; end; +// Контекст пересоздан (смена MSAA → RecreateWnd): все GL-имена текстур +// стали недействительны. Обнуляем кэши БЕЗ glDelete — старого контекста +// уже нет, а в новом эти id могут достаться другим текстурам. +procedure TSpectrumViewOpenGL.ResetGLCache; + + procedure Zap(var T: TGLTextureCache); + begin + T.Tex := 0; T.W := 0; T.H := 0; T.Dirty := True; + end; + +var + i: Integer; +begin + Zap(FGridLabelTex); + Zap(FAGCLabelTex); + Zap(FAGCHangLabelTex); + Zap(FMarkerLabelTex); + Zap(FADCOverlayTex); + Zap(FSampleOverlayTex); + Zap(FVfoOverlayTex); + Zap(FBandOverlayTex); + for i := 0 to High(FSliceTex) do Zap(FSliceTex[i].Tex); + for i := 0 to High(FLetterTex) do Zap(FLetterTex[i]); + FLastAGCText := ''; + FLastMarkerText := ''; + FGridDirty := True; + FOverlayDirty := True; + FSampleOverlayDirty := True; + FVfoOverlayDirty := True; + FBandOverlayDirty := True; + FGLSpectrumW := 0; + FGLSpectrumH := 0; +end; + function TSpectrumViewOpenGL.FormatFreqGL(Hz: Double): string; var Mhz: Int64; KHz, Rest: Integer; begin diff --git a/WaterfallView.pas b/WaterfallView.pas index 48494be..aeaf4f4 100644 --- a/WaterfallView.pas +++ b/WaterfallView.pas @@ -307,7 +307,13 @@ procedure TWaterfallView.SetWaterfallData(const Pixels: array of Single; Count: var i, N: Integer; begin N := Min(Count, WF_MAX_PIXELS); - for i := 0 to N - 1 do FWaterfallBuf[i] := Pixels[i]; + // Замедление (WfFrameInterval>1) накапливает кадры max-hold'ом, а не + // выбрасывает их — короткие посылки (CW) не выпадают из строки. + if (FWfFrameCounter = 0) or (N <> FWaterfallBufCount) then + for i := 0 to N - 1 do FWaterfallBuf[i] := Pixels[i] + else + for i := 0 to N - 1 do + if Pixels[i] > FWaterfallBuf[i] then FWaterfallBuf[i] := Pixels[i]; FWaterfallBufCount := N; Inc(FWfFrameCounter); if FWfFrameCounter >= Max(1, FWfFrameInterval) then @@ -548,6 +554,9 @@ begin finally FWfBitmap.EndUpdate(False); end; + // Строка потреблена — иначе фронтенд (OnWaterfallReady) считал бы кадр + // «несобранным» и дёргал перерисовку на каждый кадр анализатора. + FWaterfallDirty := False; end; // ────────────────────────────────────────────────────────────────────────────