diff --git a/src/CardinalPlugin.cpp b/src/CardinalPlugin.cpp index 84e6aea..f2d9134 100644 --- a/src/CardinalPlugin.cpp +++ b/src/CardinalPlugin.cpp @@ -32,7 +32,8 @@ #include #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 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 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); diff --git a/src/CardinalUI.cpp b/src/CardinalUI.cpp index 043c076..f2503a5 100644 --- a/src/CardinalUI.cpp +++ b/src/CardinalUI.cpp @@ -22,9 +22,13 @@ #include #include -#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->event = new rack::widget::EventState; - fContext->scene = new rack::app::Scene; - fContext->event->rootWidget = fContext->scene; - fContext->window = new rack::window::Window; - rack::window::WindowInit(fContext->window, this); - // Hide non-wanted menu entries - typedef rack::ui::Button rButton; - // typedef rack::ui::MenuItem rMenuItem; - typedef rack::widget::Widget rWidget; - typedef std::list::iterator rWidgetIterator; - - rWidget* const layout = fContext->scene->menuBar->children.front(); - - for (rWidgetIterator it = layout->children.begin(); it != layout->children.end(); ++it) { - if (rButton* const button = reinterpret_cast(*it)) + const ScopedContext sc(this); + + fContext->event = new rack::widget::EventState; + fContext->scene = new rack::app::Scene; + fContext->event->rootWidget = fContext->scene; + + rack::window::WindowInit(fContext->window, this); + + // Hide non-wanted menu entries + typedef rack::ui::Button rButton; + // typedef rack::ui::MenuItem rMenuItem; + typedef rack::widget::Widget rWidget; + typedef std::list::iterator rWidgetIterator; + + 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(*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(*it2)) + for (rWidgetIterator it2 = button->children.begin(); it2 != button->children.end(); ++it2) { - if (item->text == "Sample rate") + if (rMenuItem* const item = reinterpret_cast(*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 { } diff --git a/src/DistrhoPluginInfo.h b/src/DistrhoPluginInfo.h index 8cd0d28..219c232 100644 --- a/src/DistrhoPluginInfo.h +++ b/src/DistrhoPluginInfo.h @@ -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" diff --git a/src/PluginContext.hpp b/src/PluginContext.hpp index 257bf07..bcf3a46 100644 --- a/src/PluginContext.hpp +++ b/src/PluginContext.hpp @@ -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 getDeviceIds() override { - return std::vector({ 0 }); + return std::vector({ 1 }); } std::string getDeviceName(int) override diff --git a/src/Window.cpp b/src/Window.cpp index e395399..8745845 100644 --- a/src/Window.cpp +++ b/src/Window.cpp @@ -19,6 +19,7 @@ #endif #include "DistrhoUI.hpp" +#include "WindowParameters.hpp" namespace rack { namespace window { @@ -67,13 +68,19 @@ std::shared_ptr Image::load(const std::string& filename) { } -struct Window::Internal { - int mods = 0; - DISTRHO_NAMESPACE::UI* ui = nullptr; - math::Vec size = minWindowSize; +struct WindowParams { + float rackBrightness = 1.0f; +}; +struct Window::Internal { + DISTRHO_NAMESPACE::UI* ui = nullptr; + 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 + diff --git a/src/WindowParameters.hpp b/src/WindowParameters.hpp new file mode 100644 index 0000000..bdc589b --- /dev/null +++ b/src/WindowParameters.hpp @@ -0,0 +1,60 @@ +/* + * DISTRHO Cardinal Plugin + * Copyright (C) 2021 Filipe Coelho + * + * 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