Take one sound at the Output of an instrument, mixed by a Mix #331
The Output summed any number of edges as layers. That made it the only sound input of its kind, and it read as if a note could be several voices. A note was always one voice, but the graph did not say so. Now the Output takes one sound like every other sound input, and several are put together with a Mix in front of it (decided by the user after #331). - The Output's input is no longer `many`. A second edge replaces the first, as at any data input. - With only one drum sounding for a note, a Mix is its other side. A drum kit keeps the volume of each drum. - An instrument saved with several edges into the Output keeps them but does not compile. The Output says "Takes one sound: put a Mix in front of it to play several". No saved project has one: the only instrument there (cbbf160bb7e8) has a single edge. - An Output with nothing in it is a problem now, not silence. - The compiler gathers only triggers from several edges. The player still takes a list of layers, which an instrument fills with one. Tests: - instrument.test.ts: drum kit through a Mix, a second edge replacing the first, an old Output with two edges, an empty Output (33 tests) - Counter-check: `many` back on the Output turns 15 tests red, the new one about the second edge among them - e2e 34 on melektaus-atx: ALL PASSED, 47 checks - Gate on melektaus-atx: CHECK EXIT 0, 244 test files, 3789 tests Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
23bdc3f775
commit
076c564415
3 files changed
+61
-20
No files matched your search
@@ -1,5 +1,5 @@
|
||||
import { graphEmitter } from '../graph/emitGraph';
|
||||
import { type DeclaredPort, type Graph, graphFingerprint, type GraphNode } from '../graph/Graph';
|
||||
import { type DeclaredPort, edgesInto, type Graph, graphFingerprint, type GraphNode } from '../graph/Graph';
|
||||
import type { GraphLookup } from '../graph/lookup';
|
||||
import { resolveDef, terminalNode } from '../graph/nested';
|
||||
import { inlineValue, type NodeDef } from '../graph/NodeRegistry';
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
INSTRUMENT_PARAMETERS,
|
||||
INSTRUMENT_SAMPLE,
|
||||
INSTRUMENT_SOUND_FX,
|
||||
InstrumentOutput,
|
||||
isInstrumentNodeDef,
|
||||
loops,
|
||||
noteOutputKey,
|
||||
@@ -73,7 +74,7 @@ export function instrumentSourcesOf(project: Project): InstrumentSources {
|
||||
}
|
||||
|
||||
export interface InstrumentBuild {
|
||||
/** What the note is made of, each played on its own and summed; empty is silence. */
|
||||
/** What the note is made of: the one sound at the Output, or none for silence. */
|
||||
layers: readonly FxPlan[];
|
||||
/** Per node id, what keeps the graph from compiling. */
|
||||
problems: Record<string, string>;
|
||||
@@ -82,8 +83,8 @@ export interface InstrumentBuild {
|
||||
}
|
||||
|
||||
/**
|
||||
* What travels along an edge of an instrument: sounds (none where nothing plays for this note, more
|
||||
* than one only into the Output), a number, or whether a trigger is on for this note.
|
||||
* What travels along an edge of an instrument: sounds (none where nothing plays for this note), a
|
||||
* number, or whether a trigger is on for this note.
|
||||
*/
|
||||
type Value = { plans: FxPlan[] } | { amount: FxAmount } | { on: boolean };
|
||||
|
||||
@@ -116,7 +117,6 @@ export function compileInstrument(graph: Graph, note: number, sources: Instrumen
|
||||
emit: (def, inputs, node) => emitNode(def, inputs, node),
|
||||
unfed: (node, port) => {
|
||||
if (port.type === 'number') return { amount: { value: asNumber(inlineValue(node, port)) } };
|
||||
if (port.type === 'audio' && port.many) return SILENT;
|
||||
walk.problem(
|
||||
node,
|
||||
port.type === 'trigger'
|
||||
@@ -139,10 +139,8 @@ export function compileInstrument(graph: Graph, note: number, sources: Instrumen
|
||||
);
|
||||
return null;
|
||||
},
|
||||
gather: (_node, port, values) =>
|
||||
port.type === 'trigger'
|
||||
? { on: values.some((v) => 'on' in v && v.on) }
|
||||
: { plans: values.flatMap((v) => ('plans' in v ? v.plans : [])) },
|
||||
// Only a trigger takes more than one edge: either one turns it on.
|
||||
gather: (_node, _port, values) => ({ on: values.some((v) => 'on' in v && v.on) }),
|
||||
});
|
||||
|
||||
function emitNode(def: Def, inputs: Record<string, Value>, node: GraphNode): Record<string, Value> {
|
||||
@@ -214,7 +212,10 @@ export function compileInstrument(graph: Graph, note: number, sources: Instrumen
|
||||
|
||||
const output = graph.nodes.find((n) => n.type === INSTRUMENT_OUTPUT);
|
||||
if (!output) return { layers: [], problems: walk.problems, reason: 'No Output node' };
|
||||
const arrived = walk.input(output, { key: 'in', label: 'in', type: 'audio', many: true });
|
||||
// Saved while the Output still took any number of sounds: only the first would be heard.
|
||||
if (edgesInto(graph, output.id, 'in').length > 1)
|
||||
walk.problem(output, 'Takes one sound: put a Mix in front of it to play several');
|
||||
const arrived = walk.input(output, InstrumentOutput.inputs[0]!);
|
||||
const failed = walk.firstProblem();
|
||||
if (failed !== null || !arrived || !('plans' in arrived))
|
||||
return { layers: [], problems: walk.problems, reason: failed ?? 'Cannot compile' };
|
||||
|
||||
@@ -234,8 +234,8 @@ export const InstrumentOutput: InstrumentNodeDef = {
|
||||
category: 'sound',
|
||||
instrument: true,
|
||||
description:
|
||||
'What is heard of a note. It takes any number of sounds and plays them together – the layers of the instrument.',
|
||||
inputs: [{ ...audioIn, many: true }],
|
||||
'What is heard of a note. It takes one sound; to play several together, put a Mix in front of it.',
|
||||
inputs: [audioIn],
|
||||
outputs: [],
|
||||
params: [],
|
||||
};
|
||||
|
||||
Reference in new issue
Block a user