Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
960 commits
Select commit Hold shift + click to select a range
4956f5f
merge: pull in the PR #3207 conflict fix
vanceingalls Aug 13, 2026
39a38d5
merge: pull in the PR #3207 conflict fix
vanceingalls Aug 13, 2026
3c6f3c2
merge: pull in the PR #3207 conflict fix
vanceingalls Aug 13, 2026
2572b9a
merge: pull in the PR #3207 conflict fix
vanceingalls Aug 13, 2026
d433d23
merge: pull in the PR #3207 conflict fix
vanceingalls Aug 13, 2026
356b2fd
merge: pull in the PR #3207 conflict fix
vanceingalls Aug 13, 2026
a0e4f9c
merge: pull in the PR #3207 conflict fix
vanceingalls Aug 13, 2026
27c57e7
merge: pull in the PR #3207 conflict fix
vanceingalls Aug 13, 2026
96fa8a9
merge: pull in the PR #3207 conflict fix
vanceingalls Aug 13, 2026
73f61d2
merge: pull in the PR #3207 conflict fix
vanceingalls Aug 13, 2026
b475176
merge: pull in the PR #3207 conflict fix
vanceingalls Aug 13, 2026
7d2ad93
merge: pull in the PR #3207 conflict fix
vanceingalls Aug 13, 2026
b480249
merge: pull in the PR #3207 conflict fix
vanceingalls Aug 13, 2026
481835e
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
aee687c
merge: pull in the PR #3208 conflict fix
vanceingalls Aug 13, 2026
400dde8
merge: pull in the PR #3208 conflict fix
vanceingalls Aug 13, 2026
a7131f5
merge: pull in the PR #3208 conflict fix
vanceingalls Aug 13, 2026
13f9431
merge: pull in the PR #3208 conflict fix
vanceingalls Aug 13, 2026
de240ae
merge: pull in the PR #3208 conflict fix
vanceingalls Aug 13, 2026
9868222
merge: pull in the PR #3208 conflict fix
vanceingalls Aug 13, 2026
7a9e5b4
merge: pull in the PR #3208 conflict fix
vanceingalls Aug 13, 2026
d2248c5
merge: pull in the PR #3208 conflict fix
vanceingalls Aug 13, 2026
2c79039
merge: pull in the PR #3208 conflict fix
vanceingalls Aug 13, 2026
4c1b816
merge: pull in the PR #3208 conflict fix
vanceingalls Aug 13, 2026
601ed1b
merge: pull in the PR #3208 conflict fix
vanceingalls Aug 13, 2026
1bf3726
merge: pull in the PR #3208 conflict fix
vanceingalls Aug 13, 2026
d1d1911
merge: pull in the PR #3208 conflict fix
vanceingalls Aug 13, 2026
86f4410
merge: pull in the PR #3208 conflict fix
vanceingalls Aug 13, 2026
a45422c
merge: pull in the PR #3208 conflict fix
vanceingalls Aug 13, 2026
f72e06e
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
9adb9b5
chore: merge in the wa-18b-reschedule fallow/typecheck fix
vanceingalls Aug 13, 2026
4cceef6
chore: merge in the wa-18b-reschedule fallow/typecheck fix
vanceingalls Aug 13, 2026
ef4d96e
chore: merge in the wa-18b-reschedule fallow/typecheck fix
vanceingalls Aug 13, 2026
d4f4817
chore: merge in the wa-18b-reschedule fallow/typecheck fix
vanceingalls Aug 13, 2026
8f39c30
chore: merge in the wa-18b-reschedule fallow/typecheck fix
vanceingalls Aug 13, 2026
d71b33d
chore: merge in the wa-18b-reschedule fallow/typecheck fix
vanceingalls Aug 13, 2026
5404df6
chore: merge in the wa-18b-reschedule fallow/typecheck fix
vanceingalls Aug 13, 2026
2b831a1
chore: merge in the wa-18b-reschedule fallow/typecheck fix
vanceingalls Aug 13, 2026
936bbc3
chore: merge in the wa-18b-reschedule fallow/typecheck fix
vanceingalls Aug 13, 2026
620465b
chore: merge in the wa-18b-reschedule fallow/typecheck fix
vanceingalls Aug 13, 2026
89c4c4f
chore: merge in the wa-18b-reschedule fallow/typecheck fix
vanceingalls Aug 13, 2026
e556b23
chore: merge in the wa-18b-reschedule fallow/typecheck fix
vanceingalls Aug 13, 2026
2190546
chore: merge in the wa-18b-reschedule fallow/typecheck fix
vanceingalls Aug 13, 2026
7a265e5
chore: merge in the wa-18b-reschedule fallow/typecheck fix
vanceingalls Aug 13, 2026
0569d6a
chore: merge in the wa-18b-reschedule fallow/typecheck fix
vanceingalls Aug 13, 2026
6270780
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
518e75d
chore: merge in the via-conic NaN fix
vanceingalls Aug 13, 2026
2b8dd2b
chore: merge in the via-conic NaN fix
vanceingalls Aug 13, 2026
2262fef
chore: merge in the via-conic NaN fix
vanceingalls Aug 13, 2026
ba846fe
chore: merge in the via-conic NaN fix
vanceingalls Aug 13, 2026
0dbeac6
chore: merge in the via-conic NaN fix
vanceingalls Aug 13, 2026
25e4366
chore: merge in the via-conic NaN fix
vanceingalls Aug 13, 2026
42ce44c
chore: merge in the via-conic NaN fix
vanceingalls Aug 13, 2026
b9d6539
chore: merge in the via-conic NaN fix
vanceingalls Aug 13, 2026
4e2f644
chore: merge in the via-conic NaN fix
vanceingalls Aug 13, 2026
ce43f41
chore: merge in the via-conic NaN fix
vanceingalls Aug 13, 2026
af9247a
chore: merge in the via-conic NaN fix
vanceingalls Aug 13, 2026
0f8d7fa
chore: merge in the via-conic NaN fix
vanceingalls Aug 13, 2026
80e4ec0
chore: merge in the via-conic NaN fix
vanceingalls Aug 13, 2026
4cd73ab
chore: merge in the via-conic NaN fix
vanceingalls Aug 13, 2026
a90f8f3
chore: merge in the via-conic NaN fix
vanceingalls Aug 13, 2026
db3c1d2
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
48fe084
chore: merge in the waveform cache key fix
vanceingalls Aug 13, 2026
d0a7172
chore: merge in the waveform cache key fix
vanceingalls Aug 13, 2026
3b000a4
chore: merge in the waveform cache key fix
vanceingalls Aug 13, 2026
713cb7d
chore: merge in the waveform cache key fix
vanceingalls Aug 13, 2026
f6fac3e
chore: merge in the waveform cache key fix
vanceingalls Aug 13, 2026
862eafd
chore: merge in the waveform cache key fix
vanceingalls Aug 13, 2026
7db346a
chore: merge in the waveform cache key fix
vanceingalls Aug 13, 2026
689f33b
chore: merge in the waveform cache key fix
vanceingalls Aug 13, 2026
2bd6c57
chore: merge in the waveform cache key fix
vanceingalls Aug 13, 2026
0aceb3c
chore: merge in the waveform cache key fix
vanceingalls Aug 13, 2026
4938f99
chore: merge in the waveform cache key fix
vanceingalls Aug 13, 2026
c3df5c4
chore: merge in the waveform cache key fix
vanceingalls Aug 13, 2026
bfb689b
chore: merge in the waveform cache key fix
vanceingalls Aug 13, 2026
831526b
chore: merge in the waveform cache key fix
vanceingalls Aug 13, 2026
1be0fe2
chore: merge in the waveform cache key fix
vanceingalls Aug 13, 2026
7a4aea7
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
e2c3c62
chore: merge in the PropertyPanel timeout fix
vanceingalls Aug 13, 2026
2a53d7d
chore: merge in the PropertyPanel timeout fix
vanceingalls Aug 13, 2026
45a5d87
chore: merge in the PropertyPanel timeout fix
vanceingalls Aug 13, 2026
115a110
chore: merge in the PropertyPanel timeout fix
vanceingalls Aug 13, 2026
d10aa17
chore: merge in the PropertyPanel timeout fix
vanceingalls Aug 13, 2026
9257408
chore: merge in the PropertyPanel timeout fix
vanceingalls Aug 13, 2026
7ca5158
chore: merge in the PropertyPanel timeout fix
vanceingalls Aug 13, 2026
62cebb1
chore: merge in the PropertyPanel timeout fix
vanceingalls Aug 13, 2026
6029ec2
chore: merge in the PropertyPanel timeout fix
vanceingalls Aug 13, 2026
d851d2b
chore: merge in the PropertyPanel timeout fix
vanceingalls Aug 13, 2026
74b9fc2
chore: merge in the PropertyPanel timeout fix
vanceingalls Aug 13, 2026
bc17a95
chore: merge in the PropertyPanel timeout fix
vanceingalls Aug 13, 2026
a227277
chore: merge in the PropertyPanel timeout fix
vanceingalls Aug 13, 2026
f76f698
chore: merge in the PropertyPanel timeout fix
vanceingalls Aug 13, 2026
009d58e
chore: merge in the PropertyPanel timeout fix
vanceingalls Aug 13, 2026
b567640
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
e36c1ba
chore: merge in the duplicate-effect carve fix
vanceingalls Aug 13, 2026
8332da4
chore: merge in the duplicate-effect carve fix
vanceingalls Aug 13, 2026
2187440
chore: merge in the duplicate-effect carve fix
vanceingalls Aug 13, 2026
f980c45
chore: merge in the duplicate-effect carve fix
vanceingalls Aug 13, 2026
57bfee7
chore: merge in the duplicate-effect carve fix
vanceingalls Aug 13, 2026
233fd7f
chore: merge in the duplicate-effect carve fix
vanceingalls Aug 13, 2026
0e4b53f
chore: merge in the duplicate-effect carve fix
vanceingalls Aug 13, 2026
90552e4
chore: merge in the duplicate-effect carve fix
vanceingalls Aug 13, 2026
59cd0b9
chore: merge in the duplicate-effect carve fix
vanceingalls Aug 13, 2026
da1a6a1
chore: merge in the duplicate-effect carve fix
vanceingalls Aug 13, 2026
f85c90d
chore: merge in the duplicate-effect carve fix
vanceingalls Aug 13, 2026
6444c00
chore: merge in the duplicate-effect carve fix
vanceingalls Aug 13, 2026
e3d2e3b
chore: merge in the duplicate-effect carve fix
vanceingalls Aug 13, 2026
73079a4
chore: merge in the duplicate-effect carve fix
vanceingalls Aug 13, 2026
75ab57d
chore: merge in the duplicate-effect carve fix
vanceingalls Aug 13, 2026
f302746
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
de18eaa
chore: merge in the main restack from #3210
vanceingalls Aug 13, 2026
906cfad
chore: merge in the main restack from #3210
vanceingalls Aug 13, 2026
3aad9b4
chore: merge in the main restack from #3210
vanceingalls Aug 13, 2026
0086d18
chore: merge in the main restack from #3210
vanceingalls Aug 13, 2026
eabbfa6
chore: merge in the main restack from #3210
vanceingalls Aug 13, 2026
b405371
chore: merge in the main restack from #3210
vanceingalls Aug 13, 2026
fd53567
chore: merge in the main restack from #3210
vanceingalls Aug 13, 2026
793dcd2
chore: merge in the main restack from #3210
vanceingalls Aug 13, 2026
80b7092
chore: merge in the main restack from #3210
vanceingalls Aug 13, 2026
0b1e3ce
chore: merge in the main restack from #3210
vanceingalls Aug 13, 2026
2f24258
chore: merge in the main restack from #3210
vanceingalls Aug 13, 2026
fb466c0
chore: merge in the main restack from #3210
vanceingalls Aug 13, 2026
af5d7b7
chore: merge in the main restack from #3210
vanceingalls Aug 13, 2026
5cc280c
chore: merge in the main restack from #3210
vanceingalls Aug 13, 2026
1368ef6
chore: merge in the main restack from #3210
vanceingalls Aug 13, 2026
e259099
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
c8fcb64
chore: merge in the carve UI type-shape fix
vanceingalls Aug 13, 2026
1feca8e
chore: merge in the carve UI type-shape fix
vanceingalls Aug 13, 2026
9652805
chore: merge in the carve UI type-shape fix
vanceingalls Aug 13, 2026
3b8dbd1
chore: merge in the carve UI type-shape fix
vanceingalls Aug 13, 2026
14ab3f7
chore: merge in the carve UI type-shape fix
vanceingalls Aug 13, 2026
a06bcf2
chore: merge in the carve UI type-shape fix
vanceingalls Aug 13, 2026
0bc8a8c
chore: merge in the carve UI type-shape fix
vanceingalls Aug 13, 2026
abcb0e3
chore: merge in the carve UI type-shape fix
vanceingalls Aug 13, 2026
28a2637
chore: merge in the carve UI type-shape fix
vanceingalls Aug 13, 2026
983118f
chore: merge in the carve UI type-shape fix
vanceingalls Aug 13, 2026
72de452
chore: merge in the carve UI type-shape fix
vanceingalls Aug 13, 2026
8f230c3
chore: merge in the carve UI type-shape fix
vanceingalls Aug 13, 2026
08cb154
chore: merge in the carve UI type-shape fix
vanceingalls Aug 13, 2026
b41207b
chore: merge in the carve UI type-shape fix
vanceingalls Aug 13, 2026
3b5abc9
chore: merge in the carve UI type-shape fix
vanceingalls Aug 13, 2026
53032e6
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
65a484c
chore: pull in main after #3211's squash merge
vanceingalls Aug 13, 2026
1e7e5b6
chore: pull in main after #3211's squash merge
vanceingalls Aug 13, 2026
f9af8ae
chore: pull in main after #3211's squash merge
vanceingalls Aug 13, 2026
4151db0
chore: pull in main after #3211's squash merge
vanceingalls Aug 13, 2026
fe32bb2
chore: pull in main after #3211's squash merge
vanceingalls Aug 13, 2026
831bc12
chore: pull in main after #3211's squash merge
vanceingalls Aug 13, 2026
78cdd9e
chore: pull in main after #3211's squash merge
vanceingalls Aug 13, 2026
cc5211b
chore: pull in main after #3211's squash merge
vanceingalls Aug 13, 2026
5da8fba
chore: pull in main after #3211's squash merge
vanceingalls Aug 13, 2026
1c34d0a
chore: pull in main after #3211's squash merge
vanceingalls Aug 13, 2026
ee8fbd6
chore: pull in main after #3211's squash merge
vanceingalls Aug 13, 2026
77c0073
chore: pull in main after #3211's squash merge
vanceingalls Aug 13, 2026
31ba35a
chore: pull in main after #3211's squash merge
vanceingalls Aug 13, 2026
9c4131d
chore: pull in main after #3211's squash merge
vanceingalls Aug 13, 2026
c55b31b
chore: pull in main after #3211's squash merge
vanceingalls Aug 13, 2026
a800433
chore: pull in main after #3211's squash merge
vanceingalls Aug 13, 2026
c84e94d
chore: pull in main after #3190's squash merge
vanceingalls Aug 13, 2026
1aa96f3
chore: fix markdown formatting
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
63 changes: 63 additions & 0 deletions packages/core/src/audio/audioFxGraph.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -698,3 +698,66 @@ describe("a preset's run is wrapped in a wet/dry blend", () => {
expect(live).toEqual([]);
});
});

describe("a preset's wrap stays with the nodes it belongs to", () => {
const peak = (over: Partial<HfAudioFxNode>): HfAudioFxNode =>
({
type: "peaking",
enabled: true,
params: defaultAudioFxParams("peaking"),
...over,
}) as HfAudioFxNode;

it("rebuilds when a reorder moves a node across a preset boundary", () => {
// The type sequence is identical either way, so without preset identity in
// the shape the chain updated in place and left the wet/dry wrap bracketing
// the hand-added effect instead of the preset's — preview blending out the
// author's own node while the render, which rebuilds, blended out the
// preset's.
const before: HfAudioFxChain = {
version: 1,
nodes: [peak({ id: "p1", fromPreset: "boom-tame" }), peak({ id: "own" })],
};
const after: HfAudioFxChain = {
version: 1,
nodes: [peak({ id: "own" }), peak({ id: "p1", fromPreset: "boom-tame" })],
};
expect(buildFxChain(asCtx(ctx()), before).update(after)).toBe(false);
});

it("gives every run of one preset its own blend", () => {
// A preset pulled apart by a reorder occupies two runs. Keyed by id and
// assigned, the second wrap overwrote the first, so a whole-preset lane
// reached one fragment and the switch silently left the rest applied.
const split: HfAudioFxChain = {
version: 1,
nodes: [
peak({ id: "t1", fromPreset: "telephone" }),
{ type: "reverb", id: "own", enabled: true, params: defaultAudioFxParams("reverb") },
peak({ id: "t2", fromPreset: "telephone" }),
],
};
const built = buildFxChain(asCtx(ctx()), split);
// Two wraps, so two wet/dry pairs — four params under the one id.
expect(built.presets.telephone).toHaveLength(4);
});

it("pushes an amount into each run of a split preset, not one of them twice", () => {
const at = (amount: number): HfAudioFxChain => ({
version: 1,
nodes: [
peak({ id: "t1", fromPreset: "telephone", presetAmount: amount }),
{ type: "reverb", id: "own", enabled: true, params: defaultAudioFxParams("reverb") },
peak({ id: "t2", fromPreset: "telephone", presetAmount: amount }),
],
});
const built = buildFxChain(asCtx(ctx()), at(1));
expect(built.update(at(0))).toBe(true);
// Every wet leg off and every dry leg fully open: switching the preset off
// has to silence all of it, not the last fragment only.
const wets = (built.presets.telephone ?? []).filter((_, i) => i % 2 === 0);
const drys = (built.presets.telephone ?? []).filter((_, i) => i % 2 === 1);
for (const t of wets) expect(t.param.value).toBe(0);
for (const t of drys) expect(t.param.value).toBe(1);
});
});
27 changes: 23 additions & 4 deletions packages/core/src/audio/audioFxGraph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -599,7 +599,16 @@ function shapeOf(chain: HfAudioFxChain): string {
// same reason: pushed into the running graph it would be a no-op, and
// preview would keep sweeping on a triangle while the render used a sine.
const wave = node.type === "phaser" ? `~${p.type}` : "";
return `${node.type}${poles}${fixedFreq}${wave}`;
// Which preset run this node belongs to, for the same reason again: the
// wet/dry wrap is WIRED around a run at construction, so moving a node
// across a preset boundary changes the graph's shape even when the type
// sequence is identical. Without this, reordering a hand-added peaking
// filter past a preset's peaking filter kept the in-place update path and
// left the wrap bracketing the wrong effect — preview blending out the
// author's own node while the render, which rebuilds, blended out the
// preset's.
const run = node.fromPreset ? `%${node.fromPreset}` : "";
return `${node.type}${poles}${fixedFreq}${wave}${run}`;
})
.join("|");
}
Expand Down Expand Up @@ -674,8 +683,14 @@ export function buildFxChain(

const shape = shapeOf(chain);

// Accumulated, not assigned. A preset pulled apart by a reorder occupies more
// than one run, and each run gets its own wrap — keying by id and assigning
// dropped every wrap but the last, so a whole-preset lane drove one fragment
// and left the rest at full strength while the switch read "Off".
const presetTargets: Record<string, FxParamTarget[]> = {};
for (const p of presets) presetTargets[p.id] = mixTargets(p.wet.gain, p.dry.gain);
for (const p of presets) {
(presetTargets[p.id] ??= []).push(...mixTargets(p.wet.gain, p.dry.gain));
}
return {
input,
output,
Expand All @@ -699,10 +714,14 @@ export function buildFxChain(
// The blend is a value like any other: switching a preset off writes
// `presetAmount`, and pushing it into the running graph is what keeps that
// from being a rebuild — and from restarting the audio underneath it.
// Walked in step with the build, not looked up by id: `find` returned the
// first wrap for every run sharing a preset id, so a split preset wrote
// one wrap twice and never touched the other.
let wrapIndex = 0;
for (const run of presetRuns(enabledAudioFxNodes(next))) {
if (!run.preset) continue;
const wrap = presets.find((p) => p.id === run.preset);
if (!wrap) continue;
const wrap = presets[wrapIndex++];
if (!wrap || wrap.id !== run.preset) continue;
wrap.wet.gain.value = run.amount;
wrap.dry.gain.value = 1 - run.amount;
}
Expand Down
71 changes: 71 additions & 0 deletions packages/core/src/audioFxCopy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,3 +103,74 @@ describe("audioBandAt", () => {
expect(audioBandAt(Number.NaN)).toBeUndefined();
});
});

/**
* Copy that assumes the track is a voice.
*
* The rack sits on whatever the author selected — a music bed, a sound effect,
* a room tone. Every effect, every named job and every profile is offered on
* all of them, so a control that reads "Thins the voice out" on a synth pad is
* describing something the author cannot hear and does not have. It is the same
* rule this file's header already states — describe a control by what changes in
* THE SOUND — applied to the words rather than to the mechanism.
*
* The voice presets are the deliberate exception: they are voice by definition,
* and the shelf hides them on a track that classifies as music or as an effect,
* so "My voice sounds amateur" is only ever read next to a voice.
*
* This is a lint on the words, not a judgement about mixing. It exists because
* the offending strings were written one at a time over months and read fine in
* isolation — nobody notices the assumption until they apply Cut Rumble to a
* bass line and the panel tells them it will thin their voice out.
*/
describe("no copy assumes the track is a voice", () => {
/** Words that only mean something if the material is speech. */
const SPEECH =
/\b(voice|vocal|voices|speech|spoken|word|words|sentence|sentences|syllable|syllables|narration|narrator|talking|chest)\b/i;

/** Presets whose whole purpose is a voice, so their copy may say so. */
const VOICE_PRESETS = new Set(
HF_AUDIO_FX_PRESETS.filter((p) => p.family === "voice").map((p) => p.id),
);

const offenders = (entries: [string, string][]): string[] =>
entries.filter(([, text]) => SPEECH.test(text)).map(([where, text]) => `${where}: "${text}"`);

it("not in an effect's name, blurb, reach-for line or any knob", () => {
const entries: [string, string][] = [];
for (const [id, copy] of Object.entries(EFFECT_COPY)) {
entries.push([`${id}.title`, copy.title], [`${id}.does`, copy.does]);
entries.push([`${id}.reachFor`, copy.reachFor]);
entries.push([`${id}.primaryEnds.low`, copy.primaryEnds.low]);
entries.push([`${id}.primaryEnds.high`, copy.primaryEnds.high]);
for (const [key, param] of Object.entries(copy.params)) {
entries.push([`${id}.${key}.label`, param.label]);
if (param.hint) entries.push([`${id}.${key}.hint`, param.hint]);
if (param.ends) {
entries.push([`${id}.${key}.ends.low`, param.ends.low]);
entries.push([`${id}.${key}.ends.high`, param.ends.high]);
}
}
}
expect(offenders(entries)).toEqual([]);
});

it("not in the band vocabulary, which every spectral module shares", () => {
// These names get taught once and then reused everywhere, so a voice-only
// word here spreads to every filter in the rack.
expect(offenders(BANDS.map((b) => [b.name, b.says]))).toEqual([]);
});

it("not in the complaint a non-voice preset answers", () => {
const entries = Object.entries(PRESET_PROBLEM).filter(([id]) => !VOICE_PRESETS.has(id));
expect(offenders(entries as [string, string][])).toEqual([]);
});

it("still lets the voice presets say what they are for", () => {
// The exception has to be real, or the rule above is untested — a catalogue
// where nothing said "voice" would pass every assertion here vacuously.
const voiced = Object.entries(PRESET_PROBLEM).filter(([id]) => VOICE_PRESETS.has(id));
expect(voiced.length).toBeGreaterThan(0);
expect(voiced.some(([, text]) => SPEECH.test(text))).toBe(true);
});
});
27 changes: 14 additions & 13 deletions packages/core/src/audioFxCopy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,13 +66,13 @@ export const EFFECT_COPY: Record<string, EffectCopy> = {
does: "Cuts the very bottom — traffic, footsteps, air conditioning, hands on the mic.",
reachFor: "There's a low hum or thump under everything.",
primary: "frequency",
primaryEnds: { low: "Only the deepest", high: "Thins the voice out" },
primaryEnds: { low: "Only the deepest", high: "Thins it out" },
band: [20, 300],
params: {
frequency: {
label: "Cut below",
hint: "Everything under this is removed.",
ends: { low: "Only the deepest", high: "Thins the voice out" },
ends: { low: "Only the deepest", high: "Thins it out" },
},
q: { label: "Sharpness", hint: "How abruptly the cut starts." },
poles: { label: "Steepness", hint: "How fast it falls away below the point." },
Expand Down Expand Up @@ -134,7 +134,7 @@ export const EFFECT_COPY: Record<string, EffectCopy> = {
compressor: {
title: "Even Out Loudness",
does: "Brings the quiet parts up and holds the loud parts down, so nothing jumps out at the listener.",
reachFor: "Some words are much louder than others.",
reachFor: "Some parts are much louder than others.",
primary: "strength",
primaryEnds: { low: "Barely touched", high: "Very even, quite squashed" },
params: {
Expand Down Expand Up @@ -168,10 +168,10 @@ export const EFFECT_COPY: Record<string, EffectCopy> = {
},
gate: {
title: "Silence the Gaps",
does: "Mutes the pauses between words. Room tone under speech stays — this closes the silences, it does not remove noise.",
reachFor: "You can hear the room breathing between sentences.",
does: "Mutes the pauses. Whatever sits underneath stays — this closes the gaps, it does not remove noise.",
reachFor: "You can hear the room in the gaps.",
primary: "strength",
primaryEnds: { low: "Only true silence", high: "Cuts quiet words too" },
primaryEnds: { low: "Only true silence", high: "Cuts quiet parts too" },
params: {
threshold: { label: "Quieter than this is a gap" },
range: {
Expand All @@ -182,7 +182,7 @@ export const EFFECT_COPY: Record<string, EffectCopy> = {
attack: { label: "How fast it opens" },
release: {
label: "How fast it closes",
ends: { low: "Clips word endings", high: "Leaves tails intact" },
ends: { low: "Clips tails short", high: "Leaves tails intact" },
},
knee: { label: "How gradual" },
},
Expand Down Expand Up @@ -286,11 +286,11 @@ export const EFFECT_COPY: Record<string, EffectCopy> = {
*/
export const BANDS: { from: number; to: number; name: string; says: string }[] = [
{ from: 20, to: 80, name: "Rumble", says: "traffic, footsteps, handling" },
{ from: 80, to: 250, name: "Weight", says: "chest, body, warmth" },
{ from: 80, to: 250, name: "Weight", says: "body, warmth, low end" },
{ from: 250, to: 600, name: "Mud", says: "boxy, muffled, cardboard" },
{ from: 600, to: 2000, name: "Middle", says: "the body of a voice" },
{ from: 2000, to: 5000, name: "Presence", says: "consonants, intelligibility" },
{ from: 5000, to: 10000, name: "Edge", says: "sibilance, harshness" },
{ from: 600, to: 2000, name: "Middle", says: "the body of the sound" },
{ from: 2000, to: 5000, name: "Presence", says: "definition, consonants" },
{ from: 5000, to: 10000, name: "Edge", says: "harshness, sibilance" },
{ from: 10000, to: 20000, name: "Air", says: "sparkle, openness" },
];

Expand Down Expand Up @@ -318,15 +318,16 @@ export const PRESET_PROBLEM: Record<string, string> = {
"voice-broadcast": "I want it to sound like radio",
"voice-warm": "I want it intimate and close",
"rumble-cut": "There's a hum or thump underneath",
"room-gate": "I can hear the room between sentences",
"boom-tame": "My voice sounds boomy",
"room-gate": "I can hear the room in the gaps",
"boom-tame": "It sounds boomy",
"harsh-tame": "It's harsh and tiring to listen to",
telephone: "Make it sound like a phone call",
"radio-am": "Make it sound like an old radio",
megaphone: "Make it sound shouted through a horn",
"lofi-tape": "Make it sound like an old tape",
"pa-system": "Make it sound like a station announcement",
intercom: "Make it sound like a door intercom",
"doofus-worble": "Make it wobble like it is seasick",
"room-tight": "It sounds dry and stuck to the speaker",
"room-natural": "It should sound like a real place",
hall: "It should sound far away and big",
Expand Down
17 changes: 17 additions & 0 deletions packages/core/src/audioFxJobs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,20 @@ describe("named jobs", () => {
for (const id of ids) expect(getAudioFxJob(id)?.id).toBe(id);
});
});

/**
* A job's `does` is the complaint that leads to it, shown in the add menu on
* whatever track is selected — so it is under the same rule as the effect copy
* in `audioFxCopy.test.ts`: it may not assume the material is speech. Reduce Mud
* is as right on a boxy guitar as on a boxy voice, and the menu should say so.
*/
describe("no job assumes the track is a voice", () => {
const SPEECH =
/\b(voice|vocal|speech|spoken|word|words|sentence|syllable|narration|talking|chest)\b/i;
it("names the symptom without naming the source", () => {
const bad = HF_AUDIO_FX_JOBS.filter((j) => SPEECH.test(`${j.label} ${j.does}`)).map(
(j) => `${j.id}: "${j.does}"`,
);
expect(bad).toEqual([]);
});
});
4 changes: 2 additions & 2 deletions packages/core/src/audioFxJobs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export const HF_AUDIO_FX_JOBS: readonly HfAudioFxJob[] = [
{
id: "tame-boominess",
label: "Tame Boominess",
does: "Too much chest — it booms.",
does: "Too much low-end body — it booms.",
type: "peaking",
params: { frequency: 200, gain: -4, q: 1.4 },
},
Expand All @@ -72,7 +72,7 @@ export const HF_AUDIO_FX_JOBS: readonly HfAudioFxJob[] = [
{
id: "add-clarity",
label: "Add Clarity",
does: "Words are hard to make out.",
does: "It is hard to make out — it sits back.",
type: "peaking",
params: { frequency: 3000, gain: 2.5, q: 1 },
},
Expand Down
Loading
Loading