Click here to Skip to main content
Click here to Skip to main content

Play Wave file with DirectSound and display its spectrum in real time

By , 15 Sep 2008
 

DSound_Spectrum.gif

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

Maintain our circle buffer when playing

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; } // jacky_zz[2008-09-04]
    LPDIRECTSOUNDBUFFER GetDirectSoundBuffer() { return m_lpDSB; } // jacky_zz[2008-09-04]
    LPBYTE GetSampleDataBuffer() { return m_lpSampleDataBuffer; } // jacky_zz[2008-09-04]

private:

    //<DirectSound>
    WAVEFORMATEX m_WFE;
    LPDIRECTSOUND m_lpDS;
    LPDIRECTSOUNDBUFFER m_lpDSB;
    HANDLE m_pHEvent[2];
    //</DirectSound>

    //<Audio Buffer>
    LPBYTE m_lpAudioBuf;
    LPGETAUDIOSAMPLES_PROGRESS m_lpGETAUDIOSAMPLES;
    LPVOID m_lpData;
    //</Audio Buffer>

    //<SampleData> jacky_zz[2008-09-04]
    LPBYTE m_lpSampleDataBuffer;
    //</SampleData> jacky_zz[2008-09-04]

    //<Playing>
    MMRESULT m_timerID;
    DWORD m_dwCircles1;
    DWORD m_dwCircles2;
    int m_iDB;    
    //</Playing>

    //<Error Information>
    CString m_strLastError;
    //</Error Information>

    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()
{
    ...

    //Copy Audio Buffer to DirectSoundBuffer
    if (NULL == lpvAudio2)
    {
        memcpy(lpvAudio1, m_lpAudioBuf, dwRetBytes);

        //jacky_zz
        memcpy(m_lpSampleDataBuffer, m_lpAudioBuf, dwRetBytes);
        //jacky_zz
    }
    else
    {
        memcpy(lpvAudio1, m_lpAudioBuf, dwBytesAudio1);
        memcpy(lpvAudio2, m_lpAudioBuf + dwBytesAudio1, dwBytesAudio2);

        //jacky_zz
        memcpy(m_lpSampleDataBuffer, m_lpAudioBuf, 
               dwBytesAudio1 + dwBytesAudio2);
        //jacky_zz
    }

    ...
}
void CMyDirectSound::TimerCallback()
{
    ...

    //If near the end of the audio data
    if (dwRetSamples < m_WFE.nSamplesPerSec)
    {
        DWORD dwRetBytes = dwRetSamples*m_WFE.nBlockAlign;
        memset(m_lpAudioBuf+dwRetBytes, 0, 
               m_WFE.nAvgBytesPerSec - dwRetBytes);

        //jacky_zz
        memset(m_lpSampleDataBuffer+dwRetBytes, 0, 
               m_WFE.nAvgBytesPerSec*2 - dwRetBytes);
        //jacky_zz
    }

    //Copy AudioBuffer to DirectSoundBuffer
    if (NULL == lpvAudio2)
    {
        memcpy(lpvAudio1, m_lpAudioBuf, dwBytesAudio1);

        //jacky_zz
        memcpy(m_lpSampleDataBuffer+dwOffset, 
               m_lpAudioBuf, dwBytesAudio1);
        //jacky_zz
    }
    else
    {
        memcpy(lpvAudio1, m_lpAudioBuf, dwBytesAudio1);
        memcpy(lpvAudio2, m_lpAudioBuf + dwBytesAudio1, dwBytesAudio2);

        //jacky_zz
        memcpy(m_lpSampleDataBuffer+dwOffset, m_lpAudioBuf, 
               dwBytesAudio1+dwBytesAudio2);
        //jacky_zz
    }

    ...
}

Calculate values of left and right, then use FFT to calculate it

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);

Use Win32 GDI API to draw the spectrum

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;

    // Create a green pen.
    hpen = CreatePen(PS_SOLID, 1, RGB(0, 255, 0));
    // Create a red brush.
    hbrush = CreateSolidBrush(RGB(0, 0, 0));
    hbrush1 = CreateSolidBrush(RGB(125, 125, 125));

    // Select the new pen and brush, and then draw.
    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;

    //CString xx;
    RECT r;
    for(int a=0, band=0; band < SPECTRUM_BANDS; 
            a+=(int)floatMultiplier, band++)
    {
        float wFs = 0;

        // -- Average out nearest bands.
        for (int b = 0; b < floatMultiplier; b++) {
            wFs += fftData[a + b];
        }

        // -- Log filter.
        wFs = (wFs * (float) log((float)(band + 2)));
        //xx.Format(_T(&quot;%1.4f\n&quot;), wFs);
        //OutputDebugString(xx);
        if (wFs > 1.0f) {
            wFs = 1.0f;
        }

        // -- Compute SA decay...
        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;
    }

    // Do not forget to clean up.
    SelectObject(hdc, hpenOld);
    DeleteObject(hpen);
    SelectObject(hdc, hbrushOld);
    DeleteObject(hbrush);
    SelectObject(hdc, hbrushOld1);
    DeleteObject(hbrush1);
    ReleaseDC(hwnd, hdc);

    Sleep(20);
}

The position that I get is not exactly a circle buffer

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?

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

jacky_zz
Software Developer none
China China
Member
To be, or not to be, this is question. That's are all depend on your decision. What do you think?

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
Questionabout the spectrum analysismemberZhao Weifeng27 Aug '12 - 21:55 
Hi Jacky,
I have download the demo, and find it's not correct to display the spectrum.
AnswerRe: about the spectrum analysismemberjacky_zz29 Aug '12 - 15:05 
this article could be fine.
Play Audio Files with DirectSound and Display its Spectrum in Real Time - Part 3[^]
GeneralDemo File is Infected by Virusmembersatmosc24 Feb '11 - 10:16 
Compiled Demo file is infected by a virus , detected by McAfee & norton 2011
Questionleft and right value ?memberliao gang13 Jan '11 - 21:02 
left 和 right 值为什么要这么求?
AnswerRe: left and right value ?memberliao gang16 Jan '11 - 20:17 
dwCurPlayPos分为4份.
left = (float)((lpAudioBuffer[dwCurPlayPos+1]<< 8) + lpAudioBuffer[dwCurPlayPos+0])/32767;
问题为什么1位置的数据要左移8呢?
GeneralRe: left and right value ?memberjacky_zz17 Jan '11 - 15:50 
every sample need 16 bytes(2 word) store.
AnswerRe: left and right value ?memberjacky_zz17 Jan '11 - 15:54 
because sample is 16 bit, so calculate left and right's sample need left shift.
GeneralError at compilationmembercatalin7716 Apr '09 - 9:51 
When trying to compile, I got this message:
fatal error C1083: Cannot open include file: 'afx.h': No such file or directory
How can I make it work?
Anyway, thank's for showing us your project, it's a really nice work.
GeneralRe: Error at compilationmemberjacky_zz16 Apr '09 - 14:10 
hi, catalin, this article is old version, and i can compile this project with VS2008, what IDE you use?
the latest version of this article is: Play Audio Files with DirectSound and Display its Spectrum in Real Time - Part 3[^], you can reference it, with pure Win32 code, enjoy.
GeneralRe: Error at compilationmembercatalin7720 Apr '09 - 9:36 
I can compile this new version, thank you.
Your work looks great and works great. You must have worked hard to study all the documentation necesary to accomplish this.

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web02 | 2.6.130523.1 | Last Updated 16 Sep 2008
Article Copyright 2008 by jacky_zz
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid