Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
671 commits
Select commit Hold shift + click to select a range
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
533f2b6
merge: pull in the CRAP-score fix
vanceingalls Aug 13, 2026
df86bb1
merge: pull in the CRAP-score fix
vanceingalls Aug 13, 2026
d1a8158
merge: pull in the CRAP-score fix
vanceingalls Aug 13, 2026
dc884fd
merge: pull in the CRAP-score fix
vanceingalls Aug 13, 2026
8405807
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
5d6d89e
merge: pull in the PR #3025 conflict fix
vanceingalls Aug 13, 2026
4ad15f1
merge: pull in the PR #3025 conflict fix
vanceingalls Aug 13, 2026
59ac1bc
merge: pull in the PR #3025 conflict fix
vanceingalls Aug 13, 2026
84ead32
merge: pull in the PR #3025 conflict fix
vanceingalls Aug 13, 2026
39709fa
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
685d27e
merge: pull in the unused-import fix
vanceingalls Aug 13, 2026
3d54fee
merge: pull in the unused-import fix
vanceingalls Aug 13, 2026
c627c27
merge: pull in the unused-import fix
vanceingalls Aug 13, 2026
ab2350b
merge: pull in the unused-import fix
vanceingalls Aug 13, 2026
670b613
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
9d47023
merge: pull in the PR #3055 conflict fix
vanceingalls Aug 13, 2026
fccdc95
merge: pull in the PR #3055 conflict fix
vanceingalls Aug 13, 2026
eb18c23
merge: pull in the PR #3055 conflict fix
vanceingalls Aug 13, 2026
d5fca20
merge: pull in the PR #3055 conflict fix
vanceingalls Aug 13, 2026
d50549d
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
2ba9dd5
merge: pull in the PR #3207 conflict fix
vanceingalls Aug 13, 2026
644b435
merge: pull in the PR #3207 conflict fix
vanceingalls Aug 13, 2026
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
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
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
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
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
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
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
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
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
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
a3a5b11
chore: pull in main after #3180's squash merge
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
363 changes: 363 additions & 0 deletions packages/studio/src/components/editor/propertyPanelFxCarveModule.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,363 @@
/**
* The voiceover carve, as one module in the FX rack.
*
* Carve is deliberately not an entry in the chain. It is a relationship between
* two tracks — it analyses a voice and dips *this* bed where that voice sits —
* so it gets its own card with a source picker, the way a sidechain control
* lives on the track being processed. What it produces is an ordinary chain of
* peaking filters, so it composes with whatever else is on the track.
*/

import {
defaultAudioFxParams,
getAudioFxDef,
type HfAudioFxNode,
type HfAudioFxParam,
} from "@hyperframes/core/audio-fx";
import { DEFAULT_CARVE, type HfCarveSettings } from "@hyperframes/core/audio-carve";
import { fxAutomationTarget } from "@hyperframes/core/audio-automation";
import { FxParamRow } from "./propertyPanelFxControls.js";
// Shared with the timeline's lane labels: a band is named by its frequency in
// both places, and two formatters would drift.
import { formatHz } from "../../player/components/automationLaneData";

export interface AudioTrackOption {
id: string;
label: string;
}

/** What one effect inside the module is called: its own name, plus the band. */
function carveMemberName(node: HfAudioFxNode): string {
const def = getAudioFxDef(node.type);
const freq = node.params?.["frequency"];
const label = def?.label ?? node.type;
return typeof freq === "number" ? `${label} ${formatHz(freq)}` : label;
}

/** A parameter's value as the rack shows it: rounded to the step, with its unit. */
function formatParamValue(param: HfAudioFxParam, raw: number | string | undefined): string {
if (param.kind !== "number" || typeof raw !== "number") return String(raw ?? "");
const places = param.step >= 1 ? 0 : param.step >= 0.1 ? 1 : 2;
return `${Number(raw.toFixed(places))}${param.unit ? ` ${param.unit}` : ""}`;
}

/**
* Width to reserve for a parameter's value, in characters.
*
* Derived from what the parameter CAN read rather than what it currently reads, so
* the column never moves: an automated value updates 30 times a second, and
* `-1 dB` is two characters narrower than `-3.2 dB`, which was enough to shunt
* everything after it sideways on every frame. `ch` is exact here because the
* readouts are monospace and already `tabular-nums`.
*/
function paramValueWidthCh(param: HfAudioFxParam): number {
if (param.kind === "enum") {
return Math.max(1, ...param.options.map((option) => option.value.length));
}
const places = param.step >= 1 ? 0 : param.step >= 0.1 ? 1 : 2;
const digits = Math.max(
String(Math.floor(Math.abs(param.min))).length,
String(Math.floor(Math.abs(param.max))).length,
);
const sign = param.min < 0 ? 1 : 0;
const decimals = places > 0 ? places + 1 : 0;
const unit = param.unit ? param.unit.length + 1 : 0;
return sign + digits + decimals + unit;
}

/** One member of the module: what it is, and what every knob is set to. */
function FxCarveMember({
node,
automatedTargets,
liveAutomationValues,
}: {
node: HfAudioFxNode;
automatedTargets?: ReadonlySet<string>;
liveAutomationValues?: ReadonlyMap<string, number>;
}) {
const def = getAudioFxDef(node.type);
if (!def) return null;
const params = node.params ?? defaultAudioFxParams(node.type);
return (
<div className="hf-fx-carve-member flex flex-col gap-0.5 py-1 pl-3 pr-1.5">
<span className="hf-fx-carve-member-name truncate font-mono text-[9px] text-panel-text-1">
{carveMemberName(node)}
</span>
<div className="flex flex-wrap gap-x-3 gap-y-0.5">
{def.params.map((param) => {
const target = node.id ? fxAutomationTarget(node.id, param.key) : null;
const automated = Boolean(target && automatedTargets?.has(target));
// The envelope's value at the playhead when there is one, which is what
// the audio is using; the stored number is only the seed behind it.
const live = target ? liveAutomationValues?.get(target) : undefined;
const driven = automated && live !== undefined;
const value = formatParamValue(param, driven ? live : params[param.key]);
return (
<span
key={param.key}
className="flex items-baseline gap-1 font-mono text-[9px] text-panel-text-4"
{...(automated ? { "data-automated": "" } : {})}
{...(driven ? { "data-automation-live": "" } : {})}
>
<span className="text-panel-text-4">{param.label}</span>
<span
className="tabular-nums text-panel-text-1"
style={{ minWidth: `${paramValueWidthCh(param)}ch` }}
>
{value}
</span>
{/* The lane is where an automated value comes from, and where it is
edited — saying so is the difference between a stale readout and
a pointer to the thing that owns it. */}
{automated ? <span className="text-[#3CE6AC]">A</span> : null}
</span>
);
})}
</div>
</div>
);
}

/**
* The carve, as one module in the rack.
*
* A carve is one thing the author switched on; the peaking filters and the level
* stage are how it is built. Listed individually they read as hand-built effects —
* removable one at a time, reorderable, each with knobs the next strength change
* silently overwrites. So the rack shows the unit, and the unit owns everything
* that means anything for it: which voice it listens to, how hard it works,
* whether it follows that voice, and what the analysis made of it.
*
* The controls used to sit in their own block under the rack, which read as a
* second, unrelated feature that happened to produce effects somewhere else. One
* card, controls above the analysis they drive, is the same thing said once.
*
* Grouped is not hidden. Opening it lists every effect inside with all of its
* settings, because an author has to be able to see where the analysis landed — as
* readouts rather than controls, since strength is what sets them and a knob here
* would be overwritten by the next adjustment.
*/
export function FxCarveModule({
nodes,
carve,
sourceOptions,
automatedTargets,
liveAutomationValues,
open,
disabled,
analysing,
onToggleOpen,
onCarveChange,
onCarvePreview,
}: {
nodes: HfAudioFxNode[];
carve: HfCarveSettings;
sourceOptions: AudioTrackOption[];
automatedTargets?: ReadonlySet<string>;
liveAutomationValues?: ReadonlyMap<string, number>;
open: boolean;
disabled?: boolean;
analysing?: boolean;
onToggleOpen(): void;
onCarveChange(carve: HfCarveSettings): void;
onCarvePreview(carve: HfCarveSettings): void;
}) {
const bands = nodes.filter((n) => n.type === "peaking").length;
const hasLevel = nodes.some((n) => n.type === "gain");
const on = carve.enabled;
/**
* The only track this bed could be listening to, when there is exactly one.
*
* A picker with one entry is a question with one answer: it asks the author to
* confirm something already decided. So the voice reads out instead.
*
* Not when the stored source is some OTHER track, though — a name that no longer
* classifies as a voice, or a track since renamed. Reading out the one remaining
* candidate there would quietly claim the carve listens to something it does not,
* so the picker comes back and shows the mismatch.
*/
const soleVoice =
sourceOptions.length === 1 &&
(carve.sources.length === 0 ||
(carve.sources.length === 1 && carve.sources[0] === sourceOptions[0]?.id))
? sourceOptions[0]
: null;
// What the module is worth right now, in the head, so a collapsed card still
// says whether it is doing anything: the analysis it produced, or why not.
const summary = !on
? "off"
: analysing
? "analysing…"
: bands > 0
? [
`${bands} band${bands === 1 ? "" : "s"}`,
...(hasLevel ? ["level"] : []),
// Worth saying when it is more than one: the cuts follow whoever is
// speaking, and that is not obvious from a band count.
...(carve.sources.length > 1 ? [`${carve.sources.length} voices`] : []),
].join(" + ")
: carve.sources.length > 0
? "no analysis yet"
: "pick a voice";
return (
<div
className={`hf-fx-node hf-fx-carve-module hf-fx-carve rounded-[4px] border border-panel-border-input${
on ? "" : " opacity-50"
}`}
data-fx-node="carve"
data-carve-enabled={on ? "" : undefined}
>
<div className="hf-fx-node-head flex min-h-7 items-center gap-1 px-1.5">
<button
type="button"
className="hf-fx-node-name min-w-0 flex-1 truncate text-left text-[11px] font-semibold text-panel-text-1 hover:text-panel-text-0"
aria-expanded={open}
onClick={onToggleOpen}
>
Voiceover carve
</button>
<span className="hf-fx-carve-summary shrink-0 font-mono text-[9px] text-panel-text-4">
{summary}
</span>
{/* One switch, not a bypass and a delete. Off drops the effects and the
envelopes it wrote, and is remembered — otherwise the default would
re-apply the carve the next time this clip was selected. */}
<button
type="button"
className="hf-fx-bypass hf-fx-carve-toggle rounded-[3px] border border-panel-border-input px-1.5 py-0.5 font-mono text-[9px] text-panel-text-4 hover:text-panel-text-0 disabled:opacity-40"
aria-pressed={on}
title={on ? "Switch the carve off" : "Switch the carve on"}
disabled={disabled}
onClick={() => onCarveChange({ ...carve, enabled: !on })}
>
{on ? "On" : "Off"}
</button>
</div>
{open && on ? (
<div className="hf-fx-carve-body border-t border-panel-border-input">
<div className="hf-fx-carve-controls space-y-0.5 px-1.5 py-1.5">
<div className="hf-fx-row flex min-h-6 items-center gap-2">
<span className="hf-fx-label w-[86px] flex-shrink-0 truncate text-[10px] text-panel-text-4">
Listen to
</span>
{soleVoice ? (
<span
className="hf-fx-carve-source min-w-0 flex-1 truncate font-mono text-[10px] text-panel-text-1"
data-carve-source={soleVoice.id}
>
{soleVoice.label}
</span>
) : (
/* Every voice, not one of them. A bed usually runs under a whole
sequence — a narrator, an answer, a second presenter — and they are
analysed together, so the cuts follow whoever is speaking. Which
makes this a set of things to include, not a choice between them. */
<div className="hf-fx-carve-sources flex min-w-0 flex-1 flex-wrap gap-x-2.5 gap-y-0.5">
{sourceOptions.map((o) => (
<label
key={o.id}
className="flex min-w-0 items-center gap-1 font-mono text-[9px] text-panel-text-1"
title={`Make room for ${o.label}`}
>
<input
type="checkbox"
className="hf-fx-carve-source h-2.5 w-2.5 accent-panel-accent"
data-carve-source={o.id}
checked={carve.sources.includes(o.id)}
disabled={disabled}
onChange={(e) =>
onCarveChange({
...carve,
sources: e.target.checked
? [...carve.sources, o.id]
: carve.sources.filter((id) => id !== o.id),
})
}
/>
<span className="truncate">{o.label}</span>
</label>
))}
</div>
)}
</div>
{/* One knob for the whole effect. Depth, band count, width, the
intelligibility weighting and both level-match numbers move together
anyway — a gentle carve is shallow in few bands with little ducking, a
hard one is deeper in more with more — so the panel sets the strength
and `carveProfile` derives the six numbers the analysis works in. */}
<FxParamRow
param={{
kind: "number",
key: "strength",
label: "Strength",
unit: "",
min: 0,
max: 1,
step: 0.05,
default: DEFAULT_CARVE.strength,
hint: "How hard to carve: deeper cuts, in more bands, and more room made by dropping the bed's level under the voice. At 0 it carves frequencies only. Moving this re-runs the analysis on what is already here.",
}}
value={carve.strength}
disabled={disabled || carve.sources.length === 0}
onChange={(_k, v) => onCarvePreview({ ...carve, strength: Number(v) })}
onCommit={(_k, v) => onCarveChange({ ...carve, strength: Number(v) })}
/>
</div>
{/* What the analysis made of all that. Divided rather than boxed: these
are parts of one module, and a border around each would read as the
separate effects this replaced. */}
{/* While the analysis runs, the previous filters are gone rather than
stale. Every number in that list is about to be replaced — a strength
change re-derives all of them — so leaving them up reads as the
settings that are in force when they are already history, and the one
honest thing to say is that the work is happening. */}
{analysing ? (
<p className="hf-fx-carve-working flex items-center justify-center gap-1.5 border-t border-panel-border-input py-2 text-[10px] text-panel-text-4">
<svg
className="hf-fx-carve-spinner h-3 w-3 animate-spin motion-reduce:animate-none"
viewBox="0 0 24 24"
fill="none"
aria-hidden="true"
>
<circle
className="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
strokeWidth="4"
/>
<path
className="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"
/>
</svg>
Analysing…
</p>
) : nodes.length > 0 ? (
<div className="hf-fx-carve-members divide-y divide-panel-border-input/60 border-t border-panel-border-input">
<div className="hf-fx-carve-members-label px-1.5 pt-1 font-mono text-[9px] uppercase tracking-wide text-panel-text-4">
analysed
</div>
{nodes.map((node, i) => (
<FxCarveMember
key={node.id ?? `${node.type}-${i}`}
node={node}
automatedTargets={automatedTargets}
liveAutomationValues={liveAutomationValues}
/>
))}
</div>
) : (
<p className="hf-fx-carve-working border-t border-panel-border-input py-1.5 text-center text-[10px] text-panel-text-4">
{carve.sources.length > 0
? "Nothing analysed yet."
: "Pick the voices this bed should make room for."}
</p>
)}
</div>
) : null}
</div>
);
}
Loading
Loading