Unfixed Pbind duration

Hello,

Is it possible to have the next event of this Pbind played only after the synth has done playing the first event instead of having the \dur determined in the Pbind itself ?

p = Pbind(
    \instrument, \mysynth,
    \param1, Pseries(0, 1, 8),
).play;

Thanks,

As far as I know, playing Pbinds requires durations to be specified within the played pattern. The easiest workaround that I can imagine is to use a Routine to create Synths, and then waiting for every synth to be freed using waitForFree:

// a synthdef for a single percussive sound, with random duration
SynthDef(\randomDur) {|out = 0, freq=440, amp = 1|
	var env = Env.perc(0.01, Rand(0.1,1), amp).ar(Done.freeSelf);
	Out.ar(out, SinOsc.ar(freq) * env);
}.add;

// a function that takes a Pbind and uses each event to create synths, waiting for each synth to be freed before moving on to the next event
~playPbindWaitForSynth = { |argPattern|
	var eventStream = argPattern.asStream;
	var event;
	fork{
		while { event = eventStream.next(()); event.notNil } {
			Synth(event.instrument, event.asKeysValuesPairs).waitForFree;
		}
	}	
};

// calling our function with parameters from a Pbind
~playPbindWaitForSynth.(Pbind(
	\instrument, \randomDur,
	\freq, Pseq(440 * [1, 5/4, 3/2, 15/8]),
	\amp, Pseq([-80, -60, -40, -20].dbamp),
));

Here we are using Pbinds only as streams of parameters for our sequence of synths. Sadly, we are loosing all the other good functionalities of Pbinds, like multi-synth expansion, different event types, automatic conversions of, say, midinotes to freq values, etc… which would have to be re-implemented in our function if you need them.

1 Like

Nope!

You can absolutely generate events from a Pbind within a Routine and play them, while keeping timing under control of the Routine.

There are a few moving parts here and it’s easy to confuse their boundaries.

  • Pbind: Event data template – a definition only.

  • Event stream – executes the template. This is completely independent of timing. You can next an event stream based on a Pbind at any time, in any context.

  • Event:play – this is responsible for the “multi-synth expansion, different event types, automatic conversions.” If you have a completed event, you can .play it at any time, in any context.

  • EventStreamPlayer – gets events from an event stream, plays them, and schedules the next event in the sequence. This is the normal context for “running a Pbind” but the fact that it’s the normal context doesn’t mean it’s required.

It’s 100% supported to strip event stream next and event play (with all the benefits of playing an event) away from EventStreamPlayer’s automatic timing.

hjh

1 Like

This is a common request - good advice from @jamshark70.

It’s probably easier to experiment with this than one might expect… The core function in EventStreamPlayer, where new Events are pulled and the ESP is rescheduled, is prNext. The implementation is pretty readable… in particular, you can see where the next time is calculated, or where we bail out if we have a nil duration:

  nextTime = outEvent.playAndDelta(cleanup, muteCount > 0);
  if (nextTime.isNil) { this.removedFromScheduler; ^nil };
  nextBeat = inTime + nextTime;	// inval is current logical beat
  ^nextTime

Observe that, in general, Routine's can easily be restarted after a nil is returned:

r = Routine({ 
	[1, 1, nil, 2, 2].do {
		|i|
		i.postln;
		i.yield
	}
});    

r.play; // pauses after nil
r.play; // run this to begin playback again

If you want to experiment with custom scheduling / playback, just subclass EventStreamPlayer and override prNext to provide your own behavior. For example, you might try to allow events to use a Condition as \delta value, which could look something like this:

// Assuming we return an Event like: (delta: ~resume = Condition(false))
// We could then resume by doing ~resume.unhang() elsewhere.

	prNext { arg inTime;
		var nextTime;
		var outEvent = stream.next(event.copy);
		if (outEvent.isNil) {
			streamHasEnded = stream.notNil;
			cleanup.clear;
			this.removedFromScheduler;
			^nil
		}{
			nextTime = outEvent.playAndDelta(cleanup, muteCount > 0);
			if (nextTime.isKindOf(Condition)) {
				fork {
					nextTime.wait();   // wait for condition
				        nextTime = thisThread.beats; // reset time
					routine.play(); // and resume our routine....
                                };
				^nil  // This will stop our routine from resuming on the clock
			} {
				if (nextTime.isNil) { this.removedFromScheduler; ^nil };
				nextBeat = inTime + nextTime;	// inval is current logical beat
				^nextTime
			}
		};
	}

Of course, you can detect anything you want in your outEvent in order to do the “pause” behavior - you might have a special key, etc. I haven’t tested this, but I hope it’s a place to start for anyone that wants to experiment - please post if you have any interesting success (or failure)!

1 Like

just subclass EventStreamPlayer and override prNext to provide your own behavior

How to instruct the pattern to use the new subclass ?

p = PBind(...)
~myplay={ | pattern, clock, protoEvent, quant | 
	MyEventStreamPlayer(pattern.asStream, protoEvent).play(clock, false, quant)
	};

~myplay.value(p);

Is there a neater way ?

You could always do an override like:

+Pattern {
	play { arg clock, protoEvent, quant, class=EventStreamPlayer;
		^class.new(this.asStream, protoEvent).play(clock, false, quant)
	}
}

Then you’d get:

p.play(class:MyEventStreamPlayer)

If you always want your pause-able stream (I don’t see that it would hurt anything to skip using ESP altogether) you could just point the override directly to your new class - or, don’t subclass and instead just override the prPlay method on ESP directly.

It’s all a matter of preference - I sometimes avoid overriding core things because I tend to forget about them, and then discover bugs I introduced several years down the road :).

1 Like

After some thought, I wrote up a longer discussion last night on the topic of manually advancing pattern-based compositions. Advancing patterns by hand (or: unfixed pattern duration)

True, regarding the dangers of overriding – now this might sound trivial, but it’s practical – one can always define a playX method, where X can be any suffix.

2 Likes

I came up to this solution, mixing all the solution bits proposed in this thread.
Don’t know if is well written, but it seems robust. I’ve tested it also with multichannel expansion.

WaitEventStreamPlayer : EventStreamPlayer {
	prNext {
		arg inTime;
		var nextTime;
		var outEvent = stream.next(event.copy);
		var nodes;
		if (outEvent.isNil) {
			streamHasEnded = stream.notNil;
			cleanup.clear;
			this.removedFromScheduler;
			^nil
		}{

			nextTime = outEvent.playAndDelta(cleanup, muteCount > 0);
			//"waiting on %".format(outEvent[\id]).postln;
			// pre-registering the nodes
			nodes=outEvent[\id].asArray.collect(
				{ |nodeId|
					var node=Node.basicNew(nodeID: nodeId);
					NodeWatcher.register(node,true); // needed to get a valid isPlaying
					node;
			});
			// waiting per node
			nodes.do(
				{ |node|
					// postf("Node: %, isPlaying: %\n",node.nodeID,node.isPlaying);
					if (node.isPlaying) {
						node.waitForFree;
					// } {
					// 	"\tnot playing".postln;
					}
			});
			// "done".postln;
			routine.play;
			^nil
		};
	}
}

+Pattern {
	playAndWait{
		arg clock, protoEvent, quant;
		^(WaitEventStreamPlayer.new(this.asStream, protoEvent).play(clock, false, quant));
	}
}