diff --git a/dpf/FEATURES.md b/dpf/FEATURES.md new file mode 100644 index 0000000..3bba8f1 --- /dev/null +++ b/dpf/FEATURES.md @@ -0,0 +1,93 @@ +# DPF - DISTRHO Plugin Framework + +This file describes the available features for each plugin format. +The limitations could be due to the plugin format itself or within DPF. +If the limitation is within DPF, a link is provided to a description below on the reason for it. + +| Feature | JACK/Standalone | LADSPA | DSSI | LV2 | VST2 | VST3 | Feature | +|---------------------|---------------------------------------|-------------------------|---------------------|-------------------------------|--------------------------------|----------------------------------|---------------------| +| Audio port groups | [Yes*](#jack-audio-port-groups) | No | No | Yes | No | [No*](#vst3-is-work-in-progress) | Audio port groups | +| Audio port as CV | Yes | No | No | Yes | No | [No*](#vst3-is-work-in-progress) | Audio port as CV | +| Audio sidechan | Yes | No | No | Yes | [No*](#vst2-potential-support) | [No*](#vst3-is-work-in-progress) | Audio sidechan | +| Bypass control | No | No | No | Yes | [No*](#vst2-potential-support) | [No*](#vst3-is-work-in-progress) | Bypass control | +| MIDI input | Yes | No | Yes | Yes | Yes | Yes | MIDI input | +| MIDI output | Yes | No | No | Yes | Yes | Yes | MIDI output | +| Parameter changes | Yes | No | No | [No*](#lv2-parameter-changes) | Yes | Yes | Parameter changes | +| Parameter groups | No | No | No | Yes | Yes | [No*](#vst3-is-work-in-progress) | Parameter groups | +| Parameter outputs | No | No | No | Yes | No | [No*](#vst3-is-work-in-progress) | Parameter outputs | +| Parameter triggers | Yes | No | No | Yes | [No*](#parameter-triggers) | [No*](#parameter-triggers) | Parameter triggers | +| Programs | [Yes*](#jack-parameters-and-programs) | [No*](#ladspa-programs) | [Yes*](#dssi-state) | Yes | [No*](#vst2-programs) | Yes | Programs | +| States | Yes | No | [Yes*](#dssi-state) | Yes | Yes | Yes | States | +| Full/internal state | Yes | No | No | Yes | Yes | Yes | Full/internal state | +| Time position | Yes | No | No | Yes | Yes | Yes | Time position | +| UI | [Yes*](#jack-custom-ui-only) | No | External only | Yes | Embed only | Embed only | UI | +| UI bg/fg colors | No | No | No | Yes | No | No? | UI bg/fg colors | +| UI direct access | Yes | No | No | Yes | Yes | Yes | UI direct access | +| UI host-filebrowser | No | No | No | Yes | [No*](#vst2-potential-support) | [No*](#vst3-is-work-in-progress) | UI host-filebrowser | +| UI host-resize | Yes | No | Yes | Yes | No | [No*](#vst3-is-work-in-progress) | UI host-resize | +| UI remote control | No | No | Yes | Yes | No | Yes | UI remote control | +| UI send midi note | Yes | No | Yes | Yes | Yes | Yes | UI send midi note | + +For things that could be unclear: + +- "States" refers to DPF API support, supporting key-value string pairs for internal state saving +- "Full state" refers to plugins updating their state internally without outside intervention (like host or UI) +- "UI direct access" means `DISTRHO_PLUGIN_WANT_DIRECT_ACCESS` is possible, that is, running DSP and UI on the same process +- "UI remote control" means running the UI on a separate machine (for example over the network) +- An external UI on this table means that it cannot be embed into the host window, but the plugin can still provide one + +# Special notes + +## Parameter triggers + +Trigger-style parameters (parameters which value is reset to its default every run) are only supported in JACK and LV2. +For all other plugin formats DPF will simulate the behaviour through a parameter change request. + +## JACK audio port groups + +DPF will set JACK metadata information for grouping audio ports. +This is not supported by most JACK applications at the moment. + +## JACK parameters and programs + +Under JACK/Stanlone mode, MIDI input events will trigger program and parameter changes. +MIDI program change events work as expected (that is, MIDI program change 0 will load 1st plugin program). +MIDI CCs are used for parameter changes (matching the `midiCC` value you set on each parameter). + +## JACK custom UI only + +There is no generic plugin editor view. +If your plugin has no custom UI, the standalone executable will run but not show any window. + +## LADSPA programs + +Programs for LADSPA could be done via LRDF but this is not supported in DPF. + +## DSSI State + +DSSI only supports state changes when called via UI, no "full state" possible. +This also makes it impossibe to use programs and state at the same time with DSSI, +because in DPF changing programs can lead to state changes but there is no way to fetch this information on DSSI plugins. + +To make it simpler to understand, think of DSSI programs and states as UI properties. +Because in DPF changing the state happens from UI to DSP side, regular DSSI can be supported. +But if we involve programs, they would need to pass through the UI in order to work. Which goes against DPF's design. + +## LV2 parameter changes + +Although this is already implemented in DPF (through a custom extension), this is not implemented on most hosts. +So for now you can pretty much treat it as if not supported. + +## VST2 potential support + +Not supported in DPF at the moment. +It could eventually be, but likely not due to VST2 being phased out by Steinberg. +Contact DPF authors if you require such a feature. + +## VST2 programs + +VST2 program support requires saving state of all programs in memory, which is very expensive and thus not done in DPF. + +## VST3 is work in progress + +Feature is possible, just not implemented yet in DPF. diff --git a/dpf/LICENSING.md b/dpf/LICENSING.md new file mode 100644 index 0000000..ad0a4c4 --- /dev/null +++ b/dpf/LICENSING.md @@ -0,0 +1,47 @@ +# DPF - DISTRHO Plugin Framework + +Even though DPF is quite liberally licensed, not all plugin formats follow the same ideals. +This is usually due to plugin APIs/headers being tied to a specific license or having commercial restrictions. +This file describes the licensing that applies to each individual plugin format as a way to make it clear what is possible and compatible. +Note that if you are making GPLv2+ licensed plugins this does not apply to you, as so far everything is GPLv2+ compatible. + +Regardless of target format, DPF itself needs to be mentioned in attribution. +See the [LICENSE](LICENSE) file for copyright details. + +| Target | License(s) | License restrictions | Additional attribution | +|-----------------|----------------------|-----------------------|------------------------| +| JACK/Standalone | MIT (RtAudio) | Copyright attribution | **RtAudio**: 2001-2019 Gary P. Scavone | +| LADSPA | LGPLv2.1+ | ??? (*) | 2000-2002 Richard W. E. Furse, Paul Barton-Davis, Stefan Westerfeld | +| DSSI | LGPLv2.1+ | ??? (*) | **DSSI**: 2004, 2009 Chris Cannam, Steve Harris and Sean Bolton;
**ALSA**: 1998-2001 Jaroslav Kysela, Abramo Bagnara, Takashi Iwai | +| LV2 | ISC | Copyright attribution | 2006-2020 Steve Harris, David Robillard;
2000-2002 Richard W.E. Furse, Paul Barton-Davis, Stefan Westerfeld | +| VST2 | GPLv2+ or commercial | Must be GPLv2+ compatible or alternatively use Steinberg VST2 SDK (no longer available for new plugins) | GPLv2+ compatible license or custom agreement with Steinberg | +| VST3 | ISC | Copyright attribution | (none, only DPF files used) | + +### LADSPA and DSSI special note + +The header files on LADSPA and DSSI are LGPLv2.1+ licensed, which is unusual for pure APIs without libraries. +LADSPA authors mention this on ladspa.org homepage: + +> LADSPA has been released under LGPL (GNU Lesser General Public License). +> This is not intended to be the final license for LADSPA. +> In the long term it is hoped that LADSPA will have a public license that is even less restrictive, so that commercial applications can use it (in a protected way) without having to use a derived LGPL library. +> It may be that LGPL is already free enough for this, but we aren't sure. + +So the situation for LADSPA/DSSI plugins is unclear for commercial plugins. +These formats are very limited and not much used anymore anyway, feel free to skip them if this situation is a potential issue for you. + +### VST2 special note + +By default DPF uses the free reverse-engineered [vestige header](distrho/src/vestige/vestige.h) file. +This file is GPLv2+ licensed, so that applies to plugins built with it as well. +You can alternatively build DPF-based VST2 plugins using the official Steinberg VST2 SDK, +simply set the `VESTIGE_HEADER` compiler macro to `0` during build. +You will need to provide your own VST2 SDK files then, as DPF does not ship with them. +Note there are legal issues surrounding releasing new VST2 plugins using the official SDK, as that is no longer supported by Steinberg. + +### VST3 special note + +Contrary to most plugins, DPF does not use the official VST3 SDK. +Instead, the API definitions are provided by the [travesty](distrho/src/travesty/) sub-project, licensed in the same way as DPF. +This allows us to freely build plugins without being encumbered by restrictive licensing deals. +It makes the internal implementation much harder for DPF, but this is not an issue for external developers. diff --git a/dpf/Makefile.plugins.mk b/dpf/Makefile.plugins.mk index 8114387..0ba0ce4 100644 --- a/dpf/Makefile.plugins.mk +++ b/dpf/Makefile.plugins.mk @@ -33,7 +33,6 @@ endif BUILD_C_FLAGS += -I. BUILD_CXX_FLAGS += -I. -I$(DPF_PATH)/distrho -I$(DPF_PATH)/dgl -BUILD_CXX_FLAGS += -Wno-pmf-conversions ifeq ($(HAVE_ALSA),true) BASE_FLAGS += -DHAVE_ALSA @@ -128,7 +127,7 @@ SYMBOLS_LV2UI = $(DPF_PATH)/utils/symbols/lv2-ui.def SYMBOLS_LV2 = $(DPF_PATH)/utils/symbols/lv2.def SYMBOLS_VST2 = $(DPF_PATH)/utils/symbols/vst2.def SYMBOLS_VST3 = $(DPF_PATH)/utils/symbols/vst3.def -else +else ifneq ($(DEBUG),true) SYMBOLS_LADSPA = -Wl,--version-script=$(DPF_PATH)/utils/symbols/ladspa.version SYMBOLS_DSSI = -Wl,--version-script=$(DPF_PATH)/utils/symbols/dssi.version SYMBOLS_LV2DSP = -Wl,--version-script=$(DPF_PATH)/utils/symbols/lv2-dsp.version @@ -224,6 +223,13 @@ endif # TODO split dsp and ui object build flags BASE_FLAGS += $(DGL_FLAGS) +# --------------------------------------------------------------------------------------------------------------------- +# Runtime test build + +ifeq ($(DPF_RUNTIME_TESTING),true) +BUILD_CXX_FLAGS += -DDPF_RUNTIME_TESTING -Wno-pmf-conversions +endif + # --------------------------------------------------------------------------------------------------------------------- # all needs to be first @@ -386,7 +392,11 @@ endif vst3: $(vst3) +ifeq ($(HAVE_DGL),true) +$(vst3): $(OBJS_DSP) $(OBJS_UI) $(BUILD_DIR)/DistrhoPluginMain_VST3.cpp.o $(BUILD_DIR)/DistrhoUIMain_VST3.cpp.o $(DGL_LIB) +else $(vst3): $(OBJS_DSP) $(BUILD_DIR)/DistrhoPluginMain_VST3.cpp.o +endif -@mkdir -p $(shell dirname $@) @echo "Creating VST3 plugin for $(NAME)" $(SILENT)$(CXX) $^ $(BUILD_CXX_FLAGS) $(LINK_FLAGS) $(DGL_LIBS) $(SHARED) $(SYMBOLS_VST3) -o $@ diff --git a/dpf/README.md b/dpf/README.md index 0392faa..18dbc65 100644 --- a/dpf/README.md +++ b/dpf/README.md @@ -19,6 +19,12 @@ Getting time information from the host is possible.
It uses the same format as the JACK Transport API, making porting some code easier.
+## Licensing + +DPF is released under ISC, which basically means you can do whatever you want as long as you credit the original authors. +Some plugin formats may have additional restrictions, see [LICENSING.md](LICENSING.md) for details. + + ## Help and documentation Bug reports happen on the [DPF github project](https://github.com/DISTRHO/DPF/issues). diff --git a/dpf/dgl/Application.hpp b/dpf/dgl/Application.hpp index 0979987..87937cc 100644 --- a/dpf/dgl/Application.hpp +++ b/dpf/dgl/Application.hpp @@ -107,6 +107,7 @@ public: private: struct PrivateData; PrivateData* const pData; + friend class PluginApplication; friend class Window; DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(Application) diff --git a/dpf/dgl/Base.hpp b/dpf/dgl/Base.hpp index d726c6f..0288cb0 100644 --- a/dpf/dgl/Base.hpp +++ b/dpf/dgl/Base.hpp @@ -49,16 +49,16 @@ enum Modifier { /** Keyboard key codepoints. - All keys are identified by a Unicode code point in PuglEventKey::key. This - enumeration defines constants for special keys that do not have a standard - code point, and some convenience constants for control characters. Note - that all keys are handled in the same way, this enumeration is just for + All keys are identified by a Unicode code point in Widget::KeyboardEvent::key. + This enumeration defines constants for special keys that do not have a standard + code point, and some convenience constants for control characters. + Note that all keys are handled in the same way, this enumeration is just for convenience when writing hard-coded key bindings. Keys that do not have a standard code point use values in the Private Use - Area in the Basic Multilingual Plane (`U+E000` to `U+F8FF`). Applications - must take care to not interpret these values beyond key detection, the - mapping used here is arbitrary and specific to DPF. + Area in the Basic Multilingual Plane (`U+E000` to `U+F8FF`). + Applications must take care to not interpret these values beyond key detection, + the mapping used here is arbitrary and specific to DPF. */ enum Key { // Convenience symbols for ASCII control characters @@ -116,7 +116,7 @@ enum Key { /** Common flags for all events. */ -enum Flag { +enum EventFlag { kFlagSendEvent = 1, ///< Event is synthetic kFlagIsHint = 2 ///< Event is a hint (not direct user input) }; diff --git a/dpf/dgl/NanoVG.hpp b/dpf/dgl/NanoVG.hpp index cf693d3..af33ab8 100644 --- a/dpf/dgl/NanoVG.hpp +++ b/dpf/dgl/NanoVG.hpp @@ -903,7 +903,7 @@ public: Constructor for a NanoSubWidget. @see CreateFlags */ - explicit NanoBaseWidget(Widget* const parentGroupWidget, int flags = CREATE_ANTIALIAS); + explicit NanoBaseWidget(Widget* parentGroupWidget, int flags = CREATE_ANTIALIAS); /** Constructor for a NanoTopLevelWidget. @@ -912,16 +912,16 @@ public: explicit NanoBaseWidget(Window& windowToMapTo, int flags = CREATE_ANTIALIAS); /** - Constructor for a NanoStandaloneWindow without parent window. + Constructor for a NanoStandaloneWindow without transient parent window. @see CreateFlags */ explicit NanoBaseWidget(Application& app, int flags = CREATE_ANTIALIAS); /** - Constructor for a NanoStandaloneWindow with parent window. + Constructor for a NanoStandaloneWindow with transient parent window. @see CreateFlags */ - explicit NanoBaseWidget(Application& app, Window& parentWindow, int flags = CREATE_ANTIALIAS); + explicit NanoBaseWidget(Application& app, Window& transientParentWindow, int flags = CREATE_ANTIALIAS); /** Destructor. diff --git a/dpf/dgl/TopLevelWidget.hpp b/dpf/dgl/TopLevelWidget.hpp index 6c01429..5441dbc 100644 --- a/dpf/dgl/TopLevelWidget.hpp +++ b/dpf/dgl/TopLevelWidget.hpp @@ -117,7 +117,6 @@ public: protected: bool onKeyboard(const KeyboardEvent&) override; - bool onSpecial(const SpecialEvent&) override; bool onCharacterInput(const CharacterInputEvent&) override; bool onMouse(const MouseEvent&) override; bool onMotion(const MotionEvent&) override; diff --git a/dpf/dgl/Widget.hpp b/dpf/dgl/Widget.hpp index 904e005..463f7c9 100644 --- a/dpf/dgl/Widget.hpp +++ b/dpf/dgl/Widget.hpp @@ -58,7 +58,7 @@ public: These are the fields present on all Widget events. @a mod Currently active keyboard modifiers, @see Modifier. - @a mod Event flags, @see Flag. + @a mod Event flags, @see EventFlag. @a time Event timestamp (if any). */ struct BaseEvent { @@ -107,14 +107,10 @@ public: /** Special keyboard event. - This event allows the use of keys that do not have unicode points. - Note that some are non-printable keys. - - @a press True if the key was pressed, false if released. - @a key The key pressed. - @see onSpecial + DEPRECATED This used to be part of DPF due to pugl, but now deprecated and simply non-functional. + All events go through KeyboardEvent or CharacterInputEvent, use those instead. */ - struct SpecialEvent : BaseEvent { + struct DISTRHO_DEPRECATED_BY("KeyboardEvent") SpecialEvent : BaseEvent { bool press; Key key; @@ -398,12 +394,6 @@ protected: */ virtual bool onKeyboard(const KeyboardEvent&); - /** - A function called when a special key is pressed or released. - @return True to stop event propagation, false otherwise. - */ - virtual bool onSpecial(const SpecialEvent&); - /** A function called when an UTF-8 character is received. @return True to stop event propagation, false otherwise. @@ -433,6 +423,24 @@ protected: */ virtual void onResize(const ResizeEvent&); + /** + A function called when a special key is pressed or released. + DEPRECATED use onKeyboard or onCharacterInput + */ +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wdeprecated-declarations" +#elif defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__) >= 460 +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + virtual bool onSpecial(const SpecialEvent&) { return false; } +#if defined(__clang__) +# pragma clang diagnostic pop +#elif defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__) >= 460 +# pragma GCC diagnostic pop +#endif + private: struct PrivateData; PrivateData* const pData; diff --git a/dpf/dgl/Window.hpp b/dpf/dgl/Window.hpp index 9effa69..27e72dc 100644 --- a/dpf/dgl/Window.hpp +++ b/dpf/dgl/Window.hpp @@ -157,10 +157,10 @@ public: explicit Window(Application& app); /** - Constructor for a modal window, by having another window as its parent. + Constructor for a modal window, by having another window as its transient parent. The Application instance must be the same between the 2 windows. */ - explicit Window(Application& app, Window& parent); + explicit Window(Application& app, Window& transientParentWindow); /** Constructor for an embed Window without known size, @@ -369,7 +369,7 @@ public: This function does not block the event loop. */ - bool openFileBrowser(const FileBrowserOptions& options); + bool openFileBrowser(const FileBrowserOptions& options = FileBrowserOptions()); #endif /** diff --git a/dpf/dgl/src/.kdev_include_paths b/dpf/dgl/src/.kdev_include_paths new file mode 100644 index 0000000..2103f66 --- /dev/null +++ b/dpf/dgl/src/.kdev_include_paths @@ -0,0 +1 @@ +pugl-upstream/include/ diff --git a/dpf/dgl/src/ApplicationPrivateData.cpp b/dpf/dgl/src/ApplicationPrivateData.cpp index 1de1fc7..f573c2f 100644 --- a/dpf/dgl/src/ApplicationPrivateData.cpp +++ b/dpf/dgl/src/ApplicationPrivateData.cpp @@ -118,6 +118,11 @@ void Application::PrivateData::idle(const uint timeoutInMs) puglUpdate(world, timeoutInSeconds); } + triggerIdleCallbacks(); +} + +void Application::PrivateData::triggerIdleCallbacks() +{ for (std::list::iterator it = idleCallbacks.begin(), ite = idleCallbacks.end(); it != ite; ++it) { IdleCallback* const idleCallback(*it); diff --git a/dpf/dgl/src/ApplicationPrivateData.hpp b/dpf/dgl/src/ApplicationPrivateData.hpp index e35328d..0d2993c 100644 --- a/dpf/dgl/src/ApplicationPrivateData.hpp +++ b/dpf/dgl/src/ApplicationPrivateData.hpp @@ -84,6 +84,9 @@ struct Application::PrivateData { /** Run Pugl world update for @a timeoutInMs, and then each idle callback in order of registration. */ void idle(uint timeoutInMs); + /** Run each idle callback without updating pugl world. */ + void triggerIdleCallbacks(); + /** Set flag indicating application is quitting, and close all windows in reverse order of registration. For standalone mode only. */ void quit(); diff --git a/dpf/dgl/src/TopLevelWidget.cpp b/dpf/dgl/src/TopLevelWidget.cpp index e044b03..d714324 100644 --- a/dpf/dgl/src/TopLevelWidget.cpp +++ b/dpf/dgl/src/TopLevelWidget.cpp @@ -100,11 +100,6 @@ bool TopLevelWidget::onKeyboard(const KeyboardEvent&) return false; } -bool TopLevelWidget::onSpecial(const SpecialEvent&) -{ - return false; -} - bool TopLevelWidget::onCharacterInput(const CharacterInputEvent&) { return false; diff --git a/dpf/dgl/src/TopLevelWidgetPrivateData.cpp b/dpf/dgl/src/TopLevelWidgetPrivateData.cpp index 618cf35..29514bb 100644 --- a/dpf/dgl/src/TopLevelWidgetPrivateData.cpp +++ b/dpf/dgl/src/TopLevelWidgetPrivateData.cpp @@ -50,20 +50,6 @@ bool TopLevelWidget::PrivateData::keyboardEvent(const KeyboardEvent& ev) return selfw->pData->giveKeyboardEventForSubWidgets(ev); } -bool TopLevelWidget::PrivateData::specialEvent(const SpecialEvent& ev) -{ - // ignore event if we are not visible - if (! selfw->pData->visible) - return false; - - // give top-level widget chance to catch this event first - if (self->onSpecial(ev)) - return true; - - // propagate event to all subwidgets recursively - return selfw->pData->giveSpecialEventForSubWidgets(ev); -} - bool TopLevelWidget::PrivateData::characterInputEvent(const CharacterInputEvent& ev) { // ignore event if we are not visible diff --git a/dpf/dgl/src/TopLevelWidgetPrivateData.hpp b/dpf/dgl/src/TopLevelWidgetPrivateData.hpp index 6c88286..7105b2f 100644 --- a/dpf/dgl/src/TopLevelWidgetPrivateData.hpp +++ b/dpf/dgl/src/TopLevelWidgetPrivateData.hpp @@ -34,7 +34,6 @@ struct TopLevelWidget::PrivateData { ~PrivateData(); void display(); bool keyboardEvent(const KeyboardEvent& ev); - bool specialEvent(const SpecialEvent& ev); bool characterInputEvent(const CharacterInputEvent& ev); bool mouseEvent(const MouseEvent& ev); bool motionEvent(const MotionEvent& ev); diff --git a/dpf/dgl/src/Widget.cpp b/dpf/dgl/src/Widget.cpp index e6e41d5..8aaa90d 100644 --- a/dpf/dgl/src/Widget.cpp +++ b/dpf/dgl/src/Widget.cpp @@ -167,11 +167,6 @@ bool Widget::onKeyboard(const KeyboardEvent& ev) return pData->giveKeyboardEventForSubWidgets(ev); } -bool Widget::onSpecial(const SpecialEvent& ev) -{ - return pData->giveSpecialEventForSubWidgets(ev); -} - bool Widget::onCharacterInput(const CharacterInputEvent& ev) { return pData->giveCharacterInputEventForSubWidgets(ev); diff --git a/dpf/dgl/src/WidgetPrivateData.cpp b/dpf/dgl/src/WidgetPrivateData.cpp index 304ad6e..3cbe644 100644 --- a/dpf/dgl/src/WidgetPrivateData.cpp +++ b/dpf/dgl/src/WidgetPrivateData.cpp @@ -87,24 +87,6 @@ bool Widget::PrivateData::giveKeyboardEventForSubWidgets(const KeyboardEvent& ev return false; } -bool Widget::PrivateData::giveSpecialEventForSubWidgets(const SpecialEvent& ev) -{ - if (! visible) - return false; - if (subWidgets.size() == 0) - return false; - - FOR_EACH_SUBWIDGET_INV(rit) - { - SubWidget* const widget(*rit); - - if (widget->isVisible() && widget->onSpecial(ev)) - return true; - } - - return false; -} - bool Widget::PrivateData::giveCharacterInputEventForSubWidgets(const CharacterInputEvent& ev) { if (! visible) diff --git a/dpf/dgl/src/WidgetPrivateData.hpp b/dpf/dgl/src/WidgetPrivateData.hpp index ab38852..15b8409 100644 --- a/dpf/dgl/src/WidgetPrivateData.hpp +++ b/dpf/dgl/src/WidgetPrivateData.hpp @@ -44,7 +44,6 @@ struct Widget::PrivateData { void displaySubWidgets(uint width, uint height, double autoScaleFactor); bool giveKeyboardEventForSubWidgets(const KeyboardEvent& ev); - bool giveSpecialEventForSubWidgets(const SpecialEvent& ev); bool giveCharacterInputEventForSubWidgets(const CharacterInputEvent& ev); bool giveMouseEventForSubWidgets(MouseEvent& ev); bool giveMotionEventForSubWidgets(MotionEvent& ev); diff --git a/dpf/dgl/src/Window.cpp b/dpf/dgl/src/Window.cpp index c9d12df..6f2a7da 100644 --- a/dpf/dgl/src/Window.cpp +++ b/dpf/dgl/src/Window.cpp @@ -66,8 +66,8 @@ Window::Window(Application& app) pData->initPost(); } -Window::Window(Application& app, Window& parent) - : pData(new PrivateData(app, this, parent.pData)) +Window::Window(Application& app, Window& transientParentWindow) + : pData(new PrivateData(app, this, transientParentWindow.pData)) { pData->initPost(); } @@ -407,22 +407,6 @@ void Window::setTransientWinId(const uintptr_t winId) { puglSetTransientFor(pData->view, winId); } - -// ----------------------------------------------------------------------- - -bool Window::handlePluginKeyboard(const bool press, const uint key) -{ - // TODO - return false; - // return pData->handlePluginKeyboard(press, key); -} - -bool Window::handlePluginSpecial(const bool press, const Key key) -{ - // TODO - return false; - // return pData->handlePluginSpecial(press, key); -} #endif // ----------------------------------------------------------------------- diff --git a/dpf/dgl/src/WindowPrivateData.cpp b/dpf/dgl/src/WindowPrivateData.cpp index 6a1b5ed..15a1d28 100644 --- a/dpf/dgl/src/WindowPrivateData.cpp +++ b/dpf/dgl/src/WindowPrivateData.cpp @@ -33,7 +33,11 @@ #define DGL_DEBUG_EVENTS #if defined(DEBUG) && defined(DGL_DEBUG_EVENTS) -# include +# ifdef DISTRHO_PROPER_CPP11_SUPPORT +# include +# else +# include +# endif #endif START_NAMESPACE_DGL @@ -820,24 +824,6 @@ void Window::PrivateData::onPuglKey(const Widget::KeyboardEvent& ev) #endif } -void Window::PrivateData::onPuglSpecial(const Widget::SpecialEvent& ev) -{ - DGL_DBGp("onPuglSpecial : %i %u\n", ev.press, ev.key); - - if (modal.child != nullptr) - return modal.child->focus(); - -#ifndef DPF_TEST_WINDOW_CPP - FOR_EACH_TOP_LEVEL_WIDGET_INV(rit) - { - TopLevelWidget* const widget(*rit); - - if (widget->isVisible() && widget->pData->specialEvent(ev)) - break; - } -#endif -} - void Window::PrivateData::onPuglText(const Widget::CharacterInputEvent& ev) { DGL_DBGp("onPuglText : %u %u %s\n", ev.keycode, ev.character, ev.string); @@ -982,7 +968,6 @@ PuglStatus Window::PrivateData::puglEventCallback(PuglView* const view, const Pu case PUGL_KEY_RELEASE: { // unused x, y, xRoot, yRoot (double) - // TODO special keys? Widget::KeyboardEvent ev; ev.mod = event->key.state; ev.flags = event->key.flags; @@ -990,8 +975,14 @@ PuglStatus Window::PrivateData::puglEventCallback(PuglView* const view, const Pu ev.press = event->type == PUGL_KEY_PRESS; ev.key = event->key.key; ev.keycode = event->key.keycode; - if ((ev.mod & kModifierShift) != 0 && ev.key >= 'a' && ev.key <= 'z') - ev.key -= 'a' - 'A'; // a-z -> A-Z + + // keyboard events must always be lowercase + if (ev.key >= 'A' && ev.key <= 'Z') + { + ev.key += 'a' - 'A'; // A-Z -> a-z + ev.mod |= kModifierShift; + } + pData->onPuglKey(ev); break; } diff --git a/dpf/dgl/src/WindowPrivateData.hpp b/dpf/dgl/src/WindowPrivateData.hpp index 5f9f898..d8e0f8c 100644 --- a/dpf/dgl/src/WindowPrivateData.hpp +++ b/dpf/dgl/src/WindowPrivateData.hpp @@ -178,7 +178,6 @@ struct Window::PrivateData : IdleCallback { void onPuglClose(); void onPuglFocus(bool focus, CrossingMode mode); void onPuglKey(const Widget::KeyboardEvent& ev); - void onPuglSpecial(const Widget::SpecialEvent& ev); void onPuglText(const Widget::CharacterInputEvent& ev); void onPuglMouse(const Widget::MouseEvent& ev); void onPuglMotion(const Widget::MotionEvent& ev); diff --git a/dpf/distrho/DistrhoInfo.hpp b/dpf/distrho/DistrhoInfo.hpp index c70752e..6805f4a 100644 --- a/dpf/distrho/DistrhoInfo.hpp +++ b/dpf/distrho/DistrhoInfo.hpp @@ -106,13 +106,6 @@ START_NAMESPACE_DISTRHO return d_cconst('M', 'u', 't', 'e'); } - /* ---------------------------------------------------------------------------------------- - * This example has no parameters, so skip parameter stuff */ - - void initParameter(uint32_t, Parameter&) override {} - float getParameterValue(uint32_t) const override { return 0.0f; } - void setParameterValue(uint32_t, float) override {} - /* ---------------------------------------------------------------------------------------- * Audio/MIDI Processing */ @@ -507,6 +500,20 @@ START_NAMESPACE_DISTRHO */ #define DISTRHO_PLUGIN_IS_SYNTH 1 +/** + Request the minimum buffer size for the input and output event ports.@n + Currently only used in LV2, with a default value of 2048 if unset. + */ +#define DISTRHO_PLUGIN_MINIMUM_BUFFER_SIZE 2048 + +/** + Whether the plugin has an LV2 modgui. + + This will simply add a "rdfs:seeAlso " on the LV2 manifest.@n + It is up to you to create this file. + */ +#define DISTRHO_PLUGIN_USES_MODGUI 0 + /** Enable direct access between the %UI and plugin code. @see UI::getPluginInstancePointer() @@ -613,6 +620,158 @@ START_NAMESPACE_DISTRHO */ #define DISTRHO_UI_URI DISTRHO_PLUGIN_URI "#UI" +/** + Custom LV2 category for the plugin.@n + This can be one of the following values: + + - lv2:Plugin + - lv2:AllpassPlugin + - lv2:AmplifierPlugin + - lv2:AnalyserPlugin + - lv2:BandpassPlugin + - lv2:ChorusPlugin + - lv2:CombPlugin + - lv2:CompressorPlugin + - lv2:ConstantPlugin + - lv2:ConverterPlugin + - lv2:DelayPlugin + - lv2:DistortionPlugin + - lv2:DynamicsPlugin + - lv2:EQPlugin + - lv2:EnvelopePlugin + - lv2:ExpanderPlugin + - lv2:FilterPlugin + - lv2:FlangerPlugin + - lv2:FunctionPlugin + - lv2:GatePlugin + - lv2:GeneratorPlugin + - lv2:HighpassPlugin + - lv2:InstrumentPlugin + - lv2:LimiterPlugin + - lv2:LowpassPlugin + - lv2:MIDIPlugin + - lv2:MixerPlugin + - lv2:ModulatorPlugin + - lv2:MultiEQPlugin + - lv2:OscillatorPlugin + - lv2:ParaEQPlugin + - lv2:PhaserPlugin + - lv2:PitchPlugin + - lv2:ReverbPlugin + - lv2:SimulatorPlugin + - lv2:SpatialPlugin + - lv2:SpectralPlugin + - lv2:UtilityPlugin + - lv2:WaveshaperPlugin + + See http://lv2plug.in/ns/lv2core for more information. + */ +#define DISTRHO_PLUGIN_LV2_CATEGORY "lv2:Plugin" + +/** + Custom VST3 categories for the plugin.@n + This is a list of categories, separated by a @c |. + + Each effect category can be one of the following values: + + - Fx + - Fx|Ambisonics + - Fx|Analyzer + - Fx|Delay + - Fx|Distortion + - Fx|Dynamics + - Fx|EQ + - Fx|Filter + - Fx|Instrument + - Fx|Instrument|External + - Fx|Spatial + - Fx|Generator + - Fx|Mastering + - Fx|Modulation + - Fx|Network + - Fx|Pitch Shift + - Fx|Restoration + - Fx|Reverb + - Fx|Surround + - Fx|Tools + + Each instrument category can be one of the following values: + + - Instrument + - Instrument|Drum + - Instrument|External + - Instrument|Piano + - Instrument|Sampler + - Instrument|Synth + - Instrument|Synth|Sampler + + @note DPF will automatically set Mono and Stereo categories when appropriate. + */ +#define DISTRHO_PLUGIN_VST3_CATEGORIES "Fx" + +/** @} */ + +/* ------------------------------------------------------------------------------------------------------------ + * Plugin Macros */ + +/** + @defgroup ExtraPluginMacros Extra Plugin Macros + + C Macros to customize DPF behaviour. + + These are macros that do not set plugin features or information, but instead change DPF internals. + They are all optional. + + Unless stated otherwise, values are assumed to be a simple/empty define. + @{ + */ + +/** + Whether to enable runtime plugin tests.@n + This will check, during initialization of the plugin, if parameters, programs and states are setup properly.@n + Useful to enable as part of CI, can safely be skipped.@n + Under DPF makefiles this can be enabled by using `make DPF_RUNTIME_TESTING=true`. + + @note Some checks are only available with the GCC compiler, + for detecting if a virtual function has been reimplemented. + */ +#define DPF_RUNTIME_TESTING + +/** + Whether to show parameter outputs in the VST2 plugins.@n + This is disabled (unset) by default, as the VST2 format has no notion of read-only parameters. + */ +#define DPF_VST_SHOW_PARAMETER_OUTPUTS + +/** + Disable all file browser related code.@n + Must be set as compiler macro when building DGL. (e.g. `CXXFLAGS="-DDGL_FILE_BROWSER_DISABLED"`) + */ +#define DGL_FILE_BROWSER_DISABLED + +/** + Disable resource files, like internally used fonts.@n + Must be set as compiler macro when building DGL. (e.g. `CXXFLAGS="-DDGL_NO_SHARED_RESOURCES"`) + */ +#define DGL_NO_SHARED_RESOURCES + +/** + Whether to use OpenGL3 instead of the default OpenGL2 compatility profile. + Under DPF makefiles this can be enabled by using `make USE_OPENGL3=true` on the dgl build step. + + @note This is experimental and incomplete, contributions are welcome and appreciated. + */ +#define DGL_USE_OPENGL3 + +/** + Whether to use the GPLv2+ vestige header instead of the official Steinberg VST2 SDK.@n + This is a boolean, and enabled (set to 1) by default.@n + Set this to 0 in order to create non-GPL binaries. + (but then at your own discretion in regards to Steinberg licensing)@n + When set to 0, DPF will import the VST2 definitions from `"vst/aeffectx.h"` (not shipped with DPF). + */ +#define VESTIGE_HEADER 1 + /** @} */ // ----------------------------------------------------------------------------------------------------------- diff --git a/dpf/distrho/DistrhoPlugin.hpp b/dpf/distrho/DistrhoPlugin.hpp index 048fb93..e0b7ccb 100644 --- a/dpf/distrho/DistrhoPlugin.hpp +++ b/dpf/distrho/DistrhoPlugin.hpp @@ -40,7 +40,9 @@ START_NAMESPACE_DISTRHO static const uint32_t kAudioPortIsCV = 0x1; /** - Audio port should be used as sidechan (LV2 only). + Audio port should be used as sidechan (LV2 and VST3 only). + This hint should not be used with CV style ports. + @note non-sidechain audio ports must exist in the plugin if this flag is set. */ static const uint32_t kAudioPortIsSidechain = 0x2; @@ -632,6 +634,9 @@ struct MidiEvent { /** MIDI data.@n If size > kDataSize, dataExt is used (otherwise null). + + When dataExt is used, the event holder is responsible for + keeping the pointer valid during the entirety of the run function. */ uint8_t data[kDataSize]; const uint8_t* dataExt; diff --git a/dpf/distrho/DistrhoUIMain.cpp b/dpf/distrho/DistrhoUIMain.cpp index 0be2e2a..ef3aba2 100644 --- a/dpf/distrho/DistrhoUIMain.cpp +++ b/dpf/distrho/DistrhoUIMain.cpp @@ -27,7 +27,7 @@ #elif defined(DISTRHO_PLUGIN_TARGET_VST2) // nothing #elif defined(DISTRHO_PLUGIN_TARGET_VST3) -// nothing +# include "src/DistrhoUIVST3.cpp" #else # error unsupported format #endif diff --git a/dpf/distrho/extra/LeakDetector.hpp b/dpf/distrho/extra/LeakDetector.hpp index 765e467..11ede47 100644 --- a/dpf/distrho/extra/LeakDetector.hpp +++ b/dpf/distrho/extra/LeakDetector.hpp @@ -23,7 +23,25 @@ START_NAMESPACE_DISTRHO // ----------------------------------------------------------------------- // The following code was based from juce-core LeakDetector class -// Copyright (C) 2013 Raw Material Software Ltd. + +/** + Copyright (C) 2013 Raw Material Software Ltd. + + Permission is granted to use this software under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license/ + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, + OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF + USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + OF THIS SOFTWARE. +*/ /** A good old-fashioned C macro concatenation helper. This combines two items (which may themselves be macros) into a single string, diff --git a/dpf/distrho/extra/ScopedPointer.hpp b/dpf/distrho/extra/ScopedPointer.hpp index c756a1a..6349199 100644 --- a/dpf/distrho/extra/ScopedPointer.hpp +++ b/dpf/distrho/extra/ScopedPointer.hpp @@ -25,7 +25,25 @@ START_NAMESPACE_DISTRHO // ----------------------------------------------------------------------- // The following code was based from juce-core ScopedPointer class -// Copyright (C) 2013 Raw Material Software Ltd. + +/** + Copyright (C) 2013 Raw Material Software Ltd. + + Permission is granted to use this software under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license/ + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, + OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF + USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + OF THIS SOFTWARE. +*/ //============================================================================== /** diff --git a/dpf/distrho/extra/String.hpp b/dpf/distrho/extra/String.hpp index 04a959b..88a7501 100644 --- a/dpf/distrho/extra/String.hpp +++ b/dpf/distrho/extra/String.hpp @@ -516,7 +516,7 @@ public: */ String& replace(const char before, const char after) noexcept { - DISTRHO_SAFE_ASSERT_RETURN(before != '\0' && after != '\0', *this); + DISTRHO_SAFE_ASSERT_RETURN(before != '\0' /* && after != '\0' */, *this); for (std::size_t i=0; i < fBufferLen; ++i) { diff --git a/dpf/distrho/src/DistrhoDefines.h b/dpf/distrho/src/DistrhoDefines.h index afff900..281031c 100644 --- a/dpf/distrho/src/DistrhoDefines.h +++ b/dpf/distrho/src/DistrhoDefines.h @@ -109,7 +109,7 @@ #define DISTRHO_CUSTOM_SAFE_ASSERT_ONCE_CONTINUE(msg, cond) if (! (cond)) { static bool _p; if (!_p) { _p = true; d_custom_safe_assert(msg, #cond, __FILE__, __LINE__); } continue; } #define DISTRHO_CUSTOM_SAFE_ASSERT_ONCE_RETURN(msg, cond, ret) if (! (cond)) { static bool _p; if (!_p) { _p = true; d_custom_safe_assert(msg, #cond, __FILE__, __LINE__); } return ret; } -#define DISTRHO_SAFE_ASSERT_INT_BREAK(cond, value) if (! (cond)) { d_safe_assert_int(#cond, __FILE__, __LINE__, static_cast(value); break; } +#define DISTRHO_SAFE_ASSERT_INT_BREAK(cond, value) if (! (cond)) { d_safe_assert_int(#cond, __FILE__, __LINE__, static_cast(value)); break; } #define DISTRHO_SAFE_ASSERT_INT_CONTINUE(cond, value) if (! (cond)) { d_safe_assert_int(#cond, __FILE__, __LINE__, static_cast(value)); continue; } #define DISTRHO_SAFE_ASSERT_INT_RETURN(cond, value, ret) if (! (cond)) { d_safe_assert_int(#cond, __FILE__, __LINE__, static_cast(value)); return ret; } @@ -117,7 +117,7 @@ #define DISTRHO_SAFE_ASSERT_INT2_CONTINUE(cond, v1, v2) if (! (cond)) { d_safe_assert_int2(#cond, __FILE__, __LINE__, static_cast(v1), static_cast(v2)); continue; } #define DISTRHO_SAFE_ASSERT_INT2_RETURN(cond, v1, v2, ret) if (! (cond)) { d_safe_assert_int2(#cond, __FILE__, __LINE__, static_cast(v1), static_cast(v2)); return ret; } -#define DISTRHO_SAFE_ASSERT_UINT_BREAK(cond, value) if (! (cond)) { d_safe_assert_uint(#cond, __FILE__, __LINE__, static_cast(value); break; } +#define DISTRHO_SAFE_ASSERT_UINT_BREAK(cond, value) if (! (cond)) { d_safe_assert_uint(#cond, __FILE__, __LINE__, static_cast(value)); break; } #define DISTRHO_SAFE_ASSERT_UINT_CONTINUE(cond, value) if (! (cond)) { d_safe_assert_uint(#cond, __FILE__, __LINE__, static_cast(value)); continue; } #define DISTRHO_SAFE_ASSERT_UINT_RETURN(cond, value, ret) if (! (cond)) { d_safe_assert_uint(#cond, __FILE__, __LINE__, static_cast(value)); return ret; } @@ -199,6 +199,9 @@ private: \ # pragma warning(disable:4244) /* possible loss of data */ #endif +/* Useful macros */ +#define ARRAY_SIZE(ARRAY) sizeof(ARRAY)/sizeof(ARRAY[0]) + /* Useful typedefs */ typedef unsigned char uchar; typedef unsigned short int ushort; diff --git a/dpf/distrho/src/DistrhoPlugin.cpp b/dpf/distrho/src/DistrhoPlugin.cpp index c517abf..693c491 100644 --- a/dpf/distrho/src/DistrhoPlugin.cpp +++ b/dpf/distrho/src/DistrhoPlugin.cpp @@ -29,7 +29,7 @@ bool d_lastCanRequestParameterValueChanges = false; * Static fallback data, see DistrhoPluginInternal.hpp */ const String PluginExporter::sFallbackString; -const AudioPort PluginExporter::sFallbackAudioPort; +/* */ AudioPortWithBusId PluginExporter::sFallbackAudioPort; const ParameterRanges PluginExporter::sFallbackRanges; const ParameterEnumerationValues PluginExporter::sFallbackEnumValues; const PortGroupWithId PluginExporter::sFallbackPortGroup; @@ -41,7 +41,7 @@ Plugin::Plugin(uint32_t parameterCount, uint32_t programCount, uint32_t stateCou : pData(new PrivateData()) { #if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0 - pData->audioPorts = new AudioPort[DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS]; + pData->audioPorts = new AudioPortWithBusId[DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS]; #endif #ifdef DPF_ABORT_ON_ERROR diff --git a/dpf/distrho/src/DistrhoPluginChecks.h b/dpf/distrho/src/DistrhoPluginChecks.h index b885dd7..dfcda68 100644 --- a/dpf/distrho/src/DistrhoPluginChecks.h +++ b/dpf/distrho/src/DistrhoPluginChecks.h @@ -173,7 +173,6 @@ # error DISTRHO_UI_IS_STANDALONE must not be defined #endif - // ----------------------------------------------------------------------- #endif // DISTRHO_PLUGIN_CHECKS_H_INCLUDED diff --git a/dpf/distrho/src/DistrhoPluginInternal.hpp b/dpf/distrho/src/DistrhoPluginInternal.hpp index 78066fd..753d3e0 100644 --- a/dpf/distrho/src/DistrhoPluginInternal.hpp +++ b/dpf/distrho/src/DistrhoPluginInternal.hpp @@ -44,6 +44,14 @@ typedef bool (*requestParameterValueChangeFunc) (void* ptr, uint32_t index, floa // ----------------------------------------------------------------------- // Helpers +struct AudioPortWithBusId : AudioPort { + uint32_t busId; + + AudioPortWithBusId() + : AudioPort(), + busId(0) {} +}; + struct PortGroupWithId : PortGroup { uint32_t groupId; @@ -78,7 +86,7 @@ struct Plugin::PrivateData { bool isProcessing; #if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0 - AudioPort* audioPorts; + AudioPortWithBusId* audioPorts; #endif uint32_t parameterCount; @@ -162,6 +170,15 @@ struct Plugin::PrivateData { # if (DISTRHO_PLUGIN_WANT_MIDI_OUTPUT || DISTRHO_PLUGIN_WANT_STATE) parameterOffset += 1; # endif +#endif + +#ifdef DISTRHO_PLUGIN_TARGET_VST3 +# if DISTRHO_PLUGIN_WANT_MIDI_INPUT + parameterOffset += 130 * 16; // all MIDI CCs plus aftertouch and pitchbend +# endif +# if DISTRHO_PLUGIN_WANT_PROGRAMS + parameterOffset += 1; +# endif #endif } @@ -247,30 +264,26 @@ public: DISTRHO_SAFE_ASSERT_RETURN(fPlugin != nullptr,); DISTRHO_SAFE_ASSERT_RETURN(fData != nullptr,); - /* Verify that virtual functions are overriden if parameters, programs or states are in use. +#if defined(DPF_RUNTIME_TESTING) && defined(__GNUC__) && !defined(__clang__) + /* Run-time testing build. + * Verify that virtual functions are overriden if parameters, programs or states are in use. * This does not work on all compilers, but we use it purely as informational check anyway. */ -#if defined(__GNUC__) && !defined(__clang__) -# ifdef DPF_ABORT_ON_ERROR -# define DPF_ABORT abort(); -# else -# define DPF_ABORT -# endif if (fData->parameterCount != 0) { if ((void*)(fPlugin->*(&Plugin::initParameter)) == (void*)&Plugin::initParameter) { d_stderr2("DPF warning: Plugins with parameters must implement `initParameter`"); - DPF_ABORT + abort(); } if ((void*)(fPlugin->*(&Plugin::getParameterValue)) == (void*)&Plugin::getParameterValue) { d_stderr2("DPF warning: Plugins with parameters must implement `getParameterValue`"); - DPF_ABORT + abort(); } if ((void*)(fPlugin->*(&Plugin::setParameterValue)) == (void*)&Plugin::setParameterValue) { d_stderr2("DPF warning: Plugins with parameters must implement `setParameterValue`"); - DPF_ABORT + abort(); } } @@ -280,12 +293,12 @@ public: if ((void*)(fPlugin->*(&Plugin::initProgramName)) == (void*)&Plugin::initProgramName) { d_stderr2("DPF warning: Plugins with programs must implement `initProgramName`"); - DPF_ABORT + abort(); } if ((void*)(fPlugin->*(&Plugin::loadProgram)) == (void*)&Plugin::loadProgram) { d_stderr2("DPF warning: Plugins with programs must implement `loadProgram`"); - DPF_ABORT + abort(); } } # endif @@ -296,13 +309,13 @@ public: if ((void*)(fPlugin->*(&Plugin::initState)) == (void*)&Plugin::initState) { d_stderr2("DPF warning: Plugins with state must implement `initState`"); - DPF_ABORT + abort(); } if ((void*)(fPlugin->*(&Plugin::setState)) == (void*)&Plugin::setState) { d_stderr2("DPF warning: Plugins with state must implement `setState`"); - DPF_ABORT + abort(); } } # endif @@ -313,17 +326,15 @@ public: if ((void*)(fPlugin->*(&Plugin::getState)) == (void*)&Plugin::getState) { d_stderr2("DPF warning: Plugins with full state must implement `getState`"); - DPF_ABORT + abort(); } } else { d_stderr2("DPF warning: Plugins with full state must have at least 1 state"); - DPF_ABORT + abort(); } # endif - -# undef DPF_ABORT #endif #if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0 @@ -469,7 +480,7 @@ public: #endif #if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0 - const AudioPort& getAudioPort(const bool input, const uint32_t index) const noexcept + AudioPortWithBusId& getAudioPort(const bool input, const uint32_t index) const noexcept { DISTRHO_SAFE_ASSERT_RETURN(fData != nullptr, sFallbackAudioPort); @@ -488,6 +499,11 @@ public: return fData->audioPorts[index + (input ? 0 : DISTRHO_PLUGIN_NUM_INPUTS)]; } + + uint32_t getAudioPortHints(const bool input, const uint32_t index) const noexcept + { + return getAudioPort(input, index).hints; + } #endif uint32_t getParameterCount() const noexcept @@ -528,6 +544,11 @@ public: return (getParameterHints(index) & kParameterIsOutput) != 0x0; } + bool isParameterTrigger(const uint32_t index) const noexcept + { + return (getParameterHints(index) & kParameterIsTrigger) == kParameterIsTrigger; + } + bool isParameterOutputOrTrigger(const uint32_t index) const noexcept { const uint32_t hints = getParameterHints(index); @@ -603,6 +624,14 @@ public: return fData->parameters[index].groupId; } + float getParameterDefault(const uint32_t index) const + { + DISTRHO_SAFE_ASSERT_RETURN(fPlugin != nullptr, 0.0f); + DISTRHO_SAFE_ASSERT_RETURN(fData != nullptr && index < fData->parameterCount, 0.0f); + + return fData->parameters[index].ranges.def; + } + float getParameterValue(const uint32_t index) const { DISTRHO_SAFE_ASSERT_RETURN(fPlugin != nullptr, 0.0f); @@ -888,7 +917,7 @@ private: // Static fallback data, see DistrhoPlugin.cpp static const String sFallbackString; - static const AudioPort sFallbackAudioPort; + static /* */ AudioPortWithBusId sFallbackAudioPort; static const ParameterRanges sFallbackRanges; static const ParameterEnumerationValues sFallbackEnumValues; static const PortGroupWithId sFallbackPortGroup; diff --git a/dpf/distrho/src/DistrhoPluginVST2.cpp b/dpf/distrho/src/DistrhoPluginVST2.cpp index 784d89f..841dab4 100644 --- a/dpf/distrho/src/DistrhoPluginVST2.cpp +++ b/dpf/distrho/src/DistrhoPluginVST2.cpp @@ -261,7 +261,6 @@ public: using namespace DGL_NAMESPACE; - int special = 0; switch (value) { // convert some VST special values to normal keys @@ -304,37 +303,37 @@ public: - kKeyCapsLock - kKeyPrintScreen */ - case 40: special = kKeyF1; break; - case 41: special = kKeyF2; break; - case 42: special = kKeyF3; break; - case 43: special = kKeyF4; break; - case 44: special = kKeyF5; break; - case 45: special = kKeyF6; break; - case 46: special = kKeyF7; break; - case 47: special = kKeyF8; break; - case 48: special = kKeyF9; break; - case 49: special = kKeyF10; break; - case 50: special = kKeyF11; break; - case 51: special = kKeyF12; break; - case 11: special = kKeyLeft; break; - case 12: special = kKeyUp; break; - case 13: special = kKeyRight; break; - case 14: special = kKeyDown; break; - case 15: special = kKeyPageUp; break; - case 16: special = kKeyPageDown; break; - case 10: special = kKeyHome; break; - case 9: special = kKeyEnd; break; - case 21: special = kKeyInsert; break; - case 54: special = kKeyShift; break; - case 55: special = kKeyControl; break; - case 56: special = kKeyAlt; break; - case 58: special = kKeyMenu; break; - case 52: special = kKeyNumLock; break; - case 53: special = kKeyScrollLock; break; - case 5: special = kKeyPause; break; + case 40: index = kKeyF1; break; + case 41: index = kKeyF2; break; + case 42: index = kKeyF3; break; + case 43: index = kKeyF4; break; + case 44: index = kKeyF5; break; + case 45: index = kKeyF6; break; + case 46: index = kKeyF7; break; + case 47: index = kKeyF8; break; + case 48: index = kKeyF9; break; + case 49: index = kKeyF10; break; + case 50: index = kKeyF11; break; + case 51: index = kKeyF12; break; + case 11: index = kKeyLeft; break; + case 12: index = kKeyUp; break; + case 13: index = kKeyRight; break; + case 14: index = kKeyDown; break; + case 15: index = kKeyPageUp; break; + case 16: index = kKeyPageDown; break; + case 10: index = kKeyHome; break; + case 9: index = kKeyEnd; break; + case 21: index = kKeyInsert; break; + case 54: index = kKeyShift; break; + case 55: index = kKeyControl; break; + case 56: index = kKeyAlt; break; + case 58: index = kKeyMenu; break; + case 52: index = kKeyNumLock; break; + case 53: index = kKeyScrollLock; break; + case 5: index = kKeyPause; break; } - switch (special) + switch (index) { case kKeyShift: if (down) @@ -356,15 +355,26 @@ public: break; } - if (special != 0) - { - fUI.handlePluginSpecial(down, static_cast(special), fKeyboardModifiers); - return 1; - } - if (index > 0) { - fUI.handlePluginKeyboard(down, static_cast(index), fKeyboardModifiers); + // keyboard events must always be lowercase + bool needsShiftRevert = false; + if (index >= 'A' && index <= 'Z') + { + index += 'a' - 'A'; // A-Z -> a-z + + if ((fKeyboardModifiers & kModifierShift) == 0x0) + { + needsShiftRevert = true; + fKeyboardModifiers |= kModifierShift; + } + } + + fUI.handlePluginKeyboardVST2(down, static_cast(index), fKeyboardModifiers); + + if (needsShiftRevert) + fKeyboardModifiers &= ~kModifierShift; + return 1; } @@ -1056,7 +1066,7 @@ public: void vst_setParameter(const int32_t index, const float value) { - const uint32_t hints(fPlugin.getParameterHints(index)); + const uint32_t hints = fPlugin.getParameterHints(index); const ParameterRanges& ranges(fPlugin.getParameterRanges(index)); // TODO figure out how to detect kVstParameterUsesIntegerMinMax host support, and skip normalization @@ -1104,7 +1114,7 @@ public: fTimePosition.playing = (vstTimeInfo->flags & kVstTransportPlaying); fTimePosition.bbt.valid = ((vstTimeInfo->flags & kVstTempoValid) != 0 || (vstTimeInfo->flags & kVstTimeSigValid) != 0); - // ticksPerBeat is not possible with VST + // ticksPerBeat is not possible with VST2 fTimePosition.bbt.ticksPerBeat = 1920.0; if (vstTimeInfo->flags & kVstTempoValid) diff --git a/dpf/distrho/src/DistrhoPluginVST3.cpp b/dpf/distrho/src/DistrhoPluginVST3.cpp index a86c7d5..f8b3de2 100644 --- a/dpf/distrho/src/DistrhoPluginVST3.cpp +++ b/dpf/distrho/src/DistrhoPluginVST3.cpp @@ -17,280 +17,3691 @@ #include "DistrhoPluginInternal.hpp" #include "../extra/ScopedPointer.hpp" +#if DISTRHO_PLUGIN_HAS_UI && ! DISTRHO_PLUGIN_HAS_EMBED_UI +# undef DISTRHO_PLUGIN_HAS_UI +# define DISTRHO_PLUGIN_HAS_UI 0 +#endif + +#if DISTRHO_PLUGIN_HAS_UI && ! defined(HAVE_DGL) && ! DISTRHO_PLUGIN_HAS_EXTERNAL_UI +# undef DISTRHO_PLUGIN_HAS_UI +# define DISTRHO_PLUGIN_HAS_UI 0 +#endif + +#if DISTRHO_PLUGIN_HAS_UI +# include "../extra/RingBuffer.hpp" +#endif + #include "travesty/audio_processor.h" #include "travesty/component.h" #include "travesty/edit_controller.h" #include "travesty/factory.h" +#include "travesty/host.h" + +#ifdef DISTRHO_PROPER_CPP11_SUPPORT +# include +#else +// quick and dirty std::atomic replacement for the things we need +namespace std { + struct atomic_int { + volatile int value; + explicit atomic_int(volatile int v) noexcept : value(v) {} + int operator++() volatile noexcept { return __atomic_add_fetch(&value, 1, __ATOMIC_RELAXED); } + int operator--() volatile noexcept { return __atomic_sub_fetch(&value, 1, __ATOMIC_RELAXED); } + operator int() volatile noexcept { return __atomic_load_n(&value, __ATOMIC_RELAXED); } + }; +} +#endif + +#include +#include +#include + +/* TODO items: + * - parameter enumeration as lists + * - hide parameter outputs? + * - hide program parameter? + * - deal with parameter triggers + * - MIDI CC changes (need to store value to give to the host?) + * - MIDI program changes + * - MIDI sysex + * - append MIDI input events in a sorted way + * - bus arrangements + * - optional audio buses, create dummy buffer of max_block_size length for them + * - routing info, do we care? + * - set sidechain bus name from port group + * - implement getParameterValueForString (use names from enumeration if available, fallback to std::atof) + * - set factory email (needs new DPF API, useful for LV2 as well) + * - do something with get_controller_class_id and set_io_mode? + */ + +START_NAMESPACE_DISTRHO + +// -------------------------------------------------------------------------------------------------------------------- + +#if ! DISTRHO_PLUGIN_WANT_MIDI_OUTPUT +static constexpr const writeMidiFunc writeMidiCallback = nullptr; +#endif +#if ! DISTRHO_PLUGIN_WANT_PARAMETER_VALUE_CHANGE_REQUEST +static constexpr const requestParameterValueChangeFunc requestParameterValueChangeCallback = nullptr; +#endif + +typedef std::map StringMap; + +// -------------------------------------------------------------------------------------------------------------------- +// custom v3_tuid compatible type + +typedef uint32_t dpf_tuid[4]; +#ifdef DISTRHO_PROPER_CPP11_SUPPORT +static_assert(sizeof(v3_tuid) == sizeof(dpf_tuid), "uid size mismatch"); +#endif + +// -------------------------------------------------------------------------------------------------------------------- +// custom, constant uids related to DPF + +static constexpr const uint32_t dpf_id_entry = d_cconst('D', 'P', 'F', ' '); +static constexpr const uint32_t dpf_id_clas = d_cconst('c', 'l', 'a', 's'); +static constexpr const uint32_t dpf_id_comp = d_cconst('c', 'o', 'm', 'p'); +static constexpr const uint32_t dpf_id_ctrl = d_cconst('c', 't', 'r', 'l'); +static constexpr const uint32_t dpf_id_proc = d_cconst('p', 'r', 'o', 'c'); +static constexpr const uint32_t dpf_id_view = d_cconst('v', 'i', 'e', 'w'); + +// -------------------------------------------------------------------------------------------------------------------- +// plugin specific uids (values are filled in during plugin init) + +static dpf_tuid dpf_tuid_class = { dpf_id_entry, dpf_id_clas, 0, 0 }; +static dpf_tuid dpf_tuid_component = { dpf_id_entry, dpf_id_comp, 0, 0 }; +static dpf_tuid dpf_tuid_controller = { dpf_id_entry, dpf_id_ctrl, 0, 0 }; +static dpf_tuid dpf_tuid_processor = { dpf_id_entry, dpf_id_proc, 0, 0 }; +static dpf_tuid dpf_tuid_view = { dpf_id_entry, dpf_id_view, 0, 0 }; + +// -------------------------------------------------------------------------------------------------------------------- +// Utility functions + +const char* tuid2str(const v3_tuid iid) +{ + static constexpr const struct { + v3_tuid iid; + const char* name; + } extra_known_iids[] = { + { V3_ID(0x00000000,0x00000000,0x00000000,0x00000000), "(nil)" }, + // edit-controller + { V3_ID(0xF040B4B3,0xA36045EC,0xABCDC045,0xB4D5A2CC), "{v3_component_handler2|NOT}" }, + { V3_ID(0x7F4EFE59,0xF3204967,0xAC27A3AE,0xAFB63038), "{v3_edit_controller2|NOT}" }, + { V3_ID(0x067D02C1,0x5B4E274D,0xA92D90FD,0x6EAF7240), "{v3_component_handler_bus_activation|NOT}" }, + { V3_ID(0xC1271208,0x70594098,0xB9DD34B3,0x6BB0195E), "{v3_edit_controller_host_editing|NOT}" }, + // units + { V3_ID(0x8683B01F,0x7B354F70,0xA2651DEC,0x353AF4FF), "{v3_program_list_data|NOT}" }, + { V3_ID(0x6C389611,0xD391455D,0xB870B833,0x94A0EFDD), "{v3_unit_data|NOT}" }, + { V3_ID(0x4B5147F8,0x4654486B,0x8DAB30BA,0x163A3C56), "{v3_unit_handler|NOT}" }, + { V3_ID(0xF89F8CDF,0x699E4BA5,0x96AAC9A4,0x81452B01), "{v3_unit_handler2|NOT}" }, + { V3_ID(0x3D4BD6B5,0x913A4FD2,0xA886E768,0xA5EB92C1), "{v3_unit_info|NOT}" }, + // misc + { V3_ID(0x0F194781,0x8D984ADA,0xBBA0C1EF,0xC011D8D0), "{v3_info_listener|NOT}" }, + }; + + if (v3_tuid_match(iid, v3_audio_processor_iid)) + return "{v3_audio_processor}"; + if (v3_tuid_match(iid, v3_attribute_list_iid)) + return "{v3_attribute_list_iid}"; + if (v3_tuid_match(iid, v3_bstream_iid)) + return "{v3_bstream}"; + if (v3_tuid_match(iid, v3_component_iid)) + return "{v3_component}"; + if (v3_tuid_match(iid, v3_component_handler_iid)) + return "{v3_component_handler}"; + if (v3_tuid_match(iid, v3_connection_point_iid)) + return "{v3_connection_point_iid}"; + if (v3_tuid_match(iid, v3_edit_controller_iid)) + return "{v3_edit_controller}"; + if (v3_tuid_match(iid, v3_event_handler_iid)) + return "{v3_event_handler_iid}"; + if (v3_tuid_match(iid, v3_event_list_iid)) + return "{v3_event_list}"; + if (v3_tuid_match(iid, v3_funknown_iid)) + return "{v3_funknown}"; + if (v3_tuid_match(iid, v3_host_application_iid)) + return "{v3_host_application_iid}"; + if (v3_tuid_match(iid, v3_message_iid)) + return "{v3_message_iid}"; + if (v3_tuid_match(iid, v3_midi_mapping_iid)) + return "{v3_midi_mapping_iid}"; + if (v3_tuid_match(iid, v3_param_value_queue_iid)) + return "{v3_param_value_queue}"; + if (v3_tuid_match(iid, v3_param_changes_iid)) + return "{v3_param_changes}"; + if (v3_tuid_match(iid, v3_plugin_base_iid)) + return "{v3_plugin_base}"; + if (v3_tuid_match(iid, v3_plugin_factory_iid)) + return "{v3_plugin_factory}"; + if (v3_tuid_match(iid, v3_plugin_factory_2_iid)) + return "{v3_plugin_factory_2}"; + if (v3_tuid_match(iid, v3_plugin_factory_3_iid)) + return "{v3_plugin_factory_3}"; + if (v3_tuid_match(iid, v3_plugin_frame_iid)) + return "{v3_plugin_frame}"; + if (v3_tuid_match(iid, v3_plugin_view_iid)) + return "{v3_plugin_view}"; + if (v3_tuid_match(iid, v3_plugin_view_content_scale_iid)) + return "{v3_plugin_view_content_scale_iid}"; + if (v3_tuid_match(iid, v3_plugin_view_parameter_finder_iid)) + return "{v3_plugin_view_parameter_finder}"; + if (v3_tuid_match(iid, v3_process_context_requirements_iid)) + return "{v3_process_context_requirements}"; + if (v3_tuid_match(iid, v3_run_loop_iid)) + return "{v3_run_loop_iid}"; + if (v3_tuid_match(iid, v3_timer_handler_iid)) + return "{v3_timer_handler_iid}"; + + if (std::memcmp(iid, dpf_tuid_class, sizeof(dpf_tuid)) == 0) + return "{dpf_tuid_class}"; + if (std::memcmp(iid, dpf_tuid_component, sizeof(dpf_tuid)) == 0) + return "{dpf_tuid_component}"; + if (std::memcmp(iid, dpf_tuid_controller, sizeof(dpf_tuid)) == 0) + return "{dpf_tuid_controller}"; + if (std::memcmp(iid, dpf_tuid_processor, sizeof(dpf_tuid)) == 0) + return "{dpf_tuid_processor}"; + if (std::memcmp(iid, dpf_tuid_view, sizeof(dpf_tuid)) == 0) + return "{dpf_tuid_view}"; + + for (size_t i=0; i 0,); + + if (const size_t len = std::min(std::strlen(src), length-1U)) + { + std::memcpy(dst, src, len); + dst[len] = '\0'; + } + else + { + dst[0] = '\0'; + } +} + +void strncpy_utf16(int16_t* const dst, const char* const src, const size_t length) +{ + DISTRHO_SAFE_ASSERT_RETURN(length > 0,); + + if (const size_t len = std::min(std::strlen(src), length-1U)) + { + for (size_t i=0; i= 0x80) + continue; + + dst[i] = src[i]; + } + dst[len] = 0; + } + else + { + dst[0] = 0; + } +} + +// -------------------------------------------------------------------------------------------------------------------- + +template +static void snprintf_t(char* const dst, const T value, const char* const format, const size_t size) +{ + DISTRHO_SAFE_ASSERT_RETURN(size > 0,); + std::snprintf(dst, size-1, format, value); + dst[size-1] = '\0'; +} + +template +static void snprintf_utf16_t(int16_t* const dst, const T value, const char* const format, const size_t size) +{ + DISTRHO_SAFE_ASSERT_RETURN(size > 0,); + + char* const tmpbuf = (char*)std::malloc(size); + DISTRHO_SAFE_ASSERT_RETURN(tmpbuf != nullptr,); + + std::snprintf(tmpbuf, size-1, format, value); + tmpbuf[size-1] = '\0'; + + strncpy_utf16(dst, tmpbuf, size); + std::free(tmpbuf); +} + +static inline +void snprintf_u32(char* const dst, const uint32_t value, const size_t size) +{ + return snprintf_t(dst, value, "%u", size); +} + +static inline +void snprintf_f32_utf16(int16_t* const dst, const float value, const size_t size) +{ + return snprintf_utf16_t(dst, value, "%f", size); +} + +// -------------------------------------------------------------------------------------------------------------------- +// handy way to create a utf16 string on the current function scope, used for message strings + +struct ScopedUTF16String { + int16_t* str; + ScopedUTF16String(const char* const s) noexcept; + ~ScopedUTF16String() noexcept; + operator int16_t*() const noexcept; +}; + +// -------------------------------------------------------------------------------------------------------------------- + +ScopedUTF16String::ScopedUTF16String(const char* const s) noexcept + : str(nullptr) +{ + const size_t len = strlen(s); + str = (int16_t*)malloc(sizeof(int16_t) * len); + DISTRHO_SAFE_ASSERT_RETURN(str != nullptr,); + strncpy_utf16(str, s, len); +} + +ScopedUTF16String::~ScopedUTF16String() noexcept +{ + std::free(str); +} + +ScopedUTF16String::operator int16_t*() const noexcept +{ + return str; +} + +// -------------------------------------------------------------------------------------------------------------------- +// dpf_plugin_view_create (implemented on UI side) + +v3_plugin_view** dpf_plugin_view_create(v3_host_application** host, void* instancePointer, double sampleRate); + +// -------------------------------------------------------------------------------------------------------------------- + +/** + * VST3 DSP class. + * + * All the dynamic things from VST3 get implemented here, free of complex low-level VST3 pointer things. + * The DSP is created during the "initialize" component event, and destroyed during "terminate". + * + * The low-level VST3 stuff comes after. + */ +class PluginVst3 +{ + /* buses: we provide 1 for the main audio (if there is any) plus 1 for each sidechain or cv port. + * Main audio comes first, if available. + * Then sidechain, also if available. + * And finally each CV port individually. + * + * MIDI will have a single bus, nothing special there. + */ + struct BusInfo { + uint8_t audio; // either 0 or 1 + uint8_t sidechain; // either 0 or 1 + uint32_t numMainAudio; + uint32_t numSidechain; + uint32_t numCV; + + BusInfo() + : audio(0), + sidechain(0), + numMainAudio(0), + numSidechain(0), + numCV(0) {} + } inputBuses, outputBuses; + +public: + PluginVst3(v3_host_application** const context) + : fPlugin(this, writeMidiCallback, requestParameterValueChangeCallback), + fComponentHandler(nullptr), +#if DISTRHO_PLUGIN_HAS_UI + fConnection(nullptr), + fHostContext(context), +#endif + fParameterOffset(fPlugin.getParameterOffset()), + fRealParameterCount(fParameterOffset + fPlugin.getParameterCount()), + fParameterValues(nullptr), + fChangedParameterValues(nullptr) +#if DISTRHO_PLUGIN_HAS_UI + , fConnectedToUI(false) + , fNextSampleRate(0.0) +#endif +#if DISTRHO_PLUGIN_WANT_LATENCY + , fLastKnownLatency(fPlugin.getLatency()) +#endif +#if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT + , fHostEventOutputHandle(nullptr) +#endif +#if DISTRHO_PLUGIN_WANT_PROGRAMS + , fCurrentProgram(0) + , fProgramCountMinusOne(fPlugin.getProgramCount()-1) +#endif + { +#if DISTRHO_PLUGIN_NUM_INPUTS > 0 + for (uint32_t i=0; i 0 + for (uint32_t i=0; i= 0, busIndex, V3_INVALID_ARG); + +#if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0 || DISTRHO_PLUGIN_WANT_MIDI_INPUT || DISTRHO_PLUGIN_WANT_MIDI_OUTPUT + const uint32_t busId = static_cast(busIndex); +#endif + + if (mediaType == V3_AUDIO) + { + #if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0 + int32_t numChannels; + v3_bus_flags flags; + v3_bus_types busType; + v3_str_128 busName = {}; + + if (busDirection == V3_INPUT) + { + #if DISTRHO_PLUGIN_NUM_INPUTS > 0 + switch (busId) + { + case 0: + if (inputBuses.audio) + { + numChannels = inputBuses.numMainAudio; + busType = V3_MAIN; + flags = V3_DEFAULT_ACTIVE; + break; + } + // fall-through + case 1: + if (inputBuses.sidechain) + { + numChannels = inputBuses.numSidechain; + busType = V3_AUX; + flags = v3_bus_flags(0); + break; + } + // fall-through + default: + numChannels = 1; + busType = V3_AUX; + flags = V3_IS_CONTROL_VOLTAGE; + break; + } + + if (busType == V3_MAIN) + { + strncpy_utf16(busName, "Audio Input", 128); + } + else + { + for (uint32_t i=0; i 0 + switch (busId) + { + case 0: + if (outputBuses.audio) + { + numChannels = outputBuses.numMainAudio; + busType = V3_MAIN; + flags = V3_DEFAULT_ACTIVE; + break; + } + // fall-through + case 1: + if (outputBuses.sidechain) + { + numChannels = outputBuses.numSidechain; + busType = V3_AUX; + flags = v3_bus_flags(0); + break; + } + // fall-through + default: + numChannels = 1; + busType = V3_AUX; + flags = V3_IS_CONTROL_VOLTAGE; + break; + } + + if (busType == V3_MAIN) + { + strncpy_utf16(busName, "Audio Output", 128); + } + else + { + for (uint32_t i=0; imedia_type = V3_AUDIO; + info->direction = busDirection; + info->channel_count = numChannels; + std::memcpy(info->bus_name, busName, sizeof(busName)); + info->bus_type = busType; + info->flags = flags; + return V3_OK; + #else + return V3_INVALID_ARG; + #endif // DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS + } + else + { + if (busDirection == V3_INPUT) + { + #if DISTRHO_PLUGIN_WANT_MIDI_INPUT + DISTRHO_SAFE_ASSERT_RETURN(busId == 0, V3_INVALID_ARG); + #else + return V3_INVALID_ARG; + #endif + } + else + { + #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT + DISTRHO_SAFE_ASSERT_RETURN(busId == 0, V3_INVALID_ARG); + #else + return V3_INVALID_ARG; + #endif + } + info->media_type = V3_EVENT; + info->direction = busDirection; + info->channel_count = 1; + strncpy_utf16(info->bus_name, busDirection == V3_INPUT ? "Event/MIDI Input" + : "Event/MIDI Output", 128); + info->bus_type = V3_MAIN; + info->flags = V3_DEFAULT_ACTIVE; + return V3_OK; + } + } + + v3_result getRoutingInfo(v3_routing_info*, v3_routing_info*) + { + // TODO + return V3_NOT_IMPLEMENTED; + } + + v3_result activateBus(const int32_t /* mediaType */, + const int32_t /* busDirection */, + const int32_t /* busIndex */, + const bool /* state */) + { + // TODO, returning ok to make bitwig happy + return V3_OK; + } + + v3_result setActive(const bool active) + { + if (active) + fPlugin.activate(); + else + fPlugin.deactivateIfNeeded(); + + return V3_OK; + } + + /* state: we pack pairs of key-value strings each separated by a null/zero byte. + * current-program comes first, then dpf key/value states and then parameters. + * parameters are simply converted to/from strings and floats. + * the parameter symbol is used as the "key", so it is possible to reorder them or even remove and add safely. + * there are markers for begin and end of state and parameters, so they never conflict. + */ + v3_result setState(v3_bstream** const stream) + { +#if DISTRHO_PLUGIN_HAS_UI + const bool connectedToUI = fConnection != nullptr && fConnectedToUI; +#endif + const uint32_t paramCount = fPlugin.getParameterCount(); + + String key, value; + bool fillingKey = true; // if filling key or value + char queryingType = 'i'; // can be 'n', 's' or 'p' (none, states, parameters) + + char buffer[512], orig; + buffer[sizeof(buffer)-1] = '\xff'; + v3_result res; + + for (int32_t pos = 0, term = 0, read; term == 0; pos += read) + { + res = v3_cpp_obj(stream)->read(stream, buffer, sizeof(buffer)-1, &read); + DISTRHO_SAFE_ASSERT_INT_RETURN(res == V3_OK, res, res); + DISTRHO_SAFE_ASSERT_INT_RETURN(read > 0, read, V3_INTERNAL_ERR); + + for (int32_t i = 0; i < read; ++i) + { + // found terminator, stop here + if (buffer[i] == '\xfe') + { + term = 1; + break; + } + + // store character at read position + orig = buffer[read]; + + // place null character to create valid string + buffer[read] = '\0'; + + // append to temporary vars + if (fillingKey) + key += buffer + i; + else + value += buffer + i; + + // increase buffer offset by length of string + i += std::strlen(buffer + i); + + // restore read character + buffer[read] = orig; + + // if buffer offset points to null, we found the end of a string, lets check + if (buffer[i] == '\0') + { + // special keys + if (key == "__dpf_state_begin__") + { + DISTRHO_SAFE_ASSERT_INT_RETURN(queryingType == 'i' || queryingType == 'n', + queryingType, V3_INTERNAL_ERR); + queryingType = 's'; + key.clear(); + value.clear(); + continue; + } + if (key == "__dpf_state_end__") + { + DISTRHO_SAFE_ASSERT_INT_RETURN(queryingType == 's', queryingType, V3_INTERNAL_ERR); + queryingType = 'n'; + key.clear(); + value.clear(); + continue; + } + if (key == "__dpf_parameters_begin__") + { + DISTRHO_SAFE_ASSERT_INT_RETURN(queryingType == 'i' || queryingType == 'n', + queryingType, V3_INTERNAL_ERR); + queryingType = 'p'; + key.clear(); + value.clear(); + continue; + } + if (key == "__dpf_parameters_end__") + { + DISTRHO_SAFE_ASSERT_INT_RETURN(queryingType == 'p', queryingType, V3_INTERNAL_ERR); + queryingType = 'x'; + key.clear(); + value.clear(); + continue; + } + + // no special key, swap between reading real key and value + fillingKey = !fillingKey; + + // if there is no value yet keep reading until we have one (TODO check empty values on purpose) + if (value.isEmpty()) + continue; + + if (key == "__dpf_program__") + { + DISTRHO_SAFE_ASSERT_INT_RETURN(queryingType == 'i', queryingType, V3_INTERNAL_ERR); + queryingType = 'n'; + + d_stdout("found program '%s'", value.buffer()); + +#if DISTRHO_PLUGIN_WANT_PROGRAMS + const int program = std::atoi(value.buffer()); + DISTRHO_SAFE_ASSERT_CONTINUE(program >= 0); + + fCurrentProgram = static_cast(program); + fPlugin.loadProgram(fCurrentProgram); + +# if DISTRHO_PLUGIN_HAS_UI + if (connectedToUI) + { + fChangedParameterValues[0] = false; + sendParameterChangeToUI(0, fCurrentProgram); + } +# endif +#endif + } + else if (queryingType == 's') + { + d_stdout("found state '%s' '%s'", key.buffer(), value.buffer()); + +#if DISTRHO_PLUGIN_WANT_STATE + if (fPlugin.wantStateKey(key)) + { + fStateMap[key] = value; + fPlugin.setState(key, value); + +# if DISTRHO_PLUGIN_HAS_UI + if (connectedToUI) + sendStateChangeToUI(key, value); +# endif + } +#endif + } + else if (queryingType == 'p') + { + d_stdout("found parameter '%s' '%s'", key.buffer(), value.buffer()); + + // find parameter with this symbol, and set its value + for (uint32_t j=0; j < paramCount; ++j) + { + if (fPlugin.isParameterOutputOrTrigger(j)) + continue; + if (fPlugin.getParameterSymbol(j) != key) + continue; + + const float fvalue = fParameterValues[j] = std::atof(value.buffer()); + fPlugin.setParameterValue(j, fvalue); +#if DISTRHO_PLUGIN_HAS_UI + if (connectedToUI) + { + // UI parameter updates are handled outside the read loop (after host param restart) + fChangedParameterValues[j + fParameterOffset] = true; + } +#endif + break; + } + + } + + key.clear(); + value.clear(); + } + } + } + + if (paramCount != 0) + { + if (fComponentHandler != nullptr) + v3_cpp_obj(fComponentHandler)->restart_component(fComponentHandler, V3_RESTART_PARAM_VALUES_CHANGED); + +#if DISTRHO_PLUGIN_HAS_UI + if (connectedToUI) + { + for (uint32_t i=0; i < paramCount; ++i) + { + if (fPlugin.isParameterOutputOrTrigger(i)) + continue; + fChangedParameterValues[i + fParameterOffset] = false; + sendParameterChangeToUI(i + fParameterOffset, fParameterValues[i]); + } + } +#endif + } + + return V3_OK; + } + + v3_result getState(v3_bstream** const stream) + { + const uint32_t paramCount = fPlugin.getParameterCount(); +#if DISTRHO_PLUGIN_WANT_STATE + const uint32_t stateCount = fPlugin.getStateCount(); +#else + const uint32_t stateCount = 0; +#endif + + if (stateCount == 0 && paramCount == 0) + { + char buffer = '\0'; + int32_t ignored; + return v3_cpp_obj(stream)->write(stream, &buffer, 1, &ignored); + } + +#if DISTRHO_PLUGIN_WANT_FULL_STATE + // Update current state + for (StringMap::const_iterator cit=fStateMap.begin(), cite=fStateMap.end(); cit != cite; ++cit) + { + const String& key = cit->first; + fStateMap[key] = fPlugin.getState(key); + } +#endif + + String state; + +#if DISTRHO_PLUGIN_WANT_PROGRAMS + { + String tmpStr("__dpf_program__\xff"); + tmpStr += String(fCurrentProgram); + tmpStr += "\xff"; + + state += tmpStr; + } +#endif + +#if DISTRHO_PLUGIN_WANT_STATE + if (stateCount != 0) + { + state += "__dpf_state_begin__\xff"; + + for (StringMap::const_iterator cit=fStateMap.begin(), cite=fStateMap.end(); cit != cite; ++cit) + { + const String& key = cit->first; + const String& value = cit->second; + + // join key and value + String tmpStr; + tmpStr = key; + tmpStr += "\xff"; + tmpStr += value; + tmpStr += "\xff"; + + state += tmpStr; + } + + state += "__dpf_state_end__\xff"; + } +#endif + + if (paramCount != 0) + { + state += "__dpf_parameters_begin__\xff"; + + for (uint32_t i=0; i(state.length())+1; + v3_result res; + + for (int32_t wrtntotal = 0, wrtn; wrtntotal < size; wrtntotal += wrtn) + { + wrtn = 0; + res = v3_cpp_obj(stream)->write(stream, const_cast(buffer), size - wrtntotal, &wrtn); + + DISTRHO_SAFE_ASSERT_INT_RETURN(res == V3_OK, res, res); + DISTRHO_SAFE_ASSERT_INT_RETURN(wrtn > 0, wrtn, V3_INTERNAL_ERR); + } + + return V3_OK; + } + + // ---------------------------------------------------------------------------------------------------------------- + // v3_audio_processor interface calls + + v3_result setBusArrangements(v3_speaker_arrangement*, int32_t, v3_speaker_arrangement*, int32_t) + { + // TODO + return V3_NOT_IMPLEMENTED; + } + + v3_result getBusArrangement(int32_t, int32_t, v3_speaker_arrangement*) + { + // TODO + return V3_NOT_IMPLEMENTED; + } + + uint32_t getLatencySamples() const noexcept + { +#if DISTRHO_PLUGIN_WANT_LATENCY + return fPlugin.getLatency(); +#else + return 0; +#endif + } + + v3_result setupProcessing(v3_process_setup* const setup) + { + DISTRHO_SAFE_ASSERT_RETURN(setup->symbolic_sample_size == V3_SAMPLE_32, V3_INVALID_ARG); + + const bool active = fPlugin.isActive(); + fPlugin.deactivateIfNeeded(); + + // TODO process_mode can be V3_REALTIME, V3_PREFETCH, V3_OFFLINE + +#if DISTRHO_PLUGIN_HAS_UI + if (d_isNotEqual(setup->sample_rate, fPlugin.getSampleRate())) + fNextSampleRate = setup->sample_rate; +#endif + + fPlugin.setSampleRate(setup->sample_rate, true); + fPlugin.setBufferSize(setup->max_block_size, true); + + if (active) + fPlugin.activate(); + + // TODO create dummy buffer of max_block_size length, to use for disabled buses + + return V3_OK; + } + + v3_result setProcessing(const bool processing) + { + if (processing) + { + if (! fPlugin.isActive()) + fPlugin.activate(); + } + else + { + fPlugin.deactivate(); + } + + return V3_OK; + } + + v3_result process(v3_process_data* const data) + { + DISTRHO_SAFE_ASSERT_RETURN(data->symbolic_sample_size == V3_SAMPLE_32, V3_INVALID_ARG); + + if (! fPlugin.isActive()) + { + // host has not activated the plugin yet, nasty! + fPlugin.activate(); + } + +#if DISTRHO_PLUGIN_WANT_TIMEPOS + if (v3_process_context* const ctx = data->ctx) + { + fTimePosition.playing = ctx->state & V3_PROCESS_CTX_PLAYING; + fTimePosition.bbt.valid = ctx->state & (V3_PROCESS_CTX_TEMPO_VALID|V3_PROCESS_CTX_TIME_SIG_VALID); + + // ticksPerBeat is not possible with VST2 + fTimePosition.bbt.ticksPerBeat = 1920.0; + + if (ctx->state & V3_PROCESS_CTX_CONT_TIME_VALID) + fTimePosition.frame = ctx->continuous_time_in_samples; + else + fTimePosition.frame = ctx->project_time_in_samples; + + if (ctx->state & V3_PROCESS_CTX_TEMPO_VALID) + fTimePosition.bbt.beatsPerMinute = ctx->bpm; + else + fTimePosition.bbt.beatsPerMinute = 120.0; + + if (ctx->state & (V3_PROCESS_CTX_PROJECT_TIME_VALID|V3_PROCESS_CTX_TIME_SIG_VALID)) + { + const double ppqPos = std::abs(ctx->project_time_quarters); + const int ppqPerBar = ctx->time_sig_numerator * 4 / ctx->time_sig_denom; + const double barBeats = (std::fmod(ppqPos, ppqPerBar) / ppqPerBar) * ctx->time_sig_numerator; + const double rest = std::fmod(barBeats, 1.0); + + fTimePosition.bbt.bar = static_cast(ppqPos) / ppqPerBar + 1; + fTimePosition.bbt.beat = static_cast(barBeats - rest + 0.5) + 1; + fTimePosition.bbt.tick = rest * fTimePosition.bbt.ticksPerBeat; + fTimePosition.bbt.beatsPerBar = ctx->time_sig_numerator; + fTimePosition.bbt.beatType = ctx->time_sig_denom; + + if (ctx->project_time_quarters < 0.0) + { + --fTimePosition.bbt.bar; + fTimePosition.bbt.beat = ctx->time_sig_numerator - fTimePosition.bbt.beat + 1; + fTimePosition.bbt.tick = fTimePosition.bbt.ticksPerBeat - fTimePosition.bbt.tick - 1; + } + } + else + { + fTimePosition.bbt.bar = 1; + fTimePosition.bbt.beat = 1; + fTimePosition.bbt.tick = 0.0; + fTimePosition.bbt.beatsPerBar = 4.0f; + fTimePosition.bbt.beatType = 4.0f; + } + + fTimePosition.bbt.barStartTick = fTimePosition.bbt.ticksPerBeat* + fTimePosition.bbt.beatsPerBar* + (fTimePosition.bbt.bar-1); + + fPlugin.setTimePosition(fTimePosition); + } +#endif + + if (data->nframes <= 0) + { + updateParameterOutputsAndTriggers(); + return V3_OK; + } + + const float* inputs[DISTRHO_PLUGIN_NUM_INPUTS != 0 ? DISTRHO_PLUGIN_NUM_INPUTS : 1]; + /* */ float* outputs[DISTRHO_PLUGIN_NUM_OUTPUTS != 0 ? DISTRHO_PLUGIN_NUM_OUTPUTS : 1]; + + { + int32_t i = 0; + if (data->inputs != nullptr) + { + for (; i < data->inputs->num_channels; ++i) + { + DISTRHO_SAFE_ASSERT_INT_BREAK(i < DISTRHO_PLUGIN_NUM_INPUTS, i); + inputs[i] = data->inputs->channel_buffers_32[i]; + } + } + for (; i < std::max(1, DISTRHO_PLUGIN_NUM_INPUTS); ++i) + inputs[i] = nullptr; // TODO use dummy buffer + } + + { + int32_t i = 0; + if (data->outputs != nullptr) + { + for (; i < data->outputs->num_channels; ++i) + { + DISTRHO_SAFE_ASSERT_INT_BREAK(i < DISTRHO_PLUGIN_NUM_OUTPUTS, i); + outputs[i] = data->outputs->channel_buffers_32[i]; + } + } + for (; i < std::max(1, DISTRHO_PLUGIN_NUM_OUTPUTS); ++i) + outputs[i] = nullptr; // TODO use dummy buffer + } + +#if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT + fHostEventOutputHandle = data->output_events; +#endif + +#if DISTRHO_PLUGIN_WANT_MIDI_INPUT + uint32_t midiEventCount = 0; + +# if DISTRHO_PLUGIN_HAS_UI + while (fNotesRingBuffer.isDataAvailableForReading()) + { + uint8_t midiData[3]; + if (! fNotesRingBuffer.readCustomData(midiData, 3)) + break; + + MidiEvent& midiEvent(fMidiEvents[midiEventCount++]); + midiEvent.frame = 0; + midiEvent.size = 3; + std::memcpy(midiEvent.data, midiData, 3); + + if (midiEventCount == kMaxMidiEvents) + break; + } +# endif + + if (v3_event_list** const eventptr = data->input_events) + { + v3_event event; + for (uint32_t i = 0, count = v3_cpp_obj(eventptr)->get_event_count(eventptr); i < count; ++i) + { + if (v3_cpp_obj(eventptr)->get_event(eventptr, i, &event) != V3_OK) + break; + + // check if event can be encoded as MIDI + switch (event.type) + { + case V3_EVENT_NOTE_ON: + case V3_EVENT_NOTE_OFF: + // case V3_EVENT_DATA: + case V3_EVENT_POLY_PRESSURE: + break; + // case V3_EVENT_NOTE_EXP_VALUE: + // case V3_EVENT_NOTE_EXP_TEXT: + // case V3_EVENT_CHORD: + // case V3_EVENT_SCALE: + // case V3_EVENT_LEGACY_MIDI_CC_OUT: + default: + continue; + } + + MidiEvent& midiEvent(fMidiEvents[midiEventCount++]); + midiEvent.frame = event.sample_offset; + + // encode event as MIDI + switch (event.type) + { + case V3_EVENT_NOTE_ON: + midiEvent.size = 3; + midiEvent.data[0] = 0x90 | (event.note_on.channel & 0xf); + midiEvent.data[1] = event.note_on.pitch; + midiEvent.data[2] = std::max(0, std::min(127, (int)(event.note_on.velocity * 127))); + midiEvent.data[3] = 0; + break; + case V3_EVENT_NOTE_OFF: + midiEvent.size = 3; + midiEvent.data[0] = 0x80 | (event.note_off.channel & 0xf); + midiEvent.data[1] = event.note_off.pitch; + midiEvent.data[2] = std::max(0, std::min(127, (int)(event.note_off.velocity * 127))); + midiEvent.data[3] = 0; + break; + case V3_EVENT_POLY_PRESSURE: + midiEvent.size = 3; + midiEvent.data[0] = 0xA0 | (event.poly_pressure.channel & 0xf); + midiEvent.data[1] = event.poly_pressure.pitch; + midiEvent.data[2] = std::max(0, std::min(127, (int)(event.poly_pressure.pressure * 127))); + midiEvent.data[3] = 0; + break; + default: + midiEvent.size = 0; + break; + } + + if (midiEventCount == kMaxMidiEvents) + break; + } + } + + // TODO append MIDI events in a sorted way + if (v3_param_changes** const inparamsptr = data->input_params) + { + v3_param_value_queue** queue; + int32_t offset; + double value; + + for (int32_t i = 0, count = v3_cpp_obj(inparamsptr)->get_param_count(inparamsptr); i < count; ++i) + { + queue = v3_cpp_obj(inparamsptr)->get_param_data(inparamsptr, i); + DISTRHO_SAFE_ASSERT_BREAK(queue != nullptr); + + v3_param_id rindex = v3_cpp_obj(queue)->get_param_id(queue); + DISTRHO_SAFE_ASSERT_UINT_BREAK(rindex < fRealParameterCount, rindex); + + // not supported yet + if (rindex >= fParameterOffset) + continue; + +# if DISTRHO_PLUGIN_WANT_PROGRAMS + if (rindex == 0) + continue; + --rindex; +# endif + + for (int32_t j = 0, pcount = v3_cpp_obj(queue)->get_point_count(queue); j < pcount; ++j) + { + if (v3_cpp_obj(queue)->get_point(queue, j, &offset, &value) != V3_OK) + break; + + DISTRHO_SAFE_ASSERT_BREAK(offset < data->nframes); + + MidiEvent& midiEvent(fMidiEvents[midiEventCount++]); + midiEvent.frame = offset; + midiEvent.size = 3; + midiEvent.data[0] = (rindex / 130) & 0xf; + + switch (rindex) + { + case 128: // channel pressure + midiEvent.data[0] |= 0xD0; + midiEvent.data[1] = std::max(0, std::min(127, (int)(value * 127))); + midiEvent.data[2] = 0; + midiEvent.data[3] = 0; + break; + case 129: // pitchbend + midiEvent.data[0] |= 0xE0; + midiEvent.data[1] = std::max(0, std::min(16384, (int)(value * 16384))) & 0x7f; + midiEvent.data[2] = std::max(0, std::min(16384, (int)(value * 16384))) >> 7; + midiEvent.data[3] = 0; + break; + default: + midiEvent.data[0] |= 0xB0; + midiEvent.data[1] = rindex % 130; + midiEvent.data[2] = std::max(0, std::min(127, (int)(value * 127))); + midiEvent.data[3] = 0; + break; + } + + if (midiEventCount == kMaxMidiEvents) + break; + } + + if (midiEventCount == kMaxMidiEvents) + break; + } + } + + + fPlugin.run(inputs, outputs, data->nframes, fMidiEvents, midiEventCount); +#else + fPlugin.run(inputs, outputs, data->nframes); +#endif + +#if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT + fHostEventOutputHandle = nullptr; +#endif + + updateParameterOutputsAndTriggers(); + return V3_OK; + } + + uint32_t getTailSamples() const noexcept + { + return 0; + } + + // ---------------------------------------------------------------------------------------------------------------- + // v3_edit_controller interface calls + +#if 0 + v3_result setComponentState(v3_bstream*, void*) + { + // TODO + return V3_NOT_IMPLEMENTED; + } + + v3_result setState(v3_bstream*, void*) + { + // TODO + return V3_NOT_IMPLEMENTED; + } + + v3_result getState(v3_bstream*, void*) + { + // TODO + return V3_NOT_IMPLEMENTED; + } +#endif + + int32_t getParameterCount() const noexcept + { + return fRealParameterCount; + } + + v3_result getParameterInfo(int32_t rindex, v3_param_info* const info) const noexcept + { + DISTRHO_SAFE_ASSERT_RETURN(rindex >= 0, V3_INVALID_ARG); + +#if DISTRHO_PLUGIN_WANT_PROGRAMS + if (rindex == 0) + { + std::memset(info, 0, sizeof(v3_param_info)); + info->param_id = rindex; + info->flags = V3_PARAM_CAN_AUTOMATE | V3_PARAM_IS_LIST | V3_PARAM_PROGRAM_CHANGE; + info->step_count = fProgramCountMinusOne; + strncpy_utf16(info->title, "Current Program", 128); + strncpy_utf16(info->short_title, "Program", 128); + return V3_OK; + } + --rindex; +#endif +#if DISTRHO_PLUGIN_WANT_MIDI_INPUT + if (rindex <= 130*16) + { + std::memset(info, 0, sizeof(v3_param_info)); + info->param_id = rindex; + info->flags = V3_PARAM_CAN_AUTOMATE | V3_PARAM_IS_HIDDEN; + info->step_count = 127; + char ccstr[24]; + snprintf(ccstr, sizeof(ccstr)-1, "MIDI Ch. %d CC %d", rindex / 130 + 1, rindex % 130); + strncpy_utf16(info->title, ccstr, 128); + snprintf(ccstr, sizeof(ccstr)-1, "Ch.%d CC%d", rindex / 130 + 1, rindex % 130); + strncpy_utf16(info->short_title, ccstr+5, 128); + return V3_OK; + } + rindex -= 130*16; +#endif + + const uint32_t index = static_cast(rindex); + DISTRHO_SAFE_ASSERT_UINT_RETURN(index < fPlugin.getParameterCount(), index, V3_INVALID_ARG); + + // set up flags + int32_t flags = 0; + + const ParameterRanges& ranges(fPlugin.getParameterRanges(index)); + const uint32_t hints = fPlugin.getParameterHints(index); + + switch (fPlugin.getParameterDesignation(index)) + { + case kParameterDesignationNull: + break; + case kParameterDesignationBypass: + flags |= V3_PARAM_IS_BYPASS; + break; + } + + if (hints & kParameterIsAutomable) + flags |= V3_PARAM_CAN_AUTOMATE; + if (hints & kParameterIsOutput) + flags |= V3_PARAM_READ_ONLY; + // TODO V3_PARAM_IS_LIST + + // set up step_count + int32_t step_count = 0; + + if (hints & kParameterIsBoolean) + step_count = 1; + if ((hints & kParameterIsInteger) && ranges.max - ranges.min > 1) + step_count = ranges.max - ranges.min - 1; + + std::memset(info, 0, sizeof(v3_param_info)); + info->param_id = index + fParameterOffset; + info->flags = flags; + info->step_count = step_count; + info->default_normalised_value = ranges.getNormalizedValue(ranges.def); + // int32_t unit_id; + strncpy_utf16(info->title, fPlugin.getParameterName(index), 128); + strncpy_utf16(info->short_title, fPlugin.getParameterShortName(index), 128); + strncpy_utf16(info->units, fPlugin.getParameterUnit(index), 128); + return V3_OK; + } + + v3_result getParameterStringForValue(v3_param_id rindex, const double normalised, v3_str_128 output) + { + DISTRHO_SAFE_ASSERT_UINT_RETURN(rindex < fRealParameterCount, rindex, V3_INVALID_ARG); + DISTRHO_SAFE_ASSERT_RETURN(normalised >= 0.0 && normalised <= 1.0, V3_INVALID_ARG); + + +#if DISTRHO_PLUGIN_WANT_PROGRAMS + if (rindex == 0) + { + const uint32_t program = std::round(normalised * fProgramCountMinusOne); + strncpy_utf16(output, fPlugin.getProgramName(program), 128); + return V3_OK; + } + --rindex; +#endif +#if DISTRHO_PLUGIN_WANT_MIDI_INPUT + if (rindex <= 130*16) + { + snprintf_f32_utf16(output, std::round(normalised * 127), 128); + return V3_OK; + } + rindex -= 130*16; +#endif + + const ParameterRanges& ranges(fPlugin.getParameterRanges(rindex)); + snprintf_f32_utf16(output, ranges.getUnnormalizedValue(normalised), 128); + return V3_OK; + } + + v3_result getParameterValueForString(v3_param_id rindex, int16_t*, double*) + { + DISTRHO_SAFE_ASSERT_UINT_RETURN(rindex < fRealParameterCount, rindex, V3_INVALID_ARG); + +#if DISTRHO_PLUGIN_WANT_PROGRAMS + if (rindex == 0) + { + // TODO find program index based on name + return V3_NOT_IMPLEMENTED; + } + --rindex; +#endif +#if DISTRHO_PLUGIN_WANT_MIDI_INPUT + if (rindex <= 130*16) + { + // TODO + return V3_NOT_IMPLEMENTED; + } + rindex -= 130*16; +#endif + + + // TODO + return V3_NOT_IMPLEMENTED; + } + + double normalisedParameterToPlain(v3_param_id rindex, const double normalised) + { + DISTRHO_SAFE_ASSERT_UINT_RETURN(rindex < fRealParameterCount, rindex, 0.0); + +#if DISTRHO_PLUGIN_WANT_PROGRAMS + if (rindex == 0) + return std::round(normalised * fProgramCountMinusOne); + --rindex; +#endif +#if DISTRHO_PLUGIN_WANT_MIDI_INPUT + if (rindex <= 130*16) + return std::round(normalised * 127); + rindex -= 130*16; +#endif + + const ParameterRanges& ranges(fPlugin.getParameterRanges(rindex)); + return ranges.getUnnormalizedValue(normalised); + } + + double plainParameterToNormalised(v3_param_id rindex, const double plain) + { + DISTRHO_SAFE_ASSERT_UINT_RETURN(rindex < fRealParameterCount, rindex, 0.0); + +#if DISTRHO_PLUGIN_WANT_PROGRAMS + if (rindex == 0) + return std::max(0.0, std::min(1.0, plain / fProgramCountMinusOne)); + --rindex; +#endif +#if DISTRHO_PLUGIN_WANT_MIDI_INPUT + if (rindex <= 130*16) + return std::max(0.0, std::min(1.0, plain / 127)); + rindex -= 130*16; +#endif + + const ParameterRanges& ranges(fPlugin.getParameterRanges(rindex)); + return ranges.getNormalizedValue(plain); + } + + double getParameterNormalized(v3_param_id rindex) + { + DISTRHO_SAFE_ASSERT_UINT_RETURN(rindex < fRealParameterCount, rindex, 0.0); + +#if DISTRHO_PLUGIN_WANT_PROGRAMS + if (rindex == 0) + return std::max(0.0, std::min(1.0, (double)fCurrentProgram / fProgramCountMinusOne)); + --rindex; +#endif +#if DISTRHO_PLUGIN_WANT_MIDI_INPUT + if (rindex <= 130*16) + { + // TODO + return 0.0; + } + rindex -= 130*16; +#endif + + const float value = fPlugin.getParameterValue(rindex); + const ParameterRanges& ranges(fPlugin.getParameterRanges(rindex)); + return ranges.getNormalizedValue(value); + } + + v3_result setParameterNormalized(v3_param_id rindex, const double value) + { + DISTRHO_SAFE_ASSERT_UINT_RETURN(rindex < fRealParameterCount, rindex, V3_INVALID_ARG); + DISTRHO_SAFE_ASSERT_RETURN(value >= 0.0 && value <= 1.0, V3_INVALID_ARG); + +#if DISTRHO_PLUGIN_HAS_UI + const uint32_t orindex = rindex; +#endif +#if DISTRHO_PLUGIN_WANT_PROGRAMS + if (rindex == 0) + { + fCurrentProgram = std::round(value * fProgramCountMinusOne); + fPlugin.loadProgram(fCurrentProgram); + + for (uint32_t i=0, count=fPlugin.getParameterCount(); i < count; ++i) + { + if (fPlugin.isParameterOutputOrTrigger(i)) + continue; + fParameterValues[i] = fPlugin.getParameterValue(i); + } + + if (fComponentHandler != nullptr) + v3_cpp_obj(fComponentHandler)->restart_component(fComponentHandler, V3_RESTART_PARAM_VALUES_CHANGED); + +# if DISTRHO_PLUGIN_HAS_UI + fChangedParameterValues[rindex] = true; +# endif + return V3_OK; + } + --rindex; +#endif +#if DISTRHO_PLUGIN_WANT_MIDI_INPUT + if (rindex <= 130*16) + { + // TODO + fChangedParameterValues[rindex] = true; + return V3_NOT_IMPLEMENTED; + } + rindex -= 130*16; +#endif + + const uint32_t hints = fPlugin.getParameterHints(rindex); + const ParameterRanges& ranges(fPlugin.getParameterRanges(rindex)); + + float realValue = ranges.getUnnormalizedValue(value); + + if (hints & kParameterIsBoolean) + { + const float midRange = ranges.min + (ranges.max - ranges.min) / 2.0f; + realValue = realValue > midRange ? ranges.max : ranges.min; + } + + if (hints & kParameterIsInteger) + { + realValue = std::round(realValue); + } + + fParameterValues[rindex] = realValue; + fPlugin.setParameterValue(rindex, realValue); +#if DISTRHO_PLUGIN_HAS_UI + fChangedParameterValues[orindex] = true; +#endif + return V3_OK; + } + + v3_result setComponentHandler(v3_component_handler** const handler) noexcept + { + fComponentHandler = handler; + return V3_OK; + } + +#if DISTRHO_PLUGIN_HAS_UI + // ---------------------------------------------------------------------------------------------------------------- + // v3_connection_point interface calls + + void connect(v3_connection_point** const other) + { + DISTRHO_SAFE_ASSERT(fConnectedToUI == false); + + fConnection = other; + fConnectedToUI = false; + } + + void disconnect() + { + fConnection = nullptr; + fConnectedToUI = false; + } + + v3_result notify(v3_message** const message) + { + const char* const msgid = v3_cpp_obj(message)->get_message_id(message); + DISTRHO_SAFE_ASSERT_RETURN(msgid != nullptr, V3_INVALID_ARG); + + v3_attribute_list** const attrs = v3_cpp_obj(message)->get_attributes(message); + DISTRHO_SAFE_ASSERT_RETURN(attrs != nullptr, V3_INVALID_ARG); + +# if DISTRHO_PLUGIN_WANT_MIDI_INPUT + if (std::strcmp(msgid, "midi") == 0) + { + uint8_t* data; + uint32_t size; + v3_result res; + + res = v3_cpp_obj(attrs)->get_binary(attrs, "data", (const void**)&data, &size); + DISTRHO_SAFE_ASSERT_INT_RETURN(res == V3_OK, res, res); + + // known maximum size + DISTRHO_SAFE_ASSERT_UINT_RETURN(size == 3, size, V3_INTERNAL_ERR); + + return fNotesRingBuffer.writeCustomData(data, size) && fNotesRingBuffer.commitWrite() ? V3_OK : V3_NOMEM; + } +# endif + + if (std::strcmp(msgid, "init") == 0) + { + DISTRHO_SAFE_ASSERT_RETURN(fConnection != nullptr, V3_INTERNAL_ERR); + fConnectedToUI = true; + + if (const double sampleRate = fNextSampleRate) + { + fNextSampleRate = 0.0; + sendSampleRateToUI(sampleRate); + } + +# if DISTRHO_PLUGIN_WANT_PROGRAMS + fChangedParameterValues[0] = false; + sendParameterChangeToUI(0, fCurrentProgram); +# endif + +# if DISTRHO_PLUGIN_WANT_FULL_STATE + // Update current state from plugin side + for (StringMap::const_iterator cit=fStateMap.begin(), cite=fStateMap.end(); cit != cite; ++cit) + { + const String& key = cit->first; + fStateMap[key] = fPlugin.getState(key); + } +# endif + +# if DISTRHO_PLUGIN_WANT_STATE + // Set state + for (StringMap::const_iterator cit=fStateMap.begin(), cite=fStateMap.end(); cit != cite; ++cit) + { + const String& key = cit->first; + const String& value = cit->second; + + sendStateChangeToUI(key, value); + } +# endif + + for (uint32_t i=fParameterOffset; iget_int(attrs, "rindex", &rindex); + DISTRHO_SAFE_ASSERT_INT_RETURN(res == V3_OK, res, res); + + res = v3_cpp_obj(attrs)->get_int(attrs, "started", &started); + DISTRHO_SAFE_ASSERT_INT_RETURN(res == V3_OK, res, res); + + rindex -= fParameterOffset; + DISTRHO_SAFE_ASSERT_RETURN(rindex >= 0, V3_INTERNAL_ERR); + + return started != 0 ? v3_cpp_obj(fComponentHandler)->begin_edit(fComponentHandler, rindex) + : v3_cpp_obj(fComponentHandler)->end_edit(fComponentHandler, rindex); + } + + if (std::strcmp(msgid, "parameter-set") == 0) + { + int64_t rindex; + double value; + v3_result res; + + res = v3_cpp_obj(attrs)->get_int(attrs, "rindex", &rindex); + DISTRHO_SAFE_ASSERT_INT_RETURN(res == V3_OK, res, res); + DISTRHO_SAFE_ASSERT_INT_RETURN(rindex >= fParameterOffset, rindex, V3_INTERNAL_ERR); + + res = v3_cpp_obj(attrs)->get_float(attrs, "value", &value); + DISTRHO_SAFE_ASSERT_INT_RETURN(res == V3_OK, res, res); + + const uint32_t index = static_cast(rindex - fParameterOffset); + return requestParameterValueChange(index, value) ? V3_OK : V3_INTERNAL_ERR; + } + +# if DISTRHO_PLUGIN_WANT_STATE + if (std::strcmp(msgid, "state-set") == 0) + { + int16_t* key16; + int16_t* value16; + uint32_t keySize, valueSize; + v3_result res; + + res = v3_cpp_obj(attrs)->get_binary(attrs, "key", (const void**)&key16, &keySize); + DISTRHO_SAFE_ASSERT_INT_RETURN(res == V3_OK, res, res); + + res = v3_cpp_obj(attrs)->get_binary(attrs, "value", (const void**)&value16, &valueSize); + DISTRHO_SAFE_ASSERT_INT_RETURN(res == V3_OK, res, res); + + // do cheap inline conversion + char* const key = (char*)key16; + char* const value = (char*)value16; + + for (uint32_t i=0; ifirst); + + if (dkey == key) + { + it->second = value; + return V3_OK; + } + } + + d_stderr("Failed to find plugin state with key \"%s\"", key); + } + + return V3_OK; + } +# endif + + return V3_NOT_IMPLEMENTED; + } +#endif + + // ---------------------------------------------------------------------------------------------------------------- + +private: + // Plugin + PluginExporter fPlugin; + + // VST3 stuff + v3_component_handler** fComponentHandler; +#if DISTRHO_PLUGIN_HAS_UI + v3_connection_point** fConnection; + v3_host_application** const fHostContext; +#endif + + // Temporary data + const uint32_t fParameterOffset; + const uint32_t fRealParameterCount; // regular parameters + current program + float* fParameterValues; + bool* fChangedParameterValues; +#if DISTRHO_PLUGIN_HAS_UI + bool fConnectedToUI; + double fNextSampleRate; // if not zero, report to UI +#endif +#if DISTRHO_PLUGIN_WANT_LATENCY + uint32_t fLastKnownLatency; +#endif +#if DISTRHO_PLUGIN_WANT_MIDI_INPUT + MidiEvent fMidiEvents[kMaxMidiEvents]; +# if DISTRHO_PLUGIN_HAS_UI + SmallStackRingBuffer fNotesRingBuffer; +# endif +#endif +#if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT + v3_event_list** fHostEventOutputHandle; +#endif +#if DISTRHO_PLUGIN_WANT_PROGRAMS + uint32_t fCurrentProgram; + const uint32_t fProgramCountMinusOne; +#endif +#if DISTRHO_PLUGIN_WANT_STATE + StringMap fStateMap; +#endif +#if DISTRHO_PLUGIN_WANT_TIMEPOS + TimePosition fTimePosition; +#endif + + // ---------------------------------------------------------------------------------------------------------------- + // helper functions called during process, cannot block + + void updateParameterOutputsAndTriggers() + { + float curValue; + + for (uint32_t i=0, count=fPlugin.getParameterCount(); i < count; ++i) + { + if (fPlugin.isParameterOutput(i)) + { + // NOTE: no output parameter support in VST3, simulate it here + curValue = fPlugin.getParameterValue(i); + + if (d_isEqual(curValue, fParameterValues[i])) + continue; + + fParameterValues[i] = curValue; +#if DISTRHO_PLUGIN_HAS_UI + fChangedParameterValues[i] = true; +#endif + } + else if (fPlugin.isParameterTrigger(i)) + { + // NOTE: no trigger support in VST3 parameters, simulate it here + curValue = fPlugin.getParameterValue(i); + + if (d_isEqual(curValue, fPlugin.getParameterDefault(i))) + continue; + + fPlugin.setParameterValue(i, curValue); +#if DISTRHO_PLUGIN_HAS_UI + fChangedParameterValues[i] = true; +#endif + } + else + { + continue; + } + + requestParameterValueChange(i, curValue); + } + +#if DISTRHO_PLUGIN_WANT_LATENCY + const uint32_t latency = fPlugin.getLatency(); + + if (fLastKnownLatency != latency) + { + fLastKnownLatency = latency; + + if (fComponentHandler != nullptr) + v3_cpp_obj(fComponentHandler)->restart_component(fComponentHandler, V3_RESTART_LATENCY_CHANGED); + } +#endif + } + +#if DISTRHO_PLUGIN_HAS_UI + // ---------------------------------------------------------------------------------------------------------------- + // helper functions called during message passing, can block + + v3_message** createMessage(const char* const id) const + { + DISTRHO_SAFE_ASSERT_RETURN(fHostContext != nullptr, nullptr); + + v3_tuid iid; + memcpy(iid, v3_message_iid, sizeof(v3_tuid)); + v3_message** msg = nullptr; + const v3_result res = v3_cpp_obj(fHostContext)->create_instance(fHostContext, iid, iid, (void**)&msg); + DISTRHO_SAFE_ASSERT_INT_RETURN(res == V3_TRUE, res, nullptr); + DISTRHO_SAFE_ASSERT_RETURN(msg != nullptr, nullptr); + + v3_cpp_obj(msg)->set_message_id(msg, id); + return msg; + } + + void sendSampleRateToUI(const double sampleRate) const + { + v3_message** const message = createMessage("sample-rate"); + DISTRHO_SAFE_ASSERT_RETURN(message != nullptr,); + + v3_attribute_list** const attrlist = v3_cpp_obj(message)->get_attributes(message); + DISTRHO_SAFE_ASSERT_RETURN(attrlist != nullptr,); + + v3_cpp_obj(attrlist)->set_int(attrlist, "__dpf_msg_target__", 2); + v3_cpp_obj(attrlist)->set_float(attrlist, "value", sampleRate); + v3_cpp_obj(fConnection)->notify(fConnection, message); + + v3_cpp_obj_unref(message); + } + + void sendParameterChangeToUI(const v3_param_id rindex, const double value) const + { + v3_message** const message = createMessage("parameter-set"); + DISTRHO_SAFE_ASSERT_RETURN(message != nullptr,); + + v3_attribute_list** const attrlist = v3_cpp_obj(message)->get_attributes(message); + DISTRHO_SAFE_ASSERT_RETURN(attrlist != nullptr,); + + v3_cpp_obj(attrlist)->set_int(attrlist, "__dpf_msg_target__", 2); + v3_cpp_obj(attrlist)->set_int(attrlist, "rindex", rindex); + v3_cpp_obj(attrlist)->set_float(attrlist, "value", value); + v3_cpp_obj(fConnection)->notify(fConnection, message); + + v3_cpp_obj_unref(message); + } + + void sendStateChangeToUI(const char* const key, const char* const value) const + { + v3_message** const message = createMessage("state-set"); + DISTRHO_SAFE_ASSERT_RETURN(message != nullptr,); + + v3_attribute_list** const attrlist = v3_cpp_obj(message)->get_attributes(message); + DISTRHO_SAFE_ASSERT_RETURN(attrlist != nullptr,); + + v3_cpp_obj(attrlist)->set_int(attrlist, "__dpf_msg_target__", 2); + v3_cpp_obj(attrlist)->set_string(attrlist, "key", ScopedUTF16String(key)); + v3_cpp_obj(attrlist)->set_string(attrlist, "value", ScopedUTF16String(value)); + v3_cpp_obj(fConnection)->notify(fConnection, message); + + v3_cpp_obj_unref(message); + } + + void sendReadyToUI() const + { + v3_message** const message = createMessage("ready"); + DISTRHO_SAFE_ASSERT_RETURN(message != nullptr,); + + v3_attribute_list** const attrlist = v3_cpp_obj(message)->get_attributes(message); + DISTRHO_SAFE_ASSERT_RETURN(attrlist != nullptr,); + + v3_cpp_obj(attrlist)->set_int(attrlist, "__dpf_msg_target__", 2); + v3_cpp_obj(fConnection)->notify(fConnection, message); + + v3_cpp_obj_unref(message); + } +#endif + + // ---------------------------------------------------------------------------------------------------------------- + // DPF callbacks + + bool requestParameterValueChange(const uint32_t index, const float value) + { + DISTRHO_SAFE_ASSERT_RETURN(fComponentHandler != nullptr, false); + + const uint32_t rindex = index + fParameterOffset; + const double normalized = fPlugin.getParameterRanges(index).getNormalizedValue(value); + + const v3_result res_edit = v3_cpp_obj(fComponentHandler)->begin_edit(fComponentHandler, rindex); + DISTRHO_SAFE_ASSERT_INT_RETURN(res_edit == V3_TRUE || res_edit == V3_FALSE, res_edit, res_edit); + + const v3_result res_perf = v3_cpp_obj(fComponentHandler)->perform_edit(fComponentHandler, rindex, normalized); + + if (res_perf == V3_TRUE) + fParameterValues[index] = value; + + if (res_edit == V3_TRUE) + v3_cpp_obj(fComponentHandler)->end_edit(fComponentHandler, rindex); + + return res_perf == V3_TRUE; + } + +#if DISTRHO_PLUGIN_WANT_PARAMETER_VALUE_CHANGE_REQUEST + static bool requestParameterValueChangeCallback(void* const ptr, const uint32_t index, const float value) + { + return ((PluginVst3*)ptr)->requestParameterValueChange(index, value); + } +#endif + +#if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT + bool writeMidi(const MidiEvent& midiEvent) + { + DISTRHO_CUSTOM_SAFE_ASSERT_ONCE_RETURN("MIDI output unsupported", fHostEventOutputHandle != nullptr, false); + + v3_event event; + std::memset(&event, 0, sizeof(event)); + event.sample_offset = midiEvent.frame; + + const uint8_t* const data = midiEvent.size > MidiEvent::kDataSize ? midiEvent.dataExt : midiEvent.data; + + switch (data[0] & 0xf0) + { + case 0x80: + event.type = V3_EVENT_NOTE_OFF; + event.note_off.channel = data[0] & 0xf; + event.note_off.pitch = data[1]; + event.note_off.velocity = (float)data[2] / 127.0f; + // int32_t note_id; + // float tuning; + break; + case 0x90: + event.type = V3_EVENT_NOTE_ON; + event.note_on.channel = data[0] & 0xf; + event.note_on.pitch = data[1]; + // float tuning; + event.note_on.velocity = (float)data[2] / 127.0f; + // int32_t length; + // int32_t note_id; + break; + case 0xA0: + event.type = V3_EVENT_POLY_PRESSURE; + event.poly_pressure.channel = data[0] & 0xf; + event.poly_pressure.pitch = data[1]; + event.poly_pressure.pressure = (float)data[2] / 127.0f; + // int32_t note_id; + break; + case 0xB0: + event.type = V3_EVENT_LEGACY_MIDI_CC_OUT; + event.midi_cc_out.channel = data[0] & 0xf; + event.midi_cc_out.cc_number = data[1]; + event.midi_cc_out.value = data[2]; + if (midiEvent.size == 4) + event.midi_cc_out.value2 = midiEvent.size == 4; + break; + /* TODO how do we deal with program changes?? + case 0xC0: + break; + */ + case 0xD0: + event.type = V3_EVENT_LEGACY_MIDI_CC_OUT; + event.midi_cc_out.channel = data[0] & 0xf; + event.midi_cc_out.cc_number = 128; + event.midi_cc_out.value = data[1]; + break; + case 0xE0: + event.type = V3_EVENT_LEGACY_MIDI_CC_OUT; + event.midi_cc_out.channel = data[0] & 0xf; + event.midi_cc_out.cc_number = 129; + event.midi_cc_out.value = data[1]; + event.midi_cc_out.value2 = data[2]; + break; + default: + return true; + } + + return v3_cpp_obj(fHostEventOutputHandle)->add_event(fHostEventOutputHandle, &event) == V3_OK; + } + + static bool writeMidiCallback(void* ptr, const MidiEvent& midiEvent) + { + return ((PluginVst3*)ptr)->writeMidi(midiEvent); + } +#endif + +}; + +// -------------------------------------------------------------------------------------------------------------------- + +/** + * VST3 low-level pointer thingies follow, proceed with care. + */ + +// -------------------------------------------------------------------------------------------------------------------- +// v3_funknown for static instances + +static uint32_t V3_API dpf_static_ref(void*) { return 1; } +static uint32_t V3_API dpf_static_unref(void*) { return 0; } + +// -------------------------------------------------------------------------------------------------------------------- +// v3_funknown for classes with a single instance + +template +static uint32_t V3_API dpf_single_instance_ref(void* self) +{ + return ++(*(T**)self)->refcounter; +} + +template +static uint32_t V3_API dpf_single_instance_unref(void* self) +{ + return --(*(T**)self)->refcounter; +} + +#if DISTRHO_PLUGIN_HAS_UI +// -------------------------------------------------------------------------------------------------------------------- +// dpf_dsp_connection_point + +enum ConnectionPointType { + kConnectionPointComponent, + kConnectionPointController, + kConnectionPointBridge +}; + +static const char* ConnectionPointType2str(const ConnectionPointType type) +{ + switch (type) + { + case kConnectionPointComponent: + return "kConnectionPointComponent"; + case kConnectionPointController: + return "kConnectionPointController"; + case kConnectionPointBridge: + return "kConnectionPointBridge"; + } + + return "[unknown]"; +} + +struct dpf_dsp_connection_point : v3_connection_point_cpp { + std::atomic_int refcounter; + ScopedPointer& vst3; + const ConnectionPointType type; + v3_connection_point** other; + v3_connection_point** bridge; // when type is controller this points to ctrl<->view point + bool shortcircuit; // plugin as controller, should pass directly to view + + dpf_dsp_connection_point(const ConnectionPointType t, ScopedPointer& v) + : refcounter(1), + vst3(v), + type(t), + other(nullptr), + bridge(nullptr), + shortcircuit(false) + { + // v3_funknown, single instance + query_interface = query_interface_connection_point; + ref = dpf_single_instance_ref; + unref = dpf_single_instance_unref; + + // v3_connection_point + point.connect = connect; + point.disconnect = disconnect; + point.notify = notify; + } + + // ---------------------------------------------------------------------------------------------------------------- + // v3_funknown + + static v3_result V3_API query_interface_connection_point(void* self, const v3_tuid iid, void** iface) + { + if (v3_tuid_match(iid, v3_funknown_iid)) + { + *iface = self; + return V3_OK; + } + + if (v3_tuid_match(iid, v3_connection_point_iid)) + { + *iface = self; + return V3_OK; + } + + *iface = NULL; + return V3_NO_INTERFACE; + } + + // ---------------------------------------------------------------------------------------------------------------- + // v3_connection_point + + static v3_result V3_API connect(void* self, v3_connection_point** other) + { + dpf_dsp_connection_point* const point = *(dpf_dsp_connection_point**)self; + DISTRHO_SAFE_ASSERT_RETURN(point != nullptr, V3_NOT_INITIALIZED); + d_stdout("DSP|dpf_dsp_connection_point::connect => %p %p | %d:%s %d", + self, other, point->type, ConnectionPointType2str(point->type), point->shortcircuit); + DISTRHO_SAFE_ASSERT_RETURN(point->other == nullptr, V3_INVALID_ARG); + DISTRHO_SAFE_ASSERT(point->bridge == nullptr); + + point->other = other; + + if (point->type == kConnectionPointComponent) + if (PluginVst3* const vst3 = point->vst3) + vst3->connect((v3_connection_point**)self); + + return V3_OK; + } + + static v3_result V3_API disconnect(void* self, v3_connection_point** other) + { + dpf_dsp_connection_point* const point = *(dpf_dsp_connection_point**)self; + DISTRHO_SAFE_ASSERT_RETURN(point != nullptr, V3_NOT_INITIALIZED); + d_stdout("DSP|dpf_dsp_connection_point::disconnect => %p %p | %d:%s %d", + self, other, point->type, ConnectionPointType2str(point->type), point->shortcircuit); + DISTRHO_SAFE_ASSERT_RETURN(point->other != nullptr, V3_INVALID_ARG); + + if (point->type == kConnectionPointComponent) + if (PluginVst3* const vst3 = point->vst3) + vst3->disconnect(); + + if (point->type == kConnectionPointBridge) + v3_cpp_obj_unref(point->other); + + point->other = nullptr; + point->bridge = nullptr; + + return V3_OK; + } + + static v3_result V3_API notify(void* self, v3_message** message) + { + dpf_dsp_connection_point* const point = *(dpf_dsp_connection_point**)self; + DISTRHO_SAFE_ASSERT_RETURN(point != nullptr, V3_NOT_INITIALIZED); + + PluginVst3* const vst3 = point->vst3; + DISTRHO_SAFE_ASSERT_RETURN(vst3 != nullptr, V3_NOT_INITIALIZED); + + v3_connection_point** const other = point->other; + DISTRHO_SAFE_ASSERT_RETURN(other != nullptr, V3_NOT_INITIALIZED); + + v3_attribute_list** const attrlist = v3_cpp_obj(message)->get_attributes(message); + DISTRHO_SAFE_ASSERT_RETURN(attrlist != nullptr, V3_INVALID_ARG); + + int64_t target = 0; + const v3_result res = v3_cpp_obj(attrlist)->get_int(attrlist, "__dpf_msg_target__", &target); + DISTRHO_SAFE_ASSERT_RETURN(res == V3_OK, res); + DISTRHO_SAFE_ASSERT_INT_RETURN(target == 1 || target == 2, target, V3_INTERNAL_ERR); + + switch (point->type) + { + // message belongs to component (aka plugin) + case kConnectionPointComponent: + if (target == 1) + { + // view -> edit controller -> component + return vst3->notify(message); + } + else + { + // message is from component to controller to view + return v3_cpp_obj(other)->notify(other, message); + } + + // message belongs to edit controller + case kConnectionPointController: + if (target == 1) + { + // we are in view<->dsp short-circuit, all happens in the controller without bridge + if (point->shortcircuit) + return vst3->notify(message); + + // view -> edit controller -> component + return v3_cpp_obj(other)->notify(other, message); + } + else + { + // we are in view<->dsp short-circuit, all happens in the controller without bridge + if (point->shortcircuit) + return v3_cpp_obj(other)->notify(other, message); + + // message is from component to controller to view + v3_connection_point** const bridge = point->bridge; + DISTRHO_SAFE_ASSERT_RETURN(bridge != nullptr, V3_NOT_INITIALIZED); + return v3_cpp_obj(bridge)->notify(bridge, message); + } + + // message belongs to bridge (aka ui) + case kConnectionPointBridge: + if (target == 1) + { + // view -> edit controller -> component + v3_connection_point** const bridge = point->bridge; + DISTRHO_SAFE_ASSERT_RETURN(bridge != nullptr, V3_NOT_INITIALIZED); + return v3_cpp_obj(bridge)->notify(bridge, message); + } + else + { + // message is from component to controller to view + return v3_cpp_obj(other)->notify(other, message); + } + } + + return V3_INTERNAL_ERR; + } +}; +#endif + +#if DISTRHO_PLUGIN_WANT_MIDI_INPUT +// -------------------------------------------------------------------------------------------------------------------- +// dpf_midi_mapping + +struct dpf_midi_mapping : v3_midi_mapping_cpp { + dpf_midi_mapping() + { + // v3_funknown, static + query_interface = query_interface_midi_mapping; + ref = dpf_static_ref; + unref = dpf_static_unref; + + // v3_midi_mapping + map.get_midi_controller_assignment = get_midi_controller_assignment; + } + + // ---------------------------------------------------------------------------------------------------------------- + // v3_funknown + + static v3_result V3_API query_interface_midi_mapping(void* self, const v3_tuid iid, void** iface) + { + if (v3_tuid_match(iid, v3_funknown_iid)) + { + *iface = self; + return V3_OK; + } + + if (v3_tuid_match(iid, v3_midi_mapping_iid)) + { + *iface = self; + return V3_OK; + } + + *iface = NULL; + return V3_NO_INTERFACE; + } + + // ---------------------------------------------------------------------------------------------------------------- + // v3_midi_mapping + + static v3_result V3_API get_midi_controller_assignment(void*, int32_t bus, int16_t channel, int16_t cc, v3_param_id* id) + { + DISTRHO_SAFE_ASSERT_INT_RETURN(bus == 0, bus, V3_FALSE); + DISTRHO_SAFE_ASSERT_INT_RETURN(channel >= 0 && channel < 16, channel, V3_FALSE); + DISTRHO_SAFE_ASSERT_INT_RETURN(cc >= 0 && cc < 130, cc, V3_FALSE); + +# if DISTRHO_PLUGIN_WANT_PROGRAMS + static constexpr const v3_param_id offset = 1; +# else + static const constexpr v3_param_id offset = 0; +# endif + + *id = offset + channel * 130 + cc; + return V3_TRUE; + } + + DISTRHO_PREVENT_HEAP_ALLOCATION +}; +#endif + +// -------------------------------------------------------------------------------------------------------------------- +// dpf_edit_controller + +struct dpf_edit_controller : v3_edit_controller_cpp { + std::atomic_int refcounter; +#if DISTRHO_PLUGIN_HAS_UI + ScopedPointer connectionComp; // kConnectionPointController + ScopedPointer connectionBridge; // kConnectionPointBridge +#endif + ScopedPointer& vst3; + bool initialized; + // cached values + v3_component_handler** handler; + v3_host_application** const hostContextFromFactory; + v3_host_application** hostContextFromInitialize; + + dpf_edit_controller(ScopedPointer& v, v3_host_application** const h) + : refcounter(1), + vst3(v), + initialized(false), + handler(nullptr), + hostContextFromFactory(h), + hostContextFromInitialize(nullptr) + { + // v3_funknown, single instance + query_interface = query_interface_edit_controller; + ref = dpf_single_instance_ref; + unref = dpf_single_instance_unref; + + // v3_plugin_base + base.initialize = initialize; + base.terminate = terminate; + + // v3_edit_controller + ctrl.set_component_state = set_component_state; + ctrl.set_state = set_state; + ctrl.get_state = get_state; + ctrl.get_parameter_count = get_parameter_count; + ctrl.get_parameter_info = get_parameter_info; + ctrl.get_parameter_string_for_value = get_parameter_string_for_value; + ctrl.get_parameter_value_for_string = get_parameter_value_for_string; + ctrl.normalised_parameter_to_plain = normalised_parameter_to_plain; + ctrl.plain_parameter_to_normalised = plain_parameter_to_normalised; + ctrl.get_parameter_normalised = get_parameter_normalised; + ctrl.set_parameter_normalised = set_parameter_normalised; + ctrl.set_component_handler = set_component_handler; + ctrl.create_view = create_view; + } + + // ---------------------------------------------------------------------------------------------------------------- + // v3_funknown + + static v3_result V3_API query_interface_edit_controller(void* self, const v3_tuid iid, void** iface) + { + if (v3_tuid_match(iid, v3_funknown_iid)) + { + *iface = self; + return V3_OK; + } + + if (v3_tuid_match(iid, v3_plugin_base_iid)) + { + *iface = self; + return V3_OK; + } + + if (v3_tuid_match(iid, v3_edit_controller_iid)) + { + *iface = self; + return V3_OK; + } + + dpf_edit_controller* const controller = *(dpf_edit_controller**)self; + DISTRHO_SAFE_ASSERT_RETURN(controller != nullptr, V3_NO_INTERFACE); + +#if DISTRHO_PLUGIN_HAS_UI + if (v3_tuid_match(iid, v3_connection_point_iid)) + { + if (controller->connectionComp == nullptr) + controller->connectionComp = new dpf_dsp_connection_point(kConnectionPointController, + controller->vst3); + else + ++controller->connectionComp->refcounter; + *iface = &controller->connectionComp; + return V3_OK; + } +#endif + +#if DISTRHO_PLUGIN_WANT_MIDI_INPUT + if (v3_tuid_match(iid, v3_midi_mapping_iid)) + { + static dpf_midi_mapping midi_mapping; + static dpf_midi_mapping* midi_mapping_ptr = &midi_mapping; + *iface = &midi_mapping_ptr; + return V3_OK; + } +#endif + + *iface = NULL; + return V3_NO_INTERFACE; + } + + // ---------------------------------------------------------------------------------------------------------------- + // v3_plugin_base + + static v3_result V3_API initialize(void* self, v3_plugin_base::v3_funknown** context) + { + d_stdout("dpf_edit_controller::initialize => %p %p", self, context); + dpf_edit_controller* const controller = *(dpf_edit_controller**)self; + DISTRHO_SAFE_ASSERT_RETURN(controller != nullptr, V3_NOT_INITIALIZED); + + const bool initialized = controller->initialized; + DISTRHO_SAFE_ASSERT_RETURN(! initialized, V3_INVALID_ARG); + + // query for host context + v3_host_application** host = nullptr; + v3_cpp_obj_query_interface(context, v3_host_application_iid, &host); + + // save it for later so we can unref it + controller->hostContextFromInitialize = host; + + controller->initialized = true; + return V3_OK; + } + + static v3_result V3_API terminate(void* self) + { + d_stdout("dpf_edit_controller::terminate => %p", self); + dpf_edit_controller* const controller = *(dpf_edit_controller**)self; + DISTRHO_SAFE_ASSERT_RETURN(controller != nullptr, V3_NOT_INITIALIZED); + + const bool initialized = controller->initialized; + DISTRHO_SAFE_ASSERT_RETURN(initialized, V3_INVALID_ARG); + + controller->initialized = false; + +#if DISTRHO_PLUGIN_HAS_UI + // take the chance to do some cleanup if possible (we created the bridge, we need to destroy it) + if (controller->connectionBridge != nullptr) + if (controller->connectionBridge->refcounter == 1) + controller->connectionBridge = nullptr; + + if (controller->connectionComp != nullptr && controller->connectionComp->shortcircuit) + if (controller->connectionComp->refcounter == 1) + controller->connectionComp = nullptr; +#endif + + if (controller->hostContextFromInitialize != nullptr) + { + v3_cpp_obj_unref(controller->hostContextFromInitialize); + controller->hostContextFromInitialize = nullptr; + } + + return V3_OK; + } + + // ---------------------------------------------------------------------------------------------------------------- + // v3_edit_controller + + static v3_result V3_API set_component_state(void* self, v3_bstream* stream) + { + d_stdout("dpf_edit_controller::set_component_state => %p %p", self, stream); + dpf_edit_controller* const controller = *(dpf_edit_controller**)self; + DISTRHO_SAFE_ASSERT_RETURN(controller != nullptr, V3_NOT_INITIALIZED); + + PluginVst3* const vst3 = controller->vst3; + DISTRHO_SAFE_ASSERT_RETURN(vst3 != nullptr, V3_NOT_INITIALIZED); + +#if 0 + return vst3->setComponentState(stream); +#endif + + // TODO, returning ok to make renoise happy + return V3_OK; + } + + static v3_result V3_API set_state(void* self, v3_bstream* stream) + { + d_stdout("dpf_edit_controller::set_state => %p %p", self, stream); + dpf_edit_controller* const controller = *(dpf_edit_controller**)self; + DISTRHO_SAFE_ASSERT_RETURN(controller != nullptr, V3_NOT_INITIALIZED); + + PluginVst3* const vst3 = controller->vst3; + DISTRHO_SAFE_ASSERT_RETURN(vst3 != nullptr, V3_NOT_INITIALIZED); + +#if 0 + return vst3->setState(stream); +#endif + return V3_NOT_IMPLEMENTED; + } + + static v3_result V3_API get_state(void* self, v3_bstream* stream) + { + d_stdout("dpf_edit_controller::get_state => %p %p", self, stream); + dpf_edit_controller* const controller = *(dpf_edit_controller**)self; + DISTRHO_SAFE_ASSERT_RETURN(controller != nullptr, V3_NOT_INITIALIZED); + + PluginVst3* const vst3 = controller->vst3; + DISTRHO_SAFE_ASSERT_RETURN(vst3 != nullptr, V3_NOT_INITIALIZED); + +#if 0 + return vst3->getState(stream); +#endif + return V3_NOT_IMPLEMENTED; + } + + static int32_t V3_API get_parameter_count(void* self) + { + // d_stdout("dpf_edit_controller::get_parameter_count => %p", self); + dpf_edit_controller* const controller = *(dpf_edit_controller**)self; + DISTRHO_SAFE_ASSERT_RETURN(controller != nullptr, V3_NOT_INITIALIZED); + + PluginVst3* const vst3 = controller->vst3; + DISTRHO_SAFE_ASSERT_RETURN(vst3 != nullptr, V3_NOT_INITIALIZED); + + return vst3->getParameterCount(); + } + + static v3_result V3_API get_parameter_info(void* self, int32_t param_idx, v3_param_info* param_info) + { + // d_stdout("dpf_edit_controller::get_parameter_info => %p %i", self, param_idx); + dpf_edit_controller* const controller = *(dpf_edit_controller**)self; + DISTRHO_SAFE_ASSERT_RETURN(controller != nullptr, V3_NOT_INITIALIZED); + + PluginVst3* const vst3 = controller->vst3; + DISTRHO_SAFE_ASSERT_RETURN(vst3 != nullptr, V3_NOT_INITIALIZED); + + return vst3->getParameterInfo(param_idx, param_info); + } + + static v3_result V3_API get_parameter_string_for_value(void* self, v3_param_id index, double normalised, v3_str_128 output) + { + // NOTE very noisy, called many times + // d_stdout("dpf_edit_controller::get_parameter_string_for_value => %p %u %f %p", self, index, normalised, output); + dpf_edit_controller* const controller = *(dpf_edit_controller**)self; + DISTRHO_SAFE_ASSERT_RETURN(controller != nullptr, V3_NOT_INITIALIZED); + + PluginVst3* const vst3 = controller->vst3; + DISTRHO_SAFE_ASSERT_RETURN(vst3 != nullptr, V3_NOT_INITIALIZED); + + return vst3->getParameterStringForValue(index, normalised, output); + } + + static v3_result V3_API get_parameter_value_for_string(void* self, v3_param_id index, int16_t* input, double* output) + { + d_stdout("dpf_edit_controller::get_parameter_value_for_string => %p %u %p %p", self, index, input, output); + dpf_edit_controller* const controller = *(dpf_edit_controller**)self; + DISTRHO_SAFE_ASSERT_RETURN(controller != nullptr, V3_NOT_INITIALIZED); + + PluginVst3* const vst3 = controller->vst3; + DISTRHO_SAFE_ASSERT_RETURN(vst3 != nullptr, V3_NOT_INITIALIZED); + + return vst3->getParameterValueForString(index, input, output); + } + + static double V3_API normalised_parameter_to_plain(void* self, v3_param_id index, double normalised) + { + d_stdout("dpf_edit_controller::normalised_parameter_to_plain => %p %u %f", self, index, normalised); + dpf_edit_controller* const controller = *(dpf_edit_controller**)self; + DISTRHO_SAFE_ASSERT_RETURN(controller != nullptr, V3_NOT_INITIALIZED); + + PluginVst3* const vst3 = controller->vst3; + DISTRHO_SAFE_ASSERT_RETURN(vst3 != nullptr, V3_NOT_INITIALIZED); + + return vst3->normalisedParameterToPlain(index, normalised); + } + + static double V3_API plain_parameter_to_normalised(void* self, v3_param_id index, double plain) + { + d_stdout("dpf_edit_controller::plain_parameter_to_normalised => %p %u %f", self, index, plain); + dpf_edit_controller* const controller = *(dpf_edit_controller**)self; + DISTRHO_SAFE_ASSERT_RETURN(controller != nullptr, V3_NOT_INITIALIZED); + + PluginVst3* const vst3 = controller->vst3; + DISTRHO_SAFE_ASSERT_RETURN(vst3 != nullptr, V3_NOT_INITIALIZED); + + return vst3->plainParameterToNormalised(index, plain); + } + + static double V3_API get_parameter_normalised(void* self, v3_param_id index) + { + dpf_edit_controller* const controller = *(dpf_edit_controller**)self; + DISTRHO_SAFE_ASSERT_RETURN(controller != nullptr, 0.0); + + PluginVst3* const vst3 = controller->vst3; + DISTRHO_SAFE_ASSERT_RETURN(vst3 != nullptr, 0.0); + + return vst3->getParameterNormalized(index); + } + + static v3_result V3_API set_parameter_normalised(void* self, v3_param_id index, double normalised) + { + dpf_edit_controller* const controller = *(dpf_edit_controller**)self; + DISTRHO_SAFE_ASSERT_RETURN(controller != nullptr, V3_NOT_INITIALIZED); + + PluginVst3* const vst3 = controller->vst3; + DISTRHO_SAFE_ASSERT_RETURN(vst3 != nullptr, V3_NOT_INITIALIZED); + + return vst3->setParameterNormalized(index, normalised); + } + + static v3_result V3_API set_component_handler(void* self, v3_component_handler** handler) + { + d_stdout("dpf_edit_controller::set_component_handler => %p %p", self, handler); + dpf_edit_controller* const controller = *(dpf_edit_controller**)self; + DISTRHO_SAFE_ASSERT_RETURN(controller != nullptr, V3_NOT_INITIALIZED); + + controller->handler = handler; + + if (PluginVst3* const vst3 = controller->vst3) + return vst3->setComponentHandler(handler); + + return V3_NOT_INITIALIZED; + } + + static v3_plugin_view** V3_API create_view(void* self, const char* name) + { + d_stdout("dpf_edit_controller::create_view => %p %s", self, name); + dpf_edit_controller* const controller = *(dpf_edit_controller**)self; + DISTRHO_SAFE_ASSERT_RETURN(controller != nullptr, nullptr); + +#if DISTRHO_PLUGIN_HAS_UI + // plugin must be initialized + PluginVst3* const vst3 = controller->vst3; + DISTRHO_SAFE_ASSERT_RETURN(vst3 != nullptr, nullptr); + + // we require a host context for message creation + v3_host_application** host = controller->hostContextFromInitialize != nullptr + ? controller->hostContextFromInitialize + : controller->hostContextFromFactory; + DISTRHO_SAFE_ASSERT_RETURN(host != nullptr, nullptr); + + // if there is a component connection, we require it to be active + if (controller->connectionComp != nullptr) + { + DISTRHO_SAFE_ASSERT_RETURN(controller->connectionComp->other != nullptr, nullptr); + } + // otherwise short-circuit and deal with this ourselves (assume local usage) + else + { + controller->connectionComp = new dpf_dsp_connection_point(kConnectionPointController, + controller->vst3); + controller->connectionComp->shortcircuit = true; + } + + v3_plugin_view** const view = dpf_plugin_view_create(host, + vst3->getInstancePointer(), + vst3->getSampleRate()); + DISTRHO_SAFE_ASSERT_RETURN(view != nullptr, nullptr); + + v3_connection_point** uiconn = nullptr; + if (v3_cpp_obj_query_interface(view, v3_connection_point_iid, &uiconn) == V3_OK) + { + d_stdout("view connection query ok %p | shortcircuit %d", + uiconn, controller->connectionComp->shortcircuit); + + v3_connection_point** const ctrlconn = (v3_connection_point**)&controller->connectionComp; + + if (controller->connectionComp->shortcircuit) + { + vst3->disconnect(); + + v3_cpp_obj(uiconn)->connect(uiconn, ctrlconn); + v3_cpp_obj(ctrlconn)->connect(ctrlconn, uiconn); + + vst3->connect(ctrlconn); + } + else + { + controller->connectionBridge = new dpf_dsp_connection_point(kConnectionPointBridge, + controller->vst3); + + v3_connection_point** const bridgeconn = (v3_connection_point**)&controller->connectionBridge; + v3_cpp_obj(uiconn)->connect(uiconn, bridgeconn); + v3_cpp_obj(bridgeconn)->connect(bridgeconn, uiconn); + + controller->connectionComp->bridge = bridgeconn; + controller->connectionBridge->bridge = ctrlconn; + } + } + + return view; +#else + return nullptr; +#endif + } +}; + +// -------------------------------------------------------------------------------------------------------------------- +// dpf_process_context_requirements + +struct dpf_process_context_requirements : v3_process_context_requirements_cpp { + dpf_process_context_requirements() + { + // v3_funknown, static + query_interface = query_interface_process_context_requirements; + ref = dpf_static_ref; + unref = dpf_static_unref; + + // v3_process_context_requirements + req.get_process_context_requirements = get_process_context_requirements; + } + + // ---------------------------------------------------------------------------------------------------------------- + // v3_funknown + + static v3_result V3_API query_interface_process_context_requirements(void* self, const v3_tuid iid, void** iface) + { + if (v3_tuid_match(iid, v3_funknown_iid)) + { + *iface = self; + return V3_OK; + } + + if (v3_tuid_match(iid, v3_process_context_requirements_iid)) + { + *iface = self; + return V3_OK; + } + + *iface = NULL; + return V3_NO_INTERFACE; + } + + // ---------------------------------------------------------------------------------------------------------------- + // v3_process_context_requirements + + static uint32_t V3_API get_process_context_requirements(void*) + { +#if DISTRHO_PLUGIN_WANT_TIMEPOS + return 0x0 + |V3_PROCESS_CTX_NEED_CONTINUOUS_TIME // V3_PROCESS_CTX_CONT_TIME_VALID + |V3_PROCESS_CTX_NEED_PROJECT_TIME // V3_PROCESS_CTX_PROJECT_TIME_VALID + |V3_PROCESS_CTX_NEED_TEMPO // V3_PROCESS_CTX_TEMPO_VALID + |V3_PROCESS_CTX_NEED_TIME_SIG // V3_PROCESS_CTX_TIME_SIG_VALID + |V3_PROCESS_CTX_NEED_TRANSPORT_STATE; // V3_PROCESS_CTX_PLAYING +#else + return 0x0; +#endif + } + + DISTRHO_PREVENT_HEAP_ALLOCATION +}; + +// -------------------------------------------------------------------------------------------------------------------- +// dpf_audio_processor + +struct dpf_audio_processor : v3_audio_processor_cpp { + std::atomic_int refcounter; + ScopedPointer& vst3; + + dpf_audio_processor(ScopedPointer& v) + : refcounter(1), + vst3(v) + { + // v3_funknown, single instance + query_interface = query_interface_audio_processor; + ref = dpf_single_instance_ref; + unref = dpf_single_instance_unref; + + // v3_audio_processor + proc.set_bus_arrangements = set_bus_arrangements; + proc.get_bus_arrangement = get_bus_arrangement; + proc.can_process_sample_size = can_process_sample_size; + proc.get_latency_samples = get_latency_samples; + proc.setup_processing = setup_processing; + proc.set_processing = set_processing; + proc.process = process; + proc.get_tail_samples = get_tail_samples; + } + + // ---------------------------------------------------------------------------------------------------------------- + // v3_funknown + + static v3_result V3_API query_interface_audio_processor(void* self, const v3_tuid iid, void** iface) + { + if (v3_tuid_match(iid, v3_funknown_iid)) + { + *iface = self; + return V3_OK; + } + + if (v3_tuid_match(iid, v3_audio_processor_iid)) + { + *iface = self; + return V3_OK; + } + + if (v3_tuid_match(iid, v3_process_context_requirements_iid)) + { + static dpf_process_context_requirements context_req; + static dpf_process_context_requirements* context_req_ptr = &context_req; + *iface = &context_req_ptr; + return V3_OK; + } + + *iface = NULL; + return V3_NO_INTERFACE; + } + + // ---------------------------------------------------------------------------------------------------------------- + // v3_audio_processor + + static v3_result V3_API set_bus_arrangements(void* self, + v3_speaker_arrangement* inputs, int32_t num_inputs, + v3_speaker_arrangement* outputs, int32_t num_outputs) + { + // NOTE this is called a bunch of times + // d_stdout("dpf_audio_processor::set_bus_arrangements => %p %p %i %p %i", self, inputs, num_inputs, outputs, num_outputs); + dpf_audio_processor* const processor = *(dpf_audio_processor**)self; + DISTRHO_SAFE_ASSERT_RETURN(processor != nullptr, V3_NOT_INITIALIZED); + + PluginVst3* const vst3 = processor->vst3; + DISTRHO_SAFE_ASSERT_RETURN(vst3 != nullptr, V3_NOT_INITIALIZED); + + return processor->vst3->setBusArrangements(inputs, num_inputs, outputs, num_outputs); + } -START_NAMESPACE_DISTRHO + static v3_result V3_API get_bus_arrangement(void* self, int32_t bus_direction, + int32_t idx, v3_speaker_arrangement* arr) + { + d_stdout("dpf_audio_processor::get_bus_arrangement => %p %i %i %p", self, bus_direction, idx, arr); + dpf_audio_processor* const processor = *(dpf_audio_processor**)self; + DISTRHO_SAFE_ASSERT_RETURN(processor != nullptr, V3_NOT_INITIALIZED); -#if ! DISTRHO_PLUGIN_WANT_MIDI_OUTPUT -static const writeMidiFunc writeMidiCallback = nullptr; -#endif -#if ! DISTRHO_PLUGIN_WANT_PARAMETER_VALUE_CHANGE_REQUEST -static const requestParameterValueChangeFunc requestParameterValueChangeCallback = nullptr; -#endif + PluginVst3* const vst3 = processor->vst3; + DISTRHO_SAFE_ASSERT_RETURN(vst3 != nullptr, V3_NOT_INITIALIZED); -// custom v3_tuid compatible type -typedef uint32_t dpf_tuid[4]; -static_assert(sizeof(v3_tuid) == sizeof(dpf_tuid), "uid size mismatch"); + return processor->vst3->getBusArrangement(bus_direction, idx, arr); + } -// custom uids, fully created during module init -static constexpr const uint32_t dpf_id_entry = d_cconst('D', 'P', 'F', ' '); -static constexpr const uint32_t dpf_id_clas = d_cconst('c', 'l', 'a', 's'); -static constexpr const uint32_t dpf_id_comp = d_cconst('c', 'o', 'm', 'p'); -static constexpr const uint32_t dpf_id_ctrl = d_cconst('c', 't', 'r', 'l'); -static constexpr const uint32_t dpf_id_proc = d_cconst('p', 'r', 'o', 'c'); -static constexpr const uint32_t dpf_id_view = d_cconst('v', 'i', 'e', 'w'); + static v3_result V3_API can_process_sample_size(void* self, int32_t symbolic_sample_size) + { + d_stdout("dpf_audio_processor::can_process_sample_size => %p %i", self, symbolic_sample_size); + return symbolic_sample_size == V3_SAMPLE_32 ? V3_OK : V3_NOT_IMPLEMENTED; + } -static dpf_tuid dpf_tuid_class = { dpf_id_entry, dpf_id_clas, 0, 0 }; -static dpf_tuid dpf_tuid_component = { dpf_id_entry, dpf_id_comp, 0, 0 }; -static dpf_tuid dpf_tuid_controller = { dpf_id_entry, dpf_id_ctrl, 0, 0 }; -static dpf_tuid dpf_tuid_processor = { dpf_id_entry, dpf_id_proc, 0, 0 }; -static dpf_tuid dpf_tuid_view = { dpf_id_entry, dpf_id_view, 0, 0 }; + static uint32_t V3_API get_latency_samples(void* self) + { + d_stdout("dpf_audio_processor::get_latency_samples => %p", self); + dpf_audio_processor* const processor = *(dpf_audio_processor**)self; + DISTRHO_SAFE_ASSERT_RETURN(processor != nullptr, 0); -// ----------------------------------------------------------------------- + PluginVst3* const vst3 = processor->vst3; + DISTRHO_SAFE_ASSERT_RETURN(vst3 != nullptr, 0); -void strncpy(char* const dst, const char* const src, const size_t size) -{ - DISTRHO_SAFE_ASSERT_RETURN(size > 0,); + return processor->vst3->getLatencySamples(); + } - if (const size_t len = std::min(std::strlen(src), size-1U)) + static v3_result V3_API setup_processing(void* self, v3_process_setup* setup) { - std::memcpy(dst, src, len); - dst[len] = '\0'; + d_stdout("dpf_audio_processor::setup_processing => %p", self); + dpf_audio_processor* const processor = *(dpf_audio_processor**)self; + DISTRHO_SAFE_ASSERT_RETURN(processor != nullptr, V3_NOT_INITIALIZED); + + PluginVst3* const vst3 = processor->vst3; + DISTRHO_SAFE_ASSERT_RETURN(vst3 != nullptr, V3_NOT_INITIALIZED); + + d_lastBufferSize = setup->max_block_size; + d_lastSampleRate = setup->sample_rate; + return processor->vst3->setupProcessing(setup); } - else + + static v3_result V3_API set_processing(void* self, v3_bool state) { - dst[0] = '\0'; + d_stdout("dpf_audio_processor::set_processing => %p %u", self, state); + dpf_audio_processor* const processor = *(dpf_audio_processor**)self; + DISTRHO_SAFE_ASSERT_RETURN(processor != nullptr, V3_NOT_INITIALIZED); + + PluginVst3* const vst3 = processor->vst3; + DISTRHO_SAFE_ASSERT_RETURN(vst3 != nullptr, V3_NOT_INITIALIZED); + + return processor->vst3->setProcessing(state); } -} -// ----------------------------------------------------------------------- + static v3_result V3_API process(void* self, v3_process_data* data) + { + // NOTE runs during RT + // d_stdout("dpf_audio_processor::process => %p", self); + dpf_audio_processor* const processor = *(dpf_audio_processor**)self; + DISTRHO_SAFE_ASSERT_RETURN(processor != nullptr, V3_NOT_INITIALIZED); -// v3_funknown, v3_plugin_base, v3_component + PluginVst3* const vst3 = processor->vst3; + DISTRHO_SAFE_ASSERT_RETURN(vst3 != nullptr, V3_NOT_INITIALIZED); -// audio_processor + return processor->vst3->process(data); + } -class PluginVst3 -{ -public: - PluginVst3() - : fPlugin(this, writeMidiCallback, requestParameterValueChangeCallback) + static uint32_t V3_API get_tail_samples(void* self) { + d_stdout("dpf_audio_processor::get_tail_samples => %p", self); + dpf_audio_processor* const processor = *(dpf_audio_processor**)self; + DISTRHO_SAFE_ASSERT_RETURN(processor != nullptr, 0); + + PluginVst3* const vst3 = processor->vst3; + DISTRHO_SAFE_ASSERT_RETURN(vst3 != nullptr, 0); + + return processor->vst3->getTailSamples(); } +}; -private: - // Plugin - PluginExporter fPlugin; +// -------------------------------------------------------------------------------------------------------------------- +// Store components that we can't delete properly, to be cleaned up on module unload - // VST3 stuff - // TODO +struct dpf_component; -#if DISTRHO_PLUGIN_WANT_PARAMETER_VALUE_CHANGE_REQUEST - bool requestParameterValueChange(uint32_t, float) +std::vector gComponentGarbage; + +static v3_result handleUncleanComponent(dpf_component** const componentptr) +{ + gComponentGarbage.push_back(componentptr); + return V3_INVALID_ARG; +} + +// -------------------------------------------------------------------------------------------------------------------- +// dpf_component + +struct dpf_component : v3_component_cpp { + std::atomic_int refcounter; + ScopedPointer processor; +#if DISTRHO_PLUGIN_HAS_UI + ScopedPointer connection; // kConnectionPointComponent +#endif + ScopedPointer controller; + ScopedPointer vst3; + v3_host_application** const hostContextFromFactory; + v3_host_application** hostContextFromInitialize; + + dpf_component(v3_host_application** const h) + : refcounter(1), + hostContextFromFactory(h), + hostContextFromInitialize(nullptr) { - // TODO - return true; + // v3_funknown, everything custom + query_interface = query_interface_component; + ref = ref_component; + unref = unref_component; + + // v3_plugin_base + base.initialize = initialize; + base.terminate = terminate; + + // v3_component + comp.get_controller_class_id = get_controller_class_id; + comp.set_io_mode = set_io_mode; + comp.get_bus_count = get_bus_count; + comp.get_bus_info = get_bus_info; + comp.get_routing_info = get_routing_info; + comp.activate_bus = activate_bus; + comp.set_active = set_active; + comp.set_state = set_state; + comp.get_state = get_state; } - static bool requestParameterValueChangeCallback(void* const ptr, const uint32_t index, const float value) + void cleanup() { - return ((PluginVst3*)ptr)->requestParameterValueChange(index, value); - } + vst3 = nullptr; + processor = nullptr; +#if DISTRHO_PLUGIN_HAS_UI + connection = nullptr; #endif + controller = nullptr; -#if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT - bool writeMidi(const MidiEvent& midiEvent) - { - // TODO - return true; + if (hostContextFromFactory != nullptr) + v3_cpp_obj_unref(hostContextFromFactory); } - static bool writeMidiCallback(void* ptr, const MidiEvent& midiEvent) + // ---------------------------------------------------------------------------------------------------------------- + // v3_funknown + + static v3_result V3_API query_interface_component(void* self, const v3_tuid iid, void** iface) { - return ((PluginVst3*)ptr)->writeMidi(midiEvent); - } -#endif + if (v3_tuid_match(iid, v3_funknown_iid)) + { + *iface = self; + return V3_OK; + } -}; + if (v3_tuid_match(iid, v3_plugin_base_iid)) + { + *iface = self; + return V3_OK; + } -// ----------------------------------------------------------------------- -// WIP this whole section still TODO + if (v3_tuid_match(iid, v3_component_iid)) + { + *iface = self; + return V3_OK; + } -struct ControllerComponent; -struct ProcessorComponent; + dpf_component* const component = *(dpf_component**)self; + DISTRHO_SAFE_ASSERT_RETURN(component != nullptr, V3_NO_INTERFACE); -struct ComponentAdapter : v3_funknown, v3_plugin_base -{ - // needs atomic refcount, starts at 1 + if (v3_tuid_match(iid, v3_audio_processor_iid)) + { + if (component->processor == nullptr) + component->processor = new dpf_audio_processor(component->vst3); + else + ++component->processor->refcounter; + *iface = &component->processor; + return V3_OK; + } + +#if DISTRHO_PLUGIN_HAS_UI + if (v3_tuid_match(iid, v3_connection_point_iid)) + { + if (component->connection == nullptr) + component->connection = new dpf_dsp_connection_point(kConnectionPointComponent, + component->vst3); + else + ++component->connection->refcounter; + *iface = &component->connection; + return V3_OK; + } +#endif + + if (v3_tuid_match(iid, v3_edit_controller_iid)) + { + if (component->controller == nullptr) + component->controller = new dpf_edit_controller(component->vst3, + component->hostContextFromFactory); + else + ++component->controller->refcounter; + *iface = &component->controller; + return V3_OK; + } - ComponentAdapter() + *iface = NULL; + return V3_NO_INTERFACE; + } + + static uint32_t V3_API ref_component(void* self) + { + return ++(*(dpf_component**)self)->refcounter; + } + + static uint32_t V3_API unref_component(void* self) { - static const uint8_t* kSupportedFactories[] = { - v3_funknown_iid, - v3_plugin_base_iid, - /* - v3_component_iid, - v3_edit_controller_iid, - v3_audio_processor_iid - */ - }; + dpf_component** const componentptr = (dpf_component**)self; + dpf_component* const component = *componentptr; + + if (const int refcount = --component->refcounter) + { + d_stdout("dpf_component::unref => %p | refcount %i", self, refcount); + return refcount; + } + + /** + * Some hosts will have unclean instances of a few of the component child classes at this point. + * We check for those here, going through the whole possible chain to see if it is safe to delete. + * If not, we add this component to the `gComponentGarbage` global which will take care of it during unload. + */ + + bool unclean = false; + if (dpf_audio_processor* const proc = component->processor) + { + if (const int refcount = proc->refcounter) + { + unclean = true; + d_stderr("DPF warning: asked to delete component while audio processor still active (refcount %d)", refcount); + } + } - // ------------------------------------------------------------------------------------------------------------ - // v3_funknown +#if DISTRHO_PLUGIN_HAS_UI + if (dpf_dsp_connection_point* const conn = component->connection) + { + if (const int refcount = conn->refcounter) + { + unclean = true; + d_stderr("DPF warning: asked to delete component while connection point still active (refcount %d)", refcount); + } + } +#endif - query_interface = []V3_API(void* self, const v3_tuid iid, void** iface) -> v3_result + if (dpf_edit_controller* const ctrl = component->controller) { - d_stdout("ComponentAdapter::query_interface %p %p %p", self, iid, iface); + if (const int refcount = ctrl->refcounter) + { + unclean = true; + d_stderr("DPF warning: asked to delete component while edit controller still active (refcount %d)", refcount); + } - *iface = NULL; - DISTRHO_SAFE_ASSERT_RETURN(self != nullptr, V3_NO_INTERFACE); +#if DISTRHO_PLUGIN_HAS_UI + if (dpf_dsp_connection_point* const comp = ctrl->connectionComp) + { + if (const int refcount = comp->refcounter) + { + unclean = true; + d_stderr("DPF warning: asked to delete component while edit controller connection point still active (refcount %d)", refcount); + } + } - for (const uint8_t* factory_iid : kSupportedFactories) + if (dpf_dsp_connection_point* const bridge = ctrl->connectionBridge) { - if (v3_tuid_match(factory_iid, iid)) + if (const int refcount = bridge->refcounter) { - *iface = self; - return V3_OK; + unclean = true; + d_stderr("DPF warning: asked to delete component while view bridge connection still active (refcount %d)", refcount); } } +#endif + } + + if (unclean) + return handleUncleanComponent(componentptr); - return V3_NO_INTERFACE; - }; + d_stdout("dpf_component::unref => %p | refcount is zero, deleting everything now!", self); - // TODO use atomic counter - ref = []V3_API(void*) -> uint32_t { return 1; }; - unref = []V3_API(void*) -> uint32_t { return 0; }; + if (component->hostContextFromFactory != nullptr) + v3_cpp_obj_unref(component->hostContextFromFactory); + + delete component; + delete componentptr; + return 0; } -}; -struct ControllerComponent : ComponentAdapter -{ -}; + // ---------------------------------------------------------------------------------------------------------------- + // v3_plugin_base -struct ProcessorComponent : ComponentAdapter -{ + static v3_result V3_API initialize(void* self, v3_plugin_base::v3_funknown** context) + { + d_stdout("dpf_component::initialize => %p %p", self, context); + dpf_component* const component = *(dpf_component**)self; + DISTRHO_SAFE_ASSERT_RETURN(component != nullptr, V3_NOT_INITIALIZED); + DISTRHO_SAFE_ASSERT_RETURN(component->vst3 == nullptr, V3_INVALID_ARG); + + // query for host context + v3_host_application** host = nullptr; + if (context != nullptr) + v3_cpp_obj_query_interface(context, v3_host_application_iid, &host); + + // save it for later so we can unref it + component->hostContextFromInitialize = host; + + // provide the factory context to the plugin if this new one is invalid + if (host == nullptr) + host = component->hostContextFromFactory; + + // default early values + if (d_lastBufferSize == 0) + d_lastBufferSize = 2048; + if (d_lastSampleRate <= 0.0) + d_lastSampleRate = 44100.0; + + d_lastCanRequestParameterValueChanges = true; + + component->vst3 = new PluginVst3(host); + return V3_OK; + } + + static v3_result V3_API terminate(void* self) + { + d_stdout("dpf_component::terminate => %p", self); + dpf_component* const component = *(dpf_component**)self; + DISTRHO_SAFE_ASSERT_RETURN(component != nullptr, V3_NOT_INITIALIZED); + DISTRHO_SAFE_ASSERT_RETURN(component->vst3 != nullptr, V3_INVALID_ARG); + + component->vst3 = nullptr; + + if (component->hostContextFromInitialize != nullptr) + { + v3_cpp_obj_unref(component->hostContextFromInitialize); + component->hostContextFromInitialize = nullptr; + } + + return V3_OK; + } + + // ---------------------------------------------------------------------------------------------------------------- + // v3_component + + static v3_result V3_API get_controller_class_id(void* self, v3_tuid class_id) + { + d_stdout("dpf_component::get_controller_class_id => %p %s", self, tuid2str(class_id)); + dpf_component* const component = *(dpf_component**)self; + DISTRHO_SAFE_ASSERT_RETURN(component != nullptr, V3_NOT_INITIALIZED); + + PluginVst3* const vst3 = component->vst3; + DISTRHO_SAFE_ASSERT_RETURN(vst3 != nullptr, V3_NOT_INITIALIZED); + + // TODO + return V3_NOT_IMPLEMENTED; + } + + static v3_result V3_API set_io_mode(void* self, int32_t io_mode) + { + d_stdout("dpf_component::set_io_mode => %p %i", self, io_mode); + dpf_component* const component = *(dpf_component**)self; + DISTRHO_SAFE_ASSERT_RETURN(component != nullptr, V3_NOT_INITIALIZED); + + PluginVst3* const vst3 = component->vst3; + DISTRHO_SAFE_ASSERT_RETURN(vst3 != nullptr, V3_NOT_INITIALIZED); + + // TODO + return V3_NOT_IMPLEMENTED; + } + + static int32_t V3_API get_bus_count(void* self, int32_t media_type, int32_t bus_direction) + { + // NOTE runs during RT + // d_stdout("dpf_component::get_bus_count => %p %i %i", self, media_type, bus_direction); + dpf_component* const component = *(dpf_component**)self; + DISTRHO_SAFE_ASSERT_RETURN(component != nullptr, V3_NOT_INITIALIZED); + + PluginVst3* const vst3 = component->vst3; + DISTRHO_SAFE_ASSERT_RETURN(vst3 != nullptr, V3_NOT_INITIALIZED); + + return vst3->getBusCount(media_type, bus_direction); + } + + static v3_result V3_API get_bus_info(void* self, int32_t media_type, int32_t bus_direction, + int32_t bus_idx, v3_bus_info* info) + { + d_stdout("dpf_component::get_bus_info => %p %i %i %i %p", self, media_type, bus_direction, bus_idx, info); + dpf_component* const component = *(dpf_component**)self; + DISTRHO_SAFE_ASSERT_RETURN(component != nullptr, V3_NOT_INITIALIZED); + + PluginVst3* const vst3 = component->vst3; + DISTRHO_SAFE_ASSERT_RETURN(vst3 != nullptr, V3_NOT_INITIALIZED); + + return vst3->getBusInfo(media_type, bus_direction, bus_idx, info); + } + + static v3_result V3_API get_routing_info(void* self, v3_routing_info* input, v3_routing_info* output) + { + d_stdout("dpf_component::get_routing_info => %p %p %p", self, input, output); + dpf_component* const component = *(dpf_component**)self; + DISTRHO_SAFE_ASSERT_RETURN(component != nullptr, V3_NOT_INITIALIZED); + + PluginVst3* const vst3 = component->vst3; + DISTRHO_SAFE_ASSERT_RETURN(vst3 != nullptr, V3_NOT_INITIALIZED); + + return vst3->getRoutingInfo(input, output); + } + + static v3_result V3_API activate_bus(void* self, int32_t media_type, int32_t bus_direction, + int32_t bus_idx, v3_bool state) + { + // NOTE this is called a bunch of times + // d_stdout("dpf_component::activate_bus => %p %i %i %i %u", self, media_type, bus_direction, bus_idx, state); + dpf_component* const component = *(dpf_component**)self; + DISTRHO_SAFE_ASSERT_RETURN(component != nullptr, V3_NOT_INITIALIZED); + + PluginVst3* const vst3 = component->vst3; + DISTRHO_SAFE_ASSERT_RETURN(vst3 != nullptr, V3_NOT_INITIALIZED); + + return vst3->activateBus(media_type, bus_direction, bus_idx, state); + } + + static v3_result V3_API set_active(void* self, v3_bool state) + { + d_stdout("dpf_component::set_active => %p %u", self, state); + dpf_component* const component = *(dpf_component**)self; + DISTRHO_SAFE_ASSERT_RETURN(component != nullptr, V3_NOT_INITIALIZED); + + PluginVst3* const vst3 = component->vst3; + DISTRHO_SAFE_ASSERT_RETURN(vst3 != nullptr, V3_NOT_INITIALIZED); + + return component->vst3->setActive(state); + } + + static v3_result V3_API set_state(void* self, v3_bstream** stream) + { + d_stdout("dpf_component::set_state => %p", self); + dpf_component* const component = *(dpf_component**)self; + DISTRHO_SAFE_ASSERT_RETURN(component != nullptr, V3_NOT_INITIALIZED); + + PluginVst3* const vst3 = component->vst3; + DISTRHO_SAFE_ASSERT_RETURN(vst3 != nullptr, V3_NOT_INITIALIZED); + + return vst3->setState(stream); + } + + static v3_result V3_API get_state(void* self, v3_bstream** stream) + { + d_stdout("dpf_component::get_state => %p %p", self, stream); + dpf_component* const component = *(dpf_component**)self; + DISTRHO_SAFE_ASSERT_RETURN(component != nullptr, V3_NOT_INITIALIZED); + + PluginVst3* const vst3 = component->vst3; + DISTRHO_SAFE_ASSERT_RETURN(vst3 != nullptr, V3_NOT_INITIALIZED); + + return vst3->getState(stream); + } }; // -------------------------------------------------------------------------------------------------------------------- // Dummy plugin to get data from -static ScopedPointer gPluginInfo; - -static void gPluginInit() +static const PluginExporter& _getPluginInfo() { - if (gPluginInfo != nullptr) - return; - d_lastBufferSize = 512; d_lastSampleRate = 44100.0; - gPluginInfo = new PluginExporter(nullptr, nullptr, nullptr); + d_lastCanRequestParameterValueChanges = true; + static const PluginExporter gPluginInfo(nullptr, nullptr, nullptr); d_lastBufferSize = 0; d_lastSampleRate = 0.0; + d_lastCanRequestParameterValueChanges = false; + + return gPluginInfo; +} + +static const PluginExporter& getPluginInfo() +{ + static const PluginExporter& info(_getPluginInfo()); + return info; +} + +static const char* getPluginCategories() +{ + static String categories; + static bool firstInit = true; + + if (firstInit) + { +#ifdef DISTRHO_PLUGIN_VST3_CATEGORIES + categories = DISTRHO_PLUGIN_VST3_CATEGORIES; +#elif DISTRHO_PLUGIN_IS_SYNTH + categories = "Instrument"; +#endif +#if (DISTRHO_PLUGIN_NUM_INPUTS == 0 || DISTRHO_PLUGIN_NUM_INPUTS == 1) && DISTRHO_PLUGIN_NUM_OUTPUTS == 1 + if (categories.isNotEmpty()) + categories += "|"; + categories += "Mono"; +#elif (DISTRHO_PLUGIN_NUM_INPUTS == 0 || DISTRHO_PLUGIN_NUM_INPUTS == 2) && DISTRHO_PLUGIN_NUM_OUTPUTS == 2 + if (categories.isNotEmpty()) + categories += "|"; + categories += "Stereo"; +#endif + firstInit = false; + } - dpf_tuid_class[3] = dpf_tuid_component[3] = dpf_tuid_controller[3] - = dpf_tuid_processor[3] = dpf_tuid_view[3] = gPluginInfo->getUniqueId(); + return categories.buffer(); +} + +static const char* getPluginVersion() +{ + static String version; + + if (version.isEmpty()) + { + const uint32_t versionNum = getPluginInfo().getVersion(); + + char versionBuf[64]; + snprintf(versionBuf, sizeof(versionBuf)-1, "%d.%d.%d", + (versionNum >> 16) & 0xff, + (versionNum >> 8) & 0xff, + (versionNum >> 0) & 0xff); + versionBuf[sizeof(versionBuf)-1] = '\0'; + version = versionBuf; + } + + return version.buffer(); } // -------------------------------------------------------------------------------------------------------------------- // dpf_factory -struct v3_plugin_factory_cpp : v3_funknown, v3_plugin_factory { - v3_plugin_factory_2 v2; - v3_plugin_factory_3 v3; -}; - struct dpf_factory : v3_plugin_factory_cpp { + // cached values + v3_funknown** hostContext; + dpf_factory() + : hostContext(nullptr) { - static const uint8_t* kSupportedFactories[] = { - v3_funknown_iid, - v3_plugin_factory_iid, - v3_plugin_factory_2_iid - }; + dpf_tuid_class[2] = dpf_tuid_component[2] = dpf_tuid_controller[2] + = dpf_tuid_processor[2] = dpf_tuid_view[2] = getPluginInfo().getUniqueId(); - // ------------------------------------------------------------------------------------------------------------ - // v3_funknown + // v3_funknown, static + query_interface = query_interface_factory; + ref = dpf_static_ref; + unref = dpf_static_unref; - query_interface = []V3_API(void* self, const v3_tuid iid, void** iface) -> v3_result - { - *iface = NULL; - DISTRHO_SAFE_ASSERT_RETURN(self != nullptr, V3_NO_INTERFACE); + // v3_plugin_factory + v1.get_factory_info = get_factory_info; + v1.num_classes = num_classes; + v1.get_class_info = get_class_info; + v1.create_instance = create_instance; - for (const uint8_t* factory_iid : kSupportedFactories) - { - if (v3_tuid_match(factory_iid, iid)) - { - *iface = self; - return V3_OK; - } - } + // v3_plugin_factory_2 + v2.get_class_info_2 = get_class_info_2; - return V3_NO_INTERFACE; - }; + // v3_plugin_factory_3 + v3.get_class_info_utf16 = get_class_info_utf16; + v3.set_host_context = set_host_context; + } - // we only support 1 plugin per binary, so don't have to care here - ref = []V3_API(void*) -> uint32_t { return 1; }; - unref = []V3_API(void*) -> uint32_t { return 0; }; + ~dpf_factory() + { + if (gComponentGarbage.size() == 0) + return; - // ------------------------------------------------------------------------------------------------------------ - // v3_plugin_factory + d_stdout("DPF notice: cleaning up previously undeleted components now"); + + for (std::vector::iterator it = gComponentGarbage.begin(); + it != gComponentGarbage.end(); ++it) + { + dpf_component** const componentptr = *it; + dpf_component* const component = *componentptr; + component->cleanup(); + delete component; + delete componentptr; + } + + gComponentGarbage.clear(); + } + + // ---------------------------------------------------------------------------------------------------------------- + // v3_funknown - get_factory_info = []V3_API(void*, struct v3_factory_info* const info) -> v3_result + static v3_result V3_API query_interface_factory(void* self, const v3_tuid iid, void** iface) + { + if (v3_tuid_match(iid, v3_funknown_iid)) { - DISTRHO_NAMESPACE::strncpy(info->vendor, gPluginInfo->getMaker(), sizeof(info->vendor)); - DISTRHO_NAMESPACE::strncpy(info->url, gPluginInfo->getHomePage(), sizeof(info->url)); - DISTRHO_NAMESPACE::strncpy(info->email, "", sizeof(info->email)); // TODO + *iface = self; return V3_OK; - }; + } - num_classes = []V3_API(void*) -> int32_t + if (v3_tuid_match(iid, v3_plugin_factory_iid)) { - return 1; - }; + *iface = self; + return V3_OK; + } - get_class_info = []V3_API(void*, int32_t /*idx*/, struct v3_class_info* const info) -> v3_result + if (v3_tuid_match(iid, v3_plugin_factory_2_iid)) { - memcpy(info->class_id, dpf_tuid_class, sizeof(v3_tuid)); - info->cardinality = 0x7FFFFFFF; - DISTRHO_NAMESPACE::strncpy(info->category, "Audio Module Class", sizeof(info->category)); - DISTRHO_NAMESPACE::strncpy(info->name, gPluginInfo->getName(), sizeof(info->name)); + *iface = self; return V3_OK; - }; + } - create_instance = []V3_API(void* self, const v3_tuid class_id, const v3_tuid iid, void** instance) -> v3_result + if (v3_tuid_match(iid, v3_plugin_factory_3_iid)) { - d_stdout("%s %i %p %p %p %p", __PRETTY_FUNCTION__, __LINE__, self, class_id, iid, instance); - DISTRHO_SAFE_ASSERT_RETURN(v3_tuid_match(class_id, *(v3_tuid*)&dpf_tuid_class) && - v3_tuid_match(iid, v3_component_iid), V3_NO_INTERFACE); + *iface = self; + return V3_OK; + } - *instance = nullptr; // new ComponentAdapter(); - return V3_INTERNAL_ERR; - }; + *iface = NULL; + return V3_NO_INTERFACE; + } - // ------------------------------------------------------------------------------------------------------------ - // v3_plugin_factory_2 + // ---------------------------------------------------------------------------------------------------------------- + // v3_plugin_factory - v2.get_class_info_2 = []V3_API(void*, int32_t /*idx*/, struct v3_class_info_2 *info) -> v3_result - { - // get_class_info - memcpy(info->class_id, dpf_tuid_class, sizeof(v3_tuid)); - info->cardinality = 0x7FFFFFFF; - DISTRHO_NAMESPACE::strncpy(info->category, "Audio Module Class", sizeof(info->category)); - DISTRHO_NAMESPACE::strncpy(info->name, gPluginInfo->getName(), sizeof(info->name)); - // get_class_info_2 - info->class_flags = 0; - DISTRHO_NAMESPACE::strncpy(info->sub_categories, "", sizeof(info->sub_categories)); // TODO - DISTRHO_NAMESPACE::strncpy(info->vendor, gPluginInfo->getMaker(), sizeof(info->vendor)); - DISTRHO_NAMESPACE::strncpy(info->version, "", sizeof(info->version)); // TODO - DISTRHO_NAMESPACE::strncpy(info->sdk_version, "Travesty", sizeof(info->sdk_version)); // TESTING use "VST 3.7" ? - return V3_OK; - }; + static v3_result V3_API get_factory_info(void*, v3_factory_info* const info) + { + std::memset(info, 0, sizeof(*info)); + DISTRHO_NAMESPACE::strncpy(info->vendor, getPluginInfo().getMaker(), ARRAY_SIZE(info->vendor)); + DISTRHO_NAMESPACE::strncpy(info->url, getPluginInfo().getHomePage(), ARRAY_SIZE(info->url)); + // DISTRHO_NAMESPACE::strncpy(info->email, "", ARRAY_SIZE(info->email)); // TODO + info->flags = 0x10; + return V3_OK; + } + + static int32_t V3_API num_classes(void*) + { + return 1; + } + + static v3_result V3_API get_class_info(void*, int32_t idx, v3_class_info* const info) + { + std::memset(info, 0, sizeof(*info)); + DISTRHO_SAFE_ASSERT_RETURN(idx == 0, V3_INVALID_ARG); + + std::memcpy(info->class_id, dpf_tuid_class, sizeof(v3_tuid)); + info->cardinality = 0x7FFFFFFF; + DISTRHO_NAMESPACE::strncpy(info->category, "Audio Module Class", ARRAY_SIZE(info->category)); + DISTRHO_NAMESPACE::strncpy(info->name, getPluginInfo().getName(), ARRAY_SIZE(info->name)); + return V3_OK; + } + + static v3_result V3_API create_instance(void* self, const v3_tuid class_id, const v3_tuid iid, void** instance) + { + d_stdout("dpf_factory::create_instance => %p %s %s %p", self, tuid2str(class_id), tuid2str(iid), instance); + DISTRHO_SAFE_ASSERT_RETURN(v3_tuid_match(class_id, *(const v3_tuid*)&dpf_tuid_class) && + v3_tuid_match(iid, v3_component_iid), V3_NO_INTERFACE); + + dpf_factory* const factory = *(dpf_factory**)self; + DISTRHO_SAFE_ASSERT_RETURN(factory != nullptr, V3_NOT_INITIALIZED); + + // query for host context + v3_host_application** host = nullptr; + if (factory->hostContext != nullptr) + v3_cpp_obj_query_interface(factory->hostContext, v3_host_application_iid, &host); + + dpf_component** const componentptr = new dpf_component*; + *componentptr = new dpf_component(host); + *instance = static_cast(componentptr); + return V3_OK; + } + + // ---------------------------------------------------------------------------------------------------------------- + // v3_plugin_factory_2 + + static v3_result V3_API get_class_info_2(void*, int32_t idx, v3_class_info_2* info) + { + std::memset(info, 0, sizeof(*info)); + DISTRHO_SAFE_ASSERT_RETURN(idx == 0, V3_INVALID_ARG); + + std::memcpy(info->class_id, dpf_tuid_class, sizeof(v3_tuid)); + info->cardinality = 0x7FFFFFFF; + DISTRHO_NAMESPACE::strncpy(info->category, "Audio Module Class", ARRAY_SIZE(info->category)); + DISTRHO_NAMESPACE::strncpy(info->name, getPluginInfo().getName(), ARRAY_SIZE(info->name)); + +#if DISTRHO_PLUGIN_WANT_DIRECT_ACCESS + info->class_flags = 0; +#else + info->class_flags = V3_DISTRIBUTABLE; +#endif + DISTRHO_NAMESPACE::strncpy(info->sub_categories, getPluginCategories(), ARRAY_SIZE(info->sub_categories)); + DISTRHO_NAMESPACE::strncpy(info->vendor, getPluginInfo().getMaker(), ARRAY_SIZE(info->vendor)); + DISTRHO_NAMESPACE::strncpy(info->version, getPluginVersion(), ARRAY_SIZE(info->version)); + DISTRHO_NAMESPACE::strncpy(info->sdk_version, "Travesty", ARRAY_SIZE(info->sdk_version)); + return V3_OK; + } + + // ------------------------------------------------------------------------------------------------------------ + // v3_plugin_factory_3 + + static v3_result V3_API get_class_info_utf16(void*, int32_t idx, v3_class_info_3* info) + { + std::memset(info, 0, sizeof(*info)); + DISTRHO_SAFE_ASSERT_RETURN(idx == 0, V3_INVALID_ARG); + + std::memcpy(info->class_id, dpf_tuid_class, sizeof(v3_tuid)); + info->cardinality = 0x7FFFFFFF; + DISTRHO_NAMESPACE::strncpy(info->category, "Audio Module Class", ARRAY_SIZE(info->category)); + DISTRHO_NAMESPACE::strncpy_utf16(info->name, getPluginInfo().getName(), ARRAY_SIZE(info->name)); + +#if DISTRHO_PLUGIN_WANT_DIRECT_ACCESS + info->class_flags = 0; +#else + info->class_flags = V3_DISTRIBUTABLE; +#endif + DISTRHO_NAMESPACE::strncpy(info->sub_categories, getPluginCategories(), ARRAY_SIZE(info->sub_categories)); + DISTRHO_NAMESPACE::strncpy_utf16(info->vendor, getPluginInfo().getMaker(), ARRAY_SIZE(info->vendor)); + DISTRHO_NAMESPACE::strncpy_utf16(info->version, getPluginVersion(), ARRAY_SIZE(info->version)); + DISTRHO_NAMESPACE::strncpy_utf16(info->sdk_version, "Travesty", ARRAY_SIZE(info->sdk_version)); + return V3_OK; + } + + static v3_result V3_API set_host_context(void* self, v3_funknown** context) + { + d_stdout("dpf_factory::set_host_context => %p %p", self, context); + dpf_factory* const factory = *static_cast(self); + factory->hostContext = context; + return V3_OK; } -}; -static const dpf_factory dpf_factory; + DISTRHO_PREVENT_HEAP_ALLOCATION +}; END_NAMESPACE_DISTRHO @@ -303,8 +3714,9 @@ const void* GetPluginFactory(void); const void* GetPluginFactory(void) { USE_NAMESPACE_DISTRHO; - static const struct v3_plugin_factory_2* const factory = (v3_plugin_factory_2*)&dpf_factory; - return &factory; + static const dpf_factory factory; + static const v3_funknown* const factoryptr = &factory; + return &factoryptr; } // -------------------------------------------------------------------------------------------------------------------- @@ -326,8 +3738,6 @@ bool ENTRYFNNAME(void*); bool ENTRYFNNAME(void*) { - USE_NAMESPACE_DISTRHO; - gPluginInit(); return true; } @@ -336,8 +3746,6 @@ bool EXITFNNAME(void); bool EXITFNNAME(void) { - USE_NAMESPACE_DISTRHO; - gPluginInfo = nullptr; return true; } diff --git a/dpf/distrho/src/DistrhoUIInternal.hpp b/dpf/distrho/src/DistrhoUIInternal.hpp index e1d143a..64148f8 100644 --- a/dpf/distrho/src/DistrhoUIInternal.hpp +++ b/dpf/distrho/src/DistrhoUIInternal.hpp @@ -91,7 +91,7 @@ public: g_nextScaleFactor = 0.0; g_nextBundlePath = nullptr; #else - // Leave context called in the PluginWindow constructor, see DistrhoUIPrivateData.hpp + // enter context called in the PluginWindow constructor, see DistrhoUIPrivateData.hpp uiData->window->leaveContext(); #endif UI::PrivateData::s_nextPrivateData = nullptr; @@ -234,6 +234,28 @@ public: // ------------------------------------------------------------------- +#if defined(DISTRHO_PLUGIN_TARGET_VST3) && (defined(DISTRHO_OS_MAC) || defined(DISTRHO_OS_WINDOWS)) + void addIdleCallbackForVST3(IdleCallback* const cb, const uint timerFrequencyInMs) + { + uiData->window->addIdleCallback(cb, timerFrequencyInMs); + } + + void removeIdleCallbackForVST3(IdleCallback* const cb) + { + uiData->window->removeIdleCallback(cb); + } + + void idleForVST3() + { + DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,); + + uiData->app.triggerIdleCallbacks(); + ui->uiIdle(); + } +#endif + + // ------------------------------------------------------------------- + void setWindowTitle(const char* const uiTitle) { uiData->window->setTitle(uiTitle); @@ -258,23 +280,25 @@ public: } #if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI - bool handlePluginKeyboard(const bool press, const uint key, const uint16_t mods) + bool handlePluginKeyboardVST2(const bool press, const uint key, const uint16_t mods) { - // TODO also trigger Character input event DGL_NAMESPACE::Widget::KeyboardEvent ev; + ev.mod = mods; ev.press = press; - ev.key = key; - ev.mod = mods; - return ui->onKeyboard(ev); - } + ev.key = key; - bool handlePluginSpecial(const bool press, const DGL_NAMESPACE::Key key, const uint16_t mods) - { - DGL_NAMESPACE::Widget::SpecialEvent ev; - ev.press = press; - ev.key = key; - ev.mod = mods; - return ui->onSpecial(ev); + const bool ret = ui->onKeyboard(ev); + + DGL_NAMESPACE::Widget::CharacterInputEvent cev; + cev.mod = mods; + cev.character = key; + + // if shift modifier is on, convert a-z -> A-Z for character input + if (key >= 'a' && key <= 'z' && (mods & DGL_NAMESPACE::kModifierShift) != 0) + cev.character -= 'a' - 'A'; + + ui->onCharacterInput(cev); + return ret; } #endif @@ -287,6 +311,15 @@ public: ui->uiScaleFactorChanged(scaleFactor); } +#if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI + void notifyFocusChanged(const bool focus) + { + DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,); + + ui->uiFocus(focus, DGL_NAMESPACE::kCrossingNormal); + } +#endif + void setSampleRate(const double sampleRate, const bool doCallback = false) { DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,); diff --git a/dpf/distrho/src/DistrhoUIPrivateData.hpp b/dpf/distrho/src/DistrhoUIPrivateData.hpp index bf50097..93a8a89 100644 --- a/dpf/distrho/src/DistrhoUIPrivateData.hpp +++ b/dpf/distrho/src/DistrhoUIPrivateData.hpp @@ -22,7 +22,7 @@ #if DISTRHO_PLUGIN_HAS_EXTERNAL_UI # include "../extra/Sleep.hpp" #else -# include "../../dgl/Application.hpp" +# include "../../dgl/src/ApplicationPrivateData.hpp" # include "../../dgl/src/WindowPrivateData.hpp" # include "../../dgl/src/pugl.hpp" #endif @@ -33,7 +33,13 @@ # define DISTRHO_UI_IS_STANDALONE 0 #endif -#if defined(DISTRHO_PLUGIN_TARGET_VST2) || defined(DISTRHO_PLUGIN_TARGET_VST3) +#if defined(DISTRHO_PLUGIN_TARGET_VST2) +# undef DISTRHO_UI_USER_RESIZABLE +# define DISTRHO_UI_USER_RESIZABLE 0 +#endif + +// TODO figure out how to detect host support +#if defined(DISTRHO_PLUGIN_TARGET_VST3) # undef DISTRHO_UI_USER_RESIZABLE # define DISTRHO_UI_USER_RESIZABLE 0 #endif @@ -92,6 +98,7 @@ struct PluginApplication // these are not needed void idle() {} void quit() {} + void triggerIdleCallbacks() {} DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(PluginApplication) }; @@ -113,6 +120,11 @@ public: setClassName(className); } + void triggerIdleCallbacks() + { + pData->triggerIdleCallbacks(); + } + DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(PluginApplication) }; #endif @@ -325,6 +337,15 @@ struct UI::PrivateData { # if (DISTRHO_PLUGIN_WANT_MIDI_OUTPUT || DISTRHO_PLUGIN_WANT_STATE) parameterOffset += 1; # endif +#endif + +#ifdef DISTRHO_PLUGIN_TARGET_VST3 +# if DISTRHO_PLUGIN_WANT_MIDI_INPUT + parameterOffset += 130 * 16; // all MIDI CCs plus aftertouch and pitchbend +# endif +# if DISTRHO_PLUGIN_WANT_PROGRAMS + parameterOffset += 1; +# endif #endif } diff --git a/dpf/distrho/src/DistrhoUIVST3.cpp b/dpf/distrho/src/DistrhoUIVST3.cpp new file mode 100644 index 0000000..0bd6ce0 --- /dev/null +++ b/dpf/distrho/src/DistrhoUIVST3.cpp @@ -0,0 +1,1189 @@ +/* + * DISTRHO Plugin Framework (DPF) + * Copyright (C) 2012-2021 Filipe Coelho + * + * Permission to use, copy, modify, and/or distribute this software for any purpose with + * or without fee is hereby granted, provided that the above copyright notice and this + * permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "DistrhoUIInternal.hpp" + +#include "travesty/edit_controller.h" +#include "travesty/host.h" +#include "travesty/view.h" + +#ifdef DISTRHO_PROPER_CPP11_SUPPORT +# include +#else +// quick and dirty std::atomic replacement for the things we need +namespace std { + struct atomic_int { + volatile int value; + explicit atomic_int(volatile int v) noexcept : value(v) {} + int operator++() volatile noexcept { return __atomic_add_fetch(&value, 1, __ATOMIC_RELAXED); } + int operator--() volatile noexcept { return __atomic_sub_fetch(&value, 1, __ATOMIC_RELAXED); } + operator int() volatile noexcept { return __atomic_load_n(&value, __ATOMIC_RELAXED); } + }; +}; +#endif + +/* TODO items: + * - mousewheel event + * - key down/up events + * - size constraints + * - host-side resize + * - oddities with init and size callback (triggered too early?) + */ + +#if !(defined(DISTRHO_OS_MAC) || defined(DISTRHO_OS_WINDOWS)) +# define DPF_VST3_USING_HOST_RUN_LOOP +#endif + +#ifndef DPF_VST3_TIMER_INTERVAL +# define DPF_VST3_TIMER_INTERVAL 16 /* ~60 fps */ +#endif + +START_NAMESPACE_DISTRHO + +// -------------------------------------------------------------------------------------------------------------------- + +#if ! DISTRHO_PLUGIN_WANT_MIDI_INPUT +static constexpr const sendNoteFunc sendNoteCallback = nullptr; +#endif +#if ! DISTRHO_PLUGIN_WANT_STATE +static constexpr const setStateFunc setStateCallback = nullptr; +#endif + +// -------------------------------------------------------------------------------------------------------------------- +// Utility functions (defined on plugin side) + +const char* tuid2str(const v3_tuid iid); +void strncpy_utf16(int16_t* dst, const char* src, size_t length); + +struct ScopedUTF16String { + int16_t* str; + ScopedUTF16String(const char* const s) noexcept; + ~ScopedUTF16String() noexcept; + operator int16_t*() const noexcept; +}; + +// -------------------------------------------------------------------------------------------------------------------- + +/** + * VST3 UI class. + * + * All the dynamic things from VST3 get implemented here, free of complex low-level VST3 pointer things. + * The UI is created during the "attach" view event, and destroyed during "removed". + * + * Note that DPF VST3 implementation works over the connection point interface, + * rather than using edit controller directly. + * This allows the UI to be running remotely from the DSP. + * + * The low-level VST3 stuff comes after. + */ +class UIVst3 +#if defined(DISTRHO_OS_MAC) || defined(DISTRHO_OS_WINDOWS) + : public IdleCallback +#endif +{ +public: + UIVst3(v3_plugin_view** const view, + v3_host_application** const host, + v3_connection_point** const connection, + v3_plugin_frame** const frame, + const intptr_t winId, + const float scaleFactor, + const double sampleRate, + void* const instancePointer) + : fView(view), + fHostContext(host), + fConnection(connection), + fFrame(frame), + fReadyForPluginData(false), + fScaleFactor(scaleFactor), + fUI(this, winId, sampleRate, + editParameterCallback, + setParameterCallback, + setStateCallback, + sendNoteCallback, + setSizeCallback, + nullptr, // TODO file request + nullptr, // bundlePath + instancePointer, + scaleFactor) + { +#if defined(DISTRHO_OS_MAC) || defined(DISTRHO_OS_WINDOWS) + fUI.addIdleCallbackForVST3(this, DPF_VST3_TIMER_INTERVAL); +#endif + } + + ~UIVst3() + { +#if defined(DISTRHO_OS_MAC) || defined(DISTRHO_OS_WINDOWS) + fUI.removeIdleCallbackForVST3(this); +#endif + if (fConnection != nullptr) + disconnect(); + } + + void reconnectIfNeeded() + { + if (fConnection != nullptr) + connect(fConnection); + } + + // ---------------------------------------------------------------------------------------------------------------- + // v3_plugin_view interface calls + + v3_result onWheel(float /*distance*/) + { + // TODO + return V3_NOT_IMPLEMENTED; + } + + v3_result onKeyDown(int16_t /*key_char*/, int16_t /*key_code*/, int16_t /*modifiers*/) + { + // TODO + return V3_NOT_IMPLEMENTED; + } + + v3_result onKeyUp(int16_t /*key_char*/, int16_t /*key_code*/, int16_t /*modifiers*/) + { + // TODO + return V3_NOT_IMPLEMENTED; + } + + v3_result getSize(v3_view_rect* const rect) const noexcept + { + std::memset(rect, 0, sizeof(v3_view_rect)); + + rect->right = fUI.getWidth(); + rect->bottom = fUI.getHeight(); +#ifdef DISTRHO_OS_MAC + const double scaleFactor = fUI.getScaleFactor(); + rect->right /= scaleFactor; + rect->bottom /= scaleFactor; +#endif + + return V3_OK; + } + + v3_result onSize(v3_view_rect* const /*rect*/) + { + // TODO + return V3_NOT_IMPLEMENTED; + } + + v3_result onFocus(const bool state) + { +#if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI + fUI.notifyFocusChanged(state); + return V3_OK; +#else + return V3_NOT_IMPLEMENTED; + // unused + (void)state; +#endif + } + + v3_result setFrame(v3_plugin_frame** const frame) noexcept + { + fFrame = frame; + return V3_OK; + } + + v3_result checkSizeConstraint(v3_view_rect* const /*rect*/) + { + // TODO + return V3_NOT_IMPLEMENTED; + } + + // ---------------------------------------------------------------------------------------------------------------- + // v3_connection_point interface calls + + void connect(v3_connection_point** const point) noexcept + { + DISTRHO_SAFE_ASSERT_RETURN(point != nullptr,); + + fConnection = point; + + d_stdout("requesting current plugin state"); + + v3_message** const message = createMessage("init"); + DISTRHO_SAFE_ASSERT_RETURN(message != nullptr,); + + v3_attribute_list** const attrlist = v3_cpp_obj(message)->get_attributes(message); + DISTRHO_SAFE_ASSERT_RETURN(attrlist != nullptr,); + + v3_cpp_obj(attrlist)->set_int(attrlist, "__dpf_msg_target__", 1); + v3_cpp_obj(fConnection)->notify(fConnection, message); + + v3_cpp_obj_unref(message); + } + + void disconnect() noexcept + { + DISTRHO_SAFE_ASSERT_RETURN(fConnection != nullptr,); + + d_stdout("reporting UI closed"); + fReadyForPluginData = false; + + v3_message** const message = createMessage("close"); + DISTRHO_SAFE_ASSERT_RETURN(message != nullptr,); + + v3_attribute_list** const attrlist = v3_cpp_obj(message)->get_attributes(message); + DISTRHO_SAFE_ASSERT_RETURN(attrlist != nullptr,); + + v3_cpp_obj(attrlist)->set_int(attrlist, "__dpf_msg_target__", 1); + v3_cpp_obj(fConnection)->notify(fConnection, message); + + v3_cpp_obj_unref(message); + + fConnection = nullptr; + } + + v3_result notify(v3_message** const message) + { + const char* const msgid = v3_cpp_obj(message)->get_message_id(message); + DISTRHO_SAFE_ASSERT_RETURN(msgid != nullptr, V3_INVALID_ARG); + + v3_attribute_list** const attrs = v3_cpp_obj(message)->get_attributes(message); + DISTRHO_SAFE_ASSERT_RETURN(attrs != nullptr, V3_INVALID_ARG); + + if (std::strcmp(msgid, "ready") == 0) + { + DISTRHO_SAFE_ASSERT_RETURN(! fReadyForPluginData, V3_INTERNAL_ERR); + fReadyForPluginData = true; + return V3_OK; + } + + if (std::strcmp(msgid, "parameter-set") == 0) + { + int64_t rindex; + double value; + v3_result res; + + res = v3_cpp_obj(attrs)->get_int(attrs, "rindex", &rindex); + DISTRHO_SAFE_ASSERT_INT_RETURN(res == V3_OK, res, res); + + res = v3_cpp_obj(attrs)->get_float(attrs, "value", &value); + DISTRHO_SAFE_ASSERT_INT_RETURN(res == V3_OK, res, res); + +#if DISTRHO_PLUGIN_WANT_PROGRAMS + if (rindex == 0) + { + DISTRHO_SAFE_ASSERT_RETURN(value >= 0.0, V3_INTERNAL_ERR); + + fUI.programLoaded(static_cast(value + 0.5)); + } + else +#endif + { + rindex -= fUI.getParameterOffset(); + DISTRHO_SAFE_ASSERT_RETURN(rindex >= 0, V3_INTERNAL_ERR); + + fUI.parameterChanged(static_cast(rindex), value); + } + + return V3_OK; + } + +#if DISTRHO_PLUGIN_WANT_STATE + if (std::strcmp(msgid, "state-set") == 0) + { + int16_t* key16; + int16_t* value16; + uint32_t keySize, valueSize; + v3_result res; + + res = v3_cpp_obj(attrs)->get_binary(attrs, "key", (const void**)&key16, &keySize); + DISTRHO_SAFE_ASSERT_INT_RETURN(res == V3_OK, res, res); + + res = v3_cpp_obj(attrs)->get_binary(attrs, "value", (const void**)&value16, &valueSize); + DISTRHO_SAFE_ASSERT_INT_RETURN(res == V3_OK, res, res); + + // do cheap inline conversion + char* const key = (char*)key16; + char* const value = (char*)value16; + + for (uint32_t i=0; iget_float(attrs, "value", &sampleRate); + DISTRHO_SAFE_ASSERT_INT_RETURN(res == V3_OK, res, res); + DISTRHO_SAFE_ASSERT_RETURN(sampleRate > 0, V3_INVALID_ARG); + + fUI.setSampleRate(sampleRate, true); + return V3_OK; + } + + d_stdout("UIVst3 received unknown msg '%s'", msgid); + + return V3_NOT_IMPLEMENTED; + } + + // ---------------------------------------------------------------------------------------------------------------- + // v3_plugin_view_content_scale_steinberg interface calls + + v3_result setContentScaleFactor(const float factor) + { + if (d_isEqual(fScaleFactor, factor)) + return V3_OK; + + fScaleFactor = factor; + fUI.notifyScaleFactorChanged(factor); + return V3_OK; + } + + // ---------------------------------------------------------------------------------------------------------------- + // special idle callback on macOS and Windows + +#if defined(DISTRHO_OS_MAC) || defined(DISTRHO_OS_WINDOWS) + void idleCallback() override + { + if (fReadyForPluginData) + { + fReadyForPluginData = false; + requestMorePluginData(); + } + + fUI.idleForVST3(); + } +#else + // ---------------------------------------------------------------------------------------------------------------- + // v3_timer_handler interface calls + + void onTimer() + { + if (fReadyForPluginData) + { + fReadyForPluginData = false; + requestMorePluginData(); + } + + fUI.plugin_idle(); + } +#endif + + // ---------------------------------------------------------------------------------------------------------------- + +private: + // VST3 stuff + v3_plugin_view** const fView; + v3_host_application** const fHostContext; + v3_connection_point** fConnection; + v3_plugin_frame** fFrame; + + // Temporary data + bool fReadyForPluginData; + float fScaleFactor; + + // Plugin UI (after VST3 stuff so the UI can call into us during its constructor) + UIExporter fUI; + + // ---------------------------------------------------------------------------------------------------------------- + // helper functions called during message passing + + v3_message** createMessage(const char* const id) const + { + DISTRHO_SAFE_ASSERT_RETURN(fHostContext != nullptr, nullptr); + + v3_tuid iid; + memcpy(iid, v3_message_iid, sizeof(v3_tuid)); + v3_message** msg = nullptr; + const v3_result res = v3_cpp_obj(fHostContext)->create_instance(fHostContext, iid, iid, (void**)&msg); + DISTRHO_SAFE_ASSERT_INT_RETURN(res == V3_TRUE, res, nullptr); + DISTRHO_SAFE_ASSERT_RETURN(msg != nullptr, nullptr); + + v3_cpp_obj(msg)->set_message_id(msg, id); + return msg; + } + + void requestMorePluginData() const + { + DISTRHO_SAFE_ASSERT_RETURN(fConnection != nullptr,); + + v3_message** const message = createMessage("idle"); + DISTRHO_SAFE_ASSERT_RETURN(message != nullptr,); + + v3_attribute_list** const attrlist = v3_cpp_obj(message)->get_attributes(message); + DISTRHO_SAFE_ASSERT_RETURN(attrlist != nullptr,); + + v3_cpp_obj(attrlist)->set_int(attrlist, "__dpf_msg_target__", 1); + v3_cpp_obj(fConnection)->notify(fConnection, message); + + v3_cpp_obj_unref(message); + } + + // ---------------------------------------------------------------------------------------------------------------- + // DPF callbacks + + void editParameter(const uint32_t rindex, const bool started) const + { + DISTRHO_SAFE_ASSERT_RETURN(fConnection != nullptr,); + + v3_message** const message = createMessage("parameter-edit"); + DISTRHO_SAFE_ASSERT_RETURN(message != nullptr,); + + v3_attribute_list** const attrlist = v3_cpp_obj(message)->get_attributes(message); + DISTRHO_SAFE_ASSERT_RETURN(attrlist != nullptr,); + + v3_cpp_obj(attrlist)->set_int(attrlist, "__dpf_msg_target__", 1); + v3_cpp_obj(attrlist)->set_int(attrlist, "rindex", rindex); + v3_cpp_obj(attrlist)->set_int(attrlist, "started", started ? 1 : 0); + v3_cpp_obj(fConnection)->notify(fConnection, message); + + v3_cpp_obj_unref(message); + } + + static void editParameterCallback(void* ptr, uint32_t rindex, bool started) + { + ((UIVst3*)ptr)->editParameter(rindex, started); + } + + void setParameterValue(const uint32_t rindex, const float realValue) + { + DISTRHO_SAFE_ASSERT_RETURN(fConnection != nullptr,); + + v3_message** const message = createMessage("parameter-set"); + DISTRHO_SAFE_ASSERT_RETURN(message != nullptr,); + + v3_attribute_list** const attrlist = v3_cpp_obj(message)->get_attributes(message); + DISTRHO_SAFE_ASSERT_RETURN(attrlist != nullptr,); + + v3_cpp_obj(attrlist)->set_int(attrlist, "__dpf_msg_target__", 1); + v3_cpp_obj(attrlist)->set_int(attrlist, "rindex", rindex); + v3_cpp_obj(attrlist)->set_float(attrlist, "value", realValue); + v3_cpp_obj(fConnection)->notify(fConnection, message); + + v3_cpp_obj_unref(message); + } + + static void setParameterCallback(void* ptr, uint32_t rindex, float value) + { + ((UIVst3*)ptr)->setParameterValue(rindex, value); + } + + void setSize(uint width, uint height) + { + DISTRHO_SAFE_ASSERT_RETURN(fView != nullptr,); + DISTRHO_SAFE_ASSERT_RETURN(fFrame != nullptr,); + d_stdout("from UI setSize %u %u | %p %p", width, height, fView, fFrame); + +#ifdef DISTRHO_OS_MAC + const double scaleFactor = fUI.getScaleFactor(); + width /= scaleFactor; + height /= scaleFactor; +#endif + + v3_view_rect rect; + std::memset(&rect, 0, sizeof(rect)); + rect.right = width; + rect.bottom = height; + v3_cpp_obj(fFrame)->resize_view(fFrame, fView, &rect); + } + + static void setSizeCallback(void* ptr, uint width, uint height) + { + ((UIVst3*)ptr)->setSize(width, height); + } + +#if DISTRHO_PLUGIN_WANT_MIDI_INPUT + void sendNote(const uint8_t channel, const uint8_t note, const uint8_t velocity) + { + DISTRHO_SAFE_ASSERT_RETURN(fConnection != nullptr,); + + v3_message** const message = createMessage("midi"); + DISTRHO_SAFE_ASSERT_RETURN(message != nullptr,); + + v3_attribute_list** const attrlist = v3_cpp_obj(message)->get_attributes(message); + DISTRHO_SAFE_ASSERT_RETURN(attrlist != nullptr,); + + uint8_t midiData[3]; + midiData[0] = (velocity != 0 ? 0x90 : 0x80) | channel; + midiData[1] = note; + midiData[2] = velocity; + + v3_cpp_obj(attrlist)->set_int(attrlist, "__dpf_msg_target__", 1); + v3_cpp_obj(attrlist)->set_binary(attrlist, "data", midiData, sizeof(midiData)); + v3_cpp_obj(fConnection)->notify(fConnection, message); + + v3_cpp_obj_unref(message); + } + + static void sendNoteCallback(void* ptr, uint8_t channel, uint8_t note, uint8_t velocity) + { + ((UIVst3*)ptr)->sendNote(channel, note, velocity); + } +#endif + +#if DISTRHO_PLUGIN_WANT_STATE + void setState(const char* const key, const char* const value) + { + DISTRHO_SAFE_ASSERT_RETURN(fConnection != nullptr,); + + v3_message** const message = createMessage("state-set"); + DISTRHO_SAFE_ASSERT_RETURN(message != nullptr,); + + v3_attribute_list** const attrlist = v3_cpp_obj(message)->get_attributes(message); + DISTRHO_SAFE_ASSERT_RETURN(attrlist != nullptr,); + + v3_cpp_obj(attrlist)->set_int(attrlist, "__dpf_msg_target__", 1); + v3_cpp_obj(attrlist)->set_string(attrlist, "key", ScopedUTF16String(key)); + v3_cpp_obj(attrlist)->set_string(attrlist, "value", ScopedUTF16String(value)); + v3_cpp_obj(fConnection)->notify(fConnection, message); + + v3_cpp_obj_unref(message); + } + + static void setStateCallback(void* ptr, const char* key, const char* value) + { + ((UIVst3*)ptr)->setState(key, value); + } +#endif +}; + +// -------------------------------------------------------------------------------------------------------------------- + +/** + * VST3 low-level pointer thingies follow, proceed with care. + */ + +// -------------------------------------------------------------------------------------------------------------------- +// v3_funknown for classes with a single instance + +template +static uint32_t V3_API dpf_single_instance_ref(void* self) +{ + return ++(*(T**)self)->refcounter; +} + +template +static uint32_t V3_API dpf_single_instance_unref(void* self) +{ + return --(*(T**)self)->refcounter; +} + +// -------------------------------------------------------------------------------------------------------------------- +// dpf_ui_connection_point + +struct dpf_ui_connection_point : v3_connection_point_cpp { + std::atomic_int refcounter; + ScopedPointer& uivst3; + v3_connection_point** other; + + dpf_ui_connection_point(ScopedPointer& v) + : refcounter(1), + uivst3(v), + other(nullptr) + { + // v3_funknown, single instance + query_interface = query_interface_connection_point; + ref = dpf_single_instance_ref; + unref = dpf_single_instance_unref; + + // v3_connection_point + point.connect = connect; + point.disconnect = disconnect; + point.notify = notify; + } + + // ---------------------------------------------------------------------------------------------------------------- + // v3_funknown + + static v3_result V3_API query_interface_connection_point(void* self, const v3_tuid iid, void** iface) + { + d_stdout("UI|query_interface_connection_point => %p", self); + + if (v3_tuid_match(iid, v3_funknown_iid)) + { + *iface = self; + return V3_OK; + } + + if (v3_tuid_match(iid, v3_connection_point_iid)) + { + *iface = self; + return V3_OK; + } + + *iface = NULL; + return V3_NO_INTERFACE; + } + + // ---------------------------------------------------------------------------------------------------------------- + // v3_connection_point + + static v3_result V3_API connect(void* self, v3_connection_point** other) + { + d_stdout("UI|dpf_ui_connection_point::connect => %p %p", self, other); + dpf_ui_connection_point* const point = *(dpf_ui_connection_point**)self; + DISTRHO_SAFE_ASSERT_RETURN(point != nullptr, V3_NOT_INITIALIZED); + DISTRHO_SAFE_ASSERT_RETURN(point->other == nullptr, V3_INVALID_ARG); + + point->other = other; + + if (UIVst3* const uivst3 = point->uivst3) + uivst3->connect(other); + + return V3_OK; + }; + + static v3_result V3_API disconnect(void* self, v3_connection_point** other) + { + d_stdout("UI|dpf_ui_connection_point::disconnect => %p %p", self, other); + dpf_ui_connection_point* const point = *(dpf_ui_connection_point**)self; + DISTRHO_SAFE_ASSERT_RETURN(point != nullptr, V3_NOT_INITIALIZED); + DISTRHO_SAFE_ASSERT_RETURN(point->other != nullptr, V3_INVALID_ARG); + + point->other = nullptr; + + if (UIVst3* const uivst3 = point->uivst3) + uivst3->disconnect(); + + return V3_OK; + }; + + static v3_result V3_API notify(void* self, v3_message** message) + { + dpf_ui_connection_point* const point = *(dpf_ui_connection_point**)self; + DISTRHO_SAFE_ASSERT_RETURN(point != nullptr, V3_NOT_INITIALIZED); + + UIVst3* const uivst3 = point->uivst3; + DISTRHO_SAFE_ASSERT_RETURN(uivst3 != nullptr, V3_NOT_INITIALIZED); + + return uivst3->notify(message); + } +}; + +// -------------------------------------------------------------------------------------------------------------------- +// dpf_plugin_view_content_scale + +struct dpf_plugin_view_content_scale : v3_plugin_view_content_scale_cpp { + std::atomic_int refcounter; + ScopedPointer& uivst3; + // cached values + float scaleFactor; + + dpf_plugin_view_content_scale(ScopedPointer& v) + : refcounter(1), + uivst3(v), + scaleFactor(0.0f) + { + // v3_funknown, single instance + query_interface = query_interface_view_content_scale; + ref = dpf_single_instance_ref; + unref = dpf_single_instance_unref; + + // v3_plugin_view_content_scale + scale.set_content_scale_factor = set_content_scale_factor; + } + + // ---------------------------------------------------------------------------------------------------------------- + // v3_funknown + + static v3_result V3_API query_interface_view_content_scale(void* self, const v3_tuid iid, void** iface) + { + if (v3_tuid_match(iid, v3_funknown_iid)) + { + *iface = self; + return V3_OK; + } + + if (v3_tuid_match(iid, v3_plugin_view_content_scale_iid)) + { + *iface = self; + return V3_OK; + } + + *iface = NULL; + return V3_NO_INTERFACE; + } + + // ---------------------------------------------------------------------------------------------------------------- + // v3_plugin_view_content_scale + + static v3_result V3_API set_content_scale_factor(void* self, float factor) + { + d_stdout("dpf_plugin_view::set_content_scale_factor => %p %f", self, factor); + dpf_plugin_view_content_scale* const scale = *(dpf_plugin_view_content_scale**)self; + DISTRHO_SAFE_ASSERT_RETURN(scale != nullptr, V3_NOT_INITIALIZED); + + scale->scaleFactor = factor; + + if (UIVst3* const uivst3 = scale->uivst3) + return uivst3->setContentScaleFactor(factor); + + return V3_NOT_INITIALIZED; + } +}; + +#ifdef DPF_VST3_USING_HOST_RUN_LOOP +// -------------------------------------------------------------------------------------------------------------------- +// dpf_timer_handler + +struct dpf_timer_handler : v3_timer_handler_cpp { + std::atomic_int refcounter; + ScopedPointer& uivst3; + bool valid; + + dpf_timer_handler(ScopedPointer& v) + : refcounter(1), + uivst3(v), + valid(true) + { + // v3_funknown, single instance + query_interface = query_interface_timer_handler; + ref = dpf_single_instance_ref; + unref = dpf_single_instance_unref; + + // v3_timer_handler + handler.on_timer = on_timer; + } + + // ---------------------------------------------------------------------------------------------------------------- + // v3_funknown + + static v3_result V3_API query_interface_timer_handler(void* self, const v3_tuid iid, void** iface) + { + if (v3_tuid_match(iid, v3_funknown_iid)) + { + *iface = self; + return V3_OK; + } + + if (v3_tuid_match(iid, v3_timer_handler_iid)) + { + *iface = self; + return V3_OK; + } + + *iface = NULL; + return V3_NO_INTERFACE; + } + + // ---------------------------------------------------------------------------------------------------------------- + // v3_timer_handler + + static void V3_API on_timer(void* self) + { + dpf_timer_handler* const handler = *(dpf_timer_handler**)self; + DISTRHO_SAFE_ASSERT_RETURN(handler != nullptr,); + DISTRHO_SAFE_ASSERT_RETURN(handler->valid,); + + handler->uivst3->onTimer(); + } +}; +#endif + +// -------------------------------------------------------------------------------------------------------------------- +// dpf_plugin_view + +static const char* const kSupportedPlatforms[] = { +#ifdef _WIN32 + V3_VIEW_PLATFORM_TYPE_HWND, +#elif defined(__APPLE__) + V3_VIEW_PLATFORM_TYPE_NSVIEW, +#else + V3_VIEW_PLATFORM_TYPE_X11, +#endif +}; + +struct dpf_plugin_view : v3_plugin_view_cpp { + std::atomic_int refcounter; + ScopedPointer connection; + ScopedPointer scale; +#ifdef DPF_VST3_USING_HOST_RUN_LOOP + ScopedPointer timer; +#endif + ScopedPointer uivst3; + // cached values + v3_host_application** const host; + void* const instancePointer; + double sampleRate; + v3_plugin_frame** frame; + + dpf_plugin_view(v3_host_application** const h, void* const instance, const double sr) + : refcounter(1), + host(h), + instancePointer(instance), + sampleRate(sr), + frame(nullptr) + { + // v3_funknown, everything custom + query_interface = query_interface_view; + ref = ref_view; + unref = unref_view; + + // v3_plugin_view + view.is_platform_type_supported = is_platform_type_supported; + view.attached = attached; + view.removed = removed; + view.on_wheel = on_wheel; + view.on_key_down = on_key_down; + view.on_key_up = on_key_up; + view.get_size = get_size; + view.on_size = on_size; + view.on_focus = on_focus; + view.set_frame = set_frame; + view.can_resize = can_resize; + view.check_size_constraint = check_size_constraint; + } + + // ---------------------------------------------------------------------------------------------------------------- + // v3_funknown + + static v3_result V3_API query_interface_view(void* self, const v3_tuid iid, void** iface) + { + d_stdout("dpf_plugin_view::query_interface => %p %s %p", self, tuid2str(iid), iface); + *iface = NULL; + DISTRHO_SAFE_ASSERT_RETURN(self != nullptr, V3_NO_INTERFACE); + + if (v3_tuid_match(iid, v3_funknown_iid)) + { + *iface = self; + return V3_OK; + } + + if (v3_tuid_match(iid, v3_plugin_view_iid)) + { + *iface = self; + return V3_OK; + } + + dpf_plugin_view* const view = *(dpf_plugin_view**)self; + DISTRHO_SAFE_ASSERT_RETURN(view != nullptr, V3_NO_INTERFACE); + + if (v3_tuid_match(v3_connection_point_iid, iid)) + { + if (view->connection == nullptr) + view->connection = new dpf_ui_connection_point(view->uivst3); + else + ++view->connection->refcounter; + *iface = &view->connection; + return V3_OK; + } + + if (v3_tuid_match(v3_plugin_view_content_scale_iid, iid)) + { + if (view->scale == nullptr) + view->scale = new dpf_plugin_view_content_scale(view->uivst3); + else + ++view->scale->refcounter; + *iface = &view->scale; + return V3_OK; + } + + return V3_NO_INTERFACE; + } + + static uint32_t V3_API ref_view(void* self) + { + return ++(*(dpf_plugin_view**)self)->refcounter; + } + + static uint32_t V3_API unref_view(void* self) + { + dpf_plugin_view** const viewptr = (dpf_plugin_view**)self; + dpf_plugin_view* const view = *viewptr; + + if (const int refcount = --view->refcounter) + { + d_stdout("dpf_plugin_view::unref => %p | refcount %i", self, refcount); + return refcount; + } + + d_stdout("dpf_plugin_view::unref => %p | refcount is zero, deleting everything now!", self); + + if (view->connection != nullptr && view->connection->other) + v3_cpp_obj(view->connection->other)->disconnect(view->connection->other, + (v3_connection_point**)&view->connection); + + if (dpf_ui_connection_point* const conn = view->connection) + { + if (const int refcount = conn->refcounter) + { + d_stderr("DPF warning: asked to delete view while connection point still active (refcount %d)", refcount); + return V3_INVALID_ARG; + } + } + + if (dpf_plugin_view_content_scale* const scale = view->scale) + { + if (const int refcount = scale->refcounter) + { + d_stderr("DPF warning: asked to delete view while content scale still active (refcount %d)", refcount); + return V3_INVALID_ARG; + } + } + + delete view; + delete viewptr; + return 0; + } + + // ---------------------------------------------------------------------------------------------------------------- + // v3_plugin_view + + static v3_result V3_API is_platform_type_supported(void* self, const char* platform_type) + { + d_stdout("dpf_plugin_view::is_platform_type_supported => %p %s", self, platform_type); + dpf_plugin_view* const view = *(dpf_plugin_view**)self; + DISTRHO_SAFE_ASSERT_RETURN(view != nullptr, V3_NOT_INITIALIZED); + + for (size_t i=0; i %p %p %s", self, parent, platform_type); + dpf_plugin_view* const view = *(dpf_plugin_view**)self; + DISTRHO_SAFE_ASSERT_RETURN(view != nullptr, V3_NOT_INITIALIZED); + DISTRHO_SAFE_ASSERT_RETURN(view->uivst3 == nullptr, V3_INVALID_ARG); + + for (size_t i=0; iframe != nullptr, V3_INVALID_ARG); + + v3_run_loop** runloop = nullptr; + v3_cpp_obj_query_interface(view->frame, v3_run_loop_iid, &runloop); + DISTRHO_SAFE_ASSERT_RETURN(runloop != nullptr, V3_INVALID_ARG); + #endif + + const float scaleFactor = view->scale != nullptr ? view->scale->scaleFactor : 0.0f; + view->uivst3 = new UIVst3((v3_plugin_view**)self, + view->host, + view->connection != nullptr ? view->connection->other : nullptr, + view->frame, + (uintptr_t)parent, + scaleFactor, + view->sampleRate, + view->instancePointer); + + view->uivst3->reconnectIfNeeded(); + + #ifdef DPF_VST3_USING_HOST_RUN_LOOP + // register a timer host run loop stuff + view->timer = new dpf_timer_handler(view->uivst3); + v3_cpp_obj(runloop)->register_timer(runloop, + (v3_timer_handler**)&view->timer, + DPF_VST3_TIMER_INTERVAL); + #endif + + return V3_OK; + } + } + + return V3_NOT_IMPLEMENTED; + } + + static v3_result V3_API removed(void* self) + { + d_stdout("dpf_plugin_view::removed => %p", self); + dpf_plugin_view* const view = *(dpf_plugin_view**)self; + DISTRHO_SAFE_ASSERT_RETURN(view != nullptr, V3_NOT_INITIALIZED); + DISTRHO_SAFE_ASSERT_RETURN(view->uivst3 != nullptr, V3_INVALID_ARG); + + #ifdef DPF_VST3_USING_HOST_RUN_LOOP + // unregister our timer as needed + if (view->timer != nullptr) + { + v3_run_loop** runloop = nullptr; + + if (view->frame != nullptr) + v3_cpp_obj_query_interface(view->frame, v3_run_loop_iid, &runloop); + + if (runloop != nullptr) + { + v3_cpp_obj(runloop)->unregister_timer(runloop, (v3_timer_handler**)&view->timer); + + // we query it 2 times in total, so lets unref 2 times as well + v3_cpp_obj_unref(runloop); + v3_cpp_obj_unref(runloop); + + if (const int refcount = --view->timer->refcounter) + { + view->timer->valid = false; + d_stderr("VST3 warning: Host run loop did not give away timer (refcount %d)", refcount); + } + else + { + view->timer = nullptr; + } + } + else + { + view->timer->valid = false; + d_stderr("VST3 warning: Host run loop not available during dpf_plugin_view::removed"); + } + } + #endif + + view->uivst3 = nullptr; + return V3_OK; + } + + static v3_result V3_API on_wheel(void* self, float distance) + { + d_stdout("dpf_plugin_view::on_wheel => %p %f", self, distance); + dpf_plugin_view* const view = *(dpf_plugin_view**)self; + DISTRHO_SAFE_ASSERT_RETURN(view != nullptr, V3_NOT_INITIALIZED); + + UIVst3* const uivst3 = view->uivst3; + DISTRHO_SAFE_ASSERT_RETURN(uivst3 != nullptr, V3_NOT_INITIALIZED); + + return uivst3->onWheel(distance); + } + + static v3_result V3_API on_key_down(void* self, int16_t key_char, int16_t key_code, int16_t modifiers) + { + d_stdout("dpf_plugin_view::on_key_down => %p %i %i %i", self, key_char, key_code, modifiers); + dpf_plugin_view* const view = *(dpf_plugin_view**)self; + DISTRHO_SAFE_ASSERT_RETURN(view != nullptr, V3_NOT_INITIALIZED); + + UIVst3* const uivst3 = view->uivst3; + DISTRHO_SAFE_ASSERT_RETURN(uivst3 != nullptr, V3_NOT_INITIALIZED); + + return uivst3->onKeyDown(key_char, key_code, modifiers); + } + + static v3_result V3_API on_key_up(void* self, int16_t key_char, int16_t key_code, int16_t modifiers) + { + d_stdout("dpf_plugin_view::on_key_up => %p %i %i %i", self, key_char, key_code, modifiers); + dpf_plugin_view* const view = *(dpf_plugin_view**)self; + DISTRHO_SAFE_ASSERT_RETURN(view != nullptr, V3_NOT_INITIALIZED); + + UIVst3* const uivst3 = view->uivst3; + DISTRHO_SAFE_ASSERT_RETURN(uivst3 != nullptr, V3_NOT_INITIALIZED); + + return uivst3->onKeyUp(key_char, key_code, modifiers); + } + + static v3_result V3_API get_size(void* self, v3_view_rect* rect) + { + d_stdout("dpf_plugin_view::get_size => %p", self); + dpf_plugin_view* const view = *(dpf_plugin_view**)self; + DISTRHO_SAFE_ASSERT_RETURN(view != nullptr, V3_NOT_INITIALIZED); + + if (UIVst3* const uivst3 = view->uivst3) + return uivst3->getSize(rect); + + // special case: allow UI to not be attached yet, as a way to get size before window creation + + const float scaleFactor = view->scale != nullptr ? view->scale->scaleFactor : 0.0f; + UIExporter tmpUI(nullptr, 0, view->sampleRate, + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, + view->instancePointer, scaleFactor); + rect->right = tmpUI.getWidth(); + rect->bottom = tmpUI.getHeight(); + return V3_OK; + } + + static v3_result V3_API on_size(void* self, v3_view_rect* rect) + { + d_stdout("dpf_plugin_view::on_size => %p %p", self, rect); + dpf_plugin_view* const view = *(dpf_plugin_view**)self; + DISTRHO_SAFE_ASSERT_RETURN(view != nullptr, V3_NOT_INITIALIZED); + + UIVst3* const uivst3 = view->uivst3; + DISTRHO_SAFE_ASSERT_RETURN(uivst3 != nullptr, V3_NOT_INITIALIZED); + + return uivst3->onSize(rect); + } + + static v3_result V3_API on_focus(void* self, v3_bool state) + { + d_stdout("dpf_plugin_view::on_focus => %p %u", self, state); + dpf_plugin_view* const view = *(dpf_plugin_view**)self; + DISTRHO_SAFE_ASSERT_RETURN(view != nullptr, V3_NOT_INITIALIZED); + + UIVst3* const uivst3 = view->uivst3; + DISTRHO_SAFE_ASSERT_RETURN(uivst3 != nullptr, V3_NOT_INITIALIZED); + + return uivst3->onFocus(state); + } + + static v3_result V3_API set_frame(void* self, v3_plugin_frame** frame) + { + d_stdout("dpf_plugin_view::set_frame => %p %p", self, frame); + dpf_plugin_view* const view = *(dpf_plugin_view**)self; + DISTRHO_SAFE_ASSERT_RETURN(view != nullptr, V3_NOT_INITIALIZED); + + view->frame = frame; + + if (UIVst3* const uivst3 = view->uivst3) + return uivst3->setFrame(frame); + + return V3_NOT_INITIALIZED; + } + + static v3_result V3_API can_resize(void* self) + { + d_stdout("dpf_plugin_view::can_resize => %p", self); +// #if DISTRHO_UI_USER_RESIZABLE +// return V3_OK; +// #else + return V3_NOT_IMPLEMENTED; +// #endif + } + + static v3_result V3_API check_size_constraint(void* self, v3_view_rect* rect) + { + d_stdout("dpf_plugin_view::check_size_constraint => %p %p", self, rect); + dpf_plugin_view* const view = *(dpf_plugin_view**)self; + DISTRHO_SAFE_ASSERT_RETURN(view != nullptr, V3_NOT_INITIALIZED); + + UIVst3* const uivst3 = view->uivst3; + DISTRHO_SAFE_ASSERT_RETURN(uivst3 != nullptr, V3_NOT_INITIALIZED); + + return uivst3->checkSizeConstraint(rect); + } +}; + +// -------------------------------------------------------------------------------------------------------------------- +// dpf_plugin_view_create (called from plugin side) + +v3_plugin_view** dpf_plugin_view_create(v3_host_application** host, void* instancePointer, double sampleRate); + +v3_plugin_view** dpf_plugin_view_create(v3_host_application** const host, + void* const instancePointer, + const double sampleRate) +{ + dpf_plugin_view** const viewptr = new dpf_plugin_view*; + *viewptr = new dpf_plugin_view(host, instancePointer, sampleRate); + return (v3_plugin_view**)static_cast(viewptr); +} + +// -------------------------------------------------------------------------------------------------------------------- + +END_NAMESPACE_DISTRHO diff --git a/dpf/distrho/src/dssi/seq_event-compat.h b/dpf/distrho/src/dssi/seq_event-compat.h index 6619c23..ec75f70 100644 --- a/dpf/distrho/src/dssi/seq_event-compat.h +++ b/dpf/distrho/src/dssi/seq_event-compat.h @@ -1,3 +1,13 @@ +/** + * \file include/seq_event.h + * \brief Application interface library for the ALSA driver + * \author Jaroslav Kysela + * \author Abramo Bagnara + * \author Takashi Iwai + * \date 1998-2001 + * + * Application interface library for the ALSA driver + */ /* * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as diff --git a/dpf/distrho/src/lv2/atom-helpers.h b/dpf/distrho/src/lv2/atom-helpers.h deleted file mode 100644 index 97cef2c..0000000 --- a/dpf/distrho/src/lv2/atom-helpers.h +++ /dev/null @@ -1,249 +0,0 @@ -// lv2_atom_helpers.h -// -/**************************************************************************** - Copyright (C) 2005-2013, rncbc aka Rui Nuno Capela. All rights reserved. - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License - as published by the Free Software Foundation; either version 2 - of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -*****************************************************************************/ - -/* Helper functions for LV2 atom:Sequence event buffer. - * - * tentatively adapted from: - * - * - lv2_evbuf.h,c - An abstract/opaque LV2 event buffer implementation. - * - * - event-helpers.h - Helper functions for the LV2 Event extension. - * - * - * Copyright 2008-2012 David Robillard - */ - -#ifndef LV2_ATOM_HELPERS_H -#define LV2_ATOM_HELPERS_H - -#include -#include -#include -#include -#include - -#include "atom-util.h" - -#ifdef __cplusplus -extern "C" { -#endif - -// An abstract/opaque LV2 atom:Sequence buffer. -// -typedef -struct _LV2_Atom_Buffer -{ - uint32_t capacity; - uint32_t chunk_type; - uint32_t sequence_type; - LV2_Atom_Sequence atoms; - -} LV2_Atom_Buffer; - - -// Clear and initialize an existing LV2 atom:Sequenece buffer. -// -static inline -void lv2_atom_buffer_reset ( LV2_Atom_Buffer *buf, bool input ) -{ - if (input) { - buf->atoms.atom.size = sizeof(LV2_Atom_Sequence_Body); - buf->atoms.atom.type = buf->sequence_type; - } else { - buf->atoms.atom.size = buf->capacity; - buf->atoms.atom.type = buf->chunk_type; - } -} - - -// Allocate a new, empty LV2 atom:Sequence buffer. -// -static inline -LV2_Atom_Buffer *lv2_atom_buffer_new ( - uint32_t capacity, uint32_t chunk_type, uint32_t sequence_type, bool input ) -{ - LV2_Atom_Buffer *buf = (LV2_Atom_Buffer *) - malloc(sizeof(LV2_Atom_Buffer) + sizeof(LV2_Atom_Sequence) + capacity); - - buf->capacity = capacity; - buf->chunk_type = chunk_type; - buf->sequence_type = sequence_type; - - lv2_atom_buffer_reset(buf, input); - - return buf; -} - - -// Free an LV2 atom:Sequenece buffer allocated with lv2_atome_buffer_new. -// -static inline -void lv2_atom_buffer_free ( LV2_Atom_Buffer *buf ) -{ - free(buf); -} - - -// Return the total padded size of events stored in a LV2 atom:Sequence buffer. -// -static inline -uint32_t lv2_atom_buffer_get_size ( LV2_Atom_Buffer *buf ) -{ - if (buf->atoms.atom.type == buf->sequence_type) - return buf->atoms.atom.size - uint32_t(sizeof(LV2_Atom_Sequence_Body)); - else - return 0; -} - - -// Return the actual LV2 atom:Sequence implementation. -// -static inline -LV2_Atom_Sequence *lv2_atom_buffer_get_sequence ( LV2_Atom_Buffer *buf ) -{ - return &buf->atoms; -} - - -// An iterator over an atom:Sequence buffer. -// -typedef -struct _LV2_Atom_Buffer_Iterator -{ - LV2_Atom_Buffer *buf; - uint32_t offset; - -} LV2_Atom_Buffer_Iterator; - - -// Reset an iterator to point to the start of an LV2 atom:Sequence buffer. -// -static inline -bool lv2_atom_buffer_begin ( - LV2_Atom_Buffer_Iterator *iter, LV2_Atom_Buffer *buf ) -{ - iter->buf = buf; - iter->offset = 0; - - return (buf->atoms.atom.size > 0); -} - - -// Reset an iterator to point to the end of an LV2 atom:Sequence buffer. -// -static inline -bool lv2_atom_buffer_end ( - LV2_Atom_Buffer_Iterator *iter, LV2_Atom_Buffer *buf ) -{ - iter->buf = buf; - iter->offset = lv2_atom_pad_size(lv2_atom_buffer_get_size(buf)); - - return (iter->offset < buf->capacity - sizeof(LV2_Atom_Event)); -} - - -// Check if a LV2 atom:Sequenece buffer iterator is valid. -// -static inline -bool lv2_atom_buffer_is_valid ( LV2_Atom_Buffer_Iterator *iter ) -{ - return iter->offset < lv2_atom_buffer_get_size(iter->buf); -} - - -// Advance a LV2 atom:Sequenece buffer iterator forward one event. -// -static inline -bool lv2_atom_buffer_increment ( LV2_Atom_Buffer_Iterator *iter ) -{ - if (!lv2_atom_buffer_is_valid(iter)) - return false; - - LV2_Atom_Buffer *buf = iter->buf; - LV2_Atom_Sequence *atoms = &buf->atoms; - uint32_t size = ((LV2_Atom_Event *) ((char *) - LV2_ATOM_CONTENTS(LV2_Atom_Sequence, atoms) + iter->offset))->body.size; - iter->offset += lv2_atom_pad_size(uint32_t(sizeof(LV2_Atom_Event)) + size); - - return true; -} - - -// Get the event currently pointed at a LV2 atom:Sequence buffer iterator. -// -static inline -LV2_Atom_Event *lv2_atom_buffer_get ( - LV2_Atom_Buffer_Iterator *iter, uint8_t **data ) -{ - if (!lv2_atom_buffer_is_valid(iter)) - return NULL; - - LV2_Atom_Buffer *buf = iter->buf; - LV2_Atom_Sequence *atoms = &buf->atoms; - LV2_Atom_Event *ev = (LV2_Atom_Event *) ((char *) - LV2_ATOM_CONTENTS(LV2_Atom_Sequence, atoms) + iter->offset); - - *data = (uint8_t *) LV2_ATOM_BODY(&ev->body); - - return ev; -} - - -// Write an event at a LV2 atom:Sequence buffer iterator. - -static inline -bool lv2_atom_buffer_write ( - LV2_Atom_Buffer_Iterator *iter, - uint32_t frames, - uint32_t /*subframes*/, - uint32_t type, - uint32_t size, - const uint8_t *data ) -{ - LV2_Atom_Buffer *buf = iter->buf; - LV2_Atom_Sequence *atoms = &buf->atoms; - if (buf->capacity - sizeof(LV2_Atom) - atoms->atom.size - < sizeof(LV2_Atom_Event) + size) - return false; - - LV2_Atom_Event *ev = (LV2_Atom_Event*) ((char *) - LV2_ATOM_CONTENTS(LV2_Atom_Sequence, atoms) + iter->offset); - - ev->time.frames = frames; - ev->body.type = type; - ev->body.size = size; - - memcpy(LV2_ATOM_BODY(&ev->body), data, size); - - size = lv2_atom_pad_size(uint32_t(sizeof(LV2_Atom_Event)) + size); - atoms->atom.size += size; - iter->offset += size; - - return true; -} - -#ifdef __cplusplus -} /* extern "C" */ -#endif - -#endif // LV2_ATOM_HELPERS_H - -// end of lv2_atom_helpers.h diff --git a/dpf/distrho/src/lv2/lv2-midifunctions.h b/dpf/distrho/src/lv2/lv2-midifunctions.h deleted file mode 100644 index d068f49..0000000 --- a/dpf/distrho/src/lv2/lv2-midifunctions.h +++ /dev/null @@ -1,98 +0,0 @@ -/**************************************************************************** - - lv2-midifunctions.h - support file for using MIDI in LV2 plugins - - Copyright (C) 2006 Lars Luthman - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 01222-1307 USA - -****************************************************************************/ - -#ifndef LV2_MIDIFUNCTIONS -#define LV2_MIDIFUNCTIONS - -#include "lv2-miditype.h" - -#ifdef __cplusplus -extern "C" { -#endif - -typedef struct { - LV2_MIDI* midi; - uint32_t frame_count; - uint32_t position; -} LV2_MIDIState; - - -inline double lv2midi_get_event(LV2_MIDIState* state, - double* timestamp, - uint32_t* size, - unsigned char** data) { - - if (state->position >= state->midi->size) { - state->position = state->midi->size; - *timestamp = state->frame_count; - *size = 0; - *data = NULL; - return *timestamp; - } - - *timestamp = *(double*)(state->midi->data + state->position); - *size = (uint32_t)*(size_t*)(state->midi->data + state->position + sizeof(double)); - *data = state->midi->data + state->position + - sizeof(double) + sizeof(size_t); - return *timestamp; -} - - -inline double lv2midi_step(LV2_MIDIState* state) { - - if (state->position >= state->midi->size) { - state->position = state->midi->size; - return state->frame_count; - } - - state->position += (uint32_t)sizeof(double); - size_t size = *(size_t*)(state->midi->data + state->position); - state->position += (uint32_t)sizeof(size_t); - state->position += (uint32_t)size; - return *(double*)(state->midi->data + state->position); -} - - -inline void lv2midi_put_event(LV2_MIDIState* state, - double timestamp, - uint32_t size, - const unsigned char* data) { - - if (state->midi->size + sizeof(double) + sizeof(size_t) + size < state->midi->capacity) - { - *((double*)(state->midi->data + state->midi->size)) = timestamp; - state->midi->size += (uint32_t)sizeof(double); - *((size_t*)(state->midi->data + state->midi->size)) = size; - state->midi->size += (uint32_t)sizeof(size_t); - memcpy(state->midi->data + state->midi->size, data, size); - - state->midi->size += size; - state->midi->event_count++; - } -} - -#ifdef __cplusplus -} /* extern "C" */ -#endif - -#endif - diff --git a/dpf/distrho/src/lv2/lv2-miditype.h b/dpf/distrho/src/lv2/lv2-miditype.h deleted file mode 100644 index 7afcae8..0000000 --- a/dpf/distrho/src/lv2/lv2-miditype.h +++ /dev/null @@ -1,175 +0,0 @@ -/**************************************************************************** - - lv2-miditype.h - header file for using MIDI in LV2 plugins - - Copyright (C) 2006 Lars Luthman - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 01222-1307 USA - -****************************************************************************/ - -#ifndef LV2_MIDITYPE_H -#define LV2_MIDITYPE_H - -#ifdef __cplusplus -extern "C" { -#endif - -/** This data structure is used to contain the MIDI events for one run() - cycle. The port buffer for a LV2 port that has the datatype - should be a pointer - to an instance of this struct. - - To store two Note On events on MIDI channel 0 in a buffer, with timestamps - 12 and 35.5, you could use something like this code (assuming that - midi_data is a variable of type LV2_MIDI): - @code - - size_t buffer_offset = 0; - *(double*)(midi_data->data + buffer_offset) = 12; - buffer_offset += sizeof(double); - *(size_t*)(midi_data->data + buffer_offset) = 3; - buffer_offset += sizeof(size_t); - midi_data->data[buffer_offset++] = 0x90; - midi_data->data[buffer_offset++] = 0x48; - midi_data->data[buffer_offset++] = 0x64; - ++midi_data->event_count; - - *(double*)(midi_data->data + buffer_offset) = 35.5; - buffer_offset += sizeof(double); - *(size_t*)(midi_data->data + buffer_offset) = 3; - buffer_offset += sizeof(size_t); - midi_data->data[buffer_offset++] = 0x90; - midi_data->data[buffer_offset++] = 0x55; - midi_data->data[buffer_offset++] = 0x64; - ++midi_data->event_count; - - midi_data->size = buffer_offset; - - @endcode - - This would be done by the host in the case of an input port, and by the - plugin in the case of an output port. Whoever is writing events to the - buffer must also take care not to exceed the capacity of the data buffer. - - To read events from a buffer, you could do something like this: - @code - - size_t buffer_offset = 0; - uint32_t i; - for (i = 0; i < midi_data->event_count; ++i) { - double timestamp = *(double*)(midi_data->data + buffer_offset); - buffer_offset += sizeof(double); - size_t size = *(size_t*)(midi_data->data + buffer_offset); - buffer_offset += sizeof(size_t); - do_something_with_event(timestamp, size, - midi_data->data + buffer_offset); - buffer_offset += size; - } - - @endcode -*/ -typedef struct { - - /** The number of MIDI events in the data buffer. - INPUT PORTS: It's the host's responsibility to set this field to the - number of MIDI events contained in the data buffer before calling the - plugin's run() function. The plugin may not change this field. - OUTPUT PORTS: It's the plugin's responsibility to set this field to the - number of MIDI events it has stored in the data buffer before returning - from the run() function. Any initial value should be ignored by the - plugin. - */ - uint32_t event_count; - - /** The size of the data buffer in bytes. It is set by the host and may not - be changed by the plugin. The host is allowed to change this between - run() calls. - */ - uint32_t capacity; - - /** The size of the initial part of the data buffer that actually contains - data. - INPUT PORTS: It's the host's responsibility to set this field to the - number of bytes used by all MIDI events it has written to the buffer - (including timestamps and size fields) before calling the plugin's - run() function. The plugin may not change this field. - OUTPUT PORTS: It's the plugin's responsibility to set this field to - the number of bytes used by all MIDI events it has written to the - buffer (including timestamps and size fields) before returning from - the run() function. Any initial value should be ignored by the plugin. - */ - uint32_t size; - - /** The data buffer that is used to store MIDI events. The events are packed - after each other, and the format of each event is as follows: - - First there is a timestamp, which should have the type "double", - i.e. have the same bit size as a double and the same bit layout as a - double (whatever that is on the current platform). This timestamp gives - the offset from the beginning of the current cycle, in frames, that - the MIDI event occurs on. It must be strictly smaller than the 'nframes' - parameter to the current run() call. The MIDI events in the buffer must - be ordered by their timestamp, e.g. an event with a timestamp of 123.23 - must be stored after an event with a timestamp of 65.0. - - The second part of the event is a size field, which should have the type - "size_t" (as defined in the standard C header stddef.h). It should - contain the size of the MIDI data for this event, i.e. the number of - bytes used to store the actual MIDI event. The bytes used by the - timestamp and the size field should not be counted. - - The third part of the event is the actual MIDI data. There are some - requirements that must be followed: - - * Running status is not allowed. Every event must have its own status - byte. - * Note On events with velocity 0 are not allowed. These events are - equivalent to Note Off in standard MIDI streams, but in order to make - plugins and hosts easier to write, as well as more efficient, only - proper Note Off events are allowed as Note Off. - * "Realtime events" (status bytes 0xF8 to 0xFF) are allowed, but may not - occur inside other events like they are allowed to in hardware MIDI - streams. - * All events must be fully contained in a single data buffer, i.e. events - may not "wrap around" by storing the first few bytes in one buffer and - then wait for the next run() call to store the rest of the event. If - there isn't enough space in the current data buffer to store an event, - the event will either have to wait until next run() call, be ignored, - or compensated for in some more clever way. - * All events must be valid MIDI events. This means for example that - only the first byte in each event (the status byte) may have the eighth - bit set, that Note On and Note Off events are always 3 bytes long etc. - The MIDI writer (host or plugin) is responsible for writing valid MIDI - events to the buffer, and the MIDI reader (plugin or host) can assume - that all events are valid. - - On a platform where double is 8 bytes and size_t is 4 bytes, the data - buffer layout for a 3-byte event followed by a 4-byte event may look - something like this: - _______________________________________________________________ - | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ... - |TIMESTAMP 1 |SIZE 1 |DATA |TIMESTAMP 2 |SIZE 2 |DATA | ... - - */ - unsigned char* data; - -} LV2_MIDI; - -#ifdef __cplusplus -} /* extern "C" */ -#endif - -#endif diff --git a/dpf/distrho/src/travesty/README.txt b/dpf/distrho/src/travesty/README.txt index af5808e..558dc70 100644 --- a/dpf/distrho/src/travesty/README.txt +++ b/dpf/distrho/src/travesty/README.txt @@ -1,9 +1,9 @@ -This folder contains a pure C interface to Steinberg VST3 SDK, codenamed "travesty". +This folder contains a pure C VST3-compatible interface, codenamed "travesty". Name is a play on words from morphing "vestige" (the good old free VST2 reverse-engineered header file) and "three". The main target is to be able to create VST3-compatible plugins without a bloated SDK. -Everything that is required for plugins fits in a few small header files. -Also, being able to build VST3-compatible plugins in pure C code, something not possible with the original SDK. +Everything that is required for plugins fits in a few small header files as presented here. +Also being able to build VST3-compatible plugins in pure C code, something not possible with the original SDK. Please note this project is still a work in progress. Use at your own risk, and please report any issues to https://github.com/DISTRHO/DPF/. diff --git a/dpf/distrho/src/travesty/align_pop.h b/dpf/distrho/src/travesty/align_pop.h index 729c066..50eaf26 100644 --- a/dpf/distrho/src/travesty/align_pop.h +++ b/dpf/distrho/src/travesty/align_pop.h @@ -1,5 +1,5 @@ /* - * travesty, pure C interface to steinberg VST3 SDK + * travesty, pure C VST3-compatible interface * Copyright (C) 2021 Filipe Coelho * * Permission to use, copy, modify, and/or distribute this software for any purpose with @@ -14,10 +14,25 @@ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#if defined(__APPLE__) +# if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wpragma-pack" +# elif defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__) >= 460 +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wunknown-warning-option" +# pragma GCC diagnostic ignored "-Wpragma-pack" +# endif +#endif + #if defined(__APPLE__) || defined(_WIN32) # pragma pack(pop) #endif #if defined(__APPLE__) -# pragma GCC diagnostic pop +# if defined(__clang__) +# pragma clang diagnostic pop +# elif defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__) >= 460 +# pragma GCC diagnostic pop +# endif #endif diff --git a/dpf/distrho/src/travesty/align_push.h b/dpf/distrho/src/travesty/align_push.h index 55855a2..fb6d91f 100644 --- a/dpf/distrho/src/travesty/align_push.h +++ b/dpf/distrho/src/travesty/align_push.h @@ -1,5 +1,5 @@ /* - * travesty, pure C interface to steinberg VST3 SDK + * travesty, pure C VST3-compatible interface * Copyright (C) 2021 Filipe Coelho * * Permission to use, copy, modify, and/or distribute this software for any purpose with @@ -16,9 +16,16 @@ #if defined(__APPLE__) -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wunknown-warning-option" -# pragma GCC diagnostic ignored "-Wpragma-pack" +# if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wunknown-warning-option" +# pragma clang diagnostic ignored "-Wpragma-pack" +# elif defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__) >= 460 +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wunknown-warning-option" +# pragma GCC diagnostic ignored "-Wpragma-pack" +# endif + # if defined(__LP64__) || defined(_LP64) # pragma pack(push, 16) # else diff --git a/dpf/distrho/src/travesty/audio_processor.h b/dpf/distrho/src/travesty/audio_processor.h index be1b1ad..85b2297 100644 --- a/dpf/distrho/src/travesty/audio_processor.h +++ b/dpf/distrho/src/travesty/audio_processor.h @@ -1,5 +1,5 @@ /* - * travesty, pure C interface to steinberg VST3 SDK + * travesty, pure C VST3-compatible interface * Copyright (C) 2021 Filipe Coelho * * Permission to use, copy, modify, and/or distribute this software for any purpose with @@ -28,8 +28,9 @@ typedef uint64_t v3_speaker_arrangement; enum { - V3_SPEAKER_L = 1, - V3_SPEAKER_R = 1 << 1 + V3_SPEAKER_L = 1 << 0, + V3_SPEAKER_R = 1 << 1, + V3_SPEAKER_M = 1 << 19 }; /** @@ -42,14 +43,19 @@ enum v3_process_mode { V3_OFFLINE }; -inline static const char * -v3_process_mode_str(int32_t d) +static inline +const char* v3_process_mode_str(int32_t d) { - switch (d) { - case V3_REALTIME: return "V3_REALTIME"; - case V3_PREFETCH: return "V3_PREFETCH"; - case V3_OFFLINE: return "V3_OFFLINE"; - default: return "[unknown]"; + switch (d) + { + case V3_REALTIME: + return "V3_REALTIME"; + case V3_PREFETCH: + return "V3_PREFETCH"; + case V3_OFFLINE: + return "V3_OFFLINE"; + default: + return "[unknown]"; } } @@ -58,13 +64,17 @@ enum { V3_SAMPLE_64 }; -inline static const char * -v3_sample_size_str(int32_t d) +static inline +const char* v3_sample_size_str(int32_t d) { - switch (d) { - case V3_SAMPLE_32: return "V3_SAMPLE_32"; - case V3_SAMPLE_64: return "V3_SAMPLE_64"; - default: return "[unknown]"; + switch (d) + { + case V3_SAMPLE_32: + return "V3_SAMPLE_32"; + case V3_SAMPLE_64: + return "V3_SAMPLE_64"; + default: + return "[unknown]"; } } @@ -82,31 +92,24 @@ struct v3_process_setup { struct v3_param_value_queue { struct v3_funknown; - V3_API v3_param_id (*get_param_id)(void *self); - V3_API int32_t (*get_point_count)(void *self); - - V3_API v3_result (*get_point) - (void *self, int32_t idx, int32_t *sample_offset, double *value); - V3_API v3_result (*add_point) - (void *self, int32_t sample_offset, double value, int32_t *idx); + v3_param_id (V3_API* get_param_id)(void* self); + int32_t (V3_API* get_point_count)(void* self); + v3_result (V3_API* get_point)(void* self, int32_t idx, int32_t* sample_offset, double* value); + v3_result (V3_API* add_point)(void* self, int32_t sample_offset, double value, int32_t* idx); }; -static const v3_tuid v3_param_value_queue_iid = +static constexpr const v3_tuid v3_param_value_queue_iid = V3_ID(0x01263A18, 0xED074F6F, 0x98C9D356, 0x4686F9BA); struct v3_param_changes { struct v3_funknown; - V3_API int32_t (*get_param_count)(void *self); - - V3_API struct v3_param_value_queue **(*get_param_data) - (void *self, int32_t idx); - - V3_API struct v3_param_value_queue **(*add_param_data) - (void *self, v3_param_id *id, int32_t *index); + int32_t (V3_API* get_param_count)(void* self); + struct v3_param_value_queue** (V3_API* get_param_data)(void* self, int32_t idx); + struct v3_param_value_queue** (V3_API* add_param_data)(void* self, v3_param_id* id, int32_t* index); }; -static const v3_tuid v3_param_changes_iid = +static constexpr const v3_tuid v3_param_changes_iid = V3_ID(0xA4779663, 0x0BB64A56, 0xB44384A8, 0x466FEB9D); /** @@ -121,55 +124,41 @@ struct v3_frame_rate { struct v3_chord { uint8_t key_note; uint8_t root_note; - int16_t chord_mask; }; enum { - V3_PROCESS_CTX_PLAYING = 1 << 1, - V3_PROCESS_CTX_CYCLE_ACTIVE = 1 << 2, - V3_PROCESS_CTX_RECORDING = 1 << 3, - + V3_PROCESS_CTX_PLAYING = 1 << 1, + V3_PROCESS_CTX_CYCLE_ACTIVE = 1 << 2, + V3_PROCESS_CTX_RECORDING = 1 << 3, V3_PROCESS_CTX_SYSTEM_TIME_VALID = 1 << 8, - V3_PROCESS_CTX_CONT_TIME_VALID = 1 << 17, V3_PROCESS_CTX_PROJECT_TIME_VALID = 1 << 9, - + V3_PROCESS_CTX_TEMPO_VALID = 1 << 10, V3_PROCESS_CTX_BAR_POSITION_VALID = 1 << 11, V3_PROCESS_CTX_CYCLE_VALID = 1 << 12, - - V3_PROCESS_CTX_TEMPO_VALID = 1 << 10, V3_PROCESS_CTX_TIME_SIG_VALID = 1 << 13, - V3_PROCESS_CTX_CHORD_VALID = 1 << 18, - V3_PROCESS_CTX_SMPTE_VALID = 1 << 14, - - V3_PROCESS_CTX_NEXT_CLOCK_VALID = 1 << 15 + V3_PROCESS_CTX_NEXT_CLOCK_VALID = 1 << 15, + V3_PROCESS_CTX_CONT_TIME_VALID = 1 << 17, + V3_PROCESS_CTX_CHORD_VALID = 1 << 18 }; struct v3_process_context { uint32_t state; - double sample_rate; int64_t project_time_in_samples; // with loop - int64_t system_time_ns; - int64_t continuous_time_in_samples; // without loop? unclear - + int64_t continuous_time_in_samples; // without loop double project_time_quarters; double bar_position_quarters; double cycle_start_quarters; double cycle_end_quarters; - double bpm; - int32_t time_sig_numerator; int32_t time_sig_denom; - struct v3_chord chord; - int32_t smpte_offset_subframes; struct v3_frame_rate frame_rate; - int32_t samples_to_next_clock; }; @@ -194,10 +183,10 @@ enum { struct v3_process_context_requirements { struct v3_funknown; - V3_API uint32_t (*get_process_context_requirements)(void *self); + uint32_t (V3_API* get_process_context_requirements)(void* self); }; -static const v3_tuid v3_process_context_requirements_iid = +static constexpr const v3_tuid v3_process_context_requirements_iid = V3_ID(0x2A654303, 0xEF764E3D, 0x95B5FE83, 0x730EF6D0); /** @@ -207,32 +196,25 @@ static const v3_tuid v3_process_context_requirements_iid = struct v3_audio_bus_buffers { int32_t num_channels; uint64_t channel_silence_bitset; - union { - float **channel_buffers_32; - double **channel_buffers_64; + float** channel_buffers_32; + double** channel_buffers_64; }; }; struct v3_process_data { int32_t process_mode; int32_t symbolic_sample_size; - int32_t nframes; - int32_t num_input_buses; int32_t num_output_buses; - - struct v3_audio_bus_buffers *inputs; - struct v3_audio_bus_buffers *outputs; - - struct v3_param_changes **input_params; - struct v3_param_changes **output_params; - - struct v3_event_list **input_events; - struct v3_event_list **output_events; - - struct v3_process_context *ctx; + struct v3_audio_bus_buffers* inputs; + struct v3_audio_bus_buffers* outputs; + struct v3_param_changes** input_params; + struct v3_param_changes** output_params; + struct v3_event_list** input_events; + struct v3_event_list** output_events; + struct v3_process_context* ctx; }; /** @@ -242,29 +224,42 @@ struct v3_process_data { struct v3_audio_processor { struct v3_funknown; - V3_API v3_result (*set_bus_arrangements) - (void *self, v3_speaker_arrangement *inputs, int32_t num_inputs, - v3_speaker_arrangement *outputs, int32_t num_outputs); - V3_API v3_result (*get_bus_arrangement) - (void *self, int32_t bus_direction, int32_t idx, v3_speaker_arrangement *); + v3_result (V3_API* set_bus_arrangements)(void* self, v3_speaker_arrangement* inputs, int32_t num_inputs, + v3_speaker_arrangement* outputs, int32_t num_outputs); + v3_result (V3_API* get_bus_arrangement)(void* self, int32_t bus_direction, int32_t idx, v3_speaker_arrangement*); + v3_result (V3_API* can_process_sample_size)(void* self, int32_t symbolic_sample_size); + uint32_t (V3_API* get_latency_samples)(void* self); + v3_result (V3_API* setup_processing)(void* self, struct v3_process_setup* setup); + v3_result (V3_API* set_processing)(void* self, v3_bool state); + v3_result (V3_API* process)(void* self, struct v3_process_data* data); + uint32_t (V3_API* get_tail_samples)(void* self); +}; - V3_API v3_result (*can_process_sample_size) - (void *self, int32_t symbolic_sample_size); +static constexpr const v3_tuid v3_audio_processor_iid = + V3_ID(0x42043F99, 0xB7DA453C, 0xA569E79D, 0x9AAEC33D); - V3_API uint32_t (*get_latency_samples)(void *self); +#ifdef __cplusplus - V3_API v3_result (*setup_processing) - (void *self, struct v3_process_setup *); - V3_API v3_result (*set_processing) - (void *self, v3_bool state); +/** + * C++ variants + */ - V3_API v3_result (*process) - (void *self, struct v3_process_data *); +struct v3_param_value_queue_cpp : v3_funknown { + v3_param_value_queue queue; +}; - V3_API uint32_t (*get_tail_samples)(void *self); +struct v3_param_changes_cpp : v3_funknown { + v3_param_changes changes; }; -static const v3_tuid v3_audio_processor_iid = - V3_ID(0x42043F99, 0xB7DA453C, 0xA569E79D, 0x9AAEC33D); +struct v3_process_context_requirements_cpp : v3_funknown { + v3_process_context_requirements req; +}; + +struct v3_audio_processor_cpp : v3_funknown { + v3_audio_processor proc; +}; + +#endif #include "align_pop.h" diff --git a/dpf/distrho/src/travesty/base.h b/dpf/distrho/src/travesty/base.h index 1da5530..417d306 100644 --- a/dpf/distrho/src/travesty/base.h +++ b/dpf/distrho/src/travesty/base.h @@ -1,5 +1,5 @@ /* - * travesty, pure C interface to steinberg VST3 SDK + * travesty, pure C VST3-compatible interface * Copyright (C) 2021 Filipe Coelho * * Permission to use, copy, modify, and/or distribute this software for any purpose with @@ -20,6 +20,39 @@ #include #include +/** + * deal with C vs C++ differences + */ + +#ifdef __cplusplus + +/** + * cast object into its proper C++ type. + * this is needed because `struct v3_funknown;` on a C++ class does not inherit `v3_funknown`'s fields. + * we can use this as a little helper for keeping both C and C++ compatiblity. + * specialized templated calls are defined where required + * (that is, object inherits from something other than `v3_funknown`) + * + * example usage: `v3_cpp_obj(obj)->method(obj, args...);` + */ +template static inline +constexpr T* v3_cpp_obj(T** obj) +{ + /** + * this ugly piece of code is required due to C++ assuming `reinterpret_cast` by default, + * but we need everything to be `static_cast` for it to be `constexpr` compatible. + */ + return static_cast(static_cast(static_cast(static_cast(*obj)) + sizeof(void*)*3)); +} + +#else + +# ifndef constexpr +# define constexpr +# endif + +#endif + /** * various types */ @@ -31,15 +64,14 @@ typedef uint8_t v3_bool; typedef uint32_t v3_param_id; - /** * low-level ABI nonsense */ typedef uint8_t v3_tuid[16]; -inline static bool -v3_tuid_match(const v3_tuid a, const v3_tuid b) +static inline +bool v3_tuid_match(const v3_tuid a, const v3_tuid b) { return memcmp(a, b, sizeof(v3_tuid)) == 0; } @@ -54,88 +86,90 @@ v3_tuid_match(const v3_tuid a, const v3_tuid b) #if V3_COM_COMPAT enum { - V3_NO_INTERFACE = 0x80004002L, - V3_OK = 0, - V3_TRUE = 0, - V3_FALSE = 1, - V3_INVALID_ARG = 0x80070057L, - V3_NOT_IMPLEMENTED = 0x80004001L, - V3_INTERNAL_ERR = 0x80004005L, - V3_NOT_INITIALISED = 0x8000FFFFL, - V3_NOMEM = 0x8007000EL + V3_NO_INTERFACE = 0x80004002L, + V3_OK = 0, + V3_TRUE = 0, + V3_FALSE = 1, + V3_INVALID_ARG = 0x80070057L, + V3_NOT_IMPLEMENTED = 0x80004001L, + V3_INTERNAL_ERR = 0x80004005L, + V3_NOT_INITIALIZED = 0x8000FFFFL, + V3_NOMEM = 0x8007000EL }; -# define V3_ID(a, b, c, d) { \ - ((a) & 0x000000FF), \ - ((a) & 0x0000FF00) >> 8, \ - ((a) & 0x00FF0000) >> 16, \ - ((a) & 0xFF000000) >> 24, \ - \ - ((b) & 0x00FF0000) >> 16, \ - ((b) & 0xFF000000) >> 24, \ - ((b) & 0x000000FF), \ - ((b) & 0x0000FF00) >> 8, \ - \ - ((c) & 0xFF000000) >> 24, \ - ((c) & 0x00FF0000) >> 16, \ - ((c) & 0x0000FF00) >> 8, \ - ((c) & 0x000000FF), \ - \ - ((d) & 0xFF000000) >> 24, \ - ((d) & 0x00FF0000) >> 16, \ - ((d) & 0x0000FF00) >> 8, \ - ((d) & 0x000000FF), \ +# define V3_ID(a, b, c, d) { \ + ((a) & 0x000000FF), \ + ((a) & 0x0000FF00) >> 8, \ + ((a) & 0x00FF0000) >> 16, \ + ((a) & 0xFF000000) >> 24, \ + \ + ((b) & 0x00FF0000) >> 16, \ + ((b) & 0xFF000000) >> 24, \ + ((b) & 0x000000FF), \ + ((b) & 0x0000FF00) >> 8, \ + \ + ((c) & 0xFF000000) >> 24, \ + ((c) & 0x00FF0000) >> 16, \ + ((c) & 0x0000FF00) >> 8, \ + ((c) & 0x000000FF), \ + \ + ((d) & 0xFF000000) >> 24, \ + ((d) & 0x00FF0000) >> 16, \ + ((d) & 0x0000FF00) >> 8, \ + ((d) & 0x000000FF), \ } #else // V3_COM_COMPAT enum { - V3_NO_INTERFACE = -1, + V3_NO_INTERFACE = -1, V3_OK, V3_TRUE = V3_OK, V3_FALSE, V3_INVALID_ARG, V3_NOT_IMPLEMENTED, V3_INTERNAL_ERR, - V3_NOT_INITIALISED, + V3_NOT_INITIALIZED, V3_NOMEM }; -# define V3_ID(a, b, c, d) { \ - ((a) & 0xFF000000) >> 24, \ - ((a) & 0x00FF0000) >> 16, \ - ((a) & 0x0000FF00) >> 8, \ - ((a) & 0x000000FF), \ - \ - ((b) & 0xFF000000) >> 24, \ - ((b) & 0x00FF0000) >> 16, \ - ((b) & 0x0000FF00) >> 8, \ - ((b) & 0x000000FF), \ - \ - ((c) & 0xFF000000) >> 24, \ - ((c) & 0x00FF0000) >> 16, \ - ((c) & 0x0000FF00) >> 8, \ - ((c) & 0x000000FF), \ - \ - ((d) & 0xFF000000) >> 24, \ - ((d) & 0x00FF0000) >> 16, \ - ((d) & 0x0000FF00) >> 8, \ - ((d) & 0x000000FF), \ +# define V3_ID(a, b, c, d) { \ + ((a) & 0xFF000000) >> 24, \ + ((a) & 0x00FF0000) >> 16, \ + ((a) & 0x0000FF00) >> 8, \ + ((a) & 0x000000FF), \ + \ + ((b) & 0xFF000000) >> 24, \ + ((b) & 0x00FF0000) >> 16, \ + ((b) & 0x0000FF00) >> 8, \ + ((b) & 0x000000FF), \ + \ + ((c) & 0xFF000000) >> 24, \ + ((c) & 0x00FF0000) >> 16, \ + ((c) & 0x0000FF00) >> 8, \ + ((c) & 0x000000FF), \ + \ + ((d) & 0xFF000000) >> 24, \ + ((d) & 0x00FF0000) >> 16, \ + ((d) & 0x0000FF00) >> 8, \ + ((d) & 0x000000FF), \ } #endif // V3_COM_COMPAT +#define V3_ID_COPY(iid) \ + { iid[0], iid[1], iid[ 2], iid[ 3], iid[ 4], iid[ 5], iid[ 6], iid[ 7], \ + iid[8], iid[9], iid[10], iid[11], iid[12], iid[13], iid[14], iid[15] } + /** * funknown */ struct v3_funknown { - V3_API v3_result (*query_interface) - (void *self, const v3_tuid iid, void **obj); - - V3_API uint32_t (*ref)(void *self); - V3_API uint32_t (*unref)(void *self); + v3_result (V3_API* query_interface)(void* self, const v3_tuid iid, void** obj); + uint32_t (V3_API* ref)(void* self); + uint32_t (V3_API* unref)(void* self); }; -static const v3_tuid v3_funknown_iid = +static constexpr const v3_tuid v3_funknown_iid = V3_ID(0x00000000, 0x00000000, 0xC0000000, 0x00000046); /** @@ -145,10 +179,35 @@ static const v3_tuid v3_funknown_iid = struct v3_plugin_base { struct v3_funknown; - V3_API v3_result (*initialise) - (void *self, struct v3_funknown *context); - V3_API v3_result (*terminate)(void *self); + v3_result (V3_API* initialize)(void* self, struct v3_funknown** context); + v3_result (V3_API* terminate)(void* self); }; -static const v3_tuid v3_plugin_base_iid = +static constexpr const v3_tuid v3_plugin_base_iid = V3_ID(0x22888DDB, 0x156E45AE, 0x8358B348, 0x08190625); + +#ifdef __cplusplus + +/** + * helper C++ functions to manually call v3_funknown methods on an object. + */ + +template static inline +v3_result v3_cpp_obj_query_interface(T** obj, const v3_tuid iid, M*** obj2) +{ + return static_cast(static_cast(*obj))->query_interface(obj, iid, (void**)obj2); +} + +template static inline +uint32_t v3_cpp_obj_ref(T** obj) +{ + return static_cast(static_cast(*obj))->ref(obj); +} + +template static inline +uint32_t v3_cpp_obj_unref(T** obj) +{ + return static_cast(static_cast(*obj))->unref(obj); +} + +#endif diff --git a/dpf/distrho/src/travesty/bstream.h b/dpf/distrho/src/travesty/bstream.h index e00172f..a1bfcd3 100644 --- a/dpf/distrho/src/travesty/bstream.h +++ b/dpf/distrho/src/travesty/bstream.h @@ -1,5 +1,5 @@ /* - * travesty, pure C interface to steinberg VST3 SDK + * travesty, pure C VST3-compatible interface * Copyright (C) 2021 Filipe Coelho * * Permission to use, copy, modify, and/or distribute this software for any purpose with @@ -27,15 +27,11 @@ enum v3_seek_mode { struct v3_bstream { struct v3_funknown; - V3_API v3_result (*read) - (void *self, void *buffer, int32_t num_bytes, int32_t *bytes_read); - V3_API v3_result (*write) - (void *self, void *buffer, int32_t num_bytes, int32_t *bytes_written); - V3_API v3_result (*seek) - (void *self, int64_t pos, int32_t seek_mode, int64_t *result); - V3_API v3_result (*tell) - (void *self, int64_t *pos); + v3_result (V3_API *read)(void* self, void* buffer, int32_t num_bytes, int32_t* bytes_read); + v3_result (V3_API *write)(void* self, void* buffer, int32_t num_bytes, int32_t* bytes_written); + v3_result (V3_API *seek)(void* self, int64_t pos, int32_t seek_mode, int64_t* result); + v3_result (V3_API *tell)(void* self, int64_t* pos); }; -static const v3_tuid v3_bstream_iid = +static constexpr const v3_tuid v3_bstream_iid = V3_ID(0xC3BF6EA2, 0x30994752, 0x9B6BF990, 0x1EE33E9B); diff --git a/dpf/distrho/src/travesty/component.h b/dpf/distrho/src/travesty/component.h index 39e6390..4e73c47 100644 --- a/dpf/distrho/src/travesty/component.h +++ b/dpf/distrho/src/travesty/component.h @@ -1,5 +1,5 @@ /* - * travesty, pure C interface to steinberg VST3 SDK + * travesty, pure C VST3-compatible interface * Copyright (C) 2021 Filipe Coelho * * Permission to use, copy, modify, and/or distribute this software for any purpose with @@ -30,13 +30,17 @@ enum v3_media_types { V3_EVENT }; -inline static const char * -v3_media_type_str(int32_t type) +static inline +const char* v3_media_type_str(int32_t type) { - switch (type) { - case V3_AUDIO: return "V3_AUDIO"; - case V3_EVENT: return "V3_EVENT"; - default: return "[unknown]"; + switch (type) + { + case V3_AUDIO: + return "V3_AUDIO"; + case V3_EVENT: + return "V3_EVENT"; + default: + return "[unknown]"; } } @@ -45,13 +49,17 @@ enum v3_bus_direction { V3_OUTPUT }; -inline static const char * -v3_bus_direction_str(int32_t d) +static inline +const char* v3_bus_direction_str(int32_t d) { - switch (d) { - case V3_INPUT: return "V3_INPUT"; - case V3_OUTPUT: return "V3_OUTPUT"; - default: return "[unknown]"; + switch (d) + { + case V3_INPUT: + return "V3_INPUT"; + case V3_OUTPUT: + return "V3_OUTPUT"; + default: + return "[unknown]"; } } @@ -61,7 +69,7 @@ enum v3_bus_types { }; enum v3_bus_flags { - V3_DEFAULT_ACTIVE = 1, + V3_DEFAULT_ACTIVE = 1 << 0, V3_IS_CONTROL_VOLTAGE = 1 << 1 }; @@ -69,7 +77,6 @@ struct v3_bus_info { int32_t media_type; int32_t direction; int32_t channel_count; - v3_str_128 bus_name; int32_t bus_type; uint32_t flags; @@ -84,34 +91,44 @@ struct v3_routing_info; struct v3_component { struct v3_plugin_base; - V3_API v3_result (*get_controller_class_id) - (void *self, v3_tuid class_id); - - V3_API v3_result (*set_io_mode) - (void *self, int32_t io_mode); - - V3_API int32_t (*get_bus_count) - (void *self, int32_t media_type, int32_t bus_direction); - V3_API v3_result (*get_bus_info) - (void *self, int32_t media_type, int32_t bus_direction, - int32_t bus_idx, struct v3_bus_info *bus_info); - V3_API v3_result (*get_routing_info) - (void *self, struct v3_routing_info *input, - struct v3_routing_info *output); - V3_API v3_result (*activate_bus) - (void *self, int32_t media_type, int32_t bus_direction, - int32_t bus_idx, v3_bool state); - - V3_API v3_result (*set_active) - (void *self, v3_bool state); - - V3_API v3_result (*set_state) - (void *self, struct v3_bstream **); - V3_API v3_result (*get_state) - (void *self, struct v3_bstream **); + v3_result (V3_API *get_controller_class_id)(void* self, v3_tuid class_id); + v3_result (V3_API *set_io_mode)(void* self, int32_t io_mode); + int32_t (V3_API *get_bus_count)(void* self, int32_t media_type, int32_t bus_direction); + v3_result (V3_API *get_bus_info)(void* self, int32_t media_type, int32_t bus_direction, + int32_t bus_idx, struct v3_bus_info* bus_info); + v3_result (V3_API *get_routing_info)(void* self, struct v3_routing_info* input, struct v3_routing_info* output); + v3_result (V3_API *activate_bus)(void* self, int32_t media_type, int32_t bus_direction, + int32_t bus_idx, v3_bool state); + v3_result (V3_API *set_active)(void* self, v3_bool state); + v3_result (V3_API *set_state)(void* self, struct v3_bstream **); + v3_result (V3_API *get_state)(void* self, struct v3_bstream **); }; -static const v3_tuid v3_component_iid = +static constexpr const v3_tuid v3_component_iid = V3_ID(0xE831FF31, 0xF2D54301, 0x928EBBEE, 0x25697802); +#ifdef __cplusplus + +/** + * C++ variants + */ + +struct v3_component_cpp : v3_funknown { + v3_plugin_base base; + v3_component comp; +}; + +template<> inline +constexpr v3_component* v3_cpp_obj(v3_component** obj) +{ + /** + * this ugly piece of code is required due to C++ assuming `reinterpret_cast` by default, + * but we need everything to be `static_cast` for it to be `constexpr` compatible. + */ + return static_cast( + static_cast(static_cast(static_cast(*obj)) + sizeof(void*)*5)); +} + +#endif + #include "align_pop.h" diff --git a/dpf/distrho/src/travesty/edit_controller.h b/dpf/distrho/src/travesty/edit_controller.h index 349786d..055e7d7 100644 --- a/dpf/distrho/src/travesty/edit_controller.h +++ b/dpf/distrho/src/travesty/edit_controller.h @@ -1,5 +1,5 @@ /* - * travesty, pure C interface to steinberg VST3 SDK + * travesty, pure C VST3-compatible interface * Copyright (C) 2021 Filipe Coelho * * Permission to use, copy, modify, and/or distribute this software for any purpose with @@ -26,21 +26,29 @@ * component handler */ +enum { + V3_RESTART_RELOAD_COMPONENT = 1 << 0, + V3_RESTART_IO_CHANGED = 1 << 1, + V3_RESTART_PARAM_VALUES_CHANGED = 1 << 2, + V3_RESTART_LATENCY_CHANGED = 1 << 3, + V3_RESTART_PARAM_TITLES_CHANGED = 1 << 4, + V3_RESTART_MIDI_CC_ASSIGNMENT_CHANGED = 1 << 5, + V3_RESTART_NOTE_EXPRESSION_CHANGED = 1 << 6, + V3_RESTART_IO_TITLES_CHANGED = 1 << 7, + V3_RESTART_PREFETCHABLE_SUPPORT_CHANGED = 1 << 8, + V3_RESTART_ROUTING_INFO_CHANGED = 1 << 9 +}; + struct v3_component_handler { struct v3_funknown; - V3_API v3_result (*begin_edit) - (void *self, v3_param_id); - V3_API v3_result (*perform_edit) - (void *self, v3_param_id, double value_normalised); - V3_API v3_result (*end_edit) - (void *self, v3_param_id); - - V3_API v3_result (*restart_component) - (void *self, int32_t flags); + v3_result (V3_API* begin_edit)(void* self, v3_param_id); + v3_result (V3_API* perform_edit)(void* self, v3_param_id, double value_normalised); + v3_result (V3_API* end_edit)(void* self, v3_param_id); + v3_result (V3_API* restart_component)(void* self, int32_t flags); }; -static const v3_tuid v3_component_handler_iid = +static constexpr const v3_tuid v3_component_handler_iid = V3_ID(0x93A0BEA3, 0x0BD045DB, 0x8E890B0C, 0xC1E46AC6); /** @@ -48,7 +56,7 @@ static const v3_tuid v3_component_handler_iid = */ enum { - V3_PARAM_CAN_AUTOMATE = 1, + V3_PARAM_CAN_AUTOMATE = 1 << 0, V3_PARAM_READ_ONLY = 1 << 1, V3_PARAM_WRAP_AROUND = 1 << 2, V3_PARAM_IS_LIST = 1 << 3, @@ -59,15 +67,11 @@ enum { struct v3_param_info { v3_param_id param_id; - v3_str_128 title; v3_str_128 short_title; v3_str_128 units; - int32_t step_count; - double default_normalised_value; - int32_t unit_id; int32_t flags; }; @@ -75,39 +79,63 @@ struct v3_param_info { struct v3_edit_controller { struct v3_plugin_base; - V3_API v3_result (*set_component_state) - (void *self, struct v3_bstream *); - V3_API v3_result (*set_state) - (void *self, struct v3_bstream *); - V3_API v3_result (*get_state) - (void *self, struct v3_bstream *); + v3_result (V3_API* set_component_state)(void* self, struct v3_bstream*); + v3_result (V3_API* set_state)(void* self, struct v3_bstream*); + v3_result (V3_API* get_state)(void* self, struct v3_bstream*); + int32_t (V3_API* get_parameter_count)(void* self); + v3_result (V3_API* get_parameter_info)(void* self, int32_t param_idx, struct v3_param_info*); + v3_result (V3_API* get_parameter_string_for_value)(void* self, v3_param_id, double normalised, v3_str_128 output); + v3_result (V3_API* get_parameter_value_for_string)(void* self, v3_param_id, int16_t* input, double* output); + double (V3_API* normalised_parameter_to_plain)(void* self, v3_param_id, double normalised); + double (V3_API* plain_parameter_to_normalised)(void* self, v3_param_id, double plain); + double (V3_API* get_parameter_normalised)(void* self, v3_param_id); + v3_result (V3_API* set_parameter_normalised)(void* self, v3_param_id, double normalised); + v3_result (V3_API* set_component_handler)(void* self, struct v3_component_handler**); + struct v3_plugin_view** (V3_API* create_view)(void* self, const char* name); +}; - V3_API int32_t (*get_parameter_count)(void *self); - V3_API v3_result (*get_param_info) - (void *self, int32_t param_idx, struct v3_param_info *); +static constexpr const v3_tuid v3_edit_controller_iid = + V3_ID(0xDCD7BBE3, 0x7742448D, 0xA874AACC, 0x979C759E); - V3_API v3_result (*get_param_string_for_value) - (void *self, v3_param_id, double normalised, v3_str_128 output); - V3_API v3_result (*get_param_value_for_string) - (void *self, v3_param_id, int16_t *input, double *output); +/** + * midi mapping + */ + +struct v3_midi_mapping { + struct v3_funknown; + + v3_result (V3_API* get_midi_controller_assignment)(void* self, int32_t bus, int16_t channel, int16_t cc, v3_param_id* id); +}; - V3_API double (*normalised_param_to_plain) - (void *self, v3_param_id, double normalised); - V3_API double (*plain_param_to_normalised) - (void *self, v3_param_id, double normalised); +static constexpr const v3_tuid v3_midi_mapping_iid = + V3_ID(0xDF0FF9F7, 0x49B74669, 0xB63AB732, 0x7ADBF5E5); - V3_API double (*get_param_normalised)(void *self, v3_param_id); - V3_API v3_result (*set_param_normalised) - (void *self, v3_param_id, double normalised); +#ifdef __cplusplus - V3_API v3_result (*set_component_handler) - (void *self, struct v3_component_handler **); +/** + * C++ variants + */ - V3_API struct v3_plug_view **(*create_view) - (void *self, const char *name); +struct v3_edit_controller_cpp : v3_funknown { + v3_plugin_base base; + v3_edit_controller ctrl; }; -static const v3_tuid v3_edit_controller_iid = - V3_ID(0xDCD7BBE3, 0x7742448D, 0xA874AACC, 0x979C759E); +struct v3_midi_mapping_cpp : v3_funknown { + v3_midi_mapping map; +}; + +template<> inline +constexpr v3_edit_controller* v3_cpp_obj(v3_edit_controller** obj) +{ + /** + * this ugly piece of code is required due to C++ assuming `reinterpret_cast` by default, + * but we need everything to be `static_cast` for it to be `constexpr` compatible. + */ + return static_cast( + static_cast(static_cast(static_cast(*obj)) + sizeof(void*)*5)); +} + +#endif #include "align_pop.h" diff --git a/dpf/distrho/src/travesty/events.h b/dpf/distrho/src/travesty/events.h index 00d2816..72a4830 100644 --- a/dpf/distrho/src/travesty/events.h +++ b/dpf/distrho/src/travesty/events.h @@ -1,5 +1,5 @@ /* - * travesty, pure C interface to steinberg VST3 SDK + * travesty, pure C VST3-compatible interface * Copyright (C) 2021 Filipe Coelho * * Permission to use, copy, modify, and/or distribute this software for any purpose with @@ -22,18 +22,13 @@ /** * note events - * - * i know there's others, but we don't need them right now. whatever. - * handle them later. */ struct v3_event_note_on { int16_t channel; int16_t pitch; // MIDI note number - float tuning; float velocity; - int32_t length; int32_t note_id; }; @@ -41,9 +36,7 @@ struct v3_event_note_on { struct v3_event_note_off { int16_t channel; int16_t pitch; // MIDI note number - float velocity; - int32_t note_id; float tuning; }; @@ -51,14 +44,12 @@ struct v3_event_note_off { struct v3_event_data { uint32_t size; uint32_t type; - - const uint8_t *bytes; + const uint8_t* bytes; }; struct v3_event_poly_pressure { int16_t channel; int16_t pitch; - float pressure; int32_t note_id; }; @@ -67,22 +58,19 @@ struct v3_event_chord { int16_t root; int16_t bass_note; int16_t mask; - uint16_t text_len; - const int16_t *text; + const int16_t* text; }; struct v3_event_scale { int16_t root; int16_t mask; - uint16_t text_len; - const int16_t *text; + const int16_t* text; }; struct v3_event_legacy_midi_cc_out { uint8_t cc_number; - int8_t channel; int8_t value; int8_t value2; @@ -96,9 +84,8 @@ struct v3_event_note_expression_value { struct v3_event_note_expression_text { int32_t note_id; - uint32_t text_len; - const int16_t *text; + const int16_t* text; }; /** @@ -106,7 +93,7 @@ struct v3_event_note_expression_text { */ enum v3_event_flags { - V3_EVENT_IS_LIVE = 1 + V3_EVENT_IS_LIVE = 1 << 0 }; enum v3_event_type { @@ -118,19 +105,15 @@ enum v3_event_type { V3_EVENT_NOTE_EXP_TEXT = 5, V3_EVENT_CHORD = 6, V3_EVENT_SCALE = 7, - V3_EVENT_LEGACY_MIDI_CC_OUT = 65535 }; struct v3_event { int32_t bus_index; int32_t sample_offset; - double ppq_position; uint16_t flags; - uint16_t type; - union { struct v3_event_note_on note_on; struct v3_event_note_off note_off; @@ -151,14 +134,12 @@ struct v3_event { struct v3_event_list { struct v3_funknown; - V3_API uint32_t (*get_event_count)(void *self); - V3_API v3_result (*get_event) - (void *self, int32_t idx, struct v3_event *); - V3_API v3_result (*add_event) - (void *self, struct v3_event *); + uint32_t (V3_API* get_event_count)(void* self); + v3_result (V3_API* get_event)(void* self, int32_t idx, struct v3_event* event); + v3_result (V3_API* add_event)(void* self, struct v3_event* event); }; -static const v3_tuid v3_event_list_iid = +static constexpr const v3_tuid v3_event_list_iid = V3_ID(0x3A2C4214, 0x346349FE, 0xB2C4F397, 0xB9695A44); #include "align_pop.h" diff --git a/dpf/distrho/src/travesty/factory.h b/dpf/distrho/src/travesty/factory.h index 00a9c45..adad83c 100644 --- a/dpf/distrho/src/travesty/factory.h +++ b/dpf/distrho/src/travesty/factory.h @@ -1,5 +1,5 @@ /* - * travesty, pure C interface to steinberg VST3 SDK + * travesty, pure C VST3-compatible interface * Copyright (C) 2021 Filipe Coelho * * Permission to use, copy, modify, and/or distribute this software for any purpose with @@ -26,12 +26,12 @@ struct v3_factory_info { char vendor[64]; char url[256]; char email[128]; - int32_t flags; + int32_t flags; // set to 0x10 (unicode) }; struct v3_class_info { v3_tuid class_id; - int32_t cardinality; // set to 0x7FFFFFFF + int32_t cardinality; // set to 0x7FFFFFFF (many instances) char category[32]; char name[64]; }; @@ -39,31 +39,29 @@ struct v3_class_info { struct v3_plugin_factory { struct v3_funknown; - V3_API v3_result (*get_factory_info) - (void *self, struct v3_factory_info *); - - V3_API int32_t (*num_classes)(void *self); - - V3_API v3_result (*get_class_info) - (void *self, int32_t idx, struct v3_class_info *); - - V3_API v3_result (*create_instance) - (void *self, const v3_tuid class_id, const v3_tuid iid, void **instance); + v3_result (V3_API *get_factory_info)(void* self, struct v3_factory_info*); + int32_t (V3_API *num_classes)(void* self); + v3_result (V3_API *get_class_info)(void* self, int32_t idx, struct v3_class_info*); + v3_result (V3_API *create_instance)(void* self, const v3_tuid class_id, const v3_tuid iid, void** instance); }; -static const v3_tuid v3_plugin_factory_iid = +static constexpr const v3_tuid v3_plugin_factory_iid = V3_ID(0x7A4D811C, 0x52114A1F, 0xAED9D2EE, 0x0B43BF9F); /** * plugin factory v2 */ +enum { + V3_DISTRIBUTABLE = 1 << 0, + V3_SIMPLE_MODE = 1 << 1 +}; + struct v3_class_info_2 { v3_tuid class_id; int32_t cardinality; // set to 0x7FFFFFFF char category[32]; char name[64]; - uint32_t class_flags; char sub_categories[128]; char vendor[64]; @@ -74,11 +72,10 @@ struct v3_class_info_2 { struct v3_plugin_factory_2 { struct v3_plugin_factory; - V3_API v3_result (*get_class_info_2) - (void *self, int32_t idx, struct v3_class_info_2 *); + v3_result (V3_API *get_class_info_2)(void* self, int32_t idx, struct v3_class_info_2*); }; -static const v3_tuid v3_plugin_factory_2_iid = +static constexpr const v3_tuid v3_plugin_factory_2_iid = V3_ID(0x0007B650, 0xF24B4C0B, 0xA464EDB9, 0xF00B2ABB); /** @@ -93,7 +90,6 @@ struct v3_class_info_3 { int32_t cardinality; // set to 0x7FFFFFFF char category[32]; int16_t name[64]; - uint32_t class_flags; char sub_categories[128]; int16_t vendor[64]; @@ -104,12 +100,23 @@ struct v3_class_info_3 { struct v3_plugin_factory_3 { struct v3_plugin_factory_2; - V3_API v3_result (*get_class_info_utf16) - (void *self, int32_t idx, struct v3_class_info_3 *); - - V3_API v3_result (*set_host_context) - (void *self, struct v3_funknown *host); + v3_result (V3_API *get_class_info_utf16)(void* self, int32_t idx, struct v3_class_info_3*); + v3_result (V3_API *set_host_context)(void* self, struct v3_funknown** host); }; -static const v3_tuid v3_plugin_factory_3_iid = +static constexpr const v3_tuid v3_plugin_factory_3_iid = V3_ID(0x4555A2AB, 0xC1234E57, 0x9B122910, 0x36878931); + +#ifdef __cplusplus + +/** + * C++ variants + */ + +struct v3_plugin_factory_cpp : v3_funknown { + v3_plugin_factory v1; + v3_plugin_factory_2 v2; + v3_plugin_factory_3 v3; +}; + +#endif diff --git a/dpf/distrho/src/travesty/host.h b/dpf/distrho/src/travesty/host.h new file mode 100644 index 0000000..032a7ee --- /dev/null +++ b/dpf/distrho/src/travesty/host.h @@ -0,0 +1,49 @@ +/* + * travesty, pure C VST3-compatible interface + * Copyright (C) 2021 Filipe Coelho + * + * Permission to use, copy, modify, and/or distribute this software for any purpose with + * or without fee is hereby granted, provided that the above copyright notice and this + * permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#pragma once + +#include "message.h" + +#include "align_push.h" + +/** + * connection point + */ + +struct v3_host_application { + struct v3_funknown; + + v3_result (V3_API* get_name)(void* self, v3_str_128 name); // wtf? + v3_result (V3_API* create_instance)(void* self, v3_tuid cid, v3_tuid iid, void** obj); +}; + +static constexpr const v3_tuid v3_host_application_iid = + V3_ID(0x58E595CC, 0xDB2D4969, 0x8B6AAF8C, 0x36A664E5); + +#ifdef __cplusplus + +/** + * C++ variants + */ + +struct v3_host_application_cpp : v3_funknown { + v3_host_application app; +}; + +#endif + +#include "align_pop.h" diff --git a/dpf/distrho/src/travesty/message.h b/dpf/distrho/src/travesty/message.h new file mode 100644 index 0000000..3cd77a6 --- /dev/null +++ b/dpf/distrho/src/travesty/message.h @@ -0,0 +1,93 @@ +/* + * travesty, pure C VST3-compatible interface + * Copyright (C) 2021 Filipe Coelho + * + * Permission to use, copy, modify, and/or distribute this software for any purpose with + * or without fee is hereby granted, provided that the above copyright notice and this + * permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#pragma once + +#include "base.h" + +#include "align_push.h" + +/** + * attribute list + */ + +struct v3_attribute_list { + struct v3_funknown; + + v3_result (V3_API* set_int)(void* self, const char* id, int64_t value); + v3_result (V3_API* get_int)(void* self, const char* id, int64_t* value); + v3_result (V3_API* set_float)(void* self, const char* id, double value); + v3_result (V3_API* get_float)(void* self, const char* id, double* value); + v3_result (V3_API* set_string)(void* self, const char* id, const int16_t* string); + v3_result (V3_API* get_string)(void* self, const char* id, int16_t* string, uint32_t size); + v3_result (V3_API* set_binary)(void* self, const char* id, const void* data, uint32_t size); + v3_result (V3_API* get_binary)(void* self, const char* id, const void** data, uint32_t* size); +}; + +static constexpr const v3_tuid v3_attribute_list_iid = + V3_ID(0x1E5F0AEB, 0xCC7F4533, 0xA2544011, 0x38AD5EE4); + +/** + * message + */ + +struct v3_message { + struct v3_funknown; + + const char* (V3_API* get_message_id)(void* self); + void (V3_API* set_message_id)(void* self, const char* id); + v3_attribute_list** (V3_API* get_attributes)(void* self); +}; + +static constexpr const v3_tuid v3_message_iid = + V3_ID(0x936F033B, 0xC6C047DB, 0xBB0882F8, 0x13C1E613); + +/** + * connection point + */ + +struct v3_connection_point { + struct v3_funknown; + + v3_result (V3_API* connect)(void* self, struct v3_connection_point** other); + v3_result (V3_API* disconnect)(void* self, struct v3_connection_point** other); + v3_result (V3_API* notify)(void* self, struct v3_message** message); +}; + +static constexpr const v3_tuid v3_connection_point_iid = + V3_ID(0x70A4156F, 0x6E6E4026, 0x989148BF, 0xAA60D8D1); + +#ifdef __cplusplus + +/** + * C++ variants + */ + +struct v3_attribute_list_cpp : v3_funknown { + v3_attribute_list attrlist; +}; + +struct v3_message_cpp : v3_funknown { + v3_message msg; +}; + +struct v3_connection_point_cpp : v3_funknown { + v3_connection_point point; +}; + +#endif + +#include "align_pop.h" diff --git a/dpf/distrho/src/travesty/view.h b/dpf/distrho/src/travesty/view.h index e9c885c..0abbd05 100644 --- a/dpf/distrho/src/travesty/view.h +++ b/dpf/distrho/src/travesty/view.h @@ -1,5 +1,5 @@ /* - * travesty, pure C interface to steinberg VST3 SDK + * travesty, pure C VST3-compatible interface * Copyright (C) 2021 Filipe Coelho * * Permission to use, copy, modify, and/or distribute this software for any purpose with @@ -19,7 +19,7 @@ #include "base.h" /** - * base IPlugFrame stuff + * base view stuff */ struct v3_view_rect { @@ -41,50 +41,43 @@ struct v3_view_rect { # define V3_VIEW_PLATFORM_TYPE_NATIVE V3_VIEW_PLATFORM_TYPE_X11 #endif -struct v3_plug_frame; - -struct v3_plug_view { - struct v3_funknown; - - V3_API v3_result (*is_platform_type_supported) - (void *self, const char *platform_type); - - V3_API v3_result (*attached) - (void *self, void *parent, const char *platform_type); - V3_API v3_result (*removed)(void *self); - - V3_API v3_result (*on_wheel)(void *self, float distance); - V3_API v3_result (*on_key_down) - (void *self, int16_t key_char, int16_t key_code, int16_t modifiers); - V3_API v3_result (*on_key_up) - (void *self, int16_t key_char, int16_t key_code, int16_t modifiers); +/** + * plugin view + */ - V3_API v3_result (*get_size) - (void *self, struct v3_view_rect *); - V3_API v3_result (*set_size) - (void *self, struct v3_view_rect *); +struct v3_plugin_frame; - V3_API v3_result (*on_focus) - (void *self, v3_bool state); +struct v3_plugin_view { + struct v3_funknown; - V3_API v3_result (*set_frame) - (void *self, struct v3_plug_frame *); - V3_API v3_result (*can_resize)(void *self); - V3_API v3_result (*check_size_constraint) - (void *self, struct v3_view_rect *); + v3_result (V3_API* is_platform_type_supported)(void* self, const char* platform_type); + v3_result (V3_API* attached)(void* self, void* parent, const char* platform_type); + v3_result (V3_API* removed)(void* self); + v3_result (V3_API* on_wheel)(void* self, float distance); + v3_result (V3_API* on_key_down)(void* self, int16_t key_char, int16_t key_code, int16_t modifiers); + v3_result (V3_API* on_key_up)(void* self, int16_t key_char, int16_t key_code, int16_t modifiers); + v3_result (V3_API* get_size)(void* self, struct v3_view_rect*); + v3_result (V3_API* on_size)(void* self, struct v3_view_rect*); + v3_result (V3_API* on_focus)(void* self, v3_bool state); + v3_result (V3_API* set_frame)(void* self, struct v3_plugin_frame**); + v3_result (V3_API* can_resize)(void* self); + v3_result (V3_API* check_size_constraint)(void* self, struct v3_view_rect*); }; -static const v3_tuid v3_plug_view_iid = +static constexpr const v3_tuid v3_plugin_view_iid = V3_ID(0x5BC32507, 0xD06049EA, 0xA6151B52, 0x2B755B29); -struct v3_plug_frame { +/** + * plugin frame + */ + +struct v3_plugin_frame { struct v3_funknown; - V3_API v3_result (*resize_view) - (void *self, struct v3_plug_view *, struct v3_view_rect *); + v3_result (V3_API* resize_view)(void* self, struct v3_plugin_view**, struct v3_view_rect*); }; -static const v3_tuid v3_plug_frame_iid = +static constexpr const v3_tuid v3_plugin_frame_iid = V3_ID(0x367FAF01, 0xAFA94693, 0x8D4DA2A0, 0xED0882A3); /** @@ -92,26 +85,102 @@ static const v3_tuid v3_plug_frame_iid = * (same IID/iface as presonus view scaling) */ -struct v3_plug_view_content_scale_steinberg { +struct v3_plugin_view_content_scale { struct v3_funknown; - V3_API v3_result (*set_content_scale_factor) - (void *self, float factor); + v3_result (V3_API* set_content_scale_factor)(void* self, float factor); }; -static const v3_tuid v3_plug_view_content_scale_steinberg_iid = +static constexpr const v3_tuid v3_plugin_view_content_scale_iid = V3_ID(0x65ED9690, 0x8AC44525, 0x8AADEF7A, 0x72EA703F); /** * support for querying the view to find what control is underneath the mouse */ -struct v3_plug_view_param_finder { +struct v3_plugin_view_parameter_finder { struct v3_funknown; - V3_API v3_result (*find_parameter) - (void *self, int32_t x, int32_t y, v3_param_id *); + v3_result (V3_API* find_parameter)(void* self, int32_t x, int32_t y, v3_param_id *); }; -static const v3_tuid v3_plug_view_param_finder_iid = +static constexpr const v3_tuid v3_plugin_view_parameter_finder_iid = V3_ID(0x0F618302, 0x215D4587, 0xA512073C, 0x77B9D383); + +/** + * linux event handler + */ + +struct v3_event_handler { + struct v3_funknown; + + void (V3_API* on_fd_is_set)(void* self, int fd); +}; + +static constexpr const v3_tuid v3_event_handler_iid = + V3_ID(0x561E65C9, 0x13A0496F, 0x813A2C35, 0x654D7983); + +/** + * linux timer handler + */ + +struct v3_timer_handler { + struct v3_funknown; + + void (V3_API* on_timer)(void* self); +}; + +static constexpr const v3_tuid v3_timer_handler_iid = + V3_ID(0x10BDD94F, 0x41424774, 0x821FAD8F, 0xECA72CA9); + +/** + * linux host run loop + */ + +struct v3_run_loop { + struct v3_funknown; + + v3_result (V3_API* register_event_handler)(void* self, v3_event_handler** handler, int fd); + v3_result (V3_API* unregister_event_handler)(void* self, v3_event_handler** handler); + v3_result (V3_API* register_timer)(void* self, v3_timer_handler** handler, uint64_t ms); + v3_result (V3_API* unregister_timer)(void* self, v3_timer_handler** handler); +}; + +static constexpr const v3_tuid v3_run_loop_iid = + V3_ID(0x18C35366, 0x97764F1A, 0x9C5B8385, 0x7A871389); + +#ifdef __cplusplus + +/** + * C++ variants + */ + +struct v3_plugin_view_cpp : v3_funknown { + v3_plugin_view view; +}; + +struct v3_plugin_frame_cpp : v3_funknown { + v3_plugin_frame frame; +}; + +struct v3_plugin_view_content_scale_cpp : v3_funknown { + v3_plugin_view_content_scale scale; +}; + +struct v3_plugin_view_parameter_finder_cpp : v3_funknown { + v3_plugin_view_parameter_finder finder; +}; + +struct v3_event_handler_cpp : v3_funknown { + v3_event_handler handler; +}; + +struct v3_timer_handler_cpp : v3_funknown { + v3_timer_handler handler; +}; + +struct v3_run_loop_cpp : v3_funknown { + v3_run_loop loop; +}; + +#endif diff --git a/dpf/utils/lv2-ttl-generator/lv2_ttl_generator.c b/dpf/utils/lv2-ttl-generator/lv2_ttl_generator.c index 3be02a1..fb28fb2 100644 --- a/dpf/utils/lv2-ttl-generator/lv2_ttl_generator.c +++ b/dpf/utils/lv2-ttl-generator/lv2_ttl_generator.c @@ -63,7 +63,14 @@ int main(int argc, char* argv[]) } #ifdef TTL_GENERATOR_WINDOWS +# if defined(__GNUC__) && (__GNUC__ >= 9) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wcast-function-type" +# endif const TTL_Generator_Function ttlFn = (TTL_Generator_Function)GetProcAddress(handle, "lv2_generate_ttl"); +# if defined(__GNUC__) && (__GNUC__ >= 9) +# pragma GCC diagnostic pop +# endif #else const TTL_Generator_Function ttlFn = (TTL_Generator_Function)dlsym(handle, "lv2_generate_ttl"); #endif diff --git a/dpf/utils/package-osx-bundles.sh b/dpf/utils/package-osx-bundles.sh index 57a7484..12b62e7 100755 --- a/dpf/utils/package-osx-bundles.sh +++ b/dpf/utils/package-osx-bundles.sh @@ -14,10 +14,12 @@ SNAME="$(echo ${NAME} | tr -d ' ' | tr '/' '-')" rm -rf lv2 rm -rf vst2 +rm -rf vst3 -mkdir lv2 vst2 +mkdir lv2 vst2 vst3 mv *.lv2 lv2/ mv *.vst vst2/ +mv *.vst3 vst3/ pkgbuild \ --identifier "studio.kx.distrho.plugins.${SNAME}.lv2bundles" \ @@ -31,6 +33,12 @@ pkgbuild \ --root "${PWD}/vst2/" \ ../dpf-${SNAME}-vst2bundles.pkg +pkgbuild \ + --identifier "studio.kx.distrho.plugins.${SNAME}.vst3bundles" \ + --install-location "/Library/Audio/Plug-Ins/VST3/" \ + --root "${PWD}/vst3/" \ + ../dpf-${SNAME}-vst3bundles.pkg + cd .. DPF_UTILS_DIR=$(dirname ${0}) @@ -39,6 +47,7 @@ sed -e "s|@name@|${NAME}|" ${DPF_UTILS_DIR}/plugin.pkg/welcome.txt.in > build/we sed -e "s|@builddir@|${PWD}/build|" \ -e "s|@lv2bundleref@|dpf-${SNAME}-lv2bundles.pkg|" \ -e "s|@vst2bundleref@|dpf-${SNAME}-vst2bundles.pkg|" \ + -e "s|@vst3bundleref@|dpf-${SNAME}-vst3bundles.pkg|" \ -e "s|@name@|${NAME}|g" \ -e "s|@sname@|${SNAME}|g" \ ${DPF_UTILS_DIR}/plugin.pkg/package.xml.in > build/package.xml diff --git a/dpf/utils/plugin.pkg/package.xml.in b/dpf/utils/plugin.pkg/package.xml.in index 8959283..dc8e24f 100644 --- a/dpf/utils/plugin.pkg/package.xml.in +++ b/dpf/utils/plugin.pkg/package.xml.in @@ -11,8 +11,12 @@ @vst2bundleref@ + + @vst3bundleref@ + + diff --git a/dpf/utils/valgrind-dpf.supp b/dpf/utils/valgrind-dpf.supp index a7c866f..a7b8f61 100644 --- a/dpf/utils/valgrind-dpf.supp +++ b/dpf/utils/valgrind-dpf.supp @@ -39,3 +39,10 @@ fun:XInitThreads ... } +{ + ignore XrmGetStringDatabase + Memcheck:Leak + ... + fun:XrmGetStringDatabase + ... +}