Entries:
Comments:
Posts:

Loading User Information from Channel 9

Something went wrong getting user information from Channel 9

Latest Achievement:

Loading User Information from MSDN

Something went wrong getting user information from MSDN

Visual Studio Achievements

Latest Achievement:

Loading Visual Studio Achievements

Something went wrong getting the Visual Studio Achievements

Winter Visualization for Windows Media Player in C++

small Ever wondered what's behind a visualization? This is your opportunity to learn something new because there are only a couple of Windows Media Player Visualizations that deliver source code. You'll learn to build a new visualization with Visual C++ 2005 Express (you won't even require a Standard Edition)! Winter is here... Snow is here... we'll render Koch Snowflake Fractals that depend on the waveform of the playing song with the Windows Graphics Device Interface. This article will also change the way you ever looked at a visualization. Enjoy!

Paul-Valentin Borza - http://www.borza.ro

Difficulty: Intermediate
Cost: Free
Time required: 1-3 hours
Software: Visual C++ 2005 Express, Windows Server 2003 R2 Platform SDK, Windows Software Development Kit for Windows Vista
Hardware: None
Download Source: Winter Visualization Source Code

How do we build a visualization?

Please install (don't change the default installation directory):

We need to make Visual C++ 2005 Express work with visualizations:

  1. Navigate to 'C:\Program Files\Microsoft SDKs\Windows\v6.0\Samples\Multimedia\WMP_11\Wizards\VSNET';
  2. Copy 'wmpwiz2005.vsz', 'wmpwiz.vsdir' and 'wmpwiz.ico';
  3. Navigate to 'C:\Program Files\Microsoft Visual Studio 8\VC\Express\VCProjects';
  4. Paste 'wmpwiz2005.vsz', 'wmpwiz.vsdir' and 'wmpwiz.ico';
  5. Rename 'wmpwiz2005.vsz' to 'wmpwiz.vsz' (you're in 'C:\Program Files\Microsoft Visual Studio 8\VC\Express\VCProjects');
  6. Edit 'wmpwiz.vsz' using Notepad and change the line 'Param="ABSOLUTE_PATH = <path to wmpwiz directory goes here>"' with 'Param="ABSOLUTE_PATH = C:\Program Files\Microsoft SDKs\Windows\v6.0\Samples\Multimedia\WMP_11\Wizards\VSNET"';
  7. The Windows Media Player Plug-in Wizard is now installed. Launch Visual C++ 2005 Express.
    1. Click on 'File' > 'New' > 'Project...';
    2. In 'Project types:' select 'Visual C++';
    3. In 'Templates:' select 'Windows Media Player Plug-in Wizard';
    4. Change 'Name:' from 'wmpplugin1' to 'MyWinterVisualization' and click 'OK';
    5. The Windows Media Player Plug-in Wizard appears;
    6. On the 'Welcome to the Windows Media Player Plug-in Wizard' select 'Visualization' and click 'Next';
    7. On the 'Visualization Plug-in' change 'Friendly Name:' from 'MyWinterVisualization Plugin' to 'Coding4Fun Winter Visualization' and 'Description:' from 'Description of MyWinterVisualization plugin' to 'Description of Coding4Fun Winter Visualization'; don't check 'Property Page' or 'Listen to events';
    8. Click 'Next'. The wizard will create a new visualization for you; you're almost done!

You'll notice that the created project won't compile because the 'atlbase.h', 'wmpplug.h', 'effects.h' and 'winres.h' can't be opened; to overcome:

  1. Click on 'Project' > 'Properties';
  2. Expand 'Configuration Properties' > 'C/C++' > 'General';
  3. In 'Additional Include Directories' add '"C:\Program Files\Microsoft SDKs\Windows\v6.0\Include"' and click 'OK';
  4. Expand 'Configuration Properties' > 'Linker' > 'Input';
  5. In 'Additional Dependencies' add 'msimg32.lib' (we'll need this to give smoothness to the visualization);
  6. Click on 'Tools' > 'Options...';
  7. Expand 'Projects and Solutions' > 'VC++ Directories';
  8. In 'Platform:' select 'Win32' and in 'Show directories for:' select 'Include files';
  9. Add '"C:\Program Files\Microsoft Platform SDK for Windows Server 2003 R2\Include\atl"' and '"C:\Program Files\Microsoft Platform SDK for Windows Server 2003 R2\Include\mfc"';

One last trick... Edit 'C:\Program Files\Microsoft Platform SDK for Windows Server 2003 R2\Include\atl\atlbase.h' using Notepad. Find the following text (begins on line 287):

  287 PVOID __stdcall __AllocStdCallThunk(VOID);

  288 VOID  __stdcall __FreeStdCallThunk(PVOID);

  289 

  290 #define AllocStdCallThunk() __AllocStdCallThunk()

  291 #define FreeStdCallThunk(p) __FreeStdCallThunk(p)

  292 

  293 #pragma comment(lib, "atlthunk.lib")

Comment line 287 to 293 and add 'AllocStdCallThunk' and 'FreeStdCallThunk' to look like:

  287 /*

  288 PVOID __stdcall __AllocStdCallThunk(VOID);

  289 VOID  __stdcall __FreeStdCallThunk(PVOID);

  290 

  291 #define AllocStdCallThunk() __AllocStdCallThunk()

  292 #define FreeStdCallThunk(p) __FreeStdCallThunk(p)

  293 

  294 #pragma comment(lib, "atlthunk.lib")

  295 */

  296 

  297 #define AllocStdCallThunk() HeapAlloc(GetProcessHeap(), 0, sizeof(_stdcallthunk))

  298 #define FreeStdCallThunk(p) HeapFree(GetProcessHeap(), 0, p)

You should now be able to compile a visualization. Compile your visualization and launch Windows Media Player.

  1. Play a song; you can try My Fair Lady by David Byrne over ccMixter (this is the melody from the video preview);
  2. Click on 'Now Playing';
  3. Right-click inside Windows Media Player and click 'Coding4Fun Winter Visualization' > 'Coding4Fun Winter Visualization Bars' or 'Coding4Fun Winter Visualization Wave';
  4. Please take a good look at the generated visualization. Do you see the flicker (the flashing effect that you wouldn't want to see)? Don't worry, we'll make the visualization flicker-free in a few minutes.

How do we debug a visualization?

We know how to build and try a visualization; however, how do we debug one? Simple - we attach to the 'wmplayer.exe' process.

  1. Click on 'Project' > 'Properties';
  2. Expand 'Configuration Properties' > 'Debugging';
  3. In 'Command' enter 'C:\Program Files\Windows Media Player\wmplayer.exe';
  4. Change 'Attach' from 'No' to 'Yes';
  5. Click 'OK';
  6. Open 'MyWinterVisualization.cpp' and locate 'CMyWinterVisualization::Render';
  7. Add a breakpoint at line '60' in 'MyWinterVisualization.cpp';
  8. Build Solution and launch Windows Media Player;
  9. Start Debugging and select your visualization in Windows Media Player; the visualization should break in Visual Studio.

The Winter Visualization

From now on, we'll take a look at the source code for the Winter Visualization. A visualization can have one or more presets (Bars and Wave for example). I've decided to go with two presents for the Winter Visualization and named them: Snow Flakes (draws blue snowflakes) and Snow Flames (draws red snowflakes).

Configuration

Open 'WinterVisualization.h' and check the configuration values:

   18 #define EQ_ANGLE                -1.0471975511965977461542144610932f    // - PI / 3

   19 #define SNOW_MIN_SIZE            23    // in pixels

   20 #define SNOW_MAX_SIZE            337    // in pixels

   21 #define SNOW_STEP_SIZE            4    // in pixels

   22 #define SNOW_COUNT                13    // number of snowflakes

   23 #define SNOW_FLAKE_COLOR_COUNT    5    // number of blue colors

   24 #define SNOW_FLAME_COLOR_COUNT    5    // number of red colors

   25 #define SNOW_ALPHA                237    // alpha value

   26 #define KOCH_IT                    5    // number of iterations for Koch

Don't modify EQ_ANGLE as it's needed to make equilateral triangles for the Koch Snowflake Fractal. If you change SNOW_FLAKE_COLOR_COUNT or SNOW_FLAME_COLOR_COUNT, modify the appropriate lines in CWinterVisualization::FinalConstruct. The visualization involves a lot of computations and changing any configuration value could potentially slow it down. However, you're more than welcome to try anything... For example, increasing KOCH_IT to 6 will make the visualization run slower, but the snowflakes will have a more defined border. Try playing with SNOW_ALPHA (value should be between 0 and 255).

Initialization

Open 'WinterVisualization.cpp':

   37 HRESULT CWinterVisualization::FinalConstruct()

   38 {

   39     tsPrev            = 0;    // previous time stamp

   40     rcPrev.left        = 0;    // previous drawing surface

   41     rcPrev.top        = 0;    // previous drawing surface

   42     rcPrev.right    = 0;    // previous drawing surface

   43     rcPrev.bottom    = 0;    // previous drawing surface

   44 

   45     hdcPrev            = 0;    // previous handler to device context

   46     hNewBitmapPrev    = 0;    // previous drawing bitmap

   47 

   48     hdcMem            = 0;    // memory handler to device context

   49     hNewBitmapMem    = 0;    // memory drawing bitmap

   50 

   51     /* Create Elapsed Font */

   52     LOGFONT lfElapsed;

   53     lfElapsed.lfHeight            = 23;

   54     lfElapsed.lfWidth            = 0;

   55     lfElapsed.lfEscapement        = 0;

   56     lfElapsed.lfOrientation        = 0;

   57     lfElapsed.lfWeight            = FW_BOLD;

   58     lfElapsed.lfItalic            = FALSE;

   59     lfElapsed.lfUnderline        = FALSE;

   60     lfElapsed.lfStrikeOut        = FALSE;

   61     lfElapsed.lfCharSet            = DEFAULT_CHARSET;

   62     lfElapsed.lfOutPrecision    = OUT_DEFAULT_PRECIS;

   63     lfElapsed.lfClipPrecision    = CLIP_DEFAULT_PRECIS;

   64     lfElapsed.lfQuality            = ANTIALIASED_QUALITY;

   65     lfElapsed.lfPitchAndFamily    = FF_MODERN;

   66     _tcscpy_s(lfElapsed.lfFaceName, TEXT("\0"));

   67     hfElapsed = CreateFontIndirect(&lfElapsed);

   68 

   69     /* Create Plus Font */

   70     LOGFONT lfPlus;

   71     lfPlus.lfHeight            = 9;

   72     lfPlus.lfWidth            = 0;

   73     lfPlus.lfEscapement        = 0;

   74     lfPlus.lfOrientation    = 0;

   75     lfPlus.lfWeight            = FW_BOLD;

   76     lfPlus.lfItalic            = FALSE;

   77     lfPlus.lfUnderline        = FALSE;

   78     lfPlus.lfStrikeOut        = FALSE;

   79     lfPlus.lfCharSet        = DEFAULT_CHARSET;

   80     lfPlus.lfOutPrecision    = OUT_DEFAULT_PRECIS;

   81     lfPlus.lfClipPrecision    = CLIP_DEFAULT_PRECIS;

   82     lfPlus.lfQuality        = ANTIALIASED_QUALITY;

   83     lfPlus.lfPitchAndFamily    = FF_MODERN;

   84     _tcscpy_s(lfPlus.lfFaceName, TEXT("\0"));

   85     hfPlus = CreateFontIndirect(&lfPlus);

   86 

   87     /* Flake Colors */

   88     hpSnowColor[0] = CreatePen(PS_SOLID, 1, RGB(99, 184, 255));    // #63B8FF

   89     hpSnowColor[1] = CreatePen(PS_SOLID, 1, RGB(92, 172, 238));    // #5CACEE

   90     hpSnowColor[2] = CreatePen(PS_SOLID, 1, RGB(30, 144, 255));    // #1E90FF

   91     hpSnowColor[3] = CreatePen(PS_SOLID, 1, RGB(28, 134, 238));    // #1C86EE

   92     hpSnowColor[4] = CreatePen(PS_SOLID, 1, RGB(16, 78, 139));    // #104E8B

   93     /* Flame Colors */

   94     hpSnowColor[5] = CreatePen(PS_SOLID, 1, RGB(220, 20, 60));    // #DC143C

   95     hpSnowColor[6] = CreatePen(PS_SOLID, 1, RGB(255, 48, 48));    // #FF3030

   96     hpSnowColor[7] = CreatePen(PS_SOLID, 1, RGB(238, 44, 44));    // #EE2C2C

   97     hpSnowColor[8] = CreatePen(PS_SOLID, 1, RGB(205, 38, 38));    // #CD2626

   98     hpSnowColor[9] = CreatePen(PS_SOLID, 1, RGB(139, 26, 26));    // #8B1A1A

   99 

  100     hBrushBack = CreateSolidBrush(RGB(0, 0, 0));

  101 

  102     iKochCount = (SNOW_MAX_SIZE - SNOW_MIN_SIZE) / SNOW_STEP_SIZE;

  103     CreateSnow(); // populate pKoch

  104 

  105     // Make snowflakes re-initialize in DrawSnow

  106     for (INT i = 0; i < SNOW_COUNT; ++i)

  107         iSnowSize[i] = iKochCount - 1;

  108 

  109     return S_OK;

  110 }

The surface size, the device contexts, the fonts, the pens and the brushes are initialized (the variables are declared in 'WinterVisualization.h'). Notice that I'm also precomputing the Koch Snowflake Fractal because it doesn't make sense to compute it each time a snowflake is drawn; see CWinterVisualization::CreateSnow for more details.
Don't forget that we're in the native C++ world and we have to release memory! The article won't cover the memory deallocation, but you can find it inside CWinterVisualization::FinalRelease.

Rendering the Elapsed Time

CWinterVisualization::DrawText renders the elapsed time in the top-right corner; use this method when you want to see how frequent your CWinterVisualization::Render gets called by Windows Media Player.

  414 void CWinterVisualization::DrawText(TimedLevel * pLevels, HDC hdcMem, RECT rcMem)

  415 {

  416     // don't display elapsed text when screen is too small

  417     if (rcMem.bottom < 240)

  418         return;

  419 

  420     HFONT hfOld = static_cast<HFONT>(SelectObject(hdcMem, hfElapsed));

  421 

  422     SetBkMode(hdcMem, TRANSPARENT);

  423     SetTextAlign(hdcMem, TA_RIGHT | TA_TOP);

  424     SetTextColor(hdcMem, RGB(255, 255, 255));

  425 

  426     /* Convert time stamp to string */

  427     TCHAR tcElapsed[32];

  428     if (_i64tot_s(pLevels->timeStamp, tcElapsed, _countof(tcElapsed), 10) == 0)

  429         TextOut(hdcMem, rcMem.right, rcMem.top, tcElapsed, static_cast<int>(_tcslen(tcElapsed)));

  430 

  431     SelectObject(hdcMem, hfOld);

  432 }

pLevels->timeStamp is measured in 100 nanoseconds and indicates the elapsed time inside the playing song. Be aware that the 'SelectObject' method returns the previous/old selected object in the device context; you're advised to select the old object back in the device context after you've finished working with the new one.

Rendering the Wave

If you watched the video preview, you've seen that there is a wave in the middle of the screen. The CWinterVisualization::DrawPlus method draws plusses ('+') to build the wave (drawing plusses instead of connected lines gives a better effect).

  382 void CWinterVisualization::DrawPlus(TimedLevel * pLevels, HDC hdcMem, RECT rcMem)

  383 {

  384     // don't display plus line when screen is too small

  385     if (rcMem.right < 240)

  386         return;

  387 

  388     HFONT hfOld = static_cast<HFONT>(SelectObject(hdcMem, hfPlus));

  389 

  390     SetBkMode(hdcMem, TRANSPARENT);

  391     SetTextAlign(hdcMem, TA_RIGHT | TA_TOP);

  392     switch (m_nPreset)

  393     {

  394     case PRESET_FLAKE:

  395         SetTextColor(hdcMem, RGB(176, 226, 255));

  396         break;

  397     case PRESET_FLAME:

  398         SetTextColor(hdcMem, RGB(255, 64, 64));

  399         break;

  400     }

  401 

  402     TCHAR tcPlus[] = TEXT("+");

  403     INT iPlusLen = static_cast<INT>(_tcslen(tcPlus));

  404     for (INT i = 0; i < SA_BUFFER_SIZE; ++i)

  405     {

  406         INT x = static_cast<INT>(rcMem.right * i / 1024.0f);

  407         INT y = static_cast<INT>(rcMem.bottom * pLevels->waveform[0][i] / 256.0f);

  408         TextOut(hdcMem, x, y, tcPlus, iPlusLen);

  409     }

  410 

  411     SelectObject(hdcMem, hfOld);

  412 }

This method depends on the currently selected preset and changes the color of the wave: blue for Flakes and red for Flames. We'll be rendering only the first channel (mono) waveform pLevels->waveform[0]. If you decide to use both channels (stereo), you'll have to use pLevels->waveform[0] and pLevels->waveform[1]; if the song is mono, then the second array is undefined (take care)! The method scales the values to the canvas size and draws the linked plusses.

Creating a Koch Snowflake

CWinterVisualization::CreateSnow computes the Koch Snowflake as described on the MathWorld Web Site. We construct an equilateral triangle with 3 vertices and start iterating: at the first iteration we have a polygon (which starts to resemble with a snowflake) with 12 vertices; at the second iteration we have a better snowflake/polygon with 48 vertices... at the fifth iteration we have a snowflake with 3072 vertices.

  272 void CWinterVisualization::CreateSnow()

  273 {

  274     /* Create Koch */

  275     pKoch = new POINT * [iKochCount];

  276 

  277     /* Frames */

  278     for (INT w = 0; w < iKochCount; ++w)

  279     {

  280         /* Initialize Koch Fractal Snowflake with the equilateral triangle */

  281         INT        iVertexNowCount    = 3;

  282         POINT * pVertexNow        = new POINT[iVertexNowCount];

  283 

  284         INT iSize = (SNOW_MIN_SIZE + w * SNOW_STEP_SIZE) / 2;

  285         pVertexNow[0].x = - iSize;        pVertexNow[0].y = 0;            // A

  286         pVertexNow[1].x = + iSize;        pVertexNow[1].y = 23;            // B

  287         pVertexNow[2] = MakeEqTriangle(pVertexNow[0], pVertexNow[1]);    // C

  288 

  289         /* http://mathworld.wolfram.com/KochSnowflake.html */

  290         for (INT q = 1; q < KOCH_IT + 1; ++q)

  291         {

  292             INT        iVertexNewCount = iVertexNowCount * 4;

  293             POINT * pVertexNew        = new POINT[iVertexNewCount];

  294 

  295             for (INT i = 0; i < iVertexNowCount; ++i)

  296             {

  297                 INT j = (i + 1) % iVertexNowCount;

  298 

  299                 POINT pA = pVertexNow[i];

  300                 POINT pE = pVertexNow[j];

  301 

  302                 LONG lWidth        = pE.x - pA.x;

  303                 LONG lHeight    = pE.y - pA.y;

  304 

  305                 POINT pB;

  306                 pB.x = pA.x + lWidth / 3;

  307                 pB.y = pA.y + lHeight / 3;

  308                 POINT pD;

  309                 pD.x = pA.x + lWidth * 2 / 3;

  310                 pD.y = pA.y + lHeight * 2 / 3;

  311                 POINT pC = MakeEqTriangle(pD, pB); // equilateral triangle for the inner third

  312 

  313                 INT k = i * 4;

  314                 pVertexNew[k + 0] = pA;

  315                 pVertexNew[k + 1] = pB;

  316                 pVertexNew[k + 2] = pC;

  317                 pVertexNew[k + 3] = pD;

  318             }

  319             // move to the next Koch iteration

  320             delete[] pVertexNow;

  321             pVertexNow        = pVertexNew;

  322             iVertexNowCount    = iVertexNewCount;

  323         }

  324         /* Done */

  325         pKoch[w] = pVertexNow;

  326     }

  327 }

Rendering the Snowflakes

There are 13 (this is the default value) snowflakes that need to be rendered on the device context. When a snowflake reaches its maximum size, it gets reinitialized: its size depends on the waveform, its color depends on the preset and it's randomly distributed across the surface. Each snowflake is translated and rotated to its target position (this is required because of the precomputation where we considered the snowflake to be around x=0 and y=0).

  329 void CWinterVisualization::DrawSnow(TimedLevel * pLevels, HDC hdcMem, RECT rcMem)

  330 {

  331     for (INT i = 0; i < SNOW_COUNT; ++i)

  332     {

  333         /* Initialize snowflake when reached SNOW_MAX_SIZE */

  334         if (++iSnowSize[i] == iKochCount)

  335         {

  336             // size depends on waveform

  337             iSnowSize[i] = iKochCount * pLevels->waveform[0][SA_BUFFER_SIZE * i / SNOW_COUNT] / 256;

  338             switch (m_nPreset)

  339             {

  340             case PRESET_FLAKE:

  341                 iSnowColor[i] = rand() % SNOW_FLAKE_COLOR_COUNT;

  342                 break;

  343             case PRESET_FLAME:

  344                 iSnowColor[i] = SNOW_FLAKE_COLOR_COUNT + rand() % SNOW_FLAME_COLOR_COUNT;

  345                 break;

  346             }

  347             // distribute snowflakes on the screen

  348             pSnowPosition[i].x = rcMem.right * i / SNOW_COUNT;

  349             pSnowPosition[i].y = rand() % rcMem.bottom;

  350         }

  351 

  352         /* Prepare snowflake for drawing */

  353         INT iVertexNowCount    = static_cast<INT>(3 * pow(4.0f, KOCH_IT));

  354         POINT * pVertexNow    = new POINT[iVertexNowCount];

  355 

  356         for (INT j = 0; j < iVertexNowCount; ++j)

  357         {

  358             /* Just translation */

  359             //pVertexNow[j].x = pKoch[iSnowSize[i]][j].x + pSnowPosition[i].x;

  360             //pVertexNow[j].y = pKoch[iSnowSize[i]][j].y + pSnowPosition[i].y;

  361             /* Translation and rotation */

  362             FLOAT fAngle = pLevels->waveform[0][SA_BUFFER_SIZE * i / SNOW_COUNT] * 3.1415926535897932384626433832795f / 256.0f;

  363             FLOAT fAngleCos = cos(fAngle);

  364             FLOAT fAngleSin = sin(fAngle);

  365             pVertexNow[j].x = static_cast<LONG>(fAngleCos * pKoch[iSnowSize[i]][j].x - fAngleSin * pKoch[iSnowSize[i]][j].y + pSnowPosition[i].x);

  366             pVertexNow[j].y = static_cast<LONG>(fAngleSin * pKoch[iSnowSize[i]][j].x + fAngleCos * pKoch[iSnowSize[i]][j].y + pSnowPosition[i].y);

  367         }

  368 

  369         /* Draw Koch Snowflake */

  370         HPEN hpOld = static_cast<HPEN>(SelectObject(hdcMem, hpSnowColor[iSnowColor[i]]));

  371 

  372         MoveToEx(hdcMem, pVertexNow[0].x, pVertexNow[0].y, 0);

  373         PolylineTo(hdcMem, pVertexNow, iVertexNowCount);

  374         LineTo(hdcMem, pVertexNow[0].x, pVertexNow[0].y);

  375 

  376         SelectObject(hdcMem, hpOld);

  377 

  378         delete[] pVertexNow;

  379     }

  380 }

PolylineTo is the fastest way to draw a polygon when you use a pen with width 1. The width of the pen has to be one!

Wrapping up: Blending and Rendering

So far, we've seen some methods that did something if someone called them. CWinterVisualization::Render calls those methods and in turn is called by Windows Media Player (which decides how frequent to call it). Notice the line 169 which allows this method to be called only 30 times per second. Because the size of the drawing surface can change (when you resize Windows Media Player), the device contexts are deleted and created again.

  167 STDMETHODIMP CWinterVisualization::Render(TimedLevel *pLevels, HDC hdc, RECT *prc)

  168 {

  169     if (_abs64(pLevels->timeStamp - tsPrev) < 333333) // 10^7 is 1 second; use only 30fps

  170         return S_OK;

  171     tsPrev = pLevels->timeStamp; // keep time stamp

  172 

  173     RECT rcMem = {0, 0, prc->right - prc->left, prc->bottom - prc->top}; // memory rectangle

  174 

  175     /* Check whether drawing surface changed */

  176     if ((rcMem.left != rcPrev.left) || (rcMem.top != rcPrev.top) ||

  177         (rcMem.right != rcPrev.right) || (rcMem.bottom != rcPrev.bottom))

  178     {

  179         rcPrev = rcMem; // keep memory rectangle

  180 

  181         /* Delete Prev */

  182         if (hNewBitmapPrev)

  183         {

  184             SelectObject(hdcPrev, hOldBitmapPrev);

  185             DeleteObject(hNewBitmapPrev);

  186         }

  187         hNewBitmapPrev    = 0;

  188         hOldBitmapPrev    = 0;

  189         if (hdcPrev)

  190             DeleteDC(hdcPrev);

  191         hdcPrev            = 0;

  192 

  193         /* Delete Mem */

  194         if (hNewBitmapMem)

  195         {

  196             SelectObject(hdcMem, hOldBitmapMem);

  197             DeleteObject(hNewBitmapMem);

  198         }

  199         hNewBitmapMem    = 0;

  200         hOldBitmapMem    = 0;

  201         if (hdcMem)

  202             DeleteDC(hdcMem);

  203         hdcMem            = 0;

  204 

  205         // Make snowflakes re-initialize in DrawSnow

  206         for (INT i = 0; i < SNOW_COUNT; ++i)

  207             iSnowSize[i] = iKochCount - 1;

  208     }

  209 

  210     /* Create Prev */

  211     if (!hdcPrev)

  212         hdcPrev = CreateCompatibleDC(hdc);

  213     if (!hNewBitmapPrev)

  214     {

  215         hNewBitmapPrev = CreateCompatibleBitmap(hdc, rcMem.right, rcMem.bottom);

  216         hOldBitmapPrev = static_cast<HBITMAP>(SelectObject(hdcPrev, hNewBitmapPrev));

  217     }

  218 

  219     /* Create Mem */

  220     if (!hdcMem)

  221         hdcMem = CreateCompatibleDC(hdc);

  222     if (!hNewBitmapMem)

  223     {

  224         hNewBitmapMem = CreateCompatibleBitmap(hdc, rcMem.right, rcMem.bottom);

  225         hOldBitmapMem = static_cast<HBITMAP>(SelectObject(hdcMem, hNewBitmapMem));

  226     }

  227 

  228     /* Draw background on Mem */

  229     FillRect(hdcMem, &rcMem, hBrushBack);

  230 

  231     /* Blend Prev with Mem */   

  232     BLENDFUNCTION bfPrev = {AC_SRC_OVER, 0, SNOW_ALPHA, AC_SRC_ALPHA};

  233     AlphaBlend(hdcMem, rcMem.left, rcMem.top, rcMem.right, rcMem.bottom,

  234         hdcPrev, rcMem.left, rcMem.top, rcMem.right, rcMem.bottom, bfPrev);

  235 

  236     /* Draw Snow on Mem */

  237     DrawSnow(pLevels, hdcMem, rcMem);

  238     /* Draw Plus on Mem */

  239     DrawPlus(pLevels, hdcMem, rcMem);

  240 

  241     /* Copy Mem to Prev */

  242     BitBlt(hdcPrev, rcMem.left, rcMem.top, rcMem.right, rcMem.bottom,

  243         hdcMem, rcMem.left, rcMem.top, SRCCOPY);

  244 

  245     /* Draw Text on Mem */

  246     /* Uncomment the following line to draw elapsed text in the top-right corner */

  247     DrawText(pLevels, hdcMem, rcMem);

  248 

  249     /* Copy Mem to WMP */

  250     BitBlt(hdc, prc->left, prc->top, prc->right - prc->left, prc->bottom - prc->top,

  251         hdcMem, rcMem.left, rcMem.top, SRCCOPY);

  252 

  253     return S_OK;

  254 }

I haven't said anything about the flicker... how was that avoided? Take a good look at this method (especially at line 250): we weren't drawing directly on the default device context as the wizard-generated visualization did, instead we were drawing on a memory device context hdcMem which was copied to the default device context only when everything was ready. It's a simple solution and it avoids the flicker! Never draw directly to the hdc received as the argument!
What about the smooth effect? We needed the msimg32.lib, especially the AlphaBlend method which blends two device contexts together. The blend between hdcPrev and hdcMem lets the previous rendered scene to be drawn again with a darker shade, while the new scene is as vivid as it should be; hdcMem is saved to hdcPrev to keep it for the next rendering when it becomes the old scene. That's it!

Conclusion

Winter Visualization, Snow Flakes:
capture1

Winter Visualization, Snow Flames:
capture2

You've created a beautiful winter visualization... Watch the dancing snowflakes and download the Winter Visualization Source Code or download only the installer WinterVisualizationSetup.msi (share the Winter Visualization with your friends). I hope you enjoyed working with C++ and you like what you've learned today!
Thanks to the Microsoft Student Partners - Microsoft Academic Program Team Romania for support. Happy Holidays!

Reference

Bio

Paul-Valentin Borza is in its third year of study at the BabeĊŸ-Bolyai University of Cluj-Napoca, Romania. Driven by his passion for technology, he was invited in 2005 to be a Microsoft Student Partners member - Microsoft Academic Program at the Faculty of Mathematics and Computer Science. He can be reached through his web site at www.borza.ro or on the Windows Live Network with windowslive@borza.ro.

Tags:

Follow the Discussion

  • Davon MuchettDavon Muchett

    this visualization is off the hook i believe this is the best visualization ever if i could just take the whole world of it i would.

  • Davon MuchettDavon Muchett

    this is totally off the hook every one should have this i even hate it because i love it its davon yow!!!!!!!!!!!!!!!!!!!

  • Richard HaafRichard Haaf

    Thank you Paul-Valentin,

    A few weeks ago my development machine with Visual Studio .NET, Media Player 10 and Media Player 10 SDK died.  This was replaced with a new machine with Visual Studion 2005 and Media Player 11.  I've been trying for weeks to figure out how to get the Project wizard in the 2008 Windows SDK to work with Visual Studio 2005.  The Microsoft documentation doesn't give a clue how to do this.  Your explaination gave me all the info I needed to get it to work.  As an added plus, you described exactly what I've needed to get off-screen buffering to work.

    Many Thanks!!

  • AdrianaAdriana

    Last year I was doing an engineering project for  my Science Fair Project. I wanted to create a Music Visualization and this tutorial saved my grade and project. Thank you!

  • MartinMartin

    It looked like fun project, but unfortunately Visual Studio could not load the Windows Media Player Plug-In Wizard.

    Running Windows XP, Visual Studio C++ 2008 Express, SDK version 6.1. I have changed the path-adresses accordingly - that is equivalently to your description. Get the fail message on status bar at the bottom of the Studio: Creating Project ....  project creation failed. What can I have done wrong?

  • Clint RutkasClint I'm a "developer"

    @Martin, looking into it.

  • PaulPaul

    @Martin,

    Have you installed the 2003 R2 SDK also? Although it's similar with the SDK for Vista, you need to install that in order to make it work. However, those steps for changing paths are only required for the Express editions -- if you have a standard or higher version, there's no need to make those.

    Do you have a more clearer error message than "creation failed"?

  • PaulPaul

    @Martin,

    Have you installed the 2003 R2 SDK also? Although it's similar with the SDK for Vista, you need to install that in order to make it work. However, those steps for changing paths are only required for the Express editions -- if you have a standard or higher version, there's no need to make those.

    Do you have a more clearer error message than "creation failed"?

  • MartinMartin

    I found 2003 R2 SDK, and it did not install, stating I already have got the latest version of the plug-in program. Same result as before.

    And no, sorry, "creation failed" is the only message I get. It is a small, almost not notable message, at the bottom part of the Express window.

  • Gautham GGautham G

    I installed the windows media player plug-in wizard. Got Windows SDK (newer version of platform SDK)... When I create a new project and select the wizard, it opens up an IE page, but it says it can't find the webpage i'm looking for.

  • Alan ZAlan Z

    well im having trouble, but not with the installation and the other things.

    anyway when i finished writing the code for the Initialization part of the tutorial and i went 2 build the code i get the undeclared errors so then i go and add the #include "WinterVisualization.h" all the errors go away but then i get another error saying that it can't open the include file. so im wondering what i need 2 do that will hep me get past the problem.

    So does anyone know how i can declare all the undeclared units or what ever you want to call them?

    or can some one give me a link to a working code of this visualization, or even a screen shot or even email me the code.  

  • Clint RutkasClint I'm a "developer"

    @Lachlan I emailed Paul, he is currently out of the country for two weeks.  If you email me (code4fun@microsoft.com), I'll email you when I get the files.  I'll also update the post with new links on our server.

  • Clint RutkasClint I'm a "developer"

    Fixed!  Source now lives on the Coding4Fun server.  Sorry about that.  http://coding4fun.net/source/WinterVisualization.zip

  • Phil RogersPhil Rogers

    This is an excellent demonstration of how to create a visualization.  Thanks.  I only came looking for how to get the WMP SDK to work with VS2005, but ended up with a great head start on creating a visualization.

    By coincidence it's snowing outside, so the example is so appropriate!

  • Phil RogersPhil Rogers

    I built this visualization and then ran WMP 11.

    It appeared in the visualization menu, and I selected it.

    However, the visualization client area is not repainted after selecting it, so the menu is not removed until I start playing some music and the first call to Render occurs.

    I noticed the same thing happens if I open anything else that covers the visualization client area, such as the About Box.

    I created a default visualization using the Wizard in the SDK and that works as expected.  I compared the code of the two projects and there's nothing obviously missing from the Winter Visualization, so why is it not refreshing the client area correctly?

  • Paul V. BorzaPaul V. Borza

    Hi Phil,

    I haven't been playing with Windows Media Player visualizations since 2007, so my advices might not be as accurate as you'd expect. However, the rendering has to occur once the song starts playing (and it does from what you're saying). In a visualization, you have to take care of all the drawing that occurs in the specified rectangle, or the surface; that includes, taking care when user is resizing the window, right-clicks the surface and the menu appears etc.

    Usually, when you right-click, and event will be fired up telling you the invalidated region that needs repainting (the menu that you're talking about).

    First, try to see if you have the same behavior on a different machine. If you do, investigate the following:

    * has the visualization been created before playing a song (is the constructor being called, and the rendering performed);

    * has some event changed (it's been three years since I've published the article -- are you using Windows 7, a different SDK version perhaps);

    * are the events that I was using now deprecated and no longer being called properly.

    Try to attach to the WMP event and debug it to see what happens; use breakpoints in some key functions and see whether those functions are called after you right-click in the drawing area.

    If everything fails, try to reproduce the same behavior using a computer that runs Windows Vista, and be sure you use Visual Studio 2005 together with all additional dependencies.

    Thanks,

    Paul

  • Clint RutkasClint I'm a "developer"

    @Phil Rogers  Contacting Paul-Valentin to see what is up.

  • Phil RogersPhil Rogers

    Thanks for the tips.  I'll give them a try.

    I guessed it must be something to do with invalidated rectangles and events etc.  I'll just have to do some experimenting.

    Phil

  • Clint RutkasClint I'm a "developer"

    @nkateko, try the download link again at the top of the page.

  • nkatekonkateko

    i need winter vusualisation for my windows 7 windows media player

  • Clint RutkasClint I'm a "developer"

    @Alan Z, confused by your last statement, you don't want to increase the list but you want to add more?

  • Alan ZAlan Z

    so i got it working and i have been exploring and modifying the code as i see fit. the one thing that i have been trying to do and with no success yet in adding more visualizations to the list, by that i mean other choices other then Snow Flakes and Snow Flames. also with out increasing the list in the Visualization list.

  • Alan ZAlan Z

    So what I'm trying to say is that I don't want to increase the list of categories, meaning that i don want to add more to the Album Art, Alchemy, Bars and Waves, Battery, and Winter, list (the only categories that I have in my Windows Media Player). I want to add more choices in the category, meaning I want other selections in the Winter category other then Snow Flakes and Snow Flames.

  • Alan Z.Alan Z.

    So the menu I don't want to increase is the main visualization menu, I don't want to add more categories to the main list of visualizations, meaning I don't want to add more to the Album Art, Alchemy, Bars and Waves, Battery, Winter, list. I want to add more selections with in the Winter category, I want more choices other then Snow Flakes, and Snow Flames.

  • Paul V. BorzaPaul V. Borza

    Hi Alan,

    You'll have to change the following:

    1. Open WinterVisualization.rc with the visual editor (if you open the source file, you'll also have to modify resource.h by hand) and add IDS_NEWPRESET "My New Preset Name";

    3. WinterVisualization.h:31 (duplicate the enum value and change it to something like NEW_PRESET);

    4. WinterVisualization.cpp:515-517 (duplicate those lines and make the necessary changes in the switch/case);

    5. Search for "PRESET_FLAME" and whenever you find it in WinterVisualization.cpp duplicate those line and make whatever change you wish (like making the snowflakes green).

    I believe that's enough to add another option under the winter menu item. If you have additional questions, please leave a comment and we'll get back to you.

    Thanks,

    Paul

Remove this comment

Remove this thread

close

Comments Closed

Comments have been closed since this content was published more than 30 days ago, but if you'd like to continue the conversation, please create a new thread in our Forums,
or Contact Us and let us know.