Home > Articles > Programming > Games

This chapter is from the book

The T3DLIB3 Sound and Music Library

I have taken all the sound and music technology from the first Tricks and used it to create the last component to our game engine T3DLIB3. It is composed of two main source files:

  • T3DLIB3.CPP—The main C/C++ source

  • T3DLIB3.H—The header file

However, you'll also need to include the DirectSound import library DSOUND.LIB to make anything link. However, DirectMusic does not have an import library because it's a pure COM object, so there isn't a DMUSIC.LIB. On the other hand, you still need to point your compiler to the DirectSound and DirectMusic .H header files, so it can find them during compilation. Just to remind you, they are:

  • DSOUND.H—The standard DirectSound header.

  • DMKSCTRL.H—All of these are for DirectMusic.

  • DMUSICI.H

  • DMUSICC.H

  • DMUSICF.H

With all that in mind, let's take a look at the main elements of the T3DLIB3.H header file.

NOTE

With DirectX 8.0+, Microsoft integrated DirectSound and DirectMusic much more tightly and called it DirectAudio. I see no point in changing to DirectAudio, so we will simply use each component separately in this book.

The Header

The header file T3DLIB3.H contains the types, macros, and externals for T3DLIB3.CPP. Here are the #defines you'll find in the header:

#define DM_NUM_SEGMENTS 64 // number of midi segments that can be cached in memory

// midi object state defines
#define MIDI_NULL  0 // this midi object is not loaded
#define MIDI_LOADED 1 // this midi object is loaded
#define MIDI_PLAYING 2 // this midi object is loaded and playing
#define MIDI_STOPPED 3 // this midi object is loaded, but stopped

#define MAX_SOUNDS  256 // max number of sounds in system at once

// digital sound object state defines
#define SOUND_NULL  0 // " "
#define SOUND_LOADED 1
#define SOUND_PLAYING 2
#define SOUND_STOPPED 3

Not much for macros—this is just a macro to help convert from 0–100 to the Microsoft decibels scale, and one to convert multibyte characters to wide characters:

#define DSVOLUME_TO_DB(volume) ((DWORD)(-30*(100 - volume)))

// Convert from multibyte format to Unicode using the following macro
#define MULTI_TO_WIDE( x,y )
   MultiByteToWideChar( CP_ACP,MB_PRECOMPOSED, y,-1,x,_MAX_PATH)

CAUTION

The column width of this book might be too small to fit the whole macro, so the definition might be on two lines. This is a no-no in real life. Macros must be on a single line!

Next up are the types for the sound engine. First, the DirectSound object.

The Types

There are only two types for the sound engine: one to hold a digital sample, and the other to hold a MIDI segment:

// this holds a single sound
typedef struct pcm_sound_typ
 {
 LPDIRECTSOUNDBUFFER dsbuffer; // the directsound buffer
         // containing the sound
 int state; // state of the sound
 int rate; // playback rate
 int size; // size of sound
 int id;  // id number of the sound
 } pcm_sound, *pcm_sound_ptr;

And now the DirectMusic segment type:

// directmusic MIDI segment
typedef struct DMUSIC_MIDI_TYP
{
IDirectMusicSegment  *dm_segment; // the directmusic segment
IDirectMusicSegmentState *dm_segstate; // the state of the segment
int      id;   // the id of this segment
int      state;  // state of midi song

} DMUSIC_MIDI, *DMUSIC_MIDI_PTR;

Both sounds and MIDI segments will be stored by the engine in the preceding two structures, respectively. Now let's take a look at the globals.

Global Domination

T3DLIB3 contains a number of globals. Let's take a look. First, here are the globals for the DirectSound system:

LPDIRECTSOUND lpds; // directsound interface pointer
DSBUFFERDESC dsbd; // directsound description
DSCAPS  dscaps; // directsound caps
HRESULT  dsresult // general directsound result
DSBCAPS  dsbcaps; // directsound buffer caps

pcm_sound sound_fx[MAX_SOUNDS]; // array of sound buffers
WAVEFORMATEX pcmwf; // generic waveformat structure

And here are the globals for DirectMusic:

// direct music globals
IDirectMusicPerformance *dm_perf ; // the directmusic performance manager
IDirectMusicLoader   *dm_loader; // the directmusic loader
// this hold all the directmusic midi objects
DMUSIC_MIDI   dm_midi[DM_NUM_SEGMENTS];
int dm_active_id; // currently active midi segment

NOTE

I have highlighted lines in the arrays that hold sounds and MIDI segments.

You shouldn't have to mess with any of these globals, except to access the interfaces directly if you want. In general, the API will handle everything for you, but the globals are there if you want to tear them up.

There are two parts to the library: DirectSound and DirectMusic. Let's take a look at DirectSound first.

The DirectSound API Wrapper

DirectSound can be complicated or simple, depending on how you use it. If you want a "do it all" API, you're going to end up simply using most of the DirectSound functions themselves. But if you want a simpler API that enables you to initialize DirectSound and load and play sounds of a specific format, that is a lot easier to wrap up in a few functions.

So, what I've done is take much of our work in setting up DirectSound and formalize it into a set of functions for you. In addition, I've created an abstraction around the sound system, so you refer to a sound with an ID (same for the DirectMusic part) that is given to you during the loading process. Thus, you can use this ID to play the sound, check its status, or to terminate it. This way, there aren't any ugly interface pointers that you have to mess with. The new API supports the following functionality:

  • Initializing and shutting down DirectSound with single calls

  • Loading .WAV files with 11KHz 8-bit mono format

  • Playing a loaded sound file

  • Stopping a sound

  • Testing the play status of a sound

  • Changing the volume, playback rate, or stereo panning of a sound

  • Deleting sounds from memory

Let's take a look at each function one by one.

NOTE

Unless otherwise stated, all functions return TRUE (1) if successful and FALSE (0) if not.

Function Prototype:

int DSound_Init(void);

Purpose:

DSound_Init() is used to initialize the entire DirectSound system. It creates the DirectSound COM object, sets the priority level, and so forth. Just call the function at the beginning of your application if you want to use sound. Here's an example:

Example:

if (!DSound_Init(void))
 { /* error */ }

Function Prototype:

int DSound_Shutdown(void);

Purpose:

DSound_Shutdown() is used to shut down and release all the COM interfaces created during DSound_Init(). However, DSound_Shutdown() will not release all the memory allocated to all the sound. You must do this yourself with another function. Anyway, here's how you would shut down DirectSound:

Example:

if (!DSound_Shutdown())
 { /* error */ }

Function Prototype(s):

int DSound_Load_WAV(char *filename);

Purpose:

DSound_Load_WAV() creates a DirectSound buffer, loads the sound data file into memory, and prepares the sound to be played. The function takes the complete path and filename of the sound file to be loaded (including the extension .WAV) and loads the file off disk. If successful, the function returns a non-negative ID number. You must save this number, because it is used as a handle to reference the sound. If the function can't find the file, or too many sounds are loaded, it will return -1. Here's an example of loading a .WAV file named FIRE.WAV:

Example:

int fire_id = DSound_Load_WAV("FIRE.WAV");

// test for error
if (fire_id==-1)
 { /* error */}

Of course, it's up to you on how you want to save the IDs. You might want to use an array or something else.

Finally, you might wonder where the sound data is, and how to mess with it. If you really must, then you can access the data within the pcm_sound array sound_fx[] using the ID you get back from either load function as the index. For example, here's how you would access the DirectSound buffer for the sound with ID sound_id:

Example:

sound_fx[sound_id].dsbuffer

Function Prototype:

int DSound_Replicate_Sound(int source_id); // id of sound to copy

Purpose:

DSound_Replicate_Sound() is used to copy a sound without copying the memory used to hold the sound. For example, suppose you have a gunshot sound, and you want to fire three gunshots, each right after the other? The only way to do this right now would be to load three copies of the gunshot sound into three different DirectSound memory buffers, which would be a waste of memory.

Alas, there is a solution—it's possible to create a duplicate or replicant (if you're a Blade Runner fan) of the sound buffer, except for the actual sound data. Instead of copying it, we just point a pointer to it, and DirectSound is smart enough to use it as a "source" for multiple sounds using the same data. The bottom line is, if you want to play a gunshot up to eight times, for example, you should load the gunshot once, make seven copies of it, and acquire a total of eight unique IDs. Replicated sounds work exactly the same as normal sounds, but instead of using DSound_Load_WAV() to load and create them, you copy them with DSound_Replicate_Sound(). Get it? Good! I'm starting to get dizzy! Here's an example of creating eight gunshots:

Example:

int gunshot_ids[8]; // this holds all the id's

// load in the master sound
gunshot_ids[0] = Load_WAV("GUNSHOT.WAV");

// now make copies
for (int index=1; index<8; index++)
 gunshot_ids[index] = DSound_Replicate_Sound(gunshot_ids[0]);

// use gunshot_ids[0..7] anyway you which they all go bang!

Function Prototype:

int DSound_Play_Sound(int id,  // id of sound to play
      int flags=0, // 0 or DSBPLAY_LOOPING
      int volume=0, // unused
      int rate=0, // unused
      int pan=0); // unused

Purpose:

DSound_Play_Sound() plays a previously loaded sound. You simply send the ID of the sound along with the play flags—0 for a single time, or DSBPLAY_LOOPING to loop it, and the sound will start playing. If the sound is already playing, it will restart at the beginning. Here's an example of loading and playing a sound:

Example:

int fire_id = DSound_Load_WAV("FIRE.WAV");
DSound_Play_Sound(fire_id,0);

I could have also left out the 0 for flags entirely, because its default parameter is 0:

int fire_id = DSound_Load_WAV("FIRE.WAV");
DSound_Play_Sound(fire_id);

Either way, the FIRE.WAV sound will play once and then stop. To make it loop, you would send DSBPLAY_LOOPING for the flags parameter.

Function Prototype(s):

int DSound_Stop_Sound(int id);
int DSound_Stop_All_Sounds(void);

Purpose:

DSound_Stop_Sound() is used to stop a single sound from playing (if it is playing). You simply send the ID of the sound, and that's it. DSound_Stop_All_Sounds() will stop all the sounds currently playing. Here's an example of stopping the fire_id sound:

Example:

DSound_Stop_Sound(fire_id);

At the end of your program, it's a good idea to stop all the sounds from playing before exiting. You could do this with separate calls to DSound_Stop_Sound() for each sound, or a single call to DSound_Stop_All_Sounds():

//...system shutdown code
DSound_Stop_All_Sounds();

Function Prototype:

int DSound_Delete_Sound(int id); // id of sound to delete
int DSound_Delete_All_Sounds(void);

Purpose:

DSound_Delete_Sound() deletes a sound from memory and releases the DirectSound buffer associated with it. If the sound is playing, the function will stop it first. DSound_Delete_All_Sounds() deletes all previously loaded sounds. Here's an example of deleting the fire_id sound:

Example:

DSound_Delete_Sound(fire_id);

Function Prototype:

int DSound_Status_Sound(int id);

Purpose:

DSound_Status_Sound() tests the status of a loaded sound based on its ID. All you do is pass the function the ID number of the sound, and the function will return one of these values:

  • DSBSTATUS_LOOPING—The sound is currently playing and is in loop mode.

  • DSBSTATUS_PLAYING—The sound is currently playing and is in single play mode.

If the value returned from DSound_Status_Sound() is neither of the preceding constants, the sound is not playing. Here's a complete example that waits until a sound has completed playing and then deletes it.

Example:

// initialize DirectSound
DSound_DSound_Init();

// load a sound
int fire_id = DSound_Load_WAV("FIRE.WAV");

// play the sound in single mode
DSound_Play_Sound(fire_id);

// wait until the sound is done
while(DSound_Sound_Status(fire_id) &
    (DSBSTATUS_LOOPING | DSBSTATUS_PLAYING));

// delete the sound
DSound_Delete_Sound(fire_id);

// shutdown DirectSound
DSound_DSound_Shutdown();

Pretty cool, huh? A lot better than the couple hundred or so lines of code to do it manually with DirectSound!

Function Prototype:

int DSound_Set_Sound_Volume(int id, // id of sound
      int vol); // volume from 0-100	

Purpose:

DSound_Set_Sound_Volume() changes the volume of a sound in real time. Send the ID of the sound along with a value from 0–100 and the sound will change instantly. Here's an example of changing the volume of a sound to 50% of what it was loaded as:

Example:

DSound_Set_Sound_Volume(fire_id, 50);

You can always change the volume back to 100% like this:

DSound_Set_Sound_Volume(fire_id, 100);

Function Prototype:

int DSound_Set_Sound_Freq(
    int id, // sound id
    int freq); // new playback rate from 0-100000

Purpose:

DSound_Set_Sound_Freq() changes the playback frequency of the sound. Because all sounds must be loaded at 11KHz mono, here's how you would double the perceived playback rate:

Example:

DSound_Set_Sound_Freq(fire_id, 22050);

And to make you sound like Darth Vader, do this:

DSound_Set_Sound_Freq(fire_id, 6000);

Function Prototype:

int DSound_Set_Sound_Pan(
 int id, // sound id
 int pan); // panning value from -10000 to 10000

Purpose:

DSound_Set_Sound_Pan() sets the relative intensity of the sound on the right and left speakers. A value of -10,000 is hard left and 10,000 is hard right. If you want equal power, set the pan to 0. Here's how you would set the pan all the way to the right side:

Example:

DSound_Set_Sound_Pan(fire_id, 10000);

InformIT Promotional Mailings & Special Offers

I would like to receive exclusive offers and hear about products from InformIT and its family of brands. I can unsubscribe at any time.

Overview


Pearson Education, Inc., 221 River Street, Hoboken, New Jersey 07030, (Pearson) presents this site to provide information about products and services that can be purchased through this site.

This privacy notice provides an overview of our commitment to privacy and describes how we collect, protect, use and share personal information collected through this site. Please note that other Pearson websites and online products and services have their own separate privacy policies.

Collection and Use of Information


To conduct business and deliver products and services, Pearson collects and uses personal information in several ways in connection with this site, including:

Questions and Inquiries

For inquiries and questions, we collect the inquiry or question, together with name, contact details (email address, phone number and mailing address) and any other additional information voluntarily submitted to us through a Contact Us form or an email. We use this information to address the inquiry and respond to the question.

Online Store

For orders and purchases placed through our online store on this site, we collect order details, name, institution name and address (if applicable), email address, phone number, shipping and billing addresses, credit/debit card information, shipping options and any instructions. We use this information to complete transactions, fulfill orders, communicate with individuals placing orders or visiting the online store, and for related purposes.

Surveys

Pearson may offer opportunities to provide feedback or participate in surveys, including surveys evaluating Pearson products, services or sites. Participation is voluntary. Pearson collects information requested in the survey questions and uses the information to evaluate, support, maintain and improve products, services or sites, develop new products and services, conduct educational research and for other purposes specified in the survey.

Contests and Drawings

Occasionally, we may sponsor a contest or drawing. Participation is optional. Pearson collects name, contact information and other information specified on the entry form for the contest or drawing to conduct the contest or drawing. Pearson may collect additional personal information from the winners of a contest or drawing in order to award the prize and for tax reporting purposes, as required by law.

Newsletters

If you have elected to receive email newsletters or promotional mailings and special offers but want to unsubscribe, simply email information@informit.com.

Service Announcements

On rare occasions it is necessary to send out a strictly service related announcement. For instance, if our service is temporarily suspended for maintenance we might send users an email. Generally, users may not opt-out of these communications, though they can deactivate their account information. However, these communications are not promotional in nature.

Customer Service

We communicate with users on a regular basis to provide requested services and in regard to issues relating to their account we reply via email or phone in accordance with the users' wishes when a user submits their information through our Contact Us form.

Other Collection and Use of Information


Application and System Logs

Pearson automatically collects log data to help ensure the delivery, availability and security of this site. Log data may include technical information about how a user or visitor connected to this site, such as browser type, type of computer/device, operating system, internet service provider and IP address. We use this information for support purposes and to monitor the health of the site, identify problems, improve service, detect unauthorized access and fraudulent activity, prevent and respond to security incidents and appropriately scale computing resources.

Web Analytics

Pearson may use third party web trend analytical services, including Google Analytics, to collect visitor information, such as IP addresses, browser types, referring pages, pages visited and time spent on a particular site. While these analytical services collect and report information on an anonymous basis, they may use cookies to gather web trend information. The information gathered may enable Pearson (but not the third party web trend services) to link information with application and system log data. Pearson uses this information for system administration and to identify problems, improve service, detect unauthorized access and fraudulent activity, prevent and respond to security incidents, appropriately scale computing resources and otherwise support and deliver this site and its services.

Cookies and Related Technologies

This site uses cookies and similar technologies to personalize content, measure traffic patterns, control security, track use and access of information on this site, and provide interest-based messages and advertising. Users can manage and block the use of cookies through their browser. Disabling or blocking certain cookies may limit the functionality of this site.

Do Not Track

This site currently does not respond to Do Not Track signals.

Security


Pearson uses appropriate physical, administrative and technical security measures to protect personal information from unauthorized access, use and disclosure.

Children


This site is not directed to children under the age of 13.

Marketing


Pearson may send or direct marketing communications to users, provided that

  • Pearson will not use personal information collected or processed as a K-12 school service provider for the purpose of directed or targeted advertising.
  • Such marketing is consistent with applicable law and Pearson's legal obligations.
  • Pearson will not knowingly direct or send marketing communications to an individual who has expressed a preference not to receive marketing.
  • Where required by applicable law, express or implied consent to marketing exists and has not been withdrawn.

Pearson may provide personal information to a third party service provider on a restricted basis to provide marketing solely on behalf of Pearson or an affiliate or customer for whom Pearson is a service provider. Marketing preferences may be changed at any time.

Correcting/Updating Personal Information


If a user's personally identifiable information changes (such as your postal address or email address), we provide a way to correct or update that user's personal data provided to us. This can be done on the Account page. If a user no longer desires our service and desires to delete his or her account, please contact us at customer-service@informit.com and we will process the deletion of a user's account.

Choice/Opt-out


Users can always make an informed choice as to whether they should proceed with certain services offered by InformIT. If you choose to remove yourself from our mailing list(s) simply visit the following page and uncheck any communication you no longer want to receive: www.informit.com/u.aspx.

Sale of Personal Information


Pearson does not rent or sell personal information in exchange for any payment of money.

While Pearson does not sell personal information, as defined in Nevada law, Nevada residents may email a request for no sale of their personal information to NevadaDesignatedRequest@pearson.com.

Supplemental Privacy Statement for California Residents


California residents should read our Supplemental privacy statement for California residents in conjunction with this Privacy Notice. The Supplemental privacy statement for California residents explains Pearson's commitment to comply with California law and applies to personal information of California residents collected in connection with this site and the Services.

Sharing and Disclosure


Pearson may disclose personal information, as follows:

  • As required by law.
  • With the consent of the individual (or their parent, if the individual is a minor)
  • In response to a subpoena, court order or legal process, to the extent permitted or required by law
  • To protect the security and safety of individuals, data, assets and systems, consistent with applicable law
  • In connection the sale, joint venture or other transfer of some or all of its company or assets, subject to the provisions of this Privacy Notice
  • To investigate or address actual or suspected fraud or other illegal activities
  • To exercise its legal rights, including enforcement of the Terms of Use for this site or another contract
  • To affiliated Pearson companies and other companies and organizations who perform work for Pearson and are obligated to protect the privacy of personal information consistent with this Privacy Notice
  • To a school, organization, company or government agency, where Pearson collects or processes the personal information in a school setting or on behalf of such organization, company or government agency.

Links


This web site contains links to other sites. Please be aware that we are not responsible for the privacy practices of such other sites. We encourage our users to be aware when they leave our site and to read the privacy statements of each and every web site that collects Personal Information. This privacy statement applies solely to information collected by this web site.

Requests and Contact


Please contact us about this Privacy Notice or if you have any requests or questions relating to the privacy of your personal information.

Changes to this Privacy Notice


We may revise this Privacy Notice through an updated posting. We will identify the effective date of the revision in the posting. Often, updates are made to provide greater clarity or to comply with changes in regulatory requirements. If the updates involve material changes to the collection, protection, use or disclosure of Personal Information, Pearson will provide notice of the change through a conspicuous notice on this site or other appropriate way. Continued use of the site after the effective date of a posted revision evidences acceptance. Please contact us if you have questions or concerns about the Privacy Notice or any objection to any revisions.

Last Update: November 17, 2020