12 -Voltage control, Vibrato, Envelopes, Triggers, Gates, ar, kr, Reciprocals 

Early synthesizers were built with modules name with the acronyms VCO, VCA, and VCF. They stand for voltage controlled oscillator, amplifier, and filter. The terms are a bit archaic, but the concepts are at the core of synthesis in general and SC patches specifically. Control simply means changing a parameter. When you turn the volume of your stereo up you are manually controlling the amplitude parameter. When you press your finger down on the string of a guitar you manually control the length of the string and therefore the frequency. The vision of pioneers in electronic music was the precision and capacity of control through a voltage, automatically or mechanically. They realized that the limitations inherent in humans and many instruments (how high we can sing, how fast we can play, how loud a piano can play) could be surpassed with electricity. They were right, and today the limits of music generated on machines15 exceed our ability (inability) to perceive. 

Voltage control is where one ugen is used to control a parameter or argument of another. We saw it in the first patch. The LFNoise0 generated values at a given rate and those values were used as the frequency for the SinOsc. When we used a MouseX to control frequency it was manual control. Using an LFNoise0 is voltage or mechanical control. 

One simple illustration of voltage control is vibrato. Vibrato is a slight fluctuation in either the amplitude (in the case of voice) or pitch (in the case of stringed instruments). String instruments roll the finger forward and backward. The actual pitch then moves between, say 435 and 445, essentially moving in and out of tune about 5 times per second. In some previous SinOsc patches the frequency argument was a static value (such as 440). For a 
vibrato we need to replace the static value with some function or ugen that will change over time smoothly between 335 and 445. The shape of a vibrato is really like a sine wave: it moves back and forth between two values at a periodic and predictable rate. Could we use two SinOsc Ugens, the output of one as the freq argument for another SinOsc? Yes, if it is scaled and offset correctly. Draw a sine wave with 0 in the center, 1 at the top and –1 at the bottom. Change the 0 to 440, the 1 to 445 and the –1 to 435. Can you calculate what the scale and offset need to be to achieve those values? 

The answer is scale of 5 and offset of 440. 

There is one other value we need to consider, the frequency of the control SinOsc. How often should it move between 435 and 445? About 5 times per second should make a natural vibrato. So the frequency of the vibrato SinOsc should be 5 Hz. The vibrato section of the patch then would look like this. I'll use keyword arguments for clarity: 

12.1. SinOsc as vibrato 

SinOsc.ar(freq: 5, mul: 5, add: 440)

In the patch below the SinOsc with a frequency of 5 is stored in the variable "vibrato." If we were to listen to just the vibrato part of the patch we wouldn't hear anything because 5 is a frequency what we hear as pitch, and 450 is a value too high for the hardware to convert to sound. But when it is then used as the frequency argument for the SinOsc then that outer Ugen actually produces audio we can hear. 

12.2. vibrato 

(
{
	var vibrato;
	vibrato = SinOsc.ar(freq: 5, mul: 5);
	SinOsc.ar(440 + vibrato, mul: 0.5)
}.play
)

Experiment with each of the controls in the vibrato SinOsc above to see how they affect the sound. Which value affects the speed of the vibrato? Which value affects the depth? This is a fairly believable vibrato, if a bit crude. A lot of early electronic music sounded plastic, crude, or flat. Real musicians don't dive right into a vibrato; they usually start with a pure tone then gradually increase it. Which values would you change, and what values would you use to make the vibrato more natural? 

The Theremin, an early electronic instrument, and one of the first examples of voltage control, was used a lot in sci-fi sound tracks. It is most identified with the continuum or glissando between pitches and a solid wide vibrato16. Here is a virtual Theremin with a built in voltage controlled wide and consistent vibrato. 

12.3. Theremin 

(
{ 
	SinOsc.ar(
		freq: MouseY.kr(3200, 200, lagTime: 0.5, warp: 1) * 
		(1 + SinOsc.kr(6, mul: 0.02)), //Vibrato 
		mul: abs(MouseX.kr(0.02, 1)) //Amplitude
	) 
}.play
)

The mul argument of the control SinOsc will change the depth of the vibrato. If it is set to 0 there is no vibrato, if it is set to 5 it is at a good depth. But both values are static. They don't change with time as natural vibratos do. We should replace the static 5 with another time 
variant control. 

There are countless Ugens that will work. For now we can try Line.kr, (see also XLine.kr) which moves from a minimum to a maximum over a specified length of time. Here is where it would fit into the patch: 

12.4. Better vibrato (SinOsc, Line.kr) 

(
//Vibrato
{
var depthChange, vibrato;
depthChange = Line.kr(0, 5, 3);
vibrato = 	SinOsc.ar(freq: 5, 	mul: depthChange, add: 440); 
SinOsc.ar(
	vibrato, 
	mul: 0.5)
}.play
)

The first argument for Line.kr is the beginning value, the second is the ending value, and the third is the amount of time in seconds it will take to move between them. Try beginning with a high value (e.g. 10) and ending with 0. Try a faster or slower amount of time. 

In this patch we control the depth of the vibrato. Would it be more natural to control the speed (frequency) of the vibrato over time rather than the depth? What would you change to get such an effect? Could you control both? Could you also control the overall pitch, e.g. 
moving between 300 to 400? 

The vibrato SinOsc and the Line.kr are both "voltage" controls in the classic sense. That is they are controlling a parameter (frequency) of another ugen. We tried to recreate a natural sounding vibrato. A violin will rarely exceed a speed of 10 times per second or a depth beyond 40. But with synthesis, it is easy, even inviting to go beyond those limits. Try increasing the mul and the freq arguments to values beyond what you would expect from a natural vibrato: 33, 100, 1200, etc., for freq, and the same for mul. Try changing the add to other pitches. Try replacing the add with another control; a SinOsc or a Line. Any static value can be replaced with a control. Try using controls other than Line. (Calculate the offset and scale for each carefully so as not to create negative values.) The sounds generated by these excessive values are unique to synthesis and the electronic age. 

Of the two SinOsc objects one is generating frequencies that we hear. The other is generating frequencies below audio range (e.g. 10 Hz, 5 Hz). We don't actually hear that sound, but we do hear the affect it has on the audio range oscillator. Many synthesizers use the term LFO or 
Low Frequency Control. Nearly any oscillator in SC can be used at LFO rates. Here are several examples using different wave shapes. Experiment with the frequency, scale, and offset for each LFO. Make sure the offset (add) is greater than the scale (mul). If the scale is greater you'll get negative values. Don't exceed 20 Hz for the frequency of these controls (lest you stumble onto something interesting). We'll cover that later. 

12.5. Other LFO controls () 

(
{ //SinOsc (sci-fi)
var lfo;
lfo = SinOsc.ar(freq: 10, 	mul: 100, add: 400); 
SinOsc.ar(lfo, mul: 0.5)
}.play
)

(
{ //Pulse (phone ring)
var lfo;
lfo = LFPulse.ar(freq: 15, mul: 200, add: 1000); 
SinOsc.ar(lfo, mul: 0.5)
}.play
)

(
{ //Saw
var lfo;
lfo = LFSaw.ar(freq: 2, mul: -100, add: 600); 
SinOsc.ar(lfo, mul: 0.5)
}.play
)

(
{ //Noise (computer)
var lfo;
lfo = LFNoise0.ar(freq: [28, 27], mul: 1000, add: 2000); 
SinOsc.ar(lfo, mul: 0.5)
}.play
)

(
{ //Noise (manic birds)
var lfo;
lfo = LFNoise1.ar(freq: [28, 27], mul: 400, add: 2000); 
SinOsc.ar(lfo, mul: 0.5)
}.play
)

Envelopes 

The Line Ugen in the previous patch is a type of envelope. Envelopes17 describe a single event (as opposed to periodically repeating events) that changes over time. Typically they are used to control amplitude, but they can be applied to any aspect of sound such as frequency, 
or as in the last patch, the depth of a vibrato. 

An envelope in SC has an additional function: when it has ended it can signal the server to stop playing that ugen and free up the CPU. Without this the events in a composition would add up and eventually max out the CPU. 

We all know the amplitude of a musical event decreases over time (this is called decay), but there is also an attack, which is the beginning of a sound. A marimba has a sharp attack. An organ has a softer or long attack. A violin may have a short or a very long gradual attack depending on how the bow is used. A piano has a sharp attack, but not as sharp as a wood block. The difference may be only a few milliseconds, but you may be surprised to discover how easy it is to distinguish between very small variations in attack time. All presets on all synthesizers use envelopes to describe the change of volume over time. 

The most common terms used to describe envelopes are attack, decay, sustain, and release or ADSR. Simpler envelopes may include only AR, or attack and release. Below is a graph showing each event in an envelope. 

Attack Decay Sustain Release 

There are two types of envelopes. Fixed duration and sustain envelopes. The sections of a fixed duration envelope will always be the same duration. Fixed envelopes usually have an attack and a release. The duration of a sustain envelope will change depending on the length of a gate, e.g. how long a key is held down. Percussion instruments such as cymbals, marimba, chimes, etc., have fixed duration envelopes. Once they are set in motion you can't control the duration of the decay. Organs, voice, violins, brass, etc., have sustaining envelopes, where each note continues to sound until the key is released or the musician stops blowing or bowing. At that point the pitch dies away. 

SC uses EnvGen and Env to create envelopes. Env describes the shape of the envelope and EnvGen generates that shape when supplied a trigger or gate. 

Triggers, Gates, messages ar (audio rate) and kr (control rate), Reciprocals 

When you press the key of a consumer synthesizer the first piece of information that is sent to the processor is a trigger to begin that event. Below are examples of two triggers. The first is a periodic trigger, the second is a random trigger. The first two examples do not use the trigger to begin an envelope, but simply play the triggers, which will sound like pops (not really intended for audio output). They both take one argument. For Impulse it is the number of pulses per second. For Dust, it is the average number of pulses, or average density. 

Previously we learned that the mul argument in a SinOsc corresponds with volume. An EnvGen and Env can be used in conjunction with a trigger to supply events with an envelope for the mul or amplitude of the SinOsc, as in the third and fourth examples below. 

12.6. Trigger and Envelope (Impulse, Dust, EnvGen, Env) 

{Impulse.ar(4, mul: 0.5)}.play

{Dust.ar(5)}.play

(
{
	SinOsc.ar(
		440,
		mul: EnvGen.kr(Env.perc(0.001, 1.0), Impulse.kr(2))
	)
}.play
)

(
{
	SinOsc.ar(
		460,
		mul: EnvGen.kr(Env.perc(0.001, 0.3), Dust.kr(2))
	)
}.play
)	

Try changing the arguments for Impulse and Dust. The arguments for Env.perc are attack and decay. Try changing both. Notice that the decay of a current event is cut off if the next trigger begins before it can finish its decay. 

SinOsc uses the ar message while the ugens Dust and Impulse use kr. The ar stands for audio rate while kr is control rate. The SinOsc is generating actual audio so it needs to have a sample rate of 44.1k. But controls do not need such high resolution and are more efficient if 
generated at a lower rate. 

Any positive value will be interpreted by EnvGen as a trigger, as illustrated by the patch below. A SinOsc moves repeatedly from positive to negative values. When the SinOsc moves from negative to positive the EnvGen is triggered. In the second example a MouseX is used to map the X axis of the screen to values between –0.1 and 0.1 (the center of the screen is 0). When the mouse crosses the middle of the screen a trigger is sent to EnvGen. 

12.7. Trigger and Env (EnvGen, Env, Impulse, MouseX.kr) 

(
{
	SinOsc.ar(
		440,
		mul: EnvGen.kr(Env.perc(0.001, 0.3), SinOsc.ar(2))
	)
}.play
)

(
{
	SinOsc.ar(
		440,
		mul: EnvGen.kr(Env.perc(0.001, 0.3), MouseX.kr(-0.1, 0.1))
	)
}.play
)

The example below uses a variable to link several arguments together. The LFNoise is sending a new pitch at the rate of triggerSpeed. The envelope should be the same. Also, the decay rate (second argument of Env.perc) should be proportional to the trigger speed, in this case, 1/2. Try changing the trigger speed and the attack rate. Notice that the decay is longer when the trigger is slower. Can you also link the duration of the attack to the trigger speed? 

12.8. Envelope and Trigger (LFNoise0, EnvGen, Env, Impulse) 

(
var triggerSpeed;

triggerSpeed = 8;

{
	SinOsc.ar(			
		LFNoise0.ar(triggerSpeed, mul: 500, add: 1000),
		mul: EnvGen.kr(
			Env.perc(0.001, 1/triggerSpeed), 
			Impulse.kr(triggerSpeed)
		)
	)
}.play
)

Events can be expressed either as duration (how many seconds it lasts) or as frequency (the number of events in a given period, e.g. a second). In the patch above the trigger speed is a frequency, that is to say it is expressed as a number of events per second. But the decay argument in the Env.perc is a duration. How do they relate? 

Frequency and duration are reciprocals. That is, d = 1/f and f = 1/d. A frequency of 100 Hz has a duration of 1/100th of a second. A duration of 10 seconds has a frequency (in seconds) of 1/10th of or 0.1. 

Since SC understands both decimal and fractional notation my students have found a cheat; always use fractional notation and let SC do the math. The numerator is the number of events and the denominator is the number of seconds: 1/5 is one event every 5 seconds (equivalent to 0.2), 3/5 is three events in 5 seconds, 35/41 is 35 events in 41 seconds. If the denominator is 1 (15/1 or fifteen events in one second), you just drop the 1 and use 15. 

You can also set up variables at the beginning of the program that link duration with frequency. 

12.9. Duration, attack, decay 

var dur, att, dec, trigFreq;

dur = 10;
att = dur*0.1;
dec = dur*0.9;
trigFreq = 1/dur;

A gate is a trigger and duration. (A trigger is a gate with 0 duration.) Triggers are used for fixed length envelopes. A gate can be used for both fixed and sustaining envelopes, but it is necessary for sustained envelopes. The duration of the gate defines sustain time. 

Env.adsr is a gated envelope. The arguments are attack, decay, sustain and release. The attack is the length of the initial rise to 1.0. The decay is the length of time it takes to fall to the sustain phase. Note that sustain is not the length of sustain, but the level at which the sustain portion remains. The release is the time it takes to move from the sustain level back to 0. 

MouseX18 can be used as a trigger, as seen above, but it can also be a gate. As long as the MouseX value is in the positive range the gate will remain "open." While the gate is open the Env.adsr will attack, then decay to and remain at the sustain value. When the MouseX falls below 0 the gate closes and the Env.adsr releases. Try changing the values for Env.adsr. 

The second example places the EnvGen and Env in the freq argument (with appropriate changes) for SinOsc to show envelopes can be used for controls other than amplitude. Also, it is easier to distinguish changes in pitch, so you can hear the shape of the envelope better. 

12.10. Envelope and Gate (EnvGen, Env, MouseX) 

(
{
	SinOsc.ar(440,
		mul: EnvGen.kr(
			//Envelope is attack, decay, sustain level, release
			Env.adsr(0.001, 0.3, 0.2, 0.1), 
			MouseX.kr(-0.1, 0.1) //gate
		)
	)
}.play
)

(
{
	SinOsc.ar(
		400 + EnvGen.kr(
			Env.adsr(0.3, 0.4, 0.2, 1), 
			MouseX.kr(-0.1, 0.1),
			1000
		),
		mul: 0.5
	)
}.play
)

Finally, LFNoise019 is a random source that wanders between –1 and 1. It can be used to supply a random gate (random triggers and random sustain times). When it is positive the gate is open, when it falls below 0 the gate is closed. 

12.11. Envelope and Gate (EnvGen, Env, MouseX) 

(
{
	SinOsc.ar(
		LFNoise0.kr(13, 1000, 1200),
		mul: EnvGen.kr(
			//Envelope is attack, decay, sustain level, release
			Env.adsr(0.001, 0.3, 0.2, 0.1), 
			LFNoise0.kr(3) //gate
		)
	)
}.play
)

With even the most sophisticated synthesizers you rarely have envelopes with more than 3 or 4 break points (where the envelope changes direction). Real instruments are capable of much more complex variations in amplitude. SC allows for this level of complexity with the "new" message. The first two arguments are arrays. The first array contains levels for each break point, the second array contains durations between each break point. Note that there is one more value in the levels array than there is in the times array. 

12.12. Complex Envelope (Env.new) 

{SinOsc.ar(200 + EnvGen.kr(Env.new(
	[0, 1.0, 0.6, 0, 0.2, 0, 0.4, 0.6, 0.3, 1.0, 0.5]*0.5,
	[1, 0.5, 0.5, 0.7, 0.3, 0.6, 0.5, 0.8, 0, 0.4]), levelScale: 500))}.play

Finally, here are examples where the pitch or frequency for each event is chosen from a range. With many instruments the sustain of an event is short in higher frequencies and longer for lower notes. This patch demonstrates how to link the frequency of each event with the envelope duration using a variable. There are a few ugens you haven't seen before. Just read the help files. 

Practice, Bells 

12.13. Bells (var, Mix, Array.fill, Pan2.ar, SinOsc, EnvGen, Env, Dust) 

(
//frequency linked to envelope length
  //high notes short, low long
{
var frequency;
Mix.ar(
	{
		frequency = rrand(100, 5000);
		Pan2.ar(
			SinOsc.ar(
				frequency,
				mul: EnvGen.kr(
					Env.perc(0.001, 500/frequency), 
					Dust.kr(0.05),
					0.2
				)
			), 
			rrand(-1.0, 1.0)
		) 
	}.dup(100)
)
}.play
)

(
//frequency linked to decay length
  //basically the same code but more compact
  //low notes short, high long
{var frequency;
Mix.ar({
		frequency = rrand(100, 3000);
		Pan2.ar(SinOsc.ar(frequency,
			mul: EnvGen.kr(Env.perc(0.001, frequency/1000), 
				Dust.kr(0.05), 0.2)), rrand(-1.0, 1.0)) }.dup(100))}.play
)

(//high notes short, low long
{var frequency;
Mix.ar({
		frequency = rrand(100, 3000);
		Pan2.ar(SinOsc.ar(frequency,
			mul: EnvGen.kr(Env.perc(200/frequency, 0.0001), 
				Dust.kr(0.05), 0.2)), rrand(-1.0, 1.0)) }.dup(100))}.play
)

(//low notes short, high long
{var frequency;
Mix.ar({
		frequency = rrand(100, 1000);
		Pan2.ar(SinOsc.ar(frequency,
			mul: EnvGen.kr(Env.perc(frequency/500, 0001), 
				Dust.kr(0.05), 0.05)), rrand(-1.0, 1.0)) }.dup(100))}.play
)

12. Exercises 

12.1. In this patch, what is the speed of the vibrato?
{SinOsc.ar(400 + SinOsc.ar(7, mul: 5))}.play

12.2. Rewrite the patch above with a MouseX and MouseY to control the depth and rate 
of vibrato. 

12.3. Rewrite the patch above using an LFPulse rather than a SinOsc to control frequency 
deviation. 

12.4. Rewrite the Theremin patch so that vibrato increases with amplitude. 

12.5. Write a patch using a Pulse and control the kwidth with a Line moving from 0.1 to 
0.9 in 20 seconds. 

12.6. Write a patch using a SinOsc and EnvGen. Use the envelope generator to control 
frequency of the SinOsc and trigger it with Dust. 

12.7. What is the duration of a single wave with a frequency of 500 Hz? 

12.8. What is the frequency, in seconds, of two and a half minutes?