summaryrefslogtreecommitdiff
path: root/tools/timeline_editor/index.html
diff options
context:
space:
mode:
authorskal <pascal.massimino@gmail.com>2026-02-15 15:00:57 +0100
committerskal <pascal.massimino@gmail.com>2026-02-15 15:00:57 +0100
commitf23bd208b38ccfdc814daa99818a7e119f5ee313 (patch)
tree3580311b5f4376604f3dde81e3632940e262ab19 /tools/timeline_editor/index.html
parent2031e24c976d1a11eb48badb97e824b1db07741a (diff)
refactor(timeline-editor): centralize BPM calculations
Replace repeated 60.0/bpm calculations with precomputed secondsPerBeat and beatsPerSecond properties. Add computeBPMValues helper and updateBPM function for consistency. Also prevent wheel events on time markers. 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.html38
1 files changed, 26 insertions, 12 deletions
diff --git a/tools/timeline_editor/index.html b/tools/timeline_editor/index.html
index 3c5f48c..9b03fa2 100644
--- a/tools/timeline_editor/index.html
+++ b/tools/timeline_editor/index.html
@@ -199,14 +199,22 @@
const EFFECT_SPACING = 30;
const EFFECT_HEIGHT = 26;
+ // BPM computation helper
+ const computeBPMValues = (bpm) => ({
+ secondsPerBeat: 60.0 / bpm,
+ beatsPerSecond: bpm / 60.0
+ });
+
// State
+ const DEFAULT_BPM = 120;
const state = {
sequences: [], currentFile: null, selectedItem: null, pixelsPerSecond: 100,
- showBeats: true, quantizeUnit: 1, bpm: 120, isDragging: false, dragOffset: { x: 0, y: 0 },
+ showBeats: true, quantizeUnit: 1, bpm: DEFAULT_BPM, isDragging: false, dragOffset: { x: 0, y: 0 },
lastActiveSeqIndex: -1, isDraggingHandle: false, handleType: null, handleDragOffset: 0,
audioBuffer: null, audioDuration: 0, audioSource: null, audioContext: null,
isPlaying: false, playbackStartTime: 0, playbackOffset: 0, playStartPosition: 0, animationFrameId: null,
- lastExpandedSeqIndex: -1, dragMoved: false
+ lastExpandedSeqIndex: -1, dragMoved: false,
+ ...computeBPMValues(DEFAULT_BPM)
};
// DOM
@@ -251,7 +259,7 @@
let currentSequence = null, bpm = 120, currentPriority = 0;
const parseTime = (timeStr) => {
- if (timeStr.endsWith('s')) return parseFloat(timeStr.slice(0, -1)) * bpm / 60.0;
+ if (timeStr.endsWith('s')) return parseFloat(timeStr.slice(0, -1)) * bpm / 60.0; // Local bpm during parsing
if (timeStr.endsWith('b')) return parseFloat(timeStr.slice(0, -1));
return parseFloat(timeStr);
};
@@ -297,8 +305,12 @@
}
// Helpers
- const beatsToTime = (beats) => beats * 60.0 / state.bpm;
- const timeToBeats = (seconds) => seconds * state.bpm / 60.0;
+ const updateBPM = (newBPM) => {
+ state.bpm = newBPM;
+ Object.assign(state, computeBPMValues(newBPM));
+ };
+ const beatsToTime = (beats) => beats * state.secondsPerBeat;
+ const timeToBeats = (seconds) => seconds * state.beatsPerSecond;
const beatRange = (start, end) => {
const s = start.toFixed(1), e = end.toFixed(1);
const ss = beatsToTime(start).toFixed(1), es = beatsToTime(end).toFixed(1);
@@ -439,7 +451,7 @@
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);
+ if (state.audioDuration > 0) maxTime = Math.max(maxTime, state.audioDuration * state.beatsPerSecond);
const timelineWidth = maxTime * state.pixelsPerSecond;
dom.timeline.style.width = `${timelineWidth}px`;
let totalTimelineHeight = 0;
@@ -451,9 +463,9 @@
marker.textContent = `${beat}b`; timeMarkers.appendChild(marker);
}
} else {
- const maxSeconds = maxTime * 60.0 / state.bpm;
+ const maxSeconds = maxTime * state.secondsPerBeat;
for (let t = 0; t <= maxSeconds; t += 1) {
- const beatPos = t * state.bpm / 60.0, marker = document.createElement('div');
+ const beatPos = t * state.beatsPerSecond, marker = document.createElement('div');
marker.className = 'time-marker'; marker.style.left = `${beatPos * state.pixelsPerSecond}px`;
marker.textContent = `${t}s`; timeMarkers.appendChild(marker);
}
@@ -703,7 +715,8 @@
const response = await fetch(seqURL);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const content = await response.text(), parsed = parseSeqFile(content);
- state.sequences = parsed.sequences; state.bpm = parsed.bpm;
+ state.sequences = parsed.sequences;
+ updateBPM(parsed.bpm);
dom.currentBPM.value = state.bpm; dom.bpmSlider.value = state.bpm;
state.currentFile = seqURL.split('/').pop();
state.playbackOffset = 0;
@@ -731,7 +744,8 @@
reader.onload = e => {
try {
const parsed = parseSeqFile(e.target.result);
- state.sequences = parsed.sequences; state.bpm = parsed.bpm;
+ state.sequences = parsed.sequences;
+ updateBPM(parsed.bpm);
dom.currentBPM.value = state.bpm; dom.bpmSlider.value = state.bpm;
state.playbackOffset = 0;
renderTimeline(); dom.saveBtn.disabled = false; dom.addSequenceBtn.disabled = false; dom.reorderBtn.disabled = false;
@@ -787,7 +801,7 @@
// Zoom handler - managed by ViewportController
dom.bpmSlider.addEventListener('input', e => {
- state.bpm = parseInt(e.target.value);
+ updateBPM(parseInt(e.target.value));
dom.currentBPM.value = state.bpm;
if (state.audioBuffer && playbackController) playbackController.renderWaveform();
renderTimeline();
@@ -797,7 +811,7 @@
dom.currentBPM.addEventListener('change', e => {
const bpm = parseInt(e.target.value);
if (!isNaN(bpm) && bpm >= 60 && bpm <= 200) {
- state.bpm = bpm;
+ updateBPM(bpm);
dom.bpmSlider.value = bpm;
if (state.audioBuffer && playbackController) playbackController.renderWaveform();
renderTimeline();