From 2f5f9c6e7946bdc70026ed69e06a265684c6b5b9 Mon Sep 17 00:00:00 2001 From: ericek111 Date: Wed, 25 Mar 2026 22:08:09 +0100 Subject: [PATCH] peak trace: initial work --- src/ui/Application.cpp | 2 +- src/ui/Measurements.cpp | 76 +++++++++++++++++++++++++++++++++-------- src/ui/Measurements.h | 11 ++++-- 3 files changed, 71 insertions(+), 18 deletions(-) diff --git a/src/ui/Application.cpp b/src/ui/Application.cpp index 622a697..0d5def1 100644 --- a/src/ui/Application.cpp +++ b/src/ui/Application.cpp @@ -994,7 +994,7 @@ void Application::renderWaterfallPanel() { measurements_.drawWaterfall(specDisplay_, wfPosX_, wfPosY_, wfSizeX_, wfSizeY_, settings_.sampleRate, settings_.isIQ, freqScale_, - viewLo_, viewHi_); + viewLo_, viewHi_, screenRows, analyzer_.spectrumSize()); // ── Mouse interaction: zoom, pan & hover on waterfall ── ImGuiIO& io = ImGui::GetIO(); diff --git a/src/ui/Measurements.cpp b/src/ui/Measurements.cpp index 5029320..81dbc71 100644 --- a/src/ui/Measurements.cpp +++ b/src/ui/Measurements.cpp @@ -70,6 +70,14 @@ void Measurements::update(const std::vector& spectrumDB, globalPeak_.bin = bin; globalPeak_.dB = *it; globalPeak_.freq = binToFreq(bin, sampleRate, isIQ, fftSize); + + // Push into peak history circular buffer + constexpr int kMaxHistory = 4096; + if (static_cast(peakHistBins_.size()) < kMaxHistory) + peakHistBins_.resize(kMaxHistory, -1); + peakHistIdx_ = (peakHistIdx_ + 1) % kMaxHistory; + peakHistBins_[peakHistIdx_] = bin; + if (peakHistLen_ < kMaxHistory) ++peakHistLen_; } if (!enabled) { peaks_.clear(); return; } @@ -154,28 +162,66 @@ void Measurements::draw(const SpectrumDisplay& specDisplay, void Measurements::drawWaterfall(const SpectrumDisplay& specDisplay, float posX, float posY, float sizeX, float sizeY, double sampleRate, bool isIQ, FreqScale freqScale, - float viewLo, float viewHi) const { - if (!enabled || !showOnWaterfall || peaks_.empty()) return; - + float viewLo, float viewHi, + int screenRows, int spectrumSize) const { ImDrawList* dl = ImGui::GetWindowDrawList(); - auto peakColor = [](int idx) -> ImU32 { - if (idx == 0) return IM_COL32(255, 80, 80, 120); - return IM_COL32(255, 140, 60, 80); - }; + // Peak trace: red squiggly line showing peak frequency history + if (showPeakTrace && peakHistLen_ > 1 && screenRows > 1 && spectrumSize > 0) { + int histSize = static_cast(peakHistBins_.size()); + int count = std::min(screenRows, peakHistLen_); + ImU32 traceCol = IM_COL32(255, 30, 30, 200); - for (int i = 0; i < static_cast(peaks_.size()); ++i) { - const auto& p = peaks_[i]; - float x = specDisplay.freqToScreenX(p.freq, posX, sizeX, - sampleRate, isIQ, freqScale, - viewLo, viewHi); - ImU32 col = peakColor(i); - float thickness = (i == 0) ? 1.5f : 1.0f; - dl->AddLine({x, posY}, {x, posY + sizeY}, col, thickness); + // Convert bin index to screen X via normalized frequency fraction + auto binToX = [&](int bin) -> float { + float frac = (static_cast(bin) + 0.5f) / spectrumSize; + // Map through view range + float viewFrac = (frac - viewLo) / (viewHi - viewLo); + return posX + viewFrac * sizeX; + }; + + // Walk from newest (bottom) to oldest (top) + float prevX = 0, prevY = 0; + bool havePrev = false; + for (int i = 0; i < count; ++i) { + int idx = (peakHistIdx_ - i + histSize) % histSize; + int bin = peakHistBins_[idx]; + if (bin < 0) break; + + // Y: i=0 is newest (bottom), i=count-1 is oldest (top) + float y = posY + sizeY - (static_cast(i) + 0.5f) / screenRows * sizeY; + float x = binToX(bin); + + if (havePrev) { + dl->AddLine({prevX, prevY}, {x, y}, traceCol, 3.0f); + } + prevX = x; + prevY = y; + havePrev = true; + } + } + + // Vertical markers at current peak positions + if (enabled && showOnWaterfall && !peaks_.empty()) { + auto peakColor = [](int idx) -> ImU32 { + if (idx == 0) return IM_COL32(255, 80, 80, 120); + return IM_COL32(255, 140, 60, 80); + }; + + for (int i = 0; i < static_cast(peaks_.size()); ++i) { + const auto& p = peaks_[i]; + float x = specDisplay.freqToScreenX(p.freq, posX, sizeX, + sampleRate, isIQ, freqScale, + viewLo, viewHi); + ImU32 col = peakColor(i); + float thickness = (i == 0) ? 1.5f : 1.0f; + dl->AddLine({x, posY}, {x, posY + sizeY}, col, thickness); + } } } void Measurements::drawPanel() { + ImGui::Checkbox("Peak trace", &showPeakTrace); if (!enabled) return; ImGui::SetNextItemWidth(-1); diff --git a/src/ui/Measurements.h b/src/ui/Measurements.h index d10d61c..f101a58 100644 --- a/src/ui/Measurements.h +++ b/src/ui/Measurements.h @@ -25,11 +25,12 @@ public: float minDB, float maxDB, float viewLo, float viewHi) const; - // Draw vertical markers on the waterfall panel. + // Draw vertical markers and peak trace on the waterfall panel. void drawWaterfall(const SpectrumDisplay& specDisplay, float posX, float posY, float sizeX, float sizeY, double sampleRate, bool isIQ, FreqScale freqScale, - float viewLo, float viewHi) const; + float viewLo, float viewHi, + int screenRows, int spectrumSize) const; // Draw sidebar panel (ImGui widgets). void drawPanel(); @@ -44,11 +45,17 @@ public: float peakThreshold = -120.0f; // ignore peaks below this dB bool showOnSpectrum = true; // draw markers on spectrum bool showOnWaterfall = false; // draw vertical lines on waterfall + bool showPeakTrace = false; // draw peak history curve on waterfall private: PeakInfo globalPeak_; // always-tracked highest peak std::vector peaks_; + // Peak history for waterfall trace (circular buffer, newest at peakHistIdx_) + std::vector peakHistBins_; // bin index per waterfall line + int peakHistIdx_ = 0; + int peakHistLen_ = 0; // how many entries are valid + // Find top-N peaks with minimum bin separation. void findPeaks(const std::vector& spectrumDB, int maxN, int minDist, float threshold);