This section talks about the global Sound object and provides suggestions on a good design.
If you want to create a sound object that is generalized enough for any game engine, it would need to have the following qualities:
I have included a Possible Object Structure as well.
The references to the sound API (such as DirectSound) are hidden within the Sound object and cannot be accessed by anything outside. The public functions for this object, such as Initialize(), Release(), LoadSound(), PlaySound(), etc. will access the API to perform the corresponding tasks.
Like the Resources object, the Sound object has a registry of the sounds it may or may not use through the course of the running application. This registry contains all the information necessary about the sound, such as where to get the sound information (possibly in a .wav file or in a different binary file of some sort) and the play defaults of the sound (pan values, volume, priority, etc.)
The sound itself should only be loaded into the API when it is about to actually play, or if a function call tells Sound to prepare the buffer explicitly. In my own personal designs, I prefer to load the sound into system memory AND into an API object. That way, if the API object is lost for some reason, the data is already in memory to be copied back into the API buffer when it is restored without having to reload from disk again. For larger programs, if memory is running low, sounds may need to be released from memory and/or the API buffers to make room for the new ones, so you may need a system to evaluate which sounds are best to be dropped (such as the last time they were played and how many times, etc.)
If you have many sounds playing at the same time, it may be possible to bottleneck your sound system, which will slow down the game and generate poor sound performance. It is best to set a programmable maximum number of sounds to play. If you can keep track of the number of sounds currently playing, you can just discard any attempt to play more sounds if the maxmum number is already playing.
Some sound requests should cause the interruption of an existing playing sound., because some sounds are more important than others. You would want the player to hear his ship getting hit by gunfire rather than take up that sound slot by an artillery shot exploding in the distance. For reasons like this, you need to be prepared to interrupt a playing sound to make room for a different one. If you attach a priority number to every sound, that priority can be used to determine the sound that should be interrupted. In my designs, I interrupt the least priority sound, and if there is a tie, I go for volume, and if that is the tie, the sound that has been playing the longest. Remember that interrupting a sound is only necessary if the current number of playing sounds is at the maximum.
To support this, you need to keep track of all the playing sounds themselves. Each sound will take up a slot when it plays. The object should detect when a sound is no longer playing, and if so, free up that slot.
If you set the maximum number of sounds to a lower number while many sounds are playing, the object should interrupt the least priority sounds to meet the new requirement.
When a craft flies by or a bullet zings past you, it adds to the realism to have that sound pan, fade and change frequency depending on how the object it is attached to is moving.
To implement this, the sound slot will need a pointer to the item it is attached to, and a "viewpoint" around which the the sound will rotate. You will need to understand linear algebra, particularly vector algebra, to get this system to work. Volume is just based on distance from the item to the viewpoint. Pan is determined by its left-right displacement along the forward axis of the viewpoint. Frequency is determined by the item's velocity vector and position in relation to the viewpoint's velocity vector and position.
Be aware that this works for either a 2D or a 3D game, so you have to be prepared to accommodate multiple kinds of vector arithmetic (3D viewpoints are defined by 3 xyz vectors, 2D viewpoints by 2 xy vectors).
Sometimes you will want the game to play certain sounds in sequence, such as scripted phrases by in-game characters. The squawking of computer-controlled teammates can be heard as they take damage or are about to be disabled. You don't want all that chatter to be intermixed if you want the player to hear messages critical to the mission. So, if you string them all together one after the other in a queue (a linked list, or maybe an array) and play the next one after the first one stops, that will keep them from interfering with each other.
Be careful with this method, though. If a really important message needs to come through, empty out the queue and put in that message to be played. Also, it might seem a little disorganized if after a huge battle, you have to listen to all your teammates complaining about their damage one after the other. Keep it within reason.
The importance of these queued messages is determined by the priority you attach to each of your sounds. They should not be interruptable by lesser sounds. And, as mentioned above, some queued sounds are more important than others.
Your primary Sound object will be the global object, and its public functions might consist of the following:
Initialize() - Set up API, if fails, don't stop the game, you just can't
play any sounds.
Release() - Stop all sounds, destroy all buffers, release the API.
ReleaseAllSounds() - Stop all sounds, destroy all buffers. Do NOT release the API.
LoadSound() - Explicitly load up a sound and prepare it for playing.
PlaySound() - Start up a sound. Various overloads of this function include the game item, viewpoint, etc. if needed.
PauseAllSounds() - Halt the playing of all sounds.
ResumeAllSounds() - Resume playing all sounds after pausing.
StopAllSounds() - Stop all sounds, don't allow resume.
SetMaxSounds() - set the number of maximum playing sounds.
MaintainPlayingSounds() - perform maintenance on the currently playing sounds.
There will be many private functions as well for finding a free sound slot for playing, converting the file data to the API buffer, etc.
In addition, the sound object will need the following to maintain itself:
Sound name (ID number, string, whatever you prefer)
Where to get the sound (filename, reference to the contents of a binary file, etc.)
Reference to memory data if loaded
Reference to API buffer if prepared, more than one if could play more than once at the same time (see DirectSound reference)
Metrics on how many times it has been played and the last time it was requested
Pointer reference to sound in registry
Pointer reference to game item it is attached to (requires that you have at least an initial design for a game item storage area)
Pointer to viewpoint to use for pan, volume and frequency calculation
Type of vector calculation (2D or 3D) to determine the right function to use for viewpoint calculations
How long it has been playing, in milliseconds
Priority (could be different from default)
Current volume, pan and frequency for the sound
Sound Queue - this is simply a linked list, preferrably coded from a class template, which contains the sounds and the attributes to play them with, in sequence, one at a time. It only holds queued sounds. The rest of the sounds are either played or discarded immediately.
You are encouraged to come up with your own designs, of course!