summaryrefslogtreecommitdiff
path: root/tools/timeline_editor
diff options
context:
space:
mode:
authorskal <pascal.massimino@gmail.com>2026-02-15 14:54:32 +0100
committerskal <pascal.massimino@gmail.com>2026-02-15 14:54:32 +0100
commit2031e24c976d1a11eb48badb97e824b1db07741a (patch)
tree479120fe608bc5645e73c25e50e07937c986c343 /tools/timeline_editor
parenta7bd63b1fc2b573141677da2d613c2a84455260e (diff)
feat(timeline-editor): add timing tooltip and cursor in waveform view
Shows precise time (seconds) and beat position under mouse cursor with a vertical guide line for accurate sample timing measurements. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Diffstat (limited to 'tools/timeline_editor')
-rw-r--r--tools/timeline_editor/index.html8
-rw-r--r--tools/timeline_editor/timeline-viewport.js36
2 files changed, 43 insertions, 1 deletions
diff --git a/tools/timeline_editor/index.html b/tools/timeline_editor/index.html
index 4efbcd8..3c5f48c 100644
--- a/tools/timeline_editor/index.html
+++ b/tools/timeline_editor/index.html
@@ -48,6 +48,8 @@
.waveform-container { position: relative; height: 80px; overflow: hidden; background: rgba(0, 0, 0, 0.3); border-radius: var(--radius); cursor: crosshair; }
#cpuLoadCanvas { position: absolute; left: 0; bottom: 0; height: 10px; display: block; z-index: 1; }
#waveformCanvas { position: absolute; left: 0; top: 0; height: 80px; display: block; z-index: 2; }
+ .waveform-cursor { position: absolute; top: 0; bottom: 0; width: 1px; background: rgba(78, 201, 176, 0.6); pointer-events: none; z-index: 3; display: none; }
+ .waveform-tooltip { position: absolute; background: rgba(30, 30, 30, 0.95); color: var(--text-primary); padding: 6px 10px; border-radius: 4px; font-size: 12px; pointer-events: none; z-index: 4; display: none; white-space: nowrap; border: 1px solid var(--border-color); box-shadow: 0 2px 8px rgba(0,0,0,0.3); }
.playback-indicator { position: absolute; top: 0; bottom: 0; left: 20px; width: 2px; background: var(--accent-red); box-shadow: 0 0 4px rgba(244, 135, 113, 0.8); pointer-events: none; z-index: 110; display: none; }
@@ -155,6 +157,8 @@
<div class="waveform-container" id="waveformContainer">
<canvas id="cpuLoadCanvas"></canvas>
<canvas id="waveformCanvas"></canvas>
+ <div class="waveform-cursor" id="waveformCursor"></div>
+ <div class="waveform-tooltip" id="waveformTooltip"></div>
</div>
<div class="time-markers" id="timeMarkers"></div>
</div>
@@ -236,7 +240,9 @@
bpmSlider: document.getElementById('bpmSlider'),
currentBPM: document.getElementById('currentBPM'),
showBeatsCheckbox: document.getElementById('showBeatsCheckbox'),
- quantizeSelect: document.getElementById('quantizeSelect')
+ quantizeSelect: document.getElementById('quantizeSelect'),
+ waveformCursor: document.getElementById('waveformCursor'),
+ waveformTooltip: document.getElementById('waveformTooltip')
};
// Parser
diff --git a/tools/timeline_editor/timeline-viewport.js b/tools/timeline_editor/timeline-viewport.js
index becf8e9..396b648 100644
--- a/tools/timeline_editor/timeline-viewport.js
+++ b/tools/timeline_editor/timeline-viewport.js
@@ -32,6 +32,11 @@ 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());
+
+ // Waveform hover tracking
+ this.dom.waveformContainer.addEventListener('mouseenter', () => this.showWaveformCursor());
+ this.dom.waveformContainer.addEventListener('mouseleave', () => this.hideWaveformCursor());
+ this.dom.waveformContainer.addEventListener('mousemove', e => this.updateWaveformCursor(e));
}
handleZoomSlider(e) {
@@ -126,6 +131,37 @@ export class ViewportController {
}
}
+ showWaveformCursor() {
+ this.dom.waveformCursor.style.display = 'block';
+ this.dom.waveformTooltip.style.display = 'block';
+ }
+
+ hideWaveformCursor() {
+ this.dom.waveformCursor.style.display = 'none';
+ this.dom.waveformTooltip.style.display = 'none';
+ }
+
+ updateWaveformCursor(e) {
+ 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 timeSeconds = timeBeats * 60.0 / this.state.bpm;
+
+ // Position cursor
+ this.dom.waveformCursor.style.left = `${mouseX}px`;
+
+ // Position and update tooltip
+ const tooltipText = `${timeSeconds.toFixed(3)}s (${timeBeats.toFixed(2)}b)`;
+ this.dom.waveformTooltip.textContent = tooltipText;
+
+ // Position tooltip above cursor, offset to the right
+ const tooltipX = mouseX + 10;
+ const tooltipY = 5;
+ this.dom.waveformTooltip.style.left = `${tooltipX}px`;
+ this.dom.waveformTooltip.style.top = `${tooltipY}px`;
+ }
+
// Helper
timeToBeats(seconds) {
return seconds * this.state.bpm / 60.0;