| @@ -5,6 +5,7 @@ | |||||
| #include "engine/Param.hpp" | #include "engine/Param.hpp" | ||||
| #include "engine/Port.hpp" | #include "engine/Port.hpp" | ||||
| #include "engine/Light.hpp" | #include "engine/Light.hpp" | ||||
| #include "engine/ParamQuantity.hpp" | |||||
| #include <vector> | #include <vector> | ||||
| #include <jansson.h> | #include <jansson.h> | ||||
| @@ -29,6 +30,7 @@ struct Module { | |||||
| std::vector<Output> outputs; | std::vector<Output> outputs; | ||||
| std::vector<Input> inputs; | std::vector<Input> inputs; | ||||
| std::vector<Light> lights; | std::vector<Light> lights; | ||||
| std::vector<ParamQuantity*> paramQuantities; | |||||
| /** Access to adjacent modules */ | /** Access to adjacent modules */ | ||||
| int leftModuleId = -1; | int leftModuleId = -1; | ||||
| Module *leftModule = NULL; | Module *leftModule = NULL; | ||||
| @@ -48,12 +50,35 @@ struct Module { | |||||
| DEPRECATED Module(int numParams, int numInputs, int numOutputs, int numLights = 0) : Module() { | DEPRECATED Module(int numParams, int numInputs, int numOutputs, int numLights = 0) : Module() { | ||||
| config(numParams, numInputs, numOutputs, numLights); | config(numParams, numInputs, numOutputs, numLights); | ||||
| } | } | ||||
| virtual ~Module() {} | |||||
| virtual ~Module(); | |||||
| /** Configures the number of Params, Outputs, Inputs, and Lights. */ | /** Configures the number of Params, Outputs, Inputs, and Lights. */ | ||||
| void config(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); | |||||
| template <class TParamQuantity = ParamQuantity> | |||||
| void configParam(int paramId, float minValue, float maxValue, float defaultValue, std::string label = "", std::string unit = "", float displayBase = 0.f, float displayMultiplier = 1.f, float displayOffset = 0.f) { | |||||
| if (paramQuantities[paramId]) | |||||
| delete paramQuantities[paramId]; | |||||
| Param *p = ¶ms[paramId]; | |||||
| p->value = defaultValue; | |||||
| ParamQuantity *q = new TParamQuantity; | |||||
| q->module = this; | |||||
| q->paramId = paramId; | |||||
| q->minValue = minValue; | |||||
| q->maxValue = maxValue; | |||||
| q->defaultValue = defaultValue; | |||||
| if (!label.empty()) | |||||
| q->label = label; | |||||
| else | |||||
| q->label = string::f("#%d", paramId + 1); | |||||
| q->unit = unit; | |||||
| q->displayBase = displayBase; | |||||
| q->displayMultiplier = displayMultiplier; | |||||
| q->displayOffset = displayOffset; | |||||
| paramQuantities[paramId] = q; | |||||
| } | |||||
| struct ProcessArgs { | struct ProcessArgs { | ||||
| float sampleRate; | float sampleRate; | ||||
| @@ -83,6 +108,9 @@ struct Module { | |||||
| /** Called when the Module is removed from the Engine */ | /** Called when the Module is removed from the Engine */ | ||||
| virtual void onRemove() {} | virtual void onRemove() {} | ||||
| json_t *toJson(); | |||||
| void fromJson(json_t *rootJ); | |||||
| /** Override to store extra internal data in the "data" property of the module's JSON object. */ | /** Override to store extra internal data in the "data" property of the module's JSON object. */ | ||||
| virtual json_t *dataToJson() { return NULL; } | virtual json_t *dataToJson() { return NULL; } | ||||
| virtual void dataFromJson(json_t *root) {} | virtual void dataFromJson(json_t *root) {} | ||||
| @@ -5,88 +5,19 @@ | |||||
| namespace rack { | namespace rack { | ||||
| namespace app { | |||||
| struct ParamQuantity; | |||||
| } // namespace app | |||||
| namespace engine { | namespace engine { | ||||
| struct ParamQuantityFactory { | |||||
| virtual ~ParamQuantityFactory() {} | |||||
| virtual app::ParamQuantity *create() = 0; | |||||
| }; | |||||
| struct Param { | struct Param { | ||||
| /** Unstable API. Use setValue() and getValue() instead. */ | /** Unstable API. Use setValue() and getValue() instead. */ | ||||
| float value = 0.f; | float value = 0.f; | ||||
| /** The minimum allowed value. */ | |||||
| float minValue = 0.f; | |||||
| /** The maximum allowed value. Must be greater than minValue. */ | |||||
| float maxValue = 1.f; | |||||
| /** The initial value. */ | |||||
| float defaultValue = 0.f; | |||||
| /** The name of the parameter, using sentence capitalization. | |||||
| e.g. "Frequency", "Pulse width", "Alternative mode" | |||||
| */ | |||||
| std::string label; | |||||
| /** The numerical unit of measurement appended to the value. | |||||
| Units that are words should have a space to separate the numerical value from the number (e.g. " semitones", " octaves"). | |||||
| Unit abbreviations and symbols should have no space (e.g. "V", "ms", "%", "Âş"). | |||||
| */ | |||||
| std::string unit; | |||||
| /** Set to 0 for linear, positive for exponential, negative for logarithmic. */ | |||||
| float displayBase = 0.f; | |||||
| float displayMultiplier = 1.f; | |||||
| float displayOffset = 0.f; | |||||
| /** An optional one-sentence description of the parameter. */ | |||||
| std::string description; | |||||
| ParamQuantityFactory *paramQuantityFactory = NULL; | |||||
| ~Param() { | |||||
| if (paramQuantityFactory) | |||||
| delete paramQuantityFactory; | |||||
| } | |||||
| template<class TParamQuantity = app::ParamQuantity> | |||||
| void config(float minValue, float maxValue, float defaultValue, std::string label = "", std::string unit = "", float displayBase = 0.f, float displayMultiplier = 1.f, float displayOffset = 0.f) { | |||||
| this->value = defaultValue; | |||||
| this->minValue = minValue; | |||||
| this->maxValue = maxValue; | |||||
| this->defaultValue = defaultValue; | |||||
| if (!label.empty()) | |||||
| this->label = label; | |||||
| this->unit = unit; | |||||
| this->displayBase = displayBase; | |||||
| this->displayMultiplier = displayMultiplier; | |||||
| this->displayOffset = displayOffset; | |||||
| struct TParamQuantityFactory : ParamQuantityFactory { | |||||
| app::ParamQuantity *create() override {return new TParamQuantity;} | |||||
| }; | |||||
| if (paramQuantityFactory) | |||||
| delete paramQuantityFactory; | |||||
| paramQuantityFactory = new TParamQuantityFactory; | |||||
| } | |||||
| float getValue() { | float getValue() { | ||||
| return value; | return value; | ||||
| } | } | ||||
| /* Clamps and sets the value. */ | |||||
| void setValue(float value) { | void setValue(float value) { | ||||
| this->value = math::clampSafe(value, minValue, maxValue); | |||||
| } | |||||
| /** Returns whether the Param has finite range between minValue and maxValue. */ | |||||
| bool isBounded() { | |||||
| return std::isfinite(minValue) && std::isfinite(maxValue); | |||||
| this->value = value; | |||||
| } | } | ||||
| json_t *toJson(); | json_t *toJson(); | ||||
| @@ -1,6 +1,5 @@ | |||||
| #pragma once | #pragma once | ||||
| #include "Quantity.hpp" | #include "Quantity.hpp" | ||||
| #include "engine/Module.hpp" | |||||
| #include "engine/Param.hpp" | #include "engine/Param.hpp" | ||||
| @@ -8,12 +7,38 @@ namespace rack { | |||||
| namespace engine { | namespace engine { | ||||
| struct Module; | |||||
| /** A Quantity that wraps an engine::Param. */ | /** A Quantity that wraps an engine::Param. */ | ||||
| struct ParamQuantity : Quantity { | struct ParamQuantity : Quantity { | ||||
| engine::Module *module = NULL; | |||||
| Module *module = NULL; | |||||
| int paramId = 0; | int paramId = 0; | ||||
| engine::Param *getParam(); | |||||
| /** The minimum allowed value. */ | |||||
| float minValue = 0.f; | |||||
| /** The maximum allowed value. Must be greater than minValue. */ | |||||
| float maxValue = 1.f; | |||||
| /** The initial value. */ | |||||
| float defaultValue = 0.f; | |||||
| /** The name of the parameter, using sentence capitalization. | |||||
| e.g. "Frequency", "Pulse width", "Alternative mode" | |||||
| */ | |||||
| std::string label; | |||||
| /** The numerical unit of measurement appended to the value. | |||||
| Units that are words should have a space to separate the numerical value from the number (e.g. " semitones", " octaves"). | |||||
| Unit abbreviations and symbols should have no space (e.g. "V", "ms", "%", "Âş"). | |||||
| */ | |||||
| std::string unit; | |||||
| /** Set to 0 for linear, positive for exponential, negative for logarithmic. */ | |||||
| float displayBase = 0.f; | |||||
| float displayMultiplier = 1.f; | |||||
| float displayOffset = 0.f; | |||||
| /** An optional one-sentence description of the parameter. */ | |||||
| std::string description; | |||||
| Param *getParam(); | |||||
| /** Request to the engine to smoothly set the value */ | /** Request to the engine to smoothly set the value */ | ||||
| void setSmoothValue(float smoothValue); | void setSmoothValue(float smoothValue); | ||||
| float getSmoothValue(); | float getSmoothValue(); | ||||
| @@ -62,13 +62,7 @@ TParamWidget *createParam(math::Vec pos, engine::Module *module, int paramId) { | |||||
| TParamWidget *o = new TParamWidget; | TParamWidget *o = new TParamWidget; | ||||
| o->box.pos = pos; | o->box.pos = pos; | ||||
| if (module) { | if (module) { | ||||
| engine::ParamQuantityFactory *f = module->params[paramId].paramQuantityFactory; | |||||
| if (f) | |||||
| o->paramQuantity = f->create(); | |||||
| else | |||||
| o->paramQuantity = new engine::ParamQuantity; | |||||
| o->paramQuantity->module = module; | |||||
| o->paramQuantity->paramId = paramId; | |||||
| o->paramQuantity = module->paramQuantities[paramId]; | |||||
| } | } | ||||
| return o; | return o; | ||||
| } | } | ||||
| @@ -92,11 +86,8 @@ TPortWidget *createInput(math::Vec pos, engine::Module *module, int inputId) { | |||||
| template <class TPortWidget> | template <class TPortWidget> | ||||
| TPortWidget *createInputCentered(math::Vec pos, engine::Module *module, int inputId) { | TPortWidget *createInputCentered(math::Vec pos, engine::Module *module, int inputId) { | ||||
| TPortWidget *o = new TPortWidget; | |||||
| o->box.pos = pos.minus(o->box.size.div(2)); | |||||
| o->module = module; | |||||
| o->type = app::PortWidget::INPUT; | |||||
| o->portId = inputId; | |||||
| TPortWidget *o = createInput<TPortWidget>(pos, module, inputId); | |||||
| o->box.pos = o->box.pos.minus(o->box.size.div(2)); | |||||
| return o; | return o; | ||||
| } | } | ||||
| @@ -112,11 +103,8 @@ TPortWidget *createOutput(math::Vec pos, engine::Module *module, int outputId) { | |||||
| template <class TPortWidget> | template <class TPortWidget> | ||||
| TPortWidget *createOutputCentered(math::Vec pos, engine::Module *module, int outputId) { | TPortWidget *createOutputCentered(math::Vec pos, engine::Module *module, int outputId) { | ||||
| TPortWidget *o = new TPortWidget; | |||||
| o->box.pos = pos.minus(o->box.size.div(2)); | |||||
| o->module = module; | |||||
| o->type = app::PortWidget::OUTPUT; | |||||
| o->portId = outputId; | |||||
| TPortWidget *o = createOutput<TPortWidget>(pos, module, outputId); | |||||
| o->box.pos = o->box.pos.minus(o->box.size.div(2)); | |||||
| return o; | return o; | ||||
| } | } | ||||
| @@ -131,10 +119,8 @@ TModuleLightWidget *createLight(math::Vec pos, engine::Module *module, int first | |||||
| template <class TModuleLightWidget> | template <class TModuleLightWidget> | ||||
| TModuleLightWidget *createLightCentered(math::Vec pos, engine::Module *module, int firstLightId) { | TModuleLightWidget *createLightCentered(math::Vec pos, engine::Module *module, int firstLightId) { | ||||
| TModuleLightWidget *o = new TModuleLightWidget; | |||||
| o->box.pos = pos.minus(o->box.size.div(2)); | |||||
| o->module = module; | |||||
| o->firstLightId = firstLightId; | |||||
| TModuleLightWidget *o = createLight<TModuleLightWidget>(pos, module, firstLightId); | |||||
| o->box.pos = o->box.pos.minus(o->box.size.div(2)); | |||||
| return o; | return o; | ||||
| } | } | ||||
| @@ -79,20 +79,21 @@ struct MIDI_Map : Module { | |||||
| // Check if CC value has been set | // Check if CC value has been set | ||||
| if (values[cc] < 0) | if (values[cc] < 0) | ||||
| continue; | continue; | ||||
| // Get module | |||||
| // Get Module | |||||
| Module *module = paramHandles[id].module; | Module *module = paramHandles[id].module; | ||||
| if (!module) | if (!module) | ||||
| continue; | continue; | ||||
| // Get param | |||||
| // Get ParamQuantity | |||||
| int paramId = paramHandles[id].paramId; | int paramId = paramHandles[id].paramId; | ||||
| Param *param = &module->params[paramId]; | |||||
| if (!param->isBounded()) | |||||
| ParamQuantity *paramQuantity = module->paramQuantities[paramId]; | |||||
| if (!paramQuantity) | |||||
| continue; | continue; | ||||
| // Set param | |||||
| if (!paramQuantity->isBounded()) | |||||
| continue; | |||||
| // Set ParamQuantity | |||||
| float v = rescale(values[cc], 0, 127, 0.f, 1.f); | float v = rescale(values[cc], 0, 127, 0.f, 1.f); | ||||
| v = valueFilters[id].process(args.sampleTime, v); | v = valueFilters[id].process(args.sampleTime, v); | ||||
| v = rescale(v, 0.f, 1.f, param->minValue, param->maxValue); | |||||
| APP->engine->setParam(module, paramId, v); | |||||
| paramQuantity->setScaledValue(v); | |||||
| } | } | ||||
| } | } | ||||
| @@ -354,11 +355,11 @@ struct MIDI_MapChoice : LedDisplayChoice { | |||||
| int paramId = paramHandle->paramId; | int paramId = paramHandle->paramId; | ||||
| if (paramId >= (int) m->params.size()) | if (paramId >= (int) m->params.size()) | ||||
| return ""; | return ""; | ||||
| Param *param = &m->params[paramId]; | |||||
| ParamQuantity *paramQuantity = m->paramQuantities[paramId]; | |||||
| std::string s; | std::string s; | ||||
| s += mw->model->name; | s += mw->model->name; | ||||
| s += " "; | s += " "; | ||||
| s += param->label; | |||||
| s += paramQuantity->label; | |||||
| return s; | return s; | ||||
| } | } | ||||
| }; | }; | ||||
| @@ -65,7 +65,7 @@ struct ParamTooltip : ui::Tooltip { | |||||
| // Quantity string | // Quantity string | ||||
| text = paramWidget->paramQuantity->getString(); | text = paramWidget->paramQuantity->getString(); | ||||
| // Param description | // Param description | ||||
| std::string description = paramWidget->paramQuantity->getParam()->description; | |||||
| std::string description = paramWidget->paramQuantity->description; | |||||
| if (!description.empty()) | if (!description.empty()) | ||||
| text += "\n" + description; | text += "\n" + description; | ||||
| } | } | ||||
| @@ -246,8 +246,8 @@ static void Engine_step(Engine *that) { | |||||
| // decay rate is 1 graphics frame | // decay rate is 1 graphics frame | ||||
| const float smoothLambda = 60.f; | const float smoothLambda = 60.f; | ||||
| float newValue = value + (smoothValue - value) * smoothLambda * internal->sampleTime; | float newValue = value + (smoothValue - value) * smoothLambda * internal->sampleTime; | ||||
| if (value == newValue || !(param->minValue <= newValue && newValue <= param->maxValue)) { | |||||
| // Snap to actual smooth value if the value doesn't change enough (due to the granularity of floats), or if newValue is out of bounds | |||||
| if (value == newValue) { | |||||
| // Snap to actual smooth value if the value doesn't change enough (due to the granularity of floats) | |||||
| param->setValue(smoothValue); | param->setValue(smoothValue); | ||||
| internal->smoothModule = NULL; | internal->smoothModule = NULL; | ||||
| internal->smoothParamId = 0; | internal->smoothParamId = 0; | ||||
| @@ -8,15 +8,23 @@ namespace engine { | |||||
| Module::Module() { | Module::Module() { | ||||
| } | } | ||||
| Module::~Module() { | |||||
| for (ParamQuantity *paramQuantity : paramQuantities) { | |||||
| if (paramQuantity) | |||||
| delete paramQuantity; | |||||
| } | |||||
| } | |||||
| void Module::config(int numParams, int numInputs, int numOutputs, int numLights) { | void Module::config(int numParams, int numInputs, int numOutputs, int numLights) { | ||||
| params.resize(numParams); | 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); | inputs.resize(numInputs); | ||||
| outputs.resize(numOutputs); | outputs.resize(numOutputs); | ||||
| lights.resize(numLights); | lights.resize(numLights); | ||||
| paramQuantities.resize(numParams); | |||||
| // Initialize paramQuantities | |||||
| for (int i = 0; i < numParams; i++) { | |||||
| configParam(i, 0.f, 1.f, 0.f); | |||||
| } | |||||
| } | } | ||||
| json_t *Module::toJson() { | json_t *Module::toJson() { | ||||
| @@ -10,20 +10,17 @@ namespace engine { | |||||
| json_t *Param::toJson() { | json_t *Param::toJson() { | ||||
| json_t *rootJ = json_object(); | json_t *rootJ = json_object(); | ||||
| // Infinite params should serialize to 0 | |||||
| if (isBounded()) { | |||||
| json_object_set_new(rootJ, "value", json_real(value)); | |||||
| } | |||||
| // TODO Handle unbounded case | |||||
| json_object_set_new(rootJ, "value", json_real(value)); | |||||
| return rootJ; | return rootJ; | ||||
| } | } | ||||
| void Param::fromJson(json_t *rootJ) { | void Param::fromJson(json_t *rootJ) { | ||||
| if (isBounded()) { | |||||
| json_t *valueJ = json_object_get(rootJ, "value"); | |||||
| if (valueJ) | |||||
| value = json_number_value(valueJ); | |||||
| } | |||||
| // TODO Handle unbounded case | |||||
| json_t *valueJ = json_object_get(rootJ, "value"); | |||||
| if (valueJ) | |||||
| value = json_number_value(valueJ); | |||||
| } | } | ||||
| @@ -20,15 +20,15 @@ void ParamQuantity::setSmoothValue(float smoothValue) { | |||||
| } | } | ||||
| float ParamQuantity::getSmoothValue() { | float ParamQuantity::getSmoothValue() { | ||||
| if (!module) | |||||
| return 0.f; | |||||
| return APP->engine->getSmoothParam(module, paramId); | return APP->engine->getSmoothParam(module, paramId); | ||||
| } | } | ||||
| void ParamQuantity::setValue(float value) { | void ParamQuantity::setValue(float value) { | ||||
| if (!module) | if (!module) | ||||
| return; | return; | ||||
| if (!std::isfinite(value)) | |||||
| return; | |||||
| // This setter clamps the value | |||||
| value = math::clampSafe(value, getMinValue(), getMaxValue()); | |||||
| APP->engine->setParam(module, paramId, value); | APP->engine->setParam(module, paramId, value); | ||||
| } | } | ||||
| @@ -39,30 +39,24 @@ float ParamQuantity::getValue() { | |||||
| } | } | ||||
| float ParamQuantity::getMinValue() { | float ParamQuantity::getMinValue() { | ||||
| if (!module) | |||||
| return -1.f; | |||||
| return getParam()->minValue; | |||||
| return minValue; | |||||
| } | } | ||||
| float ParamQuantity::getMaxValue() { | float ParamQuantity::getMaxValue() { | ||||
| if (!module) | |||||
| return 1.f; | |||||
| return getParam()->maxValue; | |||||
| return maxValue; | |||||
| } | } | ||||
| float ParamQuantity::getDefaultValue() { | float ParamQuantity::getDefaultValue() { | ||||
| if (!module) | |||||
| return 0.f; | |||||
| return getParam()->defaultValue; | |||||
| return defaultValue; | |||||
| } | } | ||||
| float ParamQuantity::getDisplayValue() { | float ParamQuantity::getDisplayValue() { | ||||
| if (!module) | if (!module) | ||||
| return Quantity::getDisplayValue(); | return Quantity::getDisplayValue(); | ||||
| float v = getSmoothValue(); | float v = getSmoothValue(); | ||||
| float displayBase = getParam()->displayBase; | |||||
| if (displayBase == 0.f) { | if (displayBase == 0.f) { | ||||
| // Linear | // Linear | ||||
| // v is unchanged | |||||
| } | } | ||||
| else if (displayBase < 0.f) { | else if (displayBase < 0.f) { | ||||
| // Logarithmic | // Logarithmic | ||||
| @@ -72,16 +66,16 @@ float ParamQuantity::getDisplayValue() { | |||||
| // Exponential | // Exponential | ||||
| v = std::pow(displayBase, v); | v = std::pow(displayBase, v); | ||||
| } | } | ||||
| return v * getParam()->displayMultiplier + getParam()->displayOffset; | |||||
| return v * displayMultiplier + displayOffset; | |||||
| } | } | ||||
| void ParamQuantity::setDisplayValue(float displayValue) { | void ParamQuantity::setDisplayValue(float displayValue) { | ||||
| if (!module) | if (!module) | ||||
| return; | return; | ||||
| float v = (displayValue - getParam()->displayOffset) / getParam()->displayMultiplier; | |||||
| float displayBase = getParam()->displayBase; | |||||
| float v = (displayValue - displayOffset) / displayMultiplier; | |||||
| if (displayBase == 0.f) { | if (displayBase == 0.f) { | ||||
| // Linear | // Linear | ||||
| // v is unchanged | |||||
| } | } | ||||
| else if (displayBase < 0.f) { | else if (displayBase < 0.f) { | ||||
| // Logarithmic | // Logarithmic | ||||
| @@ -107,15 +101,11 @@ void ParamQuantity::setDisplayValueString(std::string s) { | |||||
| } | } | ||||
| std::string ParamQuantity::getLabel() { | std::string ParamQuantity::getLabel() { | ||||
| if (!module) | |||||
| return Quantity::getLabel(); | |||||
| return getParam()->label; | |||||
| return label; | |||||
| } | } | ||||
| std::string ParamQuantity::getUnit() { | std::string ParamQuantity::getUnit() { | ||||
| if (!module) | |||||
| return Quantity::getUnit(); | |||||
| return getParam()->unit; | |||||
| return unit; | |||||
| } | } | ||||