I know that it specifically says that \instrument does not accept an array for multichannel expansion, but I want each event to be able to send to similar, but slightly different SynthDefs.
What’s the most efficient way to achieve this without completely refactoring my code?
I’m currently attempting it with Pbindf to compose new Pbinds with only the \instrument parameter in each composite pattern, but it soon becomes far more complicate due to the chords array I’m passing to the \freq parameter. Basically for a 3 note (or more) chord I want each note to be sent to a different Synth
Perhaps I am not grasping how the Event streams are working with regard to ‘polyphonic’ events. It appears that only one Event is issued that encapsulates 3 notes that will get expanded at some point, but it doesn’t seem to happen in the \pari event - I can’t iterate through each event and assign a different instrument name before they get expanded, or played in currentEnvironment.play
With the above, the 3 notes will all get the same instrument name. Is that making sense?
You’d need to “flop” across multiple parameter sets.
Something like this, though I don’t have time to develop it into an event type. “Exercise for the reader.”
(
SynthDef(\abc, { |a = 1, b = 2, c = 3|
Out.ar(0, DC.ar(0))
}).add;
SynthDef(\bcd, { |b = 4, c = 5, d = 6|
Out.ar(0, DC.ar(0))
}).add;
)
e = (instrument: [\abc, \bcd], a: [10, 20], b: [30, 40], c: [50, 60], d: [70, 80]);
// union of all instruments' msgFuncs
(
// a = "allpairs"
a = IdentityDictionary.new;
e.use {
~instrument.do { |instr|
SynthDescLib.at(instr).msgFunc.valueEnvir
.pairsDo { |key, value|
a.put(key, value);
};
};
};
)
a
// hack up a 'flop' equivalent for dictionaries
(
var maxSize = a.maxValue { |item| item.asArray.size };
f = Array.fill(maxSize, { |i|
// I can't believe collectAs doesn't work here
// tired of methods failing for no good reason
// a.collectAs({ |item| item.wrapAt(i) }, Event)
// fine, I'll do it myself
var new = Event.new;
a.keysValuesDo { |key, value|
new.put(key, value.asArray.wrapAt(i))
};
new
});
)
// now we have an array of events
// parameters for instrument 0
// parameters for instrument 1 etc.
(
f.do { |event, i|
event.put(\instrument, e[\instrument][i])
.play;
}
)
Thanks @jamshark70 a lot to get my head around here, specifically the Environment and Event. Will dig into it and see if I can apply it to refactor something I got working with Pbindf (and Pkey, Prand).
Now we know 2 synths are needed, and we have the complete argument lists for both.
Corner case: (freq: 200, temp: [1, 2]) – how many synths?
At step 2, temp isn’t a control name in the default SynthDef. So its array will not be collected. All parameters in the argument list will then have only single values. So, only one synth will play.
So the number of synths depends not on an array merely existing anywhere in the event. The array must belong to a parameter that exists in the SynthDef.
Extending that logic: If you have multiple SynthDefs, then you need a compound parameter list consisting of keys and values for the union of control names across all SynthDefs being used. So my prototype calls the msgFunc for each instrument, and stuffs the results into one dictionary (which merges them into the set union).
Then split these out into separate events. There’s probably a better way here – alternately, this could be an array of arrays (arg lists).
The test cases should look like: (type: \polyInstr, instrument: [\a, \b], ... other parameters).play. If this works, then it will automatically work with all event patterns.