Sequence Pbinds with different TempoClocks

Hi all,

I would like to sequence Pbinds with different TempoClocks assigned to them with a pattern that has it’s own TempoClock.

Here’s where I started from:

(
~pb1 = Pbind(
	\dur, 1,
	\legato, 0.1,
	\scale, Scale.minor,
	\degree, Pseq([0,2,1,6,8,7]-7,1)
);

~pb2 = Pbind(
	\dur, 1,
	\legato, 0.1,
	\scale, Scale.minor,
	\degree, Pseq([8,7,4,7,8,1],1)
);
)
(
~bpm1 = 300/60;
~t1 = TempoClock(~bpm1);
Pseq([~pb1,Rest(1),~pb2]).play(~t1);
)

Here all Pbinds and the Pseq are playing at the same TempoClock. This seem to work how I intended it, ie. ~pb1 plays through, then a rest for 1 beat, then ~pb2 plays through, all at 300/60 bpm.

However, when I try to assign the two Pbinds — embedded in Plazy’s — and the Pseq to different TempoClocks, the Pbinds — inside the Plazy’s — seem to lump/cramp together the melody instead of playing each individual note one after the other:

(
~bpm1 = 60/60;
~bpm2 = 500/60;
~bpm3 = 300/60;
~t1 = TempoClock(~bpm1);
~t2 = TempoClock(~bpm2);
~t3 = TempoClock(~bpm3);

a = Plazy({~pb1.play(~t2)});
b = Plazy({~pb2.play(~t3)});
Pseq([
	a,
	Rest(1),
	b]).play(~t1);
)

The result I’m trying to aim for is the following:

(
~bpm1 = 30/60;
~bpm2 = 500/60;
~bpm3 = 300/60;
~t1 = TempoClock(~bpm1);
~t2 = TempoClock(~bpm2);
~t3 = TempoClock(~bpm3);

~task = Task({
	~pb1.play(~t2);
	1.wait;
	~pb2.play(~t3)
}).play(~t1);
)

I’d like to solve this issue not with a Task or Routine, but with patterns. Is there a short way to do this with a pattern?

Thank you,
crystaldreg

As the problem is stated here, no. There’s no way to play a single pattern whose components run literally on different clocks.

TL;DR, you can scale the tempo of each sub-pattern:

(
p = Pseq([
	Pstretch(~bpm1 / ~bpm2, ~pb1),
	Rest(1),
	Pstretch(~bpm1 / ~bpm3, ~pb2)
]).play(~t1);
)

So then ~t2 and ~t3 aren’t needed at all.

The reason why it’s not possible to put them physically on different clocks is: A pattern manufactures a stream – one stream, not multiple streams. When you .play an event pattern, an object (EventStreamPlayer) gets put onto the clock; this object gets the events out of the stream, performs them, and waits the appropriate amount of time until the next event.

One pattern → one stream → one player → one clock.

You can have multiple clocks playing separate streams, but those streams have to be defined by separate patterns and played separately – no way, using patterns, both to integrate and to separate.

hjh

1 Like

Thank you for your answer hjh!

One of the reasons I wanted to avoid Routines and Tasks is because — from what I understand — they are more “time” based ie. 1.wait == wait for 1 second before starting playing ~pb2 regardless whether ~pb1 finished sequencing through the melody.
Unlike patterns, that are — again to my understanding — more “unit” based, ie. ~pb1 sequences through the melody, then when it’s finished, the Rest(1) is executed, and only then ~pb2 starts seqeuncing through:

(
~pb1 = Pbind(
	\dur, 1,
	\legato, 0.1,
	\scale, Scale.minor,
	\degree, Pseq([0,2,1,6,8,7]-7,1)
);

~pb2 = Pbind(
	\dur, 1,
	\legato, 0.1,
	\scale, Scale.minor,
	\degree, Pseq([8,7,4,7,8,1],1)
);
)
( // pattern version:
~bpm1 = 300/60;
~t1 = TempoClock(~bpm1);
Pseq([~pb1,Rest(1),~pb2]).play(~t1);
)
( // Routine version
r({
	~pb1.play(~t1);
	1.wait;
	~pb2.play(~t1);
}).play(~t1)
)

So it seems to me that a Routine or Task doesn’t “care” or know when the ~pb1 sequence is done playing, unlike Pseq. Ie. waiting times with Routines and Tasks need to be always tweaked in a way that it behaves as the “pattern version” in the code example above.
Which — further thinking of it — might make automating the scheduling of such patterns with differing sequence lengths a bit more tricky, right?

However, thanks to your explanation I think understand more the underlying reasons why there’s not shorthand way to solve my issue with patterns.

Hope this makes sense :thinking:
cd

Another option is to use the \tempo key in your patterns. For simplicity I’m using the default TempoClock here:

(
~pb1 = Pbind(
	\dur, 1,
	\legato, 0.1,
	\scale, Scale.minor,
	\degree, Pseq([0,2,1,6,8,7]-7,1),
    \tempo, 300 / 60
);

~pb2 = Pbind(
	\dur, 1,
	\legato, 0.1,
	\scale, Scale.minor,
	\degree, Pseq([8,7,4,7,8,1],1),
    \tempo, 500 / 60
);
)

Pseq([~pb1,Rest(1),~pb2]).play;
1 Like

Correct – because playing a routine forks it off as a separate activity.

What you’re looking for is a way to pause the main Routine, let other things happen, and have those other things signal the routine to resume. This is a very common requirement in computer science – e.g., ask another resource for some information, and wait an unknown amount of time for the reply. That means… it’s a solved problem. People need to do this all the time.

SC implements a basic “condition variable” mechanism. To use it, we need to attach an “on-completion” action to each pattern. Pfset isn’t specifically for that purpose, but it does have a completion action, so we can press into service. The completion action signals the condition to reactivate waiting threads. In the routine, then, a subpattern can be run on any clock, and then give the instruction to wait for completion.

// I've adjusted your clock numbers for future readability
(
~t1 = TempoClock(500/60);  // for pb1
~t2 = TempoClock(300/60);  // for pb2
~t3 = TempoClock(30/60);   // main control
)

( // Routine version
var onEnd = { |pattern, action|
	Pfset(nil, pattern, action)
};

r({
	var cond = CondVar.new;
	onEnd.value(~pb1, { cond.signalAll }).play(~t1);
	cond.wait;
	1.wait;
	onEnd.value(~pb2, { cond.signalAll }).play(~t2);
	cond.wait;
	"done".postln;
}).play(~t3)
)

Patterns run in a routine, though (invisibly).

(
r = Routine { |inval|
	// renumbering tempi to match pb numbers
	inval = Pstretch(~bpm3 / ~bpm1, ~pb1).embedInStream(inval);
	inval = 1.wait;
	inval = Pstretch(~bpm3 / ~bpm2, ~pb2).embedInStream(inval);
}.play(~t3);
)

This is the real routine equivalent of the Pseq version. (Note, though, that embedInStream implies that the behavior will run under control of the upper-level routine, so it can’t go onto a different clock. The CondVar approach allows true forking onto other clocks. AFAIK the only way to run a subtask on a different clock and wait for completion is using some sort of sync mechanism such as CondVar, or a higher-level sync approach based on CondVar.)

hjh

1 Like

PS The \tempo key changes the clock’s tempo, so it also changes the duration of the Rest(1).

hjh

1 Like

Thank you PitchTrebler! It seems like a shorthand and quick option!

Thank you for your answer hjh!

It seems that scheduling events in such a way needs more advanced programming skills and a more in-depth understanding how SC works.
To my knowledge starting a new phrase/motive/material after waiting for an event finishing is standard compositional technique, and also rapidly changing of morphing tempi is quite common in music composition, so I’m in general wonder then how often does this issue come up when it comes to composing with SC.

Not an urgent question, but I’m trying to figure out a way to use SC as a flexible compositional tool/instrument. :slightly_smiling_face:

Well, you’ve been given three ways to do it:

  • Keep them on the same clock, and stretch the durations so that the phrases sound in the desired tempi;
  • Or, keep them on the same clock and change the clock’s tempo;
  • Or, run them on different clocks, with a synchronization technique that is standardized (which you’d have to learn, eventually, in other contexts as well).

Yes, it is a valid compositional need, and it’s possible to implement in SC. So from where I sit, it looks like the question has been answered and you can move ahead with your project.

Couple of other observations:

  • One can use a solution without fully understanding all its details at first – the understanding may come later.
  • Sometimes we get fixated on an approach that may not be the best. A single-clock solution may actually be better for your case!

hjh

1 Like

Thank you for your answer hjh, I was having the same thoughts and exactly that’s usually my problem, that I want to understand all its details and still try to use it.