Advertisment

Programming 3D Sound 

author-image
PCQ Bureau
New Update

Picture this: in the game you have written, you have objects whizzing past the player on either side, but sound keeps coming through the center channel. To make it more realistic you would have to manually control channels and adjust the volume according to the distance. But this would just multiply your code size without satisfactory results. Enter 3D positional audio. In this article, we will cover the basics of 3D sound programming for Windows. The first API that comes to mind when we talk of sound programming in Windows is DirectSound. This article, though, does not discuss DirectSound but another API called

OpenAL that makes the job of the game programmer a lot easier. 

Advertisment

OpenAL API is supported by two of the most aggressive players in gaming hardware-Creative and NVIDIA. This API has been designed for fast and clean integration with OpenGL code. One look at code written using this API and you will realize that function calls are remarkably similar to OpenGL calls. Also it gives the advantage that code written in OpenAL can be recompiled and run on an as is basis on platforms ranging from Windows to Linux and even MacOS. Analyzing the lower layers of OpenAL gives an idea of where it gets its power from. OpenAL does not do any sound rendering on its own. All it does is provide a wrapper around existing sound APIs, making the programmers' job easier. This means that when we are using a 3D sound interface under Windows, we are actually calling upon a DirectSound or A3D interface, but without knowing anything about Win 32 or COM (both are essential for programming in DirectX). OpenAL also received its mark of approval from the gaming industry when both Unreal Tournament 2003 and 2004 supported it.

Direct Hit!
Applies to: Game programmers
USP:

Use the OpenAL API to create a Doppler shift in sound
Links:

www.openal.org
On the Essential CD:

systems\labs

A 3D or positional audio contains three basic elements-a source, a listener and a buffer. Buffers store the sounds to be played, sources represent sources of sound in a logical 3D space and the listener represents the position of the listener in the same space. In OpenAL, you can have multiple buffers and sources for sounds but only one listener position is allowed at a time. The properties location and velocity are used to describe both the source and the listener. While the purpose of the location property is obvious, velocity property is used to calculate the Doppler Shift (phenomenon due to which the pitch of a moving car becomes higher as it approaches you and decreases when it recedes from you.) in the frequency of the perceived sound. To play a sound, all we have to do is to create a source and a listener with their respective values set. Next we load a wave file and convert it into a buffer that OpenAL can use. Finally we bind the source to the buffer and call a function to make it play.

Advertisment

Under OpenAL we use alSourcefv() and alSourcei() to set the properties of the source and alListenerfv() for setting the listener properties (Notice the similarity in the coding style with that of OpenGL). The alGenSources() function is used to generate the required number of sound sources. For manipulating buffers we have the two functions: alGenBuffers() to create the required number of buffers and alBufferData() to fill these buffers with data. We can also queue data into buffers for playing by using the function call alSourceQueueBuffers(). The alSoucePlay() and alSourceStop() functions are used to play and stop the source respectively.

Coding example



We will write a simple program that integrates the power of both OpenGL and OpenAL to generate a simple demo. Since the focus is sound and not graphics, explanation is limited to the OpenAL part of the example. We will use the glut toolkit (described in earlier articles on game programming in OpenGL) to do most of the OpenGL rendering to keep the code simple. The code will create one source and one listener, the source orbits around the listener in an elliptical orbit (a circular orbit would have kept the source-listener distance constant). We will also apply a Doppler shift to the sound to make it sound more realistic. A red sphere represents the source graphically and a green one represents the listener. The complete code is available in the file OpenAL.c. Snippets of the code are given here:

#include



#include


#include


#include


.


.


ALfloat listenerPos<>={0.0,0.0,4.0};


ALfloat listenerVel<>={0.0,0.0,0.0};


ALfloat listenerOri<>={0.0,0.0,-1.0, 0.0,1.0,0.0};


ALfloat source0Pos<>={ -2.0, 0.0, 0.0};


ALfloat source0Vel<>={ 3.0, 0.0, 3.0};


.


.


ALvoid alutInit(ALint* argc, ALbyte** argv)


{


ALcontext* context;


ALdevice* device;


int a;


MIXERCAPS mixerCaps;


mixerGetDevCaps(0, &mixerCaps, sizeof(MIXERCAPS));


device = alcOpenDevice(NULL);


if(alGetError()!= AL_NO_ERROR)


printf("\nDevice failed to open");


//Create context


context = alcCreateContext(device, 0);


//Set active context


a=alcMakeContextCurrent(context);


}

























Advertisment

The function alutInit() is analogous to glutInit() in OpenGL. This function gets a sound device, creates a context for rendering the sound and assigns the device to the context. The NULL value passed to alcOpenDevice() is used to specify that we wish to get the best device available. To choose a specific device we can also enumerate all devices on the system, and assign a specific one to the context.

void init(void)



{


.


.


glEnable(GL_DEPTH_TEST);


glEnable(GL_CULL_FACE);


alListenerfv(AL_POSITION,listenerPos);


alListenerfv(AL_VELOCITY,listenerVel);


alListenerfv(AL_ORIENTATION,listenerOri);


alListenerf(AL_GAIN,5.0f);





// Generate buffers, or no sound will be produced


alGenBuffers(NUM_BUFFERS, buffer);


alutLoadWAVFile("c.wav",&format,&data,&size,&freq, &al_bool);


alBufferData(buffer<0>,format,data,size,freq);


alutUnloadWAV(format,data,size,freq);


alGetError(); /* clear error */


alGenSources(NUM_SOURCES, source);


.


.


alDopplerVelocity(2000);


alDopplerFactor(1.2);


alSourcePlay(source<0>);


}





















Next comes the out init() function. Apart from the OpenGL specific initialization, we set the listener properties here. Next we create a buffer for the wave file and load it into memory, after which we generate a source using alGenSources() and assign the buffer to it. Once that is done, we set the Doppler shift parameters and finally play the sound. Unlike in SDL where we have to handle buffer fills ourselves for making the sound loop, all we have to do here is specify is the AL_LOOP property for the source and the sound loops infinitely.

Advertisment

void renderscene()



{


.


.


glPushMatrix() ;


//calculate the coordinates of the listener


//x=r*a*cos(theta);


//z=r*b*sin(theta);


x=30*a*cos(theta*2*PI/360);


z=30*b*sin(theta*2*PI/360);


glPushMatrix();


glTranslatef(x,0,z);


glColor3f(1.0f,0.0f,0.0f);


glutWireSphere(6,10,10);


source0Pos<0>=x/5;


source0Pos<2>=z/5;


alSourcefv(source<0>,AL_POSITION,source0Pos);


glPopMatrix();


glPopMatrix() ;


glutSwapBuffers() ;


glutPostRedisplay();


theta+=1 ;


if(theta>359)


theta=0;


}






















The above function is indirect proof of how well the OpenAL integrates with OpenGL. A common function, renderscene (), renders both graphics and sound. Here we calculate the x and z coordinates of the source using the equation of an ellipse in its trigonometric form (The source actually moves on the x-z plane, and it is rotated for aesthetic reasons). After that we assign the new coordinates to the source, just as we draw the sphere for the source, and the sound output reflects the changes. The position here is scaled by a factor of 5 to increase the volume of output sound, as it can get attenuated when the source swings away from the listener.

int main(int argc, char** argv) //finaly the main function



{


.


.


//initialise openAL


alGetError();


alutInit(0, NULL) ;


printf("\nOpenAL initialized");


.


.


return 0;


}









Advertisment

The main() function above contains no special function calls. One very useful function that OpenAL provides is alGetError() which returns an enumeration can take values AL_NO_ERROR, AL_INVALID_NAME, AL_INVALID_ENUM, AL_INVALID_VALUE, AL_INVALID_OPERATION, AL_OUT_OF_MEMORY. This simplifies the job of detecting errors in the sound system. Distance attenuation can be set to NONE, INVERSE_DISTANCE and INVERSE_DISTANCE_ CLAMPED. For mathematical descriptions of how these values affect the gain (volume), you can refer to the official OpenAL specification.

We demonstrate only the basic functionality of OpenAL. With a little creativity you can push the limits of your application that were never thought possible. In addition, OpenAL also has extensions like EAX environmental audio and certain extensions supported by NVIDIA. OpenAL is still in stages of infancy and a lot of R & D is being done. One advantage is that changing the sound rendering device from DirectSound to better future APIs doesn't require much change in our application. All we have to do is query the new device and select it if possible. For further documentation and other resources you can visit www.openal.org, the official OpenAL website. From here you can download the official specification and a programmers guide in pdf format. This site also provides useful information along with links to the developer pages of NVIDIA and Creative for you to download the OpenAL SDK for your favorite OS. 

OpenAL provides us with an easy to use, platform independent interface to accelerated sound hardware on the target system.

Advertisment

Another API that integrates easily with OpenAL is the OGG Vorbis library, which is used to compress and decompress sound, quite similar to the famous (infamous?!) mp3 format. While smaller sized files can be stored as .wav files, larger files like background music can be stored in OGG Vorbis format. With the power of such APIs, multimedia and game programmers can concentrate on the functionality of their application and not have to mess around with OS specific calls, making the mammoth task of porting applications easier. We can, therefore, say that OpenAL has the potential to open up endless avenues for a novice game programmer.

Setting up OpenAL



The required header and library files are provided for MS visual studio. These should be copied to the appropriate directories. For Borland C 5.5, the tool coff2omf.exe can be used to convert the library format. You should copy the dll files into the Windows/System folder (only if you don't already have them installed, as most vendors provide them). Alternatively you can download the OpenAL runtime installer from the official OpenAL website. 

Rakesh Iyer

Advertisment