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