summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorskal <pascal.massimino@gmail.com>2026-02-15 15:17:46 +0100
committerskal <pascal.massimino@gmail.com>2026-02-15 15:17:46 +0100
commit5a70c496b8cc635735a95961aeacb6d82937dc63 (patch)
treeebd8ae2ac58fef922c63220952307364bcc22343
parentf7edf29a4c8208c7654a1c979dba980d741ad52d (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.js28
-rw-r--r--tools/timeline_editor/timeline-viewport.js26
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