Route sounds, songs and buses in one Master routing graph (#351)
A project bus could only go into one other bus, and a sound, an
instrument, a pattern or a song into exactly one bus. A shared reverb
needs a sound to go there in part and on dry at the same time. Instead
of a list of sends on every asset, the project gets one routing graph
like the routing of a song (#350), one level up.
- Menu -> Master routing opens a tab with one graph per project. It has
a node per sound, instrument, pattern, song and bus, named after its
asset, and one Master. Embedded assets have none, their song routes
them.
- The nodes follow the assets. Every edit settles the routing in the
same undo step (KeepRoutingCommand), so an undone delete brings the
links of its asset back. A new asset gets a node and a link to the
master. What went only into a deleted bus goes to the master.
- New nodes start in three columns: what plays, the buses, and the
master a row above, so no link into it runs behind a bus.
- Links are sends: -60 to +12 dB, and a Mode on links out of a bus or a
song. Out of a sound, an instrument or a pattern a link always takes
the sound after its volume and FX. Loops are refused.
- A node without a link is silent and says "Goes nowhere".
- The rows Bus and Into are gone. Play nodes with a picked bus still
overrule the routing.
- A click on a node shows its asset in the inspector, the Master shows
the master volume and sound FX. A double click opens the asset's tab.
- The graph lives in project.routing and is held to the assets when a
project is read, but nothing is added there. The editor adds the
nodes when it opens a project. An asset without a node plays on the
master, so old projects sound as before without a migration.
- In the mix an asset with one link at 0 dB goes straight into that bus.
A sound or instrument with more gets a bus of its own with the sends.
A pattern or song sends from the master of its playing, which is
where its Mode taps. Buses of the mix can have sends now too.
Found:
- The check "the scheduler plans a looped bar" in e2e 34 wobbles. It
failed in 2 of 5 runs here and in CI task 580 at 490fcba, before
this change. HEAD passed it 2 of 2. Not fixed here.
Tests:
- Unit master-routing (new, 10), sound-library (2 new), buses
rewritten onto the routing, 5 lists without the Bus row. 5 counter
checks red.
- e2e 34 (melektaus-atx): ALL PASSED, 93 checks (3 new, 2 rewritten).
Counter check with the Sound tab ignoring the routing: the routing
check red.
- Gate (melektaus-atx): CHECK EXIT 0, 273 files, 4225 tests.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
8891f027db
commit
0e73fd85b9
46 files changed
+1253
-311
No files matched your search
@@ -0,0 +1,63 @@
|
||||
import { masterRoutingProblems } from '../../core/music/projectRouting';
|
||||
import { ROUTING_ID } from '../../core/model/Project';
|
||||
import { Button, Label, Spacer, VStack } from '../../ui';
|
||||
import { GraphCanvas } from '../graph/GraphCanvas';
|
||||
import { SnapToggle } from '../graph/SnapToggle';
|
||||
import type { EditorState } from '../state';
|
||||
import { TabHeader } from '../TabHeader';
|
||||
|
||||
const INFO_WIDTH = 220;
|
||||
|
||||
/**
|
||||
* The Master routing tab (#351), one per project: a node per sound, instrument, pattern, song and bus
|
||||
* and the master, the links between them where each one goes. Edited as the routing of a song is
|
||||
* (#350); a click on a node shows its asset in the inspector, a double click opens it.
|
||||
*/
|
||||
export class RoutingEditor extends VStack {
|
||||
readonly canvas: GraphCanvas;
|
||||
readonly fitButton: Button;
|
||||
readonly snap: SnapToggle;
|
||||
private readonly info = new Label('', { style: 'small', align: 'right' });
|
||||
|
||||
constructor(private readonly state: EditorState) {
|
||||
super();
|
||||
this.canvas = new GraphCanvas(state, 'routing');
|
||||
this.snap = new SnapToggle(() => this.canvas);
|
||||
this.fitButton = new Button('Fit', { onClick: () => this.canvas.fit() });
|
||||
this.info.minWidth = INFO_WIDTH;
|
||||
const header = new TabHeader();
|
||||
for (const widget of this.snap.widgets) header.add(widget);
|
||||
header.add(this.fitButton);
|
||||
header.add(new Spacer(), 1);
|
||||
header.add(this.info);
|
||||
this.add(header);
|
||||
this.add(this.canvas, 1);
|
||||
state.on('project', () => this.refresh());
|
||||
state.on('load', () => this.close());
|
||||
}
|
||||
|
||||
/** Shows the routing of the project; framed the first time, as it was left after that. */
|
||||
open(): void {
|
||||
this.canvas.show(ROUTING_ID);
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
/** The tab is closed: opened again, the routing starts from a fitted view. */
|
||||
close(): void {
|
||||
this.canvas.show('');
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
/** The readout: how many links, and how many nodes go nowhere and are never heard. */
|
||||
get shownInfo(): string {
|
||||
return this.info.text;
|
||||
}
|
||||
|
||||
private refresh(): void {
|
||||
if (this.canvas.currentAssetId !== ROUTING_ID) return this.info.setText('');
|
||||
const graph = this.state.project.routing;
|
||||
const silent = Object.keys(masterRoutingProblems(graph)).length;
|
||||
const links = `${graph.edges.length} link${graph.edges.length === 1 ? '' : 's'}`;
|
||||
this.info.setText(silent > 0 ? `${links} · ${silent} going nowhere` : links);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user