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
+17
-2
@@ -125,6 +125,7 @@ export type CentreTab =
|
||||
| 'routing'
|
||||
| 'eq'
|
||||
| 'compressor'
|
||||
| 'filter'
|
||||
| 'management';
|
||||
|
||||
/** A tab a plugin added; the prefix keeps it apart from every built-in one. */
|
||||
@@ -135,7 +136,7 @@ export type TabId = CentreTab | PluginTabId;
|
||||
* An open document (#171): an asset by its id, or what a Variables tab shows (#252) by its
|
||||
* `valuesKey` – as many of those as were opened. The user management (#179) and the master routing
|
||||
* (#351) are no documents, but they open and close like one. A Parametric EQ has a tab per node (#370),
|
||||
* and so has a Compressor (#378).
|
||||
* and so have a Compressor (#378) and a Filter (#385).
|
||||
*/
|
||||
export type DocumentId =
|
||||
`asset:${string}` | `values:${string}` | `${NodeTabKind}:${string}` | 'routing' | 'management';
|
||||
@@ -183,6 +184,7 @@ const PANE_ORDER = [
|
||||
'routing',
|
||||
'eq',
|
||||
'compressor',
|
||||
'filter',
|
||||
'management',
|
||||
] as const;
|
||||
|
||||
@@ -210,6 +212,7 @@ const TAB_LABELS: Record<CentreTab, string> = {
|
||||
routing: 'Master routing',
|
||||
eq: 'Parametric EQ',
|
||||
compressor: 'Compressor',
|
||||
filter: 'Filter',
|
||||
management: 'User management',
|
||||
};
|
||||
|
||||
@@ -279,6 +282,7 @@ export class Editor implements PlayHost {
|
||||
readonly routingEditor: RoutingEditor;
|
||||
readonly eqEditor: EqEditor;
|
||||
readonly compressorEditor: CompressorEditor;
|
||||
readonly filterEditor: EqEditor;
|
||||
/** What each open Variables tab shows, by its document. */
|
||||
private readonly valueTargets = new Map<DocumentId, ValuesTarget>();
|
||||
/** The node behind each tab of an EQ (#370) or a Compressor (#378). */
|
||||
@@ -384,6 +388,7 @@ export class Editor implements PlayHost {
|
||||
this.routingEditor = new RoutingEditor(state);
|
||||
this.eqEditor = new EqEditor(state);
|
||||
this.compressorEditor = new CompressorEditor(state);
|
||||
this.filterEditor = new EqEditor(state, 'filter');
|
||||
this.hierarchy = new HierarchyPanel(state);
|
||||
this.inspector = new Inspector(state);
|
||||
this.tilemapPanel = new TilemapPanel(state);
|
||||
@@ -508,6 +513,7 @@ export class Editor implements PlayHost {
|
||||
routing: this.routingEditor,
|
||||
eq: this.eqEditor,
|
||||
compressor: this.compressorEditor,
|
||||
filter: this.filterEditor,
|
||||
management: this.managementPane,
|
||||
};
|
||||
// Following a call goes on in the same tab: the tab changes its document, not its place.
|
||||
@@ -614,7 +620,7 @@ export class Editor implements PlayHost {
|
||||
if (!this.valueTargets.has(doc)) this.valueTargets.set(doc, target);
|
||||
this.openDocument(doc);
|
||||
});
|
||||
// A tab per Parametric EQ (#370) or Compressor (#378) node, opened by a double-click on it.
|
||||
// A tab per Parametric EQ (#370), Compressor (#378) or Filter (#385) node, opened by a double-click on it.
|
||||
state.on('nodeTab', () => {
|
||||
const target = state.openedNode;
|
||||
const kind = target && nodeTabOf(target.type);
|
||||
@@ -642,6 +648,7 @@ export class Editor implements PlayHost {
|
||||
this.nodeTargets.clear();
|
||||
this.eqEditor.close();
|
||||
this.compressorEditor.close();
|
||||
this.filterEditor.close();
|
||||
this.showTab('scene');
|
||||
});
|
||||
state.on('mode', () => this.editorRun.modeChanged());
|
||||
@@ -712,6 +719,9 @@ export class Editor implements PlayHost {
|
||||
// The selection is the EQ's node, still selected from the double-click: it is never what goes.
|
||||
this.eqEditor.removePicked();
|
||||
return true;
|
||||
case 'filter':
|
||||
// A Filter has its one band and nothing else to take; its node stays as well.
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -908,6 +918,7 @@ export class Editor implements PlayHost {
|
||||
if (this.nodeTargets.delete(doc)) {
|
||||
if (kind === 'eq') this.eqEditor.close();
|
||||
if (kind === 'compressor') this.compressorEditor.close();
|
||||
if (kind === 'filter') this.filterEditor.close();
|
||||
}
|
||||
if (doc === 'routing') this.routingEditor.close();
|
||||
}
|
||||
@@ -1145,6 +1156,7 @@ export class Editor implements PlayHost {
|
||||
const node = this.nodeTargets.get(doc);
|
||||
if (kind === 'eq' && node) this.eqEditor.show(node.assetId, node.nodeId);
|
||||
if (kind === 'compressor' && node) this.compressorEditor.show(node.assetId, node.nodeId);
|
||||
if (kind === 'filter' && node) this.filterEditor.show(node.assetId, node.nodeId);
|
||||
}
|
||||
|
||||
/** What a document's tab says: the asset's name, or what a Variables tab shows, as its header says. */
|
||||
@@ -1335,9 +1347,11 @@ export class Editor implements PlayHost {
|
||||
['instrument', this.instrumentEditor.canvas],
|
||||
] as const;
|
||||
if (!this.shows('eq')) this.eqEditor.hideSpectrum();
|
||||
if (!this.shows('filter')) this.filterEditor.hideSpectrum();
|
||||
if (!this.shows('compressor')) this.compressorEditor.hideLevels();
|
||||
for (const [kind, canvas] of faces) if (!this.shows(kind)) canvas.hideFaces();
|
||||
if (this.shows('eq')) this.eqEditor.tick();
|
||||
if (this.shows('filter')) this.filterEditor.tick();
|
||||
if (this.shows('compressor')) this.compressorEditor.tick();
|
||||
this.facesMoving = false;
|
||||
for (const [kind, canvas] of faces)
|
||||
@@ -1379,6 +1393,7 @@ export class Editor implements PlayHost {
|
||||
(this.shows('song') && this.songEditor.isPlaying) ||
|
||||
(this.shows('sound') && this.soundEditor.isPlaying) ||
|
||||
(this.shows('eq') && this.eqEditor.hasSpectrum) ||
|
||||
(this.shows('filter') && this.filterEditor.hasSpectrum) ||
|
||||
(this.shows('compressor') && this.compressorEditor.moving) ||
|
||||
this.facesMoving
|
||||
);
|
||||
|
||||
Reference in new issue
Block a user