Using DirectSound 3D
With the use of DirectSound 3d, a program can take advantage of a computer's surround sound system. Sound will automatically be adjusted depending on the position of the listener, unlike 2d sound where the developer has to either create their own algorithm for controlling sound pan/volume, or use one created by someone else.
Note: This tutorial will not discuss how to create a SecondaryBuffer. For an excellent and thorough explanation on setting up a SecondaryBuffer and playing a basic sound, please refer to DirectX Tutorial 1: Playing a Sound. It will be assumed that the reader is fairly familiar with creating and instantiating a SecondaryBuffer, as well as setting BufferDescription properties on the buffer.
Every sound in a 3d sound environment is placed on a 3d coordinate plane, with an X, Y, and Z axis. The X axis runs horizontally (controls left/right pan,) the Y axis vertically (controls front speaker/back speaker position,) and the Z axis altitude wise (some sound cards support the effect of "higher sounds.") Typically, programs which do not have a virtual floor set their Z values at a constant value, most of the time zero. The positioning of sound is determined by two factors:
- The 3d coordinates of the sound.
- The listener's position.
The listener is typically the virtual player. It also has position, defined by 3d coordinates. The player can also be rotated, sometimes called oriented, along a vector, giving the effect of the player facing East with a sound North of him playing from the left speaker, Etc. In the following sections, the reader will learn how to set up 3d sound, and obtain the listener. Listener orientation is beyond the scope of this guide as it involves the discussion of 3d vectors and it will be left up to the reader to research that aspect of 3d sound.
Creating a 3D sound Capable Sound Buffer
The fact is that standard SecondaryBuffers cannot utilize 3d sound. To use 3d sound, these criteria must be met:
- The loaded sound must be in mono, otherwise an exception will be thrown when making any 3d-specific changes on the buffer.
- The .Control3D property must be set on a DirectSound.BufferDescription object which was used to create the SecondaryBuffer.
Below is a function that will initialize a sound for 3d manipulation:
public class Sound3D private DSDevice as DirectSound.Device
'The constructor will instantiate a device object, nothing new public sub new(ByVal Handle as IntPTR) DSDevice=new DirectSound.Device() DSDevice.SetCooperativeLevel(Handle, DirectSound.CooperativeLevel.Priority) end sub
public function LoadSound3D(ByVal FileName as String) as DirectSound.SecondaryBuffer dim BufferDesc as DirectSound.BufferDescription=new DirectSound.BufferDescription() BufferDesc.Control3D=true 'This needs to be set on the buffer 'to enable 3d sound. return (new DirectSound.SecondaryBuffer(FileName,BufferDesc,DSDevice)) 'For those who are confused by this statement, all that was done 'was that a new DirectSound.SecondaryBuffer was instantiated, 'whose reference was returned. 'In case the reader des not know (and this is not an insult to those who do) this can be done. end function end class
The constructor above expects a window handle from the main GUI (graphical user interface,) usually the main form, which it uses to set the cooperative level on the device object. The .Handle property of a form is type IntPTR.
The LoadSound3D function expects the full path to a file to load into a buffer. The BufferDescription object is used, as discussed above, to enable 3d control on the SecondaryBuffer by setting the .Control3D property of the BufferDescription object to true. Once the flag is set, one of the overloaded constructors of SecondaryBuffer is used to allow one to pass the BufferDescription object.
Making Use of 3D Enabled SecondaryBuffers
Once the DirectSound.BufferDescription.Control3D flag has been set and the SecondaryBuffer has been instantiated, 3d effects can be applied to the SecondaryBuffer. It is necessary, however, to create a listener in order for 3d sounds to be heard correctly.
Creating a Listener
Listeners, as described above, provide a virtual center-point around which DirectSound 3D revolves. Creating a listener generally involves two steps:
- Create a primary buffer.
- Derive a listener from the primary buffer.
Creating a Primary Buffer
Creating a primary buffer is simple: instantiate a DirectSound.Buffer object, setting the BufferDescription.PrimaryBuffer property to true. Use the overloaded constructor which expects a BufferDescription and Device object to instantiate the buffer. Since a listener will be derived from this buffer, it is necessary to also set the .Control3D property to true. This is done in the following code snippet:
Note: Imports Microsoft.DirectX will apply to all code segments used in this guide; in addition, it will be assumed that the name of the DirectSound.Device object is DSDevice, and it has been instantiated and its cooperative level has been set.
dim BufferDesc as DirectSound.BufferDescription=new DirectSound.BufferDescription()
'As discussed above, these two flags must be set in order to retrieve a listener
dim Primary as DirectSound.Buffer=new DirectSound.Buffer(BufferDesc,DSDevice)
Now that the primary buffer has been created, a listener can be obtained from it by instantiating a Listener3D object, using the primary buffer created above as the parameter:
dim The Listener as DirectSound.Listener3D=new DirectSound.Listener3D(Primary)
Note: In reality, the listener object will be made global instead of local scope so that it is constantly available throughout the program. The reader will now be shown how to perform basic effects on the listener (changing the listener's coordinates on the 3d plane.)
Changing Listener Coordinates
A listener's coordinates are defined by a Microsoft.DirectX.Vector3 object. This class has a .X, .Y, and .Z property, each setting its respective coordinate. Once filled with this information, the Vector3 object will be passed to a listener object's .Position property. Below, the reader will find a function which expects three values and returns a Vector3 object.
public function GetVector3(ByVal X as Double, ByVal Y as Double, ByVal Z as Double) _
dim Vec as Microsoft.DirectX.Vector3=new Microsoft.DirectX.Vector3
Once this function has been implemented, a listener's position can be set as follows:
In a similar manner, a listener's orientation can be set. This is done by using a DirectSound.Listener3DOrientation object. The two properties in this class relevant to this discussion are .Front and .Top. Both of these are Vector3 objects.
It is assumed that the reader has an understanding of listener position. With this in mind, it is time to return to 3d buffer manipulation.
Playing a 3D Capable Buffer
To change the position in 3d space of a SecondaryBuffer on which a Control3D flag has been set, one must create a DirectSound.Buffer3D object, passing to its constructor the SecondaryBuffer in question. Once this object has been instantiated, one can set the .Position property on the Buffer3D object, which will have the effect of moving the sound produced by the SecondaryBuffer passed to Buffer3D to the specified coordinates in 3d space. If the listener was at coordinates (5,0,2) and the .Position property on the Buffer3D object was set to (4,0,2), the player would hear the sound from the SecondaryBuffer from the left. The following code snippet sets up a SecondaryBuffer, enables 3d sound manipulations on it, and shifts its position to (3,2,0):
public sub PlayASound()
'Note: GetVector3() is in effect here.
dim BufferDesc as DirectSound.BufferDescription=new DirectSound.BufferDescription()
dim TheSound as DirectSound.SecondaryBuffer=new DirectSound.SecondaryBuffer("C:\mysound.wav", _ BufferDesc, DSDevice) 'TheSound now has 3D capabilities.
'Next, create a Buffer3D object for 3D manipulations. 'Note: because SecondaryBuffers have to go through Buffer3D objects every time a 3D 'specific function must be performed, it is a good idea to create a PlaySound3D method or similar method which 'expects a SecondaryBuffer on which to apply 3D sound. dim The3DBuffer as DirectSound.Buffer3D=new DirectSound.Buffer3D(TheSound) 'The3DBuffer now has TheSound as the buffer it will manipulate. 'Changing properties of The3DBuffer will now have effect on TheSound. The3DBuffer.Position=GetVector3(3,2,0) 'Notice that Buffer3D's position is set by a Vector3 object. end sub
Notice, in the above code that, after appropriate manipulations have been done on the SecondaryBuffer, the Buffer3D object is no longer needed and may be destroyed.
As the reader can conclude from this tutorial, it is fairly easy with the introduction of managed DirectX 9 to enable 3d sound in a program. The DirectX 9 SDK contains a full class reference of the classes used in this tutorial, as well as illustrations describing the listener.