Multichannel expansion \instrument

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

Then again you run into this situation. There are workarounds, see:

https://www.listarc.bham.ac.uk/lists/sc-users-2018/msg62866.html

https://www.listarc.bham.ac.uk/lists/sc-users-2013/msg34083.html

Thanks @dkmayer!

Evidently a deeper subject than I first thought.

Funny, I thought iteration would come into play here, but I couldn’t figure out the best way to do it. A new Event is cool :slight_smile: and pretty elegant.

But…should the multichannel expansion of \midinote issue 3 separate \pari events here?:


(
Pbind(
	\type, \pari,
	\dur, 1.25,
	\midinote, Pseq([0,5,0,7,4,0,0] + 60, inf) + [0,7,9],
	\amp, Prand([0.125,0.2,0.25],inf),
	\instrument, [\nickinstr, \default],
	\pan, Prand([-1,0,1],inf)
).play
)

My intention is that each of the notes could be either \nickinstr or \default, not all 3 being the same instrument.

I hought that this Event might handle that:

(
//create Event for handling expansion of instrument array
Event.addEventType(\pari, {
	var instr = ~instrument;
	~type = \note;

	if(instr.isArray and:  { instr.isString.not }) { // just to make sure
		~instrument = instr.choose;
		currentEnvironment.play;

	} {
		currentEnvironment.play
	}
});
)

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?

cheers :slight_smile:

The process is not as simple as you think.

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;
}
)

hjh

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).

Here’s my thought process.

How does it handle an event like (freq: [200, 300])?

  1. Get the msgFunc for the \instrument.
  2. Get the argument values from the msgFunc – in this case, should be [\freq, [200, 300], \pan, 0, \amp, 0.1) (because of default values in the event).
  3. Flop that: [ [\freq, 200, \pan, 0, \amp, 0.1], [\freq, 300, \pan, 0, \amp, 0.1] ]
  4. 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 right place to put this logic is into an event type – Event | SuperCollider 3.11.1 Help

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.

hjh