Using variables with patterns

Hey. Kinda new to patterns and stuff.

I’m defining a pattern, and would like to modify it on the fly. It goes like this:

~update = {|steps=8, beats=3, offset=0, size=4, grow=1|
	Pdef(\bass, Pmono(\default,
		\dur, Pbjorklund2(beats, steps, offset: offset) * t.beatDur * 4 / steps,
		\degree, Pn(Pfin(beats, Pseq(Array.series(size, 0, grow), inf))),
		\scale, Scale.dorian,
	));
};

I use this function to define my pattern because some parameters can be used in several places in the pattern definition. However, this is a bit clunky, because I have to store and resend each parameter everytime I want to alter something.

Does this code smell? Is there a simpler way to do this? Is there a way to define a pattern with a bunch of parameters and change them with something like Pset?

This is not actually a totally awful way of doing this :slight_smile: - there is a LOT of subtlety to how Pdef incorporates changes to a playing pattern (e.g. using quant to change on beat, etc) - if you want a sort of Ableton Live style hot swapping of patterns, this is a straightforward way to take advantage of all that subtle playback stuff. The code is simple and comprehensible, and does what it looks like it does, which is never bad.

However, there are many other ways to do this as well - here are some suggestions, maybe one of these is a better fit for your workflow. I had to modify your patterns slightly in some cases, because a few things (e.g. a series with a pattern as it’s length parameter) are tricky and would confused the example :slight_smile:

[1] Pull parameters from Pdefn’s
This is nice b/c you can then use patterns in place of fixed values for each Pdef, which becomes compositionally very powerful - and you can set individual quant values for each parameter, which is cool.


(
Pdefn(\steps, 8);
Pdefn(\beats, 3);
Pdefn(\offset, 0);
Pdefn(\size, 4);
Pdefn(\grow, 1);
)

(
Pdef(\bass, Pmono(
	\default,
	\dur, Pbjorklund2(
		Pdefn(\beats), 
		Pdefn(\steps), 
	) * 1 / Pdefn(\steps),
	\degree, Pfin(
		Pdefn(\beats), 
		Pseries(0, Pdefn(\steps), 4).repeat
	),
	\scale, Scale.dorian,
).trace).play;
)

[2] Compose with another event stream
Similar to [1], but stick all your params in one event stream and access via Pkey. This is better if you want a parameter to change to be applied “all at once”, and (for me) the code is a little clearer.

(
Pdef(\params, Pbind(
	\steps, 8,
	\beats, 3,
	\offset, 0,
	\size, 4,
	\grow, 1,
))
)

(
Pdef(\bass).clear;
Pdef(\bass, Pmono(
	\default,
	\dur, Pbjorklund2(
		Pkey(\beats).trace, 
		Pkey(\steps), 
	) * 1 / Pkey(\steps),
	\degree, Pseries(0, Pkey(\steps), 4).repeat,
	\scale, Scale.dorian,
) <> Pdef(\params)).play;
)

[3] Use a function in your Pdef
Some of your original code is hard to reproduce entirely with patterns (e.g. some parameters expect numbers and not streams), so “building” the entire thing in a function is still easier. Pdefs with a function will pull that functions arguments from whatever the incoming event is, so you can compose it with Pdef(\params) (as in [2]) to fill them in.

Some notes:

  1. the function is only called when the pattern inside it has run out of events - if it’s repeating infinitely, it won’t ever reset. This can be an advantage because it means you have coherent phrases, and parameters won’t be changed until the end of the phrase… So basically, make your inner Pdef pattern finite, and then if you want to repeat it, repeat it where you use it (with repeat, cyc, Pn etc)

  2. I changed beats to numBeats - I think the beats key has special meaning and can get obliterated in some cases? I didn’t track down exactly where…

  3. I composed \bass into a Pmono - if the Pmono is inside of the function, then your note basically gets reset every time that function is called (a new Pmono is created) - this may be desirable behavior, or maybe not…

  4. There’s nice thing I do sometimes where you can wrap the pattern in \bass in a Pfindur, which means it will play an exact amount of time, and then finish - then, you can do really complex poly-rhythm stuff, but the pattern and it’s parameters are always reset at at predictable metric, so it doesn’t turn into algorithmic soup.

  5. You can use events of type: phrase to do almost exactly what I’m doing in this example. This gives you some nice things “for free”, but is also less flexible and less obvious - look at the help for recursive phrasing for more info.

(
Pdef(\params,
	Pbind(
		\steps, 8,
		\numBeats, 3,
		\offset, 0,
		\size, 4,
		\grow, 1,
	)
)
(
Pdef(\bass, {
	|steps=8, numBeats=3, offset=0, size=4, grow=1|
	[steps, numBeats, offset, size, grow].postln;
	Pbind(
		\dur, Pbjorklund2(numBeats, steps, offset: offset) * 1 / steps,
		\degree, Pfin(numBeats, Pseq(Array.series(size, 0, grow)), inf).repeat(4),
		\scale, Scale.dorian,
	)
}
);
Pdef(\player, Pmono(\default) <> (Pdef(\bass) <> Pdef(\params)).cyc).play;
)
3 Likes

One more thing: there are solutions to this that use Pbindef, Pdef(\bass).set(\beats, 4), and a few other things. There were some tricky problems with these and they didn’t work for me out-of-the-box, so I didn’t mention them - but they may also provide good solutions if you want to spend the time to dig in!

Great ideas in this thread so far, and I want to add one more perspective since “how do I make a Pattern with controls?” is a pretty common question.

The approach I’ve been working with lately involves a Class that wraps a Stream derived from an EventPatternProxy and an Environment that can be read from / written to by the Stream. Here’s a simplified version:

TestSeq {
    var <source, <environment, <stream;

    *initClass {
        var typeFunc;

        typeFunc = #{|server|
            var ev;

            ~environment = ~sequence.environment;
            ev = ~sequence.stream.next(currentEnvironment);

            ev.removeAt(\environment);
            (ev.type == \testSeq).if { ev.removeAt(\type) };

            ev.play
        };

        Event.addEventType(\testSeq, typeFunc)
    }

    *new {|source, environment| ^super.newCopyArgs(source, environment).init }

    init {
        environment = environment.notNil.if { environment.as(Environment) } { Environment.new };
        source = EventPatternProxy.new(source ? Pbind());

        stream = source.asStream
    }

    source_ {|src, quant=0|
        source.quant_(quant);
        source.source_(src)
    }

    doesNotUnderstand {|selector ...args|
        environment[selector].notNil.if { 
            ^environment[selector] 
        } {
            ((selector.isSetter).and(environment[selector.asGetter].notNil)).if {
                environment[selector.asGetter] = args[0]
            } {
                ^this.superPerformList(\doesNotUnderstand, selector, args)
            }
        }
    }
}

We also need one more “helper Class”, which is a value Pattern that gets values from the TestSeq’s Environment:

Pseqenvir : Pattern {
    var <>key, <>repeats;

    *new {|key, repeats| ^super.newCopyArgs(key, repeats) }

    storeArgs { ^[key, repeats] }

    asStream {
        var stream;
        var keyStream = key.asStream;

        stream = FuncStream.new({|ev| ev !? { ev.environment[keyStream.next(ev)] } });

        ^repeats.isNil.if { stream } { stream.fin(repeats) }
    }
}

Once we have both of these Classes, we can do something like:

(
    s.waitForBoot({
        ~seq = TestSeq.new(
            Pbind(\octave, Pseqenvir(\foo)), [\foo, 3]
        );

        ~ptn = Pbind(\type, \testSeq, \sequence, ~seq, \dur, 0.2).play
    })
)

~seq.foo_(4)

We can also get and set values in the TestSeq’s Environment from Pfunc:

Pfunc({|ev| ev.environment[\foo] })

…and we can change the source of the internal proxy, (with optional quant):

~seq.source_(Pbind(\octave, Pseqenvir(\foo) + Prand((-1..1), inf)))

This might seem like a lot of overhead at first, but since this is something I need to do quite often I think it makes sense to use a Class to get cleaner / more intuitive syntax. The major caveat with this approach is that only the “player” Pattern (~ptn in the example) will have any effect on inter-Event delta time – you can use timing keys in the TestSeq, but they’ll only affect gate length.

As you can imagine from the comprehensive posts so far, this is really an essential topic!

And as it’s a common demand, also for live-coding, people have developed different solutions and approaches. For a quick and easy approach I’d have a look at basic Pbindef, it’s a class that can give a lot of fun.

My idiosyncratic effort concerning Pattern modifications resulted in PLbindef (miSCellaneous_lib), to give a quick example (excerpt from its help file):

// store PLbindef under key \x

(
PLbindef(\x,
    \instrument, \sin_grain,
    \dur, 0.2,
    \midinote, Pwhite(60, 90)
)
)

// PLbindefEnvironment has been made and assigned to the variable ~x in currentEnvironment, check

~x


// now the PLbindefEnvironment can also be treated as a player

~x.play


// set params while playing

~x.att = Pwhite(0.01, 0.2)

~x.midinote = 75



// use method 'value' for parallel setting

~x.(\dur, 0.05, \midinote, Pwhite(70.0, 72))

Wow, thanks a lot, I realize the topic is quite a bit deeper than I expected!

Experimenting with Pdefn raises a few questions.

Pdefn(\length, 3);
p = Pfin(3, Pseries(0,1,inf)).asStream;
6.do{p.next(Event.new).postln}
p = Pfin(Pdefn(\length), Pseries(0,1,inf)).asStream;
6.do{q.next(Event.new).postln}

Here, the first definition will go 0 1 2 nil nil nil, as expected. I would expect the same from the second one, but I get 0 1 2 3 4 5. Why is that?

Next, I would like to do something like this:

Pdefn(\start, 0);
Pdefn(\step, 1);
Pdefn(\length, 3);
p = Pseries(Pdefn(\start), Pdefn(\step), Pdefn(\length)).asStream;
p.next(Event.new).postln;

ERROR: Non Boolean in test.
RECEIVER:
Instance of Pbinop {    (0x5568f69e59f8, gc=08, fmt=00, flg=00, set=02)
  instance variables [4]
    operator : Symbol '<'
    a : Integer 0
    b : instance of Pdefn (0x5568f59e1588, size=8, set=3)
    adverb : nil
}
CALL STACK:
	MethodError:reportError
		arg this = <instance of MustBeBooleanError>
	Nil:handleError
		arg this = nil
		arg error = <instance of MustBeBooleanError>
	Thread:handleError
		arg this = <instance of Thread>
		arg error = <instance of MustBeBooleanError>
	Thread:handleError
		arg this = <instance of Routine>
		arg error = <instance of MustBeBooleanError>
	Object:throw
		arg this = <instance of MustBeBooleanError>
	Object:mustBeBoolean
		arg this = <instance of Pbinop>
	Pseries:embedInStream
		arg this = <instance of Pseries>
		arg inval = <instance of Event>
		var outval = nil
		var counter = 0
		var cur = <instance of Pdefn>
		var len = <instance of Pdefn>
		var stepStr = <instance of Routine>
		var stepVal = nil
	Routine:prStart
		arg this = <instance of Routine>
		arg inval = <instance of Event>
^^ The preceding error dump is for ERROR: Non Boolean in test.
RECEIVER: a Pbinop

Apparently I can pass a Pdefn to the step parameter, but not to the two others. Why is that?

One tricky thing about patterns is that some arguments evaluate on every next value, while others evaluate only when entering the pattern (and… you don’t know which is which by looking at the argument names :flushed: – a legitimate problem in the pattern interfaces).

  • “Every value” inputs may be patterns.
  • “Evaluate-once” inputs must be simple values, functions or streams, but not patterns.

Pfin’s length input is the latter. (It has to be – what if it starts with, say, 10 values, and you’ve already output 5 values, and then the length input changes to 3? That’s the point in sci-fi movies where the robot’s head explodes.)

Same for Pseries’s start and length. You can’t start a series on one value and then change the start value.

So it should be

Pfin(Pdefn(\length).asStream, Pseries(0, 1, inf))

Pseries(Pdefn(\start).asStream, Pdefn(\step), Pdefn(\length).asStream).asStream

hjh

1 Like

just spitballing but what if that distinction between “evaluation at next” and “evaluation only on entry” could be made explicit - perhaps by adding a first character or suffix to key? confusion here has cost me a good bit of grief even with classes I wrote myself!