peak trace: initial work

This commit is contained in:
2026-03-25 22:08:09 +01:00
parent 9b8fea3c68
commit 2f5f9c6e79
3 changed files with 71 additions and 18 deletions

View File

@@ -994,7 +994,7 @@ void Application::renderWaterfallPanel() {
measurements_.drawWaterfall(specDisplay_, wfPosX_, wfPosY_, wfSizeX_, wfSizeY_, measurements_.drawWaterfall(specDisplay_, wfPosX_, wfPosY_, wfSizeX_, wfSizeY_,
settings_.sampleRate, settings_.isIQ, freqScale_, settings_.sampleRate, settings_.isIQ, freqScale_,
viewLo_, viewHi_); viewLo_, viewHi_, screenRows, analyzer_.spectrumSize());
// ── Mouse interaction: zoom, pan & hover on waterfall ── // ── Mouse interaction: zoom, pan & hover on waterfall ──
ImGuiIO& io = ImGui::GetIO(); ImGuiIO& io = ImGui::GetIO();

View File

@@ -70,6 +70,14 @@ void Measurements::update(const std::vector<float>& spectrumDB,
globalPeak_.bin = bin; globalPeak_.bin = bin;
globalPeak_.dB = *it; globalPeak_.dB = *it;
globalPeak_.freq = binToFreq(bin, sampleRate, isIQ, fftSize); globalPeak_.freq = binToFreq(bin, sampleRate, isIQ, fftSize);
// Push into peak history circular buffer
constexpr int kMaxHistory = 4096;
if (static_cast<int>(peakHistBins_.size()) < kMaxHistory)
peakHistBins_.resize(kMaxHistory, -1);
peakHistIdx_ = (peakHistIdx_ + 1) % kMaxHistory;
peakHistBins_[peakHistIdx_] = bin;
if (peakHistLen_ < kMaxHistory) ++peakHistLen_;
} }
if (!enabled) { peaks_.clear(); return; } if (!enabled) { peaks_.clear(); return; }
@@ -154,11 +162,47 @@ void Measurements::draw(const SpectrumDisplay& specDisplay,
void Measurements::drawWaterfall(const SpectrumDisplay& specDisplay, void Measurements::drawWaterfall(const SpectrumDisplay& specDisplay,
float posX, float posY, float sizeX, float sizeY, float posX, float posY, float sizeX, float sizeY,
double sampleRate, bool isIQ, FreqScale freqScale, double sampleRate, bool isIQ, FreqScale freqScale,
float viewLo, float viewHi) const { float viewLo, float viewHi,
if (!enabled || !showOnWaterfall || peaks_.empty()) return; int screenRows, int spectrumSize) const {
ImDrawList* dl = ImGui::GetWindowDrawList(); ImDrawList* dl = ImGui::GetWindowDrawList();
// Peak trace: red squiggly line showing peak frequency history
if (showPeakTrace && peakHistLen_ > 1 && screenRows > 1 && spectrumSize > 0) {
int histSize = static_cast<int>(peakHistBins_.size());
int count = std::min(screenRows, peakHistLen_);
ImU32 traceCol = IM_COL32(255, 30, 30, 200);
// Convert bin index to screen X via normalized frequency fraction
auto binToX = [&](int bin) -> float {
float frac = (static_cast<float>(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<float>(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 { auto peakColor = [](int idx) -> ImU32 {
if (idx == 0) return IM_COL32(255, 80, 80, 120); if (idx == 0) return IM_COL32(255, 80, 80, 120);
return IM_COL32(255, 140, 60, 80); return IM_COL32(255, 140, 60, 80);
@@ -173,9 +217,11 @@ void Measurements::drawWaterfall(const SpectrumDisplay& specDisplay,
float thickness = (i == 0) ? 1.5f : 1.0f; float thickness = (i == 0) ? 1.5f : 1.0f;
dl->AddLine({x, posY}, {x, posY + sizeY}, col, thickness); dl->AddLine({x, posY}, {x, posY + sizeY}, col, thickness);
} }
}
} }
void Measurements::drawPanel() { void Measurements::drawPanel() {
ImGui::Checkbox("Peak trace", &showPeakTrace);
if (!enabled) return; if (!enabled) return;
ImGui::SetNextItemWidth(-1); ImGui::SetNextItemWidth(-1);

View File

@@ -25,11 +25,12 @@ public:
float minDB, float maxDB, float minDB, float maxDB,
float viewLo, float viewHi) const; 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, void drawWaterfall(const SpectrumDisplay& specDisplay,
float posX, float posY, float sizeX, float sizeY, float posX, float posY, float sizeX, float sizeY,
double sampleRate, bool isIQ, FreqScale freqScale, 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). // Draw sidebar panel (ImGui widgets).
void drawPanel(); void drawPanel();
@@ -44,11 +45,17 @@ public:
float peakThreshold = -120.0f; // ignore peaks below this dB float peakThreshold = -120.0f; // ignore peaks below this dB
bool showOnSpectrum = true; // draw markers on spectrum bool showOnSpectrum = true; // draw markers on spectrum
bool showOnWaterfall = false; // draw vertical lines on waterfall bool showOnWaterfall = false; // draw vertical lines on waterfall
bool showPeakTrace = false; // draw peak history curve on waterfall
private: private:
PeakInfo globalPeak_; // always-tracked highest peak PeakInfo globalPeak_; // always-tracked highest peak
std::vector<PeakInfo> peaks_; std::vector<PeakInfo> peaks_;
// Peak history for waterfall trace (circular buffer, newest at peakHistIdx_)
std::vector<int> 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. // Find top-N peaks with minimum bin separation.
void findPeaks(const std::vector<float>& spectrumDB, int maxN, void findPeaks(const std::vector<float>& spectrumDB, int maxN,
int minDist, float threshold); int minDist, float threshold);