From f91cae77e837db3bde5521da8d4577335b12fc48 Mon Sep 17 00:00:00 2001 From: Tyler Date: Mon, 25 May 2026 09:17:26 -0700 Subject: [PATCH] refactor: group viewport/zoom/pan state into a Viewport sub-struct The visible-window + pan-anchor state was 10 flat fields (viewStart, viewEnd, freqView*, pan*) used densely throughout the zoom/pan/scrollbar code. Group them into a Viewport sub-struct (app.view.*). Rename anchored strictly to 'app.' so ScopeView's own view->viewStart/viewEnd (same field names) are untouched. Settled render verified pixel-identical (AE=0); End/zoom path verified via injected key input. Co-Authored-By: Claude Opus 4.7 --- src/render.c | 34 +++---- src/spectrogram.c | 190 ++++++++++++++++++++-------------------- src/spectrogram_types.h | 24 ++--- 3 files changed, 125 insertions(+), 123 deletions(-) diff --git a/src/render.c b/src/render.c index 8245878..2e5ea08 100644 --- a/src/render.c +++ b/src/render.c @@ -283,7 +283,7 @@ void DrawLabels(Rectangle bounds) // Time labels for (int i = 0; i <= 10; i++) { float t = (float)i / 10; - float timeSec = (app.viewStart + t * (app.viewEnd - app.viewStart)) * app.signal.duration; + float timeSec = (app.view.start + t * (app.view.end - app.view.start)) * app.signal.duration; float x = bounds.x + t * bounds.width; char label[32]; if (timeSec >= 60) sprintf(label, "%d:%02d", (int)(timeSec / 60), (int)(timeSec) % 60); @@ -293,8 +293,8 @@ void DrawLabels(Rectangle bounds) // Frequency labels adapted to current zoom level float maxFreq = (float)app.signal.sampleRate / 2.0f; - float freqMin = app.freqViewStart * maxFreq; - float freqMax = app.freqViewEnd * maxFreq; + float freqMin = app.view.freqStart * maxFreq; + float freqMax = app.view.freqEnd * maxFreq; // Choose tick spacing based on zoom range float freqRange = freqMax - freqMin; @@ -348,13 +348,13 @@ void DrawSelection(Rectangle bounds) Color overlayColor = Fade(BLACK, 0.25f); // Lighter overlay // Convert signal coordinates to viewport coordinates - float viewWidth = app.viewEnd - app.viewStart; - float freqWidth = app.freqViewEnd - app.freqViewStart; + float viewWidth = app.view.end - app.view.start; + float freqWidth = app.view.freqEnd - app.view.freqStart; - float selStartX = bounds.x + ((app.sel.timeStart - app.viewStart) / viewWidth) * bounds.width; - float selEndX = bounds.x + ((app.sel.timeEnd - app.viewStart) / viewWidth) * bounds.width; - float selStartY = bounds.y + bounds.height - ((app.sel.freqEnd - app.freqViewStart) / freqWidth) * bounds.height; - float selEndY = bounds.y + bounds.height - ((app.sel.freqStart - app.freqViewStart) / freqWidth) * bounds.height; + float selStartX = bounds.x + ((app.sel.timeStart - app.view.start) / viewWidth) * bounds.width; + float selEndX = bounds.x + ((app.sel.timeEnd - app.view.start) / viewWidth) * bounds.width; + float selStartY = bounds.y + bounds.height - ((app.sel.freqEnd - app.view.freqStart) / freqWidth) * bounds.height; + float selEndY = bounds.y + bounds.height - ((app.sel.freqStart - app.view.freqStart) / freqWidth) * bounds.height; // Clamp to viewport bounds selStartX = fmaxf(bounds.x, fminf(bounds.x + bounds.width, selStartX)); @@ -443,13 +443,13 @@ void DrawSelectionDrag(Rectangle bounds) (app.sel.isDragging && !IsMouseButtonDown(MOUSE_LEFT_BUTTON))) return; // Convert signal coordinates to viewport coordinates - float viewWidth = app.viewEnd - app.viewStart; - float freqWidth = app.freqViewEnd - app.freqViewStart; + float viewWidth = app.view.end - app.view.start; + float freqWidth = app.view.freqEnd - app.view.freqStart; - float selStartX = bounds.x + ((app.sel.timeStart - app.viewStart) / viewWidth) * bounds.width; - float selEndX = bounds.x + ((app.sel.timeEnd - app.viewStart) / viewWidth) * bounds.width; - float selStartY = bounds.y + bounds.height - ((app.sel.freqEnd - app.freqViewStart) / freqWidth) * bounds.height; - float selEndY = bounds.y + bounds.height - ((app.sel.freqStart - app.freqViewStart) / freqWidth) * bounds.height; + float selStartX = bounds.x + ((app.sel.timeStart - app.view.start) / viewWidth) * bounds.width; + float selEndX = bounds.x + ((app.sel.timeEnd - app.view.start) / viewWidth) * bounds.width; + float selStartY = bounds.y + bounds.height - ((app.sel.freqEnd - app.view.freqStart) / freqWidth) * bounds.height; + float selEndY = bounds.y + bounds.height - ((app.sel.freqStart - app.view.freqStart) / freqWidth) * bounds.height; // Clamp to viewport bounds selStartX = fmaxf(bounds.x, fminf(bounds.x + bounds.width, selStartX)); @@ -539,8 +539,8 @@ void DrawPlayhead(Rectangle bounds) if (!app.isPlaying || app.playheadT < 0.0f || app.playheadT > 1.0f) return; float timePos = app.sel.timeStart + app.playheadT * (app.sel.timeEnd - app.sel.timeStart); - float viewWidth = app.viewEnd - app.viewStart; - float t = (timePos - app.viewStart) / viewWidth; + float viewWidth = app.view.end - app.view.start; + float t = (timePos - app.view.start) / viewWidth; float x = bounds.x + t * bounds.width; // Clamp to bounds diff --git a/src/spectrogram.c b/src/spectrogram.c index f78678a..7b27974 100644 --- a/src/spectrogram.c +++ b/src/spectrogram.c @@ -119,7 +119,7 @@ void ResetForNewSignal(void) FreeAllCacheEntries(&app.fftCache); // Reset view + selection to full range. - app.viewStart = 0.0f; app.viewEnd = 1.0f; + app.view.start = 0.0f; app.view.end = 1.0f; ClearSelection(); // Invalidate the cached visible texture. @@ -142,15 +142,15 @@ static void ActionExport(void) { ExportPNG(&app, app.exportDir); } static void ActionResetView(void) { - app.viewStart = 0.0f; app.viewEnd = 1.0f; - app.freqViewStart = 0.0f; app.freqViewEnd = 1.0f; + app.view.start = 0.0f; app.view.end = 1.0f; + app.view.freqStart = 0.0f; app.view.freqEnd = 1.0f; app.visibleTextureValid = false; } static void ActionZoomToStart(void) { - app.viewStart = 0.0f; - app.viewEnd = 0.1f; + app.view.start = 0.0f; + app.view.end = 0.1f; app.visibleTextureValid = false; } @@ -224,8 +224,8 @@ int main(int argc, char* argv[]) app.sel.timeStart = 0.0f; app.sel.timeEnd = 1.0f; app.sel.freqStart = 0.0f; app.sel.freqEnd = 1.0f; - app.viewStart = 0.0f; app.viewEnd = 1.0f; - app.freqViewStart = 0.0f; app.freqViewEnd = 1.0f; + app.view.start = 0.0f; app.view.end = 1.0f; + app.view.freqStart = 0.0f; app.view.freqEnd = 1.0f; app.showGrid = true; app.colormap = COLORMAP_INFERNO; app.amplitudeMode = SCALE_RELATIVE; @@ -359,31 +359,31 @@ int main(int argc, char* argv[]) // --- 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; + mouseT = app.view.start + mouseT * (app.view.end - app.view.start); + float viewWidth = app.view.end - app.view.start; float newWidth = viewWidth * zoomFactor; if (newWidth < 0.02f) newWidth = 0.02f; if (newWidth > 1.0f) newWidth = 1.0f; - float leftOfMouse = mouseT - app.viewStart; - float rightOfMouse = app.viewEnd - mouseT; - 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; } + float leftOfMouse = mouseT - app.view.start; + float rightOfMouse = app.view.end - mouseT; + app.view.start = mouseT - leftOfMouse * (newWidth / viewWidth); + app.view.end = mouseT + rightOfMouse * (newWidth / viewWidth); + if (app.view.start < 0) { app.view.start = 0; app.view.end = newWidth; } + if (app.view.end > 1) { app.view.end = 1; app.view.start = 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; + mouseF = app.view.freqStart + mouseF * (app.view.freqEnd - app.view.freqStart); + float freqWidth = app.view.freqEnd - app.view.freqStart; float newFreqWidth = freqWidth * zoomFactor; if (newFreqWidth < 0.001f) newFreqWidth = 0.001f; - float belowMouse = mouseF - app.freqViewStart; - float aboveMouse = app.freqViewEnd - mouseF; - app.freqViewStart = mouseF - belowMouse * (newFreqWidth / freqWidth); - app.freqViewEnd = mouseF + aboveMouse * (newFreqWidth / freqWidth); + float belowMouse = mouseF - app.view.freqStart; + float aboveMouse = app.view.freqEnd - mouseF; + app.view.freqStart = mouseF - belowMouse * (newFreqWidth / freqWidth); + app.view.freqEnd = mouseF + aboveMouse * (newFreqWidth / freqWidth); // Clamp to physical frequency limits [0, 1] — can't see beyond Nyquist or below 0 Hz - if (app.freqViewStart < 0) { app.freqViewStart = 0; app.freqViewEnd = fminf(app.freqViewEnd, 1.0f); } - if (app.freqViewEnd > 1) { app.freqViewEnd = 1; app.freqViewStart = fmaxf(app.freqViewStart, 0.0f); } + if (app.view.freqStart < 0) { app.view.freqStart = 0; app.view.freqEnd = fminf(app.view.freqEnd, 1.0f); } + if (app.view.freqEnd > 1) { app.view.freqEnd = 1; app.view.freqStart = fmaxf(app.view.freqStart, 0.0f); } // Invalidate texture cache app.visibleTextureValid = false; @@ -393,51 +393,51 @@ int main(int argc, char* argv[]) // 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; + app.view.isPanning = true; + app.view.panStartPos = GetMousePosition(); + app.view.panStart = app.view.start; + app.view.panEnd = app.view.end; + app.view.panFreqStart = app.view.freqStart; + app.view.panFreqEnd = app.view.freqEnd; } - 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.view.isPanning && IsMouseButtonDown(MOUSE_LEFT_BUTTON)) { + float dx = (GetMousePosition().x - app.view.panStartPos.x) / viewBounds.width; + float dy = (GetMousePosition().y - app.view.panStartPos.y) / viewBounds.height; + float viewWidth = app.view.panEnd - app.view.panStart; + float freqWidth = app.view.panFreqEnd - app.view.panFreqStart; + app.view.start = app.view.panStart - dx * viewWidth; + app.view.end = app.view.panEnd - dx * viewWidth; + if (app.view.start < 0) { app.view.start = 0; app.view.end = viewWidth; } + if (app.view.end > 1) { app.view.end = 1; app.view.start = 1 - viewWidth; } + app.view.freqStart = app.view.panFreqStart + dy * freqWidth; + app.view.freqEnd = app.view.panFreqEnd + dy * freqWidth; // Clamp to physical limits [0, 1] - if (app.freqViewStart < 0) { - float actualWidth = app.freqViewEnd - app.freqViewStart; - app.freqViewStart = 0; - app.freqViewEnd = fminf(actualWidth, 1.0f); + if (app.view.freqStart < 0) { + float actualWidth = app.view.freqEnd - app.view.freqStart; + app.view.freqStart = 0; + app.view.freqEnd = fminf(actualWidth, 1.0f); } - if (app.freqViewEnd > 1) { - float actualWidth = app.freqViewEnd - app.freqViewStart; - app.freqViewEnd = 1; - app.freqViewStart = fmaxf(1.0f - actualWidth, 0.0f); + if (app.view.freqEnd > 1) { + float actualWidth = app.view.freqEnd - app.view.freqStart; + app.view.freqEnd = 1; + app.view.freqStart = fmaxf(1.0f - actualWidth, 0.0f); } - if (app.freqViewStart < 0) app.freqViewStart = 0; - if (app.freqViewEnd > 1) app.freqViewEnd = 1; + if (app.view.freqStart < 0) app.view.freqStart = 0; + if (app.view.freqEnd > 1) app.view.freqEnd = 1; app.visibleTextureValid = false; } - if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) app.isPanning = false; + if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) app.view.isPanning = false; // Foreground high-res: when user zooms in, compute missing // segments in the visible range immediately (responsive). // Background task handles the rest when idle. if (app.skipFactor > 1 && app.stft.numSegments > 0 && !app.bgFinished) { - float viewRange = app.viewEnd - app.viewStart; + float viewRange = app.view.end - app.view.start; if (viewRange <= 0.25f) { // Clamp to valid segment range - int viewStartSeg = (int)(app.viewStart * app.stft.numSegments); - int viewEndSeg = (int)(app.viewEnd * app.stft.numSegments); + int viewStartSeg = (int)(app.view.start * app.stft.numSegments); + int viewEndSeg = (int)(app.view.end * app.stft.numSegments); if (viewStartSeg < 0) viewStartSeg = 0; if (viewStartSeg >= app.stft.numSegments) viewStartSeg = app.stft.numSegments - 1; if (viewEndSeg >= app.stft.numSegments) viewEndSeg = app.stft.numSegments - 1; @@ -461,7 +461,7 @@ int main(int argc, char* argv[]) // segments at full resolution. Pauses on any interaction. // Also kicks in when zoomed out (no foreground trigger) to fill // segments outside the view range. - bool isZoomedIn = (app.skipFactor > 1 && app.viewEnd - app.viewStart <= 0.25f); + bool isZoomedIn = (app.skipFactor > 1 && app.view.end - app.view.start <= 0.25f); if (app.isBgProcessing && !app.bgFinished && !IsUserInteracting()) { int endSeg = app.bgHighResSeg + 50; // chunks of 50 segments if (endSeg > app.stft.numSegments) endSeg = app.stft.numSegments; @@ -562,10 +562,10 @@ int main(int argc, char* argv[]) bool hoverInsideSelection = false; if (hasSelection && CheckCollisionPointRec(mousePos, selBounds)) { // Convert mouse position to signal coordinates - float viewWidth = app.viewEnd - app.viewStart; - float freqWidth = app.freqViewEnd - app.freqViewStart; - float mouseTime = app.viewStart + ((mousePos.x - selBounds.x) / selBounds.width) * viewWidth; - float mouseFreq = app.freqViewStart + (1.0f - (mousePos.y - selBounds.y) / selBounds.height) * freqWidth; + float viewWidth = app.view.end - app.view.start; + float freqWidth = app.view.freqEnd - app.view.freqStart; + float mouseTime = app.view.start + ((mousePos.x - selBounds.x) / selBounds.width) * viewWidth; + float mouseFreq = app.view.freqStart + (1.0f - (mousePos.y - selBounds.y) / selBounds.height) * freqWidth; if (mouseTime >= app.sel.timeStart && mouseTime <= app.sel.timeEnd && mouseFreq >= app.sel.freqStart && mouseFreq <= app.sel.freqEnd) { @@ -614,17 +614,17 @@ int main(int argc, char* argv[]) float viewportT = (mousePos.x - selBounds.x) / selBounds.width; float viewportF = 1.0f - (mousePos.y - selBounds.y) / selBounds.height; - app.sel.timeStart = Clamp(app.viewStart + viewportT * (app.viewEnd - app.viewStart), 0.0f, 1.0f); + app.sel.timeStart = Clamp(app.view.start + viewportT * (app.view.end - app.view.start), 0.0f, 1.0f); app.sel.timeEnd = app.sel.timeStart; - app.sel.freqStart = Clamp(app.freqViewStart + viewportF * (app.freqViewEnd - app.freqViewStart), 0.0f, 1.0f); + app.sel.freqStart = Clamp(app.view.freqStart + viewportF * (app.view.freqEnd - app.view.freqStart), 0.0f, 1.0f); app.sel.freqEnd = app.sel.freqStart; } } // Dragging existing selection if (app.sel.isDragging && IsMouseButtonDown(MOUSE_LEFT_BUTTON)) { - float viewWidth = app.viewEnd - app.viewStart; - float freqWidth = app.freqViewEnd - app.freqViewStart; + float viewWidth = app.view.end - app.view.start; + float freqWidth = app.view.freqEnd - app.view.freqStart; float dx = (mousePos.x - app.sel.dragStartPos.x) / selBounds.width; float dy = (mousePos.y - app.sel.dragStartPos.y) / selBounds.height; @@ -646,8 +646,8 @@ int main(int argc, char* argv[]) float viewportT = (mousePos.x - selBounds.x) / selBounds.width; float viewportF = 1.0f - (mousePos.y - selBounds.y) / selBounds.height; - app.sel.timeEnd = Clamp(app.viewStart + viewportT * (app.viewEnd - app.viewStart), 0.0f, 1.0f); - app.sel.freqEnd = Clamp(app.freqViewStart + viewportF * (app.freqViewEnd - app.freqViewStart), 0.0f, 1.0f); + app.sel.timeEnd = Clamp(app.view.start + viewportT * (app.view.end - app.view.start), 0.0f, 1.0f); + app.sel.freqEnd = Clamp(app.view.freqStart + viewportF * (app.view.freqEnd - app.view.freqStart), 0.0f, 1.0f); } if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) { @@ -837,13 +837,13 @@ int main(int argc, char* argv[]) int imgHeight = app.spectrogramImage.height; // Calculate visible region (time and frequency) - int visibleStartX = (int)(app.viewStart * imgWidth); - int visibleEndX = (int)(app.viewEnd * imgWidth); + int visibleStartX = (int)(app.view.start * imgWidth); + int visibleEndX = (int)(app.view.end * imgWidth); int visibleWidth = visibleEndX - visibleStartX; // 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 visibleStartY = (int)((1.0f - app.view.freqEnd) * imgHeight); + int visibleEndY = (int)((1.0f - app.view.freqStart) * imgHeight); int visibleHeight = visibleEndY - visibleStartY; // Invalidate cache if view changed or texture not valid @@ -889,15 +889,15 @@ int main(int argc, char* argv[]) // Draw scrollbars // Horizontal scrollbar (time) DrawRectangleRec(hScrollbar, DARKGRAY); - float hThumbWidth = (app.viewEnd - app.viewStart) * hScrollbar.width; - float hThumbX = hScrollbar.x + app.viewStart * hScrollbar.width; + float hThumbWidth = (app.view.end - app.view.start) * hScrollbar.width; + float hThumbX = hScrollbar.x + app.view.start * 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; + float vThumbHeight = (app.view.freqEnd - app.view.freqStart) * vScrollbar.height; + float vThumbY = vScrollbar.y + (1.0f - app.view.freqEnd) * vScrollbar.height; if (vThumbHeight < 10) vThumbHeight = 10; DrawRectangle(vScrollbar.x, vThumbY, vScrollbar.width, vThumbHeight, GRAY); @@ -909,37 +909,37 @@ int main(int argc, char* argv[]) if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON) && CheckCollisionPointRec(GetMousePosition(), hScrollbar)) { draggingH = true; dragStartPos = GetMousePosition(); - dragStartViewStart = app.viewStart; + dragStartViewStart = app.view.start; } if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON) && CheckCollisionPointRec(GetMousePosition(), vScrollbar)) { draggingV = true; dragStartPos = GetMousePosition(); - dragStartFreqViewStart = app.freqViewStart; + dragStartFreqViewStart = app.view.freqStart; } 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; } + float viewWidth = app.view.end - app.view.start; + app.view.start = dragStartViewStart + dx; + app.view.end = app.view.start + viewWidth; + if (app.view.start < 0) { app.view.start = 0; app.view.end = viewWidth; } + if (app.view.end > 1) { app.view.end = 1; app.view.start = 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 = fminf(freqWidth, 1.0f); + float freqWidth = app.view.freqEnd - app.view.freqStart; + app.view.freqStart = dragStartFreqViewStart - dy; + app.view.freqEnd = app.view.freqStart + freqWidth; + if (app.view.freqStart < 0) { + app.view.freqStart = 0; + app.view.freqEnd = fminf(freqWidth, 1.0f); } - if (app.freqViewEnd > 1) { - app.freqViewEnd = 1; - app.freqViewStart = fmaxf(1.0f - freqWidth, 0.0f); + if (app.view.freqEnd > 1) { + app.view.freqEnd = 1; + app.view.freqStart = fmaxf(1.0f - freqWidth, 0.0f); } - if (app.freqViewStart < 0) app.freqViewStart = 0; - if (app.freqViewEnd > 1) app.freqViewEnd = 1; + if (app.view.freqStart < 0) app.view.freqStart = 0; + if (app.view.freqEnd > 1) app.view.freqEnd = 1; app.visibleTextureValid = false; } if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) { draggingH = false; draggingV = false; } @@ -950,8 +950,8 @@ int main(int argc, char* argv[]) DrawPlayhead(viewBounds); DrawLabels(viewBounds); float maxFreq = (float)app.signal.sampleRate / 2.0f; - float freqMin = app.freqViewStart * maxFreq; - float freqMax = app.freqViewEnd * maxFreq; + float freqMin = app.view.freqStart * maxFreq; + float freqMax = app.view.freqEnd * maxFreq; DrawTextScaled(TextFormat("Freq: %.0f-%.0f Hz", freqMin, freqMax), viewBounds.x, viewBounds.y - 30, 20, LIGHTGRAY); @@ -964,8 +964,8 @@ int main(int argc, char* argv[]) app.scopeView.width = viewBounds.width; app.scopeView.height = (int)scopeHeight; // Keep time view in sync with spectrogram view - app.scopeView.viewStart = app.viewStart; - app.scopeView.viewEnd = app.viewEnd; + app.scopeView.viewStart = app.view.start; + app.scopeView.viewEnd = app.view.end; // Update waveform data app.scopeView.data.samples = app.signal.samples; app.scopeView.data.numSamples = app.signal.numSamples; diff --git a/src/spectrogram_types.h b/src/spectrogram_types.h index 426bcec..a07358c 100644 --- a/src/spectrogram_types.h +++ b/src/spectrogram_types.h @@ -106,6 +106,17 @@ typedef struct { float dragFreqStart; // selection freq start when the move began } Selection; +// The visible window into the spectrogram (time + frequency), all 0-1 +// normalized, plus the range captured at the start of a pan drag. +typedef struct { + float start, end; // visible time range + float freqStart, freqEnd; // visible freq range (0 = 0 Hz, 1 = Nyquist) + bool isPanning; + float panStart, panEnd; // time range captured when the pan began + float panFreqStart, panFreqEnd; // freq range captured when the pan began + Vector2 panStartPos; // mouse pos when the pan began +} Viewport; + typedef struct { AudioSignal signal; StftResult stft; @@ -126,17 +137,8 @@ typedef struct { char exportDir[4096]; char exportMessage[256]; - // Viewport/zoom controls - 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; + // Visible viewport (time + frequency) and in-progress pan state. + Viewport view; // Cached visible texture Texture2D visibleTexture;