summaryrefslogtreecommitdiff
path: root/tools/spectral_editor/CACHING_OPTIMIZATION.md
blob: fc25543921f41437b79b0f0fda371ebf3d501911 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
# 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 src="curve.js"></script>`
- `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)