How could patterns be better?

Hi all!

Patterns are quite a complex system, with some people opting to use routines instead.

I was just wondering if anyone had ever thought about how this could be improved, if at all? Is it a design issue, a documentation issue… something else?

Also, does anyone who is learning patterns right now have any opinions on this? I’ve seen a few questions on patterns recently.

J

2 Likes

This is the kind of question that seems to come up most often around patterns:

// Why is my kick playing at the wrong frequency?

(
SynthDef(\kick, {
    var env = Env.perc(0.01, 0.6).ar(doneAction: 2);
    var sig = SinOsc.ar(\freq.kr(60) * XLine.ar(6, 1, 0.05));
    sig = sig * env * \amp.kr(0.3);
    Out.ar(0, sig ! 2);
}).add;
)

Pbind(\instrument, \kick).trace(\freq).play; // plays middle C

So the issue is that many new users are unaware of the default parent Event and what exactly happens behind the scenes when you .play an Event or Pbind.

One workaround is to use a custom parent (or proto) Event:

(
// ignore only pitch-related keys:
var partials = Event.partialEvents;
~proto = ().putAll(
    // partials.pitchEvent, // uncomment to include default pitch keys
    partials.ampEvent,
    partials.durEvent,
    partials.bufferEvent,
    partials.serverEvent,
    partials.playerEvent,
    partials.midiEvent
)
.parent_(()); // must do this to override defaultParentEvent??
)

// usage examples:
(instrument: \kick).parent_(~proto).play;
Pbind(\instrument, \kick).play(protoEvent: ~proto);

But that’s a lot to ask of beginners, and I think there are simpler solutions. For example, we could offer them a new event type that uses default controls unless the user specifies otherwise:

(
Event.addEventType(\noteDefault, {arg server;
    var synthLib, desc, defaults;
    synthLib = ~synthLib ? SynthDescLib.global;
    desc = synthLib.at(~instrument.asDefName);
    defaults = desc.controlDict.collect(_.defaultValue);
    currentEnvironment.proto = defaults;
    ~eventTypes[\note].value(server);
});
)

// usage examples:
(instrument: \kick, type: \noteDefault).play;
Pbind(\instrument, \kick, \type, \noteDefault).play;

Again, somewhat complicated for a new user to write a new event type, but if we include that event type in the class library or a Quark, or just in the documentation, it is pretty easy to understand the usage.

Another option might be to include something like the following code at the top of the \note event type function:

if(~useDefaultControls ? false){
    var synthLib, desc, defaults;
    synthLib = ~synthLib ? SynthDescLib.global;
    desc = synthLib.at(~instrument.asDefName);
    defaults = desc.controlDict.collect(_.defaultValue);
    currentEnvironment.proto = defaults;
};

Then the user could specify when they want to use default controls like this:

(instrument: \kick, useDefaultControls: true).play;
Pbind(\instrument, \kick, \useDefaultControls, true).play;

Thoughts? Other ideas?

4 Likes

That’s indeed an issue, but broader than the example.

I might restate the issue that you’re illustrating as: It’s not clear to incoming users what is the priority of sets of data. In the example, the expectation is that event values override SynthDef defaults, and SynthDef defaults override parent-event defaults – but the actual behavior is that event values override parent-event defaults, which override SynthDef values.

This is also an issue for \set events when not explicitly listing the control names to set.

It may be that Event has weaknesses when it comes to data inheritance. I don’t have ideas right now about how to redesign it, but I agree, that’s an issue.

Some of this may just be a matter of opinion, or taste. I think it’s fine if some people prefer to use routines.

The question itself is partly about patterns, and partly about events. (PitchTrebler’s example is not really about patterns – it’s entirely about events.)

WRT Events, I’ve heard some users say they don’t trust the “magic” in the default event prototype. Some of that could be documentation – McCartney’s streams-patterns-events documentation gets to events only in sections 4 and 5, while my own Practical Guide to Patterns presents Event as an auxiliary to Pbind. I’m not aware of any documentation that leads users through Event’s features.

(Of “matter of taste” – I think “Event magic” is a strength. I like not having to write degreeToKey in every sequence. I like that data are abstracted away from the action – if I have a pattern and I want to hear it in a VST, change the \type and give it a MIDI target, done, no rewrites of Routine logic. Sometimes criticisms of Events have been stated in exaggeratedly emotional terms, which… well, we’re all entitled to our opinion, but if heated language serves to prejudice incoming users against a really cool technology – I think Events are cool! – that might not be a win.)

WRT patterns themselves, the trouble starts when you need a behavior that doesn’t quite exist as a pattern class already. There are subtle gotchas between Pfunc { }, Prout { }, .collect { }, etc., and it’s not always clear which one to use in which case – the general issue being: it’s not as straightforward as it could be to inject imperative logic into a pattern.

Also, “pull-model” reevaluation of patterns requires delicate workarounds. Somebody worked on this problem once, but I can’t remember right now which class it was.

// push model (normal UGens): the two posted values are always exactly 1.0 apart
(
a = {
	var trig = Impulse.kr(1);
	var rand = TRand.kr(0, 1, trig);
	[rand, rand + 1].poll(trig);
	Silent.ar(1);
}.play;
)

a.free;


// pull model: 'rand' gets pulled twice,
// so the values have no predictable relation
(
var rand = Pwhite(0.0, 1.0, inf);

p = Pbind(
	\type, \rest,
	\r, Ptuple([rand, rand + 1], inf)
).trace.play;
)

p.stop;


// works, but requires manually splitting out the 'rand' op
(
var rand = Pwhite(0.0, 1.0, inf);

p = Pbind(
	\type, \rest,
	\temp, rand,
	\r, Pkey(\temp) + [0, 1]
).trace.play;
)

p.stop;


// same issue with demand units
// (expected, since DUGens are modeled after patterns)
(
a = {
	var trig = Impulse.kr(1);
	var rand = Dwhite(0.0, 1.0, inf);
	Demand.kr(trig, 0, [rand, rand + 1]).poll(trig);
	Silent.ar(1);
}.play;
)

a.free;

hjh

1 Like

I have been using sc since the start of the year, and have been writing a lot using patterns. Here are a couple of stumbling blocks I have run into in this time:

  • The difference between events, patterns and streams, and how to convert between them for a given class is often very confusing. Recently I spent a considerable amount of time working out that to call .next() on a Pbind you have to call .next(()), for example.

  • Patterns being stateless is often not musically useful, and I find myself embedding state in patterns nore often than not. Music composition - especially for algorithmic music - is frequently semantically stateful.

  • I wrote about this in another thread, but when you want to actually write a substantial composition, using patterns can lead to sprawling blocks of code.

  • No easy native support for Event patterns and LFOs | SuperCollider 3.13.0 Help (continuous LFO control section) is surprising to me. @hemiketal gave me a great function to deal with this in the discord, but I would not have been able to work it out myself.

  • Transitioning between patterns in static (not live coded) compositions is really really hard. It’s not straightforward have overlapping patterns where one fades on and another fades in for more than two parts. Something that is fairly trivial in a DAW.

  • Use of patterns with Ndef is frankly bizarre and badly documented.

  • the default event \amp not being 1 is imo a bad idea. This is like if Ableton had a master gain that was set to -6db behind a hidden menu. I dont have an issue with any of the other default parent event keys apart from this one lol.

I think most of these could be handeld with better examples and documentation. I love supercollider so far, and have had a very productive time actually writing and finishing compositions in the program - something I struggled to do with Max - most of this I attribute to using the pattern system.

To me, the documentation for patterns is frequently too technical and too verbose to find what you’re looking for quickly (It doesn’t help that google sucks at indexing the sc help pages, making searching via google difficult), and it seems like there around 10 active supercollider users on the internet who are actually good at using patterns and can answer your questions - and they can’t always be online :stuck_out_tongue_closed_eyes: .

5 Likes

Thanks all for such thoughtful responses! Particularly from @asher as talking coherently about something new is challenging and really useful for improving SC.


Lets keep this thread as an interrogation of problems and broad solutions and deal with the nitty gritty details of implementations/fixes in separate threads.

I’d consider events and streams - or anything inside patterns - to be relevant here. Lets leave NDef aside for now, unless people really want to talk about it.

Here’s a little summary of all things mentioned:

  • Defaults in the parent event - I’ll reply in a new topic @PitchTrebler
  • Relationship between events, patterns and streams.
  • Event documentation.
  • Injecting imperative logic into patterns - this is a big one!
  • Subtle differences between when and where patterns are evaluated - pull/push
  • Better ways to deal with state - or, how to think statelessly?
  • Better documentation on how to organise larger sections with patterns - I could offer some help here.
  • Snappier documentation when describing what things like PSeq (and the likes) do.

Aside from on-going documentation issues, perhaps imperative logic and state are the two big issues? One thing that I’ve found hard is when you try to write something like chords and melody where the chord changes aren’t predefined, as their state needs to be known by the melody pattern. Unfortunately, I can’t think how this could be made easier. Perhaps more of a tree like structure os possible?

1 Like

I agree. I think an important consideration for the documentation should be: what is the audience? Currently, the documentation reads as being written for programmers by programmers. This is fine and should absolutely exist, but if I want to achieve a particular musical effect/compositional technique, I should be able look up the technique, and then find the necessary pattern class/es to achieve it.

E.g. the class documentation for ppar makes no mention of polyphony! Ppar | SuperCollider 3.12.2 Help

1 Like

One issue is that there isn’t a simple map from a musical technique to a pattern, often you need a combination. This means the help page might end up being quite large… Still, I think this is worth doing, particularly if it’s well structured/linked and the user is encouraged to use the table of contents.

As this is quite a large and opinionated undertaking, I wonder if it is possible to set up a user editable wiki? @moderators is this a possibility?

I did see that Jordan was going to write something about this, but… there are a lot of ways to modularize patterns. The pattern library doesn’t mandate deep nesting.

hjh

This seems like more of a technical question than a mod one per se, but GH has wikis and it is possible to make them editable by anyone. The current SC one is mostly project info rather than user doc, so that would be a change. Of course there is also nothing stopping anyone from making an SC wiki where and when they want. If this were to be “official” in some sense that’s another conversation. To me it does sound like a big job and something that would need someone to take it on.

1 Like

Ah I was asking about a discourse (or what ever this forum is called) wiki, I read somewhere you can have community edited one? Gh could work, but the conversation happens here (no discussions).

Ah I see: Editing and creating wiki posts - Using Discourse - Discourse Meta

1 Like

Almost overlooked this.

What about this case?

(instrument: \default, degree: 0).play;

(instrument: \default, degree: [0, 2, 4]).play;

If amp == 1, then forum questions would go like this: “When I play the first event, it sounds nice and clean. When I play the second, it’s distorted. What is going on?”

That is… one could disagree about how much headroom the default Event should allow for, but I think the default situation should not be everything full-scale, all the time, no headroom unless you ask for it. (Pure Data: [osc~ 440] → [dac~] = cover your ears. Max at least has a volume slider at the right but it’s up pretty high by default, so I always have to reach for the slider before hooking up any audio signals.)

(In that sense, it’s another documentation issue: “This is like if Ableton had a master gain that was set to -6db behind a hidden menu” – so, un-hide it, but don’t ignore audio engineering good practices just because the non-fullscale default value wasn’t as visible as one might have liked.)

hjh

1 Like

I do have a question for you about this – does this apply to the Practical Guide to Patterns as well?

I took quite some time about a decade ago to try to provide a more narrative explanation of patterns, but it turns out that for whatever reason, it seems to be significantly under-utilized. (For one example, you said “I spent a considerable amount of time working out that to call .next() on a Pbind you have to call .next(())” – but – the Practical Guide has, in the Pbind chapter, “User does: p.next(Event.new);” – it’s regrettable that it cost you so much time to find that out, but the information was actually there – just poorly indexed, I guess.)

I wonder how many of these documentation issues would be alleviated if people could quickly find the large set of help files about patterns that already exists. (It’s like a 20-helpfile series – took me weeks to do!)

It’s not organized around compositional techniques, at least not in any systematic way, but the Practical Guide does have a few “pattern cookbook” documents.

Contributions of new “cookbook” help files would be very welcome.

hjh

1 Like

Patterns are particular because they’re like a small but rich language. They have many important programming ideas all in one place: non-strict lists, some stateful, others not, functors, and applicative lifting (patterns) of functions and (patterns) of values, etc., and a creamy, well-organized set of language constructs.

Of course, there are some fundamental limitations on how events are organized in time on a conceptual level.

It would not be easy to agree on the order in which a musical cookbook would be more or less didactic.

@jamshark70, your material is excellent, in my opinion.

I don’t know, but I think it’s all there already. Maybe a wiki could host other entry points? (That is, different “styles.”) This would be very effective for separating Patterns as a small language from a musical orientation on how to use them. That’s positive!

EDIT: I think it’s not a good move regarding the “default” values in Events. Now, we may institute some inheritance? I think that’s precisely what to avoid. But that’s just a personal view; I prefer to work without using any of those and with a more transparent code.

Practically, I don’t think it helps much with learning, either.

Maybe one of the problems is that we start using Patterns always inside a grid. Later, the same person will confuse Events, Patterns, etc etc

It would be good if relevant parts were linked from other help files … Since often the same material is covered in different places and the pattern guide is not usually the first to come up in a search. (e.g. if you look up event types: Event types | SuperCollider 3.12.2 Help and Pattern Guide 08: Event Types and Parameters | SuperCollider 3.12.2 Help have similar information but do not link to each other) Even if the pattern guide were linked in the “see also” portion of every event and pattern help file it would probably help a lot?

Could Patterns be improved? I think answering this question depends on an other question: what is their purpose?

I can think about two distinct situations:

  • there’s a common musical technique that should be easy to implement in SC, so let’s make a dedicated tool.
  • computer science allows us to create a tool, let’s implement it, then we can experiment new musical techniques.

I think Patterns are in the second category. Since they are super good for writing scores they look like they belong to the first category, but they don’t.

Why do I prefer to use Routines:

  • current beat is easier to track, that’s just a simple integer that increments and can be reset easily. My point here is: Patterns are easy when manipulating only Patterns, Routines allow to use variables, functions, GUI, etc…
  • Patterns force you to think ‘horizontally’. When writing large Patterns, I spend a lot of time just counting what I wrote to figure out that the 17th \amp parameter corresponds to the 17th \freq parameter, which is not on the beat, so it should be quieter. If I lose my concentration, I have to count again (that’s a simplification). I prefer writing events ‘vertically’, as a succession of notes containing all their different parameters side by side.

To me, this only means that Routines are a better tool than Patterns for what I’d like to do. When it comes to simple patterns like pre-written scores, or little rhythmical motives, I find Patterns awesome: they’re quick to setup, easy to read, randomness is easy, etc. But when it is about modularity, depending on the kind of modularity you need, they can show their limits pretty quickly.

So yeah, what’s the purpose of Patterns? If they don’t have any, I find them awesome, I just recognize they have their limits (which are also their strength), and I don’t see much reasons to change them?

I think this argument somewhat underplays our creative agency in the construction and evolution of the pattern system (or any technology).

Here is some code I whipped up that measure how on beat the current event is (1 for on the beat, 0 for off).

Pbind(
	\dur, Prand([1, 0.25, 0.5], inf),
	\isOnBeat, (Paccumulate(Pkey(\dur)) % 1 - 0.5).abs * 2
).trace.play
Paccumulate implementation

Paccumulate : FilterPattern {
	embedInStream { arg event;
		var stream = pattern.asStream;
		var next = stream.next(event);
		var sum = nil;
		while {
			next = stream.next(event);
			next.notNil;
		}{
			if (sum.isNil){
				event = 0.yield;
				sum = next;
			} {
				event = sum.yield;
				sum = next + sum;
			}
		}
		^event
	}
}

Alternatively, you could use Ptime, but it might be better to have access to the current tempo clock, taking account of when the pattern started and any global changes to tempo. This should be relatively easy to make?? Perhaps it could be called Pbeat… but I agree that there isn’t an obvious way to do this.

We could implement something like this…

Pdef(\myPattern, Plilypond2Pat("a4 af4 <bf ef>2") <> Pbind(\someKey, Pwhite(...)));

In fact, I believe some people who use custom DSL often do something similar.

Perhaps the creation, and interaction with, DSL might also be a good place for improvement?

1 Like

I actually would disagree with that, writing “scores” or to come up with formal structure is way more easy in a DAW.

1 Like

One the reason I said that Patterns are awesome is that, indeed, you can make anything. Even better, you can do anything in several ways. Which I like. However, in your example, you still need to know how both Pkey and Paccumulate work (and that they exist). But, yeah, you’ll always find a way to do whatever you want.

But that wasn’t my point. This was about ‘keys index’ associations:
Can you tell me at first glance which \freq corresponds to the 5 in the \dur key?

theme = Pbind(
	\instrument, \pulsar,
	\dur, Pseq([4, 1, 1, 6, 5, 1, 4, 1, 1] * 2, inf, 7),
	\env, Pseq([
		Pfuncn({ Env([0, 1, 0.4, 0.2, 0], [0.005, 0.5, 0.25, 4], \cubed) }, 1),
	], inf),
	\amp, Pseq([1, 0.75, 0.75, 1] * 0.2, inf, 7),
	\formantRatio, Prand([2, 4, 8, 16], inf),
	\overlap, Prand([0.1, 0.2, 0.4, 0.8], inf),
	\leftAmp, Prand([0.5, 0.6, 0.7, 0.8, 0.9, 1] / 2, inf),
	\rightAmp, Prand([0.5, 0.6, 0.7, 0.8, 0.9, 1] / 2, inf),
	\spread, Prand([0, 0.005, 0.01], inf),
	\whiteAmount, 1,
	\whiteRatio, 0.5,
	\whiteLag, 0.001,
	\powerShape, 0.75,
	\freq, Pser([
		scale[7],
		scale[6], scale[4], tritone,
		scale[4], tritone,
		scale[4],
		scale[6], scale[4]
	] * 8, inf, 7),
	\out, 0
);

Maybe I shouldn’t use Pbinds like that, but that’s how I do. @dietcv said that I should use a DAW to do this instead, but I find Prand (and other random patterns) better than UIs to implement randomness.


While redacting this message, I figured out that I might not have a representative usage of Pbinds after all, so let’s ignore those messages. IMO, Pbinds are a great introduction to SC, and this ‘purpose’ distinction might help beginners getting started. I’m probably wrong, but I think that if I want to achieve a particular musical effect/compositional technique and the documentation reads as being written for programmers by programmers are implying this distinction.

Oh I agree this is an issue… but perhaps this could be solved by introducing a DSL or some other pattern construct that lets you express notes and durations together, perhaps it could look like this…

(PUnpack is made up)

theme = PUnpack( 
	[\freq, \dur],
	[
		[scale[7], 4],
		[scale[6], 1],
		[scale[4], 1],
		[tritone, 6],
		[scale[4], 5],  // Now finding this is trivial
		[tritone, 1],
		[scale[4], 4],
		[scale[6], 1],
		[scale[4] 1],
	]
) <> Pbind(
	\instrument, \pulsar,
	\env, Pseq([
		Pfuncn({ Env([0, 1, 0.4, 0.2, 0], [0.005, 0.5, 0.25, 4], \cubed) }, 1),
	], inf),
	\amp, Pseq([1, 0.75, 0.75, 1] * 0.2, inf, 7),
	\formantRatio, Prand([2, 4, 8, 16], inf),
	\overlap, Prand([0.1, 0.2, 0.4, 0.8], inf),
	\leftAmp, Prand([0.5, 0.6, 0.7, 0.8, 0.9, 1] / 2, inf),
	\rightAmp, Prand([0.5, 0.6, 0.7, 0.8, 0.9, 1] / 2, inf),
	\spread, Prand([0, 0.005, 0.01], inf),
	\whiteAmount, 1,
	\whiteRatio, 0.5,
	\whiteLag, 0.001,
	\powerShape, 0.75,
	\out, 0
);

Point being, this is really an issue of how the data is structured, which can be changed, or at least, potentially mitigated somehow.


Edit: just realised you can already do this, but it is slightly more verbose…

theme = Pseq([
	Pbind(freq: scale[7], \dur: 4),
	Pbind(freq: scale[6], \dur: 1),
	Pbind(freq: scale[4], \dur: 1),
	Pbind(freq: tritone, \dur: 6),  // Now finding this is trivial
	Pbind(freq: scale[4], \dur: 5),
	Pbind(freq: tritone, \dur: 1),
	Pbind(freq: scale[4], \dur: 4),
	Pbind(freq: scale[6], \dur: 1),
	Pbind(freq: scale[4], \dur: 1),
], inf) <> Pbind(
	\instrument, \pulsar,
	\env, Pseq([
		Pfuncn({ Env([0, 1, 0.4, 0.2, 0], [0.005, 0.5, 0.25, 4], \cubed) }, 1),
	], inf),
	\amp, Pseq([1, 0.75, 0.75, 1] * 0.2, inf, 7),
	\formantRatio, Prand([2, 4, 8, 16], inf),
	\overlap, Prand([0.1, 0.2, 0.4, 0.8], inf),
	\leftAmp, Prand([0.5, 0.6, 0.7, 0.8, 0.9, 1] / 2, inf),
	\rightAmp, Prand([0.5, 0.6, 0.7, 0.8, 0.9, 1] / 2, inf),
	\spread, Prand([0, 0.005, 0.01], inf),
	\whiteAmount, 1,
	\whiteRatio, 0.5,
	\whiteLag, 0.001,
	\powerShape, 0.75,
	\out, 0
);
1 Like