A Filter sounds like one band of the Parametric EQ, but it keeps what the EQ cannot: linked numbers, the all-pass and +-40 dB. So it stays a node of its own and gets the EQ's editor with itself as the one band. The editors also stop running out of their room: what does not fit is left out instead of being cut. - The curve of the EQ is now BandCurve with a host the EQ and the Filter both fill. CurvePanel holds what their panels share. - FilterPanel shows the Filter as one band. A drag of its handle sets frequency and gain as one undo step. A number fed by a link is greyed out and the drag leaves it alone. - A double-click on the Filter's title opens the same in a tab of its own. Delete there does nothing, the node stays. - The Filter is tapped like the EQ, so its node and its tab draw the spectrum of what passes it. A tap is two gain nodes per voice, the analysers are made only while someone listens. - EqFace and FilterFace are one CurveFace now, given the panel. - EQ and Filter nodes have a corner to resize them, drawn like the floating window's. The size is saved on the node as `size`, each drag is one undo step, the least is 240 x 120. - In the node the help line is gone. The row of numbers comes in only once the curve keeps 120 px of height. A group of the row that does not fit is not drawn, the one on the right first. - A frequency or dB label that would run into the one before it is left out. - The all-pass is drawn flat. The curve's maths takes every shape a Filter has. Found on the way: - The instrument presets set nodes 240 apart. A Filter with its editor is about 700 wide and covered the Bitcrusher, so a Filter now takes four columns. Tests: - Unit: 8 new (filter band, resize command and save, fitting rules), stage lists with a Filter now hold its taps - e2e 34 (atx): ALL PASSED, 130 checks (4 new) - Gate (atx): CHECK EXIT 0, 284 files, 4367 tests Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
48 lines
1.7 KiB
TypeScript
48 lines
1.7 KiB
TypeScript
/**
|
|
* The nodes of a sound FX graph with a tab of their own, opened by a double-click on them: a
|
|
* Parametric EQ (#370), a Compressor (#378) and a Filter (#385). A tab is a document named by its
|
|
* kind, the asset and the node (`eq:<asset>:<node>`).
|
|
*/
|
|
import type { GraphNode } from '../../core/graph';
|
|
import type { Project } from '../../core/model/Project';
|
|
import { SoundFxCompressor, SoundFxFilter, SoundFxParametricEq } from '../../core/soundfx/nodes';
|
|
import { compressorNodeOf, compressorTabLabel } from './CompressorEditor';
|
|
import { eqTabLabel } from './EqEditor';
|
|
import { eqNodeOf } from './EqPanel';
|
|
import { filterNodeOf } from './FilterPanel';
|
|
|
|
export type NodeTabKind = 'eq' | 'compressor' | 'filter';
|
|
|
|
export const NODE_TAB_KINDS: readonly NodeTabKind[] = ['eq', 'compressor', 'filter'];
|
|
|
|
const KIND_OF_TYPE: Readonly<Record<string, NodeTabKind>> = {
|
|
[SoundFxParametricEq.type]: 'eq',
|
|
[SoundFxCompressor.type]: 'compressor',
|
|
[SoundFxFilter.type]: 'filter',
|
|
};
|
|
|
|
/** The kind of tab a node opens, or null for one without. */
|
|
export const nodeTabOf = (type: string): NodeTabKind | null => KIND_OF_TYPE[type] ?? null;
|
|
|
|
const NODE_OF: Readonly<Record<NodeTabKind, typeof eqNodeOf>> = {
|
|
eq: eqNodeOf,
|
|
compressor: compressorNodeOf,
|
|
filter: filterNodeOf,
|
|
};
|
|
|
|
/** The node a tab is about, or null once it, or its asset, is gone. */
|
|
export function nodeOfTab(
|
|
project: Project,
|
|
kind: NodeTabKind,
|
|
assetId: string,
|
|
nodeId: string,
|
|
): GraphNode | null {
|
|
return NODE_OF[kind](project, assetId, nodeId);
|
|
}
|
|
|
|
export function nodeTabLabel(project: Project, kind: NodeTabKind, assetId: string, nodeId: string): string {
|
|
return kind === 'compressor'
|
|
? compressorTabLabel(project, assetId, nodeId)
|
|
: eqTabLabel(project, assetId, nodeId);
|
|
}
|