diff options
| author | skal <pascal.massimino@gmail.com> | 2026-02-15 11:38:56 +0100 |
|---|---|---|
| committer | skal <pascal.massimino@gmail.com> | 2026-02-15 11:38:56 +0100 |
| commit | 174b318c4f2a896f3d2d30bc5a7eee2b876e0369 (patch) | |
| tree | 9bd1e0e2808b8a587c18de9fb4c58e86dcc517d9 /tools/timeline_editor | |
| parent | a0770029fbea6dbdaf9cc5b7e2a04ad82f5ea4c7 (diff) | |
fix(timeline-editor): align waveform and timeline tick positions
Waveform and timeline were using different width calculations, causing
beat markers and timeline ticks to misalign when BPM changed. Waveform
now uses same maxTime calculation as timeline (including sequence padding).
Also replaced magic constants (16, 0.4) with named constants for clarity:
- SEQUENCE_DEFAULT_DURATION = 16 beats
- WAVEFORM_AMPLITUDE_SCALE = 0.4
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Diffstat (limited to 'tools/timeline_editor')
| -rw-r--r-- | tools/timeline_editor/index.html | 30 |
1 files changed, 21 insertions, 9 deletions
diff --git a/tools/timeline_editor/index.html b/tools/timeline_editor/index.html index 4d6c81e..363c5cb 100644 --- a/tools/timeline_editor/index.html +++ b/tools/timeline_editor/index.html @@ -188,12 +188,14 @@ const VERTICAL_SCROLL_SPEED = 0.3; const SEQUENCE_GAP = 10; const SEQUENCE_DEFAULT_WIDTH = 10; + const SEQUENCE_DEFAULT_DURATION = 16; const SEQUENCE_MIN_HEIGHT = 70; const SEQUENCE_COLLAPSED_HEIGHT = 35; const SEQUENCE_TOP_PADDING = 20; const SEQUENCE_BOTTOM_PADDING = 5; const EFFECT_SPACING = 30; const EFFECT_HEIGHT = 26; + const WAVEFORM_AMPLITUDE_SCALE = 0.4; // State const state = { @@ -354,17 +356,28 @@ function renderWaveform() { if (!state.audioBuffer) return; const canvas = dom.waveformCanvas, ctx = canvas.getContext('2d'); - const w = timeToBeats(state.audioDuration) * state.pixelsPerSecond, h = 80; + + // Calculate maxTime same as timeline to ensure alignment + let maxTime = 60; + for (const seq of state.sequences) { + maxTime = Math.max(maxTime, seq.startTime + SEQUENCE_DEFAULT_DURATION); + for (const effect of seq.effects) maxTime = Math.max(maxTime, seq.startTime + effect.endTime); + } + if (state.audioDuration > 0) maxTime = Math.max(maxTime, state.audioDuration * state.bpm / 60.0); + + const w = maxTime * state.pixelsPerSecond, h = 80; canvas.width = w; canvas.height = h; canvas.style.width = `${w}px`; canvas.style.height = `${h}px`; ctx.fillStyle = 'rgba(0, 0, 0, 0.3)'; ctx.fillRect(0, 0, w, h); const channelData = state.audioBuffer.getChannelData(0); - const samplesPerPixel = Math.ceil(channelData.length / w); - const centerY = h / 2, amplitudeScale = h * 0.4; + const audioBeats = timeToBeats(state.audioDuration); + const audioPixelWidth = audioBeats * state.pixelsPerSecond; + const samplesPerPixel = Math.ceil(channelData.length / audioPixelWidth); + const centerY = h / 2, amplitudeScale = h * WAVEFORM_AMPLITUDE_SCALE; ctx.strokeStyle = '#4ec9b0'; ctx.lineWidth = 1; ctx.beginPath(); - for (let x = 0; x < w; x++) { + for (let x = 0; x < audioPixelWidth; x++) { const start = Math.floor(x * samplesPerPixel); const end = Math.min(start + samplesPerPixel, channelData.length); let min = 1.0, max = -1.0; @@ -378,13 +391,12 @@ } ctx.stroke(); ctx.strokeStyle = 'rgba(255, 255, 255, 0.1)'; - ctx.beginPath(); ctx.moveTo(0, centerY); ctx.lineTo(w, centerY); ctx.stroke(); + ctx.beginPath(); ctx.moveTo(0, centerY); ctx.lineTo(audioPixelWidth, centerY); ctx.stroke(); - // Draw beat markers - const maxBeats = timeToBeats(state.audioDuration); + // Draw beat markers across full maxTime width ctx.strokeStyle = 'rgba(255, 255, 255, 0.15)'; ctx.lineWidth = 1; - for (let beat = 0; beat <= maxBeats; beat++) { + for (let beat = 0; beat <= maxTime; beat++) { const x = beat * state.pixelsPerSecond; ctx.beginPath(); ctx.moveTo(x, 0); @@ -564,7 +576,7 @@ dom.timeline.innerHTML = ''; document.getElementById('timeMarkers').innerHTML = ''; let maxTime = 60; for (const seq of state.sequences) { - maxTime = Math.max(maxTime, seq.startTime + 16); + maxTime = Math.max(maxTime, seq.startTime + SEQUENCE_DEFAULT_DURATION); for (const effect of seq.effects) maxTime = Math.max(maxTime, seq.startTime + effect.endTime); } if (state.audioDuration > 0) maxTime = Math.max(maxTime, state.audioDuration * state.bpm / 60.0); |
