diff --git a/src/render.c b/src/render.c index 6f38a4a..1e4d67e 100644 --- a/src/render.c +++ b/src/render.c @@ -41,48 +41,79 @@ float MeasureTextScaled(const char* text, float baseSize) } // ===== Colormaps ===== +// Each colormap maps t in [0,1] to a Color. To add a colormap: add an enum +// value in spectrogram_types.h, write a Cmap* function, and add one row to +// COLORMAPS[] below — the sidebar names and lookups follow automatically. +typedef Color (*ColormapFn)(float t); + +static Color CmapGrays(float t) +{ + unsigned char v = (unsigned char)(t * 255); + return (Color){ v, v, v, 255 }; +} + +static Color CmapInferno(float t) +{ + float r = 0.0f, g = 0.0f, b = 0.0f; + if (t < 0.25f) { t = t / 0.25f; r = 0.0f + t * 0.5f; g = 0.0f; b = 0.0f + t * 0.3f; } + else if (t < 0.5f) { t = (t - 0.25f) / 0.25f; r = 0.5f + t * 0.5f; g = 0.0f + t * 0.3f; b = 0.3f + t * 0.4f; } + else if (t < 0.75f) { t = (t - 0.5f) / 0.25f; r = 1.0f; g = 0.3f + t * 0.5f; b = 0.7f + t * 0.2f; } + else { t = (t - 0.75f) / 0.25f; r = 1.0f; g = 0.8f + t * 0.2f; b = 0.9f + t * 0.1f; } + return (Color){ (unsigned char)(r * 255), (unsigned char)(g * 255), (unsigned char)(b * 255), 255 }; +} + +static Color CmapViridis(float t) +{ + float r, g, b; + if (t < 0.25f) { t = t / 0.25f; r = 0.27f + t * 0.13f; g = 0.00f + t * 0.33f; b = 0.33f + t * 0.27f; } + else if (t < 0.5f) { t = (t - 0.25f) / 0.25f; r = 0.40f + t * 0.16f; g = 0.33f + t * 0.29f; b = 0.60f - t * 0.20f; } + else if (t < 0.75f) { t = (t - 0.5f) / 0.25f; r = 0.56f + t * 0.24f; g = 0.62f + t * 0.23f; b = 0.40f - t * 0.20f; } + else { t = (t - 0.75f) / 0.25f; r = 0.80f + t * 0.17f; g = 0.85f + t * 0.12f; b = 0.20f - t * 0.15f; } + return (Color){ (unsigned char)(r * 255), (unsigned char)(g * 255), (unsigned char)(b * 255), 255 }; +} + +static Color CmapPlasma(float t) +{ + float r = 0.05f + t * 0.9f; + float g = 0.0f + t * 0.6f + (t > 0.5f ? (t - 0.5f) * 0.4f : 0.0f); + float b = 0.6f - t * 0.5f; + return (Color){ (unsigned char)(r * 255), (unsigned char)(g * 255), (unsigned char)(b * 255), 255 }; +} + +static Color CmapHot(float t) +{ + float r = Clamp(t * 3.0f, 0.0f, 1.0f); + float g = Clamp((t - 0.33f) * 3.0f, 0.0f, 1.0f); + float b = Clamp((t - 0.66f) * 3.0f, 0.0f, 1.0f); + return (Color){ (unsigned char)(r * 255), (unsigned char)(g * 255), (unsigned char)(b * 255), 255 }; +} + +static Color CmapCool(float t) +{ + return (Color){ (unsigned char)(t * 255), (unsigned char)((1.0f - t) * 255), 255, 255 }; +} + +typedef struct { const char* name; ColormapFn fn; } ColormapDef; + +static const ColormapDef COLORMAPS[COLORMAP_COUNT] = { + [COLORMAP_GRAYS] = { "Grays", CmapGrays }, + [COLORMAP_INFERNO] = { "Inferno", CmapInferno }, + [COLORMAP_VIRIDIS] = { "Viridis", CmapViridis }, + [COLORMAP_PLASMA] = { "Plasma", CmapPlasma }, + [COLORMAP_HOT] = { "Hot", CmapHot }, + [COLORMAP_COOL] = { "Cool", CmapCool }, +}; + static Color GetColormapColor(float t, ColormapType type) { - t = Clamp(t, 0.0f, 1.0f); - - switch (type) { - case COLORMAP_GRAYS: { - unsigned char v = (unsigned char)(t * 255); - return (Color){ v, v, v, 255 }; - } - case COLORMAP_INFERNO: { - float r = 0.0f, g = 0.0f, b = 0.0f; - if (t < 0.25f) { t = t / 0.25f; r = 0.0f + t * 0.5f; g = 0.0f; b = 0.0f + t * 0.3f; } - else if (t < 0.5f) { t = (t - 0.25f) / 0.25f; r = 0.5f + t * 0.5f; g = 0.0f + t * 0.3f; b = 0.3f + t * 0.4f; } - else if (t < 0.75f) { t = (t - 0.5f) / 0.25f; r = 1.0f; g = 0.3f + t * 0.5f; b = 0.7f + t * 0.2f; } - else { t = (t - 0.75f) / 0.25f; r = 1.0f; g = 0.8f + t * 0.2f; b = 0.9f + t * 0.1f; } - return (Color){ (unsigned char)(r * 255), (unsigned char)(g * 255), (unsigned char)(b * 255), 255 }; - } - case COLORMAP_VIRIDIS: { - float r, g, b; - if (t < 0.25f) { t = t / 0.25f; r = 0.27f + t * 0.13f; g = 0.00f + t * 0.33f; b = 0.33f + t * 0.27f; } - else if (t < 0.5f) { t = (t - 0.25f) / 0.25f; r = 0.40f + t * 0.16f; g = 0.33f + t * 0.29f; b = 0.60f - t * 0.20f; } - else if (t < 0.75f) { t = (t - 0.5f) / 0.25f; r = 0.56f + t * 0.24f; g = 0.62f + t * 0.23f; b = 0.40f - t * 0.20f; } - else { t = (t - 0.75f) / 0.25f; r = 0.80f + t * 0.17f; g = 0.85f + t * 0.12f; b = 0.20f - t * 0.15f; } - return (Color){ (unsigned char)(r * 255), (unsigned char)(g * 255), (unsigned char)(b * 255), 255 }; - } - case COLORMAP_PLASMA: { - float r = 0.05f + t * 0.9f; - float g = 0.0f + t * 0.6f + (t > 0.5f ? (t - 0.5f) * 0.4f : 0.0f); - float b = 0.6f - t * 0.5f; - return (Color){ (unsigned char)(r * 255), (unsigned char)(g * 255), (unsigned char)(b * 255), 255 }; - } - case COLORMAP_HOT: { - float r = Clamp(t * 3.0f, 0.0f, 1.0f); - float g = Clamp((t - 0.33f) * 3.0f, 0.0f, 1.0f); - float b = Clamp((t - 0.66f) * 3.0f, 0.0f, 1.0f); - return (Color){ (unsigned char)(r * 255), (unsigned char)(g * 255), (unsigned char)(b * 255), 255 }; - } - case COLORMAP_COOL: { - return (Color){ (unsigned char)(t * 255), (unsigned char)((1.0f - t) * 255), 255, 255 }; - } - default: return GRAY; - } + if (type < 0 || type >= COLORMAP_COUNT) return GRAY; + return COLORMAPS[type].fn(Clamp(t, 0.0f, 1.0f)); +} + +const char* ColormapName(ColormapType type) +{ + if (type < 0 || type >= COLORMAP_COUNT) return "?"; + return COLORMAPS[type].name; } void GenerateColormapTexture(void) diff --git a/src/render.h b/src/render.h index 6e061b2..2ed7ec2 100644 --- a/src/render.h +++ b/src/render.h @@ -11,6 +11,7 @@ float MeasureTextScaled(const char* text, float baseSize); // --- Colormaps --- void GenerateColormapTexture(void); +const char* ColormapName(ColormapType type); // --- Spectrogram texture --- // GenerateSpectrogramTexture recomputes the synchrosqueezed reassignment (use diff --git a/src/spectrogram.c b/src/spectrogram.c index 76387b8..527b57e 100644 --- a/src/spectrogram.c +++ b/src/spectrogram.c @@ -217,7 +217,7 @@ int main(int argc, char* argv[]) } // File browser toggle - if (IsKeyPressed(KEY_O) && !app.showFileBrowser && !app.showAbout) { + if (IsKeyPressed(KEY_O) && !UiModalOpen()) { app.showFileBrowser = true; ScanDirectory(GetWorkingDirectory()); } @@ -258,7 +258,7 @@ int main(int argc, char* argv[]) } // View controls - if (app.loaded && !app.showFileBrowser && !app.showAbout) { + if (app.loaded && !UiModalOpen()) { // Spectrogram area fills remaining window space (scaled) float viewScale = GetUIScale(); float sidebarWidth = 320 * viewScale; @@ -438,7 +438,7 @@ int main(int argc, char* argv[]) } // Keyboard shortcuts (SPACE for play/stop toggle, ESC for clear) - if (IsKeyPressed(KEY_SPACE) && !app.showFileBrowser && !app.showAbout) { + if (IsKeyPressed(KEY_SPACE) && !UiModalOpen()) { if (app.isPlaying && AudioPlaybackSound.frameCount > 0) { // Currently playing - stop it StopSound(AudioPlaybackSound); @@ -459,7 +459,7 @@ int main(int argc, char* argv[]) } // Export PNG - if (IsKeyPressed(KEY_E) && !app.showFileBrowser && !app.showAbout && app.stftComputed) { + if (IsKeyPressed(KEY_E) && !UiModalOpen() && app.stftComputed) { ExportPNG(&app, app.exportDir); } @@ -540,7 +540,7 @@ int main(int argc, char* argv[]) } // LMB drag = box select (time + frequency) OR drag existing selection - if (app.loaded && !app.showFileBrowser && !app.showAbout && CheckCollisionPointRec(mousePos, selBounds)) { + if (app.loaded && !UiModalOpen() && CheckCollisionPointRec(mousePos, selBounds)) { // Set cursor to resize all when near divider if (mouseNearDivider && !app.isDividing) { SetMouseCursor(MOUSE_CURSOR_RESIZE_ALL); @@ -639,7 +639,7 @@ int main(int argc, char* argv[]) // Handle divider drag. Works whether or not the scope is currently shown // (when hidden, the handle sits at the bottom and can be dragged back up). - if (app.loaded && !app.showFileBrowser && !app.showAbout) { + if (app.loaded && !UiModalOpen()) { // Grab the handle. The starting position is the *effective* divider, // which is the bottom of the view (1.0) while the scope is hidden. if (mouseNearDivider && IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) { diff --git a/src/spectrogram_types.h b/src/spectrogram_types.h index c937924..1b6b21e 100644 --- a/src/spectrogram_types.h +++ b/src/spectrogram_types.h @@ -226,6 +226,13 @@ extern Font mainFont; // (defined in spectrogram.c; used by every load path). void ResetForNewSignal(void); +// True when a modal overlay owns input; normal spectrogram/keyboard interaction +// is gated off while this is the case. Add new overlays here in one place. +static inline bool UiModalOpen(void) +{ + return app.showFileBrowser || app.showAbout; +} + // ============================================================================ // Small math helpers (header-inline so every module can use them) // ============================================================================ diff --git a/src/ui.c b/src/ui.c index 46ea501..2b31b50 100644 --- a/src/ui.c +++ b/src/ui.c @@ -394,7 +394,6 @@ void DrawSidebar(void) // Colormap dropdown DrawTextScaled("Colormap:", x, y, 14, LIGHTGRAY); y += 20 * scale; - const char* colormapNames[] = { "Grays", "Inferno", "Viridis", "Plasma", "Hot", "Cool" }; Rectangle cmapButton = { x, y, sidebarWidth - 10 * scale, 25 * scale }; if (CheckCollisionPointRec(GetMousePosition(), cmapButton) && IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) { app.colormap = (ColormapType)((app.colormap + 1) % COLORMAP_COUNT); @@ -403,7 +402,7 @@ void DrawSidebar(void) } DrawRectangleRec(cmapButton, (Color){ 50, 50, 60, 255 }); DrawRectangleLinesEx(cmapButton, 1, GRAY); - DrawTextScaled(colormapNames[app.colormap], cmapButton.x + 10 * scale, cmapButton.y + 6 * scale, 14, WHITE); + DrawTextScaled(ColormapName(app.colormap), cmapButton.x + 10 * scale, cmapButton.y + 6 * scale, 14, WHITE); DrawTexturePro(colormapTexture, (Rectangle){ 0, 0, 256, 1 }, (Rectangle){ cmapButton.x + cmapButton.width - 60 * scale, cmapButton.y + 5 * scale, 50 * scale, 15 * scale }, (Vector2){ 0, 0 }, 0.0f, WHITE);