It doesn’t.
If it were true that Pbind were responsible for multichannel expansion, then you would be able to find the code in Pbind that performs the multichannel expansion. But you won’t find it, because it isn’t there.
Pbind is not responsible for multichannel expansion in any way.
A couple of counterexamples:
(
p = Pbindf(
Pbind(
\freq, Pexprand(200, 800, inf),
\dur, [0.1, 0.11]
),
\amp, Pexprand(0.05, 0.2, inf)
);
// asPpar should apply to the source pattern,
// not the Pbindf
q = p.asPpar.play;
)
(
p = Pbindf(
Pbind(
\freq, Pexprand(200, 800, inf),
\amp, Pexprand(0.05, 0.2, inf)
),
\dur, [0.1, 0.11]
);
// DoesNotUnderstand:
// Pbindf is a FilterPattern, not a Pbind subclass
q = p.asPpar.play;
)
(
p = Prout { |event|
loop {
event = event.putAll((
freq: exprand(200, 800),
amp: exprand(0.05, 0.2),
dur: [0.1, 0.11]
)).yield;
}
};
// no way to implement asPpar at all here
q = p.asPpar.play;
)
The last is particularly troublesome for an asPpar proposal because there is no way to break up the user’s Prout function into separate elements to parallelize. That is, even if asPpar could be made perfectly clear and handle all the cases, there is still a brick wall – it’s just located somewhere else.
Where is the multichannel expansion code, then?
It’s in Event.
if(strum == 0 and: { (sendGate and: { sustain.isArray })
or: { offset.isArray } or: { lag.isArray } }) {
bndl = flopTogether(
bndl,
[sustain, lag, offset]
);
#sustain, lag, offset = bndl[1].flop;
bndl = bndl[0];
} {
bndl = bndl.flop
};
// produce a node id for each synth
~id = ids = Array.fill(bndl.size, { server.nextNodeID });
instrumentName = instrumentName.asArray;
bndl = bndl.collect { | msg, i |
msg[2] = ids[i];
msg[1] = instrumentName.wrapAt(i);
msg.asOSCArgArray
};
So the solution to multichannel expansion of instrument properly belongs in Event, not in Pbind.
Here, it gets tricky, because there are a lot of places where the default event prototype assumes that the instrument name will be a single symbol. See synthDefName
, which handles variant names. (So there’s another case to consider: what if you specify multiple instrument names, but only one variant name, and not all of the SynthDefs provide that variant name? Scsynth prints an error for undefined variants.)
But if you can get around that, then something like the following will collect a union of all the parameters for each instrument, which can then be flopped and performed using the existing logic.
getSynthArgsFromCurEnvir: #{ |instrument|
var msgFunc, coll, lib;
if(instrument.size < 2) {
// normal case, keep optimized version
msgFunc = ~getMsgFunc.valueEnvir;
msgFunc.valueEnvir
} {
coll = IdentityDictionary.new;
lib = ~synthLib ?? { SynthDescLib.global };
instrument.do { |name|
msgFunc = lib[name].tryPerform(\msgFunc);
if(msgFunc.isNil) {
Error("msgFunc not available for " ++ name).throw;
};
msgFunc.valueEnvir.pairsDo { |key, value|
coll.put(key, value);
};
};
// now you have a union of all key-value pairs for all instruments
coll.asPairs
}
}
p = Pbind(
\degree, Pn(Pseries(0, 1, 8), inf),
\dur, [0.1, 0.2]
).play;
Before any progress could be made on “expanding” \dur, it’s necessary to decide on semantics. What does the above pattern mean? I can think of at least two, maybe three different meanings, which the syntax does not disambiguate. Until there is a concrete, unambiguous meaning for “multichannel-expanding dur,” then this problem is a “wouldn’t it be nice if…?” but not something that can be concretely addressed.
hjh