/* LICENSE ------- Copyright (C) 1999-2002 Nullsoft, Inc. This source code 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 source code or the software it produces. Permission is granted to anyone to use this source code for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this source code must not be misrepresented; you must not claim that you wrote the original source code. If you use this source code in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original source code. 3. This notice may not be removed or altered from any source distribution. */ /* Order of Function Calls ----------------------- The only code that will be called by the plugin framework are the 12 virtual functions in plugin.h. But in what order are they called? A breakdown follows. A function name in { } means that it is only called under certain conditions. Order of function calls... When the PLUGIN launches ------------------------ INITIALIZATION OverrideDefaults MyPreInitialize MyReadConfig << DirectX gets initialized at this point >> AllocateMyNonDx8Stuff AllocateMyDX8Stuff RUNNING +--> { CleanUpMyDX8Stuff + AllocateMyDX8Stuff } // called together when user resizes window or toggles fullscreen<->windowed. | MyRenderFn | MyRenderUI | { MyWindowProc } // called, between frames, on mouse/keyboard/system events. 100% threadsafe. +----<< repeat >> CLEANUP CleanUpMyDX8Stuff CleanUpMyNonDx8Stuff << DirectX gets uninitialized at this point >> When the CONFIG PANEL launches ------------------------------ INITIALIZATION OverrideDefaults MyPreInitialize MyReadConfig << DirectX gets initialized at this point >> RUNNING { MyConfigTabProc } // called on startup & on keyboard events CLEANUP [ MyWriteConfig ] // only called if user clicked 'OK' to exit << DirectX gets uninitialized at this point >> */ #include "plugin.h" #include "utility.h" #include "support.h" #include "resource.h" #include "defines.h" #include "shell_defines.h" #include #include // for time() #include // for sliders // some local settings for this example plugin: #define BASE_FOV 71.6f // field of view, in degrees #define FOG_COLOR 0xFF440044 // format: AABBGGRR #define NUM_WAVE_MODES 8 #define TEXTURE_NAME "ex_tex.jpg" // file must be in your winamp\plugins\ dir. #define FREQ_SAMPLES (NUM_FREQUENCIES/2) // only look at 0 Hz - 11025 Hz range // Modify the help screen text here. // Watch the # of lines, though; if there are too many, they will get cut off; // and watch the length of the lines, since there is no wordwrap. // A good guideline: your entire help screen should be visible when fullscreen // @ 640x480 and using the default help screen font. char g_szHelp[] = { "ESC: exit plugin\r" // part of framework "F1: show/hide help\r" // part of framework "F2: show/hide song title\r" // <- specific to this example plugin "F3: show/hide song length\r" // <- specific to this example plugin "F5: show/hide fps\r" // <- specific to this example plugin "ALT+D: toggle desktop mode\r" // part of framework "ALT+ENTER: toggle fullscreen\r" // part of framework "\r" "F: toggle flat shading\r" // <- specific to this example plugin "T: toggle textures on/off\r" // <- specific to this example plugin "G: toggle fog\r" // <- specific to this example plugin "W: toggle wireframe\r" // <- specific to this example plugin "+/-: adjust cube size\r" // <- specific to this example plugin "SPACE: cycle through waveforms\r" // <- specific to this example plugin "\r" "PLAYBACK:\r" // part of framework " ZXCVB: prev play pause stop next\r" // part of framework " P: show/hide playlist\r" // part of framework " R: toggle repeat\r" // part of framework " S: toggle shuffle\r" // part of framework " up/down arrows: adjust vol.\r" // part of framework " left/right arrows: seek\r" // part of framework " +SHIFT: fast seek" // part of framework }; //---------------------------------------------------------------------- void CPlugin::OverrideDefaults() { // Here, you have the option of overriding the "default defaults" // for the stuff on tab 1 of the config panel, replacing them // with custom defaults for your plugin. // To override any of the defaults, just uncomment the line // and change the value. // DO NOT modify these values from any function but this one! // This example plugin only changes the default width/height // for fullscreen mode; the "default defaults" are just // 640 x 480. // If your plugin is very dependent on smooth animation and you // wanted it plugin to have the 'save cpu' option OFF by default, // for example, you could set 'm_save_cpu' to 0 here. // m_start_fullscreen = 0; // 0 or 1 // m_start_desktop = 0; // 0 or 1 // m_fake_fullscreen_mode = 0; // 0 or 1 // m_max_fps_fs = 30; // 1-120, or 0 for 'unlimited' // m_max_fps_dm = 30; // 1-120, or 0 for 'unlimited' // m_max_fps_w = 30; // 1-120, or 0 for 'unlimited' // m_show_press_f1_msg = 1; // 0 or 1 // m_allow_page_tearing_w = 1; // 0 or 1 // m_allow_page_tearing_fs = 0; // 0 or 1 // m_allow_page_tearing_dm = 1; // 0 or 1 // m_minimize_winamp = 1; // 0 or 1 // m_desktop_textlabel_boxes = 1; // 0 or 1 // m_save_cpu = 0; // 0 or 1 // strcpy(m_fontinfo[0].szFace, "Trebuchet MS"); // system font // m_fontinfo[0].nSize = 18; // m_fontinfo[0].bBold = 0; // m_fontinfo[0].bItalic = 0; // strcpy(m_fontinfo[1].szFace, "Times New Roman"); // decorative font // m_fontinfo[1].nSize = 24; // m_fontinfo[1].bBold = 0; // m_fontinfo[1].bItalic = 1; m_disp_mode_fs.Width = 1024; // normally 640 m_disp_mode_fs.Height = 768; // normally 480 m_disp_mode_fs.Format = D3DFMT_X8R8G8B8; // use either D3DFMT_X8R8G8B8 or D3DFMT_R5G6B5. The former will match to any 32-bit color format available, and the latter will match to any 16-bit color available, if that exact format can't be found. // m_disp_mode_fs.RefreshRate = 60; } //---------------------------------------------------------------------- void CPlugin::MyPreInitialize() { // Initialize EVERY data member you've added to CPlugin here; // these will be the default values. // If you want to initialize any of your variables with random values // (using rand()), be sure to seed the random number generator first! // (If you want to change the default values for settings that are part of // the plugin shell (framework), do so from OverrideDefaults() above.) // seed the system's random number generator w/the current system time: srand((unsigned)time(NULL)); // CONFIG PANEL SETTINGS THAT WE'VE ADDED m_fog_enabled = 1; m_initial_wave_mode = 0; m_anim_speed_setting = 8; // range: 0..16 // RUNTIME SETTINGS m_show_song = 0; m_show_time = 0; m_show_fps = 0; m_textures = 1; m_draw_flat = 0; m_draw_wire = 0; //m_wave_mode will be set after reading m_initial_wave_mode, in MyReadConfig(). m_cube_size = 1.0f; m_anim_time = 0; // note: this will be updated each frame, at top of MyRenderFn. m_prev_time = 0; // note: this will be updated each frame, at bottom of MyRenderFn. m_cube_rotation_offset = 0; m_cube_position_offset = 0; // OUR TEXTURE DATA m_object_tex = NULL; // OUR GEOMETRY DATA m_object_index_buf = NULL; m_object_vertex_buf = NULL; // OUR SOUND DATA m_is_beat = 0; m_has_fallen = 1; m_thresh = 1.0f; m_limit = 1.0f; m_last_hit = 1.0f; memset(m_oldspec, 0, sizeof(m_oldspec)); } //---------------------------------------------------------------------- void CPlugin::MyReadConfig() { // Read the user's settings from the .INI file. // If you've added any controls to the config panel, read their value in // from the .INI file here. // use this function declared in to read a value of this type: // ----------------- ----------- ---------------------------- // GetPrivateProfileInt Win32 API int // GetPrivateProfileBool utility.h bool // GetPrivateProfileBOOL utility.h BOOL // GetPrivateProfileFloat utility.h float // GetPrivateProfileString Win32 API string // In this example plugin, we just read values for the sample controls // on page 2 of the config panel: m_fog_enabled = GetPrivateProfileInt("settings","fog_enabled" ,m_fog_enabled ,GetConfigIniFile()); m_initial_wave_mode = GetPrivateProfileInt("settings","initial_wave_mode" ,m_initial_wave_mode ,GetConfigIniFile()); m_anim_speed_setting = GetPrivateProfileInt("settings","anim_speed_setting",m_anim_speed_setting,GetConfigIniFile()); // derived settings: m_wave_mode = m_initial_wave_mode; } //---------------------------------------------------------------------- void CPlugin::MyWriteConfig() { // Write the user's settings to the .INI file. // This gets called only when the user runs the config panel and hits OK. // If you've added any controls to the config panel, write their value out // to the .INI file here. // use this function declared in to write a value of this type: // ----------------- ----------- ---------------------------- // WritePrivateProfileInt Win32 API int // WritePrivateProfileInt utility.h bool // WritePrivateProfileInt utility.h BOOL // WritePrivateProfileFloat utility.h float // WritePrivateProfileString Win32 API string // In this example plugin, we just write values for the sample controls // on page 2 of the config panel: WritePrivateProfileInt(m_fog_enabled ,"fog_enabled" ,GetConfigIniFile(),"settings"); WritePrivateProfileInt(m_initial_wave_mode ,"initial_wave_mode" ,GetConfigIniFile(),"settings"); WritePrivateProfileInt(m_anim_speed_setting,"anim_speed_setting",GetConfigIniFile(),"settings"); } //---------------------------------------------------------------------- int CPlugin::AllocateMyNonDx8Stuff() { // This gets called only once, when your plugin is actually launched. // If only the config panel is launched, this does NOT get called. // (whereas MyPreInitialize() still does). // If anything fails here, return FALSE to safely exit the plugin, // but only after displaying a messagebox giving the user some information // about what went wrong. // (In this example plugin, there is nothing to do here.) return true; } //---------------------------------------------------------------------- void CPlugin::CleanUpMyNonDx8Stuff() { // This gets called only once, when your plugin exits. // Be sure to clean up any objects here that were // created/initialized in AllocateMyNonDx8Stuff. // (In this example plugin, there is nothing to do here.) } //---------------------------------------------------------------------- int CPlugin::AllocateMyDX8Stuff() { // (...aka OnUserResizeWindow) // (...aka OnToggleFullscreen) // Allocate and initialize all your DX8 and D3DX stuff here: textures, // surfaces, vertex/index buffers, D3DX fonts, and so on. // If anything fails here, return FALSE to safely exit the plugin, // but only after displaying a messagebox giving the user some information // about what went wrong. If the error is NON-CRITICAL, you don't *have* // to return; just make sure that the rest of the code will be still safely // run (albeit with degraded features). // If you run out of video memory, you might want to show a short messagebox // saying what failed to allocate and that the reason is a lack of video // memory, and then call SuggestHowToFreeSomeMem(), which will show them // a *second* messagebox that (intelligently) suggests how they can free up // some video memory. // Don't forget to account for each object you create/allocate here by cleaning // it up in CleanUpMyDX8Stuff! // IMPORTANT: // Note that the code here isn't just run at program startup! // When the user toggles between fullscreen and windowed modes // or resizes the window, the base class calls this function before // destroying & recreating the plugin window and DirectX object, and then // calls AllocateMyDX8Stuff afterwards, to get your plugin running again. // In this example plugin, we first create a normal texture (for the cube), from an image on disk. // note: if the texture can't be created at exactly the size of the image, // d3dx will stretch the image to fit the size of the actual texture that d3dx creates. char fname[512]; sprintf(fname, "%s%s", GetPluginsDirPath(), TEXTURE_NAME); if (D3DXCreateTextureFromFile(GetDevice(), fname, &m_object_tex) != S_OK) { // just give a warning, and move on m_object_tex = NULL; // (make sure pointer wasn't mangled by some bad driver) char msg[1024]; sprintf(msg, "Unable to load texture:\r%s", fname); MessageBox(GetPluginWindow(), msg, "WARNING", MB_OK|MB_SETFOREGROUND|MB_TOPMOST); //return false; } // Then we create & fill vertex/index buffers. // the vertex buffer is a list of 3D vertices with colors, texcoords, etc. // the index buffer is a list of indices into that array of vertices // that tells the video card how to connect the vertices to make polygons, // in this example, via a triangle list. Note that triangle strips are // far more efficient, and that using index buffers on a cube is pretty silly, // because ideally each side of the cube has 4 unique vertices to itself // (so the texture coords don't have to meet at all the edges). { int num_vertices = 8; int num_indices = 36; int sizeof_vertex = sizeof(MYVERTEX); int sizeof_index = 2; // (2 bytes for a 16-bit index) int align = 1024; // in bytes; must be a power of 2! if (GetDevice()->CreateVertexBuffer( ((sizeof_vertex*num_vertices + align-1)/align)*align, 0, MYVERTEX_FORMAT, D3DPOOL_MANAGED, &m_object_vertex_buf) != D3D_OK) { MessageBox(GetPluginWindow(), "Error creating vertex buffer", "ERROR", MB_OK|MB_SETFOREGROUND|MB_TOPMOST); return false; } if (GetDevice()->CreateIndexBuffer( ((sizeof_index*num_indices + align-1)/align)*align, 0, D3DFMT_INDEX16, D3DPOOL_MANAGED, &m_object_index_buf) != D3D_OK) { MessageBox(GetPluginWindow(), "Error creating index buffer", "ERROR", MB_OK|MB_SETFOREGROUND|MB_TOPMOST); return false; } int lock_flags = 0;//D3DLOCK_DISCARD; // fill vertex buffer with 8 vertices for a simple cube: MYVERTEX *pVerts; if (m_object_vertex_buf->Lock(0, 0, (BYTE**)&pVerts, lock_flags )!=D3D_OK) { MessageBox(GetPluginWindow(), "Error locking vertex buffer", "ERROR", MB_OK|MB_SETFOREGROUND|MB_TOPMOST); return false; } else { for (int i=0; i<8; i++) { pVerts[i].x = ((i )%2) ? -1.0f : 1.0f; pVerts[i].y = ((i/2)%2) ? -1.0f : 1.0f; pVerts[i].z = ((i/4)%2) ? -1.0f : 1.0f; int r = 128 + (rand() % 128); int g = 128 + (rand() % 128); int b = 128 + (rand() % 128); pVerts[i].Diffuse = 0xFF000000 | (r<<16) | (g<<8) | b; // color format: 0xAARRGGBB pVerts[i].tu1 = (rand() % 256)/255.0f; // texcoords are in 0..1 range pVerts[i].tv1 = (rand() % 256)/255.0f; // texcoords are in 0..1 range } m_object_vertex_buf->Unlock(); } // fill index buffer with 36 indices; each 3 vertices makes a polygon. __int16 *pIndices; if (m_object_index_buf->Lock(0, 0, (BYTE**)&pIndices, lock_flags) != D3D_OK) { MessageBox(GetPluginWindow(), "Error locking index buffer", "ERROR", MB_OK|MB_SETFOREGROUND|MB_TOPMOST); return false; } else { pIndices[ 0] = 0; pIndices[ 1] = 1; pIndices[ 2] = 2; pIndices[ 3] = 1; pIndices[ 4] = 2; pIndices[ 5] = 3; pIndices[ 6] = 2; pIndices[ 7] = 3; pIndices[ 8] = 6; pIndices[ 9] = 3; pIndices[10] = 6; pIndices[11] = 7; pIndices[12] = 6; pIndices[13] = 7; pIndices[14] = 4; pIndices[15] = 7; pIndices[16] = 4; pIndices[17] = 5; pIndices[18] = 4; pIndices[19] = 5; pIndices[20] = 0; pIndices[21] = 5; pIndices[22] = 0; pIndices[23] = 1; pIndices[24] = 0; pIndices[25] = 2; pIndices[26] = 4; pIndices[27] = 2; pIndices[28] = 4; pIndices[29] = 6; pIndices[30] = 1; pIndices[31] = 3; pIndices[32] = 5; pIndices[33] = 3; pIndices[34] = 5; pIndices[35] = 7; m_object_index_buf->Unlock(); } } return true; } //---------------------------------------------------------------------- void CPlugin::CleanUpMyDX8Stuff(int final_cleanup) { // Clean up all your DX8 and D3DX textures, fonts, buffers, etc. here. // EVERYTHING CREATED IN ALLOCATEMYDX8STUFF() SHOULD BE CLEANED UP HERE. // The input parameter, 'final_cleanup', will be 0 if this is // a routine cleanup (part of a window resize or switch between // fullscr/windowed modes), or 1 if this is the final cleanup // and the plugin is exiting. Note that even if it is a routine // cleanup, *you still have to release ALL your DirectX stuff, // because the DirectX device is being destroyed and recreated!* // Also set all the pointers back to NULL; // this is important because if we go to reallocate the DX8 // stuff later, and something fails, then CleanUp will get called, // but it will then be trying to clean up invalid pointers.) // The SafeRelease() and SafeDelete() macros make your code prettier; // they are defined here in utility.h as follows: // #define SafeRelease(x) if (x) {x->Release(); x=NULL;} // #define SafeDelete(x) if (x) {delete x; x=NULL;} // IMPORTANT: // This function ISN'T only called when the plugin exits! // It is also called whenever the user toggles between fullscreen and // windowed modes, or resizes the window. Basically, on these events, // the base class calls CleanUpMyDX8Stuff before Reset()ing the DirectX // device, and then calls AllocateMyDX8Stuff afterwards. // release stuff SafeRelease(m_object_tex); SafeRelease(m_object_vertex_buf); SafeRelease(m_object_index_buf); } //---------------------------------------------------------------------- //---------------------------------------------------------------------- //---------------------------------------------------------------------- void CPlugin::MyRenderFn(int redraw) { // Render a frame of animation here. // This function is called each frame just AFTER BeginScene(). // For timing information, call 'GetTime()' and 'GetFps()'. // The usual formula is like this (but doesn't have to be): // 1. take care of timing/other paperwork/etc. for new frame // 2. clear the background // 3. get ready for 3D drawing // 4. draw your 3D stuff // 5. call PrepareFor2DDrawing() // 6. draw your 2D stuff (overtop of your 3D scene) // If the 'redraw' flag is 1, you should try to redraw // the last frame; GetTime, GetFps, and GetFrame should // all return the same values as they did on the last // call to MyRenderFn(). Otherwise, the redraw flag will // be zero, and you can draw a new frame. The flag is // used to force the desktop to repaint itself when // running in desktop mode and Winamp is paused or stopped. // 1. take care of timing/other paperwork/etc. for new frame { // Track 'animation time' separate from framework's clock, // so that we can use it to animate the scene faster or slower // than normal, depending on the user's preference (m_anim_speed_setting). // Note that since the range of m_anim_speed_setting is 0..16, // the range of the pow function here will be -4..+4. m_anim_time += (GetTime() - m_prev_time) * powf(4.0f, (m_anim_speed_setting-8)/8.0f); m_prev_time = GetTime(); } // 2. do our own custom audio analysis for this frame: DoAudioAnalysis(); // 2. Clear the background: DWORD clear_color = (m_fog_enabled) ? FOG_COLOR : 0xFF000000; GetDevice()->Clear(0, 0, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER, clear_color, 1.0f, 0); // 3. get ready for drawing in 3D; set camera, lookat, up vector, // fov, near/far clip planes... D3DXVECTOR3 eye = D3DXVECTOR3(0,0,0); D3DXVECTOR3 lookat = D3DXVECTOR3(0,0,5); D3DXVECTOR3 up = D3DXVECTOR3(0,1,0); PrepareFor3DDrawing(GetDevice(), GetWidth(), GetHeight(), BASE_FOV, 0.1f, 10.0f, &eye, &lookat, &up); // 4. Render a generic 3D (untransformed,unlit) object: Render3D(); // 5. switch to 2D drawing mode. 2D coord system: // +--------+ Y=-1 // | | // | screen | Z=0: front of scene // | | Z=1: back of scene // +--------+ Y=1 // X=-1 X=1 PrepareFor2DDrawing(GetDevice()); // 6. Render 2D audio data: switch(m_wave_mode) { case 0: Render2DSpectraSimple(1, 1, 0); break; case 1: Render2DSpectraSimple(1, 1, 1); break; case 2: Render2DSpectraSimple(1, 0, 0); break; case 3: Render2DSpectraSimple(0, 0, 0); break; case 4: Render2DWave(); break; case 5: Render2DSpectraFancy(1, 0, 1.0f); break; case 6: Render2DSpectraFancy(16, 0, 1.0f); break; case 7: Render2DSpectraFancy(16, 1, 1.0f); break; } RenderBands(); } //---------------------------------------------------------------------- //---------------------------------------------------------------------- //---------------------------------------------------------------------- void CPlugin::Render3D() { // Draw a spinning cube. // Note that the angle of spin is based on the current (animation- // speed-adjusted) time, m_anim_time, which makes the rate of spin // independent of the framerate. IDirect3DDevice8 *pDevice = GetDevice(); // react to audio: if (m_is_beat) { m_cube_position_offset = -2.0f + 4.0f*(rand()%1000)*0.001f; m_cube_rotation_offset = 0.1f * (rand() % 1361); } // the View and Projection matrices were taken care of by PrepareFor3DDrawing; // now we set the World matrix, to place our cube in the 3D world: { D3DXMATRIX world; float t = m_anim_time + m_cube_rotation_offset; MakeWorldMatrix(&world, m_cube_position_offset, 0, 5, // object x,y,z position m_cube_size, m_cube_size, m_cube_size, // object x,y,z scaling t*0.54f, t*0.67f, t*0.33f // object pitch, yaw, roll ); pDevice->SetTransform(D3DTS_WORLD, &world); } // tweak render state: { if (m_draw_flat) pDevice->SetRenderState( D3DRS_SHADEMODE, D3DSHADE_FLAT ); if (m_draw_wire) pDevice->SetRenderState( D3DRS_FILLMODE, D3DFILL_WIREFRAME ); if (m_fog_enabled) { float fFogStart = 5.0f - 0.7f*m_cube_size; float fFogEnd = 5.0f + 0.7f*m_cube_size; pDevice->SetRenderState( D3DRS_FOGENABLE, TRUE ); pDevice->SetRenderState( D3DRS_FOGCOLOR, FOG_COLOR ); pDevice->SetRenderState( D3DRS_FOGTABLEMODE, D3DFOG_NONE ); pDevice->SetRenderState( D3DRS_FOGVERTEXMODE, D3DFOG_LINEAR ); pDevice->SetRenderState( D3DRS_FOGSTART, *((DWORD*) (&fFogStart))); pDevice->SetRenderState( D3DRS_FOGEND, *((DWORD*) (&fFogEnd))); } if (m_textures) { // set up for 1 texture, modulated with diffuse color of object pDevice->SetTexture(0, m_object_tex); pDevice->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE); pDevice->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_DIFFUSE); pDevice->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_TEXTURE); pDevice->SetTextureStageState(1, D3DTSS_COLOROP, D3DTOP_DISABLE); pDevice->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1 ); pDevice->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_DIFFUSE ); } else { // set up for no texture; just use the diffuse color of object pDevice->SetTexture(0, NULL); pDevice->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_SELECTARG1); pDevice->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_DIFFUSE); pDevice->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1 ); pDevice->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_DIFFUSE ); } } // tell DirectX the format of the vertices we're about to send in: pDevice->SetVertexShader( MYVERTEX_FORMAT ); // tell DirectX what vertex & index buffers to use: pDevice->SetStreamSource( 0, m_object_vertex_buf, sizeof(MYVERTEX) ); pDevice->SetIndices ( m_object_index_buf, 0 ); // draw it: pDevice->DrawIndexedPrimitive( D3DPT_TRIANGLELIST, 0, 8, 0, 12); } void CPlugin::RenderBands() { GetDevice()->SetVertexShader( WFVERTEX_FORMAT ); float amplitude = 0.15f; WFVERTEX verts[4]; for (int ch=0; ch<1; ch++) { for (int band=0; band<3; band++) { float x0 = (ch==0) ? -0.97f : 0.97f; float dx = (ch==0) ? 0.85f : -0.85f; float y0 = -0.8f + 0.12f*band; float dy = 0.3f; DWORD color = 0xFFFF0000 | (((2-band)*255/3) << 8); for (int i=0; i<4; i++) { verts[i].x = x0 + dx*(i&1 ? 1 : 0) * m_sound.avg[ch][band] * amplitude; verts[i].y = y0 + dy*0.5f*(i/2 ? dy : -dy); verts[i].z = 0; verts[i].Diffuse = color; } // Draw it GetDevice()->DrawPrimitiveUP(D3DPT_TRIANGLESTRIP, 2, verts, sizeof(WFVERTEX)); } } } void CPlugin::Render2DWave() { // Example of how to render the waveforms of the left & right channels // using a linestrip. GetDevice()->SetVertexShader( WFVERTEX_FORMAT ); float x0 = -1.0f; float dx = 2.0f; float y0 = -1.0f; float dy = 2.0f; WFVERTEX verts[NUM_WAVEFORM_SAMPLES]; float amplitude = 0.001f; for (int ch=0; ch<2; ch++) { float base = (ch==0) ? 0.25f : 0.75f; for (int i=0; iDrawPrimitiveUP(D3DPT_LINESTRIP, NUM_WAVEFORM_SAMPLES-1, verts, sizeof(WFVERTEX)); } } void CPlugin::Render2DSpectraSimple(int bEvenPitch, int bBoxes, int bMirror) { // Example of how to render the frequency spectra for the left & right channels // using a linestrip. GetDevice()->SetVertexShader( WFVERTEX_FORMAT ); float x0 = -1.0f; float dx = 2.0f; float y0 = -1.0f; float dy = 2.0f; WFVERTEX verts[FREQ_SAMPLES*2]; float amplitude = 0.03f; float min_freq = 200; // (200 Hz is the lowest bass frequency that the typical human ear can detect) float octaves = logf(11025/min_freq) / logf(2.0f); for (int ch=0; ch<2; ch++) { float base = (ch==0) ? 0.5f : 1.0f; float mult = 1.0f; if (bMirror && ch==1) { base = 0.5f; amplitude *= -1.0f; } int start_i = 0; int nVerts = 0; if (bEvenPitch) start_i = FREQ_SAMPLES*min_freq/11025; for (int i=start_i; i=0; i--) { verts[i*2 ] = verts[i]; verts[i*2+1] = verts[i]; } for (i=1; iDrawPrimitiveUP(D3DPT_LINESTRIP, nVerts*(bBoxes?2:1)-1, verts, sizeof(WFVERTEX)); } } void CPlugin::Render2DSpectraFancy(int blending_window_span, int radial_solid, float amplitude) { // Fancy example of how to render the frequency spectra for the left & // right channels, using a linestrip or triangle fan. // What this does that the simple version doesn't do: // 1. blends the spectrum of some number of samples ('blending_window_span') // 2. spectra are persistent and record peaks, then fall (rate = 'decrement_rate') // 3. can optionally blend spectrum so it meets @ the edges ('wrap') // 4. can display as a radial triangle fan ('radial_solid'). // Note that the rate of decrementation of the frequency spectrum // is a based on a constant divided by the current fps, GetFps(), // so that the true rate of decrement is framerate-independent. // parameters: amplitude *= 0.044f; float decrement_rate = 0.18f/GetFps(); float fmax = 1.0f; int wrap = radial_solid; // 0 or 1 GetDevice()->SetVertexShader( WFVERTEX_FORMAT ); float x0 = -1.0f; float dx = 2.0f; float y0 = -1.0f; float dy = 2.0f; for (int ch=0; ch<2; ch++) { WFVERTEX verts[FREQ_SAMPLES]; float sum = 0; float base = 0.5f;//(ch==0) ? 0.5f : 1.0f; float divisor = 0; float sign = (ch==0) ? 1.0f : -1.0f;//1.0f; float x,y; int i; DWORD color = (radial_solid) ? (ch ? 0xFFFF8800 : 0xFF0088FF) : 0xFFFFFFFF; if (wrap==1) for (i=-blending_window_span/2; i= 0) { sum -= m_sound.fSpectrum[ch][i-(blending_window_span+1)/2]; divisor -= 1.0f; } if (i+blending_window_span/2 < FREQ_SAMPLES) { sum += m_sound.fSpectrum[ch][i+blending_window_span/2]; divisor += 1.0f; } } float t2 = max(0, m_oldspec[ch][i] - decrement_rate); float t = min(max(t2, amplitude*sum/divisor), fmax); m_oldspec[ch][i] = t; if (radial_solid) { float rad = t*amplitude*45.0f; float ang = (i-1)/(float)(FREQ_SAMPLES-2)*6.28f; x = 0.5f + rad*cosf(ang); y = 0.5f + rad*sinf(ang); if (i==0) { x = 0.5f; y = 0.5f; } } else { x = i/(float)(FREQ_SAMPLES-1); y = base - sign*t; } verts[i].x = x0 + dx*x; verts[i].y = y0 + dy*y; verts[i].z = 0; verts[i].Diffuse = color; } // Draw it // note: for dynamically generated geometry that changes every frame, // DrawPrimitiveUP() is fine, but if you render any substantial static // geometry, be sure to use DrawPrimitive() instead, making use of // vertex buffers and/or index buffers! if (radial_solid) GetDevice()->DrawPrimitiveUP(D3DPT_TRIANGLEFAN, FREQ_SAMPLES-2, verts, sizeof(WFVERTEX)); else GetDevice()->DrawPrimitiveUP(D3DPT_LINESTRIP, FREQ_SAMPLES-1, verts, sizeof(WFVERTEX)); } } //---------------------------------------------------------------------- #define MTO_UPPER_RIGHT 0 #define MTO_UPPER_LEFT 1 #define MTO_LOWER_RIGHT 2 #define MTO_LOWER_LEFT 3 #define SelectFont(n) { \ pFont = GetFont(n); \ h = GetFontHeight(n); \ } #define MyTextOut(str, corner) { \ SetRect(&r, 0, 0, xR-xL, 2048); \ pFont->DrawText(str, -1, &r, DT_NOPREFIX | DT_SINGLELINE | DT_WORD_ELLIPSIS | DT_CALCRECT, 0xFFFFFFFF); \ int w = r.right - r.left; \ if (corner == MTO_UPPER_LEFT ) SetRect(&r, xL, *upper_left_corner_y, xL+w, *upper_left_corner_y + h); \ else if (corner == MTO_UPPER_RIGHT) SetRect(&r, xR-w, *upper_right_corner_y, xR, *upper_right_corner_y + h); \ else if (corner == MTO_LOWER_LEFT ) SetRect(&r, xL, *lower_left_corner_y - h, xL+w, *lower_left_corner_y); \ else if (corner == MTO_LOWER_RIGHT) SetRect(&r, xR-w, *lower_right_corner_y - h, xR, *lower_right_corner_y); \ pFont->DrawText(str, -1, &r, DT_NOPREFIX | DT_SINGLELINE | DT_WORD_ELLIPSIS, 0xFFFFFFFF); \ if (corner == MTO_UPPER_LEFT ) *upper_left_corner_y += h; \ else if (corner == MTO_UPPER_RIGHT) *upper_right_corner_y += h; \ else if (corner == MTO_LOWER_LEFT ) *lower_left_corner_y -= h; \ else if (corner == MTO_LOWER_RIGHT) *lower_right_corner_y -= h; \ } void CPlugin::MyRenderUI( int *upper_left_corner_y, // increment me! int *upper_right_corner_y, // increment me! int *lower_left_corner_y, // decrement me! int *lower_right_corner_y, // decrement me! int xL, int xR ) { // draw text messages directly to the back buffer. // when you draw text into one of the four corners, // draw the text at the current 'y' value for that corner // (one of the first 4 params to this function), // and then adjust that y value so that the next time // text is drawn in that corner, it gets drawn above/below // the previous text (instead of overtop of it). // when drawing into the upper or lower LEFT corners, // left-align your text to 'xL'. // when drawing into the upper or lower RIGHT corners, // right-align your text to 'xR'. // *** note that the 'MyTextOut' macro handles most of this stuff // for you; it even takes care of adjusting the y-values! *** // note: try to keep the bounding rectangles on the text small; // the smaller the area that has to be locked (to draw the text), // the faster it will be. (on some cards, drawing text is // ferociously slow, so even if it works okay on yours, it might // not work on another video card.) // note: if you want some text to be on the screen often, and the text // won't be changing every frame, please consider the poor folks // whose video cards hate that; in that case you should probably // draw the text just once, to a texture, and then display the // texture each frame. This is how the help screen is done; see // pluginshell.cpp for example code. RECT r; char buf[512]; LPD3DXFONT pFont = GetFont(DECORATIVE_FONT); int h = GetFontHeight(DECORATIVE_FONT); if (!pFont) return; // 1. render text in upper-right corner { // render fps to upper-right if (m_show_fps) { SelectFont(DECORATIVE_FONT); sprintf(buf, " fps: %4.2f ", GetFps()); // leave extra space @ end, so italicized fonts don't get clipped MyTextOut(buf, MTO_UPPER_RIGHT); } } // 2. render text in lower-right corner { //SelectFont(SIMPLE_FONT); //MyTextOut("some legible text for the lower right corner", MTO_LOWER_RIGHT); } // 3. render text in upper-left corner { //SelectFont(DECORATIVE_FONT); //MyTextOut("some string...", MTO_UPPER_LEFT); //MyTextOut("another string...", MTO_UPPER_LEFT); } // 4. render song info to lower left if (m_show_song || m_show_time) { SelectFont(DECORATIVE_FONT); char buf[512]; char songpos[512]; char songlen[512]; GetWinampSongPosAsText(GetWinampWindow(), songpos); // defined in utility.h/cpp GetWinampSongLenAsText(GetWinampWindow(), songlen); // defined in utility.h/cpp // render song title in lower-left corner: if (m_show_song) { GetWinampSongTitle(GetWinampWindow(), buf, sizeof(buf)-1); strcat(buf, " "); MyTextOut(buf, MTO_LOWER_LEFT); } // render song time & len above that: if (m_show_time) { if (songlen[0] && m_show_time==2) sprintf(buf, "%s / %s ", songpos, songlen); else strcpy(buf, songpos); MyTextOut(buf, MTO_LOWER_LEFT); } } } //---------------------------------------------------------------------- void CPlugin::DoAudioAnalysis() { // do some (very) simple beat detection, based on volume thresholds. // sets 'm_is_beat' to 0 or 1, based on whether or not the volume has spiked. m_is_beat = 0; if (GetFrame() == 0) { m_has_fallen = 1; m_thresh = 1; m_limit = 1; } else { float vol = 0.3333f*(m_sound.avg[0][0]/m_sound.med_avg[0][0] + m_sound.avg[0][1]/m_sound.med_avg[0][1] + m_sound.avg[0][2]/m_sound.med_avg[0][2]); float decay = AdjustRateToFPS(0.997f, 20.0f, GetFps()); // make the threshold decay over time, toward the limit. // this is fps-independent, thanks to AdjustRateToFPS(). m_thresh = m_thresh*decay + m_limit*(1-decay); if (!m_has_fallen) { // wait for it to get a bit quieter before allowing another beat if (vol < m_thresh) { m_has_fallen = 1; m_thresh = m_last_hit*1.03f; m_limit = 1.1f; } } else { // signal fell; now wait for vol to reach m_thresh, then signal a beat. if (vol > m_thresh && vol > 0.2f) { m_is_beat = 1; m_last_hit = vol; m_has_fallen = 0; m_thresh = max(1.0f, vol*0.85f); m_limit = max(1.0f, vol*0.70f); } } } } //---------------------------------------------------------------------- LRESULT CPlugin::MyWindowProc(HWND hWnd, unsigned uMsg, WPARAM wParam, LPARAM lParam) { // You can handle Windows messages here while the plugin is running, // such as mouse events (WM_MOUSEMOVE/WM_LBUTTONDOWN), keypresses // (WK_KEYDOWN/WM_CHAR), and so on. // This function is threadsafe (thanks to Winamp's architecture), // so you don't have to worry about using semaphores or critical // sections to read/write your class member variables. // If you don't handle a message, let it continue on the usual path // (to Winamp) by returning DefWindowProc(hWnd,uMsg,wParam,lParam). // If you do handle a message, prevent it from being handled again // (by Winamp) by returning 0. // IMPORTANT: For the WM_KEYDOWN, WM_KEYUP, and WM_CHAR messages, // you must return 0 if you process the message (key), // and 1 if you do not. DO NOT call DefWindowProc() // for these particular messages! The plugin shell, before // handling any keys, passes the message here to see if the // plugin wants to handle (override) the key. If we return // 1 on these messages, it means the key was not handled, so // go ahead and do the default processing. But if we return 0, // that means we handled the key and the plugin shell should // not handle it a second time. // note: use these handy values to detect keys like SHIFT+F3, CTRL+T, etc. bool bShiftHeldDown = (GetKeyState(VK_SHIFT ) & (1 << (sizeof(SHORT)*8 - 1))) != 0; bool bCtrlHeldDown = (GetKeyState(VK_CONTROL) & (1 << (sizeof(SHORT)*8 - 1))) != 0; //bool bAltHeldDown: most keys come in under WM_SYSKEYDOWN when ALT is depressed. switch (uMsg) { case WM_KEYDOWN: // Handle all "virtual keys" here. // This includes function keys, TAB, ESC, arrow keys, etc. // For a complete list of virtual-key codes (VK_*), look up the keyphrase // "virtual-key codes [win32]" in the msdn help. // Check bShiftHeldDown and/or bCtrlHeldDown to trap keys like SHIFT+VK_F3, CTRL+VK_RETURN, etc. // Return 0 if you 'eat' (handle) the key, or 1 to pass it along. switch(wParam) { case VK_F2: m_show_song = 1-m_show_song; return 0; case VK_F3: if (bShiftHeldDown) m_show_time = (m_show_time+2)%3; else m_show_time = (m_show_time+1)%3; return 0; case VK_F5: m_show_fps = 1-m_show_fps; return 0; case VK_SPACE: m_wave_mode = (m_wave_mode+1) % NUM_WAVE_MODES; return 0; case VK_ADD: // '+' key m_cube_size *= 1.05f; if (m_cube_size > 4.0f) m_cube_size = 4.0f; return 0; case VK_SUBTRACT: // '-' key m_cube_size /= 1.05f; if (m_cube_size < 0.25f) m_cube_size = 0.25f; return 0; } // for all other cases, return 1 so that the plugin shell (or winamp) gets the key next. return 1; // end case WM_KEYDOWN case WM_CHAR: // Handle all plain & simple alphanumeric keys here: A-Z, a-z, 0-9 // as well as symbols like / \ $ % * etc. // Note that you can treat upper & lowercase differently; they're just lumped together in this example. // Check bCtrlHeldDown to trap keys like CTRL+T, etc. // Return 0 if you 'eat' (handle) the key, or 1 to pass it along. switch(wParam) { case 'g': case 'G': m_fog_enabled = 1-m_fog_enabled; return 0; case 't': case 'T': m_textures = 1-m_textures; return 0; case 'f': case 'F': m_draw_flat = 1-m_draw_flat; return 0; case 'w': case 'W': m_draw_wire = 1-m_draw_wire; return 0; } // for all other cases, return 1 so that the plugin shell (or winamp) gets the key next. return 1; // end case WM_CHAR // other messages you might want to handle: case WM_KEYUP: case WM_SYSKEYDOWN: case WM_SYSKEYUP: case WM_SYSCHAR: return 1; //case WM_MOUSEMOVE: //case WM_SETCURSOR: // return DefWindowProc(hWnd, uMsg, wParam, lParam); } return DefWindowProc(hWnd, uMsg, wParam, lParam); }; //---------------------------------------------------------------------- BOOL CPlugin::MyConfigTabProc(int nPage, HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam) { // This is the only function you need to worry about for programming // tabs 2 through 8 on the config panel. (Tab 1 contains settings // that are common to all plugins, and the code is located in pluginshell.cpp). // By default, only tab 2 is enabled; to enable tabes 3+, see // 'Enabling Additional Tabs (pages) on the Config Panel' in DOCUMENTATION.TXT. // You should always return 0 for this function. // Note that you don't generally have to use critical sections or semaphores // here; Winamp controls the plugin's message queue, and only gives it message // in between frames. // // Incoming parameters: // 'nPage' indicates which tab (aka 'property page') is currently showing: 2 through 5. // 'hwnd' is the window handle of the property page (which is a dialog of its own, // embedded in the config dialog). // 'msg' is the windows message being sent. The main ones are: // // 1) WM_INITDIALOG: This means the page is being initialized, because the // user clicked on it. When you get this message, you should initialize // all the controls on the page, and set them to reflect the settings // that are stored in member variables. // // 2) WM_DESTROY: This is sent when a tab disappears, either because another // tab is about to be displayed, or because the user clicked OK or Cancel. // In any case, you should read the current settings of all the controls // on the page, and store them in CPlugin member variables. (If the user // clicked CANCEL, these values will not get saved to disk, but for simplicity, // we always poll the controls here.) // // 3) WM_HELP: This is sent when the user clicks the '?' icon (in the // titlebar of the config panel) and then clicks on a control. When you // get this message, you should display a MessageBox telling the user // a little bit about that control/setting. // // 4) WM_COMMAND: Advanced. This notifies you when the user clicks on a // control. Use this if you need to do certain things when the user // changes a setting. (For example, one control might only be enabled // when a certain checkbox is enabled; you would use EnableWindow() for // this.) // // For complete details on adding your own controls to one of the pages, please see // 'Adding Controls to the Config Panel' in DOCUMENTATION.TXT. int n; if (nPage == 2) { switch(msg) { case WM_INITDIALOG: // initialize controls here { // 1. set up a checkbox CheckDlgButton(hwnd, IDC_CB_FOG, m_fog_enabled); // 2. set up a combobox [NON-SORTING] // NOTE: be careful when adding your own new combo boxes; by default, // when you create a new one, it will auto-sort the data you // place in it, which makes it more complicated to work with! // -> To turn this off, double-click the combo box in the resource // editor, click the 'Styles' tab, and uncheck the 'Sort' option. // -> Or, if you want an auto-sorting combo box, you'll need to use // CB_SETITEMDATA and CB_GETITEMDATA; see config.cpp for examples. { int i; SendMessage( GetDlgItem( hwnd, IDC_INITIAL_WAVEMODE ), CB_RESETCONTENT, 0, 0); const char szWaveName[NUM_WAVE_MODES][64] = { "boxy spectra (x=pitch)", "boxy mirrored spectra (x=pitch)", "simple spectra (x=pitch)", "simple spectra (x=freq)", "waveforms", "mirrored spectra (responsive)", "mirrored spectra (damped)", "radial spectra", }; // populate combo box: for (i=0; iiCtrlId), ctrl_name, sizeof(ctrl_name)-1); RemoveSingleAmpersands(ctrl_name); buf[0] = 0; switch(ph->iCtrlId) { // ADD A HANDLER HERE FOR EACH ITEM YOU ADD TO THE CONFIG PANEL. // It should display a help messagebox when the user clicks the '?' // in the title bar, and then clicks on your added control. // See config.cpp for examples. // 1. help for checkbox case IDC_CB_FOG: sprintf(title, "Help on 'Enable Fog' checkbox"); sprintf(buf, "Enable this to turn fog on, by default, when the plugin starts."); break; // 2. help for combobox case IDC_INITIAL_WAVEMODE: case IDC_INITIAL_WAVEMODE_CAPTION: sprintf(title, "Help on 'Initial Wave Type' setting"); sprintf(buf, "Here you can select the initial method for rendering the\r" "audio visualization data, whenever the plugin starts up.\r" "You can also change this while the plugin is running\r" "by hitting the spacebar.\r" ); break; // 3. help for slider (trackbar) case IDC_HS_ANIM_SPEED: case IDC_HS_ANIM_SPEED_CAPTION: sprintf(title, "Help on 'Animation Speed' slider"); sprintf(buf, "Adjust this slider to the left to slow down the overall animation speed\r" "of the plugin, or to the right to speed things up.\r" ); break; // 4. add help for your new controls here... //case IDC_MY_NEW_CONTROL: // ... // break; } if (buf[0]) MessageBox(hwnd, buf, title, MB_OK|MB_SETFOREGROUND|MB_TOPMOST|MB_TASKMODAL); } break; // case WM_HELP /* case WM_COMMAND: id = LOWORD(wParam); switch(id) { // To make a button launch a popup dialog: (...for example code, see how the Fonts dialog was done, in config.cpp and fontdialog.cpp) case ID_ADVANCED_OPTIONS_DIALOG: DialogBox(GetInstance(), MAKEINTRESOURCE(IDD_MYVERYOWNDIALOG), hwnd, (DLGPROC)MyVeryOwnDialogProc); break; // To add a color picker, add a button (in the resource editor) to launch it, // then add code something like this: case IDC_BTN_COLORPICKER: { static COLORREF acrCustClr[16]; CHOOSECOLOR cc; ZeroMemory(&cc, sizeof(CHOOSECOLOR)); cc.lStructSize = sizeof(CHOOSECOLOR); cc.hwndOwner = hwnd;//NULL;//hSaverMainWindow; cc.Flags = CC_RGBINIT | CC_FULLOPEN; cc.rgbResult = RGB(m_r,m_g,m_b); cc.lpCustColors = (LPDWORD)acrCustClr; if (ChooseColor(&cc)) { m_r = GetRValue(cc.rgbResult); m_g = GetGValue(cc.rgbResult); m_b = GetBValue(cc.rgbResult); } } break; } break; */ } } else if (nPage==3) { // note: in order to get button #3 to show up, be sure to // give it a name!, in defines.h. /* switch(msg) { case WM_INITDIALOG: break; case WM_DESTROY: break; case WM_HELP: break; } */ } // else if (nPage==4) ... return false; } //----------------------------------------------------------------------