#ifdef DEBUG #define _CRTDBG_MAP_ALLOC #endif #include #include #include #include #include #include #include #include // SPOUT #include // for dpi awareness #pragma comment(lib, "shcore.lib") // for dpi awareness #include "plugin.h" #include "resource.h" #include #include //#include //#include //#include #include "..\audio\common.h" #define DLL_EXPORT __declspec(dllexport) //#define COMPILE_AS_DLL #define SAMPLE_SIZE 576 #define DEFAULT_WIDTH 800; #define DEFAULT_HEIGHT 800; CPlugin g_plugin; HINSTANCE api_orig_hinstance = nullptr; _locale_t g_use_C_locale; char keyMappings[8]; static IDirect3D9* pD3D9 = nullptr; static IDirect3DDevice9* pD3DDevice = nullptr; static D3DPRESENT_PARAMETERS d3dPp; static LONG lastWindowStyle = 0; static LONG lastWindowStyleEx = 0; static bool fullscreen = false; static bool stretch = false; static RECT lastRect = { 0 }; static HMODULE module = nullptr; static std::atomic thread = nullptr; static unsigned threadId = 0; static std::mutex pcmMutex; static unsigned char pcmLeftIn[SAMPLE_SIZE]; static unsigned char pcmRightIn[SAMPLE_SIZE]; static unsigned char pcmLeftOut[SAMPLE_SIZE]; static unsigned char pcmRightOut[SAMPLE_SIZE]; //static musik::core::sdk::IPlaybackService* playback = nullptr; static HICON icon = nullptr; void InitD3d(HWND hwnd, int width, int height) { pD3D9 = Direct3DCreate9(D3D_SDK_VERSION); D3DDISPLAYMODE mode; pD3D9->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &mode); UINT adapterId = g_plugin.m_adapterId; if (adapterId > pD3D9->GetAdapterCount()) { adapterId = D3DADAPTER_DEFAULT; } memset(&d3dPp, 0, sizeof(d3dPp)); d3dPp.BackBufferCount = 1; d3dPp.BackBufferFormat = mode.Format; d3dPp.BackBufferWidth = width; d3dPp.BackBufferHeight = height; d3dPp.SwapEffect = D3DSWAPEFFECT_COPY; d3dPp.Flags = 0; d3dPp.EnableAutoDepthStencil = TRUE; d3dPp.AutoDepthStencilFormat = D3DFMT_D24X8; d3dPp.Windowed = TRUE; d3dPp.PresentationInterval = D3DPRESENT_INTERVAL_ONE; d3dPp.MultiSampleType = D3DMULTISAMPLE_NONE; d3dPp.hDeviceWindow = (HWND) hwnd; pD3D9->CreateDevice( adapterId, D3DDEVTYPE_HAL, (HWND) hwnd, D3DCREATE_HARDWARE_VERTEXPROCESSING, &d3dPp, &pD3DDevice); } void DeinitD3d() { if (pD3DDevice) { pD3DDevice->Release(); pD3DDevice = nullptr; } if (pD3D9) { pD3D9->Release(); pD3D9 = nullptr; } } // Code from milkdropper, thank you! void ToggleStretch(HWND hwnd) { if (!stretch) { int width = GetSystemMetrics(SM_CXVIRTUALSCREEN); int height = GetSystemMetrics(SM_CYVIRTUALSCREEN); int left = GetSystemMetrics(SM_XVIRTUALSCREEN); int top = GetSystemMetrics(SM_YVIRTUALSCREEN); if (!fullscreen) { lastWindowStyle = GetWindowLong(hwnd, GWL_STYLE); lastWindowStyleEx = GetWindowLongW(hwnd, GWL_EXSTYLE); lastWindowStyleEx &= ~WS_EX_TOPMOST; GetWindowRect(hwnd, &lastRect); } d3dPp.BackBufferWidth = width; d3dPp.BackBufferHeight = height; pD3DDevice->Reset(&d3dPp); stretch = false; SetWindowLongW(hwnd, GWL_STYLE, WS_POPUP | WS_VISIBLE); SetWindowLongW(hwnd, GWL_EXSTYLE, WS_EX_APPWINDOW); SetWindowPos(hwnd, HWND_NOTOPMOST, left, top, width, height, SWP_DRAWFRAME | SWP_FRAMECHANGED); stretch = true; } else { ShowCursor(TRUE); int width = lastRect.right - lastRect.left; int height = lastRect.bottom - lastRect.top; d3dPp.BackBufferWidth = width; d3dPp.BackBufferHeight = height; pD3DDevice->Reset(&d3dPp); stretch = false; SetWindowLongW(hwnd, GWL_STYLE, lastWindowStyle); SetWindowLongW(hwnd, GWL_EXSTYLE, lastWindowStyleEx); SetWindowPos(hwnd, HWND_NOTOPMOST, lastRect.left, lastRect.top, width, height, SWP_DRAWFRAME | SWP_FRAMECHANGED); SetWindowPos(hwnd, 0, 0, 0, 0, 0, SWP_DRAWFRAME | SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOZORDER); } fullscreen = false; } void ToggleFullScreen(HWND hwnd) { if (!fullscreen) { ShowCursor(FALSE); if (!stretch) { lastWindowStyle = GetWindowLong(hwnd, GWL_STYLE); lastWindowStyleEx = GetWindowLongW(hwnd, GWL_EXSTYLE); lastWindowStyleEx &= ~WS_EX_TOPMOST; GetWindowRect(hwnd, &lastRect); } HMONITOR monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST); MONITORINFO info; info.cbSize = sizeof(MONITORINFO); GetMonitorInfoW(monitor, &info); int width = info.rcMonitor.right - info.rcMonitor.left; int height = info.rcMonitor.bottom - info.rcMonitor.top; SetWindowLongW(hwnd, GWL_STYLE, WS_POPUP | WS_VISIBLE); SetWindowLongW(hwnd, GWL_EXSTYLE, WS_EX_APPWINDOW); SetWindowPos(hwnd, HWND_TOPMOST, info.rcMonitor.left, info.rcMonitor.top, width, height, SWP_DRAWFRAME | SWP_FRAMECHANGED); d3dPp.BackBufferWidth = width; d3dPp.BackBufferHeight = height; pD3DDevice->Reset(&d3dPp); fullscreen = true; } else { ShowCursor(TRUE); int width = lastRect.right - lastRect.left; int height = lastRect.bottom - lastRect.top; d3dPp.BackBufferWidth = width; d3dPp.BackBufferHeight = height; pD3DDevice->Reset(&d3dPp); fullscreen = false; SetWindowLongW(hwnd, GWL_STYLE, lastWindowStyle); SetWindowLongW(hwnd, GWL_EXSTYLE, lastWindowStyleEx); SetWindowPos(hwnd, HWND_NOTOPMOST, lastRect.left, lastRect.top, width, height, SWP_DRAWFRAME | SWP_FRAMECHANGED); SetWindowPos(hwnd, 0, 0, 0, 0, 0, SWP_DRAWFRAME | SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOZORDER); } stretch = false; } LRESULT CALLBACK StaticWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch(uMsg) { //BeatDrop2077 DoubleClick = fullscreen on/off case WM_LBUTTONDBLCLK: ToggleFullScreen(hWnd); break; case WM_CLOSE: { DestroyWindow( hWnd ); UnregisterClassW(L"Direct3DWindowClass", NULL); return 0; } case WM_DESTROY: { PostQuitMessage(0); break; } case WM_KEYDOWN: { /*if (playback && wParam >= VK_F1 && wParam <= VK_F8) { switch (wParam) { case VK_F1: playback->PauseOrResume(); break; case VK_F2: playback->Stop(); break; case VK_F3: playback->Previous(); break; case VK_F4: playback->Next(); break; case VK_F5: playback->SetVolume(playback->GetVolume() - 0.05); break; case VK_F6: playback->SetVolume(playback->GetVolume() + 0.05); break; case VK_F7: playback->ToggleShuffle(); break; case VK_F8: playback->ToggleMute(); break; } }*/ g_plugin.PluginShellWindowProc(hWnd, uMsg, wParam, lParam); } break; case WM_SYSKEYDOWN: { if (wParam == VK_F4) { PostQuitMessage(0); } else if (wParam == VK_SHIFT) { ToggleStretch(hWnd); } else if (wParam == VK_RETURN) { ToggleFullScreen(hWnd); } else { g_plugin.PluginShellWindowProc(hWnd, uMsg, wParam, lParam); } break; } default: return g_plugin.PluginShellWindowProc(hWnd, uMsg, wParam, lParam); } return DefWindowProc(hWnd, uMsg, wParam, lParam); } void RenderFrame() { { std::unique_lock lock(pcmMutex); memcpy(pcmLeftOut, pcmLeftIn, SAMPLE_SIZE); memcpy(pcmRightOut, pcmRightIn, SAMPLE_SIZE); memset(pcmLeftIn, 0, SAMPLE_SIZE); memset(pcmRightIn, 0, SAMPLE_SIZE); } g_plugin.PluginRender( (unsigned char*) pcmLeftOut, (unsigned char*) pcmRightOut); } unsigned __stdcall CreateWindowAndRun(void* data) { HINSTANCE instance = (HINSTANCE) data; #ifdef DEBUG _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); _CrtSetBreakAlloc(60); #endif // Register the windows class WNDCLASSW wndClass; wndClass.style = CS_DBLCLKS; wndClass.lpfnWndProc = StaticWndProc; wndClass.cbClsExtra = 0; wndClass.cbWndExtra = 0; wndClass.hInstance = instance; wndClass.hIcon = NULL; wndClass.hCursor = LoadCursor(NULL, IDC_ARROW); wndClass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); wndClass.lpszMenuName = NULL; wndClass.lpszClassName = L"Direct3DWindowClass"; // SPOUT // Set Per Monitor awareness SetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE); if (!RegisterClassW(&wndClass)) { DWORD dwError = GetLastError(); if (dwError != ERROR_CLASS_ALREADY_EXISTS) { return 0; } } // SPOUT // make the window a fixed size to and avoid // resolution change for move instead of size int windowWidth = 800; int windowHeight = 800; RECT rc; SetRect(&rc, 0, 0, windowWidth, windowHeight); AdjustWindowRect(&rc, WS_OVERLAPPEDWINDOW, false); // SPOUT // Centre on the desktop work area int WindowPosLeft = 0; int WindowPosTop = 0; RECT WorkArea; SystemParametersInfo(SPI_GETWORKAREA, 0, (LPVOID)&WorkArea, 0); WindowPosLeft += ((WorkArea.right - WorkArea.left) - windowWidth) / 2; WindowPosTop += ((WorkArea.bottom - WorkArea.top) - windowHeight) / 2; // SPOUT // Remove minimize and maximize DWORD dwStyle = (WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME); // Create the render window HWND hwnd = CreateWindowW( L"Direct3DWindowClass", L"MilkDrop 3.0.A - Press F1 for help", // WS_OVERLAPPEDWINDOW, // SPOUT dwStyle, WindowPosLeft, // SPOUT WindowPosTop, // CW_USEDEFAULT, // CW_USEDEFAULT, (rc.right - rc.left), (rc.bottom - rc.top), 0, NULL, instance, 0); if (!hwnd) { DWORD dwError = GetLastError(); return 0; } if (!icon) { icon = LoadIconW(instance, MAKEINTRESOURCEW(IDI_PLUGIN_ICON)); } SendMessageW(hwnd, WM_SETICON, ICON_BIG, (LPARAM) icon); SendMessageW(hwnd, WM_SETICON, ICON_SMALL, (LPARAM) icon); ShowWindow(hwnd, SW_SHOW); // SPOUT // Make output resolution independent of the window size // The user can adjust this subsequently by resizing the BeatBox window // int lastWidth = windowWidth; // int lastHeight = windowHeight; int resolutionWidth = 800; int resolutionHeight = 800; g_plugin.PluginPreInitialize(0, 0); // InitD3d(hwnd, windowWidth, windowHeight); InitD3d(hwnd, resolutionWidth, resolutionHeight); g_plugin.PluginInitialize( pD3DDevice, &d3dPp, hwnd, // windowWidth, // windowHeight); resolutionWidth, resolutionHeight); MSG msg; msg.message = WM_NULL; PeekMessage(&msg, NULL, 0U, 0U, PM_NOREMOVE); while (WM_QUIT != msg.message) { if (PeekMessage(&msg, NULL, 0U, 0U, PM_REMOVE) != 0) { TranslateMessage(&msg); DispatchMessage(&msg); } else { GetAudioBuf(pcmLeftIn, pcmRightIn, SAMPLE_SIZE); RenderFrame(); } } g_plugin.MyWriteConfig(); g_plugin.PluginQuit(); DeinitD3d(); thread = nullptr; threadId = 0; return 1; } void StartRenderThread(HINSTANCE instance) { thread = (HANDLE) _beginthreadex( nullptr, 0, &CreateWindowAndRun, (void *) instance, 0, &threadId); } int StartThreads(HINSTANCE instance) { HRESULT hr = S_OK; hr = CoInitialize(NULL); if (FAILED(hr)) { ERR(L"CoInitialize failed: hr = 0x%08x", hr); return -__LINE__; } CoUninitializeOnExit cuoe; // argc==1 No additional params. Output disabled. // argc==3 Two additional params. Output file enabled (32bit IEEE 754 FLOAT). // argc==4 Three additional params. Output file enabled (LITTLE ENDIAN PCM). int argc = 1; LPCWSTR argv[4] = { L"", L"--file", L"loopback-capture.wav", L"--int-16" }; hr = S_OK; // parse command line CPrefs prefs(argc, argv, hr); if (FAILED(hr)) { ERR(L"CPrefs::CPrefs constructor failed: hr = 0x%08x", hr); return -__LINE__; } if (S_FALSE == hr) { // nothing to do return 0; } // create a "loopback capture has started" event HANDLE hStartedEvent = CreateEvent(NULL, FALSE, FALSE, NULL); if (NULL == hStartedEvent) { ERR(L"CreateEvent failed: last error is %u", GetLastError()); return -__LINE__; } CloseHandleOnExit closeStartedEvent(hStartedEvent); // create a "stop capturing now" event HANDLE hStopEvent = CreateEvent(NULL, FALSE, FALSE, NULL); if (NULL == hStopEvent) { ERR(L"CreateEvent failed: last error is %u", GetLastError()); return -__LINE__; } CloseHandleOnExit closeStopEvent(hStopEvent); // create arguments for loopback capture thread LoopbackCaptureThreadFunctionArguments threadArgs; threadArgs.hr = E_UNEXPECTED; // thread will overwrite this threadArgs.pMMDevice = prefs.m_pMMDevice; threadArgs.bInt16 = prefs.m_bInt16; threadArgs.hFile = prefs.m_hFile; threadArgs.hStartedEvent = hStartedEvent; threadArgs.hStopEvent = hStopEvent; threadArgs.nFrames = 0; HANDLE hThread = CreateThread( NULL, 0, LoopbackCaptureThreadFunction, &threadArgs, 0, NULL ); if (NULL == hThread) { ERR(L"CreateThread failed: last error is %u", GetLastError()); return -__LINE__; } CloseHandleOnExit closeThread(hThread); // wait for either capture to start or the thread to end HANDLE waitArray[2] = { hStartedEvent, hThread }; DWORD dwWaitResult; dwWaitResult = WaitForMultipleObjects( ARRAYSIZE(waitArray), waitArray, FALSE, INFINITE ); if (WAIT_OBJECT_0 + 1 == dwWaitResult) { ERR(L"Thread aborted before starting to loopback capture: hr = 0x%08x", threadArgs.hr); return -__LINE__; } if (WAIT_OBJECT_0 != dwWaitResult) { ERR(L"Unexpected WaitForMultipleObjects return value %u", dwWaitResult); return -__LINE__; } // at this point capture is running // wait for the user to press a key or for capture to error out /*HANDLE thread =*/ StartRenderThread(instance); WaitForSingleObject(thread, INFINITE); //NEED TO STOP CAPTURE // at this point capture is running // wait for the user to press a key or for capture to error out { WaitForSingleObjectOnExit waitForThread(hThread); SetEventOnExit setStopEvent(hStopEvent); HANDLE hStdIn = GetStdHandle(STD_INPUT_HANDLE); if (INVALID_HANDLE_VALUE == hStdIn) { ERR(L"GetStdHandle returned INVALID_HANDLE_VALUE: last error is %u", GetLastError()); return -__LINE__; } LOG(L"%s", L"Press Enter to quit..."); HANDLE rhHandles[2] = { hThread, hStdIn }; bool bKeepWaiting = true; while (bKeepWaiting) { dwWaitResult = WaitForMultipleObjects(2, rhHandles, FALSE, INFINITE); switch (dwWaitResult) { case WAIT_OBJECT_0: // hThread ERR(L"%s", L"The thread terminated early - something bad happened"); bKeepWaiting = false; break; case WAIT_OBJECT_0 + 1: // hStdIn // see if any of them was an Enter key-up event /*INPUT_RECORD rInput[128]; DWORD nEvents; if (!ReadConsoleInput(hStdIn, rInput, ARRAYSIZE(rInput), &nEvents)) { ERR(L"ReadConsoleInput failed: last error is %u", GetLastError()); bKeepWaiting = false; } else { for (DWORD i = 0; i < nEvents; i++) { if ( KEY_EVENT == rInput[i].EventType && VK_RETURN == rInput[i].Event.KeyEvent.wVirtualKeyCode && !rInput[i].Event.KeyEvent.bKeyDown ) {*/ LOG(L"%s", L"Stopping capture..."); bKeepWaiting = false; break; /* } } // if none of them were Enter key-up events, // continue waiting }*/ break; default: ERR(L"WaitForMultipleObjects returned unexpected value 0x%08x", dwWaitResult); bKeepWaiting = false; break; } // switch } // while } // naked scope // at this point the thread is definitely finished DWORD exitCode; if (!GetExitCodeThread(hThread, &exitCode)) { ERR(L"GetExitCodeThread failed: last error is %u", GetLastError()); return -__LINE__; } if (0 != exitCode) { ERR(L"Loopback capture thread exit code is %u; expected 0", exitCode); return -__LINE__; } if (S_OK != threadArgs.hr) { ERR(L"Thread HRESULT is 0x%08x", threadArgs.hr); return -__LINE__; } if (NULL != prefs.m_szFilename) { // everything went well... fixup the fact chunk in the file MMRESULT result = mmioClose(prefs.m_hFile, 0); prefs.m_hFile = NULL; if (MMSYSERR_NOERROR != result) { ERR(L"mmioClose failed: MMSYSERR = %u", result); return -__LINE__; } // reopen the file in read/write mode MMIOINFO mi = { 0 }; prefs.m_hFile = mmioOpenW(const_cast(prefs.m_szFilename), &mi, MMIO_READWRITE); if (NULL == prefs.m_hFile) { ERR(L"mmioOpen(\"%ls\", ...) failed. wErrorRet == %u", prefs.m_szFilename, mi.wErrorRet); return -__LINE__; } // descend into the RIFF/WAVE chunk MMCKINFO ckRIFF = { 0 }; ckRIFF.ckid = MAKEFOURCC('W', 'A', 'V', 'E'); // this is right for mmioDescend result = mmioDescend(prefs.m_hFile, &ckRIFF, NULL, MMIO_FINDRIFF); if (MMSYSERR_NOERROR != result) { ERR(L"mmioDescend(\"WAVE\") failed: MMSYSERR = %u", result); return -__LINE__; } // descend into the fact chunk MMCKINFO ckFact = { 0 }; ckFact.ckid = MAKEFOURCC('f', 'a', 'c', 't'); result = mmioDescend(prefs.m_hFile, &ckFact, &ckRIFF, MMIO_FINDCHUNK); if (MMSYSERR_NOERROR != result) { ERR(L"mmioDescend(\"fact\") failed: MMSYSERR = %u", result); return -__LINE__; } // write the correct data to the fact chunk LONG lBytesWritten = mmioWrite( prefs.m_hFile, reinterpret_cast(&threadArgs.nFrames), sizeof(threadArgs.nFrames) ); if (lBytesWritten != sizeof(threadArgs.nFrames)) { ERR(L"Updating the fact chunk wrote %u bytes; expected %u", lBytesWritten, (UINT32)sizeof(threadArgs.nFrames)); return -__LINE__; } // ascend out of the fact chunk result = mmioAscend(prefs.m_hFile, &ckFact, 0); if (MMSYSERR_NOERROR != result) { ERR(L"mmioAscend(\"fact\") failed: MMSYSERR = %u", result); return -__LINE__; } } // let prefs' destructor call mmioClose return 0; } #ifdef COMPILE_AS_DLL BOOL APIENTRY DllMain(HMODULE hModule, DWORD reason, LPVOID lpReserved) { module = hModule; api_orig_hinstance = hModule; return true; } #else int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { api_orig_hinstance = hInstance; return StartThreads(hInstance); } #endif static std::string title; /* class VisaulizerPlugin : public musik::core::sdk::IPlugin { public: virtual void Release() override { } virtual const char* Name() override { return "Milkdrop2 IPcmVisualizer, IPlaybackRemote"; } virtual const char* Version() override { return "0.5.3"; } virtual const char* Author() override { return "clangen"; } virtual const char* Guid() override { return "5533c371-ed2b-40cf-aabb-f897661aeec1"; } virtual bool Configurable() override { return false; } virtual void Configure() override { } virtual void Reload() override { } virtual int SdkVersion() override { return musik::core::sdk::SdkVersion; } }; class Visualizer : public musik::core::sdk::IPcmVisualizer , public musik::core::sdk::IPlaybackRemote { public: virtual const char* Name() override { return "Milkdrop2"; } virtual void Release() override { this->Hide(); } virtual void SetPlaybackService(musik::core::sdk::IPlaybackService* playback) override { g_plugin.playbackService = playback; ::playback = playback; } virtual void OnTrackChanged(musik::core::sdk::ITrack* track) override { if (track) { char buffer[1024]; track->GetString("title", buffer, 1024); g_plugin.emulatedWinampSongTitle = std::string(buffer); } else { g_plugin.emulatedWinampSongTitle = ""; } } virtual void OnPlaybackStateChanged(musik::core::sdk::PlaybackState state) override { } virtual void OnVolumeChanged(double volume) override { } virtual void OnModeChanged(musik::core::sdk::RepeatMode repeatMode, bool shuffled) override { } virtual void OnPlayQueueChanged() override { } virtual void OnPlaybackTimeChanged(double time) override { } virtual void Write(musik::core::sdk::IBuffer* buffer) override { if (Visible()) { float* b = buffer->BufferPointer(); std::unique_lock lock(pcmMutex); int n = 0; for (int i = 0; i < buffer->Samples(); i++, n++) { int x = i * 2; pcmLeftIn[n % SAMPLE_SIZE] = (unsigned char)(b[i + 0] * 255.0f); pcmRightIn[n % SAMPLE_SIZE] = (unsigned char)(b[i + 1] * 255.0f); } } } virtual void Show() override { if (!Visible()) { StartRenderThread(module); } } virtual void Hide() override { if (Visible()) { PostThreadMessage(threadId, WM_QUIT, 0, 0); WaitForSingleObject(thread, INFINITE); } } virtual bool Visible() override { return thread.load() != nullptr; } }; static VisaulizerPlugin visualizerPlugin; static Visualizer visualizer; extern "C" DLL_EXPORT musik::core::sdk::IPlugin* GetPlugin() { return &visualizerPlugin; } extern "C" DLL_EXPORT musik::core::sdk::IPcmVisualizer* GetPcmVisualizer() { return &visualizer; } extern "C" DLL_EXPORT musik::core::sdk::IPlaybackRemote* GetPlaybackRemote() { return &visualizer; } */ #ifdef DEBUG struct _DEBUG_STATE { _DEBUG_STATE() { } ~_DEBUG_STATE() { _CrtDumpMemoryLeaks(); } }; _DEBUG_STATE ds; #endif