diff options
Diffstat (limited to 'tools/timeline_editor/index.html')
| -rw-r--r-- | tools/timeline_editor/index.html | 38 |
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(); |
