Click here to Skip to main content
15,894,017 members
Articles / Multimedia / DirectX

Interactive 3D Spectrum Analyzer Visualization for Windows Media Player

Rate me:
Please Sign up or sign in to vote.
5.00/5 (12 votes)
17 May 2009CPOL12 min read 165.3K   6.5K   41  
Interactive 3D Spectrum Analyzer for Windows Media Player using DirectX 9 and some light GPGPU.
///////////////////////////////////////////////////////////////////////////////
//  Copyright (c) 2009 Carlo McWhirter. All Rights Reserved.	
//  Copyright (c) 2009 Hyteq Systems. All Rights Reserved.	
//  
//  http://www.hyteq.com
//
//	Hyteq Systems Educational License
//
//  This file is part of WM3DSpectrum, also known as 3D Spectrum Analyzer for
//  Windows Media Player. This file, the project that this file is part of, and
//  the resulting compiled program files are intended to be used for educational
//  purposes only. Use of this file or this project for any non-educational or
//  non-observatory purpose is strictly prohibited without the express written 
//  consent of all of the copyright holders listed above.
//
//  This file may only be modified and later redistributed by one or more of the
//  copyright holders listed above. Suggestions for bug fixes, enhancements,
//  and other modifications must be sent directly to one of the copyright holders.
//  
//  This file may be modified without being redistributed by any recipient of 
//  this file provided the modifications are NOT intentionaly or unintentionaly 
//  directed toward malicious or illegal purposes, but, instead, toward educational
//  purposes only.
//
//	THIS SOFTWARE IS PROVIDED 'AS-IS', WITHOUT ANY EXPRESS OR IMPLIED
//  WARRANTY. IN NO EVENT WILL THE AUTHORS BE HELD LIABLE FOR ANY DAMAGES
//  ARISING FROM THE USE OF THIS SOFTWARE.
//
//  This notice may not be removed from this file or altered in any manner.
//
///////////////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "WM3DSpectrum.h"
#include "CPropertyDialog.h"

/////////////////////////////////////////////////////////////////////////////
// CWM3DSpectrum::CWM3DSpectrum
// Constructor

CWM3DSpectrum::CWM3DSpectrum() :
m_hwndParent(NULL),
m_clrForeground(0x0000FF),
m_nPreset(0)
{
    _tcsncpy_s(m_szPluginText, sizeof(m_szPluginText) / sizeof(m_szPluginText[0]), _T("WM3DSpectrum Plugin"), sizeof(m_szPluginText) / sizeof(m_szPluginText[0]));
    m_dwAdviseCookie = 0;
	memset(&m_RenderContext, 0, sizeof(RENDERCONTEXT));
	m_vecWindowedRenderables.push_back( &m_GridRenerer );
}

/////////////////////////////////////////////////////////////////////////////
// CWM3DSpectrum::~CWM3DSpectrum
// Destructor

CWM3DSpectrum::~CWM3DSpectrum()
{
	if(m_WindowedRenderer.IsInitialized())
		m_WindowedRenderer.Destroy( &m_RenderContext );

	if(m_NonWindowedRenderer.IsInitialized())
		m_NonWindowedRenderer.Destroy( &m_RenderContext );
}

/////////////////////////////////////////////////////////////////////////////
// CWM3DSpectrum:::FinalConstruct
// Called when an effect is first loaded. Use this function to do one-time
// intializations that could fail (i.e. creating offscreen buffers) instead
// of doing this in the constructor, which cannot return an error.

HRESULT CWM3DSpectrum::FinalConstruct()
{
    return S_OK;
}

/////////////////////////////////////////////////////////////////////////////
// CWM3DSpectrum:::FinalRelease
// Called when an effect is unloaded. Use this function to free any
// resources allocated in FinalConstruct.

void CWM3DSpectrum::FinalRelease()
{
    ReleaseCore();
}


//////////////////////////////////////////////////////////////////////////////
// CWM3DSpectrum::Render
// Called when an effect should render itself to the screen.
//////////////////////////////////////////////////////////////////////////////
STDMETHODIMP CWM3DSpectrum::Render(TimedLevel *pLevels, HDC hdc, RECT *prc)
{
	m_RenderContext.bIsWindowed = false;
	m_RenderContext.bHasNewData = true;
	m_RenderContext.hWnd = m_hwndParent;
	m_RenderContext.hDC = hdc;
	m_RenderContext.lpRect = prc;
	m_RenderContext.pLevel = pLevels;
	m_RenderContext.preset = m_nPreset;
	m_RenderContext.vecRenderables.assign(m_vecNonWindowedRenderables.begin(),
										  m_vecNonWindowedRenderables.end());

	switch (m_nPreset)
    {
    case PRESET_ROSE_GARDEN:
    case PRESET_CITY_LIGHTS:
    case PRESET_ROSE_GARDEN_POINTS:
    case PRESET_CITY_LIGHTS_POINTS:
		m_RenderContext.Interpolator = &m_LinearInterpolator;
		break;
    case PRESET_ROSE_GARDEN_SMOOTH:
    case PRESET_CITY_LIGHTS_SMOOTH:
	case PRESET_ROSE_GARDEN_POINTS_SMOOTH:
	case PRESET_CITY_LIGHTS_POINTS_SMOOTH:
	default:
		m_RenderContext.Interpolator = &m_SmoothInterpolator;
    }


	if(m_NonWindowedRenderer.IsInitialized() == false)
	{
		if(m_NonWindowedRenderer.InitializationFailed() == false)
		{
			m_NonWindowedRenderer.Intialize( &m_RenderContext );

			m_NonWindowedRenderer.Render( &m_RenderContext );
		}
	}
	else
	{
		m_NonWindowedRenderer.Render( &m_RenderContext );
	}

	return S_OK;
}

//////////////////////////////////////////////////////////////////////////////
// CWM3DSpectrum::MediaInfo
// Everytime new media is loaded, this method is called to pass the
// number of channels (mono/stereo), the sample rate of the media, and the
// title of the media
//////////////////////////////////////////////////////////////////////////////
STDMETHODIMP CWM3DSpectrum::MediaInfo(LONG lChannelCount, LONG lSampleRate, BSTR bstrTitle )
{
    return S_OK;
}


//////////////////////////////////////////////////////////////////////////////
// CWM3DSpectrum::GetCapabilities
// Returns the capabilities of this effect. Flags that can be returned are:
//	EFFECT_CANGOFULLSCREEN		-- effect supports full-screen rendering
//	EFFECT_HASPROPERTYPAGE		-- effect supports a property page
//////////////////////////////////////////////////////////////////////////////
STDMETHODIMP CWM3DSpectrum::GetCapabilities(DWORD * pdwCapabilities)
{
    if (NULL == pdwCapabilities)
    {
        return E_POINTER;
    }

    *pdwCapabilities = EFFECT_HASPROPERTYPAGE;
    return S_OK;
}

//////////////////////////////////////////////////////////////////////////////
// CWM3DSpectrum::DisplayPropertyPage
// Invoked when a host wants to display the property page for the effect
//////////////////////////////////////////////////////////////////////////////
STDMETHODIMP CWM3DSpectrum::DisplayPropertyPage(HWND hwndOwner)
{
    CPropertyDialog dialog(this);

    dialog.DoModal(hwndOwner);

    return S_OK;
}

//////////////////////////////////////////////////////////////////////////////
// CWM3DSpectrum::GetTitle
// Invoked when a host wants to obtain the title of the effect
//////////////////////////////////////////////////////////////////////////////
STDMETHODIMP CWM3DSpectrum::GetTitle(BSTR* bstrTitle)
{
    USES_CONVERSION;

    if (NULL == bstrTitle)
    {
        return E_POINTER;
    }

    CComBSTR bstrTemp;
    bstrTemp.LoadString(IDS_EFFECTNAME); 
        
    if ((!bstrTemp) || (0 == bstrTemp.Length()))
    {
        return E_FAIL;
    }

    *bstrTitle = bstrTemp.Detach();

    return S_OK;
}

//////////////////////////////////////////////////////////////////////////////
// CWM3DSpectrum::GetPresetTitle
// Invoked when a host wants to obtain the title of the given preset
//////////////////////////////////////////////////////////////////////////////
STDMETHODIMP CWM3DSpectrum::GetPresetTitle(LONG nPreset, BSTR *bstrPresetTitle)
{
    USES_CONVERSION;

    if (NULL == bstrPresetTitle)
    {
        return E_POINTER;
    }

    if ((nPreset < 0) || (nPreset >= PRESET_COUNT))
    {
        return E_INVALIDARG;
    }

    CComBSTR bstrTemp;
    
    switch (nPreset)
    {
    case PRESET_ROSE_GARDEN:
        bstrTemp.LoadString(IDS_ROSEGARDEN); 
        break;
    case PRESET_CITY_LIGHTS:
        bstrTemp.LoadString(IDS_CITYLIGHTS); 
        break;
    case PRESET_ROSE_GARDEN_POINTS:
        bstrTemp.LoadString(IDS_ROSEGARDENPOINTS); 
        break;
    case PRESET_CITY_LIGHTS_POINTS:
        bstrTemp.LoadString(IDS_CITYLIGHTSPOINTS); 
        break;
    case PRESET_ROSE_GARDEN_SMOOTH:
        bstrTemp.LoadString(IDS_ROSEGARDEN_SMOOTH); 
        break;
    case PRESET_CITY_LIGHTS_SMOOTH:
        bstrTemp.LoadString(IDS_CITYLIGHTS_SMOOTH); 
        break;
	case PRESET_ROSE_GARDEN_POINTS_SMOOTH:
        bstrTemp.LoadString(IDS_ROSEGARDENPOINTS_SMOOTH); 
        break;
	case PRESET_CITY_LIGHTS_POINTS_SMOOTH:
        bstrTemp.LoadString(IDS_CITYLIGHTSPOINTS_SMOOTH); 
        break;
    }
    
    if ((!bstrTemp) || (0 == bstrTemp.Length()))
    {
        return E_FAIL;
    }

    *bstrPresetTitle = bstrTemp.Detach();

    return S_OK;
}

//////////////////////////////////////////////////////////////////////////////
// CWM3DSpectrum::GetPresetCount
// Invoked when a host wants to obtain the number of supported presets
//////////////////////////////////////////////////////////////////////////////
STDMETHODIMP CWM3DSpectrum::GetPresetCount(LONG *pnPresetCount)
{
    if (NULL == pnPresetCount)
    {
        return E_POINTER;
    }

    *pnPresetCount = PRESET_COUNT;

    return S_OK;
}

//////////////////////////////////////////////////////////////////////////////
// CWM3DSpectrum::SetCurrentPreset
// Invoked when a host wants to change the index of the current preset
//////////////////////////////////////////////////////////////////////////////
STDMETHODIMP CWM3DSpectrum::SetCurrentPreset(LONG nPreset)
{
    if ((nPreset < 0) || (nPreset >= PRESET_COUNT))
    {
        return E_INVALIDARG;
    }

    m_nPreset = nPreset;

    return S_OK;
}

//////////////////////////////////////////////////////////////////////////////
// CWM3DSpectrum::GetCurrentPreset
// Invoked when a host wants to obtain the index of the current preset
//////////////////////////////////////////////////////////////////////////////
STDMETHODIMP CWM3DSpectrum::GetCurrentPreset(LONG *pnPreset)
{
    if (NULL == pnPreset)
    {
        return E_POINTER;
    }

    *pnPreset = m_nPreset;

    return S_OK;
}

//////////////////////////////////////////////////////////////////////////////
// CWM3DSpectrum::SetCore
// Set WMP core interface
//////////////////////////////////////////////////////////////////////////////
STDMETHODIMP CWM3DSpectrum::SetCore(IWMPCore * pCore)
{
    HRESULT hr = S_OK;

    // release any existing WMP core interfaces
    ReleaseCore();

    // If we get passed a NULL core, this  means
    // that the plugin is being shutdown.

    if (pCore == NULL)
    {
        return S_OK;
    }

    m_spCore = pCore;

    // connect up the event interface
    CComPtr<IConnectionPointContainer>  spConnectionContainer;

    hr = m_spCore->QueryInterface( &spConnectionContainer );

    if (SUCCEEDED(hr))
    {
        hr = spConnectionContainer->FindConnectionPoint( __uuidof(IWMPEvents), &m_spConnectionPoint );
    }

    if (SUCCEEDED(hr))
    {
        hr = m_spConnectionPoint->Advise( GetUnknown(), &m_dwAdviseCookie );

        if ((FAILED(hr)) || (0 == m_dwAdviseCookie))
        {
            m_spConnectionPoint = NULL;
        }
    }

    return hr;
}

//////////////////////////////////////////////////////////////////////////////
// CWM3DSpectrum::Create
// Invoked when the visualization should be initialized.
//
// If hwndParent != NULL, RenderWindowed() will be called and the visualization
// should draw into the window specified by hwndParent. This will be the
// behavior when the visualization is hosted in a window.
//
// If hwndParent == NULL, Render() will be called and the visualization
// should draw into the DC passed to Render(). This will be the behavior when
// the visualization is hosted windowless (like in a skin for example).
//////////////////////////////////////////////////////////////////////////////
STDMETHODIMP CWM3DSpectrum::Create(HWND hwndParent)
{
    m_hwndParent = hwndParent;

    return S_OK;
}

//////////////////////////////////////////////////////////////////////////////
// CWM3DSpectrum::Destroy
// Invoked when the visualization should be released.
//
// Any resources allocated for rendering should be released.
//////////////////////////////////////////////////////////////////////////////
STDMETHODIMP CWM3DSpectrum::Destroy()
{
    m_hwndParent = NULL;

    return S_OK;
}

//////////////////////////////////////////////////////////////////////////////
// CWM3DSpectrum::NotifyNewMedia
// Invoked when a new media stream begins playing.
//
// The visualization can inspect this object for properties (like name or artist)
// that might be interesting for visualization.
//////////////////////////////////////////////////////////////////////////////
STDMETHODIMP CWM3DSpectrum::NotifyNewMedia(IWMPMedia *pMedia)
{
    return S_OK;
}

//////////////////////////////////////////////////////////////////////////////
// CWM3DSpectrum::OnWindowMessage
// Window messages sent to the parent window.
//////////////////////////////////////////////////////////////////////////////
STDMETHODIMP CWM3DSpectrum::OnWindowMessage(UINT msg, WPARAM WParam, LPARAM LParam, LRESULT *plResultParam )
{
    // return S_OK only if the plugin has handled the window message
    // return S_FALSE to let the defWindowProc handle the message

	if(m_hwndParent == NULL)
		m_NonWindowedRenderer.OnWindowMessage( &m_RenderContext, msg, WParam, LParam, plResultParam );
	else
		m_WindowedRenderer.OnWindowMessage( &m_RenderContext, msg, WParam, LParam, plResultParam );

    return S_FALSE;
}

//////////////////////////////////////////////////////////////////////////////
// CWM3DSpectrum::RenderWindowed
// Called when an effect should render itself to the screen.
//
// The fRequiredRender flag specifies if an update is required, otherwise the
// update is optional. This allows visualizations that are fairly static (for example,
// album art visualizations) to only render when the parent window requires it,
// instead of n times a second for dynamic visualizations.
//////////////////////////////////////////////////////////////////////////////
STDMETHODIMP CWM3DSpectrum::RenderWindowed(TimedLevel *pLevels, BOOL fRequiredRender )
{
    // NULL parent window should not happen 
    if (NULL == m_hwndParent)
    {
        return E_UNEXPECTED;
    }

    RECT rParent = { 0 };
    ::GetClientRect(m_hwndParent, &rParent);

	m_RenderContext.bIsWindowed = true;
	m_RenderContext.bHasNewData = true;
	m_RenderContext.hWnd = m_hwndParent;
	m_RenderContext.lpRect = &rParent;
	m_RenderContext.pLevel = pLevels;
	m_RenderContext.preset = m_nPreset;
	m_RenderContext.vecRenderables.assign(m_vecWindowedRenderables.begin(),
										  m_vecWindowedRenderables.end());

	switch (m_nPreset)
    {
    case PRESET_ROSE_GARDEN:
    case PRESET_CITY_LIGHTS:
    case PRESET_ROSE_GARDEN_POINTS:
    case PRESET_CITY_LIGHTS_POINTS:
		m_RenderContext.Interpolator = &m_LinearInterpolator;
		break;
    case PRESET_ROSE_GARDEN_SMOOTH:
    case PRESET_CITY_LIGHTS_SMOOTH:
	case PRESET_ROSE_GARDEN_POINTS_SMOOTH:
	case PRESET_CITY_LIGHTS_POINTS_SMOOTH:
	default:
		m_RenderContext.Interpolator = &m_SmoothInterpolator;
    }

	if(m_WindowedRenderer.IsInitialized() == false)
	{
		if(m_WindowedRenderer.InitializationFailed() == false)
		{
			m_WindowedRenderer.Intialize( &m_RenderContext );

			m_WindowedRenderer.Render( &m_RenderContext );
		}
	}
	else
	{
		m_WindowedRenderer.Render( &m_RenderContext );
	}

    return S_OK;
}

/////////////////////////////////////////////////////////////////////////////
// CWM3DSpectrum::ReleaseCore
// Release WMP core interfaces
//////////////////////////////////////////////////////////////////////////////
void CWM3DSpectrum::ReleaseCore()
{
    if (m_spConnectionPoint)
    {
        if (0 != m_dwAdviseCookie)
        {
            m_spConnectionPoint->Unadvise(m_dwAdviseCookie);
            m_dwAdviseCookie = 0;
        }
        m_spConnectionPoint = NULL;
    }

    if (m_spCore)
    {
        m_spCore = NULL;
    }
}

//////////////////////////////////////////////////////////////////////////////
// CWM3DSpectrum::get_foregroundColor
// Property get to retrieve the foregroundColor prop via the public interface.
//////////////////////////////////////////////////////////////////////////////
STDMETHODIMP CWM3DSpectrum::get_foregroundColor(BSTR *pVal)
{
	return ColorToWz( pVal, m_clrForeground);
}


//////////////////////////////////////////////////////////////////////////////
// CWM3DSpectrum::put_foregroundColor
// Property put to set the foregroundColor prop via the public interface.
//////////////////////////////////////////////////////////////////////////////
STDMETHODIMP CWM3DSpectrum::put_foregroundColor(BSTR newVal)
{
	return WzToColor(newVal, &m_clrForeground);
}


//////////////////////////////////////////////////////////////////////////////
// CWM3DSpectrum::WzToColor
// Helper function used to convert a string into a COLORREF.
//////////////////////////////////////////////////////////////////////////////
HRESULT CWM3DSpectrum::WzToColor(const WCHAR *pwszColor, COLORREF *pcrColor)
{ 
    if (NULL == pwszColor)
    {
        //NULL color string passed in
        return E_POINTER;
    }

    if (0 == lstrlenW(pwszColor))
    {
        //Empty color string passed in
        return E_INVALIDARG;
    }

    if (NULL == pcrColor)
    {
        //NULL output color DWORD passed in
        return E_POINTER;
    }
    
    if (lstrlenW(pwszColor) != 7)
    {
        //hex color string is not of the correct length
        return E_INVALIDARG;
    }

    DWORD dwRet = 0;
    for (int i = 1; i < 7; i++)
    {
        // shift dwRet by 4
        dwRet <<= 4;
        // and add in the value of this string

        if ((pwszColor[i] >= L'0') && (pwszColor[i] <= L'9'))
        {
            dwRet += pwszColor[i] - '0';
        }
        else if ((pwszColor[i] >= L'A') && (pwszColor[i] <= L'F'))
        {
            dwRet += 10 + (pwszColor[i] - L'A');
        }
        else if ((pwszColor[i] >= L'a') && (pwszColor[i] <= L'f'))
        {
            dwRet += 10 + (pwszColor[i] - L'a');
        }
        else
        {
           //Invalid hex digit in color string
            return E_INVALIDARG;
        }
    }

    *pcrColor = SwapBytes(dwRet);

    return S_OK;
} 


//////////////////////////////////////////////////////////////////////////////
// CWM3DSpectrum::ColorToWz
// Helper function used to convert a COLORREF to a BSTR.
//////////////////////////////////////////////////////////////////////////////
HRESULT CWM3DSpectrum::ColorToWz( BSTR* pbstrColor, COLORREF crColor)
{
    _ASSERT( NULL != pbstrColor );
    _ASSERT( (crColor & 0x00FFFFFF) == crColor );

    *pbstrColor = NULL;

    WCHAR wsz[8];
    HRESULT hr  = S_OK;

    swprintf_s( wsz, sizeof(wsz)/sizeof(wsz[0]), L"#%06X", SwapBytes(crColor) );
    
    *pbstrColor = ::SysAllocString( wsz );

    if (!pbstrColor)
    {
        hr = E_FAIL;
    }

    return hr;
}


//////////////////////////////////////////////////////////////////////////////
// CWM3DSpectrum::SwapBytes
// Used to convert between a DWORD and COLORREF.  Simply swaps the lowest 
// and 3rd order bytes.
//////////////////////////////////////////////////////////////////////////////
inline DWORD CWM3DSpectrum::SwapBytes(DWORD dwRet)
{
    return ((dwRet & 0x0000FF00) | ((dwRet & 0x00FF0000) >> 16) | ((dwRet & 0x000000FF) << 16));
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for 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 (Senior)
United States United States
I'm a Microsoft Certified Professional (MCP) in C++. I'm fluent in C/C++, C# and many other languages.

Comments and Discussions