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:
+28
-9
@@ -68,7 +68,6 @@ void ResetForNewSignal(void)
|
||||
app.bgHighResSeg = 0;
|
||||
app.bgFinished = false;
|
||||
app.isBgProcessing = false;
|
||||
app.amplitudeUserSet = false; // re-enable auto-scaling for the new file
|
||||
|
||||
// Cached STFT results are tied to the old signal data.
|
||||
FreeAllCacheEntries(&app.fftCache);
|
||||
@@ -124,6 +123,9 @@ int main(int argc, char* argv[])
|
||||
app.freqViewStart = 0.0f; app.freqViewEnd = 1.0f;
|
||||
app.showGrid = true;
|
||||
app.colormap = COLORMAP_INFERNO;
|
||||
app.amplitudeMode = SCALE_RELATIVE;
|
||||
app.dynRangeDb = 40.0f; // relative: show 40 dB below the peak
|
||||
app.absoluteFloorDb = -60.0f; // absolute: -60 dBFS floor
|
||||
app.amplitudeFloorDb = -60.0f;
|
||||
app.amplitudeCeilingDb = 0.0f;
|
||||
app.showFileBrowser = false;
|
||||
@@ -206,7 +208,7 @@ int main(int argc, char* argv[])
|
||||
}
|
||||
|
||||
// File browser toggle
|
||||
if (IsKeyPressed(KEY_O) && !app.showFileBrowser) {
|
||||
if (IsKeyPressed(KEY_O) && !app.showFileBrowser && !app.showAbout) {
|
||||
app.showFileBrowser = true;
|
||||
ScanDirectory(GetWorkingDirectory());
|
||||
}
|
||||
@@ -215,7 +217,12 @@ int main(int argc, char* argv[])
|
||||
if (IsKeyPressed(KEY_P)) {
|
||||
app.showScope = !app.showScope;
|
||||
}
|
||||
|
||||
|
||||
// About / help dialog (F1)
|
||||
if (IsKeyPressed(KEY_F1)) {
|
||||
app.showAbout = !app.showAbout;
|
||||
}
|
||||
|
||||
// File browser update
|
||||
if (app.showFileBrowser) {
|
||||
// Browser handles its own input
|
||||
@@ -242,7 +249,7 @@ int main(int argc, char* argv[])
|
||||
}
|
||||
|
||||
// View controls
|
||||
if (app.loaded && !app.showFileBrowser) {
|
||||
if (app.loaded && !app.showFileBrowser && !app.showAbout) {
|
||||
// Spectrogram area fills remaining window space (scaled)
|
||||
float viewScale = GetUIScale();
|
||||
float sidebarWidth = 320 * viewScale;
|
||||
@@ -422,7 +429,7 @@ int main(int argc, char* argv[])
|
||||
}
|
||||
|
||||
// Keyboard shortcuts (SPACE for play/stop toggle, ESC for clear)
|
||||
if (IsKeyPressed(KEY_SPACE) && !app.showFileBrowser) {
|
||||
if (IsKeyPressed(KEY_SPACE) && !app.showFileBrowser && !app.showAbout) {
|
||||
if (app.isPlaying && AudioPlaybackSound.frameCount > 0) {
|
||||
// Currently playing - stop it
|
||||
StopSound(AudioPlaybackSound);
|
||||
@@ -443,12 +450,14 @@ int main(int argc, char* argv[])
|
||||
}
|
||||
|
||||
// Export PNG
|
||||
if (IsKeyPressed(KEY_E) && !app.showFileBrowser && app.stftComputed) {
|
||||
if (IsKeyPressed(KEY_E) && !app.showFileBrowser && !app.showAbout && app.stftComputed) {
|
||||
ExportPNG(&app, app.exportDir);
|
||||
}
|
||||
|
||||
if (IsKeyPressed(KEY_ESCAPE)) {
|
||||
if (app.showFileBrowser) {
|
||||
if (app.showAbout) {
|
||||
app.showAbout = false;
|
||||
} else if (app.showFileBrowser) {
|
||||
app.showFileBrowser = false;
|
||||
} else {
|
||||
// Clear selections instead of exiting
|
||||
@@ -522,7 +531,7 @@ int main(int argc, char* argv[])
|
||||
}
|
||||
|
||||
// LMB drag = box select (time + frequency) OR drag existing selection
|
||||
if (app.loaded && !app.showFileBrowser && CheckCollisionPointRec(mousePos, selBounds)) {
|
||||
if (app.loaded && !app.showFileBrowser && !app.showAbout && CheckCollisionPointRec(mousePos, selBounds)) {
|
||||
// Set cursor to resize all when near divider
|
||||
if (mouseNearDivider && !app.isDividing) {
|
||||
SetMouseCursor(MOUSE_CURSOR_RESIZE_ALL);
|
||||
@@ -620,7 +629,7 @@ int main(int argc, char* argv[])
|
||||
}
|
||||
|
||||
// Handle divider drag
|
||||
if (app.loaded && app.showScope && !app.showFileBrowser) {
|
||||
if (app.loaded && app.showScope && !app.showFileBrowser && !app.showAbout) {
|
||||
// Calculate divider position (using same calculation as selBounds)
|
||||
float viewScale = GetUIScale();
|
||||
float topMargin = 50 * viewScale;
|
||||
@@ -736,6 +745,13 @@ int main(int argc, char* argv[])
|
||||
}
|
||||
}
|
||||
|
||||
// Dismiss the About dialog with a click. Handled here, after the
|
||||
// spectrogram input above (which is gated off while it's open), so the
|
||||
// dismissing click can't fall through and start a selection/pan.
|
||||
if (app.showAbout && IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) {
|
||||
app.showAbout = false;
|
||||
}
|
||||
|
||||
// Rendering
|
||||
BeginDrawing();
|
||||
ClearBackground((Color){ 30, 30, 30, 255 });
|
||||
@@ -959,6 +975,9 @@ int main(int argc, char* argv[])
|
||||
// Draw file browser on top (if active)
|
||||
if (app.showFileBrowser) DrawFileBrowser();
|
||||
|
||||
// About / help dialog (topmost)
|
||||
DrawAboutDialog();
|
||||
|
||||
// Export message notification
|
||||
if (app.exportMessage[0] != '\0') {
|
||||
int msgW = MeasureText(app.exportMessage, 20);
|
||||
|
||||
Reference in New Issue
Block a user