diff options
| -rw-r--r-- | tools/timeline_editor/README.md | 17 | ||||
| -rw-r--r-- | tools/timeline_editor/index.html | 116 | ||||
| -rw-r--r-- | workspaces/main/timeline.seq | 167 |
3 files changed, 197 insertions, 103 deletions
diff --git a/tools/timeline_editor/README.md b/tools/timeline_editor/README.md index cc13a41..72b5ae0 100644 --- a/tools/timeline_editor/README.md +++ b/tools/timeline_editor/README.md @@ -17,13 +17,26 @@ Interactive web-based editor for `timeline.seq` files. - 🎼 Quantize grid (Off, 1/32, 1/16, 1/8, 1/4, 1/2, 1 beat) - 🎛️ BPM slider (60-200 BPM) - 🔄 Re-order sequences by time -- 🗑️ Delete sequences/effects +- ✨ Add effects to sequences +- 🗑️ Delete sequences/effects (toolbar + properties panel) +- 📊 **CPU load visualization** (color-coded effect density) - ▶️ Audio playback with auto-expand/collapse - 🎚️ Sticky audio track and timeline ticks - 🔴 **Playback indicator on waveform** (NEW) - 🎯 **Double-click seek during playback** (NEW) - 📍 **Click waveform to seek** (NEW) +## CPU Load Visualization + +The editor displays a **CPU load bar** at the top (underneath audio waveform if loaded): +- **Full-height bars** (80px) show effect density at each time point +- **Color-coded:** Green (low) → Yellow (medium) → Red (high load) +- **Load calculation:** Sum of all active effects across all sequences (1.0 per effect) +- **Updates automatically** when effects/sequences are moved +- **Collapsed sequences count** toward load + +This helps identify performance hotspots in your timeline. + ## Usage 1. **Open:** `open tools/timeline_editor/index.html` or double-click in browser @@ -37,6 +50,8 @@ Interactive web-based editor for `timeline.seq` files. - Watch sequences auto-expand/collapse during playback - Red playback indicators on both timeline and waveform show current position 5. **Edit:** + - **Add Effect:** Select sequence, click "✨ Add Effect" button + - **Delete:** Click item, use "🗑️ Delete Selected" or delete button in properties panel - Drag sequences/effects to reposition (works when collapsed or expanded) - Double-click anywhere on sequence to collapse/expand - Click item to edit properties in side panel diff --git a/tools/timeline_editor/index.html b/tools/timeline_editor/index.html index 45c9f1f..d2bac0e 100644 --- a/tools/timeline_editor/index.html +++ b/tools/timeline_editor/index.html @@ -4,6 +4,7 @@ <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Timeline Editor - timeline.seq</title> + <link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><rect width='100' height='100' fill='%231e1e1e'/><rect x='10' y='30' width='15' height='40' fill='%234ec9b0'/><rect x='30' y='20' width='15' height='60' fill='%234ec9b0'/><rect x='50' y='35' width='15' height='30' fill='%234ec9b0'/><rect x='70' y='15' width='15' height='70' fill='%234ec9b0'/></svg>"> <style> :root { --bg-dark: #1e1e1e; @@ -45,7 +46,8 @@ .sticky-header { position: sticky; top: 0; background: var(--bg-medium); z-index: 100; padding: 20px 20px 10px 20px; border-bottom: 2px solid var(--bg-light); flex-shrink: 0; } .waveform-container { position: relative; height: 80px; overflow: hidden; background: rgba(0, 0, 0, 0.3); border-radius: var(--radius); cursor: crosshair; } - #waveformCanvas { position: absolute; left: 0; top: 0; height: 80px; display: block; } + #cpuLoadCanvas { position: absolute; left: 0; top: 0; height: 80px; display: block; z-index: 1; } + #waveformCanvas { position: absolute; left: 0; top: 0; height: 80px; display: block; z-index: 2; } .playback-indicator { position: absolute; top: 0; left: 0; width: 2px; background: var(--accent-red); box-shadow: 0 0 4px rgba(244, 135, 113, 0.8); pointer-events: none; z-index: 90; display: block; } @@ -111,6 +113,7 @@ <label class="file-label">🎵 Load Audio (WAV)<input type="file" id="audioInput" accept=".wav"></label> <button id="clearAudioBtn" disabled>✖ Clear Audio</button> <button id="addSequenceBtn" disabled>➕ Add Sequence</button> + <button id="addEffectBtn" disabled>✨ Add Effect</button> <button id="deleteBtn" disabled>🗑️ Delete Selected</button> <button id="reorderBtn" disabled>🔄 Re-order by Time</button> </div> @@ -145,9 +148,10 @@ <div class="timeline-container"> <div class="sticky-header"> - <div class="waveform-container" id="waveformContainer" style="display: none;"> - <div class="playback-indicator" id="waveformPlaybackIndicator"></div> + <div class="waveform-container" id="waveformContainer"> + <canvas id="cpuLoadCanvas"></canvas> <canvas id="waveformCanvas"></canvas> + <div class="playback-indicator" id="waveformPlaybackIndicator"></div> </div> <div class="time-markers" id="timeMarkers"></div> </div> @@ -175,7 +179,7 @@ const state = { sequences: [], currentFile: null, selectedItem: null, pixelsPerSecond: 100, showBeats: true, quantizeUnit: 1, bpm: 120, isDragging: false, dragOffset: { x: 0, y: 0 }, - lastActiveSeqIndex: -1, isDraggingHandle: false, handleType: null, + lastActiveSeqIndex: -1, isDraggingHandle: false, handleType: null, handleDragOffset: 0, audioBuffer: null, audioDuration: 0, audioSource: null, audioContext: null, isPlaying: false, playbackStartTime: 0, playbackOffset: 0, animationFrameId: null, lastExpandedSeqIndex: -1, dragMoved: false @@ -191,7 +195,9 @@ clearAudioBtn: document.getElementById('clearAudioBtn'), waveformCanvas: document.getElementById('waveformCanvas'), waveformContainer: document.getElementById('waveformContainer'), + cpuLoadCanvas: document.getElementById('cpuLoadCanvas'), addSequenceBtn: document.getElementById('addSequenceBtn'), + addEffectBtn: document.getElementById('addEffectBtn'), deleteBtn: document.getElementById('deleteBtn'), reorderBtn: document.getElementById('reorderBtn'), propertiesPanel: document.getElementById('propertiesPanel'), @@ -291,7 +297,6 @@ state.audioBuffer = await state.audioContext.decodeAudioData(arrayBuffer); state.audioDuration = state.audioBuffer.duration; renderWaveform(); - dom.waveformContainer.style.display = 'block'; dom.playbackControls.style.display = 'flex'; dom.clearAudioBtn.disabled = false; showMessage(`Audio loaded: ${state.audioDuration.toFixed(2)}s`, 'success'); @@ -332,10 +337,66 @@ ctx.moveTo(0, centerY); ctx.lineTo(canvasWidth, centerY); ctx.stroke(); } + function computeCPULoad() { + if (state.sequences.length === 0) return { maxTime: 60, loads: [] }; + let maxTime = 60; + for (const seq of state.sequences) { + 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); + const resolution = 0.1; + const numSamples = Math.ceil(maxTime / resolution); + const loads = new Array(numSamples).fill(0); + for (const seq of state.sequences) { + for (const effect of seq.effects) { + const startBeat = seq.startTime + effect.startTime; + const endBeat = seq.startTime + effect.endTime; + const startIdx = Math.floor(startBeat / resolution); + const endIdx = Math.ceil(endBeat / resolution); + for (let i = startIdx; i < endIdx && i < numSamples; i++) { + loads[i] += 1.0; + } + } + } + return { maxTime, loads, resolution }; + } + + function renderCPULoad() { + const canvas = dom.cpuLoadCanvas, ctx = canvas.getContext('2d'); + const { maxTime, loads, resolution } = computeCPULoad(); + const canvasWidth = maxTime * state.pixelsPerSecond, canvasHeight = 80; + canvas.width = canvasWidth; canvas.height = canvasHeight; + canvas.style.width = `${canvasWidth}px`; canvas.style.height = `${canvasHeight}px`; + ctx.fillStyle = 'rgba(0, 0, 0, 0.3)'; ctx.fillRect(0, 0, canvasWidth, canvasHeight); + if (loads.length === 0) return; + const barWidth = resolution * state.pixelsPerSecond; + for (let i = 0; i < loads.length; i++) { + const load = loads[i]; + const normalizedLoad = Math.min(load / 8, 1.0); + let r, g, b; + if (normalizedLoad < 0.5) { + r = Math.floor(normalizedLoad * 2 * 160); + g = 160; + } else { + r = 160; + g = Math.floor((1 - (normalizedLoad - 0.5) * 2) * 160); + } + b = 0; + const x = i * barWidth; + ctx.fillStyle = load === 0 ? 'rgba(0, 0, 0, 0.3)' : `rgb(${r}, ${g}, ${b})`; + ctx.fillRect(x, 0, barWidth, canvasHeight); + } + } + function clearAudio() { stopPlayback(); state.audioBuffer = null; state.audioDuration = 0; - dom.waveformContainer.style.display = 'none'; dom.playbackControls.style.display = 'none'; - dom.clearAudioBtn.disabled = true; renderTimeline(); showMessage('Audio cleared', 'success'); + dom.playbackControls.style.display = 'none'; + dom.clearAudioBtn.disabled = true; + const ctx = dom.waveformCanvas.getContext('2d'); + ctx.clearRect(0, 0, dom.waveformCanvas.width, dom.waveformCanvas.height); + renderTimeline(); showMessage('Audio cleared', 'success'); } async function startPlayback() { @@ -407,6 +468,7 @@ // Render function renderTimeline() { + renderCPULoad(); dom.timeline.innerHTML = ''; document.getElementById('timeMarkers').innerHTML = ''; let maxTime = 60; for (const seq of state.sequences) { @@ -533,13 +595,19 @@ function startHandleDrag(e, type, seqIndex, effectIndex) { e.preventDefault(); state.isDraggingHandle = true; state.handleType = type; state.selectedItem = { type: 'effect', seqIndex, effectIndex, index: seqIndex }; + const seq = state.sequences[seqIndex], effect = seq.effects[effectIndex]; + const timelineRect = dom.timeline.getBoundingClientRect(); + const mouseTimeBeats = (e.clientX - timelineRect.left + dom.timelineContent.scrollLeft) / state.pixelsPerSecond; + const handleTimeBeats = seq.startTime + (type === 'left' ? effect.startTime : effect.endTime); + state.handleDragOffset = handleTimeBeats - mouseTimeBeats; document.addEventListener('mousemove', onHandleDrag); document.addEventListener('mouseup', stopHandleDrag); } function onHandleDrag(e) { if (!state.isDraggingHandle || !state.selectedItem) return; const timelineRect = dom.timeline.getBoundingClientRect(); - let newTime = Math.max(0, (e.clientX - timelineRect.left + dom.timelineContent.scrollLeft) / state.pixelsPerSecond); + let newTime = (e.clientX - timelineRect.left + dom.timelineContent.scrollLeft) / state.pixelsPerSecond + state.handleDragOffset; + newTime = Math.max(0, newTime); if (state.quantizeUnit > 0) newTime = Math.round(newTime * state.quantizeUnit) / state.quantizeUnit; const seq = state.sequences[state.selectedItem.seqIndex], effect = seq.effects[state.selectedItem.effectIndex]; const relativeTime = newTime - seq.startTime; @@ -556,7 +624,9 @@ function selectItem(type, seqIndex, effectIndex = null) { state.selectedItem = { type, index: seqIndex, seqIndex, effectIndex }; - renderTimeline(); updateProperties(); dom.deleteBtn.disabled = false; + renderTimeline(); updateProperties(); + dom.deleteBtn.disabled = false; + dom.addEffectBtn.disabled = type !== 'sequence'; } // Properties @@ -566,11 +636,13 @@ if (state.selectedItem.type === 'sequence') { const seq = state.sequences[state.selectedItem.index]; dom.propertiesContent.innerHTML = ` - <div class="property-group"><label>Name</label><input type="text" id="propName" value="${seq.name || ''}" placeholder="Sequence name"></div> - <div class="property-group"><label>Start Time (seconds)</label><input type="number" id="propStartTime" value="${seq.startTime}" step="0.1" min="0"></div> + <div class="property-group"><label>Name</label><input type="text" id="propName" value="${seq.name || ''}" placeholder="Sequence name" inputmode="text"></div> + <div class="property-group"><label>Start Time (beats)</label><input type="number" id="propStartTime" value="${seq.startTime}" step="0.1" min="0"></div> + <div class="property-group"><button id="propDeleteBtn" style="width: 100%; background: var(--accent-red);">🗑️ Delete Sequence</button></div> `; document.getElementById('propName').addEventListener('input', applyProperties); document.getElementById('propStartTime').addEventListener('input', applyProperties); + document.getElementById('propDeleteBtn').addEventListener('click', () => dom.deleteBtn.click()); } else if (state.selectedItem.type === 'effect') { const effect = state.sequences[state.selectedItem.seqIndex].effects[state.selectedItem.effectIndex]; const effects = state.sequences[state.selectedItem.seqIndex].effects; @@ -588,6 +660,7 @@ </div> <button id="togglePriorityBtn" style="width: 100%;">${samePriority ? '✓ Same as Above (=)' : 'Increment (+)'}</button> </div> + <div class="property-group"><button id="propDeleteBtn" style="width: 100%; background: var(--accent-red);">🗑️ Delete Effect</button></div> `; document.getElementById('propClassName').addEventListener('input', applyProperties); document.getElementById('propStartTime').addEventListener('input', applyProperties); @@ -596,6 +669,7 @@ document.getElementById('moveUpBtn').addEventListener('click', moveEffectUp); document.getElementById('moveDownBtn').addEventListener('click', moveEffectDown); document.getElementById('togglePriorityBtn').addEventListener('click', toggleSamePriority); + document.getElementById('propDeleteBtn').addEventListener('click', () => dom.deleteBtn.click()); } } @@ -734,11 +808,19 @@ renderTimeline(); showMessage('New sequence added', 'success'); }); + dom.addEffectBtn.addEventListener('click', () => { + if (!state.selectedItem || state.selectedItem.type !== 'sequence') return; + const seq = state.sequences[state.selectedItem.index]; + seq.effects.push({ type: 'effect', className: 'Effect', startTime: 0, endTime: 10, priority: 0, priorityModifier: '+', args: '' }); + seq._collapsed = false; + renderTimeline(); showMessage('New effect added', 'success'); + }); + dom.deleteBtn.addEventListener('click', () => { if (!state.selectedItem) return; if (state.selectedItem.type === 'sequence') state.sequences.splice(state.selectedItem.index, 1); else if (state.selectedItem.type === 'effect') state.sequences[state.selectedItem.seqIndex].effects.splice(state.selectedItem.effectIndex, 1); - state.selectedItem = null; dom.deleteBtn.disabled = true; renderTimeline(); updateProperties(); + state.selectedItem = null; dom.deleteBtn.disabled = true; dom.addEffectBtn.disabled = true; renderTimeline(); updateProperties(); showMessage('Item deleted', 'success'); }); @@ -768,7 +850,7 @@ dom.quantizeSelect.addEventListener('change', e => { state.quantizeUnit = parseFloat(e.target.value); }); dom.panelToggle.addEventListener('click', () => { dom.propertiesPanel.classList.add('collapsed'); dom.panelCollapseBtn.classList.add('visible'); dom.panelToggle.textContent = '▲ Expand'; }); dom.panelCollapseBtn.addEventListener('click', () => { dom.propertiesPanel.classList.remove('collapsed'); dom.panelCollapseBtn.classList.remove('visible'); dom.panelToggle.textContent = '▼ Collapse'; }); - dom.timeline.addEventListener('click', () => { state.selectedItem = null; dom.deleteBtn.disabled = true; renderTimeline(); updateProperties(); }); + dom.timeline.addEventListener('click', () => { state.selectedItem = null; dom.deleteBtn.disabled = true; dom.addEffectBtn.disabled = true; renderTimeline(); updateProperties(); }); dom.timeline.addEventListener('dblclick', async e => { if (e.target !== dom.timeline) return; @@ -800,10 +882,10 @@ }); dom.timelineContent.addEventListener('scroll', () => { - if (dom.waveformCanvas) { - dom.waveformCanvas.style.left = `-${dom.timelineContent.scrollLeft}px`; - dom.waveformPlaybackIndicator.style.transform = `translateX(-${dom.timelineContent.scrollLeft}px)`; - } + const scrollLeft = dom.timelineContent.scrollLeft; + dom.cpuLoadCanvas.style.left = `-${scrollLeft}px`; + dom.waveformCanvas.style.left = `-${scrollLeft}px`; + dom.waveformPlaybackIndicator.style.transform = `translateX(-${scrollLeft}px)`; }); dom.timelineContent.addEventListener('wheel', e => { diff --git a/workspaces/main/timeline.seq b/workspaces/main/timeline.seq index ab9e40d..4e8dc69 100644 --- a/workspaces/main/timeline.seq +++ b/workspaces/main/timeline.seq @@ -2,104 +2,101 @@ # Generated by Timeline Editor # BPM 120 -SEQUENCE 0.00s 0 -EFFECT - FlashCubeEffect 0.00s 2.44s -EFFECT + FlashEffect 0.00s 1.00s color=1.0,0.5,0.5 decay=0.95 -EFFECT + FadeEffect 0.10s 1.00s -EFFECT + SolarizeEffect 0.00s 2.00s -EFFECT + VignetteEffect 0.00s 2.50s radius=0.6 softness=0.1 +SEQUENCE 0.00 0 + EFFECT - FlashCubeEffect 0.00 4.88 + EFFECT + FlashEffect 0.00 2.00 color=1.0,0.5,0.5 decay=0.95 + EFFECT + FadeEffect 0.20 2.00 + EFFECT + SolarizeEffect 0.00 4.00 + EFFECT + VignetteEffect 0.00 5.00 radius=0.6 softness=0.1 -SEQUENCE 2.50s 0 "rotating cube" -EFFECT + CircleMaskEffect 0.00s 4.00s 0.50 -EFFECT + RotatingCubeEffect 0.00s 4.00s -EFFECT + GaussianBlurEffect 1.00s 2.00s strength=1.0 -EFFECT + GaussianBlurEffect 3.00s 4.00s strength=2.0 +SEQUENCE 5.00 0 "rotating cube" + EFFECT + CircleMaskEffect 0.00 8.00 0.50 + EFFECT + RotatingCubeEffect 0.00 8.00 + EFFECT + GaussianBlurEffect 2.00 4.00 strength=1.0 + EFFECT + GaussianBlurEffect 6.00 8.00 strength=2.0 -SEQUENCE 5.93s 0 -EFFECT - FlashCubeEffect 0.11s 1.45s -EFFECT + FlashEffect 0.00s 0.20s +SEQUENCE 12.00 0 + EFFECT - FlashCubeEffect 0.22 2.90 + EFFECT + FlashEffect 0.00 0.40 -SEQUENCE 6.90s 1 "spray" -EFFECT + ParticleSprayEffect 0.00s 2.00s -EFFECT + ParticlesEffect 0.00s 3.00s -EFFECT = GaussianBlurEffect 0.00s 2.00s strength=3.0 +SEQUENCE 14.00 1 "spray" + EFFECT + ParticleSprayEffect 0.00 4.00 + EFFECT + ParticlesEffect 0.00 6.00 + EFFECT = GaussianBlurEffect 0.00 4.00 strength=3.0 -SEQUENCE 8.50s 2 "Hybrid3D" -EFFECT + ThemeModulationEffect 0.00s 2.00s -EFFECT + HeptagonEffect 0.20s 2.00s -EFFECT + ParticleSprayEffect 0.00s 2.00s -EFFECT = ParticlesEffect 0.00s 2.00s -EFFECT + Hybrid3DEffect 0.00s 2.00s -EFFECT + GaussianBlurEffect 0.00s 2.00s -EFFECT + CNNEffect 0.0s 2.0s layers=3 blend=.9 -# EFFECT + ChromaAberrationEffect 0.00 1.50 offset=0.01 angle=1.57 +SEQUENCE 17.00 2 "Hybrid3D" + EFFECT + ThemeModulationEffect 0.00 4.00 + EFFECT + HeptagonEffect 0.40 4.00 + EFFECT + ParticleSprayEffect 0.00 4.00 + EFFECT = ParticlesEffect 0.00 4.00 + EFFECT + Hybrid3DEffect 0.00 4.00 + EFFECT + GaussianBlurEffect 0.00 4.00 + EFFECT + CNNEffect 0.00 4.00 layers=3 blend=.9 -SEQUENCE 10.50s 0 "CNN effect" -EFFECT + HeptagonEffect 0.0s 12.00s -# EFFECT + RotatingCubeEffect 0.00 12.0 -# EFFECT + Hybrid3DEffect 0.00 12.00 -EFFECT + Scene1Effect 0.0s 12.0s -EFFECT + CNNEffect 1.0s 12.0s layers=3 blend=.5 +SEQUENCE 21.00 0 "CNN effect" + EFFECT + HeptagonEffect 0.00 22.00 + EFFECT + Scene1Effect 0.00 24.00 + EFFECT + CNNEffect 2.00 24.00 layers=3 blend=.5 -SEQUENCE 22.0s 0 "buggy" -EFFECT + HeptagonEffect 0.00s 0.20s -EFFECT + FadeEffect 0.11s 1.01s +SEQUENCE 44.00 0 "buggy" + EFFECT + HeptagonEffect 0.00 0.40 + EFFECT + FadeEffect 0.22 2.02 -SEQUENCE 22.14s 3 -EFFECT + ThemeModulationEffect 0.00s 4.00s -EFFECT = HeptagonEffect 0.00s 4.00s -EFFECT + GaussianBlurEffect 0.00s 5.00s strength=1.5 -EFFECT + ChromaAberrationEffect 0.00s 5.00s offset=0.03 angle=0.785 -EFFECT + SolarizeEffect 0.00s 5.00s +SEQUENCE 44.00 3 "Seq-8" + EFFECT + ThemeModulationEffect 0.00 8.00 + EFFECT = HeptagonEffect 0.00 8.00 + EFFECT + GaussianBlurEffect 0.00 10.00 strength=1.5 + EFFECT + ChromaAberrationEffect 0.00 10.00 offset=0.03 angle=0.785 + EFFECT + SolarizeEffect 0.00 10.00 -SEQUENCE 23.00s 2 -EFFECT - FlashCubeEffect 0.20s 1.50s -EFFECT + HeptagonEffect 0.00s 2.00s -EFFECT + ParticleSprayEffect 0.00s 2.00s -EFFECT + ParticlesEffect 0.00s 2.00s +SEQUENCE 46.00 2 + EFFECT - FlashCubeEffect 0.40 3.00 + EFFECT + HeptagonEffect 0.00 4.00 + EFFECT + ParticleSprayEffect 0.00 4.00 + EFFECT + ParticlesEffect 0.00 4.00 -SEQUENCE 22.75s 2 "Fade" -EFFECT - FlashCubeEffect 0.20s 1.50s -EFFECT + FlashEffect 0.00s 1.00s +SEQUENCE 46.00 2 "Fade" + EFFECT - FlashCubeEffect 0.40 3.00 + EFFECT + FlashEffect 0.00 2.00 -SEQUENCE 23.88s 10 -EFFECT - FlashCubeEffect 0.20s 1.50s -EFFECT + GaussianBlurEffect 0.00s 2.00s -EFFECT + FlashEffect 0.00s 0.20s -EFFECT = FlashEffect 0.50s 0.20s +SEQUENCE 48.00 10 + EFFECT - FlashCubeEffect 0.40 3.00 + EFFECT + GaussianBlurEffect 0.00 4.00 + EFFECT + FlashEffect 0.00 0.40 + EFFECT = FlashEffect 1.00 0.40 -SEQUENCE 25.59s 1 -EFFECT + ThemeModulationEffect 0.00s 8.00s -EFFECT + HeptagonEffect 0.20s 2.00s -EFFECT + ParticleSprayEffect 0.00s 8.00s -EFFECT + Hybrid3DEffect 0.00s 8.06s -EFFECT + GaussianBlurEffect 0.00s 8.00s -EFFECT + ChromaAberrationEffect 0.00s 8.14s -EFFECT + SolarizeEffect 0.00s 7.88s +SEQUENCE 51.00 1 + EFFECT + ThemeModulationEffect 0.00 16.00 + EFFECT + HeptagonEffect 0.40 4.00 + EFFECT + ParticleSprayEffect 0.00 16.00 + EFFECT + Hybrid3DEffect 0.00 16.12 + EFFECT + GaussianBlurEffect 0.00 16.00 + EFFECT + ChromaAberrationEffect 0.00 16.28 + EFFECT + SolarizeEffect 0.00 15.76 -SEQUENCE 33.08s 0 -EFFECT + ThemeModulationEffect 0.00s 3.00s -EFFECT + VignetteEffect 0.00s 3.00s radius=0.6 softness=0.3 -EFFECT + SolarizeEffect 0.00s 3.00s +SEQUENCE 66.00 0 + EFFECT + ThemeModulationEffect 0.00 6.00 + EFFECT + VignetteEffect 0.00 6.00 radius=0.6 softness=0.3 + EFFECT + SolarizeEffect 0.00 6.00 -SEQUENCE 35.31s 0 -EFFECT + ThemeModulationEffect 0.00s 4.00s -EFFECT + HeptagonEffect 0.20s 2.00s -EFFECT + GaussianBlurEffect 0.00s 8.00s -EFFECT + SolarizeEffect 0.00s 2.00s +SEQUENCE 71.00 0 + EFFECT + ThemeModulationEffect 0.00 8.00 + EFFECT + HeptagonEffect 0.40 4.00 + EFFECT + GaussianBlurEffect 0.00 16.00 + EFFECT + SolarizeEffect 0.00 4.00 -SEQUENCE 42.29s 0 -EFFECT + ThemeModulationEffect 0.00s 6.00s -EFFECT = HeptagonEffect 0.20s 2.00s -EFFECT + Hybrid3DEffect 0.00s 4.00s -EFFECT + ParticleSprayEffect 0.00s 5.50s -EFFECT + HeptagonEffect 0.00s 8.00s -EFFECT + ChromaAberrationEffect 0.00s 7.50s -EFFECT + GaussianBlurEffect 0.00s 8.00s +SEQUENCE 85.00 0 "double hepta!" + EFFECT + ThemeModulationEffect 0.00 12.00 + EFFECT = HeptagonEffect 0.40 4.00 + EFFECT + Hybrid3DEffect 0.00 8.00 + EFFECT + ParticleSprayEffect 0.00 11.00 + EFFECT + HeptagonEffect 0.00 16.00 + EFFECT + ChromaAberrationEffect 0.00 15.00 + EFFECT + GaussianBlurEffect 0.00 16.00 -SEQUENCE 50.02s 0 -EFFECT + ThemeModulationEffect 0.00s 4.00s -EFFECT + HeptagonEffect 0.00s 9.50s -EFFECT + ChromaAberrationEffect 0.00s 9.00s -EFFECT + GaussianBlurEffect 0.00s 8.00s +SEQUENCE 100.00 0 + EFFECT + ThemeModulationEffect 0.00 8.00 + EFFECT + HeptagonEffect 0.00 19.00 + EFFECT + ChromaAberrationEffect 0.00 18.00 + EFFECT + GaussianBlurEffect 0.00 16.00 |
