Understanding Digital Sound Waves: Sine Wave Synthesizer

 

    Following the inspiration of the last blog post, I decided to share another program that any of my readers can build and run without any additional equipment beyond their computer and its built-in speakers (or whatever external speakers you choose to use). This blog will also serve to build toward understanding audio programming better, hopefully setting us up to experiment with some wave shaping distortion functions. I decided that it would be a good idea to share my experience learning audio programming, and that means I should show the steps it took me to really understand these concepts. The next step for me was understanding a simple sine wave synthesizer and I want to share this with you so that you can also run the program and see what’s going on under the hood.

               To start, I must say this program is not mine and you can access it here. Hopefully I can help explain it in depth and relate it to general audio programming in a way that adds some insight, as some building blocks are not explained there. The idea here is to build a program that will output a simple sine wave at variable frequencies depending on what value you set a slider to. When you are finished walking through it you should better understand how frequencies are output in digital audio.

               This program uses the same basic audio application template that the white noise generator did, so we will still do the majority of our work in the getNextAudioBlock() method. This time, however, we will also need to use the prepareToPlay() function to set up the angle of the sine wave from sample to sample. A new method called updateAngleDelta() is added to be called in prepareToPlay() and, as the name suggests, handles updating the angle between samples. Finally, we need a few global variables to track these values. These are named currentSampleRate, currentAngle, and angleDelta.

               The currentSample rate is set in prepareToPlay() method as it is a member of the AudioAppComponent class and has the sample rate passed in as an argument. Once the currentSampleRate is set, the updateAngleDelta() method is called. This method gives us the change of the necessary change of angle from one sample to the next to generate a sine wave. This is done by taking the desired frequency in hertz (given to use by the slider value) and dividing it by the current sample rate to give us the cycles per sample. For example, the human range of hearing is 20hz to 20,000hz with hz or Hertz refers to how many cycles per second a wave completes. So for a 20kHz wave sampled at 44.1kHz we would divide the two values and get about 0.454 cycles per second. For a 20hz wave sampled at the same 44.1kHz (44,100hz) the values would be divided and we would have 0.000454 cycles/sample. Now to get the angle delta between each sample at the given sample rate we just need to multiply the cycles per sample value by 2pi radians, or one full cycle of a sine wave. For the 20kHz example this would be about 2.853 radians, and for the 20hz example this would be about 0.00283 radians. Those would be our angle deltas between samples in each case.


Sample rate of the system is passed in when the method is automatically called

Note the use of the juce math constants


                 Now that we have a function for calculating angle deltas between samples it can be called whenever the slider value is changed by dragging it. I will not go into explaining the sliders again here, but I am happy to answer questions about them if you have any. The main feature of the slider here is the value range of it. This is already set for you in the downloadable file on the website, but you can play with the frequency range to see how it behaves. If you want to test your hearing (and have good enough speakers to cover the whole frequency range) you can set the range to 20, 20,000 which provides the 20hz to 20,000hz human hearing range. If you can hear the whole range you are doing great!

Outputting a 500hz sine wave


               Anyway, the final important section is the callback function that actually generates samples and outputs them for us to hear, the getNextAudioBlock() function. Here there is a level variable to multiply all of the generated values by. This is to keep the volume lower as a -1 to 1 output range would produce very loud sine waves. I would not recommend testing this, especially with powerful speakers. Two write pointers are declared to the left and right output channels. Most of you will probably have two out put channels if you are using speakers or headphones. Each sample of these output channels (with the samples being generated at the default sample rate of whatever audio device you are using) is set equal to the sine of the current angle, and then the currentAngle variable is incremented by the angleDelta variable so the next sample will be the sine of that new value. Each sample is then multiplied by the level variable I mentioned before, set in this case to 0.125.




               I encourage you all to download and run this program as it will give you a good idea of sample rates and frequencies. It is common to mimic sound waves in audio programming and this is the most simple example. More complex waves can be generated that will give have a different sound quality. Sine waves are the simplest as they have one fundamental frequency, where all other waves can be seen as a bunch of sine waves that add up to a different kind of wave shape.

               My goal is still to dive into wave shaping so stay tuned and I will get there before the end of this semester.

Comments

Popular posts from this blog

Detour: A Simple White Noise Generator

Introduction To Juce DSP Module: Building An Oscillator

Conclusion