• 02/13/03 06:33 PM
    Sign in to follow this  
    Followers 0

    3D Sound with DirectX Audio

    Graphics and GPU Programming

    Myopic Rhino
    After spending months writing my first game using DirectX 8, I'd finally finished the graphics, AI, menus and everything else. One final element remained which I had been putting off since beginning the project: sound. Don't get me wrong; I love sound. Music and sound are what makes half of the gaming experience, the other half of course being the cool graphics.

    Having decided it was time to add sound I began digging through the SDK documentation and quickly realised that a few things had changed since I last looked at DirectSound. As DirectDraw and Direct3D had been combined to form DirectX Graphics, it seemed that DirectSound and DirectMusc had also been combined to form DirectX Audio. Feeling that it was time to get hip with DirectX Audio, I took a deep breath and jumped right in, and before long had a few classes together which nicely game me the 3D sound I wanted in my game.

    This tutorial gives a basic explanation of how to use DirectX Audio to create and play 3D sound effects loaded from .WAV files, and also includes the classes that I wrote to use in my game.


    [size="5"]Setting Up DirectX Audio

    Setting up DirectX Audio begins with creating a few DirectMusic interfaces. These interfaces include a DirectMusic Performance; which is used to play all the sounds and a DirectMusic Loader; which is used to load all the sounds. Having created these interfaces, DirectX Audio must be initialised using the [font="Courier New"][color="#000080"]InitAudio()[/color][/font] method of the Perormance interface. The code to accomplish all this follows:

    // the performance and loader interfaces
    IDirectMusicPerformance8 *pPerformance = NULL;
    IDirectMusicLoader8 *pLoader = NULL;

    // Initialize COM
    CoInitialize(NULL);

    // Create loader interface
    CoCreateInstance(CLSID_DirectMusicLoader, NULL, CLSCTX_INPROC,
    IID_IDirectMusicLoader8, (void**)&pLoader );

    // Create performance interface
    CoCreateInstance(CLSID_DirectMusicPerformance, NULL, CLSCTX_INPROC,
    IID_IDirectMusicPerformance8, (void**)&pPerformance );

    // Initialise DirectX Audio for 3D sound
    pPerformance->InitAudio(NULL, NULL, NULL,
    DMUS_APATH_DYNAMIC_3D, 64,
    DMUS_AUDIOF_ALL, NULL );
    First, all the COM stuff has to be setup with the call to [font="Courier New"][color="#000080"]CoInitialize()[/color][/font]. Then using the COM function [font="Courier New"][color="#000080"]CoCreateInstance()[/color][/font] we create the desired interfaces for our Performance and Loader. Finally DirectX Audio is initialised with a call to the [font="Courier New"][color="#000080"]InitAudio()[/color][/font] method of the DirectMusic Performance interface. The parameters in this call indicate that we wish to setup DirectX Audio with a default 3D Audiopath, which is required for 3D sound. For more information on any of the above code, see the SDK documentation and the first DirectMusic tutorial.


    [size="5"]Creating a 3D Sound

    Now that we have DirectX Audio setup, we'd really like to be able to play something with it. This next section describes how to load and create a 3D sound using the previous interfaces.

    Strangely enough, to load a sound, we must use the DirectMusic Loader. The loader can be used to load a fair variety of sounds, however we shall be using it to load .WAV files. (If you want to load MP3 sounds then you'll have to use DirectShow, which isn't covered here.)

    When the Loader loads a sound, it is put into a DirectMusic Segment. A segment on its own is OK if you just want to be able to play a sound, however if you want to be able to alter the sound in any way (which you must do for 3D sound) then you have to give each segment its own Audiopath. The Audiopath is what the segment is played through, and by altering the Audiopath you alter the way in which a segment is played.

    For 3D sounds, each sound needs its own 3D Audiopath, which makes sense since it is a 3D sound. What makes a 3D Audiopaths special, and suitable for 3D sounds, is that they contain a 3D Sound Buffer. The 3D Sound Buffer can be altered to change the position of a sound in space, which is what you want for 3D Sound.

    This works fine if you are always listening to your sounds from the origin (0,0,0). However, if you are in a world where you are moving around, then you won't always be listening from the origin. To change where the 3D sound is heard from, you need a 3D Sound Listener which (suprisingly) 'listens' to the 3D Sound. Each 3D sound has its own listener, as the listener is associated with the 3D Audiopath.

    To create a 3D sound, we therefore have to load the sound into a segment. Create a 3D Audiopath for that segment. Get access to the 3D Sound Buffer contained in the Audiopath, and get access to the Listener which is also contained in the Audiopath. We can then play the sound, and alter its position in 3D space, as well as the position of the listener of the sound. The code to accomplish this follows:

    // points to a zero terminated wide character string specifying the
    // filename of the sound to load
    WCHAR *pwsFileName;

    // the segment for our sound
    IDirectMusicSegment8 *pSegment = NULL;

    // the 3d audiopath for the sound
    IDirectMusicAudioPath8* p3DAudioPath = NULL;

    // the 3d sound buffer for the sound
    IDirectSound3DBuffer8* pDSB = NULL;

    // the listener for the sound
    IDirectSound3DListener8* pListener = NULL;

    // load the soundfile into the segment
    pLoader->LoadObjectFromFile(CLSID_DirectMusicSegment,
    IID_IDirectMusicSegment8,
    pwsFileName,
    (LPVOID*) pSegment );

    // Download the segment's instruments (this must be done for all .WAV files)
    pSegment->Download(pPerformance);

    // Create the 3D audiopath with a 3d buffer.
    // We can then play this segment through this audiopath (and hence the buffer)
    // and alter its 3D parameters.
    pPerformance->CreateStandardAudioPath(DMUS_APATH_DYNAMIC_3D,
    64, TRUE, &p3DAudioPath);

    // Get the 3D Sound Buffer from the 3D audiopath
    p3DAudioPath->GetObjectInPath(DMUS_PCHANNEL_ALL, DMUS_PATH_BUFFER, 0,
    GUID_NULL, 0, IID_IDirectSound3DBuffer,
    (LPVOID*) &pDSB);

    // get the listener from the 3d audiopath
    p3DAudioPath->GetObjectInPath(0, DMUS_PATH_PRIMARY_BUFFER,
    0, GUID_All_Objects, 0,
    IID_IDirectSound3DListener,
    (void **)&pListener);
    The soundfile is loaded using the [font="Courier New"][color="#000080"]LoadObjectFromFile()[/color][/font] method of the Loader interface. We indicate that we wish to create a DirectMusic Segment here and load the file specified in the wide character string. Next the instruments for the segment are downloaded. This step must be done for all .WAV samples before they can be played.

    Following this we create a 3D Audiopath for our sound, using the [font="Courier New"][color="#000080"]CreateStandardAudioPath()[/color][/font] method of the Performance interface. We indicate that we want a 3D audiopath and that it should be activated upon creation. We then use the [font="Courier New"][color="#000080"]GetObjectInPath()[/color][/font] method of the Audiopath interface to request an interface pointer to the 3D Sound Buffer and the 3D Listener.

    Having achieved this, our sound is ready to be positioned, and played in 3D Space.


    [size="5"]Positioning the Sound and Listener

    To position the sound or the listener, we must call the SetPosition()method of the appropriate interface. To position the sound, we call the method on the 3D Sound Buffer interface. To position the listener, we call the method on the (yep you guessed it) Listener interface. The following code positions the sound and the llistener.

    // the following store the coordinates of the sound and the listener.
    float fSoundPosX, fSoundPosY, fSoundPosZ;
    float fListenerPosX, fListenerPosY, fListenerPosZ;

    // Set the position of sound
    pDSB->SetPosition( fSoundPosX, fSoundPosY, fSoundPosZ, DS3D_IMMEDIATE);

    // Set the position of the listener
    pListener->SetPosition(fListenerPosX, fListenerPosY, fListenerPosZ, DS3D_IMMEDIATE);
    The [font="Courier New"][color="#000080"]DS3D_IMMEDIATE[/color][/font] flag in the above calls indicate that we want the change to occur as soon as possible.

    Righto, are we ready to hear these sounds or what?


    [size="5"]Playing The Sound

    The sound itself is played using the DirectMusic Performance interface, where we specify which segment to play and the Audiopath we wish to play it through. We want to play our segment through its own audiopath, so that the changes that we made with the above code (where we moved the sound and its listener) actually take effect. The code to play the sound follows:

    // play the segment on the 3D audiopath - play it as a secondary
    // segment so that we can play multiple sounds at once.
    pPerformance->PlaySegmentEx(pSegment, NULL, NULL, DMUS_SEGF_SECONDARY,
    0, NULL, NULL, p3DAudioPath );
    The [font="Courier New"][color="#000080"]DMUS_SEGF_SECONDARY[/color][/font] flag is used to indicate that we wish this segment to be played as a secondary segment. This allows us to play more than one sound at a time.

    So now you can load a sound, set its 3D parameters and play it. All that's left now is to show you how to clean up after yourself.


    [size="5"]Cleanup and Housekeeping

    You should always clean up your mess, so here's the code to do that.

    // release the 3d sound buffer
    if (pDSB)
    pDSB->Release();
    pDSB = NULL;

    // release the listener
    if (pListener)
    pListener->Release();
    pListener = NULL;

    // release the 3d audiopath
    if (p3DAudioPath)
    p3DAudioPath->Release();
    p3DAudioPath = NULL;

    // release the segment
    if (pSegment)
    pSegment->Release();
    pSegment = NULL;

    // release the loader
    if (pLoader)
    pLoader->Release();
    pLoader = NULL;

    // finally close down the performance, and release it
    if (pPerformance)
    pPerformance->CloseDown();

    if (pPerformance)
    pPerformance->Release();
    pPerformance = NULL;

    // close down COM
    CoUninitialize();
    Each interface must be released in the reverse of the order that they were created in. Before we release our Performance it must first be closed down using its [font="Courier New"][color="#000080"]CloseDown()[/color][/font] method. Finally COM must be closed down, using the standard [font="Courier New"][color="#000080"]CoUninitialize()[/color][/font] call.

    One thing I haven't mentioned so far is what you'll have to link your project with and the files you need to include to get all this working. You'll have to link with [font="Courier New"][color="#000080"]dxguid.lib[/color][/font] and include the following precompiler directives:

    // the following need to be included - also link with dxguid.lib
    #define INITGUID
    #include
    #include
    #include
    #include
    And that's all folks... For more information on everything here see the SDK and the first two DirectMusic tutorials. Also I haven't done any error testing in the above code, however this should always be done - I've just left it out to increase readability.

    One last thing before I go - I've made a couple classes: One ([font="Courier New"][color="#000080"]CDXAudio[/color][/font]) to do the DirectX Audio initialisation and shutdown, and another ([font="Courier New"][color="#000080"]C3DSound[/color][/font]) used to create and destroy 3d sounds, position them and their listener in space and then play them. They are used in the project source, which is an application that creates a sound and plays it in various locations.
    0


    Sign in to follow this  
    Followers 0


    User Feedback

    Create an account or sign in to leave a review

    You need to be a member in order to leave a review

    Create an account

    Sign up for a new account in our community. It's easy!


    Register a new account

    Sign in

    Already have an account? Sign in here.


    Sign In Now

    There are no reviews to display.