Note: This article was published
on GameDev.net
website ! |
|
What
is a Sound Server ? |
A Sound
Server is some code to play sound. (What a general definition !).
Each time you want to code computer sound, you have to use a Sound
Server. A Sound Server can be represented as a single thread, managing
the System sound device all the time. The user (you !) just send
some sound data to the Sound Server regulary.
|
|
Why
need I a sound Server ?? |
Let's
imagine you just coded a great demo, and you want to add some music.
You like these old soundchip tunes and you want to play an YM file
(ST-Sound music file format). You download the package to play an
YM file, but unfortunatly the package is only a "music rendering"
package. That is, with the library, you can ONLY generate some samples
in memory, not into the sound device ! Many music library are made
like this. Tradionnaly, the library provides a function like:
MusicCompute(void
*pSampleBuffer,long bufferSize)
So
the SoundServer is for you !!! My SoundServer provides all Windows
sound device managing, and call your callback regulary. Here is
what your code should be:
#include
<windows.h>
#include "SoundServer"
static CSoundServer soundServer;
void
myCallback(void *pSampleBuffer,long bufferLen)
{
MusicCompute(pSampleBuffer,bufferLen); // original music package
API
}
void main(void)
{
soundServer.open(myCallback);
// wait a key or anything you want
soundServer.close();
}
|
|
How
does it works |
Managing
sound device under Windows can be done with various API. Today we'll
use the classic Multimedia API called WaveOut. So our Sound Server
will work properly even if you don't have DirectSound. We'll see
a DirectSound version of the Sound Server in the next article.
The
main problem is that we're speaking of sound, so we have some rules
to respect to avoid listening some nasty *blips* in the sound stream.
Let's imagine we want to play an YM file at 44100Khz, 16bits, mono,
and we have internal buffers of 1 second. First, we fill our buffer
with the start of the music, and we play the buffer through the
Windows API. After one second, buffer is finished, so Windows tell
us that the buffer is done, and we have to fill it again with the
next of the song. We can fill our buffer again, and send back to
the sound device. BUT, in this case, playback is stopped until we
fill the buffer again, so we hear some *blips* !!
To
avoid that problem, we'll use the queuing capability of the WaveOut
API. Just imagine you have two buffers of one second each, already
filled with valid sound data (let's call them buffer1 and buffer2).
If you play buffer1 and IMMEDIATLY play buffer2, buffer1 is not
cutted. Buffer2 is just "queued", and buffer1 is still
playing. when buffer1 is finished, Windows starts IMMEDIATLY buffer2
so there is no *blips*, and inform you buffer1 is finished through
a callback. So you have 1 second to fill buffer1 again and send
it to the sound device. Quite simple, no ?
|
|
Let's
do the code |
All
the sound server is encapsulated in a class called CSoundServer.
You start the server by calling the "open" method. Open
method gets your own callback function as an argument. Then we initialize
the Windows WaveOut API by calling
waveOutOpen(
&m_hWaveOut, WAVE_MAPPER, &wfx, (DWORD)waveOutProc, (DWORD)this,
// User data. (DWORD)CALLBACK_FUNCTION);
Please
note that waveOutProc is our internal callback. And this callback
will call your user-callback.
Then
we fill all our sound buffer (remember the multi-buffering to avoid
*blips*).
for
(i=0;i<REPLAY_NBSOUNDBUFFER,i++)
{
fillNextSoundBuffer();
}
Let's
have a look to the most important function: "fillNextSoundBuffer".
First, we have to call your user callback, to fill the sound buffer
with real sample data.
//
Call the user function to fill the buffer with anything you want
! :-)
if (m_pUserCallback) m_pUserCallback(m_pSoundBuffer[m_currentBuffer],m_bufferSize);
Then
we have to prepare the buffer before sending it to the sound device:
//
Prepare the buffer to be sent to the WaveOut API
m_waveHeader[m_currentBuffer].lpData = (char*)m_pSoundBuffer[m_currentBuffer];
m_waveHeader[m_currentBuffer].dwBufferLength = m_bufferSize; waveOutPrepareHeader(m_hWaveOut,&m_waveHeader[m_currentBuffer],sizeof(WAVEHDR));
and
finnaly we can send it to the device with the waveOutWrite
//
Send the buffer the the WaveOut queue
waveOutWrite(m_hWaveOut,&m_waveHeader[m_currentBuffer],sizeof(WAVEHDR));
That's
all folks !! Quite easy, no ??
|
|
How
can I use it ? |
I like
"clean and short" code. Traditionnaly, when I get a source
code from the web, it's always a nightmare to compile and run it.
So I try to do things as simple as possible. To use the sound server,
just copy SoundServer.cpp and SoundServer.h files in your project
directory.
WARNING:
Do not forget to link your project with WINMM.LIB to use the Sound
Server.
Download
the Sound Server source code AND a sample project, playing a generated
sin-wave.
|
|
|
|