Server-side Sequencing and Triggers


Exploring some UGens that let you sequence on the server, without any language intervention; timing patterns all within a Synth. Like analogue sequencing modules...







Clocking UGens: 



Impulse


A sequence of isochronous clicks, can make a good clock signal: 


//from rhythmic to audio rate

{Impulse.ar(MouseX.kr(1,100))}.play



Dust


Rather than evenly spaced clicks, the opposite is randomly occurring (stochastic) clicks. 


//from rhythmic to audio rate; the Mouse is controlling the average number of clicks per second, they are not evenly spaced! 

{Dust.ar(MouseX.kr(1,100))}.play



Types of LFNoise for linear random noise between -1 and 1 at a certain rate


(

{

[

LFNoise0.ar(100), //step

LFNoise1.ar(100), //linear interpolation

LFNoise2.ar(100) //quadratic interpolation

]

}.plot(0.1)

)



















Triggers


When a signal crosses from a nonpositive value to a positive value, the transition can act as a trigger in the input of some UGens.  


There are rounding errors to watch out for, and you need to avoid positive zero; usually safer to force a transition from -0.01 to 1, for example, rather than 0 to 1 


The clock signals often make good trigger sources, for instance, Impulse. 




















Stepper responds to triggers to go through a sequence: 


Stepper.ar(trig,reset,min,max,step,resetval)


We'll trigger it with an Impulse and make it go between 1 and 10 in steps of 1 (values must be integers).


//plot it out 

{ Stepper.ar(Impulse.ar(100), 0,1,10,1) }.plot(0.3,minval:0,maxval:10); 


Slowed down and used to control a SinOsc frequency


{ SinOsc.ar(Stepper.ar(Impulse.ar(10), 0,1,10,1)*100,0,0.1) }.play

















To get arbitrary pitches (rather than just a monotonic sequence), Stepper can be combined with Select: 


//impulse frequency of 4 is 4 events per second

//kr used since slow rates and Select works with array data second input if kr but not ar

{ Saw.ar(Select.kr(Stepper.kr(Impulse.kr(4,0.1),0,0,7,1),[72,63,67,72,55,62,63,60].midicps),0.1) }.play


//speed control

{ Saw.ar(Select.kr(Stepper.kr(Impulse.kr(MouseX.kr(1,40),0.1),0,0,7,1),[75,63,67,72,55,62,63,60].midicps),0.1) }.play



(As well as this sort of sequencing, Select can also be used to dynamically choose between UGens in a single running Synth)




















Any signal can be turned into triggers. The Trig and Trig1 UGens give 'spiky' signals as output (they hold for a user-specified duration when triggered; Trig1 always outputs a 1, Trig follows the stimulus value).  



//trigger at start of every sinusoidal cycle (where sine goes from negative to positive)

(

{

var source = SinOsc.ar(100); 

//plot both original signal, and the trigger pattern

[source, Trig1.ar(source,0.001)]

}.plot(0.1)

)







In the following examples we'll show going from LFNoise UGens to the trigger points. 


//trigger whenever crossing from negative to positive

(

{var source, trigger; 


source= LFNoise0.ar(100); 

trigger= Trig1.ar(source, 0.001); //0.001 is duration of trigger signal output


[source, trigger]

}.plot(0.2)

)


//trigger on all ups

(

{var source, trigger; 


source= LFNoise0.ar(100); 

trigger= Trig1.ar(source- Delay1.ar(source), 0.001); //0.001 is duration of trigger signal output


[source, trigger]

}.plot(0.2)

)



//trigger on any change

(

{var source, trigger; 


source= LFNoise0.ar(100); 

trigger= Trig1.ar(abs(source- Delay1.ar(source)), 0.001); //0.001 is duration of trigger signal output


[source, trigger]

}.plot(0.2)

)






Latch: on a trigger, hold an input value

Latch.ar(in, trig)


Allows resampling and triggered rendering



//grab the sine's current value 100 times a second

{Latch.ar(SinOsc.ar(133), Impulse.ar(100))}.plot(0.5)



{Latch.ar(LFNoise2.ar(100), Impulse.ar(100))}.plot(0.1) //removes smoothing!




//could be used to create sequencing patterns! 

{SinOsc.ar(300+(200*Latch.ar(SinOsc.ar(13.3), Impulse.ar(10))))*0.2}.play









Non-sustaining envelopes can be retriggered via the gate input to an EnvGen



{EnvGen.ar(Env([0,1,0],[0.01,0.01]),Impulse.kr(50))}.plot(0.1)



//If you set the envelope up carefully, this could be used like a more flexible Stepper

{EnvGen.ar(Env([0,1,0,0.5,-0.4],0.01!4),Impulse.kr(25))}.plot(0.1)



//slowed down by factor of 10 to be heard as held pitches

{SinOsc.ar(400*(1+EnvGen.ar(Env([0,1,0,0.5,-0.4],0.1!4,curve:\step),Impulse.kr(2.5))))}.play



//use midicps on output to get scales

{SinOsc.ar(EnvGen.ar(Env([63,63,60,55,60],0.125!4,curve:\step),Impulse.kr(2)).midicps)}.play

//the Impulse's rate acts like a beats per second here, and the envelope timings are in beats (0.125 per transition)








//percussive sound retriggered 3 times a second

(

{

var sound,env, trig; 


trig= Impulse.ar(3); //trigger source 


sound= Mix(LFPulse.ar(110*[1,5/2],0.0,0.5,0.2));


env= EnvGen.ar(Env.perc(0.02,0.2),trig); //with retriggering controlled by impulse


Pan2.ar(sound*env,0.0)

}.play

)




(Note that if the envelope has a release node, the gate input to an EnvGen is used instead as a control which keeps the envelope held open (gate=1) until released (gate=0); see the EnvGen and Env help files)











Triggers can be set up in SynthDefs with a shortcut; they appear in SynthDefs as t_xxxx arguments or as specified as an explicit \tr in SynthDef rates argument. This is useful when you want to manually force a trigger via a .set message to a Synth 


(

SynthDef(\mytriggersynth,{arg trig=0;

var env;


//must have additional starting level in envelope, else no nodes to go back to

env= EnvGen.ar(Env([2,2,1],[0.0,0.5],'exponential'),trig);


Out.ar(0,Pan2.ar(Resonz.ar(Saw.ar(env*440),1000,0.1),0.0))

},[\tr]).add

)


a= Synth(\mytriggersynth);


a.set(\trig, 1) //if this wasn't an explicit trigger input, this wouldn't reset the envelope














Another triggering example: you could collect triggers from one signal and use them to spawn changes in another part of the UGen graph



//value of third frequency component is a new random number with each trigger

(

{

var sound,env, trig; 


//> is itself a UGen when used in this context; it is outputting the result of comparing the LFNoise0 with the value 0 every sample! 

trig= LFNoise0.ar(13)>0; //trigger source (might also use Dust, for example)


//TRand chooses a random number in its range when triggered

sound= Mix(LFPulse.ar(110*[1,5/2,TRand.ar(3.9,4.1,trig)],0.0,0.5,0.2));


env= EnvGen.ar(Env.perc(0.02,0.1),trig); //with retriggering controlled by impulse


Pan2.ar(sound*env,0.0)

}.play

)















Some oscillators can be retriggered, for example SyncSaw


For sync oscillators: 

hard sync = immediate reset of slave oscillator

soft sync = wait till start of next period for a reset of state


The final frequency and timbre is an interaction of the frequency of the slave oscillator and the syncing (resetting) signal



//SyncSaw is hard sync

{ SyncSaw.ar(MouseX.kr(1,400), MouseY.kr(400,800), 0.1) }.play;















Making a custom hard sync oscillator using an EnvGen and a trigger source


{EnvGen.ar(Env([0,0,1,-1,0],MouseY.kr(0,1)*[0,128,256,128]/SampleRate.ir),Impulse.ar(MouseX.kr(10,300,'exponential')))}.play











To add smoothing and portamento to hard-edged signals, the Lag filter is useful


Lag UGen; shortcut is .lag


(

{

var source; 


source= LFNoise0.ar(10);


[

source, //step

source.lag(0.1) //step with lag of period; so only gets to target value at end of step

]

}.plot(1.0)

)



//portamento/glide

{Saw.ar((Stepper.ar(Impulse.ar(10),0,1,10,1)*200).lag(MouseX.kr(0.0,0.2)))}.play




//another example: Ringz is a resonant filter, exprange puts values from -1 to 1 to the desired range (100 to 2000) with an exponential mapping more fitting to human perception of frequency values

{Ringz.ar(Saw.ar(LFNoise0.kr(5).lag(0.1).exprange(100,2000),0.2), 1000, 0.01)}.play



//.round used to make frequency values round off to nearest 20 Hz (re-quantising the signal)

//perceived speed-ups due to interaction of slower lag and rounding of frequency

{Ringz.ar(Saw.ar(LFNoise0.kr(5).lag(MouseX.kr(0.01,0.3)).exprange(100,2000).round(20),0.2), 1000, 0.01)}.play




The Decay UGen can also be used to put a smoothed tail on an impulse (or any signal)


(

{

Decay.ar(Impulse.ar(100),0.01)

}.plot(0.1)

)


See also Decay2 for smoothing at attack and release. 


(

{

Decay2.ar(Impulse.ar(100),0.005,0.01)

}.plot(0.1)

)



Other mechanisms (later):

Demand rate UGens


Sequencing and event reactive functionality can be constructed with other UGens like Index, IEnvGen, PulseCount, PulseDivider, ToggleFF, SetResetFF, Timer, Schmidt and more