diff --git a/CMakeLists.txt b/CMakeLists.txt index 31b7844f..7c66026e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,6 +24,7 @@ cmake_minimum_required(VERSION 3.21) cmake_policy(SET CMP0091 NEW) +cmake_policy(SET CMP0149 NEW) if (NOT DEFINED CMAKE_OSX_DEPLOYMENT_TARGET) message(STATUS "[clap-wrapper]: OSX_DEPLOYEMNT_TARGET is undefined. Setting to 10.13") set(CMAKE_OSX_DEPLOYMENT_TARGET 10.13 CACHE STRING "Minimum macOS version") diff --git a/cmake/shared_prologue.cmake b/cmake/shared_prologue.cmake index f527ade3..cf179106 100644 --- a/cmake/shared_prologue.cmake +++ b/cmake/shared_prologue.cmake @@ -85,7 +85,7 @@ if(CMAKE_CXX_COMPILER_ID MATCHES "Clang|GNU") endif() if(CMAKE_CXX_COMPILER_ID MATCHES "MSVC") - target_compile_options(clap-wrapper-compile-options INTERFACE /utf-8) + target_compile_options(clap-wrapper-compile-options INTERFACE /utf-8 /Zc:__cplusplus) if (${CMAKE_CXX_STANDARD} GREATER_EQUAL 20) message(STATUS "clap-wrapper: Turning off char8_t c++20 changes") target_compile_options(clap-wrapper-compile-options-public INTERFACE /Zc:char8_t-) diff --git a/cmake/wrap_standalone.cmake b/cmake/wrap_standalone.cmake index 5001cdac..503f68ee 100644 --- a/cmake/wrap_standalone.cmake +++ b/cmake/wrap_standalone.cmake @@ -11,7 +11,7 @@ function(target_add_standalone_wrapper) STATICALLY_LINKED_CLAP_ENTRY HOSTED_CLAP_NAME - WIN32_ICON + WINDOWS_ICON MACOS_EMBEDDED_CLAP_LOCATION ) @@ -37,8 +37,8 @@ function(target_add_standalone_wrapper) set(SA_OUTPUT_NAME ${SA_TARGET}) endif() - if (NOT DEFINED SA_WIN32_ICON) - set(SA_WIN32_ICON "") + if (NOT DEFINED SA_WINDOWS_ICON) + set(SA_WINDOWS_ICON "") endif() guarantee_rtaudio() @@ -102,16 +102,20 @@ function(target_add_standalone_wrapper) MACOS_EMBEDDED_CLAP_LOCATION ${SA_MACOS_EMBEDDED_CLAP_LOCATION}) elseif(WIN32 AND (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC" OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang")) - if(NOT "${SA_WIN32_ICON}" STREQUAL "") - message(STATUS "Win32 icon found: ${SA_WIN32_ICON}") - file(WRITE "${CMAKE_BINARY_DIR}/standalone_win32.rc" "1 ICON \"standalone_win32.ico\"") - file(COPY_FILE ${SA_WIN32_ICON} "${CMAKE_BINARY_DIR}/standalone_win32.ico") - else() - message(STATUS "Win32 icon not found, using default") + guarantee_wil() + + if(NOT "${SA_WINDOWS_ICON}" STREQUAL "") + file(WRITE "${CMAKE_BINARY_DIR}/generated_icons/windows_standalone_${SA_TARGET}.rc" "1 ICON \"windows_standalone_${SA_TARGET}.ico\"") + file(COPY_FILE ${SA_WINDOWS_ICON} "${CMAKE_BINARY_DIR}/generated_icons/windows_standalone_${SA_TARGET}.ico") + target_sources(${SA_TARGET} PRIVATE + "${CMAKE_BINARY_DIR}/generated_icons/windows_standalone_${SA_TARGET}.rc" + ) endif() - set_target_properties(${SA_TARGET} PROPERTIES - WIN32_EXECUTABLE TRUE + target_sources(${SA_TARGET} PRIVATE + "${CLAP_WRAPPER_CMAKE_CURRENT_SOURCE_DIR}/src/wrapasstandalone_windows.cpp" + "${CLAP_WRAPPER_CMAKE_CURRENT_SOURCE_DIR}/src/detail/standalone/windows/windows_standalone.cpp" + "${CLAP_WRAPPER_CMAKE_CURRENT_SOURCE_DIR}/src/detail/standalone/windows/windows_standalone.manifest" ) target_compile_definitions(${salib} PUBLIC @@ -120,23 +124,10 @@ function(target_add_standalone_wrapper) CLAP_WRAPPER_HAS_WIN32 ) - target_sources(${SA_TARGET} PRIVATE - "${CLAP_WRAPPER_CMAKE_CURRENT_SOURCE_DIR}/src/wrapasstandalone_win32.cpp" - "${CLAP_WRAPPER_CMAKE_CURRENT_SOURCE_DIR}/src/detail/standalone/windows/host_window.cpp" - # "${CLAP_WRAPPER_CMAKE_CURRENT_SOURCE_DIR}/src/detail/standalone/windows/settings_window.cpp" - "${CLAP_WRAPPER_CMAKE_CURRENT_SOURCE_DIR}/src/detail/standalone/windows/helpers.cpp" - "${CLAP_WRAPPER_CMAKE_CURRENT_SOURCE_DIR}/src/detail/standalone/windows/standalone.manifest" + set_target_properties(${SA_TARGET} PROPERTIES + WIN32_EXECUTABLE TRUE ) - guarantee_wil() - target_link_libraries(${SA_TARGET} PRIVATE base-sdk-wil) - - if(NOT "${SA_WIN32_ICON}" STREQUAL "") - target_sources(${SA_TARGET} PRIVATE - "${CMAKE_BINARY_DIR}/standalone_win32.rc" - ) - endif() - if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") target_link_options( ${SA_TARGET} @@ -151,6 +142,8 @@ function(target_add_standalone_wrapper) ) endif() + target_link_libraries(${SA_TARGET} PRIVATE base-sdk-wil ComCtl32.Lib) + elseif(UNIX) target_sources(${SA_TARGET} PRIVATE ${CLAP_WRAPPER_CMAKE_CURRENT_SOURCE_DIR}/src/wrapasstandalone.cpp) diff --git a/src/detail/standalone/entry.cpp b/src/detail/standalone/entry.cpp index 26adac52..334076f3 100644 --- a/src/detail/standalone/entry.cpp +++ b/src/detail/standalone/entry.cpp @@ -63,6 +63,7 @@ std::shared_ptr mainCreatePlugin(const clap_plugin_entry *ee, cons try { LOG << "Trying to save default clap wrapper settings" << std::endl; + fs::create_directories(loadPath); standaloneHost->saveStandaloneAndPluginSettings(loadPath, "defaults.clapwrapper"); } catch (const fs::filesystem_error &e) diff --git a/src/detail/standalone/standalone_host.h b/src/detail/standalone/standalone_host.h index 3a2de27f..9c09368c 100644 --- a/src/detail/standalone/standalone_host.h +++ b/src/detail/standalone/standalone_host.h @@ -235,6 +235,8 @@ struct StandaloneHost : Clap::IHost }; ClapWrapper::detail::shared::fixedqueue midiToAudioQueue; std::vector> midiIns; + uint32_t numMidiPorts{0}; + std::vector currentMidiPorts; void startMIDIThread(); void stopMIDIThread(); void processMIDIEvents(double deltatime, std::vector *message); @@ -246,10 +248,15 @@ struct StandaloneHost : Clap::IHost // Actual audio IO In standalone_host_audio.cpp std::unique_ptr rtaDac; std::function displayAudioError{nullptr}; + RtAudio::Api audioApi{RtAudio::Api::UNSPECIFIED}; + std::string audioApiName{RtAudio::getApiName(RtAudio::Api::UNSPECIFIED)}; + std::string audioApiDisplayName{RtAudio::getApiDisplayName(RtAudio::Api::UNSPECIFIED)}; unsigned int audioInputDeviceID{0}, audioOutputDeviceID{0}; bool audioInputUsed{true}, audioOutputUsed{true}; int32_t currentSampleRate{0}; + uint32_t currentBufferSize{0}; void guaranteeRtAudioDAC(); + void setAudioApi(RtAudio::Api api); std::tuple getDefaultAudioInOutSampleRate(); void startAudioThread(); void startAudioThreadOn(unsigned int inputDeviceID, uint32_t inputChannels, bool useInput, @@ -271,8 +278,11 @@ struct StandaloneHost : Clap::IHost void activatePlugin(int32_t sr, int32_t minBlock, int32_t maxBlock); bool isActive{false}; + std::vector getCompiledApi(); std::vector getInputAudioDevices(); std::vector getOutputAudioDevices(); + std::vector getSampleRates(); + std::vector getBufferSizes(); clap_input_events inputEvents{}; clap_output_events outputEvents{}; diff --git a/src/detail/standalone/standalone_host_audio.cpp b/src/detail/standalone/standalone_host_audio.cpp index ab6088ef..d83e0510 100644 --- a/src/detail/standalone/standalone_host_audio.cpp +++ b/src/detail/standalone/standalone_host_audio.cpp @@ -59,11 +59,19 @@ void StandaloneHost::guaranteeRtAudioDAC() if (!rtaDac) { LOG << "Creating RtAudio DAC" << std::endl; - rtaDac = std::make_unique(RtAudio::UNSPECIFIED, &rtaErrorCallback); - rtaDac->showWarnings(true); + setAudioApi(RtAudio::Api::UNSPECIFIED); } } +void StandaloneHost::setAudioApi(RtAudio::Api api) +{ + rtaDac = std::make_unique(api, &rtaErrorCallback); + audioApi = api; + audioApiName = rtaDac->getApiName(api); + audioApiDisplayName = rtaDac->getApiDisplayName(api); + rtaDac->showWarnings(true); +} + std::tuple StandaloneHost::getDefaultAudioInOutSampleRate() { guaranteeRtAudioDAC(); @@ -109,6 +117,16 @@ std::vector filterDevicesBy(const std::unique_ptr return res; } +std::vector StandaloneHost::getCompiledApi() +{ + guaranteeRtAudioDAC(); + + std::vector compiledApi; + rtaDac->getCompiledApi(compiledApi); + + return compiledApi; +} + std::vector StandaloneHost::getInputAudioDevices() { guaranteeRtAudioDAC(); @@ -121,6 +139,31 @@ std::vector StandaloneHost::getOutputAudioDevices() return filterDevicesBy(rtaDac, [](auto &a) { return a.outputChannels > 0; }); } +std::vector StandaloneHost::getSampleRates() +{ + guaranteeRtAudioDAC(); + + std::vector res; + + auto samplesRates{rtaDac->getDeviceInfo(audioInputDeviceID).sampleRates}; + + for (auto &sampleRate : rtaDac->getDeviceInfo(audioInputDeviceID).sampleRates) + { + res.push_back(sampleRate); + } + + return res; +} + +std::vector StandaloneHost::getBufferSizes() +{ + guaranteeRtAudioDAC(); + + std::vector res{16, 32, 48, 64, 96, 128, 144, 160, + 192, 224, 256, 480, 512, 1024, 2048, 4096}; + return res; +} + void StandaloneHost::startAudioThreadOn(unsigned int inputDeviceID, uint32_t inputChannels, bool useInput, unsigned int outputDeviceID, uint32_t outputChannels, bool useOutput, int32_t reqSampleRate) @@ -197,9 +240,14 @@ void StandaloneHost::startAudioThreadOn(unsigned int inputDeviceID, uint32_t inp * this for now at 256 and return to it shortly. */ LOG << "[WARNING] Hardcoding frame size to 256 samples for now" << std::endl; - uint32_t bufferFrames{256}; + + if (currentBufferSize == 0) + { + currentBufferSize = 256; + } + if (rtaDac->openStream((useOutput) ? &oParams : nullptr, (useInput) ? &iParams : nullptr, - RTAUDIO_FLOAT32, sampleRate, &bufferFrames, &rtaCallback, (void *)this, + RTAUDIO_FLOAT32, sampleRate, ¤tBufferSize, &rtaCallback, (void *)this, &options)) { LOG << "[ERROR]" << rtaDac->getErrorText() << std::endl; @@ -207,7 +255,7 @@ void StandaloneHost::startAudioThreadOn(unsigned int inputDeviceID, uint32_t inp return; } - activatePlugin(sampleRate, 1, bufferFrames * 2); + activatePlugin(sampleRate, 1, currentBufferSize * 2); LOG << "RtAudio Attached Devices" << std::endl; if (useOutput) @@ -268,4 +316,4 @@ void StandaloneHost::stopAudioThread() } return; } -} // namespace freeaudio::clap_wrapper::standalone \ No newline at end of file +} // namespace freeaudio::clap_wrapper::standalone diff --git a/src/detail/standalone/standalone_host_midi.cpp b/src/detail/standalone/standalone_host_midi.cpp index 45f6e729..4173e080 100644 --- a/src/detail/standalone/standalone_host_midi.cpp +++ b/src/detail/standalone/standalone_host_midi.cpp @@ -16,12 +16,11 @@ namespace freeaudio::clap_wrapper::standalone { void StandaloneHost::startMIDIThread() { - unsigned int numPorts{0}; try { LOG << "Initializing Midi" << std::endl; auto midiIn = std::make_unique(); - numPorts = midiIn->getPortCount(); + numMidiPorts = midiIn->getPortCount(); } catch (RtMidiError &error) { @@ -29,8 +28,8 @@ void StandaloneHost::startMIDIThread() exit(EXIT_FAILURE); } - LOG << "MIDI: There are " << numPorts << " MIDI input sources available. Binding all.\n"; - for (unsigned int i = 0; i < numPorts; i++) + LOG << "MIDI: There are " << numMidiPorts << " MIDI input sources available. Binding all.\n"; + for (unsigned int i = 0; i < numMidiPorts; i++) { try { @@ -74,4 +73,4 @@ void StandaloneHost::stopMIDIThread() } } -} // namespace freeaudio::clap_wrapper::standalone \ No newline at end of file +} // namespace freeaudio::clap_wrapper::standalone diff --git a/src/detail/standalone/windows/helpers.cpp b/src/detail/standalone/windows/helpers.cpp deleted file mode 100644 index e9fac631..00000000 --- a/src/detail/standalone/windows/helpers.cpp +++ /dev/null @@ -1,214 +0,0 @@ -#include "helpers.h" - -namespace freeaudio::clap_wrapper::standalone::windows::helpers -{ -Size getClientSize(::HWND window) -{ - ::RECT clientRect{}; - ::GetClientRect(window, &clientRect); - - return Size{static_cast(clientRect.right - clientRect.left), - static_cast(clientRect.bottom - clientRect.top)}; -} - -::HMODULE getInstance() -{ - ::HMODULE hInstance; - ::GetModuleHandleExW(0, nullptr, &hInstance); - - return hInstance; -} - -bool activateWindow(::HWND window) -{ - return ::ShowWindow(window, SW_NORMAL); -} - -bool showWindow(::HWND window) -{ - return ::ShowWindow(window, SW_SHOW); -} - -bool hideWindow(::HWND window) -{ - return ::ShowWindow(window, SW_HIDE); -} - -void centerWindow(::HWND window, int width, int height) -{ - ::MONITORINFO mi{sizeof(::MONITORINFO)}; - ::GetMonitorInfoW(::MonitorFromWindow(window, MONITOR_DEFAULTTONEAREST), &mi); - - auto monitorWidth{checkSafeSize(mi.rcWork.right - mi.rcWork.left)}; - auto monitorHeight{checkSafeSize(mi.rcWork.bottom - mi.rcWork.top)}; - - if (monitorWidth > width && monitorHeight > height) - { - auto x{(monitorWidth - width) / 2}; - auto y{(monitorHeight - height) / 2}; - ::SetWindowPos(window, nullptr, x, y, width, height, 0); - } -} - -bool closeWindow(::HWND window) -{ - return ::CloseWindow(window); -} - -bool checkWindowVisibility(::HWND window) -{ - return ::IsWindowVisible(window); -} - -unsigned int getCurrentDpi(::HWND window) -{ - return ::GetDpiForWindow(window); -} - -double getScale(::HWND window) -{ - return static_cast(::GetDpiForWindow(window)) / static_cast(USER_DEFAULT_SCREEN_DPI); -} - -bool startTimer(::HWND window, UINT_PTR timerId, UINT intervalMs) -{ - const auto res = ::SetTimer(window, timerId, intervalMs, (::TIMERPROC)NULL); - return res != 0; -} - -bool stopTimer(::HWND window, UINT_PTR timerId) -{ - return ::KillTimer(window, timerId); -} - -void abort(unsigned int exitCode) -{ - ::ExitProcess(exitCode); -} - -void quit(unsigned int exitCode) -{ - ::PostQuitMessage(exitCode); -} - -int messageLoop() -{ - ::MSG msg{}; - int r{}; - - while ((r = ::GetMessageW(&msg, nullptr, 0, 0)) != 0) - { - if (r == -1) - { - return EXIT_FAILURE; - } - - else - { - ::TranslateMessage(&msg); - ::DispatchMessageW(&msg); - } - } - - return static_cast(msg.wParam); -} - -std::wstring toUTF16(std::string_view utf8) -{ - std::wstring utf16; - - if (utf8.length() > 0) - { - int safeSize{checkSafeSize(utf8.length())}; - - auto length{::MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, utf8.data(), safeSize, nullptr, 0)}; - - utf16.resize(length); - - if (::MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, utf8.data(), safeSize, utf16.data(), - length) == 0) - { - throw std::exception("UTF8 to UTF16 conversion failed"); - } - } - - return utf16; -} - -std::string toUTF8(std::wstring_view utf16) -{ - std::string utf8; - - if (utf16.length() > 0) - { - int safeSize{checkSafeSize(utf16.length())}; - - auto length{::WideCharToMultiByte(CP_UTF8, WC_NO_BEST_FIT_CHARS | WC_ERR_INVALID_CHARS, utf16.data(), - safeSize, nullptr, 0, nullptr, nullptr)}; - - utf8.resize(length); - - if (::WideCharToMultiByte(CP_UTF8, WC_NO_BEST_FIT_CHARS | WC_ERR_INVALID_CHARS, utf16.data(), - safeSize, utf8.data(), length, nullptr, nullptr) == 0) - { - throw std::exception("UTF16 to UTF8 conversion failed"); - } - } - - return utf8; -} - -void log(const std::string& message) -{ - ::OutputDebugStringW(toUTF16(message).c_str()); - ::OutputDebugStringW(L"\n"); -} - -void log(const std::wstring& message) -{ - ::OutputDebugStringW(message.c_str()); - ::OutputDebugStringW(L"\n"); -} - -void messageBox(const std::string& message) -{ - ::MessageBoxW(nullptr, toUTF16(message).c_str(), nullptr, MB_OK | MB_ICONASTERISK); -} - -void messageBox(const std::wstring& message) -{ - ::MessageBoxW(nullptr, message.c_str(), nullptr, MB_OK | MB_ICONASTERISK); -} - -void errorBox(const std::string& message) -{ - ::MessageBoxW(nullptr, toUTF16(message).c_str(), nullptr, MB_OK | MB_ICONHAND); -} - -void errorBox(const std::wstring& message) -{ - ::MessageBoxW(nullptr, message.c_str(), nullptr, MB_OK | MB_ICONHAND); -} - -::HBRUSH loadBrushFromSystem(int name) -{ - return static_cast<::HBRUSH>(::GetStockObject(name)); -} - -::HCURSOR loadCursorFromSystem(LPSTR name) -{ - return static_cast<::HCURSOR>( - ::LoadImageA(nullptr, name, IMAGE_CURSOR, 0, 0, LR_SHARED | LR_DEFAULTSIZE)); -} - -::HICON loadIconFromSystem(LPSTR name) -{ - return static_cast<::HICON>(::LoadImageA(nullptr, name, IMAGE_ICON, 0, 0, LR_SHARED | LR_DEFAULTSIZE)); -} - -::HICON loadIconFromResource() -{ - return static_cast<::HICON>( - ::LoadImageW(getInstance(), MAKEINTRESOURCEW(1), IMAGE_ICON, 0, 0, LR_SHARED | LR_DEFAULTSIZE)); -} -} // namespace freeaudio::clap_wrapper::standalone::windows::helpers diff --git a/src/detail/standalone/windows/helpers.h b/src/detail/standalone/windows/helpers.h deleted file mode 100644 index 72d1a27e..00000000 --- a/src/detail/standalone/windows/helpers.h +++ /dev/null @@ -1,167 +0,0 @@ -#pragma once - -#include -#include - -#include -#include -#include -#include - -#include "detail/standalone/entry.h" - -#define FMT_HEADER_ONLY -#include -#include - -namespace freeaudio::clap_wrapper::standalone::windows::helpers -{ -struct Size -{ - uint32_t width; - uint32_t height; -}; - -Size getClientSize(::HWND window); - -::HMODULE getInstance(); - -bool activateWindow(::HWND window); -bool showWindow(::HWND window); -bool hideWindow(::HWND window); -void centerWindow(::HWND window, int width, int height); -bool closeWindow(::HWND window); -bool checkWindowVisibility(::HWND window); -unsigned int getCurrentDpi(::HWND window); -double getScale(::HWND window); -bool startTimer(::HWND window, UINT_PTR timerId, UINT intervalMs); -bool stopTimer(::HWND window, UINT_PTR timerId); - -void abort(unsigned int exitCode = EXIT_FAILURE); -void quit(unsigned int exitCode = EXIT_SUCCESS); - -int messageLoop(); - -std::wstring toUTF16(std::string_view utf8); -std::string toUTF8(std::wstring_view utf16); - -void log(const std::string& message); -void log(const std::wstring& message); - -void messageBox(const std::string& message); -void messageBox(const std::wstring& message); - -void errorBox(const std::string& message); -void errorBox(const std::wstring& message); - -::HBRUSH loadBrushFromSystem(int name = BLACK_BRUSH); -::HCURSOR loadCursorFromSystem(LPSTR name = IDC_ARROW); -::HICON loadIconFromSystem(LPSTR name = IDI_APPLICATION); -::HICON loadIconFromResource(); - -template -auto createWindow(std::string_view name, T* self) -> ::HWND -{ - auto className{toUTF16(name)}; - auto hInstance{getInstance()}; - auto iconFromResource{loadIconFromResource()}; - - ::WNDCLASSEXW windowClass{sizeof(::WNDCLASSEXW), - 0, - self->wndProc, - 0, - sizeof(intptr_t), - hInstance, - iconFromResource ? iconFromResource : loadIconFromSystem(), - loadCursorFromSystem(), - loadBrushFromSystem(), - nullptr, - className.c_str(), - iconFromResource ? iconFromResource : loadIconFromSystem()}; - - if (::GetClassInfoExW(hInstance, className.c_str(), &windowClass) == 0) - { - ::RegisterClassExW(&windowClass); - } - - return ::CreateWindowExW(0, className.c_str(), className.c_str(), - WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN, CW_USEDEFAULT, CW_USEDEFAULT, - CW_USEDEFAULT, CW_USEDEFAULT, nullptr, nullptr, hInstance, self); -} - -template -T* instanceFromWndProc(HWND hWnd, UINT uMsg, LPARAM lParam) -{ - T* self{nullptr}; - - if (uMsg == WM_NCCREATE) - { - auto lpCreateStruct{reinterpret_cast<::LPCREATESTRUCTW>(lParam)}; - self = static_cast(lpCreateStruct->lpCreateParams); - ::SetWindowLongPtrW(hWnd, 0, reinterpret_cast(self)); - self->m_hWnd.reset(hWnd); - } - - else - { - self = reinterpret_cast(::GetWindowLongPtrW(hWnd, 0)); - } - - return self; -} - -template -auto checkSafeSize(T value) -> R -{ - constexpr R max{std::numeric_limits::max()}; - - if (value > static_cast(max)) - { - throw std::overflow_error("Unsafe size"); - } - - return static_cast(value); -} - -template -auto log(const fmt::format_string fmt, Args&&... args) -> void -{ - ::OutputDebugStringW(toUTF16(fmt::vformat(fmt.get(), fmt::make_format_args(args...))).c_str()); - ::OutputDebugStringW(L"\n"); -} - -template -auto log(const fmt::wformat_string fmt, Args&&... args) -> void -{ - ::OutputDebugStringW(fmt::vformat(fmt.get(), fmt::make_wformat_args(args...)).c_str()); - ::OutputDebugStringW(L"\n"); -} - -template -auto messageBox(const fmt::format_string fmt, Args&&... args) -> void -{ - ::MessageBoxW(nullptr, toUTF16(fmt::vformat(fmt.get(), fmt::make_format_args(args...))).c_str(), - nullptr, MB_OK | MB_ICONASTERISK); -} - -template -auto messageBox(const fmt::wformat_string fmt, Args&&... args) -> void -{ - ::MessageBoxW(nullptr, fmt::vformat(fmt.get(), fmt::make_wformat_args(args...)).c_str(), nullptr, - MB_OK | MB_ICONASTERISK); -} - -template -auto errorBox(const fmt::format_string fmt, Args&&... args) -> void -{ - ::MessageBoxW(nullptr, toUTF16(fmt::vformat(fmt.get(), fmt::make_format_args(args...))).c_str(), - nullptr, MB_OK | MB_ICONHAND); -} - -template -auto errorBox(const fmt::wformat_string fmt, Args&&... args) -> void -{ - ::MessageBoxW(nullptr, fmt::vformat(fmt.get(), fmt::make_wformat_args(args...)).c_str(), nullptr, - MB_OK | MB_ICONHAND); -} -} // namespace freeaudio::clap_wrapper::standalone::windows::helpers diff --git a/src/detail/standalone/windows/host_window.cpp b/src/detail/standalone/windows/host_window.cpp deleted file mode 100644 index e0df17b6..00000000 --- a/src/detail/standalone/windows/host_window.cpp +++ /dev/null @@ -1,341 +0,0 @@ -#include -#include "detail/standalone/entry.h" -#include "host_window.h" -#include "helpers.h" - -#define IDM_SETTINGS 1001 -#define IDM_SAVE_STATE 1002 -#define IDM_LOAD_STATE 1003 -#define IDM_RESET_STATE 1004 - -namespace freeaudio::clap_wrapper::standalone::windows -{ -// We could also just have these at the call-site, but this kinda makes them easier to find. -static constexpr auto s_timerId{0}; -static constexpr auto s_timerIntervalMs{8}; - -HostWindow::HostWindow(std::shared_ptr clapPlugin) - : m_clapPlugin{clapPlugin} - , m_plugin{m_clapPlugin->_plugin} - , m_pluginGui{m_clapPlugin->_ext._gui} - , m_pluginState{m_clapPlugin->_ext._state} -{ - freeaudio::clap_wrapper::standalone::windows::helpers::createWindow(OUTPUT_NAME, this); - - setupMenu(); - - setupStandaloneHost(); - - setupPlugin(); - - helpers::activateWindow(m_hWnd.get()); -} - -void HostWindow::setupMenu() -{ - auto hMenu{::GetSystemMenu(m_hWnd.get(), FALSE)}; - - ::MENUITEMINFOW seperator{sizeof(::MENUITEMINFOW)}; - seperator.fMask = MIIM_FTYPE; - seperator.fType = MFT_SEPARATOR; - - ::MENUITEMINFOW settings{sizeof(::MENUITEMINFOW)}; - settings.fMask = MIIM_STATE | MIIM_STRING | MIIM_ID; - settings.wID = IDM_SETTINGS; - settings.dwTypeData = const_cast(L"Audio/MIDI Settings"); - settings.fState = MFS_DISABLED; - - ::MENUITEMINFOW saveState{sizeof(::MENUITEMINFOW)}; - saveState.fMask = MIIM_STRING | MIIM_ID; - saveState.wID = IDM_SAVE_STATE; - saveState.dwTypeData = const_cast(L"Save plugin state..."); - - ::MENUITEMINFOW loadState{sizeof(::MENUITEMINFOW)}; - loadState.fMask = MIIM_STRING | MIIM_ID; - loadState.wID = IDM_LOAD_STATE; - loadState.dwTypeData = const_cast(L"Load plugin state..."); - - ::MENUITEMINFOW resetState{sizeof(::MENUITEMINFOW)}; - resetState.fMask = MIIM_STRING | MIIM_ID; - resetState.wID = IDM_RESET_STATE; - resetState.dwTypeData = const_cast(L"Reset to default plugin state"); - - if (hMenu != INVALID_HANDLE_VALUE) - { - ::InsertMenuItemW(hMenu, 1, TRUE, &seperator); - ::InsertMenuItemW(hMenu, 2, TRUE, &settings); - ::InsertMenuItemW(hMenu, 3, TRUE, &seperator); - ::InsertMenuItemW(hMenu, 4, TRUE, &saveState); - ::InsertMenuItemW(hMenu, 5, TRUE, &loadState); - ::InsertMenuItemW(hMenu, 6, TRUE, &seperator); - ::InsertMenuItemW(hMenu, 7, TRUE, &resetState); - ::InsertMenuItemW(hMenu, 8, TRUE, &seperator); - } -} - -void HostWindow::setupStandaloneHost() -{ - freeaudio::clap_wrapper::standalone::getStandaloneHost()->onRequestResize = - [this](uint32_t width, uint32_t height) { return setWindowSize(width, height); }; - // Launch our timer - m_isTimerRunning = helpers::startTimer(m_hWnd.get(), s_timerId, s_timerIntervalMs); - if (!m_isTimerRunning) - { - TRACE; // maybe - } -} - -void HostWindow::setupPlugin() -{ - m_pluginGui->create(m_plugin, CLAP_WINDOW_API_WIN32, false); - - m_pluginGui->set_scale(m_plugin, helpers::getScale(m_hWnd.get())); - - uint32_t width{0}; - uint32_t height{0}; - - if (m_pluginGui->can_resize(m_plugin)) - { - // We can restore size here - // setWindowSize(previousWidth, previousHeight); - } - else - { - // We can't resize, so disable WS_THICKFRAME and WS_MAXIMIZEBOX - ::SetWindowLongPtrW(m_hWnd.get(), GWL_STYLE, - ::GetWindowLongPtrW(m_hWnd.get(), GWL_STYLE) & ~WS_OVERLAPPEDWINDOW | - WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX); - } - - m_pluginGui->get_size(m_plugin, &width, &height); - - setWindowSize(width, height); - - clap_window clapWindow{CLAP_WINDOW_API_WIN32, static_cast(m_hWnd.get())}; - - m_pluginGui->set_parent(m_plugin, &clapWindow); -} - -bool HostWindow::setWindowSize(uint32_t width, uint32_t height) -{ - ::RECT rect{0, 0, static_cast(width), static_cast(height)}; - - ::AdjustWindowRectExForDpi(&rect, static_cast<::DWORD>(::GetWindowLongPtrW(m_hWnd.get(), GWL_STYLE)), - ::GetMenu(m_hWnd.get()) != nullptr, - static_cast<::DWORD>(::GetWindowLongPtrW(m_hWnd.get(), GWL_EXSTYLE)), - helpers::getCurrentDpi(m_hWnd.get())); - - ::SetWindowPos(m_hWnd.get(), nullptr, 0, 0, (rect.right - rect.left), (rect.bottom - rect.top), - SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE); - - return true; -} - -LRESULT CALLBACK HostWindow::wndProc(::HWND hWnd, ::UINT uMsg, ::WPARAM wParam, ::LPARAM lParam) -{ - auto self{helpers::instanceFromWndProc(hWnd, uMsg, lParam)}; - - if (self) - { - switch (uMsg) - { - case WM_DPICHANGED: - return self->onDpiChanged(); - case WM_WINDOWPOSCHANGED: - return self->onWindowPosChanged(lParam); - case WM_SYSCOMMAND: - return self->onSysCommand(hWnd, uMsg, wParam, lParam); - case WM_DESTROY: - return self->onDestroy(); - case WM_TIMER: - return self->onTimerEvent(wParam); - } - } - - return ::DefWindowProcW(hWnd, uMsg, wParam, lParam); -} - -int HostWindow::onDpiChanged() -{ - m_pluginGui->set_scale(m_plugin, helpers::getScale(m_hWnd.get())); - - return 0; -} - -int HostWindow::onWindowPosChanged(::LPARAM lParam) -{ - auto windowPos{reinterpret_cast<::LPWINDOWPOS>(lParam)}; - - if (windowPos->flags & SWP_SHOWWINDOW) - { - m_pluginGui->show(m_plugin); - } - - if (windowPos->flags & SWP_HIDEWINDOW) - { - m_pluginGui->hide(m_plugin); - } - - if (m_pluginGui->can_resize(m_plugin)) - { - auto size{helpers::getClientSize(m_hWnd.get())}; - - m_pluginGui->adjust_size(m_plugin, &size.width, &size.height); - m_pluginGui->set_size(m_plugin, size.width, size.height); - } - - return 0; -} - -int HostWindow::onSysCommand(::HWND hWnd, ::UINT uMsg, ::WPARAM wParam, ::LPARAM lParam) -{ - switch (wParam) - { - case IDM_SETTINGS: - { - // Disabled until settings window is finished - // helpers::checkWindowVisibility(m_settingsWindow.m_hWnd.get()) - // ? helpers::hideWindow(m_settingsWindow.m_hWnd.get()) - // : helpers::showWindow(m_settingsWindow.m_hWnd.get()); - - return 0; - } - - case IDM_SAVE_STATE: - { - auto coUninitialize{wil::CoInitializeEx(COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE)}; - - auto fileSaveDialog{wil::CoCreateInstance(CLSID_FileSaveDialog)}; - - fileSaveDialog->SetDefaultExtension(m_fileTypes.at(0).pszName); - fileSaveDialog->SetFileTypes(static_cast(m_fileTypes.size()), m_fileTypes.data()); - - fileSaveDialog->Show(m_hWnd.get()); - - wil::com_ptr shellItem; - auto hr{fileSaveDialog->GetResult(&shellItem)}; - - if (SUCCEEDED(hr)) - { - wil::unique_cotaskmem_string result; - shellItem->GetDisplayName(SIGDN_FILESYSPATH, &result); - - auto saveFile{std::filesystem::path(result.get())}; - - helpers::log("{}", saveFile.string()); - - try - { - freeaudio::clap_wrapper::standalone::getStandaloneHost()->saveStandaloneAndPluginSettings( - saveFile.parent_path(), saveFile.filename()); - } - catch (const fs::filesystem_error& e) - { - helpers::errorBox("Unable to save state: {}", e.what()); - } - } - - return 0; - } - - case IDM_LOAD_STATE: - { - auto coUninitialize{wil::CoInitializeEx(COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE)}; - - auto fileOpenDialog{wil::CoCreateInstance(CLSID_FileOpenDialog)}; - - fileOpenDialog->SetDefaultExtension(m_fileTypes.at(0).pszName); - fileOpenDialog->SetFileTypes(static_cast(m_fileTypes.size()), m_fileTypes.data()); - - fileOpenDialog->Show(m_hWnd.get()); - - wil::com_ptr shellItem; - auto hr{fileOpenDialog->GetResult(&shellItem)}; - - if (SUCCEEDED(hr)) - { - wil::unique_cotaskmem_string result; - shellItem->GetDisplayName(SIGDN_FILESYSPATH, &result); - - auto saveFile{std::filesystem::path(result.get())}; - - helpers::log("{}", saveFile.string()); - - try - { - if (fs::exists(saveFile)) - { - freeaudio::clap_wrapper::standalone::getStandaloneHost()->tryLoadStandaloneAndPluginSettings( - saveFile.parent_path(), saveFile.filename()); - } - } - catch (const fs::filesystem_error& e) - { - helpers::errorBox("Unable to load state: {}", e.what()); - } - } - - return 0; - } - - case IDM_RESET_STATE: - { - auto pt{freeaudio::clap_wrapper::standalone::getStandaloneSettingsPath()}; - - if (pt.has_value()) - { - auto loadPath{*pt / m_plugin->desc->id}; - - try - { - if (fs::exists(loadPath / "defaults.clapwrapper")) - { - freeaudio::clap_wrapper::standalone::getStandaloneHost()->tryLoadStandaloneAndPluginSettings( - loadPath, "defaults.clapwrapper"); - } - } - catch (const fs::filesystem_error& /* e */) - { - // - } - } - - return 0; - } - } - - ::DefWindowProcW(hWnd, uMsg, wParam, lParam); - - return 0; -} - -int HostWindow::onDestroy() -{ - m_pluginGui->destroy(m_plugin); - if (m_isTimerRunning) - { - if (!helpers::stopTimer(m_hWnd.get(), s_timerId)) - { - // We can grab the error with ::GetLastError, and format it with ::FormatMessage - but not sure if that's overkill - TRACE; - } - } - - freeaudio::clap_wrapper::standalone::mainFinish(); - - helpers::quit(); - - return 0; -} - -int HostWindow::onTimerEvent(::WPARAM wParam) -{ - if (wParam != s_timerId) return -1; // or assert fail, etc - auto* standaloneHost = freeaudio::clap_wrapper::standalone::getStandaloneHost(); - if (standaloneHost->callbackRequested.exchange(false)) - { - m_plugin->on_main_thread(m_plugin); - } - return 0; -} -} // namespace freeaudio::clap_wrapper::standalone::windows diff --git a/src/detail/standalone/windows/host_window.h b/src/detail/standalone/windows/host_window.h deleted file mode 100644 index 4fab36c1..00000000 --- a/src/detail/standalone/windows/host_window.h +++ /dev/null @@ -1,39 +0,0 @@ -#pragma once - -#include -#include -#include - -#include "detail/standalone/standalone_host.h" -// #include "settings_window.h" - -namespace freeaudio::clap_wrapper::standalone::windows -{ -struct HostWindow -{ - HostWindow(std::shared_ptr plugin); - - void setupMenu(); - void setupStandaloneHost(); - void setupPlugin(); - - bool setWindowSize(uint32_t width, uint32_t height); - - static LRESULT CALLBACK wndProc(::HWND hWnd, ::UINT uMsg, ::WPARAM wParam, ::LPARAM lParam); - int onDpiChanged(); - int onWindowPosChanged(::LPARAM lParam); - int onSysCommand(::HWND hWnd, ::UINT uMsg, ::WPARAM wParam, ::LPARAM lParam); - int onDestroy(); - int onTimerEvent(::WPARAM wParam); - std::shared_ptr m_clapPlugin; - const clap_plugin_t* m_plugin; - const clap_plugin_gui_t* m_pluginGui; - const clap_plugin_state_t* m_pluginState; - freeaudio::clap_wrapper::standalone::StandaloneHost* m_standaloneHost; - std::vector m_fileTypes{{L"clapwrapper", L"*.clapwrapper"}}; - bool m_isTimerRunning{false}; - - // SettingsWindow m_settingsWindow; - wil::unique_hwnd m_hWnd; -}; -} // namespace freeaudio::clap_wrapper::standalone::windows diff --git a/src/detail/standalone/windows/settings_window.cpp b/src/detail/standalone/windows/settings_window.cpp deleted file mode 100644 index 5961de9d..00000000 --- a/src/detail/standalone/windows/settings_window.cpp +++ /dev/null @@ -1,63 +0,0 @@ -#include - -#include "detail/standalone/standalone_details.h" -#include "settings_window.h" -#include "helpers.h" - -#define ID_COMBOBOX1 101 -#define ID_COMBOBOX2 102 -#define ID_COMBOBOX3 103 -#define ID_BUTTON1 104 -#define ID_BUTTON2 105 - -namespace freeaudio::clap_wrapper::standalone::windows -{ -SettingsWindow::SettingsWindow() -{ - freeaudio::clap_wrapper::standalone::windows::helpers::createWindow("Audio/MIDI Settings", this); - - helpers::centerWindow(m_hWnd.get(), 400, 360); -} - -LRESULT CALLBACK SettingsWindow::wndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) -{ - auto self{helpers::instanceFromWndProc(hWnd, uMsg, lParam)}; - - if (self) - { - switch (uMsg) - { - case WM_CREATE: - return self->onCreate(); - case WM_CLOSE: - return self->onClose(); - } - } - - return ::DefWindowProcW(hWnd, uMsg, wParam, lParam); -} - -int SettingsWindow::onCreate() -{ - CreateWindow("COMBOBOX", NULL, WS_CHILD | WS_VISIBLE | CBS_DROPDOWNLIST, 10, 10, 150, 100, - m_hWnd.get(), (HMENU)ID_COMBOBOX1, NULL, NULL); - CreateWindow("COMBOBOX", NULL, WS_CHILD | WS_VISIBLE | CBS_DROPDOWNLIST, 10, 50, 150, 100, - m_hWnd.get(), (HMENU)ID_COMBOBOX2, NULL, NULL); - CreateWindow("COMBOBOX", NULL, WS_CHILD | WS_VISIBLE | CBS_DROPDOWNLIST, 10, 90, 150, 100, - m_hWnd.get(), (HMENU)ID_COMBOBOX3, NULL, NULL); - - CreateWindow("BUTTON", "Button 1", WS_CHILD | WS_VISIBLE, 10, 130, 80, 30, m_hWnd.get(), - (HMENU)ID_BUTTON1, NULL, NULL); - CreateWindow("BUTTON", "Button 2", WS_CHILD | WS_VISIBLE, 100, 130, 80, 30, m_hWnd.get(), - (HMENU)ID_BUTTON2, NULL, NULL); - - return 0; -} - -int SettingsWindow::onClose() -{ - helpers::hideWindow(m_hWnd.get()); - - return 0; -} -} // namespace freeaudio::clap_wrapper::standalone::windows diff --git a/src/detail/standalone/windows/settings_window.h b/src/detail/standalone/windows/settings_window.h deleted file mode 100644 index 89508059..00000000 --- a/src/detail/standalone/windows/settings_window.h +++ /dev/null @@ -1,18 +0,0 @@ -#pragma once - -#include -#include - -namespace freeaudio::clap_wrapper::standalone::windows -{ -struct SettingsWindow -{ - SettingsWindow(); - - static LRESULT CALLBACK wndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); - int onCreate(); - int onClose(); - - wil::unique_hwnd m_hWnd; -}; -} // namespace freeaudio::clap_wrapper::standalone::windows diff --git a/src/detail/standalone/windows/windows_standalone.cpp b/src/detail/standalone/windows/windows_standalone.cpp new file mode 100644 index 00000000..6acfd415 --- /dev/null +++ b/src/detail/standalone/windows/windows_standalone.cpp @@ -0,0 +1,1387 @@ +#include "windows_standalone.h" + +namespace freeaudio::clap_wrapper::standalone::windows_standalone +{ +::HMODULE getInstance() +{ + ::HMODULE module; + ::GetModuleHandleExW( + GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT | GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, + (LPCWSTR)&getInstance, &module); + + return module; +} + +::HFONT getFontFromSystem(int name) +{ + return static_cast<::HFONT>(::GetStockObject(name)); +} + +::HFONT getScaledFontFromSystem(double scale) +{ + ::LOGFONTW fontAttributes{}; + ::GetObjectW(getFontFromSystem(), sizeof(fontAttributes), &fontAttributes); + fontAttributes.lfHeight = fontAttributes.lfHeight * scale; + + return ::CreateFontIndirectW(&fontAttributes); +} + +::HBRUSH getBrushFromSystem(int name) +{ + return static_cast<::HBRUSH>(::GetStockObject(name)); +} + +::HCURSOR getCursorFromSystem(::LPCSTR name) +{ + return static_cast<::HCURSOR>( + ::LoadImageA(nullptr, name, IMAGE_CURSOR, 0, 0, LR_SHARED | LR_DEFAULTSIZE)); +} + +::HICON getIconFromSystem(::LPCSTR name) +{ + return static_cast<::HICON>(::LoadImageA(nullptr, name, IMAGE_ICON, 0, 0, LR_SHARED | LR_DEFAULTSIZE)); +} + +::HICON getIconFromResource() +{ + return static_cast<::HICON>( + ::LoadImageW(getInstance(), MAKEINTRESOURCEW(1), IMAGE_ICON, 0, 0, LR_SHARED | LR_DEFAULTSIZE)); +} + +::HMENU getSystemMenu(::HWND hwnd) +{ + return ::GetSystemMenu(hwnd, FALSE); +} + +std::wstring toUTF16(std::string_view input) +{ + std::wstring output; + + if (input.length() > 0) + { + if (input.length() < std::numeric_limits::max()) + { + auto length{static_cast(input.length())}; + + length = ::MultiByteToWideChar(CP_UTF8, 0, input.data(), length, nullptr, 0); + + output.resize(length); + + if (::MultiByteToWideChar(CP_UTF8, 0, input.data(), length, output.data(), length) == 0) + { + log("toUTF16(): {}", getLastError()); + } + } + else + { + log("toUTF16(): String too long"); + } + } + + return output; +} + +std::string toUTF8(std::wstring_view input) +{ + std::string output; + + if (input.length() > 0) + { + if (input.length() < std::numeric_limits::max()) + { + auto length{static_cast(input.length())}; + + length = ::WideCharToMultiByte(CP_UTF8, 0, input.data(), length, nullptr, 0, nullptr, nullptr); + + output.resize(length); + + if (::WideCharToMultiByte(CP_UTF8, 0, input.data(), length, output.data(), length, nullptr, + nullptr) == 0) + { + log("toUTF8(): {}", getLastError()); + } + } + else + { + log("toUTF8(): String too long"); + } + } + + return output; +} + +std::string formatMessage(::HRESULT errorCode) +{ + wil::unique_hlocal_string buffer; + + ::FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_MAX_WIDTH_MASK, + nullptr, errorCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + wil::out_param_ptr<::LPWSTR>(buffer), 0, nullptr); + + return toUTF8(buffer.get()); +} + +std::string getLastError() +{ + return formatMessage(::GetLastError()); +} + +void log(const std::string& message) +{ + ::OutputDebugStringW(toUTF16(message).c_str()); + ::OutputDebugStringW(L"\n"); +} + +void log(const std::wstring& message) +{ + ::OutputDebugStringW(message.c_str()); + ::OutputDebugStringW(L"\n"); +} + +int run() +{ + ::MSG msg{}; + int r{}; + + while ((r = ::GetMessageW(&msg, nullptr, 0, 0)) != 0) + { + if (r == -1) + { + return EXIT_FAILURE; + } + + else + { + ::TranslateMessage(&msg); + ::DispatchMessageW(&msg); + } + } + + return static_cast(msg.wParam); +} + +int abort(int exitCode) +{ + ::ExitProcess(exitCode); + + return exitCode; +} + +int quit(int exitCode) +{ + ::PostQuitMessage(exitCode); + + return exitCode; +} + +bool MessageHandler::on(::UINT msg, MessageCallback callback) +{ + auto emplace{map.try_emplace(msg, callback)}; + + return emplace.second; +} + +bool MessageHandler::contains(::UINT msg) +{ + return (map.find(msg) != map.end()); +} + +::LRESULT MessageHandler::invoke(Message message) +{ + return map.find(message.msg)->second({message.hwnd, message.msg, message.wparam, message.lparam}); +} + +void MessageHandler::box(const std::string& message) +{ + ::MessageBoxW(nullptr, toUTF16(message).c_str(), nullptr, MB_OK | MB_ICONASTERISK); +} + +void MessageHandler::box(const std::wstring& message) +{ + ::MessageBoxW(nullptr, message.c_str(), nullptr, MB_OK | MB_ICONASTERISK); +} + +void MessageHandler::error(const std::string& message) +{ + ::MessageBoxW(nullptr, toUTF16(message).c_str(), nullptr, MB_OK | MB_ICONHAND); +} + +void MessageHandler::error(const std::wstring& message) +{ + ::MessageBoxW(nullptr, message.c_str(), nullptr, MB_OK | MB_ICONHAND); +} + +void Window::create(const std::string& title) +{ + std::wstring className{L"Window"}; + + auto instance{getInstance()}; + auto resourceIcon{getIconFromResource()}; + auto systemIcon{getIconFromSystem()}; + auto systemCursor{getCursorFromSystem()}; + auto systemBrush{getBrushFromSystem()}; + + WNDCLASSEXW windowClass{sizeof(::WNDCLASSEXW)}; + windowClass.style = 0; + windowClass.lpfnWndProc = procedure; + windowClass.cbClsExtra = 0; + windowClass.cbWndExtra = sizeof(void*); + windowClass.hInstance = instance; + windowClass.hIcon = resourceIcon ? resourceIcon : systemIcon; + windowClass.hCursor = systemCursor; + windowClass.hbrBackground = systemBrush; + windowClass.lpszMenuName = nullptr; + windowClass.lpszClassName = className.c_str(); + windowClass.hIconSm = resourceIcon ? resourceIcon : systemIcon; + + if (::GetClassInfoExW(instance, className.c_str(), &windowClass) == 0) + { + ::RegisterClassExW(&windowClass); + } + + ::CreateWindowExW(0, className.c_str(), toUTF16(title).c_str(), WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN, + CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, nullptr, nullptr, + instance, this); +} + +::LRESULT CALLBACK Window::procedure(::HWND hwnd, ::UINT msg, ::WPARAM wparam, ::LPARAM lparam) +{ + if (msg == WM_NCCREATE) + { + auto create{reinterpret_cast<::CREATESTRUCTW*>(lparam)}; + + if (auto self{static_cast(create->lpCreateParams)}; self) + { + ::SetWindowLongPtrW(hwnd, 0, reinterpret_cast<::LONG_PTR>(self)); + self->hwnd.reset(hwnd); + self->dpi = static_cast(::GetDpiForWindow(hwnd)); + self->scale = (static_cast(self->dpi) / static_cast(USER_DEFAULT_SCREEN_DPI)); + } + } + + if (auto self{reinterpret_cast(::GetWindowLongPtrW(hwnd, 0))}; self) + { + if (msg == WM_NCDESTROY) + { + ::SetWindowLongPtrW(hwnd, 0, reinterpret_cast<::LONG_PTR>(nullptr)); + } + + if (msg == WM_WINDOWPOSCHANGED) + { + auto windowPos{reinterpret_cast<::LPWINDOWPOS>(lparam)}; + + self->window.x = windowPos->x; + self->window.y = windowPos->y; + self->window.width = windowPos->cx; + self->window.height = windowPos->cy; + + ::GetWindowPlacement(hwnd, &self->placement); + + ::RECT rect{}; + ::GetClientRect(hwnd, &rect); + + self->client.x = rect.left; + self->client.y = rect.top; + self->client.width = rect.right - rect.left; + self->client.height = rect.bottom - rect.top; + + ::MONITORINFO mi{sizeof(::MONITORINFO)}; + ::GetMonitorInfoW(::MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST), &mi); + + self->monitor.x = mi.rcWork.left; + self->monitor.y = mi.rcWork.top; + self->monitor.width = mi.rcWork.right - mi.rcWork.left; + self->monitor.height = mi.rcWork.bottom - mi.rcWork.top; + } + + if (msg == WM_DPICHANGED) + { + auto rect{reinterpret_cast<::LPRECT>(lparam)}; + + self->suggested.x = rect->left; + self->suggested.y = rect->top; + self->suggested.width = rect->left - rect->right; + self->suggested.height = rect->bottom - rect->top; + + self->dpi = static_cast(::GetDpiForWindow(hwnd)); + self->scale = (static_cast(self->dpi) / static_cast(USER_DEFAULT_SCREEN_DPI)); + } + + if (self->message.contains(msg)) + { + return self->message.invoke({hwnd, msg, wparam, lparam}); + } + + if (msg == WM_CLOSE) + { + self->hwnd.reset(); + + return 0; + } + } + + return ::DefWindowProcW(hwnd, msg, wparam, lparam); +} + +void Window::activate() +{ + ::ShowWindow(hwnd.get(), SW_NORMAL); +} + +void Window::show() +{ + ::ShowWindow(hwnd.get(), SW_SHOW); +} + +void Window::hide() +{ + ::ShowWindow(hwnd.get(), SW_HIDE); +} + +bool Window::isVisible() +{ + return ::IsWindowVisible(hwnd.get()); +} + +void Window::adjustSize(uint32_t width, uint32_t height) +{ + ::RECT rect{}; + rect.left = 0; + rect.top = 0; + rect.right = width; + rect.bottom = height; + + ::AdjustWindowRectExForDpi(&rect, static_cast<::DWORD>(::GetWindowLongPtrW(hwnd.get(), GWL_STYLE)), + ::GetMenu(hwnd.get()) != nullptr, + static_cast<::DWORD>(::GetWindowLongPtrW(hwnd.get(), GWL_EXSTYLE)), dpi); + + ::SetWindowPos(hwnd.get(), nullptr, 0, 0, (rect.right - rect.left), (rect.bottom - rect.top), + SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE); +} + +void Window::setPosition(Position position) +{ + ::SetWindowPos(hwnd.get(), nullptr, position.x, position.y, position.width, position.height, + SWP_NOZORDER | SWP_NOACTIVATE); +} + +void Window::setStyle(::LONG_PTR style) +{ + ::SetWindowLongPtrW(hwnd.get(), GWL_STYLE, style); +} + +void Window::toggleCentered(bool centered) +{ + restore = window; + + if (centered) + { + if (monitor.width > window.width && monitor.height > window.height) + { + auto x{static_cast((monitor.width - window.width) / 2)}; + auto y{static_cast((monitor.height - window.height) / 2)}; + + ::SetWindowPos(hwnd.get(), nullptr, x, y, 0, 0, SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOSIZE); + } + } + else + { + setPosition(restore); + } +} + +void Window::toggleTopmost(bool topmost) +{ + ::SetWindowPos(hwnd.get(), topmost ? HWND_TOPMOST : HWND_NOTOPMOST, 0, 0, 0, 0, + SWP_NOMOVE | SWP_NOSIZE); +} + +void Window::repaint() +{ + ::RECT r{}; + ::GetClientRect(hwnd.get(), &r); + ::InvalidateRect(hwnd.get(), &r, true); +} + +bool Window::startTimer(::UINT_PTR timerId, ::UINT intervalMs) +{ + return ::SetTimer(hwnd.get(), timerId, intervalMs, nullptr) != 0 ? true : false; +} + +bool Window::stopTimer(::UINT_PTR timerId) +{ + return ::KillTimer(hwnd.get(), timerId); +} + +void Control::setPosition(uint32_t x, uint32_t y, uint32_t width, uint32_t height) +{ + position.x = x; + position.y = y; + position.width = width; + position.height = height; + + ::SetWindowPos(hwnd.get(), nullptr, position.x, position.y, position.width, position.height, + SWP_NOZORDER | SWP_NOACTIVATE); +} + +void Control::refreshFont(double scale) +{ + font.reset(getScaledFontFromSystem(scale)); + message.send(hwnd.get(), WM_SETFONT, font.get(), TRUE); +} + +void ComboBox::create(const std::string& name, uintptr_t id, ::HWND parentHwnd) +{ + hwnd.reset(::CreateWindowExW(0, toUTF16(WC_COMBOBOX).c_str(), toUTF16(name).c_str(), + WS_CHILD | WS_VISIBLE | CBS_DROPDOWNLIST, 0, 0, 0, 0, parentHwnd, + (::HMENU)id, getInstance(), nullptr)); + + ::SetWindowSubclass(hwnd.get(), &procedure, id, reinterpret_cast<::DWORD_PTR>(this)); + + message.send(hwnd.get(), WM_SETFONT, getFontFromSystem(), TRUE); +} + +void ComboBox::reset() +{ + message.send(hwnd.get(), CB_RESETCONTENT, 0, 0); +} + +void ComboBox::add(const std::string& string) +{ + message.send(hwnd.get(), CB_ADDSTRING, 0, toUTF16(string).c_str()); +} + +bool ComboBox::set(int index) +{ + return message.send(hwnd.get(), CB_SETCURSEL, index, 0) != CB_ERR ? true : false; +} + +bool ComboBox::set(const std::string& searchString) +{ + return message.send(hwnd.get(), CB_SELECTSTRING, -1, toUTF16(searchString).c_str()) != CB_ERR ? true + : false; +} + +::LRESULT ComboBox::get() +{ + return message.send(hwnd.get(), CB_GETCURSEL, 0, 0); +} + +::LRESULT ComboBox::getItemHeight() +{ + return message.send(hwnd.get(), CB_GETITEMHEIGHT, 0, 0); +} + +::LRESULT CALLBACK ComboBox::procedure(::HWND hwnd, ::UINT msg, ::WPARAM wparam, ::LPARAM lparam, + ::UINT_PTR id, ::DWORD_PTR data) +{ + if (auto self{reinterpret_cast(data)}; self) + { + if (msg == WM_WINDOWPOSCHANGED) + { + auto windowPos{reinterpret_cast<::LPWINDOWPOS>(lparam)}; + + self->position.x = windowPos->x; + self->position.y = windowPos->y; + self->position.width = windowPos->cx; + self->position.height = windowPos->cy; + } + } + + return ::DefSubclassProc(hwnd, msg, wparam, lparam); +} + +void ListBox::create(const std::string& name, uintptr_t id, ::HWND parentHwnd) +{ + hwnd.reset(::CreateWindowExW(0, toUTF16(WC_LISTBOX).c_str(), toUTF16(name).c_str(), + WS_CHILD | WS_VISIBLE | LBS_MULTIPLESEL | LBS_NOTIFY, 0, 0, 0, 0, + parentHwnd, (::HMENU)id, getInstance(), nullptr)); + + ::SetWindowSubclass(hwnd.get(), &procedure, id, reinterpret_cast<::DWORD_PTR>(this)); + + message.send(hwnd.get(), WM_SETFONT, getFontFromSystem(), TRUE); +} + +void ListBox::reset() +{ + message.send(hwnd.get(), LB_RESETCONTENT, 0, 0); +} + +void ListBox::add(const std::string& string) +{ + message.send(hwnd.get(), LB_ADDSTRING, 0, toUTF16(string).c_str()); +} + +bool ListBox::set(int index) +{ + return message.send(hwnd.get(), LB_SETCURSEL, index, 0) != CB_ERR ? true : false; +} + +bool ListBox::set(const std::string& searchString) +{ + return message.send(hwnd.get(), LB_SELECTSTRING, -1, toUTF16(searchString).c_str()) != CB_ERR ? true + : false; +} + +::LRESULT ListBox::get() +{ + return message.send(hwnd.get(), LB_GETCURSEL, 0, 0); +} + +::LRESULT ListBox::getItems(std::vector& buffer) +{ + buffer.resize(getItemsCount()); + return message.send(hwnd.get(), LB_GETSELITEMS, getItemsCount(), buffer.data()); +} + +::LRESULT ListBox::getItemsCount() +{ + return message.send(hwnd.get(), LB_GETSELCOUNT, 0, 0); +} + +::LRESULT ListBox::getItemHeight() +{ + return message.send(hwnd.get(), LB_GETITEMHEIGHT, 0, 0); +} + +::LRESULT CALLBACK ListBox::procedure(::HWND hwnd, ::UINT msg, ::WPARAM wparam, ::LPARAM lparam, + ::UINT_PTR id, ::DWORD_PTR data) +{ + if (auto self{reinterpret_cast(data)}; self) + { + if (msg == WM_WINDOWPOSCHANGED) + { + auto windowPos{reinterpret_cast<::LPWINDOWPOS>(lparam)}; + + self->position.x = windowPos->x; + self->position.y = windowPos->y; + self->position.width = windowPos->cx; + self->position.height = windowPos->cy; + } + } + + return ::DefSubclassProc(hwnd, msg, wparam, lparam); +} + +void SystemMenu::add(std::wstring& name, ::UINT id) +{ + item.emplace_back(::MENUITEMINFOW{sizeof(::MENUITEMINFOW), MIIM_STRING | MIIM_ID, 0, 0, id, nullptr, + nullptr, nullptr, 0, name.data(), 0, nullptr}); +} + +void SystemMenu::addToggle(std::wstring& name, ::UINT id, bool checked) +{ + item.emplace_back(::MENUITEMINFOW{sizeof(::MENUITEMINFOW), + MIIM_STRING | MIIM_ID | MIIM_CHECKMARKS | MIIM_STATE, 0, + static_cast<::UINT>(checked ? MFS_CHECKED : MFS_UNCHECKED), id, + nullptr, nullptr, nullptr, 0, name.data(), 0, nullptr}); +} + +void SystemMenu::addSeparator() +{ + item.emplace_back(::MENUITEMINFOW{sizeof(::MENUITEMINFOW), MIIM_FTYPE, MFT_SEPARATOR, 0, 0, nullptr, + nullptr, nullptr, 0, nullptr, 0, nullptr}); +} + +void SystemMenu::populate(::HWND hwnd) +{ + if (auto systemMenu{getSystemMenu(hwnd)}; systemMenu != INVALID_HANDLE_VALUE) + { + for (int i{0}; i < item.size(); i++) + { + ::InsertMenuItemW(systemMenu, i, TRUE, &item[i]); + } + } +} + +Plugin::Plugin(const clap_plugin_entry* entry, int argc, char** argv) +{ + plugin.clap = + freeaudio::clap_wrapper::standalone::mainCreatePlugin(entry, PLUGIN_ID, PLUGIN_INDEX, argc, argv); + + plugin.plugin = plugin.clap->_plugin; + plugin.gui = plugin.clap->_ext._gui; + plugin.state = plugin.clap->_ext._state; + + message.on(WM_CREATE, + [this](Message msg) + { + menu.add(menu.audioMidiSettings, Menu::Identifier::AudioMidiSettings); + menu.addToggle(menu.muteInput, Menu::Identifier::MuteInput); + menu.addSeparator(); + menu.add(menu.saveState, Menu::Identifier::SaveState); + menu.add(menu.loadState, Menu::Identifier::LoadState); + menu.add(menu.resetState, Menu::Identifier::ResetState); + menu.addSeparator(); + + menu.populate(hwnd.get()); + + return 0; + }); + + settings.message.on( + WM_CREATE, + [this](Message msg) + { + settings.setStyle(WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX); + + settings.api.create("API", Settings::Identifier::AudioApi, settings.hwnd.get()); + settings.output.create("Output", Settings::Identifier::AudioOutput, settings.hwnd.get()); + settings.input.create("Input", Settings::Identifier::AudioInput, settings.hwnd.get()); + settings.sampleRate.create("Sample Rate", Settings::Identifier::AudioSamplerate, + settings.hwnd.get()); + settings.bufferSize.create("Buffer Size", Settings::Identifier::AudioBuffersize, + settings.hwnd.get()); + settings.midiIn.create("MIDI Inputs", Settings::Identifier::MidiInputs, settings.hwnd.get()); + + return 0; + }); + + message.on(WM_WINDOWPOSCHANGED, + [this](Message msg) + { + auto windowPos{reinterpret_cast<::LPWINDOWPOS>(msg.lparam)}; + + if (plugin.gui) + { + if (windowPos->flags & SWP_SHOWWINDOW) + { + plugin.gui->show(plugin.plugin); + } + + if (windowPos->flags & SWP_HIDEWINDOW) + { + plugin.gui->hide(plugin.plugin); + } + + if (plugin.gui->can_resize(plugin.plugin)) + { + plugin.gui->adjust_size(plugin.plugin, &client.width, &client.height); + plugin.gui->set_size(plugin.plugin, client.width, client.height); + } + } + + position.x = windowPos->x; + position.y = windowPos->y; + position.width = windowPos->cx; + position.height = windowPos->cy; + + saveSettings(); + + return 0; + }); + + settings.message.on(WM_WINDOWPOSCHANGED, + [this](Message msg) + { + auto windowPos{reinterpret_cast<::LPWINDOWPOS>(msg.lparam)}; + + if (windowPos->flags & SWP_SHOWWINDOW) + { + settings.toggleCentered(true); + } + + return 0; + }); + + message.on(WM_DPICHANGED, + [this](Message msg) + { + if (plugin.gui) + { + plugin.gui->set_scale(plugin.plugin, scale); + } + + return 0; + }); + + settings.message.on(WM_DPICHANGED, + [this](Message msg) + { + refreshLayout(); + + return 0; + }); + + message.on(WM_SYSCOMMAND, + [this](Message msg) + { + switch (msg.wparam) + { + case Menu::Identifier::AudioMidiSettings: + { + settings.isVisible() ? settings.hide() : settings.show(); + + return 0; + } + + case Menu::Identifier::MuteInput: + { + if (menu.item[1].fState == MFS_UNCHECKED) + { + sah->audioInputUsed = false; + menu.item[1].fState = MFS_CHECKED; + } + else + { + sah->audioInputUsed = true; + menu.item[1].fState = MFS_UNCHECKED; + } + + SetMenuItemInfoW(getSystemMenu(hwnd.get()), 1, FALSE, &menu.item[1]); + + saveSettings(); + startAudio(); + + return 0; + } + + case Menu::Identifier::SaveState: + { + auto fileSaveDialog{wil::CoCreateInstance<::IFileSaveDialog>(CLSID_FileSaveDialog)}; + + fileSaveDialog->SetDefaultExtension(fileTypes.at(0).pszName); + fileSaveDialog->SetFileTypes(static_cast<::UINT>(fileTypes.size()), fileTypes.data()); + fileSaveDialog->Show(hwnd.get()); + + wil::com_ptr<::IShellItem> shellItem; + + if (auto hr{fileSaveDialog->GetResult(&shellItem)}; SUCCEEDED(hr)) + { + wil::unique_cotaskmem_string result; + shellItem->GetDisplayName(SIGDN_FILESYSPATH, &result); + + auto saveFile{std::filesystem::path(result.get())}; + + try + { + sah->saveStandaloneAndPluginSettings(saveFile.parent_path(), saveFile.filename()); + } + catch (const fs::filesystem_error& e) + { + message.error("Unable to save state: {}", e.what()); + } + } + + return 0; + } + + case Menu::Identifier::LoadState: + { + auto fileOpenDialog{wil::CoCreateInstance<::IFileOpenDialog>(CLSID_FileOpenDialog)}; + + fileOpenDialog->SetDefaultExtension(fileTypes.at(0).pszName); + fileOpenDialog->SetFileTypes(static_cast<::UINT>(fileTypes.size()), fileTypes.data()); + fileOpenDialog->Show(hwnd.get()); + + wil::com_ptr<::IShellItem> shellItem; + + if (auto hr{fileOpenDialog->GetResult(&shellItem)}; SUCCEEDED(hr)) + { + wil::unique_cotaskmem_string result; + shellItem->GetDisplayName(SIGDN_FILESYSPATH, &result); + + auto saveFile{std::filesystem::path(result.get())}; + + try + { + if (fs::exists(saveFile)) + { + sah->tryLoadStandaloneAndPluginSettings(saveFile.parent_path(), + saveFile.filename()); + } + } + catch (const fs::filesystem_error& e) + { + message.error("Unable to load state: {}", e.what()); + } + } + + return 0; + } + + case Menu::Identifier::ResetState: + { + auto pt{freeaudio::clap_wrapper::standalone::getStandaloneSettingsPath()}; + + if (pt.has_value()) + { + auto loadPath{*pt / plugin.plugin->desc->id}; + + try + { + if (fs::exists(loadPath / "defaults.clapwrapper")) + { + sah->tryLoadStandaloneAndPluginSettings(loadPath, "defaults.clapwrapper"); + } + } + catch (const fs::filesystem_error& e) + { + message.error("Unable to reset state: {}", e.what()); + } + } + + return 0; + } + } + + ::DefWindowProcW(msg.hwnd, msg.msg, msg.wparam, msg.lparam); + + return 0; + }); + + settings.message.on( + WM_COMMAND, + [this](Message msg) + { + if (HIWORD(msg.wparam) == CBN_SELCHANGE) + { + if (LOWORD(msg.wparam) == Settings::Identifier::AudioApi) + { + initializeAudio(sah->getCompiledApi()[settings.api.get()]); + + refreshOutputs(); + refreshInputs(); + + settings.output.set(sah->rtaDac->getDeviceInfo(sah->audioOutputDeviceID).name); + settings.input.set(sah->rtaDac->getDeviceInfo(sah->audioInputDeviceID).name); + + refreshSampleRates(); + refreshBufferSizes(); + + settings.sampleRate.set(std::to_string(sah->currentSampleRate)); + settings.bufferSize.set(std::to_string(sah->currentBufferSize)); + + saveSettings(); + startAudio(); + } + + if (LOWORD(msg.wparam) == Settings::Identifier::AudioOutput) + { + sah->audioOutputDeviceID = sah->getOutputAudioDevices()[settings.output.get()].ID; + + sah->totalOutputChannels = + sah->getOutputAudioDevices()[settings.output.get()].outputChannels; + + refreshSampleRates(); + refreshBufferSizes(); + + saveSettings(); + startAudio(); + } + + if (LOWORD(msg.wparam) == Settings::Identifier::AudioInput) + { + sah->audioInputDeviceID = sah->getInputAudioDevices()[settings.input.get()].ID; + + sah->totalInputChannels = sah->getInputAudioDevices()[settings.input.get()].inputChannels; + + refreshSampleRates(); + refreshBufferSizes(); + + saveSettings(); + startAudio(); + } + + if (LOWORD(msg.wparam) == Settings::Identifier::AudioSamplerate) + { + auto sampleRates{sah->getInputAudioDevices()[settings.input.get()].sampleRates}; + + auto newRate{sampleRates[settings.sampleRate.get()]}; + + sah->currentSampleRate = newRate; + + saveSettings(); + startAudio(); + } + + if (LOWORD(msg.wparam) == Settings::Identifier::AudioBuffersize) + { + auto bufferSizes{sah->getBufferSizes()}; + + auto bufferSize{bufferSizes[settings.bufferSize.get()]}; + + sah->currentBufferSize = bufferSize; + + saveSettings(); + startAudio(); + } + } + + if (HIWORD(msg.wparam) == LBN_SELCHANGE) + { + if (LOWORD(msg.wparam) == Settings::Identifier::MidiInputs) + { + std::vector ports; + settings.midiIn.getItems(ports); + + for (auto& midiIn : sah->midiIns) + { + midiIn.reset(); + } + sah->midiIns.clear(); + sah->currentMidiPorts.clear(); + + for (auto port : ports) + { + if (ports.size() != 0) + { + try + { + auto midiIn{std::make_unique()}; + midiIn->openPort(port); + midiIn->setCallback(sah->midiCallback, sah); + sah->midiIns.push_back(std::move(midiIn)); + sah->currentMidiPorts.push_back(port); + } + catch (RtMidiError& error) + { + log("{}", error.getMessage()); + }; + } + else + { + sah->stopMIDIThread(); + } + } + + saveSettings(); + } + } + + return 0; + }); + + message.on(WM_TIMER, + [this](Message msg) + { + if (msg.wparam == timerId) + { + if (sah->callbackRequested.exchange(false)) + { + plugin.plugin->on_main_thread(plugin.plugin); + } + } + + return 0; + }); + + message.on(WM_DESTROY, + [this](Message msg) + { + saveSettings(); + + sah->onRequestResize = nullptr; + sah->displayAudioError = nullptr; + + if (plugin.gui) + { + plugin.gui->destroy(plugin.plugin); + } + + if (isTimerRunning) + { + if (stopTimer(timerId)) + { + log(getLastError()); + } + } + + freeaudio::clap_wrapper::standalone::mainFinish(); + + quit(); + + return 0; + }); + + settings.message.on(WM_CLOSE, + [this](Message msg) + { + settings.hide(); + + return 0; + }); + + settings.message.on( + WM_PAINT, + [this](Message msg) + { + ::PAINTSTRUCT ps; + ::HDC hdc{::BeginPaint(msg.hwnd, &ps)}; + + ::SetTextColor(hdc, RGB(255, 255, 255)); + ::SetBkMode(hdc, TRANSPARENT); + + auto font{getScaledFontFromSystem(settings.scale)}; + ::SelectObject(hdc, font); + + ::TextOutW(hdc, 10, 10, L"API: ", 5); + ::TextOutW(hdc, 10, settings.api.position.y + settings.api.position.height + 10, L"Output: ", 8); + ::TextOutW(hdc, 10, settings.output.position.y + settings.output.position.height + 10, + L"Input: ", 7); + ::TextOutW(hdc, 10, settings.input.position.y + settings.input.position.height + 10, + L"Sample Rate: ", 13); + ::TextOutW(hdc, 10, settings.sampleRate.position.y + settings.sampleRate.position.height + 10, + L"Buffer Size: ", 13); + + if (sah->numMidiPorts != 0) + { + ::TextOutW(hdc, 10, settings.bufferSize.position.y + settings.bufferSize.position.height + 10, + L"MIDI Inputs: ", 13); + } + + ::EndPaint(msg.hwnd, &ps); + + return 0; + }); + + create(OUTPUT_NAME); + + settings.create("Audio/MIDI Settings"); + + isTimerRunning = startTimer(timerId, 8); + + if (!isTimerRunning) + { + log(getLastError()); + } + + if (loadSettings()) + { + initializeAudio(); + menu.item[1].fState = sah->audioInputUsed ? MFS_UNCHECKED : MFS_CHECKED; + SetMenuItemInfoW(getSystemMenu(hwnd.get()), 1, FALSE, &menu.item[1]); + sah->setAudioApi(sah->audioApi); + } + else + { + initializeAudio(); + saveSettings(); + } + + if (plugin.gui) + { + if (plugin.gui->is_api_supported(plugin.plugin, CLAP_WINDOW_API_WIN32, false)) + { + plugin.gui->create(plugin.plugin, CLAP_WINDOW_API_WIN32, false); + plugin.gui->set_scale(plugin.plugin, scale); + + if (plugin.gui->can_resize(plugin.plugin)) + { + // We can restore size here + // setWindowPosition(m_window.hwnd.get(), previousWidth, previousHeight); + // log("x: {} y: {}", sah->position.x, sah->position.y); + if (position.width != 0 | position.height != 0) + { + setPosition(position); + } + } + else + { + // We can't resize, so disable WS_THICKFRAME and WS_MAXIMIZEBOX + setStyle(WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX); + } + + Position pluginSize; + plugin.gui->get_size(plugin.plugin, &pluginSize.width, &pluginSize.height); + log("{}, {}", pluginSize.width, pluginSize.height); + + adjustSize(pluginSize.width, pluginSize.height); + + clap_window clapWindow{CLAP_WINDOW_API_WIN32, static_cast(hwnd.get())}; + plugin.gui->set_parent(plugin.plugin, &clapWindow); + } + else + { + log("CLAP_WINDOW_API_WIN32 is not supported"); + } + } + else + { + setStyle(WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX); + adjustSize(500 * scale, 0); + toggleCentered(true); + } + + sah->onRequestResize = [this](uint32_t width, uint32_t height) + { + if (placement.showCmd != SW_MAXIMIZE) + { + adjustSize(width, height); + } + + return true; + }; + + startMIDI(); + refreshMIDIInputs(); + + sah->displayAudioError = [this](auto& errorText) + { message.error("Unable to configure audio: {}", errorText); }; + + refreshApis(); + refreshOutputs(); + refreshInputs(); + refreshSampleRates(); + refreshBufferSizes(); + + settings.api.set(sah->audioApiDisplayName); + settings.output.set(sah->rtaDac->getDeviceInfo(sah->audioOutputDeviceID).name); + settings.input.set(sah->rtaDac->getDeviceInfo(sah->audioInputDeviceID).name); + settings.sampleRate.set(std::to_string(sah->currentSampleRate)); + settings.bufferSize.set(std::to_string(sah->currentBufferSize)); + + refreshLayout(); + + startAudio(); + + activate(); +} + +std::optional Plugin::getResizeHints() +{ + clap_gui_resize_hints resizeHints{}; + + if (!plugin.gui) + { + return std::nullopt; + } + + return plugin.gui->get_resize_hints(plugin.plugin, &resizeHints) ? std::make_optional(resizeHints) + : std::nullopt; +} + +void Plugin::refreshLayout() +{ + settings.repaint(); + + settings.api.refreshFont(settings.scale); + settings.output.refreshFont(settings.scale); + settings.input.refreshFont(settings.scale); + settings.sampleRate.refreshFont(settings.scale); + settings.bufferSize.refreshFont(settings.scale); + settings.midiIn.refreshFont(settings.scale); + + auto x{150 * settings.scale}; + auto width{500 * settings.scale}; + auto height{settings.api.position.height}; + + settings.api.setPosition(x, 10, width - x - 10, height); + + settings.output.setPosition(x, settings.api.position.y + settings.api.position.height + 10, + width - x - 10, height); + + settings.input.setPosition(x, settings.output.position.y + settings.output.position.height + 10, + width - x - 10, height); + + settings.sampleRate.setPosition(x, settings.input.position.y + settings.input.position.height + 10, + width - x - 10, height); + + settings.bufferSize.setPosition( + x, settings.sampleRate.position.y + settings.sampleRate.position.height + 10, width - x - 10, + height); + + settings.midiIn.setPosition(x, + settings.bufferSize.position.y + settings.bufferSize.position.height + 10, + width - x - 10, (settings.midiIn.getItemHeight() * sah->numMidiPorts)); + + if (sah->numMidiPorts != 0) + { + settings.adjustSize(width, (settings.api.position.height * 5) + (10 * 6) + + (settings.midiIn.position.height) + (10 * 1)); + } + else + { + settings.adjustSize(width, (settings.api.position.height * 5) + (10 * 6)); + } +} + +void Plugin::refreshApis() +{ + settings.api.reset(); + + for (auto& api : sah->getCompiledApi()) + { + settings.api.add(sah->rtaDac->getApiDisplayName(api)); + } +} + +void Plugin::refreshOutputs() +{ + settings.output.reset(); + + for (auto& device : sah->getOutputAudioDevices()) + { + settings.output.add(device.name); + } +} + +void Plugin::refreshInputs() +{ + settings.input.reset(); + + for (auto& device : sah->getInputAudioDevices()) + { + settings.input.add(device.name); + } +} + +void Plugin::refreshSampleRates() +{ + settings.sampleRate.reset(); + + auto sampleRates{sah->getSampleRates()}; + + for (auto sampleRate : sampleRates) + { + settings.sampleRate.add(std::to_string(sampleRate)); + } + + if (!settings.sampleRate.set(std::to_string(sah->currentSampleRate))) + { + settings.sampleRate.set(0); + } +} + +void Plugin::refreshBufferSizes() +{ + settings.bufferSize.reset(); + + auto bufferSizes{sah->getBufferSizes()}; + + for (auto bufferSize : bufferSizes) + { + settings.bufferSize.add(std::to_string(bufferSize)); + } + + if (!settings.bufferSize.set(std::to_string(sah->currentBufferSize))) + { + settings.bufferSize.set(0); + } +} + +void Plugin::refreshMIDIInputs() +{ + settings.midiIn.reset(); + + auto midiIn{std::make_unique()}; + + for (uint32_t port{0}; port < sah->numMidiPorts; port++) + { + settings.midiIn.add(midiIn->getPortName(port)); + } +} + +bool Plugin::saveSettings() +{ + auto settingsPath{getStandaloneSettingsPath()}; + + if (settingsPath.has_value()) + { + std::filesystem::create_directories(settingsPath.value() / plugin.plugin->desc->id); + + settings.set("audioApiName", sah->audioApiName); + settings.set("audioInputDeviceID", sah->audioInputDeviceID); + settings.set("audioOutputDeviceID", sah->audioOutputDeviceID); + settings.set("audioInputUsed", sah->audioInputUsed); + settings.set("audioOutputUsed", sah->audioOutputUsed); + settings.set("currentSampleRate", sah->currentSampleRate); + settings.set("currentBufferSize", sah->currentBufferSize); + settings.set("position", position); + + auto settingsFilePath{settingsPath.value() / plugin.plugin->desc->id / "settings.json"}; + std::wofstream file(settingsFilePath.c_str(), std::ios::binary | std::ios::out); + + if (file.is_open()) + { + auto serialized{settings.json.Stringify()}; + file.write(serialized.c_str(), serialized.size()); + + return file ? true : false; + } + } + + return false; +} + +bool Plugin::loadSettings() +{ + auto settingsPath{getStandaloneSettingsPath()}; + + if (settingsPath.has_value()) + { + auto settingsFilePath{settingsPath.value() / plugin.plugin->desc->id / "settings.json"}; + std::wifstream file(settingsFilePath.c_str(), std::ios::binary | std::ios::in); + + if (file.is_open()) + { + std::wstring buffer; + file.ignore(std::numeric_limits::max()); + buffer.resize(file.gcount()); + file.clear(); + file.seekg(0, std::ios::beg); + file.read(buffer.data(), buffer.size()); + if (auto parsed{settings.json.TryParse(buffer, settings.json)}; parsed) + { + sah->audioApiName = settings.get("audioApiName"); + sah->audioApi = sah->rtaDac->getCompiledApiByName(sah->audioApiName); + sah->audioApiDisplayName = sah->rtaDac->getApiDisplayName(sah->audioApi); + sah->audioInputDeviceID = settings.get("audioInputDeviceID"); + sah->audioOutputDeviceID = settings.get("audioOutputDeviceID"); + sah->audioInputUsed = settings.get("audioInputUsed"); + sah->audioOutputUsed = settings.get("audioOutputUsed"); + sah->currentSampleRate = settings.get("currentSampleRate"); + sah->currentBufferSize = settings.get("currentBufferSize"); + position = settings.get("position"); + + return parsed; + } + } + } + + return false; +} + +void Plugin::initializeMIDI() +{ + auto midiIn{std::make_unique()}; + + sah->currentMidiPorts.clear(); + + for (uint32_t port{0}; port < sah->numMidiPorts; port++) + { + sah->currentMidiPorts.push_back(port); + } +} + +void Plugin::startMIDI() +{ + sah->startMIDIThread(); +} + +void Plugin::initializeAudio(RtAudio::Api api) +{ + sah->setAudioApi(api); + + auto [input, output, sampleRate]{sah->getDefaultAudioInOutSampleRate()}; + + sah->audioInputDeviceID = input; + sah->totalInputChannels = sah->rtaDac->getDeviceInfo(input).inputChannels; + sah->audioInputUsed = true; + + sah->audioOutputDeviceID = output; + sah->totalOutputChannels = sah->rtaDac->getDeviceInfo(output).outputChannels; + sah->audioOutputUsed = true; + + sah->currentSampleRate = sampleRate; + sah->currentBufferSize = sah->getBufferSizes()[0]; +} + +void Plugin::startAudio() +{ + sah->startAudioThreadOn(sah->audioInputDeviceID, sah->totalInputChannels, sah->audioInputUsed, + sah->audioOutputDeviceID, sah->totalOutputChannels, sah->audioOutputUsed, + sah->currentSampleRate); +} +} // namespace freeaudio::clap_wrapper::standalone::windows_standalone diff --git a/src/detail/standalone/windows/windows_standalone.h b/src/detail/standalone/windows/windows_standalone.h new file mode 100644 index 00000000..c3830366 --- /dev/null +++ b/src/detail/standalone/windows/windows_standalone.h @@ -0,0 +1,408 @@ +#pragma once + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#define FMT_HEADER_ONLY +#include +#include + +#include "detail/standalone/entry.h" +#include "detail/standalone/standalone_host.h" + +namespace winrt +{ +using namespace winrt::Windows::Data::Json; +}; // namespace winrt + +namespace freeaudio::clap_wrapper::standalone::windows_standalone +{ +::HMODULE getInstance(); +::HFONT getFontFromSystem(int name = DEFAULT_GUI_FONT); +::HFONT getScaledFontFromSystem(double scale); +::HBRUSH getBrushFromSystem(int name = BLACK_BRUSH); +::HCURSOR getCursorFromSystem(::LPCSTR name = IDC_ARROW); +::HICON getIconFromSystem(::LPCSTR name = IDI_APPLICATION); +::HICON getIconFromResource(); +::HMENU getSystemMenu(::HWND hwnd); + +std::wstring toUTF16(std::string_view utf8); +std::string toUTF8(std::wstring_view utf16); + +std::string formatMessage(::HRESULT errorCode); +std::string getLastError(); + +void log(const std::string& message); +void log(const std::wstring& message); + +template +void log(const fmt::format_string fmt, Args&&... args) +{ + ::OutputDebugStringW(toUTF16(fmt::vformat(fmt.get(), fmt::make_format_args(args...))).c_str()); + ::OutputDebugStringW(L"\n"); +} + +template +void log(const fmt::wformat_string fmt, Args&&... args) +{ + ::OutputDebugStringW(fmt::vformat(fmt.get(), fmt::make_wformat_args(args...)).c_str()); + ::OutputDebugStringW(L"\n"); +} + +int run(); +int abort(int exitCode = EXIT_FAILURE); +int quit(int exitCode = EXIT_SUCCESS); + +struct Message +{ + ::HWND hwnd; + ::UINT msg; + ::WPARAM wparam; + ::LPARAM lparam; +}; + +struct MessageHandler +{ + using MessageCallback = std::function<::LRESULT(Message)>; + + bool on(::UINT msg, MessageCallback callback); + bool contains(::UINT msg); + ::LRESULT invoke(Message message); + + template + ::LRESULT send(::HWND hwnd, ::UINT msg, W wparam, L lparam) + { + return ::SendMessageW(hwnd, msg, (::WPARAM)wparam, (::LPARAM)lparam); + } + + void box(const std::string& message); + void box(const std::wstring& message); + + void error(const std::string& errorMessage); + void error(const std::wstring& errorMessage); + + template + void box(const fmt::format_string fmt, Args&&... args) + { + ::MessageBoxW(nullptr, toUTF16(fmt::vformat(fmt.get(), fmt::make_format_args(args...))).c_str(), + nullptr, MB_OK | MB_ICONASTERISK); + } + + template + void box(const fmt::wformat_string fmt, Args&&... args) + { + ::MessageBoxW(nullptr, fmt::vformat(fmt.get(), fmt::make_wformat_args(args...)).c_str(), nullptr, + MB_OK | MB_ICONASTERISK); + } + + template + void error(const fmt::format_string fmt, Args&&... args) + { + ::MessageBoxW(nullptr, toUTF16(fmt::vformat(fmt.get(), fmt::make_format_args(args...))).c_str(), + nullptr, MB_OK | MB_ICONHAND); + } + + template + void error(const fmt::wformat_string fmt, Args&&... args) + { + ::MessageBoxW(nullptr, fmt::vformat(fmt.get(), fmt::make_wformat_args(args...)).c_str(), nullptr, + MB_OK | MB_ICONHAND); + } + + private: + std::unordered_map<::UINT, MessageCallback> map; +}; + +struct Position +{ + uint32_t x{0}; + uint32_t y{0}; + uint32_t width{0}; + uint32_t height{0}; +}; + +struct Window +{ + void create(const std::string& title); + + static ::LRESULT CALLBACK procedure(::HWND hwnd, ::UINT msg, ::WPARAM wparam, ::LPARAM lparam); + + void activate(); + void show(); + void hide(); + bool isVisible(); + + void adjustSize(uint32_t width, uint32_t height); + void setPosition(Position position); + void setStyle(::LONG_PTR style); + + void toggleCentered(bool centered); + void toggleTopmost(bool topmost); + + void repaint(); + + bool startTimer(::UINT_PTR timerId, ::UINT intervalMs); + bool stopTimer(::UINT_PTR timerId); + + Position window; + Position client; + Position monitor; + ::WINDOWPLACEMENT placement; + uint32_t dpi; + double scale; + + Position restore; + Position suggested; + + MessageHandler message; + wil::unique_hwnd hwnd; +}; + +struct Control +{ + void setPosition(uint32_t x, uint32_t y, uint32_t width, uint32_t height); + void refreshFont(double scale); + + Position position; + + wil::unique_hfont font; + + MessageHandler message; + wil::unique_hwnd hwnd; +}; + +struct ComboBox final : public Control +{ + void create(const std::string& name, uintptr_t id, ::HWND parentHwnd); + void reset(); + void add(const std::string& string); + + bool set(int index); + bool set(const std::string& searchString); + ::LRESULT get(); + ::LRESULT getItemHeight(); + + static ::LRESULT CALLBACK procedure(::HWND hwnd, ::UINT msg, ::WPARAM wparam, ::LPARAM lparam, + ::UINT_PTR id, ::DWORD_PTR data); +}; + +struct ListBox final : public Control +{ + void create(const std::string& name, uintptr_t id, ::HWND parentHwnd); + void reset(); + void add(const std::string& string); + + bool set(int index); + bool set(const std::string& searchString); + ::LRESULT get(); + ::LRESULT getItems(std::vector& buffer); + ::LRESULT getItemsCount(); + ::LRESULT getItemHeight(); + + static ::LRESULT CALLBACK procedure(::HWND hwnd, ::UINT msg, ::WPARAM wparam, ::LPARAM lparam, + ::UINT_PTR id, ::DWORD_PTR data); +}; + +struct SystemMenu +{ + void add(std::wstring& name, ::UINT id); + void addToggle(std::wstring& name, ::UINT id, bool checked = false); + void addSeparator(); + void populate(::HWND hwnd); + + std::vector<::MENUITEMINFOW> item; +}; + +struct Plugin final : public Window +{ + struct Menu final : public SystemMenu + { + enum Identifier + { + AudioMidiSettings, + MuteInput, + SaveState, + LoadState, + ResetState + }; + + std::wstring audioMidiSettings{L"Audio/MIDI Settings"}; + std::wstring muteInput{L"Mute input"}; + std::wstring saveState{L"Save state..."}; + std::wstring loadState{L"Load state..."}; + std::wstring resetState{L"Reset state"}; + }; + + struct Settings final : public Window + { + enum Identifier + { + AudioApi, + AudioOutput, + AudioInput, + AudioSamplerate, + AudioBuffersize, + MidiInputs + }; + + template + auto set(std::string_view key, U value) -> void + { + if constexpr (std::is_same_v) + { + json.SetNamedValue(toUTF16(key), winrt::JsonValue::CreateStringValue(toUTF16(value))); + } + + if constexpr (std::is_same_v) + { + json.SetNamedValue(toUTF16(key), winrt::JsonValue::CreateBooleanValue(value)); + } + + if constexpr (std::is_same_v) + { + json.SetNamedValue(toUTF16(key), winrt::JsonValue::CreateNumberValue(value)); + } + + if constexpr (std::is_same_v) + { + auto pos{winrt::JsonObject()}; + pos.SetNamedValue(L"x", winrt::JsonValue::CreateNumberValue(value.x)); + pos.SetNamedValue(L"y", winrt::JsonValue::CreateNumberValue(value.y)); + pos.SetNamedValue(L"width", winrt::JsonValue::CreateNumberValue(value.width)); + pos.SetNamedValue(L"height", winrt::JsonValue::CreateNumberValue(value.height)); + json.SetNamedValue(toUTF16(key), pos); + } + } + + template + auto get(std::string_view key) -> T + { + auto value{json.GetNamedValue(toUTF16(key), nullptr)}; + + if constexpr (std::is_same_v) + { + if (value && value.ValueType() == winrt::JsonValueType::String) + { + return toUTF8(value.GetString()); + } + else + { + return {}; + } + } + + if constexpr (std::is_same_v) + { + if (value && value.ValueType() == winrt::JsonValueType::Boolean) + { + return value.GetBoolean(); + } + else + { + return false; + } + } + + if constexpr (std::is_same_v) + { + if (value && value.ValueType() == winrt::JsonValueType::Number) + { + return value.GetNumber(); + } + else + { + return 0.0; + } + } + + if constexpr (std::is_same_v) + { + if (value && value.ValueType() == winrt::JsonValueType::Object) + { + auto parsedPos{value.GetObject()}; + + Position buffer; + buffer.x = static_cast(parsedPos.GetNamedNumber(L"x")); + buffer.y = static_cast(parsedPos.GetNamedNumber(L"y")); + buffer.width = static_cast(parsedPos.GetNamedNumber(L"width")); + buffer.height = static_cast(parsedPos.GetNamedNumber(L"height")); + + return buffer; + } + else + { + return {}; + } + } + } + + ComboBox api; + ComboBox output; + ComboBox input; + ComboBox sampleRate; + ComboBox bufferSize; + ListBox midiIn; + + winrt::JsonObject json; + }; + + struct ClapPlugin + { + std::shared_ptr clap; + const clap_plugin_t* plugin; + const clap_plugin_gui_t* gui; + const clap_plugin_state_t* state; + }; + + explicit Plugin(const clap_plugin_entry* entry, int argc, char** argv); + + std::optional getResizeHints(); + void refreshLayout(); + void refreshApis(); + void refreshOutputs(); + void refreshInputs(); + void refreshSampleRates(); + void refreshBufferSizes(); + void refreshMIDIInputs(); + + bool saveSettings(); + bool loadSettings(); + + void initializeMIDI(); + void startMIDI(); + + void initializeAudio(RtAudio::Api api = RtAudio::Api::WINDOWS_WASAPI); + void startAudio(); + + freeaudio::clap_wrapper::standalone::StandaloneHost* sah{ + freeaudio::clap_wrapper::standalone::getStandaloneHost()}; + + std::vector<::COMDLG_FILTERSPEC> fileTypes{{L"clapwrapper", L"*.clapwrapper"}}; + + bool isTimerRunning{false}; + const ::UINT_PTR timerId{0}; + + ClapPlugin plugin; + Position position; + Menu menu; + Settings settings; +}; +} // namespace freeaudio::clap_wrapper::standalone::windows_standalone diff --git a/src/detail/standalone/windows/standalone.manifest b/src/detail/standalone/windows/windows_standalone.manifest similarity index 100% rename from src/detail/standalone/windows/standalone.manifest rename to src/detail/standalone/windows/windows_standalone.manifest diff --git a/src/wrapasstandalone_win32.cpp b/src/wrapasstandalone_win32.cpp deleted file mode 100644 index f938c6d3..00000000 --- a/src/wrapasstandalone_win32.cpp +++ /dev/null @@ -1,52 +0,0 @@ -#include "detail/standalone/standalone_details.h" -#include "detail/standalone/entry.h" -#include "detail/standalone/windows/host_window.h" -#include "detail/standalone/windows/helpers.h" - -int main(int argc, char** argv) -{ - const clap_plugin_entry* entry{nullptr}; -#ifdef STATICALLY_LINKED_CLAP_ENTRY - extern const clap_plugin_entry clap_entry; - entry = &clap_entry; -#else - std::string clapName{HOSTED_CLAP_NAME}; - freeaudio::clap_wrapper::standalone::windows::helpers::log("Loading {}", clapName); - - auto lib{Clap::Library()}; - - for (const auto& searchPaths : Clap::getValidCLAPSearchPaths()) - { - auto clapPath{searchPaths / (clapName + ".clap")}; - - if (fs::exists(clapPath) && !entry) - { - lib.load(clapPath); - entry = lib._pluginEntry; - } - } -#endif - - if (!entry) - { - freeaudio::clap_wrapper::standalone::windows::helpers::errorBox("No entry as configured"); - freeaudio::clap_wrapper::standalone::windows::helpers::abort(3); - } - - auto clapPlugin{ - freeaudio::clap_wrapper::standalone::mainCreatePlugin(entry, PLUGIN_ID, PLUGIN_INDEX, argc, argv)}; - freeaudio::clap_wrapper::standalone::mainStartAudio(); - - if (clapPlugin->_ext._gui->is_api_supported(clapPlugin->_plugin, CLAP_WINDOW_API_WIN32, false)) - { - freeaudio::clap_wrapper::standalone::windows::HostWindow hostWindow{clapPlugin}; - - return freeaudio::clap_wrapper::standalone::windows::helpers::messageLoop(); - } - else - { - freeaudio::clap_wrapper::standalone::windows::helpers::errorBox( - "CLAP_WINDOW_API_WIN32 is not supported"); - freeaudio::clap_wrapper::standalone::windows::helpers::abort(); - } -} diff --git a/src/wrapasstandalone_windows.cpp b/src/wrapasstandalone_windows.cpp new file mode 100644 index 00000000..e670a3cf --- /dev/null +++ b/src/wrapasstandalone_windows.cpp @@ -0,0 +1,39 @@ +#include "detail/standalone/windows/windows_standalone.h" + +int main(int argc, char** argv) +{ + auto coUninitialize{wil::CoInitializeEx(COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE)}; + + const clap_plugin_entry* entry{nullptr}; +#ifdef STATICALLY_LINKED_CLAP_ENTRY + extern const clap_plugin_entry clap_entry; + entry = &clap_entry; +#else + std::string clapName{HOSTED_CLAP_NAME}; + freeaudio::clap_wrapper::standalone::windows_standalone::log("Loading {}", clapName); + + auto lib{Clap::Library()}; + + for (const auto& searchPaths : Clap::getValidCLAPSearchPaths()) + { + auto clapPath{searchPaths / (clapName + ".clap")}; + + if (fs::exists(clapPath) && !entry) + { + lib.load(clapPath); + entry = lib._pluginEntry; + } + } +#endif + + if (!entry) + { + freeaudio::clap_wrapper::standalone::windows_standalone::log("No entry as configured"); + + return 3; + } + + freeaudio::clap_wrapper::standalone::windows_standalone::Plugin plugin{entry, argc, argv}; + + return freeaudio::clap_wrapper::standalone::windows_standalone::run(); +}