diff --git a/.gitignore b/.gitignore index 4196b1a0..d232a9b0 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,6 @@ node_modules/ *.lock *.sublime-* *.code-* + +/vst/download +/vst/external/VST_SDK diff --git a/CMakeLists.txt b/CMakeLists.txt index 91a45430..00adb2e6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,6 +27,7 @@ endif() option (ENABLE_LTO "Enable Link Time Optimization [default: ON]" ON) option (SFIZZ_JACK "Enable JACK stand-alone build [default: ON]" ON) option (SFIZZ_LV2 "Enable LV2 plug-in build [default: ON]" ON) +option (SFIZZ_VST "Enable VST plug-in build [default: OFF]" OFF) option (SFIZZ_BENCHMARKS "Enable benchmarks build [default: OFF]" OFF) option (SFIZZ_TESTS "Enable tests build [default: OFF]" OFF) option (SFIZZ_SHARED "Enable shared library build [default: ON]" ON) @@ -51,6 +52,10 @@ if (SFIZZ_LV2) add_subdirectory (lv2) endif() +if (SFIZZ_VST) + add_subdirectory (vst) +endif() + if (SFIZZ_BENCHMARKS) add_subdirectory (benchmarks) endif() diff --git a/cmake/VSTConfig.cmake b/cmake/VSTConfig.cmake new file mode 100644 index 00000000..0d1de3e1 --- /dev/null +++ b/cmake/VSTConfig.cmake @@ -0,0 +1,4 @@ +set (VSTPLUGIN_NAME "sfizz") +set (VSTPLUGIN_VENDOR "Paul Ferrand") +set (VSTPLUGIN_URL "http://sfztools.github.io/sfizz") +set (VSTPLUGIN_EMAIL "paul@ferrand.cc") diff --git a/vst/CMakeLists.txt b/vst/CMakeLists.txt new file mode 100644 index 00000000..4cafc961 --- /dev/null +++ b/vst/CMakeLists.txt @@ -0,0 +1,77 @@ +set (VSTPLUGIN_PRJ_NAME "${PROJECT_NAME}_vst3") +set (VSTPLUGIN_BUNDLE_NAME "${PROJECT_NAME}.vst3") + +set (VST3SDK_BASEDIR "${CMAKE_CURRENT_SOURCE_DIR}/external/VST_SDK/VST3_SDK") +set (VST3SDK_ARCHIVE "vst-sdk_3.6.14_build-24_2019-11-29.zip") + +if (NOT EXISTS "${VST3SDK_BASEDIR}") + message (STATUS "VST3 SDK is not found, downloading") + + execute_process ( + COMMAND "${CMAKE_COMMAND}" -E make_directory "${CMAKE_CURRENT_SOURCE_DIR}/download") + + if (NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/download/${VST3SDK_ARCHIVE}") + file (DOWNLOAD + "https://download.steinberg.net/sdk_downloads/${VST3SDK_ARCHIVE}" + "${CMAKE_CURRENT_SOURCE_DIR}/download/${VST3SDK_ARCHIVE}" + SHOW_PROGRESS) + endif() + + execute_process ( + COMMAND "${CMAKE_COMMAND}" -E make_directory "${CMAKE_CURRENT_SOURCE_DIR}/external" + COMMAND "${CMAKE_COMMAND}" -E tar xvf "../download/${VST3SDK_ARCHIVE}" + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/external") +endif() + +# stop trying to include this atrocity.. build it ourselves +#add_subdirectory("${VST3SDK_BASEDIR}" EXCLUDE_FROM_ALL) + +# VST plugin specific settings +include (VSTConfig) + +configure_file (VstPluginDefs.h.in "${CMAKE_CURRENT_BINARY_DIR}/VstPluginDefs.h") + +# Build VST3 SDK +include("cmake/Vst3.cmake") + +# Build the plugin +add_library(${VSTPLUGIN_PRJ_NAME} MODULE + SfizzVstProcessor.cpp + SfizzVstController.cpp + SfizzVstEditor.cpp + SfizzVstState.cpp + VstPluginFactory.cpp) +target_link_libraries(${VSTPLUGIN_PRJ_NAME} + PRIVATE ${PROJECT_NAME}::${PROJECT_NAME}) +target_include_directories(${VSTPLUGIN_PRJ_NAME} + PRIVATE "${CMAKE_CURRENT_BINARY_DIR}") +set_target_properties(${VSTPLUGIN_PRJ_NAME} PROPERTIES + OUTPUT_NAME "${PROJECT_NAME}" + PREFIX "") + +plugin_add_vst3sdk(${VSTPLUGIN_PRJ_NAME}) +plugin_add_vstgui(${VSTPLUGIN_PRJ_NAME}) + +if (${CMAKE_SYSTEM_NAME} MATCHES "Linux") + target_link_libraries(${VSTPLUGIN_PRJ_NAME} PRIVATE + "-Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/vst3.version") +endif() + +# Create the bundle (see "VST 3 Locations / Format") +if(WIN32) + set_target_properties(${VSTPLUGIN_PRJ_NAME} PROPERTIES + LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${VSTPLUGIN_BUNDLE_NAME}/Contents/${VST3_PACKAGE_ARCHITECTURE}-win") + # TODO: make desktop.ini, Plugin.ico +elseif(APPLE) + set_target_properties(${VSTPLUGIN_PRJ_NAME} PROPERTIES + LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${VSTPLUGIN_BUNDLE_NAME}/Contents/MacOS") + # TODO: make Info.plist, PkgInfo +else() + set_target_properties(${VSTPLUGIN_PRJ_NAME} PROPERTIES + LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${VSTPLUGIN_BUNDLE_NAME}/Contents/${VST3_PACKAGE_ARCHITECTURE}-linux") +endif() + +# To help debugging the link only +if (FALSE) + target_link_options(${VSTPLUGIN_PRJ_NAME} PRIVATE "-Wl,-no-undefined") +endif() diff --git a/vst/RTSemaphore.h b/vst/RTSemaphore.h new file mode 100644 index 00000000..0cd6135b --- /dev/null +++ b/vst/RTSemaphore.h @@ -0,0 +1,167 @@ +// Copyright Jean Pierre Cimalando 2018-2020. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#pragma once +#if defined(__APPLE__) +#include +#elif defined(_WIN32) +#include +#include +#else +#include +#include +#endif +#include + +class RTSemaphore { +public: + explicit RTSemaphore(unsigned value = 0); + ~RTSemaphore(); + + RTSemaphore(const RTSemaphore &) = delete; + RTSemaphore &operator=(const RTSemaphore &) = delete; + + void post(); + void wait(); + bool try_wait(); + +private: +#if defined(__APPLE__) + semaphore_t sem_; +#elif defined(_WIN32) + HANDLE sem_; +#else + sem_t sem_; +#endif +}; + +#if defined(__APPLE__) +inline RTSemaphore::RTSemaphore(unsigned value) +{ + if (semaphore_create(mach_task_self(), &sem_, SYNC_POLICY_FIFO, value) != 0) + throw std::runtime_error("RTSemaphore::RTSemaphore"); +} + +inline RTSemaphore::~RTSemaphore() +{ + semaphore_destroy(mach_task_self(), sem_); +} + +inline void RTSemaphore::post() +{ + if (semaphore_signal(sem_) != KERN_SUCCESS) + throw std::runtime_error("RTSemaphore::post"); +} + +inline void RTSemaphore::wait() +{ + do { + switch (semaphore_wait(sem_)) { + case KERN_SUCCESS: + return; + case KERN_ABORTED: + break; + default: + throw std::runtime_error("RTSemaphore::wait"); + } + } while (1); +} + +inline bool RTSemaphore::try_wait() +{ + do { + const mach_timespec_t timeout = {0, 0}; + switch (semaphore_timedwait(sem_, timeout)) { + case KERN_SUCCESS: + return true; + case KERN_OPERATION_TIMED_OUT: + return false; + case KERN_ABORTED: + break; + default: + throw std::runtime_error("RTSemaphore::try_wait"); + } + } while (1); +} +#elif defined(_WIN32) +inline RTSemaphore::RTSemaphore(unsigned value) +{ + sem_ = CreateRTSemaphore(nullptr, value, LONG_MAX, nullptr); + if (!sem_) + throw std::runtime_error("RTSemaphore::RTSemaphore"); +} + +inline RTSemaphore::~RTSemaphore() +{ + CloseHandle(sem_); +} + +inline void RTSemaphore::post() +{ + if (!ReleaseRTSemaphore(sem_, 1, nullptr)) + throw std::runtime_error("RTSemaphore::post"); +} + +inline void RTSemaphore::wait() +{ + if (WaitForSingleObject(sem_, INFINITE) != WAIT_OBJECT_0) + throw std::runtime_error("RTSemaphore::wait"); +} + +inline bool RTSemaphore::try_wait() +{ + switch (WaitForSingleObject(sem_, 0)) { + case WAIT_OBJECT_0: + return true; + case WAIT_TIMEOUT: + return false; + default: + throw std::runtime_error("RTSemaphore::try_wait"); + } +} +#else +inline RTSemaphore::RTSemaphore(unsigned value) +{ + if (sem_init(&sem_, 0, value) != 0) + throw std::runtime_error("RTSemaphore::RTSemaphore"); +} + +inline RTSemaphore::~RTSemaphore() +{ + sem_destroy(&sem_); +} + +inline void RTSemaphore::post() +{ + while (sem_post(&sem_) != 0) { + if (errno != EINTR) + throw std::runtime_error("RTSemaphore::post"); + } +} + +inline void RTSemaphore::wait() +{ + while (sem_wait(&sem_) != 0) { + if (errno != EINTR) + throw std::runtime_error("RTSemaphore::wait"); + } +} + +inline bool RTSemaphore::try_wait() +{ + do { + if (sem_trywait(&sem_) == 0) + return true; + switch (errno) { + case EINTR: + break; + case EAGAIN: + return false; + default: + throw std::runtime_error("RTSemaphore::try_wait"); + } + } while (1); +} +#endif diff --git a/vst/SfizzVstController.cpp b/vst/SfizzVstController.cpp new file mode 100644 index 00000000..88fcc6f0 --- /dev/null +++ b/vst/SfizzVstController.cpp @@ -0,0 +1,121 @@ +// SPDX-License-Identifier: BSD-2-Clause + +// This code is part of the sfizz library and is licensed under a BSD 2-clause +// license. You should have receive a LICENSE.md file along with the code. +// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz + +#include "SfizzVstController.h" +#include "SfizzVstEditor.h" +#include "base/source/fstreamer.h" +#include "pluginterfaces/vst/ivstmidicontrollers.h" + +tresult PLUGIN_API SfizzVstControllerNoUi::initialize(FUnknown* context) +{ + tresult result = EditController::initialize(context); + if (result != kResultTrue) + return result; + + Vst::ParamID pid = 0; + + // MIDI controllers + for (unsigned i = 0; i < numControllerParams; ++i) { + Steinberg::String title; + Steinberg::String shortTitle; + title.printf("Controller %u", i); + shortTitle.printf("CC%u", i); + + parameters.addParameter( + title, nullptr, 0, 0, Vst::ParameterInfo::kCanAutomate, + pid++, Vst::kRootUnitId, shortTitle); + } + + // MIDI extra controllers + parameters.addParameter(Steinberg::String("Aftertouch"), nullptr, 0, 0.5, 0, pid++, Vst::kRootUnitId); + parameters.addParameter(Steinberg::String("Pitch Bend"), nullptr, 0, 0.5, 0, pid++, Vst::kRootUnitId); + + return kResultTrue; +} + +tresult PLUGIN_API SfizzVstControllerNoUi::terminate() +{ + return EditController::terminate(); +} + +tresult PLUGIN_API SfizzVstControllerNoUi::getMidiControllerAssignment(int32 busIndex, int16 channel, Vst::CtrlNumber midiControllerNumber, Vst::ParamID& id) +{ + switch (midiControllerNumber) { + case Vst::kAfterTouch: + id = kPidMidiAftertouch; + return kResultTrue; + + case Vst::kPitchBend: + id = kPidMidiPitchBend; + return kResultTrue; + + default: + if (midiControllerNumber < 0 || midiControllerNumber >= numControllerParams) + return kResultFalse; + + id = kPidMidiCC0 + midiControllerNumber; + return kResultTrue; + } +} + +// --- Controller with UI --- // + +IPlugView* PLUGIN_API SfizzVstController::createView(FIDString _name) +{ + ConstString name(_name); + + if (name != Vst::ViewType::kEditor) + return nullptr; + + return new SfizzVstEditor(this); +} + +tresult PLUGIN_API SfizzVstController::setParamNormalized(Vst::ParamID tag, Vst::ParamValue value) +{ + tresult r = SfizzVstControllerNoUi::setParamNormalized(tag, value); + if (r != kResultTrue) + return r; + + return kResultTrue; +} + +tresult PLUGIN_API SfizzVstController::setComponentState(IBStream* state) +{ + SfizzVstState s; + + tresult r = s.load(state); + if (r != kResultTrue) + return r; + + for (StateListener* listener : _stateListeners) + listener->onStateChanged(); + + _state = s; + return kResultTrue; +} + +void SfizzVstController::addStateListener(StateListener* listener) +{ + _stateListeners.push_back(listener); +} + +void SfizzVstController::removeStateListener(StateListener* listener) +{ + auto it = std::find(_stateListeners.begin(), _stateListeners.end(), listener); + if (it != _stateListeners.end()) + _stateListeners.erase(it); +} + +FUnknown* SfizzVstController::createInstance(void*) +{ + return static_cast(new SfizzVstController); +} + +/* + Note(jpc) Generated at random with uuidgen. + Can't find docs on it... maybe it's to register somewhere? + */ +FUID SfizzVstController::cid(0x7129736c, 0xbc784134, 0xbb899d56, 0x2ebafe4f); diff --git a/vst/SfizzVstController.h b/vst/SfizzVstController.h new file mode 100644 index 00000000..47c3ba3c --- /dev/null +++ b/vst/SfizzVstController.h @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: BSD-2-Clause + +// This code is part of the sfizz library and is licensed under a BSD 2-clause +// license. You should have receive a LICENSE.md file along with the code. +// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz + +#pragma once +#include "SfizzVstState.h" +#include "public.sdk/source/vst/vsteditcontroller.h" +#include "public.sdk/source/vst/vstparameters.h" +#include "vstgui/plugin-bindings/vst3editor.h" +class SfizzVstState; + +using namespace Steinberg; +using namespace VSTGUI; + +class SfizzVstControllerNoUi : public Vst::EditController, + public Vst::IMidiMapping { +public: + virtual ~SfizzVstControllerNoUi() {} + + tresult PLUGIN_API initialize(FUnknown* context) override; + tresult PLUGIN_API terminate() override; + + tresult PLUGIN_API getMidiControllerAssignment(int32 busIndex, int16 channel, Vst::CtrlNumber midiControllerNumber, Vst::ParamID& id) override; + + enum { numControllerParams = 128 }; + + // interfaces + OBJ_METHODS(SfizzVstControllerNoUi, Vst::EditController) + DEFINE_INTERFACES + DEF_INTERFACE(Vst::IMidiMapping) + END_DEFINE_INTERFACES(Vst::EditController) + REFCOUNT_METHODS(Vst::EditController) + + enum { + kPidMidiCC0, + kPidMidiCCLast = kPidMidiCC0 + numControllerParams - 1, + kPidMidiAftertouch, + kPidMidiPitchBend, + /* Reserved */ + }; +}; + +class SfizzVstController : public SfizzVstControllerNoUi, public VSTGUI::VST3EditorDelegate { +public: + IPlugView* PLUGIN_API createView(FIDString name) override; + + tresult PLUGIN_API setParamNormalized(Vst::ParamID tag, Vst::ParamValue value) override; + tresult PLUGIN_API setComponentState(IBStream* state) override; + + struct StateListener { + virtual void onStateChanged() = 0; + }; + + const SfizzVstState& getSfizzState() const { return _state; } + + void addStateListener(StateListener* listener); + void removeStateListener(StateListener* listener); + + /// + static FUnknown* createInstance(void*); + + static FUID cid; + +private: + SfizzVstState _state; + std::vector _stateListeners; +}; diff --git a/vst/SfizzVstEditor.cpp b/vst/SfizzVstEditor.cpp new file mode 100644 index 00000000..46bd7139 --- /dev/null +++ b/vst/SfizzVstEditor.cpp @@ -0,0 +1,180 @@ +// SPDX-License-Identifier: BSD-2-Clause + +// This code is part of the sfizz library and is licensed under a BSD 2-clause +// license. You should have receive a LICENSE.md file along with the code. +// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz + +#include "SfizzVstEditor.h" +#include "SfizzVstState.h" +#if !defined(__APPLE__) && !defined(_WIN32) +#include "x11runloop.h" +#endif + +using namespace VSTGUI; + +static constexpr int kEditorWidth = 800; +static constexpr int kEditorHeight = 40; + +SfizzVstEditor::SfizzVstEditor(void *controller) + : VSTGUIEditor(controller) +{ + static_cast(getController())->addStateListener(this); +} + +SfizzVstEditor::~SfizzVstEditor() +{ + static_cast(getController())->removeStateListener(this); +} + +bool PLUGIN_API SfizzVstEditor::open(void* parent, const VSTGUI::PlatformType& platformType) +{ + CRect wsize(0, 0, kEditorWidth, kEditorHeight); + CFrame *frame = new CFrame(wsize, this); + this->frame = frame; + + IPlatformFrameConfig* config = nullptr; + +#if !defined(__APPLE__) && !defined(_WIN32) + X11::FrameConfig x11config; + x11config.runLoop = VSTGUI::owned(new RunLoop(plugFrame)); + config = &x11config; +#endif + + createFrameContents(); + updateStateDisplay(); + + frame->open(parent, platformType, config); + return true; +} + +void PLUGIN_API SfizzVstEditor::close() +{ + CFrame *frame = this->frame; + if (frame) { + frame->forget(); + this->frame = nullptr; + } +} + +/// +void SfizzVstEditor::valueChanged(CControl* ctl) +{ + int32_t tag = ctl->getTag(); + float value = ctl->getValue(); + + switch (tag) { + case kTagLoadSfzFile: + if (value != 1) + break; + + chooseSfzFile(); + + break; + } +} + +void SfizzVstEditor::onStateChanged() +{ + updateStateDisplay(); +} + +/// +void SfizzVstEditor::chooseSfzFile() +{ + SharedPointer fs(CNewFileSelector::create(frame)); + + fs->setTitle("Load SFZ file"); + fs->setDefaultExtension(CFileExtension("SFZ", "sfz")); + + if (fs->runModal()) { + UTF8StringPtr file = fs->getSelectedFile(0); + if (file) + loadSfzFile(file); + } +} + +void SfizzVstEditor::loadSfzFile(const std::string& filePath) +{ + _fileLabel->setText(filePath.c_str()); + + Vst::EditController* ctl = getController(); + + Vst::IMessage *msg = ctl->allocateMessage(); + if (msg) { + msg->setMessageID("LoadSfz"); + Vst::IAttributeList* attr = msg->getAttributes(); + attr->setString("File", Steinberg::String(filePath.c_str()).text()); + ctl->sendMessage(msg); + msg->release(); + } +} + +/// +class SimpleButton : public CControl { +public: + explicit SimpleButton(const CRect& size, IControlListener* listener = nullptr, int32_t tag = -1, UTF8StringPtr title = nullptr) + : CControl(size, listener, tag), _title(title ? title : "") { + } + + void draw(CDrawContext *dc) override + { + CRect bounds = getViewSize(); + dc->setFrameColor(CColor(0xff, 0xff, 0xff)); + dc->drawRect(bounds, kDrawStroked); + dc->drawString(_title.c_str(), bounds); + } + + CMouseEventResult onMouseDown(CPoint& where, const CButtonState& buttons) override + { + if (!buttons.isLeftButton()) + return kMouseEventNotHandled; + + value = getMin(); + if (isDirty()) { + valueChanged(); + invalid(); + } + value = getMax(); + if (isDirty()) + { + valueChanged(); + invalid(); + } + + return kMouseEventHandled; + } + + CLASS_METHODS(SimpleButton, CControl) + +private: + std::string _title; +}; + +/// +void SfizzVstEditor::createFrameContents() +{ + CFrame* frame = this->frame; + CRect bounds = frame->getViewSize(); + + CTextLabel *label; + CRect rect; + CRect rect2; + + rect = CRect(10.0, 10.0, 120.0, 30.0); + frame->addView(new SimpleButton(rect, this, kTagLoadSfzFile, "Load SFZ file")); + + rect2 = CRect(150.0, 10.0, bounds.right - 10.0, 30.0); + frame->addView((label = new CTextLabel(rect2, "no file"))); + label->setHoriAlign(kLeftText); + _fileLabel = label; +} + +void SfizzVstEditor::updateStateDisplay() +{ + if (!frame) + return; + + const SfizzVstState& state = static_cast(getController())->getSfizzState(); + + _fileLabel->setText(state.sfzFile.c_str()); +} diff --git a/vst/SfizzVstEditor.h b/vst/SfizzVstEditor.h new file mode 100644 index 00000000..4c3c44c5 --- /dev/null +++ b/vst/SfizzVstEditor.h @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: BSD-2-Clause + +// This code is part of the sfizz library and is licensed under a BSD 2-clause +// license. You should have receive a LICENSE.md file along with the code. +// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz + +#pragma once +#include "SfizzVstController.h" +#include "public.sdk/source/vst/vstguieditor.h" + +using namespace Steinberg; +using namespace VSTGUI; + +class SfizzVstEditor : public Vst::VSTGUIEditor, public IControlListener, public SfizzVstController::StateListener { +public: + explicit SfizzVstEditor(void *controller); + ~SfizzVstEditor(); + + bool PLUGIN_API open(void* parent, const VSTGUI::PlatformType& platformType = VSTGUI::kDefaultNative) override; + void PLUGIN_API close() override; + + // IControlListener + void valueChanged(CControl* ctl) override; + + // SfizzVstController::StateListener + void onStateChanged() override; + +private: + void chooseSfzFile(); + void loadSfzFile(const std::string& filePath); + + void createFrameContents(); + void updateStateDisplay(); + + enum { + kTagLoadSfzFile, + }; + + CTextLabel* _fileLabel = nullptr; +}; diff --git a/vst/SfizzVstProcessor.cpp b/vst/SfizzVstProcessor.cpp new file mode 100644 index 00000000..88e71d1a --- /dev/null +++ b/vst/SfizzVstProcessor.cpp @@ -0,0 +1,284 @@ +// SPDX-License-Identifier: BSD-2-Clause + +// This code is part of the sfizz library and is licensed under a BSD 2-clause +// license. You should have receive a LICENSE.md file along with the code. +// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz + +#include "SfizzVstProcessor.h" +#include "SfizzVstController.h" +#include "SfizzVstState.h" +#include "base/source/fstreamer.h" +#include "pluginterfaces/vst/ivstevents.h" +#include "pluginterfaces/vst/ivstparameterchanges.h" +#include + +#pragma message("TODO: send tempo") + +SfizzVstProcessor::SfizzVstProcessor() + : _fifoToWorker(1024) +{ + setControllerClass(SfizzVstController::cid); +} + +SfizzVstProcessor::~SfizzVstProcessor() +{ + setActive(false); // to be sure +} + +tresult PLUGIN_API SfizzVstProcessor::initialize(FUnknown* context) +{ + tresult result = AudioEffect::initialize(context); + if (result != kResultTrue) + return result; + + addAudioOutput(STR16("Audio Output"), Vst::SpeakerArr::kStereo); + addEventInput(STR16("Event Input"), 1); + + return result; +} + +tresult PLUGIN_API SfizzVstProcessor::setBusArrangements(Vst::SpeakerArrangement* inputs, int32 numIns, Vst::SpeakerArrangement* outputs, int32 numOuts) +{ + bool isStereo = numIns == 0 && numOuts == 1 && outputs[0] == Vst::SpeakerArr::kStereo; + + if (!isStereo) + return kResultFalse; + + return AudioEffect::setBusArrangements(inputs, numIns, outputs, numOuts); +} + +tresult PLUGIN_API SfizzVstProcessor::setState(IBStream* state) +{ + SfizzVstState s; + + tresult r = s.load(state); + if (r != kResultTrue) + return r; + + loadSfzFile(s.sfzFile); + + return r; +} + +tresult PLUGIN_API SfizzVstProcessor::getState(IBStream* state) +{ + SfizzVstState s; + { + std::lock_guard lock(_processMutex); + s.sfzFile = _sfzFile; + } + + return s.store(state); +} + +tresult PLUGIN_API SfizzVstProcessor::canProcessSampleSize(int32 symbolicSampleSize) +{ + if (symbolicSampleSize != Vst::kSample32) + return kResultFalse; + + return kResultTrue; +} + +tresult PLUGIN_API SfizzVstProcessor::setActive(TBool state) +{ + stopBackgroundWork(); + _synth.reset(); + + if (state) { + fprintf(stderr, "[Sfizz] new synth\n"); + sfz::Sfizz* synth = new sfz::Sfizz; + _synth.reset(synth); + + synth->setSampleRate(processSetup.sampleRate); + synth->setSamplesPerBlock(processSetup.maxSamplesPerBlock); + + loadSfzFile(_sfzFile); + + _workRunning = true; + _worker = std::thread([this]() { doBackgroundWork(); }); + } + + return kResultTrue; +} + +tresult PLUGIN_API SfizzVstProcessor::process(Vst::ProcessData& data) +{ + sfz::Sfizz& synth = *_synth; + + if (data.numOutputs < 1) // flush mode + return kResultTrue; + + uint32 numFrames = data.numSamples; + constexpr uint32 numChannels = 2; + float* outputs[numChannels]; + + assert(numChannels == data.outputs[0].numChannels); + + for (unsigned c = 0; c < numChannels; ++c) + outputs[c] = data.outputs[0].channelBuffers32[c]; + + std::unique_lock lock(_processMutex, std::try_to_lock); + if (!lock.owns_lock()) { + for (unsigned c = 0; c < numChannels; ++c) + std::memset(outputs[c], 0, numFrames * sizeof(float)); + data.outputs[0].silenceFlags = 3; + return kResultTrue; + } + + if (Vst::IParameterChanges* pc = data.inputParameterChanges) { + uint32 paramCount = pc->getParameterCount(); + + for (uint32 paramIndex = 0; paramIndex < paramCount; ++paramIndex) { + Vst::IParamValueQueue* vq = pc->getParameterData(paramIndex); + + Vst::ParamID id = vq->getParameterId(); + + switch (id) { + default: + if (id >= SfizzVstController::kPidMidiCC0 && id <= SfizzVstController::kPidMidiCCLast) { + int ccNumber = id - SfizzVstController::kPidMidiCC0; + for (uint32 pointIndex = 0, pointCount = vq->getPointCount(); pointIndex < pointCount; ++pointIndex) { + int32 sampleOffset; + Vst::ParamValue value; + if (vq->getPoint(pointIndex, sampleOffset, value) == kResultTrue) + synth.cc(sampleOffset, ccNumber, (int)(0.5 + value * 127.0)); + } + } + break; + + case SfizzVstController::kPidMidiAftertouch: + for (uint32 pointIndex = 0, pointCount = vq->getPointCount(); pointIndex < pointCount; ++pointIndex) { + int32 sampleOffset; + Vst::ParamValue value; + if (vq->getPoint(pointIndex, sampleOffset, value) == kResultTrue) + synth.aftertouch(sampleOffset, (int)(0.5 + value * 127.0)); + } + break; + + case SfizzVstController::kPidMidiPitchBend: + for (uint32 pointIndex = 0, pointCount = vq->getPointCount(); pointIndex < pointCount; ++pointIndex) { + int32 sampleOffset; + Vst::ParamValue value; + if (vq->getPoint(pointIndex, sampleOffset, value) == kResultTrue) + synth.pitchWheel(sampleOffset, (int)(0.5 + value * 16383) - 8192); + } + break; + } + } + } + + if (Vst::IEventList* events = data.inputEvents) { + uint32 numEvents = events->getEventCount(); + + for (uint32 i = 0; i < numEvents; i++) { + Vst::Event e; + if (events->getEvent(i, e) != kResultTrue) + continue; + + auto convertVelocityFromFloat = [](float x) -> int { + return std::min(127, std::max(0, (int)(x * 127.0f))); + }; + + switch (e.type) { + case Vst::Event::kNoteOnEvent: + synth.noteOn(e.sampleOffset, e.noteOn.pitch, convertVelocityFromFloat(e.noteOn.velocity)); + break; + case Vst::Event::kNoteOffEvent: + synth.noteOff(e.sampleOffset, e.noteOff.pitch, convertVelocityFromFloat(e.noteOff.velocity)); + break; + // case Vst::Event::kPolyPressureEvent: + // synth.aftertouch(e.sampleOffset, convertVelocityFromFloat(e.polyPressure.pressure)); + // break; + } + } + } + + synth.renderBlock(outputs, numFrames, numChannels); + return kResultTrue; +} + +tresult PLUGIN_API SfizzVstProcessor::notify(Vst::IMessage* message) +{ + tresult result = AudioEffect::notify(message); + if (result != kResultFalse) + return result; + + if (!_fifoToWorker.push(message)) + return kOutOfMemory; + + message->addRef(); + _semaToWorker.post(); + + return kResultTrue; +} + +FUnknown* SfizzVstProcessor::createInstance(void*) +{ + return static_cast(new SfizzVstProcessor); +} + +void SfizzVstProcessor::loadSfzFile(std::string file) +{ + std::lock_guard lock(_processMutex); + + if (_synth) { + fprintf(stderr, "[Sfizz] load SFZ file: %s\n", file.c_str()); + _synth->loadSfzFile(file); + } + + _sfzFile = std::move(file); +} + +void SfizzVstProcessor::doBackgroundWork() +{ + constexpr uint32 maxPathLen = 32768; + + for (;;) { + _semaToWorker.wait(); + + if (!_workRunning) + break; + + Vst::IMessage* msg; + if (!_fifoToWorker.pop(msg)) { + fprintf(stderr, "[Sfizz] message synchronization error in worker\n"); + std::abort(); + } + + const char* id = msg->getMessageID(); + Vst::IAttributeList* attr = msg->getAttributes(); + + if (!std::strcmp(id, "LoadSfz")) { + std::vector path(maxPathLen + 1); + if (attr->getString("File", path.data(), maxPathLen) == kResultTrue) + loadSfzFile(Steinberg::String(path.data()).text8()); + } + + msg->release(); + } +} + +void SfizzVstProcessor::stopBackgroundWork() +{ + if (!_workRunning) + return; + + _workRunning = false; + _semaToWorker.post(); + _worker.join(); + + while (_semaToWorker.try_wait()) { + Vst::IMessage* msg; + if (!_fifoToWorker.pop(msg)) { + fprintf(stderr, "[Sfizz] message synchronization error in processor\n"); + std::abort(); + } + msg->release(); + } +} + +/* + Note(jpc) Generated at random with uuidgen. + Can't find docs on it... maybe it's to register somewhere? + */ +FUID SfizzVstProcessor::cid(0xe8fab718, 0x15ed46e3, 0x8b598310, 0x1e12993f); diff --git a/vst/SfizzVstProcessor.h b/vst/SfizzVstProcessor.h new file mode 100644 index 00000000..86bbab81 --- /dev/null +++ b/vst/SfizzVstProcessor.h @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: BSD-2-Clause + +// This code is part of the sfizz library and is licensed under a BSD 2-clause +// license. You should have receive a LICENSE.md file along with the code. +// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz + +#pragma once +#include "public.sdk/source/vst/vstaudioeffect.h" +#include "public.sdk/source/vst/utility/ringbuffer.h" +#include "RTSemaphore.h" +#include +#include +#include +#include + +using namespace Steinberg; + +class SfizzVstProcessor : public Vst::AudioEffect { +public: + SfizzVstProcessor(); + ~SfizzVstProcessor(); + + tresult PLUGIN_API initialize(FUnknown* context) override; + tresult PLUGIN_API setBusArrangements(Vst::SpeakerArrangement* inputs, int32 numIns, Vst::SpeakerArrangement* outputs, int32 numOuts) override; + + tresult PLUGIN_API setState(IBStream* state) override; + tresult PLUGIN_API getState(IBStream* state) override; + + tresult PLUGIN_API canProcessSampleSize(int32 symbolicSampleSize) override; + tresult PLUGIN_API setActive(TBool state) override; + tresult PLUGIN_API process(Vst::ProcessData& data) override; + + tresult PLUGIN_API notify(Vst::IMessage* message) override; + + static FUnknown* createInstance(void*); + + static FUID cid; + + // --- Sfizz stuff here below --- +private: + std::unique_ptr _synth; + std::thread _worker; + volatile bool _workRunning = false; + Steinberg::OneReaderOneWriter::RingBuffer _fifoToWorker; + RTSemaphore _semaToWorker; + std::mutex _processMutex; + + // state + std::string _sfzFile; + + // + void loadSfzFile(std::string file); + + // worker + void doBackgroundWork(); + void stopBackgroundWork(); +}; diff --git a/vst/SfizzVstState.cpp b/vst/SfizzVstState.cpp new file mode 100644 index 00000000..447f7ad0 --- /dev/null +++ b/vst/SfizzVstState.cpp @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: BSD-2-Clause + +// This code is part of the sfizz library and is licensed under a BSD 2-clause +// license. You should have receive a LICENSE.md file along with the code. +// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz + +#include "SfizzVstState.h" +#include +#include + +tresult SfizzVstState::load(IBStream* state) +{ + IBStreamer s(state, kLittleEndian); + + uint64 version = 0; + if (!s.readInt64u(version)) + return kResultFalse; + + while (const char* key = s.readStr8()) { + if (!std::strcmp(key, "SfzFile")) { + const char* value = s.readStr8(); + if (!value) + return kResultFalse; + sfzFile = value; + } + } + + return kResultTrue; +} + +tresult SfizzVstState::store(IBStream* state) const +{ + IBStreamer s(state, kLittleEndian); + + if (!s.writeInt64u(currentStateVersion)) + return kResultFalse; + + if (!s.writeStr8("SfzFile") || !s.writeStr8(sfzFile.c_str())) + return kResultFalse; + + return kResultTrue; +} diff --git a/vst/SfizzVstState.h b/vst/SfizzVstState.h new file mode 100644 index 00000000..df0adecc --- /dev/null +++ b/vst/SfizzVstState.h @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: BSD-2-Clause + +// This code is part of the sfizz library and is licensed under a BSD 2-clause +// license. You should have receive a LICENSE.md file along with the code. +// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz + +#pragma once +#include "base/source/fstreamer.h" +#include + +using namespace Steinberg; + +class SfizzVstState { +public: + std::string sfzFile; + + static constexpr uint64 currentStateVersion = 0; + + tresult load(IBStream* state); + tresult store(IBStream* state) const; +}; diff --git a/vst/VstPluginDefs.h.in b/vst/VstPluginDefs.h.in new file mode 100644 index 00000000..c67283d8 --- /dev/null +++ b/vst/VstPluginDefs.h.in @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: BSD-2-Clause + +// This code is part of the sfizz library and is licensed under a BSD 2-clause +// license. You should have receive a LICENSE.md file along with the code. +// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz + +#define VSTPLUGIN_NAME "@VSTPLUGIN_NAME@" +#define VSTPLUGIN_VENDOR "@VSTPLUGIN_VENDOR@" +#define VSTPLUGIN_URL "@VSTPLUGIN_URL@" +#define VSTPLUGIN_EMAIL "@VSTPLUGIN_EMAIL@" +#define VSTPLUGIN_VERSION "@PROJECT_VERSION@" diff --git a/vst/VstPluginFactory.cpp b/vst/VstPluginFactory.cpp new file mode 100644 index 00000000..cda0d7f3 --- /dev/null +++ b/vst/VstPluginFactory.cpp @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: BSD-2-Clause + +// This code is part of the sfizz library and is licensed under a BSD 2-clause +// license. You should have receive a LICENSE.md file along with the code. +// If not, contact the sfizz maintainers at https://github.com/sfztools/sfizz + +#include "SfizzVstProcessor.h" +#include "SfizzVstController.h" +#include "VstPluginDefs.h" +#include "public.sdk/source/main/pluginfactory.h" +#include "pluginterfaces/vst/ivstcomponent.h" +#include "pluginterfaces/vst/ivstaudioprocessor.h" +#include "pluginterfaces/vst/ivsteditcontroller.h" + +BEGIN_FACTORY_DEF(VSTPLUGIN_VENDOR, + VSTPLUGIN_URL, + "mailto:" VSTPLUGIN_EMAIL) + +DEF_CLASS2 (INLINE_UID_FROM_FUID(SfizzVstProcessor::cid), + PClassInfo::kManyInstances, + kVstAudioEffectClass, + VSTPLUGIN_NAME, + Vst::kDistributable, + Vst::PlugType::kInstrumentSynth, + VSTPLUGIN_VERSION, + kVstVersionString, + SfizzVstProcessor::createInstance) + +DEF_CLASS2 (INLINE_UID_FROM_FUID(SfizzVstController::cid), + PClassInfo::kManyInstances, + kVstComponentControllerClass, + VSTPLUGIN_NAME, + 0, // not used here + "", // not used here + VSTPLUGIN_VERSION, + kVstVersionString, + SfizzVstController::createInstance) + +END_FACTORY + +bool InitModule() +{ + return true; +} + +bool DeinitModule() +{ + return true; +} diff --git a/vst/cmake/Vst3.cmake b/vst/cmake/Vst3.cmake new file mode 100644 index 00000000..32b9b96a --- /dev/null +++ b/vst/cmake/Vst3.cmake @@ -0,0 +1,244 @@ +find_package(Threads REQUIRED) + +# --- VST3SDK --- +function(plugin_add_vst3sdk NAME) + target_sources("${NAME}" PRIVATE + "${VST3SDK_BASEDIR}/base/source/baseiids.cpp" + "${VST3SDK_BASEDIR}/base/source/fbuffer.cpp" + "${VST3SDK_BASEDIR}/base/source/fdebug.cpp" + "${VST3SDK_BASEDIR}/base/source/fdynlib.cpp" + "${VST3SDK_BASEDIR}/base/source/fobject.cpp" + "${VST3SDK_BASEDIR}/base/source/fstreamer.cpp" + "${VST3SDK_BASEDIR}/base/source/fstring.cpp" + # "${VST3SDK_BASEDIR}/base/source/timer.cpp" + "${VST3SDK_BASEDIR}/base/source/updatehandler.cpp" + "${VST3SDK_BASEDIR}/base/thread/source/fcondition.cpp" + "${VST3SDK_BASEDIR}/base/thread/source/flock.cpp" + "${VST3SDK_BASEDIR}/pluginterfaces/base/conststringtable.cpp" + "${VST3SDK_BASEDIR}/pluginterfaces/base/coreiids.cpp" + "${VST3SDK_BASEDIR}/pluginterfaces/base/funknown.cpp" + "${VST3SDK_BASEDIR}/pluginterfaces/base/ustring.cpp" + "${VST3SDK_BASEDIR}/public.sdk/source/common/commoniids.cpp" + "${VST3SDK_BASEDIR}/public.sdk/source/common/pluginview.cpp" + "${VST3SDK_BASEDIR}/public.sdk/source/main/pluginfactory.cpp" + "${VST3SDK_BASEDIR}/public.sdk/source/vst/vstaudioeffect.cpp" + "${VST3SDK_BASEDIR}/public.sdk/source/vst/vstbus.cpp" + "${VST3SDK_BASEDIR}/public.sdk/source/vst/vstcomponent.cpp" + "${VST3SDK_BASEDIR}/public.sdk/source/vst/vstcomponentbase.cpp" + "${VST3SDK_BASEDIR}/public.sdk/source/vst/vsteditcontroller.cpp" + "${VST3SDK_BASEDIR}/public.sdk/source/vst/vstinitiids.cpp" + "${VST3SDK_BASEDIR}/public.sdk/source/vst/vstnoteexpressiontypes.cpp" + "${VST3SDK_BASEDIR}/public.sdk/source/vst/vstparameters.cpp" + "${VST3SDK_BASEDIR}/public.sdk/source/vst/vstpresetfile.cpp" + "${VST3SDK_BASEDIR}/public.sdk/source/vst/vstrepresentation.cpp") + if(WIN32) + target_sources("${NAME}" PRIVATE + "${VST3SDK_BASEDIR}/public.sdk/source/common/threadchecker_win32.cpp" + "${VST3SDK_BASEDIR}/public.sdk/source/vst/vstgui_win32_bundle_support.cpp" + "${VST3SDK_BASEDIR}/public.sdk/source/main/dllmain.cpp") + elseif(APPLE) + target_sources("${NAME}" PRIVATE + "${VST3SDK_BASEDIR}/public.sdk/source/main/macmain.cpp") + else() + target_sources("${NAME}" PRIVATE + "${VST3SDK_BASEDIR}/public.sdk/source/common/threadchecker_linux.cpp" + "${VST3SDK_BASEDIR}/public.sdk/source/main/linuxmain.cpp") + endif() + target_include_directories("${NAME}" PRIVATE "${VST3SDK_BASEDIR}") + target_link_libraries("${NAME}" PRIVATE Threads::Threads) +endfunction() + +# --- VSTGUI --- +function(plugin_add_vstgui NAME) + target_sources("${NAME}" PRIVATE + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/animation/animations.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/animation/animator.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/animation/timingfunctions.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/cbitmap.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/cbitmapfilter.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/ccolor.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/cdatabrowser.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/cdrawcontext.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/cdrawmethods.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/cdropsource.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/cfileselector.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/cfont.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/cframe.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/cgradientview.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/cgraphicspath.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/clayeredviewcontainer.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/clinestyle.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/coffscreencontext.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/controls/cautoanimation.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/controls/cbuttons.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/controls/ccolorchooser.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/controls/ccontrol.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/controls/cfontchooser.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/controls/cknob.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/controls/clistcontrol.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/controls/cmoviebitmap.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/controls/cmoviebutton.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/controls/coptionmenu.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/controls/cparamdisplay.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/controls/cscrollbar.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/controls/csearchtextedit.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/controls/csegmentbutton.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/controls/cslider.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/controls/cspecialdigit.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/controls/csplashscreen.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/controls/cstringlist.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/controls/cswitch.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/controls/ctextedit.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/controls/ctextlabel.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/controls/cvumeter.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/controls/cxypad.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/copenglview.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/cpoint.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/crect.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/crowcolumnview.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/cscrollview.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/cshadowviewcontainer.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/csplitview.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/cstring.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/ctabview.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/ctooltipsupport.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/cview.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/cviewcontainer.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/cvstguitimer.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/genericstringlistdatabrowsersource.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/common/genericoptionmenu.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/vstguidebug.cpp") + + if(WIN32) + target_sources("${NAME}" PRIVATE + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/common/fileresourceinputstream.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/win32/direct2d/d2dbitmap.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/win32/direct2d/d2ddrawcontext.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/win32/direct2d/d2dfont.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/win32/direct2d/d2dgraphicspath.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/win32/win32datapackage.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/win32/win32dragging.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/win32/win32frame.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/win32/win32openglview.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/win32/win32optionmenu.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/win32/win32support.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/win32/win32textedit.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/win32/winfileselector.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/win32/winstring.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/win32/wintimer.cpp") + elseif(APPLE) + target_sources("${NAME}" PRIVATE + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/common/fileresourceinputstream.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/common/genericoptionmenu.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/common/generictextedit.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/mac/carbon/hiviewframe.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/mac/carbon/hiviewoptionmenu.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/mac/carbon/hiviewtextedit.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/mac/caviewlayer.mm" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/mac/cfontmac.mm" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/mac/cgbitmap.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/mac/cgdrawcontext.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/mac/cocoa/autoreleasepool.mm" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/mac/cocoa/cocoahelpers.mm" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/mac/cocoa/cocoaopenglview.mm" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/mac/cocoa/cocoatextedit.mm" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/mac/cocoa/nsviewdraggingsession.mm" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/mac/cocoa/nsviewframe.mm" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/mac/cocoa/nsviewoptionmenu.mm" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/mac/macclipboard.mm" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/mac/macfileselector.mm" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/mac/macglobals.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/mac/macstring.mm" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/mac/mactimer.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/mac/quartzgraphicspath.cpp") + else() + target_sources("${NAME}" PRIVATE + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/common/fileresourceinputstream.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/common/generictextedit.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/linux/cairobitmap.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/linux/cairocontext.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/linux/cairofont.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/linux/cairogradient.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/linux/cairopath.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/linux/linuxstring.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/linux/x11fileselector.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/linux/x11frame.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/linux/x11platform.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/linux/x11timer.cpp" + "${VST3SDK_BASEDIR}/vstgui4/vstgui/lib/platform/linux/x11utils.cpp") + endif() + + target_include_directories("${NAME}" PRIVATE "${VST3SDK_BASEDIR}/vstgui4") + + if(WIN32) + # + elseif(APPLE) + # + else() + find_package(X11 REQUIRED) + find_package(Freetype REQUIRED) + find_package(PkgConfig REQUIRED) + pkg_check_modules(LIBXCB REQUIRED xcb) + pkg_check_modules(LIBXCB_UTIL REQUIRED xcb-util) + pkg_check_modules(LIBXCB_CURSOR REQUIRED xcb-cursor) + pkg_check_modules(LIBXCB_KEYSYMS REQUIRED xcb-keysyms) + pkg_check_modules(LIBXCB_XKB REQUIRED xcb-xkb) + pkg_check_modules(LIBXKB_COMMON REQUIRED xkbcommon) + pkg_check_modules(LIBXKB_COMMON_X11 REQUIRED xkbcommon-x11) + pkg_check_modules(CAIRO REQUIRED cairo) + pkg_check_modules(FONTCONFIG REQUIRED fontconfig) + target_include_directories("${NAME}" PRIVATE + ${X11_INCLUDE_DIRS} + ${FREETYPE_INCLUDE_DIRS} + ${LIBXCB_INCLUDE_DIRS} + ${LIBXCB_UTIL_INCLUDE_DIRS} + ${LIBXCB_CURSOR_INCLUDE_DIRS} + ${LIBXCB_KEYSYMS_INCLUDE_DIRS} + ${LIBXCB_XKB_INCLUDE_DIRS} + ${LIBXKB_COMMON_INCLUDE_DIRS} + ${LIBXKB_COMMON_X11_INCLUDE_DIRS} + ${CAIRO_INCLUDE_DIRS} + ${FONTCONFIG_INCLUDE_DIRS}) + target_link_libraries("${NAME}" PRIVATE + ${X11_LIBRARIES} + ${FREETYPE_LIBRARIES} + ${LIBXCB_LIBRARIES} + ${LIBXCB_UTIL_LIBRARIES} + ${LIBXCB_CURSOR_LIBRARIES} + ${LIBXCB_KEYSYMS_LIBRARIES} + ${LIBXCB_XKB_LIBRARIES} + ${LIBXKB_COMMON_LIBRARIES} + ${LIBXKB_COMMON_X11_LIBRARIES} + ${CAIRO_LIBRARIES} + ${FONTCONFIG_LIBRARIES}) + find_library(DL_LIBRARY "dl") + if(DL_LIBRARY) + target_link_libraries("${NAME}" PRIVATE "${DL_LIBRARY}") + endif() + endif() + + target_sources("${NAME}" PRIVATE + "${VST3SDK_BASEDIR}/public.sdk/source/vst/vstguieditor.cpp") + + target_include_directories("${NAME}" PRIVATE + external/steinberg/src) +endfunction() + +# --- VST3 Bundle architecture --- +if(NOT VST3_PACKAGE_ARCHITECTURE) + if(APPLE) + # VST3 packages are universal on Apple, architecture string not needed + else() + if(CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64") + set(VST3_PACKAGE_ARCHITECTURE "x86_64") + elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^i.86$") + if(WIN32) + set(VST3_PACKAGE_ARCHITECTURE "x86") + else() + set(VST3_PACKAGE_ARCHITECTURE "i386") + endif() + else() + message(FATAL_ERROR "We don't know this architecture for VST3: ${CMAKE_SYSTEM_PROCESSOR}.") + endif() + endif() +endif() diff --git a/vst/external/steinberg/LICENSE b/vst/external/steinberg/LICENSE new file mode 100644 index 00000000..a75f1d5b --- /dev/null +++ b/vst/external/steinberg/LICENSE @@ -0,0 +1,27 @@ +//----------------------------------------------------------------------------- +// VSTGUI LICENSE +// (c) 2018, Steinberg Media Technologies, 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. +//----------------------------------------------------------------------------- diff --git a/vst/external/steinberg/src/x11runloop.h b/vst/external/steinberg/src/x11runloop.h new file mode 100644 index 00000000..7cece455 --- /dev/null +++ b/vst/external/steinberg/src/x11runloop.h @@ -0,0 +1,110 @@ +#include "vstgui/lib/platform/linux/x11frame.h" +#include "pluginterfaces/gui/iplugview.h" +#include "base/source/fstring.h" + +namespace VSTGUI { + +// Map Steinberg Vst Interface to VSTGUI Interface +class RunLoop : public X11::IRunLoop, public AtomicReferenceCounted +{ +public: + struct EventHandler : Steinberg::Linux::IEventHandler, public Steinberg::FObject + { + X11::IEventHandler* handler {nullptr}; + + void PLUGIN_API onFDIsSet (Steinberg::Linux::FileDescriptor) override + { + if (handler) + handler->onEvent (); + } + DELEGATE_REFCOUNT (Steinberg::FObject) + DEFINE_INTERFACES + DEF_INTERFACE (Steinberg::Linux::IEventHandler) + END_DEFINE_INTERFACES (Steinberg::FObject) + }; + struct TimerHandler : Steinberg::Linux::ITimerHandler, public Steinberg::FObject + { + X11::ITimerHandler* handler {nullptr}; + + void PLUGIN_API onTimer () final + { + if (handler) + handler->onTimer (); + } + DELEGATE_REFCOUNT (Steinberg::FObject) + DEFINE_INTERFACES + DEF_INTERFACE (Steinberg::Linux::ITimerHandler) + END_DEFINE_INTERFACES (Steinberg::FObject) + }; + + bool registerEventHandler (int fd, X11::IEventHandler* handler) final + { + if(!runLoop) + return false; + + auto smtgHandler = Steinberg::owned (new EventHandler ()); + smtgHandler->handler = handler; + if (runLoop->registerEventHandler (smtgHandler, fd) == Steinberg::kResultTrue) + { + eventHandlers.push_back (smtgHandler); + return true; + } + return false; + } + bool unregisterEventHandler (X11::IEventHandler* handler) final + { + if(!runLoop) + return false; + + for (auto it = eventHandlers.begin (), end = eventHandlers.end (); it != end; ++it) + { + if ((*it)->handler == handler) + { + runLoop->unregisterEventHandler ((*it)); + eventHandlers.erase (it); + return true; + } + } + return false; + } + bool registerTimer (uint64_t interval, X11::ITimerHandler* handler) final + { + if(!runLoop) + return false; + + auto smtgHandler = Steinberg::owned (new TimerHandler ()); + smtgHandler->handler = handler; + if (runLoop->registerTimer (smtgHandler, interval) == Steinberg::kResultTrue) + { + timerHandlers.push_back (smtgHandler); + return true; + } + return false; + } + bool unregisterTimer (X11::ITimerHandler* handler) final + { + if(!runLoop) + return false; + + for (auto it = timerHandlers.begin (), end = timerHandlers.end (); it != end; ++it) + { + if ((*it)->handler == handler) + { + runLoop->unregisterTimer ((*it)); + timerHandlers.erase (it); + return true; + } + } + return false; + } + + RunLoop (Steinberg::FUnknown* runLoop) : runLoop (runLoop) {} +private: + using EventHandlers = std::vector>; + using TimerHandlers = std::vector>; + EventHandlers eventHandlers; + TimerHandlers timerHandlers; + Steinberg::FUnknownPtr runLoop; +}; + +} // namespace diff --git a/vst/vst3.version b/vst/vst3.version new file mode 100644 index 00000000..f86d95c3 --- /dev/null +++ b/vst/vst3.version @@ -0,0 +1,7 @@ +VST3ABI_1.0 { + global: + *GetPluginFactory*; + *ModuleEntry*; + *ModuleExit*; + local: *; +};