some more refactoring

- UIState.h: Replaced C-style arrays bool[] and ImVec4[] with
std::array<bool, kMaxChannels> and std::array<ImVec4, kMaxChannels> for
type safety and STL compatibility.
  - Application.cpp: Extracted duplicated DPI calculation (lines 189-196
and 475-481) into a systemDpiScale() helper method, reducing both call
sites to single-line calls.
  - Application.cpp: Replaced magic 1400, 900 window dimensions with
kDefaultWindowWidth/kDefaultWindowHeight constants (defined in Types.h).
  - AudioEngine.cpp: Named the magic epsilon thresholds 1e-20f →
kLinearEpsilon and 1e-30f → kLogGuard with explanatory comments, defined
in an anonymous namespace.
  - Types.h: Added kDefaultWindowWidth and kDefaultWindowHeight
constants.
This commit is contained in:
2026-03-28 16:09:29 +01:00
parent b42d7fb69b
commit baf6fd94cc
11 changed files with 105 additions and 109 deletions

View File

@@ -104,6 +104,17 @@ void Application::requestUIScale(float scale) {
pendingScale_ = scale;
}
float Application::systemDpiScale() const {
#ifdef __EMSCRIPTEN__
return js_devicePixelRatio();
#else
float ddpi = 0;
if (SDL_GetDisplayDPI(0, &ddpi, nullptr, nullptr) == 0 && ddpi > 0)
return ddpi / 96.0f;
return 1.0f;
#endif
}
// ── Lifecycle ───────────────────────────────────────────────────────────────
Application::~Application() {
@@ -147,7 +158,7 @@ bool Application::init(int argc, char** argv) {
window_ = SDL_CreateWindow("Baudmine Spectrum Analyzer",
SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
1400, 900,
kDefaultWindowWidth, kDefaultWindowHeight,
SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE |
SDL_WINDOW_ALLOW_HIGHDPI);
if (!window_) {
@@ -186,14 +197,9 @@ bool Application::init(int argc, char** argv) {
// DPI-aware UI scaling
{
float dpiScale = 1.0f;
float dpiScale = systemDpiScale();
#ifdef __EMSCRIPTEN__
dpiScale = js_devicePixelRatio();
lastDpr_ = dpiScale;
#else
float ddpi = 0;
if (SDL_GetDisplayDPI(0, &ddpi, nullptr, nullptr) == 0 && ddpi > 0)
dpiScale = ddpi / 96.0f;
#endif
applyUIScale((uiScale_ > 0.0f) ? uiScale_ : dpiScale);
}
@@ -472,15 +478,7 @@ void Application::render() {
int curPct = static_cast<int>(appliedScale_ * 100.0f + 0.5f);
if (ImGui::MenuItem("Auto", nullptr, uiScale_ == 0.0f)) {
uiScale_ = 0.0f;
float dpiScale = 1.0f;
#ifdef __EMSCRIPTEN__
dpiScale = js_devicePixelRatio();
#else
float ddpi = 0;
if (SDL_GetDisplayDPI(0, &ddpi, nullptr, nullptr) == 0 && ddpi > 0)
dpiScale = ddpi / 96.0f;
#endif
requestUIScale(dpiScale);
requestUIScale(systemDpiScale());
saveConfig();
}
for (int s : kScales) {
@@ -542,9 +540,9 @@ void Application::render() {
measurements_, colorMap_, waterfall_);
ImGui::EndChild();
if (controlPanel_.needsAnalyzerUpdate())
if (controlPanel_.consumeUpdateRequest())
updateAnalyzerSettings();
if (controlPanel_.needsSave())
if (controlPanel_.consumeSaveRequest())
saveConfig();
ImGui::SameLine();

View File

@@ -66,6 +66,7 @@ private:
float lastDpr_ = 0.0f;
void applyUIScale(float scale);
void requestUIScale(float scale);
float systemDpiScale() const;
void syncCanvasSize();
// UI visibility

View File

@@ -128,7 +128,7 @@ void ControlPanel::render(AudioEngine& audio, UIState& ui,
ImGui::SameLine();
if (ImGui::Button(isLog ? "Logarithmic" : "Linear", {ImGui::GetContentRegionAvail().x, 0})) {
if (canLog) {
constexpr float kMinBF = 0.001f;
constexpr float kMinBF = kMinLogBinFrac;
float logMin = std::log10(kMinBF);
auto screenToBin = [&](float sf) -> float {
if (isLog) return std::pow(10.0f, logMin + sf * (0.0f - logMin));

View File

@@ -16,15 +16,14 @@ class WaterfallDisplay;
class ControlPanel {
public:
// Render the sidebar. Returns true if config should be saved.
void render(AudioEngine& audio, UIState& ui,
SpectrumDisplay& specDisplay, Cursors& cursors,
Measurements& measurements, ColorMap& colorMap,
WaterfallDisplay& waterfall);
// Action flags — checked and cleared by Application after render().
bool needsSave() { bool v = needsSave_; needsSave_ = false; return v; }
bool needsAnalyzerUpdate() { bool v = needsUpdate_; needsUpdate_ = false; return v; }
// Consume action flags set during render(). Returns true once, then resets.
bool consumeSaveRequest() { bool v = needsSave_; needsSave_ = false; return v; }
bool consumeUpdateRequest() { bool v = needsUpdate_; needsUpdate_ = false; return v; }
// FFT / analysis controls
static constexpr int kFFTSizes[] = {256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536};

View File

@@ -8,12 +8,37 @@
namespace baudmine {
bool DisplayPanel::splitReleased() {
bool v = splitWasReleased_;
splitWasReleased_ = false;
return v;
namespace {
// Zoom the view range centered on a screen-fraction cursor position.
void zoomView(float& viewLo, float& viewHi, float cursorScreenFrac, float wheelDir) {
float viewFrac = viewLo + cursorScreenFrac * (viewHi - viewLo);
float factor = (wheelDir > 0) ? kZoomFactor : 1.0f / kZoomFactor;
float newSpan = std::clamp((viewHi - viewLo) * factor, 0.001f, 1.0f);
float newLo = viewFrac - cursorScreenFrac * newSpan;
float newHi = newLo + newSpan;
if (newLo < 0.0f) { newHi -= newLo; newLo = 0.0f; }
if (newHi > 1.0f) { newLo -= (newHi - 1.0f); newHi = 1.0f; }
viewLo = std::clamp(newLo, 0.0f, 1.0f);
viewHi = std::clamp(newHi, 0.0f, 1.0f);
}
// Pan the view range by a screen-pixel delta.
void panView(float& viewLo, float& viewHi, float dxPixels, float panelWidth) {
float panFrac = -dxPixels / panelWidth * (viewHi - viewLo);
float span = viewHi - viewLo;
float newLo = viewLo + panFrac;
float newHi = viewHi + panFrac;
if (newLo < 0.0f) { newLo = 0.0f; newHi = span; }
if (newHi > 1.0f) { newHi = 1.0f; newLo = 1.0f - span; }
viewLo = newLo;
viewHi = newHi;
}
} // anonymous namespace
void DisplayPanel::renderSpectrum(AudioEngine& audio, UIState& ui,
SpectrumDisplay& specDisplay, Cursors& cursors,
Measurements& measurements) {
@@ -117,7 +142,7 @@ void DisplayPanel::renderWaterfall(AudioEngine& audio, UIState& ui,
{pos.x + availW, yStart + spanH},
{ui.viewLo, v1}, {ui.viewHi, v0});
} else {
constexpr float kMinBinFrac = 0.001f;
constexpr float kMinBinFrac = kMinLogBinFrac;
float logMin2 = std::log10(kMinBinFrac);
float logMax2 = 0.0f;
int numStrips = std::min(512, static_cast<int>(availW));
@@ -154,12 +179,12 @@ void DisplayPanel::renderWaterfall(AudioEngine& audio, UIState& ui,
// ── Frequency axis labels ──
ImU32 textCol = IM_COL32(180, 180, 200, 200);
double freqFullMin = settings.isIQ ? -settings.sampleRate / 2.0 : 0.0;
double freqFullMax = settings.isIQ ? settings.sampleRate / 2.0 : settings.sampleRate / 2.0;
double freqFullMin = freqMin(settings.sampleRate, settings.isIQ);
double freqFullMax = freqMax(settings.sampleRate, settings.isIQ);
auto viewFracToFreq = [&](float vf) -> double {
if (logMode) {
constexpr float kMinBinFrac = 0.001f;
constexpr float kMinBinFrac = kMinLogBinFrac;
float logMin2 = std::log10(kMinBinFrac);
float logMax2 = 0.0f;
float binFrac = std::pow(10.0f, logMin2 + vf * (logMax2 - logMin2));
@@ -206,8 +231,8 @@ void DisplayPanel::renderWaterfall(AudioEngine& audio, UIState& ui,
settings.isIQ, ui.freqScale,
ui.viewLo, ui.viewHi);
int bins = audio.spectrumSize();
double fMin = settings.isIQ ? -settings.sampleRate / 2.0 : 0.0;
double fMax = settings.isIQ ? settings.sampleRate / 2.0 : settings.sampleRate / 2.0;
double fMin = freqMin(settings.sampleRate, settings.isIQ);
double fMax = freqMax(settings.sampleRate, settings.isIQ);
int bin = static_cast<int>((freq - fMin) / (fMax - fMin) * (bins - 1));
bin = std::clamp(bin, 0, bins - 1);
@@ -225,35 +250,11 @@ void DisplayPanel::renderWaterfall(AudioEngine& audio, UIState& ui,
}
if (inWaterfall) {
if (io.MouseWheel != 0) {
float cursorFrac = (mx - pos.x) / availW;
float viewFrac = ui.viewLo + cursorFrac * (ui.viewHi - ui.viewLo);
if (io.MouseWheel != 0)
zoomView(ui.viewLo, ui.viewHi, (mx - pos.x) / availW, io.MouseWheel);
float zoomFactor = (io.MouseWheel > 0) ? 0.85f : 1.0f / 0.85f;
float newSpan = (ui.viewHi - ui.viewLo) * zoomFactor;
newSpan = std::clamp(newSpan, 0.001f, 1.0f);
float newLo = viewFrac - cursorFrac * newSpan;
float newHi = newLo + newSpan;
if (newLo < 0.0f) { newHi -= newLo; newLo = 0.0f; }
if (newHi > 1.0f) { newLo -= (newHi - 1.0f); newHi = 1.0f; }
// Safe to cast away const on ui here — caller passes mutable UIState
ui.viewLo = std::clamp(newLo, 0.0f, 1.0f);
ui.viewHi = std::clamp(newHi, 0.0f, 1.0f);
}
if (ImGui::IsMouseDragging(ImGuiMouseButton_Middle, 1.0f)) {
float dx = io.MouseDelta.x;
float panFrac = -dx / availW * (ui.viewHi - ui.viewLo);
float newLo = ui.viewLo + panFrac;
float newHi = ui.viewHi + panFrac;
float span = ui.viewHi - ui.viewLo;
if (newLo < 0.0f) { newLo = 0.0f; newHi = span; }
if (newHi > 1.0f) { newHi = 1.0f; newLo = 1.0f - span; }
ui.viewLo = newLo;
ui.viewHi = newHi;
}
if (ImGui::IsMouseDragging(ImGuiMouseButton_Middle, 1.0f))
panView(ui.viewLo, ui.viewHi, io.MouseDelta.x, availW);
if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Middle)) {
ui.viewLo = 0.0f;
@@ -293,8 +294,8 @@ void DisplayPanel::renderHoverOverlay(const AudioEngine& audio, const UIState& u
// Hover info (right side)
{
int bins = audio.spectrumSize();
double fMin = settings.isIQ ? -settings.sampleRate / 2.0 : 0.0;
double fMax = settings.isIQ ? settings.sampleRate / 2.0 : settings.sampleRate / 2.0;
double fMin = freqMin(settings.sampleRate, settings.isIQ);
double fMax = freqMax(settings.sampleRate, settings.isIQ);
double binCenterFreq = fMin + (static_cast<double>(cursors.hover.bin) + 0.5)
/ bins * (fMax - fMin);
@@ -336,9 +337,9 @@ void DisplayPanel::handleSpectrumInput(AudioEngine& audio, UIState& ui,
float dB = specDisplay.screenYToDB(my, posY, sizeY, ui.minDB, ui.maxDB);
int bins = audio.spectrumSize();
double freqMin = settings.isIQ ? -settings.sampleRate / 2.0 : 0.0;
double freqMax = settings.isIQ ? settings.sampleRate / 2.0 : settings.sampleRate / 2.0;
int bin = static_cast<int>((freq - freqMin) / (freqMax - freqMin) * (bins - 1));
double fMin = freqMin(settings.sampleRate, settings.isIQ);
double fMax = freqMax(settings.sampleRate, settings.isIQ);
int bin = static_cast<int>((freq - fMin) / (fMax - fMin) * (bins - 1));
bin = std::clamp(bin, 0, bins - 1);
int curCh = std::clamp(ui.waterfallChannel, 0, audio.totalNumSpectra() - 1);
@@ -371,33 +372,11 @@ void DisplayPanel::handleSpectrumInput(AudioEngine& audio, UIState& ui,
}
}
else if (io.MouseWheel != 0) {
float cursorFrac = (mx - posX) / sizeX;
float viewFrac = ui.viewLo + cursorFrac * (ui.viewHi - ui.viewLo);
float zoomFactor = (io.MouseWheel > 0) ? 0.85f : 1.0f / 0.85f;
float newSpan = (ui.viewHi - ui.viewLo) * zoomFactor;
newSpan = std::clamp(newSpan, 0.001f, 1.0f);
float newLo = viewFrac - cursorFrac * newSpan;
float newHi = newLo + newSpan;
if (newLo < 0.0f) { newHi -= newLo; newLo = 0.0f; }
if (newHi > 1.0f) { newLo -= (newHi - 1.0f); newHi = 1.0f; }
ui.viewLo = std::clamp(newLo, 0.0f, 1.0f);
ui.viewHi = std::clamp(newHi, 0.0f, 1.0f);
zoomView(ui.viewLo, ui.viewHi, (mx - posX) / sizeX, io.MouseWheel);
}
if (ImGui::IsMouseDragging(ImGuiMouseButton_Middle, 1.0f)) {
float dx = io.MouseDelta.x;
float panFrac = -dx / sizeX * (ui.viewHi - ui.viewLo);
float newLo = ui.viewLo + panFrac;
float newHi = ui.viewHi + panFrac;
float span = ui.viewHi - ui.viewLo;
if (newLo < 0.0f) { newLo = 0.0f; newHi = span; }
if (newHi > 1.0f) { newHi = 1.0f; newLo = 1.0f - span; }
ui.viewLo = newLo;
ui.viewHi = newHi;
}
if (ImGui::IsMouseDragging(ImGuiMouseButton_Middle, 1.0f))
panView(ui.viewLo, ui.viewHi, io.MouseDelta.x, sizeX);
if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Middle)) {
ui.viewLo = 0.0f;
@@ -427,6 +406,7 @@ void DisplayPanel::handleTouch(const SDL_Event& event, UIState& ui, SDL_Window*
if (nf >= 2) {
SDL_Finger* f0 = SDL_GetTouchFinger(tid, 0);
SDL_Finger* f1 = SDL_GetTouchFinger(tid, 1);
if (!f0 || !f1) return;
float x0 = f0->x * w, x1 = f1->x * w;
float dx = x1 - x0, dy = (f1->y - f0->y) * h;
touch_.startDist = std::sqrt(dx * dx + dy * dy);
@@ -446,6 +426,7 @@ void DisplayPanel::handleTouch(const SDL_Event& event, UIState& ui, SDL_Window*
if (nf >= 2) {
SDL_Finger* f0 = SDL_GetTouchFinger(tid, 0);
SDL_Finger* f1 = SDL_GetTouchFinger(tid, 1);
if (!f0 || !f1) return;
float x0 = f0->x * w, x1 = f1->x * w;
float dx = x1 - x0, dy = (f1->y - f0->y) * h;
float dist = std::sqrt(dx * dx + dy * dy);

View File

@@ -42,9 +42,6 @@ public:
float spectrumFrac = 0.35f;
bool draggingSplit = false;
// Returns true if split was just released (caller should save config).
bool splitReleased();
private:
void handleSpectrumInput(AudioEngine& audio, UIState& ui,
SpectrumDisplay& specDisplay, Cursors& cursors,
@@ -60,8 +57,6 @@ private:
float lastDist = 0.0f;
} touch_;
bool splitWasReleased_ = false;
// Scratch buffers
std::vector<std::vector<float>> wfSpectraScratch_;
std::vector<WaterfallChannelInfo> wfChInfoScratch_;

View File

@@ -2,6 +2,7 @@
#include "core/Types.h"
#include <imgui.h>
#include <array>
namespace baudmine {
@@ -16,8 +17,8 @@ struct UIState {
int waterfallChannel = 0;
bool waterfallMultiCh = true;
bool channelEnabled[kMaxChannels] = {true,true,true,true,true,true,true,true};
ImVec4 channelColors[kMaxChannels] = {
std::array<bool, kMaxChannels> channelEnabled = {true,true,true,true,true,true,true,true};
std::array<ImVec4, kMaxChannels> channelColors = {{
{0.20f, 0.90f, 0.30f, 1.0f}, // green
{0.70f, 0.30f, 1.00f, 1.0f}, // purple
{1.00f, 0.55f, 0.00f, 1.0f}, // orange
@@ -26,7 +27,7 @@ struct UIState {
{1.00f, 1.00f, 0.30f, 1.0f}, // yellow
{0.50f, 0.80f, 0.50f, 1.0f}, // light green
{0.80f, 0.50f, 0.80f, 1.0f}, // pink
};
}};
};
} // namespace baudmine