diff options
| author | skal <pascal.massimino@gmail.com> | 2026-02-06 23:16:18 +0100 |
|---|---|---|
| committer | skal <pascal.massimino@gmail.com> | 2026-02-06 23:16:18 +0100 |
| commit | 036114d12d024273e752ffbb68a95a04ee34d4fa (patch) | |
| tree | c3b8939480249c22f4b0a2a67a788088e4bd3c85 | |
| parent | 0c98c830b382d66c420524ff395e12164a566dd8 (diff) | |
Root Cause:
The frequency axis uses logarithmic scale (20 Hz to 16 kHz), but the zoom
calculation was treating it as linear. This caused coordinate calculation
errors when zooming, resulting in curves and frequency ticks moving up
when the content hit the viewport edge.
Changes:
- Zoom now only affects horizontal axis (time/frame)
- Removed vertical zoom (pixelsPerBin changes) during Ctrl/Cmd + wheel
- Disabled vertical pan (normal wheel) for logarithmic mode
- Horizontal pan (Shift + wheel) still works correctly
Explanation:
With logarithmic frequency scale, the frequency range (FREQ_MIN to FREQ_MAX)
is always scaled to fit canvas height. There's no "extra content" to zoom
into vertically. The frequency axis should remain fixed while only the
time axis (which is linear) supports zoom.
The bug manifested as vertical drift because the offset calculation used
linear math (viewportOffsetY = freqUnderCursor * pixelsPerBin - mouseY)
on a logarithmic coordinate system, causing accumulated errors.
Fixes: Curves and frequency ticks now stay stable during horizontal zoom.
| -rw-r--r-- | tools/spectral_editor/script.js | 29 |
1 files changed, 13 insertions, 16 deletions
diff --git a/tools/spectral_editor/script.js b/tools/spectral_editor/script.js index 024005e..7c424f9 100644 --- a/tools/spectral_editor/script.js +++ b/tools/spectral_editor/script.js @@ -867,28 +867,20 @@ function onCanvasWheel(e) { // Zoom mode: Ctrl/Cmd + wheel if (e.ctrlKey || e.metaKey) { - // Calculate frame and frequency under cursor BEFORE zoom + // Calculate frame under cursor BEFORE zoom const frameUnderCursor = (mouseX + state.viewportOffsetX) / state.pixelsPerFrame; - const freqUnderCursor = (mouseY + state.viewportOffsetY) / state.pixelsPerBin; - // Calculate new zoom level + // Calculate new zoom level (horizontal only - logarithmic frequency axis doesn't zoom) const zoomFactor = e.deltaY > 0 ? 0.9 : 1.1; // Wheel down = zoom out, wheel up = zoom in - const oldPixelsPerFrame = state.pixelsPerFrame; - const oldPixelsPerBin = state.pixelsPerBin; - state.pixelsPerFrame = Math.max(0.5, Math.min(20.0, state.pixelsPerFrame * zoomFactor)); - state.pixelsPerBin = Math.max(0.1, Math.min(5.0, state.pixelsPerBin * zoomFactor)); - // Adjust viewport offset so frame/freq under cursor stays in same screen position + // Adjust viewport offset so frame under cursor stays in same screen position // After zoom: new_offset = frame * newPixelsPerFrame - mouseX state.viewportOffsetX = frameUnderCursor * state.pixelsPerFrame - mouseX; - state.viewportOffsetY = freqUnderCursor * state.pixelsPerBin - mouseY; // Clamp viewport offset to valid range const maxOffsetX = Math.max(0, state.referenceNumFrames * state.pixelsPerFrame - state.canvasWidth); - const maxOffsetY = Math.max(0, DCT_SIZE * state.pixelsPerBin - state.canvasHeight); state.viewportOffsetX = Math.max(0, Math.min(maxOffsetX, state.viewportOffsetX)); - state.viewportOffsetY = Math.max(0, Math.min(maxOffsetY, state.viewportOffsetY)); render(); return; @@ -903,11 +895,16 @@ function onCanvasWheel(e) { return; } - // Normal mode: pan vertically - state.viewportOffsetY += e.deltaY; - const maxOffsetY = Math.max(0, DCT_SIZE * state.pixelsPerBin - state.canvasHeight); - state.viewportOffsetY = Math.max(0, Math.min(maxOffsetY, state.viewportOffsetY)); - render(); + // Normal mode: pan vertically (disabled for logarithmic frequency axis) + // Note: With logarithmic frequency scale, vertical pan doesn't make sense + // because the frequency range (FREQ_MIN to FREQ_MAX) is always scaled to fit canvas height. + // Vertical pan only works in linear frequency mode. + if (!USE_LOG_SCALE) { + state.viewportOffsetY += e.deltaY; + const maxOffsetY = Math.max(0, DCT_SIZE * state.pixelsPerBin - state.canvasHeight); + state.viewportOffsetY = Math.max(0, Math.min(maxOffsetY, state.viewportOffsetY)); + render(); + } } // ============================================================================ |
