| @@ -32,7 +32,8 @@ | |||
| #include <osdialog.h> | |||
| #include "PluginContext.hpp" | |||
| #include "extra/Mutex.hpp" | |||
| #include "WindowParameters.hpp" | |||
| #include "extra/Base64.hpp" | |||
| namespace rack { | |||
| namespace plugin { | |||
| @@ -128,8 +129,13 @@ class CardinalPlugin : public CardinalBasePlugin | |||
| rack::audio::Device* fCurrentDevice; | |||
| Mutex fDeviceMutex; | |||
| float fParameters[kWindowParameterCount]; | |||
| struct ScopedContext { | |||
| ScopedContext(CardinalPlugin* const plugin) | |||
| const MutexLocker cml; | |||
| ScopedContext(const CardinalPlugin* const plugin) | |||
| : cml(plugin->contextMutex) | |||
| { | |||
| rack::contextSet(plugin->fContext); | |||
| } | |||
| @@ -142,13 +148,18 @@ class CardinalPlugin : public CardinalBasePlugin | |||
| public: | |||
| CardinalPlugin() | |||
| : CardinalBasePlugin(0, 0, 0), | |||
| : CardinalBasePlugin(kWindowParameterCount, 0, 1), | |||
| fContext(new CardinalPluginContext(this)), | |||
| fAudioBufferIn(nullptr), | |||
| fAudioBufferOut(nullptr), | |||
| fIsActive(false), | |||
| fCurrentDevice(nullptr) | |||
| { | |||
| fParameters[kWindowParameterCableOpacity] = 50.0f; | |||
| fParameters[kWindowParameterCableTension] = 50.0f; | |||
| fParameters[kWindowParameterRackBrightness] = 100.0f; | |||
| fParameters[kWindowParameterHaloBrightness] = 25.0f; | |||
| // create unique temporary path for this instance | |||
| try { | |||
| char uidBuf[24]; | |||
| @@ -232,60 +243,38 @@ protected: | |||
| /* -------------------------------------------------------------------------------------------------------- | |||
| * Information */ | |||
| /** | |||
| Get the plugin label. | |||
| A plugin label follows the same rules as Parameter::symbol, with the exception that it can start with numbers. | |||
| */ | |||
| const char* getLabel() const override | |||
| { | |||
| return "Cardinal"; | |||
| } | |||
| /** | |||
| Get an extensive comment/description about the plugin. | |||
| */ | |||
| const char* getDescription() const override | |||
| { | |||
| return "..."; | |||
| return "" | |||
| "Cardinal is an open-source self-contained special plugin version of VCVRack, using DPF.\n" | |||
| "It is NOT an official VCV project, and it is not affiliated with it in any way.\n"; | |||
| } | |||
| /** | |||
| Get the plugin author/maker. | |||
| */ | |||
| const char* getMaker() const override | |||
| { | |||
| return "DISTRHO"; | |||
| } | |||
| /** | |||
| Get the plugin homepage. | |||
| */ | |||
| const char* getHomePage() const override | |||
| { | |||
| return "https://github.com/DISTRHO/Cardinal"; | |||
| } | |||
| /** | |||
| Get the plugin license name (a single line of text). | |||
| For commercial plugins this should return some short copyright information. | |||
| */ | |||
| const char* getLicense() const override | |||
| { | |||
| return "ISC"; | |||
| return "GPLv3+"; | |||
| } | |||
| /** | |||
| Get the plugin version, in hexadecimal. | |||
| */ | |||
| uint32_t getVersion() const override | |||
| { | |||
| return d_version(1, 0, 0); | |||
| return d_version(2, 0, 0); | |||
| } | |||
| /** | |||
| Get the plugin unique Id. | |||
| This value is used by LADSPA, DSSI and VST plugin formats. | |||
| */ | |||
| int64_t getUniqueId() const override | |||
| { | |||
| return d_cconst('d', 'C', 'd', 'n'); | |||
| @@ -294,9 +283,110 @@ protected: | |||
| /* -------------------------------------------------------------------------------------------------------- | |||
| * Init */ | |||
| void initParameter(const uint32_t index, Parameter& parameter) override | |||
| { | |||
| switch (index) | |||
| { | |||
| case kWindowParameterCableOpacity: | |||
| parameter.name = "Cable Opacity"; | |||
| parameter.symbol = "cableOpacity"; | |||
| parameter.unit = "%"; | |||
| parameter.hints = kParameterIsAutomable; | |||
| parameter.ranges.def = 50.0f; | |||
| parameter.ranges.min = 0.0f; | |||
| parameter.ranges.max = 100.0f; | |||
| break; | |||
| case kWindowParameterCableTension: | |||
| parameter.name = "Cable Tension"; | |||
| parameter.symbol = "cableTension"; | |||
| parameter.unit = "%"; | |||
| parameter.hints = kParameterIsAutomable; | |||
| parameter.ranges.def = 50.0f; | |||
| parameter.ranges.min = 0.0f; | |||
| parameter.ranges.max = 100.0f; | |||
| break; | |||
| case kWindowParameterRackBrightness: | |||
| parameter.name = "Rack Brightness"; | |||
| parameter.symbol = "rackBrightness"; | |||
| parameter.unit = "%"; | |||
| parameter.hints = kParameterIsAutomable; | |||
| parameter.ranges.def = 100.0f; | |||
| parameter.ranges.min = 0.0f; | |||
| parameter.ranges.max = 100.0f; | |||
| break; | |||
| case kWindowParameterHaloBrightness: | |||
| parameter.name = "Halo Brightness"; | |||
| parameter.symbol = "haloBrightness"; | |||
| parameter.unit = "%"; | |||
| parameter.hints = kParameterIsAutomable; | |||
| parameter.ranges.def = 25.0f; | |||
| parameter.ranges.min = 0.0f; | |||
| parameter.ranges.max = 100.0f; | |||
| break; | |||
| } | |||
| } | |||
| void initState(const uint32_t index, String& stateKey, String& defaultStateValue) override | |||
| { | |||
| DISTRHO_SAFE_ASSERT_RETURN(index == 0,); | |||
| stateKey = "patch"; | |||
| defaultStateValue = ""; | |||
| } | |||
| /* -------------------------------------------------------------------------------------------------------- | |||
| * Internal data */ | |||
| float getParameterValue(const uint32_t index) const override | |||
| { | |||
| return fParameters[index]; | |||
| } | |||
| void setParameterValue(const uint32_t index, float value) override | |||
| { | |||
| fParameters[index] = value; | |||
| } | |||
| String getState(const char* const key) const override | |||
| { | |||
| if (std::strcmp(key, "patch") != 0) | |||
| return String(); | |||
| if (fAutosavePath.empty()) | |||
| return String(); | |||
| std::vector<uint8_t> data; | |||
| { | |||
| const ScopedContext sc(this); | |||
| fContext->engine->prepareSave(); | |||
| fContext->patch->saveAutosave(); | |||
| fContext->patch->cleanAutosave(); | |||
| data = rack::system::archiveDirectory(fAutosavePath, 1); | |||
| } | |||
| return String::asBase64(data.data(), data.size()); | |||
| } | |||
| void setState(const char* const key, const char* const value) override | |||
| { | |||
| if (std::strcmp(key, "patch") != 0) | |||
| return; | |||
| if (fAutosavePath.empty()) | |||
| return; | |||
| const std::vector<uint8_t> data(d_getChunkFromBase64String(value)); | |||
| const ScopedContext sc(this); | |||
| rack::system::removeRecursively(fAutosavePath); | |||
| rack::system::createDirectories(fAutosavePath); | |||
| rack::system::unarchiveToDirectory(data, fAutosavePath); | |||
| fContext->patch->loadAutosave(); | |||
| } | |||
| /* -------------------------------------------------------------------------------------------------------- | |||
| * Process */ | |||
| @@ -329,9 +419,6 @@ protected: | |||
| fAudioBufferIn = fAudioBufferOut = nullptr; | |||
| } | |||
| /** | |||
| Run/process function for plugins without MIDI input. | |||
| */ | |||
| void run(const float** const inputs, float** const outputs, const uint32_t frames) override | |||
| { | |||
| /* | |||
| @@ -340,8 +427,9 @@ protected: | |||
| */ | |||
| const MutexLocker cml(fDeviceMutex); | |||
| // const MutexTryLocker cmtl(fPatchMutex); | |||
| if (fCurrentDevice == nullptr) | |||
| if (fCurrentDevice == nullptr /*|| cmtl.wasNotLocked()*/) | |||
| { | |||
| std::memset(outputs[0], 0, sizeof(float)*frames); | |||
| std::memset(outputs[1], 0, sizeof(float)*frames); | |||
| @@ -22,9 +22,13 @@ | |||
| #include <ui/MenuItem.hpp> | |||
| #include <window/Window.hpp> | |||
| #include "PluginContext.hpp" | |||
| #ifdef NDEBUG | |||
| # undef DEBUG | |||
| #endif | |||
| #include "DistrhoUI.hpp" | |||
| #include "PluginContext.hpp" | |||
| #include "WindowParameters.hpp" | |||
| #include "ResizeHandle.hpp" | |||
| GLFWAPI const char* glfwGetClipboardString(GLFWwindow* window) { return nullptr; } | |||
| @@ -45,20 +49,29 @@ START_NAMESPACE_DISTRHO | |||
| CardinalPluginContext* getRackContextFromPlugin(void* ptr); | |||
| class CardinalUI : public UI | |||
| class CardinalUI : public UI, | |||
| public WindowParametersCallback | |||
| { | |||
| CardinalPluginContext* const fContext; | |||
| rack::math::Vec fLastMousePos; | |||
| ResizeHandle fResizeHandle; | |||
| WindowParameters fWindowParameters; | |||
| struct ScopedContext { | |||
| CardinalPluginContext* const context; | |||
| const MutexLocker cml; | |||
| ScopedContext(CardinalUI* const ui) | |||
| : context(ui->fContext), | |||
| cml(context->plugin->contextMutex) | |||
| { | |||
| rack::contextSet(ui->fContext); | |||
| rack::contextSet(context); | |||
| WindowParametersRestore(context->window); | |||
| } | |||
| ~ScopedContext() | |||
| { | |||
| WindowParametersSave(context->window); | |||
| rack::contextSet(nullptr); | |||
| } | |||
| }; | |||
| @@ -72,58 +85,63 @@ public: | |||
| if (isResizable()) | |||
| fResizeHandle.hide(); | |||
| const ScopedContext sc(this); | |||
| fContext->window = new rack::window::Window; | |||
| fContext->event = new rack::widget::EventState; | |||
| fContext->scene = new rack::app::Scene; | |||
| fContext->event->rootWidget = fContext->scene; | |||
| { | |||
| const ScopedContext sc(this); | |||
| fContext->window = new rack::window::Window; | |||
| rack::window::WindowInit(fContext->window, this); | |||
| fContext->event = new rack::widget::EventState; | |||
| fContext->scene = new rack::app::Scene; | |||
| fContext->event->rootWidget = fContext->scene; | |||
| // Hide non-wanted menu entries | |||
| typedef rack::ui::Button rButton; | |||
| // typedef rack::ui::MenuItem rMenuItem; | |||
| typedef rack::widget::Widget rWidget; | |||
| typedef std::list<rWidget*>::iterator rWidgetIterator; | |||
| rack::window::WindowInit(fContext->window, this); | |||
| rWidget* const layout = fContext->scene->menuBar->children.front(); | |||
| // Hide non-wanted menu entries | |||
| typedef rack::ui::Button rButton; | |||
| // typedef rack::ui::MenuItem rMenuItem; | |||
| typedef rack::widget::Widget rWidget; | |||
| typedef std::list<rWidget*>::iterator rWidgetIterator; | |||
| for (rWidgetIterator it = layout->children.begin(); it != layout->children.end(); ++it) | |||
| { | |||
| if (rButton* const button = reinterpret_cast<rButton*>(*it)) | |||
| rWidget* const layout = fContext->scene->menuBar->children.front(); | |||
| for (rWidgetIterator it = layout->children.begin(); it != layout->children.end(); ++it) | |||
| { | |||
| /* FIXME this doesnt work | |||
| if (button->text == "Engine") | |||
| if (rButton* const button = reinterpret_cast<rButton*>(*it)) | |||
| { | |||
| for (rWidgetIterator it2 = button->children.begin(); it2 != button->children.end(); ++it2) | |||
| /* FIXME this doesnt work | |||
| if (button->text == "Engine") | |||
| { | |||
| if (rMenuItem* const item = reinterpret_cast<rMenuItem*>(*it2)) | |||
| for (rWidgetIterator it2 = button->children.begin(); it2 != button->children.end(); ++it2) | |||
| { | |||
| if (item->text == "Sample rate") | |||
| if (rMenuItem* const item = reinterpret_cast<rMenuItem*>(*it2)) | |||
| { | |||
| button->children.erase(it2); | |||
| delete button; | |||
| break; | |||
| if (item->text == "Sample rate") | |||
| { | |||
| button->children.erase(it2); | |||
| delete button; | |||
| break; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| */ | |||
| if (button->text == "Library") | |||
| { | |||
| layout->children.erase(it); | |||
| delete button; | |||
| break; | |||
| */ | |||
| if (button->text == "Library") | |||
| { | |||
| layout->children.erase(it); | |||
| delete button; | |||
| break; | |||
| } | |||
| } | |||
| } | |||
| // we need to reload current patch for things to show on screen :( | |||
| // FIXME always save | |||
| if (! fContext->patch->hasAutosave()) | |||
| fContext->patch->saveAutosave(); | |||
| fContext->patch->loadAutosave(); | |||
| } | |||
| // we need to reload current patch for things to show on screen :( | |||
| // FIXME always save | |||
| if (! fContext->patch->hasAutosave()) | |||
| fContext->patch->saveAutosave(); | |||
| fContext->patch->loadAutosave(); | |||
| WindowParametersSetCallback(fContext->window, this); | |||
| } | |||
| ~CardinalUI() override | |||
| @@ -143,7 +161,6 @@ public: | |||
| void onNanoDisplay() override | |||
| { | |||
| const ScopedContext sc(this); | |||
| fContext->window->step(); | |||
| } | |||
| @@ -152,6 +169,35 @@ public: | |||
| repaint(); | |||
| } | |||
| void WindowParametersChanged(const WindowParameterList param, const float value) override | |||
| { | |||
| float mult; | |||
| switch (param) | |||
| { | |||
| case kWindowParameterCableOpacity: | |||
| mult = 100.0f; | |||
| fWindowParameters.cableOpacity = value; | |||
| break; | |||
| case kWindowParameterCableTension: | |||
| mult = 100.0f; | |||
| fWindowParameters.cableTension = value; | |||
| break; | |||
| case kWindowParameterRackBrightness: | |||
| mult = 100.0f; | |||
| fWindowParameters.rackBrightness = value; | |||
| break; | |||
| case kWindowParameterHaloBrightness: | |||
| mult = 100.0f; | |||
| fWindowParameters.haloBrightness = value; | |||
| break; | |||
| default: | |||
| return; | |||
| } | |||
| setParameterValue((uint)param, value * mult); | |||
| } | |||
| protected: | |||
| /* -------------------------------------------------------------------------------------------------------- | |||
| * DSP/Plugin Callbacks */ | |||
| @@ -160,7 +206,30 @@ protected: | |||
| A parameter has changed on the plugin side. | |||
| This is called by the host to inform the UI about parameter changes. | |||
| */ | |||
| void parameterChanged(uint32_t index, float value) override | |||
| void parameterChanged(const uint32_t index, const float value) override | |||
| { | |||
| switch (index) | |||
| { | |||
| case kWindowParameterCableOpacity: | |||
| fWindowParameters.cableOpacity = value / 100.0f; | |||
| break; | |||
| case kWindowParameterCableTension: | |||
| fWindowParameters.cableTension = value / 100.0f; | |||
| break; | |||
| case kWindowParameterRackBrightness: | |||
| fWindowParameters.rackBrightness = value / 100.0f; | |||
| break; | |||
| case kWindowParameterHaloBrightness: | |||
| fWindowParameters.haloBrightness = value / 100.0f; | |||
| break; | |||
| default: | |||
| return; | |||
| } | |||
| WindowParametersSetValues(fContext->window, fWindowParameters); | |||
| } | |||
| void stateChanged(const char* key, const char* value) override | |||
| { | |||
| } | |||
| @@ -25,6 +25,8 @@ | |||
| #define DISTRHO_PLUGIN_HAS_UI 1 | |||
| #define DISTRHO_PLUGIN_NUM_INPUTS 2 | |||
| #define DISTRHO_PLUGIN_NUM_OUTPUTS 2 | |||
| #define DISTRHO_PLUGIN_WANT_FULL_STATE 1 | |||
| #define DISTRHO_PLUGIN_WANT_STATE 1 | |||
| #define DISTRHO_PLUGIN_WANT_TIMEPOS 1 | |||
| #define DISTRHO_PLUGIN_WANT_DIRECT_ACCESS 1 | |||
| // #define DISTRHO_PLUGIN_LV2_CATEGORY "lv2:AnalyserPlugin" | |||
| @@ -25,6 +25,7 @@ | |||
| #endif | |||
| #include "DistrhoPlugin.hpp" | |||
| #include "extra/Mutex.hpp" | |||
| START_NAMESPACE_DISTRHO | |||
| @@ -39,6 +40,9 @@ public: | |||
| virtual bool canAssignDevice() const noexcept = 0; | |||
| virtual void assignDevice(rack::audio::Device* dev) noexcept = 0; | |||
| virtual bool clearDevice(rack::audio::Device* dev) noexcept = 0; | |||
| // ensure context validity through UI and setState | |||
| Mutex contextMutex; | |||
| }; | |||
| // ----------------------------------------------------------------------------------------------------------- | |||
| @@ -110,7 +114,7 @@ struct CardinalAudioDriver : rack::audio::Driver { | |||
| std::vector<int> getDeviceIds() override | |||
| { | |||
| return std::vector<int>({ 0 }); | |||
| return std::vector<int>({ 1 }); | |||
| } | |||
| std::string getDeviceName(int) override | |||
| @@ -19,6 +19,7 @@ | |||
| #endif | |||
| #include "DistrhoUI.hpp" | |||
| #include "WindowParameters.hpp" | |||
| namespace rack { | |||
| namespace window { | |||
| @@ -67,13 +68,19 @@ std::shared_ptr<Image> Image::load(const std::string& filename) { | |||
| } | |||
| struct WindowParams { | |||
| float rackBrightness = 1.0f; | |||
| }; | |||
| struct Window::Internal { | |||
| int mods = 0; | |||
| DISTRHO_NAMESPACE::UI* ui = nullptr; | |||
| math::Vec size = minWindowSize; | |||
| DISTRHO_NAMESPACE::WindowParameters params; | |||
| DISTRHO_NAMESPACE::WindowParametersCallback* callback = nullptr; | |||
| math::Vec size = minWindowSize; | |||
| std::string lastWindowTitle; | |||
| int mods = 0; | |||
| int frame = 0; | |||
| int frameSwapInterval = 1; | |||
| double monitorRefreshRate = 60.0; // FIXME | |||
| @@ -107,6 +114,9 @@ void WindowInit(Window* const window, DISTRHO_NAMESPACE::UI* const ui) | |||
| window->uiFont = window->loadFont(asset::system("res/fonts/DejaVuSans.ttf")); | |||
| bndSetFont(window->uiFont->handle); | |||
| // Init settings | |||
| WindowParametersRestore(window); | |||
| if (APP->scene) { | |||
| widget::Widget::ContextCreateEvent e; | |||
| APP->scene->onContextCreate(e); | |||
| @@ -321,3 +331,59 @@ bool& Window::fbDirtyOnSubpixelChange() { | |||
| } // namespace window | |||
| } // namespace rack | |||
| START_NAMESPACE_DISTRHO | |||
| void WindowParametersSave(rack::window::Window* const window) | |||
| { | |||
| if (d_isNotEqual(window->internal->params.cableOpacity, rack::settings::cableOpacity)) | |||
| { | |||
| window->internal->params.cableOpacity = rack::settings::cableOpacity; | |||
| if (window->internal->callback != nullptr) | |||
| window->internal->callback->WindowParametersChanged(kWindowParameterCableOpacity, | |||
| rack::settings::cableOpacity); | |||
| } | |||
| if (d_isNotEqual(window->internal->params.cableTension, rack::settings::cableTension)) | |||
| { | |||
| window->internal->params.cableTension = rack::settings::cableTension; | |||
| if (window->internal->callback != nullptr) | |||
| window->internal->callback->WindowParametersChanged(kWindowParameterCableTension, | |||
| rack::settings::cableTension); | |||
| } | |||
| if (d_isNotEqual(window->internal->params.rackBrightness, rack::settings::rackBrightness)) | |||
| { | |||
| window->internal->params.rackBrightness = rack::settings::rackBrightness; | |||
| if (window->internal->callback != nullptr) | |||
| window->internal->callback->WindowParametersChanged(kWindowParameterRackBrightness, | |||
| rack::settings::rackBrightness); | |||
| } | |||
| if (d_isNotEqual(window->internal->params.haloBrightness, rack::settings::haloBrightness)) | |||
| { | |||
| window->internal->params.haloBrightness = rack::settings::haloBrightness; | |||
| if (window->internal->callback != nullptr) | |||
| window->internal->callback->WindowParametersChanged(kWindowParameterHaloBrightness, | |||
| rack::settings::haloBrightness); | |||
| } | |||
| } | |||
| void WindowParametersRestore(rack::window::Window* const window) | |||
| { | |||
| rack::settings::cableOpacity = window->internal->params.cableOpacity; | |||
| rack::settings::cableTension = window->internal->params.cableTension; | |||
| rack::settings::rackBrightness = window->internal->params.rackBrightness; | |||
| rack::settings::haloBrightness = window->internal->params.haloBrightness; | |||
| } | |||
| void WindowParametersSetCallback(rack::window::Window* const window, WindowParametersCallback* const callback) | |||
| { | |||
| window->internal->callback = callback; | |||
| } | |||
| void WindowParametersSetValues(rack::window::Window* const window, const WindowParameters& params) | |||
| { | |||
| window->internal->params = params; | |||
| } | |||
| END_NAMESPACE_DISTRHO | |||
| @@ -0,0 +1,60 @@ | |||
| /* | |||
| * DISTRHO Cardinal Plugin | |||
| * Copyright (C) 2021 Filipe Coelho <falktx@falktx.com> | |||
| * | |||
| * 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 3 of | |||
| * the License, or 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. | |||
| * | |||
| * For a full copy of the GNU General Public License see the LICENSE file. | |||
| */ | |||
| #pragma once | |||
| #include "DistrhoUtils.hpp" | |||
| namespace rack { | |||
| namespace window { | |||
| struct Window; | |||
| } | |||
| } | |||
| START_NAMESPACE_DISTRHO | |||
| // ----------------------------------------------------------------------------------------------------------- | |||
| enum WindowParameterList { | |||
| kWindowParameterCableOpacity, | |||
| kWindowParameterCableTension, | |||
| kWindowParameterRackBrightness, | |||
| kWindowParameterHaloBrightness, | |||
| kWindowParameterCount, | |||
| }; | |||
| struct WindowParameters { | |||
| float cableOpacity = 0.5f; | |||
| float cableTension = 0.5f; | |||
| float rackBrightness = 1.0f; | |||
| float haloBrightness = 0.25f; | |||
| // KnobMode knobMode = KNOB_MODE_LINEAR; | |||
| }; | |||
| struct WindowParametersCallback { | |||
| virtual ~WindowParametersCallback() {} | |||
| virtual void WindowParametersChanged(WindowParameterList param, float value) = 0; | |||
| }; | |||
| void WindowParametersSave(rack::window::Window* window); | |||
| void WindowParametersRestore(rack::window::Window* window); | |||
| void WindowParametersSetCallback(rack::window::Window* window, WindowParametersCallback* callback); | |||
| void WindowParametersSetValues(rack::window::Window* window, const WindowParameters& params); | |||
| // ----------------------------------------------------------------------------------------------------------- | |||
| END_NAMESPACE_DISTRHO | |||