This tutorial gives you an introduction to working directly with the Frontend::Sound API. This is the most low level interface Frontend provides to the sound hardware, but its actually quite simple and neat to use. In this tutorial, we will not load the sound from a file, but generate the waveforms ourselves through a simple software synthesizer loop.

Applies to:

  • Frontend 2.0 Beta 2 (all platforms)

Frontend::Sound Code Setup

You should now be pretty familiar with the Frontend headers and namespaces. The Frontend::Sound namespace and symbols are declared in Frontend2Sound.h, and the entry point we will use is in the standard FWG implementation header FWG/Frontend2Impl.h.

#include <OpenFrontend2.h>

using namespace Frontend;

#include <stdio.h>

#include <math.h>

Next up we define some constants we’ll use throughout the program

// The sample rate will be 44100 Hz (CD-Quality)

const int sampleRate = 44100;

// We will use a sound buffer of 44100 samples

const int soundBufferSize = 44100;

// We define 2*PI to

const float PI_2 = 6.28318530f;

// We are going to have stereo sound

const int channels = 2;

// We will refresh the sound buffer 4096 samples at the time

const int streamInterval = 4096;

We then create our main function and do the try..catch thing so we get a sensible error message in case anything goes wrong.

int main(int argc, char** argv)

{

 try

 {

                // CODE GOES HERE

 }

 catch (Exception&amp; e)

 {

   printf("%s\n", e.GetErrorMessage());

 }

}

In the CODE GOES HERE-block we start off by creating a window. As many platforms associate sound playback with a window, this is also required by Frontend. You don’t always need to make your window visible, however.

// First we need a window to associate our sound device with

Window* wnd = CreatePlatformWindow(320, 240, "Frontend::Sound Tutorial 1", false);// Then we create a sound device on the window

Sound::Device* sdevice = Frontend::Sound::CreateDevice(wnd);

You now got a Frontend::Sound::Device object ready. If you compile and run your programs with no errors, you are good to go. Remember to link to a Frontend2 Sound implementation (the FWG implementation for instance) in addition to the other frontend-libraries you would like to use.

Creating a sound buffer

Sound::Buffer objects are arrays of sound data managed by the audio driver, pretty similar to Graphics::Buffer objects. Sound buffers has a fixed size and wave format, and can be played once or looped until stopped. Sound buffers can be created through the sound device interface we created earlier.

Sound::Buffer* sbuffer = sdevice->CreateBuffer(soundBufferSize*sizeof(short), channels, 16, sampleRate);

The first parameter is the size, in bytes, of the buffer. We set it to be the number of samples we defined the buffer to be, times the size of a short integer (usually 2 bytes). The next parameter is the number of channels, which is 2 for stereo. The third parameter is the number of bits pr sample, which is 16 for short integers. The last parameter is the sample frequency, which is 44100. As you might know, 44100Hz and 16-bit samples gives us a CD-Quality sound buffer ready to play some sound for us.

Synthesize me!

Frontend::Sound can only play PCM wave data. In order to play compressed formats such as OGG, you should use Frontend::Utils or a third-party library to decode the data into PCM wave. If you’re completely new to how wave data is stored and played on a computer, its recommended to read more about this elsewhere before proceeding. To summarize, sound is stored as a series of integer-samples representing the position of the speaker element in small discrete intervals of time.

Next up, we need some sensible data to fill the buffer with. For that purpose, we present this little nifty function, which synthesizes some psychedelic sounds in the correct format. We won’t go into detail on how this works, but feel free to copy the code bellow and play around with it. Changing various constants will change the properties of the sound

void renderSound(short* buffer, int sampleCount)

{

 static float time = 0.0f;

 static float oscTime = 0.0f;

 float timeInterval = 1.0f / float(sampleRate);    for (int i = 0; i < sampleCount*2; i += 2)

 {

   time += timeInterval;

// Calculate a low-frequency ocillating pitch for the oscillator

   float pitch = 200+sin(sin(time*2.0f)+cos(time*1.2f)*6.5f)*100.0f;

// Oscillate

   oscTime += timeInterval*pitch;

   float sample = sin(oscTime*PI_2);

// Add some stereo distortion effect

   float distort = atanf(sin(sample*sample*3)*7.9f);

   float fade = sin(sin(time*0.88)*4.4f)*0.5f+0.5f;

   float left = sample*fade + distort*(1.0f-fade);

   float right = sample*(1.0f-fade) + distort*fade;

// Some filtering madness

   float cutoff = 0.75f+sinf(time*20.0f)*0.25f;

   static float gainLeft = 0;

   static float gainRight = 0;

   gainLeft = gainLeft*cutoff + left*(1-cutoff);

   gainRight = gainRight*cutoff + right*(1-cutoff);

// Manual clipping

   if (gainLeft > 0.999f) gainLeft = 0.999f;

   if (gainLeft < -0.999f) gainLeft = -0.999f;

   if (gainRight > 0.999f) gainRight = 0.999f;

   if (gainRight < -0.999f) gainRight = -0.999f;

// Write out the left and right samples

   buffer[i+0] = (short)(65535.0f*0.5f*gainLeft);

   buffer[i+1] = (short)(65535.0f*0.5f*gainRight);

 }

}

Streaming data to the buffer

We want the “synth” above to play forever until the application closes, so we are going to play the buffer in loop mode. The sound buffer we created can barely hold a second of sound, so just filling it once and looping it will be quite dull. We want to set up a streaming mechanism which fills the buffer with new data when it runs out.

We start by filling the buffer with some “getting started”-data, as we don’t want to start playing before we have any data to play. We use the renderSound function to generate some milliseconds of sound and write it to the buffer using the WriteData() function.

// Creating a small buffer in system memory to hold data temporarily

short* soundData = new short[streamInterval*channels];// Render some data using the "synthesizer" and write it to the sound buffer

renderSound(soundData, streamInterval);

sbuffer->WriteData(soundData, streamInterval*channels*sizeof(short));

// Start playing forever

sbuffer->PlayLoop(0);

Finally, we enter an eternal loop which refills the buffer as it needs to. We use the handy GetWriteCapacity() function on the buffer to see if it has room for more data. The function increases as data gets played back.

while (1)

{

    // If the buffer has room for another streamInterval bytes of data

    // we render another block and write it to the buffer.

    // This keeps the "synth" playing forever

    while (sbuffer->GetWriteCapacity() > streamInterval*channels*2)

    {

        renderSound(soundData, streamInterval);

         sbuffer->WriteData(soundData, streamInterval*channels*2);

    }

}

Note: You can of course combine Frontend::Sound with Frontend::Input to make the program exit on the escape key or whatever, but we wanted to keep the example code simple and keep to the point. Kill the program with Ctrl+C or Alt+F4.

Downloads

Download source code for this tutorial . The zip file also contains project files for Visual Studio.




FireStats icon Powered by FireStats