peak trace: initial work
This commit is contained in:
@@ -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();
|
||||||
|
|||||||
@@ -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,28 +162,66 @@ 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();
|
||||||
|
|
||||||
auto peakColor = [](int idx) -> ImU32 {
|
// Peak trace: red squiggly line showing peak frequency history
|
||||||
if (idx == 0) return IM_COL32(255, 80, 80, 120);
|
if (showPeakTrace && peakHistLen_ > 1 && screenRows > 1 && spectrumSize > 0) {
|
||||||
return IM_COL32(255, 140, 60, 80);
|
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) {
|
// Convert bin index to screen X via normalized frequency fraction
|
||||||
const auto& p = peaks_[i];
|
auto binToX = [&](int bin) -> float {
|
||||||
float x = specDisplay.freqToScreenX(p.freq, posX, sizeX,
|
float frac = (static_cast<float>(bin) + 0.5f) / spectrumSize;
|
||||||
sampleRate, isIQ, freqScale,
|
// Map through view range
|
||||||
viewLo, viewHi);
|
float viewFrac = (frac - viewLo) / (viewHi - viewLo);
|
||||||
ImU32 col = peakColor(i);
|
return posX + viewFrac * sizeX;
|
||||||
float thickness = (i == 0) ? 1.5f : 1.0f;
|
};
|
||||||
dl->AddLine({x, posY}, {x, posY + sizeY}, col, thickness);
|
|
||||||
|
// 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() {
|
void Measurements::drawPanel() {
|
||||||
|
ImGui::Checkbox("Peak trace", &showPeakTrace);
|
||||||
if (!enabled) return;
|
if (!enabled) return;
|
||||||
|
|
||||||
ImGui::SetNextItemWidth(-1);
|
ImGui::SetNextItemWidth(-1);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user