Set Pattern's \dur right away

Hello all,

I feel like I have a very dumb question that I can’t find an answer to: how to set the \dur of a Pbind right away, instead of waiting the full time from the previous trigger? Is there a way to force the Pattern to fetch a new value? Ideally, I’d love a solution with Pfunc, as that’s what I am dealing with in a much more complex use case, which I have stripped down here to have a basic understanding.

(
s.waitForBoot({
	SynthDef(\imp, {
		Out.ar(\out.ir(0), EnvGen.ar(Env([1, 0], SampleDur.ir * 10), doneAction:2).dup * 0.3);
	}).add;
	
	s.sync;
	
	~dur = 3;
	
	d = Pbind(
		\instrument, \imp,
		\dur, Pfuncn( { ~dur }, inf);
	).play;
});
)

//Trigger this in between two impulses...
//How to set it right away instead of waiting the full 3 seconds of the previous one?
~dur = 0.5;

A much longer discussion of this topic over here.

If you want a jump-start, this is roughly the code you need to play a Pbind “yourself” (meaning, ignoring /durs and /deltas, pulling notes, and playing them when you want).

Thanks a lot. Basically, are you suggesting to just handle the triggering of the Events by myself, using whatever means? (manually, or with Routines, Tasks, etc…).

I’m not too excited about this idea, but it’s a good tradeoff for my usecase.

If you don’t care about timing accuracy, you can stop and start the EventStreamPlayer:

~stream = Pbind(\degree, Pseries(0, 1) % 20).asStream;

~player = EventStreamPlayer(~stream, Event.parentEvents.default);
~player.play;
~player.stop;
~player.play;

Your stop and start won’t happen on the same clock that your events are being played on, so you won’t be able to get musically accurate timing, but this may not matter in your case.

Timing is indeed quite important in my use case.I came up with this code, but I’d say it’s not an elegant solution at all:

(
s.waitForBoot({
	SynthDef(\imp, {
		Out.ar(\out.ir(0), EnvGen.ar(Env([1, 0], SampleDur.ir * 10), doneAction:2).dup * 0.3);
	}).add;
	
	s.sync;
	
	~dur = 3;
	
	~condition = Condition();
	
	~routineFunc = {
		loop {
			(
				\type: \note,
				\instrument: \imp,
			).play;
			
			//Reset on change
			fork {
				~condition.hang;
				
				//Stop old one and create new one
				r.stop;
				r = Routine(~routineFunc).play;
			};
			
			~dur.next.wait;
		}
	};
	
	r = Routine(~routineFunc).play
});
)

(
~dur = 0.5;
~condition.unhang;
)

I think you’re roughly correct with your fork-and-condition-hang construction, I use this often. It’s probably a little more flexible to use EventStreamPlayer rather than handle the waits yourself, and just a looping Routine. Here’s a slightly refactored version that might be appealing:

~stream = Pbind(\dur, 4, \degree, Pseries(0, 1) % 20).asStream;
~condition = Condition(false);

~routine = Routine({
	loop {
		"playing next".postln;
		~player !? _.stop(); // stop the old one, if there is an old one
		~player = EventStreamPlayer(~stream, Event.parentEvents.default);
		~player.play();
		~condition.hang;
	}
}).play(TempoClock.default);

So, a call to ~condition.unhang() does your immediate-next-event behavior. You can pass a quant value in to ~player.play() to control exactly when it resumes (e.g. on beat). As long as you call asStream only once, and keep re-using that stream, you should always pick up with the next event.

2 Likes

Thanks a lot, this is a much more elegant and composable solution :slight_smile:

It might be possible to simplify further.

var lastTime;

p = Pbind(... all your stuff here...);

// here's the "play" part
q = Pbindf(
    p,
    \updateTime, Pfunc {
        lastTime = thisThread.beats;
    }
).play;

~resched = { |player, newDelta|
    var stream = player.stream;
    var clock = player.clock;
    player.stop;
    player = EventStreamPlayer(stream, player.event).refresh;
    clock.schedAbs(lastTime + newDelta, player);
    player
};

// to reschedule for 0.5 beats after the previous time:
q = ~resched.(q, 0.5);

IMO this is easier than rearchitecting the entire scheduling mechanism for event streams. (In fairness, it took me years to find this trick. SC’s documentation tends to bias your mind to believe that the stream player should be a persistent object, when in fact it’s disposable. The stream needs to persist but you can pass this stream among multiple players in sequence.)

There’s no compromise about timing accuracy here.

This could be packaged into a class for simpler use.

hjh

2 Likes

Here’s a sketch of a class.

ReschedulingEventStreamPlayer {
	var player;
	var lastTime;

	*new { |stream, event| ^super.new.init(stream, event) }

	init { |stream, event|
		player = EventStreamPlayer(stream.collect { |event|
			lastTime = thisThread.beats;
			event
		}, event);
	}

	// hmm, haven't handled dependent notifications here
	play { |argClock, doReset(false), quant|
		player.play(argClock, doReset, quant)
	}
	stop { player.stop }
	reset { player.reset }
	refresh { player.refresh }

	rescheduleAbs { |newBeats|
		var stream = player.stream;
		var clock = player.clock;
		player.stop;
		player = EventStreamPlayer(stream, player.event).refresh;
		clock.schedAbs(newBeats, player);
	}
	reschedule { |newDelta|
		this.rescheduleAbs(lastTime + newDelta)
	}

	stream { player.stream }
	asEventStreamPlayer {}
	canPause { ^player.canPause }
	event { ^player.event }
	event_ { |event| player.event_(event) }
	mute { player.mute }
	unmute { player.unmute }
	muteCount { ^player.muteCount }
	muteCount_ { |count| player.muteCount_(count) }
}

+ Pattern {
	play2 { arg clock, protoEvent, quant;
		^ReschedulingEventStreamPlayer(this.asStream, protoEvent)
		.play(clock, false, quant)
	}
}

Usage:

p = Pbind(\degree, Pwhite(0, 7, inf), \dur, 2).play2;

p.reschedule(1);

p.stop;

hjh

1 Like

Ha, this is very cool, especially since it doesn’t require additional threading with Condidions. Thanks a lot!