Pchain - Several pbinds filtered by a single pbind

Is it possible to filter several pbinds with one single ‘control-pbind’ without duplicating the control-bind?

To exemplify:

// Two simple synthdefs, \a is panned left, \b is panned right
(
SynthDef(\a, {|freq = 220, gate = 1|
	var sig = Pulse.ar(freq) * 0.2;
	var env = Env.adsr.kr(2, gate);
	Out.ar(0, Pan2.ar(sig, -1) * env)
}).add;

SynthDef(\b, {|freq = 220, gate = 1|
	var sig = Saw.ar(freq) * 0.1;
	var env = Env.adsr.kr(2, gate);
	Out.ar(0, Pan2.ar(sig, 1) * env)
}).add
)
 /// The below has the desired output except that the controlpb is running twice - see post window.

(
~controlpb = Pbind(
	\dur, Pseq([0.5, 0.25, 0.5, 0.25], inf),
	\midinote, Pseq([40, 52, 50, 54], inf),
	\postWin, Pstep(Pfunc{"start of pattern".postln}, 2),
	\atk, 0.1,
	\rel, 0.1,
);

~a = Pbind(\instrument, \a); 
~b = Pbind(\instrument, \b);

Pdef(\test, Ppar([
	Pchain(~controlpb, ~a),
	Pchain(~controlpb, ~b),
])).play;
)

/// I was hoping this would do the trick, but it does not work.

Pdef(\test, Pchain(~controlpb, Ppar([~a, ~b]))).play

Pdef(\test).stop

In the above case it doesn’t really matter that the controlpb is running twice but say you used the controlpb to update a counter or call a function without wanting it to be called more than once.

Pchain’s job is to let one event pattern perform its processing on an event obtained from another pattern. Pbind inserts values, so the result is a union of the key-value pairs.

If you’re getting two events, then it must perform its process for both events.

So, one question is, how do you define “running twice”? Consider if the two patterns have a different rhythm or are offset in time, so that their events do not occur simultaneously. How do you determine when the controlpb should “run once” in that case?

Or, another way to ask the same question is: currently the time delta between the A and B events is 0, so it seems obvious that they’re “together.” But what if their time delta is 0.1 second? Or 0.01? Or 0.001? Where is the exact point where they are no longer “together”? Then you realize that the events being “together” isn’t such an obvious concept after all (in which case, SC’s per-event approach is clear and simple).

If controlpb doesn’t fire, what values should the B pattern receive? Because Pbind inserts – if it doesn’t process, then it doesn’t insert.

All of this is about defining the desired behavior, which is really the first step. “It should just run once” is ambiguous, once you start trying to nail down the details.

hjh

What I was hoping to do was this:

Run ~a and ~b and possibly more similar pbinds in parallel with a single controlpb pattern filtering all the parallel binds. ~a and ~b get their freqs and durs from the controlpb. ~a and ~b will always run in parallel, never individually offset and always filtered by controlpb. So in other words what I am after is the layering of several synths with more control that simply specifying \instrument, [\a, \b…], e.g having ~a be a PmonoArtic (which does not take a \instrument key) and ~b being a Pbind (which needs the \instrument key). Also, ~a and ~b can have different legato, outbus etc. keys but will always play the freqs and durs output by the controlpb. I hope this explains the desired behavior.

There would be two ways to go:

  1. Turn the problem on its head: controlpb drives the train, and each output event from this gets split into two or more events. I’m not at the computer now so I can’t quite work out an example, but you could define a custom event that loops through as many player streams as you need, each iteration copying the control event and passing the copy into another Pbind or Pmono stream, before playing the modified copy.

  2. Use Pclutch with a time condition to hold the control event when the logical time increment is 0.

Number 1 seems to me to describe your situation better – one stream of notes is played simultaneously by multiple players.

hjh

Thanks @jamshark70 for the suggestions. Regarding option 1, I have never tried defining a custom event and I am not quite sure how define such an event and how to pass data from the controlpb to the parallel patterns. Would such an approach still include Pchain?

Another way is to collect over the controlpb and write its data to a global variable ~ev, then use a bunch of Pfuncs inside ~a and ~b, like \dur, Pfunc{~ev.dur}. I was initially trying to avoid this solutions but maybe it would be the most practical after all. I have not tested this approach yet and I am not able to predict if such a solution produces new obstacles. I suspect it will work as long as the controlpb is first in the temporal order.

As I mentioned, I was hoping

Pdef(\test, Pchain(~controlpb, Ppar([~a, ~b]))).play

would do the trick, but it does not work. I wonder what really happens when the above line is executed. It sounds like everything works EXCEPT (which is crucial) that the \dur key plays the default value inside ~a and ~b rather than picking up the \dur key from ~controlpb. Could this be the case?

I may be misunderstanding, but I think Pbindf should allow you to do exactly this?

~controlpb = Pbind(
	\dur, Pseq([0.5, 0.25, 0.5, 0.25], inf),
	\midinote, Pseq([40, 52, 50, 54], inf),
	\postWin, Pstep(Pfunc{"start of pattern".postln}, 2),
	\atk, 0.1,
	\rel, 0.1,
);

~a = Pbindf(~controlpb, \instrument, \a); 
~b = Pbindf(~controlpb, \instrument, \b);

~controlpb would now supply ~a and ~b with the key value pairs that are not specified in ~a or ~b.

Other alternative:

Pdef(\test, Pchain(Ppar([~a, ~b]), Pdup(2, ~controlpb))).play

The Pdup solution seems to do the job, thanks @dkmayer. I probably would have never thought of that myself :wink: Now I only get the readout of the controlpb once per cycle = there is only one controlpb running. This will also allow me to use randomness (un-seeded) inside the controlpb.

Thanks to everyone for contributing.

I’ll post an example later… for a variety of “beyond my control” reasons I don’t have time right at the moment.

hjh

Ok, here it is…

(
Event.addEventType(\spray, {
	var event = currentEnvironment;
	~streams.do { |stream|
		// 'next' applied to the incoming event
		// replicates Pchain's behavior
		var ev = stream.next(
			event  // the received controlpb data
			.copy  // don't cross the streams
		);
		// this is necessary! and easy to forget
		// you do not want to 'play' a sub-event of type 'spray'
		// if you do, you can't check out and you can never leave either
		if(ev[\type] == \spray) {
			ev[\type] = \note;
		};
		ev.play
	};
});
)

// usage:
(
~controlpb = Pbind(
	\dur, Pseq([0.5, 0.25, 0.5, 0.25], inf),
	\midinote, Pseq([40, 52, 50, 54], inf),
	\postWin, Pstep(Pfunc{"start of pattern".postln}, 2),
	\atk, 0.1,
	\rel, 0.1,
);

p = Pbindf(
	~controlpb,
	\type, \spray,
	\streams, [
		// Two simple synthdefs, \a is panned left, \b is panned right
		Pbind(\instrument, \a),
		Pbind(\instrument, \b)
	].collect(_.asStream)
).play;
)(
~controlpb = Pbind(
	\dur, Pseq([0.5, 0.25, 0.5, 0.25], inf),
	\midinote, Pseq([40, 52, 50, 54], inf),
	\postWin, Pstep(Pfunc{"start of pattern".postln}, 2),
	\atk, 0.1,
	\rel, 0.1,
);

p = Pbindf(
	~controlpb,
	\type, \spray,
	\streams, [
		// Two simple synthdefs, \a is panned left, \b is panned right
		Pbind(\instrument, \a),
		Pbind(\instrument, \b)
	].collect(_.asStream)  // asStream is necessary!
).play;
)

p.stop;

hjh

@jamshark70 thanks a lot, I will play around with this over the next few days.