summaryrefslogtreecommitdiff
path: root/tools/timeline_editor/index.html
diff options
context:
space:
mode:
Diffstat (limited to 'tools/timeline_editor/index.html')
-rw-r--r--tools/timeline_editor/index.html95
1 files changed, 55 insertions, 40 deletions
diff --git a/tools/timeline_editor/index.html b/tools/timeline_editor/index.html
index 15e92a6..62b426f 100644
--- a/tools/timeline_editor/index.html
+++ b/tools/timeline_editor/index.html
@@ -477,9 +477,11 @@
<div class="zoom-controls">
<label>Zoom: <input type="range" id="zoomSlider" min="10" max="200" value="100" step="10"></label>
<span id="zoomLevel">100%</span>
+ <label style="margin-left: 20px">BPM: <input type="range" id="bpmSlider" min="60" max="200" value="120" step="1"></label>
+ <span id="currentBPM">120</span>
<label class="checkbox-label" style="margin-left: 20px">
- <input type="checkbox" id="showBeatsCheckbox">
- Show Beats (BPM: <span id="currentBPM">120</span>)
+ <input type="checkbox" id="showBeatsCheckbox" checked>
+ Show Beats
</label>
</div>
@@ -510,7 +512,7 @@
let currentFile = null;
let selectedItem = null;
let pixelsPerSecond = 100;
- let showBeats = false;
+ let showBeats = true;
let bpm = 120;
let isDragging = false;
let dragOffset = { x: 0, y: 0 };
@@ -547,13 +549,18 @@
let bpm = 120; // Default BPM
let currentPriority = 0; // Track priority for + = - modifiers
- // Helper: Convert time notation to seconds
+ // Helper: Parse time notation (returns beats)
function parseTime(timeStr) {
+ if (timeStr.endsWith('s')) {
+ // Explicit seconds: "2.5s" = convert to beats
+ const seconds = parseFloat(timeStr.slice(0, -1));
+ return seconds * bpm / 60.0;
+ }
if (timeStr.endsWith('b')) {
- // Beat notation: "4b" = 4 beats
- const beats = parseFloat(timeStr.slice(0, -1));
- return beats * (60.0 / bpm);
+ // Explicit beats: "4b" = 4 beats
+ return parseFloat(timeStr.slice(0, -1));
}
+ // Default: beats
return parseFloat(timeStr);
}
@@ -633,7 +640,7 @@
return { sequences, bpm };
}
- // Serializer: JavaScript objects → timeline.seq
+ // Serializer: JavaScript objects → timeline.seq (outputs beats)
function serializeSeqFile(sequences) {
let output = '# Demo Timeline\n';
output += '# Generated by Timeline Editor\n';
@@ -687,8 +694,9 @@
const canvas = waveformCanvas;
const ctx = canvas.getContext('2d');
- // Set canvas size based on audio duration and zoom
- const canvasWidth = audioDuration * pixelsPerSecond;
+ // Set canvas size based on audio duration (convert to beats) and zoom
+ const audioDurationBeats = audioDuration * bpm / 60.0;
+ const canvasWidth = audioDurationBeats * pixelsPerSecond;
const canvasHeight = 80;
// Set actual canvas resolution (for sharp rendering)
@@ -767,10 +775,10 @@
const timeMarkers = document.getElementById('timeMarkers');
timeMarkers.innerHTML = '';
- // Calculate max time
- let maxTime = 30; // Default 30 seconds
+ // Calculate max time (in beats)
+ let maxTime = 60; // Default 60 beats (15 bars)
for (const seq of sequences) {
- const seqEnd = seq.startTime + 10; // Default sequence duration
+ const seqEnd = seq.startTime + 16; // Default 4 bars
maxTime = Math.max(maxTime, seqEnd);
for (const effect of seq.effects) {
@@ -780,7 +788,8 @@
// Extend timeline to fit audio if loaded
if (audioDuration > 0) {
- maxTime = Math.max(maxTime, audioDuration);
+ const audioBeats = audioDuration * bpm / 60.0;
+ maxTime = Math.max(maxTime, audioBeats);
}
// Render time markers
@@ -788,23 +797,22 @@
timeline.style.width = `${timelineWidth}px`;
if (showBeats) {
- // Show beats
- const beatDuration = 60.0 / bpm; // seconds per beat
- const maxBeats = Math.ceil(maxTime / beatDuration);
- for (let beat = 0; beat <= maxBeats; beat++) {
- const timeSec = beat * beatDuration;
+ // Show beats (default)
+ for (let beat = 0; beat <= maxTime; beat += 4) {
const marker = document.createElement('div');
marker.className = 'time-marker';
- marker.style.left = `${timeSec * pixelsPerSecond}px`;
+ marker.style.left = `${beat * pixelsPerSecond}px`;
marker.textContent = `${beat}b`;
timeMarkers.appendChild(marker);
}
} else {
// Show seconds
- for (let t = 0; t <= maxTime; t += 1) {
+ const maxSeconds = maxTime * 60.0 / bpm;
+ for (let t = 0; t <= maxSeconds; t += 1) {
+ const beatPos = t * bpm / 60.0;
const marker = document.createElement('div');
marker.className = 'time-marker';
- marker.style.left = `${t * pixelsPerSecond}px`;
+ marker.style.left = `${beatPos * pixelsPerSecond}px`;
marker.textContent = `${t}s`;
timeMarkers.appendChild(marker);
}
@@ -927,16 +935,14 @@
effectDiv.style.width = `${effectWidth}px`;
effectDiv.style.height = '26px';
- // Format time display based on mode (for tooltip)
- let timeDisplay;
- if (showBeats) {
- const beatDuration = 60.0 / bpm;
- const startBeat = (effect.startTime / beatDuration).toFixed(1);
- const endBeat = (effect.endTime / beatDuration).toFixed(1);
- timeDisplay = `${startBeat}-${endBeat}b`;
- } else {
- timeDisplay = `${effect.startTime.toFixed(1)}-${effect.endTime.toFixed(1)}s`;
- }
+ // Format time display (beats primary, seconds in tooltip)
+ const startBeat = effect.startTime.toFixed(1);
+ const endBeat = effect.endTime.toFixed(1);
+ const startSec = (effect.startTime * 60.0 / bpm).toFixed(1);
+ const endSec = (effect.endTime * 60.0 / bpm).toFixed(1);
+ const timeDisplay = showBeats
+ ? `${startBeat}-${endBeat}b (${startSec}-${endSec}s)`
+ : `${startSec}-${endSec}s (${startBeat}-${endBeat}b)`;
// Show only class name, full info on hover
effectDiv.innerHTML = `
@@ -1012,11 +1018,9 @@
const newX = e.clientX - timelineRect.left - dragOffset.x;
let newTime = Math.max(0, newX / pixelsPerSecond);
- // Snap to beat when in beat mode
+ // Snap to beat when enabled
if (showBeats) {
- const beatDuration = 60.0 / bpm;
- const nearestBeat = Math.round(newTime / beatDuration);
- newTime = nearestBeat * beatDuration;
+ newTime = Math.round(newTime);
}
if (selectedItem.type === 'sequence') {
@@ -1063,11 +1067,9 @@
const newX = e.clientX - timelineRect.left;
let newTime = Math.max(0, newX / pixelsPerSecond);
- // Snap to beat when in beat mode
+ // Snap to beat when enabled
if (showBeats) {
- const beatDuration = 60.0 / bpm;
- const nearestBeat = Math.round(newTime / beatDuration);
- newTime = nearestBeat * beatDuration;
+ newTime = Math.round(newTime);
}
const seq = sequences[selectedItem.seqIndex];
@@ -1239,6 +1241,7 @@
sequences = parsed.sequences;
bpm = parsed.bpm;
document.getElementById('currentBPM').textContent = bpm;
+ document.getElementById('bpmSlider').value = bpm;
renderTimeline();
saveBtn.disabled = false;
addSequenceBtn.disabled = false;
@@ -1338,6 +1341,18 @@
renderTimeline();
});
+ // BPM slider
+ const bpmSlider = document.getElementById('bpmSlider');
+ const currentBPMDisplay = document.getElementById('currentBPM');
+ bpmSlider.addEventListener('input', (e) => {
+ bpm = parseInt(e.target.value);
+ currentBPMDisplay.textContent = bpm;
+ if (audioBuffer) {
+ renderWaveform();
+ }
+ renderTimeline();
+ });
+
// Beats toggle
const showBeatsCheckbox = document.getElementById('showBeatsCheckbox');
showBeatsCheckbox.addEventListener('change', (e) => {