Beginning Game Audio Programming [Electronic resources] نسخه متنی

اینجــــا یک کتابخانه دیجیتالی است

با بیش از 100000 منبع الکترونیکی رایگان به زبان فارسی ، عربی و انگلیسی

Beginning Game Audio Programming [Electronic resources] - نسخه متنی

Mason McCuskey

| نمايش فراداده ، افزودن یک نقد و بررسی
افزودن به کتابخانه شخصی
ارسال به دوستان
جستجو در متن کتاب
بیشتر
تنظیمات قلم

فونت

اندازه قلم

+ - پیش فرض

حالت نمایش

روز نیمروز شب
جستجو در لغت نامه
بیشتر
توضیحات
افزودن یادداشت جدید





Figure 17-1 shows a screenshot of the Ch17p1_Visuals sample program. As you can see, it has a very minimal GUI, consisting of two buttons, browse and play, above a large rectangle where the spectrum analyzer is displayed.


Loading the Wave


This sample program, like the one from Chapter 2, uses DirectSound, the low-level DirectX Audio interface. I chose to use DirectSound mainly because in DirectSound, it's significantly easier to figure out what byte of a sample is coming through the speakers at any given instant than in DirectMusic. It's also a symmetric end to the book—so as we started with the low-level API, we return in the end to the low-level API.

Unfortunately, using DirectSound means you must load the wave file yourself—you can't just load it into a DirectMusic segment. To help out a little, you can use the code contained in CWAVFile to actually read the wave, like so:


CWAVFile wavfile;
wavfile.Load(filename);
CreateSecondaryBuffer(&g_pWAVBuffer, wavfile);
unsigned char *ptr1 = NULL;
unsigned long len1 = 0;
g_pWAVBuffer-&-Lock(0, 0, (void **)&ptr1, &len1, NULL, NULL,
DSBLOCK_ENTIREBUFFER);
memcpy(ptr1, wavfile.GetData(), wavfile.m_DataLen);
g_pWAVBuffer->Unlock(ptr1, len1, NULL, NULL);

This chunk of code was taken from Ch17p1_Visual's LoadSound function. Here you can see the code loading the WAV into wavfile, then locking a secondary DirectSound buffer, filling in its contents, and finally unlocking it (consult chapter 2 if you need a refresher on how to lock and unlock a DirectSound buffer).


CDiscreteFourierTransform


Ch17p1_Visuals relies on a new class: CDiscreteFourierTransform. This new class is designed to contain all of the data and methods needed for performing Discrete Fourier Transforms. Here's the declaration:


class CDiscreteFourierTransform
{
public:
CDiscreteFourierTransform(int bufsize, int samplerate, int
bitspersample, int channels);
virtual ~CDiscreteFourierTransform();
void LoadInputData(unsigned char *data);
void PerformSlowFourierTransform();
float GetAmp(int ndx) { return(ndx > m_BufSize ? 0.0 :
m_AmpOutput[ndx]); }
float GetFreq(int ndx) { return(ndx > m_BufSize ? 0.0 :
m_FreqOutput[ndx]); }
int m_SampleRate;
int m_NumChannels;
int m_BitsPerSample;

protected:
int m_BufSize;
float *m_Input;
float *m_FreqOutput;
float *m_AmpOutput;
};

Nothing terribly complex here—the main members are the three float pointers m_Input, m_FreqOutput, and m_AmpOutput. These are actually dynamically allocated arrays of floating point numbers—their size depends on the buffer size (m_BufSize). The m_Input array is used to store the array of input samples. The m_AmpOutput array contains the amplitudes of each frequency—the heart of the spectrum analyzer. The m_FreqOutput tells the client code the frequency of a given index. It's useful if you want to put labels on your spectrum analyzer.

Constructing the Arrays


The floating point arrays are constructed when the object is, as shown in the following code:


CDiscreteFourierTransform::CDiscreteFourierTransform(
int bufsize, int samplerate, int bitspersample, int channels) :
m_BufSize(bufsize), m_SampleRate(samplerate),
m_BitsPerSample(bitspersample), m_NumChannels(channels)
{
// make space for buffers
m_Input = new float[bufsize];
m_FreqOutput = new float[(bufsize/2)+1];
m_AmpOutput = new float[(bufsize/2)+1];
// we know already what the frequencies of each bin will be
for (int bin=0; bin < m_BufSize/2; bin++) {
m_FreqOutput[bin] = (float)bin * m_SampleRate / m_BufSize;
}
}

Also notice that the code takes this opportunity to create the frequency array, m_FreqOutput. Since this array remains constant, this is the best time to calculate its values.

Loading the Values


When the client code wants to update the spectrum analyzer, it begins by loading a new set of values into CDiscreteFourierTransform. These new values are the snippet of the wave, starting at the sample that's currently playing and extending for BUFFERSIZE samples (a constant integer set to 512).

Before the DFT can be calculated, the CDiscreteFourierTransform class must convert the array of samples from 0-255 (or 0-65535 for 16 bit samples) into floating point values from –1.0 to 1.0. This is accomplished as follows:


void CDiscreteFourierTransform::LoadInputData(unsigned char *data)
{
// convert each data point from 0-255 to floating point -1..1
if (m_BitsPerSample == 8) {
for (int q=0; q < m_BufSize; q++) {
float d = data[q*m_NumChannels];
m_Input[q] = (d-127.0f)/127.0f;
if (m_Input[q] < -1.0f) m_Input[q] = -1.0f;
if (m_Input[q] > 1.0f) m_Input[q] = 1.0f;>
}
}
else if (m_BitsPerSample == 16) {
short int *sidata = reinterpret_cast_short int *>(data);
for (int q=0; q < m_BufSize; q++) {
float d = sidata[q*m_NumChannels];
m_Input[q] = (d-32767.0f)/32767.0f;
if (m_Input[q] < -1.0f) m_Input[q] = -1.0f;
if (m_Input[q] > 1.0f) m_Input[q] = 1.0f;
}
}
else {
// not supported
for (int q=0; q < m_BufSize; q++) { m_Input[q] = 0.0f; }
}
}

As you can see, there are some pointer tricks at play here. The code treats the incoming data variable as either an array of 8-bit values, or an array of short ints (16-bit values), depending on the m_BitsPerSample value. Also note that it uses m_NumChannels, so that in stereo samples, it uses only the first channel.

Calculating the DFT


Finally, the moment you've been waiting for. Once the arrays are set up and the m_Input array is loaded with floating point values, the client code calls the PerformSlowFourierTransform method to calculate the DFT and populate the m_AmpOutput array:


void CDiscreteFourierTransform::PerformSlowFourierTransform()
{
for (int bin = 0; bin <= m_BufSize/2; bin++) {
float cosAmp = 0.0f;
float sinAmp = 0.0f;
for (int k = 0; k < m_BufSize; k++) {
float x = 2.0f * M_PI * (float)bin * (float)k / (float)m_BufSize;
sinAmp += m_Input[k] * sin(x);
cosAmp += m_Input[k] * cos(x);
}
m_AmpOutput[bin] = sqrt(sinAmp*sinAmp + cosAmp*cosAmp);
}
}

At this point, there should be no surprises in that code. It's based on the theory you learned at the beginning of the chapter.


Rendering the Graph


Finally, here's the code that uses CDiscreteFourierTransform's m_AmpOutput array to actually render the spectrum analyzer:


void RenderSpectrum(HDC hdc, HWND hDlg)
{
HWND framewin = GetDlgItem(hDlg, IDC_FRAME);
RECT framerect;
int framewidth,frameheight;
GetWindowRect(framewin, &framerect);
ScreenToClient(hDlg, (POINT *)&framerect.left);
ScreenToClient(hDlg, (POINT *)&framerect.right);
framewidth = framerect.right - framerect.left;
frameheight = framerect.bottom - framerect.top;
// draw each bar
int n=0;
for (float x=framerect.left; x < framerect.right;
x += framewidth / ((float)BUFFERSIZE/2.0f), n++) {
RECT barrect;
barrect.left = x;
barrect.right = x + (framewidth / ((float)BUFFERSIZE/2.0f));
barrect.bottom = framerect.bottom;

barrect.top = framerect.bottom -
(g_FT->GetAmp(n) / 50.0f * (float)frameheight);
if (barrect.top < framerect.top) { barrect.top = framerect.top; }
FillRect(hdc, &barrect, (HBRUSH)GetStockObject(GRAY_BRUSH));
// fill in the top portion black
if (barrect.top > framerect.top) {
barrect.bottom = barrect.top;
barrect.top = framerect.top;
FillRect(hdc, &barrect, (HBRUSH)GetStockObject(BLACK_BRUSH));
}
}
}

This is bare-bones Win32 API GDI code, so it's a little involved, but it's not doing anything complex. It simply loops from the left side of the output rectangle to the right, calculating the height of each bar and drawing two rectangles, one gray (the bottom of the bar), and one black (the top of the bar). In this manner, some of the flickering is reduced, since we're not clearing the whole rectangle and then immediately drawing bars on top of it.

If you're so inclined, I encourage you to make this look better. A common effect is to make a redto-green gradient on the bars, making the spectrum analyzer a little more colorful.

/ 127