Routines/Pattern basics

Just getting started learning SC using Eli Fieldsteel’s book and have a basic question about routines, patterns, and looping Pbinds. As a simple example, I’d just like to play ascending chromatic scales up 12 notes starting at middle C, and then starting on C#, and then starting on the next semitone up etc. Would the normal, most concise way be to put the Pbind in a Routine loop, or use Pspawner, or some other way, or is there a way to do all of this with just one Pbind with nested patterns?

// Using Routine
(
Routine({
	var startNote = 0;
	  12.do {
		 Pbind(\dur, 0.15, 
  		          \note, Pseries(start: startNote, step: 1, length: 12),
			).play; 
          2.yield;
	  startNote = startNote + 1;
	  };
}).play;
)
// Using Pspawner
(
Pspawner({ | sp |
	var startNote = 0;
	12.do  {
		sp.seq(
			Pbind(*[ note: Pseries(start: startNote, step: 1, length: 12), dur: 0.15]),
		);
		startNote = startNote + 1;
	};
    sp.suspendAll;
}).play
)

With Pspawner, it doesn’t look like I’d need a wait/yield in the loop since that sp.seq seems to block until the Pbind is finished playing? Also, am wondering what the * means and why the Pbind params are specified in an array here versus the Pbind format in the Routine example? I was looking at the Pspawner docs for this format. Thanks for any info!

1 Like

I don’t know if this is the most consise way, but:

(
Pbind(
  \note, Pn(Pseries(0, 1, 12), inf) + Pdup(12, Pseries(0, 1)),
  \dur, 0.15
).play
)

Pn will repeat an entire pattern some number of times (in this case Pseries(0, 1, 12) infinitely).

Pdup will repeat each element of a pattern (in this case Pseries(0, 1)) some number of times (in this case 12, so 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2...)

Add them together to get what you want :slight_smile:

FYI, lots of ways to do this, here is the same thing purely as a Routine with no Patterns:

(
fork {
  var dur = 0.15;
  var startNote = 60;
  
  inf.do { |i|
    var semitone = i % 12; // wrap between 0 and 11, inclusive
    var base = (i / 12).floor; // increment by 1 every 12 notes
    var synth;
    // use s.bind to ensure good timing (see Scheduling and Server Timing helpfile)
    s.bind { synth = Synth(\default, [\freq, (semitone + base + startNote).midicps]) };
    dur.wait;
    s.bind { synth.release };
  };
};
)

It is just a syntax shortcut for expanding the contents of an array into method arguments

f = { |a, b, c| (a + b + c).postln };
// these are identical:
f.(1, 2, 3);
f.(*[1, 2, 3]);

For Pbind this can be nice because of another quirk, which is that arrays can be written as key/value pairs

[note: 5, dur: 1] == [\note, 5, \dur, 1] // -> true

So some people prefer to write

Pbind(*[
  note: 5,
  dur: 1,
])

rather than

Pbind(
  \note, 5,
  \dur, 1
)

but they are identical.

(the Syntax Shortcuts helpfile is a good place to look when you encounter weird syntax stuff like this, also Symbolic Notations helpfile)

1 Like

Hopefully in the next release of SC you can just use keyword arguments Pbind(foo: 1). This works in the current dev.

4 Likes

This specific case is tailor-made for the binary operator adverb .x. x is for cross-product, not that data values are multiplied, but that each value in one set gets paired up with every value in the other set and the total output size is size of a * size of b.

For streams, it works like ornamentation. The left value is the base, and it holds steady until the right-hand stream ends. The right-hand pattern expands and “ornaments” the base value. I’ll use midinote instead; you could do it with \note as Eric did (totally valid) but if I did it here, the left and right patterns would look basically the same = pedagogically more confusing.

\midinote, Pseries(60, 1, inf) +.x Pseries(0, 1, 12)

Basically the same as Eric’s first suggestion, with the advantage that you don’t have to know how many times to Pdup.

hjh

1 Like

Thanks to all for the super helpful responses! If I wanted to do things each time the new transposition starts ( the outer loop basically ) like increase synth params ( e.g. filter resonance ) or increase the Pbind dur, would I still do this with just a Pbind or maybe better in a Routine loop?

You can use Pkey to access previously defined Pbind keys, for instance to share the outer loop (here I’ve created a key called “iteration” that only influences other keys and isn’t directly a synth parameter):

(
Pbind(
  \iteration, Pdup(12, Pseries(0, 1)),
  \note, Pn(Pseries(0, 1, 12), inf) + Pkey(\iteration),
  \dur, 0.15 + (Pkey(\iteration) * 0.1)
).play
)
2 Likes

Ah, interesting, thanks so much! Looks like there’s a lot about Pbinds/patterns I don’t know. All I’ve seen so far are simple examples, is there a good resource for viewing more advanced examples? Also, in general for iteration/nested iteration, is there a preference to use Pbinds or Routines/loops? I can see that the latter might be easier to read (albeit less concise), but that may be because I don’t know the more advanced usage of Pbinds.

1 Like

http://doc.sccode.org/Browse.html#Streams-Patterns-Events>A-Practical-Guide

Depends on the case. One other trick, btw, is Pgate, where one key in a Pbind can set a flag that allows another key to advance.

hjh

1 Like

Thanks @jamshark70 ! For your example using the adverb .x, how could I enhance that to something like Eric’s example where he references the /iteration key, so that I could use it to change dur on each outer loop iteration like he did?

The below wouldn’t work but am curious how I’d fix it…

(
Pbind(
    \iteration, Pseries(0, 1, 12), 
	\note, Pseries(0, 1, inf) +.x Pkey(\iteration),
    \dur, 0.15 + (Pkey(\iteration) * 0.1)
).play
)

I don’t think that’s compatible. But it’s a good case for Pgate:

(
p = Pbind(
	\chromatic, Pn(Pseries(0, 1, 12), inf, \go),
	\iteration, Pgate(Pseries(0, 1, inf), inf, \go),
	\midinote, 60 + Pkey(\chromatic) + Pkey(\iteration),
	\dur, 0.15 + (Pkey(\iteration) * 0.1)
).play;
)

One advantage this way is that you can have a random length of chromatic segment. That’s a bit harder to do with Pdup (as in, I can’t think of a clean way).

(
p = Pbind(
	// now the chromatic segment may be 3-12 notes
	\chromatic, Pn(Pseries(0, 1, { rrand(3, 12) }), inf, \go),
	\iteration, Pgate(Pseries(0, 1, inf), inf, \go),
	\midinote, 60 + Pkey(\chromatic) + Pkey(\iteration),
	\dur, 0.15 + (Pkey(\iteration) * 0.1)
).play;
)

p.stop;

hjh

1 Like

Ah, great thanks, much appreciated!

One other question about the Routine loop in my original example. Of course it’s using the manually set yield val to wait before starting the next Pbind iteration. But if I remove the yield, is there any other way to indicate that I want the Pbind to basically finish before returning control to the loop ( basically to block)? Instead of specifying an arbitrary yield val, I just want the outer loop to only continue when the Pbind finishes its chromatic scale. So if I increase or decrease the dur value, I don’t want to manually figure out how the yield val should change in order for the transpositions to smoothly follow each other. Any thoughts?

(
Routine({
	var startNote = 0;
	  12.do {
		 Pbind(\dur, 0.15, 
  		          \note, Pseries(start: startNote, step: 1, length: 12),
			).play; 
          2.yield;
	  startNote = startNote + 1;
	  };
}).play;
)

I think the easiest way to do this is Pspawner, because it avoids some pattern/stream internals.

(
// always assign the 'play' result to a variable
// so you can stop it later w/o cmd-.
p = Pspawner({ |sp|
	var startNote = 0;
	12.do {
		// .seq means "run this and block the Pspawner thread
		// until the pattern is over"
		sp.seq(Pbind(\dur, 0.15,
			\note, Pseries(start: startNote, step: 1, length: 12),
		));
		startNote = startNote + 1;
	};
}).play;
)

p.stop;

The Routine way requires a little bit of explicit handling of the input event – and it needs to be a pattern:

(
p = Prout({ |inval|
	var startNote = 0;
	12.do {
		inval = Pbind(\dur, 0.15,
			\note, Pseries(start: startNote, step: 1, length: 12),
		).embedInStream(inval);
		startNote = startNote + 1;
	};
}).play;
)

p.stop;

hjh

1 Like

One option that might work could be useing patterns with side effects to release a condvar.

Routine {
   var c = CondVar();
   var done = false;
   Pseq([
      Pbind(... original stuff goes here ...),
      Pbind(\dur, 0, \foo, Pfuncn({ done = true; c.signalAll }, 1))
   ]).play;
  
   c.wait {done};

}.play


On mobile so can’t check this works. Also I can’t remember if the second Pbind terminates, if not you need to add another pattern to it that only fires once.

Edited Pfunc → Pfuncn @jamshark70

Sure – to clarify the difference between the CondVar approach and the ones in my post:

  • In the CondVar approach, the Routine runs as its own thing, and each Pbind gets spawned off as a separate parallel thread – then the controlling Routine is asked to wait until getting a notification via CondVar that it’s time to move ahead. So there are two streams of execution to manage.
    • The second Pbind won’t stop on its own; but using Pfuncn({ done = true; c.signalAll }, 1) would.
    • Also, here, the question is not about a signal at the end of all loop iterations, but about a signal at the end of each iteration. The OP is looking for a routine containing a loop, where each pass through the loop plays a scale.
  • The Pspawner approach has a Routine (more or less) that’s essentially running on top of a Ppar-like engine – but the seq method handles the “wait until inner stuff is done before proceeding” requirement. In this one, the internals are tricky to explain but the usage is straightforward: you ask for them to be played in sequence, and they are.
  • The embedInStream approach just has one thread, and calls the pattern as if it were a subroutine. In terms of program logic, this is the easiest to explain (but does require the inval stuff, which is a bit inscrutable at first glance).

My opinion is that CondVar is overkill here, and the two streams are a bit of unneeded complexity – a bit easier to keep it under one roof as it were.

hjh

Thinking about the question a bit more I noticed you don’t need a routine to achieve this behaviour.

Pbind(
   \dur, 0.1,
   \degree, Pn(Pseries(0, 1, 12), 12) + Pdup(12, Pseries(0, 1, 12))
).poll.play

Otherwise I think all the possible options have been presented :blush:

Thank you all, this has been helpful to understanding how different approaches work.