Monday, October 8, 2012

In Which the Sum is Greater

Hey all, sorry for the rather long hiatus, life took over with life-related things (how aggravating). I did make someone a really neat birthday gift, though (alas, unrequited love).

At any rate, this week's post features several items of interest. I shall first talk about various "standard" waveforms which we may generate, including the sine wave, the square wave, both the rising and falling sawtooth waves, and the triangle wave. Next, I will touch upon the basics additive synthesis. Finally, I intend on doing some high level outlining of what I have in store for the next few weeks of this independent study. Exciting stuff, so let's get going; adventure awaits!

Geometric Waveforms
Sound waves in the wild are extremely complex, nuanced entities. However, using some cool magicks (or science, whichever you prefer), we can emulate these waves using simpler waveforms manipulated in various manners. There are several simple waveforms about which we are interested. First up is the infamous and ubiquitous sine wave. The sine wave, like all the waves we shall discuss henceforth, is a periodic wave. In this case, the sine wave is the graphical comparison of the value of sine at its corresponding radian measure in a circle. Since radians essentially --appear-- to repeat in a circle every two pi, the sine wave repeats its pattern every two pi, thus giving it its periodic properties.

Fortunately for us, the C libraries help us avoid having to write the code to efficiently calculate the sine value for a given radian measure. Here is the code to create an 8-bit sine wave:

1:  int * generateSine( int tableSize ) {  
2:      int * sine = (int *) malloc( tableSize * sizeof( int ) );  
3:      float currentPhase = 0.0f;  
4:      float phaseIncrement = ( 2.0f * M_PI ) / (float) tableSize;  
5:        
6:      int i;  
7:      for( i = 0; i < tableSize; i++ ) {  
8:          sine[i] = (int) ( ( 127.0f * sin( currentPhase ) ) + 127.0f );  
9:          currentPhase += phaseIncrement;  
10:      }  
11:        
12:      return sine;  
13:  }  

Bear in mind that the float literals in line 8 amplify and transpose each sample of the sine wave such that it can be represented in an 8-bit unsigned char. This value can be output in parallel and converted to an analog signal via a DAC.

Here is the output of the sine wave above from an AVR microcontroller, captured on an oscilloscope:


The slightly flat trough is due to some clipping I had from my amplifier circuit. The wavetable values are output at such a rate that a single full iteration through the table occurs at a rate of 440 Hz, or concert A.

Not only may we generate fancy little sine waves in this manner, but we may also generate other geometric waveforms as well. Below is the source code and the resulting oscilloscope captures for the triangle, rising sawtooth, falling sawtooth, and square waves.

triangle:
1:  int * generateTriangle( int tableSize ) {  
2:      int * triangle = (int *) malloc( tableSize * sizeof( int ) );  
3:      float currentPhase = 0.0f;  
4:      float phaseIncrement = ( 2.0f * M_PI ) / (float) tableSize;  
5:        
6:      int i;  
7:      for( i = 0; i < tableSize; ++i ) {  
8:          float sample = ( ( 1.0f / M_PI ) * currentPhase - 1.0f );  
9:          if( sample < 0.0f ) {  
10:              sample = -sample;  
11:          }  
12:          sample = 2.0f * ( sample - 0.5f );  
13:          triangle[i] = 255 - ( 127 + 127.0f * sample );  
14:          currentPhase += phaseIncrement;  
15:      }  
16:      return triangle;  
17:  }  



rising sawtooth:
1:  int * generateRisingSawtooth( int tableSize ) {  
2:      int * rsawtooth = (int *) malloc( tableSize * sizeof( int ) );  
3:      float currentPhase = 0.0f;  
4:      float phaseIncrement = ( 2.0f * M_PI ) / (float) tableSize;  
5:    
6:      int i;  
7:      for( i = 0; i < tableSize; ++i ) {  
8:          rsawtooth[i] = 127 + 127.0f * ( ( 1.0f / M_PI ) * currentPhase - 1.0f );  
9:          currentPhase += phaseIncrement;  
10:      }  
11:        
12:      return rsawtooth;  
13:  }  




falling sawtooth:
1:  int * generateFallingSawtooth( int tableSize ) {  
2:      int * fsawtooth = (int *) malloc( tableSize * sizeof( int ) );  
3:      float currentPhase = 0.0f;  
4:      float phaseIncrement = ( 2.0f * M_PI ) / (float) tableSize;  
5:        
6:      int i;  
7:      for( i = 0; i < tableSize; ++i ) {  
8:          fsawtooth[i] = 127 + 127.0f * ( ( -1.0f / M_PI ) * currentPhase + 1.0f );  
9:          currentPhase += phaseIncrement;  
10:      }  
11:      return fsawtooth;  
12:  }  



square:
1:  int * generateSquare( int tableSize ) {  
2:      int * square = (int *) malloc( tableSize * sizeof( int ) );  
3:      float currentPhase = 0.0f;  
4:      float phaseIncrement = ( 2.0f * M_PI ) / (float) tableSize;  
5:        
6:      int i;  
7:      for( i = 0; i < tableSize; ++i ) {  
8:          if( currentPhase <= M_PI ) {  
9:              square[i] = 255;  
10:          } else {  
11:              square[i] = 0;  
12:          }  
13:          currentPhase += phaseIncrement;  
14:      }  
15:      return square;  
16:  }  


Notice how the square wave isn't quite square at its high level? This is most likely due to the high pass filter in my circuit. The high pass filter allows high frequency signals through while filtering out the low frequency signals - in this case, DC voltages.

Additive Synthesis
So, the 800-lb gorilla in the post is just how can we construct super cool waves that emulate the smooth sounds of Kenny G's sax or the dirty wail of Jimi Hendrix's guitar? Well, Fourier gave us a tool which can allow us to do so. The Fourier series tells us that a complex wave can be described as the sum of many, many simple waves. This technique is known as additive synthesis in digital sound. We can continually add harmonics (integer multiples of a fundamental frequency) at different weights to create a fuller sound. Below is the code to create sound waves with one or two harmonic tones above the fundamental frequency. The waveforms are all sine waves. Both of these functions use the generateSine() function listed above.

One harmonic:
1:  int * addOneHarmonic( int tableSize, float fundamentalRatio, float harmonicRatio ) {  
2:      int * fundamental = generateSine( tableSize );  
3:      int * table = (int *) malloc( tableSize * sizeof( int ) );  
4:        
5:      float max = 0.0f;  
6:      int i;  
7:      int j = 0;  
8:      for( i = 0; i < tableSize; ++i ) {  
9:          table[i] = fundamentalRatio * fundamental[i] +  
10:          harmonicRatio * fundamental[j % tableSize];  
11:          if( (int) max < table[i] ) {  
12:              max = table[i];  
13:          }  
14:          j += 2;  
15:      }  
16:        
17:      float scalar = 255.0f / max;  
18:        
19:      // normalize the samples  
20:      for( i = 0; i < tableSize; ++i ) {  
21:          table[i] *= scalar;  
22:      }  
23:        
24:      free( fundamental );  
25:      return table;  
26:  }  

Two harmonics:
1:  int * addTwoHarmonics( int tableSize, float fundamentalRatio,  
2:  float harmonicOneRatio, float harmonicTwoRatio ) {  
3:      int * fundamental = generateSine( tableSize );  
4:      int * table = (int *) malloc( tableSize * sizeof( int ) );  
5:      float max = 0.0f;  
6:      int i;  
7:      int j = 0;  
8:      int k = 0;  
9:      for( i = 0; i < tableSize; ++i ) {  
10:          table[i] = fundamentalRatio * fundamental[i] +  
11:          harmonicOneRatio * fundamental[j % tableSize] +  
12:          harmonicTwoRatio * fundamental[k % tableSize];  
13:          if( (int) max < table[i] ) {  
14:              max = table[i];  
15:          }  
16:          j += 2;  
17:          k += 3;  
18:      }  
19:        
20:      float scalar = 255.0f / max;  
21:        
22:      // normalize the samples  
23:      for( i = 0; i < tableSize; ++i ) {  
24:          table[i] *= scalar;  
25:      }  
26:        
27:      free( fundamental );  
28:      return table;  
29:  }  

In each of the for loops, we iterate through the fundamental frequency table at different rates. The first harmonic above the fundamental frequency, we iterate twice as fast, and for the second harmonic, three times as fast. This emulates frequencies which are integer multiples of the fundamental frequency.

Here are the oscilloscope outputs for each of the above bits of source code:

One harmonic:

Two harmonics:

This can be extrapolated to however many added waves your greedy little heart desires.

Oh, and here's a photo of my circuit! The mess of resistors is an R-2R ladder, which acts as my DAC, and the IC is an op amp used in a non-inverting amplifier setup. This allows me to actually hear a tone coming out of that speaker, since the microcontroller doesn't quite have the punch to drive the speaker on its own.

Pretty spiffy, huh?

Concluding Stuffs
Soo I had said I would talk about what's in store for the next few weeks, but this post took awhile, and I'm feeling rather tuckered out, so I'll save it for tomorrow or the next day. Sorry folks! Keep on truckin' though, and if anyone wants some source code or circuit diagrams, just drop a comment below. Happy adventures!

--End Transmission--

No comments:

Post a Comment