feat: spectrum-slice (PSD) panel + two-point marker/ruler tool

Spectrum slice (S): floating panel plotting the time-averaged power
spectrum of the current selection (or the visible view when there's no
selection). Frequency on X over the region's band, auto-ranged dB on Y,
with a peak marker. Backed by ComputePowerSpectrum() in stft.c (mean
linear power per bin over the time span). The selection stat panel now
biases to the left when this panel is up so the two don't overlap.

Marker/ruler tool (M): press-drag-release drops point A and B; the
overlay shows crosshairs, a connecting line, and a readout of the
ham-useful deltas — Δt, Δf, tone spacing (1/Δt), and drift (Δf/Δt).
Marker mode swaps the LMB-drag gesture from box-select to marker drop
(Alt/middle still pan); RMB/Esc clear the measurement. Markers reset on
new-file load alongside the selection.

Both are gated toggles (off by default), wired into the keymap (so they
self-document in the About dialog) and the sidebar.

Verified headlessly: idle viewport pixel-identical to baseline (AE=0,
deterministic); both panels render correctly with sensible numbers.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-25 10:53:57 -07:00
parent 542126261e
commit e8ed19d338
7 changed files with 325 additions and 10 deletions
+17
View File
@@ -426,6 +426,23 @@ void DrawSidebar(void)
DrawPanelBox(gridCheck, app.showGrid ? BLUE : DARKGRAY, WHITE);
DrawTextScaled("Show Grid", x + 25 * scale, y + 2 * scale, 14, LIGHTGRAY); y += 28 * scale;
// Analysis toggles: marker/ruler tool and spectrum-slice (PSD) panel.
Rectangle markerBtn = { x, y, sidebarWidth - 10 * scale, 24 * scale };
if (Clicked(markerBtn)) app.markerMode = !app.markerMode;
DrawPanelBox(markerBtn, app.markerMode ? (Color){ 110, 70, 30, 255 } : (Color){ 50, 50, 60, 255 },
app.markerMode ? ORANGE : GRAY);
DrawTextScaled(app.markerMode ? "Marker Tool: ON (M)" : "Marker Tool (M)",
markerBtn.x + 10 * scale, markerBtn.y + 5 * scale, 13, WHITE);
y += 28 * scale;
Rectangle specBtn = { x, y, sidebarWidth - 10 * scale, 24 * scale };
if (Clicked(specBtn)) app.showSpectrum = !app.showSpectrum;
DrawPanelBox(specBtn, app.showSpectrum ? (Color){ 30, 70, 90, 255 } : (Color){ 50, 50, 60, 255 },
app.showSpectrum ? SKYBLUE : GRAY);
DrawTextScaled(app.showSpectrum ? "Spectrum: ON (S)" : "Spectrum Slice (S)",
specBtn.x + 10 * scale, specBtn.y + 5 * scale, 13, WHITE);
y += 30 * scale;
// File loading
DrawTextScaled("File:", x, y, 14, LIGHTGRAY); y += 20 * scale;
Rectangle fileButton = { x, y, sidebarWidth - 10 * scale, 25 * scale };