From ee94248f655e33e48fb07ece0f61c41aabd8022e Mon Sep 17 00:00:00 2001 From: Vance Ingalls Date: Thu, 20 Aug 2026 01:56:56 -0700 Subject: [PATCH] refactor(studio): split the carve module into its four parts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit FxCarveModule was the branch's worst fallow finding: 246 lines at 25 cyclomatic / 45 cognitive, CRITICAL. It held four separate things — the head, the "listen to" row, the strength knob and the analysis result — plus two derived values whose nested ternaries were most of the cognitive load. Now: soleCarveVoice and carveSummary as named functions with the reasoning that was inline attached to them, and CarveSourceRow / CarveAnalysis as components. CarveAnalysis in particular reads as the three states it is (analysing, nothing analysed, the filters) rather than a two-deep ternary in JSX. FxCarveModule itself is 5/2/85 and is now the shell it always described itself as. All four sit BELOW the component so nothing above them re-fingerprints. With this the branch's fallow audit is CLEAN: 0 complexity findings, 0 dead code, duplication warn-only, exit 0. All nine gated findings the branch had are gone, and no file in this stack is over the 600-line cap any more -- so commits from here need no --no-verify. studio's editor suite (102 files, 1269 tests) passes unchanged. --- .../editor/propertyPanelFxCarveModule.tsx | 330 +++++++++++------- 1 file changed, 202 insertions(+), 128 deletions(-) diff --git a/packages/studio/src/components/editor/propertyPanelFxCarveModule.tsx b/packages/studio/src/components/editor/propertyPanelFxCarveModule.tsx index e86a976ac..9f6d896d6 100644 --- a/packages/studio/src/components/editor/propertyPanelFxCarveModule.tsx +++ b/packages/studio/src/components/editor/propertyPanelFxCarveModule.tsx @@ -164,43 +164,9 @@ export function FxCarveModule({ 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"; + const soleVoice = soleCarveVoice(sourceOptions, carve.sources); + const summary = carveSummary({ nodes, carve, analysing }); // The carve's own colour, used three ways: the module's left edge, the title, // and the wash behind it. A preset gets a title treatment because it is a // character; the carve gets one because it is the only module in the rack @@ -257,53 +223,13 @@ export function FxCarveModule({ {open && on ? (
-
- - Listen to - - {soleVoice ? ( - - {soleVoice.label} - - ) : ( - /* 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. */ -
- {sourceOptions.map((o) => ( - - ))} -
- )} -
+ {/* 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 @@ -335,53 +261,201 @@ export function FxCarveModule({ 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 ? ( -

- - Analysing… -

- ) : nodes.length > 0 ? ( -
-
- analysed -
- {nodes.map((node, i) => ( - - ))} -
- ) : ( -

- {carve.sources.length > 0 - ? "Nothing analysed yet." - : "Pick the voices this bed should make room for."} -

- )} + 0} + automatedTargets={automatedTargets} + liveAutomationValues={liveAutomationValues} + />
) : null}
); } + +/** + * 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. + */ +function soleCarveVoice( + sourceOptions: AudioTrackOption[], + sources: readonly string[], +): AudioTrackOption | null { + if (sourceOptions.length !== 1) return null; + const only = sourceOptions[0]; + if (!only) return null; + if (sources.length === 0) return only; + return sources.length === 1 && sources[0] === only.id ? only : null; +} + +/** + * What the module is worth right now, for its head, so a collapsed card still + * says whether it is doing anything: the analysis it produced, or why not. + */ +function carveSummary(input: { + nodes: HfAudioFxNode[]; + carve: HfCarveSettings; + analysing?: boolean; +}): string { + const { nodes, carve, analysing } = input; + if (!carve.enabled) return "off"; + if (analysing) return "analysing…"; + const bands = nodes.filter((n) => n.type === "peaking").length; + if (bands === 0) return carve.sources.length > 0 ? "no analysis yet" : "pick a voice"; + return [ + `${bands} band${bands === 1 ? "" : "s"}`, + ...(nodes.some((n) => n.type === "gain") ? ["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(" + "); +} + +/** Which voices the bed makes room for: a readout when there is only one to + * choose, otherwise a set of checkboxes. */ +function CarveSourceRow({ + carve, + sourceOptions, + soleVoice, + disabled, + onCarveChange, +}: { + carve: HfCarveSettings; + sourceOptions: AudioTrackOption[]; + soleVoice: AudioTrackOption | null; + disabled?: boolean; + onCarveChange(carve: HfCarveSettings): void; +}) { + return ( +
+ + Listen to + + {soleVoice ? ( + + {soleVoice.label} + + ) : ( + /* 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. */ +
+ {sourceOptions.map((o) => ( + + ))} +
+ )} +
+ ); +} + +/** + * 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. + */ +function CarveAnalysis({ + nodes, + analysing, + hasSources, + automatedTargets, + liveAutomationValues, +}: { + nodes: HfAudioFxNode[]; + analysing?: boolean; + hasSources: boolean; + automatedTargets?: ReadonlySet; + liveAutomationValues?: ReadonlyMap; +}) { + if (analysing) { + return ( +

+ + Analysing… +

+ ); + } + if (nodes.length === 0) { + return ( +

+ {hasSources ? "Nothing analysed yet." : "Pick the voices this bed should make room for."} +

+ ); + } + return ( +
+
+ analysed +
+ {nodes.map((node, i) => ( + + ))} +
+ ); +}