Edit a Filter in its node and resize EQ and Filter nodes (#385)

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>
This commit is contained in:
adminandClaude Opus 5.5 committed 2026-10-10 14:30:00 +02:00
1 parent 17f47f6956
commit c087993000
26 files changed
+1514 -656

No files matched your search

+7 -2
View File
@@ -393,6 +393,11 @@ function held<K extends string>(
/** Room between the nodes of a preset, left to right and top to bottom: a Chip is a tall node. */
const COLUMN = 240;
const ROW = 340;
/** Columns a node takes that is drawn as its editor in the graph (#385): a Filter's curve is wide. */
const COLUMNS_OF: Readonly<Record<string, number>> = { [SoundFxFilter.type]: 4 };
/** The column right of a node, past all it takes. */
const columnAfter = (node: GraphNode): number => node.x / COLUMN + (COLUMNS_OF[node.type] ?? 1);
/** A node to make: its type and its params. */
type Step = readonly [type: string, params: PropertyBag];
@@ -416,7 +421,7 @@ class Sketch {
line(from: GraphNode, ...steps: readonly Step[]): GraphNode {
let last = from;
for (const [type, params] of steps) {
const next = this.add(type, last.x / COLUMN + 1, last.y / ROW, params);
const next = this.add(type, columnAfter(last), last.y / ROW, params);
this.wire(last, 'out', next, 'in');
last = next;
}
@@ -510,7 +515,7 @@ const echo = (time: number, feedback: number, wet: number, pingPong = false) =>
/** Two ways into one Mix, a column right of the further one; `b` weighs `share`. */
function mixed(s: Sketch, a: GraphNode, b: GraphNode, share: number, curve = 'crossfade'): GraphNode {
const mix = s.add(SoundFxMix.type, Math.max(a.x, b.x) / COLUMN + 1, Math.min(a.y, b.y) / ROW, {
const mix = s.add(SoundFxMix.type, Math.max(columnAfter(a), columnAfter(b)), Math.min(a.y, b.y) / ROW, {
curve,
mix: share,
});