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

  1. //BasicPanning.c
  2. //gcc MKAiff.c BasicPanning.c -o BasicPanning
  3. #include "MKAiff.h"
  4. #include <math.h>
  5. #define SAMPLE_RATE 44100
  6. #define NUM_CHANNELS 2
  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 frequency1 = 440, angle1 = 0, pan1 = 0.2;
  18. double frequency2 = 550, angle2 = 0, pan2 = 0.5;
  19. double frequency3 = 660, angle3 = 0, pan3 = 0.8;
  20. int i;
  21. for(i=0; i<numSamples; i+=NUM_CHANNELS)
  22. {
  23. audioBuffer[i ] = sin(angle1)* pan1 + sin(angle2)* pan2 + sin(angle3)* pan3;
  24. audioBuffer[i+1] = sin(angle1)*(1-pan1) + sin(angle2)*(1-pan2) + sin(angle3)*(1-pan3);
  25. audioBuffer[i ] /= 3;
  26. audioBuffer[i+1] /= 3;
  27. angle1 += frequency1 * TWO_PI_OVER_SAMPLE_RATE;
  28. angle2 += frequency2 * TWO_PI_OVER_SAMPLE_RATE;
  29. angle3 += frequency3 * TWO_PI_OVER_SAMPLE_RATE;
  30. }
  31. aiffAppendFloatingPointSamples(aiff, audioBuffer, numSamples, aiffFloatSampleType);
  32. aiffSaveWithFilename(aiff, "BasicPanning.aif");
  33. aiffDestroy(aiff);
  34. return 0;
  35. }

Output:

Explanation of the Concepts

In an audio environment with multiple speakers, panning is the technique of making a sound louder in some speakers than in others so that the sound appears to be emanating from a distinct physical location amongst the speakers. In a stereo environment with two speakers, for instance, a sound that is equally as loud in both speakers will seem to be emanating from the location directly betwixt them. Increasing the volume of that sound in the right speaker and decreasing it in the left will make it seem to be emanating from a location closer to the right speaker. Your brain actually finds the location of sounds by by comparing the phase relationship of the signal entering each ear, more than by comparing the loudness. Because of this, panning tends to sound somewhat 'artificial'. Nonetheless, it is very effective and very widely used.

Explanation of the Code

On line 9, the number of audio channels has been declared as 2 (stereo). The first channel, by convention, will be bound for the right ear, and the second channel the left. In line 27, it will be noticed that the loop that iterates over the contents of the audio buffer is declared such that it skips over the audio buffer one frame at a time. Remember that a frame contains one sample for each channel, so in this case, the loop increments by two samples at a time. Because of this, in the body of the loop,

audioBuffer[i]

always represents the first, or right, sample of the frame, and

audioBuffer[i+1]

always represents the second, or left sample.

Aside from that, this example is nearly identical to the "Playing Chords" example that just sums three sine-waves. Here, however, on lines 21-23, each sine-wave is assigned a pan location between 0 and 1, where 1 indicates that the sound will be panned far right, 0 is far left, and 0.5 is center. On line 29, the right channel of the audio buffer is filled with the sum of the three, but each wave has its amplitude adjusted according to its respective pan location. On line 30, the left channel is filled with the sum of the same three waves, but each wave, rather than being multiplied by pan, is multiplied by 1-pan, so that sounds that are maximally loud (1) in the right ear will be maximally quiet (1-1, or 0) in the left ear.

Sometimes, for reasons that were demonstrated in the previous chapter on Volume Control, it will be better to multiply a wave not simply by pan, but rather by the square of pan, like so:

audioBuffer[i ] = sin(angle1)* pow(pan1, 2);
audioBuffer[i+1] = sin(angle1)*(1-pow(pan1, 2));