diff --git a/src/CardinalPlugin.cpp b/src/CardinalPlugin.cpp index de654ea..fd26266 100644 --- a/src/CardinalPlugin.cpp +++ b/src/CardinalPlugin.cpp @@ -87,11 +87,6 @@ namespace plugin { void initStaticPlugins(); void destroyStaticPlugins(); } -#ifndef HEADLESS -namespace window { -void WindowInit(Window* window, DISTRHO_NAMESPACE::Plugin* plugin); -} -#endif } START_NAMESPACE_DISTRHO diff --git a/src/CardinalRemote/RemoteUI.cpp b/src/CardinalRemote/RemoteUI.cpp index 97f4038..9555565 100644 --- a/src/CardinalRemote/RemoteUI.cpp +++ b/src/CardinalRemote/RemoteUI.cpp @@ -27,53 +27,31 @@ #include #include "AsyncDialog.hpp" -#include "WindowParameters.hpp" - -// -------------------------------------------------------------------------------------------------------------------- - -namespace rack { -namespace app { - widget::Widget* createMenuBar(bool isStandalone); -} -namespace window { - void WindowSetPluginRemote(Window* window, NanoTopLevelWidget* tlw); - void WindowSetMods(Window* window, int mods); - void WindowSetInternalSize(rack::window::Window* window, math::Vec size); -} -} // -------------------------------------------------------------------------------------------------------------------- CardinalRemoteUI::CardinalRemoteUI(Window& window, const std::string& templatePath) : NanoTopLevelWidget(window) { - CardinalPluginContext& context(*static_cast(rack::contextGet())); - context.nativeWindowId = getWindow().getNativeWindowHandle(); - context.tlw = this; - - window.setIgnoringKeyRepeat(true); - context.nativeWindowId = window.getNativeWindowHandle(); - - const double scaleFactor = getScaleFactor(); + CardinalPluginContext* const context = static_cast(rack::contextGet()); + context->nativeWindowId = window.getNativeWindowHandle(); + context->tlw = this; - setGeometryConstraints(648 * scaleFactor, 538 * scaleFactor); + // -------------------------------------------------------------------------- - if (scaleFactor != 1.0) - setSize(DISTRHO_UI_DEFAULT_WIDTH * scaleFactor, DISTRHO_UI_DEFAULT_HEIGHT * scaleFactor); + rack::window::WindowSetPluginRemote(context->window, this); - rack::window::WindowSetPluginRemote(context.window, this); - - if (rack::widget::Widget* const menuBar = context.scene->menuBar) + if (rack::widget::Widget* const menuBar = context->scene->menuBar) { - context.scene->removeChild(menuBar); + context->scene->removeChild(menuBar); delete menuBar; } - context.scene->menuBar = rack::app::createMenuBar(true); - context.scene->addChildBelow(context.scene->menuBar, context.scene->rackScroll); + context->scene->menuBar = rack::app::createMenuBar(true); + context->scene->addChildBelow(context->scene->menuBar, context->scene->rackScroll); // hide "Browse VCV Library" button - rack::widget::Widget* const browser = context.scene->browser->children.back(); + rack::widget::Widget* const browser = context->scene->browser->children.back(); rack::widget::Widget* const headerLayout = browser->children.front(); rack::widget::Widget* const libraryButton = headerLayout->children.back(); libraryButton->hide(); @@ -84,46 +62,157 @@ CardinalRemoteUI::CardinalRemoteUI(Window& window, const std::string& templatePa if (rack::asset::systemDir.empty()) { errorMessage = "Failed to locate Cardinal plugin bundle.\n" - "Install Cardinal with its plugin bundle folder intact and try again."; + "Install Cardinal with its plugin bundle folder intact and try again."; } else if (! rack::system::exists(rack::asset::systemDir)) { errorMessage = rack::string::f("System directory \"%s\" does not exist. " - "Make sure Cardinal was downloaded and installed correctly.", - rack::asset::systemDir.c_str()); + "Make sure Cardinal was downloaded and installed correctly.", + rack::asset::systemDir.c_str()); } if (! errorMessage.empty()) - { - static bool shown = false; + asyncDialog::create(errorMessage.c_str()); - if (! shown) - { - shown = true; - asyncDialog::create(errorMessage.c_str()); - } - } + context->window->step(); + + WindowParametersSetCallback(context->window, this); - context.window->step(); + // -------------------------------------------------------------------------- - // WindowParametersSetCallback(context.window, this); + addIdleCallback(this); } CardinalRemoteUI::~CardinalRemoteUI() { - CardinalPluginContext& context(*static_cast(rack::contextGet())); - context.nativeWindowId = 0; + removeIdleCallback(this); + + // -------------------------------------------------------------------------- + + CardinalPluginContext* const context = static_cast(rack::contextGet()); + + context->nativeWindowId = 0; + + if (rack::widget::Widget* const menuBar = context->scene->menuBar) + { + context->scene->removeChild(menuBar); + delete menuBar; + } + + context->scene->menuBar = rack::app::createMenuBar(true); + context->scene->addChildBelow(context->scene->menuBar, context->scene->rackScroll); + + rack::window::WindowSetPluginRemote(context->window, nullptr); } void CardinalRemoteUI::onNanoDisplay() { - CardinalPluginContext& context(*static_cast(rack::contextGet())); - context.window->step(); + CardinalPluginContext* const context = static_cast(rack::contextGet()); + const ScopedContext sc(context); + context->window->step(); +} - // TODO +void CardinalRemoteUI::idleCallback() +{ + /* + if (filebrowserhandle != nullptr && fileBrowserIdle(filebrowserhandle)) + { + { + const char* const path = fileBrowserGetPath(filebrowserhandle); + + const ScopedContext sc(this); + filebrowseraction(path != nullptr ? strdup(path) : nullptr); + } + + fileBrowserClose(filebrowserhandle); + filebrowseraction = nullptr; + filebrowserhandle = nullptr; + } + */ + + if (windowParameters.rateLimit != 0 && ++rateLimitStep % (windowParameters.rateLimit * 2)) + return; + + rateLimitStep = 0; repaint(); } +void CardinalRemoteUI::WindowParametersChanged(const WindowParameterList param, float value) +{ + float mult = 1.0f; + + switch (param) + { + case kWindowParameterShowTooltips: + windowParameters.tooltips = value > 0.5f; + break; + case kWindowParameterCableOpacity: + mult = 100.0f; + windowParameters.cableOpacity = value; + break; + case kWindowParameterCableTension: + mult = 100.0f; + windowParameters.cableTension = value; + break; + case kWindowParameterRackBrightness: + mult = 100.0f; + windowParameters.rackBrightness = value; + break; + case kWindowParameterHaloBrightness: + mult = 100.0f; + windowParameters.haloBrightness = value; + break; + case kWindowParameterKnobMode: + switch (static_cast(value + 0.5f)) + { + case rack::settings::KNOB_MODE_LINEAR: + value = 0; + windowParameters.knobMode = rack::settings::KNOB_MODE_LINEAR; + break; + case rack::settings::KNOB_MODE_ROTARY_ABSOLUTE: + value = 1; + windowParameters.knobMode = rack::settings::KNOB_MODE_ROTARY_ABSOLUTE; + break; + case rack::settings::KNOB_MODE_ROTARY_RELATIVE: + value = 2; + windowParameters.knobMode = rack::settings::KNOB_MODE_ROTARY_RELATIVE; + break; + } + break; + case kWindowParameterWheelKnobControl: + windowParameters.knobScroll = value > 0.5f; + break; + case kWindowParameterWheelSensitivity: + mult = 1000.0f; + windowParameters.knobScrollSensitivity = value; + break; + case kWindowParameterLockModulePositions: + windowParameters.lockModules = value > 0.5f; + break; + case kWindowParameterUpdateRateLimit: + windowParameters.rateLimit = static_cast(value + 0.5f); + rateLimitStep = 0; + break; + case kWindowParameterBrowserSort: + windowParameters.browserSort = static_cast(value + 0.5f); + break; + case kWindowParameterBrowserZoom: + windowParameters.browserZoom = value; + value = std::pow(2.f, value) * 100.0f; + break; + case kWindowParameterInvertZoom: + windowParameters.invertZoom = value > 0.5f; + break; + case kWindowParameterSqueezeModulePositions: + windowParameters.squeezeModules = value > 0.5f; + break; + default: + return; + } + + // setParameterValue(kModuleParameters + param + 1, value * mult); +} + // -------------------------------------------------------------------------------------------------------------------- static int glfwMods(const uint mod) noexcept @@ -187,6 +276,7 @@ bool CardinalRemoteUI::onMouse(const MouseEvent& ev) #endif CardinalPluginContext* context = static_cast(rack::contextGet()); + const ScopedContext sc(context, mods); return context->event->handleButton(lastMousePos, button, action, mods); } @@ -198,6 +288,7 @@ bool CardinalRemoteUI::onMotion(const MotionEvent& ev) lastMousePos = mousePos; CardinalPluginContext* context = static_cast(rack::contextGet()); + const ScopedContext sc(context); return context->event->handleHover(mousePos, mouseDelta); } @@ -211,6 +302,7 @@ bool CardinalRemoteUI::onScroll(const ScrollEvent& ev) const int mods = glfwMods(ev.mod); CardinalPluginContext* context = static_cast(rack::contextGet()); + const ScopedContext sc(context, mods); return context->event->handleScroll(lastMousePos, scrollDelta); } @@ -222,6 +314,7 @@ bool CardinalRemoteUI::onCharacterInput(const CharacterInputEvent& ev) const int mods = glfwMods(ev.mod); CardinalPluginContext* context = static_cast(rack::contextGet()); + const ScopedContext sc(context, mods); return context->event->handleText(lastMousePos, ev.character); } @@ -303,6 +396,7 @@ bool CardinalRemoteUI::onKeyboard(const KeyboardEvent& ev) } CardinalPluginContext* context = static_cast(rack::contextGet()); + const ScopedContext sc(context, mods); return context->event->handleKey(lastMousePos, key, ev.keycode, action, mods); } @@ -311,8 +405,7 @@ void CardinalRemoteUI::onResize(const ResizeEvent& ev) NanoTopLevelWidget::onResize(ev); CardinalPluginContext* context = static_cast(rack::contextGet()); - if (context->window != nullptr) - WindowSetInternalSize(context->window, rack::math::Vec(ev.size.getWidth(), ev.size.getHeight())); + WindowSetInternalSize(context->window, rack::math::Vec(ev.size.getWidth(), ev.size.getHeight())); } // -------------------------------------------------------------------------------------------------------------------- diff --git a/src/CardinalRemote/RemoteUI.hpp b/src/CardinalRemote/RemoteUI.hpp index ca29d28..402c48f 100644 --- a/src/CardinalRemote/RemoteUI.hpp +++ b/src/CardinalRemote/RemoteUI.hpp @@ -19,12 +19,54 @@ #include "NanoVG.hpp" #include "PluginContext.hpp" +#include "WindowParameters.hpp" -#include +#include -class CardinalRemoteUI : public NanoTopLevelWidget +// -------------------------------------------------------------------------------------------------------------------- + +namespace rack { +namespace app { + widget::Widget* createMenuBar(bool isStandalone); +} +namespace window { + void WindowSetPluginRemote(Window* window, NanoTopLevelWidget* tlw); + void WindowSetMods(Window* window, int mods); + void WindowSetInternalSize(rack::window::Window* window, math::Vec size); +} +} + +// -------------------------------------------------------------------------------------------------------------------- + +class CardinalRemoteUI : public NanoTopLevelWidget, + public IdleCallback, + public WindowParametersCallback { rack::math::Vec lastMousePos; + WindowParameters windowParameters; + int rateLimitStep = 0; + + struct ScopedContext { + CardinalPluginContext* const context; + + ScopedContext(CardinalPluginContext* const c) + : context(c) + { + WindowParametersRestore(context->window); + } + + ScopedContext(CardinalPluginContext* const c, const int mods) + : context(c) + { + rack::window::WindowSetMods(context->window, mods); + WindowParametersRestore(context->window); + } + + ~ScopedContext() + { + WindowParametersSave(context->window); + } + }; public: explicit CardinalRemoteUI(Window& window, const std::string& templatePath); @@ -32,6 +74,8 @@ public: protected: void onNanoDisplay() override; + void idleCallback() override; + void WindowParametersChanged(const WindowParameterList param, float value) override; bool onMouse(const MouseEvent& ev) override; bool onMotion(const MotionEvent& ev) override; bool onScroll(const ScrollEvent& ev) override; @@ -41,3 +85,5 @@ protected: DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(CardinalRemoteUI) }; + +// -------------------------------------------------------------------------------------------------------------------- diff --git a/src/CardinalRemote/main.cpp b/src/CardinalRemote/main.cpp index b5b6788..381b086 100644 --- a/src/CardinalRemote/main.cpp +++ b/src/CardinalRemote/main.cpp @@ -30,13 +30,24 @@ #include #include - #include "PluginContext.hpp" +#include "extra/ScopedValueSetter.hpp" + +#define CARDINAL_TEMPLATE_NAME "init/main.vcv" + +extern const std::string CARDINAL_VERSION; namespace rack { +namespace asset { +std::string patchesPath(); +void destroy(); +} +namespace engine { +void Engine_setAboutToClose(Engine*); +} namespace plugin { - void initStaticPlugins(); - void destroyStaticPlugins(); +void initStaticPlugins(); +void destroyStaticPlugins(); } } @@ -54,17 +65,32 @@ bool requestBufferSizeChange(uint) { return false; } bool requestMIDI() { return false; } const char* getPluginFormatName() noexcept { return "Remote"; } +FileBrowserHandle fileBrowserCreate(bool, ulong, double, const FileBrowserOptions&) { return nullptr; } + uint32_t Plugin::getBufferSize() const noexcept { return 128; } double Plugin::getSampleRate() const noexcept { return 48000; } bool Plugin::writeMidiEvent(const MidiEvent&) noexcept { return false; } +void UI::editParameter(uint, bool) {} +void UI::setParameterValue(uint, float) {} +void UI::setState(const char*, const char*) {} +bool UI::openFileBrowser(const FileBrowserOptions&) { return false; } + END_NAMESPACE_DISTRHO int main(const int argc, const char* argv[]) { using namespace rack; + std::string templatePath, factoryTemplatePath; + + // -------------------------------------------------------------------------- + + #ifdef DISTRHO_OS_WASM + settings::allowCursorLock = true; + #else settings::allowCursorLock = false; + #endif settings::autoCheckUpdates = false; settings::autosaveInterval = 0; settings::devMode = true; @@ -98,20 +124,18 @@ int main(const int argc, const char* argv[]) random::init(); ui::init(); - std::string templatePath; #ifdef CARDINAL_PLUGIN_SOURCE_DIR // Make system dir point to source code location as fallback asset::systemDir = CARDINAL_PLUGIN_SOURCE_DIR DISTRHO_OS_SEP_STR "Rack"; + asset::bundlePath.clear(); - if (system::exists(system::join(asset::systemDir, "res"))) - { - templatePath = CARDINAL_PLUGIN_SOURCE_DIR DISTRHO_OS_SEP_STR "template.vcv"; - } // If source code dir does not exist use install target prefix as system dir - else + if (!system::exists(system::join(asset::systemDir, "res"))) #endif { - #if defined(ARCH_MAC) + #if defined(DISTRHO_OS_WASM) + asset::systemDir = "/resources"; + #elif defined(ARCH_MAC) asset::systemDir = "/Library/Application Support/Cardinal"; #elif defined(ARCH_WIN) const std::string commonprogfiles = getSpecialPath(kSpecialPathCommonProgramFiles); @@ -121,33 +145,37 @@ int main(const int argc, const char* argv[]) asset::systemDir = CARDINAL_PLUGIN_PREFIX "/share/cardinal"; #endif - if (! asset::systemDir.empty()) - { - asset::bundlePath = system::join(asset::systemDir, "PluginManifests"); - templatePath = system::join(asset::systemDir, "template.vcv"); - } + asset::bundlePath = system::join(asset::systemDir, "PluginManifests"); } - + asset::userDir = asset::systemDir; + const std::string patchesPath = asset::patchesPath(); + #ifdef DISTRHO_OS_WASM + templatePath = system::join(patchesPath, CARDINAL_WASM_WELCOME_TEMPLATE_FILENAME); + #else + templatePath = system::join(patchesPath, CARDINAL_TEMPLATE_NAME); + #endif + factoryTemplatePath = system::join(patchesPath, CARDINAL_TEMPLATE_NAME); + // Log environment - INFO("%s %s version %s", APP_NAME.c_str(), APP_EDITION.c_str(), APP_VERSION.c_str()); + INFO("%s %s %s, compatible with Rack version %s", APP_NAME.c_str(), APP_EDITION.c_str(), CARDINAL_VERSION.c_str(), APP_VERSION.c_str()); INFO("%s", system::getOperatingSystemInfo().c_str()); -// INFO("Binary filename: %s", getBinaryFilename()); INFO("System directory: %s", asset::systemDir.c_str()); INFO("User directory: %s", asset::userDir.c_str()); INFO("Template patch: %s", templatePath.c_str()); + INFO("System template patch: %s", factoryTemplatePath.c_str()); // Report to user if something is wrong with the installation if (asset::systemDir.empty()) { d_stderr2("Failed to locate Cardinal plugin bundle.\n" - "Install Cardinal with its bundle folder intact and try again."); + "Install Cardinal with its bundle folder intact and try again."); } else if (! system::exists(asset::systemDir)) { d_stderr2("System directory \"%s\" does not exist.\n" - "Make sure Cardinal was downloaded and installed correctly.", asset::systemDir.c_str()); + "Make sure Cardinal was downloaded and installed correctly.", asset::systemDir.c_str()); } INFO("Initializing plugins"); @@ -156,8 +184,10 @@ int main(const int argc, const char* argv[]) INFO("Initializing plugin browser DB"); app::browserInit(); + // -------------------------------------------------------------------------- + // create unique temporary path for this instance - std::string autosavePath; + std::string fAutosavePath; try { char uidBuf[24]; @@ -165,58 +195,92 @@ int main(const int argc, const char* argv[]) for (int i=1;; ++i) { - std::snprintf(uidBuf, sizeof(uidBuf), "CardinalRemote.%04d", i); + std::snprintf(uidBuf, sizeof(uidBuf), "Cardinal.%04d", i); const std::string trypath = rack::system::join(tmp, uidBuf); if (! rack::system::exists(trypath)) { if (rack::system::createDirectories(trypath)) - autosavePath = trypath; + fAutosavePath = trypath; break; } } } DISTRHO_SAFE_EXCEPTION("create unique temporary path"); - CardinalPluginContext context(nullptr); - rack::contextSet(&context); + CardinalPluginContext* const context = new CardinalPluginContext(nullptr); + rack::contextSet(context); + + const float sampleRate = 48000; + rack::settings::sampleRate = sampleRate; + + context->bufferSize = 512; + context->sampleRate = sampleRate; - context.bufferSize = 512; - rack::settings::sampleRate = context.sampleRate = 48000; + context->engine = new rack::engine::Engine; + context->engine->setSampleRate(sampleRate); - context.engine = new rack::engine::Engine; - context.engine->setSampleRate(context.sampleRate); + context->history = new rack::history::State; + context->patch = new rack::patch::Manager; + context->patch->autosavePath = fAutosavePath; + context->patch->templatePath = templatePath; + context->patch->factoryTemplatePath = factoryTemplatePath; - context.history = new rack::history::State; - context.patch = new rack::patch::Manager; - context.patch->autosavePath = autosavePath; - context.patch->templatePath = templatePath; + context->event = new rack::widget::EventState; + context->scene = new rack::app::Scene; + context->event->rootWidget = context->scene; - context.event = new rack::widget::EventState; - context.scene = new rack::app::Scene; - context.event->rootWidget = context.scene; - context.window = new rack::window::Window; + context->window = new rack::window::Window; - context.patch->loadTemplate(); - context.scene->rackScroll->reset(); + context->patch->loadTemplate(); + context->scene->rackScroll->reset(); + // swap to factory template after first load + context->patch->templatePath = context->patch->factoryTemplatePath; + + // -------------------------------------------------------------------------- Application app; - Window win(app); - win.setResizable(true); - win.setTitle("CardinalRemote"); + Window window(app); + window.setIgnoringKeyRepeat(true); + window.setResizable(true); + window.setTitle("CardinalRemote"); + + // -------------------------------------------------------------------------- + + const double scaleFactor = window.getScaleFactor(); + + window.setGeometryConstraints(648 * scaleFactor, 538 * scaleFactor); + + if (scaleFactor != 1.0) + window.setSize(DISTRHO_UI_DEFAULT_WIDTH * scaleFactor, + DISTRHO_UI_DEFAULT_HEIGHT * scaleFactor); + + // -------------------------------------------------------------------------- + ScopedPointer remoteUI; { - const Window::ScopedGraphicsContext sgc(win); - remoteUI = new CardinalRemoteUI(win, templatePath); + const Window::ScopedGraphicsContext sgc(window); + remoteUI = new CardinalRemoteUI(window, templatePath); } - win.show(); + window.show(); app.exec(); - context.patch->clear(); + // -------------------------------------------------------------------------- + + { + context->patch->clear(); + + // do a little dance to prevent context scene deletion from saving to temp dir + const ScopedValueSetter svs(rack::settings::headless, true); + Engine_setAboutToClose(context->engine); + delete context; + } + + if (! fAutosavePath.empty()) + rack::system::removeRecursively(fAutosavePath); - if (! autosavePath.empty()) - rack::system::removeRecursively(autosavePath); + // -------------------------------------------------------------------------- INFO("Clearing asset paths"); asset::bundlePath.clear(); @@ -226,11 +290,16 @@ int main(const int argc, const char* argv[]) INFO("Destroying plugins"); plugin::destroyStaticPlugins(); + INFO("Destroying colourized assets"); + asset::destroy(); + INFO("Destroying settings"); settings::destroy(); INFO("Destroying logger"); logger::destroy(); + // -------------------------------------------------------------------------- + return 0; }