Reading Sounds from Disk
Unfortunately, DirectSound has no support for loading sound files. I mean no support. No .VOC loader, no .WAV loader, no nothing! It's a darn shame. So, you'll have to write one yourself. The problem is that sound files are extremely complex, and it would take half a chapter to do a good job of explaining them. So what I'm going to do is give you a .WAV loader and explain how it works in general.
TIP
The guys at Microsoft got sick of writing their own .WAV loaders, along with a lot of other utility functions, so they wrote one that you can use if you want. The only problem is, the API isn't standard and will probably change. But if you're interested, check out all the goodies in DDUTIL*.CPP|H, located in one of the SOURCE directories of the SDK install, usually in the SAMPLES or EXAMPLES directory.
The .WAV Format
The .WAV format is a Windows sound format based on the .IFF format originally created by Electronic Arts. IFF stands for Interchange File Format. It's a standard that allows many different file types to be encoded using a general header/data structure with nesting. The .WAV format uses this encoding, and although it's very clean and logical, it's a pain to read the files in. You must parse a lot of header information, which takes a lot of code, and then you have to extract the sound data.
The parsing is so difficult that Microsoft created a set of functions called the multimedia I/O interface (MMIO) to help you load .WAV files and other similar types. All the functions of this library are prefixed with mmio*. The moral of the story is that writing a .WAV file reader isn't that easy, and it's tedious programming that has nothing to do with game programming. So, I'm just going to give you a heavily commented .WAV loader and a little explanation. If you want more, find a good reference on sound file formats.
Reading .WAV Files
The .WAV file format is based on chunks—ID chunks, format chunks, and data chunks. In essence, you need to open up a .WAV file and read in the header and format information, which then tells you how many channels there are, the bits per channel, the playback rate, and so forth, along with the length of the sampled sound. Then you load the sound.
Now, to help facilitate loading and playing sounds, you're going to create a sound library API, and hence a set of globals and wrapper functions around all this DirectSound stuff to make things easy. Let's begin with a data structure that will hold a virtual sound, which you'll use instead of the lower-level DirectSound stuff:
// this holds a single sound
typedef struct pcm_sound_typ
{
LPDIRECTSOUNDBUFFER dsbuffer; // the ds 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;
This nicely contains the DirectSound buffer associated with a sound, along with a copy of the important information about the sound. Now let's create an array to hold all the sounds in the system:
pcm_sound sound_fx[MAX_SOUNDS]; // the array of secondary sound buffers
So, when you load a sound, the idea is to find an open space and set up a pcm_sound structure. This is exactly what the following DSound_Load_WAV() function does:
int DSound_Load_WAV(char *filename, int control_flags = DSBCAPS_CTRLDEFAULT)
{
// this function loads a .wav file, sets up the directsound
// buffer and loads the data into memory, the function returns
// the id number of the sound
HMMIO hwav; // handle to wave file
MMCKINFO parent, // parent chunk
child; // child chunk
WAVEFORMATEX wfmtx; // wave format structure
int sound_id = -1, // id of sound to be loaded
index; // looping variable
UCHAR *snd_buffer, // temporary sound buffer to hold voc data
*audio_ptr_1=NULL, // data ptr to first write buffer
*audio_ptr_2=NULL; // data ptr to second write buffer
DWORD audio_length_1=0, // length of first write buffer
audio_length_2=0; // length of second write buffer
// step one: are there any open id's ?
for (index=0; index < MAX_SOUNDS; index++)
{
// make sure this sound is unused
if (sound_fx[index].state==SOUND_NULL)
{
sound_id = index;
break;
} // end if
} // end for index
// did we get a free ID?
if (sound_id==-1)
return(-1);
// set up chunk info structure
parent.ckid = (FOURCC)0;
parent.cksize = 0;
parent.fccType = (FOURCC)0;
parent.dwDataOffset = 0;
parent.dwFlags = 0;
// copy data
child = parent;
// open the WAV file
if ((hwav = mmioOpen(filename, NULL, MMIO_READ | MMIO_ALLOCBUF))==NULL)
return(-1);
// descend into the RIFF
parent.fccType = mmioFOURCC('W', 'A', 'V', 'E');
if (mmioDescend(hwav, &parent, NULL, MMIO_FINDRIFF))
{
// close the file
mmioClose(hwav, 0);
// return error, no wave section
return(-1);
} // end if
// descend to the WAVEfmt
child.ckid = mmioFOURCC('f', 'm', 't', ' ');
if (mmioDescend(hwav, &child, &parent, 0))
{
// close the file
mmioClose(hwav, 0);
// return error, no format section
return(-1);
} // end if
// now read the wave format information from file
if (mmioRead(hwav, (char *)&wfmtx, sizeof(wfmtx)) != sizeof(wfmtx))
{
// close file
mmioClose(hwav, 0);
// return error, no wave format data
return(-1);
} // end if
// make sure that the data format is PCM
if (wfmtx.wFormatTag != WAVE_FORMAT_PCM)
{
// close the file
mmioClose(hwav, 0);
// return error, not the right data format
return(-1);
} // end if
// now ascend up one level, so we can access data chunk
if (mmioAscend(hwav, &child, 0))
{
// close file
mmioClose(hwav, 0);
// return error, couldn't ascend
return(-1);
} // end if
// descend to the data chunk
child.ckid = mmioFOURCC('d', 'a', 't', 'a');
if (mmioDescend(hwav, &child, &parent, MMIO_FINDCHUNK))
{
// close file
mmioClose(hwav, 0);
// return error, no data
return(-1);
} // end if
// finally!!!! now all we have to do is read the data in and
// set up the directsound buffer
// allocate the memory to load sound data
snd_buffer = (UCHAR *)malloc(child.cksize);
// read the wave data
mmioRead(hwav, (char *)snd_buffer, child.cksize);
// close the file
mmioClose(hwav, 0);
// set rate and size in data structure
sound_fx[sound_id].rate = wfmtx.nSamplesPerSec;
sound_fx[sound_id].size = child.cksize;
sound_fx[sound_id].state = SOUND_LOADED;
// set up the format data structure
memset(&pcmwf, 0, sizeof(WAVEFORMATEX));
pcmwf.wFormatTag = WAVE_FORMAT_PCM; // pulse code modulation
pcmwf.nChannels = 1; // mono
pcmwf.nSamplesPerSec = 11025; // always this rate
pcmwf.nBlockAlign = 1;
pcmwf.nAvgBytesPerSec = pcmwf.nSamplesPerSec * pcmwf.nBlockAlign;
pcmwf.wBitsPerSample = 8;
pcmwf.cbSize = 0;
// prepare to create sounds buffer
dsbd.dwSize = sizeof(DSBUFFERDESC);
dsbd.dwFlags = control_flags | DSBCAPS_STATIC |
DSBCAPS_LOCSOFTWARE;
dsbd.dwBufferBytes = child.cksize;
dsbd.lpwfxFormat = &pcmwf;
// create the sound buffer
if (lpds->CreateSoundBuffer(&dsbd,
&sound_fx[sound_id].dsbuffer,NULL)!=DS_OK)
{
// release memory
free(snd_buffer);
// return error
return(-1);
} // end if
// copy data into sound buffer
if (sound_fx[sound_id].dsbuffer->Lock(0,
child.cksize,
(void **) &audio_ptr_1,
&audio_length_1,
(void **)&audio_ptr_2,
&audio_length_2,
DSBLOCK_FROMWRITECURSOR)!=DS_OK)
return(0);
// copy first section of circular buffer
memcpy(audio_ptr_1, snd_buffer, audio_length_1);
// copy last section of circular buffer
memcpy(audio_ptr_2, (snd_buffer+audio_length_1),audio_length_2);
// unlock the buffer
if (sound_fx[sound_id].dsbuffer->Unlock(audio_ptr_1,
audio_length_1,
audio_ptr_2,
audio_length_2)!=DS_OK)
return(0);
// release the temp buffer
free(snd_buffer);
// return id
return(sound_id);
} // end DSound_Load_WAV
You simply pass the filename and the standard DirectSound control flags to the function, such as DSBCAPS_CTRLDEFAULT or whatever. After that, here's what happens:
The function opens up the .WAV file from the disk and extracts the important information about it.
The function proceeds to create a DirectSound buffer and fills it in.
The function stores the information in an open slot in the sound_fx[] array and returns the index, which I refer to as the ID of the sound.
Finally, the rest of your API will use the ID number to refer to the sound, and you can do whatever you want with it, such as play it. Here's an example:
// load the sound
int id = DSound_Load_WAV("test.wav");
// manually play the sound buffer, later we will wrapper this
sound_fx1.lpdsbuffer->Play(0,0,DSBPLAY_LOOPING);
Make sure to check out DEMO10_3.CPP on the disk. (Remember to link with DSOUND.LIB, and WINMM.LIB.) It's a complete demo of DirectSound and the Dsound_Load_WAV() function. In addition, the program lets you manipulate the sound in real-time with scrollbars, so not only is it cool, but you can see how to add scrollbar controls to your applications!
Of course, I'm going to take all this DirectSound stuff and show you the complete library (T3DLIB3.CPP|H), but first let's take a look at DirectMusic.
|