Add FLAG_WINDOW_RESIZABLE, fullscreen button, and proportional UI scaling
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
+83
-58
@@ -34,6 +34,18 @@
|
|||||||
#define MAX_SAMPLE_RATE 48000
|
#define MAX_SAMPLE_RATE 48000
|
||||||
#define LOUDNESS_FLOOR_DB -80.0f
|
#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
|
// Colormap types
|
||||||
typedef enum {
|
typedef enum {
|
||||||
COLORMAP_GRAYS = 0,
|
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)
|
static void ApplyBandpassFilterFFT(float* samples, int numSamples, int sampleRate, float freqLow, float freqHigh)
|
||||||
{
|
{
|
||||||
if (freqLow <= 0 && freqHigh >= sampleRate / 2.0f) return;
|
if (freqLow <= 0 && freqHigh >= sampleRate / 2.0f) return;
|
||||||
|
|
||||||
int fftSize = 1;
|
int fftSize = 1;
|
||||||
while (fftSize < numSamples) fftSize *= 2;
|
while (fftSize < numSamples) fftSize *= 2;
|
||||||
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 *fftInput = (float complex*)malloc(fftSize * sizeof(float complex));
|
||||||
float complex *fftOutput = (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++) {
|
for (int i = 0; i < numSamples; i++) {
|
||||||
float window = 0.5f * (1.0f - cosf(2.0f * M_PI * i / (numSamples - 1)));
|
paddedSamples[i] = samples[i];
|
||||||
windowedSamples[i] = samples[i] * window;
|
|
||||||
}
|
}
|
||||||
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);
|
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)
|
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)
|
static void DrawSidebar(void)
|
||||||
{
|
{
|
||||||
float sidebarWidth = 300;
|
float scale = GetUIScale();
|
||||||
float x = 10;
|
float sidebarWidth = 300 * scale;
|
||||||
float y = 10;
|
float x = 10 * scale;
|
||||||
int fontSize = 12;
|
float y = 10 * scale;
|
||||||
|
int fontSize = (int)(12 * scale);
|
||||||
bool needsRegen = false;
|
bool needsRegen = false;
|
||||||
|
|
||||||
// Dark sidebar background
|
// Dark sidebar background
|
||||||
DrawRectangle(0, 0, sidebarWidth + 20, GetScreenHeight(), (Color){ 35, 35, 40, 255 });
|
DrawRectangle(0, 0, (int)(sidebarWidth + 20 * scale), GetScreenHeight(), (Color){ 35, 35, 40, 255 });
|
||||||
DrawLine(sidebarWidth + 10, 0, sidebarWidth + 10, GetScreenHeight(), GRAY);
|
DrawLine((int)(sidebarWidth + 10 * scale), 0, (int)(sidebarWidth + 10 * scale), GetScreenHeight(), GRAY);
|
||||||
|
|
||||||
// Title
|
// 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
|
// 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;
|
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);
|
DrawRectangleLinesEx(playButton, 1, app.isPlaying ? RED : GREEN);
|
||||||
DrawText(playText, playButton.x + 10, playButton.y + 12, fontSize, WHITE);
|
DrawText(playText, playButton.x + 10, playButton.y + 12, fontSize, WHITE);
|
||||||
y += 45;
|
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
|
// Reset/Clear buttons
|
||||||
Rectangle resetButton = { x, y, (sidebarWidth - 15) / 2, 25 };
|
Rectangle resetButton = { x, y, (sidebarWidth - 15) / 2, 25 };
|
||||||
if (CheckCollisionPointRec(GetMousePosition(), resetButton) && IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) {
|
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[])
|
int main(int argc, char* argv[])
|
||||||
{
|
{
|
||||||
SetConfigFlags(FLAG_VSYNC_HINT);
|
SetConfigFlags(FLAG_VSYNC_HINT | FLAG_WINDOW_RESIZABLE);
|
||||||
InitWindow(1280, 800, "Spectrogram Viewer");
|
InitWindow(1280, 800, "Spectrogram Viewer");
|
||||||
SetTargetFPS(60);
|
SetTargetFPS(60);
|
||||||
SetTraceLogLevel(LOG_WARNING); // Suppress INFO texture logs
|
SetTraceLogLevel(LOG_WARNING); // Suppress INFO texture logs
|
||||||
@@ -1279,25 +1303,25 @@ int main(int argc, char* argv[])
|
|||||||
|
|
||||||
// View controls
|
// View controls
|
||||||
if (app.loaded && !app.showFileBrowser) {
|
if (app.loaded && !app.showFileBrowser) {
|
||||||
// Spectrogram area fills remaining window space
|
// Spectrogram area fills remaining window space (scaled)
|
||||||
// Space for: spectrogram + time labels (15px below) + scrollbar (18px below that)
|
float viewScale = GetUIScale();
|
||||||
float sidebarWidth = 320;
|
float sidebarWidth = 320 * viewScale;
|
||||||
float labelHeight = 15;
|
float labelHeight = 15 * viewScale;
|
||||||
float scrollbarHeight = 18;
|
float scrollbarHeight = 18 * viewScale;
|
||||||
float freqLabelWidth = 65;
|
float freqLabelWidth = 65 * viewScale;
|
||||||
float vScrollbarWidth = 18;
|
float vScrollbarWidth = 18 * viewScale;
|
||||||
float topMargin = 50;
|
float topMargin = 50 * viewScale;
|
||||||
float bottomMargin = 10;
|
float bottomMargin = 10 * viewScale;
|
||||||
|
|
||||||
Rectangle viewBounds = {
|
Rectangle viewBounds = {
|
||||||
sidebarWidth + freqLabelWidth,
|
sidebarWidth + freqLabelWidth,
|
||||||
topMargin,
|
topMargin,
|
||||||
GetScreenWidth() - sidebarWidth - freqLabelWidth - vScrollbarWidth - 20,
|
GetScreenWidth() - sidebarWidth - freqLabelWidth - vScrollbarWidth - 20 * viewScale,
|
||||||
GetScreenHeight() - topMargin - bottomMargin - labelHeight - scrollbarHeight - 10
|
GetScreenHeight() - topMargin - bottomMargin - labelHeight - scrollbarHeight - 10 * viewScale
|
||||||
};
|
};
|
||||||
|
|
||||||
// Zoom with mouse wheel (zooms both time and frequency to maintain aspect ratio)
|
// 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();
|
int wheel = GetMouseWheelMove();
|
||||||
if (wheel != 0) {
|
if (wheel != 0) {
|
||||||
float zoomFactor = (wheel > 0) ? 0.8f : 1.2f;
|
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
|
// Selection: box select with LMB drag, right-click to clear
|
||||||
float selSidebarWidth = 320;
|
float selScale = GetUIScale();
|
||||||
float selLabelHeight = 15;
|
float selSidebarWidth = 320 * selScale;
|
||||||
float selScrollbarHeight = 18;
|
float selLabelHeight = 15 * selScale;
|
||||||
float selFreqLabelWidth = 65;
|
float selScrollbarHeight = 18 * selScale;
|
||||||
float selVScrollbarWidth = 18;
|
float selFreqLabelWidth = 65 * selScale;
|
||||||
float selTopMargin = 50;
|
float selVScrollbarWidth = 18 * selScale;
|
||||||
float selBottomMargin = 10;
|
float selTopMargin = 50 * selScale;
|
||||||
|
float selBottomMargin = 10 * selScale;
|
||||||
|
|
||||||
Rectangle selBounds = {
|
Rectangle selBounds = {
|
||||||
selSidebarWidth + selFreqLabelWidth,
|
selSidebarWidth + selFreqLabelWidth,
|
||||||
@@ -1564,29 +1589,29 @@ int main(int argc, char* argv[])
|
|||||||
BeginDrawing();
|
BeginDrawing();
|
||||||
ClearBackground((Color){ 30, 30, 30, 255 });
|
ClearBackground((Color){ 30, 30, 30, 255 });
|
||||||
|
|
||||||
// Layout: sidebar on left, spectrogram on right
|
// Layout: sidebar on left, spectrogram on right (scaled)
|
||||||
// Space for: spectrogram + time labels (15px below) + scrollbar (18px below that)
|
float renderScale = GetUIScale();
|
||||||
float sidebarWidth = 320;
|
float sidebarWidth = 320 * renderScale;
|
||||||
float labelHeight = 15;
|
float labelHeight = 15 * renderScale;
|
||||||
float scrollbarHeight = 18;
|
float scrollbarHeight = 18 * renderScale;
|
||||||
float freqLabelWidth = 65;
|
float freqLabelWidth = 65 * renderScale;
|
||||||
float vScrollbarWidth = 18;
|
float vScrollbarWidth = 18 * renderScale;
|
||||||
float topMargin = 50;
|
float topMargin = 50 * renderScale;
|
||||||
float bottomMargin = 10;
|
float bottomMargin = 10 * renderScale;
|
||||||
|
|
||||||
// Spectrogram area (excludes labels and scrollbars)
|
// Spectrogram area (excludes labels and scrollbars)
|
||||||
Rectangle viewBounds = {
|
Rectangle viewBounds = {
|
||||||
sidebarWidth + freqLabelWidth,
|
sidebarWidth + freqLabelWidth,
|
||||||
topMargin,
|
topMargin,
|
||||||
GetScreenWidth() - sidebarWidth - freqLabelWidth - vScrollbarWidth - 20,
|
GetScreenWidth() - sidebarWidth - freqLabelWidth - vScrollbarWidth - 20 * renderScale,
|
||||||
GetScreenHeight() - topMargin - bottomMargin - labelHeight - scrollbarHeight - 10
|
GetScreenHeight() - topMargin - bottomMargin - labelHeight - scrollbarHeight - 10 * renderScale
|
||||||
};
|
};
|
||||||
// Time labels sit just below the spectrogram
|
// Time labels sit just below the spectrogram
|
||||||
Rectangle timeLabelArea = { viewBounds.x, viewBounds.y + viewBounds.height, viewBounds.width, labelHeight };
|
Rectangle timeLabelArea = { viewBounds.x, viewBounds.y + viewBounds.height, viewBounds.width, labelHeight };
|
||||||
// Horizontal scrollbar sits below the time labels
|
// 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
|
// 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)
|
// Draw sidebar first (on top left)
|
||||||
DrawSidebar();
|
DrawSidebar();
|
||||||
|
|||||||
Reference in New Issue
Block a user