diff options
| author | skal <pascal.massimino@gmail.com> | 2026-02-15 15:17:46 +0100 |
|---|---|---|
| committer | skal <pascal.massimino@gmail.com> | 2026-02-15 15:17:46 +0100 |
| commit | 5a70c496b8cc635735a95961aeacb6d82937dc63 (patch) | |
| tree | ebd8ae2ac58fef922c63220952307364bcc22343 | |
| parent | f7edf29a4c8208c7654a1c979dba980d741ad52d (diff) | |
fix(timeline-editor): disable waveform tooltip when no audio loaded
- Add audioBuffer check before showing tooltip/cursor in viewport controller
- Sync variable renames in module files (pixelsPerBeat, audioDurationSeconds)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
| -rw-r--r-- | tools/timeline_editor/timeline-playback.js | 28 | ||||
| -rw-r--r-- | tools/timeline_editor/timeline-viewport.js | 26 |
2 files changed, 28 insertions, 26 deletions
diff --git a/tools/timeline_editor/timeline-playback.js b/tools/timeline_editor/timeline-playback.js index 17223ac..5427499 100644 --- a/tools/timeline_editor/timeline-playback.js +++ b/tools/timeline_editor/timeline-playback.js @@ -29,7 +29,7 @@ export class PlaybackController { this.dom.playPauseBtn.addEventListener('click', async () => { if (this.state.isPlaying) this.stopPlayback(); else { - if (this.state.playbackOffset >= this.state.audioDuration) { + if (this.state.playbackOffset >= this.state.audioDurationSeconds) { this.state.playbackOffset = 0; } this.state.playStartPosition = this.state.playbackOffset; @@ -51,12 +51,12 @@ export class PlaybackController { const rect = this.dom.waveformContainer.getBoundingClientRect(); const canvasOffset = parseFloat(this.dom.waveformCanvas.style.left) || 0; const clickX = e.clientX - rect.left - canvasOffset; - const clickBeats = clickX / this.state.pixelsPerSecond; + const clickBeats = clickX / this.state.pixelsPerBeat; const clickTime = this.beatsToTime(clickBeats); const wasPlaying = this.state.isPlaying; if (wasPlaying) this.stopPlayback(false); - this.state.playbackOffset = Math.max(0, Math.min(clickTime, this.state.audioDuration)); + this.state.playbackOffset = Math.max(0, Math.min(clickTime, this.state.audioDurationSeconds)); const pausedBeats = this.timeToBeats(this.state.playbackOffset); this.dom.playbackTime.textContent = `${this.state.playbackOffset.toFixed(2)}s (${pausedBeats.toFixed(2)}b)`; this.viewport.updateIndicatorPosition(pausedBeats, false); @@ -85,7 +85,7 @@ export class PlaybackController { } this.state.audioBuffer = await this.state.audioContext.decodeAudioData(arrayBuffer); - this.state.audioDuration = this.state.audioBuffer.duration; + this.state.audioDurationSeconds = this.state.audioBuffer.duration; this.state.originalSampleRate = originalSampleRate; this.state.resampleRatio = this.state.audioContext.sampleRate / originalSampleRate; @@ -96,7 +96,7 @@ export class PlaybackController { this.dom.playbackIndicator.style.display = 'block'; this.dom.clearAudioBtn.disabled = false; this.dom.replayBtn.disabled = false; - this.showMessage(`Audio loaded: ${this.state.audioDuration.toFixed(2)}s @ ${originalSampleRate}Hz`, 'success'); + this.showMessage(`Audio loaded: ${this.state.audioDurationSeconds.toFixed(2)}s @ ${originalSampleRate}Hz`, 'success'); this.renderCallback('audioLoaded'); } catch (err) { this.showMessage(`Error loading audio: ${err.message}`, 'error'); @@ -116,11 +116,11 @@ export class PlaybackController { maxTime = Math.max(maxTime, seq.startTime + effect.endTime); } } - if (this.state.audioDuration > 0) { - maxTime = Math.max(maxTime, this.state.audioDuration * this.state.beatsPerSecond); + if (this.state.audioDurationSeconds > 0) { + maxTime = Math.max(maxTime, this.state.audioDurationSeconds * this.state.beatsPerSecond); } - const w = maxTime * this.state.pixelsPerSecond; + const w = maxTime * this.state.pixelsPerBeat; const h = 80; canvas.width = w; canvas.height = h; @@ -131,8 +131,8 @@ export class PlaybackController { ctx.fillRect(0, 0, w, h); const channelData = this.state.audioBuffer.getChannelData(0); - const audioBeats = this.timeToBeats(this.state.audioDuration); - const audioPixelWidth = audioBeats * this.state.pixelsPerSecond; + const audioBeats = this.timeToBeats(this.state.audioDurationSeconds); + const audioPixelWidth = audioBeats * this.state.pixelsPerBeat; const samplesPerPixel = Math.ceil(channelData.length / audioPixelWidth); const centerY = h / 2; const amplitudeScale = h * this.WAVEFORM_AMPLITUDE_SCALE; @@ -171,7 +171,7 @@ export class PlaybackController { ctx.strokeStyle = 'rgba(255, 255, 255, 0.15)'; ctx.lineWidth = 1; for (let beat = 0; beat <= maxTime; beat++) { - const x = beat * this.state.pixelsPerSecond; + const x = beat * this.state.pixelsPerBeat; ctx.beginPath(); ctx.moveTo(x, 0); ctx.lineTo(x, h); @@ -182,7 +182,7 @@ export class PlaybackController { clearAudio() { this.stopPlayback(); this.state.audioBuffer = null; - this.state.audioDuration = 0; + this.state.audioDurationSeconds = 0; this.state.playbackOffset = 0; this.state.playStartPosition = 0; @@ -245,7 +245,7 @@ export class PlaybackController { if (this.state.isPlaying && savePosition) { const elapsed = this.state.audioContext.currentTime - this.state.playbackStartTime; - this.state.playbackOffset = Math.min(this.state.playbackOffset + elapsed, this.state.audioDuration); + this.state.playbackOffset = Math.min(this.state.playbackOffset + elapsed, this.state.audioDurationSeconds); } this.state.isPlaying = false; @@ -301,7 +301,7 @@ export class PlaybackController { const wasPlaying = this.state.isPlaying; if (wasPlaying) this.stopPlayback(false); - this.state.playbackOffset = Math.max(0, Math.min(clickTime, this.state.audioDuration)); + this.state.playbackOffset = Math.max(0, Math.min(clickTime, this.state.audioDurationSeconds)); const pausedBeats = this.timeToBeats(this.state.playbackOffset); this.dom.playbackTime.textContent = `${this.state.playbackOffset.toFixed(2)}s (${pausedBeats.toFixed(2)}b)`; this.viewport.updateIndicatorPosition(pausedBeats, false); diff --git a/tools/timeline_editor/timeline-viewport.js b/tools/timeline_editor/timeline-viewport.js index eeb2582..30f2403 100644 --- a/tools/timeline_editor/timeline-viewport.js +++ b/tools/timeline_editor/timeline-viewport.js @@ -41,8 +41,8 @@ export class ViewportController { } handleZoomSlider(e) { - this.state.pixelsPerSecond = parseInt(e.target.value); - this.dom.zoomLevel.textContent = `${this.state.pixelsPerSecond}%`; + this.state.pixelsPerBeat = parseInt(e.target.value); + this.dom.zoomLevel.textContent = `${this.state.pixelsPerBeat}%`; this.renderCallback('zoom'); } @@ -74,15 +74,15 @@ export class ViewportController { const rect = this.dom.timelineContent.getBoundingClientRect(); const mouseX = e.clientX - rect.left; const scrollLeft = this.dom.timelineContent.scrollLeft; - const timeUnderCursor = (scrollLeft + mouseX) / this.state.pixelsPerSecond; + const timeUnderCursor = (scrollLeft + mouseX) / this.state.pixelsPerBeat; const zoomDelta = e.deltaY > 0 ? -10 : 10; - const newPixelsPerSecond = Math.max(10, Math.min(500, this.state.pixelsPerSecond + zoomDelta)); + const newPixelsPerSecond = Math.max(10, Math.min(500, this.state.pixelsPerBeat + zoomDelta)); - if (newPixelsPerSecond !== this.state.pixelsPerSecond) { - this.state.pixelsPerSecond = newPixelsPerSecond; - this.dom.zoomSlider.value = this.state.pixelsPerSecond; - this.dom.zoomLevel.textContent = `${this.state.pixelsPerSecond}%`; + if (newPixelsPerSecond !== this.state.pixelsPerBeat) { + this.state.pixelsPerBeat = newPixelsPerSecond; + this.dom.zoomSlider.value = this.state.pixelsPerBeat; + this.dom.zoomLevel.textContent = `${this.state.pixelsPerBeat}%`; this.renderCallback('zoomWheel'); this.dom.timelineContent.scrollLeft = timeUnderCursor * newPixelsPerSecond - mouseX; this.updateIndicatorPosition(this.timeToBeats(this.state.playbackOffset), false); @@ -92,8 +92,8 @@ export class ViewportController { autoScrollToSequence() { const currentScrollLeft = this.dom.timelineContent.scrollLeft; const viewportWidth = this.dom.timelineContent.clientWidth; - const slack = (viewportWidth / this.state.pixelsPerSecond) * 0.1; - const currentTime = (currentScrollLeft / this.state.pixelsPerSecond) + slack; + const slack = (viewportWidth / this.state.pixelsPerBeat) * 0.1; + const currentTime = (currentScrollLeft / this.state.pixelsPerBeat) + slack; let targetSeqIndex = 0; for (let i = 0; i < this.state.sequences.length; i++) { @@ -119,7 +119,7 @@ export class ViewportController { } updateIndicatorPosition(beats, smoothScroll = false) { - const timelineX = beats * this.state.pixelsPerSecond; + const timelineX = beats * this.state.pixelsPerBeat; const scrollLeft = this.dom.timelineContent.scrollLeft; this.dom.playbackIndicator.style.left = `${timelineX - scrollLeft + this.TIMELINE_LEFT_PADDING}px`; @@ -133,6 +133,7 @@ export class ViewportController { } showWaveformCursor() { + if (!this.state.audioBuffer) return; this.dom.waveformCursor.style.display = 'block'; this.dom.waveformTooltip.style.display = 'block'; } @@ -143,10 +144,11 @@ export class ViewportController { } updateWaveformCursor(e) { + if (!this.state.audioBuffer) return; const rect = this.dom.waveformContainer.getBoundingClientRect(); const mouseX = e.clientX - rect.left; const scrollLeft = this.dom.timelineContent.scrollLeft; - const timeBeats = (scrollLeft + mouseX - this.TIMELINE_LEFT_PADDING) / this.state.pixelsPerSecond; + const timeBeats = (scrollLeft + mouseX - this.TIMELINE_LEFT_PADDING) / this.state.pixelsPerBeat; const timeSeconds = timeBeats * this.state.secondsPerBeat; // Position cursor |
