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 <noreply@anthropic.com>
This commit is contained in:
+17
-17
@@ -283,7 +283,7 @@ void DrawLabels(Rectangle bounds)
|
|||||||
// Time labels
|
// Time labels
|
||||||
for (int i = 0; i <= 10; i++) {
|
for (int i = 0; i <= 10; i++) {
|
||||||
float t = (float)i / 10;
|
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;
|
float x = bounds.x + t * bounds.width;
|
||||||
char label[32];
|
char label[32];
|
||||||
if (timeSec >= 60) sprintf(label, "%d:%02d", (int)(timeSec / 60), (int)(timeSec) % 60);
|
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
|
// Frequency labels adapted to current zoom level
|
||||||
float maxFreq = (float)app.signal.sampleRate / 2.0f;
|
float maxFreq = (float)app.signal.sampleRate / 2.0f;
|
||||||
float freqMin = app.freqViewStart * maxFreq;
|
float freqMin = app.view.freqStart * maxFreq;
|
||||||
float freqMax = app.freqViewEnd * maxFreq;
|
float freqMax = app.view.freqEnd * maxFreq;
|
||||||
|
|
||||||
// Choose tick spacing based on zoom range
|
// Choose tick spacing based on zoom range
|
||||||
float freqRange = freqMax - freqMin;
|
float freqRange = freqMax - freqMin;
|
||||||
@@ -348,13 +348,13 @@ void DrawSelection(Rectangle bounds)
|
|||||||
Color overlayColor = Fade(BLACK, 0.25f); // Lighter overlay
|
Color overlayColor = Fade(BLACK, 0.25f); // Lighter overlay
|
||||||
|
|
||||||
// Convert signal coordinates to viewport coordinates
|
// Convert signal coordinates to viewport coordinates
|
||||||
float viewWidth = app.viewEnd - app.viewStart;
|
float viewWidth = app.view.end - app.view.start;
|
||||||
float freqWidth = app.freqViewEnd - app.freqViewStart;
|
float freqWidth = app.view.freqEnd - app.view.freqStart;
|
||||||
|
|
||||||
float selStartX = bounds.x + ((app.sel.timeStart - app.viewStart) / viewWidth) * bounds.width;
|
float selStartX = bounds.x + ((app.sel.timeStart - app.view.start) / viewWidth) * bounds.width;
|
||||||
float selEndX = bounds.x + ((app.sel.timeEnd - app.viewStart) / 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.freqViewStart) / freqWidth) * bounds.height;
|
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.freqViewStart) / freqWidth) * bounds.height;
|
float selEndY = bounds.y + bounds.height - ((app.sel.freqStart - app.view.freqStart) / freqWidth) * bounds.height;
|
||||||
|
|
||||||
// Clamp to viewport bounds
|
// Clamp to viewport bounds
|
||||||
selStartX = fmaxf(bounds.x, fminf(bounds.x + bounds.width, selStartX));
|
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;
|
(app.sel.isDragging && !IsMouseButtonDown(MOUSE_LEFT_BUTTON))) return;
|
||||||
|
|
||||||
// Convert signal coordinates to viewport coordinates
|
// Convert signal coordinates to viewport coordinates
|
||||||
float viewWidth = app.viewEnd - app.viewStart;
|
float viewWidth = app.view.end - app.view.start;
|
||||||
float freqWidth = app.freqViewEnd - app.freqViewStart;
|
float freqWidth = app.view.freqEnd - app.view.freqStart;
|
||||||
|
|
||||||
float selStartX = bounds.x + ((app.sel.timeStart - app.viewStart) / viewWidth) * bounds.width;
|
float selStartX = bounds.x + ((app.sel.timeStart - app.view.start) / viewWidth) * bounds.width;
|
||||||
float selEndX = bounds.x + ((app.sel.timeEnd - app.viewStart) / 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.freqViewStart) / freqWidth) * bounds.height;
|
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.freqViewStart) / freqWidth) * bounds.height;
|
float selEndY = bounds.y + bounds.height - ((app.sel.freqStart - app.view.freqStart) / freqWidth) * bounds.height;
|
||||||
|
|
||||||
// Clamp to viewport bounds
|
// Clamp to viewport bounds
|
||||||
selStartX = fmaxf(bounds.x, fminf(bounds.x + bounds.width, selStartX));
|
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;
|
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 timePos = app.sel.timeStart + app.playheadT * (app.sel.timeEnd - app.sel.timeStart);
|
||||||
float viewWidth = app.viewEnd - app.viewStart;
|
float viewWidth = app.view.end - app.view.start;
|
||||||
float t = (timePos - app.viewStart) / viewWidth;
|
float t = (timePos - app.view.start) / viewWidth;
|
||||||
float x = bounds.x + t * bounds.width;
|
float x = bounds.x + t * bounds.width;
|
||||||
|
|
||||||
// Clamp to bounds
|
// Clamp to bounds
|
||||||
|
|||||||
+95
-95
@@ -119,7 +119,7 @@ void ResetForNewSignal(void)
|
|||||||
FreeAllCacheEntries(&app.fftCache);
|
FreeAllCacheEntries(&app.fftCache);
|
||||||
|
|
||||||
// Reset view + selection to full range.
|
// 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();
|
ClearSelection();
|
||||||
|
|
||||||
// Invalidate the cached visible texture.
|
// Invalidate the cached visible texture.
|
||||||
@@ -142,15 +142,15 @@ static void ActionExport(void) { ExportPNG(&app, app.exportDir); }
|
|||||||
|
|
||||||
static void ActionResetView(void)
|
static void ActionResetView(void)
|
||||||
{
|
{
|
||||||
app.viewStart = 0.0f; app.viewEnd = 1.0f;
|
app.view.start = 0.0f; app.view.end = 1.0f;
|
||||||
app.freqViewStart = 0.0f; app.freqViewEnd = 1.0f;
|
app.view.freqStart = 0.0f; app.view.freqEnd = 1.0f;
|
||||||
app.visibleTextureValid = false;
|
app.visibleTextureValid = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void ActionZoomToStart(void)
|
static void ActionZoomToStart(void)
|
||||||
{
|
{
|
||||||
app.viewStart = 0.0f;
|
app.view.start = 0.0f;
|
||||||
app.viewEnd = 0.1f;
|
app.view.end = 0.1f;
|
||||||
app.visibleTextureValid = false;
|
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.timeStart = 0.0f; app.sel.timeEnd = 1.0f;
|
||||||
app.sel.freqStart = 0.0f; app.sel.freqEnd = 1.0f;
|
app.sel.freqStart = 0.0f; app.sel.freqEnd = 1.0f;
|
||||||
app.viewStart = 0.0f; app.viewEnd = 1.0f;
|
app.view.start = 0.0f; app.view.end = 1.0f;
|
||||||
app.freqViewStart = 0.0f; app.freqViewEnd = 1.0f;
|
app.view.freqStart = 0.0f; app.view.freqEnd = 1.0f;
|
||||||
app.showGrid = true;
|
app.showGrid = true;
|
||||||
app.colormap = COLORMAP_INFERNO;
|
app.colormap = COLORMAP_INFERNO;
|
||||||
app.amplitudeMode = SCALE_RELATIVE;
|
app.amplitudeMode = SCALE_RELATIVE;
|
||||||
@@ -359,31 +359,31 @@ int main(int argc, char* argv[])
|
|||||||
|
|
||||||
// --- Time axis zoom (around cursor X) ---
|
// --- Time axis zoom (around cursor X) ---
|
||||||
float mouseT = (GetMousePosition().x - viewBounds.x) / viewBounds.width;
|
float mouseT = (GetMousePosition().x - viewBounds.x) / viewBounds.width;
|
||||||
mouseT = app.viewStart + mouseT * (app.viewEnd - app.viewStart);
|
mouseT = app.view.start + mouseT * (app.view.end - app.view.start);
|
||||||
float viewWidth = app.viewEnd - app.viewStart;
|
float viewWidth = app.view.end - app.view.start;
|
||||||
float newWidth = viewWidth * zoomFactor;
|
float newWidth = viewWidth * zoomFactor;
|
||||||
if (newWidth < 0.02f) newWidth = 0.02f;
|
if (newWidth < 0.02f) newWidth = 0.02f;
|
||||||
if (newWidth > 1.0f) newWidth = 1.0f;
|
if (newWidth > 1.0f) newWidth = 1.0f;
|
||||||
float leftOfMouse = mouseT - app.viewStart;
|
float leftOfMouse = mouseT - app.view.start;
|
||||||
float rightOfMouse = app.viewEnd - mouseT;
|
float rightOfMouse = app.view.end - mouseT;
|
||||||
app.viewStart = mouseT - leftOfMouse * (newWidth / viewWidth);
|
app.view.start = mouseT - leftOfMouse * (newWidth / viewWidth);
|
||||||
app.viewEnd = mouseT + rightOfMouse * (newWidth / viewWidth);
|
app.view.end = mouseT + rightOfMouse * (newWidth / viewWidth);
|
||||||
if (app.viewStart < 0) { app.viewStart = 0; app.viewEnd = newWidth; }
|
if (app.view.start < 0) { app.view.start = 0; app.view.end = newWidth; }
|
||||||
if (app.viewEnd > 1) { app.viewEnd = 1; app.viewStart = 1 - newWidth; }
|
if (app.view.end > 1) { app.view.end = 1; app.view.start = 1 - newWidth; }
|
||||||
|
|
||||||
// --- Frequency axis zoom (around cursor Y) ---
|
// --- Frequency axis zoom (around cursor Y) ---
|
||||||
float mouseF = 1.0f - (GetMousePosition().y - viewBounds.y) / viewBounds.height;
|
float mouseF = 1.0f - (GetMousePosition().y - viewBounds.y) / viewBounds.height;
|
||||||
mouseF = app.freqViewStart + mouseF * (app.freqViewEnd - app.freqViewStart);
|
mouseF = app.view.freqStart + mouseF * (app.view.freqEnd - app.view.freqStart);
|
||||||
float freqWidth = app.freqViewEnd - app.freqViewStart;
|
float freqWidth = app.view.freqEnd - app.view.freqStart;
|
||||||
float newFreqWidth = freqWidth * zoomFactor;
|
float newFreqWidth = freqWidth * zoomFactor;
|
||||||
if (newFreqWidth < 0.001f) newFreqWidth = 0.001f;
|
if (newFreqWidth < 0.001f) newFreqWidth = 0.001f;
|
||||||
float belowMouse = mouseF - app.freqViewStart;
|
float belowMouse = mouseF - app.view.freqStart;
|
||||||
float aboveMouse = app.freqViewEnd - mouseF;
|
float aboveMouse = app.view.freqEnd - mouseF;
|
||||||
app.freqViewStart = mouseF - belowMouse * (newFreqWidth / freqWidth);
|
app.view.freqStart = mouseF - belowMouse * (newFreqWidth / freqWidth);
|
||||||
app.freqViewEnd = mouseF + aboveMouse * (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
|
// 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.view.freqStart < 0) { app.view.freqStart = 0; app.view.freqEnd = fminf(app.view.freqEnd, 1.0f); }
|
||||||
if (app.freqViewEnd > 1) { app.freqViewEnd = 1; app.freqViewStart = fmaxf(app.freqViewStart, 0.0f); }
|
if (app.view.freqEnd > 1) { app.view.freqEnd = 1; app.view.freqStart = fmaxf(app.view.freqStart, 0.0f); }
|
||||||
|
|
||||||
// Invalidate texture cache
|
// Invalidate texture cache
|
||||||
app.visibleTextureValid = false;
|
app.visibleTextureValid = false;
|
||||||
@@ -393,51 +393,51 @@ int main(int argc, char* argv[])
|
|||||||
// Pan with Alt+drag or middle mouse button (pans both axes)
|
// 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);
|
bool canPan = IsKeyDown(KEY_LEFT_ALT) || IsKeyDown(KEY_RIGHT_ALT) || IsMouseButtonDown(MOUSE_BUTTON_MIDDLE);
|
||||||
if (canPan && IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) {
|
if (canPan && IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) {
|
||||||
app.isPanning = true;
|
app.view.isPanning = true;
|
||||||
app.panStartPos = GetMousePosition();
|
app.view.panStartPos = GetMousePosition();
|
||||||
app.panStartViewStart = app.viewStart;
|
app.view.panStart = app.view.start;
|
||||||
app.panStartViewEnd = app.viewEnd;
|
app.view.panEnd = app.view.end;
|
||||||
app.panStartFreqViewStart = app.freqViewStart;
|
app.view.panFreqStart = app.view.freqStart;
|
||||||
app.panStartFreqViewEnd = app.freqViewEnd;
|
app.view.panFreqEnd = app.view.freqEnd;
|
||||||
}
|
}
|
||||||
if (app.isPanning && IsMouseButtonDown(MOUSE_LEFT_BUTTON)) {
|
if (app.view.isPanning && IsMouseButtonDown(MOUSE_LEFT_BUTTON)) {
|
||||||
float dx = (GetMousePosition().x - app.panStartPos.x) / viewBounds.width;
|
float dx = (GetMousePosition().x - app.view.panStartPos.x) / viewBounds.width;
|
||||||
float dy = (GetMousePosition().y - app.panStartPos.y) / viewBounds.height;
|
float dy = (GetMousePosition().y - app.view.panStartPos.y) / viewBounds.height;
|
||||||
float viewWidth = app.panStartViewEnd - app.panStartViewStart;
|
float viewWidth = app.view.panEnd - app.view.panStart;
|
||||||
float freqWidth = app.panStartFreqViewEnd - app.panStartFreqViewStart;
|
float freqWidth = app.view.panFreqEnd - app.view.panFreqStart;
|
||||||
app.viewStart = app.panStartViewStart - dx * viewWidth;
|
app.view.start = app.view.panStart - dx * viewWidth;
|
||||||
app.viewEnd = app.panStartViewEnd - dx * viewWidth;
|
app.view.end = app.view.panEnd - dx * viewWidth;
|
||||||
if (app.viewStart < 0) { app.viewStart = 0; app.viewEnd = viewWidth; }
|
if (app.view.start < 0) { app.view.start = 0; app.view.end = viewWidth; }
|
||||||
if (app.viewEnd > 1) { app.viewEnd = 1; app.viewStart = 1 - viewWidth; }
|
if (app.view.end > 1) { app.view.end = 1; app.view.start = 1 - viewWidth; }
|
||||||
app.freqViewStart = app.panStartFreqViewStart + dy * freqWidth;
|
app.view.freqStart = app.view.panFreqStart + dy * freqWidth;
|
||||||
app.freqViewEnd = app.panStartFreqViewEnd + dy * freqWidth;
|
app.view.freqEnd = app.view.panFreqEnd + dy * freqWidth;
|
||||||
// Clamp to physical limits [0, 1]
|
// Clamp to physical limits [0, 1]
|
||||||
if (app.freqViewStart < 0) {
|
if (app.view.freqStart < 0) {
|
||||||
float actualWidth = app.freqViewEnd - app.freqViewStart;
|
float actualWidth = app.view.freqEnd - app.view.freqStart;
|
||||||
app.freqViewStart = 0;
|
app.view.freqStart = 0;
|
||||||
app.freqViewEnd = fminf(actualWidth, 1.0f);
|
app.view.freqEnd = fminf(actualWidth, 1.0f);
|
||||||
}
|
}
|
||||||
if (app.freqViewEnd > 1) {
|
if (app.view.freqEnd > 1) {
|
||||||
float actualWidth = app.freqViewEnd - app.freqViewStart;
|
float actualWidth = app.view.freqEnd - app.view.freqStart;
|
||||||
app.freqViewEnd = 1;
|
app.view.freqEnd = 1;
|
||||||
app.freqViewStart = fmaxf(1.0f - actualWidth, 0.0f);
|
app.view.freqStart = fmaxf(1.0f - actualWidth, 0.0f);
|
||||||
}
|
}
|
||||||
if (app.freqViewStart < 0) app.freqViewStart = 0;
|
if (app.view.freqStart < 0) app.view.freqStart = 0;
|
||||||
if (app.freqViewEnd > 1) app.freqViewEnd = 1;
|
if (app.view.freqEnd > 1) app.view.freqEnd = 1;
|
||||||
app.visibleTextureValid = false;
|
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
|
// Foreground high-res: when user zooms in, compute missing
|
||||||
// segments in the visible range immediately (responsive).
|
// segments in the visible range immediately (responsive).
|
||||||
// Background task handles the rest when idle.
|
// Background task handles the rest when idle.
|
||||||
if (app.skipFactor > 1 && app.stft.numSegments > 0 && !app.bgFinished) {
|
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) {
|
if (viewRange <= 0.25f) {
|
||||||
// Clamp to valid segment range
|
// Clamp to valid segment range
|
||||||
int viewStartSeg = (int)(app.viewStart * app.stft.numSegments);
|
int viewStartSeg = (int)(app.view.start * app.stft.numSegments);
|
||||||
int viewEndSeg = (int)(app.viewEnd * app.stft.numSegments);
|
int viewEndSeg = (int)(app.view.end * app.stft.numSegments);
|
||||||
if (viewStartSeg < 0) viewStartSeg = 0;
|
if (viewStartSeg < 0) viewStartSeg = 0;
|
||||||
if (viewStartSeg >= app.stft.numSegments) viewStartSeg = app.stft.numSegments - 1;
|
if (viewStartSeg >= app.stft.numSegments) viewStartSeg = app.stft.numSegments - 1;
|
||||||
if (viewEndSeg >= app.stft.numSegments) viewEndSeg = 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.
|
// segments at full resolution. Pauses on any interaction.
|
||||||
// Also kicks in when zoomed out (no foreground trigger) to fill
|
// Also kicks in when zoomed out (no foreground trigger) to fill
|
||||||
// segments outside the view range.
|
// 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()) {
|
if (app.isBgProcessing && !app.bgFinished && !IsUserInteracting()) {
|
||||||
int endSeg = app.bgHighResSeg + 50; // chunks of 50 segments
|
int endSeg = app.bgHighResSeg + 50; // chunks of 50 segments
|
||||||
if (endSeg > app.stft.numSegments) endSeg = app.stft.numSegments;
|
if (endSeg > app.stft.numSegments) endSeg = app.stft.numSegments;
|
||||||
@@ -562,10 +562,10 @@ int main(int argc, char* argv[])
|
|||||||
bool hoverInsideSelection = false;
|
bool hoverInsideSelection = false;
|
||||||
if (hasSelection && CheckCollisionPointRec(mousePos, selBounds)) {
|
if (hasSelection && CheckCollisionPointRec(mousePos, selBounds)) {
|
||||||
// Convert mouse position to signal coordinates
|
// Convert mouse position to signal coordinates
|
||||||
float viewWidth = app.viewEnd - app.viewStart;
|
float viewWidth = app.view.end - app.view.start;
|
||||||
float freqWidth = app.freqViewEnd - app.freqViewStart;
|
float freqWidth = app.view.freqEnd - app.view.freqStart;
|
||||||
float mouseTime = app.viewStart + ((mousePos.x - selBounds.x) / selBounds.width) * viewWidth;
|
float mouseTime = app.view.start + ((mousePos.x - selBounds.x) / selBounds.width) * viewWidth;
|
||||||
float mouseFreq = app.freqViewStart + (1.0f - (mousePos.y - selBounds.y) / selBounds.height) * freqWidth;
|
float mouseFreq = app.view.freqStart + (1.0f - (mousePos.y - selBounds.y) / selBounds.height) * freqWidth;
|
||||||
|
|
||||||
if (mouseTime >= app.sel.timeStart && mouseTime <= app.sel.timeEnd &&
|
if (mouseTime >= app.sel.timeStart && mouseTime <= app.sel.timeEnd &&
|
||||||
mouseFreq >= app.sel.freqStart && mouseFreq <= app.sel.freqEnd) {
|
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 viewportT = (mousePos.x - selBounds.x) / selBounds.width;
|
||||||
float viewportF = 1.0f - (mousePos.y - selBounds.y) / selBounds.height;
|
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.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;
|
app.sel.freqEnd = app.sel.freqStart;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dragging existing selection
|
// Dragging existing selection
|
||||||
if (app.sel.isDragging && IsMouseButtonDown(MOUSE_LEFT_BUTTON)) {
|
if (app.sel.isDragging && IsMouseButtonDown(MOUSE_LEFT_BUTTON)) {
|
||||||
float viewWidth = app.viewEnd - app.viewStart;
|
float viewWidth = app.view.end - app.view.start;
|
||||||
float freqWidth = app.freqViewEnd - app.freqViewStart;
|
float freqWidth = app.view.freqEnd - app.view.freqStart;
|
||||||
|
|
||||||
float dx = (mousePos.x - app.sel.dragStartPos.x) / selBounds.width;
|
float dx = (mousePos.x - app.sel.dragStartPos.x) / selBounds.width;
|
||||||
float dy = (mousePos.y - app.sel.dragStartPos.y) / selBounds.height;
|
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 viewportT = (mousePos.x - selBounds.x) / selBounds.width;
|
||||||
float viewportF = 1.0f - (mousePos.y - selBounds.y) / selBounds.height;
|
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.timeEnd = Clamp(app.view.start + viewportT * (app.view.end - app.view.start), 0.0f, 1.0f);
|
||||||
app.sel.freqEnd = Clamp(app.freqViewStart + viewportF * (app.freqViewEnd - app.freqViewStart), 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)) {
|
if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) {
|
||||||
@@ -837,13 +837,13 @@ int main(int argc, char* argv[])
|
|||||||
int imgHeight = app.spectrogramImage.height;
|
int imgHeight = app.spectrogramImage.height;
|
||||||
|
|
||||||
// Calculate visible region (time and frequency)
|
// Calculate visible region (time and frequency)
|
||||||
int visibleStartX = (int)(app.viewStart * imgWidth);
|
int visibleStartX = (int)(app.view.start * imgWidth);
|
||||||
int visibleEndX = (int)(app.viewEnd * imgWidth);
|
int visibleEndX = (int)(app.view.end * imgWidth);
|
||||||
int visibleWidth = visibleEndX - visibleStartX;
|
int visibleWidth = visibleEndX - visibleStartX;
|
||||||
|
|
||||||
// Frequency: 0 = bottom of image (bin 0), 1 = top of image (bin max)
|
// Frequency: 0 = bottom of image (bin 0), 1 = top of image (bin max)
|
||||||
int visibleStartY = (int)((1.0f - app.freqViewEnd) * imgHeight);
|
int visibleStartY = (int)((1.0f - app.view.freqEnd) * imgHeight);
|
||||||
int visibleEndY = (int)((1.0f - app.freqViewStart) * imgHeight);
|
int visibleEndY = (int)((1.0f - app.view.freqStart) * imgHeight);
|
||||||
int visibleHeight = visibleEndY - visibleStartY;
|
int visibleHeight = visibleEndY - visibleStartY;
|
||||||
|
|
||||||
// Invalidate cache if view changed or texture not valid
|
// Invalidate cache if view changed or texture not valid
|
||||||
@@ -889,15 +889,15 @@ int main(int argc, char* argv[])
|
|||||||
// Draw scrollbars
|
// Draw scrollbars
|
||||||
// Horizontal scrollbar (time)
|
// Horizontal scrollbar (time)
|
||||||
DrawRectangleRec(hScrollbar, DARKGRAY);
|
DrawRectangleRec(hScrollbar, DARKGRAY);
|
||||||
float hThumbWidth = (app.viewEnd - app.viewStart) * hScrollbar.width;
|
float hThumbWidth = (app.view.end - app.view.start) * hScrollbar.width;
|
||||||
float hThumbX = hScrollbar.x + app.viewStart * hScrollbar.width;
|
float hThumbX = hScrollbar.x + app.view.start * hScrollbar.width;
|
||||||
if (hThumbWidth < 10) hThumbWidth = 10;
|
if (hThumbWidth < 10) hThumbWidth = 10;
|
||||||
DrawRectangle(hThumbX, hScrollbar.y, hThumbWidth, hScrollbar.height, GRAY);
|
DrawRectangle(hThumbX, hScrollbar.y, hThumbWidth, hScrollbar.height, GRAY);
|
||||||
|
|
||||||
// Vertical scrollbar (frequency)
|
// Vertical scrollbar (frequency)
|
||||||
DrawRectangleRec(vScrollbar, DARKGRAY);
|
DrawRectangleRec(vScrollbar, DARKGRAY);
|
||||||
float vThumbHeight = (app.freqViewEnd - app.freqViewStart) * vScrollbar.height;
|
float vThumbHeight = (app.view.freqEnd - app.view.freqStart) * vScrollbar.height;
|
||||||
float vThumbY = vScrollbar.y + (1.0f - app.freqViewEnd) * vScrollbar.height;
|
float vThumbY = vScrollbar.y + (1.0f - app.view.freqEnd) * vScrollbar.height;
|
||||||
if (vThumbHeight < 10) vThumbHeight = 10;
|
if (vThumbHeight < 10) vThumbHeight = 10;
|
||||||
DrawRectangle(vScrollbar.x, vThumbY, vScrollbar.width, vThumbHeight, GRAY);
|
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)) {
|
if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON) && CheckCollisionPointRec(GetMousePosition(), hScrollbar)) {
|
||||||
draggingH = true;
|
draggingH = true;
|
||||||
dragStartPos = GetMousePosition();
|
dragStartPos = GetMousePosition();
|
||||||
dragStartViewStart = app.viewStart;
|
dragStartViewStart = app.view.start;
|
||||||
}
|
}
|
||||||
if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON) && CheckCollisionPointRec(GetMousePosition(), vScrollbar)) {
|
if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON) && CheckCollisionPointRec(GetMousePosition(), vScrollbar)) {
|
||||||
draggingV = true;
|
draggingV = true;
|
||||||
dragStartPos = GetMousePosition();
|
dragStartPos = GetMousePosition();
|
||||||
dragStartFreqViewStart = app.freqViewStart;
|
dragStartFreqViewStart = app.view.freqStart;
|
||||||
}
|
}
|
||||||
if (draggingH && IsMouseButtonDown(MOUSE_LEFT_BUTTON)) {
|
if (draggingH && IsMouseButtonDown(MOUSE_LEFT_BUTTON)) {
|
||||||
float dx = (GetMousePosition().x - dragStartPos.x) / hScrollbar.width;
|
float dx = (GetMousePosition().x - dragStartPos.x) / hScrollbar.width;
|
||||||
float viewWidth = app.viewEnd - app.viewStart;
|
float viewWidth = app.view.end - app.view.start;
|
||||||
app.viewStart = dragStartViewStart + dx;
|
app.view.start = dragStartViewStart + dx;
|
||||||
app.viewEnd = app.viewStart + viewWidth;
|
app.view.end = app.view.start + viewWidth;
|
||||||
if (app.viewStart < 0) { app.viewStart = 0; app.viewEnd = viewWidth; }
|
if (app.view.start < 0) { app.view.start = 0; app.view.end = viewWidth; }
|
||||||
if (app.viewEnd > 1) { app.viewEnd = 1; app.viewStart = 1 - viewWidth; }
|
if (app.view.end > 1) { app.view.end = 1; app.view.start = 1 - viewWidth; }
|
||||||
app.visibleTextureValid = false;
|
app.visibleTextureValid = false;
|
||||||
}
|
}
|
||||||
if (draggingV && IsMouseButtonDown(MOUSE_LEFT_BUTTON)) {
|
if (draggingV && IsMouseButtonDown(MOUSE_LEFT_BUTTON)) {
|
||||||
float dy = (GetMousePosition().y - dragStartPos.y) / vScrollbar.height;
|
float dy = (GetMousePosition().y - dragStartPos.y) / vScrollbar.height;
|
||||||
float freqWidth = app.freqViewEnd - app.freqViewStart;
|
float freqWidth = app.view.freqEnd - app.view.freqStart;
|
||||||
app.freqViewStart = dragStartFreqViewStart - dy;
|
app.view.freqStart = dragStartFreqViewStart - dy;
|
||||||
app.freqViewEnd = app.freqViewStart + freqWidth;
|
app.view.freqEnd = app.view.freqStart + freqWidth;
|
||||||
if (app.freqViewStart < 0) {
|
if (app.view.freqStart < 0) {
|
||||||
app.freqViewStart = 0;
|
app.view.freqStart = 0;
|
||||||
app.freqViewEnd = fminf(freqWidth, 1.0f);
|
app.view.freqEnd = fminf(freqWidth, 1.0f);
|
||||||
}
|
}
|
||||||
if (app.freqViewEnd > 1) {
|
if (app.view.freqEnd > 1) {
|
||||||
app.freqViewEnd = 1;
|
app.view.freqEnd = 1;
|
||||||
app.freqViewStart = fmaxf(1.0f - freqWidth, 0.0f);
|
app.view.freqStart = fmaxf(1.0f - freqWidth, 0.0f);
|
||||||
}
|
}
|
||||||
if (app.freqViewStart < 0) app.freqViewStart = 0;
|
if (app.view.freqStart < 0) app.view.freqStart = 0;
|
||||||
if (app.freqViewEnd > 1) app.freqViewEnd = 1;
|
if (app.view.freqEnd > 1) app.view.freqEnd = 1;
|
||||||
app.visibleTextureValid = false;
|
app.visibleTextureValid = false;
|
||||||
}
|
}
|
||||||
if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) { draggingH = false; draggingV = false; }
|
if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) { draggingH = false; draggingV = false; }
|
||||||
@@ -950,8 +950,8 @@ int main(int argc, char* argv[])
|
|||||||
DrawPlayhead(viewBounds);
|
DrawPlayhead(viewBounds);
|
||||||
DrawLabels(viewBounds);
|
DrawLabels(viewBounds);
|
||||||
float maxFreq = (float)app.signal.sampleRate / 2.0f;
|
float maxFreq = (float)app.signal.sampleRate / 2.0f;
|
||||||
float freqMin = app.freqViewStart * maxFreq;
|
float freqMin = app.view.freqStart * maxFreq;
|
||||||
float freqMax = app.freqViewEnd * maxFreq;
|
float freqMax = app.view.freqEnd * maxFreq;
|
||||||
DrawTextScaled(TextFormat("Freq: %.0f-%.0f Hz", freqMin, freqMax),
|
DrawTextScaled(TextFormat("Freq: %.0f-%.0f Hz", freqMin, freqMax),
|
||||||
viewBounds.x, viewBounds.y - 30, 20, LIGHTGRAY);
|
viewBounds.x, viewBounds.y - 30, 20, LIGHTGRAY);
|
||||||
|
|
||||||
@@ -964,8 +964,8 @@ int main(int argc, char* argv[])
|
|||||||
app.scopeView.width = viewBounds.width;
|
app.scopeView.width = viewBounds.width;
|
||||||
app.scopeView.height = (int)scopeHeight;
|
app.scopeView.height = (int)scopeHeight;
|
||||||
// Keep time view in sync with spectrogram view
|
// Keep time view in sync with spectrogram view
|
||||||
app.scopeView.viewStart = app.viewStart;
|
app.scopeView.viewStart = app.view.start;
|
||||||
app.scopeView.viewEnd = app.viewEnd;
|
app.scopeView.viewEnd = app.view.end;
|
||||||
// Update waveform data
|
// Update waveform data
|
||||||
app.scopeView.data.samples = app.signal.samples;
|
app.scopeView.data.samples = app.signal.samples;
|
||||||
app.scopeView.data.numSamples = app.signal.numSamples;
|
app.scopeView.data.numSamples = app.signal.numSamples;
|
||||||
|
|||||||
+13
-11
@@ -106,6 +106,17 @@ typedef struct {
|
|||||||
float dragFreqStart; // selection freq start when the move began
|
float dragFreqStart; // selection freq start when the move began
|
||||||
} Selection;
|
} 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 {
|
typedef struct {
|
||||||
AudioSignal signal;
|
AudioSignal signal;
|
||||||
StftResult stft;
|
StftResult stft;
|
||||||
@@ -126,17 +137,8 @@ typedef struct {
|
|||||||
char exportDir[4096];
|
char exportDir[4096];
|
||||||
char exportMessage[256];
|
char exportMessage[256];
|
||||||
|
|
||||||
// Viewport/zoom controls
|
// Visible viewport (time + frequency) and in-progress pan state.
|
||||||
float viewStart; // 0-1, start of visible time region
|
Viewport view;
|
||||||
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
|
// Cached visible texture
|
||||||
Texture2D visibleTexture;
|
Texture2D visibleTexture;
|
||||||
|
|||||||
Reference in New Issue
Block a user