PSloop Pattern to derive loops from a given Pattern 


Part of: miSCellaneous


Inherits from: Pattern


See also: MemoRoutine, PSx stream patterns, PS, PSdup PSrecur



Although PSloop is not a subclass of PStream, instances of PSloop have a state by storing a PStream of the source pattern in an instance variable. So the specific characteristics of PSx patterns are indirectly inherited by PSloop. This especially concerns the way to generate new, fresh PSloop streams, see examples in PSx stream patterns.


Note that impossible lookBack and indices values will be clipped, see descriptions of args bufSize, lookBack and indices. 



Creation / Class Methods


*new (srcPat, length, bufSize, lookBack, doSkip, loopFunc, loopFuncPerItem, loopFuncAsGoFunc, goFunc, indices, copyItems, copySets)

Creates a new PSloop object.

srcPat - Source pattern for looping, can be value or event pattern.

length - Number of output items, may be Pattern or Stream, defaults to inf.


bufSize - Integer size of buffer to store last values. Determines and clips the maximum lookBack index. Defaults to 1.


lookBack - Non-negative Integer determining the depth of looking backwards when looping, may be Pattern or Function also.

Default 0 means going on with the Stream of the source pattern srcPat.

If no indices is given, a positive Integer lookBack = n causes straight looping from the nth item

in the past up to the last item of the Stream.

If indices is given, an index array is produced by this function applied to lookBack = n. 

The array refers to the nth item in the past by array index 0.

If lookBack values are derived from a Pattern or Function, they might be polled at the end of the loops

or with every item, this is determined by the current value of doSkip. 

lookBack will be clipped by bufSize if it is greater than the latter.


doSkip - Integer or Boolean or: Function or Stream returning such.

Determines if lookBack values are polled at the end of loops (0, false) or within loops (1, true) also.

In the latter case a new lookBack value will stop the current loop and start a new one. Defaults to 1.

loopFunc - Function or Pattern returning Functions, to be applied to items of the loops. 

If Functions are streamed by passing a Function-generating Pattern loopFuncPerItem determines if

a Function will be applied per item (1, true) or to all items of a loop (0, false).

When loopFunc is nil or the loopFunc stream returns nil, no Function is applied.

loopFuncPerItem - Integer or Boolean or: Function or Stream returning such.

If loopFunc is given, loopFuncPerItem determines if a Function will be applied per item (1, true) or 

to all items of a loop (0, false).


loopFuncAsGoFunc - Integer or Boolean or: Function or Stream returning such.

Determines if loopFunc will be used for new items outside loops too (1, true).

In this case goFunc is ignored (resp. nothing is polled from a goFunc stream) and nil values 

from the loopFunc stream are also taken over (no Function is applied outside loops then).

goFunc - Function or Pattern returning Functions, to be applied to new items outside loops. 

When goFunc is nil or the goFunc stream returns nil, no Function is applied.

Note that goFunc has no effect when loopFuncAsGoFunc determines to use loopFunc generally.


indices - SequenceableCollection, Function, or Pattern returning SequenceableCollections or Functions.

If no indices is passed (default nil) a positive Integer lookBack = n causes straight looping from the nth item

in the past up to the last item of the Stream.

A SequenceableCollection passed via indices is taken as array of indices and determines the items of the buffer, 

identifying the nth item in the past with array index 0.

A Function passed via indices takes lookBack = n as argument and must return an index array which also

determines the items of the buffer, identifying the nth item in the past with array index 0.

A Pattern passed via indices should be defined to generate Functions or SequenceableCollections, 

which are interpreted in the same way as above.

In case of a Pattern a new indices value is polled within a loop if current doSkip equals 1 or true.

indices will be clipped by lookBack - 1, lookBack itself might be clipped by bufSize.

copyItems - Argument passed to the PS wrapper of srcPat. See PS.

Determines if and how to copy items, which are either non-Sets or member of Sets. 

Takes Integer 0 (or false or Symbol \false), 1 (or true or Symbol \true) or 2 (or Symbol \deep). 

Other values are interpreted as 0. Defaults to 0.

0: original item 

1: copy item

2: deepCopy item

copySets - Argument passed to the PS wrapper of srcPat. See PS.

Determines if and how to copy Sets (and hence Events). 

Takes Integer 0 (or false or Symbol \false), 1 (or true or Symbol \true). 

Other values are interpreted as 0. Defaults to 1.

0: original Set 

1: copy Set

NOTE 1: The distinction of copying items and sets makes sense in the case of event streams.

Per default Events are copied (copySets == 1), not their values (copyItems == 0). 

By playing Events those are used to store additional data (synth ids, msgFuncs …) 

which is mostly not of interest when refering to the event stream, e.g. with PSx patterns which use 

MemoRoutine - copied Events will not contain this additional data. 

If values of Events or values returned directly by the stream (being no kind of Sets) are unstructured 

then copying makes no sense, this is the normal case, so copyItems defaults to 0.

When going to alter the ouput, you might want to set copyItems to 1 for a PSx returning 

simple arrays or 2 for nested arrays (deepCopy). For deepCopying Events you'd have to set

copySets to 1 and copyItems to 2 (an option copySets == 2 doesn't exist as

it would be contradictory in combination with copyItems < 2).


NOTE 2: Copy options concern copying into PS's buffer as well as 

the output of a Stream derived from the PS. When such a Stream is outputting copies 

this prevents unintended altering of items stored in the buffer of the source. On the other hand

storing copies in PSrecur's buffer prevents these from being altered unintendedly.



Instance Methods


psPat, psPat(value)

Instance variable getter and setter methods. 

psPat  holds a Pstream with args srcPat, length, bufSize, copyItems, copySets.


lookBack, lookBack(value)

Instance variable getter and setter methods. 


doSkip, doSkip(value)

Instance variable getter and setter methods. 


loopFunc, loopFunc(value)

Instance variable getter and setter methods. 


loopFuncPerItem, loopFuncPerItem(value)

Instance variable getter and setter methods. 


loopFuncAsGoFunc, loopFuncAsGoFunc(value)

Instance variable getter and setter methods. 


goFunc, goFunc(value)

Instance variable getter and setter methods. 


indices, indices(value)

Instance variable getter and setter methods. 





Examples


(

s = Server.local;

Server.default = s;

s.boot;

)



Ex. 1a:   PSloop used as value pattern


// straight usage as value pattern for midinotes


(

// lookBack determines number of items to loop, must start with 0 (no loop),

// doSkip determines if loops should be completed or not with new lookBack values.


// Passing both via Functions enables to set on the fly,

// enable immediate loop change by setting doSkip to 1


~lookBack = 0;

~doSkip = true;


p = Pbind(

\dur, 1/5,

\midinote, PSloop(

Prand((60..80), inf), 

bufSize: 10, 

lookBack: { ~lookBack },

doSkip: { ~doSkip }

)

).play

)


// wait a bit to fill buffer

// keep running, set loop lengths immediately


~lookBack = 4


~lookBack = 2


~lookBack = 7



// change to skip per loop (default)


~doSkip = false


~lookBack = 3


~lookBack = 5


~lookBack = 9



// go on


~lookBack = 0


p.stop;




Ex. 1b:   PSloop used as event pattern


// rhythms are looped too


(

~lookBack = 0;

~doSkip= 1;


p = PSloop(

Pbind(

\dur, Pn(Pshuf([1, 1, 2, Pseq(2!3/3)]/5)),

\midinote, Prand((60..80), inf) 

), 

bufSize: 10, 

lookBack: { ~lookBack },

doSkip: { ~doSkip}

).play

)


// wait a bit to fill buffer

// keep running, set loop lengths immediately


~lookBack = 6


~lookBack = 5



// go on and loop again


~lookBack = 0


~lookBack = 7



p.stop;




Ex. 2a:   PSloop used as value pattern, lookBack given as pattern


// lookBack passed as pattern, 

// lookBack pattern must start with zeros, 

// otherwise it will immediately stop with buffer values = nil 


// PSloop as value pattern: in general rhythms aren't looped

(

p = Pbind(

\dur, Pn(Pshuf([1, 1, 2]/6)),

\midinote, PSloop(

Prand((60..80), inf), 

bufSize: 10, 

lookBack: Pseq([0, 0, 0, 2, 2, 3, 3], inf)

)

).play

)


p.stop




Ex. 2b:   PSloop used as event pattern, lookBack given as pattern


// rhythms looped too


(

p = PSloop(

Pbind(

\dur, Pn(Pshuf([1, 1, 2]/6)),

\midinote, Prand((60..80), inf) 

), 

bufSize: 10, 

lookBack: Pseq([0, 0, 0, 2, 2, 3, 3], inf)

).play

)


p.stop



Ex. 3a:   lookBack indices given as array


// With the indices arg arbitrary series from the buffer can be played.

// Passing an array we define an index sequence which doesn't depend on lookBack


(

~lookBack = 0; 


p = PSloop(

Pbind(

\dur, Pn(Pshuf([1, 1, 2]/6)),

\midinote, Pseq((60..80), inf)

), 

bufSize: 10, 

lookBack: { ~lookBack },

indices: [0, 1, 0, 1, 2, 0, 1, 2, 3]

).trace.play

)


// wait a bit to fill buffer

// start loop with useful lookBack (here > 3)


~lookBack = 4


~lookBack = 6



// go on and loop again


~lookBack = 0;


~lookBack = 5



p.stop




Ex. 3b:   lookBack indices given as Function


// If a Function is passed to indices it takes the current lookBack arg as argument.


// mirroring the full lookBack size

// start without loop


(

~lookBack = 0;


p = PSloop(

Pbind(

\dur, Pn(Pshuf([1, 1, 2]/6)),

\midinote, Prand((60..80), inf)

), 

bufSize: 10, 

lookBack: { ~lookBack },

indices: { |l| (0..l-1).mirror }

).trace.play

)


// wait a bit to fill buffer

// mirrored loop i.e. loop with added retrograde


~lookBack = 3



// go on and loop again


~lookBack = 0


~lookBack = 3


p.stop




Ex. 3c:   lookBack indices given as Pattern


// The indices arg might get a pattern which can generate Functions as well as arrays.

// Start with sufficient number of zeros as lookBack to fill buffer.


(

p = PSloop(

Pbind(

\dur, Pn(Pshuf([1, 1, 2]/6)),

\midinote, Prand((60..80), inf)

), 

bufSize: 10, 

lookBack: Pseq([Pn(0, 7), Pstutter(4, Pwhite(3, 5, 1))], inf).trace,

indices: Pseq([1!2, { |l| (l-1..0).postln }], inf).trace

).trace.play

)


p.stop




Ex. 4a:   loopFunc given as Function


// a Function passed to loopFunc is applied to all items of the looping


(

~lookBack = 0;


p = Pbind(

\dur, Pn(Pshuf([1, 1, 2]/6)),

\midinote, PSloop(

Prand((60..80), inf),

bufSize: 10, 

lookBack: { ~lookBack },

loopFunc: { |x| x + [0, 5] }

)

).play

)


// wait a bit to fill buffer

// loop with Function applied


~lookBack = 3


~lookBack = 6



// go on and stop


~lookBack = 0


p.stop




Ex. 4b:   loopFunc given as Pattern


// loopFunc also takes a pattern of Functions, in this case

// the arg loopFuncPerItem decides if next Functions are polled per item or per loop.


(

~lookBack = 0;

~loopFuncPerItem = true;  // or 1


p = Pbind(

\dur, Pn(Pshuf([1, 1, 2]/6)),

\midinote, PSloop(

Prand((60..80), inf),

bufSize: 10, 

lookBack: { ~lookBack },

// every number gets a Function that adds an interval of that size

loopFunc: Pxrand((3..11), inf).collect { |x| { |y| y - [0, x].postln } },

loopFuncPerItem: { ~loopFuncPerItem }

)

).trace.play

)


// wait a bit to fill buffer

// start looping with Functions polled per item 


~lookBack = 3


~lookBack = 6



// one interval per loop


~loopFuncPerItem = false   // or 0


~lookBack = 9



// switch back to new interval per item


~loopFuncPerItem = 1



// go on and stop


~lookBack = 0


p.stop




Ex. 4c:   loopFunc vs. goFunc 


// Besides loopFunc a separate Function (or Pattern of Functions) can be defined for

// application outside loops via the goFunc arg.

// The arg loopFuncAsGoFunc decides if loopFunc should be taken for that task

// (and goFunc should be ignored in that case).


(

~lookBack = 0;

~loopFuncPerItem = false;  // one func per loop

~loopFuncAsGoFunc = false;  // start with dedicated goFunc


p = Pbind(

\dur, Pn(Pshuf([1, 1, 2]/6)),

\midinote, PSloop(

Prand((60..80), inf),

bufSize: 10, 

lookBack: { ~lookBack },

// every number gets a Function that adds an interval of that size

loopFunc: Pxrand((3..11), inf).collect { |x| { |y| y - [0, x].postln } },

loopFuncPerItem: { ~loopFuncPerItem },

goFunc: { |x| [x, x + 2] },

loopFuncAsGoFunc: { ~loopFuncAsGoFunc }

)

).trace.play

)


// wait a bit to fill buffer

// start looping with Functions polled per loop 


~lookBack = 3


~lookBack = 6



// loop per item


~loopFuncPerItem = true



// prepare to use loopFunc when going on


~loopFuncAsGoFunc = true



// go on


~lookBack = 0



// switch to dedicated goFunc and stop


~loopFuncAsGoFunc = false


p.stop





Ex. 4d:   Turning loopFunc and goFunc on and off


// There are no separate PSloop flags needed for that:

// If loopFunc or goFunc return nil, no Function is applied,

// so suited Patterns using flags can be passed as loopFunc / goFunc args.


(

~lookBack = 0;

~loopFuncPerItem = false;  // one func per loop

~loopFuncAsGoFunc = false;  // start with dedicated goFunc


~turnOnLoopFunc = true;

~turnOnGoFunc = true;


p = Pbind(

\dur, Pn(Pshuf([1, 1, 2]/6)),

\midinote, PSloop(

Prand((60..80), inf),

bufSize: 10, 

lookBack: { ~lookBack },

// every number gets a Function that adds an interval of that size,

// if ~turnOnLoopFunc evaluates to false the stream returns nil instead of a Function

loopFunc: Pxrand((3..11), inf).collect { |x| ~turnOnLoopFunc.if { { |y| y - [0, x].postln } } },

loopFuncPerItem: { ~loopFuncPerItem },

goFunc: Pfunc { ~turnOnGoFunc.if { { |x| [x, x + 2] } } },

loopFuncAsGoFunc: { ~loopFuncAsGoFunc }

)

).trace.play

)


// wait a bit to fill buffer

// start looping with Functions polled per loop 


~lookBack = 3


~lookBack = 6



// when Functions are polled per loop, turning loopFunc off also works per loop


~turnOnLoopFunc = false



// use loopFunc again, namely per item


~turnOnLoopFunc = true


~loopFuncPerItem = true 



// now turning it off works immediately


~turnOnLoopFunc = false




// go on


~lookBack = 0



// as loopFunc is now turned off, this applies also when used for new values 


~loopFuncAsGoFunc = true



// switch to goFunc again


~loopFuncAsGoFunc = false



// silently turn loopFunc on


~turnOnLoopFunc = true



// turn goFunc off and again use loopFunc as goFunc


~turnOnGoFunc = false


~loopFuncAsGoFunc = true




// loop, again with loopFunc per item


~lookBack = 5



p.stop




Ex. 5:   PSloops controlling different sound params


// Overlapping of loops on different sound params can give interesting polyrhythmic structures


(

SynthDef(\psloop_1, { |out = 0, freq = 440, centDif = 5, rq = 0.1, cutoff = 1000, amp = 0.1

att = 0.01, sus = 0.0, rel = 0.01|

var sig = Saw.ar(freq * [1, (centDif * 0.01).midiratio], amp);

Out.ar(0, BPF.ar(sig, cutoff.clip(30, 8000)) * EnvGen.ar(Env.linen(att, sus, rel), doneAction: 2));

}, 0.01!6).add

)


(

// Function to generate a standard PSloop with lookBack arg to by controlled by env variable with suffix LB

// Pfunc could also be written as: PL((name ++ \LB).asSymbol)


~psLoop = { |src, name| PSloop(src, bufSize: 10, lookBack: Pfunc { currentEnvironment[(name ++ \LB).asSymbol] }) };


p = Pbind(

\instrument, \psloop_1,

\att, 0.01,

\amp, Pfunc { ~amp },

\sus, ~psLoop.(Prand([0.1, 0.2], inf), \sus),

\rel, ~psLoop.(Prand([0.01, 0.1, 1], inf), \rel),

\dur, ~psLoop.(Pn(Pshuf([1, 1, 2]/5)), \dur),

\centDif, Pshuf([5, -20, 40], inf),

\midinote, ~psLoop.(Pxrand((30..50), inf), \midinote),

\cutoff, ~psLoop.(Pshuf([500, 1500, 5000], inf), \cutoff)

);


q = Pbindf(p, \midinote, ~psLoop.(Pxrand((50..70), inf), \midinote));

r = Pbindf(p, \midinote, ~psLoop.(Pxrand((75..95), inf), \midinote));

  


// VarGui for control of lookBack params of 3 parallel streams

// zero values for args with suffix LB mean: no looping


// play loops by setting several params to non-zero values,

// you can grab params of different voices by using the alt key while

// moving a slider


VarGui({ |i| [

\susLB, [0, 7, \lin, 1, 0],

\relLB, [0, 7, \lin, 1, 0],

\durLB, [0, 7, \lin, 1, 0],

\midinoteLB, [0, 7, \lin, 1, 0],

\cutoffLB, [0, 7, \lin, 1, 0],

\amp, [0, 1, \lin, 0, 0.2 + (i * 0.1)]

] }!3, stream: [r, q, p], quant: 1/5

).gui(labelWidth: 80)

)