Shape a sound with a Parametric EQ and its own curve editor (#367)
A sound FX or an instrument had a 5-Band-EQ with fixed bands and a Filter with one band. The Parametric EQ holds up to ten bands of any type, and a double-click on the node opens an editor that draws the curve they make together. - The bands are a list on the node (`bands`): type, frequency, gain, Q and slope. A hand-edited project is held to the editor's ranges and to ten bands. - Each band compiles to the existing `pass` stage, so the audio core is unchanged. A peak or a shelf at 0 dB is left out. - The curve is worked out in `parametricEq.ts` with the biquad formulas of the Web Audio spec, so the editor draws what is heard. - The editor is a window that floats over the editor without a backdrop, so the tab's Play stays usable. A double-click adds a peak, a drag moves a band, the wheel turns its Q, a right-click removes it. Fields below the curve set the picked band. It opens in the middle of the screen and moves by its title bar. - A drag or a scrub writes the node as a preview and makes one undo step on release. The panels hear of the preview once a frame, and the window lays itself out only when one of its texts changes. - The area under the curve is filled as one shape for each side of 0 dB. As a single shape it crossed itself and came apart. - The curve works out each band's coefficients once and adds the sum up from the bands. A drag took 295 ms of script time in a profile before and 64 ms after. Decided on my own: - Bands are added by a double-click, so a single click can let go of the picked band without adding one. - The cuts are labelled "Low cut" and "High cut" as in an EQ, not "High-pass" and "Low-pass" as on the Filter. - The bands have no input, so no parameter or Envelope moves them. Tests: - Unit parametric-eq (new) and soundfx: 69 green. Counter-probe with no stages emitted: the compile test red. Counter-probe with one fill shape: the fill test red. - e2e 34 (melektaus-atx): ALL PASSED, 96 checks (3 new). - Counter-probe e2e: no stages emitted, the playing check red. - Gate (melektaus-atx): CHECK EXIT 0, 274 files, 4230 tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
204c4a41e8
commit
1f13794b0c
9 files changed
+1105
-4
No files matched your search
@@ -1,5 +1,6 @@
|
||||
import { NameTransitionCommand } from '../../core/commands/graphCommands';
|
||||
import { currentNodePalette } from '../graph/NodePalette';
|
||||
import { currentEqEditor } from '../soundfx/EqEditor';
|
||||
import { rectOf } from '../textProbe';
|
||||
import { centreOf, snapButton, type HookContext } from './hookHelpers';
|
||||
|
||||
@@ -114,6 +115,35 @@ export function graphHooks({ state, editor }: HookContext) {
|
||||
/** The header's parameter values, and setting one as its field would. */
|
||||
params: () => editor.soundFxEditor.previewValues,
|
||||
setParam: (key: string, value: number) => editor.soundFxEditor.setPreviewValue(key, value),
|
||||
nodeAt: (nodeId: string) => editor.soundFxEditor.canvas.nodeGlobal(nodeId),
|
||||
},
|
||||
/** The Parametric EQ's window (#367): for which node it is open, its bands and where to aim. */
|
||||
eq: {
|
||||
shown: () => currentEqEditor()?.shownFor ?? null,
|
||||
bands: () => currentEqEditor()?.bands() ?? null,
|
||||
picked: () => currentEqEditor()?.picked ?? null,
|
||||
handleAt: (index: number) => currentEqEditor()?.curve.handleGlobal(index) ?? null,
|
||||
pointAt: (hz: number, db: number) => currentEqEditor()?.curve.pointGlobal(hz, db) ?? null,
|
||||
fields: () => {
|
||||
const eq = currentEqEditor();
|
||||
return (
|
||||
eq && {
|
||||
type: eq.typeButton.text,
|
||||
slope: eq.slopeButton.enabled ? eq.slopeButton.text : null,
|
||||
gain: eq.gainField.enabled ? eq.gainField.value : null,
|
||||
q: eq.qField.enabled ? eq.qField.value : null,
|
||||
frequency: eq.frequencyField.value,
|
||||
}
|
||||
);
|
||||
},
|
||||
typeAt: () => {
|
||||
const eq = currentEqEditor();
|
||||
return eq ? centreOf(eq.typeButton) : null;
|
||||
},
|
||||
closeAt: () => {
|
||||
const eq = currentEqEditor();
|
||||
return eq ? centreOf(eq.closeButton) : null;
|
||||
},
|
||||
},
|
||||
instrument: {
|
||||
current: () => editor.instrumentEditor.currentAssetId,
|
||||
|
||||
Reference in new issue
Block a user