PLx and live coding with Strings PLx patterns as placeholders for sequencing with letters


Part of: miSCellaneous


See also: PLx suite, PsymNilSafe, PLbindef, PLbindefPar, EventShortcuts



Strings and Chars as high-level representations for musical objects can be used for sequencing with very condensed syntax. This is already possible with standard patterns like Pseq etc. – PLx list patterns fit this concept as their referenced Arrays/Strings can be replaced on the fly. Examples below also use EventShortcuts to minimize typing.


WARNING: Sequencing with infinite Patterns/Streams has always the potential of hangs. E.g. Psym hangs if all referenced pattern return nil (SC 3.7.2). Here convenience method 'symplay' is suggested: it employs PsymNilSafe, its method 'embedInStream' performs a check like in James Harkins' PnNilSafe from ddwPatterns quark (which can't be used directly this case). 'symplay' thus avoids hangs of that type, see Ex. 2b.



(

s = Server.local;

Server.default = s;

s.boot;

)


(

// synthdefs to play with, use of EventShortcuts


SynthDef(\noise, { |out = 0, freq = 400, att = 0.005, rel = 0.1, rq = 0.1, amp = 0.1|

    var sig = { WhiteNoise.ar } ! 2;

    sig = BPF.ar(sig, freq, rq) *

        EnvGen.ar(Env.perc(att, rel, amp), doneAction: 2) *

        (rq ** -1) * (250 / (freq ** 0.8));

    OffsetOut.ar(out, sig);

}).add;


SynthDef(\sin, { |out = 0, freq = 400, att = 0.005, rel = 0.1, amp = 0.1|

    var sig = { SinOsc.ar(freq, Rand(0, 2pi)) } ! 2;

    sig = sig * EnvGen.ar(Env.perc(att, rel, amp), doneAction: 2);

    OffsetOut.ar(out, sig);

}).add;



SynthDef(\saw, { |out = 0, freq = 400, att = 0.005, rel = 0.1, amp = 0.1|

    var sig = { VarSaw.ar(freq, Rand(0, 1)) } ! 2;

    sig = sig * EnvGen.ar(Env.perc(att, rel, amp), doneAction: 2);

    OffsetOut.ar(out, sig);

}).add;


EventShortcuts.on;

)



Ex.1) Straight usage with finite Patterns and Events


(

// use EventShortcuts


EventShortcuts.on;

EventShortcuts.postAll;


// base Pbind


~x = Pbind(\d, 0.1, \i, \sin);


// chars for the string, event patterns or events


~a = Pbind(\m, Pseries(60, 2, 4)) <> ~x;

~b = Pbind(\m, Pseries(80, -2, 8)) <> ~x;

~c = (i: \saw, m: [95, 96, 97], d: 0.8);


// define sequence


~p = "aab"

)


// symplay wraps into a PsymNilSafe

// PLseq defaults to repeats = inf and refers to 'p' in current Environment,

// thus the String "aab", the EventStreamPlayer should also get a name for start/stop,

// in this case we take the same letter (p) as interpreter variable


p = PLseq(\p).symplay



// replace String of PLseq


~p = "aacb"



// use of basic String operations


~p = ~p ++ "cc"


~p = ~p.reverse



(

// new char and sequence


~d = Pbind(\m, Pwhite(60, 90, 2), \i, \noise) <> ~x;

~p = "adadcb";

)


// replace String


~p = "bbcbcad"



// new chars

// the Function as first arg of Pseries is evaluated with every embedding,

// thus movements up and down start on different pitches


(

~a = Pbind(\m, Pseries({ rrand(60, 75) }, 7, 4)) <> ~x;

~b = Pbind(\m, Pseries({ rrand(85, 95) }, -5, 8)) <> ~x;

~c = Pbind(\i, \noise, \m, Pn((95..100), 2)) <> ~x

)



// modify ~b


~b = Pbind(\i, PLrand([\sin, \saw])) <> ~b;



// modify list, loop goes on

// evaluate several times, compare sound and posted String


~p = ~p.scramble


p.stop




Ex.2) Repeated embedding


// Control of embedding resp. the number of Events, that one Patterns should produce when played,

// is a subtle topic. A basic distinction is whether an embedded sequence should be produced with 

// desired behaviour from begin to end (2a) or it should be paused and resumed (2b). 

// The latter is the classical behaviour of Streams, but it can be mimiced with PSx stream patterns. 

// In any case embedding can be done with varying length, e.g. by defined sequences or by interaction.



Ex.2a) Embedding without continuation


(

// base Pbinds

~x = Pbind(\d, 0.15, \i, \sin, \rel, 1.5, \a, 0.02);

~y = Pbind(\d, Pn(0.9, 1), \i, \saw, \att, 0.8, \rel, 4, \a, 0.006);


// variable for repeats arg

~ar = 4;


// descending sequence, note the repeats arg: as with the start arg

// the Function is evaluated with every embedding,

// ~ar can be a Stream or a value

~a = Pbind(\m, Pseries({ rrand(75.0, 95) }, -7, { ~ar.next })) <> ~x;


// chords without octave doubling

~b = Pbind(\m, Pfunc { { [48, 60, 72].choose } ! 9 + (0..12).scramble.drop(3) }) <> ~y;


// define sequence

~p = "ba"

)



p = PLseq(\p).symplay


// change to other repeats number


~ar = 6


// make it a sequence with a Stream


~ar = PLseq([2, 2, 4]).asStream  // or shorter: PLseq([2, 2, 4]).iter



// go back to num and change String


~ar = 3


~p = "baaa"


~p = "baabaaaa"



p.stop




Ex.2b) Embedding with continuation


// This can be done by feeding a stream into a pattern, either directly or with PSx


(

// base Pbind

~x = Pbind(\d, 0.2, \rel, 0.5, \a, 0.03);


// Streams to be continued


~as = (Pbind(\m, Pn(Pseries(60, 1, 16)), \i, \saw) <> ~x).asStream;

~bs = (Pbind(\m, Pn(Pseries(90, -1, 16)), \i, \sin) <> ~x).asStream;


// variable for repeats args

~ar = 4;

~br = 4;


// for getting next values of an event stream we must pass an empty event as arg '.next(())'

~a = Pfuncn({ ~as.next(()) }, { ~ar.next });

~b = Pfuncn({ ~bs.next(()) }, { ~br.next });


// define sequence

~p = "ab"

)



p = PLseq(\p).symplay



// change repeats


~ar = 2;

~br = 1;



// this causes ~a to produce nils, only ~b still returns events


~ar = nil;


// This stops the player.

// Note that this kind of nil-detection works because 'symplay' employs PsymNilSafe.

// A construct like Psym(Pseq("ab", inf), ...).play would hang in that case


~br = nil;


// Note that this is different from the case, when the dictionary's key itself is nil.

// Then we get silent events, that also would't cause a hang with Psym(Pseq("ab", inf), ...).play



// evaluate above code starting from ~x = ... again and run


p = PLseq(\p).trace.symplay


// now we get a rest event


~a = nil


// player keeps running silently, stop explicitely


~b = nil


p.stop



// same as above written with PSx stream patterns


(

// base Pbind

~x = Pbind(\d, 0.2, \rel, 0.5, \a, 0.03);


// variable for repeats args

~ar = 4;

~br = 4;


~a = PS(Pbind(\m, Pn(Pseries(60, 1, 16)), \i, \saw) <> ~x, { ~ar.next });

~b = PS(Pbind(\m, Pn(Pseries(90, -1, 16)), \i, \sin) <> ~x, { ~br.next });


// define sequence

~p = "ab"

)



p = PLseq(\p).symplay


// change repeats


~ar = 2;

~br = 1;


p.stop;



Ex.3) Parallel embedding


// This can e.g. be done with Ppar, Ptuple or Pspawner, which is most flexible.

// There's a tiny isssue here in combination with EventShortcuts, duration keys

// should be in full length (You could apply method 'eventShortcuts' inside Ppar,

// but that's even more typing in that case, so we just write 'dur' instead of 'd').


(

// base Pbind


~x = Pbind(\dur, 0.1, \i, \sin);

~y = Pbind(\dur, 0.05, \i, \sin);


// chars for the String, event patterns or events


~a = Pbind(\m, Pseries({ rrand(60.0, 65) }, 1, 8)) <> ~x;

~b = Pbind(\m, Pseries({ rrand(85.0, 95) }, -1, 8)) <> ~y;


~c = Ppar([~a, ~b]);


~d = (i: \saw, m: [95, 96, 97], d: 0.8);


// define sequence


~p = "accd"

)



p = PLseq(\p).symplay


// equivalent with Pspawner


~c = Pspawner { |sp| sp.par(~a); sp.par(~b) };



// with Pspawner you have precise control over embedding of subsequences


~c = Pspawner { |sp| sp.par(~a); 4.do { sp.par(~b) } };



~e = Pspawner { |sp| sp.par(~a); 3.do { sp.seq(~b) } };


~p = "acde"


p.stop




Ex.4) Use of other PLx list patterns


// We can keep the String constant and switch to different PLx list patterns.


(

// base Pbind


~x = Pbind(\d, 0.1, \i, \saw);

~y = Pbind(\d, 0.2, \i, \sin);


// chars for the String, event patterns or events


~a = Pbind(\m, Pseries(60, Pwhite(1.0, 7.0), 4)) <> ~x;

~b = Pbind(\m, Pseries(80, Pwhite(1.0, 7.0), 4)) <> ~y;


~c = ~x <> ~b;

~d = ~y <> ~a;


~e = Pbind(\i, \noise, \m, Pn(70, 2)) <> ~x;


// define sequence


~p = "abcde"

)



// we need another proxy in that case, take general PL


~l = PLseq(\p);


p = PL(\l).symplay;



// scramble sequence and keep


~l = PLshuf(\p);



// scramble with every loop


~l = PLshufn(\p);




// weighted random


~l = PLwrand(\p, [4, 1, 3, 1, 1]/10);


p.stop;




Ex.5) PLbindef and PLbindefPar


// High-level control of Strings can be combined with replacing key streams with PLbindef/PLbindefPar



Ex.5a) PLbindef


(

// base PLbindef, continued embedding with PS as in Ex. 2b


~x = PS(PLbindef(\y, \dur, 0.1, \i, \noise, \rq, 0.5, \att, 0.05, \rel, 0.1, \a, 0.05));


// chars for the String, event patterns or events


~a = Pbind(\m, Pn(75, 1)) <> ~x;

~b = Pbind(\m, Pn(80, 1)) <> ~x;


~c = Ppar([~a, ~b]);


// define sequence


~p = "ab"

)


p = PLseq(\p).symplay



// update PLbindef's rq and amplitude with patterns


~y.rq = PLseq((100..1).mirror / 1000)


~y.a = PLseq((20..80).mirror / 1000)



// update String


~p = "c"


~p = "ababccc"



// stop and cleanup


p.stop


PLbindef(\y).remove




Ex.5b) PLbindefPar


(

// data base for two PLbindefPars, continued embedding with PS as in Ex. 2b


~w = [\dur, 0.1, \i, \noise, \rq, 0.5, \att, 0.05, \rel, 0.1, \a, 0.02];


~chord = (46, 53..95);


~a = PS(PLbindefPar(\u, 7, \m, ~chord, *~w), 1);

~b = PS(PLbindefPar(\v, 7, \m, ~chord + 2, *~w), 1);


// define sequence


~p = "ab"

)


p = PLseq(\p).symplay



~u.rq = 0.005


~v.i = \saw



// evolving changes


~u.rq = PLseq((100, 95..5).mirror / 1000)


~v.a = Pseg(PLseq([0.01, 0.04]), Pwhite(4, 7))



// change sequence per String


~p = "aabb"


~p = "aabbabab"


~p = "aabbcababc"


~p = "aaaab"



// fade out


~v.a = Pseg(Pseq([0.02, 0]), 20)


~u.a = Pseg(Pseq([0.02, 0]), 20)



// stop and cleanup


(

p.stop;

PLbindef(\u).remove;

PLbindef(\v).remove;

)




Ex.6) String sequencing with PbindFx


// Control with Strings can be thought in many ways.

// With effects one can e.g. use different Strings for src and fx sequencing.



Ex.6a) PbindFx


// boot server with extended resources for PbindFx


(

s.options.numPrivateAudioBusChannels = 1024;

s.options.memSize = 8192 * 16;

s.reboot;


// fx synths


SynthDef(\resample, { |out = 0, in, mix = 1, amp = 1,

resampleRate = 22050, lagTime = 1|

var sig, inSig = In.ar(in, 2);

sig = Latch.ar(inSig, Impulse.ar(resampleRate)); // resampling

// lag in milliseconds for smoothing

sig = sig.lag(lagTime * 0.001);

Out.ar(out, (1 - mix) * inSig + (sig * mix));

}).add;


SynthDef(\wah, { |out, in, resLo = 200, resHi = 5000,

    cutOffMoveFreq = 0.5, rq = 0.1, amp = 1, mix = 1|

    var sig, inSig = In.ar(in, 2);

    sig = RLPF.ar(

        inSig,

        LinExp.kr(LFDNoise3.kr(cutOffMoveFreq), -1, 1, resLo, resHi),

        rq,

        amp

    ).softclip;

    Out.ar(out, (1 - mix) * inSig + (sig * mix));

}).add;



// src synths


SynthDef(\noise, { |out = 0, freq = 400, att = 0.005, rel = 0.1, rq = 0.1, amp = 0.1|

    var sig = { WhiteNoise.ar } ! 2;

    sig = BPF.ar(sig, freq, rq) *

        EnvGen.ar(Env.perc(att, rel, amp), doneAction: 2) *

        (rq ** -1) * (250 / (freq ** 0.8));

    OffsetOut.ar(out, sig);

}).add;


SynthDef(\saw, { |out = 0, freq = 400, att = 0.005, rel = 0.1, amp = 0.1|

    var sig = { VarSaw.ar(freq, Rand(0, 1)) } ! 2;

    sig = sig * EnvGen.ar(Env.perc(att, rel, amp), doneAction: 2);

    OffsetOut.ar(out, sig);

}).add;



// prepare EventShortcuts for additional keys


EventShortcuts.addOnBase(\default, \fxExs, (

dec: \decayTime,

cd: \cleanupDelay,

cf: \cutOffMoveFreq,

fxo: \fxOrder,

rs: \resampleRate

), true);


EventShortcuts.makeCurrent(\fxExs);


EventShortcuts.on;

)




(

// base Pbind

// PbindFx's fxOrder (short: fxo) syntax employed by Symbol mapping:

// u: no fx, x: resample, y: wah, z: resample and wah in sequence


~r = Pbind(

\fxo, Psym(PLseq(\fx), (u:0, x:1, y:2, z:[1, 2])),

\a, 0.07,

\att, 0.01,

\rel, 0.3,

    \cd, Pkey(\att)+ Pkey(\rel) + 0.1

);


// PS to embed


~a = PS(Pbind(\i, \saw, \d, 0.1, \m, PLseq([60, 60, 60, 62])) <> ~r, 1);


~b = PS(Pbind(

\i, \noise,

\d, 0.2,

\m, PLseq([Pwhite(41.0, 50),Pwhite(71.0, 80)]),

\rq, 0.01

) <> ~r, 1);


// no cleanupDelay defaults for fxs as they don't delay

q = PbindFx(PsymNilSafe(PLseq(\p)), [

    \fx, \resample,

\mix, 0.3,

\rs, Pwhite(200, 500),

\a, 1

],[

   \fx, \wah,

\mix, 0.8,

\cf, Pwhite(0.5, 5),

\a, 0.7

]);

)


// start instrument sequence with no fx


(

~fx = "u";

~p = "aab";

p = q.play;

)


// fx sequences


~fx = "uuxy"


~fx = "uyuxzzzz"



~p = "b"


p.stop




Ex.6b) PbindFx and PLbindef


// There's more fine-tuned control if we can replace key streams also


(

// base pairs for PLbindef


~r = [

\fxo, Psym(PLseq(\fx), (u:0, x:1, y:2, z:[1, 2])),

\a, 0.07,

\att, 0.01,

\rel, 0.3,

    \cd, Pkey(\att) + Pkey(\rel) + 0.001

];


// PS to embed


~a = PS(PLbindef(\aa, \i, \saw, \d, 0.1, \m, PLseq([60, 60, 60, 62]), *~r), 1);


~b = PS(PLbindef(\bb,

\i, \noise,

\d, 0.2,

\m, PLseq([Pwhite(41.0, 50),Pwhite(71.0, 80)]),

\rq, 0.1,

*~r

), 1);


// as we have defined fx chars 'x' and 'y' above,

// choose related names 'xx' and'yy' for PLbindefs


q = PbindFx(PsymNilSafe(PLseq(\p)),

PLbindef(\xx,

    \fx, \resample,

\mix, 0.3,

\rs, Pwhite(200, 500),

\a, 1

),

PLbindef(\yy,

   \fx, \wah,

\mix, 0.8,

\cf, Pwhite(0.5, 5),

\a, 0.7

)

);

)


// start with no fxs


(

~fx = "u";

~p = "aab";

p = q.play;

)


// fx sequence


~fx = "uuxy"



// midinote for ~a and ~b


~aa.m = [50, 52]


~bb.m = Pwhite(80, 96)




// switch to single fx resample for testing changes of its control streams

// resample, test rate


~fx = "x"


~xx.rs = Pwhite(1000, 3000)



// same with wah


~fx = "y"


~yy.cf = 3



// src changes


~aa.rel = 0.1


~bb.rel = 0.5



// further playing


~fx = "xxy"


~p = "aabaabaaaabb"



~aa.m = [38, 40]


~bb.rq = 0.7


~fx = "z"



// fade out


~aa.a = Pseg(Pseq([0.04, 0]), 20)


~bb.a = Pseg(Pseq([0.04, 0]), 20)



// cleanup


p.stop


Pdef.removeAll