One Pattern Playing Multiple Synths

Hi Everyone,

My questions is simple enough but I’ve had a tough time finding a suitable solution. I’m working on a piece right now that uses synthDefs designed around organ stops. Like organ stops, I want to be able to play the same music with multiple stops, or synthDefs, engaged. In other words, I would like to have one pattern play multiple synths.

The patterns in question generates a good amount of random data, which means if I Pchain the note/dur producing pattern to different instruments I get slightly different patterns rather than the same pattern with different instruments.

That’s really the gist of it so, for now, I’ll simply add that while the synthDefs I want to play are different, I have made sure that the principal pattern’s key\value pairs work with each synthDef and provide no conflicting information with their arguments.

Thanks!

1 Like

My knowledge of Patterns is very limited so I don’t know if this is practical but here goes:

Is it possible to generate your note values as a stream and then pass that to a Pattern (or Task?) which selects instruments, passes each note value to them in turn and then plays the events?

Just a thought

Richard

Richard,

Thanks for your response. Yes, I think something like that is in order. I’m fairly used to sending a .collect message to a ‘master’ Pbind and having, an offset later, ‘slave’ Pbinds access those values using Pfunc.

I can make that work in this context but what I really want isn’t just one or two values from the ‘master’ Pbind, I want the whole thing minus the instrument key. Finding an efficient way to do that has been difficult.

Hi,

you can try with Julian’s \pari event type he suggested a few days ago on the SC mailing list:

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

The synth combos can be defined in a separate pattern, e.g.

(
SynthDef(\synth_0, {|out = 0, freq = 440, amp = 0.1, pan = 0|
    Out.ar(out, Pan2.ar(SinOsc.ar(freq, mul: amp), pan) * EnvGate.new)
}).add;

SynthDef(\synth_1, {|out = 0, freq = 440, amp = 0.1, pan = 0|
    Out.ar(out, Pan2.ar(Saw.ar(freq, mul: amp), pan) * EnvGate.new)
}).add;


SynthDef(\synth_2, {|out = 0, freq = 440, amp = 0.1, pan = 0|
    Out.ar(out, Pan2.ar(Pulse.ar(freq, mul: amp), pan) * EnvGate.new)
}).add;

SynthDef(\synth_3, {|out = 0, freq = 440, amp = 0.1, pan = 0|
    Out.ar(out, Pan2.ar(Formant.ar(freq, mul: amp), pan) * EnvGate.new)
}).add;


Event.addEventType(\pari, {
    var instr = ~instrument;
    ~type = \note;
    if(instr.isArray and:  { instr.isString.not }) { // just to make sure
        instr.do { |each|
            ~instrument = each;
            currentEnvironment.play
        }
    } {
        currentEnvironment.play
    }
});
)


(
p = Pbind(
    \midinote, Pstutter(Pwhite(3, 8), Pwhite(60, 90)),
    \amp, Pstutter(Pwhite(3, 8), Pseq([0.05, 0.1], inf)),
    \dur, 0.1,
    \type, \pari
);

q = Pbind(
    \instrument, Pstutter(Pwhite(1, 3), Pseq([[0], [1], [2], [3], [1, 2], [1, 3], [2, 3], [1, 2, 3]], inf))
    .collect { |c| c.collect { |i| ("synth_" ++ i).asSymbol } }
) <> p;

x = q.trace.play
)

x.stop
1 Like

Yes! Thank you Daniel. In my case, the instrument key doesn’t need to be a pattern itself but it will be good to keep that in mind.

I often struggle with finding ways to do this. The most clear and flexible I’ve come up with is using Pchain (usually via the <> operator) with Ppar, Pbinds, captured in Pdef's. Ex:

(
SynthDef(\saw, {
	var sig, env;
	env = Env.adsr().kr(gate:\gate.kr(1), doneAction:2);
	sig = LPF.ar(Saw.ar(\freq.kr, env), (1 + \filt.kr) * 2000);
	Out.ar(\out.kr, sig);
}).add;

SynthDef(\pulse, {
	var sig, env;
	env = Env.adsr().kr(gate:\gate.kr(1), doneAction:2);
	sig = LPF.ar(Pulse.ar(\freq.kr, env, env), (1 + \filt.kr) * 600);
	Out.ar(\out.kr, sig);
}).add;

Pdef(\twoInstruments, Ppar([
	Pbind(\instrument, \saw, \octave, 4),
	Pbind(\instrument, \pulse, \octave, 5, \degree, Pkey(\degree) + Pseq([2, 4, 9], inf))
]));

Pdef(\rotate, Pbind(\filt, Penv([-0.5, 1.4, -0.5], [30, 30], \sin).repeat));

Pdef(\pattern, Pdef(\twoInstruments) <> Pbind(
	\scale, Scale.melodicMinor,
	\degree, Pstutter(16, Pseq([0, -2, -1.5, 4.05, 7], inf)),
	\dur, 0.25,
	\legato, 5
) <> Pdef(\rotate)).play;
)

4 Likes

Hi Scott

combining Pchain and Ppar is a nice idea - actually I think that one advantage of this approach doesn’t show up in your example: Ppar’s characteristic to turn several patterns into one stream – in other contexts often unwanted – here ironically turns into a strength: you can define a random sequence in your source template and it’s taken over!

Your example with a random sequence

(
SynthDef(\saw, {
    var sig, env;
    env = Env.adsr().kr(gate:\gate.kr(1), doneAction:2);
    sig = LPF.ar(Saw.ar(\freq.kr, env), (1 + \filt.kr) * 2000);
    Out.ar(\out.kr, sig);
}).add;

SynthDef(\pulse, {
    var sig, env;
    env = Env.adsr().kr(gate:\gate.kr(1), doneAction:2);
    sig = LPF.ar(Pulse.ar(\freq.kr, env, env), (1 + \filt.kr) * 600);
    Out.ar(\out.kr, sig);
}).add;

Pdef(\twoInstruments, Ppar([
    Pbind(\instrument, \saw, \octave, 4),
    Pbind(\instrument, \pulse, \octave, 5, \note, Pkey(\note) + Pseq([-5, 0, 4], inf))
]));

Pdef(\rotate, Pbind(\filt, Penv([-0.5, 1.4, -0.5] + 0.5, [30, 30], \sin).repeat));

// always 8 repeats, random base note taken for both voices
// took note instead of degrees because with microtones note gives more control 

Pdef(\pattern, Pdef(\twoInstruments) <> Pbind(
    \note, Pstutter(16, Pwhite(-10.0, 5.0)),
    \dur, 0.25,
    \legato, 1
) <> Pdef(\rotate)).trace.play;
)

This works fine as long as the number of voices is constant, if not, then other measure would have to be taken, e.g. fiddling with rests, but I suppose in this case Julian’s event type would be more comfortable.

(
SynthDef(\saw, {
    var sig, env;
    env = Env.adsr().kr(gate:\gate.kr(1), doneAction:2);
    sig = LPF.ar(Saw.ar(\freq.kr, env), (1 + \filt.kr) * 2000);
    Out.ar(\out.kr, sig);
}).add;

SynthDef(\pulse, {
    var sig, env;
    env = Env.adsr().kr(gate:\gate.kr(1), doneAction:2);
    sig = LPF.ar(Pulse.ar(\freq.kr, env, env), (1 + \filt.kr) * 600);
    Out.ar(\out.kr, sig);
}).add;

Pdef(\rotate, Pbind(\filt, Penv([-0.5, 1.4, -0.5] + 0.5, [30, 30], \sin).repeat));

Pdef(\pattern, Pdef(\polyInst) <> Pbind(
	\instrument, Pseq([ \saw, [\pulse, \saw], \pulse], inf),
    \noteBase, Pstutter(16, Pseq([0, -2, -1.5, 4.05, 7], inf)),
	\noteOffset, Pseq([2, 4, 9], inf),
	\note, Ptuple([Pkey(\noteBase), Pkey(\noteBase) + Pkey(\noteOffset)]),
	\dur, 0.25,
	\legato, 2
) <> Pdef(\rotate)).play;
)

The question is, what is Pdef(\polyInst)? In other words, can you have a mix-in Pbind that enables polyphonic instrument parameters “naturally” without using Ppar or creating a new event type from scratch (with the requisite code duplication, potential for changed behavior, etc)…

Considering the amount of time I’ve spent trying to crack this one, I’m pretty happy that this thread rattled loose whatever final neuron was required.

the answer below.....
Pdef(\polyInst, Pbind(
	\getMsgFunc, {
		|instrument|
		var funcs;

		if (instrument.isKindOf(Array)) {
			funcs = instrument.collect({
				|i|
				Event.parentEvents[\default][\getMsgFunc].value(i)
			});
			~instrument = instrument;
			{
				funcs.collect({
					|f|
					f.valueEnvir
				}).flop
			}
		} {
			Event.parentEvents[\default][\getMsgFunc].value(instrument)
		};
	},
	\synthDefName, {
		if (~instrument.isArray) {
			var collectedInstruments, instrument = ~instrument;
			collectedInstruments = instrument.collect({ 
				|i|
				~instrument = i;
				Event.parentEvents[\default][\synthDefName].valueEnvir;
			});
			~instrument = instrument;
			collectedInstruments;
		} {
			Event.parentEvents[\default][\synthDefName].valueEnvir
		}
	}
));

2 Likes

Surprisingly, this seems to work just fine even when you e.g. use \strum and \lag, which deal with the bundle structure directly - there may still be some other places that expect a monophonic \instrument, but they’d probably be easy to fix.

gonna bookmark this one

1 Like

Actually you are transfering an “event-type-like definition” to a dedicated helper pattern.
Not tested yet, but I can imagine the merits to do so, you don’t block the option to use other event types that way.

I discovered, that one pattern playing multiple synths works out of the box when using the \set event type.

This pattern

Pbind(
    \type, \set,
    \id, Pfunc({ [synth1.nodeID, synth2.nodeID] }),
    \degree, Pwhite(0, 4)
)

works just as you would expect and want. Perhaps the only reason it isn’t supported out of the box with the \note event type has to do with the complication of looking up the synth by name in the SynthLib simply not being implemented - as opposed to some intrinsic limitation of Patterns. It seems like it would be nice to have this capability built-in.

The problem is the case with multiple instruments, with one instrument it’s straight:

// can hardly be be heard here, but see number of running synths

Pbind(
	\amp, 0.1!2, // you need at least one array to indicate expansion
	\degree, Pwhite(0, 4)
).play


// audible here

Pbind(
	\degree, Pwhite(0, 4),
	\ctranspose, [0, 0.5]
).play


// 3 synths, usual mapping convention: 
// L gets ctranspose 0
// Mid gets ctranspose 0.5
// R gets ctranspose 0

Pbind(
	\degree, Pwhite(0, 4),
	\pan, [-0.9, 0, 0.9],
	\ctranspose, [0, 0.5] 
).play

Again, if the problem is a limitation in the Event, you can address it in the Event:

(
Event.addEventType(\polySynth, { |server|
	var original = currentEnvironment.copy
	// note, don't omit this! or you get infinite recursion
	.put(\type, \note);
	~instrument.do { |instrument|
		original.copy.put(\instrument, instrument).play;
	};
});

// for demo purposes, two SynthDefs with different controls
SynthDef(\a, { |out, gate = 1, freq = 440, amp = 0.1|
	Out.ar(out, DC.ar(0));  // doesn't matter
}).add;

SynthDef(\b, { |out, gate = 1, freq = 440, pan = 0|
	Out.ar(out, DC.ar(0));  // doesn't matter
}).add;
)

(type: \polySynth, instrument: [\a, \b]).asOSC.do(_.postln);

[ 0.0, [ 9, a, 1000, 0, 1, out, 0, freq, 261.6255653006, amp, 0.1 ] ]  <<-- 'a' finds 'amp'
[ 0.0, [ 9, b, 1001, 0, 1, out, 0, freq, 261.6255653006, pan, 0.0 ] ]  <<-- 'b' finds 'pan'
[ 0.8, [ 15, 1000, gate, 0 ] ]
[ 0.8, [ 15, 1001, gate, 0 ] ]

hjh

Right, that’s what Julian suggested with the \pari event type.
BTW, a problem with old links to the mailing list is, that you’d have to update them with the year, so

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

must become

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

What I’m wondering is if it just works with the \set event type why doesn’t it just work with the \note event type? From a high level aren’t they doing exactly the same thing?

What I remember from old discussions especially with Julian - and I have to agree - is that multi-synth expansion becomes either ambivalent or super-complicated if you have arrays of different lengths which should be mapped to different instruments.

But indeed, maybe it’s worth rethinking it … late here, so just some loose thoughts …

I think the case with type ‘set’ and multiple ids is a less problematic one: the ids for the event are given, it’s easier then to find a convention to map all other data - arrays of possibly different lengths - to those.
But in the case of type ‘note’ and a given array of instruments the ids are not yet there, the decision what and how many synths should be created has to be done, and will depend on the other data too: again arrays of possibly different lengths. So here freedom for a new convention is a greater and more difficult one. I don’t say that it’s impossible to cope with that, but nobody has done it so far.

I’ll try to sketch the problems than would occur. Suppose you have an event like this, when played, expansion works according to the current convention, 4 synths with instrument \a, freqs 100 to 400 and pannings -1, 0, 1, -1.

// (0)

(
	\instrument, \a,
	\freq, [100, 200, 300, 400],
	\pan, [-1, 0, 1]
)

Now, what should be done with this ?

(
	\instrument, [\a, \b],
	\freq, [100, 200, 300, 400],
	\pan, [-1, 0, 1]
)

(A) According to the current convention we could produce 4 synths with frequencies 100 to 400 and instruments \a, \b, \a, \b.

(B) On the other hand one might think, no, with this writing I want the expansion of type (0), but on two instruments.

Both thoughts are legitimate, but which one should be preferred ? Ok, we could decide for two types, say ‘noteX1’ and ‘noteX2’ but next, if we invent the opportunity to have multiple instruments we would also want to differentiate the inputs for both instruments, which could again be numbers or arrays. This forces to cope with the handling of unpleasant nested lists and their possible ambiguities, the option of array arguments in either instrument makes it yet worse! All in all this looks like a nightmare to implement and I’m not surprised that nobody wants to touch it …

I kinda think by default it should just let flop figure it out (I believe this is what the \set event type is doing). If your arrays are symmetrical (or you only specify 1 instrument) then you’re good. If not, you end up with this:

[ [ a, 100, -1 ], [ b, 200, 0 ], [ a, 300, 1 ], [ b, 400, -1 ] ]

which would result in valid messages sent to the server but may not produce the sounds you want. That’s the point when a custom event type or other technique could come into the picture.