Add frequency zoom, scrollbars, dynamic layout that fills window
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
+204
-54
@@ -27,7 +27,9 @@
|
||||
// Configuration
|
||||
// ============================================================================
|
||||
|
||||
#define FFT_SIZE 128
|
||||
#define FFT_SIZE_DEFAULT 2048
|
||||
#define FFT_SIZE_MAX 2048
|
||||
#define FFT_SIZE_MIN 128
|
||||
#define HOP_RATIO 4 // FFT_SIZE / HOP_SIZE = 4 means 75% overlap
|
||||
#define MAX_SAMPLE_RATE 48000
|
||||
#define LOUDNESS_FLOOR_DB -80.0f
|
||||
@@ -96,17 +98,23 @@ typedef struct {
|
||||
bool isFreqSelecting;
|
||||
|
||||
// Viewport/zoom controls
|
||||
float viewStart; // 0-1, start of visible region
|
||||
float viewEnd; // 0-1, end of visible region
|
||||
float viewStart; // 0-1, start of visible time region
|
||||
float viewEnd; // 0-1, end of visible time region
|
||||
float freqViewStart; // 0-1, start of visible frequency region (0 = 0Hz)
|
||||
float freqViewEnd; // 0-1, end of visible frequency region (1 = Nyquist)
|
||||
bool isPanning;
|
||||
float panStartViewStart;
|
||||
float panStartViewEnd;
|
||||
float panStartFreqViewStart;
|
||||
float panStartFreqViewEnd;
|
||||
Vector2 panStartPos;
|
||||
|
||||
// Cached visible texture
|
||||
Texture2D visibleTexture;
|
||||
int cachedVisibleStart;
|
||||
int cachedVisibleEnd;
|
||||
int cachedVisibleStartY;
|
||||
int cachedVisibleEndY;
|
||||
bool visibleTextureValid;
|
||||
|
||||
// Display settings
|
||||
@@ -114,6 +122,7 @@ typedef struct {
|
||||
float amplitudeCeilingDb;
|
||||
ColormapType colormap;
|
||||
bool showGrid;
|
||||
int fftSize; // Current FFT size (128-2048)
|
||||
|
||||
// File browser state
|
||||
bool showFileBrowser;
|
||||
@@ -969,7 +978,27 @@ static void DrawSidebar(void)
|
||||
|
||||
// Title
|
||||
DrawText("Spectrogram Controls", x, y, 14, WHITE); y += 25;
|
||||
|
||||
|
||||
// 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;
|
||||
Rectangle fftMinus = { x, y, 30, 25 };
|
||||
Rectangle fftPlus = { x + sidebarWidth - 40, y, 30, 25 };
|
||||
if (CheckCollisionPointRec(GetMousePosition(), fftMinus) && IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) {
|
||||
int newFFT = app.fftSize / 2;
|
||||
if (newFFT >= FFT_SIZE_MIN) { app.fftSize = newFFT; app.stftComputed = false; needsRegen = true; }
|
||||
}
|
||||
if (CheckCollisionPointRec(GetMousePosition(), fftPlus) && IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) {
|
||||
int newFFT = app.fftSize * 2;
|
||||
if (newFFT <= FFT_SIZE_MAX) { app.fftSize = newFFT; app.stftComputed = false; needsRegen = true; }
|
||||
}
|
||||
DrawRectangleRec(fftMinus, (Color){ 50, 50, 60, 255 });
|
||||
DrawRectangleLinesEx(fftMinus, 1, GRAY);
|
||||
DrawText("-", fftMinus.x + 12, fftMinus.y + 5, 16, WHITE);
|
||||
DrawRectangleRec(fftPlus, (Color){ 50, 50, 60, 255 });
|
||||
DrawRectangleLinesEx(fftPlus, 1, GRAY);
|
||||
DrawText("+", fftPlus.x + 10, fftPlus.y + 5, 16, WHITE);
|
||||
y += 30;
|
||||
|
||||
// dB Floor slider
|
||||
DrawText(TextFormat("dB Floor: %.1f", app.amplitudeFloorDb), x, y, fontSize, LIGHTGRAY); y += 18;
|
||||
Rectangle dbSlider = { x, y, sidebarWidth - 10, 20 };
|
||||
@@ -980,7 +1009,7 @@ static void DrawSidebar(void)
|
||||
needsRegen = true; // Trigger immediate redraw
|
||||
}
|
||||
y += 25;
|
||||
|
||||
|
||||
// Colormap dropdown
|
||||
DrawText("Colormap:", x, y, fontSize, LIGHTGRAY); y += 18;
|
||||
const char* colormapNames[] = { "Grays", "Inferno", "Viridis", "Plasma", "Hot", "Cool" };
|
||||
@@ -993,11 +1022,11 @@ static void DrawSidebar(void)
|
||||
DrawRectangleRec(cmapButton, (Color){ 50, 50, 60, 255 });
|
||||
DrawRectangleLinesEx(cmapButton, 1, GRAY);
|
||||
DrawText(colormapNames[app.colormap], cmapButton.x + 10, cmapButton.y + 6, fontSize, WHITE);
|
||||
DrawTexturePro(colormapTexture, (Rectangle){ 0, 0, 256, 1 },
|
||||
DrawTexturePro(colormapTexture, (Rectangle){ 0, 0, 256, 1 },
|
||||
(Rectangle){ cmapButton.x + cmapButton.width - 60, cmapButton.y + 5, 50, 15 },
|
||||
(Vector2){ 0, 0 }, 0.0f, WHITE);
|
||||
y += 30;
|
||||
|
||||
|
||||
// Grid toggle
|
||||
Rectangle gridCheck = { x, y, 18, 18 };
|
||||
if (CheckCollisionPointRec(GetMousePosition(), gridCheck) && IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) {
|
||||
@@ -1065,7 +1094,7 @@ static void DrawSidebar(void)
|
||||
if (app.loaded) {
|
||||
DrawText(TextFormat("Sample Rate: %d Hz", app.signal.sampleRate), x, y, fontSize, GRAY); y += 16;
|
||||
DrawText(TextFormat("Duration: %.2f sec", app.signal.duration), x, y, fontSize, GRAY); y += 16;
|
||||
DrawText(TextFormat("FFT: %d, %.1f Hz/bin (synchrosqueezed)", FFT_SIZE, (float)app.signal.sampleRate / FFT_SIZE), x, y, fontSize, GRAY); y += 16;
|
||||
DrawText(TextFormat("Max Freq: %.1f kHz", (float)app.signal.sampleRate / 2000.0f), x, y, fontSize, GRAY); y += 16;
|
||||
} else {
|
||||
DrawText("No file loaded", x, y, fontSize, GRAY); y += 16;
|
||||
}
|
||||
@@ -1121,6 +1150,7 @@ int main(int argc, char* argv[])
|
||||
app.timeSelectionStart = 0.0f; app.timeSelectionEnd = 1.0f;
|
||||
app.freqSelectionStart = 0.0f; app.freqSelectionEnd = 1.0f;
|
||||
app.viewStart = 0.0f; app.viewEnd = 1.0f;
|
||||
app.freqViewStart = 0.0f; app.freqViewEnd = 1.0f;
|
||||
app.showGrid = true;
|
||||
app.colormap = COLORMAP_INFERNO;
|
||||
app.amplitudeFloorDb = -60.0f;
|
||||
@@ -1130,7 +1160,10 @@ int main(int argc, char* argv[])
|
||||
app.visibleTexture = (Texture2D){ 0 };
|
||||
app.cachedVisibleStart = -1;
|
||||
app.cachedVisibleEnd = -1;
|
||||
app.cachedVisibleStartY = -1;
|
||||
app.cachedVisibleEndY = -1;
|
||||
app.visibleTextureValid = false;
|
||||
app.fftSize = FFT_SIZE_DEFAULT;
|
||||
app.isPlaying = false;
|
||||
app.playbackFinished = false;
|
||||
|
||||
@@ -1197,64 +1230,91 @@ int main(int argc, char* argv[])
|
||||
|
||||
// View controls
|
||||
if (app.loaded && !app.showFileBrowser) {
|
||||
// Spectrogram area is to the right of sidebar (320px)
|
||||
Rectangle viewBounds = { 330, 50, GetScreenWidth() - 380, GetScreenHeight() - 150 };
|
||||
// Spectrogram area fills remaining window space
|
||||
// Leave room for scrollbars (20px) and axis labels
|
||||
float sidebarWidth = 320;
|
||||
float scrollbarSize = 18;
|
||||
float labelMarginLeft = 65;
|
||||
float labelMarginBottom = 35;
|
||||
float topMargin = 50;
|
||||
|
||||
Rectangle viewBounds = {
|
||||
sidebarWidth + labelMarginLeft,
|
||||
topMargin,
|
||||
GetScreenWidth() - sidebarWidth - labelMarginLeft - scrollbarSize - 10,
|
||||
GetScreenHeight() - topMargin - labelMarginBottom - scrollbarSize - 10
|
||||
};
|
||||
|
||||
// Zoom with mouse wheel (zooms to cursor position)
|
||||
if (GetMousePosition().x > 320 && CheckCollisionPointRec(GetMousePosition(), viewBounds)) {
|
||||
// Zoom with mouse wheel (zooms both time and frequency to maintain aspect ratio)
|
||||
if (GetMousePosition().x > 380 && CheckCollisionPointRec(GetMousePosition(), viewBounds)) {
|
||||
int wheel = GetMouseWheelMove();
|
||||
if (wheel != 0) {
|
||||
// Calculate mouse position as normalized time (0-1)
|
||||
float mouseT = (GetMousePosition().x - viewBounds.x) / viewBounds.width;
|
||||
mouseT = Clamp(mouseT + app.viewStart, 0.0f, 1.0f);
|
||||
|
||||
float viewWidth = app.viewEnd - app.viewStart;
|
||||
float zoomFactor = (wheel > 0) ? 0.8f : 1.2f;
|
||||
|
||||
// --- Time axis zoom (around cursor X) ---
|
||||
float mouseT = (GetMousePosition().x - viewBounds.x) / viewBounds.width;
|
||||
mouseT = app.viewStart + mouseT * (app.viewEnd - app.viewStart);
|
||||
float viewWidth = app.viewEnd - app.viewStart;
|
||||
float newWidth = viewWidth * zoomFactor;
|
||||
if (newWidth < 0.02f) newWidth = 0.02f;
|
||||
if (newWidth > 1.0f) newWidth = 1.0f;
|
||||
|
||||
// Zoom around cursor position
|
||||
float leftOfMouse = mouseT - app.viewStart;
|
||||
float rightOfMouse = app.viewEnd - mouseT;
|
||||
|
||||
float newLeftOfMouse = leftOfMouse * (newWidth / viewWidth);
|
||||
float newRightOfMouse = rightOfMouse * (newWidth / viewWidth);
|
||||
|
||||
app.viewStart = mouseT - newLeftOfMouse;
|
||||
app.viewEnd = mouseT + newRightOfMouse;
|
||||
|
||||
// Clamp to valid range
|
||||
app.viewStart = mouseT - leftOfMouse * (newWidth / viewWidth);
|
||||
app.viewEnd = mouseT + rightOfMouse * (newWidth / viewWidth);
|
||||
if (app.viewStart < 0) { app.viewStart = 0; app.viewEnd = newWidth; }
|
||||
if (app.viewEnd > 1) { app.viewEnd = 1; app.viewStart = 1 - newWidth; }
|
||||
|
||||
// --- Frequency axis zoom (around cursor Y) ---
|
||||
float mouseF = 1.0f - (GetMousePosition().y - viewBounds.y) / viewBounds.height;
|
||||
mouseF = app.freqViewStart + mouseF * (app.freqViewEnd - app.freqViewStart);
|
||||
float freqWidth = app.freqViewEnd - app.freqViewStart;
|
||||
float newFreqWidth = freqWidth * zoomFactor;
|
||||
if (newFreqWidth < 0.02f) newFreqWidth = 0.02f;
|
||||
if (newFreqWidth > 1.0f) newFreqWidth = 1.0f;
|
||||
float belowMouse = mouseF - app.freqViewStart;
|
||||
float aboveMouse = app.freqViewEnd - mouseF;
|
||||
app.freqViewStart = mouseF - belowMouse * (newFreqWidth / freqWidth);
|
||||
app.freqViewEnd = mouseF + aboveMouse * (newFreqWidth / freqWidth);
|
||||
if (app.freqViewStart < 0) { app.freqViewStart = 0; app.freqViewEnd = newFreqWidth; }
|
||||
if (app.freqViewEnd > 1) { app.freqViewEnd = 1; app.freqViewStart = 1 - newFreqWidth; }
|
||||
|
||||
// Invalidate texture cache
|
||||
app.visibleTextureValid = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Pan with Alt+drag or middle mouse button
|
||||
// Pan with Alt+drag or middle mouse button (pans both axes)
|
||||
bool canPan = IsKeyDown(KEY_LEFT_ALT) || IsKeyDown(KEY_RIGHT_ALT) || IsMouseButtonDown(MOUSE_BUTTON_MIDDLE);
|
||||
if (canPan && IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) {
|
||||
app.isPanning = true;
|
||||
app.panStartPos = GetMousePosition();
|
||||
app.panStartViewStart = app.viewStart;
|
||||
app.panStartViewEnd = app.viewEnd;
|
||||
app.panStartFreqViewStart = app.freqViewStart;
|
||||
app.panStartFreqViewEnd = app.freqViewEnd;
|
||||
}
|
||||
if (app.isPanning && IsMouseButtonDown(MOUSE_LEFT_BUTTON)) {
|
||||
float dx = (GetMousePosition().x - app.panStartPos.x) / viewBounds.width;
|
||||
float dy = (GetMousePosition().y - app.panStartPos.y) / viewBounds.height;
|
||||
float viewWidth = app.panStartViewEnd - app.panStartViewStart;
|
||||
float freqWidth = app.panStartFreqViewEnd - app.panStartFreqViewStart;
|
||||
app.viewStart = app.panStartViewStart - dx * viewWidth;
|
||||
app.viewEnd = app.panStartViewEnd - dx * viewWidth;
|
||||
if (app.viewStart < 0) { app.viewStart = 0; app.viewEnd = viewWidth; }
|
||||
if (app.viewEnd > 1) { app.viewEnd = 1; app.viewStart = 1 - viewWidth; }
|
||||
app.freqViewStart = app.panStartFreqViewStart + dy * freqWidth;
|
||||
app.freqViewEnd = app.panStartFreqViewEnd + dy * freqWidth;
|
||||
if (app.freqViewStart < 0) { app.freqViewStart = 0; app.freqViewEnd = freqWidth; }
|
||||
if (app.freqViewEnd > 1) { app.freqViewEnd = 1; app.freqViewStart = 1 - freqWidth; }
|
||||
app.visibleTextureValid = false;
|
||||
}
|
||||
if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) app.isPanning = false;
|
||||
|
||||
// Home/End keys
|
||||
if (IsKeyPressed(KEY_HOME)) {
|
||||
app.viewStart = 0.0f; app.viewEnd = 1.0f;
|
||||
if (IsKeyPressed(KEY_HOME)) {
|
||||
app.viewStart = 0.0f; app.viewEnd = 1.0f;
|
||||
app.freqViewStart = 0.0f; app.freqViewEnd = 1.0f;
|
||||
app.visibleTextureValid = false;
|
||||
}
|
||||
if (IsKeyPressed(KEY_END)) {
|
||||
@@ -1297,10 +1357,21 @@ int main(int argc, char* argv[])
|
||||
}
|
||||
|
||||
// Selection (only when mouse is in spectrogram area, not sidebar)
|
||||
Rectangle selBounds = { 330, 50, GetScreenWidth() - 380, GetScreenHeight() - 150 };
|
||||
float selSidebarWidth = 320;
|
||||
float selScrollbarSize = 18;
|
||||
float selLabelMarginLeft = 65;
|
||||
float selLabelMarginBottom = 35;
|
||||
float selTopMargin = 50;
|
||||
|
||||
Rectangle selBounds = {
|
||||
selSidebarWidth + selLabelMarginLeft,
|
||||
selTopMargin,
|
||||
GetScreenWidth() - selSidebarWidth - selLabelMarginLeft - selScrollbarSize - 10,
|
||||
GetScreenHeight() - selTopMargin - selLabelMarginBottom - selScrollbarSize - 10
|
||||
};
|
||||
Vector2 mousePos = GetMousePosition();
|
||||
|
||||
if (app.loaded && !app.showFileBrowser && mousePos.x > 320 && CheckCollisionPointRec(mousePos, selBounds)) {
|
||||
if (app.loaded && !app.showFileBrowser && mousePos.x > 380 && CheckCollisionPointRec(mousePos, selBounds)) {
|
||||
bool shiftHeld = IsKeyDown(KEY_LEFT_SHIFT) || IsKeyDown(KEY_RIGHT_SHIFT);
|
||||
|
||||
if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON) && !app.isPanning) {
|
||||
@@ -1348,7 +1419,7 @@ int main(int argc, char* argv[])
|
||||
if (app.loaded && !app.stftComputed) {
|
||||
TraceLog(LOG_INFO, "Computing STFT...");
|
||||
double startTime = GetTime();
|
||||
ComputeSTFT(&app.signal, &app.stft, FFT_SIZE);
|
||||
ComputeSTFT(&app.signal, &app.stft, app.fftSize);
|
||||
TraceLog(LOG_INFO, "STFT computed in %.2f sec (%d segments)", GetTime() - startTime, app.stft.numSegments);
|
||||
GenerateSpectrogramTexture(&app.stft, &app.spectrogramImage, &app.spectrogramTexture);
|
||||
app.stftComputed = true;
|
||||
@@ -1358,61 +1429,140 @@ int main(int argc, char* argv[])
|
||||
BeginDrawing();
|
||||
ClearBackground((Color){ 30, 30, 30, 255 });
|
||||
|
||||
// Spectrogram area is to the right of sidebar (320px)
|
||||
Rectangle viewBounds = { 330, 50, GetScreenWidth() - 380, GetScreenHeight() - 150 };
|
||||
// Spectrogram area fills remaining window space
|
||||
// Sidebar: 320px wide, spectrogram takes the rest
|
||||
// Leave room for scrollbars (20px) and axis labels
|
||||
float sidebarWidth = 320;
|
||||
float scrollbarSize = 18;
|
||||
float labelMarginLeft = 65;
|
||||
float labelMarginBottom = 35;
|
||||
float topMargin = 50;
|
||||
|
||||
Rectangle viewBounds = {
|
||||
sidebarWidth + labelMarginLeft,
|
||||
topMargin,
|
||||
GetScreenWidth() - sidebarWidth - labelMarginLeft - scrollbarSize - 10,
|
||||
GetScreenHeight() - topMargin - labelMarginBottom - scrollbarSize - 10
|
||||
};
|
||||
Rectangle hScrollbar = { viewBounds.x, viewBounds.y + viewBounds.height + 10, viewBounds.width, scrollbarSize };
|
||||
Rectangle vScrollbar = { viewBounds.x + viewBounds.width + 10, viewBounds.y, scrollbarSize, viewBounds.height };
|
||||
|
||||
// Draw sidebar first (on top left)
|
||||
DrawSidebar();
|
||||
|
||||
// Draw spectrogram (background, in its own area)
|
||||
if (app.loaded && app.stftComputed) {
|
||||
int imgWidth = app.spectrogramImage.width;
|
||||
int imgHeight = app.spectrogramImage.height;
|
||||
|
||||
// Calculate visible region (time and frequency)
|
||||
int visibleStartX = (int)(app.viewStart * imgWidth);
|
||||
int visibleEndX = (int)(app.viewEnd * imgWidth);
|
||||
int visibleWidth = visibleEndX - visibleStartX;
|
||||
|
||||
// Calculate visible region
|
||||
int visibleStart = (int)(app.viewStart * app.spectrogramImage.width);
|
||||
int visibleEnd = (int)(app.viewEnd * app.spectrogramImage.width);
|
||||
int visibleWidth = visibleEnd - visibleStart;
|
||||
// Frequency: 0 = bottom of image (bin 0), 1 = top of image (bin max)
|
||||
int visibleStartY = (int)((1.0f - app.freqViewEnd) * imgHeight);
|
||||
int visibleEndY = (int)((1.0f - app.freqViewStart) * imgHeight);
|
||||
int visibleHeight = visibleEndY - visibleStartY;
|
||||
|
||||
// Invalidate cache if view changed or texture not valid
|
||||
bool cacheInvalid = !app.visibleTextureValid ||
|
||||
visibleStart != app.cachedVisibleStart ||
|
||||
visibleEnd != app.cachedVisibleEnd ||
|
||||
visibleWidth <= 0;
|
||||
|
||||
if (cacheInvalid && visibleWidth > 0 && visibleStart >= 0) {
|
||||
bool cacheInvalid = !app.visibleTextureValid ||
|
||||
visibleStartX != app.cachedVisibleStart ||
|
||||
visibleEndX != app.cachedVisibleEnd ||
|
||||
visibleStartY != app.cachedVisibleStartY ||
|
||||
visibleEndY != app.cachedVisibleEndY ||
|
||||
visibleWidth <= 0 || visibleHeight <= 0;
|
||||
|
||||
if (cacheInvalid && visibleWidth > 0 && visibleStartX >= 0 && visibleHeight > 0 && visibleStartY >= 0) {
|
||||
// Free old texture if exists
|
||||
if (app.visibleTexture.id != 0) UnloadTexture(app.visibleTexture);
|
||||
|
||||
|
||||
// Create a sub-image for the visible region
|
||||
Image visibleImage = GenImageColor(visibleWidth, app.spectrogramImage.height, BLACK);
|
||||
Image visibleImage = GenImageColor(visibleWidth, visibleHeight, BLACK);
|
||||
Color* srcPixels = (Color*)app.spectrogramImage.data;
|
||||
Color* dstPixels = (Color*)visibleImage.data;
|
||||
|
||||
for (int y = 0; y < app.spectrogramImage.height; y++) {
|
||||
for (int y = 0; y < visibleHeight; y++) {
|
||||
for (int x = 0; x < visibleWidth; x++) {
|
||||
dstPixels[y * visibleWidth + x] = srcPixels[y * app.spectrogramImage.width + visibleStart + x];
|
||||
dstPixels[y * visibleWidth + x] = srcPixels[(visibleStartY + y) * imgWidth + visibleStartX + x];
|
||||
}
|
||||
}
|
||||
|
||||
app.visibleTexture = LoadTextureFromImage(visibleImage);
|
||||
UnloadImage(visibleImage);
|
||||
|
||||
app.cachedVisibleStart = visibleStart;
|
||||
app.cachedVisibleEnd = visibleEnd;
|
||||
|
||||
app.cachedVisibleStart = visibleStartX;
|
||||
app.cachedVisibleEnd = visibleEndX;
|
||||
app.cachedVisibleStartY = visibleStartY;
|
||||
app.cachedVisibleEndY = visibleEndY;
|
||||
app.visibleTextureValid = true;
|
||||
}
|
||||
|
||||
// Draw cached texture
|
||||
if (app.visibleTextureValid && app.visibleTexture.id != 0) {
|
||||
DrawTexturePro(app.visibleTexture,
|
||||
(Rectangle){ 0, 0, visibleWidth, app.spectrogramImage.height },
|
||||
(Rectangle){ 0, 0, visibleWidth, visibleHeight },
|
||||
viewBounds, (Vector2){ 0, 0 }, 0.0f, WHITE);
|
||||
}
|
||||
|
||||
// Draw scrollbars
|
||||
// Horizontal scrollbar (time)
|
||||
DrawRectangleRec(hScrollbar, DARKGRAY);
|
||||
float hThumbWidth = (app.viewEnd - app.viewStart) * hScrollbar.width;
|
||||
float hThumbX = hScrollbar.x + app.viewStart * hScrollbar.width;
|
||||
if (hThumbWidth < 10) hThumbWidth = 10;
|
||||
DrawRectangle(hThumbX, hScrollbar.y, hThumbWidth, hScrollbar.height, GRAY);
|
||||
|
||||
// Vertical scrollbar (frequency)
|
||||
DrawRectangleRec(vScrollbar, DARKGRAY);
|
||||
float vThumbHeight = (app.freqViewEnd - app.freqViewStart) * vScrollbar.height;
|
||||
float vThumbY = vScrollbar.y + (1.0f - app.freqViewEnd) * vScrollbar.height;
|
||||
if (vThumbHeight < 10) vThumbHeight = 10;
|
||||
DrawRectangle(vScrollbar.x, vThumbY, vScrollbar.width, vThumbHeight, GRAY);
|
||||
|
||||
// Handle scrollbar dragging
|
||||
static bool draggingH = false, draggingV = false;
|
||||
static Vector2 dragStartPos;
|
||||
static float dragStartViewStart, dragStartFreqViewStart;
|
||||
|
||||
if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON) && CheckCollisionPointRec(GetMousePosition(), hScrollbar)) {
|
||||
draggingH = true;
|
||||
dragStartPos = GetMousePosition();
|
||||
dragStartViewStart = app.viewStart;
|
||||
}
|
||||
if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON) && CheckCollisionPointRec(GetMousePosition(), vScrollbar)) {
|
||||
draggingV = true;
|
||||
dragStartPos = GetMousePosition();
|
||||
dragStartFreqViewStart = app.freqViewStart;
|
||||
}
|
||||
if (draggingH && IsMouseButtonDown(MOUSE_LEFT_BUTTON)) {
|
||||
float dx = (GetMousePosition().x - dragStartPos.x) / hScrollbar.width;
|
||||
float viewWidth = app.viewEnd - app.viewStart;
|
||||
app.viewStart = dragStartViewStart + dx;
|
||||
app.viewEnd = app.viewStart + viewWidth;
|
||||
if (app.viewStart < 0) { app.viewStart = 0; app.viewEnd = viewWidth; }
|
||||
if (app.viewEnd > 1) { app.viewEnd = 1; app.viewStart = 1 - viewWidth; }
|
||||
app.visibleTextureValid = false;
|
||||
}
|
||||
if (draggingV && IsMouseButtonDown(MOUSE_LEFT_BUTTON)) {
|
||||
float dy = (GetMousePosition().y - dragStartPos.y) / vScrollbar.height;
|
||||
float freqWidth = app.freqViewEnd - app.freqViewStart;
|
||||
app.freqViewStart = dragStartFreqViewStart - dy;
|
||||
app.freqViewEnd = app.freqViewStart + freqWidth;
|
||||
if (app.freqViewStart < 0) { app.freqViewStart = 0; app.freqViewEnd = freqWidth; }
|
||||
if (app.freqViewEnd > 1) { app.freqViewEnd = 1; app.freqViewStart = 1 - freqWidth; }
|
||||
app.visibleTextureValid = false;
|
||||
}
|
||||
if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) { draggingH = false; draggingV = false; }
|
||||
|
||||
if (app.showGrid) DrawSpectrogramGrid(viewBounds, 10, 8, Fade(GRAY, 0.3f));
|
||||
DrawSelection(viewBounds);
|
||||
DrawLabels(viewBounds);
|
||||
DrawText("Spectrogram", viewBounds.x, viewBounds.y - 30, 20, LIGHTGRAY);
|
||||
DrawText(TextFormat("Frequency (Hz) - Max: %d", app.signal.sampleRate / 2),
|
||||
float maxFreq = (float)app.signal.sampleRate / 2.0f;
|
||||
float freqMin = app.freqViewStart * maxFreq;
|
||||
float freqMax = app.freqViewEnd * maxFreq;
|
||||
DrawText(TextFormat("Freq: %.0f-%.0f Hz", freqMin, freqMax),
|
||||
viewBounds.x - 50, viewBounds.y + viewBounds.height / 2 - 10, 14, Fade(LIGHTGRAY, 0.7f));
|
||||
} else if (!app.showFileBrowser) {
|
||||
const char* msg1 = "Press 'O' or click 'Open File Browser' to load a WAV";
|
||||
|
||||
Reference in New Issue
Block a user