Thread: Updated sound engine code

  1. #1
    Registered User VirtualAce's Avatar
    Join Date
    Aug 2001
    Posts
    9,607

    Updated sound engine code

    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.

    Usage instructions

    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:

    Code:
    #include "CDXAudio.h"
     
    CDXAudio SoundEngine;
    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.

    CDXAudio::GetPerformance()
    CDXAudio::GetLoader()

    Code example
    Code:
     
    #include "CDXAudio.h"
     
    CDXAudio SoundEngine;
    CDXSoundEmitter TestEmitter;
     
    void Setup(void)
    {
    //Create the audio object and init
    SoundEngine.Create();
     
    TestEmitter.Create(SoundEngine.GetLoader(),SoundEngine.GetPerformance());
     
    }
    Loading sounds into the CDXSoundEmitter class
    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.

    Code:
    unsigned int test_sound=TestEmitter.LoadSound(L"test.wav");
    TestEmitter.Play(test_sound);
    Checking to see if the sound is currently stopped/playing
    To test to see if the sound in question is already playing simply do this:

    Code:
    ...
    if (TestEmitter.IsPlaying(sound_ID)) 
    {
    //Sound is currently playing
    }
    ...
    I have not provided a function to see if the sound has stopped because this can be deduced from IsPlaying().

    Setting the volume for the sound
    To set the volume for the sound (only prototyped in the class - not functional yet):

    Code:
     
    ...
    TestEmitter.SetVolume(sound_ID,.5f);
    ...
    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.

    Problems/Bug reports
    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.

    SFX Latency
    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
    Last edited by VirtualAce; 11-15-2004 at 01:19 AM.

  2. #2
    Registered User
    Join Date
    Aug 2003
    Posts
    1,218
    oooo more candy to implement . Thanks a lot for this Bubba! It is much appreciated.

  3. #3
    Registered User
    Join Date
    Aug 2003
    Posts
    1,218
    I found 2 nasty bugs so far, the first one has to do with the unloading and destroying the objects, to fix it I commented out these 2 lines in CDXAudio.h:
    Code:
    SAFE_RELEASE(Performance);
    SAFE_RELEASE(Loader);
    The second one was easy to fix, unsigned int CDXSoundEmitter::LoadSound(WCHAR *Filename) should return Sounds.size()-1.

  4. #4
    Registered User VirtualAce's Avatar
    Join Date
    Aug 2001
    Posts
    9,607
    If you comment out SAFE_RELEASE(COM_object) then you are fragging COM for future use and possible causing more errors in Direct3D down the line.

    There must be another error somewhere or perhaps the COM objects are being destroyed before you can release them.

    And as for the size-1 yes that makes sense. I increase the size of the vector which means the actual ID of the sound is vector.size()-1. Thanks a million for pointing that out because it could cause some very nasty bugs and cause the wrong sound to be played.

    Here is the macro for SAFE_RELEASE

    #define SAFE_RELEASE(object) (if (object) object->Release())

    It checks for the validity of object prior to releasing so I'm not sure what is taking place. Perhaps a debug dump is in order here.
    I will check it out on my system. I'm not getting any errors from MSVC or Direct3D when I shutdown.

  5. #5
    Registered User
    Join Date
    Aug 2003
    Posts
    1,218
    Ok I have done some coding and the only problem I have now with the CDXSoundEmitter destructor, I always get access violation when the program tries to do this line: (*s_obj).Segment->Unload(Performance);

    Note, I changed the for.loop you had since the other one had a little bug as well lol, here is the new for loop in CDXSoundEmitter destructor: for(s_obj = Sounds.begin(); s_obj != Sounds.end(); s_obj++)

  6. #6
    Registered User VirtualAce's Avatar
    Join Date
    Aug 2001
    Posts
    9,607
    Weird. Because each segment is not auto-unloaded from the performance so it should still be downloaded into the performance which means you must remove it.

    I'm working on the CDXAudioPath class right now. It will allow you to change the frequency of the buffer being played by using IDirectAudioPath8::GetObjectInPath(). This will also open up a whole new realm of sound effects.

    But you must change the way you are thinking. Currently the system works by using sound emitters which is ok but there isn't any spatial positioning. The 3D system will be object-based but will also be spatially-based. Most sound cards only allow for about 32 to 64 hardware buffers before things get really ugly. So in order to effectively use resources I must constantly monitor the position of object relative to the camera view being rendered. If an object is too far away, I silence it or give its audiopath to another, closer object. So it is not a matter of how many sounds can be played at once, but how many 3D positions can be played through.

    Lets say we are at the race track and there are 40 cars. We only have 16 buffers for this example. Naturally the nearest 16 cars get audiopaths. Now each of these cars can have multiple sounds playing on them at once, engine noise, tire squeals, dude's cursing at ya (like you would hear it), etc., etc. But we can only play through 16 objects at any one point in time. So you must write a function to monitor the sound emitters and assign audio paths to the nearest ones. I will leave that to you because there are about a million spatial algorithms to use and tying you into one is not my goal. Perhaps I will provide a default distance squared algorithm which will simply subtract the vectors in question and add the components. If that distance is greater than your sound threshhold value (distance squared) then I will re-assign an audio path. No need to use sqrt() here. We will just work in terms of distance squared.

    Also you will now be able to set the conical proportions of your sounds. Yes, your sounds can actually have direction to them. The first cone determines the falloff or fallin area where the sound begins to fade or begins to amplify. The second cone determines the area in which the sound can definitely be heard and at different rates depending on how close you are to the actual sound vector. Quite nice really. We can also do occlusion effects like muffling or pitch shifting a sound if an object occludes the emitter.

    I'm also looking into adding in EAX special effects code that will only work on EAX cards. The EAX SDK is available at www.creative.com if you are interested in assisting me with this....as I will probably need it.

    EDIT: I just ordered SoundForge Audio Studio from Sony Pictures Digital. I couldn't get the pro edition it was like 400 bucks. But the Audio Studio version seems to have what I need to produce sound loops and sound effects. Not to mention it comes with a CD of over 1000 sound effects. I can't wait.
    Last edited by VirtualAce; 11-17-2004 at 12:45 PM.

  7. #7
    Registered User VirtualAce's Avatar
    Join Date
    Aug 2001
    Posts
    9,607
    Major sound bugs found thus far

    Known issues
    • Sounds don't play until sound slot 0 is playing. Then everything seems to work ok. But if you attempt to play a sound other than 0 prior to starting sound 0, the whole engine really takes a beating. Frames drop from about 75 (with vsync) to 45. What a mess. This is a major bug and I have no idea what is causing it.
    • This error seems to happen only on .NET platforms and I'm not sure what is causing it. Shakti has reported than when the destructor for CDXSoundSegment attempts to release the segment it causes an access violation error. This is because Segment has already been released, however this should not be true. The question here is if you call Vector::erase() or Vector::clear() does that in turn also call the destructor of the class type for the vector? If so, then does the compiler in turn also attempt to generate a destructor call to destroy the object? If so then it is attempting to release the Segment twice when the reference count is only 1. This is causing the error. However it is my understanding that if the vector class calls the destructor of the class type for the vector, the compiler in turn should NOT call the class destructor. Perhaps I'm wrong.
    • Currently there is not a way to create another Performance object without attempting to re-create the base Audio object. It is all done in the Create() function for CDXAudio. However this should be relatively easy to fix.
    • 3D Audiopaths and Audiopaths are not supported - code is in progress.
    • Calling CDXSoundSegment::SetLooping(bool _cond) fails in all cases. I'm not sure why. This simply calls Performance->SetLooping(IDirectMusicSoundSegment8 Segment,bool _condition). It should work but it does not.
    • It is still possible to invalidate the Performance and Loader objects outside of the CDXAudio class. This can cause serious crashes within the engine and will definitely result in a CTD.
    • There are some serious latency issues with long-length sound effects. This is well documented in the DirectX SDK and when the Audiopath code is done, this will be dialed down via driver calls. However this dial-down code will only work on newer cards that support DirectX 9. Older cards/drivers will definitely glitch the sound if it is dialed down too far.
    • Currently no EAX support. This is being looked into.
    • Might be some pchannel collision issues. I'm in a major dilemma about how many pchannels I need to use just to play WAV files. According to my docs I only need 1, but this is not working out so well. More research is needed.
    Fixed issues
    • The sound engine did not support sound sampling rates >22050 which has since been fixed.
    • There was no way to specify whether or not a sound segment was primary or secondary - has been fixed by specifying CDX_SECONDARY_SEG or CDX_PRIMARY_SEG in calls to functions that operate on segments.
    • Segment iterator code has been fine-tuned to be more stable.
    • CDXSegment creation code has been altered so as not to use dynamic memory. The object is now created on the function stack and is popped off at the end of the function. This is to ensure that the Segment interface remains valid and cannot be destroyed inadvertently or by misuse of the class. Thanks to Shakti for correcting this.
    • Sound_IDs were not correctly being returned to the caller. The vector::size() was being returned but it should have been vector::size()-1. Thanks to Shakti again for correcting this problem.
    Last edited by VirtualAce; 11-18-2004 at 12:03 AM.

  8. #8
    Registered User
    Join Date
    Aug 2003
    Posts
    1,218
    >>Sounds don't play until sound slot 0 is playing. Then everything seems to work ok. But if you attempt to play a sound other than 0 prior to starting sound 0, the whole engine really takes a beating. Frames drop from about 75 (with vsync) to 45. What a mess. This is a major bug and I have no idea what is causing it.

    I dont seem to have this problem though, that sounds really weird.

    Also I just remembered something about those errors when doing SAFE_RELEASE, I can only see it when I run the program through the debugger, but nontheless, the bug is there.
    Last edited by Shakti; 11-18-2004 at 12:37 PM.

  9. #9
    Registered User VirtualAce's Avatar
    Join Date
    Aug 2001
    Posts
    9,607
    Same here. The error only occurs when the debugger is running. It doesn't like this line.


    mov eax, dword ptr [ecx+8]

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. how do the game engine and the api interact?
    By Shadow12345 in forum Game Programming
    Replies: 9
    Last Post: 12-08-2010, 12:08 AM
  2. Enforcing Machine Code Restrictions?
    By SMurf in forum Tech Board
    Replies: 21
    Last Post: 03-30-2009, 07:34 AM
  3. Low latency sound effects
    By VirtualAce in forum Game Programming
    Replies: 0
    Last Post: 12-21-2004, 01:58 AM
  4. Example code for threads and sound
    By CSoFun in forum C++ Programming
    Replies: 2
    Last Post: 01-21-2003, 09:49 PM
  5. Interface Question
    By smog890 in forum C Programming
    Replies: 11
    Last Post: 06-03-2002, 05:06 PM