Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
482 commits
Select commit Hold shift + click to select a range
830a21c
merge: pull in the wet/dry dedupe fix
vanceingalls Aug 12, 2026
42b945d
merge: pull in the wet/dry dedupe fix
vanceingalls Aug 12, 2026
8baa70c
merge: pull in the wet/dry dedupe fix
vanceingalls Aug 12, 2026
039005a
merge: pull in the wet/dry dedupe fix
vanceingalls Aug 12, 2026
f32f45d
merge: pull in the wet/dry dedupe fix
vanceingalls Aug 12, 2026
e9a74fb
merge: pull in the wet/dry dedupe fix
vanceingalls Aug 12, 2026
2115c1f
merge: pull in the wet/dry dedupe fix
vanceingalls Aug 12, 2026
3d025d9
merge: pull in the wet/dry dedupe fix
vanceingalls Aug 12, 2026
04205bf
merge: pull in the wet/dry dedupe fix
vanceingalls Aug 12, 2026
5dead7f
merge: pull in the wet/dry dedupe fix
vanceingalls Aug 12, 2026
21cb098
merge: pull in the wet/dry dedupe fix
vanceingalls Aug 12, 2026
13d13b9
Merge remote-tracking branch 'origin/main' into wa-3-automation-model
vanceingalls Aug 12, 2026
e98a651
merge: pull in the main-merge conflict fix
vanceingalls Aug 12, 2026
be2fcbc
merge: pull in the main-merge conflict fix
vanceingalls Aug 12, 2026
ed9f61e
merge: pull in the main-merge conflict fix
vanceingalls Aug 12, 2026
17b6003
merge: pull in the main-merge conflict fix
vanceingalls Aug 12, 2026
830d887
merge: pull in the main-merge conflict fix
vanceingalls Aug 12, 2026
bd9083d
merge: pull in the main-merge conflict fix
vanceingalls Aug 12, 2026
2a722ef
merge: pull in the main-merge conflict fix
vanceingalls Aug 12, 2026
7f4f249
merge: pull in the main-merge conflict fix
vanceingalls Aug 12, 2026
e79620a
merge: pull in the main-merge conflict fix
vanceingalls Aug 12, 2026
c8b53f6
merge: pull in the main-merge conflict fix
vanceingalls Aug 12, 2026
f5b428b
merge: pull in the main-merge conflict fix
vanceingalls Aug 12, 2026
4c87e9a
merge: pull in the main-merge conflict fix
vanceingalls Aug 12, 2026
b08614c
merge: pull in the main-merge conflict fix
vanceingalls Aug 12, 2026
c59bf9d
merge: pull in the main-merge conflict fix
vanceingalls Aug 12, 2026
73bae9a
merge: pull in the main-merge conflict fix
vanceingalls Aug 12, 2026
e6fed38
merge: pull in the main-merge conflict fix
vanceingalls Aug 12, 2026
ef13bad
merge: pull in the main-merge conflict fix
vanceingalls Aug 12, 2026
f70a76a
merge: pull in the main-merge conflict fix
vanceingalls Aug 12, 2026
bc22dab
merge: pull in the main-merge conflict fix
vanceingalls Aug 12, 2026
e140ab3
merge: pull in the main-merge conflict fix
vanceingalls Aug 12, 2026
3a8a758
merge: pull in the main-merge conflict fix
vanceingalls Aug 12, 2026
7ae0fd4
Merge remote-tracking branch 'origin/main' into wa-5-automation-runti…
vanceingalls Aug 12, 2026
b28e620
merge: pull in the main-merge conflict fix
vanceingalls Aug 12, 2026
199dbeb
merge: pull in the main-merge conflict fix
vanceingalls Aug 12, 2026
150e8f4
merge: pull in the main-merge conflict fix
vanceingalls Aug 12, 2026
6f378b0
merge: pull in the main-merge conflict fix
vanceingalls Aug 12, 2026
908d909
merge: pull in the main-merge conflict fix
vanceingalls Aug 12, 2026
8870d86
merge: pull in the main-merge conflict fix
vanceingalls Aug 12, 2026
a697226
merge: pull in the main-merge conflict fix
vanceingalls Aug 12, 2026
1b2109d
merge: pull in the main-merge conflict fix
vanceingalls Aug 12, 2026
e214048
merge: pull in the main-merge conflict fix
vanceingalls Aug 12, 2026
a8acb12
merge: pull in the main-merge conflict fix
vanceingalls Aug 12, 2026
434286c
merge: pull in the main-merge conflict fix
vanceingalls Aug 12, 2026
581b6a0
merge: pull in the main-merge conflict fix
vanceingalls Aug 12, 2026
535ac26
merge: pull in the main-merge conflict fix
vanceingalls Aug 12, 2026
866e753
merge: pull in the main-merge conflict fix
vanceingalls Aug 12, 2026
b0b4b29
merge: pull in the main-merge conflict fix
vanceingalls Aug 12, 2026
9094910
merge: pull in the main-merge conflict fix
vanceingalls Aug 12, 2026
ccaa33d
merge: pull in the main-merge conflict fix
vanceingalls Aug 12, 2026
e3c9d9a
merge: pull in the main-merge conflict fix
vanceingalls Aug 12, 2026
b4cea20
merge: pull in the main-merge conflict fix
vanceingalls Aug 12, 2026
76fbfcb
fix(core): remove the build-audio-fx-runtime.ts stray resurrected by …
vanceingalls Aug 12, 2026
0467055
merge: pull in the stray-file cleanup
vanceingalls Aug 12, 2026
a664bb8
merge: pull in the stray-file cleanup
vanceingalls Aug 12, 2026
4fe82ee
merge: pull in the stray-file cleanup
vanceingalls Aug 12, 2026
c96b429
merge: pull in the stray-file cleanup
vanceingalls Aug 12, 2026
16a471c
merge: pull in the stray-file cleanup
vanceingalls Aug 12, 2026
182af24
merge: pull in the stray-file cleanup
vanceingalls Aug 12, 2026
1bfdf40
merge: pull in the stray-file cleanup
vanceingalls Aug 12, 2026
6bcd100
merge: pull in the stray-file cleanup
vanceingalls Aug 12, 2026
c812ecb
merge: pull in the stray-file cleanup
vanceingalls Aug 12, 2026
e8a9a2c
merge: pull in the stray-file cleanup
vanceingalls Aug 12, 2026
ed09bbb
merge: pull in the stray-file cleanup
vanceingalls Aug 12, 2026
282315d
merge: pull in the stray-file cleanup
vanceingalls Aug 12, 2026
815739e
merge: pull in the stray-file cleanup
vanceingalls Aug 12, 2026
2df9ff9
merge: pull in the stray-file cleanup
vanceingalls Aug 12, 2026
6381e9a
merge: pull in the stray-file cleanup
vanceingalls Aug 12, 2026
b1e779a
merge: pull in the stray-file cleanup
vanceingalls Aug 12, 2026
46f9e52
merge: pull in the stray-file cleanup
vanceingalls Aug 12, 2026
129f256
merge: pull in the stray-file cleanup
vanceingalls Aug 12, 2026
a4699e4
Merge remote-tracking branch 'origin/main' into wa-10-lane-component
vanceingalls Aug 12, 2026
7359e64
merge: pull in the main-merge conflict fix
vanceingalls Aug 12, 2026
dac4f9e
merge: pull in the main-merge conflict fix
vanceingalls Aug 12, 2026
44ec112
merge: pull in the main-merge conflict fix
vanceingalls Aug 12, 2026
b886ae4
merge: pull in the main-merge conflict fix
vanceingalls Aug 12, 2026
006027f
merge: pull in the main-merge conflict fix
vanceingalls Aug 12, 2026
b8e5e83
merge: pull in the main-merge conflict fix
vanceingalls Aug 12, 2026
c44f776
merge: pull in the main-merge conflict fix
vanceingalls Aug 12, 2026
b6e650f
merge: pull in the main-merge conflict fix
vanceingalls Aug 12, 2026
5987b0f
merge: pull in the main-merge conflict fix
vanceingalls Aug 12, 2026
477f57b
merge: pull in the main-merge conflict fix
vanceingalls Aug 12, 2026
f676c61
merge: pull in the main-merge conflict fix
vanceingalls Aug 12, 2026
f224f68
merge: pull in the main-merge conflict fix
vanceingalls Aug 12, 2026
ce19e90
merge: pull in the main-merge conflict fix
vanceingalls Aug 12, 2026
d572ef8
merge: pull in the main-merge conflict fix
vanceingalls Aug 12, 2026
55aa6b0
merge: pull in the main-merge conflict fix
vanceingalls Aug 12, 2026
562bf86
merge: pull in the main-merge conflict fix
vanceingalls Aug 12, 2026
115fa11
merge: pull in the main-merge conflict fix
vanceingalls Aug 12, 2026
7bd053a
fix(studio): pull TimelineLanes under the 600-line cap
vanceingalls Aug 12, 2026
6232f24
merge: pull in the file-size fix
vanceingalls Aug 12, 2026
c6f1db7
merge: pull in the file-size fix
vanceingalls Aug 12, 2026
f99bf5e
merge: pull in the file-size fix
vanceingalls Aug 12, 2026
43407fe
merge: pull in the file-size fix
vanceingalls Aug 12, 2026
2936314
merge: pull in the file-size fix
vanceingalls Aug 12, 2026
c03fd4a
fix(studio): split the extracted pointerdown handler under the CRAP t…
vanceingalls Aug 13, 2026
1a5bac7
merge: pull in the CRAP-score fix
vanceingalls Aug 13, 2026
a9f33b6
merge: pull in the CRAP-score fix
vanceingalls Aug 13, 2026
0cfd6fd
merge: pull in the CRAP-score fix
vanceingalls Aug 13, 2026
b35c9d8
merge: pull in the CRAP-score fix
vanceingalls Aug 13, 2026
afcbd9e
merge: pull in the CRAP-score fix
vanceingalls Aug 13, 2026
3a38cc2
merge: pull in the CRAP-score fix
vanceingalls Aug 13, 2026
a259d14
merge: pull in the CRAP-score fix
vanceingalls Aug 13, 2026
4cd60ec
merge: pull in the CRAP-score fix
vanceingalls Aug 13, 2026
4652d4f
merge: pull in the CRAP-score fix
vanceingalls Aug 13, 2026
836760e
merge: pull in the CRAP-score fix
vanceingalls Aug 13, 2026
fb286aa
merge: pull in the CRAP-score fix
vanceingalls Aug 13, 2026
2c5e7ee
merge: pull in the CRAP-score fix
vanceingalls Aug 13, 2026
cefde3c
merge: pull in the CRAP-score fix
vanceingalls Aug 13, 2026
7f2b063
merge: pull in the CRAP-score fix
vanceingalls Aug 13, 2026
ab23ff5
merge: pull in the CRAP-score fix
vanceingalls Aug 13, 2026
2c2cdf4
merge: pull in the CRAP-score fix
vanceingalls Aug 13, 2026
e910444
merge: pull in the CRAP-score fix
vanceingalls Aug 13, 2026
0bdfaca
Merge remote-tracking branch 'origin/main' into wa-11-panel-writes
vanceingalls Aug 13, 2026
76703a8
merge: pull in the PR #3025 conflict fix
vanceingalls Aug 13, 2026
919e588
merge: pull in the PR #3025 conflict fix
vanceingalls Aug 13, 2026
d938c79
merge: pull in the PR #3025 conflict fix
vanceingalls Aug 13, 2026
d1222df
merge: pull in the PR #3025 conflict fix
vanceingalls Aug 13, 2026
fe0e70e
merge: pull in the PR #3025 conflict fix
vanceingalls Aug 13, 2026
6815d46
merge: pull in the PR #3025 conflict fix
vanceingalls Aug 13, 2026
7fc994b
merge: pull in the PR #3025 conflict fix
vanceingalls Aug 13, 2026
00bb341
merge: pull in the PR #3025 conflict fix
vanceingalls Aug 13, 2026
7bf1fff
merge: pull in the PR #3025 conflict fix
vanceingalls Aug 13, 2026
2284323
merge: pull in the PR #3025 conflict fix
vanceingalls Aug 13, 2026
10a280b
merge: pull in the PR #3025 conflict fix
vanceingalls Aug 13, 2026
9bdf345
merge: pull in the PR #3025 conflict fix
vanceingalls Aug 13, 2026
e5d70cd
merge: pull in the PR #3025 conflict fix
vanceingalls Aug 13, 2026
c14c5fe
merge: pull in the PR #3025 conflict fix
vanceingalls Aug 13, 2026
897529e
merge: pull in the PR #3025 conflict fix
vanceingalls Aug 13, 2026
7ae88f4
merge: pull in the PR #3025 conflict fix
vanceingalls Aug 13, 2026
cf02c1e
fix(studio): drop the unused DomEditSelection import in PropertyPanel…
vanceingalls Aug 13, 2026
50e48e1
merge: pull in the unused-import fix
vanceingalls Aug 13, 2026
144ae1a
merge: pull in the unused-import fix
vanceingalls Aug 13, 2026
4f9317e
merge: pull in the unused-import fix
vanceingalls Aug 13, 2026
86ace73
merge: pull in the unused-import fix
vanceingalls Aug 13, 2026
e4b4734
merge: pull in the unused-import fix
vanceingalls Aug 13, 2026
add851f
merge: pull in the unused-import fix
vanceingalls Aug 13, 2026
1afa49b
merge: pull in the unused-import fix
vanceingalls Aug 13, 2026
5f70922
merge: pull in the unused-import fix
vanceingalls Aug 13, 2026
45aefad
merge: pull in the unused-import fix
vanceingalls Aug 13, 2026
3e48807
merge: pull in the unused-import fix
vanceingalls Aug 13, 2026
c0e4d35
merge: pull in the unused-import fix
vanceingalls Aug 13, 2026
996db02
merge: pull in the unused-import fix
vanceingalls Aug 13, 2026
569ee8d
merge: pull in the unused-import fix
vanceingalls Aug 13, 2026
0703348
merge: pull in the unused-import fix
vanceingalls Aug 13, 2026
e3f6ce0
merge: pull in the unused-import fix
vanceingalls Aug 13, 2026
0f4ec28
Merge remote-tracking branch 'origin/main' into wa-15-lane-selection
vanceingalls Aug 13, 2026
f4e0584
merge: pull in the PR #3050 conflict fix
vanceingalls Aug 13, 2026
92069b5
merge: pull in the PR #3050 conflict fix
vanceingalls Aug 13, 2026
d0570f8
merge: pull in the PR #3050 conflict fix
vanceingalls Aug 13, 2026
ccacc78
merge: pull in the PR #3050 conflict fix
vanceingalls Aug 13, 2026
1eede7f
merge: pull in the PR #3050 conflict fix
vanceingalls Aug 13, 2026
9341941
merge: pull in the PR #3050 conflict fix
vanceingalls Aug 13, 2026
ae71729
Merge remote-tracking branch 'origin/main' into wa-16-lane-shapes
vanceingalls Aug 13, 2026
cb920a4
merge: pull in the PR #3055 conflict fix
vanceingalls Aug 13, 2026
8fbcc15
merge: pull in the PR #3055 conflict fix
vanceingalls Aug 13, 2026
4279188
merge: pull in the PR #3055 conflict fix
vanceingalls Aug 13, 2026
3a43040
merge: pull in the PR #3055 conflict fix
vanceingalls Aug 13, 2026
57e696e
merge: pull in the PR #3055 conflict fix
vanceingalls Aug 13, 2026
3eae9eb
merge: pull in the PR #3055 conflict fix
vanceingalls Aug 13, 2026
5cafa11
merge: pull in the PR #3055 conflict fix
vanceingalls Aug 13, 2026
146f9ac
merge: pull in the PR #3055 conflict fix
vanceingalls Aug 13, 2026
9e2ffa3
merge: pull in the PR #3055 conflict fix
vanceingalls Aug 13, 2026
424f893
merge: pull in the PR #3055 conflict fix
vanceingalls Aug 13, 2026
5427e69
merge: pull in the PR #3055 conflict fix
vanceingalls Aug 13, 2026
3e6597f
Merge remote-tracking branch 'origin/main' into wa-18a-retime-stretch
vanceingalls Aug 13, 2026
69bff86
merge: pull in the PR #3207 conflict fix
vanceingalls Aug 13, 2026
d59e6cf
merge: pull in the PR #3207 conflict fix
vanceingalls Aug 13, 2026
1656bdb
merge: pull in the PR #3207 conflict fix
vanceingalls Aug 13, 2026
13f40e5
merge: pull in the PR #3207 conflict fix
vanceingalls Aug 13, 2026
c461a7d
merge: pull in the PR #3207 conflict fix
vanceingalls Aug 13, 2026
3165b4a
merge: pull in the PR #3207 conflict fix
vanceingalls Aug 13, 2026
f9c26b2
merge: pull in the PR #3207 conflict fix
vanceingalls Aug 13, 2026
487ab39
merge: pull in the PR #3207 conflict fix
vanceingalls Aug 13, 2026
5fdfc75
merge: pull in the PR #3207 conflict fix
vanceingalls Aug 13, 2026
ea18fb5
Merge remote-tracking branch 'origin/main' into wa-18b-reschedule
vanceingalls Aug 13, 2026
8e748ac
merge: pull in the PR #3208 conflict fix
vanceingalls Aug 13, 2026
1befa91
merge: pull in the PR #3208 conflict fix
vanceingalls Aug 13, 2026
bcc3747
merge: pull in the PR #3208 conflict fix
vanceingalls Aug 13, 2026
0122e1b
merge: pull in the PR #3208 conflict fix
vanceingalls Aug 13, 2026
de04d9f
merge: pull in the PR #3208 conflict fix
vanceingalls Aug 13, 2026
9f3963d
merge: pull in the PR #3208 conflict fix
vanceingalls Aug 13, 2026
6c8c2b1
merge: pull in the PR #3208 conflict fix
vanceingalls Aug 13, 2026
2313aec
merge: pull in the PR #3208 conflict fix
vanceingalls Aug 13, 2026
4dacf28
fix(studio): close the typecheck and fallow gaps wa-18b-reschedule op…
vanceingalls Aug 13, 2026
32d4918
merge: pull in the wa-18b-reschedule fallow/typecheck fix
vanceingalls Aug 13, 2026
252b1c2
chore: merge in the wa-18b-reschedule fallow/typecheck fix
vanceingalls Aug 13, 2026
aed1e7b
chore: merge in the wa-18b-reschedule fallow/typecheck fix
vanceingalls Aug 13, 2026
d993284
chore: merge in the wa-18b-reschedule fallow/typecheck fix
vanceingalls Aug 13, 2026
00d0b2a
chore: merge in the wa-18b-reschedule fallow/typecheck fix
vanceingalls Aug 13, 2026
67bcf88
chore: merge in the wa-18b-reschedule fallow/typecheck fix
vanceingalls Aug 13, 2026
577002a
chore: merge in the wa-18b-reschedule fallow/typecheck fix
vanceingalls Aug 13, 2026
51f144c
chore: merge in the wa-18b-reschedule fallow/typecheck fix
vanceingalls Aug 13, 2026
9e828a0
fix(core): cap the via conic's weight so an edge-clamped via point ca…
vanceingalls Aug 13, 2026
36b63cc
chore: merge in the via-conic NaN fix
vanceingalls Aug 13, 2026
adbc910
chore: merge in the via-conic NaN fix
vanceingalls Aug 13, 2026
024470c
chore: merge in the via-conic NaN fix
vanceingalls Aug 13, 2026
4ceeb6f
chore: merge in the via-conic NaN fix
vanceingalls Aug 13, 2026
f21d4d1
chore: merge in the via-conic NaN fix
vanceingalls Aug 13, 2026
9fc52ee
chore: merge in the via-conic NaN fix
vanceingalls Aug 13, 2026
70c3191
chore: merge in the via-conic NaN fix
vanceingalls Aug 13, 2026
f790a7f
chore: merge in the via-conic NaN fix
vanceingalls Aug 13, 2026
f6fc7e9
fix(studio-server): fingerprint the proactive waveform cache key too
vanceingalls Aug 13, 2026
248fd70
chore: merge in the waveform cache key fix
vanceingalls Aug 13, 2026
32598dd
chore: merge in the waveform cache key fix
vanceingalls Aug 13, 2026
3722f9c
chore: merge in the waveform cache key fix
vanceingalls Aug 13, 2026
bed05ba
chore: merge in the waveform cache key fix
vanceingalls Aug 13, 2026
9d0b75b
chore: merge in the waveform cache key fix
vanceingalls Aug 13, 2026
a5478a0
chore: merge in the waveform cache key fix
vanceingalls Aug 13, 2026
99f0c4b
style(docs): run oxfmt on the /hyperframes-audio skill docs
vanceingalls Aug 13, 2026
cc631a2
chore: merge in the skill-docs format fix
vanceingalls Aug 13, 2026
009fb88
fix(core): stop \b from missing underscore-separated names, guard cli…
vanceingalls Aug 13, 2026
64eccec
chore: merge in the carve regex+overlap fix
vanceingalls Aug 13, 2026
06aff52
fix(studio): widen PropertyPanel's resetModules render timeout again
vanceingalls Aug 13, 2026
93c7abf
chore: merge in the PropertyPanel timeout fix
vanceingalls Aug 13, 2026
a81b652
chore: merge in the PropertyPanel timeout fix
vanceingalls Aug 13, 2026
cfd5551
chore: merge in the PropertyPanel timeout fix
vanceingalls Aug 13, 2026
4efaa33
chore: merge in the PropertyPanel timeout fix
vanceingalls Aug 13, 2026
011a9f0
chore: merge in the PropertyPanel timeout fix
vanceingalls Aug 13, 2026
aa4c875
chore: merge in the PropertyPanel timeout fix
vanceingalls Aug 13, 2026
2ea9b38
chore: merge in the PropertyPanel timeout fix
vanceingalls Aug 13, 2026
d02190a
chore: merge in the PropertyPanel timeout fix
vanceingalls Aug 13, 2026
13285b6
fix(studio): stop the single-candidate auto-apply carve firing twice
vanceingalls Aug 13, 2026
e965111
chore: merge in the duplicate-effect carve fix
vanceingalls Aug 13, 2026
7565f5d
chore: merge in the duplicate-effect carve fix
vanceingalls Aug 13, 2026
8faa870
chore: merge in the duplicate-effect carve fix
vanceingalls Aug 13, 2026
33d6f14
chore: merge in the duplicate-effect carve fix
vanceingalls Aug 13, 2026
9a94c82
chore: pull in main after #3210's squash merge
vanceingalls Aug 13, 2026
49c619b
chore: merge in the main restack from #3210
vanceingalls Aug 13, 2026
df4daca
chore: merge in the main restack from #3210
vanceingalls Aug 13, 2026
16bf7ca
chore: merge in the main restack from #3210
vanceingalls Aug 13, 2026
df5ea4d
chore: merge in the main restack from #3210
vanceingalls Aug 13, 2026
49be0c9
chore: merge in the main restack from #3210
vanceingalls Aug 13, 2026
15f6fa0
chore: merge in the main restack from #3210
vanceingalls Aug 13, 2026
93921f0
feat(core): carve against every voice over a bed, always (#3212)
vanceingalls Aug 13, 2026
03c1634
fix(studio): port the carve UI off the removed source/dynamic fields
vanceingalls Aug 13, 2026
9a7e19e
chore: merge in the carve UI type-shape fix
vanceingalls Aug 13, 2026
590043c
chore: merge in the carve UI type-shape fix
vanceingalls Aug 13, 2026
b1401f7
chore: merge in the carve UI type-shape fix
vanceingalls Aug 13, 2026
2284c8c
chore: merge in the carve UI type-shape fix
vanceingalls Aug 13, 2026
007eee8
chore: merge in the carve UI type-shape fix
vanceingalls Aug 13, 2026
49e3623
chore: merge in the carve UI type-shape fix
vanceingalls Aug 13, 2026
de8f963
chore: pull in main after #3211's squash merge
vanceingalls Aug 13, 2026
9fae6e8
chore: pull in main after #3211's squash merge
vanceingalls Aug 13, 2026
4e46fae
chore: pull in main after #3211's squash merge
vanceingalls Aug 13, 2026
b02131f
chore: pull in main after #3211's squash merge
vanceingalls Aug 13, 2026
d41e754
chore: pull in main after #3211's squash merge
vanceingalls Aug 13, 2026
cbbc8ed
chore: pull in main after #3173's squash merge
vanceingalls Aug 13, 2026
d666fd0
fix(cli): stop render.test.ts from downloading a real browser
vanceingalls Aug 13, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions packages/cli/src/commands/render.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,17 @@ vi.mock("../browser/preflight.js", () => ({
runEnvironmentChecks: vi.fn(async () => preflightState.result),
}));

// The "render command explicit composition" test below drives the real
// `render.js` command handler, which takes the plan-based `execute.ts` path
// (not the `renderLocal` unit under test above) — that path calls
// `ensureBrowser` directly instead of going through the mocked preflight.
// Unmocked, it performs a real network download of chrome-headless-shell into
// the shared `~/.cache/hyperframes/chrome`, racing other packages' browser
// tests in CI.
vi.mock("../browser/manager.js", () => ({
ensureBrowser: vi.fn(async () => ({ executablePath: "/mock/chrome", source: "cache" })),
}));

vi.mock("../utils/orphanCleanup.js", () => ({
killOrphanedProcesses: vi.fn(() => {
orphanCleanupState.calls += 1;
Expand Down
118 changes: 118 additions & 0 deletions packages/core/src/runtime/audioFx.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,124 @@ describe("attachElementFxChain", () => {
});
});

/**
* Lanes are committed to absolute context times, so the schedule is only
* right for the rate it was booked at. Bumping `playbackRate` alone left a
* lowpass sweeping over its original 10 wall-clock seconds while the audio
* underneath ran through 20 clip-seconds of material — and the runtime's
* stopAll()+reschedule recovery never fired for an unbounded source.
*/
describe("a rate change mid-playback", () => {
/** Records what was booked and when, without the browser's overlap rules. */
class TimedParam {
curves: { time: number; duration: number }[] = [];
ramps: number[] = [];
value = 0;
setValueAtTime(v: number): void {
this.value = v;
}
linearRampToValueAtTime(v: number, t: number): void {
this.ramps.push(t);
this.value = v;
}
setValueCurveAtTime(_v: Float32Array, time: number, duration: number): void {
this.curves.push({ time, duration });
}
cancelScheduledValues(): void {}
cancelAndHoldAtTime(): void {}
/** The last span booked, however the scheduler chose to express it. */
last(): { time: number; duration: number } | undefined {
return this.curves.at(-1);
}
}

const sweep = {
version: 1,
nodes: [{ type: "lowpass", id: "n1", params: { frequency: 300, q: 0.707 } }],
};
const lane = JSON.stringify({
version: 1,
lanes: [
{
target: "fx.n1.frequency",
points: [
{ t: 0, v: 300 },
{ t: 8, v: 3000 },
],
},
],
});

const build = () => {
const clock = { currentTime: 0 };
const made: { frequency: TimedParam }[] = [];
class TimedNode extends Node {
override frequency = new TimedParam() as unknown as { value: number };
}
class TimedCtx extends Ctx {
get currentTime(): number {
return clock.currentTime;
}
override createBiquadFilter(): Node {
const n = new TimedNode();
made.push(n as unknown as { frequency: TimedParam });
return n;
}
}
const node = document.createElement("audio");
node.setAttribute("data-fx-chain", JSON.stringify(sweep));
node.setAttribute("data-automation", lane);
document.body.append(node);
const handle = attachElementFxChain(
new TimedCtx() as unknown as BaseAudioContext,
node,
new Node() as never,
new Node() as never,
{ scheduledAt: 0, elapsed: 0, rate: 1 },
);
return { clock, node, handle, param: () => made[0]?.frequency as unknown as TimedParam };
};

it("re-aims the envelope so the sweep still ends with the material", () => {
const { clock, handle, param } = build();
// Booked at 1x: the whole 8 s lane spans 8 s of context time.
expect(param().last()).toEqual({ time: 0, duration: 8 });

clock.currentTime = 2;
handle?.setRate(2);

// 6 clip-seconds are left, and at 2x they take 3 wall-clock seconds.
// Without this the sweep kept its original plan to t=8 while the audio
// ran out at t=5.
expect(param().last()).toEqual({ time: 2, duration: 3 });
});

it("measures later edits from the new rate, not the one it started at", async () => {
// `elapsed` advances at whatever rate the reference frame holds, so a
// frame left at 1x re-aims every subsequent edit at the wrong clip
// position for as long as the track plays.
const { clock, node, handle, param } = build();
clock.currentTime = 2;
handle?.setRate(2);

clock.currentTime = 4;
// 2 wall-clock seconds at 2x is 4 clip-seconds, so the playhead is at 6
// and 2 clip-seconds remain: 1 second of wall clock.
node.setAttribute("data-automation", lane);
await new Promise((r) => setTimeout(r, 0));
expect(param().last()).toEqual({ time: 4, duration: 1 });
});

it("ignores a rate that is not a rate", () => {
const { clock, handle, param } = build();
clock.currentTime = 2;
handle?.setRate(0);
handle?.setRate(Number.NaN);
handle?.setRate(1);
expect(param().last()).toEqual({ time: 0, duration: 8 });
});
});

it("tears the chain down on dispose", () => {
const src = new Node();
const dst = new Node();
Expand Down
43 changes: 36 additions & 7 deletions packages/core/src/runtime/audioFx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,15 +93,29 @@ export function readElementAutomation(el: {
* first effect is then heard without rescheduling the source.
*
* With `timing`, the element's automation lanes are scheduled onto the built
* effects as AudioParam ramps, and rescheduled when the attribute is edited.
* effects as AudioParam ramps, and rescheduled when the attribute is edited or
* `setRate` reports the transport changed speed.
*/
export interface ElementFxHandle {
dispose(): void;
/**
* Re-aim every booked envelope at a new playback rate.
*
* Lanes are committed to absolute context times, so a param scheduled at 1×
* keeps its original wall-clock plan while the audio underneath runs at the
* new speed: a lowpass sweeping over 10 clip-seconds, switched to 2×, eats
* 20 s of material in 10 s of wall clock with the sweep unchanged.
*/
setRate(rate: number): void;
}

export function attachElementFxChain(
ctx: BaseAudioContext,
el: { getAttribute?(name: string): string | null },
source: AudioNode,
destination: AudioNode,
timing?: AutomationTiming,
): { dispose(): void } | null {
): ElementFxHandle | null {
const { chain } = readChain(el);

// Null means the source runs straight into its gain: an empty chain, or one
Expand Down Expand Up @@ -178,20 +192,26 @@ export function attachElementFxChain(
at && handle ? scheduleChainAutomation(readAutomation(el, next), next, handle.nodes, at) : [];
};

// The reference frame every later reschedule measures from. Mutable because a
// rate change rebases it: `elapsed` has to stop advancing at the old rate the
// instant the new one takes effect, or every subsequent edit re-aims the
// envelope at the wrong clip position.
let frame: AutomationTiming | null = timing ? { ...timing } : null;

attach(chain);
scheduleFor(chain, timing ?? null);
scheduleFor(chain, frame);

/**
* Re-aim the envelope at the live playhead. An edit lands mid-playback, so
* the clip has advanced past the offset the source was scheduled with.
*/
const timingNow = (): AutomationTiming | null => {
if (!timing) return null;
const now = typeof ctx.currentTime === "number" ? ctx.currentTime : timing.scheduledAt;
if (!frame) return null;
const now = typeof ctx.currentTime === "number" ? ctx.currentTime : frame.scheduledAt;
return {
scheduledAt: now,
elapsed: timing.elapsed + (now - timing.scheduledAt) * timing.rate,
rate: timing.rate,
elapsed: frame.elapsed + (now - frame.scheduledAt) * frame.rate,
rate: frame.rate,
};
};

Expand Down Expand Up @@ -251,6 +271,15 @@ export function attachElementFxChain(
}

return {
setRate: (rate: number) => {
const at = timingNow();
if (disposed || !at || !Number.isFinite(rate) || rate <= 0 || rate === at.rate) return;
// Rebased at the playhead the OLD rate carried us to, then replayed from
// there at the new one.
frame = { ...at, rate };
cancelParamLane(automated, at.scheduledAt);
scheduleFor(readChain(el).chain, frame);
},
dispose: () => {
disposed = true;
observer?.disconnect();
Expand Down
17 changes: 17 additions & 0 deletions packages/core/src/runtime/webAudioTransport.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,23 @@ describe("WebAudioTransport", () => {
expect(mock.sourceNode.playbackRate.value).toBe(2);
});

it("setRate re-aims each source's FX automation, not just its playback rate", async () => {
// The lanes are committed to absolute context times when the source is
// scheduled, so bumping playbackRate alone left every automated parameter
// running its original plan over audio moving at a different speed.
const { transport, mock, gen } = setupTransport(100);
await transport.schedulePlayback(mockEl, mockBuffer, 5, 0, 8, 1, gen, 1);
const active = (transport as unknown as { _activeSources: { fx?: unknown }[] })
._activeSources;
const setRate = vi.fn();
active[0]!.fx = { dispose: vi.fn(), setRate };

transport.setRate(2);

expect(setRate).toHaveBeenCalledWith(2);
expect(mock.sourceNode.playbackRate.value).toBe(2);
});

it("setRate before any sources are scheduled does not throw", () => {
const transport = new WebAudioTransport();
expect(() => transport.setRate(2)).not.toThrow();
Expand Down
13 changes: 11 additions & 2 deletions packages/core/src/runtime/webAudioTransport.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { attachElementFxChain, readElementAutomation } from "./audioFx.js";
import { attachElementFxChain, readElementAutomation, type ElementFxHandle } from "./audioFx.js";
import {
scheduleParamLane,
volumeLane,
Expand Down Expand Up @@ -82,7 +82,7 @@ export type ScheduledSource = {
sourceNode: AudioBufferSourceNode;
gainNode: GainNode;
/** FX chain spliced between source and gain, when the element carries one. */
fx?: { dispose(): void } | null;
fx?: ElementFxHandle | null;
compositionStart: number;
mediaStart: number;
scheduledAt: number;
Expand Down Expand Up @@ -295,6 +295,14 @@ export class WebAudioTransport {
* `getTime()` stays continuous across the change. Sources scheduled to
* start in the future keep their original wallclock start time — callers
* that need rate-correct future starts should `stopAll()` and reschedule.
*
* Each source's FX automation is re-aimed too. Lanes are committed to
* absolute context times when the source is scheduled, so bumping only
* `playbackRate` left every automated parameter running its original plan
* over audio moving at a different speed. The `stopAll()`+reschedule recovery
* in the runtime is no help here: it only fires for bounded sources, and a
* project-level music bed with no `data-duration` is unbounded, so it never
* recovered at all.
*/
setRate(rate: number): boolean {
const safeRate = normalizeRate(rate);
Expand All @@ -307,6 +315,7 @@ export class WebAudioTransport {
for (const source of this._activeSources) {
try {
source.sourceNode.playbackRate.value = safeRate;
source.fx?.setRate(safeRate);
} catch (err) {
swallow("webAudioTransport.setRate", err);
}
Expand Down
Loading
Loading