One Pattern Playing Multiple Synths


#1

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!


Is there any way to play several Pbind together?
#2

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


#3

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.


#4

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

#5

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.


#6

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


#7

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.


#8
(
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
		}
	}
));


#9

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.


#10

gonna bookmark this one


#11

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.