diff --git a/include/helpers.hpp b/include/helpers.hpp index 6c21c34..8ffbe7f 100644 --- a/include/helpers.hpp +++ b/include/helpers.hpp @@ -1,6 +1,6 @@ /* * DISTRHO Cardinal Plugin - * Copyright (C) 2021 Filipe Coelho + * Copyright (C) 2021-2022 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 diff --git a/include/rack.hpp b/include/rack.hpp deleted file mode 100644 index 3ca4cc6..0000000 --- a/include/rack.hpp +++ /dev/null @@ -1,35 +0,0 @@ -/* - * 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_next - -#ifdef BUILDING_PLUGIN_MODULES -namespace rack { -namespace app { -struct CardinalModuleWidget : ModuleWidget { - CardinalModuleWidget() : ModuleWidget() {} - DEPRECATED CardinalModuleWidget(engine::Module* module) : ModuleWidget() { - setModule(module); - } - void onButton(const ButtonEvent& e) override; -}; -} -} -# define ModuleWidget CardinalModuleWidget -#endif diff --git a/plugins/BidooDark/plugin.cpp b/plugins/BidooDark/plugin.cpp index 9e2a23d..0fc3ddf 100644 --- a/plugins/BidooDark/plugin.cpp +++ b/plugins/BidooDark/plugin.cpp @@ -1,5 +1,4 @@ #include "../Bidoo/src/plugin.hpp" -#undef ModuleWidget void InstantiateExpanderItem::onAction(const event::Action &e) { engine::Module* module = model->createModule(); @@ -36,5 +35,5 @@ void BidooWidget::prepareThemes(const std::string& filename) { } void BidooWidget::step() { - CardinalModuleWidget::step(); + ModuleWidget::step(); } diff --git a/plugins/WhatTheRack b/plugins/WhatTheRack index e373378..82c3c5e 160000 --- a/plugins/WhatTheRack +++ b/plugins/WhatTheRack @@ -1 +1 @@ -Subproject commit e373378491d2cf3b8257137d154aef1d389c5204 +Subproject commit 82c3c5e11176c364b61d501059d9182e86464eca diff --git a/plugins/forsitan-modulare b/plugins/forsitan-modulare index 056cc2e..494fefa 160000 --- a/plugins/forsitan-modulare +++ b/plugins/forsitan-modulare @@ -1 +1 @@ -Subproject commit 056cc2ec9186a4175d9214eee91e4ff5cc2e5fb1 +Subproject commit 494fefaf38cb48928a165fd20d1535a0690bf613 diff --git a/src/CardinalModuleWidget.cpp b/src/CardinalModuleWidget.cpp index 39aa43a..854e2fd 100644 --- a/src/CardinalModuleWidget.cpp +++ b/src/CardinalModuleWidget.cpp @@ -40,94 +40,11 @@ #include #include +#undef ModuleWidget + namespace rack { namespace app { -struct CardinalModuleWidget : ModuleWidget { - CardinalModuleWidget() : ModuleWidget() {} - DEPRECATED CardinalModuleWidget(engine::Module* module) : ModuleWidget() { - setModule(module); - } - void onButton(const ButtonEvent& e) override; -}; - -struct ModuleWidget::Internal { - math::Vec dragOffset; - math::Vec dragRackPos; - bool dragEnabled; - widget::Widget* panel; -}; - -static void CardinalModuleWidget__loadDialog(ModuleWidget* const w) -{ - std::string presetDir = w->model->getUserPresetDirectory(); - system::createDirectories(presetDir); - - WeakPtr weakThis = w; - - async_dialog_filebrowser(false, nullptr, presetDir.c_str(), "Load preset", [=](char* pathC) { - // Delete directories if empty - DEFER({ - try { - system::remove(presetDir); - system::remove(system::getDirectory(presetDir)); - } - catch (Exception& e) { - // Ignore exceptions if directory cannot be removed. - } - }); - - if (!weakThis) - return; - if (!pathC) - return; - - try { - weakThis->loadAction(pathC); - } - catch (Exception& e) { - async_dialog_message(e.what()); - } - - std::free(pathC); - }); -} - -void CardinalModuleWidget__saveDialog(ModuleWidget* const w) -{ - const std::string presetDir = w->model->getUserPresetDirectory(); - system::createDirectories(presetDir); - - WeakPtr weakThis = w; - - async_dialog_filebrowser(true, "preset.vcvm", presetDir.c_str(), "Save preset", [=](char* pathC) { - // Delete directories if empty - DEFER({ - try { - system::remove(presetDir); - system::remove(system::getDirectory(presetDir)); - } - catch (Exception& e) { - // Ignore exceptions if directory cannot be removed. - } - }); - - if (!weakThis) - return; - if (!pathC) - return; - - std::string path = pathC; - std::free(pathC); - - // Automatically append .vcvm extension - if (system::getExtension(path) != ".vcvm") - path += ".vcvm"; - - weakThis->save(path); - }); -} - // Create ModulePresetPathItems for each patch in a directory. static void appendPresetItems(ui::Menu* menu, WeakPtr moduleWidget, std::string presetDir) { bool foundPresets = false; @@ -165,117 +82,6 @@ static void appendPresetItems(ui::Menu* menu, WeakPtr moduleWidget } }; -static void CardinalModuleWidget__createContextMenu(ModuleWidget* const w, - plugin::Model* const model, - engine::Module* const module) { - DISTRHO_SAFE_ASSERT_RETURN(model != nullptr,); - - ui::Menu* menu = createMenu(); - - WeakPtr weakThis = w; - - // Brand and module name - menu->addChild(createMenuLabel(model->name)); - menu->addChild(createMenuLabel(model->plugin->brand)); - - // Info - menu->addChild(createSubmenuItem("Info", "", [model](ui::Menu* menu) { - model->appendContextMenu(menu); - })); - - // Preset - menu->addChild(createSubmenuItem("Preset", "", [weakThis](ui::Menu* menu) { - menu->addChild(createMenuItem("Copy", RACK_MOD_CTRL_NAME "+C", [weakThis]() { - if (!weakThis) - return; - weakThis->copyClipboard(); - })); - - menu->addChild(createMenuItem("Paste", RACK_MOD_CTRL_NAME "+V", [weakThis]() { - if (!weakThis) - return; - weakThis->pasteClipboardAction(); - })); - - menu->addChild(createMenuItem("Open", "", [weakThis]() { - if (!weakThis) - return; - CardinalModuleWidget__loadDialog(weakThis); - })); - - /* TODO requires setting up user dir - menu->addChild(createMenuItem("Save as", "", [weakThis]() { - if (!weakThis) - return; - CardinalModuleWidget__saveDialog(weakThis); - })); - - // Scan `/presets//` for presets. - menu->addChild(new ui::MenuSeparator); - menu->addChild(createMenuLabel("User presets")); - appendPresetItems(menu, weakThis, weakThis->model->getUserPresetDirectory()); - */ - - // Scan `/presets/` for presets. - appendPresetItems(menu, weakThis, weakThis->model->getFactoryPresetDirectory()); - })); - - // Initialize - menu->addChild(createMenuItem("Initialize", RACK_MOD_CTRL_NAME "+I", [weakThis]() { - if (!weakThis) - return; - weakThis->resetAction(); - })); - - // Randomize - menu->addChild(createMenuItem("Randomize", RACK_MOD_CTRL_NAME "+R", [weakThis]() { - if (!weakThis) - return; - weakThis->randomizeAction(); - })); - - // Disconnect cables - menu->addChild(createMenuItem("Disconnect cables", RACK_MOD_CTRL_NAME "+U", [weakThis]() { - if (!weakThis) - return; - weakThis->disconnectAction(); - })); - - // Bypass - std::string bypassText = RACK_MOD_CTRL_NAME "+E"; - bool bypassed = module && module->isBypassed(); - if (bypassed) - bypassText += " " CHECKMARK_STRING; - menu->addChild(createMenuItem("Bypass", bypassText, [weakThis, bypassed]() { - if (!weakThis) - return; - weakThis->bypassAction(!bypassed); - })); - - // Duplicate - menu->addChild(createMenuItem("Duplicate", RACK_MOD_CTRL_NAME "+D", [weakThis]() { - if (!weakThis) - return; - weakThis->cloneAction(false); - })); - - // Duplicate with cables - menu->addChild(createMenuItem("└ with cables", RACK_MOD_SHIFT_NAME "+" RACK_MOD_CTRL_NAME "+D", [weakThis]() { - if (!weakThis) - return; - weakThis->cloneAction(true); - })); - - // Delete - menu->addChild(createMenuItem("Delete", "Backspace/Delete", [weakThis]() { - if (!weakThis) - return; - weakThis->removeAction(); - }, false, true)); - - w->appendContextMenu(menu); -} - static void CardinalModuleWidget__saveSelectionDialog(RackWidget* const w) { std::string selectionDir = asset::user("selections"); @@ -304,75 +110,6 @@ static void CardinalModuleWidget__saveSelectionDialog(RackWidget* const w) }); } -void CardinalModuleWidget::onButton(const ButtonEvent& e) -{ - const bool selected = APP->scene->rack->isSelected(this); - - if (selected) { - if (e.button == GLFW_MOUSE_BUTTON_RIGHT) { - if (e.action == GLFW_PRESS) { - // Open selection context menu on right-click - ui::Menu* menu = createMenu(); - patchUtils::appendSelectionContextMenu(menu); - } - e.consume(this); - } - - if (e.button == GLFW_MOUSE_BUTTON_LEFT) { - if (e.action == GLFW_PRESS) { - // Toggle selection on Shift-click - if ((e.mods & RACK_MOD_MASK) == GLFW_MOD_SHIFT) { - APP->scene->rack->select(this, false); - e.consume(NULL); - return; - } - - // If module positions are locked, don't consume left-click - if (settings::lockModules) { - return; - } - - internal->dragOffset = e.pos; - } - - e.consume(this); - } - - return; - } - - // Dispatch event to children - Widget::onButton(e); - e.stopPropagating(); - if (e.isConsumed()) - return; - - if (e.button == GLFW_MOUSE_BUTTON_LEFT) { - if (e.action == GLFW_PRESS) { - // Toggle selection on Shift-click - if ((e.mods & RACK_MOD_MASK) == GLFW_MOD_SHIFT) { - APP->scene->rack->select(this, true); - e.consume(NULL); - return; - } - - // If module positions are locked, don't consume left-click - if (settings::lockModules) { - return; - } - - internal->dragOffset = e.pos; - } - e.consume(this); - } - - // Open context menu on right-click - if (e.button == GLFW_MOUSE_BUTTON_RIGHT && e.action == GLFW_PRESS) { - CardinalModuleWidget__createContextMenu(this, model, module); - e.consume(this); - } -} - } } diff --git a/src/Makefile b/src/Makefile index c2d1895..aa5219e 100644 --- a/src/Makefile +++ b/src/Makefile @@ -140,6 +140,7 @@ RACK_FILES += override/plugin.cpp RACK_FILES += override/Engine.cpp RACK_FILES += override/MenuBar.cpp RACK_FILES += override/Model.cpp +RACK_FILES += override/ModuleWidget.cpp RACK_FILES += override/OpenGlWidget.cpp RACK_FILES += override/Scene.cpp @@ -164,6 +165,7 @@ IGNORED_FILES += Rack/src/rtmidi.cpp IGNORED_FILES += Rack/src/app/AudioDisplay.cpp IGNORED_FILES += Rack/src/app/MenuBar.cpp IGNORED_FILES += Rack/src/app/MidiDisplay.cpp +IGNORED_FILES += Rack/src/app/ModuleWidget.cpp IGNORED_FILES += Rack/src/app/Scene.cpp IGNORED_FILES += Rack/src/app/TipWindow.cpp IGNORED_FILES += Rack/src/dsp/minblep.cpp diff --git a/src/override/.generate-diffs.sh b/src/override/.generate-diffs.sh index 6e72394..df024be 100755 --- a/src/override/.generate-diffs.sh +++ b/src/override/.generate-diffs.sh @@ -7,6 +7,7 @@ diff -U3 ../Rack/src/common.cpp common.cpp > diffs/common.cpp.diff diff -U3 ../Rack/src/context.cpp context.cpp > diffs/context.cpp.diff diff -U3 ../Rack/src/plugin.cpp plugin.cpp > diffs/plugin.cpp.diff diff -U3 ../Rack/src/app/MenuBar.cpp MenuBar.cpp > diffs/MenuBar.cpp.diff +diff -U3 ../Rack/src/app/ModuleWidget.cpp ModuleWidget.cpp > diffs/ModuleWidget.cpp.diff diff -U3 ../Rack/src/app/Scene.cpp Scene.cpp > diffs/Scene.cpp.diff diff -U3 ../Rack/src/engine/Engine.cpp Engine.cpp > diffs/Engine.cpp.diff diff -U3 ../Rack/src/dsp/minblep.cpp minblep.cpp > diffs/minblep.cpp.diff diff --git a/src/override/ModuleWidget.cpp b/src/override/ModuleWidget.cpp new file mode 100644 index 0000000..438b8cf --- /dev/null +++ b/src/override/ModuleWidget.cpp @@ -0,0 +1,1151 @@ +/* + * DISTRHO Cardinal Plugin + * Copyright (C) 2021-2022 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. + */ + +/** + * This file is an edited version of VCVRack's ModuleWidget.cpp + * Copyright (C) 2016-2021 VCV. + * + * 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 (at your option) any later version. + */ + +#include "../../CardinalCommon.hpp" + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace rack { +namespace app { + + +static const char PRESET_FILTERS[] = "VCV Rack module preset (.vcvm):vcvm"; + + +struct ModuleWidget::Internal { + /** The module position clicked on to start dragging in the rack. + */ + math::Vec dragOffset; + + /** Global rack position the user clicked on. + */ + math::Vec dragRackPos; + bool dragEnabled = true; + + widget::Widget* panel = NULL; +}; + + +ModuleWidget::ModuleWidget() { + internal = new Internal; + box.size = math::Vec(0, RACK_GRID_HEIGHT); +} + +ModuleWidget::~ModuleWidget() { + clearChildren(); + setModule(NULL); + delete internal; +} + +plugin::Model* ModuleWidget::getModel() { + return model; +} + +void ModuleWidget::setModel(plugin::Model* model) { + assert(!this->model); + this->model = model; +} + +engine::Module* ModuleWidget::getModule() { + return module; +} + +void ModuleWidget::setModule(engine::Module* module) { + if (this->module) { + APP->engine->removeModule(this->module); + delete this->module; + this->module = NULL; + } + this->module = module; +} + +widget::Widget* ModuleWidget::getPanel() { + return internal->panel; +} + +void ModuleWidget::setPanel(widget::Widget* panel) { + // Remove existing panel + if (internal->panel) { + removeChild(internal->panel); + delete internal->panel; + internal->panel = NULL; + } + + if (panel) { + addChildBottom(panel); + internal->panel = panel; + box.size.x = std::round(panel->box.size.x / RACK_GRID_WIDTH) * RACK_GRID_WIDTH; + // If width is zero, set it to 12HP for sanity + if (box.size.x == 0.0) + box.size.x = 12 * RACK_GRID_WIDTH; + } +} + +void ModuleWidget::setPanel(std::shared_ptr svg) { + // Create SvgPanel + SvgPanel* panel = new SvgPanel; + panel->setBackground(svg); + setPanel(panel); +} + +void ModuleWidget::addParam(ParamWidget* param) { + addChild(param); +} + +void ModuleWidget::addInput(PortWidget* input) { + // Check that the port is an input + assert(input->type == engine::Port::INPUT); + // Check that the port doesn't have a duplicate ID + PortWidget* input2 = getInput(input->portId); + assert(!input2); + // Add port + addChild(input); +} + +void ModuleWidget::addOutput(PortWidget* output) { + // Check that the port is an output + assert(output->type == engine::Port::OUTPUT); + // Check that the port doesn't have a duplicate ID + PortWidget* output2 = getOutput(output->portId); + assert(!output2); + // Add port + addChild(output); +} + +template +T* getFirstDescendantOfTypeWithCondition(widget::Widget* w, F f) { + T* t = dynamic_cast(w); + if (t && f(t)) + return t; + + for (widget::Widget* child : w->children) { + T* foundT = getFirstDescendantOfTypeWithCondition(child, f); + if (foundT) + return foundT; + } + return NULL; +} + +ParamWidget* ModuleWidget::getParam(int paramId) { + return getFirstDescendantOfTypeWithCondition(this, [&](ParamWidget* pw) -> bool { + return pw->paramId == paramId; + }); +} + +PortWidget* ModuleWidget::getInput(int portId) { + return getFirstDescendantOfTypeWithCondition(this, [&](PortWidget* pw) -> bool { + return pw->type == engine::Port::INPUT && pw->portId == portId; + }); +} + +PortWidget* ModuleWidget::getOutput(int portId) { + return getFirstDescendantOfTypeWithCondition(this, [&](PortWidget* pw) -> bool { + return pw->type == engine::Port::OUTPUT && pw->portId == portId; + }); +} + +template +void doIfTypeRecursive(widget::Widget* w, F f) { + T* t = dynamic_cast(w); + if (t) + f(t); + + for (widget::Widget* child : w->children) { + doIfTypeRecursive(child, f); + } +} + +std::vector ModuleWidget::getParams() { + std::vector pws; + doIfTypeRecursive(this, [&](ParamWidget* pw) { + pws.push_back(pw); + }); + return pws; +} + +std::vector ModuleWidget::getPorts() { + std::vector pws; + doIfTypeRecursive(this, [&](PortWidget* pw) { + pws.push_back(pw); + }); + return pws; +} + +std::vector ModuleWidget::getInputs() { + std::vector pws; + doIfTypeRecursive(this, [&](PortWidget* pw) { + if (pw->type == engine::Port::INPUT) + pws.push_back(pw); + }); + return pws; +} + +std::vector ModuleWidget::getOutputs() { + std::vector pws; + doIfTypeRecursive(this, [&](PortWidget* pw) { + if (pw->type == engine::Port::OUTPUT) + pws.push_back(pw); + }); + return pws; +} + +void ModuleWidget::draw(const DrawArgs& args) { + nvgScissor(args.vg, RECT_ARGS(args.clipBox)); + + if (module && module->isBypassed()) { + nvgAlpha(args.vg, 0.33); + } + + Widget::draw(args); + + // Meter + if (module && settings::cpuMeter) { + float sampleRate = APP->engine->getSampleRate(); + const float* meterBuffer = module->meterBuffer(); + int meterLength = module->meterLength(); + int meterIndex = module->meterIndex(); + + // // Text background + // nvgBeginPath(args.vg); + // nvgRect(args.vg, 0.0, box.size.y - infoHeight, box.size.x, infoHeight); + // nvgFillColor(args.vg, nvgRGBAf(0, 0, 0, 0.75)); + // nvgFill(args.vg); + + // Draw time plot + const float plotHeight = box.size.y - BND_WIDGET_HEIGHT; + nvgBeginPath(args.vg); + nvgMoveTo(args.vg, 0.0, plotHeight); + math::Vec p1; + for (int i = 0; i < meterLength; i++) { + int index = math::eucMod(meterIndex + i + 1, meterLength); + float meter = math::clamp(meterBuffer[index] * sampleRate, 0.f, 1.f); + meter = std::max(0.f, meter); + math::Vec p; + p.x = (float) i / (meterLength - 1) * box.size.x; + p.y = (1.f - meter) * plotHeight; + if (i == 0) { + nvgLineTo(args.vg, VEC_ARGS(p)); + } + else { + math::Vec p2 = p; + p2.x -= 0.5f / (meterLength - 1) * box.size.x; + nvgBezierTo(args.vg, VEC_ARGS(p1), VEC_ARGS(p2), VEC_ARGS(p)); + } + p1 = p; + p1.x += 0.5f / (meterLength - 1) * box.size.x; + } + nvgLineTo(args.vg, box.size.x, plotHeight); + nvgClosePath(args.vg); + NVGcolor color = componentlibrary::SCHEME_ORANGE; + nvgFillColor(args.vg, color::alpha(color, 0.75)); + nvgFill(args.vg); + nvgStrokeWidth(args.vg, 2.0); + nvgStrokeColor(args.vg, color); + nvgStroke(args.vg); + + // Text background + bndMenuBackground(args.vg, 0.0, plotHeight, box.size.x, BND_WIDGET_HEIGHT, BND_CORNER_ALL); + + // Text + float percent = meterBuffer[meterIndex] * sampleRate * 100.f; + // float microseconds = meterBuffer[meterIndex] * 1e6f; + std::string meterText = string::f("%.1f", percent); + // Only append "%" if wider than 2 HP + if (box.getWidth() > RACK_GRID_WIDTH * 2) + meterText += "%"; + math::Vec pt; + pt.x = box.size.x - bndLabelWidth(args.vg, -1, meterText.c_str()) + 3; + pt.y = plotHeight + 0.5; + bndMenuLabel(args.vg, VEC_ARGS(pt), INFINITY, BND_WIDGET_HEIGHT, -1, meterText.c_str()); + } + + // Selection + if (APP->scene->rack->isSelected(this)) { + nvgBeginPath(args.vg); + nvgRect(args.vg, 0.0, 0.0, VEC_ARGS(box.size)); + nvgFillColor(args.vg, nvgRGBAf(1, 0, 0, 0.25)); + nvgFill(args.vg); + nvgStrokeWidth(args.vg, 2.0); + nvgStrokeColor(args.vg, nvgRGBAf(1, 0, 0, 0.5)); + nvgStroke(args.vg); + } + + nvgResetScissor(args.vg); +} + +void ModuleWidget::drawLayer(const DrawArgs& args, int layer) { + if (layer == -1) { + nvgBeginPath(args.vg); + float r = 20; // Blur radius + float c = 20; // Corner radius + math::Rect shadowBox = box.zeroPos().grow(math::Vec(10, -30)); + math::Rect shadowOutsideBox = shadowBox.grow(math::Vec(r, r)); + nvgRect(args.vg, RECT_ARGS(shadowOutsideBox)); + NVGcolor shadowColor = nvgRGBAf(0, 0, 0, 0.2); + NVGcolor transparentColor = nvgRGBAf(0, 0, 0, 0); + nvgFillPaint(args.vg, nvgBoxGradient(args.vg, RECT_ARGS(shadowBox), c, r, shadowColor, transparentColor)); + nvgFill(args.vg); + } + else { + Widget::drawLayer(args, layer); + } +} + +void ModuleWidget::onHover(const HoverEvent& e) { + if (APP->scene->rack->isSelected(this)) { + e.consume(this); + } + + OpaqueWidget::onHover(e); +} + +void ModuleWidget::onHoverKey(const HoverKeyEvent& e) { + if (e.action == GLFW_PRESS || e.action == GLFW_REPEAT) { + if (e.keyName == "c" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { + copyClipboard(); + e.consume(this); + } + if (e.keyName == "v" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { + if (pasteClipboardAction()) { + e.consume(this); + } + } + if (e.keyName == "d" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { + cloneAction(false); + e.consume(this); + } + if (e.keyName == "d" && (e.mods & RACK_MOD_MASK) == (RACK_MOD_CTRL | GLFW_MOD_SHIFT)) { + cloneAction(true); + e.consume(this); + } + if (e.keyName == "i" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { + resetAction(); + e.consume(this); + } + if (e.keyName == "r" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { + randomizeAction(); + e.consume(this); + } + if (e.keyName == "u" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { + disconnectAction(); + e.consume(this); + } + if (e.keyName == "e" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { + bypassAction(!module->isBypassed()); + e.consume(this); + } + if ((e.key == GLFW_KEY_DELETE || e.key == GLFW_KEY_BACKSPACE) && (e.mods & RACK_MOD_MASK) == 0) { + // Deletes `this` + removeAction(); + e.consume(NULL); + return; + } + if (e.key == GLFW_KEY_F1 && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { + std::string manualUrl = model->getManualUrl(); + if (!manualUrl.empty()) + system::openBrowser(manualUrl); + e.consume(this); + } + } + + if (e.isConsumed()) + return; + OpaqueWidget::onHoverKey(e); +} + +void ModuleWidget::onButton(const ButtonEvent& e) { + const bool selected = APP->scene->rack->isSelected(this); + + if (selected) { + if (e.button == GLFW_MOUSE_BUTTON_RIGHT) { + if (e.action == GLFW_PRESS) { + // Open selection context menu on right-click + ui::Menu* menu = createMenu(); + patchUtils::appendSelectionContextMenu(menu); + } + e.consume(this); + } + + if (e.button == GLFW_MOUSE_BUTTON_LEFT) { + if (e.action == GLFW_PRESS) { + // Toggle selection on Shift-click + if ((e.mods & RACK_MOD_MASK) == GLFW_MOD_SHIFT) { + APP->scene->rack->select(this, false); + e.consume(NULL); + return; + } + + // If module positions are locked, don't consume left-click + if (settings::lockModules) { + return; + } + + internal->dragOffset = e.pos; + } + + e.consume(this); + } + + return; + } + + // Dispatch event to children + Widget::onButton(e); + e.stopPropagating(); + if (e.isConsumed()) + return; + + if (e.button == GLFW_MOUSE_BUTTON_LEFT) { + if (e.action == GLFW_PRESS) { + // Toggle selection on Shift-click + if ((e.mods & RACK_MOD_MASK) == GLFW_MOD_SHIFT) { + APP->scene->rack->select(this, true); + e.consume(NULL); + return; + } + + // If module positions are locked, don't consume left-click + if (settings::lockModules) { + return; + } + + internal->dragOffset = e.pos; + } + e.consume(this); + } + + // Open context menu on right-click + if (e.button == GLFW_MOUSE_BUTTON_RIGHT && e.action == GLFW_PRESS) { + createContextMenu(); + e.consume(this); + } +} + +void ModuleWidget::onDragStart(const DragStartEvent& e) { + if (e.button == GLFW_MOUSE_BUTTON_LEFT) { + // HACK Disable FramebufferWidget redrawing subpixels while dragging + APP->window->fbDirtyOnSubpixelChange() = false; + + // Clear dragRack so dragging in not enabled until mouse is moved a bit. + internal->dragRackPos = math::Vec(NAN, NAN); + + // Prepare initial position of modules for history. + APP->scene->rack->updateModuleOldPositions(); + } +} + +void ModuleWidget::onDragEnd(const DragEndEvent& e) { + if (e.button == GLFW_MOUSE_BUTTON_LEFT) { + APP->window->fbDirtyOnSubpixelChange() = true; + + // The next time the module is dragged, it should always move immediately + internal->dragEnabled = true; + + history::ComplexAction* h = APP->scene->rack->getModuleDragAction(); + if (!h->isEmpty()) + APP->history->push(h); + else + delete h; + } +} + +void ModuleWidget::onDragMove(const DragMoveEvent& e) { + if (e.button == GLFW_MOUSE_BUTTON_LEFT) { + math::Vec mousePos = APP->scene->rack->getMousePos(); + + if (!internal->dragEnabled) { + // Set dragRackPos on the first time after dragging + if (!internal->dragRackPos.isFinite()) + internal->dragRackPos = mousePos; + // Check if the mouse has moved enough to start dragging the module. + const float minDist = RACK_GRID_WIDTH; + if (internal->dragRackPos.minus(mousePos).square() >= std::pow(minDist, 2)) + internal->dragEnabled = true; + } + + // Move module + if (internal->dragEnabled) { + // Round y coordinate to nearest rack height + math::Vec pos = mousePos; + pos.x -= internal->dragOffset.x; + pos.y -= RACK_GRID_HEIGHT / 2; + + if (APP->scene->rack->isSelected(this)) { + pos = (pos / RACK_GRID_SIZE).round() * RACK_GRID_SIZE; + math::Vec delta = pos.minus(box.pos); + APP->scene->rack->setSelectionPosNearest(delta); + } + else { + if (settings::squeezeModules) { + APP->scene->rack->setModulePosSqueeze(this, pos); + } + else { + if ((APP->window->getMods() & RACK_MOD_MASK) == RACK_MOD_CTRL) + APP->scene->rack->setModulePosForce(this, pos); + else + APP->scene->rack->setModulePosNearest(this, pos); + } + } + } + } +} + +void ModuleWidget::onDragHover(const DragHoverEvent& e) { + if (APP->scene->rack->isSelected(this)) { + e.consume(this); + } + + OpaqueWidget::onDragHover(e); +} + +json_t* ModuleWidget::toJson() { + json_t* moduleJ = APP->engine->moduleToJson(module); + return moduleJ; +} + +void ModuleWidget::fromJson(json_t* moduleJ) { + APP->engine->moduleFromJson(module, moduleJ); +} + +bool ModuleWidget::pasteJsonAction(json_t* moduleJ) { + engine::Module::jsonStripIds(moduleJ); + + json_t* oldModuleJ = toJson(); + DEFER({json_decref(oldModuleJ);}); + + try { + fromJson(moduleJ); + } + catch (Exception& e) { + WARN("%s", e.what()); + return false; + } + + // history::ModuleChange + history::ModuleChange* h = new history::ModuleChange; + h->name = "paste module preset"; + h->moduleId = module->id; + json_incref(oldModuleJ); + h->oldModuleJ = oldModuleJ; + json_incref(moduleJ); + h->newModuleJ = moduleJ; + APP->history->push(h); + return true; +} + +void ModuleWidget::copyClipboard() { + json_t* moduleJ = toJson(); + engine::Module::jsonStripIds(moduleJ); + + DEFER({json_decref(moduleJ);}); + char* json = json_dumps(moduleJ, JSON_INDENT(2)); + DEFER({std::free(json);}); + glfwSetClipboardString(APP->window->win, json); +} + +bool ModuleWidget::pasteClipboardAction() { + const char* json = glfwGetClipboardString(APP->window->win); + if (!json) { + WARN("Could not get text from clipboard."); + return false; + } + + json_error_t error; + json_t* moduleJ = json_loads(json, 0, &error); + if (!moduleJ) { + WARN("JSON parsing error at %s %d:%d %s", error.source, error.line, error.column, error.text); + return false; + } + DEFER({json_decref(moduleJ);}); + + return pasteJsonAction(moduleJ); +} + +void ModuleWidget::load(std::string filename) { + FILE* file = std::fopen(filename.c_str(), "r"); + if (!file) + throw Exception("Could not load patch file %s", filename.c_str()); + DEFER({std::fclose(file);}); + + INFO("Loading preset %s", filename.c_str()); + + json_error_t error; + json_t* moduleJ = json_loadf(file, 0, &error); + if (!moduleJ) + throw Exception("File is not a valid patch file. JSON parsing error at %s %d:%d %s", error.source, error.line, error.column, error.text); + DEFER({json_decref(moduleJ);}); + + engine::Module::jsonStripIds(moduleJ); + fromJson(moduleJ); +} + +void ModuleWidget::loadAction(std::string filename) { + // history::ModuleChange + history::ModuleChange* h = new history::ModuleChange; + h->name = "load module preset"; + h->moduleId = module->id; + h->oldModuleJ = toJson(); + + try { + load(filename); + } + catch (Exception& e) { + delete h; + throw; + } + + // TODO We can use `moduleJ` here instead to save a toJson() call. + h->newModuleJ = toJson(); + APP->history->push(h); +} + +void ModuleWidget::loadTemplate() { + std::string templatePath = system::join(model->getUserPresetDirectory(), "template.vcvm"); + try { + load(templatePath); + } + catch (Exception& e) { + // Do nothing + } +} + +void ModuleWidget::loadDialog() { + std::string presetDir = model->getUserPresetDirectory(); + system::createDirectories(presetDir); + + WeakPtr weakThis = this; + + async_dialog_filebrowser(false, nullptr, presetDir.c_str(), "Load preset", [=](char* pathC) { + // Delete directories if empty + DEFER({ + try { + system::remove(presetDir); + system::remove(system::getDirectory(presetDir)); + } + catch (Exception& e) { + // Ignore exceptions if directory cannot be removed. + } + }); + + if (!weakThis) + return; + if (!pathC) + return; + + try { + weakThis->loadAction(pathC); + } + catch (Exception& e) { + async_dialog_message(e.what()); + } + + std::free(pathC); + }); +} + +void ModuleWidget::save(std::string filename) { + INFO("Saving preset %s", filename.c_str()); + + json_t* moduleJ = toJson(); + assert(moduleJ); + DEFER({json_decref(moduleJ);}); + + engine::Module::jsonStripIds(moduleJ); + + FILE* file = std::fopen(filename.c_str(), "w"); + if (!file) { + std::string message = string::f("Could not save preset to file %s", filename.c_str()); + osdialog_message(OSDIALOG_WARNING, OSDIALOG_OK, message.c_str()); + return; + } + DEFER({std::fclose(file);}); + + json_dumpf(moduleJ, file, JSON_INDENT(2)); +} + +void ModuleWidget::saveTemplate() { + std::string presetDir = model->getUserPresetDirectory(); + system::createDirectories(presetDir); + std::string templatePath = system::join(presetDir, "template.vcvm"); + save(templatePath); +} + +void ModuleWidget::saveTemplateDialog() { + if (hasTemplate()) { + std::string message = string::f("Overwrite default preset for %s?", model->getFullName().c_str()); + if (!osdialog_message(OSDIALOG_INFO, OSDIALOG_OK_CANCEL, message.c_str())) + return; + } + saveTemplate(); +} + +bool ModuleWidget::hasTemplate() { + std::string presetDir = model->getUserPresetDirectory(); + std::string templatePath = system::join(presetDir, "template.vcvm"); + return system::exists(templatePath);; +} + +void ModuleWidget::clearTemplate() { + std::string presetDir = model->getUserPresetDirectory(); + std::string templatePath = system::join(presetDir, "template.vcvm"); + system::remove(templatePath); +} + +void ModuleWidget::clearTemplateDialog() { + std::string message = string::f("Delete default preset for %s?", model->getFullName().c_str()); + if (!osdialog_message(OSDIALOG_INFO, OSDIALOG_OK_CANCEL, message.c_str())) + return; + clearTemplate(); +} + +void ModuleWidget::saveDialog() { + const std::string presetDir = model->getUserPresetDirectory(); + system::createDirectories(presetDir); + + WeakPtr weakThis = this; + + async_dialog_filebrowser(true, "preset.vcvm", presetDir.c_str(), "Save preset", [=](char* pathC) { + // Delete directories if empty + DEFER({ + try { + system::remove(presetDir); + system::remove(system::getDirectory(presetDir)); + } + catch (Exception& e) { + // Ignore exceptions if directory cannot be removed. + } + }); + + if (!weakThis) + return; + if (!pathC) + return; + + std::string path = pathC; + std::free(pathC); + + // Automatically append .vcvm extension + if (system::getExtension(path) != ".vcvm") + path += ".vcvm"; + + weakThis->save(path); + }); +} + +void ModuleWidget::disconnect() { + for (PortWidget* pw : getPorts()) { + APP->scene->rack->clearCablesOnPort(pw); + } +} + +void ModuleWidget::resetAction() { + assert(module); + + // history::ModuleChange + history::ModuleChange* h = new history::ModuleChange; + h->name = "reset module"; + h->moduleId = module->id; + h->oldModuleJ = toJson(); + + APP->engine->resetModule(module); + + h->newModuleJ = toJson(); + APP->history->push(h); +} + +void ModuleWidget::randomizeAction() { + assert(module); + + // history::ModuleChange + history::ModuleChange* h = new history::ModuleChange; + h->name = "randomize module"; + h->moduleId = module->id; + h->oldModuleJ = toJson(); + + APP->engine->randomizeModule(module); + + h->newModuleJ = toJson(); + APP->history->push(h); +} + +void ModuleWidget::appendDisconnectActions(history::ComplexAction* complexAction) { + for (PortWidget* pw : getPorts()) { + for (CableWidget* cw : APP->scene->rack->getCompleteCablesOnPort(pw)) { + // history::CableRemove + history::CableRemove* h = new history::CableRemove; + h->setCable(cw); + complexAction->push(h); + // Delete cable + APP->scene->rack->removeCable(cw); + delete cw; + } + }; +} + +void ModuleWidget::disconnectAction() { + history::ComplexAction* complexAction = new history::ComplexAction; + complexAction->name = "disconnect cables"; + appendDisconnectActions(complexAction); + + if (!complexAction->isEmpty()) + APP->history->push(complexAction); + else + delete complexAction; +} + +void ModuleWidget::cloneAction(bool cloneCables) { + // history::ComplexAction + history::ComplexAction* h = new history::ComplexAction; + h->name = "duplicate module"; + + // Save patch store in this module so we can copy it below + APP->engine->prepareSaveModule(module); + + // JSON serialization is the obvious way to do this + json_t* moduleJ = toJson(); + DEFER({ + json_decref(moduleJ); + }); + engine::Module::jsonStripIds(moduleJ); + + // Clone Module + INFO("Creating module %s", model->getFullName().c_str()); + engine::Module* clonedModule = model->createModule(); + + // Set ID here so we can copy module storage dir + clonedModule->id = random::u64() % (1ull << 53); + system::copy(module->getPatchStorageDirectory(), clonedModule->getPatchStorageDirectory()); + + // This doesn't need a lock (via Engine::moduleFromJson()) because the Module is not added to the Engine yet. + try { + clonedModule->fromJson(moduleJ); + } + catch (Exception& e) { + WARN("%s", e.what()); + } + APP->engine->addModule(clonedModule); + + // Clone ModuleWidget + INFO("Creating module widget %s", model->getFullName().c_str()); + ModuleWidget* clonedModuleWidget = model->createModuleWidget(clonedModule); + APP->scene->rack->updateModuleOldPositions(); + APP->scene->rack->addModule(clonedModuleWidget); + // Place module to the right of `this` module, by forcing it to 1 HP to the right. + math::Vec clonedPos = box.pos; + clonedPos.x += clonedModuleWidget->box.getWidth(); + if (settings::squeezeModules) + APP->scene->rack->squeezeModulePos(clonedModuleWidget, clonedPos); + else + APP->scene->rack->setModulePosNearest(clonedModuleWidget, clonedPos); + h->push(APP->scene->rack->getModuleDragAction()); + APP->scene->rack->updateExpanders(); + + // history::ModuleAdd + history::ModuleAdd* hma = new history::ModuleAdd; + hma->setModule(clonedModuleWidget); + h->push(hma); + + if (cloneCables) { + // Clone cables attached to input ports + for (PortWidget* pw : getInputs()) { + for (CableWidget* cw : APP->scene->rack->getCompleteCablesOnPort(pw)) { + // Create cable attached to cloned ModuleWidget's input + engine::Cable* clonedCable = new engine::Cable; + clonedCable->inputModule = clonedModule; + clonedCable->inputId = cw->cable->inputId; + // If cable is self-patched, attach to cloned module instead + if (cw->cable->outputModule == module) + clonedCable->outputModule = clonedModule; + else + clonedCable->outputModule = cw->cable->outputModule; + clonedCable->outputId = cw->cable->outputId; + APP->engine->addCable(clonedCable); + + app::CableWidget* clonedCw = new app::CableWidget; + clonedCw->setCable(clonedCable); + clonedCw->color = cw->color; + APP->scene->rack->addCable(clonedCw); + + // history::CableAdd + history::CableAdd* hca = new history::CableAdd; + hca->setCable(clonedCw); + h->push(hca); + } + } + } + + APP->history->push(h); +} + +void ModuleWidget::bypassAction(bool bypassed) { + assert(module); + + // history::ModuleBypass + history::ModuleBypass* h = new history::ModuleBypass; + h->moduleId = module->id; + h->bypassed = bypassed; + if (!bypassed) + h->name = "un-bypass module"; + APP->history->push(h); + + APP->engine->bypassModule(module, bypassed); +} + +void ModuleWidget::removeAction() { + history::ComplexAction* h = new history::ComplexAction; + h->name = "delete module"; + + // Disconnect cables + appendDisconnectActions(h); + + // Unset module position from rack. + APP->scene->rack->updateModuleOldPositions(); + if (settings::squeezeModules) + APP->scene->rack->unsqueezeModulePos(this); + h->push(APP->scene->rack->getModuleDragAction()); + + // history::ModuleRemove + history::ModuleRemove* moduleRemove = new history::ModuleRemove; + moduleRemove->setModule(this); + h->push(moduleRemove); + + APP->history->push(h); + + // This removes the module and transfers ownership to caller + APP->scene->rack->removeModule(this); + delete this; + + APP->scene->rack->updateExpanders(); +} + + +// Create ModulePresetPathItems for each patch in a directory. +static void appendPresetItems(ui::Menu* menu, WeakPtr moduleWidget, std::string presetDir) { + bool hasPresets = false; + if (system::isDirectory(presetDir)) { + // Note: This is not cached, so opening this menu each time might have a bit of latency. + std::vector entries = system::getEntries(presetDir); + std::sort(entries.begin(), entries.end()); + for (std::string path : entries) { + std::string name = system::getStem(path); + // Remove "1_", "42_", "001_", etc at the beginning of preset filenames + std::regex r("^\\d+_"); + name = std::regex_replace(name, r, ""); + + if (system::isDirectory(path)) { + hasPresets = true; + + menu->addChild(createSubmenuItem(name, "", [=](ui::Menu* menu) { + if (!moduleWidget) + return; + appendPresetItems(menu, moduleWidget, path); + })); + } + else if (system::getExtension(path) == ".vcvm" && name != "template") { + hasPresets = true; + + menu->addChild(createMenuItem(name, "", [=]() { + if (!moduleWidget) + return; + try { + moduleWidget->loadAction(path); + } + catch (Exception& e) { + osdialog_message(OSDIALOG_WARNING, OSDIALOG_OK, e.what()); + } + })); + } + } + } + if (!hasPresets) { + menu->addChild(createMenuLabel("(None)")); + } +}; + + +void ModuleWidget::createContextMenu() { + ui::Menu* menu = createMenu(); + assert(model); + + WeakPtr weakThis = this; + + // Brand and module name + menu->addChild(createMenuLabel(model->name)); + menu->addChild(createMenuLabel(model->plugin->brand)); + + // Info + menu->addChild(createSubmenuItem("Info", "", [weakThis](ui::Menu* menu) { + if (!weakThis) + return; + weakThis->model->appendContextMenu(menu); + })); + + // Preset + menu->addChild(createSubmenuItem("Preset", "", [weakThis](ui::Menu* menu) { + menu->addChild(createMenuItem("Copy", RACK_MOD_CTRL_NAME "+C", [weakThis]() { + if (!weakThis) + return; + weakThis->copyClipboard(); + })); + + menu->addChild(createMenuItem("Paste", RACK_MOD_CTRL_NAME "+V", [weakThis]() { + if (!weakThis) + return; + weakThis->pasteClipboardAction(); + })); + + menu->addChild(createMenuItem("Open", "", [weakThis]() { + if (!weakThis) + return; + weakThis->loadDialog(); + })); + + /* TODO requires setting up user dir + menu->addChild(createMenuItem("Save as", "", [weakThis]() { + if (!weakThis) + return; + CardinalModuleWidget__saveDialog(weakThis); + })); + + // Scan `/presets//` for presets. + menu->addChild(new ui::MenuSeparator); + menu->addChild(createMenuLabel("User presets")); + appendPresetItems(menu, weakThis, weakThis->model->getUserPresetDirectory()); + */ + + // Scan `/presets/` for presets. + appendPresetItems(menu, weakThis, weakThis->model->getFactoryPresetDirectory()); + })); + + // Initialize + menu->addChild(createMenuItem("Initialize", RACK_MOD_CTRL_NAME "+I", [weakThis]() { + if (!weakThis) + return; + weakThis->resetAction(); + })); + + // Randomize + menu->addChild(createMenuItem("Randomize", RACK_MOD_CTRL_NAME "+R", [weakThis]() { + if (!weakThis) + return; + weakThis->randomizeAction(); + })); + + // Disconnect cables + menu->addChild(createMenuItem("Disconnect cables", RACK_MOD_CTRL_NAME "+U", [weakThis]() { + if (!weakThis) + return; + weakThis->disconnectAction(); + })); + + // Bypass + std::string bypassText = RACK_MOD_CTRL_NAME "+E"; + bool bypassed = module && module->isBypassed(); + if (bypassed) + bypassText += " " CHECKMARK_STRING; + menu->addChild(createMenuItem("Bypass", bypassText, [weakThis, bypassed]() { + if (!weakThis) + return; + weakThis->bypassAction(!bypassed); + })); + + // Duplicate + menu->addChild(createMenuItem("Duplicate", RACK_MOD_CTRL_NAME "+D", [weakThis]() { + if (!weakThis) + return; + weakThis->cloneAction(false); + })); + + // Duplicate with cables + menu->addChild(createMenuItem("└ with cables", RACK_MOD_SHIFT_NAME "+" RACK_MOD_CTRL_NAME "+D", [weakThis]() { + if (!weakThis) + return; + weakThis->cloneAction(true); + })); + + // Delete + menu->addChild(createMenuItem("Delete", "Backspace/Delete", [weakThis]() { + if (!weakThis) + return; + weakThis->removeAction(); + }, false, true)); + + appendContextMenu(menu); +} + +math::Vec ModuleWidget::getGridPosition() { + return ((getPosition() - RACK_OFFSET) / RACK_GRID_SIZE).round(); +} + +void ModuleWidget::setGridPosition(math::Vec pos) { + setPosition(pos * RACK_GRID_SIZE + RACK_OFFSET); +} + +math::Vec ModuleWidget::getGridSize() { + return (getSize() / RACK_GRID_SIZE).round(); +} + +math::Rect ModuleWidget::getGridBox() { + return math::Rect(getGridPosition(), getGridSize()); +} + +math::Vec& ModuleWidget::dragOffset() { + return internal->dragOffset; +} + +bool& ModuleWidget::dragEnabled() { + return internal->dragEnabled; +} + +engine::Module* ModuleWidget::releaseModule() { + engine::Module* module = this->module; + this->module = NULL; + return module; +} + + +} // namespace app +} // namespace rack \ No newline at end of file diff --git a/src/override/diffs/Engine.cpp.diff b/src/override/diffs/Engine.cpp.diff index 7b8616d..59d9496 100644 --- a/src/override/diffs/Engine.cpp.diff +++ b/src/override/diffs/Engine.cpp.diff @@ -1,5 +1,5 @@ ---- ../Rack/src/engine/Engine.cpp 2022-09-21 19:49:12.200540736 +0100 -+++ Engine.cpp 2022-11-25 17:57:38.799958734 +0000 +--- ../Rack/src/engine/Engine.cpp 2022-11-23 23:11:38.000000000 +0000 ++++ Engine.cpp 2022-11-25 22:31:29.000000000 +0000 @@ -1,3 +1,30 @@ +/* + * DISTRHO Cardinal Plugin diff --git a/src/override/diffs/MenuBar.cpp.diff b/src/override/diffs/MenuBar.cpp.diff index 0e9f9ad..186ef5f 100644 --- a/src/override/diffs/MenuBar.cpp.diff +++ b/src/override/diffs/MenuBar.cpp.diff @@ -1,5 +1,5 @@ ---- ../Rack/src/app/MenuBar.cpp 2022-09-21 19:49:12.198540676 +0100 -+++ MenuBar.cpp 2022-09-21 19:41:45.883648777 +0100 +--- ../Rack/src/app/MenuBar.cpp 2022-11-23 23:11:38.000000000 +0000 ++++ MenuBar.cpp 2022-11-25 23:27:58.000000000 +0000 @@ -1,8 +1,33 @@ +/* + * DISTRHO Cardinal Plugin @@ -446,7 +446,7 @@ struct EngineButton : MenuButton { void onAction(const ActionEvent& e) override { ui::Menu* menu = createMenu(); -@@ -541,268 +648,40 @@ +@@ -541,268 +648,46 @@ settings::cpuMeter ^= true; })); @@ -725,7 +725,13 @@ - if (osdialog_message(OSDIALOG_INFO, OSDIALOG_OK_CANCEL, "All plugins have been downloaded. Close and re-launch Rack to load new updates.")) { - APP->window->close(); + if (supportsBufferSizeChanges()) { -+ static const std::vector bufferSizes = {256, 512, 1024, 2048, 4096, 8192, 16384}; ++ static const std::vector bufferSizes = { ++ #ifdef DISTRHO_OS_WASM ++ 256, 512, 1024, 2048, 4096, 8192, 16384 ++ #else ++ 128, 256, 512, 1024, 2048, 4096, 8192 ++ #endif ++ }; + const uint32_t currentBufferSize = getBufferSize(); + menu->addChild(createSubmenuItem("Buffer Size", std::to_string(currentBufferSize), [=](ui::Menu* menu) { + for (uint32_t bufferSize : bufferSizes) { @@ -742,7 +748,7 @@ } }; -@@ -813,65 +692,23 @@ +@@ -813,65 +698,23 @@ struct HelpButton : MenuButton { @@ -814,7 +820,7 @@ } }; -@@ -921,7 +758,9 @@ +@@ -921,7 +764,9 @@ struct MenuBar : widget::OpaqueWidget { MeterLabel* meterLabel; @@ -825,7 +831,7 @@ const float margin = 5; box.size.y = BND_WIDGET_HEIGHT + 2 * margin; -@@ -930,7 +769,7 @@ +@@ -930,7 +775,7 @@ layout->spacing = math::Vec(0, 0); addChild(layout); @@ -834,7 +840,7 @@ fileButton->text = "File"; layout->addChild(fileButton); -@@ -946,10 +785,6 @@ +@@ -946,10 +791,6 @@ engineButton->text = "Engine"; layout->addChild(engineButton); @@ -845,7 +851,7 @@ HelpButton* helpButton = new HelpButton; helpButton->text = "Help"; layout->addChild(helpButton); -@@ -984,7 +819,11 @@ +@@ -984,7 +825,11 @@ widget::Widget* createMenuBar() { diff --git a/src/override/diffs/Model.cpp.diff b/src/override/diffs/Model.cpp.diff index 1c251b2..cbe6c0f 100644 --- a/src/override/diffs/Model.cpp.diff +++ b/src/override/diffs/Model.cpp.diff @@ -1,5 +1,5 @@ ---- ../Rack/src/plugin/Model.cpp 2022-09-21 19:49:12.200540736 +0100 -+++ Model.cpp 2022-09-21 19:41:45.883648777 +0100 +--- ../Rack/src/plugin/Model.cpp 2022-11-23 23:11:38.000000000 +0000 ++++ Model.cpp 2022-11-23 23:06:41.000000000 +0000 @@ -1,3 +1,30 @@ +/* + * DISTRHO Cardinal Plugin diff --git a/src/override/diffs/ModuleWidget.cpp.diff b/src/override/diffs/ModuleWidget.cpp.diff new file mode 100644 index 0000000..1c9300b --- /dev/null +++ b/src/override/diffs/ModuleWidget.cpp.diff @@ -0,0 +1,517 @@ +--- ../Rack/src/app/ModuleWidget.cpp 2022-11-23 23:11:38.000000000 +0000 ++++ ModuleWidget.cpp 2022-11-30 20:10:06.000000000 +0000 +@@ -1,3 +1,32 @@ ++/* ++ * DISTRHO Cardinal Plugin ++ * Copyright (C) 2021-2022 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. ++ */ ++ ++/** ++ * This file is an edited version of VCVRack's ModuleWidget.cpp ++ * Copyright (C) 2016-2021 VCV. ++ * ++ * 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 (at your option) any later version. ++ */ ++ ++#include "../../CardinalCommon.hpp" ++ + #include + #include + +@@ -368,71 +397,71 @@ + } + + void ModuleWidget::onButton(const ButtonEvent& e) { +- bool selected = APP->scene->rack->isSelected(this); +- +- if (selected) { +- if (e.button == GLFW_MOUSE_BUTTON_RIGHT) { +- if (e.action == GLFW_PRESS) { +- // Open selection context menu on right-click +- ui::Menu* menu = createMenu(); +- APP->scene->rack->appendSelectionContextMenu(menu); +- } +- e.consume(this); +- } +- +- if (e.button == GLFW_MOUSE_BUTTON_LEFT) { +- if (e.action == GLFW_PRESS) { +- // Toggle selection on Shift-click +- if ((e.mods & RACK_MOD_MASK) == GLFW_MOD_SHIFT) { +- APP->scene->rack->select(this, false); +- e.consume(NULL); +- return; +- } +- +- // If module positions are locked, don't consume left-click +- if (settings::lockModules) { +- return; +- } ++ const bool selected = APP->scene->rack->isSelected(this); + +- internal->dragOffset = e.pos; +- } +- +- e.consume(this); +- } +- +- return; +- } +- +- // Dispatch event to children +- Widget::onButton(e); +- e.stopPropagating(); +- if (e.isConsumed()) +- return; +- +- if (e.button == GLFW_MOUSE_BUTTON_LEFT) { +- if (e.action == GLFW_PRESS) { +- // Toggle selection on Shift-click +- if ((e.mods & RACK_MOD_MASK) == GLFW_MOD_SHIFT) { +- APP->scene->rack->select(this, true); +- e.consume(NULL); +- return; +- } +- +- // If module positions are locked, don't consume left-click +- if (settings::lockModules) { +- return; +- } +- +- internal->dragOffset = e.pos; +- } +- e.consume(this); +- } +- +- // Open context menu on right-click +- if (e.button == GLFW_MOUSE_BUTTON_RIGHT && e.action == GLFW_PRESS) { +- createContextMenu(); +- e.consume(this); +- } ++ if (selected) { ++ if (e.button == GLFW_MOUSE_BUTTON_RIGHT) { ++ if (e.action == GLFW_PRESS) { ++ // Open selection context menu on right-click ++ ui::Menu* menu = createMenu(); ++ patchUtils::appendSelectionContextMenu(menu); ++ } ++ e.consume(this); ++ } ++ ++ if (e.button == GLFW_MOUSE_BUTTON_LEFT) { ++ if (e.action == GLFW_PRESS) { ++ // Toggle selection on Shift-click ++ if ((e.mods & RACK_MOD_MASK) == GLFW_MOD_SHIFT) { ++ APP->scene->rack->select(this, false); ++ e.consume(NULL); ++ return; ++ } ++ ++ // If module positions are locked, don't consume left-click ++ if (settings::lockModules) { ++ return; ++ } ++ ++ internal->dragOffset = e.pos; ++ } ++ ++ e.consume(this); ++ } ++ ++ return; ++ } ++ ++ // Dispatch event to children ++ Widget::onButton(e); ++ e.stopPropagating(); ++ if (e.isConsumed()) ++ return; ++ ++ if (e.button == GLFW_MOUSE_BUTTON_LEFT) { ++ if (e.action == GLFW_PRESS) { ++ // Toggle selection on Shift-click ++ if ((e.mods & RACK_MOD_MASK) == GLFW_MOD_SHIFT) { ++ APP->scene->rack->select(this, true); ++ e.consume(NULL); ++ return; ++ } ++ ++ // If module positions are locked, don't consume left-click ++ if (settings::lockModules) { ++ return; ++ } ++ ++ internal->dragOffset = e.pos; ++ } ++ e.consume(this); ++ } ++ ++ // Open context menu on right-click ++ if (e.button == GLFW_MOUSE_BUTTON_RIGHT && e.action == GLFW_PRESS) { ++ createContextMenu(); ++ e.consume(this); ++ } + } + + void ModuleWidget::onDragStart(const DragStartEvent& e) { +@@ -624,36 +653,37 @@ + } + + void ModuleWidget::loadDialog() { +- std::string presetDir = model->getUserPresetDirectory(); +- system::createDirectories(presetDir); ++ std::string presetDir = model->getUserPresetDirectory(); ++ system::createDirectories(presetDir); + +- // Delete directories if empty +- DEFER({ +- try { +- system::remove(presetDir); +- system::remove(system::getDirectory(presetDir)); +- } +- catch (Exception& e) { +- // Ignore exceptions if directory cannot be removed. +- } +- }); ++ WeakPtr weakThis = this; + +- osdialog_filters* filters = osdialog_filters_parse(PRESET_FILTERS); +- DEFER({osdialog_filters_free(filters);}); ++ async_dialog_filebrowser(false, nullptr, presetDir.c_str(), "Load preset", [=](char* pathC) { ++ // Delete directories if empty ++ DEFER({ ++ try { ++ system::remove(presetDir); ++ system::remove(system::getDirectory(presetDir)); ++ } ++ catch (Exception& e) { ++ // Ignore exceptions if directory cannot be removed. ++ } ++ }); ++ ++ if (!weakThis) ++ return; ++ if (!pathC) ++ return; ++ ++ try { ++ weakThis->loadAction(pathC); ++ } ++ catch (Exception& e) { ++ async_dialog_message(e.what()); ++ } + +- char* pathC = osdialog_file(OSDIALOG_OPEN, presetDir.c_str(), NULL, filters); +- if (!pathC) { +- // No path selected +- return; +- } +- DEFER({std::free(pathC);}); +- +- try { +- loadAction(pathC); +- } +- catch (Exception& e) { +- osdialog_message(OSDIALOG_WARNING, OSDIALOG_OK, e.what()); +- } ++ std::free(pathC); ++ }); + } + + void ModuleWidget::save(std::string filename) { +@@ -712,36 +742,37 @@ + } + + void ModuleWidget::saveDialog() { +- std::string presetDir = model->getUserPresetDirectory(); +- system::createDirectories(presetDir); ++ const std::string presetDir = model->getUserPresetDirectory(); ++ system::createDirectories(presetDir); + +- // Delete directories if empty +- DEFER({ +- try { +- system::remove(presetDir); +- system::remove(system::getDirectory(presetDir)); +- } +- catch (Exception& e) { +- // Ignore exceptions if directory cannot be removed. +- } +- }); +- +- osdialog_filters* filters = osdialog_filters_parse(PRESET_FILTERS); +- DEFER({osdialog_filters_free(filters);}); +- +- char* pathC = osdialog_file(OSDIALOG_SAVE, presetDir.c_str(), "Untitled.vcvm", filters); +- if (!pathC) { +- // No path selected +- return; +- } +- DEFER({std::free(pathC);}); ++ WeakPtr weakThis = this; + +- std::string path = pathC; +- // Automatically append .vcvm extension +- if (system::getExtension(path) != ".vcvm") +- path += ".vcvm"; ++ async_dialog_filebrowser(true, "preset.vcvm", presetDir.c_str(), "Save preset", [=](char* pathC) { ++ // Delete directories if empty ++ DEFER({ ++ try { ++ system::remove(presetDir); ++ system::remove(system::getDirectory(presetDir)); ++ } ++ catch (Exception& e) { ++ // Ignore exceptions if directory cannot be removed. ++ } ++ }); ++ ++ if (!weakThis) ++ return; ++ if (!pathC) ++ return; ++ ++ std::string path = pathC; ++ std::free(pathC); ++ ++ // Automatically append .vcvm extension ++ if (system::getExtension(path) != ".vcvm") ++ path += ".vcvm"; + +- save(path); ++ weakThis->save(path); ++ }); + } + + void ModuleWidget::disconnect() { +@@ -981,118 +1012,108 @@ + + WeakPtr weakThis = this; + +- // Brand and module name +- menu->addChild(createMenuLabel(model->name)); +- menu->addChild(createMenuLabel(model->plugin->brand)); +- +- // Info +- menu->addChild(createSubmenuItem("Info", "", [=](ui::Menu* menu) { +- model->appendContextMenu(menu); +- })); +- +- // Preset +- menu->addChild(createSubmenuItem("Preset", "", [=](ui::Menu* menu) { +- menu->addChild(createMenuItem("Copy", RACK_MOD_CTRL_NAME "+C", [=]() { +- if (!weakThis) +- return; +- weakThis->copyClipboard(); +- })); +- +- menu->addChild(createMenuItem("Paste", RACK_MOD_CTRL_NAME "+V", [=]() { +- if (!weakThis) +- return; +- weakThis->pasteClipboardAction(); +- })); +- +- menu->addChild(createMenuItem("Open", "", [=]() { +- if (!weakThis) +- return; +- weakThis->loadDialog(); +- })); +- +- menu->addChild(createMenuItem("Save as", "", [=]() { +- if (!weakThis) +- return; +- weakThis->saveDialog(); +- })); +- +- menu->addChild(createMenuItem("Save default", "", [=]() { +- if (!weakThis) +- return; +- weakThis->saveTemplateDialog(); +- })); +- +- menu->addChild(createMenuItem("Clear default", "", [=]() { +- if (!weakThis) +- return; +- weakThis->clearTemplateDialog(); +- }, !weakThis->hasTemplate())); +- +- // Scan `/presets//` for presets. +- menu->addChild(new ui::MenuSeparator); +- menu->addChild(createMenuLabel("User presets")); +- appendPresetItems(menu, weakThis, weakThis->model->getUserPresetDirectory()); +- +- // Scan `/presets/` for presets. +- menu->addChild(new ui::MenuSeparator); +- menu->addChild(createMenuLabel("Factory presets")); +- appendPresetItems(menu, weakThis, weakThis->model->getFactoryPresetDirectory()); +- })); ++ // Brand and module name ++ menu->addChild(createMenuLabel(model->name)); ++ menu->addChild(createMenuLabel(model->plugin->brand)); + +- // Initialize +- menu->addChild(createMenuItem("Initialize", RACK_MOD_CTRL_NAME "+I", [=]() { ++ // Info ++ menu->addChild(createSubmenuItem("Info", "", [weakThis](ui::Menu* menu) { + if (!weakThis) + return; +- weakThis->resetAction(); +- })); ++ weakThis->model->appendContextMenu(menu); ++ })); + +- // Randomize +- menu->addChild(createMenuItem("Randomize", RACK_MOD_CTRL_NAME "+R", [=]() { +- if (!weakThis) +- return; +- weakThis->randomizeAction(); +- })); +- +- // Disconnect cables +- menu->addChild(createMenuItem("Disconnect cables", RACK_MOD_CTRL_NAME "+U", [=]() { +- if (!weakThis) +- return; +- weakThis->disconnectAction(); +- })); +- +- // Bypass +- std::string bypassText = RACK_MOD_CTRL_NAME "+E"; +- bool bypassed = module && module->isBypassed(); +- if (bypassed) +- bypassText += " " CHECKMARK_STRING; +- menu->addChild(createMenuItem("Bypass", bypassText, [=]() { +- if (!weakThis) +- return; +- weakThis->bypassAction(!bypassed); +- })); +- +- // Duplicate +- menu->addChild(createMenuItem("Duplicate", RACK_MOD_CTRL_NAME "+D", [=]() { +- if (!weakThis) +- return; +- weakThis->cloneAction(false); +- })); +- +- // Duplicate with cables +- menu->addChild(createMenuItem("└ with cables", RACK_MOD_SHIFT_NAME "+" RACK_MOD_CTRL_NAME "+D", [=]() { +- if (!weakThis) +- return; +- weakThis->cloneAction(true); +- })); +- +- // Delete +- menu->addChild(createMenuItem("Delete", "Backspace/Delete", [=]() { +- if (!weakThis) +- return; +- weakThis->removeAction(); +- }, false, true)); ++ // Preset ++ menu->addChild(createSubmenuItem("Preset", "", [weakThis](ui::Menu* menu) { ++ menu->addChild(createMenuItem("Copy", RACK_MOD_CTRL_NAME "+C", [weakThis]() { ++ if (!weakThis) ++ return; ++ weakThis->copyClipboard(); ++ })); ++ ++ menu->addChild(createMenuItem("Paste", RACK_MOD_CTRL_NAME "+V", [weakThis]() { ++ if (!weakThis) ++ return; ++ weakThis->pasteClipboardAction(); ++ })); ++ ++ menu->addChild(createMenuItem("Open", "", [weakThis]() { ++ if (!weakThis) ++ return; ++ weakThis->loadDialog(); ++ })); ++ ++ /* TODO requires setting up user dir ++ menu->addChild(createMenuItem("Save as", "", [weakThis]() { ++ if (!weakThis) ++ return; ++ CardinalModuleWidget__saveDialog(weakThis); ++ })); ++ ++ // Scan `/presets//` for presets. ++ menu->addChild(new ui::MenuSeparator); ++ menu->addChild(createMenuLabel("User presets")); ++ appendPresetItems(menu, weakThis, weakThis->model->getUserPresetDirectory()); ++ */ ++ ++ // Scan `/presets/` for presets. ++ appendPresetItems(menu, weakThis, weakThis->model->getFactoryPresetDirectory()); ++ })); ++ ++ // Initialize ++ menu->addChild(createMenuItem("Initialize", RACK_MOD_CTRL_NAME "+I", [weakThis]() { ++ if (!weakThis) ++ return; ++ weakThis->resetAction(); ++ })); ++ ++ // Randomize ++ menu->addChild(createMenuItem("Randomize", RACK_MOD_CTRL_NAME "+R", [weakThis]() { ++ if (!weakThis) ++ return; ++ weakThis->randomizeAction(); ++ })); ++ ++ // Disconnect cables ++ menu->addChild(createMenuItem("Disconnect cables", RACK_MOD_CTRL_NAME "+U", [weakThis]() { ++ if (!weakThis) ++ return; ++ weakThis->disconnectAction(); ++ })); ++ ++ // Bypass ++ std::string bypassText = RACK_MOD_CTRL_NAME "+E"; ++ bool bypassed = module && module->isBypassed(); ++ if (bypassed) ++ bypassText += " " CHECKMARK_STRING; ++ menu->addChild(createMenuItem("Bypass", bypassText, [weakThis, bypassed]() { ++ if (!weakThis) ++ return; ++ weakThis->bypassAction(!bypassed); ++ })); ++ ++ // Duplicate ++ menu->addChild(createMenuItem("Duplicate", RACK_MOD_CTRL_NAME "+D", [weakThis]() { ++ if (!weakThis) ++ return; ++ weakThis->cloneAction(false); ++ })); ++ ++ // Duplicate with cables ++ menu->addChild(createMenuItem("└ with cables", RACK_MOD_SHIFT_NAME "+" RACK_MOD_CTRL_NAME "+D", [weakThis]() { ++ if (!weakThis) ++ return; ++ weakThis->cloneAction(true); ++ })); ++ ++ // Delete ++ menu->addChild(createMenuItem("Delete", "Backspace/Delete", [weakThis]() { ++ if (!weakThis) ++ return; ++ weakThis->removeAction(); ++ }, false, true)); + +- appendContextMenu(menu); ++ appendContextMenu(menu); + } + + math::Vec ModuleWidget::getGridPosition() { diff --git a/src/override/diffs/OpenGlWidget.cpp.diff b/src/override/diffs/OpenGlWidget.cpp.diff index 1fb7156..3a18876 100644 --- a/src/override/diffs/OpenGlWidget.cpp.diff +++ b/src/override/diffs/OpenGlWidget.cpp.diff @@ -1,5 +1,5 @@ ---- ../Rack/src/widget/OpenGlWidget.cpp 2022-09-21 19:49:12.201540766 +0100 -+++ OpenGlWidget.cpp 2022-09-21 19:41:45.883648777 +0100 +--- ../Rack/src/widget/OpenGlWidget.cpp 2022-11-23 23:11:38.000000000 +0000 ++++ OpenGlWidget.cpp 2022-11-23 23:06:41.000000000 +0000 @@ -1,3 +1,30 @@ +/* + * DISTRHO Cardinal Plugin diff --git a/src/override/diffs/Scene.cpp.diff b/src/override/diffs/Scene.cpp.diff index b364a2a..5fac20a 100644 --- a/src/override/diffs/Scene.cpp.diff +++ b/src/override/diffs/Scene.cpp.diff @@ -1,5 +1,5 @@ ---- ../Rack/src/app/Scene.cpp 2022-09-21 19:49:12.199540706 +0100 -+++ Scene.cpp 2022-09-21 19:41:45.883648777 +0100 +--- ../Rack/src/app/Scene.cpp 2022-11-23 23:11:38.000000000 +0000 ++++ Scene.cpp 2022-11-25 22:32:04.000000000 +0000 @@ -1,3 +1,30 @@ +/* + * DISTRHO Cardinal Plugin diff --git a/src/override/diffs/Window.cpp.diff b/src/override/diffs/Window.cpp.diff index 2e273a4..be862b6 100644 --- a/src/override/diffs/Window.cpp.diff +++ b/src/override/diffs/Window.cpp.diff @@ -1,5 +1,5 @@ ---- ../Rack/src/window/Window.cpp 2022-09-21 19:49:12.202540796 +0100 -+++ Window.cpp 2022-09-21 19:41:45.883648777 +0100 +--- ../Rack/src/window/Window.cpp 2022-11-23 23:11:38.000000000 +0000 ++++ Window.cpp 2022-11-25 22:32:06.000000000 +0000 @@ -1,33 +1,87 @@ +/* + * DISTRHO Cardinal Plugin diff --git a/src/override/diffs/blendish.c.diff b/src/override/diffs/blendish.c.diff index 90a2481..907df01 100644 --- a/src/override/diffs/blendish.c.diff +++ b/src/override/diffs/blendish.c.diff @@ -1,5 +1,5 @@ ---- ../Rack/dep/oui-blendish/blendish.c 2022-09-21 19:49:29.973066921 +0100 -+++ blendish.c 2022-09-21 19:41:45.883648777 +0100 +--- ../Rack/dep/oui-blendish/blendish.c 2022-11-23 23:11:56.000000000 +0000 ++++ blendish.c 2022-11-23 23:06:41.000000000 +0000 @@ -61,7 +61,7 @@ } diff --git a/src/override/diffs/common.cpp.diff b/src/override/diffs/common.cpp.diff index 3a41586..36b5215 100644 --- a/src/override/diffs/common.cpp.diff +++ b/src/override/diffs/common.cpp.diff @@ -1,5 +1,5 @@ ---- ../Rack/src/common.cpp 2022-09-21 19:49:12.199540706 +0100 -+++ common.cpp 2022-09-21 19:41:45.883648777 +0100 +--- ../Rack/src/common.cpp 2022-11-23 23:11:38.000000000 +0000 ++++ common.cpp 2022-11-25 22:32:13.000000000 +0000 @@ -1,33 +1,77 @@ +/* + * DISTRHO Cardinal Plugin diff --git a/src/override/diffs/context.cpp.diff b/src/override/diffs/context.cpp.diff index 8b6fa4d..837b5b9 100644 --- a/src/override/diffs/context.cpp.diff +++ b/src/override/diffs/context.cpp.diff @@ -1,5 +1,5 @@ ---- ../Rack/src/context.cpp 2022-09-21 19:49:12.199540706 +0100 -+++ context.cpp 2022-09-21 19:41:45.883648777 +0100 +--- ../Rack/src/context.cpp 2022-11-23 23:11:38.000000000 +0000 ++++ context.cpp 2022-11-25 22:31:32.000000000 +0000 @@ -1,3 +1,30 @@ +/* + * DISTRHO Cardinal Plugin @@ -31,21 +31,22 @@ #include #include #include -@@ -6,9 +33,13 @@ +@@ -6,10 +33,14 @@ #include #include +#ifdef NDEBUG +# undef DEBUG +#endif - --namespace rack { ++ +#include "DistrhoUtils.hpp" -+namespace rack { + namespace rack { +- Context::~Context() { // Deleting NULL is safe in C++. + @@ -44,7 +75,7 @@ static thread_local Context* threadContext = NULL; diff --git a/src/override/diffs/minblep.cpp.diff b/src/override/diffs/minblep.cpp.diff index 5d549e8..b97a6f7 100644 --- a/src/override/diffs/minblep.cpp.diff +++ b/src/override/diffs/minblep.cpp.diff @@ -1,5 +1,5 @@ ---- ../Rack/src/dsp/minblep.cpp 2022-09-21 19:49:12.200540736 +0100 -+++ minblep.cpp 2022-09-21 19:41:45.884648820 +0100 +--- ../Rack/src/dsp/minblep.cpp 2022-11-23 23:11:38.000000000 +0000 ++++ minblep.cpp 2022-11-23 23:06:41.000000000 +0000 @@ -1,3 +1,30 @@ +/* + * DISTRHO Cardinal Plugin diff --git a/src/override/diffs/plugin.cpp.diff b/src/override/diffs/plugin.cpp.diff index e95f377..c2c7027 100644 --- a/src/override/diffs/plugin.cpp.diff +++ b/src/override/diffs/plugin.cpp.diff @@ -1,5 +1,5 @@ ---- ../Rack/src/plugin.cpp 2022-09-21 19:49:12.200540736 +0100 -+++ plugin.cpp 2022-09-21 19:41:45.884648820 +0100 +--- ../Rack/src/plugin.cpp 2022-11-23 23:11:38.000000000 +0000 ++++ plugin.cpp 2022-11-25 23:27:58.000000000 +0000 @@ -1,342 +1,41 @@ -#include -#include @@ -372,7 +372,15 @@ /** Given slug => fallback slug. -@@ -389,8 +88,19 @@ +@@ -348,6 +47,7 @@ + {"VultModules", "VultModulesFree"}, + {"AudibleInstrumentsPreview", "AudibleInstruments"}, + {"SequelSequencers", "DanielDavies"}, ++ {"DelexanderVol1", "DelexandraVol1"}, + // {"", ""}, + }; + +@@ -389,8 +89,19 @@ */ using PluginModuleSlug = std::tuple; static const std::map moduleSlugFallbacks = { @@ -393,7 +401,7 @@ // {{"", ""}, {"", ""}}, }; -@@ -478,7 +188,6 @@ +@@ -478,7 +189,6 @@ }