From 9d0008ac2ca47d4ff3e0de1da3f3c4aab4d4680e Mon Sep 17 00:00:00 2001 From: Tyler Date: Wed, 15 Apr 2026 15:40:17 -0700 Subject: [PATCH] Add FLAG_WINDOW_RESIZABLE, fullscreen button, and proportional UI scaling Co-authored-by: Qwen-Coder --- src/spectrogram.c | 141 +++++++++++++++++++++++++++------------------- 1 file changed, 83 insertions(+), 58 deletions(-) diff --git a/src/spectrogram.c b/src/spectrogram.c index 633320f..5494776 100644 --- a/src/spectrogram.c +++ b/src/spectrogram.c @@ -34,6 +34,18 @@ #define MAX_SAMPLE_RATE 48000 #define LOUDNESS_FLOOR_DB -80.0f +// Base resolution for proportional UI scaling +#define BASE_WIDTH 1280 +#define BASE_HEIGHT 800 + +// Get uniform scale factor based on current window size +static float GetUIScale(void) +{ + float scaleX = (float)GetScreenWidth() / BASE_WIDTH; + float scaleY = (float)GetScreenHeight() / BASE_HEIGHT; + return (scaleX + scaleY) / 2.0f; +} + // Colormap types typedef enum { COLORMAP_GRAYS = 0, @@ -563,20 +575,20 @@ static void GenerateSpectrogramTexture(StftResult* stft, Image* image, Texture2D static void ApplyBandpassFilterFFT(float* samples, int numSamples, int sampleRate, float freqLow, float freqHigh) { if (freqLow <= 0 && freqHigh >= sampleRate / 2.0f) return; - + int fftSize = 1; while (fftSize < numSamples) fftSize *= 2; fftSize *= 2; - - float* windowedSamples = (float*)calloc(fftSize, sizeof(float)); + + float* paddedSamples = (float*)calloc(fftSize, sizeof(float)); float complex *fftInput = (float complex*)malloc(fftSize * sizeof(float complex)); float complex *fftOutput = (float complex*)malloc(fftSize * sizeof(float complex)); - + + // Copy samples directly without windowing (windowing causes fade in/out) for (int i = 0; i < numSamples; i++) { - float window = 0.5f * (1.0f - cosf(2.0f * M_PI * i / (numSamples - 1))); - windowedSamples[i] = samples[i] * window; + paddedSamples[i] = samples[i]; } - for (int i = 0; i < fftSize; i++) fftInput[i] = windowedSamples[i] + 0.0f * I; + for (int i = 0; i < fftSize; i++) fftInput[i] = paddedSamples[i] + 0.0f * I; FFT(fftInput, fftOutput, fftSize, false); @@ -615,7 +627,7 @@ static void ApplyBandpassFilterFFT(float* samples, int numSamples, int sampleRat } } - free(windowedSamples); free(fftInput); free(fftOutput); free(ifftOutput); + free(paddedSamples); free(fftInput); free(fftOutput); free(ifftOutput); } static void ApplyBandpassFilter(float* samples, int numSamples, int sampleRate, float freqLow, float freqHigh) @@ -1010,18 +1022,19 @@ static void DrawSelectionDrag(Rectangle bounds) static void DrawSidebar(void) { - float sidebarWidth = 300; - float x = 10; - float y = 10; - int fontSize = 12; + float scale = GetUIScale(); + float sidebarWidth = 300 * scale; + float x = 10 * scale; + float y = 10 * scale; + int fontSize = (int)(12 * scale); bool needsRegen = false; - + // Dark sidebar background - DrawRectangle(0, 0, sidebarWidth + 20, GetScreenHeight(), (Color){ 35, 35, 40, 255 }); - DrawLine(sidebarWidth + 10, 0, sidebarWidth + 10, GetScreenHeight(), GRAY); + DrawRectangle(0, 0, (int)(sidebarWidth + 20 * scale), GetScreenHeight(), (Color){ 35, 35, 40, 255 }); + DrawLine((int)(sidebarWidth + 10 * scale), 0, (int)(sidebarWidth + 10 * scale), GetScreenHeight(), GRAY); // Title - DrawText("Spectrogram Controls", x, y, 14, WHITE); y += 25; + DrawText("Spectrogram Controls", x, y, (int)(14 * scale), WHITE); y += 25 * scale; // FFT Size clicker DrawText(TextFormat("FFT Size: %d (%.1f Hz/bin)", app.fftSize, (float)app.signal.sampleRate / app.fftSize), x, y, fontSize, LIGHTGRAY); y += 18; @@ -1108,7 +1121,18 @@ static void DrawSidebar(void) DrawRectangleLinesEx(playButton, 1, app.isPlaying ? RED : GREEN); DrawText(playText, playButton.x + 10, playButton.y + 12, fontSize, WHITE); y += 45; - + + // Fullscreen toggle + Rectangle fsButton = { x, y, sidebarWidth - 10, 25 }; + bool isFullscreen = IsWindowFullscreen(); + if (CheckCollisionPointRec(GetMousePosition(), fsButton) && IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) { + ToggleFullscreen(); + } + DrawRectangleRec(fsButton, isFullscreen ? (Color){ 40, 80, 120, 255 } : (Color){ 50, 50, 60, 255 }); + DrawRectangleLinesEx(fsButton, 1, GRAY); + DrawText(isFullscreen ? "Exit Fullscreen (F11)" : "Fullscreen (F11)", (int)(fsButton.x + 10 * scale), (int)(fsButton.y + 6 * scale), fontSize, WHITE); + y += 35; + // Reset/Clear buttons Rectangle resetButton = { x, y, (sidebarWidth - 15) / 2, 25 }; if (CheckCollisionPointRec(GetMousePosition(), resetButton) && IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) { @@ -1184,7 +1208,7 @@ static bool UpdateSlider(Rectangle bounds, float* value) int main(int argc, char* argv[]) { - SetConfigFlags(FLAG_VSYNC_HINT); + SetConfigFlags(FLAG_VSYNC_HINT | FLAG_WINDOW_RESIZABLE); InitWindow(1280, 800, "Spectrogram Viewer"); SetTargetFPS(60); SetTraceLogLevel(LOG_WARNING); // Suppress INFO texture logs @@ -1279,25 +1303,25 @@ int main(int argc, char* argv[]) // View controls if (app.loaded && !app.showFileBrowser) { - // Spectrogram area fills remaining window space - // Space for: spectrogram + time labels (15px below) + scrollbar (18px below that) - float sidebarWidth = 320; - float labelHeight = 15; - float scrollbarHeight = 18; - float freqLabelWidth = 65; - float vScrollbarWidth = 18; - float topMargin = 50; - float bottomMargin = 10; - - Rectangle viewBounds = { - sidebarWidth + freqLabelWidth, - topMargin, - GetScreenWidth() - sidebarWidth - freqLabelWidth - vScrollbarWidth - 20, - GetScreenHeight() - topMargin - bottomMargin - labelHeight - scrollbarHeight - 10 + // Spectrogram area fills remaining window space (scaled) + float viewScale = GetUIScale(); + float sidebarWidth = 320 * viewScale; + float labelHeight = 15 * viewScale; + float scrollbarHeight = 18 * viewScale; + float freqLabelWidth = 65 * viewScale; + float vScrollbarWidth = 18 * viewScale; + float topMargin = 50 * viewScale; + float bottomMargin = 10 * viewScale; + + Rectangle viewBounds = { + sidebarWidth + freqLabelWidth, + topMargin, + GetScreenWidth() - sidebarWidth - freqLabelWidth - vScrollbarWidth - 20 * viewScale, + GetScreenHeight() - topMargin - bottomMargin - labelHeight - scrollbarHeight - 10 * viewScale }; // Zoom with mouse wheel (zooms both time and frequency to maintain aspect ratio) - if (GetMousePosition().x > 385 && CheckCollisionPointRec(GetMousePosition(), viewBounds)) { + if (GetMousePosition().x > sidebarWidth + 5 && CheckCollisionPointRec(GetMousePosition(), viewBounds)) { int wheel = GetMouseWheelMove(); if (wheel != 0) { float zoomFactor = (wheel > 0) ? 0.8f : 1.2f; @@ -1408,13 +1432,14 @@ int main(int argc, char* argv[]) } // Selection: box select with LMB drag, right-click to clear - float selSidebarWidth = 320; - float selLabelHeight = 15; - float selScrollbarHeight = 18; - float selFreqLabelWidth = 65; - float selVScrollbarWidth = 18; - float selTopMargin = 50; - float selBottomMargin = 10; + float selScale = GetUIScale(); + float selSidebarWidth = 320 * selScale; + float selLabelHeight = 15 * selScale; + float selScrollbarHeight = 18 * selScale; + float selFreqLabelWidth = 65 * selScale; + float selVScrollbarWidth = 18 * selScale; + float selTopMargin = 50 * selScale; + float selBottomMargin = 10 * selScale; Rectangle selBounds = { selSidebarWidth + selFreqLabelWidth, @@ -1564,29 +1589,29 @@ int main(int argc, char* argv[]) BeginDrawing(); ClearBackground((Color){ 30, 30, 30, 255 }); - // Layout: sidebar on left, spectrogram on right - // Space for: spectrogram + time labels (15px below) + scrollbar (18px below that) - float sidebarWidth = 320; - float labelHeight = 15; - float scrollbarHeight = 18; - float freqLabelWidth = 65; - float vScrollbarWidth = 18; - float topMargin = 50; - float bottomMargin = 10; - + // Layout: sidebar on left, spectrogram on right (scaled) + float renderScale = GetUIScale(); + float sidebarWidth = 320 * renderScale; + float labelHeight = 15 * renderScale; + float scrollbarHeight = 18 * renderScale; + float freqLabelWidth = 65 * renderScale; + float vScrollbarWidth = 18 * renderScale; + float topMargin = 50 * renderScale; + float bottomMargin = 10 * renderScale; + // Spectrogram area (excludes labels and scrollbars) - Rectangle viewBounds = { - sidebarWidth + freqLabelWidth, - topMargin, - GetScreenWidth() - sidebarWidth - freqLabelWidth - vScrollbarWidth - 20, - GetScreenHeight() - topMargin - bottomMargin - labelHeight - scrollbarHeight - 10 + Rectangle viewBounds = { + sidebarWidth + freqLabelWidth, + topMargin, + GetScreenWidth() - sidebarWidth - freqLabelWidth - vScrollbarWidth - 20 * renderScale, + GetScreenHeight() - topMargin - bottomMargin - labelHeight - scrollbarHeight - 10 * renderScale }; // Time labels sit just below the spectrogram Rectangle timeLabelArea = { viewBounds.x, viewBounds.y + viewBounds.height, viewBounds.width, labelHeight }; // Horizontal scrollbar sits below the time labels - Rectangle hScrollbar = { viewBounds.x, viewBounds.y + viewBounds.height + labelHeight + 5, viewBounds.width, scrollbarHeight }; + Rectangle hScrollbar = { viewBounds.x, viewBounds.y + viewBounds.height + labelHeight + 5 * renderScale, viewBounds.width, scrollbarHeight }; // Vertical scrollbar sits to the right of the spectrogram - Rectangle vScrollbar = { viewBounds.x + viewBounds.width + 5, viewBounds.y, vScrollbarWidth, viewBounds.height }; + Rectangle vScrollbar = { viewBounds.x + viewBounds.width + 5 * renderScale, viewBounds.y, vScrollbarWidth, viewBounds.height }; // Draw sidebar first (on top left) DrawSidebar();