diff options
| -rw-r--r-- | tools/timeline_editor/README.md | 137 | ||||
| -rw-r--r-- | tools/timeline_editor/index.html | 110 |
2 files changed, 127 insertions, 120 deletions
diff --git a/tools/timeline_editor/README.md b/tools/timeline_editor/README.md index adf9d4e..a76a5ed 100644 --- a/tools/timeline_editor/README.md +++ b/tools/timeline_editor/README.md @@ -1,73 +1,35 @@ # Timeline Editor -Interactive web-based editor for `demo.seq` timeline files. +Interactive web-based editor for `timeline.seq` files. ## Features -✅ **Implemented:** -- 📂 Load/save `demo.seq` files -- 📊 Visual Gantt-style timeline -- 🎯 Drag & drop sequences along timeline -- 🎯 Drag & drop effects along timeline -- 🎯 Resize effects with left/right handles -- ⏱️ Edit timing (start/end times) +- 📂 Load/save `timeline.seq` files +- 📊 Visual Gantt-style timeline with sticky time markers +- 🎯 Drag & drop sequences and effects +- 🎯 Resize effects with handles +- 📦 Collapsible sequences (double-click to collapse) +- 📏 Vertical grid lines synchronized with time ticks +- ⏱️ Edit timing and properties - ⚙️ Stack-order based priority system -- ⚙️ Edit effect class names and constructor arguments -- 🔍 Zoom in/out (10% - 200%) -- 🎵 Audio waveform visualization (WAV files) -- 📋 Real-time statistics +- 🔍 Zoom (10%-500%) with mouse wheel + Ctrl/Cmd +- 🎵 Audio waveform visualization +- 🎼 Snap-to-beat mode +- 🔄 Re-order sequences by time - 🗑️ Delete sequences/effects -- ➕ Add new sequences -- 🎼 Snap-to-beat mode with beat markers ## Usage -1. **Open the editor:** - ```bash - open tools/timeline_editor/index.html - ``` - Or double-click `index.html` in Finder. - -2. **Load a timeline:** - - Click "📂 Load demo.seq" - - Select your `assets/demo.seq` file - -3. **Edit the timeline:** - - **Drag sequences/effects** to move them along the timeline - - **Click an item** to select it and view properties - - **Edit properties** in the panel below - - **Click "Apply"** to save property changes - -4. **Save your changes:** - - Click "💾 Save demo.seq" - - Choose where to save the modified file - -5. **Load audio waveform (optional):** - - Click "🎵 Load Audio (WAV)" to visualize your music track - - The waveform appears above the timeline for visual reference - - Use it to align sequences with beats, drops, and musical phrases - - Click "✖ Clear Audio" to remove the waveform - - **Tip:** Generate a WAV file from your demo using: - ```bash - ./build/demo64k --dump_wav output.wav - ``` - Then load `output.wav` in the timeline editor to align sequences with the actual audio output. - -6. **Zoom controls:** - - Use the zoom slider to adjust timeline scale - - Higher zoom = more pixels per second - - Waveform scales automatically with zoom - -7. **Snap-to-beat mode:** - - Enable "Show Beats" checkbox to display beat markers - - Sequences and effects snap to beat boundaries when dragged - - Helps maintain musical timing - -## Keyboard Shortcuts - -- **Delete key**: Delete selected item (when implemented) -- **Escape**: Deselect current item +1. **Open:** `open tools/timeline_editor/index.html` or double-click in browser +2. **Load timeline:** Click "📂 Load timeline.seq" → select `workspaces/main/timeline.seq` +3. **Edit:** + - Drag sequences/effects to reposition + - Double-click sequence header to collapse/expand + - Click item to edit properties in side panel + - Drag effect handles to resize +4. **Zoom:** Ctrl/Cmd + mouse wheel (zooms at cursor position) +5. **Audio:** Load WAV file for waveform visualization +6. **Save:** Click "💾 Save timeline.seq" ## File Format @@ -106,52 +68,11 @@ SEQUENCE 4b 1 "Beat Drop" EFFECT = ParticlesEffect 0.0 2.0 # Priority 0 (same layer) ``` -## Color Coding - -- **Blue boxes**: Sequences (container for effects) -- **Gray boxes**: Effects (visual elements) -- **Green highlight**: Selected sequence -- **Orange highlight**: Selected effect - -## Tips - -- **Sequences** have absolute start times -- **Effects** have start/end times **relative to their sequence** -- Priority determines rendering order (higher = rendered later = on top) -- Effect constructor arguments are passed as-is to the C++ code - -## Limitations - -- No preview rendering (this is intentional - it's just a timeline editor) -- No automatic overlap detection yet -- No undo/redo (coming soon) -- Cannot add effects to sequences (manually edit properties for now) - -## Future Enhancements - -- [ ] Undo/redo functionality -- [ ] Add effect button (create new effects within sequences) -- [ ] Overlap detection warnings -- [ ] Timeline playback indicator -- [ ] Multiple file comparison -- [ ] Export to different formats -- [ ] **Music.track visualization**: Parse `music.track` file and overlay tracker patterns/samples on timeline for alignment assistance - -## Technical Details - -- Pure HTML/CSS/JavaScript (no dependencies) -- No backend required -- Works offline -- All processing happens in the browser -- Files are saved via browser download API - -## Integration - -After editing in the timeline editor: - -1. Save the modified `demo.seq` -2. Copy it to `assets/demo.seq` -3. Rebuild the project: `cmake --build build` -4. The new timeline will be compiled into the demo +## Technical Notes -No code changes needed - the `seq_compiler` automatically processes the updated file. +- Pure HTML/CSS/JavaScript (no dependencies, works offline) +- Sequences have absolute times, effects are relative to parent sequence +- Priority determines render order (higher = on top) +- Collapsed sequences show 35px title bar, expanded show full effect stack +- Time markers sticky at top when scrolling +- Vertical grid lines aid alignment diff --git a/tools/timeline_editor/index.html b/tools/timeline_editor/index.html index db71beb..ccd2750 100644 --- a/tools/timeline_editor/index.html +++ b/tools/timeline_editor/index.html @@ -3,7 +3,7 @@ <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> - <title>Timeline Editor - demo.seq</title> + <title>Timeline Editor - timeline.seq</title> <style> * { margin: 0; @@ -132,10 +132,13 @@ } .time-markers { - position: relative; + position: sticky; + top: 0; height: 30px; margin-bottom: 10px; border-bottom: 1px solid #3c3c3c; + background: #252526; + z-index: 100; } .time-marker { @@ -155,6 +158,17 @@ background: #3c3c3c; } + .time-marker::after { + content: ''; + position: absolute; + left: 0; + top: 30px; + width: 1px; + height: 10000px; + background: rgba(60, 60, 60, 0.2); + pointer-events: none; + } + .sequence { position: absolute; background: #264f78; @@ -190,6 +204,36 @@ } } + .sequence-header { + position: absolute; + top: 0; + left: 0; + right: 0; + padding: 8px; + z-index: 5; + cursor: pointer; + user-select: none; + } + + .sequence-header-name { + font-size: 14px; + font-weight: bold; + color: #ffffff; + } + + .sequence:not(.collapsed) .sequence-header-name { + display: none; + } + + .sequence.collapsed { + overflow: hidden !important; + background: #1a3a4a !important; + } + + .sequence.collapsed .sequence-name { + display: none !important; + } + .sequence-name { position: absolute; top: 50%; @@ -408,15 +452,15 @@ <div class="container"> <header> <h1>📊 Timeline Editor</h1> - <p>Interactive editor for demo.seq files</p> + <p>Interactive editor for timeline.seq files</p> </header> <div class="controls"> <label class="file-label"> - 📂 Load demo.seq + 📂 Load timeline.seq <input type="file" id="fileInput" accept=".seq"> </label> - <button id="saveBtn" disabled>💾 Save demo.seq</button> + <button id="saveBtn" disabled>💾 Save timeline.seq</button> <label class="file-label"> 🎵 Load Audio (WAV) <input type="file" id="audioInput" accept=".wav"> @@ -493,7 +537,7 @@ const pixelsPerSecLabel = document.getElementById('pixelsPerSec'); const stats = document.getElementById('stats'); - // Parser: demo.seq → JavaScript objects + // Parser: timeline.seq → JavaScript objects // Format specification: doc/SEQUENCE.md function parseSeqFile(content) { const sequences = []; @@ -551,7 +595,8 @@ startTime: parseTime(seqMatch[1]), priority: parseInt(seqMatch[2]), effects: [], - name: seqMatch[3] || '' + name: seqMatch[3] || '', + _collapsed: false }; sequences.push(currentSequence); currentPriority = -1; // Reset effect priority for new sequence @@ -587,7 +632,7 @@ return { sequences, bpm }; } - // Serializer: JavaScript objects → demo.seq + // Serializer: JavaScript objects → timeline.seq function serializeSeqFile(sequences) { let output = '# Demo Timeline\n'; output += '# Generated by Timeline Editor\n'; @@ -786,20 +831,53 @@ const seqVisualWidth = seqVisualEnd - seqVisualStart; + // Initialize collapsed state if undefined + if (seq._collapsed === undefined) { + seq._collapsed = false; + } + // Calculate sequence height based on number of effects (stacked vertically) const numEffects = seq.effects.length; const effectSpacing = 30; - const seqHeight = Math.max(70, 20 + numEffects * effectSpacing + 5); + const fullHeight = Math.max(70, 20 + numEffects * effectSpacing + 5); + const seqHeight = seq._collapsed ? 35 : fullHeight; seqDiv.style.left = `${seqVisualStart * pixelsPerSecond}px`; seqDiv.style.top = `${cumulativeY}px`; seqDiv.style.width = `${seqVisualWidth * pixelsPerSecond}px`; seqDiv.style.height = `${seqHeight}px`; + seqDiv.style.minHeight = `${seqHeight}px`; + seqDiv.style.maxHeight = `${seqHeight}px`; // Store Y position for this sequence (used by effects and scroll) seq._yPosition = cumulativeY; cumulativeY += seqHeight + sequenceGap; + // Create sequence header (double-click to collapse) + const seqHeaderDiv = document.createElement('div'); + seqHeaderDiv.className = 'sequence-header'; + + const headerName = document.createElement('span'); + headerName.className = 'sequence-header-name'; + headerName.textContent = seq.name || `Sequence ${seqIndex + 1}`; + + seqHeaderDiv.appendChild(headerName); + + // Prevent drag on header + seqHeaderDiv.addEventListener('mousedown', (e) => { + e.stopPropagation(); + }); + + // Double-click to toggle collapse + seqHeaderDiv.addEventListener('dblclick', (e) => { + e.stopPropagation(); + e.preventDefault(); + seq._collapsed = !seq._collapsed; + renderTimeline(); + }); + + seqDiv.appendChild(seqHeaderDiv); + // Create sequence name overlay (large, centered, fades on hover) const seqNameDiv = document.createElement('div'); seqNameDiv.className = 'sequence-name'; @@ -807,6 +885,11 @@ seqDiv.appendChild(seqNameDiv); + // Apply collapsed state + if (seq._collapsed) { + seqDiv.classList.add('collapsed'); + } + if (selectedItem && selectedItem.type === 'sequence' && selectedItem.index === seqIndex) { seqDiv.classList.add('selected'); } @@ -827,7 +910,8 @@ timeline.appendChild(seqDiv); - // Render effects within sequence + // Render effects within sequence (skip if collapsed) + if (!seq._collapsed) { seq.effects.forEach((effect, effectIndex) => { const effectDiv = document.createElement('div'); effectDiv.className = 'effect'; @@ -894,6 +978,7 @@ timeline.appendChild(effectDiv); }); + } }); updateStats(); @@ -1172,7 +1257,7 @@ const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; - a.download = currentFile || 'demo.seq'; + a.download = currentFile || 'timeline.seq'; a.click(); URL.revokeObjectURL(url); showMessage('File saved', 'success'); @@ -1194,7 +1279,8 @@ type: 'sequence', startTime: 0, priority: 0, - effects: [] + effects: [], + _collapsed: false }); renderTimeline(); showMessage('New sequence added', 'success'); |
