| @@ -12,6 +12,7 @@ In this document, Mod is Ctrl on Windows/Linux and Cmd on Mac. | |||||
| - Add "Primary module" context menu item to VCV Audio modules to select which audio device clocks the engine. | - Add "Primary module" context menu item to VCV Audio modules to select which audio device clocks the engine. | ||||
| - Allow other modules such as VCV Recorder to be the primary module, to render audio faster than real-time. | - Allow other modules such as VCV Recorder to be the primary module, to render audio faster than real-time. | ||||
| - Remove "Real-time priority" menu item, since the thread priority is now managed elsewhere (RtAudio, etc). | - Remove "Real-time priority" menu item, since the thread priority is now managed elsewhere (RtAudio, etc). | ||||
| - Replace module disabling with bypassing, which directly routes certain inputs to outputs if specified by the plugin. | |||||
| - Duplicate cables patched to inputs when a module is duplicated. | - Duplicate cables patched to inputs when a module is duplicated. | ||||
| - Add module tags to module context menu. | - Add module tags to module context menu. | ||||
| - Add module manual URL (if plugin developer supplies it) to module context menu item. | - Add module manual URL (if plugin developer supplies it) to module context menu item. | ||||
| @@ -82,7 +82,7 @@ struct ModuleWidget : widget::OpaqueWidget { | |||||
| void randomizeAction(); | void randomizeAction(); | ||||
| void disconnectAction(); | void disconnectAction(); | ||||
| void cloneAction(); | void cloneAction(); | ||||
| void disableAction(); | |||||
| void bypassAction(); | |||||
| /** Deletes `this` */ | /** Deletes `this` */ | ||||
| void removeAction(); | void removeAction(); | ||||
| void createContextMenu(); | void createContextMenu(); | ||||
| @@ -50,7 +50,7 @@ struct Engine { | |||||
| Module* getModule(int moduleId); | Module* getModule(int moduleId); | ||||
| void resetModule(Module* module); | void resetModule(Module* module); | ||||
| void randomizeModule(Module* module); | void randomizeModule(Module* module); | ||||
| void disableModule(Module* module, bool disabled); | |||||
| void bypassModule(Module* module, bool bypassed); | |||||
| // Cables | // Cables | ||||
| /** Adds a cable to the rack engine. | /** Adds a cable to the rack engine. | ||||
| @@ -85,10 +85,7 @@ struct Module { | |||||
| Only written when CPU timing is enabled, since time measurement is expensive. | Only written when CPU timing is enabled, since time measurement is expensive. | ||||
| */ | */ | ||||
| float cpuTime = 0.f; | float cpuTime = 0.f; | ||||
| /** Whether the Module is skipped from stepping by the engine. | |||||
| Module subclasses should not read/write this variable. | |||||
| */ | |||||
| bool disabled = false; | |||||
| bool bypassed = false; | |||||
| /** Constructs a Module with no params, inputs, outputs, and lights. */ | /** Constructs a Module with no params, inputs, outputs, and lights. */ | ||||
| Module(); | Module(); | ||||
| @@ -181,6 +178,7 @@ struct Module { | |||||
| virtual void step() {} | virtual void step() {} | ||||
| /** Called instead of process() when Module is bypassed. | /** Called instead of process() when Module is bypassed. | ||||
| Typically you do not need to override this. Use configBypass() instead. | Typically you do not need to override this. Use configBypass() instead. | ||||
| If you do override it, avoid reading param values, since the state of the module should have no effect on routing. | |||||
| */ | */ | ||||
| virtual void processBypass(const ProcessArgs& args); | virtual void processBypass(const ProcessArgs& args); | ||||
| @@ -219,15 +217,15 @@ struct Module { | |||||
| onRemove(); | onRemove(); | ||||
| } | } | ||||
| struct EnableEvent {}; | |||||
| /** Called after enabling the module. | |||||
| struct BypassEvent {}; | |||||
| /** Called after bypassing the module. | |||||
| */ | */ | ||||
| virtual void onEnable(const EnableEvent& e) {} | |||||
| virtual void onBypass(const BypassEvent& e) {} | |||||
| struct DisableEvent {}; | |||||
| /** Called after disabling the module. | |||||
| struct UnBypassEvent {}; | |||||
| /** Called after enabling the module. | |||||
| */ | */ | ||||
| virtual void onDisable(const DisableEvent& e) {} | |||||
| virtual void onUnBypass(const UnBypassEvent& e) {} | |||||
| struct PortChangeEvent { | struct PortChangeEvent { | ||||
| /** True if connecting, false if disconnecting. */ | /** True if connecting, false if disconnecting. */ | ||||
| @@ -97,12 +97,12 @@ struct ModuleMove : ModuleAction { | |||||
| }; | }; | ||||
| struct ModuleDisable : ModuleAction { | |||||
| bool disabled; | |||||
| struct ModuleBypass : ModuleAction { | |||||
| bool bypassed; | |||||
| void undo() override; | void undo() override; | ||||
| void redo() override; | void redo() override; | ||||
| ModuleDisable() { | |||||
| name = "disable module"; | |||||
| ModuleBypass() { | |||||
| name = "bypass module"; | |||||
| } | } | ||||
| }; | }; | ||||
| @@ -257,10 +257,10 @@ struct ModuleCloneItem : ui::MenuItem { | |||||
| }; | }; | ||||
| struct ModuleDisableItem : ui::MenuItem { | |||||
| struct ModuleBypassItem : ui::MenuItem { | |||||
| ModuleWidget* moduleWidget; | ModuleWidget* moduleWidget; | ||||
| void onAction(const event::Action& e) override { | void onAction(const event::Action& e) override { | ||||
| moduleWidget->disableAction(); | |||||
| moduleWidget->bypassAction(); | |||||
| } | } | ||||
| }; | }; | ||||
| @@ -299,7 +299,7 @@ ModuleWidget::~ModuleWidget() { | |||||
| void ModuleWidget::draw(const DrawArgs& args) { | void ModuleWidget::draw(const DrawArgs& args) { | ||||
| nvgScissor(args.vg, RECT_ARGS(args.clipBox)); | nvgScissor(args.vg, RECT_ARGS(args.clipBox)); | ||||
| if (module && module->disabled) { | |||||
| if (module && module->bypassed) { | |||||
| nvgGlobalAlpha(args.vg, 0.33); | nvgGlobalAlpha(args.vg, 0.33); | ||||
| } | } | ||||
| @@ -409,7 +409,7 @@ void ModuleWidget::onHoverKey(const event::HoverKey& e) { | |||||
| } break; | } break; | ||||
| case GLFW_KEY_E: { | case GLFW_KEY_E: { | ||||
| if ((e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { | if ((e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { | ||||
| disableAction(); | |||||
| bypassAction(); | |||||
| e.consume(this); | e.consume(this); | ||||
| } | } | ||||
| } break; | } break; | ||||
| @@ -833,14 +833,14 @@ void ModuleWidget::cloneAction() { | |||||
| APP->history->push(h); | APP->history->push(h); | ||||
| } | } | ||||
| void ModuleWidget::disableAction() { | |||||
| void ModuleWidget::bypassAction() { | |||||
| assert(module); | assert(module); | ||||
| APP->engine->disableModule(module, !module->disabled); | |||||
| APP->engine->bypassModule(module, !module->bypassed); | |||||
| // history::ModuleDisable | |||||
| history::ModuleDisable* h = new history::ModuleDisable; | |||||
| // history::ModuleBypass | |||||
| history::ModuleBypass* h = new history::ModuleBypass; | |||||
| h->moduleId = module->id; | h->moduleId = module->id; | ||||
| h->disabled = module->disabled; | |||||
| h->bypassed = module->bypassed; | |||||
| APP->history->push(h); | APP->history->push(h); | ||||
| } | } | ||||
| @@ -905,13 +905,13 @@ void ModuleWidget::createContextMenu() { | |||||
| cloneItem->moduleWidget = this; | cloneItem->moduleWidget = this; | ||||
| menu->addChild(cloneItem); | menu->addChild(cloneItem); | ||||
| ModuleDisableItem* disableItem = new ModuleDisableItem; | |||||
| disableItem->text = "Disable"; | |||||
| disableItem->rightText = RACK_MOD_CTRL_NAME "+E"; | |||||
| if (module && module->disabled) | |||||
| disableItem->rightText = CHECKMARK_STRING " " + disableItem->rightText; | |||||
| disableItem->moduleWidget = this; | |||||
| menu->addChild(disableItem); | |||||
| ModuleBypassItem* bypassItem = new ModuleBypassItem; | |||||
| bypassItem->text = "Bypass"; | |||||
| bypassItem->rightText = RACK_MOD_CTRL_NAME "+E"; | |||||
| if (module && module->bypassed) | |||||
| bypassItem->rightText = CHECKMARK_STRING " " + bypassItem->rightText; | |||||
| bypassItem->moduleWidget = this; | |||||
| menu->addChild(bypassItem); | |||||
| ModuleDeleteItem* deleteItem = new ModuleDeleteItem; | ModuleDeleteItem* deleteItem = new ModuleDeleteItem; | ||||
| deleteItem->text = "Delete"; | deleteItem->text = "Delete"; | ||||
| @@ -229,7 +229,7 @@ static void Engine_stepModulesWorker(Engine* that, int threadId) { | |||||
| } | } | ||||
| // Step module | // Step module | ||||
| if (!module->disabled) | |||||
| if (!module->bypassed) | |||||
| module->process(processArgs); | module->process(processArgs); | ||||
| else | else | ||||
| module->processBypass(processArgs); | module->processBypass(processArgs); | ||||
| @@ -626,25 +626,25 @@ void Engine::randomizeModule(Module* module) { | |||||
| } | } | ||||
| void Engine::disableModule(Module* module, bool disabled) { | |||||
| void Engine::bypassModule(Module* module, bool bypassed) { | |||||
| std::lock_guard<std::recursive_mutex> lock(internal->mutex); | std::lock_guard<std::recursive_mutex> lock(internal->mutex); | ||||
| assert(module); | assert(module); | ||||
| if (module->disabled == disabled) | |||||
| if (module->bypassed == bypassed) | |||||
| return; | return; | ||||
| // Clear outputs and set to 1 channel | // Clear outputs and set to 1 channel | ||||
| for (Output& output : module->outputs) { | for (Output& output : module->outputs) { | ||||
| // This zeros all voltages, but the channel is set to 1 if connected | // This zeros all voltages, but the channel is set to 1 if connected | ||||
| output.setChannels(0); | output.setChannels(0); | ||||
| } | } | ||||
| module->disabled = disabled; | |||||
| module->bypassed = bypassed; | |||||
| // Trigger event | // Trigger event | ||||
| if (disabled) { | |||||
| Module::DisableEvent eDisable; | |||||
| module->onDisable(eDisable); | |||||
| if (bypassed) { | |||||
| Module::BypassEvent eBypass; | |||||
| module->onBypass(eBypass); | |||||
| } | } | ||||
| else { | else { | ||||
| Module::EnableEvent eEnable; | |||||
| module->onEnable(eEnable); | |||||
| Module::UnBypassEvent eUnBypass; | |||||
| module->onUnBypass(eUnBypass); | |||||
| } | } | ||||
| } | } | ||||
| @@ -91,9 +91,9 @@ json_t* Module::toJson() { | |||||
| } | } | ||||
| json_object_set_new(rootJ, "params", paramsJ); | json_object_set_new(rootJ, "params", paramsJ); | ||||
| // disabled | |||||
| if (disabled) | |||||
| json_object_set_new(rootJ, "disabled", json_boolean(disabled)); | |||||
| // bypassed | |||||
| if (bypassed) | |||||
| json_object_set_new(rootJ, "bypassed", json_boolean(bypassed)); | |||||
| // leftModuleId | // leftModuleId | ||||
| if (leftExpander.moduleId >= 0) | if (leftExpander.moduleId >= 0) | ||||
| @@ -183,13 +183,16 @@ void Module::fromJson(json_t* rootJ) { | |||||
| params[paramId].setValue(json_number_value(valueJ)); | params[paramId].setValue(json_number_value(valueJ)); | ||||
| } | } | ||||
| // disabled | |||||
| json_t* disabledJ = json_object_get(rootJ, "disabled"); | |||||
| // legacy bypass | |||||
| if (!disabledJ) | |||||
| disabledJ = json_object_get(rootJ, "bypass"); | |||||
| if (disabledJ) | |||||
| disabled = json_boolean_value(disabledJ); | |||||
| // bypassed | |||||
| json_t* bypassedJ = json_object_get(rootJ, "bypassed"); | |||||
| // legacy "bypass" in v0.6 or early v1 (don't remember) | |||||
| if (!bypassedJ) | |||||
| bypassedJ = json_object_get(rootJ, "bypass"); | |||||
| // legacy "disabled" in v1 | |||||
| if (!bypassedJ) | |||||
| bypassedJ = json_object_get(rootJ, "disabled"); | |||||
| if (bypassedJ) | |||||
| bypassed = json_boolean_value(bypassedJ); | |||||
| // These do not need to be deserialized, since the module positions will set them correctly when added to the rack. | // These do not need to be deserialized, since the module positions will set them correctly when added to the rack. | ||||
| // // leftModuleId | // // leftModuleId | ||||
| @@ -83,16 +83,16 @@ void ModuleMove::redo() { | |||||
| } | } | ||||
| void ModuleDisable::undo() { | |||||
| void ModuleBypass::undo() { | |||||
| engine::Module* module = APP->engine->getModule(moduleId); | engine::Module* module = APP->engine->getModule(moduleId); | ||||
| assert(module); | assert(module); | ||||
| APP->engine->disableModule(module, !disabled); | |||||
| APP->engine->bypassModule(module, !bypassed); | |||||
| } | } | ||||
| void ModuleDisable::redo() { | |||||
| void ModuleBypass::redo() { | |||||
| engine::Module* module = APP->engine->getModule(moduleId); | engine::Module* module = APP->engine->getModule(moduleId); | ||||
| assert(module); | assert(module); | ||||
| APP->engine->disableModule(module, disabled); | |||||
| APP->engine->bypassModule(module, bypassed); | |||||
| } | } | ||||
| @@ -102,7 +102,7 @@ void Plugin::fromJson(json_t* rootJ) { | |||||
| size_t moduleId; | size_t moduleId; | ||||
| json_t* moduleJ; | json_t* moduleJ; | ||||
| json_array_foreach(modulesJ, moduleId, moduleJ) { | json_array_foreach(modulesJ, moduleId, moduleJ) { | ||||
| // Check if module is disabled | |||||
| // Check if model is disabled | |||||
| json_t* disabledJ = json_object_get(moduleJ, "disabled"); | json_t* disabledJ = json_object_get(moduleJ, "disabled"); | ||||
| if (disabledJ) { | if (disabledJ) { | ||||
| if (json_boolean_value(disabledJ)) | if (json_boolean_value(disabledJ)) | ||||