diff --git a/CMakeLists.txt b/CMakeLists.txt index aade7ad8..f8867402 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,6 +37,7 @@ option_ex(PLUGIN_LV2_UI "Enable LV2 plug-in user interface" ON) option_ex(PLUGIN_PUREDATA "Enable Puredata plug-in build" OFF) option_ex(PLUGIN_VST2 "Enable VST2 plug-in build (unsupported)" OFF) option_ex(PLUGIN_VST3 "Enable VST3 plug-in build" ON) +option_ex(SFIZZ_STANDALONE "Enable standalone build" OFF) option_ex(SFIZZ_USE_SYSTEM_LV2 "Use LV2 headers preinstalled on system" OFF) option_ex(SFIZZ_USE_SYSTEM_VST3SDK "Use VST3SDK source files preinstalled on system" OFF) @@ -52,13 +53,17 @@ if(WIN32) endif() # Override sfizz CXX standard since vstgui requires 17 anyway -if (PLUGIN_AU OR PLUGIN_LV2_UI OR PLUGIN_VST3 OR PLUGIN_VST2) +if (PLUGIN_AU OR PLUGIN_LV2_UI OR PLUGIN_VST3 OR PLUGIN_VST2 OR SFIZZ_STANDALONE) set(CMAKE_CXX_STANDARD 17 CACHE STRING "C++ standard to be used") endif() include(SfizzConfig) # Re-used for this project include(BundleDylibs) +if(SFIZZ_STANDALONE) + include(StandaloneDeps) +endif() + add_subdirectory(library) add_subdirectory(plugins) diff --git a/cmake/StandaloneDeps.cmake b/cmake/StandaloneDeps.cmake new file mode 100644 index 00000000..88cd58c9 --- /dev/null +++ b/cmake/StandaloneDeps.cmake @@ -0,0 +1,71 @@ +# Find Linux system libraries +add_library(dl INTERFACE) + +if(NOT WIN32 AND NOT APPLE) + find_library(DL_LIBRARY "dl") + target_link_libraries(dl INTERFACE "${DL_LIBRARY}") + + # JACK + find_package(PkgConfig REQUIRED) + pkg_check_modules(JACK "jack" REQUIRED) + + # The X11 library + find_package(X11 REQUIRED) + add_library(sfizz_x11 INTERFACE) + target_include_directories(sfizz_x11 INTERFACE "${X11_INCLUDE_DIR}") + target_link_libraries(sfizz_x11 INTERFACE "${X11_X11_LIB}") + add_library(sfizz::x11 ALIAS sfizz_x11) + + # The GtkMM library + find_package(PkgConfig REQUIRED) + pkg_check_modules(GTKMM "gtkmm-3.0" REQUIRED) + add_library(sfizz_gtkmm INTERFACE) + target_include_directories(sfizz_gtkmm INTERFACE ${GTKMM_INCLUDE_DIRS}) + target_link_libraries(sfizz_gtkmm INTERFACE ${GTKMM_LIBRARIES}) + link_libraries(${GTKMM_LIBRARY_DIRS}) + add_library(sfizz::gtkmm ALIAS sfizz_gtkmm) +endif() + +add_library(sfizz::dl ALIAS dl) + +# Find C++ filesystem +function(sfizz_find_std_fs TARGET) + add_library("${TARGET}" INTERFACE) + + set(_fs_src +"#include +int main() { return std::filesystem::exists(\"myfile\") ? 0 : 1; }") + set(_expfs_src +"#include +int main() { return std::experimental::filesystem::exists(\"myfile\") ? 0 : 1; }") + + check_cxx_source_compiles("${_fs_src}" HAVE_STDFS_DIRECT) + check_cxx_source_compiles("${_expfs_src}" HAVE_STDFS_EXPERIMENTAL_DIRECT) + if(HAVE_STDFS_DIRECT OR HAVE_STDFS_EXPERIMENTAL_DIRECT) + return() + endif() + + find_library(STDCPPFS_LIBRARY "stdc++fs") + if(STDCPPFS_LIBRARY) + set(CMAKE_REQUIRED_LIBRARIES "${STDCPPFS_LIBRARY}") + check_cxx_source_compiles("${_fs_src}" HAVE_STDFS_LIBSTDCPPFS) + check_cxx_source_compiles("${_expfs_src}" HAVE_STDFS_EXPERIMENTAL_LIBSTDCPPFS) + if(HAVE_STDFS_LIBSTDCPPFS OR HAVE_STDFS_EXPERIMENTAL_LIBSTDCPPFS) + target_link_libraries("${TARGET}" INTERFACE "${STDCPPFS_LIBRARY}") + return() + endif() + endif() + + find_library(CPPFS_LIBRARY "c++fs") + if(CPPFS_LIBRARY) + set(CMAKE_REQUIRED_LIBRARIES "${CPPFS_LIBRARY}") + check_cxx_source_compiles("${_fs_src}" HAVE_STDFS_STDCPPFS) + check_cxx_source_compiles("${_expfs_src}" HAVE_STDFS_EXPERIMENTAL_STDCPPFS) + if(HAVE_STDFS_STDCPPFS OR HAVE_STDFS_EXPERIMENTAL_STDCPPFS) + target_link_libraries("${TARGET}" INTERFACE "${CPPFS_LIBRARY}") + return() + endif() + endif() +endfunction() +sfizz_find_std_fs(stdfs) +add_library(sfizz::stdfs ALIAS stdfs) diff --git a/plugins/vst/CMakeLists.txt b/plugins/vst/CMakeLists.txt index 5e291f96..f1bcbd5b 100644 --- a/plugins/vst/CMakeLists.txt +++ b/plugins/vst/CMakeLists.txt @@ -374,3 +374,70 @@ if(PLUGIN_VST2) endif() endif() endif() + +# --- Standalone program --- # + +if(SFIZZ_STANDALONE) + set(SOURCES_STANDALONE + standalone/standalonehost.h + standalone/standalonehost.cpp + standalone/media/audioclient.h + standalone/media/imediaserver.h + standalone/media/iparameterclient.h + standalone/media/miditovst.h + standalone/media/audioclient.cpp + standalone/platform/appinit.h + standalone/platform/iapplication.h + standalone/platform/iplatform.h + standalone/platform/iwindow.h + ) + set(SOURCES_STANDALONE_WIN32 + standalone/platform/win32/platform.cpp + standalone/platform/win32/window.h + standalone/platform/win32/window.cpp + ) + set(SOURCES_STANDALONE_MACOS + standalone/platform/mac/platform.mm + standalone/platform/mac/window.h + standalone/platform/mac/window.mm + ) + set(SOURCES_STANDALONE_UNIX + standalone/media/jack/jackclient.cpp + standalone/platform/linux/platform.cpp +# Was used for X11 version +# standalone/platform/linux/runloop.h +# standalone/platform/linux/runloop.cpp + standalone/platform/linux/window.h + standalone/platform/linux/window.cpp + ) + source_group("Sources" FILES + ${SOURCES_STANDALONE} + ${SOURCES_STANDALONE_WIN32} + ${SOURCES_STANDALONE_MACOS} + ${SOURCES_STANDALONE_UNIX} + ) + add_executable(sfizz_standalone WIN32 + ${SOURCES_STANDALONE} + ) + set_target_properties(sfizz_standalone PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/$<0:>" + ) + target_include_directories(sfizz_standalone PRIVATE "standalone" "${CMAKE_CURRENT_BINARY_DIR}") + target_link_libraries(sfizz_standalone PRIVATE vst3sdk_hosting sfizz::filesystem) + if(WIN32) + target_sources(sfizz_standalone PRIVATE ${SOURCES_STANDALONE_WIN32} + ) + elseif(APPLE) + target_sources(sfizz_standalone PRIVATE ${SOURCES_STANDALONE_MACOS}) + target_link_libraries(sfizz_standalone PRIVATE "${APPLE_COCOA_LIBRARY}") + else() + target_sources(sfizz_standalone PRIVATE ${SOURCES_STANDALONE_UNIX}) + target_compile_definitions(sfizz_standalone PRIVATE "EDITORHOST_GTK=1") + target_link_libraries(sfizz_standalone PRIVATE + sfizz::jack + sfizz::gtkmm + sfizz::x11 + ) + endif() + install(TARGETS sfizz_standalone DESTINATION "${CMAKE_INSTALL_BINDIR}" COMPONENT "standalone") +endif() diff --git a/plugins/vst/cmake/Vst3.cmake b/plugins/vst/cmake/Vst3.cmake index f461453f..2b541e71 100644 --- a/plugins/vst/cmake/Vst3.cmake +++ b/plugins/vst/cmake/Vst3.cmake @@ -31,27 +31,36 @@ add_library(vst3sdk STATIC EXCLUDE_FROM_ALL "${VST3SDK_BASEDIR}/public.sdk/source/vst/vstparameters.cpp" "${VST3SDK_BASEDIR}/public.sdk/source/vst/vstpresetfile.cpp" "${VST3SDK_BASEDIR}/public.sdk/source/vst/vstrepresentation.cpp" - "${VST3SDK_BASEDIR}/public.sdk/source/vst/utility/stringconvert.cpp") + "${VST3SDK_BASEDIR}/public.sdk/source/vst/utility/stringconvert.cpp" +) if(WIN32) target_sources(vst3sdk PRIVATE - "${VST3SDK_BASEDIR}/public.sdk/source/common/threadchecker_win32.cpp") + "${VST3SDK_BASEDIR}/public.sdk/source/common/threadchecker_win32.cpp" + ) elseif(APPLE) target_sources(vst3sdk PRIVATE - "${VST3SDK_BASEDIR}/public.sdk/source/common/threadchecker_mac.mm") + "${VST3SDK_BASEDIR}/public.sdk/source/common/threadchecker_mac.mm" + ) else() target_sources(vst3sdk PRIVATE - "${VST3SDK_BASEDIR}/public.sdk/source/common/threadchecker_linux.cpp") + "${VST3SDK_BASEDIR}/public.sdk/source/common/threadchecker_linux.cpp" + ) endif() target_include_directories(vst3sdk PUBLIC "${VST3SDK_BASEDIR}") target_link_libraries(vst3sdk PUBLIC Threads::Threads) + if(APPLE) target_link_libraries(vst3sdk PUBLIC ${APPLE_FOUNDATION_LIBRARY}) endif() + if(MINGW) target_compile_definitions(vst3sdk PUBLIC - "_NATIVE_WCHAR_T_DEFINED=1" "__wchar_t=wchar_t") + "_NATIVE_WCHAR_T_DEFINED=1" "__wchar_t=wchar_t" + ) endif() + set(_vst_release_build_types MinSizeRel Release RelWithDebInfo) + if(CMAKE_BUILD_TYPE IN_LIST _vst_release_build_types) target_compile_definitions(vst3sdk PUBLIC "RELEASE") else() @@ -62,16 +71,20 @@ function(plugin_add_vst3sdk NAME) target_link_libraries("${NAME}" PRIVATE vst3sdk) target_sources("${NAME}" PRIVATE "${VST3SDK_BASEDIR}/public.sdk/source/main/moduleinit.cpp" - "${VST3SDK_BASEDIR}/public.sdk/source/main/pluginfactory.cpp") + "${VST3SDK_BASEDIR}/public.sdk/source/main/pluginfactory.cpp" + ) if(WIN32) target_sources("${NAME}" PRIVATE - "${VST3SDK_BASEDIR}/public.sdk/source/main/dllmain.cpp") + "${VST3SDK_BASEDIR}/public.sdk/source/main/dllmain.cpp" + ) elseif(APPLE) target_sources("${NAME}" PRIVATE - "${VST3SDK_BASEDIR}/public.sdk/source/main/macmain.cpp") + "${VST3SDK_BASEDIR}/public.sdk/source/main/macmain.cpp" + ) else() target_sources("${NAME}" PRIVATE - "${VST3SDK_BASEDIR}/public.sdk/source/main/linuxmain.cpp") + "${VST3SDK_BASEDIR}/public.sdk/source/main/linuxmain.cpp" + ) endif() endfunction() @@ -80,32 +93,42 @@ add_library(vst3sdk_hosting STATIC EXCLUDE_FROM_ALL "${VST3SDK_BASEDIR}/public.sdk/source/vst/hosting/connectionproxy.cpp" "${VST3SDK_BASEDIR}/public.sdk/source/vst/hosting/eventlist.cpp" "${VST3SDK_BASEDIR}/public.sdk/source/vst/hosting/hostclasses.cpp" + "${VST3SDK_BASEDIR}/public.sdk/source/vst/hosting/module.cpp" "${VST3SDK_BASEDIR}/public.sdk/source/vst/hosting/parameterchanges.cpp" "${VST3SDK_BASEDIR}/public.sdk/source/vst/hosting/pluginterfacesupport.cpp" "${VST3SDK_BASEDIR}/public.sdk/source/vst/hosting/plugprovider.cpp" - "${VST3SDK_BASEDIR}/public.sdk/source/vst/hosting/processdata.cpp") -if(FALSE) - if(WIN32) - target_sources(vst3sdk_hosting PRIVATE - "${VST3SDK_BASEDIR}/public.sdk/source/vst/hosting/module_win32.cpp") - elseif(APPLE) - target_sources(vst3sdk_hosting PRIVATE - "${VST3SDK_BASEDIR}/public.sdk/source/vst/hosting/module_mac.mm") - else() - target_sources(vst3sdk_hosting PRIVATE - "${VST3SDK_BASEDIR}/public.sdk/source/vst/hosting/module_linux.cpp") - endif() + "${VST3SDK_BASEDIR}/public.sdk/source/vst/hosting/processdata.cpp" +) +if(WIN32) + target_sources(vst3sdk_hosting PRIVATE + "${VST3SDK_BASEDIR}/public.sdk/source/vst/hosting/module_win32.cpp" + ) +elseif(APPLE) + target_sources(vst3sdk_hosting PRIVATE + "${VST3SDK_BASEDIR}/public.sdk/source/vst/hosting/module_mac.mm" + ) +else() + target_sources(vst3sdk_hosting PRIVATE + "${VST3SDK_BASEDIR}/public.sdk/source/vst/hosting/module_linux.cpp" + ) endif() -target_link_libraries(vst3sdk_hosting PUBLIC vst3sdk) +target_link_libraries(vst3sdk_hosting + PUBLIC vst3sdk + PRIVATE sfizz::stdfs +) # --- VSTGUI --- + add_library(vst3sdk_vstgui STATIC EXCLUDE_FROM_ALL - "${VST3SDK_BASEDIR}/public.sdk/source/vst/vstguieditor.cpp") + "${VST3SDK_BASEDIR}/public.sdk/source/vst/vstguieditor.cpp" +) if(WIN32) target_sources(vst3sdk_vstgui PRIVATE - "${VST3SDK_BASEDIR}/public.sdk/source/vst/vstgui_win32_bundle_support.cpp") + "${VST3SDK_BASEDIR}/public.sdk/source/vst/vstgui_win32_bundle_support.cpp" + ) target_compile_definitions(vst3sdk_vstgui PRIVATE "SMTG_MODULE_IS_BUNDLE=1") endif() + target_link_libraries(vst3sdk_vstgui PUBLIC vst3sdk sfizz::vstgui) function(plugin_add_vstgui NAME) @@ -116,7 +139,8 @@ endfunction() foreach(_target vst3sdk_vstgui vst3sdk) gw_target_warn("${_target}" PUBLIC "-Wno-extra" - "-Wno-class-memaccess") + "-Wno-class-memaccess" + ) gw_target_warn("${_target}" PRIVATE "-Wno-multichar" "-Wno-reorder" @@ -125,5 +149,6 @@ foreach(_target vst3sdk_vstgui vst3sdk) "-Wno-unknown-pragmas" "-Wno-unused-function" "-Wno-unused-parameter" - "-Wno-unused-variable") + "-Wno-unused-variable" + ) endforeach() diff --git a/plugins/vst/standalone/media/audioclient.cpp b/plugins/vst/standalone/media/audioclient.cpp new file mode 100644 index 00000000..ed11da59 --- /dev/null +++ b/plugins/vst/standalone/media/audioclient.cpp @@ -0,0 +1,459 @@ +//----------------------------------------------------------------------------- +// Flags : clang-format auto +// Project : VST SDK +// +// Category : AudioHost +// Filename : public.sdk/samples/vst-hosting/audiohost/source/media/audioclient.cpp +// Created by : Steinberg 09.2016 +// Description : Audio Host Example for VST 3 +// +//----------------------------------------------------------------------------- +// LICENSE +// (c) 2023, Steinberg Media Technologies GmbH, All Rights Reserved +//----------------------------------------------------------------------------- +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of the Steinberg Media Technologies nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +// OF THE POSSIBILITY OF SUCH DAMAGE. +//----------------------------------------------------------------------------- + +#include "audioclient.h" + +#include "miditovst.h" +#include "public.sdk/source/vst/hosting/eventlist.h" +#include "public.sdk/source/vst/hosting/parameterchanges.h" +#include "public.sdk/source/vst/utility/stringconvert.h" +#include "pluginterfaces/vst/ivsteditcontroller.h" +#include "pluginterfaces/vst/ivstmidicontrollers.h" +#include + +#include +#include + +//------------------------------------------------------------------------ +namespace Steinberg { +namespace Vst { + +//------------------------------------------------------------------------ +// From Vst2Wrapper +static MidiCCMapping initMidiCtrlerAssignment (IComponent* component, IMidiMapping* midiMapping) +{ + MidiCCMapping midiCCMapping {}; + + if (!midiMapping || !component) + return midiCCMapping; + + int32 busses = std::min (component->getBusCount (kEvent, kInput), kMaxMidiMappingBusses); + + if (midiCCMapping[0][0].empty ()) + { + for (int32 b = 0; b < busses; b++) + for (int32 i = 0; i < kMaxMidiChannels; i++) + midiCCMapping[b][i].resize (Vst::kCountCtrlNumber); + } + + ParamID paramID; + for (int32 b = 0; b < busses; b++) + { + for (int16 ch = 0; ch < kMaxMidiChannels; ch++) + { + for (int32 i = 0; i < Vst::kCountCtrlNumber; i++) + { + paramID = kNoParamId; + if (midiMapping->getMidiControllerAssignment (b, ch, (CtrlNumber)i, paramID) == + kResultTrue) + { + // TODO check if tag is associated to a parameter + midiCCMapping[b][ch][i] = paramID; + } + else + midiCCMapping[b][ch][i] = kNoParamId; + } + } + } + return midiCCMapping; +} + +//------------------------------------------------------------------------ +static void assignBusBuffers (const IAudioClient::Buffers& buffers, HostProcessData& processData, + bool unassign = false) +{ + // Set outputs + auto bufferIndex = 0; + for (auto busIndex = 0; busIndex < processData.numOutputs; busIndex++) + { + auto channelCount = processData.outputs[busIndex].numChannels; + for (auto chanIndex = 0; chanIndex < channelCount; chanIndex++) + { + if (bufferIndex < buffers.numOutputs) + { + processData.setChannelBuffer (BusDirections::kOutput, busIndex, chanIndex, + unassign ? nullptr : buffers.outputs[bufferIndex]); + bufferIndex++; + } + } + } + + // Set inputs + bufferIndex = 0; + for (auto busIndex = 0; busIndex < processData.numInputs; busIndex++) + { + auto channelCount = processData.inputs[busIndex].numChannels; + for (auto chanIndex = 0; chanIndex < channelCount; chanIndex++) + { + if (bufferIndex < buffers.numInputs) + { + processData.setChannelBuffer (BusDirections::kInput, busIndex, chanIndex, + unassign ? nullptr : buffers.inputs[bufferIndex]); + + bufferIndex++; + } + } + } +} + +//------------------------------------------------------------------------ +static void unassignBusBuffers (const IAudioClient::Buffers& buffers, HostProcessData& processData) +{ + assignBusBuffers (buffers, processData, true); +} + +//------------------------------------------------------------------------ +// Vst3Processor +//------------------------------------------------------------------------ +AudioClient::AudioClient () +{ +} + +//------------------------------------------------------------------------ +AudioClient::~AudioClient () +{ + + terminate (); +} + +//------------------------------------------------------------------------ +AudioClientPtr AudioClient::create (const Name& name, IComponent* component, + IMidiMapping* midiMapping) +{ + auto newProcessor = std::make_shared (); + newProcessor->initialize (name, component, midiMapping); + return newProcessor; +} + +//------------------------------------------------------------------------ +void AudioClient::initProcessContext () +{ + processContext = {}; + processContext.tempo = 120; +} + +//------------------------------------------------------------------------ +void AudioClient::createLocalMediaServer (const Name& name) +{ + mediaServer = createMediaServer (name); + mediaServer->registerAudioClient (this); + mediaServer->registerMidiClient (this); +} + +//------------------------------------------------------------------------ +bool AudioClient::initialize (const Name& name, IComponent* _component, IMidiMapping* midiMapping) +{ + component = _component; + if (!component) + return false; + + initProcessData (); + + paramTransferrer.setMaxParameters (1000); + + if (midiMapping) + midiCCMapping = initMidiCtrlerAssignment (component, midiMapping); + + createLocalMediaServer (name); + return true; +} + +//------------------------------------------------------------------------ +void AudioClient::terminate () +{ + mediaServer = nullptr; + + FUnknownPtr processor = component; + if (!processor) + return; + + processor->setProcessing (false); + component->setActive (false); +} + +//------------------------------------------------------------------------ +void AudioClient::initProcessData () +{ + // processData.prepare will be done in setBlockSize + + processData.inputEvents = &eventList; + processData.inputParameterChanges = &inputParameterChanges; + processData.processContext = &processContext; + + initProcessContext (); +} + +//------------------------------------------------------------------------ +IMidiClient::IOSetup AudioClient::getMidiIOSetup () const +{ + IMidiClient::IOSetup iosetup; + auto count = component->getBusCount (MediaTypes::kEvent, BusDirections::kInput); + for (int32_t i = 0; i < count; i++) + { + BusInfo info; + if (component->getBusInfo (MediaTypes::kEvent, BusDirections::kInput, i, info) != kResultOk) + continue; + + auto busName = VST3::StringConvert::convert (info.name, 128); + iosetup.inputs.push_back (busName); + } + + count = component->getBusCount (MediaTypes::kEvent, BusDirections::kOutput); + for (int32_t i = 0; i < count; i++) + { + BusInfo info; + if (component->getBusInfo (MediaTypes::kEvent, BusDirections::kOutput, i, info) != + kResultOk) + continue; + + auto busName = VST3::StringConvert::convert (info.name, 128); + iosetup.outputs.push_back (busName); + } + + return iosetup; +} + +//------------------------------------------------------------------------ +IAudioClient::IOSetup AudioClient::getIOSetup () const +{ + IAudioClient::IOSetup iosetup; + auto count = component->getBusCount (MediaTypes::kAudio, BusDirections::kOutput); + for (int32_t i = 0; i < count; i++) + { + BusInfo info; + if (component->getBusInfo (MediaTypes::kAudio, BusDirections::kOutput, i, info) != + kResultOk) + continue; + + for (int32_t j = 0; j < info.channelCount; j++) + { + auto channelName = VST3::StringConvert::convert (info.name, 128); + iosetup.outputs.push_back (channelName + " " + std::to_string (j)); + } + } + + count = component->getBusCount (MediaTypes::kAudio, BusDirections::kInput); + for (int32_t i = 0; i < count; i++) + { + BusInfo info; + if (component->getBusInfo (MediaTypes::kAudio, BusDirections::kInput, i, info) != kResultOk) + continue; + + for (int32_t j = 0; j < info.channelCount; j++) + { + auto channelName = VST3::StringConvert::convert (info.name, 128); + iosetup.inputs.push_back (channelName + " " + std::to_string (j)); + } + } + + return iosetup; +} + +//------------------------------------------------------------------------ +void AudioClient::preprocess (Buffers& buffers, int64_t continousFrames) +{ + processData.numSamples = buffers.numSamples; + processContext.continousTimeSamples = continousFrames; + assignBusBuffers (buffers, processData); + paramTransferrer.transferChangesTo (inputParameterChanges); +} + +//------------------------------------------------------------------------ +bool AudioClient::process (Buffers& buffers, int64_t continousFrames) +{ + FUnknownPtr processor = component; + if (!processor || !isProcessing) + return false; + + preprocess (buffers, continousFrames); + + if (processor->process (processData) != kResultOk) + return false; + + postprocess (buffers); + + return true; +} +//------------------------------------------------------------------------ +void AudioClient::postprocess (Buffers& buffers) +{ + eventList.clear (); + inputParameterChanges.clearQueue (); + unassignBusBuffers (buffers, processData); +} + +//------------------------------------------------------------------------ +bool AudioClient::setSamplerate (SampleRate value) +{ + if (sampleRate == value) + return true; + + sampleRate = value; + processContext.sampleRate = sampleRate; + if (blockSize == 0) + return true; + + return updateProcessSetup (); +} + +//------------------------------------------------------------------------ +bool AudioClient::setBlockSize (int32 value) +{ + if (blockSize == value) + return true; + + blockSize = value; + if (sampleRate == 0) + return true; + + processData.prepare (*component, blockSize, kSample32); + return updateProcessSetup (); +} + +//------------------------------------------------------------------------ +bool AudioClient::updateProcessSetup () +{ + FUnknownPtr processor = component; + if (!processor) + return false; + + if (isProcessing) + { + if (processor->setProcessing (false) != kResultOk) + return false; + + if (component->setActive (false) != kResultOk) + return false; + } + + ProcessSetup setup {kRealtime, kSample32, blockSize, sampleRate}; + + if (processor->setupProcessing (setup) != kResultOk) + return false; + + if (component->setActive (true) != kResultOk) + return false; + + processor->setProcessing (true); // != kResultOk + /* +if (processor->setProcessing(true) != kResultOk) +return false;*/ + + isProcessing = true; + return isProcessing; +} + +//------------------------------------------------------------------------ +bool AudioClient::isPortInRange (int32 port, int32 channel) const +{ + return port < kMaxMidiMappingBusses && !midiCCMapping[port][channel].empty (); +} + +//------------------------------------------------------------------------ +bool AudioClient::processVstEvent (const IMidiClient::Event& event, int32 port) +{ + auto vstEvent = midiToEvent (event.type, event.channel, event.data0, event.data1); + if (vstEvent) + { + vstEvent->busIndex = port; + if (eventList.addEvent (*vstEvent) != kResultOk) + { + assert (false && "Event was not added to EventList!"); + } + + return true; + } + + return false; +} + +//------------------------------------------------------------------------ +bool AudioClient::processParamChange (const IMidiClient::Event& event, int32 port) +{ + auto paramMapping = [port, this] (int32 channel, MidiData data1) -> ParamID { + if (!isPortInRange (port, channel)) + return kNoParamId; + + return midiCCMapping[port][channel][data1]; + }; + + auto paramChange = + midiToParameter (event.type, event.channel, event.data0, event.data1, paramMapping); + if (paramChange) + { + int32 index = 0; + IParamValueQueue* queue = + inputParameterChanges.addParameterData ((*paramChange).first, index); + if (queue) + { + if (queue->addPoint (event.timestamp, (*paramChange).second, index) != kResultOk) + { + assert (false && "Parameter point was not added to ParamValueQueue!"); + } + } + + return true; + } + + return false; +} + +//------------------------------------------------------------------------ +bool AudioClient::onEvent (const IMidiClient::Event& event, int32_t port) +{ + // Try to create Event first. + if (processVstEvent (event, port)) + return true; + + // In case this is no event it must be a parameter. + if (processParamChange (event, port)) + return true; + + // TODO: Something else??? + + return true; +} + +//------------------------------------------------------------------------ +void AudioClient::setParameter (ParamID id, ParamValue value, int32 sampleOffset) +{ + paramTransferrer.addChange (id, value, sampleOffset); +} + +//------------------------------------------------------------------------ +} // Vst +} // Steinberg diff --git a/plugins/vst/standalone/media/audioclient.h b/plugins/vst/standalone/media/audioclient.h new file mode 100644 index 00000000..62181a3a --- /dev/null +++ b/plugins/vst/standalone/media/audioclient.h @@ -0,0 +1,128 @@ +//----------------------------------------------------------------------------- +// Flags : clang-format auto +// Project : VST SDK +// +// Category : AudioHost +// Filename : public.sdk/samples/vst-hosting/audiohost/source/audioclient.h +// Created by : Steinberg 09.2016 +// Description : Audio Host Example for VST 3 +// +//----------------------------------------------------------------------------- +// LICENSE +// (c) 2023, Steinberg Media Technologies GmbH, All Rights Reserved +//----------------------------------------------------------------------------- +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of the Steinberg Media Technologies nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +// OF THE POSSIBILITY OF SUCH DAMAGE. +//----------------------------------------------------------------------------- + +#pragma once + +#include "imediaserver.h" +#include "iparameterclient.h" +#include "public.sdk/source/vst/hosting/eventlist.h" +#include "public.sdk/source/vst/hosting/parameterchanges.h" +#include "public.sdk/source/vst/hosting/processdata.h" +#include "pluginterfaces/vst/ivstaudioprocessor.h" +#include + +//------------------------------------------------------------------------ +namespace Steinberg { +namespace Vst { + +//------------------------------------------------------------------------ +class IMidiMapping; +class IComponent; + +enum +{ + kMaxMidiMappingBusses = 4, + kMaxMidiChannels = 16 +}; +using Controllers = std::vector; +using Channels = std::array; +using Busses = std::array; +using MidiCCMapping = Busses; + +//------------------------------------------------------------------------ +using AudioClientPtr = std::shared_ptr; +//------------------------------------------------------------------------ +class AudioClient : public IAudioClient, public IMidiClient, public IParameterClient +{ +public: +//-------------------------------------------------------------------- + using Name = std::string; + + AudioClient (); + virtual ~AudioClient (); + + static AudioClientPtr create (const Name& name, IComponent* component, + IMidiMapping* midiMapping); + + // IAudioClient + bool process (Buffers& buffers, int64_t continousFrames) override; + bool setSamplerate (SampleRate value) override; + bool setBlockSize (int32 value) override; + IAudioClient::IOSetup getIOSetup () const override; + + // IMidiClient + bool onEvent (const Event& event, int32_t port) override; + IMidiClient::IOSetup getMidiIOSetup () const override; + + // IParameterClient + void setParameter (ParamID id, ParamValue value, int32 sampleOffset) override; + + bool initialize (const Name& name, IComponent* component, IMidiMapping* midiMapping); + +//-------------------------------------------------------------------- +private: + void createLocalMediaServer (const Name& name); + void terminate (); + void updateBusBuffers (Buffers& buffers, HostProcessData& processData); + void initProcessData (); + void initProcessContext (); + bool updateProcessSetup (); + void preprocess (Buffers& buffers, int64_t continousFrames); + void postprocess (Buffers& buffers); + bool isPortInRange (int32 port, int32 channel) const; + bool processVstEvent (const IMidiClient::Event& event, int32 port); + bool processParamChange (const IMidiClient::Event& event, int32 port); + + SampleRate sampleRate = 0; + int32 blockSize = 0; + HostProcessData processData; + ProcessContext processContext; + EventList eventList; + ParameterChanges inputParameterChanges; + IComponent* component = nullptr; + ParameterChangeTransfer paramTransferrer; + + MidiCCMapping midiCCMapping; + IMediaServerPtr mediaServer; + bool isProcessing = false; + + Name name; +}; + +//------------------------------------------------------------------------ +} // Vst +} // Steinberg diff --git a/plugins/vst/standalone/media/imediaserver.h b/plugins/vst/standalone/media/imediaserver.h new file mode 100644 index 00000000..b3cbd91a --- /dev/null +++ b/plugins/vst/standalone/media/imediaserver.h @@ -0,0 +1,118 @@ +//----------------------------------------------------------------------------- +// Flags : clang-format auto +// Project : VST SDK +// +// Category : AudioHost +// Filename : public.sdk/samples/vst-hosting/audiohost/source/media/imediaserver.h +// Created by : Steinberg 09.2016 +// Description : Audio Host Example for VST 3 +// +//----------------------------------------------------------------------------- +// LICENSE +// (c) 2023, Steinberg Media Technologies GmbH, All Rights Reserved +//----------------------------------------------------------------------------- +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of the Steinberg Media Technologies nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +// OF THE POSSIBILITY OF SUCH DAMAGE. +//----------------------------------------------------------------------------- + +#pragma once + +#include +#include +#include +#include + +//---------------------------------------------------------------------------------- +namespace Steinberg { +namespace Vst { +using IOName = std::string; +using IONames = std::vector; +using AudioClientName = std::string; + +struct IAudioClient +{ + struct Buffers + { + float** inputs; + int32_t numInputs; + float** outputs; + int32_t numOutputs; + int32_t numSamples; + }; + + struct IOSetup + { + IONames inputs; + IONames outputs; + }; + + virtual bool process (Buffers& buffers, int64_t continousFrames) = 0; + virtual bool setSamplerate (SampleRate value) = 0; + virtual bool setBlockSize (int32 value) = 0; + virtual IOSetup getIOSetup () const = 0; + + virtual ~IAudioClient () {} +}; + +//---------------------------------------------------------------------------------- +struct IMidiClient +{ + using MidiData = uint8_t; + + struct Event + { + MidiData type; + MidiData channel; + MidiData data0; + MidiData data1; + int64_t timestamp; + }; + + struct IOSetup + { + IONames inputs; + IONames outputs; + }; + + virtual bool onEvent (const Event& event, int32_t port) = 0; + virtual IOSetup getMidiIOSetup () const = 0; + + virtual ~IMidiClient () {} +}; + +//---------------------------------------------------------------------------------- +struct IMediaServer +{ + virtual bool registerAudioClient (IAudioClient* client) = 0; + virtual bool registerMidiClient (IMidiClient* client) = 0; + + virtual ~IMediaServer () {} +}; + +//---------------------------------------------------------------------------------- +using IMediaServerPtr = std::shared_ptr; + +IMediaServerPtr createMediaServer (const AudioClientName& name); +//---------------------------------------------------------------------------------- +} // Vst +} // Steinberg diff --git a/plugins/vst/standalone/media/iparameterclient.h b/plugins/vst/standalone/media/iparameterclient.h new file mode 100644 index 00000000..60a4207e --- /dev/null +++ b/plugins/vst/standalone/media/iparameterclient.h @@ -0,0 +1,59 @@ +//----------------------------------------------------------------------------- +// Flags : clang-format auto +// Project : VST SDK +// +// Category : AudioHost +// Filename : public.sdk/samples/vst-hosting/audiohost/source/media/iparameterclient.h +// Created by : Steinberg 09.2016 +// Description : Audio Host Example for VST 3 +// +//----------------------------------------------------------------------------- +// LICENSE +// (c) 2023, Steinberg Media Technologies GmbH, All Rights Reserved +//----------------------------------------------------------------------------- +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of the Steinberg Media Technologies nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +// OF THE POSSIBILITY OF SUCH DAMAGE. +//----------------------------------------------------------------------------- + +#pragma once + +#include +#include + +//---------------------------------------------------------------------------------- +namespace Steinberg { +namespace Vst { + +//---------------------------------------------------------------------------------- +struct IParameterClient +{ + virtual void setParameter (ParamID id, ParamValue value, int32 sampleOffset) = 0; + + virtual ~IParameterClient () {} +}; +//---------------------------------------------------------------------------------- +using IParameterClientPtr = std::weak_ptr; + +//---------------------------------------------------------------------------------- +} // Vst +} // Steinberg diff --git a/plugins/vst/standalone/media/jack/jackclient.cpp b/plugins/vst/standalone/media/jack/jackclient.cpp new file mode 100644 index 00000000..7024a1b2 --- /dev/null +++ b/plugins/vst/standalone/media/jack/jackclient.cpp @@ -0,0 +1,426 @@ +//----------------------------------------------------------------------------- +// Flags : clang-format auto +// Project : VST SDK +// +// Category : AudioHost +// Filename : public.sdk/samples/vst-hosting/audiohost/source/media/jack/jackclient.cpp +// Created by : Steinberg 09.2016 +// Description : Audio Host Example for VST 3 using Jack +// +//----------------------------------------------------------------------------- +// LICENSE +// (c) 2023, Steinberg Media Technologies GmbH, All Rights Reserved +//----------------------------------------------------------------------------- +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of the Steinberg Media Technologies nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +// OF THE POSSIBILITY OF SUCH DAMAGE. +//----------------------------------------------------------------------------- + +#include "media/imediaserver.h" + +#include + +//! Workaround for Jack on Windows +#if defined(SMTG_OS_WINDOWS) && defined(_STDINT) +#define _STDINT_H +#endif + +#include +#include + +//------------------------------------------------------------------------ +namespace Steinberg { +namespace Vst { + +static const int kJackSuccess = 0; +//------------------------------------------------------------------------ +// jack Client +//------------------------------------------------------------------------ +class JackClient : public IMediaServer +{ +public: +//-------------------------------------------------------------------- + using JackPorts = std::vector; + using JackName = std::string; + + JackClient () = default; + virtual ~JackClient (); + + // IMediaServer interface + bool registerAudioClient (IAudioClient* client) override; + bool registerMidiClient (IMidiClient* client) override; + + bool initialize (JackName name); + + // jack process callback + int process (jack_nframes_t nframes); + +//-------------------------------------------------------------------- +private: + jack_client_t* registerClient (JackName name); + bool registerAudioPorts (IAudioClient* processor); + bool registerMidiPorts (IMidiClient* processor); + bool addAudioOutputPort (JackName name); + bool addAudioInputPort (JackName name); + bool addMidiInputPort (JackName name); + int processMidi (jack_nframes_t nframes); + bool setupJackProcessCallbacks (jack_client_t* client); + bool autoConnectAudioPorts (jack_client_t* client); + bool autoConnectMidiPorts (jack_client_t* client); + void updateAudioBuffers (jack_nframes_t nframes); + + // Jack objects + jack_client_t* jackClient = nullptr; + JackPorts audioOutputPorts; + JackPorts audioInputPorts; + JackPorts midiInputPorts; + + IAudioClient* audioClient = nullptr; + IMidiClient* midiClient = nullptr; + using BufferPointers = std::vector; + BufferPointers audioOutputPointers; + BufferPointers audioInputPointers; + IAudioClient::Buffers buffers {nullptr}; +}; + +//------------------------------------------------------------------------ +int jack_on_process (jack_nframes_t nframes, void* arg) +{ + auto client = reinterpret_cast (arg); + + return client->process (nframes); +} + +//------------------------------------------------------------------------ +int jack_on_set_sample_rate (jack_nframes_t nframes, void* arg) +{ + auto client = reinterpret_cast (arg); + client->setSamplerate (static_cast (nframes)); + return kJackSuccess; +} + +//------------------------------------------------------------------------ +int jack_on_set_block_size (jack_nframes_t nframes, void* arg) +{ + auto client = reinterpret_cast (arg); + client->setBlockSize (static_cast (nframes)); + return kJackSuccess; +} + +//------------------------------------------------------------------------ +IMediaServerPtr createMediaServer (const AudioClientName& name) +{ + auto client = std::make_shared (); + client->initialize (name); + return client; +} + +//------------------------------------------------------------------------ +JackClient::~JackClient () +{ + //! We do not need to "unregister" ports. It is done automatically with "jack_client_close" + jack_deactivate (jackClient); // Stops calls of process + jack_client_close (jackClient); // Remove client from process graph and remove all ports +} + +//------------------------------------------------------------------------ +bool JackClient::registerAudioClient (IAudioClient* client) +{ + if (audioClient) + return false; + + audioClient = client; + + //! First thing to do: register the audio ports. + if (!registerAudioPorts (audioClient)) + return false; + + //! Setup all the callbacks like setSampleRate and process etc. + if (!setupJackProcessCallbacks (jackClient)) + return false; + + //! Activate after defining the callbacks. It is said in the documentation. + if (jack_activate (jackClient) != kJackSuccess) + return false; + + //! AFTER activation, register the ports. + if (!autoConnectAudioPorts (jackClient)) + return false; + + return true; +} + +//------------------------------------------------------------------------ +bool JackClient::registerMidiClient (IMidiClient* client) +{ + if (midiClient) + return false; + + midiClient = client; + + //! Register the midi ports. + if (!registerMidiPorts (midiClient)) + return false; + + //! Afterwards auto-connect them. + if (!autoConnectMidiPorts (jackClient)) + return false; + + return true; +} + +//------------------------------------------------------------------------ +bool JackClient::initialize (JackClient::JackName name) +{ + jackClient = registerClient (name); + if (!jackClient) + return false; + + return true; +} + +//------------------------------------------------------------------------ +void JackClient::updateAudioBuffers (jack_nframes_t nframes) +{ + int outputIndex = 0; + for (auto audioOutputPort : audioOutputPorts) + { + auto* portBuffer = jack_port_get_buffer (audioOutputPort, nframes); + if (!portBuffer) + continue; + + buffers.outputs[outputIndex++] = static_cast (portBuffer); + } + + int inputIndex = 0; + for (auto audioInputPort : audioInputPorts) + { + auto* portBuffer = jack_port_get_buffer (audioInputPort, nframes); + if (!portBuffer) + continue; + + buffers.inputs[inputIndex++] = static_cast (portBuffer); + } +} + +//------------------------------------------------------------------------ +int JackClient::process (jack_nframes_t nframes) +{ + processMidi (nframes); + buffers.numSamples = nframes; + + updateAudioBuffers (nframes); + if (!audioClient) + return 0; + + if (audioClient->process (buffers, jack_last_frame_time (jackClient)) == false) + { + assert (false); + } + + return kJackSuccess; +} + +//------------------------------------------------------------------------ +bool JackClient::registerAudioPorts (IAudioClient* processor) +{ + auto ioSetup = processor->getIOSetup (); + + for (const auto& output : ioSetup.outputs) + addAudioOutputPort (output); + + for (const auto& input : ioSetup.inputs) + addAudioInputPort (input); + + buffers.inputs = audioInputPointers.data (); + buffers.numInputs = (int32_t)audioInputPointers.size (); + buffers.numOutputs = (int32_t)audioOutputPointers.size (); + buffers.outputs = audioOutputPointers.data (); + + return true; +} + +//------------------------------------------------------------------------ +bool JackClient::registerMidiPorts (IMidiClient* processor) +{ + const auto ioSetup = processor->getMidiIOSetup (); + for (const auto& input : ioSetup.inputs) + addMidiInputPort (input); + + return true; +} + +//------------------------------------------------------------------------ +bool JackClient::addAudioOutputPort (JackClient::JackName name) +{ + auto port = + jack_port_register (jackClient, name.data (), JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0); + if (!port) + return false; + + audioOutputPorts.push_back (port); + audioOutputPointers.resize (audioOutputPorts.size ()); + return true; +} + +//------------------------------------------------------------------------ +bool JackClient::addAudioInputPort (JackClient::JackName name) +{ + auto port = + jack_port_register (jackClient, name.data (), JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0); + if (!port) + return false; + + audioInputPorts.push_back (port); + audioInputPointers.resize (audioOutputPorts.size ()); + return true; +} + +//------------------------------------------------------------------------ +bool JackClient::addMidiInputPort (JackClient::JackName name) +{ + auto port = + jack_port_register (jackClient, name.data (), JACK_DEFAULT_MIDI_TYPE, JackPortIsInput, 0); + if (!port) + return false; + + midiInputPorts.push_back (port); + return true; +} + +//------------------------------------------------------------------------ +int JackClient::processMidi (jack_nframes_t nframes) +{ + static const uint8_t kChannelMask = 0x0F; + static const uint8_t kStatusMask = 0xF0; + static const uint32_t kDataMask = 0x7F; + + for (int32_t portIndex = 0, count = static_cast (midiInputPorts.size ()); + portIndex < count; ++portIndex) + { + auto midiInputPort = midiInputPorts.at (portIndex); + auto* portBuffer = jack_port_get_buffer (midiInputPort, nframes); + if (!portBuffer) + continue; + + jack_midi_event_t in_event; + auto event_count = jack_midi_get_event_count (portBuffer); + for (uint32_t i = 0; i < event_count; i++) + { + jack_midi_event_get (&in_event, portBuffer, i); + if (in_event.size == 0) + continue; + + auto midiData = in_event.buffer; + Steinberg::Vst::IMidiClient::MidiData channel = midiData[0] & kChannelMask; + Steinberg::Vst::IMidiClient::MidiData status = midiData[0] & kStatusMask; + Steinberg::Vst::IMidiClient::MidiData data0 = midiData[1] & kDataMask; + Steinberg::Vst::IMidiClient::MidiData data1 = midiData[2] & kDataMask; + midiClient->onEvent ({status, channel, data0, data1, in_event.time}, portIndex); + } + } + + return kJackSuccess; +} + +//------------------------------------------------------------------------ +jack_client_t* JackClient::registerClient (JackClient::JackName name) +{ + jack_options_t options = JackNullOption; + jack_status_t status; + + jackClient = jack_client_open (name.data (), options, &status, nullptr); + return jackClient; + + /* Use the status to check for errors: + if (status & JackServerFailed) + { + fprintf (stderr, "Unable to connect to JACK server\n"); + }*/ +} + +//------------------------------------------------------------------------ +bool JackClient::setupJackProcessCallbacks (jack_client_t* client) +{ + if (jack_set_process_callback (client, jack_on_process, this) != kJackSuccess) + return false; + + if (jack_set_sample_rate_callback (client, jack_on_set_sample_rate, audioClient) != + kJackSuccess) + return false; + + if (jack_set_buffer_size_callback (client, jack_on_set_block_size, audioClient) != kJackSuccess) + return false; + + return true; +} + +//------------------------------------------------------------------------ +bool JackClient::autoConnectAudioPorts (jack_client_t* client) +{ + int portIndex = 0; + + //! Connect Audio Outputs + auto ports = jack_get_ports (client, nullptr, nullptr, JackPortIsPhysical | JackPortIsInput); + for (auto& port : audioOutputPorts) + { + if (!ports[portIndex]) + break; + + auto output__port_name = ports[portIndex++]; + auto res = jack_connect (client, jack_port_name (port), output__port_name); + if (res != 0) + break; + } + + jack_free (ports); + return true; +} + +//------------------------------------------------------------------------ +bool JackClient::autoConnectMidiPorts (jack_client_t* client) +{ + int portIndex = 1; + + //! Connect MIDI Inputs + auto ports = jack_get_ports (client, nullptr, "midi", JackPortIsPhysical | JackPortIsOutput); + if (!ports) + return false; + + for (auto& port : midiInputPorts) + { + if (!ports[portIndex]) + break; + + auto inputPortName = ports[portIndex++]; + auto res = jack_connect (client, inputPortName, jack_port_name (port)); + if (res != 0) + continue; + } + + jack_free (ports); + return true; +} + +//------------------------------------------------------------------------ +} // Vst +} // Steinberg diff --git a/plugins/vst/standalone/media/miditovst.h b/plugins/vst/standalone/media/miditovst.h new file mode 100644 index 00000000..24f1dc2f --- /dev/null +++ b/plugins/vst/standalone/media/miditovst.h @@ -0,0 +1,159 @@ +//----------------------------------------------------------------------------- +// Flags : clang-format auto +// Project : VST SDK +// +// Category : AudioHost +// Filename : public.sdk/samples/vst-hosting/audiohost/source/media/miditovst.h +// Created by : Steinberg 09.2016 +// Description : Audio Host Example for VST 3 +// +//----------------------------------------------------------------------------- +// LICENSE +// (c) 2023, Steinberg Media Technologies GmbH, All Rights Reserved +//----------------------------------------------------------------------------- +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of the Steinberg Media Technologies nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +// OF THE POSSIBILITY OF SUCH DAMAGE. +//----------------------------------------------------------------------------- + +#pragma once + +#include "public.sdk/source/vst/utility/optional.h" +#include "pluginterfaces/vst/ivstevents.h" +#include "pluginterfaces/vst/ivstmidicontrollers.h" +#include +#include + +//------------------------------------------------------------------------ +namespace Steinberg { +namespace Vst { + +const uint8_t kNoteOff = 0x80; ///< note, off velocity +const uint8_t kNoteOn = 0x90; ///< note, on velocity +const uint8_t kPolyPressure = 0xA0; ///< note, pressure +const uint8_t kController = 0xB0; ///< controller, value +const uint8_t kProgramChangeStatus = 0xC0; ///< program change +const uint8_t kAfterTouchStatus = 0xD0; ///< channel pressure +const uint8_t kPitchBendStatus = 0xE0; ///< lsb, msb +static const uint32 kDataMask = 0x7F; + +const float kMidiScaler = 1.f / 127.f; + +using MidiData = uint8_t; + +float toNormalized (const MidiData& data) +{ + return (float)data * kMidiScaler; +} + +using OptionalEvent = VST3::Optional; + +using ParameterChange = std::pair; +using OptionParamChange = VST3::Optional; + +OptionalEvent midiToEvent (MidiData status, MidiData channel, MidiData midiData0, + MidiData midiData1) +{ + Event new_event = {}; + if (status == kNoteOn || status == kNoteOff) + { + if (status == kNoteOff) // note off + { + new_event.noteOff.noteId = -1; + new_event.type = Event::kNoteOffEvent; + new_event.noteOff.channel = channel; + new_event.noteOff.pitch = midiData0; + new_event.noteOff.velocity = toNormalized (midiData1); + return std::move (new_event); + } + else if (status == kNoteOn) // note on + { + new_event.noteOn.noteId = -1; + new_event.type = Event::kNoteOnEvent; + new_event.noteOn.channel = channel; + new_event.noteOn.pitch = midiData0; + new_event.noteOn.velocity = toNormalized (midiData1); + return std::move (new_event); + } + } + //--- ----------------------------- + else if (status == kPolyPressure) + { + new_event.type = Vst::Event::kPolyPressureEvent; + new_event.polyPressure.channel = channel; + new_event.polyPressure.pitch = midiData0; + new_event.polyPressure.pressure = toNormalized (midiData1); + return std::move (new_event); + } + + return {}; +} + +//------------------------------------------------------------------------ +using ToParameterIdFunc = std::function; +OptionParamChange midiToParameter (MidiData status, MidiData channel, MidiData midiData1, + MidiData midiData2, const ToParameterIdFunc& toParamID) +{ + if (!toParamID) + return {}; + + ParameterChange paramChange; + if (status == kController) // controller + { + paramChange.first = toParamID (channel, midiData1); + if (paramChange.first != kNoParamId) + { + paramChange.second = (double)midiData2 * kMidiScaler; + return std::move (paramChange); + } + } + else if (status == kPitchBendStatus) + { + paramChange.first = toParamID (channel, Vst::kPitchBend); + if (paramChange.first != kNoParamId) + { + const double kPitchWheelScaler = 1. / (double)0x3FFF; + + const int32 ctrl = (midiData1 & kDataMask) | (midiData2 & kDataMask) << 7; + paramChange.second = kPitchWheelScaler * (double)ctrl; + return std::move (paramChange); + }; + } + else if (status == kAfterTouchStatus) + { + paramChange.first = toParamID (channel, Vst::kAfterTouch); + if (paramChange.first != kNoParamId) + { + paramChange.second = (ParamValue) (midiData1 & kDataMask) * kMidiScaler; + return std::move (paramChange); + }; + } + else if (status == kProgramChangeStatus) + { + // TODO + } + + return {}; +} +//------------------------------------------------------------------------ +} // Vst +} // Steinberg diff --git a/plugins/vst/standalone/platform/appinit.h b/plugins/vst/standalone/platform/appinit.h new file mode 100644 index 00000000..7b9ddfd6 --- /dev/null +++ b/plugins/vst/standalone/platform/appinit.h @@ -0,0 +1,60 @@ +//------------------------------------------------------------------------ +// Flags : clang-format auto +// Project : VST SDK +// +// Category : StandaloneHost +// Filename : public.sdk/samples/vst-hosting/editorhost/source/platform/appinit.h +// Created by : Steinberg, 04/2005 +// Description : Editor Host Example for VST SDK 3 +// +//----------------------------------------------------------------------------- +// LICENSE +// (c) 2023, Steinberg Media Technologies GmbH, All Rights Reserved +//----------------------------------------------------------------------------- +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of the Steinberg Media Technologies nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +// OF THE POSSIBILITY OF SUCH DAMAGE. +//----------------------------------------------------------------------------- + +#pragma once + +#include "platform/iapplication.h" +#include "platform/iplatform.h" + +//------------------------------------------------------------------------ +namespace Steinberg { +namespace Vst { +namespace StandaloneHost { + +//------------------------------------------------------------------------ +struct AppInit +{ + explicit AppInit (ApplicationPtr&& app) + { + IPlatform::instance ().setApplication (std::move (app)); + } +}; + +//------------------------------------------------------------------------ +} // StandaloneHost +} // Vst +} // Steinberg diff --git a/plugins/vst/standalone/platform/iapplication.h b/plugins/vst/standalone/platform/iapplication.h new file mode 100644 index 00000000..26731d09 --- /dev/null +++ b/plugins/vst/standalone/platform/iapplication.h @@ -0,0 +1,64 @@ +//----------------------------------------------------------------------------- +// Flags : clang-format auto +// Project : VST SDK +// +// Category : StandaloneHost +// Filename : public.sdk/samples/vst-hosting/editorhost/source/platform/iapplication.h +// Created by : Steinberg 09.2016 +// Description : Example of opening a plug-in editor +// +//----------------------------------------------------------------------------- +// LICENSE +// (c) 2023, Steinberg Media Technologies GmbH, All Rights Reserved +//----------------------------------------------------------------------------- +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of the Steinberg Media Technologies nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +// OF THE POSSIBILITY OF SUCH DAMAGE. +//----------------------------------------------------------------------------- + +#pragma once + +#include +#include +#include + +//------------------------------------------------------------------------ +namespace Steinberg { +namespace Vst { +namespace StandaloneHost { + +//------------------------------------------------------------------------ +class IApplication +{ +public: + virtual ~IApplication () noexcept = default; + + virtual void init (const std::vector& cmdArgs) = 0; + virtual void terminate () = 0; +}; + +using ApplicationPtr = std::unique_ptr; + +//------------------------------------------------------------------------ +} // StandaloneHost +} // Vst +} // Steinberg diff --git a/plugins/vst/standalone/platform/iplatform.h b/plugins/vst/standalone/platform/iplatform.h new file mode 100644 index 00000000..6b97a6a3 --- /dev/null +++ b/plugins/vst/standalone/platform/iplatform.h @@ -0,0 +1,70 @@ +//----------------------------------------------------------------------------- +// Flags : clang-format auto +// Project : VST SDK +// +// Category : StandaloneHost +// Filename : public.sdk/samples/vst-hosting/editorhost/source/platform/iplatform.h +// Created by : Steinberg 09.2016 +// Description : Example of opening a plug-in editor +// +//----------------------------------------------------------------------------- +// LICENSE +// (c) 2023, Steinberg Media Technologies GmbH, All Rights Reserved +//----------------------------------------------------------------------------- +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of the Steinberg Media Technologies nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +// OF THE POSSIBILITY OF SUCH DAMAGE. +//----------------------------------------------------------------------------- + +#pragma once + +#include "platform/iapplication.h" +#include "platform/iwindow.h" +#include + +//------------------------------------------------------------------------ +namespace Steinberg { +namespace Vst { +namespace StandaloneHost { + +//------------------------------------------------------------------------ +class IPlatform +{ +public: + virtual ~IPlatform () noexcept = default; + + virtual void setApplication (ApplicationPtr&& app) = 0; + + virtual WindowPtr createWindow (const std::string& title, Size size, bool resizeable, + const WindowControllerPtr& controller) = 0; + virtual void quit () = 0; + virtual void kill (int resultCode, const std::string& reason) = 0; + + static IPlatform& instance (); +}; + +//------------------------------------------------------------------------ +} // StandaloneHost +} // Vst +} // Steinberg + +extern void ApplicationInit (int argc, const char* argv[]); diff --git a/plugins/vst/standalone/platform/iwindow.h b/plugins/vst/standalone/platform/iwindow.h new file mode 100644 index 00000000..775312e9 --- /dev/null +++ b/plugins/vst/standalone/platform/iwindow.h @@ -0,0 +1,140 @@ +//----------------------------------------------------------------------------- +// Flags : clang-format auto +// Project : VST SDK +// +// Category : StandaloneHost +// Filename : public.sdk/samples/vst-hosting/editorhost/source/platform/iwindow.h +// Created by : Steinberg 09.2016 +// Description : Example of opening a plug-in editor +// +//----------------------------------------------------------------------------- +// LICENSE +// (c) 2023, Steinberg Media Technologies GmbH, All Rights Reserved +//----------------------------------------------------------------------------- +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of the Steinberg Media Technologies nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +// OF THE POSSIBILITY OF SUCH DAMAGE. +//----------------------------------------------------------------------------- + +#pragma once + +#include "pluginterfaces/gui/iplugview.h" +#include +#include + +//------------------------------------------------------------------------ +namespace Steinberg { +namespace Vst { +namespace StandaloneHost { + +using Coord = int32_t; + +//------------------------------------------------------------------------ +struct Point +{ + Coord x; + Coord y; +}; + +//------------------------------------------------------------------------ +struct Size +{ + Coord width; + Coord height; +}; + +//------------------------------------------------------------------------ +inline bool operator!= (const Size& lhs, const Size& rhs) +{ + return lhs.width != rhs.width || lhs.height != rhs.height; +} + +//------------------------------------------------------------------------ +inline bool operator== (const Size& lhs, const Size& rhs) +{ + return lhs.width == rhs.width && lhs.height == rhs.height; +} + +//------------------------------------------------------------------------ +struct Rect +{ + Point origin; + Size size; +}; + +//------------------------------------------------------------------------ +inline Rect ViewRectToRect (ViewRect r) +{ + Rect result {}; + result.origin.x = r.left; + result.origin.y = r.top; + result.size.width = r.right - r.left; + result.size.height = r.bottom - r.top; + return result; +} + +//------------------------------------------------------------------------ +struct NativePlatformWindow +{ + FIDString type; + void* ptr; +}; + +class IWindow; + +//------------------------------------------------------------------------ +class IWindowController +{ +public: + virtual ~IWindowController () noexcept = default; + + virtual void onShow (IWindow& window) = 0; + virtual void onClose (IWindow& window) = 0; + virtual void onResize (IWindow& window, Size newSize) = 0; + virtual void onContentScaleFactorChanged (IWindow& window, float newScaleFactor) = 0; + virtual Size constrainSize (IWindow& window, Size requestedSize) = 0; +}; + +using WindowControllerPtr = std::shared_ptr; + +//------------------------------------------------------------------------ +class IWindow +{ +public: + virtual ~IWindow () noexcept = default; + + virtual void show () = 0; + virtual void close () = 0; + virtual void resize (Size newSize) = 0; + virtual Size getContentSize () = 0; + + virtual NativePlatformWindow getNativePlatformWindow () const = 0; + + virtual tresult queryInterface (const TUID iid, void** obj) = 0; +}; + +using WindowPtr = std::shared_ptr; + +//------------------------------------------------------------------------ +} // StandaloneHost +} // Vst +} // Steinberg diff --git a/plugins/vst/standalone/platform/linux/platform.cpp b/plugins/vst/standalone/platform/linux/platform.cpp new file mode 100644 index 00000000..9ab09a44 --- /dev/null +++ b/plugins/vst/standalone/platform/linux/platform.cpp @@ -0,0 +1,257 @@ +//----------------------------------------------------------------------------- +// Flags : clang-format auto +// Project : VST SDK +// +// Category : StandaloneHost +// Filename : public.sdk/samples/vst-hosting/editorhost/source/platform/linux/platform.cpp +// Created by : Steinberg 09.2016 +// Description : Example of opening a plug-in editor +// +//----------------------------------------------------------------------------- +// LICENSE +// (c) 2023, Steinberg Media Technologies GmbH, All Rights Reserved +//----------------------------------------------------------------------------- +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of the Steinberg Media Technologies nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +// OF THE POSSIBILITY OF SUCH DAMAGE. +//----------------------------------------------------------------------------- + +#include "platform/iplatform.h" +#include "platform/linux/window.h" +#ifndef EDITORHOST_GTK +#include "platform/linux/runloop.h" +#endif + +#ifdef EDITORHOST_GTK +#include +#endif + +#include +#include +#include +#include + +//------------------------------------------------------------------------ +namespace Steinberg { +namespace Vst { +namespace StandaloneHost { + +using namespace std::chrono; +using clock = high_resolution_clock; + +//------------------------------------------------------------------------ +static int pause (int milliSeconds) +{ + struct timeval timeOut; + if (milliSeconds > 0) + { + timeOut.tv_usec = (milliSeconds % (unsigned long)1000) * 1000; + timeOut.tv_sec = milliSeconds / (unsigned long)1000; + + select (0, nullptr, nullptr, nullptr, &timeOut); + } + return (milliSeconds > 0 ? milliSeconds : 0); +} + +//------------------------------------------------------------------------ +//------------------------------------------------------------------------ +class Platform : public IPlatform +{ +public: + static Platform& instance () + { + static Platform gInstance; + return gInstance; + } + + void setApplication (ApplicationPtr&& app) override; + WindowPtr createWindow (const std::string& title, Size size, bool resizeable, + const WindowControllerPtr& controller) override; + + void quit () override; + void kill (int resultCode, const std::string& reason) override; + + void run (const std::vector& cmdArgs); + + static const int kMinEventLoopRate = 16; // 60Hz +private: + void onWindowClosed (X11Window* window); + void closeAllWindows (); + void eventLoop (); + + ApplicationPtr application; + Display* xDisplay {nullptr}; + +#ifdef EDITORHOST_GTK + Glib::RefPtr app; +#endif + std::vector> windows; +}; + +//------------------------------------------------------------------------ +IPlatform& IPlatform::instance () +{ + return Platform::instance (); +} + +//------------------------------------------------------------------------ +void Platform::setApplication (ApplicationPtr&& app) +{ + application = std::move (app); +} + +//------------------------------------------------------------------------ +WindowPtr Platform::createWindow (const std::string& title, Size size, bool resizeable, + const WindowControllerPtr& controller) +{ + auto window = X11Window::make (title, size, resizeable, controller, xDisplay, + [this] (X11Window* window) { onWindowClosed (window); }); + if (window) + windows.push_back (std::static_pointer_cast (window)); + return window; +} + +//------------------------------------------------------------------------ +void Platform::onWindowClosed (X11Window* window) +{ + for (auto it = windows.begin (); it != windows.end (); ++it) + { + if (it->get () == window) + { + windows.erase (it); + break; + } + } +} + +//------------------------------------------------------------------------ +void Platform::closeAllWindows () +{ + for (auto& w : windows) + { + w->close (); + } +} + +//------------------------------------------------------------------------ +void Platform::quit () +{ + static bool recursiveGuard = false; + if (recursiveGuard) + return; + recursiveGuard = true; + + closeAllWindows (); + + if (application) + application->terminate (); + +#ifndef EDITORHOST_GTK + RunLoop::instance ().stop (); +#endif + + recursiveGuard = false; +} + +//------------------------------------------------------------------------ +void Platform::kill (int resultCode, const std::string& reason) +{ + std::cout << reason << "\n"; + exit (resultCode); +} + +//------------------------------------------------------------------------ +void Platform::run (const std::vector& cmdArgs) +{ +#ifdef EDITORHOST_GTK + app = Gtk::Application::create ("net.steinberg.vstsdk.editorhost"); + + application->init (cmdArgs); + eventLoop (); + +#else + // Connect to X server + std::string displayName (getenv ("DISPLAY")); + if (displayName.empty ()) + displayName = ":0.0"; + + if ((xDisplay = XOpenDisplay (displayName.data ())) == nullptr) + { + return; + } + + RunLoop::instance ().setDisplay (xDisplay); + + application->init (cmdArgs); + + eventLoop (); + + XCloseDisplay (xDisplay); +#endif +} + +//------------------------------------------------------------------------ +void Platform::eventLoop () +{ +#ifdef EDITORHOST_GTK + while (!windows.empty ()) + { + auto startTime = clock::now (); + while (gtk_events_pending ()) + { + gtk_main_iteration_do (true); + } + for (auto item : windows) + { + item->onIdle (); + } + // prevent running faster than a given rate + auto duration = duration_cast (clock::now () - startTime).count (); + if (duration < kMinEventLoopRate) + pause (kMinEventLoopRate - duration); + } +#else + RunLoop::instance ().start (); +#endif +} + +//------------------------------------------------------------------------ +} // StandaloneHost +} // Vst +} // Steinberg + +//------------------------------------------------------------------------ +//------------------------------------------------------------------------ +int main (int argc, char* argv[]) +{ +#ifdef EDITORHOST_GTK + gtk_init (&argc, &argv); +#endif + + std::vector cmdArgs; + for (int i = 0; i < argc; ++i) + cmdArgs.push_back (argv[i]); + + Steinberg::Vst::StandaloneHost::Platform::instance ().run (cmdArgs); + + return 0; +} diff --git a/plugins/vst/standalone/platform/linux/runloop.cpp b/plugins/vst/standalone/platform/linux/runloop.cpp new file mode 100644 index 00000000..8d658c0f --- /dev/null +++ b/plugins/vst/standalone/platform/linux/runloop.cpp @@ -0,0 +1,300 @@ +//----------------------------------------------------------------------------- +// Flags : clang-format auto +// Project : VST SDK +// +// Category : StandaloneHost +// Filename : public.sdk/samples/vst-hosting/editorhost/source/platform/linux/runloop.cpp +// Created by : Steinberg 09.2016 +// Description : Example of opening a plug-in editor +// +//----------------------------------------------------------------------------- +// LICENSE +// (c) 2023, Steinberg Media Technologies GmbH, All Rights Reserved +//----------------------------------------------------------------------------- +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of the Steinberg Media Technologies nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +// OF THE POSSIBILITY OF SUCH DAMAGE. +//----------------------------------------------------------------------------- + +#include "platform/linux/runloop.h" +#include +#include + +//------------------------------------------------------------------------ +namespace Steinberg { +namespace Vst { +namespace StandaloneHost { + +using LockGuard = std::lock_guard; + +//------------------------------------------------------------------------ +RunLoop& RunLoop::instance () +{ + static RunLoop gInstance; + return gInstance; +} + +//------------------------------------------------------------------------ +void RunLoop::setDisplay (Display* display) +{ + this->display = display; +} + +//------------------------------------------------------------------------ +void RunLoop::registerWindow (XID window, const EventCallback& callback) +{ + map.emplace (window, callback); +} + +//------------------------------------------------------------------------ +void RunLoop::unregisterWindow (XID window) +{ + auto it = map.find (window); + if (it == map.end ()) + return; + map.erase (it); +} + +//------------------------------------------------------------------------ +void RunLoop::registerFileDescriptor (int fd, const FileDescriptorCallback& callback) +{ + fileDescriptors.emplace (fd, callback); +} + +//------------------------------------------------------------------------ +void RunLoop::unregisterFileDescriptor (int fd) +{ + auto it = fileDescriptors.find (fd); + if (it == fileDescriptors.end ()) + return; + fileDescriptors.erase (it); +} + +//------------------------------------------------------------------------ +void RunLoop::select (timeval* timeout) +{ + int nfds = 0; + fd_set readFDs = {}, writeFDs = {}, exceptFDs = {}; + + for (auto& e : fileDescriptors) + { + int fd = e.first; + FD_SET (fd, &readFDs); + FD_SET (fd, &writeFDs); + FD_SET (fd, &exceptFDs); + nfds = std::max (nfds, fd); + } + + int result = ::select (nfds, &readFDs, &writeFDs, nullptr, timeout); + + if (result > 0) + { + for (auto& e : fileDescriptors) + { + if (FD_ISSET (e.first, &readFDs) || FD_ISSET (e.first, &writeFDs) || + FD_ISSET (e.first, &exceptFDs)) + e.second (e.first); + } + } +} + +//------------------------------------------------------------------------ +bool RunLoop::handleEvents () +{ + auto count = XPending (display); + if (count == 0) + return false; + for (auto i = 0; i < count; ++i) + { + XEvent event {}; + XNextEvent (display, &event); + auto it = map.find (event.xany.window); + if (it != map.end ()) + { + it->second (event); + if (event.type == DestroyNotify) + { + map.erase (it); + } + } + else + { + XPutBackEvent (display, &event); + break; + } + } + return true; +} + +//------------------------------------------------------------------------ +TimerID RunLoop::registerTimer (TimerInterval interval, const TimerCallback& callback) +{ + return timerProcessor.registerTimer (interval, callback); +} + +//------------------------------------------------------------------------ +void RunLoop::unregisterTimer (TimerID id) +{ + timerProcessor.unregisterTimer (id); +} + +//------------------------------------------------------------------------ +bool timeValEmpty (timeval& val) +{ + return val.tv_sec == 0 && val.tv_usec == 0; +} + +//------------------------------------------------------------------------ +void RunLoop::start () +{ + using namespace std::chrono; + using clock = high_resolution_clock; + + running = true; + + auto fd = XConnectionNumber (display); + registerFileDescriptor (fd, [this] (int) { handleEvents (); }); + + XSync (display, false); + handleEvents (); + timeval selectTimeout {}; + while (running && !map.empty ()) + { + select (timeValEmpty (selectTimeout) ? nullptr : &selectTimeout); + auto nextFireTime = timerProcessor.handleTimersAndReturnNextFireTimeInMs (); + if (nextFireTime == TimerProcessor::noTimers) + { + selectTimeout = {}; + } + else + { + selectTimeout.tv_sec = nextFireTime / 1000; + selectTimeout.tv_usec = (nextFireTime - (selectTimeout.tv_sec * 1000)) * 1000; + } + } +} + +//------------------------------------------------------------------------ +void RunLoop::stop () +{ + running = false; +} + +//------------------------------------------------------------------------ +//------------------------------------------------------------------------ +//------------------------------------------------------------------------ +uint64_t TimerProcessor::handleTimersAndReturnNextFireTimeInMs () +{ + using std::chrono::time_point_cast; + + if (timers.empty ()) + return noTimers; + + auto current = time_point_cast (Clock::now ()); + + std::vector timersToFire; + for (auto& timer : timers) + { + if (timer.nextFireTime > current) + break; + timersToFire.push_back (timer.id); + updateTimerNextFireTime (timer, current); + } + + for (auto id : timersToFire) + { + for (auto& timer : timers) + { + if (timer.id == id) + { + timer.callback (timer.id); + break; + } + } + } + if (timersToFire.empty ()) + return noTimers; + + sortTimers (); + + auto nextFireTime = timers.front ().nextFireTime; + current = now (); + if (nextFireTime < current) + return 0; + return (nextFireTime - current).count (); +} + +//------------------------------------------------------------------------ +void TimerProcessor::updateTimerNextFireTime (Timer& timer, TimePoint current) +{ + timer.nextFireTime = current + Millisecond (timer.interval); +} + +//------------------------------------------------------------------------ +void TimerProcessor::sortTimers () +{ + std::sort (timers.begin (), timers.end (), + [] (const Timer& t1, const Timer& t2) { return t1.nextFireTime < t2.nextFireTime; }); +} + +//------------------------------------------------------------------------ +auto TimerProcessor::now () -> TimePoint +{ + using std::chrono::time_point_cast; + + return time_point_cast (Clock::now ()); +} + +//------------------------------------------------------------------------ +auto TimerProcessor::registerTimer (TimerInterval interval, const TimerCallback& callback) + -> TimerID +{ + auto timerId = ++timerIdCounter; + Timer timer; + timer.id = timerId; + timer.callback = callback; + timer.interval = interval; + updateTimerNextFireTime (timer, now ()); + + timers.emplace_back (std::move (timer)); + sortTimers (); + + return timerId; +} + +//------------------------------------------------------------------------ +void TimerProcessor::unregisterTimer (TimerID id) +{ + for (auto it = timers.begin (), end = timers.end (); it != end; ++it) + { + if (it->id == id) + { + timers.erase (it); + break; + } + } +} + +//------------------------------------------------------------------------ +} // StandaloneHost +} // Vst +} // Steinberg diff --git a/plugins/vst/standalone/platform/linux/runloop.h b/plugins/vst/standalone/platform/linux/runloop.h new file mode 100644 index 00000000..d376d9bf --- /dev/null +++ b/plugins/vst/standalone/platform/linux/runloop.h @@ -0,0 +1,130 @@ +//----------------------------------------------------------------------------- +// Flags : clang-format auto +// Project : VST SDK +// +// Category : StandaloneHost +// Filename : public.sdk/samples/vst-hosting/editorhost/source/platform/linux/runloop.h +// Created by : Steinberg 09.2016 +// Description : Example of opening a plug-in editor +// +//----------------------------------------------------------------------------- +// LICENSE +// (c) 2023, Steinberg Media Technologies GmbH, All Rights Reserved +//----------------------------------------------------------------------------- +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of the Steinberg Media Technologies nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +// OF THE POSSIBILITY OF SUCH DAMAGE. +//----------------------------------------------------------------------------- + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +//------------------------------------------------------------------------ +namespace Steinberg { +namespace Vst { +namespace StandaloneHost { + +//------------------------------------------------------------------------ +using TimerID = uint64_t; +using TimerInterval = uint64_t; +using TimerCallback = std::function; + +//------------------------------------------------------------------------ +class TimerProcessor +{ +public: + TimerID registerTimer (TimerInterval interval, const TimerCallback& callback); + void unregisterTimer (TimerID id); + + static constexpr uint64_t noTimers = std::numeric_limits::max (); + uint64_t handleTimersAndReturnNextFireTimeInMs (); + +private: + using Clock = std::chrono::steady_clock; + using Millisecond = std::chrono::milliseconds; + using TimePoint = std::chrono::time_point; + + struct Timer + { + TimerID id; + TimerInterval interval; + TimerCallback callback; + TimePoint nextFireTime; + }; + using Timers = std::vector; + Timers timers; + TimerID timerIdCounter {0}; + + void updateTimerNextFireTime (Timer& timer, TimePoint current); + void sortTimers (); + TimePoint now (); +}; + +//------------------------------------------------------------------------ +class RunLoop +{ +public: + using EventCallback = std::function; + using FileDescriptorCallback = std::function; + + static RunLoop& instance (); + + void setDisplay (Display* display); + + void registerWindow (XID window, const EventCallback& callback); + void unregisterWindow (XID window); + + void registerFileDescriptor (int fd, const FileDescriptorCallback& callback); + void unregisterFileDescriptor (int fd); + + TimerID registerTimer (TimerInterval interval, const TimerCallback& callback); + void unregisterTimer (TimerID id); + + void start (); + void stop (); + +private: + void select (timeval* timeout = nullptr); + bool handleEvents (); + + using WindowMap = std::unordered_map; + using FileDescriptorCallbacks = std::unordered_map; + + WindowMap map; + FileDescriptorCallbacks fileDescriptors; + TimerProcessor timerProcessor; + + Display* display {nullptr}; + bool running {false}; +}; + +//------------------------------------------------------------------------ +} // StandaloneHost +} // Vst +} // Steinberg diff --git a/plugins/vst/standalone/platform/linux/window.cpp b/plugins/vst/standalone/platform/linux/window.cpp new file mode 100644 index 00000000..9bf7b76a --- /dev/null +++ b/plugins/vst/standalone/platform/linux/window.cpp @@ -0,0 +1,1023 @@ +//----------------------------------------------------------------------------- +// Flags : clang-format auto +// Project : VST SDK +// +// Category : StandaloneHost +// Filename : public.sdk/samples/vst-hosting/editorhost/source/platform/linux/window.cpp +// Created by : Steinberg 09.2016 +// Description : Example of opening a plug-in editor +// +//----------------------------------------------------------------------------- +// LICENSE +// (c) 2023, Steinberg Media Technologies GmbH, All Rights Reserved +//----------------------------------------------------------------------------- +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of the Steinberg Media Technologies nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +// OF THE POSSIBILITY OF SUCH DAMAGE. +//----------------------------------------------------------------------------- + +#include "window.h" + +#ifdef EDITORHOST_GTK +#include +#endif + +#include "public.sdk/source/vst/utility/stringconvert.h" +#include +#include +#include +#include + +#ifdef EDITORHOST_GTK +#include +#include +#else +#include "platform/linux/runloop.h" +#include +#endif + +//------------------------------------------------------------------------ +namespace Steinberg { +namespace Vst { +namespace StandaloneHost { + +#ifdef EDITORHOST_GTK + +struct ContentView : Gtk::Fixed +{ + void on_size_allocate (Gtk::Allocation& allocation) override + { + Gtk::Widget::on_size_allocate (allocation); + for (auto child : get_children ()) + { + child->size_allocate (allocation); + } + } +}; + +struct WindowGtk : Gtk::Window +{ + WindowGtk () + { + add (container); + container.put (label, 0, 0); + container.add (socket); + show_all_children (); + } + + void on_show () override + { + Gtk::Window::on_show (); + if (controller) + controller->onShow (*window); + } + + void on_unmap () override + { + Gtk::Window::on_unmap (); + if (controller) + controller->onClose (*window); + if (windowClosedFunc) + windowClosedFunc (window); + } + + bool on_configure_event (GdkEventConfigure* event) override + { + // TODO: does not work yet + Gtk::Window::on_configure_event (event); + return false; + if (controller) + { + if (currentSize.width != event->width || currentSize.height != event->height) + { + Size newSize {event->width, event->height}; + auto size = controller->constrainSize (*window, newSize); + if (size != newSize) + { + resize (size.width, size.height); + } + } + } + return true; + } + + void on_size_allocate (Gtk::Allocation& allocation) override + { + auto size = Size {allocation.get_width (), allocation.get_height ()}; + if (size != currentSize) + { + if (controller) + { + size = controller->constrainSize (*window, size); + controller->onResize (*window, size); + } + currentSize = size; + allocation.set_width (currentSize.width); + allocation.set_height (currentSize.height); + resize (currentSize.width, currentSize.height); + } + Gtk::Window::on_size_allocate (allocation); + } + + void checkSize () + { + int width, height; + get_size (width, height); + if (currentSize.width != width || currentSize.height != height) + { + resize (currentSize.width, currentSize.height); + } + } + + ::Window getId () + { + if (!socket.is_visible ()) + return 0; + std::cout << "Socket ID: " << socket.get_id () << "\n"; + std::cout.flush (); + return socket.get_id (); + } + + X11Window::WindowClosedFunc windowClosedFunc; + WindowControllerPtr controller {nullptr}; + X11Window* window {nullptr}; + Size currentSize; + ContentView container; + Gtk::Label label {"HostLabel"}; + Gtk::Socket socket; +}; +#else + +#endif + +//------------------------------------------------------------------------ +struct X11Window::Impl : public Linux::IRunLoop +{ + Impl (X11Window* x11Window); + bool init (const std::string& name, Size size, bool resizeable, + const WindowControllerPtr& controller, Display* display, + const WindowClosedFunc& windowClosedFunc); + + void show (); + void close (); + Size getSize () const; + void resize (Size newSize, bool force); + +#ifdef EDITORHOST_GTK + WindowGtk window; + +//------------------------------------------------------------------------ + struct EventHandler + { + IPtr handler; + GIOChannel* ioChannel; + }; + +//------------------------------------------------------------------------ + using TimerHandler = IPtr; + using TimerID = guint; +#else +//------------------------------------------------------------------------ + struct XEmbedInfo + { + uint32_t version; + uint32_t flags; + }; + + ~Impl (); + void onClose (); + bool handleMainWindowEvent (const XEvent& event); + bool handlePlugEvent (const XEvent& event); + XEmbedInfo* getXEmbedInfo (); + void checkSize (); + void callPlugEventHandlers (); + + WindowControllerPtr controller {nullptr}; + WindowClosedFunc windowClosedFunc; + Display* xDisplay {nullptr}; + XEmbedInfo* xembedInfo {nullptr}; + Window xWindow {}; + Window plugParentWindow {}; + Window plugWindow {}; + GC xGraphicContext {}; + Atom xEmbedInfoAtom {None}; + Atom xEmbedAtom {None}; + bool isMapped {false}; + + using EventHandler = IPtr; + using TimerHandler = IPtr; +#endif + Size mCurrentSize {}; + X11Window* x11Window {nullptr}; + + using EventHandlers = std::unordered_map; + EventHandlers eventHandlers; + + using TimerHandlers = std::unordered_map; + TimerHandlers timerHandlers; + + tresult PLUGIN_API registerEventHandler (Linux::IEventHandler* handler, + Linux::FileDescriptor fd) override; + tresult PLUGIN_API unregisterEventHandler (Linux::IEventHandler* handler) override; + tresult PLUGIN_API registerTimer (Linux::ITimerHandler* handler, + Linux::TimerInterval milliseconds) override; + tresult PLUGIN_API unregisterTimer (Linux::ITimerHandler* handler) override; + + uint32 PLUGIN_API addRef () override { return 1000; } + uint32 PLUGIN_API release () override { return 1000; } + tresult PLUGIN_API queryInterface (const TUID, void**) override { return kNoInterface; } +}; + +//------------------------------------------------------------------------ +auto X11Window::make (const std::string& name, Size size, bool resizeable, + const WindowControllerPtr& controller, Display* display, + const WindowClosedFunc& windowClosedFunc) -> Ptr +{ + auto window = std::make_shared (); + if (window->init (name, size, resizeable, controller, display, windowClosedFunc)) + { + return window; + } + return nullptr; +} + +//------------------------------------------------------------------------ +X11Window::X11Window () +{ + impl = std::unique_ptr (new Impl (this)); +} + +//------------------------------------------------------------------------ +X11Window::~X11Window () +{ +} + +//------------------------------------------------------------------------ +bool X11Window::init (const std::string& name, Size size, bool resizeable, + const WindowControllerPtr& controller, Display* display, + const WindowClosedFunc& windowClosedFunc) +{ + return impl->init (name, size, resizeable, controller, display, windowClosedFunc); +} + +//------------------------------------------------------------------------ +Size X11Window::getSize () const +{ + return impl->getSize (); +} + +//------------------------------------------------------------------------ +void X11Window::show () +{ + impl->show (); +} + +//------------------------------------------------------------------------ +void X11Window::close () +{ + impl->close (); +} + +//------------------------------------------------------------------------ +void X11Window::resize (Size newSize) +{ + impl->resize (newSize, false); +} + +//------------------------------------------------------------------------ +Size X11Window::getContentSize () +{ + return {}; +} + +//------------------------------------------------------------------------ +NativePlatformWindow X11Window::getNativePlatformWindow () const +{ +#ifdef EDITORHOST_GTK + return {kPlatformTypeX11EmbedWindowID, reinterpret_cast (impl->window.getId ())}; +#else + return {kPlatformTypeX11EmbedWindowID, reinterpret_cast (impl->plugParentWindow)}; +#endif +} + +//------------------------------------------------------------------------ +tresult X11Window::queryInterface (const TUID iid, void** obj) +{ + if (FUnknownPrivate::iidEqual (iid, Linux::IRunLoop::iid)) + { + *obj = impl.get (); + return kResultTrue; + } + return kNoInterface; +} + +//------------------------------------------------------------------------ +void X11Window::onIdle () +{ +#ifdef EDITORHOST_GTK + impl->window.checkSize (); +#endif +} + +#ifdef EDITORHOST_GTK +//------------------------------------------------------------------------ +X11Window::Impl::Impl (X11Window* x11Window) : x11Window (x11Window) +{ +} + +//------------------------------------------------------------------------ +bool X11Window::Impl::init (const std::string& name, Size size, bool resizeable, + const WindowControllerPtr& controller, Display* display, + const WindowClosedFunc& windowClosedFunc) +{ + window.set_title (name.data ()); + window.set_default_size (size.width, size.height); + window.set_resizable (resizeable); + window.show (); + window.set_visible (false); + + window.window = x11Window; + window.controller = controller; + window.windowClosedFunc = windowClosedFunc; + + return true; +} + +//------------------------------------------------------------------------ +void X11Window::Impl::show () +{ + window.set_visible (true); + window.show (); +} + +//------------------------------------------------------------------------ +void X11Window::Impl::close () +{ + window.close (); +} + +//------------------------------------------------------------------------ +Size X11Window::Impl::getSize () const +{ + int width, height; + window.get_size (width, height); + return {width, height}; +} + +//------------------------------------------------------------------------ +void X11Window::Impl::resize (Size newSize, bool force) +{ + if (!force && mCurrentSize == newSize) + return; + window.resize (newSize.width, newSize.height); + mCurrentSize = newSize; +} + +//------------------------------------------------------------------------ +tresult PLUGIN_API X11Window::Impl::registerEventHandler (Linux::IEventHandler* handler, + Linux::FileDescriptor fd) +{ + if (!handler) + return kInvalidArgument; + + if (eventHandlers.find (fd) != eventHandlers.end ()) + return kInvalidArgument; + + auto display = gdk_window_get_display (window.get_window ()->gobj ()); + auto xDisplay = gdk_x11_display_get_xdisplay (display); + if (XConnectionNumber (xDisplay) == fd) + { + // the plug-in is using GTK as well, so no need in registering it as event handler + return kResultTrue; + } + + auto ioChannel = g_io_channel_unix_new (fd); + g_io_add_watch (ioChannel, (GIOCondition) (G_IO_IN | G_IO_OUT | G_IO_ERR | G_IO_HUP), + [] (auto* source, auto condition, auto data) { + (void)condition; + auto handler = reinterpret_cast (data); + handler->onFDIsSet (g_io_channel_unix_get_fd (source)); + return static_cast (0); + }, + handler); + + eventHandlers[fd] = {handler, ioChannel}; + + return kResultTrue; +} + +//------------------------------------------------------------------------ +tresult PLUGIN_API X11Window::Impl::unregisterEventHandler (Linux::IEventHandler* handler) +{ + if (!handler) + return kInvalidArgument; + + for (auto it = eventHandlers.begin (), end = eventHandlers.end (); it != end; ++it) + { + if (it->second.handler == handler) + { + g_io_channel_unref (it->second.ioChannel); + eventHandlers.erase (it); + return kResultTrue; + } + } + + return kResultFalse; +} + +//------------------------------------------------------------------------ +tresult PLUGIN_API X11Window::Impl::registerTimer (Linux::ITimerHandler* handler, + Linux::TimerInterval milliseconds) +{ + if (!handler || milliseconds == 0) + return kInvalidArgument; + + auto id = g_timeout_add (milliseconds, + [] (auto data) { + auto handler = reinterpret_cast (data); + handler->onTimer (); + return static_cast (true); + }, + handler); + timerHandlers.emplace (id, handler); + return kResultTrue; +} + +//------------------------------------------------------------------------ +tresult PLUGIN_API X11Window::Impl::unregisterTimer (Linux::ITimerHandler* handler) +{ + if (!handler) + return kInvalidArgument; + + for (auto it = timerHandlers.begin (), end = timerHandlers.end (); it != end; ++it) + { + if (it->second == handler) + { + if (auto source = g_main_context_find_source_by_id (nullptr, it->first)) + { + g_source_destroy (source); + } + timerHandlers.erase (it); + return kResultTrue; + } + } + + return kNotImplemented; +} + +#else + +/* XEMBED messages */ +#define XEMBED_EMBEDDED_NOTIFY 0 +#define XEMBED_WINDOW_ACTIVATE 1 +#define XEMBED_WINDOW_DEACTIVATE 2 +#define XEMBED_REQUEST_FOCUS 3 +#define XEMBED_FOCUS_IN 4 +#define XEMBED_FOCUS_OUT 5 +#define XEMBED_FOCUS_NEXT 6 +#define XEMBED_FOCUS_PREV 7 +/* 8-9 were used for XEMBED_GRAB_KEY/XEMBED_UNGRAB_KEY */ +#define XEMBED_MODALITY_ON 10 +#define XEMBED_MODALITY_OFF 11 +#define XEMBED_REGISTER_ACCELERATOR 12 +#define XEMBED_UNREGISTER_ACCELERATOR 13 +#define XEMBED_ACTIVATE_ACCELERATOR 14 + +void send_xembed_message (Display* dpy, /* display */ + Window w, /* receiver */ + Atom messageType, long message, /* message opcode */ + long detail, /* message detail */ + long data1, /* message data 1 */ + long data2 /* message data 2 */ + ) +{ + XEvent ev; + memset (&ev, 0, sizeof (ev)); + ev.xclient.type = ClientMessage; + ev.xclient.window = w; + ev.xclient.message_type = messageType; + ev.xclient.format = 32; + ev.xclient.data.l[0] = CurrentTime; + ev.xclient.data.l[1] = message; + ev.xclient.data.l[2] = detail; + ev.xclient.data.l[3] = data1; + ev.xclient.data.l[4] = data2; + XSendEvent (dpy, w, False, NoEventMask, &ev); + XSync (dpy, False); +} + +#define XEMBED_MAPPED (1 << 0) + +//------------------------------------------------------------------------ +X11Window::Impl::Impl (X11Window* x11Window) : x11Window (x11Window) +{ +} + +//------------------------------------------------------------------------ +X11Window::Impl::~Impl () +{ +} + +//------------------------------------------------------------------------ +bool X11Window::Impl::init (const std::string& name, Size size, bool resizeable, + const WindowControllerPtr& controller, Display* display, + const WindowClosedFunc& windowClosedFunc) +{ + this->windowClosedFunc = windowClosedFunc; + this->controller = controller; + xDisplay = display; + + xEmbedInfoAtom = XInternAtom (xDisplay, "_XEMBED_INFO", true); + if (xEmbedInfoAtom == None) + { + std::cerr << "_XEMBED_INFO does not exist" << std::endl; + return false; + } + + // Get screen size from display + auto screen_num = DefaultScreen (xDisplay); + auto displayWidth = DisplayWidth (xDisplay, screen_num); + auto displayHeight = DisplayHeight (xDisplay, screen_num); + unsigned int border_width = 1; + + XVisualInfo vInfo; + if (!XMatchVisualInfo (xDisplay, screen_num, 24, TrueColor, &vInfo)) + { + exit (-1); + } + + XSetWindowAttributes winAttr {}; + winAttr.border_pixel = BlackPixel (xDisplay, screen_num); + winAttr.background_pixel = WhitePixel (xDisplay, screen_num); + winAttr.colormap = + XCreateColormap (xDisplay, XDefaultRootWindow (xDisplay), vInfo.visual, AllocNone); + uint32_t winAttrMask = CWBackPixel | CWColormap | CWBorderPixel; + xWindow = XCreateWindow (xDisplay, RootWindow (xDisplay, screen_num), 0, 0, displayWidth, + displayHeight, border_width, vInfo.depth, InputOutput, vInfo.visual, + winAttrMask, &winAttr); + XFlush (xDisplay); + + resize (size, true); + + XSelectInput (xDisplay, xWindow, /* KeyPressMask | ButtonPressMask |*/ + ExposureMask | /*ResizeRedirectMask |*/ StructureNotifyMask | + SubstructureNotifyMask | FocusChangeMask); + + auto sizeHints = XAllocSizeHints (); + sizeHints->flags = PMinSize; + if (!resizeable) + { + sizeHints->flags |= PMaxSize; + sizeHints->min_width = sizeHints->max_width = size.width; + sizeHints->min_height = sizeHints->max_height = size.height; + } + else + { + sizeHints->min_width = sizeHints->min_height = 80; + } + XSetWMNormalHints (xDisplay, xWindow, sizeHints); + XFree (sizeHints); + + // set a title + XStoreName (xDisplay, xWindow, name.data ()); + + XTextProperty iconName; + auto icon_name = const_cast (name.data ()); + XStringListToTextProperty (&icon_name, 1, &iconName); + XSetWMIconName (xDisplay, xWindow, &iconName); + + Atom wm_delete_window; + wm_delete_window = XInternAtom (xDisplay, "WM_DELETE_WINDOW", False); + XSetWMProtocols (xDisplay, xWindow, &wm_delete_window, 1); + + xGraphicContext = XCreateGC (xDisplay, xWindow, 0, 0); + XSetForeground (xDisplay, xGraphicContext, WhitePixel (xDisplay, screen_num)); + XSetBackground (xDisplay, xGraphicContext, BlackPixel (xDisplay, screen_num)); + + winAttr = {}; + winAttr.override_redirect = true; + winAttr.event_mask = + ExposureMask | KeyPressMask | ButtonPressMask | ButtonReleaseMask | ButtonMotionMask; + plugParentWindow = + XCreateWindow (xDisplay, xWindow, 0, 0, size.width, size.height, border_width, vInfo.depth, + InputOutput, CopyFromParent, winAttrMask, &winAttr); + + XSelectInput (xDisplay, plugParentWindow, SubstructureNotifyMask | PropertyChangeMask); + + XMapWindow (xDisplay, plugParentWindow); + + RunLoop::instance ().registerWindow (plugParentWindow, + [this] (const XEvent& e) { return handlePlugEvent (e); }); + + RunLoop::instance ().registerWindow ( + xWindow, [this] (const XEvent& e) { return handleMainWindowEvent (e); }); + + return true; +} + +//------------------------------------------------------------------------ +tresult PLUGIN_API X11Window::Impl::registerEventHandler (Linux::IEventHandler* handler, + Linux::FileDescriptor fd) +{ + if (!handler || eventHandlers.find (fd) != eventHandlers.end ()) + return kInvalidArgument; + + RunLoop::instance ().registerFileDescriptor (fd, + [handler] (int fd) { handler->onFDIsSet (fd); }); + eventHandlers.emplace (fd, handler); + return kResultTrue; +} + +//------------------------------------------------------------------------ +tresult PLUGIN_API X11Window::Impl::unregisterEventHandler (Linux::IEventHandler* handler) +{ + if (!handler) + return kInvalidArgument; + + for (auto it = eventHandlers.begin (), end = eventHandlers.end (); it != end; ++it) + { + if (it->second == handler) + { + RunLoop::instance ().unregisterFileDescriptor (it->first); + eventHandlers.erase (it); + return kResultTrue; + } + } + + return kResultFalse; +} + +//------------------------------------------------------------------------ +tresult PLUGIN_API X11Window::Impl::registerTimer (Linux::ITimerHandler* handler, + Linux::TimerInterval milliseconds) +{ + if (!handler || milliseconds == 0) + return kInvalidArgument; + + auto id = RunLoop::instance ().registerTimer (milliseconds, + [handler] (auto) { handler->onTimer (); }); + timerHandlers.emplace (id, handler); + return kResultTrue; +} + +//------------------------------------------------------------------------ +tresult PLUGIN_API X11Window::Impl::unregisterTimer (Linux::ITimerHandler* handler) +{ + if (!handler) + return kInvalidArgument; + + for (auto it = timerHandlers.begin (), end = timerHandlers.end (); it != end; ++it) + { + if (it->second == handler) + { + RunLoop::instance ().unregisterTimer (it->first); + timerHandlers.erase (it); + return kResultTrue; + } + } + + return kNotImplemented; +} + +//------------------------------------------------------------------------ +void X11Window::Impl::callPlugEventHandlers () +{ + for (auto& e : eventHandlers) + { + e.second->onFDIsSet (e.first); + } +} + +//------------------------------------------------------------------------ +void X11Window::Impl::show () +{ + XMapWindow (xDisplay, xWindow); +} + +//------------------------------------------------------------------------ +void X11Window::Impl::close () +{ + XUnmapWindow (xDisplay, xWindow); +} + +//------------------------------------------------------------------------ +void X11Window::Impl::onClose () +{ + XFreeGC (xDisplay, xGraphicContext); + XDestroyWindow (xDisplay, xWindow); + + xDisplay = nullptr; + xWindow = 0; + + isMapped = false; + if (windowClosedFunc) + windowClosedFunc (x11Window); +} + +//------------------------------------------------------------------------ +void X11Window::Impl::resize (Size newSize, bool force) +{ + if (!force && mCurrentSize == newSize) + return; + if (xWindow) + XResizeWindow (xDisplay, xWindow, newSize.width, newSize.height); + if (plugParentWindow) + XResizeWindow (xDisplay, plugParentWindow, newSize.width, newSize.height); + mCurrentSize = newSize; +} + +//------------------------------------------------------------------------ +Size X11Window::Impl::getSize () const +{ + ::Window root; + int x, y; + unsigned int width, height; + unsigned int border_width; + unsigned int depth; + + XGetGeometry (xDisplay, xWindow, &root, &x, &y, &width, &height, &border_width, &depth); + + return {static_cast (width), static_cast (height)}; +} + +//------------------------------------------------------------------------ +void X11Window::Impl::checkSize () +{ + if (getSize () != mCurrentSize) + { + resize (mCurrentSize, true); + } +} + +//------------------------------------------------------------------------ +bool X11Window::Impl::handleMainWindowEvent (const XEvent& event) +{ +#if LOG_EVENTS + std::cout << "event " << event.type << "\n"; +#endif + bool res = false; + + switch (event.type) + { + case Expose: + if (event.xexpose.count == 0) + { + XClearWindow (xDisplay, xWindow); + XFillRectangle (xDisplay, xWindow, xGraphicContext, 0, 0, mCurrentSize.width, + mCurrentSize.height); + } + res = true; + break; + + //--- StructureNotifyMask ------------------------------ + // Window has been resized + case ConfigureNotify: + { + if (event.xconfigure.window != xWindow) + break; + + auto width = event.xconfigure.width; + auto height = event.xconfigure.height; + + Size size {width, height}; + if (mCurrentSize != size) + { + auto constraintSize = controller->constrainSize (*x11Window, size); + if (constraintSize != mCurrentSize) + { + mCurrentSize = size; + controller->onResize (*x11Window, size); + } + if (constraintSize != size) + resize (constraintSize, true); + else + { + if (plugParentWindow) + XResizeWindow (xDisplay, plugParentWindow, size.width, size.height); + } + +#if LOG_EVENTS + std::cout << "new size " << width << " x " << height << "\n"; +#endif + } + res = true; + } + break; + + // Window has been map to the screen + case MapNotify: + { + if (event.xany.window == xWindow && !isMapped) + { + controller->onShow (*x11Window); + isMapped = true; + res = true; + } + } + break; + + case UnmapNotify: + { + if (event.xunmap.window == xWindow) + { + controller->onClose (*x11Window); + onClose (); + res = true; + } + break; + } + case DestroyNotify: break; + + case ClientMessage: + { + if (event.xany.window == xWindow) + { + controller->onClose (*x11Window); + onClose (); + res = true; + } + break; + } + + case FocusIn: + { + if (xembedInfo) + send_xembed_message (xDisplay, plugWindow, xEmbedAtom, XEMBED_WINDOW_ACTIVATE, 0, + plugParentWindow, xembedInfo->version); + break; + } + case FocusOut: + { + if (xembedInfo) + send_xembed_message (xDisplay, plugWindow, xEmbedAtom, XEMBED_WINDOW_DEACTIVATE, 0, + plugParentWindow, xembedInfo->version); + break; + } + + //--- ResizeRedirectMask -------------------------------- + case ResizeRequest: + { + if (event.xany.window == xWindow) + { + auto width = event.xresizerequest.width; + auto height = event.xresizerequest.height; + Size request {width, height}; + if (mCurrentSize != request) + { +#if LOG_EVENTS + std::cout << "requested Size " << width << " x " << height << "\n"; +#endif + + auto constraintSize = controller->constrainSize (*x11Window, request); + if (constraintSize != request) + { +#if LOG_EVENTS + std::cout << "constraint Size " << constraintSize.width << " x " + << constraintSize.height << "\n"; +#endif + } + resize (constraintSize, true); + } + res = true; + } + } + break; + } + return res; +} + +//------------------------------------------------------------------------ +auto X11Window::Impl::getXEmbedInfo () -> XEmbedInfo* +{ + int actualFormat; + unsigned long itemsReturned; + unsigned long bytesAfterReturn; + Atom actualType; + XEmbedInfo* xembedInfo = NULL; + if (xEmbedInfoAtom == None) + xEmbedInfoAtom = XInternAtom (xDisplay, "_XEMBED_INFO", true); + auto err = + XGetWindowProperty (xDisplay, plugWindow, xEmbedInfoAtom, 0, sizeof (xembedInfo), false, + xEmbedInfoAtom, &actualType, &actualFormat, &itemsReturned, + &bytesAfterReturn, reinterpret_cast (&xembedInfo)); + if (err != Success) + return nullptr; + return xembedInfo; +} + +//------------------------------------------------------------------------ +bool X11Window::Impl::handlePlugEvent (const XEvent& event) +{ + bool res = false; + + switch (event.type) + { + // XEMBED specific + case ClientMessage: + { + auto name = XGetAtomName (xDisplay, event.xclient.message_type); + std::cout << name << std::endl; + if (event.xclient.message_type == xEmbedAtom) + { + switch (event.xclient.data.l[1]) + { + case XEMBED_REQUEST_FOCUS: + { + send_xembed_message (xDisplay, plugWindow, xEmbedAtom, XEMBED_FOCUS_IN, 0, + plugParentWindow, xembedInfo->version); + break; + } + } + } + break; + } + case PropertyNotify: + { + auto name = XGetAtomName (xDisplay, event.xproperty.atom); + std::cout << name << std::endl; + if (event.xany.window == plugWindow) + { + if (event.xproperty.atom == xEmbedInfoAtom) + { + if (auto embedInfo = getXEmbedInfo ()) + { + } + } + else + { + } + } + break; + } + case CreateNotify: + { + if (event.xcreatewindow.parent != plugParentWindow) + { + res = true; + break; + } + + plugWindow = event.xcreatewindow.window; + + xembedInfo = getXEmbedInfo (); + if (!xembedInfo) + { + std::cerr << "XGetWindowProperty for _XEMBED_INFO failed" << std::endl; + exit (-1); + } + if (xembedInfo->flags & XEMBED_MAPPED) + { + std::cerr << "Window already mapped error" << std::endl; + exit (-1); + } + RunLoop::instance ().registerWindow ( + plugWindow, [this] (const XEvent& e) { return handlePlugEvent (e); }); + + // XSelectInput (xDisplay, plugWindow, PropertyChangeMask); + + if (xEmbedAtom == None) + xEmbedAtom = XInternAtom (xDisplay, "_XEMBED", true); + assert (xEmbedAtom != None); + + send_xembed_message (xDisplay, plugWindow, xEmbedAtom, XEMBED_EMBEDDED_NOTIFY, 0, + plugParentWindow, xembedInfo->version); + XMapWindow (xDisplay, plugWindow); + XResizeWindow (xDisplay, plugWindow, mCurrentSize.width, mCurrentSize.height); + // XSetInputFocus (xDisplay, plugWindow, RevertToParent, CurrentTime); + send_xembed_message (xDisplay, plugWindow, xEmbedAtom, XEMBED_WINDOW_ACTIVATE, 0, + plugParentWindow, xembedInfo->version); + send_xembed_message (xDisplay, plugWindow, xEmbedAtom, XEMBED_FOCUS_IN, 0, + plugParentWindow, xembedInfo->version); + XSync (xDisplay, False); + res = true; + break; + } + } + + return res; +} +#endif + +//------------------------------------------------------------------------ +} // StandaloneHost +} // Vst +} // Steinberg diff --git a/plugins/vst/standalone/platform/linux/window.h b/plugins/vst/standalone/platform/linux/window.h new file mode 100644 index 00000000..6981a2f4 --- /dev/null +++ b/plugins/vst/standalone/platform/linux/window.h @@ -0,0 +1,92 @@ +//----------------------------------------------------------------------------- +// Flags : clang-format auto +// Project : VST SDK +// +// Category : StandaloneHost +// Filename : public.sdk/samples/vst-hosting/editorhost/source/platform/linux/window.h +// Created by : Steinberg 09.2016 +// Description : Example of opening a plug-in editor +// +//----------------------------------------------------------------------------- +// LICENSE +// (c) 2023, Steinberg Media Technologies GmbH, All Rights Reserved +//----------------------------------------------------------------------------- +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of the Steinberg Media Technologies nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +// OF THE POSSIBILITY OF SUCH DAMAGE. +//----------------------------------------------------------------------------- + +#pragma once + +// Use X11 instead of GTK +// #define EDITORHOST_GTK + +#include "platform/iwindow.h" +#include + +struct _XDisplay; +typedef struct _XDisplay Display; + +//------------------------------------------------------------------------ +namespace Steinberg { +namespace Vst { +namespace StandaloneHost { + +//------------------------------------------------------------------------ +class X11Window : public IWindow +{ +public: + using Ptr = std::shared_ptr; + using WindowClosedFunc = std::function; + + static Ptr make (const std::string& name, Size size, bool resizeable, + const WindowControllerPtr& controller, Display* display, + const WindowClosedFunc& windowClosedFunc); + + X11Window (); + ~X11Window () override; + + bool init (const std::string& name, Size size, bool resizeable, + const WindowControllerPtr& controller, Display* display, + const WindowClosedFunc& windowClosedFunc); + + void show () override; + void close () override; + void resize (Size newSize) override; + Size getContentSize () override; + + NativePlatformWindow getNativePlatformWindow () const override; + tresult queryInterface (const TUID iid, void** obj) override; + + void onIdle (); + +private: + Size getSize () const; + + struct Impl; + std::unique_ptr impl; +}; + +//------------------------------------------------------------------------ +} // StandaloneHost +} // Vst +} // Steinberg diff --git a/plugins/vst/standalone/platform/mac/platform.mm b/plugins/vst/standalone/platform/mac/platform.mm new file mode 100644 index 00000000..e98d59ca --- /dev/null +++ b/plugins/vst/standalone/platform/mac/platform.mm @@ -0,0 +1,234 @@ +//----------------------------------------------------------------------------- +// Flags : clang-format auto +// Project : VST SDK +// +// Category : StandaloneHost +// Filename : public.sdk/samples/vst-hosting/editorhost/source/platform/mac/platform.mm +// Created by : Steinberg 09.2016 +// Description : Example of opening a plug-in editor +// +//----------------------------------------------------------------------------- +// LICENSE +// (c) 2023, Steinberg Media Technologies GmbH, All Rights Reserved +//----------------------------------------------------------------------------- +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of the Steinberg Media Technologies nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +// OF THE POSSIBILITY OF SUCH DAMAGE. +//----------------------------------------------------------------------------- + +#import "platform/iplatform.h" +#import "platform/mac/window.h" + +#import +#import + +#if !__has_feature(objc_arc) +#error this file needs to be compiled with automatic reference counting enabled +#endif + +//------------------------------------------------------------------------ +namespace Steinberg { +namespace Vst { +namespace StandaloneHost { + +//------------------------------------------------------------------------ +class Platform : public IPlatform +{ +public: + static Platform& instance () + { + static Platform gInstance; + return gInstance; + } + + Platform (); + void setApplication (ApplicationPtr&& app) override; + WindowPtr createWindow (const std::string& title, Size size, bool resizeable, + const WindowControllerPtr& controller) override; + void quit () override; + void kill (int resultCode, const std::string& reason) override; + + ApplicationPtr application; + bool quitRequested {false}; +}; + +//------------------------------------------------------------------------ +Platform::Platform () +{ + NSApplicationLoad (); +} + +//------------------------------------------------------------------------ +void Platform::setApplication (ApplicationPtr&& app) +{ + application = std::move (app); +} + +//------------------------------------------------------------------------ +WindowPtr Platform::createWindow (const std::string& title, Size size, bool resizeable, + const WindowControllerPtr& controller) +{ + return Window::make (title, size, resizeable, controller); +} + +//------------------------------------------------------------------------ +void Platform::quit () +{ + if (quitRequested) + return; + quitRequested = true; + + dispatch_after (dispatch_time (DISPATCH_TIME_NOW, (int64_t) (0.1 * NSEC_PER_SEC)), + dispatch_get_main_queue (), ^{ + @autoreleasepool + { + for (NSWindow* window in NSApp.windows) + [window close]; + } + + application = nullptr; + [NSApp terminate:nil]; + }); +} + +//------------------------------------------------------------------------ +void Platform::kill (int resultCode, const std::string& reason) +{ + std::cout << reason << "\n"; + std::cout.flush (); + + auto alert = [NSAlert new]; + alert.messageText = [NSString stringWithUTF8String:reason.data ()]; + alert.alertStyle = NSCriticalAlertStyle; + [alert addButtonWithTitle:@"Quit"]; + [alert runModal]; + + exit (resultCode); +} + +//------------------------------------------------------------------------ +IPlatform& IPlatform::instance () +{ + return Platform::instance (); +} + +//------------------------------------------------------------------------ +} // StandaloneHost +} // Vst +} // Steinberg + +//------------------------------------------------------------------------ +@interface VSTSDK_AppDelegate : NSObject +{ + std::vector cmdArgs; +} +@end + +//------------------------------------------------------------------------ +@implementation VSTSDK_AppDelegate + +//------------------------------------------------------------------------ +- (void)setCmdArgs:(std::vector&&)args +{ + cmdArgs = std::move (args); +} + +//------------------------------------------------------------------------ +- (NSMenu*)createAppMenu +{ + + auto appName = + static_cast ([[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleName"]); + NSMenu* menu = [[NSMenu alloc] initWithTitle:appName]; + [menu addItemWithTitle:[NSString stringWithFormat:@"Hide %@", appName] + action:@selector (hide:) + keyEquivalent:@"h"]; + [menu addItemWithTitle:@"Hide Others" + action:@selector (hideOtherApplications:) + keyEquivalent:@""]; + [menu addItemWithTitle:@"Show All" action:@selector (unhideAllApplications:) keyEquivalent:@""]; + [menu addItem:[NSMenuItem separatorItem]]; + [menu addItemWithTitle:[NSString stringWithFormat:@"Quit %@", appName] + action:@selector (terminate:) + keyEquivalent:@"q"]; + return menu; +} + +//------------------------------------------------------------------------ +- (NSMenu*)createFileMenu +{ + NSMenu* menu = [[NSMenu alloc] initWithTitle:@"File"]; + [menu addItemWithTitle:@"Close Window" action:@selector (performClose:) keyEquivalent:@"w"]; + return menu; +} + +//------------------------------------------------------------------------ +- (void)setupMenubar +{ + auto mainMenu = [NSMenu new]; + [NSApp setMainMenu:mainMenu]; + + auto appMenuItem = [[NSMenuItem alloc] initWithTitle:@"App" action:nil keyEquivalent:@""]; + [mainMenu addItem:appMenuItem]; + appMenuItem.submenu = [self createAppMenu]; + + auto fileMenuItem = [[NSMenuItem alloc] initWithTitle:@"File" action:nil keyEquivalent:@""]; + [mainMenu addItem:fileMenuItem]; + fileMenuItem.submenu = [self createFileMenu]; +} + +//------------------------------------------------------------------------ +- (BOOL)application:(NSApplication*)sender openFile:(NSString*)filename +{ + if (cmdArgs.empty ()) + cmdArgs.push_back ([filename UTF8String]); + return YES; +} + +//------------------------------------------------------------------------ +- (void)applicationDidFinishLaunching:(NSNotification*)notification +{ + [self setupMenubar]; + Steinberg::Vst::StandaloneHost::Platform::instance ().application->init (cmdArgs); + cmdArgs.clear (); +} + +//------------------------------------------------------------------------ +- (void)applicationWillTerminate:(NSNotification*)notification +{ + if (auto& app = Steinberg::Vst::StandaloneHost::Platform::instance ().application) + app->terminate (); +} + +@end + +//------------------------------------------------------------------------ +int main (int argc, const char* argv[]) +{ + auto delegate = [VSTSDK_AppDelegate new]; + std::vector cmdArgs; + for (int i = 0; i < argc; ++i) + cmdArgs.push_back (argv[i]); + [delegate setCmdArgs:std::move (cmdArgs)]; + [NSApplication sharedApplication].delegate = delegate; + return NSApplicationMain (argc, argv); +} diff --git a/plugins/vst/standalone/platform/mac/window.h b/plugins/vst/standalone/platform/mac/window.h new file mode 100644 index 00000000..6eab74f9 --- /dev/null +++ b/plugins/vst/standalone/platform/mac/window.h @@ -0,0 +1,80 @@ +//----------------------------------------------------------------------------- +// Flags : clang-format auto +// Project : VST SDK +// +// Category : StandaloneHost +// Filename : public.sdk/samples/vst-hosting/editorhost/source/platform/mac/window.h +// Created by : Steinberg 09.2016 +// Description : Example of opening a plug-in editor +// +//----------------------------------------------------------------------------- +// LICENSE +// (c) 2023, Steinberg Media Technologies GmbH, All Rights Reserved +//----------------------------------------------------------------------------- +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of the Steinberg Media Technologies nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +// OF THE POSSIBILITY OF SUCH DAMAGE. +//----------------------------------------------------------------------------- + +#pragma once + +#include "platform/iwindow.h" + +//------------------------------------------------------------------------ +namespace Steinberg { +namespace Vst { +namespace StandaloneHost { + +//------------------------------------------------------------------------ +class Window : public IWindow, public std::enable_shared_from_this +{ +public: + static WindowPtr make (const std::string& name, Size size, bool resizeable, + const WindowControllerPtr& controller); + + Window (); + ~Window () noexcept override; + + bool init (const std::string& name, Size size, bool resizeable, + const WindowControllerPtr& controller); + + void show () override; + void close () override; + void resize (Size newSize) override; + Size getContentSize () override; + + NativePlatformWindow getNativePlatformWindow () const override; + + tresult queryInterface (const TUID iid, void** obj) override { return kNoInterface; } + + WindowControllerPtr getController () const; + void windowClosed (); + +private: + struct Impl; + std::unique_ptr impl; +}; + +//------------------------------------------------------------------------ +} // StandaloneHost +} // Vst +} // Steinberg diff --git a/plugins/vst/standalone/platform/mac/window.mm b/plugins/vst/standalone/platform/mac/window.mm new file mode 100644 index 00000000..9820e486 --- /dev/null +++ b/plugins/vst/standalone/platform/mac/window.mm @@ -0,0 +1,229 @@ +//----------------------------------------------------------------------------- +// Flags : clang-format auto +// Project : VST SDK +// +// Category : StandaloneHost +// Filename : public.sdk/samples/vst-hosting/editorhost/source/platform/mac/window.mm +// Created by : Steinberg 09.2016 +// Description : Example of opening a plug-in editor +// +//----------------------------------------------------------------------------- +// LICENSE +// (c) 2023, Steinberg Media Technologies GmbH, All Rights Reserved +//----------------------------------------------------------------------------- +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of the Steinberg Media Technologies nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +// OF THE POSSIBILITY OF SUCH DAMAGE. +//----------------------------------------------------------------------------- + +#import "platform/mac/window.h" + +#import + +#if !__has_feature(objc_arc) +#error this file needs to be compiled with automatic reference counting enabled +#endif + +using namespace Steinberg::Vst; + +//------------------------------------------------------------------------ +@interface VSTSDK_WindowDelegate : NSObject +#if __i386__ +{ + std::shared_ptr _window; + NSWindow* _nsWindow; +} +#endif +@property (readonly) std::shared_ptr window; +@property (strong, readwrite) NSWindow* nsWindow; + +- (id)initWithWindow:(std::shared_ptr)window; +@end + +//------------------------------------------------------------------------ +namespace Steinberg { +namespace Vst { +namespace StandaloneHost { + +//------------------------------------------------------------------------ +struct Window::Impl +{ + WindowControllerPtr controller; + VSTSDK_WindowDelegate* nsWindowDelegate {nullptr}; +}; + +//------------------------------------------------------------------------ +WindowPtr Window::make (const std::string& name, Size size, bool resizeable, + const WindowControllerPtr& controller) +{ + auto window = std::make_shared (); + if (window->init (name, size, resizeable, controller)) + return window; + return nullptr; +} + +//------------------------------------------------------------------------ +Window::Window () +{ + impl = std::unique_ptr (new Impl); +} + +//------------------------------------------------------------------------ +bool Window::init (const std::string& name, Size size, bool resizeable, + const WindowControllerPtr& controller) +{ + impl->controller = controller; + NSUInteger styleMask = NSTitledWindowMask | NSClosableWindowMask; + if (resizeable) + styleMask |= NSResizableWindowMask; + auto contentRect = + NSMakeRect (0., 0., static_cast (size.width), static_cast (size.height)); + impl->nsWindowDelegate = [[VSTSDK_WindowDelegate alloc] initWithWindow:shared_from_this ()]; + auto nsWindow = [[NSWindow alloc] initWithContentRect:contentRect + styleMask:styleMask + backing:NSBackingStoreBuffered + defer:YES]; + [nsWindow setDelegate:impl->nsWindowDelegate]; + nsWindow.releasedWhenClosed = NO; + impl->nsWindowDelegate.nsWindow = nsWindow; + [nsWindow center]; + return true; +} + +//------------------------------------------------------------------------ +Window::~Window () noexcept +{ +} + +//------------------------------------------------------------------------ +void Window::show () +{ + auto nsWindow = impl->nsWindowDelegate.nsWindow; + impl->controller->onShow (*this); + [nsWindow makeKeyAndOrderFront:nil]; +} + +//------------------------------------------------------------------------ +void Window::close () +{ + auto nsWindow = impl->nsWindowDelegate.nsWindow; + [nsWindow close]; +} + +//------------------------------------------------------------------------ +void Window::resize (Size newSize) +{ + auto nsWindow = impl->nsWindowDelegate.nsWindow; + auto r = [nsWindow contentRectForFrameRect:nsWindow.frame]; + auto diff = newSize.height - r.size.height; + r.size.width = newSize.width; + r.size.height = newSize.height; + r.origin.y -= diff; + [nsWindow setFrame:[nsWindow frameRectForContentRect:r] + display:[nsWindow isVisible] + animate:NO]; +} + +//------------------------------------------------------------------------ +Size Window::getContentSize () +{ + auto nsWindow = impl->nsWindowDelegate.nsWindow; + auto r = [nsWindow contentRectForFrameRect:nsWindow.frame]; + return {static_cast (r.size.width), static_cast (r.size.height)}; +} + +//------------------------------------------------------------------------ +NativePlatformWindow Window::getNativePlatformWindow () const +{ + auto nsWindow = impl->nsWindowDelegate.nsWindow; + return {kPlatformTypeNSView, (__bridge void*)([nsWindow contentView])}; +} + +//------------------------------------------------------------------------ +WindowControllerPtr Window::getController () const +{ + return impl->controller; +} + +//------------------------------------------------------------------------ +void Window::windowClosed () +{ + impl->controller->onClose (*this); + impl->nsWindowDelegate = nullptr; +} + +//------------------------------------------------------------------------ +} // StandaloneHost +} // Vst +} // Steinberg + +//------------------------------------------------------------------------ +@implementation VSTSDK_WindowDelegate +#if __i386__ +@synthesize window = _window; +#endif + +//------------------------------------------------------------------------ +- (id)initWithWindow:(std::shared_ptr)window +{ + if (self = [super init]) + { + self->_window = window; + } + return self; +} + +//------------------------------------------------------------------------ +- (NSSize)windowWillResize:(nonnull NSWindow*)sender toSize:(NSSize)frameSize +{ + NSRect r {}; + r.size = frameSize; + r = [sender contentRectForFrameRect:r]; + StandaloneHost::Size size {static_cast (r.size.width), + static_cast (r.size.height)}; + size = self.window->getController ()->constrainSize (*self.window, size); + r.size.width = size.width; + r.size.height = size.height; + r = [sender frameRectForContentRect:r]; + return r.size; +} + +//------------------------------------------------------------------------ +- (void)windowDidResize:(nonnull NSNotification*)notification +{ + NSWindow* window = [notification object]; + NSRect r = window.frame; + r = [window contentRectForFrameRect:r]; + StandaloneHost::Size size {static_cast (r.size.width), + static_cast (r.size.height)}; + self.window->getController ()->onResize (*self.window, size); +} + +//------------------------------------------------------------------------ +- (void)windowWillClose:(nonnull NSNotification*)notification +{ + self.window->windowClosed (); + self->_window = nullptr; + self->_nsWindow = nullptr; +} + +@end diff --git a/plugins/vst/standalone/platform/win32/platform.cpp b/plugins/vst/standalone/platform/win32/platform.cpp new file mode 100644 index 00000000..ac96eae1 --- /dev/null +++ b/plugins/vst/standalone/platform/win32/platform.cpp @@ -0,0 +1,171 @@ +//----------------------------------------------------------------------------- +// Flags : clang-format auto +// Project : VST SDK +// +// Category : StandaloneHost +// Filename : public.sdk/samples/vst-hosting/editorhost/source/platform/win32/platform.cpp +// Created by : Steinberg 09.2016 +// Description : Example of opening a plug-in editor +// +//----------------------------------------------------------------------------- +// LICENSE +// (c) 2023, Steinberg Media Technologies GmbH, All Rights Reserved +//----------------------------------------------------------------------------- +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of the Steinberg Media Technologies nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +// OF THE POSSIBILITY OF SUCH DAMAGE. +//----------------------------------------------------------------------------- + +#include "pluginterfaces/base/ftypes.h" +#include "platform/iplatform.h" +#include "platform/win32/window.h" +#include "public.sdk/source/vst/utility/stringconvert.h" + +#include +#include +#include +#include + +//------------------------------------------------------------------------ +namespace Steinberg { +namespace Vst { +namespace StandaloneHost { + +//------------------------------------------------------------------------ +class Platform : public IPlatform +{ +public: + static Platform& instance () + { + static Platform gInstance; + return gInstance; + } + + void setApplication (ApplicationPtr&& app) override; + WindowPtr createWindow (const std::string& title, Size size, bool resizeable, + const WindowControllerPtr& controller) override; + void quit () override; + void kill (int resultCode, const std::string& reason) override; + + void run (LPWSTR lpCmdLine, HINSTANCE instance); + +private: + ApplicationPtr application; + HINSTANCE hInstance {nullptr}; + bool quitRequested {false}; +}; + +//------------------------------------------------------------------------ +IPlatform& IPlatform::instance () +{ + return Platform::instance (); +} + +//------------------------------------------------------------------------ +void Platform::setApplication (ApplicationPtr&& app) +{ + application = std::move (app); +} + +//------------------------------------------------------------------------ +WindowPtr Platform::createWindow (const std::string& title, Size size, bool resizeable, + const WindowControllerPtr& controller) +{ + return Window::make (title, size, resizeable, controller, hInstance); +} + +//------------------------------------------------------------------------ +void Platform::quit () +{ + if (quitRequested) + return; + quitRequested = true; + + for (auto& window : Window::getWindows ()) + window->closeImmediately (); + + if (application) + application->terminate (); + + PostQuitMessage (0); +} + +//------------------------------------------------------------------------ +void Platform::kill (int resultCode, const std::string& reason) +{ + auto str = VST3::StringConvert::convert (reason); + MessageBox (nullptr, reinterpret_cast (str.data ()), nullptr, MB_OK); + exit (resultCode); +} + +//------------------------------------------------------------------------ +void Platform::run (LPWSTR lpCmdLine, HINSTANCE _hInstance) +{ + hInstance = _hInstance; + std::vector cmdArgStrings; + + for (int i = 0; i < __argc; ++i) + { + cmdArgStrings.push_back ( + VST3::StringConvert::convert (Steinberg::wscast (__wargv[i]))); + } + + auto noHIDPI = std::find (cmdArgStrings.begin (), cmdArgStrings.end (), "-noHIDPI"); + if (noHIDPI == cmdArgStrings.end ()) + ShcoreLibrary::instance ().setProcessDpiAwareness (true); + + application->init (cmdArgStrings); + + MSG msg; + while (GetMessage (&msg, nullptr, 0, 0)) + { + TranslateMessage (&msg); + DispatchMessage (&msg); + } +} + +//------------------------------------------------------------------------ +} // StandaloneHost +} // Vst +} // Steinberg + +#ifndef _In_ +#define _In_ +#endif +#ifndef _In_opt_ +#define _In_opt_ +#endif + +//------------------------------------------------------------------------ +int APIENTRY wWinMain (_In_ HINSTANCE instance, _In_opt_ HINSTANCE /*prevInstance*/, + _In_ LPWSTR lpCmdLine, _In_ int /*nCmdShow*/) +{ + HRESULT hr = CoInitialize (nullptr); + if (FAILED (hr)) + return FALSE; + + Steinberg::Vst::StandaloneHost::Platform::instance ().run (lpCmdLine, instance); + + CoUninitialize (); + + return 0; +} diff --git a/plugins/vst/standalone/platform/win32/window.cpp b/plugins/vst/standalone/platform/win32/window.cpp new file mode 100644 index 00000000..12314ed8 --- /dev/null +++ b/plugins/vst/standalone/platform/win32/window.cpp @@ -0,0 +1,281 @@ +//----------------------------------------------------------------------------- +// Flags : clang-format auto +// Project : VST SDK +// +// Category : StandaloneHost +// Filename : public.sdk/samples/vst-hosting/editorhost/source/platform/win32/window.cpp +// Created by : Steinberg 09.2016 +// Description : Example of opening a plug-in editor +// +//----------------------------------------------------------------------------- +// LICENSE +// (c) 2023, Steinberg Media Technologies GmbH, All Rights Reserved +//----------------------------------------------------------------------------- +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of the Steinberg Media Technologies nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +// OF THE POSSIBILITY OF SUCH DAMAGE. +//----------------------------------------------------------------------------- + +#include "window.h" +#include "public.sdk/source/vst/utility/stringconvert.h" +#include + +#ifndef WM_DPICHANGED +#define WM_DPICHANGED 0x02E0 +#endif + +//------------------------------------------------------------------------ +namespace Steinberg { +namespace Vst { +namespace StandaloneHost { + +//------------------------------------------------------------------------ +namespace { + +static Window::WindowList gAllWindows; + +//------------------------------------------------------------------------ +static void addWindow (Window* window) +{ + gAllWindows.push_back (window); +} + +//------------------------------------------------------------------------ +static void removeWindow (Window* window) +{ + auto it = std::find (gAllWindows.begin (), gAllWindows.end (), window); + if (it != gAllWindows.end ()) + gAllWindows.erase (it); +} + +//------------------------------------------------------------------------ +} // anonymous + +//------------------------------------------------------------------------ +static const WCHAR* gWindowClassName = L"VSTSDK WindowClass"; + +//------------------------------------------------------------------------ +WindowPtr Window::make (const std::string& name, Size size, bool resizeable, + const WindowControllerPtr& controller, HINSTANCE instance) +{ + auto window = std::make_shared (); + if (window->init (name, size, resizeable, controller, instance)) + return window; + return nullptr; +} + +//------------------------------------------------------------------------ +LRESULT CALLBACK Window::WndProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + auto* window = reinterpret_cast ((LONG_PTR)GetWindowLongPtr (hWnd, GWLP_USERDATA)); + if (window) + return window->proc (message, wParam, lParam); + return DefWindowProc (hWnd, message, wParam, lParam); +} + +//------------------------------------------------------------------------ +void Window::registerWindowClass (HINSTANCE instance) +{ + static bool once = true; + if (!once) + return; + once = true; + + WNDCLASSEX wcex {}; + + wcex.cbSize = sizeof (WNDCLASSEX); + + wcex.style = CS_DBLCLKS; + wcex.lpfnWndProc = WndProc; + wcex.hInstance = instance; + wcex.hCursor = LoadCursor (instance, IDC_ARROW); + wcex.hbrBackground = nullptr; + wcex.lpszClassName = gWindowClassName; + + RegisterClassEx (&wcex); +} + +//------------------------------------------------------------------------ +auto Window::getWindows () -> WindowList +{ + return gAllWindows; +} + +//------------------------------------------------------------------------ +bool Window::init (const std::string& name, Size size, bool resizeable, + const WindowControllerPtr& _controller, HINSTANCE instance) +{ + controller = _controller; + registerWindowClass (instance); + DWORD exStyle = WS_EX_APPWINDOW; + DWORD dwStyle = WS_CAPTION | WS_SYSMENU | WS_CLIPCHILDREN | WS_CLIPSIBLINGS; + if (resizeable) + dwStyle |= WS_SIZEBOX | WS_MAXIMIZEBOX; + auto windowTitle = VST3::StringConvert::convert (name); + + RECT rect {0, 0, size.width, size.height}; + AdjustWindowRectEx (&rect, dwStyle, false, exStyle); + + hwnd = CreateWindowEx (exStyle, gWindowClassName, (const TCHAR*)windowTitle.data (), dwStyle, 0, + 0, rect.right - rect.left, rect.bottom - rect.top, nullptr, nullptr, + instance, nullptr); + if (hwnd) + { + SetWindowLongPtr (hwnd, GWLP_USERDATA, (__int3264) (LONG_PTR)this); + This = shared_from_this (); + addWindow (this); + } + return hwnd != nullptr; +} + +//------------------------------------------------------------------------ +LRESULT Window::proc (UINT message, WPARAM wParam, LPARAM lParam) +{ + switch (message) + { + case WM_ERASEBKGND: + { + return TRUE; // do not draw background + } + case WM_PAINT: + { + PAINTSTRUCT ps {}; + BeginPaint (hwnd, &ps); + EndPaint (hwnd, &ps); + return FALSE; + } + case WM_SIZE: + { + controller->onResize (*this, getContentSize ()); + break; + } + case WM_SIZING: + { + auto* newSize = reinterpret_cast (lParam); + RECT oldSize; + GetWindowRect (hwnd, &oldSize); + RECT clientSize; + GetClientRect (hwnd, &clientSize); + + auto diffX = (newSize->right - newSize->left) - (oldSize.right - oldSize.left); + auto diffY = (newSize->bottom - newSize->top) - (oldSize.bottom - oldSize.top); + + Size newClientSize = {(clientSize.right - clientSize.left), + (clientSize.bottom - clientSize.top)}; + newClientSize.width += diffX; + newClientSize.height += diffY; + + auto constraintSize = controller->constrainSize (*this, newClientSize); + if (constraintSize != newClientSize) + { + auto diffX2 = (oldSize.right - oldSize.left) - (clientSize.right - clientSize.left); + auto diffY2 = (oldSize.bottom - oldSize.top) - (clientSize.bottom - clientSize.top); + newSize->right = newSize->left + static_cast (constraintSize.width + diffX2); + newSize->bottom = newSize->top + static_cast (constraintSize.height + diffY2); + } + return TRUE; + } + case WM_CLOSE: + { + closeImmediately (); + return TRUE; + } + case WM_DPICHANGED: + { + controller->onContentScaleFactorChanged (*this, getContentScaleFactor ()); + break; + } + } + return DefWindowProc (hwnd, message, wParam, lParam); +} + +//------------------------------------------------------------------------ +void Window::closeImmediately () +{ + close (); + removeWindow (this); + controller->onClose (*this); + SetWindowLongPtr (hwnd, GWLP_USERDATA, (__int3264) (LONG_PTR) nullptr); + This = nullptr; +} + +//------------------------------------------------------------------------ +Size Window::getContentSize () +{ + RECT r; + GetClientRect (hwnd, &r); + return {r.right - r.left, r.bottom - r.top}; +} + +//------------------------------------------------------------------------ +float Window::getContentScaleFactor () const +{ + if (auto dpiForWindow = ShcoreLibrary::instance ().getDpiForWindow (hwnd)) + { + return static_cast (dpiForWindow->x) / 96.f; + } + else + { + return 1.f; + } +} + +//------------------------------------------------------------------------ +void Window::show () +{ + controller->onContentScaleFactorChanged (*this, getContentScaleFactor ()); + controller->onShow (*this); + SetWindowPos (hwnd, HWND_TOP, 0, 0, 0, 0, + SWP_NOSIZE | SWP_NOMOVE | SWP_NOCOPYBITS | SWP_SHOWWINDOW); +} + +//------------------------------------------------------------------------ +void Window::close () +{ + ShowWindow (hwnd, false); +} + +//------------------------------------------------------------------------ +void Window::resize (Size newSize) +{ + if (getContentSize () == newSize) + return; + WINDOWINFO windowInfo {0}; + GetWindowInfo (hwnd, &windowInfo); + RECT clientRect {}; + clientRect.right = newSize.width; + clientRect.bottom = newSize.height; + AdjustWindowRectEx (&clientRect, windowInfo.dwStyle, false, windowInfo.dwExStyle); + SetWindowPos (hwnd, HWND_TOP, 0, 0, clientRect.right - clientRect.left, + clientRect.bottom - clientRect.top, SWP_NOMOVE | SWP_NOCOPYBITS | SWP_NOACTIVATE); +} + +//------------------------------------------------------------------------ +NativePlatformWindow Window::getNativePlatformWindow () const +{ + return {kPlatformTypeHWND, hwnd}; +} + +//------------------------------------------------------------------------ +} // StandaloneHost +} // Vst +} // Steinberg diff --git a/plugins/vst/standalone/platform/win32/window.h b/plugins/vst/standalone/platform/win32/window.h new file mode 100644 index 00000000..3425db45 --- /dev/null +++ b/plugins/vst/standalone/platform/win32/window.h @@ -0,0 +1,173 @@ +//----------------------------------------------------------------------------- +// Flags : clang-format auto +// Project : VST SDK +// +// Category : StandaloneHost +// Filename : public.sdk/samples/vst-hosting/editorhost/source/platform/win32/window.h +// Created by : Steinberg 09.2016 +// Description : Example of opening a plug-in editor +// +//----------------------------------------------------------------------------- +// LICENSE +// (c) 2023, Steinberg Media Technologies GmbH, All Rights Reserved +//----------------------------------------------------------------------------- +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of the Steinberg Media Technologies nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +// OF THE POSSIBILITY OF SUCH DAMAGE. +//----------------------------------------------------------------------------- + +#pragma once + +#include "platform/iwindow.h" +#include "public.sdk/source/vst/utility/optional.h" +#include +#include + +//------------------------------------------------------------------------ +namespace Steinberg { +namespace Vst { +namespace StandaloneHost { + +//------------------------------------------------------------------------ +class Window : public IWindow, public std::enable_shared_from_this +{ +public: + static WindowPtr make (const std::string& name, Size size, bool resizeable, + const WindowControllerPtr& controller, HINSTANCE instance); + + bool init (const std::string& name, Size size, bool resizeable, + const WindowControllerPtr& controller, HINSTANCE instance); + + void show () override; + void close () override; + void resize (Size newSize) override; + Size getContentSize () override; + + NativePlatformWindow getNativePlatformWindow () const override; + + tresult queryInterface (const TUID /*iid*/, void** /*obj*/) override { return kNoInterface; } + + void closeImmediately (); + + using WindowList = std::vector; + static WindowList getWindows (); + +private: + LRESULT CALLBACK proc (UINT message, WPARAM wParam, LPARAM lParam); + static LRESULT CALLBACK WndProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); + + float getContentScaleFactor () const; + void registerWindowClass (HINSTANCE instance); + + WindowPtr This; + WindowControllerPtr controller {nullptr}; + HWND hwnd {nullptr}; +}; + +//------------------------------------------------------------------------ +struct DynamicLibrary +{ + DynamicLibrary (const char* name) { module = LoadLibraryA (name); } + + ~DynamicLibrary () { FreeLibrary (module); } + + template + T getProcAddress (const char* name) + { + return module ? reinterpret_cast (GetProcAddress (module, name)) : nullptr; + } + +private: + HMODULE module {nullptr}; +}; + +struct ShcoreLibrary : DynamicLibrary +{ + static ShcoreLibrary& instance () + { + static ShcoreLibrary gInstance; + return gInstance; + } + + struct DPI + { + UINT x; + UINT y; + }; + + VST3::Optional getDpiForWindow (HWND window) const + { + if (!getDpiForMonitorProc) + return {}; + auto monitor = MonitorFromWindow (window, MONITOR_DEFAULTTONEAREST); + UINT x, y; + getDpiForMonitorProc (monitor, MDT_EFFECTIVE_DPI, &x, &y); + return DPI {x, y}; + } + + HRESULT setProcessDpiAwareness (bool perMonitor = true) + { + if (!setProcessDpiAwarenessProc) + return S_FALSE; + return setProcessDpiAwarenessProc (perMonitor ? PROCESS_PER_MONITOR_DPI_AWARE : + PROCESS_SYSTEM_DPI_AWARE); + } + +private: + enum PROCESS_DPI_AWARENESS + { + PROCESS_DPI_UNAWARE = 0, + PROCESS_SYSTEM_DPI_AWARE = 1, + PROCESS_PER_MONITOR_DPI_AWARE = 2 + }; + + enum MONITOR_DPI_TYPE + { + MDT_EFFECTIVE_DPI = 0, + MDT_ANGULAR_DPI = 1, + MDT_RAW_DPI = 2, + MDT_DEFAULT = MDT_EFFECTIVE_DPI + }; + + using GetDpiForMonitorProc = HRESULT (WINAPI*) (HMONITOR, MONITOR_DPI_TYPE, UINT*, UINT*); + using SetProcessDpiAwarenessProc = HRESULT (WINAPI*) (PROCESS_DPI_AWARENESS); + + ShcoreLibrary () : DynamicLibrary ("Shcore.dll") + { + getDpiForMonitorProc = getProcAddress ("GetDpiForMonitor"); + setProcessDpiAwarenessProc = + getProcAddress ("SetProcessDpiAwareness"); + } + + GetDpiForMonitorProc getDpiForMonitorProc {nullptr}; + SetProcessDpiAwarenessProc setProcessDpiAwarenessProc {nullptr}; +}; + +#ifndef DPI_ENUMS_DECLARED + +#define DPI_ENUMS_DECLARED +#endif // (DPI_ENUMS_DECLARED) + +//------------------------------------------------------------------------ +} // StandaloneHost +} // Vst +} // Steinberg diff --git a/plugins/vst/standalone/standalonehost.cpp b/plugins/vst/standalone/standalonehost.cpp new file mode 100644 index 00000000..1869e5dd --- /dev/null +++ b/plugins/vst/standalone/standalonehost.cpp @@ -0,0 +1,493 @@ +//----------------------------------------------------------------------------- +// Flags : clang-format auto +// Project : VST SDK +// +// Category : StandaloneHost +// Filename : public.sdk/samples/vst-hosting/editorhost/source/editorhost.cpp +// Created by : Steinberg 09.2016 +// Description : Example of opening a plug-in editor +// +//----------------------------------------------------------------------------- +// LICENSE +// (c) 2023, Steinberg Media Technologies GmbH, All Rights Reserved +//----------------------------------------------------------------------------- +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of the Steinberg Media Technologies nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +// OF THE POSSIBILITY OF SUCH DAMAGE. +//----------------------------------------------------------------------------- + +#include "standalonehost.h" +#include "platform/appinit.h" +#include "base/source/fcommandline.h" +#include "pluginterfaces/base/funknown.h" +#include "pluginterfaces/gui/iplugview.h" +#include "pluginterfaces/gui/iplugviewcontentscalesupport.h" +#include "pluginterfaces/vst/ivstaudioprocessor.h" +#include "pluginterfaces/vst/ivsteditcontroller.h" +#include "pluginterfaces/vst/vsttypes.h" +#include +#include +#include + +#include "VstPluginDefs.h" + +//------------------------------------------------------------------------ +namespace Steinberg { + +//------------------------------------------------------------------------ +inline bool operator== (const ViewRect& r1, const ViewRect& r2) +{ + return memcmp (&r1, &r2, sizeof (ViewRect)) == 0; +} + +//------------------------------------------------------------------------ +inline bool operator!= (const ViewRect& r1, const ViewRect& r2) +{ + return !(r1 == r2); +} + +namespace Vst { +namespace StandaloneHost { + +static AppInit gInit (std::make_unique ()); + +//------------------------------------------------------------------------ +class WindowController : public IWindowController, public IPlugFrame +{ +public: + WindowController (const IPtr& plugView); + ~WindowController () noexcept override; + + void onShow (IWindow& w) override; + void onClose (IWindow& w) override; + void onResize (IWindow& w, Size newSize) override; + Size constrainSize (IWindow& w, Size requestedSize) override; + void onContentScaleFactorChanged (IWindow& window, float newScaleFactor) override; + + // IPlugFrame + tresult PLUGIN_API resizeView (IPlugView* view, ViewRect* newSize) override; + + void closePlugView (); + +private: + tresult PLUGIN_API queryInterface (const TUID _iid, void** obj) override + { + if (FUnknownPrivate::iidEqual (_iid, IPlugFrame::iid) || + FUnknownPrivate::iidEqual (_iid, FUnknown::iid)) + { + *obj = this; + addRef (); + return kResultTrue; + } + if (window) + return window->queryInterface (_iid, obj); + return kNoInterface; + } + // we do not care here of the ref-counting. A plug-in call of release should not destroy this + // class! + uint32 PLUGIN_API addRef () override { return 1000; } + uint32 PLUGIN_API release () override { return 1000; } + + IPtr plugView; + IWindow* window {nullptr}; + bool resizeViewRecursionGard {false}; +}; + +//------------------------------------------------------------------------ +class ComponentHandler : public IComponentHandler +{ +public: + tresult PLUGIN_API beginEdit (ParamID id) override + { + SMTG_DBPRT1 ("beginEdit called (%d)\n", id); + return kNotImplemented; + } + tresult PLUGIN_API performEdit (ParamID id, ParamValue valueNormalized) override + { + SMTG_DBPRT2 ("performEdit called (%d, %f)\n", id, valueNormalized); + return kNotImplemented; + } + tresult PLUGIN_API endEdit (ParamID id) override + { + SMTG_DBPRT1 ("endEdit called (%d)\n", id); + return kNotImplemented; + } + tresult PLUGIN_API restartComponent (int32 flags) override + { + SMTG_DBPRT1 ("restartComponent called (%d)\n", flags); + return kNotImplemented; + } + +private: + tresult PLUGIN_API queryInterface (const TUID /*_iid*/, void** /*obj*/) override + { + return kNoInterface; + } + // we do not care here of the ref-counting. A plug-in call of release should not destroy this + // class! + uint32 PLUGIN_API addRef () override { return 1000; } + uint32 PLUGIN_API release () override { return 1000; } +}; + +static ComponentHandler gComponentHandler; + +//------------------------------------------------------------------------ +App::~App () noexcept +{ + terminate (); +} + +//------------------------------------------------------------------------ +void App::openEditor (const std::string& path, VST3::Optional effectID, uint32 flags) +{ + std::string error; + module = VST3::Hosting::Module::create (path, error); + if (!module) + { + std::string reason = "Could not create Module for file:"; + reason += path; + reason += "\nError: "; + reason += error; + IPlatform::instance ().kill (-1, reason); + } + + auto factory = module->getFactory (); + for (auto& classInfo : factory.classInfos ()) + { + if (classInfo.category () == kVstAudioEffectClass) + { + if (effectID) + { + if (*effectID != classInfo.ID ()) + continue; + } + plugProvider = owned (new PlugProvider (factory, classInfo, true)); + if (plugProvider->initialize () == false) + plugProvider = nullptr; + break; + } + } + if (!plugProvider) + { + if (effectID) + error = + "No VST3 Audio Module Class with UID " + effectID->toString () + " found in file "; + else + error = "No VST3 Audio Module Class found in file "; + error += path; + IPlatform::instance ().kill (-1, error); + } + + OPtr component = plugProvider->getComponent (); + if (!component) + { + error = "No Component found in file " + path; + IPlatform::instance ().kill (-1, error); + } + + OPtr editController = plugProvider->getController (); + if (!editController) + { + error = "No EditController found (needed for allowing editor) in file " + path; + IPlatform::instance ().kill (-1, error); + } + //editController->release (); // plugProvider does an addRef + + if (flags & kSetComponentHandler) + { + SMTG_DBPRT0 ("setComponentHandler is used\n"); + editController->setComponentHandler (&gComponentHandler); + } + + SMTG_DBPRT1 ("Open Editor for %s...\n", path.c_str ()); + createViewAndShow (editController); + + if (flags & kSecondWindow) + { + SMTG_DBPRT0 ("Open 2cd Editor...\n"); + createViewAndShow (editController); + } + + FUnknownPtr midiMapping (editController); + + //! TODO: Query the plugProvider for a proper name which gets displayed in JACK. + vst3Processor = AudioClient::create (VST3_PLUGIN_NAME, component, midiMapping); +} + +//------------------------------------------------------------------------ +void App::createViewAndShow (IEditController* controller) +{ + auto view = owned (controller->createView (ViewType::kEditor)); + if (!view) + { + IPlatform::instance ().kill (-1, "EditController does not provide its own editor"); + } + + ViewRect plugViewSize {}; + auto result = view->getSize (&plugViewSize); + if (result != kResultTrue) + { + IPlatform::instance ().kill (-1, "Could not get editor view size"); + } + + auto viewRect = ViewRectToRect (plugViewSize); + + windowController = std::make_shared (view); + window = IPlatform::instance ().createWindow ( + "Editor", viewRect.size, view->canResize () == kResultTrue, windowController); + if (!window) + { + IPlatform::instance ().kill (-1, "Could not create window"); + } + + window->show (); +} + +//------------------------------------------------------------------------ +void App::init (const std::vector& cmdArgs) +{ + auto helpText = R"( +usage: StandaloneHost [options] pluginPath + +options: + +--componentHandler +set optional component handler on edit controller + +--secondWindow +create a second window + +--uid UID +use effect class with unique class ID==UID +)"; + fs::path vstBundlePath; + + VST3::Optional uid; + uint32 flags {}; + uint32 positionalArgNumber = 0; + for (size_t argNumber = 1, argCount = cmdArgs.size (); argNumber < argCount; ++argNumber) + { + const std::string& arg = cmdArgs[argNumber]; + if (arg == "-h" || arg == "--help") + IPlatform::instance ().kill (0, helpText); + else if (arg == "--componentHandler") + flags |= kSetComponentHandler; + else if (arg == "--secondWindow") + flags |= kSecondWindow; + else if (arg == "--uid") + { + if (++argNumber != argCount) + uid = VST3::UID::fromString (arg); + if (!uid) + IPlatform::instance ().kill (-1, "wrong argument to --uid"); + } + else + { + if (positionalArgNumber == 0) + vstBundlePath = fs::u8path (arg); + else + IPlatform::instance ().kill (-1, helpText); + ++positionalArgNumber; + } + } + + PluginContextFactory::instance ().setPluginContext (&pluginContext); + +// openEditor (cmdArgs.back (), std::move (uid), flags); + + if (vstBundlePath.empty () && !cmdArgs.empty ()) + { + fs::path modulePath = fs::u8path (cmdArgs.front ()).parent_path(); + fs::path currentFilePath = modulePath / fs::u8path (VST3_PLUGIN_NAME ".vst3"); + if (fs::exists (currentFilePath)) + vstBundlePath = currentFilePath; + } + + if (vstBundlePath.empty ()) + { + for (const std::string& modulePath : VST3::Hosting::Module::getModulePaths ()) + { + fs::path currentFilePath = fs::u8path (modulePath) / fs::u8path (VST3_PLUGIN_NAME ".vst3"); + if (fs::exists (currentFilePath)) + { + vstBundlePath = currentFilePath; + break; + } + } + } + + if (vstBundlePath.empty ()) + { + IPlatform::instance ().kill (-1, "module not found"); + } + + openEditor (vstBundlePath.u8string (), std::move (uid), flags); +} + +//------------------------------------------------------------------------ +void App::terminate () +{ + if (windowController) + windowController->closePlugView (); + windowController.reset (); + plugProvider.reset (); + module.reset (); + PluginContextFactory::instance ().setPluginContext (nullptr); +} + +//------------------------------------------------------------------------ +WindowController::WindowController (const IPtr& plugView) : plugView (plugView) +{ +} + +//------------------------------------------------------------------------ +WindowController::~WindowController () noexcept +{ +} + +//------------------------------------------------------------------------ +void WindowController::onShow (IWindow& w) +{ + SMTG_DBPRT1 ("onShow called (%p)\n", (void*)&w); + + window = &w; + if (!plugView) + return; + + auto platformWindow = window->getNativePlatformWindow (); + if (plugView->isPlatformTypeSupported (platformWindow.type) != kResultTrue) + { + IPlatform::instance ().kill (-1, std::string ("PlugView does not support platform type:") + + platformWindow.type); + } + + plugView->setFrame (this); + + if (plugView->attached (platformWindow.ptr, platformWindow.type) != kResultTrue) + { + IPlatform::instance ().kill (-1, "Attaching PlugView failed"); + } +} + +//------------------------------------------------------------------------ +void WindowController::closePlugView () +{ + if (plugView) + { + plugView->setFrame (nullptr); + if (plugView->removed () != kResultTrue) + { + IPlatform::instance ().kill (-1, "Removing PlugView failed"); + } + plugView = nullptr; + } + window = nullptr; +} + +//------------------------------------------------------------------------ +void WindowController::onClose (IWindow& w) +{ + SMTG_DBPRT1 ("onClose called (%p)\n", (void*)&w); + + closePlugView (); + + // TODO maybe quit only when the last window is closed + IPlatform::instance ().quit (); +} + +//------------------------------------------------------------------------ +void WindowController::onResize (IWindow& w, Size newSize) +{ + SMTG_DBPRT1 ("onResize called (%p)\n", (void*)&w); + + if (plugView) + { + ViewRect r {}; + r.right = newSize.width; + r.bottom = newSize.height; + ViewRect r2 {}; + if (plugView->getSize (&r2) == kResultTrue && r != r2) + plugView->onSize (&r); + } +} + +//------------------------------------------------------------------------ +Size WindowController::constrainSize (IWindow& w, Size requestedSize) +{ + SMTG_DBPRT1 ("constrainSize called (%p)\n", (void*)&w); + + ViewRect r {}; + r.right = requestedSize.width; + r.bottom = requestedSize.height; + if (plugView && plugView->checkSizeConstraint (&r) != kResultTrue) + { + plugView->getSize (&r); + } + requestedSize.width = r.right - r.left; + requestedSize.height = r.bottom - r.top; + return requestedSize; +} + +//------------------------------------------------------------------------ +void WindowController::onContentScaleFactorChanged (IWindow& w, float newScaleFactor) +{ + SMTG_DBPRT1 ("onContentScaleFactorChanged called (%p)\n", (void*)&w); + + FUnknownPtr css (plugView); + if (css) + { + css->setContentScaleFactor (newScaleFactor); + } +} + +//------------------------------------------------------------------------ +tresult PLUGIN_API WindowController::resizeView (IPlugView* view, ViewRect* newSize) +{ + SMTG_DBPRT1 ("resizeView called (%p)\n", (void*)view); + + if (newSize == nullptr || view == nullptr || view != plugView) + return kInvalidArgument; + if (!window) + return kInternalError; + if (resizeViewRecursionGard) + return kResultFalse; + ViewRect r; + if (plugView->getSize (&r) != kResultTrue) + return kInternalError; + if (r == *newSize) + return kResultTrue; + + resizeViewRecursionGard = true; + Size size {newSize->right - newSize->left, newSize->bottom - newSize->top}; + window->resize (size); + resizeViewRecursionGard = false; + if (plugView->getSize (&r) != kResultTrue) + return kInternalError; + if (r != *newSize) + plugView->onSize (newSize); + return kResultTrue; +} + +//------------------------------------------------------------------------ +} // StandaloneHost +} // Vst +} // Steinberg diff --git a/plugins/vst/standalone/standalonehost.h b/plugins/vst/standalone/standalonehost.h new file mode 100644 index 00000000..118ee8b1 --- /dev/null +++ b/plugins/vst/standalone/standalonehost.h @@ -0,0 +1,83 @@ +//----------------------------------------------------------------------------- +// Flags : clang-format auto +// Project : VST SDK +// +// Category : StandaloneHost +// Filename : public.sdk/samples/vst-hosting/editorhost/source/editorhost.h +// Created by : Steinberg 09.2016 +// Description : Example of opening a Plug-in editor +// +//----------------------------------------------------------------------------- +// LICENSE +// (c) 2023, Steinberg Media Technologies GmbH, All Rights Reserved +//----------------------------------------------------------------------------- +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of the Steinberg Media Technologies nor the names of its +// contributors may be used to endorse or promote products derived from this +// software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +// OF THE POSSIBILITY OF SUCH DAMAGE. +//----------------------------------------------------------------------------- + +#pragma once + +#include "platform/iapplication.h" +#include "platform/iwindow.h" +#include "media/audioclient.h" +#include "public.sdk/source/vst/hosting/hostclasses.h" +#include "public.sdk/source/vst/hosting/module.h" +#include "public.sdk/source/vst/hosting/plugprovider.h" +#include "public.sdk/source/vst/utility/optional.h" + +//------------------------------------------------------------------------ +namespace Steinberg { +namespace Vst { +namespace StandaloneHost { + +class WindowController; + +//------------------------------------------------------------------------ +class App : public IApplication +{ +public: + ~App () noexcept override; + void init (const std::vector& cmdArgs) override; + void terminate () override; + +private: + enum OpenFlags + { + kSetComponentHandler = 1 << 0, + kSecondWindow = 1 << 1, + }; + void openEditor (const std::string& path, VST3::Optional effectID, uint32 flags); + void createViewAndShow (IEditController* controller); + + VST3::Hosting::Module::Ptr module {nullptr}; + IPtr plugProvider {nullptr}; + AudioClientPtr vst3Processor; + Vst::HostApplication pluginContext; + WindowPtr window; + std::shared_ptr windowController; +}; + +//------------------------------------------------------------------------ +} // StandaloneHost +} // Vst +} // Steinberg