How could patterns be better?

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

This is also a big deal for me. Funny enough, I ended up using routines transition between different sections, changing the Pdefn. Maybe it’s not the easiest way to do this but patterns had so many complications I just didn’t want to deal with them anymore.

~iterCount = 0;
~createGroups.();
~createSrcFaders.();
~createSynthFaders.();
~initGrps.();
~setSectionPdefn.(~s1_evnt.(expBuf: ~expBuf, exps: ~exps)); // Sets the Pdefns for the current section
~initProuts.(event: ~s1_evnt.(expBuf: ~expBuf, exps: ~exps));

~initPatterns = Routine({
	var sectionDur, fltScl;
	loop{
		if(3 == ~iterCount) // The Pdefn for the next section have to be initalized at the previous iteration
		{
			~setSectionPdefn.(~s1b_evnt.(expBuf: ~expBuf, exps: ~exps));
			~initProuts.(event: ~s1b_evnt.(expBuf: ~expBuf, exps: ~exps));

			"Stage 1b".postln;
			
			~atkVar.wait; // Wait until current iteration ends
		};
		if(4 == ~iterCount){ // Trigger the new Pbinds
			~bell = ~bellTrigger.(event: ~s1b_evnt.(expBuf: ~expBuf, exps: ~exps), time: ~atkVar).play(quant:0);
			~fluteScaleAcc = true;
			~fltScl = ~fluteScaleTrigger.(~s1b_evnt.(expBuf: ~expBuf, exps: ~exps)).play(quant:0);
			~fluteAccAcc = true;
			~fltAcc = ~fluteAccTrigger.(~s1b_evnt.(expBuf: ~expBuf, exps: ~exps)).play(quant:0);
			~glissAcc = true;
			~gl = ~glissTrigger.(~s1b_evnt.(expBuf: ~expBuf, exps: ~exps)).play(quant:0);
			~atkVar.wait;
		};
		0.1.wait;
	};

});

I also wanted to set limited duration for some patterns and the duration had to change at every iteration. I found Pfindur and I though, “well, yeah that’s nice” until I discovered I cannot put Pseq as a findur. So my work around was to have a function to generate a Pfindur at each iteration like this:

~fluteAccP =  {
	arg findur = ~atkVar;

	["findur",findur].postln;

	Pfindur(findur,
		Pbind(
			\instrument, \fluteAcc_waveguide,
			\freq, Pdefn(\fluteAccFreq),
			//\strum, 1,

			\dur, Pdefn(\fluteAccDur),
			\time, Pdefn(\fluteAccTime),//Pwhite(2.0, 4.0),
			\ibreath, Pdefn(\fluteAccBreath),
			\amp, 0.4,
			\dBamp, 0,

			\out, ~fluteAccBus,
			\group, Pdefn(\fluteAccGrp),
		);
)};

And I have another function to trigger those functions at each iteration:

~fluteAccTrigger = {
	arg event, time = ~atkVar;
	var flAcc;
	Prout({
		loop{
			~atkVar.postln;
			if(~fluteAccAcc)
			{
			Pdefn(\fluteAccDur, ~accel.(time: time, x: event[\fluteAccx], startHi: event[\fluteAccstartHi], startLo: event[\fluteAccstartLo]));
				flAcc = ~fluteAccP.(~atkVar).play;
				//"Acc ON".postln;
			}
			{Pdefn(\fluteAccDur, event[\fluteAccDur]);
				flAcc = ~fluteAccP.(~atkVar).play;
			};
			~atkVar.wait;
			flAcc.stop;
	}});
};

Did you see what happened there? I ended up using a Prout! Does this approach count as using patterns? On the outside yes. But I feel like I ended up using a routine after all, to do all of this and I could just use a routine instead, from the beginning and maybe it wouldn’t need so many functions. And if there was a way to do this with patterns in an easier way, I just couldn’t find it.

1 Like

This is how I have been doing cross fades

Pdef(\v1, Pbind(\dur, 0.2, \foo, Pwhite()));

Pdef(\v2, Pbind(\dur, 0.2, \bar, Pwhite()));

~crossFade = { |a, b, hold, transition|
	Ppar([
		Pseq([
			Pfindur(hold + transition, a),
		]) <> Pbind(\amp, Penv([1, 1, 0], [hold, transition])),
		Pseq([
			Pfindur(hold, Pbind(\dur, Rest(hold))),
			b,
		]) <> Pbind(\amp, Pseq([Penv([0, 0, 1], [hold, transition]), Pn(1)]))
	])
};

Pdef(\player,
	~crossFade.(Pdef(\v1), Pdef(\v2), hold: 4, transition: 2)
);

Pdef(\player).trace.play

It has the same issue with not being able to set the durations with pattern due to Pfindur.

1 Like

This sort of works but can be much improved

Pdef(\v1, Pbind(\dur, 0.2, \freq, 300, \foo, Pwhite()));
Pdef(\v2, Pbind(\dur, 0.2, \freq, 400, \bar, Pwhite()));

~crossFade = { |a, b, hold, trans|
    var tE = ((hold + trans) / 0.2).round;
    var cE = (trans / 0.2).round;
    
    Ppar([
        a <> Pbind(
            \amp, Pseq([
                1.0.dup(tE - cE),
                Pseries(1, -1/cE, cE)
            ].flat)
        ),
        b <> Pbind(
            \amp, Pseq([
                0.0.dup(tE - cE),
                Pseries(0, 1/cE, cE),
                Pn(1)
            ].flat)
        )
    ])
};

Pdef(\player,
    ~crossFade.(Pdef(\v1), Pdef(\v2), 2, 4)
);

Pdef(\player).trace.play;

There is also a fadeTime instance variable in EventPatternProxy / Pdef that allows you to do this conveniently, as long as your synth has an amp argument.

1 Like

Yea, I forget. Thanks. ))

Anyway, the Pchain thing can be used for something else, not just the amp values

Can this be used to cross fade patterns inside of a pattern?

1 Like

This seems to work pretty well? I couldn’t work out how to get Pbindef working with Pmono if anyone’s got any ideas.

(
// fade between two Pdefs
Pspawner({| sp |
    Pdef(\p1).fadeTime = 16;
    'a'.postln;
    a = sp.par(
        Pdef(\p1,
            Pbind(
                \instrument, \default,
                \dur, 1/2
            )
        )
    );

    sp.wait(4);
    Pdef(\p1,
        Pbind(
            \instrument, \default,
            \dur, 1/2,
            \degree, 3
        )
    )
    
    sp.wait(4);
    //can also use pbindef
    Pbindef(\p1, \degree, 3).quant = 0.0;

}).play(t);
)

for pmono:

(
    SynthDef(\driftingSines_mono, {
        var sig;
        var lfo = LFTri.ar(\lfoFreq.kr(0.01), 1).linlin(-1, 1, 0, 1);
        var freq = \freq.kr(404);
        var freqs = ([freq, freq/2, freq/5, freq/7] + LFNoise2.kr(0.1 ! 4, \pitchDev.kr(15)));
        var amps = ([0.4, 0.3, 0.2, 0.5] + LFNoise2.kr(0.1 ! 4, 0.1));
        var q = 0.8;
    
        sig = Blip.ar(freqs, \numHarm.kr(0)) * amps;
        sig = BLowPass4.ar(sig, (5000 * lfo), q);
        sig = Splay.ar(sig);
        sig = LeakDC.ar(sig).sanitize;
    
        sig = sig * 0.25;
        sig = sig * \gain.kr(0).dbamp;
        sig = sig * \amp.kr(1);
        Out.ar(\out.kr(0), sig);
    }).add;

//fade between two pdefs with pmono (must specify dur key)
Pspawner({| sp |
    Pdef(\p1).fadeTime = 8;

    'a'.postln;
    a = sp.par(
        Pdef(\p1,
            Pmono(\driftingSines_mono, \dur, 0.1)
        )
    );

    sp.wait(8);
    Pdef(\p1,
        Pmono(\driftingSines_mono, \freq, 404, \dur, 0.1)
    )    
}).play(t);
)

AFAIK it can’t be done directly. You need an access point to the Pbindef separate from the chained-on Pmono:

(
Pbindef(\source,
	\degree, Pwhite(-7, 7, inf),
	\dur, 0.25
);

Pdef(\mono_ify, Pmono(\default, \dummy, 1) <> Pbindef(\source));
)

Pdef(\mono_ify).play;

Pbindef(\source, \degree, Pn(Pseries({ rrand(-7, -3) }, 2, { rrand(3, 7) }), inf));

Pdef(\mono_ify).stop;

hjh

1 Like

Yea, different things for me, too, but that’s more how you look at things. .fadetime makes more sense for live coders, and I happen to have done other things, but not much of that.

But there isn’t an ‘idea’ yet about what is missing and what cases we are looking for. Then, how to decide on implementation details, etc.

But it’s very different from .fadetime, not just because there are concretely different kinds of operations but also because they will probably be used in very different situations, which is equally important.

For fades, we also have the classes PfadeIn and PfadeOut. They are undocumented but pretty self-explanatory. I have this class PfadeInOut that combines PfadeIn, PfadeOut, and Pfindur:

// save this in Platform.userExtensionDir:
PfadeInOut : FilterPattern {
    *new {|pattern, fadeInTime = 1.0, holdTime = 0.0, fadeOutTime = 1.0, tolerance = 0.0001|
        var res = PfadeIn(pattern, fadeInTime, 0.0, tolerance);
        res = PfadeOut(res, fadeOutTime, fadeInTime + holdTime, tolerance);
        ^res.finDur(fadeInTime + holdTime + fadeOutTime);
    }
}

And a lot of my code ends up looking like this:

p = Pbind(\dur, 0.2, \note, 0).trace;
q = Pbind(\dur, 0.2, \note, 3).trace;

// add fades:
p = PfadeInOut(p, fadeInTime: 2, holdTime: 1, fadeOutTime: 2);
q = PfadeInOut(q, fadeInTime: 2, holdTime: 1, fadeOutTime: 2);

Ptpar([0, p, 2, q]).play;

Or, if you prefer to use functions instead of adding a new class, the same can be achieved like this:

(
~fadeInOut = {|pattern, fadeInTime = 1.0, holdTime = 0.0, fadeOutTime = 1.0, tolerance = 0.0001|
    var res = PfadeIn(pattern, fadeInTime, 0.0, tolerance);
    res = PfadeOut(res, fadeOutTime, fadeInTime + holdTime, tolerance);
    res.finDur(fadeInTime + holdTime + fadeOutTime);
};

p = Pbind(\dur, 0.2, \note, 0).trace;
q = Pbind(\dur, 0.2, \note, 3).trace;

// add fades:
p = ~fadeInOut.(p, fadeInTime: 2, holdTime: 1, fadeOutTime: 2);
q = ~fadeInOut.(q, fadeInTime: 2, holdTime: 1, fadeOutTime: 2);

Ptpar([0, p, 2, q]).play;
)
2 Likes

What are your attempts of crossfading between sets of params instead of just amps?

1 Like

You must add to the Pbind that “filters” the original pattern. That is, can be anything.

It can, for example, change other aspects affecting timbre or reverberation.

But it is hard to frame your question into something

I would probably use envelopes for that. Something like this:

(
~paramSets = [
    (time: 0, note: -4, pan: -1),
    (time: 2.5, note: 4, pan: 1),
    (time: 4, note: 0, pan: 0)
];

~envPairs = ();
~paramSets.do{|paramSet|
    (paramSet.keys - \time).do{|key|
        ~envPairs[key] = ~envPairs[key].add([paramSet[\time], paramSet[key]]);
    };
};

~crossFade = Pbind(*
    ~envPairs.asPairs.collect{|item, i|
        if(i % 2 == 1) {Env.pairs(item)} {item}
    }
);

p = Pbind(\dur, Pseq([1, 2, 1], inf) / 8) <> ~crossFade;

p.play;
)
1 Like

Hi Jordan (OP).

I love Patterns because it is both what I use every day to make music with but it is also something I can get anyone started with in an hour. I think Patterns strikes the perfect balance between power and elegance. Easy to get started with, but takes a lifetime to master. So many interesting ways to combine Pattern objects to get different behaviors. Infinite use of finite means.

I do not not know who you are. Are you a dev who is maybe thinking about adding or changing something in Patterns?

I personally would enjoy it if some gotchas could be dealt with somehow. Not for my sake, I can mostly remember to work around them, but for new users who are running into them within the first hour of using Patterns. What I mean here is stuff that makes the pattern stop playing. I know I should try to give an exhaustive list of what I specifically mean, with code examples, but I can’t do that now for entirely legitimate reasons.

My dream for Patterns broadly is to make it more robust. I would like to see it more likely to keep on truckin’, no matter what. I think anything that halts a running Pattern would benefit from another looking at.

The tricky thing is that what stops a Pbind is also what allows a Pseq or Prand or Pfsm made of Pbinds to advance (the “words to phrases” chapter of the Pattern Guide), and this sequence-ability is part of the brilliance of patterns.

I’ve run into the same problem in my live coding system – inject the wrong pattern into a parameter and it can shut down the owner process. I ended up handling that by writing my own alternative to PbindProxy, which resets child patterns when they terminate for any reason. I was ok with this because I use it in a specific place where this behavior makes sense, but if it were the global default Pbind behavior, it would break sequences of event patterns, so I wouldn’t advocate it as a general solution.

Perhaps a variant of Pbind to use at the top level would help (though that’s a lot of documentation to edit).

hjh

Personally, I love Patterns and I think they are one of the strengths of SC. But as you say, Patterns are a huge topic and – for that very reason – a didactic challenge.
James’ Pattern Guide is a great contribution to SC doc. Indeed, as mentioned, it doesn’t pop up immediately in the help system. E.g., I still have in mind the title “A Practical Guide to Patterns” under which it was once published, but under this name it isn’t found in SC doc.

There’s also “A Gentle Introduction to SuperCollider” by Bruno Ruviaro, written with the intention to even start learning SC with Patterns in mind as early as possible:

Eli Fieldsteel’s YT tutorials also contains Pattern videos

The miSCellaneous_lib quark contains some general Patterns tutorials and special purpose Pattern classes:

From miSCellaneous_lib overview:

  1. General tutorials: “Event patterns and LFOs” contains an overview of related LFO-control setups with event patterns. “Event patterns and Functions” is treating requirements of the control of EventStreamPlayers with VarGui. “Event patterns and array args” focusses on passing arrays to synths with patterns.

  2. “PLx suite”, dynamic scope variants of common Pattern classes for convenient replacement. Can be used for shorter writing of Pbinds to be played with VarGui and / or live coding, see “PLx and live coding with Strings”. PLbindef and PLbindefPar as subclasses of Pdef allow replacement of key streams in shortcut pseudomethod syntax.

  3. PSx stream patterns, Pattern variants that have a state and can remember their last values. Can be used for recording streams and event streams (PS), data sharing between event streams (PSdup), comfortable defining of subpatterns with counted embedding, defining of recursive (event) sequences (PSrecur) and building loops on given Patterns/Streams with a variety of options, also for live interaction (PSloop).

  4. Event pattern classes for use with effects: PbindFx can handle arbitrary effect graphs per event, PmonoPar and PpolyPar follow the Pmono paradigm. The tutorial kitchen studies contains the documented source code of a fixed media piece using PbindFx for granulation.

  5. Further Pattern classes: PlaceAll, Pshufn, PsymNilSafe. PSPdiv is a dynamic multi-layer pulse divider based on Pspawner.

  1. EventShortcuts, a class for user-defined keywords for events and event patterns. The tutorial “Other event and patterns shortcuts” collects some further abbreviations, e.g. functional reference within events and event patterns, similar to Pkey.

  2. An implementation of Xenakis’ Sieves as class and pattern family, see “Sieves and Psieve patterns” for an overview and examples.

Alright I’ve got something here that I wonder if it maybe could be handled differently.
Have you ever accidentally Pkeyed something which isn’t there?

For example:

( // this thing is silent when first evaluated as is
s.waitForBoot{
	SynthDef.new(\mks, {
		arg dur, attack=0.01, release=1.0,
		t_gate=1, out, freq=440, cutoff=5500,
		rq=1, pan=0.0, amp=0.5;
		var env = EnvGen.kr(Env.perc(attack, release), t_gate, timeScale: dur, doneAction: 2);
		var sig = DPW3Tri.ar(freq: freq, mul: env);
		sig = RLPF.ar(sig, cutoff.clip(20.0, 20000.0), rq.clip(0.0, 1.0));
		sig = Pan2.ar(sig, pan);
		Out.ar(out, sig * amp);
	}).add;

	s.sync;

	Pdef(0,
		Pbind(*[
			instrument: \mks,
			freq: 64 * Plprand(1, 5),
			cutoff: Pkey(\freq) * 4,
			//dup: Plprand(5, 11), // uncomment me and eval again
			dur: 1 / Pdup(Pkey(\dup), Plprand(5, 11)),
		])
	).play;
}
)

This kind of thing fails silently on my machine. What do you think James? Anything to be done here?

I think that is the wrong way to think about it. The whole pattern system is built on the concept ( to my understanding at least) that any key returning nil will stop the pattern and in this case Pkey(\dup) is undefined therefore returning nil. So I wouldn’t say it fails silently, rather it does exactly what patterns are supposed to do - stop when it encounters a nil value. Since patterns can be combined in any number of ways, there is no way of knowing before hand if Pkey(dup) will return nil or not. I could for instance pchain your Pdef with something that delivers the Pdup key:

(Pdef(0,
		Pbind(*[
			instrument: \mks,
			freq: 64 * Plprand(1, 5),
			cutoff: Pkey(\freq) * 4,
			//dup: Plprand(5, 11), // uncomment me and eval again
			dur: 1 / Pdup(Pkey(\dup), Plprand(5, 11)),
		])
) <> Pbind(\dup, Plprand(5, 11))).play;

Yes. I’ll go along with that. My memory of this was that it made such a bad hiccup that I had to reboot the interpreter. That is not happening, so. Patterns is great and I’m having a hard time trying to break things to have code to discuss here. Will try again.

after playing with patterns a lot. I don’t think any kind of xoxoxoox type of thing that mimics a drum machine style is any good. The best thing about Patterns is the nesting. The weird things you can do. Not basic mimicking of old machines.
Let the system be itself and grow with it. Instead of shoe horn some other thing like trying to be a cirklon