diff --git a/src/audio/AudioEngine.cpp b/src/audio/AudioEngine.cpp index bfd585a..c6545a8 100644 --- a/src/audio/AudioEngine.cpp +++ b/src/audio/AudioEngine.cpp @@ -147,6 +147,8 @@ void AudioEngine::clearHistory() { analyzer_.clearHistory(); for (auto& ed : extraDevices_) ed->analyzer.clearHistory(); + for (auto& mw : mathWaterfalls_) + mw.clear(); } int AudioEngine::processAudio() { @@ -204,6 +206,10 @@ int AudioEngine::processAudio() { } } + // Track overruns: spectra lost because they exceeded the history capacity. + if (spectraThisFrame > kWaterfallHistory) + overrunCount_ += spectraThisFrame - kWaterfallHistory; + return spectraThisFrame; } @@ -294,10 +300,17 @@ const char* AudioEngine::getDeviceName(int globalCh) const { // ── Math channels ──────────────────────────────────────────────────────────── +const std::deque>& AudioEngine::mathWaterfallHistory(int mi) const { + static const std::deque> empty; + if (mi < 0 || mi >= static_cast(mathWaterfalls_.size())) return empty; + return mathWaterfalls_[mi]; +} + void AudioEngine::computeMathChannels() { int nPhys = totalNumSpectra(); int specSz = analyzer_.spectrumSize(); mathSpectra_.resize(mathChannels_.size()); + mathWaterfalls_.resize(mathChannels_.size()); for (size_t mi = 0; mi < mathChannels_.size(); ++mi) { const auto& mc = mathChannels_[mi]; @@ -368,6 +381,11 @@ void AudioEngine::computeMathChannels() { } out[i] = val; } + + // Push to math waterfall history. + mathWaterfalls_[mi].push_back(out); + if (mathWaterfalls_[mi].size() > kWaterfallHistory) + mathWaterfalls_[mi].pop_front(); } } diff --git a/src/audio/AudioEngine.h b/src/audio/AudioEngine.h index 4ca13e6..532d80b 100644 --- a/src/audio/AudioEngine.h +++ b/src/audio/AudioEngine.h @@ -6,6 +6,7 @@ #include "dsp/SpectrumAnalyzer.h" #include +#include #include #include #include @@ -52,8 +53,15 @@ public: std::vector& mathChannels() { return mathChannels_; } const std::vector& mathChannels() const { return mathChannels_; } const std::vector>& mathSpectra() const { return mathSpectra_; } + const std::deque>& mathWaterfallHistory(int mi) const; void computeMathChannels(); + // ── Overrun tracking ── + // Counts spectra lost because more were produced in a single frame + // than the waterfall history deque can hold. + int overrunCount() const { return overrunCount_; } + void resetOverrunCount() { overrunCount_ = 0; } + // ── Device selection state (for config persistence) ── int deviceIdx() const { return deviceIdx_; } void setDeviceIdx(int i) { deviceIdx_ = i; } @@ -80,8 +88,12 @@ private: AnalyzerSettings settings_; // Math - std::vector mathChannels_; - std::vector> mathSpectra_; + std::vector mathChannels_; + std::vector> mathSpectra_; + std::vector>> mathWaterfalls_; + + // Overrun + int overrunCount_ = 0; // Device state std::vector devices_; diff --git a/src/core/Types.h b/src/core/Types.h index f288f62..18d786c 100644 --- a/src/core/Types.h +++ b/src/core/Types.h @@ -15,7 +15,7 @@ namespace baudmine { constexpr int kMinFFTSize = 256; constexpr int kMaxFFTSize = 65536; constexpr int kDefaultFFTSize = 4096; -constexpr int kWaterfallHistory = 2048; +constexpr int kWaterfallHistory = 512; constexpr int kDefaultWindowWidth = 1400; constexpr int kDefaultWindowHeight = 900; diff --git a/src/dsp/SpectrumAnalyzer.cpp b/src/dsp/SpectrumAnalyzer.cpp index f4f43e0..0c281f7 100644 --- a/src/dsp/SpectrumAnalyzer.cpp +++ b/src/dsp/SpectrumAnalyzer.cpp @@ -105,7 +105,7 @@ void SpectrumAnalyzer::processBlock() { for (float& v : channelSpectra_[ch]) v += windowCorrection_; channelWaterfalls_[ch].push_back(channelSpectra_[ch]); - if (channelWaterfalls_[ch].size() > kWaterfallHistory) + if (channelWaterfalls_[ch].size() > static_cast(kWaterfallHistory)) channelWaterfalls_[ch].pop_front(); } newSpectrumReady_ = true; diff --git a/src/ui/Application.cpp b/src/ui/Application.cpp index 1ef430f..de65b7e 100644 --- a/src/ui/Application.cpp +++ b/src/ui/Application.cpp @@ -334,16 +334,20 @@ void Application::processAudio() { wfInfo.push_back({c.x, c.y, c.z, ui_.channelEnabled[ch % kMaxChannels]}); } - // Math channels only available for the latest spectrum; - // include them only on the last iteration. - if (si == histSz - 1) { - for (size_t mi = 0; mi < mathChannels.size(); ++mi) { - if (mathChannels[mi].enabled && mathChannels[mi].waterfall && - mi < mathSpectra.size()) { - const auto& c = mathChannels[mi].color; + // Math channels: use their own waterfall history. + for (size_t mi = 0; mi < mathChannels.size(); ++mi) { + if (mathChannels[mi].enabled && mathChannels[mi].waterfall && + mi < mathSpectra.size()) { + const auto& c = mathChannels[mi].color; + const auto& mHist = audio_.mathWaterfallHistory(static_cast(mi)); + int mHistSz = static_cast(mHist.size()); + int mIdx = std::max(0, mHistSz - (histSz - si)); + if (mIdx < mHistSz) { + wfSpectra.push_back(mHist[mIdx]); + } else { wfSpectra.push_back(mathSpectra[mi]); - wfInfo.push_back({c[0], c[1], c[2], true}); } + wfInfo.push_back({c[0], c[1], c[2], true}); } } waterfall_.pushLineMulti(wfSpectra, wfInfo, ui_.minDB, ui_.maxDB); diff --git a/src/ui/ControlPanel.cpp b/src/ui/ControlPanel.cpp index f055126..35d1f7a 100644 --- a/src/ui/ControlPanel.cpp +++ b/src/ui/ControlPanel.cpp @@ -95,6 +95,23 @@ void ControlPanel::render(AudioEngine& audio, UIState& ui, ImGui::GetWindowDrawList()->AddText({tx, ty}, IM_COL32(255, 255, 255, 220), overlayText); if (ImGui::IsItemHovered()) ImGui::SetTooltip("Overlap"); } + + // Overrun indicator + { + int overruns = audio.overrunCount(); + float now = static_cast(ImGui::GetTime()); + if (overruns > lastOverrunCount_) { + lastOverrunCount_ = overruns; + lastOverrunTime_ = now; + } + if (overruns > 0 && (now - lastOverrunTime_) < 3.0f) { + ImGui::TextColored({1.0f, 0.4f, 0.4f, 1.0f}, + "Input overrun: %d FFTs", overruns); + } else if (overruns > 0) { + audio.resetOverrunCount(); + lastOverrunCount_ = 0; + } + } } // ── Display ── diff --git a/src/ui/ControlPanel.h b/src/ui/ControlPanel.h index e9c4848..97c9c0a 100644 --- a/src/ui/ControlPanel.h +++ b/src/ui/ControlPanel.h @@ -45,6 +45,10 @@ private: bool needsSave_ = false; bool needsUpdate_ = false; + // Overrun display + int lastOverrunCount_ = 0; + float lastOverrunTime_ = 0.0f; // ImGui time of last overrun + void flagSave() { needsSave_ = true; } void flagUpdate() { needsUpdate_ = true; } };