다음을 통해 공유


Intro to Audio Programming Part 4: Algorithms for Different Sound Waves in C#

In the last article, we saw how to synthesize a sine wave at 440Hz and save it to a working WAV file. Next, we’ll expand on that application and learn how to implement some other common waveforms.

image

First of all, I copied WaveFun from the last article as a foundation to work from and called it WaveFun2. The UI provides a few more configuration options, as shown here to the right.

>> DOWNLOAD THE DELICIOUS CODE HERE (21 KB) <<

You can now specify where you want the file to go, as well as the frequency and volume of the waveform. The code has been lightly refactored to support feeding these values from the UI. I’m not going to go into great detail on these changes in this post, but I will show you how to generate the waves.

You can pick from five total waveforms: Sine, Square, Sawtooth, Triangle and White Noise. These are very common waveforms used in synthesizing music.

Let’s learn more about these different waveforms.

Also, for the sine and square waves, we are using a variable t that represents the angular frequency (basically, the “tone” frequency in radians and adjusted for sample rate).

Sine

We have already done some work around the sine wave. It is the smoothest sounding tone signal we can produce.

The Shape

A sine wave looks like this:

image

The Equation

Sample = Amplitude * sin(t*i)

where t is the angular frequency and i is a unit of time.

The Algorithm

Generating just one 16-bit channel of a sine wave (mono) is very easy.

 for (int i = 0; i < numSamples; i++)
{
    data.shortArray[i] = Convert.ToInt16(amplitude * Math.Sin(t * i));
}

 

If you wanted to generate two identical, properly aligned channels of sine data, you have to write the same value twice in a row because channel data is interleaved. Further examples will show this in multichannel format, like below.

 for (int i = 0; i < numSamples - 1; i ++)
{
    for (int channel = 0; channel < format.wChannels; channel ++)
    {
        data.shortArray[i + channel] = Convert.ToInt16(amplitude * Math.Sin(t * i));
    }
}

Square

The square wave is closely related to the sine wave, although it is not in sine form. The square wave produces a very harsh tone due to the abrupt rises and falloffs in the waveform:

image

The Equation

There are a number of ways to generate square waves, and many of them generate imperfect square waves (especially electronics). Since we are using a very precise program with nice things like for loops, we can generate a square wave that is absolutely perfect.

The equation we will be using is:

Sample = Amplitude * sgn(sin(t * i))

In this case the sgn function just tells us whether the value of the sine function is positive, negative, or zero.

The Algorithm

To generate two channels of a square wave:

 for (int i = 0; i < numSamples - 1; i++)
{                        
    for (int channel = 0; channel < format.wChannels; channel++)
    {
        data.shortArray[i] = Convert.ToInt16(amplitude * Math.Sign(Math.Sin(t * i)));
    }
}

Sawtooth

The sawtooth wave has tonal qualities somewhere between a sine and a square wave, almost like a saxophone. It ramps up in a linear fashion and then falls off.

image

The Equation

A “proper” sawtooth wave is created with additive synthesis, which we’ll get into later. Multiple sine waves are added together to create harmonics, until eventually the wave takes the shape of the sawtooth (check this nice animation from wikipedia).

Synthesizing a sawtooth in this manner is best done with an algorithm known as a fast Fourier transform, which we won’t get into just yet because it’s kinda complicated, although all kinds of filters and effects are calculated using FFT algorithms. Instead, we’ll be calculating it as if we were plotting a graph, which means we get “infinite” harmonics. This isn’t really a good thing; it will sound less smooth than a sawtooth calculated with a FFT, but whatever.

The equation for a sawtooth wave is sometimes expressed in this way:

y(t) = x – floor(x);

However, we’ll be generating the wave procedurally.

The Algorithm

The algorithm we will be using is a lot like the one we’d use just to plot this wave on a chart.

We have to determine the “step” of the y-coordinate such that we get a nice diagonal line that goes from minimum amplitude to maximum amplitude over one wavelength, and this is based on the frequency of the wave. The “step” is the difference in amplitude between adjacent samples.

image

    1: // Determine the number of samples per wavelength
    2: int samplesPerWavelength = Convert.ToInt32(format.dwSamplesPerSec / (frequency / format.wChannels));
    3:  
    4: // Determine the amplitude step for consecutive samples
    5: short ampStep = Convert.ToInt16((amplitude * 2) / samplesPerWavelength);
    6:  
    7: // Temporary sample value, added to as we go through the loop
    8: short tempSample = (short)-amplitude;
    9:  
   10: // Total number of samples written so we know when to stop
   11: int totalSamplesWritten = 0;
   12:  
   13: while (totalSamplesWritten < numSamples)
   14: {
   15:     tempSample = (short)-amplitude;
   16:  
   17:     for (uint i = 0; i < samplesPerWavelength && totalSamplesWritten < numSamples; i++)
   18:     {
   19:         for (int channel = 0; channel < format.wChannels; channel++)
   20:         {
   21:             tempSample += ampStep;
   22:             data.shortArray[totalSamplesWritten] = tempSample;
   23:  
   24:             totalSamplesWritten++;
   25:         }
   26:     }                        
   27: }

On line 2, we calculate the number of samples in 1 “tooth.”

On line 5, we calculate the amplitude step by taking the total amplitude range (amplitude * 2) and dividing it by the number of samples per saw tooth.

On line 8, we declare a temporary sample to use. This sample has the amplitude step added to it until we get to maximum amplitude.

We put this in a while loop, because our sample data might end in the middle of a sawtooth wave and we don’t want to go out of bounds.

On line 15, we reset the sample to the minimum amplitude (this happens before each saw tooth is calculated).

We use the same structure – two for loops – for this algorithm. We also add checks to make sure we’re not at the end of the sample data yet.

On line 21 we increment the temporary sample value by the amplitude step and assign it to the data array.

Triangle

The triangle wave is like the sawtooth wave, but instead of having a sharp falloff between wavelengths, the amplitude rises and falls in a smooth linear fashion.

The triangle wave produces a slightly more coarse tone than the sine wave.

image

The Formula

… again, uses a fast Fourier transform to synthesize from a sine wave using odd harmonics. So let’s just create one the easy way!

The Algorithm

The algorithm we use is similar to that of the sawtooth wave shown above. All we are doing is changing the sign of the ampStep variable whenever the current sample (absolute value) is greater than the specified amplitude. So when the wave reaches the max and min, the step changes its sign so the wave goes the other way.

 for (int i = 0; i < numSamples - 1; i++)
{
    for (int channel = 0; channel < format.wChannels; channel++)
    {
        // Negate ampstep whenever it hits the amplitude boundary
        if (Math.Abs(tempSample) > amplitude)
            ampStep = (short)-ampStep;

        tempSample += ampStep;
        data.shortArray[i + channel] = tempSample;
    }
}                    

White Noise

Generating white noise is probably the easiest of them all. White noise consists of totally random samples. Interestingly, white noise is sometimes used to generate random numbers.

image

The Equation

… There isn’t one.

The Algorithm

All you need to do is randomize every sample from –amplitude to amplitude. We don’t care about the number of channels in most cases so we just fill every sample with a new random number.

 Random rnd = new Random();
short randomValue = 0;

for (int i = 0; i < numSamples; i++)
{
    randomValue = Convert.ToInt16(rnd.Next(-amplitude, amplitude));
    data.shortArray[i] = randomValue;
}

Conclusion

These are just some examples of waves that you can use to create interesting sounds. Next, we are going to learn to combine them to create more complex waveforms.

Currently Playing: Black Label Society – 1919 Eternal – Demise of Sanity

Comments

  • Anonymous
    June 29, 2009
    Very interesting arrticle - can you write the wav to somekind of memory stream and play it back in real time so you can create some kind of synth keyboard thing?

  • Anonymous
    July 07, 2009
    Small correction: In the Square wave example, shouldn't the two channel loop be: data.shortArray[i + channel]?

  • Anonymous
    October 06, 2009
    Great post. Did some wave stuff before but lost everything with a HDD crash, these projects are a great place to start again. Will part 5 be up anytime soon? Thanks for the efforts so far!

  • Anonymous
    December 12, 2009
    The comment has been removed

  • Anonymous
    March 07, 2010
    thx for this ! took me ages to find out such a "walkthrough" how to make audio signals woohoo!

  • Anonymous
    May 04, 2010
    i read the all 4 articales that u have written cool articles, if i input a wave file and what to know the frequencies how may i approach that problem........... ex: input a wav file with a song and capturing the frequencies in each second.. can u give me a feedback on this plz thank you.....

  • Anonymous
    June 13, 2010
    very excited to find this blog !! i am looking for exactly the same thing ! can anyone please help me to download this code file    wavefun2.zip it gives me an error "Server Application Unavailable " :(  help ?

  • Anonymous
    June 15, 2010
    oh , now it works! i was able to downlaod the file!!!!!!!!!! i guess i picked a bad day last time to try and download . this is    e x a c t l y    what i needed !!!! a-m-a-z-i-n-g thanks!

  • Anonymous
    June 15, 2010
    The comment has been removed

  • Anonymous
    August 31, 2010
    This is a ver interesting document. Is it possible to add reverbation to this wave using a delay buffer? How I can do that? do you have any ideia? Thanks for your time

  • Anonymous
    December 11, 2012
    what about a sound that varies in frq. duration (vol) and pitch... you need something bettter... like what i am working on..... it is for sale

  • Anonymous
    September 26, 2013
    your articles leave me speechless, just so great, thanks

  • Anonymous
    January 27, 2015
    download of .zip file does not seem to work