Event patterns and LFOs summary of some LFO control strategies for event patterns 


Part of: miSCellaneous


See also: Working with HS and HSpar, VarGui, VarGui shortcut builds, HS with VarGui



Basic distinction: synths generated by an event pattern can be controlled directly by a LFO (synths wired or mapped to control buses) or on a per-event base. The latter can be achieved by language-only strategies or with help of control synths. For the sake of clarity and comparison most examples use the default instrument and pitch as control parameter. 



1.) Control by new values per event


Example 1a:   Functions of time



(

s = Server.local;

Server.default = s;

s.boot;

)



(

// LFO defined as Function


f = { |x| (x * 2).sin * 3 + (x * 0.3).sin }; 


p = Pbind( 

    \dev, Ptime().collect(f), // current time passed to function

    \midinote, Pkey(\dev) + 60 + [0, 4], 

    \amp, 0.05, 

    \dur, 0.15 

).play; 

)


p.stop;



////////////////////////// 


// example with GUI



(

// parametric control function


f = { |x| (x * ~a[0]).sin * ~b[0] + ((x * ~a[1]).sin * ~b[1]) }; 


p = Pbind( 

    \dev, Ptime().collect(f), 

    \midinote, Pkey(\dev) + 60 + [0, 4], 

    \amp, 0.05,

    \dur, 0.15 

); 


v = VarGui([

\a, { [0, 10, \lin, 0.01, rrand(0.0, 10)] } ! 2,

\b, { [0, 10, \lin, 0.01, rrand(0.0, 10)] } ! 2],

stream: p

).gui

)


// add a hook that re-plots control function after every slider change


(

u = Plotter(bounds: Rect(200, 200, 700, 500));


// evaluation points


x = (0, 0.1..20);


// EventStreamPlayer is run in separate Environment, 

// function values have to be polled from there,

// evaluate function initially and add it as mouseUp slider action


g = { u.value_(f.inEnvir(v.envirs.first).(x)).refresh };


g.();


v.addSliderAction(g);

)




////////////////////////// 


// GUI example with two LFOs



(

// parametric control function


f = { |x| (x * ~a[0]).sin * ~b[0] + ((x * ~a[1]).sin * ~b[1]) }; 


p = Pbind( 

    \dev, Ptime().collect(f), 

    \midinote, Pkey(\dev) + Pfunc { ~add }, 

    \amp, 0.05,

    \dur, 0.15 

); 


// add some harmonies


q = Padd(\midinote, [-12, -7, 0, 5], p);

r = Padd(\midinote, [0, 2.5], p);


v = VarGui([70, 55].collect { |x| [

\a, { [0, 5, \lin, 0.01, rrand(0.0, 5)] } ! 2,

\b, { [0, 5, \lin, 0.01, rrand(0.0, 5)] } ! 2,

\add, [x-2, x+2, \lin, 0.01, x]]

},

stream: [r,q], quant: 0.15

).gui

)


// add a hook that re-plots control function after every slider change


(

u = Plotter(bounds: Rect(200, 200, 700, 500));


// evaluation points


x = (0, 0.1..20);


// EventStreamPlayer is run in separate Environment, 

// function values have to be polled from there,

// evaluate function initially and add it as mouseUp slider action


g = { u.value_(v.envirs.collect { |e| f.inEnvir(e).(x) }).refresh };



g.();


v.addSliderAction(g);

)




Example 1b:   Envelopes



(

e = Env([0, 10, 0], [2,1], \sin);

e.plot;

)


// currently (SC 3.4.4) this will play the envelope once 

// and then continue with the end value


(

p = Pbind( 

    \dev, e, 

    \midinote, Pkey(\dev) + 60 + [0, 4], 

    \amp, 0.05, 

    \dur, 0.15 

).play; 

)


p.stop;



// you can use modulo calculus for looping


(

p = Pbind( 

    \dev, Ptime().collect { |t| e[t % (e.times.sum)] }, 

    \midinote, Pkey(\dev) + 60 + [0, 4], 

    \amp, 0.05, 

    \dur, 0.15 

).play; 

)


p.stop;



////////////////////////// 


// example with GUI


(

n = 5;


p = Pbind( 

    \dev, Ptime().collect { |t| e[t % (e.times.sum)] }, 

    \midinote, Pkey(\dev) + 60 + [0, 4], 

    \amp, 0.05, 

    \dur, 0.15 

);


v = VarGui([

\levels, { [-15, 15, \lin, 0.01, rrand(-15.0, 15)] } ! n,

\times, { [0.5, 3, \lin, 0.01, rrand(0.5, 3)] } ! (n-1),

\curves, { [0, 7, \lin, 1, rrand(0, 7)] } ! (n-1),

\stretch, [0.1, 10, \lin, 0.01, 1]],

stream: p

).gui;

)


// add a hook that plots envelope after every slider change


(

u = Plotter(bounds: Rect(200, 200, 700, 500));


g = { 

v.envirs.first.use { e = Env(~levels, ~times * ~stretch, ~curves) };

u.value_(e.asSignal).refresh; 

};


g.();


v.addSliderAction(g);

)





Example 1c:   SharedOut / shared memory



SharedOut will be deprecated in future releases of SC and replaced by a shared memory mechanism (Tim Blechmann). 

SharedOut is at least included in version 3.4.4.



// internal server needed for all examples with SharedOut / shared memory


(

s = Server.internal;

Server.default = s;

s.boot;

)


// start LFO


{ SharedOut.kr(0, LFDNoise3.kr(0.3, 20, 70)) }.play;



// play event pattern


(

p = Pbind(

\dur, 0.15, 

\midinote, Pfunc { s.getSharedControl(0) }

).play;

)

p.stop;



////////////////////////// 


// example with GUI


// ATTENTION: this version of the example works with SC versions > 3.4.4

// in which SharedOut is still supported and which already include 

// the fix of a minor bug which blocked adding of SynthDefs.

// See below for an equivalent example with shared memory.


// If you're using a version <= 3.4.4 you can fix it by yourself, 

// adding this method to SharedOut and recompile:

// *numFixedArgs { ^1 }


// ... or take the example version below the following version


(

s = Server.internal;

Server.default = s;

s.boot;

)


(

SynthDef(\control, { |midiCenter, dev, devFreq| SharedOut.kr(0, LFDNoise3.kr(devFreq, dev, midiCenter)) }).add;


p = Pbind(

\dur, 0.15, 

\midinote, Pfunc { s.getSharedControl(0) } + [0, 4]

);


// start control synth before stream

v = VarGui(synthCtr: [

\midiCenter, [50, 80, \lin, 0.01, 70],

\dev, [0, 20, \lin, 0.01, 20],

\devFreq, [0, 3, \lin, 0.01, 0.5]],

synth: \control, stream: p

).gui(playerPriority: \synth);

)



// this version of the example works also with SC versions <= 3.4.4

  

(

SynthDef(\control, { |midiCenter, dev, devFreq| SharedOut.kr(0, LFDNoise3.kr(devFreq, dev, midiCenter)) }).send(s);

)


(

x = Synth(\control).register;  


// or start paused:

// x = Synth.newPaused(\control).register; 


p = Pbind(

\dur, 0.15, 

\midinote, Pfunc { s.getSharedControl(0) } + [0, 4]

);

)


(

v = VarGui(synthCtr: [

\midiCenter, [50, 80, \lin, 0.01, 70],

\dev, [0, 20, \lin, 0.01, 20],

\devFreq, [0, 3, \lin, 0.01, 0.5]],

synth: x, stream: p

).gui(playerPriority: \synth);

)




// shared memory example, SC version >= 3.5

// start LFO


c = Bus.control(s, 1);


{ Out.kr(c, LFDNoise3.kr(0.3, 20, 70)) }.play;



// play event pattern


(

p = Pbind(

\dur, 0.15, 

\midinote, Pfunc { c.getSynchronous }

).play;

)

       

p.stop;




Example 1d:   HS / PHS and related


With HS server values can be used in PHS objects which mimic event patterns. This is achieved by an OSC demand and respond mechanism which introduces a small amount of additional latency. It works with local and internal server, see Working with HS and HSpar for further details. Using the HS family with VarGui is discussed in HS with VarGui. 

The HS / PHS approach would especially be of interest if control behaviour could more easily be defined by server means than in SC lang (e.g. specific and / or nested UGens) but data should also be further manipulated in the language (e.g. for some kind of combinatorial use such as harmonic or polyphonic calculations).


(

s = Server.local;

Server.default = s;

s.boot;

)


// a HS contains the control synth definition but will also hold playing Synth instances


h = HS(s, { |midiCenter = 70, dev = 20, devFreq = 1| LFDNoise3.kr(devFreq, dev, midiCenter) });



// a PHS refers to a HS and, when played, takes control over the control synth


p = PHS(h, [], 0.15, [ \midinote, Pkey(\val) + [0, 4] ]).play;



// stop player and control synth 


p.free; 



Methods of linked playing / stopping and resuming (stream + help synth) are supported as well as reference to an already playing HS by PHSuse. Various kinds of control synth control and synth value reference are possible with two or more help synths (see HSpar, PHSpar and PHSparUse). 



Example 1e:   audio synths reading from a control bus, discretized


Derived from (2a), disadvantage: SynthDef must be adapted to control needs beforehand.


(

c = Bus.control(s,1);

d = Bus.control(s,1);


SynthDef(\perc_1e, {|amp = 0.1, bus, att = 0.01, rel = 1|

var in = In.kr(bus, 1);

Out.ar(0, (SinOsc.ar(Latch.kr(in, in).midicps, 0, amp) * 

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

}).add;

)


(

x = { Out.kr(c, LFDNoise3.kr(1, 5, 75)) }.play;

y = { Out.kr(d, LFDNoise3.kr(1, 5, 65)) }.play;


p = Pbind(

\instrument, \perc_1e,

\dur, 0.3,

\amp, 0.07,

\bus, [c, d]

).play;

)


(

p.stop;

x.free;

y.free;

)



2.) Continuous LFO control



Example 2a:   audio synths reading from a control bus


The disadvantage of this strategy (compared to 2b) is that synth definitions have to be written especially for control purposes. It must be known in advance which parameters should be controlled by another synth.


(

c = Bus.control(s,1);


SynthDef(\perc_2a, {|amp = 0.1, bus = 0, att = 0.01, rel = 0.25|

Out.ar(0, (SinOsc.ar(In.kr(bus, 1).midicps, 0, amp) * 

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

}).add;

)


(

x = { Out.kr(c, LFDNoise3.kr(3, 10, 65)) }.play;


p = Pbind(

\instrument, \perc_2a,

\dur, 0.3,

\bus, c

).play;

)


(

p.stop;

x.free;

)




Example 2b:   audio synths mapped to a control bus


In general more practical than (2a), though by SC vs 3.4.4 reserved keys (e.g. \freq) can't be mapped to a bus, under these circumstances args would have to be renamed.


(

c = Bus.control(s,1);

d = Bus.control(s,1);


SynthDef(\perc_2b, {|amp = 0.1, midi = 60, att = 0.01, rel = 0.25|

Out.ar(0, (SinOsc.ar(midi.midicps, 0, amp) * 

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

}).add;

)


(

x = { Out.kr(c, LFDNoise3.kr(1, 5, 75)) }.play;

y = { Out.kr(d, LFDNoise3.kr(1, 5, 65)) }.play;


p = Pbind(

\instrument, \perc_2b,

\dur, 0.3,

\midi, [c, d].collect(_.asMap)

).play;

)


(

p.stop;

x.free;

y.free;

)



////////////////////////// 


// example with GUI



(

c = Bus.control(s,1);

d = Bus.control(s,1);


SynthDef(\perc_2b, {|amp = 0.1, midi = 60, att = 0.01, rel = 0.25|

Out.ar(0, (SinOsc.ar(midi.midicps, 0, amp) * 

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

}).add;


SynthDef(\control_2b, { |midiCenter = 70, dev = 20, devFreq = 1, out = 0| 

Out.kr(out, LFDNoise3.kr(devFreq, dev, midiCenter)) 

}).add;

)


(

p = Pbind(

\instrument, \perc_2b,

\dur, Pfunc { ~dur },

// following values will be collections of two elements

\amp, Pfunc { ~amp },

\att, Pfunc { ~att },

\rel, Pfunc { ~rel },

\midi, [c, d].collect(_.asMap)

);


// in gui start control synths before stream player !


v = VarGui([

\dur, [0.05, 0.5, \lin, 0.005, 0.2],

// setting envir variables to collections of two elements

\amp, [0, 0.1, \lin, 0.005, 0.07] ! 2,

\att, [0.005, 0.1, \lin, 0.005, 0.01] ! 2,

\rel, [0.005, 0.5, \lin, 0.005, 0.1] ! 2

],

2.collect { |i| 

var bus = [c,d][i].index;

[\midiCenter, [60, 80, \lin, 0.01, [65, 75][i] ],

\dev, [0, 10, \lin, 0.01, 10],

\devFreq, [0, 3, \lin, 0.01, 0.5],

\out, [bus, bus, \lin, 1, bus]] 

}, p, \control_2b ! 2 

).gui(sliderPriority: \synth, playerPriority: \synth);

)