diff --git a/README.md b/README.md index eb7f4a0d..7c6e567c 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ This README includes instructions for building Rack from source. For information ## The [Issue Tracker](https://github.com/VCVRack/Rack/issues) *is* the official developer's forum Bug reports, feature requests, and even *questions/discussions* are welcome on the GitHub Issue Tracker for all VCVRack repos. -However, please search before posting to avoid duplicates. +However, please search before posting to avoid duplicates, and limit to one issue per post. You may vote on feature requests by using the Thumbs Up/Down reaction on the first post. diff --git a/compile.mk b/compile.mk index cf0e6bf4..9ec706a3 100644 --- a/compile.mk +++ b/compile.mk @@ -42,6 +42,15 @@ DEPS = $(patsubst %, build/%.d, $(SOURCES)) $(TARGET): $(OBJECTS) $(CXX) -o $@ $^ $(LDFLAGS) +%.so: + $(CXX) -o $@ $^ $(LDFLAGS) + +%.dylib: + $(CXX) -o $@ $^ $(LDFLAGS) + +%.dll: + $(CXX) -o $@ $^ $(LDFLAGS) + # Object targets -include $(DEPS) diff --git a/include/asset.hpp b/include/asset.hpp index 53f94458..043cea86 100644 --- a/include/asset.hpp +++ b/include/asset.hpp @@ -13,9 +13,9 @@ std::string assetGlobal(std::string filename); /** Searches for a local resource */ std::string assetLocal(std::string filename); -/** Searches for a plugin resource, given a Plugin object +/** Searches for a manufacturer resource, given a Manufacturer object */ -std::string assetPlugin(Plugin *plugin, std::string filename); +std::string assetManufacturer(Manufacturer *manufacturer, std::string filename); } // namespace rack diff --git a/include/plugin.hpp b/include/plugin.hpp index 19b7693e..9c82afe6 100644 --- a/include/plugin.hpp +++ b/include/plugin.hpp @@ -10,37 +10,41 @@ struct ModuleWidget; struct Model; // Subclass this and return a pointer to a new one when init() is called -struct Plugin { - virtual ~Plugin(); - - /** A unique identifier for your plugin, e.g. "foo" */ - std::string slug; - /** Human readable name for your plugin, e.g. "Foo Modular" */ - std::string name; - /** A list of the models made available by this plugin */ +struct Manufacturer { + /** A list of the models available by this manufacturer, add with addModel() */ std::list models; /** The file path of the plugins directory */ std::string path; /** OS-dependent library handle */ void *handle = NULL; + + // You may set everything below this point in your plugin + + /** A unique identifier for the manufacturer, e.g. "foo" */ + std::string slug; + /** Human readable name for the manufacturer, e.g. "Foo Modular" */ + std::string name; /** Optional metadata for the Add Module context menu */ std::string homepageUrl; std::string manualUrl; std::string version; + + virtual ~Manufacturer(); + void addModel(Model *model); }; struct Model { - virtual ~Model() {} - - Plugin *plugin; - /** A unique identifier for the model in this plugin, e.g. "VCO" */ + Manufacturer *manufacturer = NULL; + /** A unique identifier for the model, e.g. "VCO" */ std::string slug; /** Human readable name for your model, e.g. "Voltage Controlled Oscillator" */ std::string name; + + virtual ~Model() {} virtual ModuleWidget *createModuleWidget() { return NULL; } }; -extern std::list gPlugins; +extern std::list gManufacturers; extern std::string gToken; void pluginInit(); @@ -62,7 +66,8 @@ std::string pluginGetLoginStatus(); // Implemented by plugin //////////////////// -/** Called once to initialize and return the Plugin instance. +/** Called once to initialize and return the Manufacturer instance. +You must implement this in your plugin */ extern "C" -void init(rack::Plugin *plugin); +void init(rack::Manufacturer *manufacturer); diff --git a/include/rack.hpp b/include/rack.hpp index abf27412..7863bf1d 100644 --- a/include/rack.hpp +++ b/include/rack.hpp @@ -18,7 +18,7 @@ namespace rack { //////////////////// template -Model *createModel(Plugin *plugin, std::string slug, std::string name) { +Model *createModel(std::string slug, std::string name) { struct TModel : Model { ModuleWidget *createModuleWidget() override { ModuleWidget *moduleWidget = new TModuleWidget(); @@ -27,13 +27,8 @@ Model *createModel(Plugin *plugin, std::string slug, std::string name) { } }; Model *model = new TModel(); - model->plugin = plugin; model->slug = slug; model->name = name; - // Create bi-directional association between the Plugin and Model - if (plugin) { - plugin->models.push_back(model); - } return model; } diff --git a/include/widgets.hpp b/include/widgets.hpp index 16795064..af9b1346 100644 --- a/include/widgets.hpp +++ b/include/widgets.hpp @@ -399,6 +399,7 @@ struct ZoomWidget : Widget { struct TextField : OpaqueWidget { std::string text; std::string placeholder; + bool multiline = false; int begin = 0; int end = 0; diff --git a/plugin.mk b/plugin.mk index b40c4b9a..cdb1776f 100644 --- a/plugin.mk +++ b/plugin.mk @@ -8,23 +8,21 @@ include ../../arch.mk ifeq ($(ARCH), lin) LDFLAGS += -shared - TARGET = plugin.so + PLUGIN_EXTENSION = so endif ifeq ($(ARCH), mac) LDFLAGS += -shared -undefined dynamic_lookup - TARGET = plugin.dylib + PLUGIN_EXTENSION = dylib endif ifeq ($(ARCH), win) LDFLAGS += -shared -L../../ -lRack - TARGET = plugin.dll + PLUGIN_EXTENSION = dll endif -all: $(TARGET) - clean: - rm -rfv build $(TARGET) dist + rm -rfv build *.$(PLUGIN_EXTENSION) dist include ../../compile.mk diff --git a/src/app/ModuleWidget.cpp b/src/app/ModuleWidget.cpp index 1d768575..00376944 100644 --- a/src/app/ModuleWidget.cpp +++ b/src/app/ModuleWidget.cpp @@ -46,7 +46,7 @@ json_t *ModuleWidget::toJson() { json_t *rootJ = json_object(); // plugin - json_object_set_new(rootJ, "plugin", json_string(model->plugin->slug.c_str())); + json_object_set_new(rootJ, "plugin", json_string(model->manufacturer->slug.c_str())); // model json_object_set_new(rootJ, "model", json_string(model->slug.c_str())); // pos @@ -245,7 +245,7 @@ Menu *ModuleWidget::createContextMenu() { Menu *menu = gScene->createMenu(); MenuLabel *menuLabel = new MenuLabel(); - menuLabel->text = model->plugin->name + ": " + model->name; + menuLabel->text = model->manufacturer->name + ": " + model->name; menu->pushChild(menuLabel); ResetMenuItem *resetItem = new ResetMenuItem(); diff --git a/src/app/RackScene.cpp b/src/app/RackScene.cpp index a4c8bd81..98b8e52d 100644 --- a/src/app/RackScene.cpp +++ b/src/app/RackScene.cpp @@ -2,6 +2,7 @@ #include "gui.hpp" #include "util/request.hpp" #include "../ext/osdialog/osdialog.h" +#include #include @@ -17,7 +18,7 @@ static void checkVersion() { json_t *versionJ = json_object_get(resJ, "version"); if (versionJ) { const char *version = json_string_value(versionJ); - if (version && version != gApplicationVersion) { + if (version && strlen(version) > 0 && version != gApplicationVersion) { newVersion = version; } } @@ -45,7 +46,7 @@ RackScene::RackScene() { scrollWidget->box.pos.y = gToolbar->box.size.y; // Check for new version - if (gApplicationVersion != "dev") { + if (!gApplicationVersion.empty()) { std::thread versionThread(checkVersion); versionThread.detach(); } diff --git a/src/app/RackWidget.cpp b/src/app/RackWidget.cpp index 87de4129..10762c99 100644 --- a/src/app/RackWidget.cpp +++ b/src/app/RackWidget.cpp @@ -208,21 +208,21 @@ void RackWidget::fromJson(json_t *rootJ) { const char *modelSlug = json_string_value(modelSlugJ); // Search for plugin - Plugin *plugin = NULL; - for (Plugin *p : gPlugins) { - if (p->slug == pluginSlug) { - plugin = p; + Manufacturer *manufacturer = NULL; + for (Manufacturer *m : gManufacturers) { + if (m->slug == pluginSlug) { + manufacturer = m; break; } } - if (!plugin) { + if (!manufacturer) { message += stringf("Could not find plugin \"%s\" for module \"%s\".\n", pluginSlug, modelSlug); continue; } // Get for model Model *model = NULL; - for (Model *m : plugin->models) { + for (Model *m : manufacturer->models) { if (m->slug == modelSlug) { model = m; break; @@ -388,13 +388,13 @@ struct UrlItem : MenuItem { } }; -struct AddPluginMenuItem : MenuItem { - Plugin *plugin; +struct AddManufacturerMenuItem : MenuItem { + Manufacturer *manufacturer; Vec modulePos; Menu *createChildMenu() override { // Model items Menu *menu = new Menu(); - for (Model *model : plugin->models) { + for (Model *model : manufacturer->models) { AddModuleMenuItem *item = new AddModuleMenuItem(); item->text = model->name; item->model = model; @@ -409,34 +409,34 @@ struct AddPluginMenuItem : MenuItem { } { MenuLabel *label = new MenuLabel(); - label->text = plugin->name; + label->text = manufacturer->name; menu->pushChild(label); } - if (!plugin->homepageUrl.empty()) { + if (!manufacturer->homepageUrl.empty()) { UrlItem *item = new UrlItem(); item->text = "Homepage"; - item->url = plugin->homepageUrl; + item->url = manufacturer->homepageUrl; menu->pushChild(item); } - if (!plugin->manualUrl.empty()) { + if (!manufacturer->manualUrl.empty()) { UrlItem *item = new UrlItem(); item->text = "Manual"; - item->url = plugin->manualUrl; + item->url = manufacturer->manualUrl; menu->pushChild(item); } - if (!plugin->path.empty()) { + if (!manufacturer->path.empty()) { UrlItem *item = new UrlItem(); item->text = "Browse directory"; - item->url = plugin->path; + item->url = manufacturer->path; menu->pushChild(item); } - if (!plugin->version.empty()) { + if (!manufacturer->version.empty()) { MenuLabel *item = new MenuLabel(); - item->text = "Version: v" + plugin->version; + item->text = "Version: v" + manufacturer->version; menu->pushChild(item); } @@ -452,10 +452,10 @@ void RackWidget::onMouseDownOpaque(int button) { MenuLabel *menuLabel = new MenuLabel(); menuLabel->text = "Add module"; menu->pushChild(menuLabel); - for (Plugin *plugin : gPlugins) { - AddPluginMenuItem *item = new AddPluginMenuItem(); - item->text = plugin->name; - item->plugin = plugin; + for (Manufacturer *manufacturer : gManufacturers) { + AddManufacturerMenuItem *item = new AddManufacturerMenuItem(); + item->text = manufacturer->name; + item->manufacturer = manufacturer; item->modulePos = modulePos; menu->pushChild(item); } diff --git a/src/asset.cpp b/src/asset.cpp index 8fc4d13c..7608cc22 100644 --- a/src/asset.cpp +++ b/src/asset.cpp @@ -88,10 +88,10 @@ std::string assetLocal(std::string filename) { return path; } -std::string assetPlugin(Plugin *plugin, std::string filename) { - assert(plugin); +std::string assetManufacturer(Manufacturer *manufacturer, std::string filename) { + assert(manufacturer); std::string path; - path = plugin->path + "/" + filename; + path = manufacturer->path + "/" + filename; return path; } diff --git a/src/core/MidiClockToCV.cpp b/src/core/MidiClockToCV.cpp index cd94991f..d37ef9ff 100644 --- a/src/core/MidiClockToCV.cpp +++ b/src/core/MidiClockToCV.cpp @@ -241,7 +241,7 @@ MIDIClockToCVWidget::MIDIClockToCVWidget() { { Label *label = new Label(); label->box.pos = Vec(box.size.x - margin - 7 * 15, margin); - label->text = "MIDI Clock to CV"; + label->text = "MIDI Clk-CV"; addChild(label); yPos = labelHeight * 2; } diff --git a/src/core/core.cpp b/src/core/core.cpp index a083c008..6397a085 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -2,15 +2,16 @@ #include "MidiIO.hpp" -void init(rack::Plugin *plugin) { - plugin->slug = "Core"; - plugin->name = "Core"; - plugin->homepageUrl = "https://vcvrack.com/"; - createModel(plugin, "AudioInterface", "Audio Interface"); - createModel(plugin, "MIDIToCVInterface", "MIDI-to-CV Interface"); - createModel(plugin, "MIDICCToCVInterface", "MIDI CC-to-CV Interface"); - createModel(plugin, "MIDIClockToCVInterface", "MIDI Clock-to-CV Interface"); - createModel(plugin, "MIDITriggerToCVInterface", "MIDI Trigger-to-CV Interface"); - // createModel(plugin, "Bridge", "Bridge"); - createModel(plugin, "Blank", "Blank"); +void init(rack::Manufacturer *m) { + m->slug = "Core"; + m->name = "Core"; + m->homepageUrl = "https://vcvrack.com/"; + m->addModel(createModel("AudioInterface", "Audio Interface")); + m->addModel(createModel("MIDIToCVInterface", "MIDI-to-CV Interface")); + m->addModel(createModel("MIDICCToCVInterface", "MIDI CC-to-CV Interface")); + m->addModel(createModel("MIDIClockToCVInterface", "MIDI Clock-to-CV Interface")); + m->addModel(createModel("MIDITriggerToCVInterface", "MIDI Trigger-to-CV Interface")); + // m->addModel(createModel("Bridge", "Bridge")); + m->addModel(createModel("Blank", "Blank")); + m->addModel(createModel("Notes", "Notes")); } diff --git a/src/core/core.hpp b/src/core/core.hpp index 661cd87b..c5df6b79 100644 --- a/src/core/core.hpp +++ b/src/core/core.hpp @@ -26,3 +26,10 @@ struct BlankWidget : ModuleWidget { json_t *toJson() override; void fromJson(json_t *rootJ) override; }; + +struct NotesWidget : ModuleWidget { + TextField *textField; + NotesWidget(); + json_t *toJson() override; + void fromJson(json_t *rootJ) override; +}; diff --git a/src/plugin.cpp b/src/plugin.cpp index 3de1457b..3a906694 100644 --- a/src/plugin.cpp +++ b/src/plugin.cpp @@ -28,7 +28,7 @@ namespace rack { -std::list gPlugins; +std::list gManufacturers; std::string gToken; static bool isDownloading = false; @@ -38,12 +38,19 @@ static std::string loginStatus; -Plugin::~Plugin() { +Manufacturer::~Manufacturer() { for (Model *model : models) { delete model; } } +void Manufacturer::addModel(Model *model) { + assert(!model->manufacturer); + model->manufacturer = this; + models.push_back(model); +} + + static int loadPlugin(std::string path) { std::string libraryFilename; #if ARCH_LIN @@ -70,8 +77,8 @@ static int loadPlugin(std::string path) { } #endif - // Call plugin init() function - typedef void (*InitCallback)(Plugin *); + // Call plugin's init() function + typedef void (*InitCallback)(Manufacturer *); InitCallback initCallback; #if ARCH_WIN initCallback = (InitCallback) GetProcAddress(handle, "init"); @@ -83,27 +90,23 @@ static int loadPlugin(std::string path) { return -2; } - // Construct and initialize Plugin instance - Plugin *plugin = new Plugin(); - plugin->path = path; - plugin->handle = handle; - initCallback(plugin); + // Construct and initialize Manufacturer instance + Manufacturer *manufacturer = new Manufacturer(); + manufacturer->path = path; + manufacturer->handle = handle; + initCallback(manufacturer); // Check that this is a unique slug - for (Plugin *otherPlugin : gPlugins) { - assert(plugin->slug != otherPlugin->slug); + for (Manufacturer *otherManufacturer : gManufacturers) { + assert(manufacturer->slug != otherManufacturer->slug); } - // Add plugin to list - gPlugins.push_back(plugin); + // Add manufacturer to list + gManufacturers.push_back(manufacturer); fprintf(stderr, "Loaded plugin %s\n", libraryFilename.c_str()); return 0; } -static bool comparePlugins(Plugin *a, Plugin *b) { - return a->slug < b->slug; -} - static void loadPlugins(std::string path) { DIR *dir = opendir(path.c_str()); if (dir) { @@ -115,9 +118,6 @@ static void loadPlugins(std::string path) { } closedir(dir); } - - // Sort plugins - gPlugins.sort(comparePlugins); } //////////////////// @@ -201,8 +201,8 @@ static void refreshPurchase(json_t *pluginJ) { url += gToken; // Find slug in plugins list, and return silently if slug already exists - for (Plugin *p : gPlugins) { - if (p->slug == slug) { + for (Manufacturer *m : gManufacturers) { + if (m->slug == slug) { return; } } @@ -237,9 +237,9 @@ static void refreshPurchase(json_t *pluginJ) { void pluginInit() { // Load core // This function is defined in core.cpp - Plugin *corePlugin = new Plugin(); - init(corePlugin); - gPlugins.push_back(corePlugin); + Manufacturer *coreManufacturer = new Manufacturer(); + init(coreManufacturer); + gManufacturers.push_back(coreManufacturer); // Load plugins from global directory std::string globalPlugins = assetGlobal("plugins"); @@ -255,20 +255,20 @@ void pluginInit() { } void pluginDestroy() { - for (Plugin *plugin : gPlugins) { + for (Manufacturer *manufacturer : gManufacturers) { // Free library handle #if ARCH_WIN - if (plugin->handle) - FreeLibrary((HINSTANCE)plugin->handle); + if (manufacturer->handle) + FreeLibrary((HINSTANCE)manufacturer->handle); #elif ARCH_LIN || ARCH_MAC - if (plugin->handle) - dlclose(plugin->handle); + if (manufacturer->handle) + dlclose(manufacturer->handle); #endif // For some reason this segfaults - // delete plugin; + // delete manufacturer; } - gPlugins.clear(); + gManufacturers.clear(); } void pluginLogIn(std::string email, std::string password) { diff --git a/src/widgets/FramebufferWidget.cpp b/src/widgets/FramebufferWidget.cpp index e34a4a11..21a14158 100644 --- a/src/widgets/FramebufferWidget.cpp +++ b/src/widgets/FramebufferWidget.cpp @@ -42,6 +42,7 @@ void FramebufferWidget::step() { internal->box.size = box.size; internal->box.size = Vec(ceilf(internal->box.size.x), ceilf(internal->box.size.y)); Vec fbSize = internal->box.size.mult(gPixelRatio * oversample); + // assert(fbSize.isFinite()); // Reject zero area size if (fbSize.x <= 0.0 || fbSize.y <= 0.0) @@ -71,6 +72,20 @@ void FramebufferWidget::step() { } void FramebufferWidget::draw(NVGcontext *vg) { + // { + // float xform[6]; + // nvgCurrentTransform(vg, xform); + // printf("%f %f %f %f %f %f\n", xform[0], xform[1], xform[2], xform[3], xform[4], xform[5]); + // nvgSave(vg); + // nvgResetTransform(vg); + // nvgTranslate(vg, xform[5], xform[6]); + // nvgBeginPath(vg); + // nvgRect(vg, 0, 0, 50, 50); + // nvgFillColor(vg, nvgRGBf(1.0, 0.0, 0.0)); + // nvgFill(vg); + // nvgRestore(vg); + // } + if (!internal->fb) { // Bypass framebuffer cache entirely // Widget::draw(vg); diff --git a/src/widgets/TextField.cpp b/src/widgets/TextField.cpp index 0e359b8e..16b051f4 100644 --- a/src/widgets/TextField.cpp +++ b/src/widgets/TextField.cpp @@ -101,6 +101,14 @@ bool TextField::onFocusKey(int key) { } } break; + case GLFW_KEY_ENTER: + if (multiline) { + insertText("\n"); + } + else { + onAction(); + } + break; } begin = mini(maxi(begin, 0), text.size());