summaryrefslogtreecommitdiff
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
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>
-rw-r--r--tools/timeline_editor/index.html38
-rw-r--r--tools/timeline_editor/timeline-playback.js6
-rw-r--r--tools/timeline_editor/timeline-viewport.js5
3 files changed, 32 insertions, 17 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();
diff --git a/tools/timeline_editor/timeline-playback.js b/tools/timeline_editor/timeline-playback.js
index bfeb75a..17223ac 100644
--- a/tools/timeline_editor/timeline-playback.js
+++ b/tools/timeline_editor/timeline-playback.js
@@ -117,7 +117,7 @@ export class PlaybackController {
}
}
if (this.state.audioDuration > 0) {
- maxTime = Math.max(maxTime, this.state.audioDuration * this.state.bpm / 60.0);
+ maxTime = Math.max(maxTime, this.state.audioDuration * this.state.beatsPerSecond);
}
const w = maxTime * this.state.pixelsPerSecond;
@@ -313,10 +313,10 @@ export class PlaybackController {
// Helpers
beatsToTime(beats) {
- return beats * 60.0 / this.state.bpm;
+ return beats * this.state.secondsPerBeat;
}
timeToBeats(seconds) {
- return seconds * this.state.bpm / 60.0;
+ return seconds * this.state.beatsPerSecond;
}
}
diff --git a/tools/timeline_editor/timeline-viewport.js b/tools/timeline_editor/timeline-viewport.js
index 396b648..eeb2582 100644
--- a/tools/timeline_editor/timeline-viewport.js
+++ b/tools/timeline_editor/timeline-viewport.js
@@ -32,6 +32,7 @@ export class ViewportController {
this.dom.propertiesPanel.addEventListener('wheel', e => e.stopPropagation());
document.querySelector('.zoom-controls').addEventListener('wheel', e => e.stopPropagation());
document.querySelector('.stats').addEventListener('wheel', e => e.stopPropagation());
+ document.getElementById('timeMarkers').addEventListener('wheel', e => e.stopPropagation());
// Waveform hover tracking
this.dom.waveformContainer.addEventListener('mouseenter', () => this.showWaveformCursor());
@@ -146,7 +147,7 @@ export class ViewportController {
const mouseX = e.clientX - rect.left;
const scrollLeft = this.dom.timelineContent.scrollLeft;
const timeBeats = (scrollLeft + mouseX - this.TIMELINE_LEFT_PADDING) / this.state.pixelsPerSecond;
- const timeSeconds = timeBeats * 60.0 / this.state.bpm;
+ const timeSeconds = timeBeats * this.state.secondsPerBeat;
// Position cursor
this.dom.waveformCursor.style.left = `${mouseX}px`;
@@ -164,6 +165,6 @@ export class ViewportController {
// Helper
timeToBeats(seconds) {
- return seconds * this.state.bpm / 60.0;
+ return seconds * this.state.beatsPerSecond;
}
}