Newbie Question: Changing Pattern during playback

Hi, I’ve just started to learn SC using “The SuperCollider Book” (still in “Beginner’s Tutorial”) and the PDF “A Gentle Introduction To SuperCollider” (just finished the Pattern section). I don’t speak any other computer languages, but have a solid background in analogue synthesizers.

I am interested in realtime manipulation of Patterns, i.e. changing notes and other parameters while the pattern is playing, so that I don’t have to stop it and re-evaluate the code. I figured so much that this can be done using Pfunc. But I have difficulties understanding and using it.

When I evaluate the following two code blocks together, a chord is being played over and over, I can change the numbers in ~notes and re-evaluate just that line to change the chord without loosing the “rhythm”.

~notes = [0, 1, 2, 3];

(
Pbind(
	\degree, Pfuncn({~notes}, inf),
	\dur, 0.5
).trace.play
)

While this allows me to change one chord in real-time, I wonder how I can play the notes in ~notes as a sequence of notes and change these individually in real-time. The trace output from the example above tells me that this array [0, 1, 2, 3] is written into the event stream Pbind creates. This leads me to believe that the result of
Pfuncn({~notes})
is actually this:
[0, 1, 2, 3]
So I figured I can use this array directly as the list argument for Pseq, in the hope that this will now play the notes in succession, instead all of them together. So I wrote:

(
Pbind(
	\degree, Pseq(Pfuncn({~notes}), inf),
	\dur, 0.5
).trace.play
)

Alas, it didn’t work, and the error messages tells me Pseq expected a non-empty collection, whereas I gave it a Pfunc. But when I enclose Pfunc with square brackets to turn it into a collection, I am back at “square one”, because the notes are now played together as a chord again.

Please, can somebody be so kind and help me understand why this doesn’t work and where my thinking went wrong, and how I can accomplish the goal to play the notes in succession and change them in realtime? Thanks much in advance, and sorry for the long & winding post.

Hello and welcome,

it’s one of those questions that come frequently, one solution would be this:

~notes = [1, 2, 3]

(
Pbind(
	\degree, Pn(Plazy { Pseq([~notes]).flatten }),
	\dur, 0.5
).trace.play
)

~notes = [1, 2, 7]

Three other variants for the nearly identical question here:

Also see this thread with the alternative writing with miSCellaneous_lib quarks’s PLx patterns

Greetings

Daniel

1 Like

Thanks much, Daniel! I searched for “pattern realtime manipulation” before asking, but didn’t find the topics you mentioned.

Interestingly, all I needed to do was flattening the array to turn the chord into a sequence:

~notes = [-2, 1, 2, 3];

(
Pbind(
	\degree, Pseq([Pfuncn({~notes})].flatten, inf),
	\dur, 0.5
).trace.play
)

As both your and my version need to finish the currently playing note collection before using a new collection, I wonder about the benefit of using Pn and Plazy. Please, would you mind explaining?

Ha, that’s another variant, that I didn’t mention so far (there are expected to be many others), and - good catch - it’s a hidden one :slight_smile:

Following happens: method ‘flatten’ is - so-called ‘polymorphism’ - defined for a number of classes, among them: Collection and Pattern. You get all definitions in the help if you select ‘flatten’ and do Cmd-D. What happens with Collections is pretty clear from examples:

// flatten one level

[1, 2, [3, [5]]].flatten


// flatten two levels

[1, 2, [3, [5]]].flatten(2)


// general flattening with flat

[1, 2, [3, [5]]].flat

But flatten is also defined for Patterns, it turns a Pattern into a Pflatten pattern - which is unfortunately undocumented !

Now see what happens here: flatten with default arg 1 applied to the arrayed Pfunc gives an Array with a Pflatten as it is applied to all items of the Array (the only one here: the Pfunc). That’s a hidden side-effect that - I assume - the vast majority of SC users would not expect !

[Pfunc { ~notes }].flatten(1)
-> [ a Pflatten ]  // allowed input for Pseq as it's an Array

But now we have a valid argument for the Pseq, which expects Arrays, its repeats arg inf ensures the looping, voila.

The difference between Pfunc and Pfuncn is a minor one, Pfuncn is finite by default.
The Pn + Plazy contruct ensures the looping of the Plazy which is expected to return a Pattern with its Function.

If this all sounds confusing, don’t be worried - it is not straight and you have found and extra-delicate case !

However it also shows that the need for a rather clear way of handling such replacements is reasonable. Pdef and related is a unified way for doing this in main SC, combos with Pfunc and Plazy (and other Patterns that involve Functions) are also fine and PLseq and related are another way.

Hope that helps, best

Daniel

2 Likes

Daniel, thanks again for the explanation!

I was surprised to find out that while flatten is defined for Patterns, flat seems not to be defined for Patterns, as it gives an error when used with Patterns – is this to be expected?

And while we are it: What does it mean that Pattern is formatted with strike-thru in SC help?

I found the SC issue list here, searched for “Pflatten”, and as the search returned no results, I guess I should file a bug for this.

Still I can’t get my head around what a Pflatten really is: How does it differ from an array (as it looks like an array argument for Pseq), how does it differ from a Pattern?

Discovering Pattern in “A Gentle Introduction to SuperCollider” was a “this is great!” moment, while finding out that there is no easily discoverable way of influencing a running pattern in realtime felt like a serious road-bump.

OK, thanks again, now I will go back to reading thru “A Gentle Introduction to SuperCollider” and “The Supercollider Book” in the intended order (well…perhaps with some peeking ahead for Pdef and Plazy :wink: ).

I never encountered a use case for this and hadn’t expected it to be implemented.
Whenever needed it could be written like this:

// rather artificial example
// [x].flat necessary here as an atomic item cannot be flattened / flatted

x = Pseq([1, [2, [[[3, 4], 5], 6]], [7], [[8]] ], inf).collect({ |x| [x].flat }).flatten.asStream;

x.nextN(30)

No decription in help

I think a documentation request would be helpful, thanks.

It’s a subclass of Pattern which takes a Pattern as argument. See its superclass Pclump which performs the inverse operation.

PLx suite solves this problem especially for list replacements but I see that while diving into the (huge) Pattern topic an extension of the language might add even more confusion, that’s why I have mentioned some alternatives.

BTW also don’t miss James’ Pattern Guide in SCDoc.

Please do not file general requests for adding documentation as issues, as it is already known that many classes and methods are undocumented. I will close those issues, as they are not useful in any way for development. File a PR instead :slight_smile: or leave a comment on the general “undocumented classes” issue here[1] if you want it to receive special attention.

[1] https://github.com/supercollider/supercollider/issues/2434

Sorry, I saw this too late, and already filed it as a bug. I will rewrite it as a PR.
EDIT: Done.

Sorry, I think my fault, my recommendation to file it was too general.

Thanks, that makes things a bit clearer for me (and gives me another fun thing to play with :slight_smile: ).

Thanks, on it,
also found “Understanding Streams, Patterns and Events”…

…enough food for thought!

I’m just a beginner here myself, but it seems to me that a pinned FAQ or the like might help for both these instances?

I’m just noticing that this thread has been going on for a month, without anybody mentioning PbindProxy or Pbindef – which are the most straightforward built-in ways to manipulate event patterns on the fly.

Pbindef(\x, \degree, Pseq([0, 2, 4, 2, 3, 1], inf), \dur, 0.25).play;

Pbindef(\x, \degree, Pseq([Pseq([0, 2, 4, 2, 3, 1], 1), Pwhite(-7, 14, { rrand(5, 11) })], inf));

Pbindef(\x, \dur, Pexprand(0.08, 0.8, inf));

Pbindef(\x).stop;

hjh

Not really true, I mentioned them implicitely:

… as Pbindef and PbindProxy are sub- and superclasses of Pdef.

Well… oh-kay… I’m not gonna quite expect a new user to figure out which subclass to use.

(Pdef inherits from EventPatternProxy, not PbindProxy, though.)

hjh