OpenAL Lesson 5: Sources Sharing Buffers

At this point in the OpenAL series I will show one method of having your buffers be shared among many sources. This is a very logical and natural step, and it is so easy that some of you may have already done this yourself. If you have you may just skip this tutorial in total and move on. But for those keeners who want to read all of the info I've got to give, you may find this interesting. Plus, we will be implementing the Alc layer directly so that we can use some of that knowledge gained in lesson 4. On top of that we will create a program you might even use!

Well, here we go. I've decided to only go over bits of the code that are significant, since most of the code has been repeated so far in the series. Check out the full source code in the download. One more thing: this tutorial will be using vectors from the Standard Template Library, so make sure you have it installed and have at least a little knowledge of it's operation. I will not cover the STL here because this is an OpenAL tutorial.

// These index the buffers.
#define THUNDER     0
#define WATERDROP   1
#define STREAM      2
#define RAIN        3
#define CHIMES      4
#define OCEAN       5
#define NUM_BUFFERS 6

// Buffers hold sound data.
ALuint Buffers[NUM_BUFFERS];

// A vector list of sources for multiple emissions.
vector<ALuint> Sources;

First I've written out a few macros that we can use to index the buffer array. We will be using several wav files so we need quite a few buffers here. Instead of using an array for storing the sources we will use an STL vector. We chose to do this because it allows us to have a dynamic number of sources. We can just keep adding sources to the scene until OpenAL runs out of them. This is also the first tutorial where we will deal with sources as being a resource that will run out. And yes, they will run out; they are finite.

ALboolean InitOpenAL()
    ALCdevice* pDevice;
    ALCcontext* pContext;
    ALCubyte* deviceSpecifier;
    ALCubyte deviceName[] = "DirectSound3D";

    // Get handle to device.
    pDevice = alcOpenDevice(deviceName);

    // Get the device specifier.
    deviceSpecifier = alcGetString(pDevice, ALC_DEVICE_SPECIFIER);

    printf("Using device '%s'.\n", szDeviceSpecifier);

    // Create audio context.
    pContext = alcCreateContext(pDevice, NULL);

    // Set active context.

    // Check for an error.
    if (alcGetError() != ALC_NO_ERROR)
        return AL_FALSE;

    return AL_TRUE;

This is some sample code from what we learned in the last tutorial. We get a handle to the device "DirectSound3D", and then obtain a rendering context for our application. This context is set to current and the function will check if everything went smoothly before we return success.

void ExitOpenAL()
    ALCcontext* pCurContext;
    ALCdevice* pCurDevice;

    // Get the current context.
    pCurContext = alcGetCurrentContext();

    // Get the device used by that context.
    pCurDevice = alcGetContextsDevice(pCurContext);

    // Reset the current context to NULL.

    // Release the context and the device.

This will do the opposite we did in the previous code. It retrieves the context and device that our application was using and releases them. It also sets the current context to NULL (the default) which will suspend the processing of any data sent to OpenAL. It is important to reset the current context to NULL or else you will have an invalid context trying to process data. The results of doing this can be unpredictable.

If you are using a multi-context application you may need to have a more advanced way of dealing with initialization and shutdown. I would recommend making all devices and contexts global and closing them individually, rather than retrieving the current context.

ALboolean LoadALData()
    // Variables to load into.
    ALenum format;
    ALsizei size;
    ALvoid* data;
    ALsizei freq;
    ALboolean loop;

    // Load wav data into buffers.
    alGenBuffers(NUM_BUFFERS, Buffers);

    if(alGetError() != AL_NO_ERROR)
        return AL_FALSE;

    alutLoadWAVFile("wavdata/thunder.wav", &format, &data, &size, &freq, &loop);
    alBufferData(Buffers[THUNDER], format, data, size, freq);
    alutUnloadWAV(format, data, size, freq);

    alutLoadWAVFile("wavdata/waterdrop.wav", &format, &data, &size, &freq, &loop);
    alBufferData(Buffers[WATERDROP], format, data, size, freq);
    alutUnloadWAV(format, data, size, freq);

    alutLoadWAVFile("wavdata/stream.wav", &format, &data, &size, &freq, &loop);
    alBufferData(Buffers[STREAM], format, data, size, freq);
    alutUnloadWAV(format, data, size, freq);

    alutLoadWAVFile("wavdata/rain.wav", &format, &data, &size, &freq, &loop);
    alBufferData(Buffers[RAIN], format, data, size, freq);
    alutUnloadWAV(format, data, size, freq);

    alutLoadWAVFile("wavdata/ocean.wav", &format, &data, &size, &freq, &loop);
    alBufferData(Buffers[OCEAN], format, data, size, freq);
    alutUnloadWAV(format, data, size, freq);

    alutLoadWAVFile("wavdata/chimes.wav", &format, &data, &size, &freq, &loop);
    alBufferData(Buffers[CHIMES], format, data, size, freq);
    alutUnloadWAV(format, data, size, freq);

    // Do another error check and return.
    if (alGetError() != AL_NO_ERROR)
        return AL_FALSE;

    return AL_TRUE;

We've totally removed the source generation from this function. That's because from now on we will be initializing the sources separately.

void AddSource(ALint type)
    ALuint source;

    alGenSources(1, &source);

    if (alGetError() != AL_NO_ERROR)
        printf("Error generating audio source.");

    alSourcei (source, AL_BUFFER,   Buffers[type]);
    alSourcef (source, AL_PITCH,    1.0          );
    alSourcef (source, AL_GAIN,     1.0          );
    alSourcefv(source, AL_POSITION, SourcePos    );
    alSourcefv(source, AL_VELOCITY, SourceVel    );
    alSourcei (source, AL_LOOPING,  AL_TRUE      );



Here's the function that will generate the sources for us. This function will generate a single source for any one of the loaded buffers we generated in the previous source. Given the buffer index 'type', which is one of the macros we created right from the start of this tutorial. We do an error check to make sure we have a source to play (like I said, they are finite). If a source cannot be allocated then the program will exit.

void KillALData()
    for (vector<ALuint>::iterator iter = Sources.begin(); iter != Sources.end(); ++iter)
        alDeleteSources(1, iter);
    alDeleteBuffers(NUM_BUFFERS, Buffers);

This function has been modified a bit to accommodate the STL list. We have to delete each source in the list individually and then clear the list which will effectively destroy it.

ALubyte c = ' ';

    while (c != 'q')
        c = getche();

        switch (c)
            case 'w': AddSource(WATERDROP); break;
            case 't': AddSource(THUNDER);   break;
            case 's': AddSource(STREAM);    break;
            case 'r': AddSource(RAIN);      break;
            case 'o': AddSource(OCEAN);     break;
            case 'c': AddSource(CHIMES);    break;

Here is the programs inner loop taken straight out of our main. Basically it waits for some keyboard input and on certain key hits it will create a new source of a certain type and add it to the audio scene. Essentially what we have created here is something like one of those nature tapes that people listen to for relaxation. Ours is a little better since it allows the user to customize which sounds that they want in the background. Pretty neat eh? I've been listening to mine while I code. It's a Zen experience (I'm listening to it right now).

The program can be expanded for using more wav files, and have the added feature of placing the sources around the scene in arbitrary positions. You could even allow for sources to play with a given frequency rather than have them loop. However this would require GUI routines that go beyond the scope of the tutorial. A full featured "Weathering Engine" would be a nifty program to make though. ;)

Download the Dev-C++ source and project file
Download the Visual C++ 6.0 source and project file - (ported by TheCell)
Download the Java source code - (ported by Athomas Goldberg)
Download the Linux port of this tutorial - (ported by Lee Trager)

* See the Java Bindings for OpenAL page for the Java version of this tutorial (adapted by: Athomas Goldberg)



Commenting will be coming soon. In the meantime, feel free to create a discussion topic on the forums.