diff --git a/include/engine/Engine.hpp b/include/engine/Engine.hpp index e939347c..b5718da2 100644 --- a/include/engine/Engine.hpp +++ b/include/engine/Engine.hpp @@ -35,6 +35,7 @@ struct Engine { void removeWire(Wire *wire); void setParam(Module *module, int paramId, float value); void setParamSmooth(Module *module, int paramId, float value); + int getNextModuleId(); void setSampleRate(float sampleRate); float getSampleRate(); diff --git a/include/engine/Module.hpp b/include/engine/Module.hpp index dd506ddc..8e9d4847 100644 --- a/include/engine/Module.hpp +++ b/include/engine/Module.hpp @@ -13,32 +13,23 @@ namespace rack { struct Module { + int id = -1; std::vector params; std::vector inputs; std::vector outputs; std::vector lights; - /** For CPU usage meter */ + /** For power meter */ float cpuTime = 0.f; /** Constructs a Module with no params, inputs, outputs, and lights */ - Module() {} - /** Constructs a Module with a fixed number of params, inputs, outputs, and lights */ - Module(int numParams, int numInputs, int numOutputs, int numLights = 0) { + Module(); + /** Deprecated. Use setup() instead. */ + Module(int numParams, int numInputs, int numOutputs, int numLights = 0) : Module() { setup(numParams, numInputs, numOutputs, numLights); } virtual ~Module() {} - void setup(int numParams, int numInputs, int numOutputs, int numLights = 0) { - params.resize(numParams); - // Create default param labels - for (int i = 0; i < numParams; i++) { - params[i].label = string::f("#%d", i + 1); - } - inputs.resize(numInputs); - outputs.resize(numOutputs); - lights.resize(numLights); - } - + void setup(int numParams, int numInputs, int numOutputs, int numLights = 0); json_t *toJson(); void fromJson(json_t *rootJ); void reset(); diff --git a/src/app/ModuleWidget.cpp b/src/app/ModuleWidget.cpp index 7cac7f6b..be10d063 100644 --- a/src/app/ModuleWidget.cpp +++ b/src/app/ModuleWidget.cpp @@ -337,7 +337,7 @@ void ModuleWidget::onButton(event::Button &e) { } void ModuleWidget::onHoverKey(event::HoverKey &e) { - if (e.action == GLFW_PRESS) { + if (e.action == GLFW_PRESS || e.action == GLFW_REPEAT) { switch (e.key) { case GLFW_KEY_I: { if (context()->window->isModPressed() && !context()->window->isShiftPressed()) { diff --git a/src/app/RackWidget.cpp b/src/app/RackWidget.cpp index 3886d142..8f6da323 100644 --- a/src/app/RackWidget.cpp +++ b/src/app/RackWidget.cpp @@ -81,14 +81,23 @@ void RackWidget::loadDialog() { else { dir = string::directory(lastPath); } + osdialog_filters *filters = osdialog_filters_parse(PATCH_FILTERS.c_str()); + DEFER({ + osdialog_filters_free(filters); + }); + char *path = osdialog_file(OSDIALOG_OPEN, dir.c_str(), NULL, filters); - if (path) { - load(path); - lastPath = path; - free(path); + if (!path) { + // Fail silently + return; } - osdialog_filters_free(filters); + DEFER({ + free(path); + }); + + load(path); + lastPath = path; } void RackWidget::saveDialog() { @@ -111,21 +120,29 @@ void RackWidget::saveAsDialog() { dir = string::directory(lastPath); filename = string::filename(lastPath); } + osdialog_filters *filters = osdialog_filters_parse(PATCH_FILTERS.c_str()); - char *path = osdialog_file(OSDIALOG_SAVE, dir.c_str(), filename.c_str(), filters); + DEFER({ + osdialog_filters_free(filters); + }); - if (path) { - std::string pathStr = path; + char *path = osdialog_file(OSDIALOG_SAVE, dir.c_str(), filename.c_str(), filters); + if (!path) { + // Fail silently + return; + } + DEFER({ free(path); - std::string extension = string::extension(pathStr); - if (extension.empty()) { - pathStr += ".vcv"; - } + }); + - save(pathStr); - lastPath = pathStr; + std::string pathStr = path; + if (string::extension(pathStr).empty()) { + pathStr += ".vcv"; } - osdialog_filters_free(filters); + + save(pathStr); + lastPath = pathStr; } void RackWidget::save(std::string filename) { @@ -133,14 +150,20 @@ void RackWidget::save(std::string filename) { json_t *rootJ = toJson(); if (!rootJ) return; + DEFER({ + json_decref(rootJ); + }); FILE *file = fopen(filename.c_str(), "w"); - if (file) { - json_dumpf(rootJ, file, JSON_INDENT(2) | JSON_REAL_PRECISION(9)); - fclose(file); + if (!file) { + // Fail silently + return; } + DEFER({ + fclose(file); + }); - json_decref(rootJ); + json_dumpf(rootJ, file, JSON_INDENT(2) | JSON_REAL_PRECISION(9)); } void RackWidget::load(std::string filename) { @@ -150,20 +173,23 @@ void RackWidget::load(std::string filename) { // Exit silently return; } + DEFER({ + fclose(file); + }); json_error_t error; json_t *rootJ = json_loadf(file, 0, &error); - if (rootJ) { - clear(); - fromJson(rootJ); - json_decref(rootJ); - } - else { + if (!rootJ) { std::string message = string::f("JSON parsing error at %s %d:%d %s", error.source, error.line, error.column, error.text); osdialog_message(OSDIALOG_WARNING, OSDIALOG_OK, message.c_str()); + return; } + DEFER({ + json_decref(rootJ); + }); - fclose(file); + clear(); + fromJson(rootJ); } void RackWidget::revert() { @@ -178,11 +204,7 @@ void RackWidget::disconnect() { if (!osdialog_message(OSDIALOG_WARNING, OSDIALOG_OK_CANCEL, "Remove all patch cables?")) return; - for (Widget *w : moduleContainer->children) { - ModuleWidget *moduleWidget = dynamic_cast(w); - assert(moduleWidget); - moduleWidget->disconnect(); - } + wireContainer->removeAllWires(NULL); } json_t *RackWidget::toJson() { @@ -195,16 +217,14 @@ json_t *RackWidget::toJson() { // modules json_t *modulesJ = json_array(); - std::map moduleIds; - int moduleId = 0; for (Widget *w : moduleContainer->children) { ModuleWidget *moduleWidget = dynamic_cast(w); assert(moduleWidget); - moduleIds[moduleWidget] = moduleId; - moduleId++; // module json_t *moduleJ = moduleWidget->toJson(); { + // id + json_object_set_new(moduleJ, "id", json_integer(moduleWidget->module->id)); // pos math::Vec pos = moduleWidget->box.pos.div(RACK_GRID_SIZE).round(); json_t *posJ = json_pack("[i, i]", (int) pos.x, (int) pos.y); @@ -219,29 +239,22 @@ json_t *RackWidget::toJson() { for (Widget *w : wireContainer->children) { WireWidget *wireWidget = dynamic_cast(w); assert(wireWidget); + + Port *outputPort = wireWidget->outputPort; + Port *inputPort = wireWidget->inputPort; // Only serialize WireWidgets connected on both ends - if (!(wireWidget->outputPort && wireWidget->inputPort)) + if (!(outputPort && inputPort)) continue; // wire json_t *wire = wireWidget->toJson(); - // Get the modules at each end of the wire - ModuleWidget *outputModuleWidget = wireWidget->outputPort->getAncestorOfType(); - assert(outputModuleWidget); - int outputModuleId = moduleIds[outputModuleWidget]; + assert(outputPort->module); + assert(inputPort->module); - ModuleWidget *inputModuleWidget = wireWidget->inputPort->getAncestorOfType(); - assert(inputModuleWidget); - int inputModuleId = moduleIds[inputModuleWidget]; - - // Get output/input ports - int outputId = wireWidget->outputPort->portId; - int inputId = wireWidget->inputPort->portId; - - json_object_set_new(wire, "outputModuleId", json_integer(outputModuleId)); - json_object_set_new(wire, "outputId", json_integer(outputId)); - json_object_set_new(wire, "inputModuleId", json_integer(inputModuleId)); - json_object_set_new(wire, "inputId", json_integer(inputId)); + json_object_set_new(wire, "outputModuleId", json_integer(outputPort->module->id)); + json_object_set_new(wire, "outputId", json_integer(outputPort->portId)); + json_object_set_new(wire, "inputModuleId", json_integer(inputPort->module->id)); + json_object_set_new(wire, "inputId", json_integer(inputPort->portId)); json_array_append_new(wires, wire); } @@ -256,8 +269,10 @@ void RackWidget::fromJson(json_t *rootJ) { // version std::string version; json_t *versionJ = json_object_get(rootJ, "version"); - if (versionJ) { + if (versionJ) version = json_string_value(versionJ); + if (version != APP_VERSION) { + INFO("Patch made with Rack version %s, current Rack version is %s", version.c_str(), APP_VERSION.c_str()); } // Detect old patches with ModuleWidget::params/inputs/outputs indices. @@ -266,17 +281,21 @@ void RackWidget::fromJson(json_t *rootJ) { if (string::startsWith(version, "0.3.") || string::startsWith(version, "0.4.") || string::startsWith(version, "0.5.") || version == "" || version == "dev") { legacy = 1; } + else if (string::startsWith(version, "0.6.")) { + legacy = 2; + } if (legacy) { INFO("Loading patch using legacy mode %d", legacy); } // modules - std::map moduleWidgets; json_t *modulesJ = json_object_get(rootJ, "modules"); - if (!modulesJ) return; - size_t moduleId; + if (!modulesJ) + return; + std::map moduleWidgets; + size_t moduleIndex; json_t *moduleJ; - json_array_foreach(modulesJ, moduleId, moduleJ) { + json_array_foreach(modulesJ, moduleIndex, moduleJ) { // Add "legacy" property if in legacy mode if (legacy) { json_object_set(moduleJ, "legacy", json_integer(legacy)); @@ -285,19 +304,32 @@ void RackWidget::fromJson(json_t *rootJ) { ModuleWidget *moduleWidget = moduleFromJson(moduleJ); if (moduleWidget) { + // id + json_t *idJ = json_object_get(moduleJ, "id"); + int id = -1; + if (idJ) + id = json_integer_value(idJ); // pos json_t *posJ = json_object_get(moduleJ, "pos"); double x, y; json_unpack(posJ, "[F, F]", &x, &y); math::Vec pos = math::Vec(x, y); if (legacy && legacy <= 1) { + // Before 0.6, positions were in pixel units moduleWidget->box.pos = pos; } else { moduleWidget->box.pos = pos.mult(RACK_GRID_SIZE); } - moduleWidgets[moduleId] = moduleWidget; + if (legacy && legacy <= 2) { + // Before 1.0, the module ID was the index in the "modules" array + moduleWidgets[moduleIndex] = moduleWidget; + } + else { + moduleWidgets[id] = moduleWidget; + } + addModule(moduleWidget); } else { json_t *pluginSlugJ = json_object_get(moduleJ, "plugin"); @@ -310,10 +342,10 @@ void RackWidget::fromJson(json_t *rootJ) { // wires json_t *wiresJ = json_object_get(rootJ, "wires"); - if (!wiresJ) return; - size_t wireId; + assert(wiresJ); + size_t wireIndex; json_t *wireJ; - json_array_foreach(wiresJ, wireId, wireJ) { + json_array_foreach(wiresJ, wireIndex, wireJ) { int outputModuleId = json_integer_value(json_object_get(wireJ, "outputModuleId")); int outputId = json_integer_value(json_object_get(wireJ, "outputId")); int inputModuleId = json_integer_value(json_object_get(wireJ, "inputModuleId")); @@ -329,8 +361,7 @@ void RackWidget::fromJson(json_t *rootJ) { Port *outputPort = NULL; Port *inputPort = NULL; if (legacy && legacy <= 1) { - // Legacy 1 mode - // The index of the "ports" array is the index of the Port in the `outputs` and `inputs` vector. + // Before 0.6, the index of the "ports" array was the index of the Port in the `outputs` and `inputs` vector. outputPort = outputModuleWidget->outputs[outputId]; inputPort = inputModuleWidget->inputs[inputId]; } @@ -387,7 +418,6 @@ ModuleWidget *RackWidget::moduleFromJson(json_t *moduleJ) { ModuleWidget *moduleWidget = model->createModuleWidget(); assert(moduleWidget); moduleWidget->fromJson(moduleJ); - moduleContainer->addChild(moduleWidget); return moduleWidget; } @@ -402,12 +432,12 @@ void RackWidget::pastePresetClipboard() { json_t *moduleJ = json_loads(moduleJson, 0, &error); if (moduleJ) { ModuleWidget *moduleWidget = moduleFromJson(moduleJ); + json_decref(moduleJ); + addModule(moduleWidget); // Set moduleWidget position math::Rect newBox = moduleWidget->box; newBox.pos = lastMousePos.minus(newBox.size.div(2)); requestModuleBoxNearest(moduleWidget, newBox); - - json_decref(moduleJ); } else { WARN("JSON parsing error at %s %d:%d %s", error.source, error.line, error.column, error.text); @@ -427,6 +457,7 @@ void RackWidget::cloneModule(ModuleWidget *m) { json_t *moduleJ = m->toJson(); ModuleWidget *clonedModuleWidget = moduleFromJson(moduleJ); json_decref(moduleJ); + addModule(clonedModuleWidget); math::Rect clonedBox = clonedModuleWidget->box; clonedBox.pos = m->box.pos; requestModuleBoxNearest(clonedModuleWidget, clonedBox); @@ -491,7 +522,8 @@ void RackWidget::step() { } // Autosave every 15 seconds - if (context()->window->frame % (60 * 15) == 0) { + int frame = context()->window->frame; + if (frame > 0 && frame % (60 * 15) == 0) { save(asset::user("autosave.vcv")); settings::save(asset::user("settings.json")); } diff --git a/src/app/WireContainer.cpp b/src/app/WireContainer.cpp index 23a9d1ae..ec05934c 100644 --- a/src/app/WireContainer.cpp +++ b/src/app/WireContainer.cpp @@ -64,7 +64,7 @@ void WireContainer::removeAllWires(Port *port) { for (Widget *child : children) { WireWidget *wire = dynamic_cast(child); assert(wire); - if (wire->inputPort == port || wire->outputPort == port) { + if (!wire || wire->inputPort == port || wire->outputPort == port) { if (activeWire == wire) { activeWire = NULL; } diff --git a/src/engine/Engine.cpp b/src/engine/Engine.cpp index b96fefc4..c4b31ec8 100644 --- a/src/engine/Engine.cpp +++ b/src/engine/Engine.cpp @@ -52,6 +52,7 @@ struct Engine::Internal { Module *resetModule = NULL; Module *randomizeModule = NULL; + int nextModuleId = 0; // Parameter smoothing Module *smoothModule = NULL; @@ -216,6 +217,9 @@ void Engine::addModule(Module *module) { assert(module); VIPLock vipLock(internal->vipMutex); std::lock_guard lock(internal->mutex); + // Set ID + assert(module->id == -1); + module->id = internal->nextModuleId++; // Check that the module is not already added auto it = std::find(modules.begin(), modules.end(), module); assert(it == modules.end()); @@ -238,8 +242,10 @@ void Engine::removeModule(Module *module) { // Check that the module actually exists auto it = std::find(modules.begin(), modules.end(), module); assert(it != modules.end()); - // Remove it + // Remove the module modules.erase(it); + // Remove id + module->id = -1; } void Engine::resetModule(Module *module) { @@ -313,6 +319,10 @@ void Engine::setParamSmooth(Module *module, int paramId, float value) { internal->smoothModule = module; } +int Engine::getNextModuleId() { + return internal->nextModuleId++; +} + void Engine::setSampleRate(float newSampleRate) { internal->sampleRateRequested = newSampleRate; } diff --git a/src/engine/Module.cpp b/src/engine/Module.cpp index a6589582..3f705aef 100644 --- a/src/engine/Module.cpp +++ b/src/engine/Module.cpp @@ -4,9 +4,26 @@ namespace rack { +Module::Module() { +} + +void Module::setup(int numParams, int numInputs, int numOutputs, int numLights) { + params.resize(numParams); + // Create default param labels + for (int i = 0; i < numParams; i++) { + params[i].label = string::f("#%d", i + 1); + } + inputs.resize(numInputs); + outputs.resize(numOutputs); + lights.resize(numLights); +} + json_t *Module::toJson() { json_t *rootJ = json_object(); + // id + json_object_set_new(rootJ, "id", json_integer(id)); + // params json_t *paramsJ = json_array(); for (Param ¶m : params) { @@ -25,6 +42,11 @@ json_t *Module::toJson() { } void Module::fromJson(json_t *rootJ) { + // id + json_t *idJ = json_object_get(rootJ, "id"); + if (idJ) + id = json_integer_value(idJ); + // params json_t *paramsJ = json_object_get(rootJ, "params"); size_t i;