Wednesday, May 26, 2010

Envelopes

In the last post, we wrote a function to play overtones, but it played them all at the same time. In order to write a piece of music, we need both to be able to tell notes when to start and for how long they should last. This section will be on the latter part of the problem.

"Just like human beings, sounds are born, reach their prime and die; but the life of a sound, from creation to evanescence last only a few seconds" Yamaha GX-1 Guide

We control this process with a type of UGen called an envelope.

(
 SynthDef.new("example3", {arg out = 0, freq = 440, amp = 0.2,
       dur = 1;
  var sin, env_gen, env;
  
  env = Env.triangle(dur, amp);
  env_gen = EnvGen.kr(env);
  sin = SinOsc.ar(freq, mul: env_gen);
  Out.ar(out, sin);
 }).load(s);
)

Synth.new("example3");

There are a few things that make that SynthDef different. One is that it uses variables. Variables are a good way to keep SynthDefs more readable. The easier it is to read, the easier it is to understand, to fix and to change.

The other obvious change is the addition of an envelope. Envelopes have two parts. One is Env. The Env class lets you describe what shape the envelope should have. In this case, we're using a fixed duration envelope shaped like a triangle. When we use a fixed duration envelope, we know the length of the envelope when we create an instance of the Synth. There are other envelopes that we will not know the envelope of until we decide to end the note, for instance, in response to a key up on a keyboard.

triangle is a constructor. It creates a new instance of an Env. The arguments it takes are the duration and the maximum amplitude.

The other part of the envelope is the EnvGen. Since EnvGen is a class, kr must be a constructor. The two most common UGen constructors are ar and kr. You may recall that ar stands for “audio rate.” kr stands for “control rate.” Because the EnvGen is not actually creating sound, but is controlling the amplitude of a sound, we want the control rate. Control rate signals change less often than audio rate signals, and so using kr reduces the load on our computer and makes our SynthDef more efficient.

EnvGen.kr generates the envelope based on the specifications of its first argument, an instance of Env. So, with Env, we define an Envelope. We pass that definition to an Envelop Generator, which plays back the envelope that we defined. We use that envelope to control the amplitude of a SinOsc. We send the output of the SinOsc to an output bus. When the envelope is done, the amplitude of it has returned to zero, so the SinOsc is no longer audible.

With our previous examples, the Synth never ended. The way to stop sound was to stop execution with apple-. . With our new SynthDef, the sound goes on for exactly the length of dur and then ends with no way to restart it. After we create a Synth and play it, there's nothing that can be done with it.

Try running Synth.new("example3"); several times. If you look at the localhost server window, you will see the number of UGens and Synths grow every time you run Synth.new. The Avg and Peak CPU may also increase.

Every time we create a new instance of Synth, we use up more system resources. Eventually we will either run out of memory or the CPU will climb over 100%. We can clear the Synths by hitting apple-., but this still would limit the number of sounds we could play in a row without stopping execution.

We need an automatic way to remove our synths from the server and deallocate their resources. That means that some new Synths would be able to use the resources the old ones were taking up.

Fortunately, EnvGens can both silence a Synth forever and lay it to rest so iti s deallocated. EnvGen takes an argument called “doneAction”. Adding a doneAction to your EnvGen would look like:

env_gen = EnvGen.kr(env, doneAction: 2);

Try changing adding a doneAction to the SynthDef and running Synth.new("example3"); a few times. Notice that now the Synths don’t accumulate. Now, after a Synth stops making sound, it has ended its useful life and so it gets removed from the server because of the doneAction: 2. However, note that the doneAction removes only instances of the Synth. The SynthDef persists and can be used as many times as we'd like.

Envelopes don’t have to only control amplitude, but can control (almost) any aspect of any UGen. You can’t set the bus on Out.ar with an Envelope, but you can control almost anything else. For example, we can cause the frequency to warble


(
 SynthDef.new("example3a", {arg out = 0, freq = 440, amp = 0.2,
       dur = 1;

  var sin, env_gen, env, freq_env;
  
  env = Env.triangle(dur, amp);
  env_gen = EnvGen.kr(env, doneAction: 2);
  freq_env = EnvGen.kr(Env.sine(dur, 50));

  sin = SinOsc.ar(freq + freq_env, mul: env_gen);
  Out.ar(out, sin);
 }).load(s);
)

Synth.new("example3a");

Note that we only need one of the envelopes to have a doneAction.

Summary

  • Envelopes can be used to control how the amplitude of a Synth changes over time.
  • Env is used to describe the shape of an enevelope.
  • EnvGen.kr is what actually plays the envelope.
  • Synths can be deallocated by using the doneAction argument of EnvGen.
  • Envelopes can also be used to control frequency and many other parameters of SynthDefs.

Problems

  1. Try out the different fixed duration envelopes and try them out with different Oscillators. Especially try out the Env.perc envelope with different noise generators.

No comments: