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.
using namespace Frontend;
#include <stdio.h>
#include <math.h>
Next up we define some constants we’ll use throughout the program
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
{
// CODE GOES HERE
}
catch (Exception& e)
{
printf("%s\n", e.GetErrorMessage());
}
}
In the
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.
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
{
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.
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.
{
// 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.