Both were asked for in #367 "for later". The node of a Parametric EQ now shows its curve in small, and its tab shows what passes the node while it is seen. #375, the curve in the node: - NodeDef has an optional `picture`: a height, and shapes made from the settings as plain point lists (guide, area, line). It is general, the Parametric EQ is its first user. - NodeView draws it in a box below the rows, again only when the box or the settings change. A drag in the EQ tab changes them, so the node follows it live. - Without a band the box stays, flat on its 0 dB line, so the node does not grow at the first band. - The axes of the curve, `eqCurvePoints` and `fillShapes` move from editor/soundfx/eqView to core/soundfx/parametricEq. Tab and node draw the sum from the same function. #374, the spectrum: - A node marked `tapped` gets a `tap` stage in front of its own stages and one behind them when compiled, in a sound FX and in an instrument alike. Its id is asset and node, since a copied asset keeps its node ids. - A tap is a gain of 1. SpectrumTaps (new, viewport/spectrum.ts) holds the tap of every voice and bus while it plays. - While the EQ tab is seen, in the tab row or in a window, the taps of that node feed one analyser per side (8192 points). Nothing is made before a tap of the node plays, so no AudioContext is opened early. - The tab draws both behind the curve, on a scale of 0 to -96 dBFS: the sound coming in faint and grey, the sound going on in the colour of a sound port. The analyser smooths it, and once the sound has gone the sum still hands it silence, so it falls away. - Once the tab is not seen, Editor.tick lets the analysers go, like the meters of the Song tab. - A tap does nothing to the sound, so `stats().last.stages` leaves it out, and a bypassed EQ keeps its taps. Decided on my own: - Every sound passing the node counts: Play of the sound FX tab, an instrument's keys, a song, a run. The issue proposed that. - The spectrum has no labels of its own. Its scale is in the wiki. Tests: Gate (melektaus-atx) 275 files, 4247 tests (11 new), EXIT 0 e2e 34 (melektaus-atx) ALL PASSED, 98 checks (2 new), twice Counter-checks, each put back by copy and compared with cmp: - The node's curve drawn without its bands: 2 unit tests fail. - The spectrum without the loudest bin between two points: 1 fails. - SpectrumTaps.stop leaving the taps linked: 1 fails. - Taps that never feed the analyser: e2e 34 fails in exactly the spectrum check (frames 0). Run in the container only. The screenshots of the second run were looked at: the blip as one peak behind the curve, and the dip at 4 kHz in the node. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
25084f8576
commit
687377682c
19 files changed
+802
-99
No files matched your search
@@ -11,6 +11,7 @@ import {
|
||||
type NodeCategory,
|
||||
type NodeDef,
|
||||
nodeDef,
|
||||
type NodePicture,
|
||||
type PortDef,
|
||||
type PortType,
|
||||
type Value,
|
||||
@@ -125,6 +126,8 @@ export interface PortHit {
|
||||
/** Draws one graph node entirely from its NodeDef; positioned in graph coordinates by GraphCanvas. */
|
||||
export class NodeView extends Widget {
|
||||
private readonly box = new Graphics();
|
||||
/** What the node draws of its settings below its rows (#375). */
|
||||
private readonly picture = new Graphics();
|
||||
private readonly ports = new Graphics();
|
||||
private readonly texts: Text[] = [];
|
||||
/** The input and output text of each port row, among `texts`. */
|
||||
@@ -159,6 +162,7 @@ export class NodeView extends Widget {
|
||||
*/
|
||||
private textsOf = '';
|
||||
private drawnOf = '';
|
||||
private pictureOf = '';
|
||||
/** How often the texts and the box were really made, for the checks. */
|
||||
readonly made = { texts: 0, box: 0 };
|
||||
|
||||
@@ -178,8 +182,9 @@ export class NodeView extends Widget {
|
||||
// Pixi draws a shape of more than 200 vertices on its own, and the round pins are: a few hundred
|
||||
// nodes were a few hundred draw calls. In the batch they share one.
|
||||
this.box.context.batchMode = 'batch';
|
||||
this.picture.context.batchMode = 'batch';
|
||||
this.ports.context.batchMode = 'batch';
|
||||
this.addChild(this.box, this.ports);
|
||||
this.addChild(this.box, this.picture, this.ports);
|
||||
this.sync(node, { nodes: [node], edges: [] }, null, def);
|
||||
}
|
||||
|
||||
@@ -250,7 +255,10 @@ export class NodeView extends Widget {
|
||||
inputs: graph.edges.some((e) => e.to.node === node.id),
|
||||
outputs: graph.edges.some((e) => e.from.node === node.id),
|
||||
};
|
||||
const height = this.collapsed ? TITLE_HEIGHT : this.headerHeight() + this.rowCount() * ROW_HEIGHT + PAD;
|
||||
const picture = this.shownPicture();
|
||||
const height = this.collapsed
|
||||
? TITLE_HEIGHT
|
||||
: this.rowsEnd() + PAD + (picture ? picture.height + PAD / 2 : 0);
|
||||
// Sized before the texts: output labels are placed against the right edge.
|
||||
this.resize(this.collapsed ? this.collapsedWidth() : NODE_WIDTH, height);
|
||||
const texts = this.textSignature();
|
||||
@@ -387,6 +395,47 @@ export class NodeView extends Widget {
|
||||
return TITLE_HEIGHT + PAD + lines * ROW_HEIGHT;
|
||||
}
|
||||
|
||||
/** Below the last port row. */
|
||||
private rowsEnd(): number {
|
||||
return this.headerHeight() + this.rowCount() * ROW_HEIGHT;
|
||||
}
|
||||
|
||||
/** The node's picture, while the node is open. */
|
||||
private shownPicture(): NodePicture | null {
|
||||
return (!this.collapsed && this.def.picture) || null;
|
||||
}
|
||||
|
||||
/** Where the picture sits, in node units. */
|
||||
private pictureBox(): { x: number; y: number; w: number; h: number } | null {
|
||||
const picture = this.shownPicture();
|
||||
return picture && { x: PAD, y: this.rowsEnd() + PAD / 2, w: this.w - PAD * 2, h: picture.height };
|
||||
}
|
||||
|
||||
/** Drawn again when the settings or the size have changed: a drag in the EQ's tab changes them. */
|
||||
private drawPicture(): void {
|
||||
const picture = this.shownPicture();
|
||||
const box = this.pictureBox();
|
||||
const drawn = picture && box ? JSON.stringify([box, this.node.params]) : '';
|
||||
if (drawn === this.pictureOf) return;
|
||||
this.pictureOf = drawn;
|
||||
this.picture.clear();
|
||||
if (!picture || !box) return;
|
||||
const c = Theme.colors;
|
||||
this.picture.position.set(box.x, box.y);
|
||||
this.picture.rect(0, 0, box.w, box.h).fill(c.graphBackground).stroke({ color: c.nodeBorder, width: 1 });
|
||||
for (const { kind, points } of picture.shapes(this.node.params, box.w)) {
|
||||
if (kind === 'area') {
|
||||
this.picture.poly(points).fill({ color: c.accent, alpha: 0.18 });
|
||||
continue;
|
||||
}
|
||||
this.picture.moveTo(points[0]!, points[1]!);
|
||||
for (let i = 2; i < points.length; i += 2) this.picture.lineTo(points[i]!, points[i + 1]!);
|
||||
this.picture.stroke(
|
||||
kind === 'guide' ? { color: c.textMuted, width: 1, alpha: 0.5 } : { color: c.accent, width: 1.5 },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/** Local position of a port's centre. */
|
||||
portPosition(key: string, isOutput: boolean): { x: number; y: number } | null {
|
||||
const list = isOutput ? this.def.outputs : this.def.inputs;
|
||||
@@ -490,6 +539,7 @@ export class NodeView extends Widget {
|
||||
}
|
||||
|
||||
override redraw(): void {
|
||||
this.drawPicture();
|
||||
const drawn = this.drawSignature();
|
||||
if (drawn === this.drawnOf) return;
|
||||
this.drawnOf = drawn;
|
||||
|
||||
Reference in new issue
Block a user