# Spectral Editor - Caching Optimization ## Problem The spectral editor had severe performance issues due to redundant `drawCurveToSpectrogram()` calls: - **Main render loop**: Called for every curve on every frame - **Audio playback**: Called when generating audio - **Mini spectrum viewer**: Called on every update With multiple curves, this resulted in hundreds of unnecessary spectrogram computations per second. ## Solution Implemented object-oriented `Curve` class with intelligent caching: ### New Architecture **File: `curve.js`** - `Curve` class encapsulates all curve logic - Maintains cached spectrogram (`cachedSpectrogram`) - Dirty flag tracking (`dirty`) - Automatic cache invalidation on parameter changes **Key Methods:** - `getSpectrogram()` - Returns cached version or recomputes if dirty - `markDirty()` - Invalidates cache (called automatically by setters) - `setProfileType()`, `setProfileSigma()`, `setVolume()` - Setter methods that mark dirty - `addControlPoint()`, `updateControlPoint()`, `deleteControlPoint()` - Control point management - `setDimensions()` - Update DCT size/frame count (invalidates cache) ### Changes to `script.js` **Curve Creation:** ```javascript // Old: const curve = { id: ..., controlPoints: [], profile: {...}, color: ..., volume: ... }; // New: const curve = new Curve(id, dctSize, numFrames); curve.setColor(color); ``` **Rendering:** ```javascript // Old: const curveSpec = new Float32Array(dctSize * numFrames); drawCurveToSpectrogram(curve, curveSpec, dctSize, numFrames); // New: const curveSpec = curve.getSpectrogram(); // Cached! ``` **Parameter Updates:** ```javascript // Old: curve.profile.type = 'gaussian'; curve.profile.sigma = 30.0; // New: curve.setProfileType('gaussian'); // Automatically marks dirty curve.setProfileSigma(30.0); // Automatically marks dirty ``` **Control Point Updates:** ```javascript // Old: curve.controlPoints.push(point); curve.controlPoints[idx] = newPoint; curve.controlPoints.splice(idx, 1); // New: curve.addControlPoint(point); // Marks dirty curve.updateControlPoint(idx, newPoint); // Marks dirty curve.deleteControlPoint(idx); // Marks dirty ``` **Undo/Redo:** ```javascript // Old: snapshot.curves = JSON.parse(JSON.stringify(state.curves)); state.curves = JSON.parse(JSON.stringify(snapshot.curves)); // New: snapshot.curves = state.curves.map(c => c.toJSON()); state.curves = snapshot.curves.map(json => Curve.fromJSON(json, dctSize, numFrames) ); ``` ### Removed Code - `drawCurveToSpectrogram()` - Moved to Curve.computeSpectrogram() - `evaluateBezierLinear()` - Moved to Curve.evaluateBezierLinear() - `evaluateProfile()` - Moved to Curve.evaluateProfile() ### Performance Impact **Before:** - Every render frame: N curves × spectrogram computation - 60 FPS × 3 curves = 180 spectrogram computations per second - Each computation: ~512 frames × 512 bins × profile evaluation = ~260K operations **After:** - Spectrogram computed once when curve changes - Subsequent renders: Direct array access (cached) - ~99% reduction in computation for static curves **Example Timeline:** 1. User adds control point → Curve marked dirty 2. Next render: `getSpectrogram()` recomputes (cached) 3. Next 59 frames: `getSpectrogram()` returns cache (instant) 4. User drags point → Curve marked dirty 5. Next render: `getSpectrogram()` recomputes (cached) 6. ... repeat ### Cache Invalidation Triggers The cache is automatically marked dirty when: - Control points added/updated/deleted - Profile type changed - Profile sigma changed - Curve volume changed - Dimensions changed (DCT size / frame count) The cache is **NOT** marked dirty when: - Curve color changed (visual only, doesn't affect spectrogram) - Curve selected/deselected (UI state) ### Testing Checklist - [x] Curve creation works - [x] Control point manipulation triggers cache invalidation - [x] Profile changes trigger cache invalidation - [x] Volume changes trigger cache invalidation - [x] Rendering uses cached spectrograms - [x] Audio playback uses cached spectrograms - [x] Mini spectrum viewer uses cached spectrograms - [x] Undo/redo properly reconstructs Curve instances - [x] Save/load preserves curve data ### File Changes Summary **New Files:** - `curve.js` (280 lines) - Curve class implementation **Modified Files:** - `index.html` - Added `` - `script.js` - - Updated curve creation to use `new Curve()` - Updated all curve property access to use setters - Updated rendering to use `curve.getSpectrogram()` - Updated undo/redo to use `toJSON()`/`fromJSON()` - Removed 89 lines of redundant functions - Changed `profile.param1` to `profile.sigma` throughout **Total Changes:** - +280 lines (curve.js) - -89 lines (removed functions) - ~150 lines modified (refactored calls) - Net: +341 lines, significantly improved performance ### Future Enhancements Potential optimizations: - Incremental cache updates (only recompute affected frames when dragging) - Shared spectrogram pool (memory optimization for many curves) - Web Worker for background spectrogram computation - Progressive rendering (render cached curves first, compute dirty ones async) ### Notes - All existing functionality preserved - Zero visual changes to UI - Backwards compatible with existing procedural_params.txt format - Cache invalidation is conservative (marks dirty on any parameter change) - Memory usage: +1 Float32Array per curve (typically ~1-2 MB total)