@@ -16,7 +16,7 @@ Two facts make this cheaper here than in most editors:
16161 . ** Web Audio has native envelope playback.** ` AudioParam ` scheduling
1717 (` linearRampToValueAtTime ` , ` setValueCurveAtTime ` ) is sample-accurate and
1818 runs on the audio thread. No per-frame JS evaluates the envelope; the studio
19- only * schedules * it.
19+ only _ schedules _ it.
20202 . ** Preview and render share one graph.** The render runs the same builders in
2121 an ` OfflineAudioContext ` , so an envelope scheduled the same way in both
2222 places is identical by construction. No parity harness needed.
@@ -50,16 +50,16 @@ panel).
5050
5151## 3. UX spec (Ableton mapping)
5252
53- | Ableton | Here |
54- | --- | --- |
55- | Automation triangle on track header | Expand toggle on audio track rows in the timeline gutter |
53+ | Ableton | Here |
54+ | --------------------------------------------- | --------------------------------------------------------------------------------------------------- --- |
55+ | Automation triangle on track header | Expand toggle on audio track rows in the timeline gutter |
5656| One parameter per lane, selector at lane left | Same. Selector lists ` Volume ` + every automatable param of every chain node (` Compressor · Threshold ` ) |
57- | Breakpoint envelope over the clip | SVG envelope drawn over the existing waveform, clip-local |
58- | Double-click segment → add point | Same |
59- | Drag point (value tooltip) | Same; tooltip shows value + unit from the registry |
60- | Drag segment vertically → bend curvature | Same (Phase 2; format supports it from v1) |
61- | Delete key / right-click → remove point | Same |
62- | Dimmed line when no automation | Flat line at the current static value; first edit creates the lane |
57+ | Breakpoint envelope over the clip | SVG envelope drawn over the existing waveform, clip-local |
58+ | Double-click segment → add point | Same |
59+ | Drag point (value tooltip) | Same; tooltip shows value + unit from the registry |
60+ | Drag segment vertically → bend curvature | Same (Phase 2; format supports it from v1) |
61+ | Delete key / right-click → remove point | Same |
62+ | Dimmed line when no automation | Flat line at the current static value; first edit creates the lane |
6363
6464Lane height ~ 48 px expanded. Multiple lanes per track may be open at once
6565(one per parameter), matching Ableton's "+" lanes — Phase 2; V1 shows one lane
@@ -75,7 +75,10 @@ coalesced per gesture — free.
7575Serialised on the element, versioned, same pattern as ` data-fx-chain ` :
7676
7777``` html
78- <audio id =" music" src =" ..." data-volume =" 0.55"
78+ <audio
79+ id =" music"
80+ src =" ..."
81+ data-volume =" 0.55"
7982 data-fx-chain =' {"version":1,"nodes":[{"id":"n1","type":"peaking",...}]}'
8083 data-automation =' {
8184 "version": 1,
@@ -85,7 +88,8 @@ Serialised on the element, versioned, same pattern as `data-fx-chain`:
8588 { "target": "fx.n1.frequency",
8689 "points": [ {"t":0,"v":200}, {"t":4,"v":8000} ] }
8790 ]
88- }' >
91+ }'
92+ ></audio >
8993```
9094
9195- ** ` t ` ** — seconds, ** clip-local** (relative to the element's ` data-start ` ).
@@ -96,7 +100,7 @@ Serialised on the element, versioned, same pattern as `data-fx-chain`:
96100 (dB for a compressor threshold, Hz for a cutoff). Volume is ** linear 0..1** ,
97101 consistent with ` data-volume ` and the existing linear-domain envelope
98102 machinery — no dB conversion enters the volume path.
99- - ** ` curve ` ** — optional, ` -1..1 ` , curvature of the segment * leaving * this
103+ - ** ` curve ` ** — optional, ` -1..1 ` , curvature of the segment _ leaving _ this
100104 point. ` 0 ` /absent = linear. Power-curve bend, Ableton-style.
101105- ** ` target ` ** — ` "volume" ` or ` "fx.<nodeId>.<paramKey>" ` .
102106
@@ -106,43 +110,7 @@ so reordering the chain never re-targets a lane. Chains without ids stay
106110valid — they just can't be automation targets until the panel touches them.
107111
108112** Normalization** (` normalizeAutomation ` , mirrors ` normalizeAudioFxParams ` ):
109- - points sorted by ` t ` ; duplicate ` t ` keeps the later point
110- - ` v ` clamped to the target's registry range; non-finite → point dropped
111- - lanes targeting a node id that no longer exists in the chain are ** dropped**
112- (the author deleted the device; its automation dies with it — panel also
113- removes them eagerly on node delete)
114- - 1-point lane = constant; empty lanes array = attribute removed
115-
116- ** Precedence for volume** (documented + linted):
117- ` data-automation ` volume lane → GSAP volume tween → ` data-volume ` .
118- New lint rule ` audio_volume_double_automation ` (warning) when an element has
119- both a volume lane and a GSAP tween on ` volume ` .
120-
121- ## 5. Interpolation semantics
122-
123- - Between points: linear in the parameter's ** working domain** . Params with
124- registry ` scale: "log" ` (frequency, some times) interpolate in log domain —
125- a 200 Hz → 8 kHz sweep is perceptually linear, matching what a DAW does.
126- - ` curve ` bends the segment: ` f(x) = x^(2^(k·s)) ` shaping applied in the
127- working domain (s = curve, k ≈ 2). Exact constant chosen to visually match
128- Ableton's feel; pinned by unit tests once chosen.
129- - Before the first point: hold first value. After the last: hold last value.
130- - One shared implementation ` sampleAutomationLane(lane, t) ` in core — used by
131- the lane renderer (drawing), the scheduler (curve sampling), and the render
132- path. One interpolator, three consumers, or preview and picture drift.
133-
134- ## 6. Preview architecture
135-
136- Scheduling hooks into ` schedulePlayback ` (transport), which already runs on
137- play / seek / rate change with the clip's ` elapsed ` offset:
138-
139- - ** Volume lane** → scheduled on the source's existing ` gainNode.gain `
140- (post-FX, i.e. fader semantics — matches Ableton, matches the render order
141- where FX runs before the volume bake).
142- - ** FX param lanes** → scheduled on AudioParams exposed by the graph builders
143- (§8) of the chain instance spliced for this source.
144-
145- Mechanics per lane, at schedule time:
113+
1461141 . Convert clip-local envelope → context-time segments starting at
147115 ` scheduledAt ` , offset by ` elapsed ` , scaled by playback rate.
1481162 . Linear segments → ` setValueAtTime ` + ` linearRampToValueAtTime ` (log-domain
@@ -180,17 +148,18 @@ mid-playback without rescheduling the source.
180148
181149** Automatable in V1** (param maps to a real AudioParam):
182150
183- | Effect | Params |
184- | --- | --- |
185- | Peaking / shelves | frequency, gain, Q |
186- | High/low-pass (2-pole) | frequency, Q |
187- | Delay | time (delayTime), feedback, mix |
188- | Chorus | rate, depth, mix |
189- | Phaser | rate, wet/dry gains |
190- | Reverb | wet, dry |
191- | * Volume * | (transport gainNode) |
151+ | Effect | Params |
152+ | ---------------------- | ---------------------------- --- |
153+ | Peaking / shelves | frequency, gain, Q |
154+ | High/low-pass (2-pole) | frequency, Q |
155+ | Delay | time (delayTime), feedback, mix |
156+ | Chorus | rate, depth, mix |
157+ | Phaser | rate, wet/dry gains |
158+ | Reverb | wet, dry |
159+ | _ Volume _ | (transport gainNode) |
192160
193161** Not automatable in V1** , greyed out in the selector, with reasons:
162+
194163- ** Worklet effects** (compressor, limiter, gate, bitcrush): params travel by
195164 ` postMessage ` , not AudioParams. V2 path: declare
196165 ` parameterDescriptors ` in the processors and read ` parameters ` in
@@ -227,14 +196,14 @@ mid-playback without rescheduling the source.
227196
228197## 11. PR breakdown (all < 1000 LOC)
229198
230- | PR | Scope | Est. LOC |
231- | --- | --- | --- |
232- | A ` wa-10-automation-model ` | core: types, parse/normalize/serialize, ` sampleAutomationLane ` , curvature math, chain node ids, lint rule | ~ 450 |
233- | B ` wa-11-param-exposure ` | core: ` automatable ` flags, ` FxNodeHandle.params ` , invariant test | ~ 350 |
234- | C ` wa-12-preview-scheduling ` | core: transport + attach-path scheduling, cancel/re-schedule on live edit | ~ 400 |
235- | D ` wa-13-render-scheduling ` | core/engine: offline scheduling in runtime entry, volume→bake bridge, sweep fixture test | ~ 350 |
236- | E ` wa-14-lane-ui ` | studio: lane component, expand toggle, selector, point editing, orphan cleanup | ~ 800 |
237- | F ` wa-15-curvature ` (Phase 2) | studio: segment-bend drag; worklet ` parameterDescriptors ` migration | ~ 300+ |
199+ | PR | Scope | Est. LOC |
200+ | ----------------------------- | --------------------------------------------------------------------------------------------------------- | ----- --- |
201+ | A ` wa-10-automation-model ` | core: types, parse/normalize/serialize, ` sampleAutomationLane ` , curvature math, chain node ids, lint rule | ~ 450 |
202+ | B ` wa-11-param-exposure ` | core: ` automatable ` flags, ` FxNodeHandle.params ` , invariant test | ~ 350 |
203+ | C ` wa-12-preview-scheduling ` | core: transport + attach-path scheduling, cancel/re-schedule on live edit | ~ 400 |
204+ | D ` wa-13-render-scheduling ` | core/engine: offline scheduling in runtime entry, volume→bake bridge, sweep fixture test | ~ 350 |
205+ | E ` wa-14-lane-ui ` | studio: lane component, expand toggle, selector, point editing, orphan cleanup | ~ 800 |
206+ | F ` wa-15-curvature ` (Phase 2) | studio: segment-bend drag; worklet ` parameterDescriptors ` migration | ~ 300+ |
238207
239208A→B→C→D are dependency-ordered; E needs A+B (draws and writes) and benefits
240209from C (audible while editing). F is optional polish.
@@ -243,13 +212,13 @@ from C (audible while editing). F is optional polish.
243212
2442131 . ** Volume lane display unit** — data stays linear either way; show the axis
245214 as % (matches ` data-volume ` ) or dB (matches DAW muscle memory)?
246- * Default if unanswered: %.*
215+ _ Default if unanswered: %._
2472162 . ** Curvature in V1?** Format supports it from day one regardless. Building
248- the bend-drag in V1 adds ~ 2 days to E. * Default : defer to F, straight lines
249- first.*
217+ the bend-drag in V1 adds ~ 2 days to E. _ Default : defer to F, straight lines
218+ first._
2502193 . ** Worklet-param automation deferral acceptable?** Compressor threshold
251- automation is the notable absence. * Default : defer; it's a self-contained
252- follow-up.*
220+ automation is the notable absence. _ Default : defer; it's a self-contained
221+ follow-up._
2532224 . ** Clip-envelope semantics confirmed?** Automation travels with the clip.
254- If you expected Ableton * arrangement * behaviour (stays put), say so now —
223+ If you expected Ableton _ arrangement _ behaviour (stays put), say so now —
255224 it changes the data model (composition-global times, stored off-element).
0 commit comments