aboutsummaryrefslogtreecommitdiff
path: root/Src/Winamp/VIS.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'Src/Winamp/VIS.cpp')
-rw-r--r--Src/Winamp/VIS.cpp839
1 files changed, 839 insertions, 0 deletions
diff --git a/Src/Winamp/VIS.cpp b/Src/Winamp/VIS.cpp
new file mode 100644
index 00000000..27075d31
--- /dev/null
+++ b/Src/Winamp/VIS.cpp
@@ -0,0 +1,839 @@
+#include <windowsx.h>
+
+#include "Main.h"
+#include "vis.h"
+#include "fft.h"
+#include <bfc/platform/types.h>
+#include <math.h>
+#include <assert.h>
+#include "./api.h"
+#include "../nsutil/window.h"
+#include "../nu/threadname.h"
+
+static winampVisModule *vis_mod;
+static DWORD WINAPI vis_thread(void *tmp);
+static HANDLE hThread;
+static DWORD visThreadId=0;
+static volatile int killThread;
+static int _nch = 2, _numframes=1;
+int _srate = 44100;
+static wchar_t _visplugin_name[512];
+static int _visplugin_num;
+static char *vsa_data;
+static int vsa_position, vsa_entrysize=577*4;
+static int vsa_length,size=576*4;
+static int vis_stopping;
+static CRITICAL_SECTION cs;
+static HWND external_window = NULL;
+static HWND external_window_host = NULL;
+
+#ifdef _M_IX86
+__inline static int lrint(float flt)
+{
+ int intgr;
+
+ _asm
+ {
+ fld flt
+ fistp intgr
+ }
+
+ return intgr;
+}
+#else
+__inline static int lrint(float flt)
+{
+ return (int)flt;
+}
+#endif
+
+// quantizes to 23 bits - use appropriately
+#define FASTMIN(x,b) { x = b - x; x += (float)fabs(x); x *= 0.5f; x = b - x; }
+
+static size_t vis_refCount=0;
+static winampVisModule *GetVis()
+{
+ winampVisModule *ret = 0;
+ EnterCriticalSection(&cs);
+ if (vis_stopping)
+ {
+ LeaveCriticalSection(&cs);
+ return 0;
+ }
+ if (vis_mod)
+ {
+ ret = vis_mod;
+ vis_refCount++;
+ }
+ LeaveCriticalSection(&cs);
+ return ret;
+}
+
+static void DestroyVis()
+{
+ killThread=1;
+ vis_stopping=1;
+
+ if (GetCurrentThreadId() != visThreadId)
+ {
+ HANDLE thisThread = hThread;
+ LeaveCriticalSection(&cs);
+
+ // run message pump. this shouldn't last long.
+ int x=200;
+ while (WaitForSingleObject(thisThread,10) == WAIT_TIMEOUT && x-- > 0)
+ {
+ WASABI_API_APP->app_messageLoopStep();
+ }
+ WaitForSingleObject(thisThread,INFINITE);
+ EnterCriticalSection(&cs);
+ }
+
+ CloseHandle(hThread);
+ if (vis_mod)
+ FreeLibrary(vis_mod->hDllInstance);
+ vis_stopping=0;
+ vis_mod=0;
+
+ hThread=0;
+ killThread=0;
+}
+
+static void ReleaseVis(winampVisModule *vis)
+{
+ if (vis)
+ {
+ EnterCriticalSection(&cs);
+ vis_refCount--;
+ if (vis_refCount == 0)
+ {
+ DestroyVis();
+ }
+
+ LeaveCriticalSection(&cs);
+ }
+}
+
+void vis_init(void)
+{
+ InitializeCriticalSectionAndSpinCount(&cs, 4000);
+}
+
+static char *vsa_get(int timestamp);
+static void vsa_setdatasize();
+
+int vis_running()
+{
+ int running=0;
+ winampVisModule *vis = GetVis();
+ if (vis)
+ {
+ running = !vis_stopping;
+ ReleaseVis(vis);
+ }
+ return running;
+}
+
+static int priorities[5] =
+{
+ THREAD_PRIORITY_LOWEST,
+ THREAD_PRIORITY_BELOW_NORMAL,
+ THREAD_PRIORITY_NORMAL,
+ THREAD_PRIORITY_ABOVE_NORMAL,
+ THREAD_PRIORITY_HIGHEST
+};
+
+void vis_start(HWND hwnd, wchar_t *fn)
+{
+ if (vis_stopping || g_safeMode) return;
+ vis_stop();
+ vsa_deinit();
+ if (!config_visplugin_name[0]) return;
+
+ killThread=0;
+ if (!fn || !*fn)
+ {
+ PathCombineW(_visplugin_name, VISDIR, config_visplugin_name);
+ _visplugin_num=config_visplugin_num;
+ }
+ else
+ {
+ wchar_t buf[MAX_PATH] = {0};
+ wchar_t *p;
+ StringCchCopyW(buf,MAX_PATH,fn);
+ p=wcsstr(buf,L",");
+ if (p)
+ {
+ *p++=0;
+ _visplugin_num=_wtoi(p);
+ }
+ else _visplugin_num=0;
+ if (PathIsFileSpecW(buf) || PathIsRelativeW(buf))
+ PathCombineW(_visplugin_name, VISDIR, buf);
+ else
+ StringCchCopyW(_visplugin_name,512,buf);
+ }
+ hThread = (HANDLE) CreateThread(NULL,0,(LPTHREAD_START_ROUTINE) vis_thread,NULL,0,&visThreadId);
+ SetThreadPriority(hThread,priorities[config_visplugin_priority]);
+}
+
+void vis_setprio()
+{
+ if (vis_stopping) return;
+ if (hThread) SetThreadPriority(hThread,priorities[config_visplugin_priority]);
+}
+
+void vis_stop()
+{
+ if (vis_stopping||!hThread) return;
+ EnterCriticalSection(&cs); // go into critical section so vis_mod doesn't suddenly appear out of nowhere
+ winampVisModule *thisVis = vis_mod;
+ LeaveCriticalSection(&cs);
+ ReleaseVis(thisVis);
+}
+
+void vis_setinfo(int srate, int nch)
+{
+ if (srate > 0) _srate = srate;
+ if (nch > 0) _nch = nch;
+ if (!vis_running()) return;
+ EnterCriticalSection(&cs);
+ winampVisModule *vis = GetVis();
+ if (vis)
+ {
+ vis->sRate = _srate;
+ vis->nCh = _nch;
+ ReleaseVis(vis);
+ }
+ LeaveCriticalSection(&cs);
+}
+
+void vis_setextwindow(HWND hwnd)
+{
+ HWND test_window;
+ unsigned int test_window_style_ex;
+ unsigned long window_thread_id;
+
+ EnterCriticalSection(&cs);
+
+ external_window = hwnd;
+ external_window_host = external_window;
+
+ window_thread_id = GetWindowThreadProcessId(external_window, NULL);
+ while(NULL != external_window_host)
+ {
+ test_window = GetAncestor(external_window_host, GA_PARENT);
+ if (NULL != test_window &&
+ window_thread_id == GetWindowThreadProcessId(test_window, NULL))
+ {
+ test_window_style_ex = (unsigned int)GetWindowLongPtrW(test_window, GWL_STYLE);
+ if (0 != (WS_EX_CONTROLPARENT & test_window_style_ex))
+ {
+ external_window_host = test_window;
+ continue;
+ }
+ }
+ break;
+ }
+
+ LeaveCriticalSection(&cs);
+}
+
+static winampVisModule *CreateVis(HINSTANCE visLib)
+{
+ EnterCriticalSection(&cs);
+ winampVisModule *ret = 0;
+ ret = GetVis();
+ if (ret)
+ {
+ LeaveCriticalSection(&cs);
+ return ret;
+ }
+
+ winampVisGetHeaderType pr;
+ pr = (winampVisGetHeaderType) GetProcAddress(visLib,"winampVisGetHeader");
+ if (!pr)
+ {
+ LeaveCriticalSection(&cs);
+ return 0;
+ }
+
+ winampVisHeader* pv = pr(hMainWindow);
+ if (!pv)
+ {
+ LeaveCriticalSection(&cs);
+ return 0;
+ }
+
+ vis_mod = pv->getModule(_visplugin_num);
+ if (!vis_mod)
+ {
+ LeaveCriticalSection(&cs);
+ return 0;
+ }
+
+ vis_mod->sRate = _srate;
+ vis_mod->nCh = _nch;
+ vis_mod->hwndParent = hMainWindow;
+ vis_mod->hDllInstance = visLib;
+
+ vis_refCount++;
+ LeaveCriticalSection(&cs);
+ return vis_mod;
+}
+
+static BOOL vis_process_message(MSG *msg)
+{
+ if (msg->message >= WM_KEYFIRST && msg->message <= WM_KEYLAST &&
+ msg->hwnd == external_window &&
+ NULL != external_window)
+ {
+ return FALSE;
+ }
+
+ if (WM_MOUSEWHEEL == msg->message &&
+ NULL != external_window_host)
+ {
+ POINT cursor;
+ HWND target_window;
+
+ POINTSTOPOINT(cursor, msg->lParam);
+ target_window = WindowFromPoint(cursor);
+
+ if (NULL != target_window &&
+ FALSE == IsChild(external_window_host, target_window ) &&
+ GetWindowThreadProcessId(target_window, NULL) != GetWindowThreadProcessId(external_window_host, NULL))
+ {
+ PostMessageW(hMainWindow, msg->message, msg->wParam, msg->lParam);
+ return TRUE;
+ }
+
+ }
+
+ if (NULL != external_window_host)
+ return IsDialogMessageW(external_window_host, msg);
+
+ return FALSE;
+}
+
+static DWORD WINAPI vis_thread(void *tmp)
+{
+ winampVisModule *vis = 0;
+ MSG Msg;
+ HINSTANCE hLib=0;
+ int t=0;
+ SetThreadName((DWORD)-1, "Vis (plugin) thread");
+ hLib = LoadLibrary(_visplugin_name);
+ if (!hLib)
+ {
+ t=1;
+ }
+ else
+ {
+ vis = CreateVis(hLib);
+ if (!vis)
+ {
+ FreeLibrary(hLib);
+ hLib = 0;
+ t=1;
+ }
+ }
+
+ if (!t)
+ {
+ if (!(config_no_visseh&1))
+ {
+ __try
+ {
+ t = (vis ? vis->Init(vis) : 1);
+ }
+ __except(EXCEPTION_EXECUTE_HANDLER)
+ {
+ t=1;
+ char errstr[512] = {0};
+ char caption[512] = {0};
+ getString(IDS_PLUGINERROR,errstr,512);
+ StringCchCatA(errstr, 512, " (1)");
+ MessageBoxA(NULL,errstr,getString(IDS_ERROR,caption,512),MB_OK|MB_ICONEXCLAMATION);
+ }
+ }
+ else
+ {
+ t = vis->Init(vis);
+ }
+ }
+
+ if (!t)
+ {
+ if (config_disvis) sa_setthread(0);
+ vsa_setdatasize();
+
+ while (!killThread)
+ {
+ if (PeekMessage(&Msg,NULL,0,0,PM_REMOVE))
+ {
+ if (Msg.message == WM_QUIT)
+ break;
+
+ if (FALSE == vis_process_message(&Msg))
+ {
+ TranslateMessage(&Msg);
+ DispatchMessage(&Msg);
+ }
+ }
+ else if (!paused)
+ {
+ static int upd;
+ int p=playing;
+ char *data=0;
+ if (in_mod && p) data = vsa_get(in_mod->GetOutputTime()+vis->latencyMs);
+ if (data)
+ {
+ int l=vis->spectrumNch;
+ for (int n = 0; n < l; n ++)
+ {
+ memcpy(vis->spectrumData[n],data,576);
+ data += 576;
+ }
+ l=vis->waveformNch;
+ for (int n = 0; n < l; n ++)
+ {
+ memcpy(vis->waveformData[n],data,576);
+ data += 576;
+ }
+ }
+ if (!data)
+ {
+ memset(vis->spectrumData,0,576*2);
+ memset(vis->waveformData,0,576*2);
+ }
+ if (p) upd=1;
+ if (upd)
+ if (!(config_no_visseh&1))
+ {
+ __try
+ {
+ if (vis->Render(vis))
+ {
+ break;
+ }
+ }
+ __except(EXCEPTION_EXECUTE_HANDLER)
+ {
+ char errstr[512] = {0};
+ char caption[512] = {0};
+ getString(IDS_PLUGINERROR,errstr,512);
+ StringCchCatA(errstr, 512, " (2)");
+ MessageBoxA(NULL,errstr,getString(IDS_ERROR,caption,512),MB_OK|MB_ICONEXCLAMATION);
+ break;
+ }
+ }
+ else
+ {
+ if (vis->Render(vis))
+ {
+ break;
+ }
+ }
+ if (!p) upd=0;
+ Sleep(vis->delayMs);
+ }
+ else Sleep(min(1,vis->delayMs));
+ }
+ vsa_deinit();
+ sa_setthread(config_sa);
+ if (!(config_no_visseh&1))
+ {
+ __try
+ {
+ vis->Quit(vis);
+ }
+ __except(EXCEPTION_EXECUTE_HANDLER)
+ {
+ wchar_t errstr[512] = {0};
+ wchar_t caption[512] = {0};
+ getStringW(IDS_PLUGINERROR,errstr,512);
+ StringCchCatW(errstr, 512, L" (3)");
+ MessageBoxW(NULL,errstr,getStringW(IDS_ERROR,caption,512),MB_OK|MB_ICONEXCLAMATION);
+ }
+ }
+ else
+ {
+ vis->Quit(vis);
+ }
+ EnterCriticalSection(&cs);
+ if (killThread)
+ {
+ LeaveCriticalSection(&cs);
+ return 0;
+ }
+ else
+ {
+ ReleaseVis(vis);
+ LeaveCriticalSection(&cs);
+ return 0;
+ }
+ }
+
+ ReleaseVis(vis);
+ if (hLib)
+ FreeLibrary(hLib);
+ hLib = 0;
+ return 0;
+}
+
+static int last_pos;
+
+static void _vsa_init()
+{
+ vsa_deinit();
+ if (_numframes < 1) _numframes=1;
+ vsa_entrysize = 4+size;
+ vsa_data = (char *) GlobalAlloc(GPTR,vsa_entrysize * _numframes);
+ vsa_position=0;
+ vsa_length = _numframes;
+}
+
+void vsa_init(int numframes)
+{
+ EnterCriticalSection(&cs);
+ if (vis_running())
+ {
+ last_pos=0;
+ _numframes=numframes;
+ _vsa_init();
+ }
+ else
+ {
+ last_pos=0;
+ _numframes=numframes;
+ }
+ LeaveCriticalSection(&cs);
+}
+
+static void vsa_setdatasize()
+{
+ EnterCriticalSection(&cs);
+ winampVisModule *vis = GetVis();
+ if (vis)
+ {
+ size=576*(vis->waveformNch+vis->spectrumNch);
+ _vsa_init();
+ ReleaseVis(vis);
+ }
+
+ LeaveCriticalSection(&cs);
+}
+
+int vsa_add(void *data, int timestamp)
+{
+ if (!vsa_data) return 1;
+ EnterCriticalSection(&cs);
+ if (vsa_data) // check again, it might have gone away while we were waiting on the CS
+ {
+ if (vsa_length < 2)
+ {
+ vsa_position = 0;
+ }
+ char *c = vsa_data + vsa_position*vsa_entrysize;
+ *(int32_t *)c=timestamp;
+
+ memcpy(c+4,data,vsa_entrysize-4);
+ if (++vsa_position >= vsa_length) vsa_position -= vsa_length;
+ LeaveCriticalSection(&cs);
+ return 0;
+ }
+ else
+ {
+ LeaveCriticalSection(&cs);
+ return 1;
+ }
+}
+
+void vsa_deinit(void)
+{
+ EnterCriticalSection(&cs);
+ if (vsa_data)
+ {
+ GlobalFree(vsa_data);
+ vsa_data=0;
+ vsa_length=0;
+ }
+ LeaveCriticalSection(&cs);
+}
+
+static char *vsa_get(int timestamp)
+{
+ int i,x, closest=1000000, closest_v = -1;
+ if (!vsa_data) return NULL;
+ if (vsa_length<2)
+ {
+ return vsa_data+4;
+ }
+ EnterCriticalSection(&cs);
+ x=last_pos;
+ if (x >= vsa_length) x=0;
+ for (i = 0; i < vsa_length; i ++)
+ {
+ int *q = (int *)(vsa_data+x*vsa_entrysize);
+ int d = timestamp-*q;
+ if (++x == vsa_length) x=0;
+ if (d < 0) d = -d;
+ if (d < closest)
+ {
+ closest = d;
+ closest_v = x;
+ }
+ else if (closest<200) break;
+ }
+ if (closest_v >= 0)
+ {
+ static char data[576*4];
+ last_pos=closest_v;
+ memcpy(data,vsa_data+vsa_entrysize*closest_v+4,vsa_entrysize-4);
+ LeaveCriticalSection(&cs);
+ return data;
+ }
+ else
+ {
+ LeaveCriticalSection(&cs);
+ return 0;
+ }
+}
+
+int vsa_getmode(int *sp, int *wa)
+{
+ int rv=0;
+ winampVisModule *vis = GetVis();
+ if (vis)
+ {
+ EnterCriticalSection(&cs);
+ *sp=vis->spectrumNch;
+ *wa=vis->waveformNch;
+ rv=1;
+
+ LeaveCriticalSection(&cs);
+ ReleaseVis(vis);
+ }
+ else *sp=*wa=0;
+ return rv;
+}
+
+void FillRealSamples_8Bit(unsigned char *data, const int stride, const int channels, float *samples, const float divider)
+{
+ int frame,c;
+ const float p = (float)channels*divider;
+
+ for (frame = 0; frame <512; frame ++)
+ {
+ //done by memset - samples[x*2]=0;
+ for (c=0;c<channels;c++)
+ {
+ samples[frame] += (float)(*data - 128);
+ data+=stride; // jump to the next sample (channels are interleaved)
+ }
+
+ samples[frame] /= p;
+ //done by memset - wavetrum[x*2+1] = 0.0f;
+ }
+ nsutil_window_Hann_F32_IP(samples, 512);
+}
+
+#define SA_DC_FILTER
+void FillRealSamples(char *ptr, const int stride, const int channels, float *samples, const float divider)
+{
+#ifdef SA_DC_FILTER
+ float x1=0, y1=0;
+#endif
+ int frame, c;
+ const float p=(float)channels * divider;
+
+ // we're calculating using only the most significant byte,
+ // because we only end up with 6 bit data anyway
+ // if you want full resolution, check out CVS tag BETA_2005_1122_182830, file: vis.c
+
+ for (frame = 0;frame <512;frame++)
+ {
+ //done by memset - wavetrum[x*2]=0;
+ float x=0;
+ for (c=0;c<channels;c++)
+ {
+ x += (float)(*ptr);
+ ptr+=stride; // jump to the next sample (channels are interleaved)
+ }
+#ifdef SA_DC_FILTER
+ float y = x - x1 + 0.99f * y1;
+ y1=y;
+ x1=x;
+#else
+ float y=x;
+#endif
+ y/=p;
+
+ samples[frame]=y;
+ //done by memset - wavetrum[x*2+1] = 0.0f;
+ }
+ nsutil_window_Hann_F32_IP(samples, 512);
+}
+
+void vsa_addpcmdata(void *_data_buf, int numChannels, int numBits, int ts)
+{
+ char *data_buf = reinterpret_cast<char *>(_data_buf);
+ // begin vis plugin stuff
+ winampVisModule *vis = GetVis();
+ if (vis)
+ {
+ __declspec(align(32)) float wavetrum[512];
+ extern int vsa_add(void *data, int timestamp);
+ char data[576*4*2] = {0};
+ int data_offs=0;
+ int y,x,spectrumChannels, waveformChannels, stride;
+
+ spectrumChannels=min(numChannels,vis->spectrumNch);
+ stride=numBits/8;
+
+ for (y = 0; y < spectrumChannels; y ++)
+ {
+ if (spectrumChannels == 1) // downmix to mono, if necessary
+ {
+ if (numBits == 8)
+ FillRealSamples_8Bit((unsigned char*)data_buf, 1, numChannels, wavetrum, 1.f);
+ else
+ {
+ const int stride=numBits/8; // number of bytes between samples
+ char *ptr = data_buf+y*stride+stride-1; // offset for little endian
+ FillRealSamples(ptr, stride, numChannels, wavetrum, 1.f);
+ }
+ }
+ else // TODO: deal with 'downmixing' to stereo if channels>2
+ {
+ if (numBits == 8)
+ FillRealSamples_8Bit((unsigned char*)data_buf, numChannels, 1, wavetrum, 1.f);
+ else
+ {
+ const int stride=numBits/8; // number of bytes between samples
+ char *ptr = data_buf+y*stride+stride-1; // offset for little endian
+ FillRealSamples(ptr, stride*numChannels, 1, wavetrum, 1.f);
+ }
+ }
+ fft_9(wavetrum);
+ {
+ float la=0;
+ int thisBand=0;
+
+ for (x = 0; x < 256; x ++)
+ {
+ float sinT = wavetrum[x*2];
+ float cosT = wavetrum[x*2+1];
+
+ float thisValue=(float)sqrt(sinT*sinT+cosT*cosT)/16.0f;
+ thisBand++;
+
+ FASTMIN(thisValue, 255.f);
+ data[data_offs++] = lrint((thisValue + la)/2.f);
+ //data[data_offs++] = lrint((thisValue + thisValue + la)/3.f);
+ data[data_offs++] = lrint(thisValue);
+ la=thisValue;
+ }
+ while ((data_offs % 576)!=0)
+ {
+ la/=2;
+ data[data_offs++]=lrint(la);
+ }
+
+ assert((data_offs % 576)==0);
+ }
+ }
+
+ if (numChannels == 1 && vis->spectrumNch == 2) // upmix, if necessary
+ {
+ memcpy(data+data_offs,data+data_offs-576,576);
+ data_offs+=576;
+ }
+
+ waveformChannels=min(numChannels,vis->waveformNch);
+ if (waveformChannels == 1) // downmix to mono, if necessary
+ {
+ char *ptr = data_buf+stride-1; // offset for little endian
+ for (x=0;x<576;x++)
+ {
+ __int32 mix=0;
+ for (int channel=0;channel<numChannels;channel++)
+ {
+ mix += (*ptr);
+ ptr+=stride; // jump to the next sample (channels are interleaved)
+ }
+ data[data_offs++] = (char)(mix / numChannels);
+ }
+ }
+ else // TODO: deal with 'downmixing' to stereo if numChannels>2
+ {
+ for (y = 0; y < waveformChannels; y++)
+ {
+ char *ptr = data_buf+y*stride+stride-1; // offset for little endian
+ for (x=0;x<576;x++)
+ {
+ data[data_offs++] = *ptr;
+ ptr+=stride*numChannels;
+ }
+ }
+ }
+
+ if (numChannels == 1 && vis->waveformNch == 2)
+ {
+ memcpy(data+data_offs,data+data_offs-576,576);
+ //data_offs+=576;
+ }
+ vsa_add(data,ts);
+ ReleaseVis(vis);
+ }
+}
+
+HWND hVisWindow, hPLVisWindow;
+
+LRESULT CALLBACK VIS_WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
+{
+ switch (message)
+ {
+ case WM_LBUTTONDOWN:
+ case WM_LBUTTONUP:
+ case WM_RBUTTONDOWN:
+ case WM_RBUTTONUP:
+ case WM_LBUTTONDBLCLK:
+ {
+ RECT r1, r2;
+ int xPos = GET_X_LPARAM(lParam); // horizontal position of cursor
+ int yPos = GET_Y_LPARAM(lParam);
+ if (hwnd == hVisWindow)
+ {
+ GetWindowRect(hMainWindow,&r1);
+ }
+ else GetWindowRect(hPLWindow,&r1);
+ GetWindowRect(hwnd,&r2);
+ xPos += r2.left-r1.left;
+ yPos += r2.top-r1.top;
+ lParam = MAKELPARAM(xPos,yPos);
+ SendMessageW(hwnd == hVisWindow?hMainWindow:hPLWindow,message,wParam,lParam);
+ return 0;
+ }
+ case WM_USER+0xebe:
+ case WM_DROPFILES:
+ return SendMessageW(GetParent(hwnd),message,wParam,lParam);
+ case WM_CREATE:
+ if (NULL != WASABI_API_APP)
+ WASABI_API_APP->app_registerGlobalWindow(hwnd);
+ break;
+ case WM_DESTROY:
+ if (NULL != WASABI_API_APP)
+ WASABI_API_APP->app_unregisterGlobalWindow(hwnd);
+ break;
+ }
+
+ if (FALSE != IsDirectMouseWheelMessage(message))
+ {
+ SendMessageW(hwnd, WM_MOUSEWHEEL, wParam, lParam);
+ return TRUE;
+ }
+
+ return DefWindowProcW(hwnd,message,wParam,lParam);
+} \ No newline at end of file