Harmonic Oscillators through Additive Synthesis

Sinewave Addition in Tables

One can also add sine waves together (additive synthesis) to approximate the basic forms presented earlier in this section. Individual sine waves of appropriate frequencies and amplitudes (with matching phases) can be used or one can use a lookup method and fill the table with the sum of the waves. The latter method is shown here.

Sawtooth and Ramp

The sawtooth has perhaps the most simple formula: given a harmonic series of sine waves, multiply each by the reciprocal of their harmonic number and sum. For instance, the first harmonic is multiplied by 1/1, the second harmonic, is multiplied by 1/2, the third harmonic is multiplied by 1/3, and so on. In other words:

 x_{saw}(t) = \sum_{i=1}^{k} \frac{1}{k} sin(2\pi fkt)

where k is the number of harmonics and sin(2πfkt) is a sine wave.

float table[1024];
int harmonics = 10; // 10 harmonics
float amp = 1.0; // amplitude
float max = 0; // for normalization

// clear the table
for(i=0; i < tableSize; i++) '
   table[i] = 0.0;

// fill the table
for(k=1; k <= harmonics; k++) 
{
    // for each harmonic, go through and add it to the table
    for(i=0; i < tableSize; i++) 
    {
        float samp;
        samp = (i*k)/tableSize; // get the increment
        samp = samp * 2PI; // scale the frequency
        table[i] = table[i] + (sin(samp) * 1/harmonic); // add it
        if (table[i] > max)
            max = table[i]; // remember it if it's the largest value we've encountere
    }
}

// normalize
for(i=0; i < tableSize; i++)
   table[i] = table[i]/max; // scale

The following is an animation of the process. N is the number of harmonics used to construct the wave. Notice that as N increases, the result is increasingly sharp and similar to an algebraically constructed sawtooth.

Ramp

The ramp is created by simply inverting the phase of the sine wave used in the sawtooth:

 x_{ramp}(t) = \sum_{i=1}^{k} \frac{-1}{k} sin(2\pi fkt)

Square

The square wave is created in much the same way as the sawtooth but using only the odd-numbered harmonics:

x_{square}(t) = \sum_{i=1}^{2k+1} \frac{1}{k} sin(2\pi fkt)
float table[1024];
int harmonics = 10; // 10 harmonics
float amp = 1.0; // amplitude
float max = 0; // for normalization

for(i=0; i<tableSize; i++)
    table[i] = 0.0;
// fill the table
for(k=1; k <= harmonics; k++) 
{
   // if the harmonic is odd, go through and add it to the table
   if (k & 1 == 1) 
   {
      for(i=0; i < tableSize; i++) 
      {
          float samp;
          samp = (i*k)/tableSize; // get the increment
          samp = samp * 2PI; // scale the frequency
          table[i] = table[i] + (sin(samp) * 1/harmonic); // add it
          if (table[i] > max)
              max = table[i]; // remember it if it's the largest value we've encountered
      }
   }
}

// normalize
for(i=0; i < tableSize; i++)
    table[i] = table[i]/max; // scale

Notice that in the same way as the sawtooth, when we increase the number of harmonics of the square wave we more closely resemble an algebraically constructed one.

Triangle

The triangle wave, like the square wave, uses only odd harmonics but the phase of every other harmonic is inverted (multiplied by -1 or phase change by π). The amplitude of the harmonics, however, is the reciprocal of the square of their mode number. This means that the first harmonic is multiplied by 1/1<sup>2</sup>, the third harmonic is multiplied by 1/3<sup>2</sup>, the fifth by 1/5<sup>2</sup>, etc.

x_{triangle}(t) = \sum_{i=1}^{k} (-1)^{i} n^{-2} (\text{sin}[nt])
float table[1024];
int harmonics = 10; // 10 harmonics
float amp = 1.0; // amplitude
float max = 0; // for normalization
boolean invertPhase = false; // boolean to invert the phase

for(i=0; i<tableSize; i++)
    table[i] = 0.0;

//fill the table 
for(k=1; k <= harmonics; k++) 
{ 
    // if the harmonic is odd, go through and add it to the table 
    if (k & 1 == 1) 
    { 
        for(i=0; i < tableSize; i++) 
        { 
            float samp; 
            samp = (i*k)/tableSize; // get the increment 
            samp = samp * 2PI; 
            // scale the frequency 
            table[i] = table[i] + (sin(samp) * 1/(harmonic**2)); 
            // add it 
            if (invertPhase) 
            { 
                table[i] = -1 * table[i]; // invert the phase 
                invertPhase = false; // don't invert next time 
            } 
            else 
                invertPhase = true; // invert the next time 
            if (table[i] > max)
                max = table[i]; // remember it if it's the largest value we've encountered for normalization 
         }
     } 
}

// normalize 
for(i=0; i < tableSize; i++)
    table[i] = table[i]/max; // scale

Notice that in the same way as the sawtooth, when we increase the number of harmonics of the triangle wave we more closely resemble an algebraically constructed one.

Addition using individual Sinewaves

In the same way we can populate a table to create a waveform, one can also generate and add the same sine tones individually and sum them in the output. The technique is to essentially calculate the relative amplitudes using the same formulas as before, put them in a table, and read from them during playback.

float frequency = 440; // fundamental in Hz
int harms = 10; // number of harmonics
float phase; float harmonicAmp[harms+1]; // a table to keep the amplitudes in // define a function to calculate the amplitudes double sawtoothAmp(int harm) {
return(1.0/(double)harm); } // populate the table of amplitudes for(i=1; i <= harms; i++) { harmonicAmp[i] = sawtoothAmp(i); } // calculate a block
for(sample = 0; sample < samples; sample++)
{
// the phase increment is linearly
// related to sample rate
phase += (frequency * 6.28318531)/samplerate;
sawtooth = 0.0; for(harm = 1; harm <= harms; harm++)
sawtooth += sin(phase * harm) * harmonicAmp[harm];
output[sample] = sawtooth; }

Using this technique, one could also address the amplitude of each harmonic individually after the waveform has been created. This could allow one to blend waveforms in time.

Leave a Reply

Your email address will not be published.