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:
@@ -8,6 +8,11 @@
|
|||||||
|
|
||||||
namespace baudmine {
|
namespace baudmine {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
constexpr float kLinearEpsilon = kLinearEpsilon; // threshold for log10 of linear power
|
||||||
|
constexpr float kLogGuard = 1e-30f; // guard against log10(0) in compressed scale
|
||||||
|
} // namespace
|
||||||
|
|
||||||
AudioEngine::AudioEngine() = default;
|
AudioEngine::AudioEngine() = default;
|
||||||
|
|
||||||
// ── Device enumeration ───────────────────────────────────────────────────────
|
// ── Device enumeration ───────────────────────────────────────────────────────
|
||||||
@@ -271,7 +276,7 @@ void AudioEngine::computeMathChannels() {
|
|||||||
out.resize(specSz);
|
out.resize(specSz);
|
||||||
|
|
||||||
if (!mc.enabled) {
|
if (!mc.enabled) {
|
||||||
std::fill(out.begin(), out.end(), -200.0f);
|
std::fill(out.begin(), out.end(), kNoSignalDB);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -283,7 +288,7 @@ void AudioEngine::computeMathChannels() {
|
|||||||
const auto& yC = getComplex(sy);
|
const auto& yC = getComplex(sy);
|
||||||
|
|
||||||
for (int i = 0; i < specSz; ++i) {
|
for (int i = 0; i < specSz; ++i) {
|
||||||
float val = -200.0f;
|
float val = kNoSignalDB;
|
||||||
switch (mc.op) {
|
switch (mc.op) {
|
||||||
case MathOp::Negate: val = -xDB[i]; break;
|
case MathOp::Negate: val = -xDB[i]; break;
|
||||||
case MathOp::Absolute: val = std::abs(xDB[i]); break;
|
case MathOp::Absolute: val = std::abs(xDB[i]); break;
|
||||||
@@ -292,21 +297,21 @@ void AudioEngine::computeMathChannels() {
|
|||||||
case MathOp::Sqrt: val = 0.5f * xDB[i]; break;
|
case MathOp::Sqrt: val = 0.5f * xDB[i]; break;
|
||||||
case MathOp::Log: {
|
case MathOp::Log: {
|
||||||
float lin = std::pow(10.0f, xDB[i] / 10.0f);
|
float lin = std::pow(10.0f, xDB[i] / 10.0f);
|
||||||
val = 10.0f * std::log10(lin + 1e-30f);
|
val = 10.0f * std::log10(lin + kLogGuard);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case MathOp::Add: {
|
case MathOp::Add: {
|
||||||
float lx = std::pow(10.0f, xDB[i] / 10.0f);
|
float lx = std::pow(10.0f, xDB[i] / 10.0f);
|
||||||
float ly = std::pow(10.0f, yDB[i] / 10.0f);
|
float ly = std::pow(10.0f, yDB[i] / 10.0f);
|
||||||
float s = lx + ly;
|
float s = lx + ly;
|
||||||
val = (s > 1e-20f) ? 10.0f * std::log10(s) : -200.0f;
|
val = (s > kLinearEpsilon) ? 10.0f * std::log10(s) : kNoSignalDB;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case MathOp::Subtract: {
|
case MathOp::Subtract: {
|
||||||
float lx = std::pow(10.0f, xDB[i] / 10.0f);
|
float lx = std::pow(10.0f, xDB[i] / 10.0f);
|
||||||
float ly = std::pow(10.0f, yDB[i] / 10.0f);
|
float ly = std::pow(10.0f, yDB[i] / 10.0f);
|
||||||
float d = std::abs(lx - ly);
|
float d = std::abs(lx - ly);
|
||||||
val = (d > 1e-20f) ? 10.0f * std::log10(d) : -200.0f;
|
val = (d > kLinearEpsilon) ? 10.0f * std::log10(d) : kNoSignalDB;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case MathOp::Multiply:
|
case MathOp::Multiply:
|
||||||
@@ -317,7 +322,7 @@ void AudioEngine::computeMathChannels() {
|
|||||||
i < static_cast<int>(yC.size())) {
|
i < static_cast<int>(yC.size())) {
|
||||||
auto cross = xC[i] * std::conj(yC[i]);
|
auto cross = xC[i] * std::conj(yC[i]);
|
||||||
val = std::atan2(cross.imag(), cross.real())
|
val = std::atan2(cross.imag(), cross.real())
|
||||||
* (180.0f / 3.14159265f);
|
* (180.0f / kPi);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -326,7 +331,7 @@ void AudioEngine::computeMathChannels() {
|
|||||||
i < static_cast<int>(yC.size())) {
|
i < static_cast<int>(yC.size())) {
|
||||||
auto cross = xC[i] * std::conj(yC[i]);
|
auto cross = xC[i] * std::conj(yC[i]);
|
||||||
float mag2 = std::norm(cross);
|
float mag2 = std::norm(cross);
|
||||||
val = (mag2 > 1e-20f) ? 10.0f * std::log10(mag2) : -200.0f;
|
val = (mag2 > kLinearEpsilon) ? 10.0f * std::log10(mag2) : kNoSignalDB;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,7 +50,6 @@ public:
|
|||||||
// ── Math channels ──
|
// ── Math channels ──
|
||||||
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_; }
|
||||||
std::vector<std::vector<float>>& mathSpectra() { return mathSpectra_; }
|
|
||||||
const std::vector<std::vector<float>>& mathSpectra() const { return mathSpectra_; }
|
const std::vector<std::vector<float>>& mathSpectra() const { return mathSpectra_; }
|
||||||
void computeMathChannels();
|
void computeMathChannels();
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,15 @@ 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 = 2048;
|
||||||
|
constexpr int kDefaultWindowWidth = 1400;
|
||||||
|
constexpr int kDefaultWindowHeight = 900;
|
||||||
|
|
||||||
|
// ── Shared display constants ────────────────────────────────────────────────
|
||||||
|
|
||||||
|
constexpr float kNoSignalDB = -200.0f; // sentinel for "no signal" in dB
|
||||||
|
constexpr float kZoomFactor = 0.85f; // scroll-wheel zoom step
|
||||||
|
constexpr float kMinLogBinFrac = 0.001f; // minimum bin fraction for log scale
|
||||||
|
constexpr double kPi = 3.14159265358979323846;
|
||||||
|
|
||||||
// ── Enumerations ─────────────────────────────────────────────────────────────
|
// ── Enumerations ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
@@ -87,6 +96,16 @@ inline const char* inputFormatName(InputFormat f) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Frequency range helpers ──────────────────────────────────────────────────
|
||||||
|
|
||||||
|
inline double freqMin(double sampleRate, bool isIQ) {
|
||||||
|
return isIQ ? -sampleRate / 2.0 : 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline double freqMax(double sampleRate, bool isIQ) {
|
||||||
|
return sampleRate / 2.0;
|
||||||
|
}
|
||||||
|
|
||||||
// ── Formatting helpers ───────────────────────────────────────────────────────
|
// ── Formatting helpers ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
// Format frequency into buf with fixed-width numeric field per unit range.
|
// Format frequency into buf with fixed-width numeric field per unit range.
|
||||||
|
|||||||
@@ -4,8 +4,6 @@
|
|||||||
|
|
||||||
namespace baudmine {
|
namespace baudmine {
|
||||||
|
|
||||||
static constexpr double kPi = 3.14159265358979323846;
|
|
||||||
|
|
||||||
void WindowFunctions::generate(WindowType type, int size, std::vector<float>& out,
|
void WindowFunctions::generate(WindowType type, int size, std::vector<float>& out,
|
||||||
float kaiserBeta) {
|
float kaiserBeta) {
|
||||||
out.resize(size);
|
out.resize(size);
|
||||||
|
|||||||
@@ -104,6 +104,17 @@ void Application::requestUIScale(float scale) {
|
|||||||
pendingScale_ = 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 ───────────────────────────────────────────────────────────────
|
// ── Lifecycle ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
Application::~Application() {
|
Application::~Application() {
|
||||||
@@ -147,7 +158,7 @@ bool Application::init(int argc, char** argv) {
|
|||||||
|
|
||||||
window_ = SDL_CreateWindow("Baudmine Spectrum Analyzer",
|
window_ = SDL_CreateWindow("Baudmine Spectrum Analyzer",
|
||||||
SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
|
SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
|
||||||
1400, 900,
|
kDefaultWindowWidth, kDefaultWindowHeight,
|
||||||
SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE |
|
SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE |
|
||||||
SDL_WINDOW_ALLOW_HIGHDPI);
|
SDL_WINDOW_ALLOW_HIGHDPI);
|
||||||
if (!window_) {
|
if (!window_) {
|
||||||
@@ -186,14 +197,9 @@ bool Application::init(int argc, char** argv) {
|
|||||||
|
|
||||||
// DPI-aware UI scaling
|
// DPI-aware UI scaling
|
||||||
{
|
{
|
||||||
float dpiScale = 1.0f;
|
float dpiScale = systemDpiScale();
|
||||||
#ifdef __EMSCRIPTEN__
|
#ifdef __EMSCRIPTEN__
|
||||||
dpiScale = js_devicePixelRatio();
|
|
||||||
lastDpr_ = dpiScale;
|
lastDpr_ = dpiScale;
|
||||||
#else
|
|
||||||
float ddpi = 0;
|
|
||||||
if (SDL_GetDisplayDPI(0, &ddpi, nullptr, nullptr) == 0 && ddpi > 0)
|
|
||||||
dpiScale = ddpi / 96.0f;
|
|
||||||
#endif
|
#endif
|
||||||
applyUIScale((uiScale_ > 0.0f) ? uiScale_ : dpiScale);
|
applyUIScale((uiScale_ > 0.0f) ? uiScale_ : dpiScale);
|
||||||
}
|
}
|
||||||
@@ -472,15 +478,7 @@ void Application::render() {
|
|||||||
int curPct = static_cast<int>(appliedScale_ * 100.0f + 0.5f);
|
int curPct = static_cast<int>(appliedScale_ * 100.0f + 0.5f);
|
||||||
if (ImGui::MenuItem("Auto", nullptr, uiScale_ == 0.0f)) {
|
if (ImGui::MenuItem("Auto", nullptr, uiScale_ == 0.0f)) {
|
||||||
uiScale_ = 0.0f;
|
uiScale_ = 0.0f;
|
||||||
float dpiScale = 1.0f;
|
requestUIScale(systemDpiScale());
|
||||||
#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);
|
|
||||||
saveConfig();
|
saveConfig();
|
||||||
}
|
}
|
||||||
for (int s : kScales) {
|
for (int s : kScales) {
|
||||||
@@ -542,9 +540,9 @@ void Application::render() {
|
|||||||
measurements_, colorMap_, waterfall_);
|
measurements_, colorMap_, waterfall_);
|
||||||
ImGui::EndChild();
|
ImGui::EndChild();
|
||||||
|
|
||||||
if (controlPanel_.needsAnalyzerUpdate())
|
if (controlPanel_.consumeUpdateRequest())
|
||||||
updateAnalyzerSettings();
|
updateAnalyzerSettings();
|
||||||
if (controlPanel_.needsSave())
|
if (controlPanel_.consumeSaveRequest())
|
||||||
saveConfig();
|
saveConfig();
|
||||||
|
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ private:
|
|||||||
float lastDpr_ = 0.0f;
|
float lastDpr_ = 0.0f;
|
||||||
void applyUIScale(float scale);
|
void applyUIScale(float scale);
|
||||||
void requestUIScale(float scale);
|
void requestUIScale(float scale);
|
||||||
|
float systemDpiScale() const;
|
||||||
void syncCanvasSize();
|
void syncCanvasSize();
|
||||||
|
|
||||||
// UI visibility
|
// UI visibility
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ void ControlPanel::render(AudioEngine& audio, UIState& ui,
|
|||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
if (ImGui::Button(isLog ? "Logarithmic" : "Linear", {ImGui::GetContentRegionAvail().x, 0})) {
|
if (ImGui::Button(isLog ? "Logarithmic" : "Linear", {ImGui::GetContentRegionAvail().x, 0})) {
|
||||||
if (canLog) {
|
if (canLog) {
|
||||||
constexpr float kMinBF = 0.001f;
|
constexpr float kMinBF = kMinLogBinFrac;
|
||||||
float logMin = std::log10(kMinBF);
|
float logMin = std::log10(kMinBF);
|
||||||
auto screenToBin = [&](float sf) -> float {
|
auto screenToBin = [&](float sf) -> float {
|
||||||
if (isLog) return std::pow(10.0f, logMin + sf * (0.0f - logMin));
|
if (isLog) return std::pow(10.0f, logMin + sf * (0.0f - logMin));
|
||||||
|
|||||||
@@ -16,15 +16,14 @@ class WaterfallDisplay;
|
|||||||
|
|
||||||
class ControlPanel {
|
class ControlPanel {
|
||||||
public:
|
public:
|
||||||
// Render the sidebar. Returns true if config should be saved.
|
|
||||||
void render(AudioEngine& audio, UIState& ui,
|
void render(AudioEngine& audio, UIState& ui,
|
||||||
SpectrumDisplay& specDisplay, Cursors& cursors,
|
SpectrumDisplay& specDisplay, Cursors& cursors,
|
||||||
Measurements& measurements, ColorMap& colorMap,
|
Measurements& measurements, ColorMap& colorMap,
|
||||||
WaterfallDisplay& waterfall);
|
WaterfallDisplay& waterfall);
|
||||||
|
|
||||||
// Action flags — checked and cleared by Application after render().
|
// Consume action flags set during render(). Returns true once, then resets.
|
||||||
bool needsSave() { bool v = needsSave_; needsSave_ = false; return v; }
|
bool consumeSaveRequest() { bool v = needsSave_; needsSave_ = false; return v; }
|
||||||
bool needsAnalyzerUpdate() { bool v = needsUpdate_; needsUpdate_ = false; return v; }
|
bool consumeUpdateRequest() { bool v = needsUpdate_; needsUpdate_ = false; return v; }
|
||||||
|
|
||||||
// FFT / analysis controls
|
// FFT / analysis controls
|
||||||
static constexpr int kFFTSizes[] = {256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536};
|
static constexpr int kFFTSizes[] = {256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536};
|
||||||
|
|||||||
@@ -8,12 +8,37 @@
|
|||||||
|
|
||||||
namespace baudmine {
|
namespace baudmine {
|
||||||
|
|
||||||
bool DisplayPanel::splitReleased() {
|
namespace {
|
||||||
bool v = splitWasReleased_;
|
|
||||||
splitWasReleased_ = false;
|
// Zoom the view range centered on a screen-fraction cursor position.
|
||||||
return v;
|
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,
|
void DisplayPanel::renderSpectrum(AudioEngine& audio, UIState& ui,
|
||||||
SpectrumDisplay& specDisplay, Cursors& cursors,
|
SpectrumDisplay& specDisplay, Cursors& cursors,
|
||||||
Measurements& measurements) {
|
Measurements& measurements) {
|
||||||
@@ -117,7 +142,7 @@ void DisplayPanel::renderWaterfall(AudioEngine& audio, UIState& ui,
|
|||||||
{pos.x + availW, yStart + spanH},
|
{pos.x + availW, yStart + spanH},
|
||||||
{ui.viewLo, v1}, {ui.viewHi, v0});
|
{ui.viewLo, v1}, {ui.viewHi, v0});
|
||||||
} else {
|
} else {
|
||||||
constexpr float kMinBinFrac = 0.001f;
|
constexpr float kMinBinFrac = kMinLogBinFrac;
|
||||||
float logMin2 = std::log10(kMinBinFrac);
|
float logMin2 = std::log10(kMinBinFrac);
|
||||||
float logMax2 = 0.0f;
|
float logMax2 = 0.0f;
|
||||||
int numStrips = std::min(512, static_cast<int>(availW));
|
int numStrips = std::min(512, static_cast<int>(availW));
|
||||||
@@ -154,12 +179,12 @@ void DisplayPanel::renderWaterfall(AudioEngine& audio, UIState& ui,
|
|||||||
|
|
||||||
// ── Frequency axis labels ──
|
// ── Frequency axis labels ──
|
||||||
ImU32 textCol = IM_COL32(180, 180, 200, 200);
|
ImU32 textCol = IM_COL32(180, 180, 200, 200);
|
||||||
double freqFullMin = settings.isIQ ? -settings.sampleRate / 2.0 : 0.0;
|
double freqFullMin = freqMin(settings.sampleRate, settings.isIQ);
|
||||||
double freqFullMax = settings.isIQ ? settings.sampleRate / 2.0 : settings.sampleRate / 2.0;
|
double freqFullMax = freqMax(settings.sampleRate, settings.isIQ);
|
||||||
|
|
||||||
auto viewFracToFreq = [&](float vf) -> double {
|
auto viewFracToFreq = [&](float vf) -> double {
|
||||||
if (logMode) {
|
if (logMode) {
|
||||||
constexpr float kMinBinFrac = 0.001f;
|
constexpr float kMinBinFrac = kMinLogBinFrac;
|
||||||
float logMin2 = std::log10(kMinBinFrac);
|
float logMin2 = std::log10(kMinBinFrac);
|
||||||
float logMax2 = 0.0f;
|
float logMax2 = 0.0f;
|
||||||
float binFrac = std::pow(10.0f, logMin2 + vf * (logMax2 - logMin2));
|
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,
|
settings.isIQ, ui.freqScale,
|
||||||
ui.viewLo, ui.viewHi);
|
ui.viewLo, ui.viewHi);
|
||||||
int bins = audio.spectrumSize();
|
int bins = audio.spectrumSize();
|
||||||
double fMin = settings.isIQ ? -settings.sampleRate / 2.0 : 0.0;
|
double fMin = freqMin(settings.sampleRate, settings.isIQ);
|
||||||
double fMax = settings.isIQ ? settings.sampleRate / 2.0 : settings.sampleRate / 2.0;
|
double fMax = freqMax(settings.sampleRate, settings.isIQ);
|
||||||
int bin = static_cast<int>((freq - fMin) / (fMax - fMin) * (bins - 1));
|
int bin = static_cast<int>((freq - fMin) / (fMax - fMin) * (bins - 1));
|
||||||
bin = std::clamp(bin, 0, bins - 1);
|
bin = std::clamp(bin, 0, bins - 1);
|
||||||
|
|
||||||
@@ -225,35 +250,11 @@ void DisplayPanel::renderWaterfall(AudioEngine& audio, UIState& ui,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (inWaterfall) {
|
if (inWaterfall) {
|
||||||
if (io.MouseWheel != 0) {
|
if (io.MouseWheel != 0)
|
||||||
float cursorFrac = (mx - pos.x) / availW;
|
zoomView(ui.viewLo, ui.viewHi, (mx - pos.x) / availW, io.MouseWheel);
|
||||||
float viewFrac = ui.viewLo + cursorFrac * (ui.viewHi - ui.viewLo);
|
|
||||||
|
|
||||||
float zoomFactor = (io.MouseWheel > 0) ? 0.85f : 1.0f / 0.85f;
|
if (ImGui::IsMouseDragging(ImGuiMouseButton_Middle, 1.0f))
|
||||||
float newSpan = (ui.viewHi - ui.viewLo) * zoomFactor;
|
panView(ui.viewLo, ui.viewHi, io.MouseDelta.x, availW);
|
||||||
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::IsMouseDoubleClicked(ImGuiMouseButton_Middle)) {
|
if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Middle)) {
|
||||||
ui.viewLo = 0.0f;
|
ui.viewLo = 0.0f;
|
||||||
@@ -293,8 +294,8 @@ void DisplayPanel::renderHoverOverlay(const AudioEngine& audio, const UIState& u
|
|||||||
// Hover info (right side)
|
// Hover info (right side)
|
||||||
{
|
{
|
||||||
int bins = audio.spectrumSize();
|
int bins = audio.spectrumSize();
|
||||||
double fMin = settings.isIQ ? -settings.sampleRate / 2.0 : 0.0;
|
double fMin = freqMin(settings.sampleRate, settings.isIQ);
|
||||||
double fMax = settings.isIQ ? settings.sampleRate / 2.0 : settings.sampleRate / 2.0;
|
double fMax = freqMax(settings.sampleRate, settings.isIQ);
|
||||||
double binCenterFreq = fMin + (static_cast<double>(cursors.hover.bin) + 0.5)
|
double binCenterFreq = fMin + (static_cast<double>(cursors.hover.bin) + 0.5)
|
||||||
/ bins * (fMax - fMin);
|
/ 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);
|
float dB = specDisplay.screenYToDB(my, posY, sizeY, ui.minDB, ui.maxDB);
|
||||||
|
|
||||||
int bins = audio.spectrumSize();
|
int bins = audio.spectrumSize();
|
||||||
double freqMin = settings.isIQ ? -settings.sampleRate / 2.0 : 0.0;
|
double fMin = freqMin(settings.sampleRate, settings.isIQ);
|
||||||
double freqMax = settings.isIQ ? settings.sampleRate / 2.0 : settings.sampleRate / 2.0;
|
double fMax = freqMax(settings.sampleRate, settings.isIQ);
|
||||||
int bin = static_cast<int>((freq - freqMin) / (freqMax - freqMin) * (bins - 1));
|
int bin = static_cast<int>((freq - fMin) / (fMax - fMin) * (bins - 1));
|
||||||
bin = std::clamp(bin, 0, bins - 1);
|
bin = std::clamp(bin, 0, bins - 1);
|
||||||
|
|
||||||
int curCh = std::clamp(ui.waterfallChannel, 0, audio.totalNumSpectra() - 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) {
|
else if (io.MouseWheel != 0) {
|
||||||
float cursorFrac = (mx - posX) / sizeX;
|
zoomView(ui.viewLo, ui.viewHi, (mx - posX) / sizeX, io.MouseWheel);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ImGui::IsMouseDragging(ImGuiMouseButton_Middle, 1.0f)) {
|
if (ImGui::IsMouseDragging(ImGuiMouseButton_Middle, 1.0f))
|
||||||
float dx = io.MouseDelta.x;
|
panView(ui.viewLo, ui.viewHi, io.MouseDelta.x, sizeX);
|
||||||
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::IsMouseDoubleClicked(ImGuiMouseButton_Middle)) {
|
if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Middle)) {
|
||||||
ui.viewLo = 0.0f;
|
ui.viewLo = 0.0f;
|
||||||
@@ -427,6 +406,7 @@ void DisplayPanel::handleTouch(const SDL_Event& event, UIState& ui, SDL_Window*
|
|||||||
if (nf >= 2) {
|
if (nf >= 2) {
|
||||||
SDL_Finger* f0 = SDL_GetTouchFinger(tid, 0);
|
SDL_Finger* f0 = SDL_GetTouchFinger(tid, 0);
|
||||||
SDL_Finger* f1 = SDL_GetTouchFinger(tid, 1);
|
SDL_Finger* f1 = SDL_GetTouchFinger(tid, 1);
|
||||||
|
if (!f0 || !f1) return;
|
||||||
float x0 = f0->x * w, x1 = f1->x * w;
|
float x0 = f0->x * w, x1 = f1->x * w;
|
||||||
float dx = x1 - x0, dy = (f1->y - f0->y) * h;
|
float dx = x1 - x0, dy = (f1->y - f0->y) * h;
|
||||||
touch_.startDist = std::sqrt(dx * dx + dy * dy);
|
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) {
|
if (nf >= 2) {
|
||||||
SDL_Finger* f0 = SDL_GetTouchFinger(tid, 0);
|
SDL_Finger* f0 = SDL_GetTouchFinger(tid, 0);
|
||||||
SDL_Finger* f1 = SDL_GetTouchFinger(tid, 1);
|
SDL_Finger* f1 = SDL_GetTouchFinger(tid, 1);
|
||||||
|
if (!f0 || !f1) return;
|
||||||
float x0 = f0->x * w, x1 = f1->x * w;
|
float x0 = f0->x * w, x1 = f1->x * w;
|
||||||
float dx = x1 - x0, dy = (f1->y - f0->y) * h;
|
float dx = x1 - x0, dy = (f1->y - f0->y) * h;
|
||||||
float dist = std::sqrt(dx * dx + dy * dy);
|
float dist = std::sqrt(dx * dx + dy * dy);
|
||||||
|
|||||||
@@ -42,9 +42,6 @@ public:
|
|||||||
float spectrumFrac = 0.35f;
|
float spectrumFrac = 0.35f;
|
||||||
bool draggingSplit = false;
|
bool draggingSplit = false;
|
||||||
|
|
||||||
// Returns true if split was just released (caller should save config).
|
|
||||||
bool splitReleased();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void handleSpectrumInput(AudioEngine& audio, UIState& ui,
|
void handleSpectrumInput(AudioEngine& audio, UIState& ui,
|
||||||
SpectrumDisplay& specDisplay, Cursors& cursors,
|
SpectrumDisplay& specDisplay, Cursors& cursors,
|
||||||
@@ -60,8 +57,6 @@ private:
|
|||||||
float lastDist = 0.0f;
|
float lastDist = 0.0f;
|
||||||
} touch_;
|
} touch_;
|
||||||
|
|
||||||
bool splitWasReleased_ = false;
|
|
||||||
|
|
||||||
// Scratch buffers
|
// Scratch buffers
|
||||||
std::vector<std::vector<float>> wfSpectraScratch_;
|
std::vector<std::vector<float>> wfSpectraScratch_;
|
||||||
std::vector<WaterfallChannelInfo> wfChInfoScratch_;
|
std::vector<WaterfallChannelInfo> wfChInfoScratch_;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include "core/Types.h"
|
#include "core/Types.h"
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
|
#include <array>
|
||||||
|
|
||||||
namespace baudmine {
|
namespace baudmine {
|
||||||
|
|
||||||
@@ -16,8 +17,8 @@ struct UIState {
|
|||||||
|
|
||||||
int waterfallChannel = 0;
|
int waterfallChannel = 0;
|
||||||
bool waterfallMultiCh = true;
|
bool waterfallMultiCh = true;
|
||||||
bool channelEnabled[kMaxChannels] = {true,true,true,true,true,true,true,true};
|
std::array<bool, kMaxChannels> channelEnabled = {true,true,true,true,true,true,true,true};
|
||||||
ImVec4 channelColors[kMaxChannels] = {
|
std::array<ImVec4, kMaxChannels> channelColors = {{
|
||||||
{0.20f, 0.90f, 0.30f, 1.0f}, // green
|
{0.20f, 0.90f, 0.30f, 1.0f}, // green
|
||||||
{0.70f, 0.30f, 1.00f, 1.0f}, // purple
|
{0.70f, 0.30f, 1.00f, 1.0f}, // purple
|
||||||
{1.00f, 0.55f, 0.00f, 1.0f}, // orange
|
{1.00f, 0.55f, 0.00f, 1.0f}, // orange
|
||||||
@@ -26,7 +27,7 @@ struct UIState {
|
|||||||
{1.00f, 1.00f, 0.30f, 1.0f}, // yellow
|
{1.00f, 1.00f, 0.30f, 1.0f}, // yellow
|
||||||
{0.50f, 0.80f, 0.50f, 1.0f}, // light green
|
{0.50f, 0.80f, 0.50f, 1.0f}, // light green
|
||||||
{0.80f, 0.50f, 0.80f, 1.0f}, // pink
|
{0.80f, 0.50f, 0.80f, 1.0f}, // pink
|
||||||
};
|
}};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace baudmine
|
} // namespace baudmine
|
||||||
|
|||||||
Reference in New Issue
Block a user