feat: dB scale modes (relative/absolute dBFS), about dialog, fix white-out

dB floor handling:
- Fix the white-out/inversion when the floor was cranked up: guard the
  (ceiling - floor) range in ColorizeSpectrogram so floor >= ceiling
  degrades to a hard threshold instead of dividing by ~0 (white) or a
  negative (inverted colors).
- Add two amplitude scale modes, toggled in the sidebar:
  * Relative: ceiling tracks the signal peak, floor sits N dB below it
    (slider = dynamic range, 10..100 dB). Floor can't cross the ceiling.
  * Absolute: fixed dBFS scale (0 dBFS = full scale), slider sets an
    absolute floor in dBFS. Brightness reflects real level.
  Mode + both slider values persist across loads. This replaces the
  amplitudeUserSet flag — storing the intent (range / absolute floor)
  preserves it across re-scales structurally.

About / Help dialog (no help menu existed):
- F1 or sidebar button; documents the scale modes and the caveats
  (dBFS not dBm — a WAV has no power reference; ~6 dB Hann window
  offset; pixels are reassigned energy, not raw bins) plus key list.
- Modal: underlying spectrogram input is gated while open; open/close
  handled in the main loop so the opening click can't self-close it and
  a dismissing click can't fall through into a selection.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-25 01:43:02 -07:00
parent ddbbe2734c
commit 3863ab8620
6 changed files with 174 additions and 30 deletions
+16 -2
View File
@@ -49,6 +49,14 @@ typedef enum {
COLORMAP_COUNT
} ColormapType;
// How the colorizer maps amplitude to brightness:
// - RELATIVE: ceiling tracks the signal peak; floor sits dynRangeDb below it.
// - ABSOLUTE: fixed dBFS scale (0 dBFS ceiling = full scale, absolute floor).
typedef enum {
SCALE_RELATIVE = 0,
SCALE_ABSOLUTE
} AmplitudeScaleMode;
typedef struct {
float frequency;
float amplitude;
@@ -136,10 +144,13 @@ typedef struct {
int cachedVisibleEndY;
bool visibleTextureValid;
// Display settings
// Display settings. amplitudeFloorDb/CeilingDb are the values the colorizer
// actually uses; they're derived from the mode + the controls below.
float amplitudeFloorDb;
float amplitudeCeilingDb;
bool amplitudeUserSet; // true once the user adjusts the dB floor; suppresses auto-scale
AmplitudeScaleMode amplitudeMode;
float dynRangeDb; // RELATIVE mode: dB of range shown below the peak
float absoluteFloorDb; // ABSOLUTE mode: floor in dBFS (ceiling pinned at 0)
ColormapType colormap;
bool showGrid;
int fftSize; // Current FFT size (128-2048)
@@ -150,6 +161,9 @@ typedef struct {
int reassignWidth;
int reassignHeight;
// Overlays
bool showAbout; // About / help dialog
// File browser state
bool showFileBrowser;
char browserPath[512];