# Lightningbeam TODO ## Animation System Refactoring ### Completed - ✅ Implement AnimationData curve-based system (Keyframe, AnimationCurve, AnimationData classes) - ✅ Add GraphicsObject.currentTime property - ✅ Migrate shape rendering to use AnimationData curves (exists, zOrder) - ✅ Binary search optimization for keyframe lookups ### In Progress - Migrating from Frame-based to AnimationData curve-based system throughout codebase ### Pending Features #### Animation Curve Enhancements - [ ] Implement extrapolation modes (separate for start vs end): - "hold" (default) - hold value at first/last keyframe - "extend" - linearly extend the curve beyond keyframes - "repeat" - repeat the animation - "decay" - exponential decay to a target value - [ ] Add position, scale, rotation animation curves for shapes - [ ] Add shape morphing/tweening between keyframes #### Keyframing Behavior - [ ] Add user preference for keyframing behavior when editing objects: - Auto-keyframe (current default): create/update keyframe at current time - Edit previous (Flash-style): update most recent keyframe before current time - Ephemeral (Blender-style): changes don't persist without manual keyframe - Optional: Add modifier key (e.g. Shift) to toggle between modes #### Shape Ordering - [ ] Add "Bring Forward" menu option (swap zOrder with shape in front) - [ ] Add "Send Backward" menu option (swap zOrder with shape behind) - [ ] Add "Bring to Front" menu option (set zOrder to max + 1) - [ ] Add "Send to Back" menu option (set zOrder to min - 1) #### Code Cleanup - [ ] Remove all remaining references to Frame-based system - [ ] Remove legacy Frame class once migration is complete - [ ] Clean up GraphicsObject.shapes[] array (shapes should only live in Layers) ## Known Issues / Platform Limitations ### Animation: Tweens are broken (Rust codebase) — LOW PRIORITY - **Issue**: Animation tweening between keyframes (shape/vector interpolation, and the `tween_after` behavior on keyframes) does not work correctly in the current Rust app. Needs investigation + fix. Not urgent — revisit later. - (Older JS-codebase animation entries below reference `src/*.js` and are stale.) ### Audio: Oscillator Timbre Drift (Phase Accumulation Error) - **Issue**: Oscillators exhibit timbre changes over time due to floating-point phase accumulation errors - **Affected Files**: - `daw-backend/src/effects/synth.rs:117-120` (SimpleSynth) - `daw-backend/src/audio/node_graph/nodes/oscillator.rs:167-170` (OscillatorNode) - **Root Cause**: Current phase wrapping uses conditional subtraction (`if phase >= 1.0 { phase -= 1.0 }`), which accumulates f32 rounding errors over time, especially for long-playing notes - **Current Code**: ```rust self.phase += frequency / sample_rate; if self.phase >= 1.0 { self.phase -= 1.0; } ``` - **Recommended Fix**: Replace with `.fract()` for numerically stable wraparound: ```rust self.phase += frequency / sample_rate; self.phase = self.phase.fract(); ``` - **Impact**: Medium - affects audio quality for sustained notes, becomes noticeable after several seconds - **Priority**: Medium - should be addressed before production use ### UI: Node Connections Render Behind VoiceAllocator Child Nodes - **Issue**: Connection lines (SVG paths) inside expanded VoiceAllocator nodes render behind child nodes due to z-index stacking - **Affected File**: `src/styles.css:1128` - **Root Cause**: Child nodes have `z-index: 10` while connection SVG paths have default/lower z-index - **Current Code**: ```css .drawflow .drawflow-node.child-node { opacity: 0.9; border: 1px solid #5a5aaa !important; box-shadow: 0 2px 8px rgba(90, 90, 170, 0.3); z-index: 10; } ``` - **Recommended Fix**: Either: 1. Remove `z-index: 10` from `.child-node` (simplest), or 2. Add higher z-index to connection SVG paths, or 3. Use CSS `isolation: isolate` on the VoiceAllocator contents area to create a new stacking context - **Impact**: Low - visual issue only, connections still function but appear to go "behind" nodes - **Priority**: Low - cosmetic issue that doesn't affect functionality ### UI: VoiceAllocator Child Nodes Don't Move with Parent - **Issue**: When a VoiceAllocator node is moved, its child nodes remain in their original positions instead of moving with the parent - **Affected File**: `src/main.js:6202-6207` - **Root Cause**: The `nodeMoved` event handler only handles the case where a child node is moved (resizes parent), but doesn't handle when the VoiceAllocator itself is moved - **Current Code**: ```javascript editor.on("nodeMoved", (nodeId) => { const node = editor.getNodeFromId(nodeId); if (node && node.data.parentNodeId) { resizeVoiceAllocatorToFit(node.data.parentNodeId); } }); ``` - **Recommended Fix**: Add logic to detect when a VoiceAllocator is moved and update all child node positions: ```javascript editor.on("nodeMoved", (nodeId) => { const node = editor.getNodeFromId(nodeId); // Case 1: A child node was moved - resize parent if (node && node.data.parentNodeId) { resizeVoiceAllocatorToFit(node.data.parentNodeId); } // Case 2: A VoiceAllocator was moved - move all children if (node && node.data.nodeType === 'VoiceAllocator') { // Calculate delta from previous position (need to track) // Update all child node positions by the delta // Call editor.updateConnectionNodes() for parent and all children } }); ``` - **Impact**: High - child nodes become disconnected from parent visually - **Priority**: High - breaks expected behavior of grouped nodes ### UI: VoiceAllocator Expansion Doesn't Update Connection Positions - **Issue**: When expanding/collapsing a VoiceAllocator, connection endpoints don't update to match the new port positions - **Affected File**: `src/main.js:6496-6555` (handleNodeDoubleClick function) - **Root Cause**: The expand/collapse logic shows/hides child nodes and resizes the container, but never calls `editor.updateConnectionNodes()` to refresh connection positions - **Current Code**: In `handleNodeDoubleClick()`, after expanding or collapsing: ```javascript // Expand expandedNodes.add(nodeId); nodeElement.classList.add('expanded'); nodeElement.style.width = '600px'; nodeElement.style.height = '400px'; // ... shows child nodes ... // Missing: editor.updateConnectionNodes(`node-${nodeId}`) ``` - **Recommended Fix**: Call `editor.updateConnectionNodes()` after resizing: ```javascript // After expanding expandedNodes.add(nodeId); nodeElement.classList.add('expanded'); // ... resize and show children ... // Update connection positions for VoiceAllocator and all children editor.updateConnectionNodes(`node-${nodeId}`); for (const [childId, parentId] of nodeParents.entries()) { if (parentId === nodeId) { editor.updateConnectionNodes(`node-${childId}`); } } ``` - **Impact**: Medium - connections appear in wrong positions until manually moved - **Priority**: Medium - visual issue that affects usability ### UI: Node Editor Allows Editing Without MIDI Layer Selected - **Issue**: The node editor pane allows adding/editing instrument nodes even when no MIDI layer is selected, and always uses hardcoded `trackId: 0` - **Affected File**: `src/main.js:6045-6920` (nodeEditor function) - **Root Cause**: The node editor never checks if `context.activeObject.activeLayer` exists or is a MIDI track, and all backend commands use hardcoded `trackId: 0` - **Current Code**: All graph commands hardcode track 0: ```javascript const commandArgs = parentNodeId ? { trackId: 0, // HARDCODED! voiceAllocatorId: editor.getNodeFromId(parentNodeId).data.backendId, nodeType: nodeType, x: x, y: y } : { trackId: 0, // HARDCODED! nodeType: nodeType, x: x, y: y }; ``` - **Recommended Fix**: 1. Check if activeLayer is a MIDI track before allowing edits: ```javascript function getSelectedMidiTrack() { const activeLayer = context.activeObject?.activeLayer; if (!activeLayer || activeLayer.type !== 'midi') { return null; } return activeLayer; } ``` 2. Show placeholder when no MIDI track selected: ```javascript function nodeEditor() { const container = document.createElement("div"); const midiTrack = getSelectedMidiTrack(); if (!midiTrack) { container.innerHTML = '