Click here to Skip to main content
15,879,326 members
Articles / Desktop Programming / MFC
Article

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

Rate me:
Please Sign up or sign in to vote.
3.44/5 (8 votes)
15 Sep 2008CPOL2 min read 144.5K   8.4K   58   13
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)


Written By
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 Pin
Zhao Weifeng27-Aug-12 21:55
Zhao Weifeng27-Aug-12 21:55 
AnswerRe: about the spectrum analysis Pin
jackyxinli29-Aug-12 15:05
jackyxinli29-Aug-12 15:05 
GeneralDemo File is Infected by Virus Pin
satmosc24-Feb-11 10:16
satmosc24-Feb-11 10:16 
Questionleft and right value ? Pin
liao gang13-Jan-11 21:02
liao gang13-Jan-11 21:02 
AnswerRe: left and right value ? Pin
liao gang16-Jan-11 20:17
liao gang16-Jan-11 20:17 
GeneralRe: left and right value ? Pin
jackyxinli17-Jan-11 15:50
jackyxinli17-Jan-11 15:50 
AnswerRe: left and right value ? Pin
jackyxinli17-Jan-11 15:54
jackyxinli17-Jan-11 15:54 
GeneralError at compilation Pin
catalin7716-Apr-09 9:51
catalin7716-Apr-09 9:51 
GeneralRe: Error at compilation Pin
jackyxinli16-Apr-09 14:10
jackyxinli16-Apr-09 14:10 
GeneralRe: Error at compilation Pin
catalin7720-Apr-09 9:36
catalin7720-Apr-09 9:36 
GeneralRe: Error at compilation Pin
jackyxinli20-Apr-09 21:06
jackyxinli20-Apr-09 21:06 
GeneralMissing Files Pin
Breezium22-Sep-08 3:16
Breezium22-Sep-08 3:16 
GeneralRe: Missing Files Pin
jackyxinli22-Sep-08 14:35
jackyxinli22-Sep-08 14:35 

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

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