summaryrefslogtreecommitdiff
path: root/tools/timeline_editor/index.html
diff options
context:
space:
mode:
authorskal <pascal.massimino@gmail.com>2026-02-15 11:38:56 +0100
committerskal <pascal.massimino@gmail.com>2026-02-15 11:38:56 +0100
commit174b318c4f2a896f3d2d30bc5a7eee2b876e0369 (patch)
tree9bd1e0e2808b8a587c18de9fb4c58e86dcc517d9 /tools/timeline_editor/index.html
parenta0770029fbea6dbdaf9cc5b7e2a04ad82f5ea4c7 (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/index.html')
-rw-r--r--tools/timeline_editor/index.html30
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);