Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
542 commits
Select commit Hold shift + click to select a range
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
09884db
merge: pull in the main-merge conflict fix
vanceingalls Aug 12, 2026
9c46df4
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
407e67d
merge: pull in the stray-file cleanup
vanceingalls Aug 12, 2026
3aff10a
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
d7629b6
merge: pull in the main-merge conflict fix
vanceingalls Aug 12, 2026
c84df9a
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
001bead
merge: pull in the CRAP-score fix
vanceingalls Aug 13, 2026
10e31b1
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
2d2ff34
merge: pull in the PR #3025 conflict fix
vanceingalls Aug 13, 2026
e4d9635
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
2865303
merge: pull in the unused-import fix
vanceingalls Aug 13, 2026
78550ca
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
169bf06
merge: pull in the PR #3055 conflict fix
vanceingalls Aug 13, 2026
6e00388
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
9fabbd8
merge: pull in the PR #3207 conflict fix
vanceingalls Aug 13, 2026
8832671
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
059cbef
merge: pull in the PR #3208 conflict fix
vanceingalls Aug 13, 2026
58371d6
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
e0a510e
chore: merge in the wa-18b-reschedule fallow/typecheck fix
vanceingalls Aug 13, 2026
459fe0a
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
ddaae3e
chore: merge in the via-conic NaN fix
vanceingalls Aug 13, 2026
a43f818
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
0658830
chore: merge in the waveform cache key fix
vanceingalls Aug 13, 2026
332ef78
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
ad6bfdd
chore: merge in the PropertyPanel timeout fix
vanceingalls Aug 13, 2026
fb5ea90
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
d08724a
chore: merge in the duplicate-effect carve fix
vanceingalls Aug 13, 2026
eab3096
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
d785c14
chore: merge in the main restack from #3210
vanceingalls Aug 13, 2026
f7b5a21
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
93dd2bb
chore: merge in the carve UI type-shape fix
vanceingalls Aug 13, 2026
816671f
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
f0d6bfb
chore: pull in main after #3211's squash merge
vanceingalls Aug 13, 2026
8f35103
chore: pull in main after #3211's squash merge
vanceingalls Aug 13, 2026
7ad64a1
chore: pull in main after #3175's squash merge
vanceingalls Aug 13, 2026
36c5a5b
docs(plans): fix pre-existing oxfmt formatting drift in audio-fx-presets
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
33 changes: 33 additions & 0 deletions packages/core/src/audio/audioFxGraph.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,39 @@ describe("buildFxChain", () => {
expect(c.created.find((x) => x.kind === "biquad")!.frequency.value).toBe(2000);
});

it("lines new values up against the built nodes, skipping a bypassed one", () => {
// A bypassed node is not in the graph, so the update has to walk the
// ENABLED nodes to stay aligned with what was built. Walking `next.nodes`
// instead shifts everything after the bypass by one and pushes each node's
// parameters into its neighbour — and the shape is unchanged either way,
// so nothing forces a rebuild that would hide it.
const c = ctx();
const withBypass = (frequency: number): HfAudioFxChain => ({
version: 1,
nodes: [
{
type: "highpass",
enabled: true,
params: { ...defaultAudioFxParams("highpass"), frequency: 100 },
},
{ type: "peaking", enabled: false, params: defaultAudioFxParams("peaking") },
{
type: "lowpass",
enabled: true,
params: { ...defaultAudioFxParams("lowpass"), frequency },
},
],
});
const h = buildFxChain(asCtx(c), withBypass(8000));
const biquads = c.created.filter((n) => n.kind === "biquad");
expect(biquads).toHaveLength(2);

expect(h.update(withBypass(3000))).toBe(true);
// The lowpass took the new cutoff; the highpass was left where it was.
expect(biquads[0]!.frequency.value).toBe(100);
expect(biquads[1]!.frequency.value).toBe(3000);
});

it("reports that a rebuild is needed when the chain shape changes", () => {
const c = ctx();
const h = buildFxChain(asCtx(c), chain("peaking"));
Expand Down
16 changes: 8 additions & 8 deletions packages/core/src/audio/audioFxGraph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
*/

import {
enabledAudioFxNodes,
getAudioFxDef,
normalizeAudioFxParams,
type HfAudioFxChain,
Expand Down Expand Up @@ -484,8 +485,7 @@ export interface FxChainHandle {
* into the running nodes; when it changes, the caller rebuilds.
*/
function shapeOf(chain: HfAudioFxChain): string {
return chain.nodes
.filter((node) => node.enabled !== false)
return enabledAudioFxNodes(chain)
.map((node) => {
const p = normalizeAudioFxParams(node.type, node.params);
const poles = p.poles !== undefined ? `:${p.poles}` : "";
Expand All @@ -510,25 +510,23 @@ export function buildFxChain(ctx: BaseAudioContext, chain: HfAudioFxChain): FxCh
const handles: { id?: string; type: string; handle: FxNodeHandle }[] = [];

let tail: AudioNode = input;
for (const node of chain.nodes) {
if (node.enabled === false) continue;
for (const node of enabledAudioFxNodes(chain)) {
const handle = buildFxNode(ctx, node.type, node.params ?? {});
tail.connect(handle.input);
tail = handle.output;
handles.push({ ...(node.id ? { id: node.id } : {}), type: node.type, handle });
}
tail.connect(output);

let shape = shapeOf(chain);
const shape = shapeOf(chain);

return {
input,
output,
nodes: handles,
update(next) {
if (shapeOf(next) !== shape) return false;
const active = next.nodes.filter((node) => node.enabled !== false);
active.forEach((node, i) => {
enabledAudioFxNodes(next).forEach((node, i) => {
const held = handles[i];
if (!held) return;
held.handle.update(normalizeAudioFxParams(node.type, node.params));
Expand All @@ -541,7 +539,9 @@ export function buildFxChain(ctx: BaseAudioContext, chain: HfAudioFxChain): FxCh
if (node.id === undefined) delete held.id;
else held.id = node.id;
});
shape = shapeOf(next);
// `shape` is not reassigned: the early return above already established
// that `shapeOf(next)` equals it, so recomputing was a whole normalise +
// join per observer tick to write back the string that was already there.
return true;
},
dispose() {
Expand Down
10 changes: 10 additions & 0 deletions packages/core/src/audioAutomation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import {
applyCurve,
shapeProgress,
fxAutomationTarget,
HF_AUDIO_AUTOMATION_ATTR,
HF_AUDIO_AUTOMATION_DATA_KEY,
isConstantLane,
parseAutomation,
parseAutomationTarget,
Expand Down Expand Up @@ -30,6 +32,14 @@ const lane = (points: HfAutomationLane["points"], target = "volume"): HfAutomati
points,
});

describe("the automation attribute's two spellings", () => {
it("names the same attribute either way", () => {
// Same split as the FX chain: written as an attribute, read as a dataset
// key. Derived, so a rename cannot half-land.
expect(HF_AUDIO_AUTOMATION_ATTR).toBe(`data-${HF_AUDIO_AUTOMATION_DATA_KEY}`);
});
});

describe("targets", () => {
it("reads volume and fx targets, and rejects anything else", () => {
expect(parseAutomationTarget("volume")).toEqual({ kind: "volume" });
Expand Down
20 changes: 11 additions & 9 deletions packages/core/src/audioAutomation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,13 @@
* curve, three consumers, or the picture and the sound disagree.
*/

import { getAudioFxDef, type HfAudioFxChain, type HfAudioFxNumberParam } from "./audioFx.js";
import { getAudioFxDef, type HfAudioFxChain } from "./audioFx.js";

export const HF_AUDIO_AUTOMATION_ATTR = "data-automation";

/** The same attribute as a `dataset` / `dataAttributes` key. See `HF_AUDIO_FX_DATA_KEY`. */
export const HF_AUDIO_AUTOMATION_DATA_KEY = HF_AUDIO_AUTOMATION_ATTR.slice("data-".length);

/** Automation files are versioned; a reader must refuse a version it doesn't know. */
export const HF_AUDIO_AUTOMATION_VERSION = 1;

Expand Down Expand Up @@ -138,15 +141,14 @@ export function resolveAutomationRange(
const def = getAudioFxDef(node.type);
const param = def?.params.find((p) => p.key === parsed.param);
if (!param || param.kind !== "number") return null;
const p = param as HfAudioFxNumberParam;
return {
min: p.min,
max: p.max,
step: p.step,
unit: p.unit,
label: `${def?.label ?? node.type} · ${p.label}`,
scale: p.scale === "log" && p.min > 0 ? "log" : "linear",
default: p.default,
min: param.min,
max: param.max,
step: param.step,
unit: param.unit,
label: `${def?.label ?? node.type} · ${param.label}`,
scale: param.scale === "log" && param.min > 0 ? "log" : "linear",
default: param.default,
};
}

Expand Down
11 changes: 11 additions & 0 deletions packages/core/src/audioFx.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import {
enabledAudioFxNodes,
getAudioFxDef,
HF_AUDIO_FX,
HF_AUDIO_FX_ATTR,
HF_AUDIO_FX_CHAIN_VERSION,
HF_AUDIO_FX_DATA_KEY,
HF_AUDIO_FX_IDS,
normalizeAudioFxParams,
parseAudioFxChain,
Expand Down Expand Up @@ -47,6 +49,15 @@ describe("effect registry", () => {
});
});

describe("the chain attribute's two spellings", () => {
it("names the same attribute either way", () => {
// The studio WRITES through the attribute and READS through the dataset
// key, so the read side used to carry its own `"fx-chain"` literal and a
// rename would only half-land. Derived now; this is what keeps it derived.
expect(HF_AUDIO_FX_ATTR).toBe(`data-${HF_AUDIO_FX_DATA_KEY}`);
});
});

describe("normalizeAudioFxParams", () => {
it("fills missing keys with defaults", () => {
expect(normalizeAudioFxParams("peaking", {})).toEqual(defaultAudioFxParams("peaking"));
Expand Down
10 changes: 10 additions & 0 deletions packages/core/src/audioFx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,16 @@

export const HF_AUDIO_FX_ATTR = "data-fx-chain";

/**
* The same attribute as a `dataset` / `dataAttributes` key — the `data-` prefix
* is not part of that spelling.
*
* Derived rather than restated: the studio writes through
* `HF_AUDIO_FX_ATTR` and reads through the key, so a hardcoded `"fx-chain"`
* on the read side is a rename waiting to half-land.
*/
export const HF_AUDIO_FX_DATA_KEY = HF_AUDIO_FX_ATTR.slice("data-".length);

/** Chain files are versioned; a reader must refuse a version it doesn't know. */
export const HF_AUDIO_FX_CHAIN_VERSION = 1;

Expand Down
4 changes: 2 additions & 2 deletions packages/studio/src/components/editor/audioFxSummary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@
* grouping exists to prevent — that they are seven things to manage.
*/

import { parseAudioFxChain } from "@hyperframes/core/audio-fx";
import { HF_AUDIO_FX_DATA_KEY, parseAudioFxChain } from "@hyperframes/core/audio-fx";
import type { DomEditSelection } from "./domEditingTypes";

export function audioFxSummary(element: DomEditSelection): string {
const raw = element.dataAttributes?.["fx-chain"];
const raw = element.dataAttributes?.[HF_AUDIO_FX_DATA_KEY];
const carveAttr = element.dataAttributes?.["fx-carve"];
let handBuilt = 0;
let carveNodes = 0;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
/**
* The audio FX panel's bridge to the element/attribute world.
*
Expand All @@ -11,6 +11,7 @@
import {
defaultAudioFxParams,
HF_AUDIO_FX_ATTR,
HF_AUDIO_FX_DATA_KEY,
mintAudioFxNodeId,
parseAudioFxChain,
serializeAudioFxChain,
Expand Down Expand Up @@ -41,6 +42,7 @@
automatedTargetsOf,
automationAttrValue,
HF_AUDIO_AUTOMATION_ATTR,
HF_AUDIO_AUTOMATION_DATA_KEY,
readPanelAutomation,
resolveAutomationRange,
withoutLane,
Expand Down Expand Up @@ -109,7 +111,7 @@
onSetAttributeLive: (attr: string, value: string | null) => void | Promise<void>;
}) {
const chain = ((): HfAudioFxChain => {
const raw = element.dataAttributes?.["fx-chain"];
const raw = element.dataAttributes?.[HF_AUDIO_FX_DATA_KEY];
if (!raw) return { version: 1, nodes: [] };
try {
return parseAudioFxChain(raw);
Expand All @@ -120,7 +122,10 @@
}
})();

const automation = readPanelAutomation(element.dataAttributes?.["automation"], chain);
const automation = readPanelAutomation(
element.dataAttributes?.[HF_AUDIO_AUTOMATION_DATA_KEY],
chain,
);
const automatedTargets = automatedTargetsOf(automation);

/**
Expand Down Expand Up @@ -485,11 +490,24 @@
const analyse = async (active: HfCarveSettings | null = carve): Promise<void> => {
if (!active?.sources.length) return;
const doc = element.element?.ownerDocument;
if (!doc) return;
// Every named voice that is actually there with something to decode. A source
// naming a deleted track is skipped rather than failing the whole analysis.
const voices = active.sources
.map((id) => doc?.getElementById(id) as HTMLAudioElement | null)
.filter((el): el is HTMLAudioElement => Boolean(el?.getAttribute("src")));
//
// Read out to plain values here rather than carrying elements around: it is
// what lets the src and the start be non-null by construction downstream
// instead of by assertion.
const voices: { src: string; start: string | null }[] = [];
for (const id of active.sources) {
const el = doc.getElementById(id);
// By tag name, not `instanceof HTMLAudioElement`: these elements belong to
// the composition's iframe document, so the constructor they were made
// from is not this realm's and the instanceof is false for every one.
if (el?.tagName !== "AUDIO") continue;
const src = el.getAttribute("src");
if (!src) continue;
voices.push({ src, start: el.getAttribute("data-start") });
}
if (voices.length === 0) return;
setAnalysing(true);
try {
Expand All @@ -502,7 +520,7 @@
.webkitOfflineAudioContext;
if (!Ctor) return;
const decode = async (relative: string): Promise<AudioBuffer> => {
const res = await fetch(new URL(relative, doc!.baseURI).href);
const res = await fetch(new URL(relative, doc.baseURI).href);
return new Ctor(1, 1, DECODE_SAMPLE_RATE).decodeAudioData(await res.arrayBuffer());
};
const bedStart = clipStart(element.dataAttributes?.["start"]);
Expand All @@ -512,9 +530,9 @@
// is also what lets the bands and the envelopes stay a single set: the chain is
// fixed, so there is no per-voice filter to switch between.
const decoded = await Promise.all(
voices.map(async (el) => ({
samples: (await decode(el.getAttribute("src")!)).getChannelData(0),
offsetSeconds: clipStart(el.getAttribute("data-start")) - bedStart,
voices.map(async (voice) => ({
samples: (await decode(voice.src)).getChannelData(0),
offsetSeconds: clipStart(voice.start) - bedStart,
})),
);
const voiceMix = mixCarveSources(decoded, DECODE_SAMPLE_RATE);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import {
HF_AUDIO_AUTOMATION_ATTR,
HF_AUDIO_AUTOMATION_DATA_KEY,
parseAutomation,
resolveAutomation,
resolveAutomationRange,
Expand Down Expand Up @@ -77,4 +78,4 @@ export function automationAttrValue(automation: HfAutomation): string {
return automation.lanes.length > 0 ? serializeAutomation(automation) : "";
}

export { HF_AUDIO_AUTOMATION_ATTR, resolveAutomationRange };
export { HF_AUDIO_AUTOMATION_ATTR, HF_AUDIO_AUTOMATION_DATA_KEY, resolveAutomationRange };
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,55 @@ describe("FxSection chain", () => {
expect(next.nodes.map((n) => n.type)).toEqual(["reverb", "peaking"]);
});

it("keeps a half-typed value with its own effect across a reorder", () => {
// Rows used to be keyed `${type}-${index}`, so two effects of the same type
// kept their keys through a reorder and React reused each row where it
// stood. The controls hold real state — a number field mid-edit is held as
// text — so the buffer stayed at the position and landed on whichever
// effect moved into it.
const peaking = (id: string, frequency: number) => ({
type: "peaking",
id,
enabled: true,
params: { ...defaultAudioFxParams("peaking"), frequency },
});
const a = peaking("pa", 400);
const b = peaking("pb", 1600);
const chainOfNodes = (...nodes: unknown[]): HfAudioFxChain =>
({ version: 1, nodes }) as HfAudioFxChain;

const host = document.createElement("div");
document.body.append(host);
const root = createRoot(host);
const render = (chain: HfAudioFxChain) =>
act(() => {
root.render(
<FxSection
chain={chain}
onChainChange={vi.fn()}
onChainPreview={vi.fn()}
carve={null}
onCarveChange={vi.fn()}
sourceOptions={[]}
/>,
);
});

render(chainOfNodes(a, b));
// Only the first card is open, which is the one being edited.
const openFrequency = (): HTMLInputElement =>
host.querySelector<HTMLInputElement>(".hf-fx-node .hf-fx-number")!;

expect(openFrequency().value).toBe("400");
typeInto(openFrequency(), "123");
expect(openFrequency().value).toBe("123");

// The author moves that effect down; the other one takes the open slot.
render(chainOfNodes(b, a));

expect(openFrequency().value).toBe("1600");
});

it("cannot move the ends past themselves", () => {
const { host } = mount({ chain: chainOf("peaking", "reverb") });
const ups = host.querySelectorAll('.hf-fx-move[title="Move up"]');
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
/**
* The FX section for an audio element: the chain, plus the voiceover carve.
*
Expand Down Expand Up @@ -795,7 +795,13 @@
handBuilt.map(({ node, i }) => {
return (
<FxNodeRow
key={`${node.type}-${i}`}
// Keyed by id, as the carve module's list above already is.
// On `${type}-${index}` two effects of the same type keep their
// keys through a reorder, so React reuses each row where it
// stands — and the controls hold real state (a half-typed
// number, an in-flight drag), which then lands on whichever
// effect moved into that slot.
key={node.id ?? `${node.type}-${i}`}
node={node}
index={i}
automatedTargets={automatedTargets}
Expand Down
Loading
Loading