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_,
settings_.sampleRate, settings_.isIQ, freqScale_,
viewLo_, viewHi_);
viewLo_, viewHi_, screenRows, analyzer_.spectrumSize());
// ── Mouse interaction: zoom, pan & hover on waterfall ──
ImGuiIO& io = ImGui::GetIO();

View File

@@ -70,6 +70,14 @@ void Measurements::update(const std::vector<float>& 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<int>(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<int>(peakHistBins_.size());
int count = std::min(screenRows, peakHistLen_);
ImU32 traceCol = IM_COL32(255, 30, 30, 200);
for (int i = 0; i < static_cast<int>(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<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 {
if (idx == 0) return IM_COL32(255, 80, 80, 120);
return IM_COL32(255, 140, 60, 80);
};
for (int i = 0; i < static_cast<int>(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);

View File

@@ -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<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.
void findPeaks(const std::vector<float>& spectrumDB, int maxN,
int minDist, float threshold);