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:
1 parent
17f47f6956
commit
c087993000
26 files changed
+1514
-656
No files matched your search
@@ -735,6 +735,139 @@ export async function sound(t) {
|
||||
JSON.stringify(eqNodeHeard.spectrum),
|
||||
);
|
||||
await shot('34-eq-node');
|
||||
// #385: the EQ node is resized by its lower right corner, one undo step: small, the row under its
|
||||
// curve goes, and a little larger it comes back with what fits of it, from the left.
|
||||
const eqCorner = (await eqPanel()).corner;
|
||||
await drag(eqCorner.x, eqCorner.y, eqCorner.x - 600, eqCorner.y - 400);
|
||||
const eqLeast = {
|
||||
panel: await eqPanel(),
|
||||
size: await json(
|
||||
`__gm.state.project.assets.find((a) => a.id === ${JSON.stringify(fxId)}).graph.nodes.find((n) => n.id === 'fx-eq').size`,
|
||||
),
|
||||
};
|
||||
await press('z', { ctrl: true });
|
||||
const eqFull = await eqPanel();
|
||||
const zoom = eqFull.rect.w / eqFull.size.w;
|
||||
await drag(eqFull.corner.x, eqFull.corner.y, eqFull.corner.x - 300 * zoom, eqFull.corner.y - 160 * zoom);
|
||||
const eqMid = await eqPanel();
|
||||
const eqRow = ['Band', 'Type', 'Hz', 'dB', 'Q', 'Slope', 'End'];
|
||||
const leftPart = (row) => row.every((name, i) => name === eqRow[i]);
|
||||
check(
|
||||
'an EQ node is resized by its corner as one undo step: small, its row goes; larger, the groups that fit come back from the left',
|
||||
eqLeast.size?.w === 240 &&
|
||||
eqLeast.size.h === 120 &&
|
||||
eqLeast.panel.size.w === 240 &&
|
||||
eqLeast.panel.rows.row.length === 0 &&
|
||||
eqFull.size.w === 720 &&
|
||||
eqFull.rows.row.join() === eqRow.join() &&
|
||||
eqFull.rows.help === false &&
|
||||
eqMid.size.w < 720 &&
|
||||
eqMid.rows.row.length >= 1 &&
|
||||
eqMid.rows.row.length < eqRow.length &&
|
||||
leftPart(eqMid.rows.row),
|
||||
JSON.stringify({
|
||||
eqLeast,
|
||||
eqFull: { size: eqFull.size, rows: eqFull.rows },
|
||||
eqMid: { size: eqMid.size, rows: eqMid.rows },
|
||||
}),
|
||||
);
|
||||
await press('z', { ctrl: true });
|
||||
// #385: a Filter node is edited in its node as one band of the curve: its handle is dragged for the
|
||||
// frequency and the gain as one undo step. A double-click on its title opens the same in a tab,
|
||||
// Delete there leaves the node, and a small window draws what fits of the row and no help.
|
||||
await mutate(`
|
||||
const graph = __gm.state.project.assets.find((a) => a.id === ${JSON.stringify(fxId)}).graph;
|
||||
graph.nodes.push(${JSON.stringify(node('fx-filter', 'SoundFxFilter', -240, 600, { filter: 'peaking', cutoff: 600, gain: -8, q: 2 }))});
|
||||
graph.edges = graph.edges.filter((e) => e.id !== 'fx-e11');
|
||||
graph.edges.push(
|
||||
${JSON.stringify(edge('fx-e12', 'fx-eq', 'out', 'fx-filter', 'in'))},
|
||||
${JSON.stringify(edge('fx-e13', 'fx-filter', 'out', 'fx-out', 'in'))},
|
||||
);`);
|
||||
await evaluate('__gm.soundfx.fit()');
|
||||
await sleep(100);
|
||||
const filterPanel = () => json("__gm.eq.panel('fx-filter')");
|
||||
const filterFace = await filterPanel();
|
||||
const filterRect = filterFace.rect;
|
||||
const filterTo = await json("__gm.eq.panelPointAt('fx-filter', 2000, 6)");
|
||||
await drag(filterFace.handles[0].x, filterFace.handles[0].y, filterTo.x, filterTo.y);
|
||||
const filterDragged = await filterPanel();
|
||||
await press('z', { ctrl: true });
|
||||
const filterUndone = await filterPanel();
|
||||
check(
|
||||
'a Filter node shows its curve with one band between its ports, dragged for frequency and gain as one undo step',
|
||||
filterFace.shown &&
|
||||
filterFace.size.w === 560 &&
|
||||
filterFace.size.h === 280 &&
|
||||
Object.values(filterFace.inputs).every((p) => p.x < filterRect.x) &&
|
||||
Object.values(filterFace.outputs).every((p) => p.x > filterRect.x + filterRect.w) &&
|
||||
filterFace.rows.row.join() === 'Type,Hz,dB,Q,Slope' &&
|
||||
filterFace.fields.type === 'Peaking' &&
|
||||
bandNear(filterFace.bands[0], 600, -8) &&
|
||||
bandNear(filterDragged.bands[0], 2000, 6) &&
|
||||
bandNear(filterUndone.bands[0], 600, -8) &&
|
||||
filterDragged.bands[0].q === 2,
|
||||
JSON.stringify({ filterFace, filterDragged: filterDragged.bands, filterUndone: filterUndone.bands }),
|
||||
);
|
||||
const filterTitle = await json("__gm.soundfx.nodeAt('fx-filter')");
|
||||
await t.doubleClick(filterTitle.x, filterTitle.y);
|
||||
const filterTab = {
|
||||
shown: await json('__gm.filter.shown()'),
|
||||
rows: await json('__gm.filter.rows()'),
|
||||
docs: await json('__gm.documents()'),
|
||||
};
|
||||
await press('Delete');
|
||||
const filterKept = await json(
|
||||
`__gm.state.project.assets.find((a) => a.id === ${JSON.stringify(fxId)}).graph.nodes.some((n) => n.id === 'fx-filter')`,
|
||||
);
|
||||
await evaluate(`__gm.floatDocument(${JSON.stringify(`filter:${fxId}:fx-filter`)})`);
|
||||
const filterWindow = (await json('__gm.floatWindows()')).find((w) => w.kind === 'filter');
|
||||
await drag(filterWindow.grip.x, filterWindow.grip.y, filterWindow.box.x + 470, filterWindow.box.y + 250);
|
||||
const filterSmall = { rows: await json('__gm.filter.rows()'), labels: await json('__gm.filter.labels()') };
|
||||
await shot('34-filter-window');
|
||||
const filterRow = ['Type', 'Hz', 'dB', 'Q', 'Slope'];
|
||||
check(
|
||||
'a double-click opens the Filter in a tab with its help, Delete there leaves the node, and a small window draws what fits of the row and no help',
|
||||
filterTab.shown?.nodeId === 'fx-filter' &&
|
||||
filterTab.docs.active === `filter:${fxId}:fx-filter` &&
|
||||
filterTab.rows.row.join() === filterRow.join() &&
|
||||
filterTab.rows.help === true &&
|
||||
filterKept &&
|
||||
filterSmall.rows.help === false &&
|
||||
filterSmall.rows.row.length >= 1 &&
|
||||
filterSmall.rows.row.length < filterRow.length &&
|
||||
filterSmall.rows.row.every((name, i) => name === filterRow[i]),
|
||||
JSON.stringify({ filterTab, filterKept, filterSmall }),
|
||||
);
|
||||
// #385: played, the Filter node and its window both draw what passes it, as the EQ does.
|
||||
await evaluate('__gm.sound.stopAll()');
|
||||
const filterSpectra = async () => ({
|
||||
node: (await filterPanel()).spectrum,
|
||||
tab: await json('__gm.filter.spectrum()'),
|
||||
});
|
||||
const filterFrames = await filterSpectra();
|
||||
await evaluate('__gm.soundfx.play()');
|
||||
const filterHeard = await waitFor(
|
||||
filterSpectra,
|
||||
(s) => s.node.frames >= filterFrames.node.frames + 3 && s.tab.frames >= filterFrames.tab.frames + 3,
|
||||
);
|
||||
await evaluate('__gm.sound.stopAll()');
|
||||
check(
|
||||
'played, the Filter node and its window draw the blip’s 880 Hz behind the curve',
|
||||
filterHeard.node.frames >= filterFrames.node.frames + 3 &&
|
||||
filterHeard.tab.frames >= filterFrames.tab.frames + 3 &&
|
||||
nearBlip(filterHeard.node.peakIn) &&
|
||||
nearBlip(filterHeard.tab.peakIn) &&
|
||||
filterHeard.tab.taps.listened.split(' ').includes(`${fxId}:fx-filter`),
|
||||
JSON.stringify({ filterFrames, filterHeard }),
|
||||
);
|
||||
const filterClose = (await json('__gm.floatWindows()')).find((w) => w.kind === 'filter');
|
||||
await click(filterClose.close.x, filterClose.close.y);
|
||||
await evaluate(`__gm.openDocument(${JSON.stringify(`asset:${fxId}`)})`);
|
||||
await mutate(`
|
||||
const graph = __gm.state.project.assets.find((a) => a.id === ${JSON.stringify(fxId)}).graph;
|
||||
graph.nodes = graph.nodes.filter((n) => n.id !== 'fx-filter');
|
||||
graph.edges = graph.edges.filter((e) => !['fx-e12', 'fx-e13'].includes(e.id));
|
||||
graph.edges.push(${JSON.stringify(edge('fx-e11', 'fx-eq', 'out', 'fx-out', 'in'))});`);
|
||||
await evaluate('__gm.sound.stopAll()');
|
||||
const eqBefore = await stats();
|
||||
await click(fxPlayAt.x, fxPlayAt.y);
|
||||
|
||||
Reference in new issue
Block a user