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