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:
@@ -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();
|
||||
|
||||
@@ -66,6 +66,7 @@ private:
|
||||
float lastDpr_ = 0.0f;
|
||||
void applyUIScale(float scale);
|
||||
void requestUIScale(float scale);
|
||||
float systemDpiScale() const;
|
||||
void syncCanvasSize();
|
||||
|
||||
// UI visibility
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user