summaryrefslogtreecommitdiff
path: root/tools/timeline_editor
diff options
context:
space:
mode:
Diffstat (limited to 'tools/timeline_editor')
-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);