diff options
| author | skal <pascal.massimino@gmail.com> | 2026-02-06 22:53:29 +0100 |
|---|---|---|
| committer | skal <pascal.massimino@gmail.com> | 2026-02-06 22:53:29 +0100 |
| commit | 0c98c830b382d66c420524ff395e12164a566dd8 (patch) | |
| tree | dc80376bd0f0a492b7a74037ae84a926af18bf15 /tools/editor/dct.js | |
| parent | 64145080cddbc0fe9fec7159e9ffdedca48ae9be (diff) | |
feat(spectral_editor): Add cursor-centered zoom and pan with mouse wheel
Implemented zoom and pan system for the spectral editor:
Core Features:
- Viewport offset system (viewportOffsetX, viewportOffsetY) for panning
- Three wheel interaction modes:
* Ctrl/Cmd + wheel: Cursor-centered zoom (both axes)
* Shift + wheel: Horizontal pan
* Normal wheel: Vertical pan
- Zoom range: 0.5-20.0x horizontal, 0.1-5.0x vertical
- Zoom factor: 0.9/1.1 per wheel notch (10% change)
Technical Implementation:
- Calculate data position under cursor before zoom
- Apply zoom to pixelsPerFrame and pixelsPerBin
- Adjust viewport offsets to keep cursor position stable
- Clamp offsets to valid ranges (0 to max content size)
- Updated all coordinate conversion functions (screenToSpectrogram, spectrogramToScreen)
- Updated playhead rendering with visibility check
- Reset viewport offsets on file load
Algorithm (cursor-centered zoom):
1. Calculate frame and frequency under cursor: pos = (screen + offset) / scale
2. Apply zoom: scale *= zoomFactor
3. Adjust offset: offset = pos * scale - screen
4. Clamp offset to [0, maxOffset]
This matches the zoom behavior of the timeline editor, adapted for 2D spectrogram display.
handoff(Claude): Spectral editor zoom implementation complete
Diffstat (limited to 'tools/editor/dct.js')
| -rw-r--r-- | tools/editor/dct.js | 168 |
1 files changed, 0 insertions, 168 deletions
diff --git a/tools/editor/dct.js b/tools/editor/dct.js deleted file mode 100644 index c081473..0000000 --- a/tools/editor/dct.js +++ /dev/null @@ -1,168 +0,0 @@ -const dctSize = 512; // Default DCT size, read from header - -// --- Utility Functions for Audio Processing --- - -// Hanning window for smooth audio transitions (JavaScript equivalent) -function hanningWindow(size) { - const window = new Float32Array(size); - const PI = Math.PI; - for (let i = 0; i < size; i++) { - window[i] = 0.5 * (1 - Math.cos((2 * PI * i) / (size - 1))); - } - return window; -} - -const hanningWindowArray = hanningWindow(dctSize); // Pre-calculate window - -// ============================================================================ -// FFT-based DCT/IDCT Implementation -// ============================================================================ - -// Bit-reversal permutation (in-place) -function bitReversePermute(real, imag, N) { - let temp_bits = N; - let num_bits = 0; - while (temp_bits > 1) { - temp_bits >>= 1; - num_bits++; - } - - for (let i = 0; i < N; i++) { - let j = 0; - let temp = i; - for (let b = 0; b < num_bits; b++) { - j = (j << 1) | (temp & 1); - temp >>= 1; - } - - if (j > i) { - const tmp_real = real[i]; - const tmp_imag = imag[i]; - real[i] = real[j]; - imag[i] = imag[j]; - real[j] = tmp_real; - imag[j] = tmp_imag; - } - } -} - -// In-place radix-2 FFT -function fftRadix2(real, imag, N, direction) { - const PI = Math.PI; - - for (let stage_size = 2; stage_size <= N; stage_size *= 2) { - const half_stage = stage_size / 2; - const angle = direction * 2.0 * PI / stage_size; - - let wr = 1.0; - let wi = 0.0; - const wr_delta = Math.cos(angle); - const wi_delta = Math.sin(angle); - - for (let k = 0; k < half_stage; k++) { - for (let group_start = k; group_start < N; group_start += stage_size) { - const i = group_start; - const j = group_start + half_stage; - - const temp_real = real[j] * wr - imag[j] * wi; - const temp_imag = real[j] * wi + imag[j] * wr; - - real[j] = real[i] - temp_real; - imag[j] = imag[i] - temp_imag; - real[i] = real[i] + temp_real; - imag[i] = imag[i] + temp_imag; - } - - const wr_old = wr; - wr = wr_old * wr_delta - wi * wi_delta; - wi = wr_old * wi_delta + wi * wr_delta; - } - } -} - -function fftForward(real, imag, N) { - bitReversePermute(real, imag, N); - fftRadix2(real, imag, N, +1); -} - -function fftInverse(real, imag, N) { - bitReversePermute(real, imag, N); - fftRadix2(real, imag, N, -1); - - const scale = 1.0 / N; - for (let i = 0; i < N; i++) { - real[i] *= scale; - imag[i] *= scale; - } -} - -// DCT-II via FFT using reordering method -function javascript_dct_fft(input, N) { - const PI = Math.PI; - - const real = new Float32Array(N); - const imag = new Float32Array(N); - - for (let i = 0; i < N / 2; i++) { - real[i] = input[2 * i]; - real[N - 1 - i] = input[2 * i + 1]; - } - - fftForward(real, imag, N); - - const output = new Float32Array(N); - for (let k = 0; k < N; k++) { - const angle = -PI * k / (2.0 * N); - const wr = Math.cos(angle); - const wi = Math.sin(angle); - - const dct_value = real[k] * wr - imag[k] * wi; - - if (k === 0) { - output[k] = dct_value * Math.sqrt(1.0 / N); - } else { - output[k] = dct_value * Math.sqrt(2.0 / N); - } - } - - return output; -} - -// IDCT (DCT-III) via FFT using reordering method -function javascript_idct_fft(input, N) { - const PI = Math.PI; - - const real = new Float32Array(N); - const imag = new Float32Array(N); - - for (let k = 0; k < N; k++) { - const angle = PI * k / (2.0 * N); - const wr = Math.cos(angle); - const wi = Math.sin(angle); - - let scaled; - if (k === 0) { - scaled = input[k] / Math.sqrt(1.0 / N); - } else { - scaled = input[k] / Math.sqrt(2.0 / N) * 2.0; - } - - real[k] = scaled * wr; - imag[k] = scaled * wi; - } - - fftInverse(real, imag, N); - - const output = new Float32Array(N); - for (let i = 0; i < N / 2; i++) { - output[2 * i] = real[i]; - output[2 * i + 1] = real[N - 1 - i]; - } - - return output; -} - -// Fast O(N log N) IDCT using FFT -function javascript_idct_512(input) { - return javascript_idct_fft(input, dctSize); -} |
