| @@ -2,6 +2,7 @@ | |||
| #include "ui/Quantity.hpp" | |||
| #include "engine/Module.hpp" | |||
| #include "engine/Param.hpp" | |||
| #include "engine/ParamInfo.hpp" | |||
| namespace rack { | |||
| @@ -18,6 +19,7 @@ struct ParamQuantity : Quantity { | |||
| float snapValue = 0.f; | |||
| Param *getParam(); | |||
| ParamInfo *getParamInfo(); | |||
| void commitSnap(); | |||
| void setValue(float value) override; | |||
| @@ -5,6 +5,7 @@ | |||
| #include "engine/Input.hpp" | |||
| #include "engine/Output.hpp" | |||
| #include "engine/Light.hpp" | |||
| #include "engine/ParamInfo.hpp" | |||
| #include <vector> | |||
| #include <jansson.h> | |||
| @@ -18,19 +19,20 @@ struct Module { | |||
| std::vector<Input> inputs; | |||
| std::vector<Output> outputs; | |||
| std::vector<Light> lights; | |||
| std::vector<ParamInfo> paramInfos; | |||
| /** For power meter */ | |||
| float cpuTime = 0.f; | |||
| bool bypass = false; | |||
| /** Constructs a Module with no params, inputs, outputs, and lights */ | |||
| Module(); | |||
| /** Deprecated. Use setup() instead. */ | |||
| Module(int numParams, int numInputs, int numOutputs, int numLights = 0) : Module() { | |||
| setup(numParams, numInputs, numOutputs, numLights); | |||
| /** Deprecated. Use config() instead. */ | |||
| DEPRECATED Module(int numParams, int numInputs, int numOutputs, int numLights = 0) : Module() { | |||
| config(numParams, numInputs, numOutputs, numLights); | |||
| } | |||
| virtual ~Module() {} | |||
| void setup(int numParams, int numInputs, int numOutputs, int numLights = 0); | |||
| void config(int numParams, int numInputs, int numOutputs, int numLights = 0); | |||
| json_t *toJson(); | |||
| void fromJson(json_t *rootJ); | |||
| void reset(); | |||
| @@ -12,23 +12,11 @@ struct Param { | |||
| float maxValue = 1.f; | |||
| float defaultValue = 0.f; | |||
| // For formatting/displaying the value | |||
| /** Set to 0 for linear, nonzero for exponential */ | |||
| float displayBase = 0.f; | |||
| float displayMultiplier = 1.f; | |||
| std::string label; | |||
| std::string unit; | |||
| std::string description; | |||
| void setup(float minValue, float maxValue, float defaultValue, std::string label = "", std::string unit = "", float displayBase = 0.f, float displayMultiplier = 1.f) { | |||
| void config(float minValue, float maxValue, float defaultValue) { | |||
| this->value = defaultValue; | |||
| this->minValue = minValue; | |||
| this->maxValue = maxValue; | |||
| this->defaultValue = defaultValue; | |||
| this->label = label; | |||
| this->unit = unit; | |||
| this->displayBase = displayBase; | |||
| this->displayMultiplier = displayMultiplier; | |||
| } | |||
| json_t *toJson(); | |||
| @@ -0,0 +1,48 @@ | |||
| #pragma once | |||
| #include "common.hpp" | |||
| namespace rack { | |||
| struct ParamQuantity; | |||
| struct ParamQuantityFactory { | |||
| virtual ~ParamQuantityFactory() {} | |||
| virtual ParamQuantity *create() = 0; | |||
| }; | |||
| struct ParamInfo { | |||
| // For formatting/displaying the value | |||
| /** Set to 0 for linear, nonzero for exponential */ | |||
| std::string label; | |||
| std::string unit; | |||
| float displayBase = 0.f; | |||
| float displayMultiplier = 1.f; | |||
| std::string description; | |||
| ParamQuantityFactory *paramQuantityFactory = NULL; | |||
| ~ParamInfo() { | |||
| delete paramQuantityFactory; | |||
| } | |||
| template<class TParamQuantity = ParamQuantity> | |||
| void config(std::string label = "", std::string unit = "", float displayBase = 0.f, float displayMultiplier = 1.f) { | |||
| this->label = label; | |||
| this->unit = unit; | |||
| this->displayBase = displayBase; | |||
| this->displayMultiplier = displayMultiplier; | |||
| struct TParamQuantityFactory : ParamQuantityFactory { | |||
| ParamQuantity *create() override {return new TParamQuantity;} | |||
| }; | |||
| if (paramQuantityFactory) | |||
| delete paramQuantityFactory; | |||
| paramQuantityFactory = new TParamQuantityFactory; | |||
| } | |||
| }; | |||
| } // namespace rack | |||
| @@ -57,21 +57,22 @@ template <class TParamWidget> | |||
| TParamWidget *createParam(math::Vec pos, Module *module, int paramId) { | |||
| TParamWidget *o = new TParamWidget; | |||
| o->box.pos = pos; | |||
| ParamQuantity *q = new ParamQuantity; | |||
| q->module = module; | |||
| q->paramId = paramId; | |||
| o->paramQuantity = q; | |||
| if (module) { | |||
| ParamQuantityFactory *f = module->paramInfos[paramId].paramQuantityFactory; | |||
| if (f) | |||
| o->paramQuantity = f->create(); | |||
| else | |||
| o->paramQuantity = new ParamQuantity; | |||
| o->paramQuantity->module = module; | |||
| o->paramQuantity->paramId = paramId; | |||
| } | |||
| return o; | |||
| } | |||
| template <class TParamWidget> | |||
| TParamWidget *createParamCentered(math::Vec pos, Module *module, int paramId) { | |||
| TParamWidget *o = new TParamWidget; | |||
| o->box.pos = pos.minus(o->box.size.div(2)); | |||
| ParamQuantity *q = new ParamQuantity; | |||
| q->module = module; | |||
| q->paramId = paramId; | |||
| o->paramQuantity = q; | |||
| TParamWidget *o = createParam<TParamWidget>(pos, module, paramId); | |||
| o->box.pos = o->box.pos.minus(o->box.size.div(2)); | |||
| return o; | |||
| } | |||
| @@ -17,6 +17,17 @@ struct Action { | |||
| }; | |||
| /** Batches multiple actions into one */ | |||
| struct ComplexAction : Action { | |||
| /** Ordered by time occurred. Undoing will replay them backwards. */ | |||
| std::vector<Action*> actions; | |||
| ~ComplexAction(); | |||
| void undo() override; | |||
| void redo() override; | |||
| void push(Action *action); | |||
| }; | |||
| /** An action operating on a module | |||
| Subclass this to create your own custom actions for your module. | |||
| */ | |||
| @@ -37,16 +48,6 @@ struct ModuleRemove : ModuleAction { | |||
| Model *model; | |||
| math::Vec pos; | |||
| json_t *moduleJ; | |||
| struct CableInfo { | |||
| int cableId; | |||
| int outputModuleId; | |||
| int outputId; | |||
| int inputModuleId; | |||
| int inputId; | |||
| }; | |||
| std::vector<CableInfo> cableInfos; | |||
| ~ModuleRemove(); | |||
| void undo() override; | |||
| void redo() override; | |||
| @@ -92,21 +93,6 @@ struct CableRemove : Action { | |||
| }; | |||
| struct CableMove : Action { | |||
| int cableId; | |||
| int oldOutputModuleId; | |||
| int oldOutputId; | |||
| int oldInputModuleId; | |||
| int oldInputId; | |||
| int newOutputModuleId; | |||
| int newOutputId; | |||
| int newInputModuleId; | |||
| int newInputId; | |||
| void undo() override; | |||
| void redo() override; | |||
| }; | |||
| struct State { | |||
| std::vector<Action*> actions; | |||
| int actionIndex = 0; | |||
| @@ -118,7 +118,7 @@ struct AudioInterface : Module { | |||
| dsp::DoubleRingBuffer<dsp::Frame<AUDIO_OUTPUTS>, 16> outputBuffer; | |||
| AudioInterface() { | |||
| setup(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); | |||
| config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); | |||
| onSampleRateChange(); | |||
| } | |||
| @@ -25,7 +25,7 @@ struct MIDICCToCVInterface : Module { | |||
| int learnedCcs[16] = {}; | |||
| MIDICCToCVInterface() { | |||
| setup(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); | |||
| config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); | |||
| onReset(); | |||
| } | |||
| @@ -56,7 +56,7 @@ struct MIDIToCVInterface : Module { | |||
| bool gate; | |||
| MIDIToCVInterface() { | |||
| setup(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); | |||
| config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); | |||
| heldNotes.resize(128, 0); | |||
| onReset(); | |||
| } | |||
| @@ -28,7 +28,7 @@ struct MIDITriggerToCVInterface : Module { | |||
| bool velocity = false; | |||
| MIDITriggerToCVInterface() { | |||
| setup(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); | |||
| config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); | |||
| onReset(); | |||
| } | |||
| @@ -51,7 +51,7 @@ struct QuadMIDIToCVInterface : Module { | |||
| int stealIndex; | |||
| QuadMIDIToCVInterface() { | |||
| setup(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); | |||
| config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); | |||
| cachedNotes.resize(128, 0); | |||
| onReset(); | |||
| } | |||
| @@ -282,18 +282,14 @@ void ModuleWidget::draw(NVGcontext *vg) { | |||
| nvgBeginPath(vg); | |||
| nvgRect(vg, | |||
| 0, box.size.y - 20, | |||
| 55, 20); | |||
| 65, 20); | |||
| nvgFillColor(vg, nvgRGBAf(0, 0, 0, 0.5)); | |||
| nvgFill(vg); | |||
| std::string cpuText = string::f("%.0f mS", module->cpuTime * 1000.f); | |||
| // TODO Use blendish text function | |||
| nvgFontFaceId(vg, app()->window->uiFont->handle); | |||
| nvgFontSize(vg, 12); | |||
| nvgFillColor(vg, nvgRGBf(1, 1, 1)); | |||
| nvgText(vg, 10.0, box.size.y - 6.0, cpuText.c_str(), NULL); | |||
| std::string cpuText = string::f("%.2f ÎĽs", module->cpuTime * 1e6f); | |||
| bndLabel(vg, 2.0, box.size.y - 20.0, INFINITY, INFINITY, -1, cpuText.c_str()); | |||
| float p = math::clamp(module->cpuTime, 0.f, 1.f); | |||
| float p = math::clamp(module->cpuTime / app()->engine->getSampleTime(), 0.f, 1.f); | |||
| nvgBeginPath(vg); | |||
| nvgRect(vg, | |||
| 0, (1.f - p) * box.size.y, | |||
| @@ -318,13 +314,17 @@ void ModuleWidget::drawShadow(NVGcontext *vg) { | |||
| } | |||
| static void ModuleWidget_removeAction(ModuleWidget *moduleWidget) { | |||
| history::ComplexAction *complexAction = new history::ComplexAction; | |||
| // Push ModuleRemove history action | |||
| history::ModuleRemove *h = new history::ModuleRemove; | |||
| h->model = moduleWidget->model; | |||
| h->moduleId = moduleWidget->module->id; | |||
| h->pos = moduleWidget->box.pos; | |||
| h->moduleJ = moduleWidget->toJson(); | |||
| app()->history->push(h); | |||
| history::ModuleRemove *moduleRemove = new history::ModuleRemove; | |||
| moduleRemove->model = moduleWidget->model; | |||
| moduleRemove->moduleId = moduleWidget->module->id; | |||
| moduleRemove->pos = moduleWidget->box.pos; | |||
| moduleRemove->moduleJ = moduleWidget->toJson(); | |||
| complexAction->push(moduleRemove); | |||
| app()->history->push(complexAction); | |||
| app()->scene->rackWidget->removeModule(moduleWidget); | |||
| delete moduleWidget; | |||
| @@ -9,6 +9,11 @@ Param *ParamQuantity::getParam() { | |||
| return &module->params[paramId]; | |||
| } | |||
| ParamInfo *ParamQuantity::getParamInfo() { | |||
| assert(module); | |||
| return &module->paramInfos[paramId]; | |||
| } | |||
| void ParamQuantity::commitSnap() { | |||
| // TODO | |||
| } | |||
| @@ -49,34 +54,34 @@ float ParamQuantity::getDefaultValue() { | |||
| float ParamQuantity::getDisplayValue() { | |||
| if (!module) | |||
| return Quantity::getDisplayValue(); | |||
| if (getParam()->displayBase == 0.f) { | |||
| if (getParamInfo()->displayBase == 0.f) { | |||
| // Linear | |||
| return getValue() * getParam()->displayMultiplier; | |||
| return getValue() * getParamInfo()->displayMultiplier; | |||
| } | |||
| else if (getParam()->displayBase == 1.f) { | |||
| else if (getParamInfo()->displayBase == 1.f) { | |||
| // Fixed (special case of exponential) | |||
| return getParam()->displayMultiplier; | |||
| return getParamInfo()->displayMultiplier; | |||
| } | |||
| else { | |||
| // Exponential | |||
| return std::pow(getParam()->displayBase, getValue()) * getParam()->displayMultiplier; | |||
| return std::pow(getParamInfo()->displayBase, getValue()) * getParamInfo()->displayMultiplier; | |||
| } | |||
| } | |||
| void ParamQuantity::setDisplayValue(float displayValue) { | |||
| if (!module) | |||
| return; | |||
| if (getParam()->displayBase == 0.f) { | |||
| if (getParamInfo()->displayBase == 0.f) { | |||
| // Linear | |||
| setValue(displayValue / getParam()->displayMultiplier); | |||
| setValue(displayValue / getParamInfo()->displayMultiplier); | |||
| } | |||
| else if (getParam()->displayBase == 1.f) { | |||
| else if (getParamInfo()->displayBase == 1.f) { | |||
| // Fixed | |||
| setValue(getParam()->displayMultiplier); | |||
| setValue(getParamInfo()->displayMultiplier); | |||
| } | |||
| else { | |||
| // Exponential | |||
| setValue(std::log(displayValue / getParam()->displayMultiplier) / std::log(getParam()->displayBase)); | |||
| setValue(std::log(displayValue / getParamInfo()->displayMultiplier) / std::log(getParamInfo()->displayBase)); | |||
| } | |||
| } | |||
| @@ -95,13 +100,13 @@ int ParamQuantity::getDisplayPrecision() { | |||
| std::string ParamQuantity::getLabel() { | |||
| if (!module) | |||
| return Quantity::getLabel(); | |||
| return getParam()->label; | |||
| return getParamInfo()->label; | |||
| } | |||
| std::string ParamQuantity::getUnit() { | |||
| if (!module) | |||
| return Quantity::getUnit(); | |||
| return getParam()->unit; | |||
| return getParamInfo()->unit; | |||
| } | |||
| @@ -82,7 +82,7 @@ void ParamWidget::step() { | |||
| // Quantity string | |||
| tooltip->text = paramQuantity->getString(); | |||
| // Param description | |||
| std::string description = paramQuantity->getParam()->description; | |||
| std::string description = paramQuantity->getParamInfo()->description; | |||
| if (!description.empty()) | |||
| tooltip->text += "\n" + description; | |||
| } | |||
| @@ -144,10 +144,10 @@ static void Engine_step(Engine *engine) { | |||
| module->step(); | |||
| auto stopTime = std::chrono::high_resolution_clock::now(); | |||
| float cpuTime = std::chrono::duration<float>(stopTime - startTime).count() * engine->internal->sampleRate; | |||
| float cpuTime = std::chrono::duration<float>(stopTime - startTime).count(); | |||
| // Smooth cpu time | |||
| float powerLambda = 2.f / 10.f; | |||
| module->cpuTime += (cpuTime - module->cpuTime) * engine->internal->sampleTime * powerLambda; | |||
| float powerLambda = engine->internal->sampleTime / 2.f; | |||
| module->cpuTime += (cpuTime - module->cpuTime) * powerLambda; | |||
| } | |||
| else { | |||
| module->step(); | |||
| @@ -240,6 +240,7 @@ void Engine::addModule(Module *module) { | |||
| } | |||
| else { | |||
| // Manual ID | |||
| assert(module->id < internal->nextModuleId); | |||
| // Check that the ID is not already taken | |||
| for (Module *m : modules) { | |||
| assert(module->id != m->id); | |||
| @@ -315,6 +316,7 @@ void Engine::addCable(Cable *cable) { | |||
| } | |||
| else { | |||
| // Manual ID | |||
| assert(cable->id < internal->nextCableId); | |||
| // Check that the ID is not already taken | |||
| for (Cable *w : cables) { | |||
| assert(cable->id != w->id); | |||
| @@ -7,15 +7,16 @@ namespace rack { | |||
| Module::Module() { | |||
| } | |||
| void Module::setup(int numParams, int numInputs, int numOutputs, int numLights) { | |||
| void Module::config(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); | |||
| paramInfos.resize(numParams); | |||
| // Create default param labels | |||
| for (int i = 0; i < numParams; i++) { | |||
| paramInfos[i].label = string::f("#%d", i + 1); | |||
| } | |||
| } | |||
| json_t *Module::toJson() { | |||
| @@ -9,8 +9,10 @@ namespace rack { | |||
| json_t *Param::toJson() { | |||
| json_t *rootJ = json_object(); | |||
| float v = 0.f; | |||
| // Infinite params should serialize to 0 | |||
| float v = (std::isfinite(minValue) && std::isfinite(maxValue)) ? value : 0.f; | |||
| if (std::isfinite(minValue) && std::isfinite(maxValue)) | |||
| v = value; | |||
| json_object_set_new(rootJ, "value", json_real(v)); | |||
| return rootJ; | |||
| @@ -7,6 +7,30 @@ namespace rack { | |||
| namespace history { | |||
| ComplexAction::~ComplexAction() { | |||
| for (Action *action : actions) { | |||
| delete action; | |||
| } | |||
| } | |||
| void ComplexAction::undo() { | |||
| for (auto it = actions.rbegin(); it != actions.rend(); it++) { | |||
| Action *action = *it; | |||
| action->undo(); | |||
| } | |||
| } | |||
| void ComplexAction::redo() { | |||
| for (Action *action : actions) { | |||
| action->redo(); | |||
| } | |||
| } | |||
| void ComplexAction::push(Action *action) { | |||
| actions.push_back(action); | |||
| } | |||
| void ModuleAdd::undo() { | |||
| ModuleWidget *moduleWidget = app()->scene->rackWidget->getModule(moduleId); | |||
| assert(moduleWidget); | |||
| @@ -36,11 +60,6 @@ void ModuleRemove::undo() { | |||
| moduleWidget->box.pos = pos; | |||
| moduleWidget->fromJson(moduleJ); | |||
| app()->scene->rackWidget->addModule(moduleWidget); | |||
| // Add cables | |||
| for (CableInfo &cableInfo : cableInfos) { | |||
| // TODO Add cable | |||
| } | |||
| } | |||
| void ModuleRemove::redo() { | |||