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
Post a Comment