An Audio Synthesis Textbook For Musicians, Digital Artists and Programmers by Mike Krzyzaniak

  1. //LinearEnvelope.c
  2. //gcc MKAiff.c LinearEnvelope.c -o LinearEnvelope
  3. #include "MKAiff.h"
  4. #include <math.h>
  5. #define SAMPLE_RATE 44100
  6. #define NUM_CHANNELS 1
  7. #define BITS_PER_SAMPLE 16
  8. #define NUM_SECONDS 3
  9. const int numSamples = NUM_SECONDS * NUM_CHANNELS * SAMPLE_RATE;
  10. #define PI 3.141592653589793
  11. const double TWO_PI_OVER_SAMPLE_RATE = 2*PI/SAMPLE_RATE;
  12. int main()
  13. {
  14. MKAiff* aiff = aiffWithDurationInSeconds(NUM_CHANNELS, SAMPLE_RATE, BITS_PER_SAMPLE, NUM_SECONDS);
  15. if(aiff == NULL) return 0;
  16. float audioBuffer[numSamples];
  17. double frequency = 440, angle = 0;
  18. double envelopeDuration=3, startingValue=0, endingValue=1;
  19. envelopeDuration *= SAMPLE_RATE;
  20. double currentValue = startingValue;
  21. double valueIncrement = ((endingValue - startingValue)/envelopeDuration);
  22. envelopeDuration *= SAMPLE_RATE;
  23. int i;
  24. for(i=0; i<numSamples; i+=NUM_CHANNELS)
  25. {
  26. audioBuffer[i] = sin(angle) * currentValue;
  27. angle += frequency * TWO_PI_OVER_SAMPLE_RATE;
  28. if(i/NUM_CHANNELS < envelopeDuration) currentValue += valueIncrement;
  29. }
  30. aiffAppendFloatingPointSamples(aiff, audioBuffer, numSamples, aiffFloatSampleType);
  31. aiffSaveWithFilename(aiff, "LinearEnvelope.aif");
  32. aiffDestroy(aiff);
  33. return 0;
  34. }

Output:

Explanation of the Concepts

The simplest envelope just moves linearly from 0 to 1 (or vice versa) over the course of its duration thus (the envelope's value is plotted as a function of time):

Envelope

When applied to the amplitude of a wave, as here, it can be heard that the sound seems to get loud all at once at the beginning of the envelope, and seems to get louder in progressively smaller increments as the envelope continues. An explanation of why this happens can be found in the chapter on 'Basic Volume Control'.

In this example, the envelope's duration is the same as the duration of the entire file. In most real-life situations this will not be the case. This raises the question of what will happen to the envelope once it finishes its course. In this example, if the audio file were lengthened, the envelope would hold the value of 1 once it is done. In most analog applications it will return to 0 and hold that value. In the digital dominion, however, the envelope will usually need to terminate whatever created it once it finishes. In an object-oriented programming environment, for instance, the oscillator that the envelope controls will have to set itself as the envelope's delegate, so that when the envelope terminates it can alert the oscillator which can then deallocate itself. In such a case, the envelope will neither hold 1 nor 0 nor any other value once it terminates. The specifics of how all of that works are somewhat beyond the scope of this chapter. It is merely important to note that one will need to pay special attention to the end of the envelope, although that will be somewhat glossed over here in attempt to make the basic programatic control of an envelope clear.

Explanation of the Code

The envelope's duration in seconds, starting value and ending value are declared on line 23. On line 24, the envelope duration in seconds is converted to a duration in samples by multiplying it by the sample rate. At any point in time, the current value of the envelope will be stored in the variable declared on line 25 which is set equal to, obviously, the starting value of the envelope. For each frame, the current value of the envelope will need to be incremented by some value. This is computed on line 26, the same as it was for a sawtooth wave, by dividing the entire range of the envelope (endingValue - startingValue) by the total number of samples in the envelope.

On line 32, the familiar sine wave's amplitude is multiplied by the current value of the envelope. On line 34 the current value of the envelope is incremented, but only if the current frame (i/NUM_CHANNELS) has not surpassed the envelope's duration. If it were necessary for this envelope to return to 0 after its expiration, line 34 could be followed by:

else currentValue = 0;