Introduction
Do you want to play a Wave file with DirectSound and display its spectrum in real time? This article uses the DirectSound Win32 GDI API to achieve this. This article includes two parts: one is playing the Wave file, which is based on the article: Using DirectSound to Play Audio Stream Data; the other is how to display the sound spectrum in real time? This is our main work.
Contents
In this section, I add some code to maintain a circle buffer with red color displayed, which is based on the article: Using DirectSound to Play Audio Stream Data.
class CMyDirectSound
{
public:
CMyDirectSound();
virtual ~CMyDirectSound();
void SetFormat(WAVEFORMATEX WFE);
void SetCallback(LPGETAUDIOSAMPLES_PROGRESS Function_Callback,
LPVOID lpData);
void Play();
void Pause();
void Stop();
DWORD GetSamplesPlayed(DWORD* pCurPlayPos);
void TimerCallback();
LPDIRECTSOUNDBUFFER GetSoundBuffer() { return m_lpDSB; }
WAVEFORMATEX GetWaveFormateEx() { return m_WFE; }
LPDIRECTSOUNDBUFFER GetDirectSoundBuffer() { return m_lpDSB; }
LPBYTE GetSampleDataBuffer() { return m_lpSampleDataBuffer; }
private:
WAVEFORMATEX m_WFE;
LPDIRECTSOUND m_lpDS;
LPDIRECTSOUNDBUFFER m_lpDSB;
HANDLE m_pHEvent[2];
LPBYTE m_lpAudioBuf;
LPGETAUDIOSAMPLES_PROGRESS m_lpGETAUDIOSAMPLES;
LPVOID m_lpData;
LPBYTE m_lpSampleDataBuffer;
MMRESULT m_timerID;
DWORD m_dwCircles1;
DWORD m_dwCircles2;
int m_iDB;
CString m_strLastError;
DWORD m_dwThreadID;
HANDLE m_hThread;
};
The member m_lpSampleDataBuffer
is two seconds long, the same as the second buffer, m_lpDSB
, in the class CMyDirectSound
. The class CMyDirectSound
reads the Wave file sound data and fills it into the second buffer, and I add some code here to copy sound data into m_lpSampleDataBuffer
.
void CMyDirectSound::Play()
{
...
if (NULL == lpvAudio2)
{
memcpy(lpvAudio1, m_lpAudioBuf, dwRetBytes);
memcpy(m_lpSampleDataBuffer, m_lpAudioBuf, dwRetBytes);
}
else
{
memcpy(lpvAudio1, m_lpAudioBuf, dwBytesAudio1);
memcpy(lpvAudio2, m_lpAudioBuf + dwBytesAudio1, dwBytesAudio2);
memcpy(m_lpSampleDataBuffer, m_lpAudioBuf,
dwBytesAudio1 + dwBytesAudio2);
}
...
}
void CMyDirectSound::TimerCallback()
{
...
if (dwRetSamples < m_WFE.nSamplesPerSec)
{
DWORD dwRetBytes = dwRetSamples*m_WFE.nBlockAlign;
memset(m_lpAudioBuf+dwRetBytes, 0,
m_WFE.nAvgBytesPerSec - dwRetBytes);
memset(m_lpSampleDataBuffer+dwRetBytes, 0,
m_WFE.nAvgBytesPerSec*2 - dwRetBytes);
}
if (NULL == lpvAudio2)
{
memcpy(lpvAudio1, m_lpAudioBuf, dwBytesAudio1);
memcpy(m_lpSampleDataBuffer+dwOffset,
m_lpAudioBuf, dwBytesAudio1);
}
else
{
memcpy(lpvAudio1, m_lpAudioBuf, dwBytesAudio1);
memcpy(lpvAudio2, m_lpAudioBuf + dwBytesAudio1, dwBytesAudio2);
memcpy(m_lpSampleDataBuffer+dwOffset, m_lpAudioBuf,
dwBytesAudio1+dwBytesAudio2);
}
...
}
In this section, I will describe how to calculate the values of left and right, then use FFT to calculate it. I want to say some things before doing it. I referenced a few articles like FFT of waveIn audio signals, Simple Audio Out Oscilloscope and Spectrum Analyzer, and Multimedia PeakMeter control. All of them cast the sound data (in bytes) to a short
array with reinterpret_cast
or (short*)
. But this method does not work right in my program, so I used a Java MP3 player which can display the spectrum.
LPBYTE lpAudioBuffer = playmod->pMyDS->GetSampleDataBuffer();
if(lpAudioBuffer == NULL)
return;
float left, right;
for(int i=0;i<FFT_SAMPLE_SIZE;i++) {
if(dwCurPlayPos > dw2SecondByteSize)
dwCurPlayPos -= dw2SecondByteSize;
left = (float)((lpAudioBuffer[dwCurPlayPos+1] << 8) +
lpAudioBuffer[dwCurPlayPos+0])/32767;
right = (float)((lpAudioBuffer[dwCurPlayPos+3] << 8) +
lpAudioBuffer[dwCurPlayPos+2])/32767;
floatSamples[i] = (left+right)/2;
dwCurPlayPos+=4;
}
FFT* fft = (FFT*)playmod->fft;
float* lpFloatFFTData = fft->calculate(floatSamples, FFT_SAMPLE_SIZE);
memcpy(floatMag, lpFloatFFTData, FFT_SAMPLE_SIZE/2);
SendMessage(playmod->hWndMain, WM_PAINT+913, (WPARAM)0, (LPARAM)13);
void DrawSpectrum(HWND hwnd, float* fftData)
{
if(fftData == NULL)
return;
HDC hdc = GetWindowDC(hwnd);
SetBkMode(hdc, TRANSPARENT);
HPEN hpen, hpenOld;
HBRUSH hbrush, hbrushOld;
HBRUSH hbrush1, hbrushOld1;
RECT rect;
rect.left = 4;
rect.top = 23;
rect.right = rect.left+SPECTRUM_WIDTH;
rect.bottom = rect.top+SPECTRUM_HEIGHT;
hpen = CreatePen(PS_SOLID, 1, RGB(0, 255, 0));
hbrush = CreateSolidBrush(RGB(0, 0, 0));
hbrush1 = CreateSolidBrush(RGB(125, 125, 125));
hpenOld = (HPEN)SelectObject(hdc, hpen);
hbrushOld = (HBRUSH)SelectObject(hdc, hbrush);
hbrushOld1 = (HBRUSH)SelectObject(hdc, hbrush1);
Rectangle(hdc, rect.left, rect.top, rect.right, rect.bottom);
int maxFreq = FFT_SIZE / 2;
int height = 0;
int maxHeight = SPECTRUM_HEIGHT;
float c = 0;
float floatFrrh = 1.0;
float floatDecay = (float)SPECTRUM_DECAY;
float floatSadFrr = (floatFrrh*floatDecay);
float floatBandWidth = ((float)SPECTRUM_WIDTH/(float)SPECTRUM_BANDS);
float floatMultiplier = 2.0;
RECT r;
for(int a=0, band=0; band < SPECTRUM_BANDS;
a+=(int)floatMultiplier, band++)
{
float wFs = 0;
for (int b = 0; b < floatMultiplier; b++) {
wFs += fftData[a + b];
}
wFs = (wFs * (float) log((float)(band + 2)));
if (wFs > 1.0f) {
wFs = 1.0f;
}
if (wFs >= (floatOldMag[a] - floatSadFrr)) {
floatOldMag[a] = wFs;
} else {
floatOldMag[a] -= floatSadFrr;
if (floatOldMag[a] < 0) {
floatOldMag[a] = 0;
}
wFs = floatOldMag[a];
}
r.left = rect.left + (int)c + 1;
r.right = r.left + (int)(floatBandWidth-1);
r.top = SPECTRUM_HEIGHT - (int)(wFs*SPECTRUM_HEIGHT);
if(r.top < rect.top)
r.top = rect.top + 2;
r.top += 22;
r.bottom = rect.bottom-2;
FillRect(hdc, &r, hbrushOld1);
int height = HEIGHT(r);
if(height > intPeaks[band])
{
intPeaks[band] = height;
intPeaksDelay[band] = SPECTRUM_DELAY;
}
else
{
intPeaksDelay[band]--;
if (intPeaksDelay[band] < 0) {
intPeaks[band]--;
}
if (intPeaks[band] < 0) {
intPeaks[band] = 0;
}
}
r.top -= intPeaks[band];
if(r.top < rect.top)
r.top = rect.top + 2;
r.top += 22;
if(r.top >= rect.bottom)
r.top = rect.bottom - 2;
r.bottom = r.top + 1;
FillRect(hdc, &r, hbrushOld1);
c += floatBandWidth;
}
SelectObject(hdc, hpenOld);
DeleteObject(hpen);
SelectObject(hdc, hbrushOld);
DeleteObject(hbrush);
SelectObject(hdc, hbrushOld1);
DeleteObject(hbrush1);
ReleaseDC(hwnd, hdc);
Sleep(20);
}
This program is not mature. So, some problems still exist, some of which I am not able to get the right way to resolve. I hope people at CodeProject can help me. One of the issues is that the position that I get is not exactly a circle buffer. I have tried methods like using a thread or a timer. Do you think you could help me resolve it?