Click here to Skip to main content
Click here to Skip to main content
Go to top

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

, 15 Sep 2008
Rate this:
Please Sign up or sign in to vote.
An article to show how to play a Wave file with DirectSound and display its spectrum in real time.

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)

Share

About the Author

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

Comments and Discussions

 
Questionabout the spectrum analysis PinmemberZhao Weifeng27-Aug-12 21:55 
AnswerRe: about the spectrum analysis Pinmemberjacky_zz29-Aug-12 15:05 
GeneralDemo File is Infected by Virus Pinmembersatmosc24-Feb-11 10:16 
Questionleft and right value ? Pinmemberliao gang13-Jan-11 21:02 
AnswerRe: left and right value ? Pinmemberliao gang16-Jan-11 20:17 
GeneralRe: left and right value ? Pinmemberjacky_zz17-Jan-11 15:50 
AnswerRe: left and right value ? Pinmemberjacky_zz17-Jan-11 15:54 
GeneralError at compilation Pinmembercatalin7716-Apr-09 9:51 
GeneralRe: Error at compilation Pinmemberjacky_zz16-Apr-09 14:10 
GeneralRe: Error at compilation Pinmembercatalin7720-Apr-09 9:36 
GeneralRe: Error at compilation Pinmemberjacky_zz20-Apr-09 21:06 
GeneralMissing Files PinmemberBreezium22-Sep-08 3:16 
GeneralRe: Missing Files Pinmemberjacky_zz22-Sep-08 14:35 

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

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web01 | 2.8.140916.1 | Last Updated 16 Sep 2008
Article Copyright 2008 by jacky_zz
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid