implement the FFT deques also for math channels, add an "input overrun" indication
This commit is contained in:
@@ -147,6 +147,8 @@ void AudioEngine::clearHistory() {
|
|||||||
analyzer_.clearHistory();
|
analyzer_.clearHistory();
|
||||||
for (auto& ed : extraDevices_)
|
for (auto& ed : extraDevices_)
|
||||||
ed->analyzer.clearHistory();
|
ed->analyzer.clearHistory();
|
||||||
|
for (auto& mw : mathWaterfalls_)
|
||||||
|
mw.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
int AudioEngine::processAudio() {
|
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;
|
return spectraThisFrame;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -294,10 +300,17 @@ const char* AudioEngine::getDeviceName(int globalCh) const {
|
|||||||
|
|
||||||
// ── Math channels ────────────────────────────────────────────────────────────
|
// ── Math channels ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
const std::deque<std::vector<float>>& AudioEngine::mathWaterfallHistory(int mi) const {
|
||||||
|
static const std::deque<std::vector<float>> empty;
|
||||||
|
if (mi < 0 || mi >= static_cast<int>(mathWaterfalls_.size())) return empty;
|
||||||
|
return mathWaterfalls_[mi];
|
||||||
|
}
|
||||||
|
|
||||||
void AudioEngine::computeMathChannels() {
|
void AudioEngine::computeMathChannels() {
|
||||||
int nPhys = totalNumSpectra();
|
int nPhys = totalNumSpectra();
|
||||||
int specSz = analyzer_.spectrumSize();
|
int specSz = analyzer_.spectrumSize();
|
||||||
mathSpectra_.resize(mathChannels_.size());
|
mathSpectra_.resize(mathChannels_.size());
|
||||||
|
mathWaterfalls_.resize(mathChannels_.size());
|
||||||
|
|
||||||
for (size_t mi = 0; mi < mathChannels_.size(); ++mi) {
|
for (size_t mi = 0; mi < mathChannels_.size(); ++mi) {
|
||||||
const auto& mc = mathChannels_[mi];
|
const auto& mc = mathChannels_[mi];
|
||||||
@@ -368,6 +381,11 @@ void AudioEngine::computeMathChannels() {
|
|||||||
}
|
}
|
||||||
out[i] = val;
|
out[i] = val;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Push to math waterfall history.
|
||||||
|
mathWaterfalls_[mi].push_back(out);
|
||||||
|
if (mathWaterfalls_[mi].size() > kWaterfallHistory)
|
||||||
|
mathWaterfalls_[mi].pop_front();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#include "dsp/SpectrumAnalyzer.h"
|
#include "dsp/SpectrumAnalyzer.h"
|
||||||
|
|
||||||
#include <complex>
|
#include <complex>
|
||||||
|
#include <deque>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
@@ -52,8 +53,15 @@ public:
|
|||||||
std::vector<MathChannel>& mathChannels() { return mathChannels_; }
|
std::vector<MathChannel>& mathChannels() { return mathChannels_; }
|
||||||
const std::vector<MathChannel>& mathChannels() const { return mathChannels_; }
|
const std::vector<MathChannel>& mathChannels() const { return mathChannels_; }
|
||||||
const std::vector<std::vector<float>>& mathSpectra() const { return mathSpectra_; }
|
const std::vector<std::vector<float>>& mathSpectra() const { return mathSpectra_; }
|
||||||
|
const std::deque<std::vector<float>>& mathWaterfallHistory(int mi) const;
|
||||||
void computeMathChannels();
|
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) ──
|
// ── Device selection state (for config persistence) ──
|
||||||
int deviceIdx() const { return deviceIdx_; }
|
int deviceIdx() const { return deviceIdx_; }
|
||||||
void setDeviceIdx(int i) { deviceIdx_ = i; }
|
void setDeviceIdx(int i) { deviceIdx_ = i; }
|
||||||
@@ -82,6 +90,10 @@ private:
|
|||||||
// Math
|
// Math
|
||||||
std::vector<MathChannel> mathChannels_;
|
std::vector<MathChannel> mathChannels_;
|
||||||
std::vector<std::vector<float>> mathSpectra_;
|
std::vector<std::vector<float>> mathSpectra_;
|
||||||
|
std::vector<std::deque<std::vector<float>>> mathWaterfalls_;
|
||||||
|
|
||||||
|
// Overrun
|
||||||
|
int overrunCount_ = 0;
|
||||||
|
|
||||||
// Device state
|
// Device state
|
||||||
std::vector<MiniAudioSource::DeviceInfo> devices_;
|
std::vector<MiniAudioSource::DeviceInfo> devices_;
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ namespace baudmine {
|
|||||||
constexpr int kMinFFTSize = 256;
|
constexpr int kMinFFTSize = 256;
|
||||||
constexpr int kMaxFFTSize = 65536;
|
constexpr int kMaxFFTSize = 65536;
|
||||||
constexpr int kDefaultFFTSize = 4096;
|
constexpr int kDefaultFFTSize = 4096;
|
||||||
constexpr int kWaterfallHistory = 2048;
|
constexpr int kWaterfallHistory = 512;
|
||||||
constexpr int kDefaultWindowWidth = 1400;
|
constexpr int kDefaultWindowWidth = 1400;
|
||||||
constexpr int kDefaultWindowHeight = 900;
|
constexpr int kDefaultWindowHeight = 900;
|
||||||
|
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ void SpectrumAnalyzer::processBlock() {
|
|||||||
for (float& v : channelSpectra_[ch])
|
for (float& v : channelSpectra_[ch])
|
||||||
v += windowCorrection_;
|
v += windowCorrection_;
|
||||||
channelWaterfalls_[ch].push_back(channelSpectra_[ch]);
|
channelWaterfalls_[ch].push_back(channelSpectra_[ch]);
|
||||||
if (channelWaterfalls_[ch].size() > kWaterfallHistory)
|
if (channelWaterfalls_[ch].size() > static_cast<size_t>(kWaterfallHistory))
|
||||||
channelWaterfalls_[ch].pop_front();
|
channelWaterfalls_[ch].pop_front();
|
||||||
}
|
}
|
||||||
newSpectrumReady_ = true;
|
newSpectrumReady_ = true;
|
||||||
|
|||||||
@@ -334,16 +334,20 @@ void Application::processAudio() {
|
|||||||
wfInfo.push_back({c.x, c.y, c.z,
|
wfInfo.push_back({c.x, c.y, c.z,
|
||||||
ui_.channelEnabled[ch % kMaxChannels]});
|
ui_.channelEnabled[ch % kMaxChannels]});
|
||||||
}
|
}
|
||||||
// Math channels only available for the latest spectrum;
|
// Math channels: use their own waterfall history.
|
||||||
// include them only on the last iteration.
|
|
||||||
if (si == histSz - 1) {
|
|
||||||
for (size_t mi = 0; mi < mathChannels.size(); ++mi) {
|
for (size_t mi = 0; mi < mathChannels.size(); ++mi) {
|
||||||
if (mathChannels[mi].enabled && mathChannels[mi].waterfall &&
|
if (mathChannels[mi].enabled && mathChannels[mi].waterfall &&
|
||||||
mi < mathSpectra.size()) {
|
mi < mathSpectra.size()) {
|
||||||
const auto& c = mathChannels[mi].color;
|
const auto& c = mathChannels[mi].color;
|
||||||
|
const auto& mHist = audio_.mathWaterfallHistory(static_cast<int>(mi));
|
||||||
|
int mHistSz = static_cast<int>(mHist.size());
|
||||||
|
int mIdx = std::max(0, mHistSz - (histSz - si));
|
||||||
|
if (mIdx < mHistSz) {
|
||||||
|
wfSpectra.push_back(mHist[mIdx]);
|
||||||
|
} else {
|
||||||
wfSpectra.push_back(mathSpectra[mi]);
|
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);
|
waterfall_.pushLineMulti(wfSpectra, wfInfo, ui_.minDB, ui_.maxDB);
|
||||||
|
|||||||
@@ -95,6 +95,23 @@ void ControlPanel::render(AudioEngine& audio, UIState& ui,
|
|||||||
ImGui::GetWindowDrawList()->AddText({tx, ty}, IM_COL32(255, 255, 255, 220), overlayText);
|
ImGui::GetWindowDrawList()->AddText({tx, ty}, IM_COL32(255, 255, 255, 220), overlayText);
|
||||||
if (ImGui::IsItemHovered()) ImGui::SetTooltip("Overlap");
|
if (ImGui::IsItemHovered()) ImGui::SetTooltip("Overlap");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Overrun indicator
|
||||||
|
{
|
||||||
|
int overruns = audio.overrunCount();
|
||||||
|
float now = static_cast<float>(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 ──
|
// ── Display ──
|
||||||
|
|||||||
@@ -45,6 +45,10 @@ private:
|
|||||||
bool needsSave_ = false;
|
bool needsSave_ = false;
|
||||||
bool needsUpdate_ = false;
|
bool needsUpdate_ = false;
|
||||||
|
|
||||||
|
// Overrun display
|
||||||
|
int lastOverrunCount_ = 0;
|
||||||
|
float lastOverrunTime_ = 0.0f; // ImGui time of last overrun
|
||||||
|
|
||||||
void flagSave() { needsSave_ = true; }
|
void flagSave() { needsSave_ = true; }
|
||||||
void flagUpdate() { needsUpdate_ = true; }
|
void flagUpdate() { needsUpdate_ = true; }
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user