To all who have been waiting for me to develop my sound engine, I am sorry for the wait but it is getting much closer to being completed.
The main problem I was having was the sound effect(s) were not mixing and thus only one sound could be played at one time. I have fixed this and it was pure oversight on my part that caused the problem.
Shakti, you probably want to read this very closely because the code you have thus far only needs to be altered just a little bit to play multiple sounds. Since you are not using 3D audiopaths, all you really need to do is setup a sound emitter class with a vector of sounds relating to that sound emitter. Here is how to fix the code:
In the call to IDirectMusicPerformance8::PlaySegmentEx() you must specify the segment as being a SECONDARY segment. I was specifying NULL which defaulted to a PRIMARY segment. There can only be one primary segment playing at any one time. If another primary segment needs to play the currently playing one is stopped and the new one is started. This is rather ugly. Primary segments are really intended for music use only as they do not have much use in sound effects. When you declare the segment as a secondary segment each instance of the playing sound will be auto-mixed into the DirectSoundBuffer8 relating to that sound and then mixed down into the primary buffer to be played. This is what you want to do.
Here are updated versions of CDXAudio, CDXSoundSegment, and CDXSoundEmitter classes.
Instantiation/Creation of CDXAudio
First you must instantiate the CDXAudio class. The constructor for this class does not take any parameters and can simply be instantiated globally like this:
Initialization of CDXAudio
Second, you must call CDXAudio::Create(). This will auto-setup the IDirectMusicPerformance8 and IDirectMusicLoader8 interfaces. This create function will also do all of the necessary COM intialization setup so that you do not have to mess with it.
This is where it gets a little fuzzy. The way that DirectMusic works is the Loader loads the sample into the Segment and then the Segment downloads the data into the Performance. Then the Performance can play/stop and do other things with the sound. This makes for a very strange class setup. Here is what I opted to do.
Object-based sound engine
My sound engine is object-based. In other words every sound played comes from some type of object in the world. So if you have a tank in your game that has engine sounds, bullets sounds, and firing sounds it needs to load all these sounds at initialization. This is quite simple really. Each object is a CDXSoundEmitter object. The CDXSoundEmitter object holds pointers to the IDirectMusicPerformance8 and IDirectMusicLoader8 interfaces and they are declared as public for quick access to them. I saw no benefit in creating accessor functions just to return a pointer and for the sake of simply hiding data. Data hiding just adds unnecessary stack frame overhead in this case. If you want to implement ambient sound effects simply create a CDXSoundEmitter and load all the ambient sounds into it. You may wish to derive from it in order to add your own functionality such as to add a randomness to when/how ambient sounds are played.
Initialization of CDXSoundEmitter
Ok so you have your CDXSoundEmitter. The class constructor does not take any parameters.
To init the class you MUST pass the valid IDirectMusicPerformance8 and IDirectMusicLoader8 interface pointers to the CDXSoundEmitter::Create() function. These were created when you called CDXAudio::Create(). To retrieve the pointers from CDXAudio use the following functions.
Loading sounds into the CDXSoundEmitter class
//Create the audio object and init
Ok now you have a valid CDXAudio object and a valid CDXSoundEmitter object. All that is left to do is load the sounds into the object.
This is done by calling CDXSoundEmitter::LoadSound(WCHAR *_Filename)
This next part is very important. Since any one object can emit more than one sound simply keeping track of one Segment to play is not sufficient. CDXSoundEmitter holds a vector of CDXSegment objects. Note that you do not and should not directly instantiate CDXSegment, this is already done for you. Here is what happens inside of CDXSoundEmitter::LoadSound()
- A temporary CDXSegment object is created.
- The file provided is opened and the data is loaded into the IDirectMusicSegment8 pointer (CDXSegment::Segment).
- The object is then added to the CDXSegment vector inside of CDXSoundEmitter.
- The temporary object is deleted and the size of the vector is returned to the caller. This value is the sound ID number and is extremely important. All future calls to play, stop, change volume, pan, frequency, etc., for this sound segment will be accessed via this ID number.
NOTE: All access to the sounds for your sound emitter are provided through the CDXSoundEmitter class interface. Directly accessing CDXSegment class members should be avoided. In future releases I will ensure that CDXSegment cannot be misused in this way.
What this means for the programmer is that he/she does not have to mess with sound segments at all. All that needs to be done to operate on a certain sound is to save the ID number returned from CDXSoundEmitter::LoadSound() and then pass that ID number to the desired CDXSoundEmitter function. For instance here is code that will play a sound.
Checking to see if the sound is currently stopped/playing
unsigned int test_sound=TestEmitter.LoadSound(L"test.wav");
To test to see if the sound in question is already playing simply do this:
I have not provided a function to see if the sound has stopped because this can be deduced from IsPlaying().
//Sound is currently playing
Setting the volume for the sound
To set the volume for the sound (only prototyped in the class - not functional yet):
The second parameter here might be a bit confusing for you. Instead of messing with decibels and strange values I have opted to use normalized volume values. A value of 0.0f means the sound is turned all the way down or off. A sound value of 1.0f means the sound is at max volume. This allows the programmer to easily implement volume linear interpolation effects.
For a complete description of all the classes and functions please consult CDXAudio.h
AudioPaths and 3D AudioPaths
This sound system does not implement 3D audiopaths or audiopaths as of yet and as such no spatially oriented sounds are supported. It is in my code base but has been disabled. It really isn't that much more code and simply means creating a class to encapsulate DirectMusic audio paths and then including a pointer to that class inside of CDXSoundEmitter. I'm tweaking that system right now. Also scripting is coded but not currently supported, but will be available soon. This will really aid in creating cool sound effects.
I also hope to include occlusion sound effects and other effects that can easily be done inside of DirectMusic.
Should you have any trouble using this module and/or have found a bug I've overlooked or would like to add some functionality, please post in this thread.
I'm also working on some code to interface with the driver to dial down the latency for sound effects to insane levels. This is only supported under DirectX9 and older sound cards might play some sound glitches if you attempt to use this on them. Music as of yet is also not completed. In my engine the music and sound effects are SEPARATE Performances. This will eliminate pchannel, groove level, chord progression, and tempo change interference between music and sfx. Thanks to Scott Selfon and Todor J. Fay for writing DirectX9 Audio Exposed. Much of my sound code and sound system structure was influenced by their suggestions.
Included in the ZIP
Here are the files in the zip:
CDXAudio.h - header for CDXAudio.cpp
CDXAudio.cpp - sound engine module
CQuickCom.h - macros for working with COM