@@ -5,6 +5,7 @@ | |||
#include "engine/Param.hpp" | |||
#include "engine/Port.hpp" | |||
#include "engine/Light.hpp" | |||
#include "engine/ParamQuantity.hpp" | |||
#include <vector> | |||
#include <jansson.h> | |||
@@ -29,6 +30,7 @@ struct Module { | |||
std::vector<Output> outputs; | |||
std::vector<Input> inputs; | |||
std::vector<Light> lights; | |||
std::vector<ParamQuantity*> paramQuantities; | |||
/** Access to adjacent modules */ | |||
int leftModuleId = -1; | |||
Module *leftModule = NULL; | |||
@@ -48,12 +50,35 @@ struct Module { | |||
DEPRECATED Module(int numParams, int numInputs, int numOutputs, int numLights = 0) : Module() { | |||
config(numParams, numInputs, numOutputs, numLights); | |||
} | |||
virtual ~Module() {} | |||
virtual ~Module(); | |||
/** Configures the number of Params, Outputs, Inputs, and Lights. */ | |||
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 { | |||
float sampleRate; | |||
@@ -83,6 +108,9 @@ struct Module { | |||
/** Called when the Module is removed from the Engine */ | |||
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. */ | |||
virtual json_t *dataToJson() { return NULL; } | |||
virtual void dataFromJson(json_t *root) {} | |||
@@ -5,88 +5,19 @@ | |||
namespace rack { | |||
namespace app { | |||
struct ParamQuantity; | |||
} // namespace app | |||
namespace engine { | |||
struct ParamQuantityFactory { | |||
virtual ~ParamQuantityFactory() {} | |||
virtual app::ParamQuantity *create() = 0; | |||
}; | |||
struct Param { | |||
/** Unstable API. Use setValue() and getValue() instead. */ | |||
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() { | |||
return value; | |||
} | |||
/* Clamps and sets the 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(); | |||
@@ -1,6 +1,5 @@ | |||
#pragma once | |||
#include "Quantity.hpp" | |||
#include "engine/Module.hpp" | |||
#include "engine/Param.hpp" | |||
@@ -8,12 +7,38 @@ namespace rack { | |||
namespace engine { | |||
struct Module; | |||
/** A Quantity that wraps an engine::Param. */ | |||
struct ParamQuantity : Quantity { | |||
engine::Module *module = NULL; | |||
Module *module = NULL; | |||
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 */ | |||
void setSmoothValue(float smoothValue); | |||
float getSmoothValue(); | |||
@@ -62,13 +62,7 @@ TParamWidget *createParam(math::Vec pos, engine::Module *module, int paramId) { | |||
TParamWidget *o = new TParamWidget; | |||
o->box.pos = pos; | |||
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; | |||
} | |||
@@ -92,11 +86,8 @@ TPortWidget *createInput(math::Vec pos, engine::Module *module, int inputId) { | |||
template <class TPortWidget> | |||
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; | |||
} | |||
@@ -112,11 +103,8 @@ TPortWidget *createOutput(math::Vec pos, engine::Module *module, int outputId) { | |||
template <class TPortWidget> | |||
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; | |||
} | |||
@@ -131,10 +119,8 @@ TModuleLightWidget *createLight(math::Vec pos, engine::Module *module, int first | |||
template <class TModuleLightWidget> | |||
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; | |||
} | |||
@@ -79,20 +79,21 @@ struct MIDI_Map : Module { | |||
// Check if CC value has been set | |||
if (values[cc] < 0) | |||
continue; | |||
// Get module | |||
// Get Module | |||
Module *module = paramHandles[id].module; | |||
if (!module) | |||
continue; | |||
// Get param | |||
// Get ParamQuantity | |||
int paramId = paramHandles[id].paramId; | |||
Param *param = &module->params[paramId]; | |||
if (!param->isBounded()) | |||
ParamQuantity *paramQuantity = module->paramQuantities[paramId]; | |||
if (!paramQuantity) | |||
continue; | |||
// Set param | |||
if (!paramQuantity->isBounded()) | |||
continue; | |||
// Set ParamQuantity | |||
float v = rescale(values[cc], 0, 127, 0.f, 1.f); | |||
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; | |||
if (paramId >= (int) m->params.size()) | |||
return ""; | |||
Param *param = &m->params[paramId]; | |||
ParamQuantity *paramQuantity = m->paramQuantities[paramId]; | |||
std::string s; | |||
s += mw->model->name; | |||
s += " "; | |||
s += param->label; | |||
s += paramQuantity->label; | |||
return s; | |||
} | |||
}; | |||
@@ -65,7 +65,7 @@ struct ParamTooltip : ui::Tooltip { | |||
// Quantity string | |||
text = paramWidget->paramQuantity->getString(); | |||
// Param description | |||
std::string description = paramWidget->paramQuantity->getParam()->description; | |||
std::string description = paramWidget->paramQuantity->description; | |||
if (!description.empty()) | |||
text += "\n" + description; | |||
} | |||
@@ -246,8 +246,8 @@ static void Engine_step(Engine *that) { | |||
// decay rate is 1 graphics frame | |||
const float smoothLambda = 60.f; | |||
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); | |||
internal->smoothModule = NULL; | |||
internal->smoothParamId = 0; | |||
@@ -8,15 +8,23 @@ namespace engine { | |||
Module::Module() { | |||
} | |||
Module::~Module() { | |||
for (ParamQuantity *paramQuantity : paramQuantities) { | |||
if (paramQuantity) | |||
delete paramQuantity; | |||
} | |||
} | |||
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); | |||
paramQuantities.resize(numParams); | |||
// Initialize paramQuantities | |||
for (int i = 0; i < numParams; i++) { | |||
configParam(i, 0.f, 1.f, 0.f); | |||
} | |||
} | |||
json_t *Module::toJson() { | |||
@@ -10,20 +10,17 @@ namespace engine { | |||
json_t *Param::toJson() { | |||
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; | |||
} | |||
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() { | |||
if (!module) | |||
return 0.f; | |||
return APP->engine->getSmoothParam(module, paramId); | |||
} | |||
void ParamQuantity::setValue(float value) { | |||
if (!module) | |||
return; | |||
if (!std::isfinite(value)) | |||
return; | |||
// This setter clamps the value | |||
value = math::clampSafe(value, getMinValue(), getMaxValue()); | |||
APP->engine->setParam(module, paramId, value); | |||
} | |||
@@ -39,30 +39,24 @@ float ParamQuantity::getValue() { | |||
} | |||
float ParamQuantity::getMinValue() { | |||
if (!module) | |||
return -1.f; | |||
return getParam()->minValue; | |||
return minValue; | |||
} | |||
float ParamQuantity::getMaxValue() { | |||
if (!module) | |||
return 1.f; | |||
return getParam()->maxValue; | |||
return maxValue; | |||
} | |||
float ParamQuantity::getDefaultValue() { | |||
if (!module) | |||
return 0.f; | |||
return getParam()->defaultValue; | |||
return defaultValue; | |||
} | |||
float ParamQuantity::getDisplayValue() { | |||
if (!module) | |||
return Quantity::getDisplayValue(); | |||
float v = getSmoothValue(); | |||
float displayBase = getParam()->displayBase; | |||
if (displayBase == 0.f) { | |||
// Linear | |||
// v is unchanged | |||
} | |||
else if (displayBase < 0.f) { | |||
// Logarithmic | |||
@@ -72,16 +66,16 @@ float ParamQuantity::getDisplayValue() { | |||
// Exponential | |||
v = std::pow(displayBase, v); | |||
} | |||
return v * getParam()->displayMultiplier + getParam()->displayOffset; | |||
return v * displayMultiplier + displayOffset; | |||
} | |||
void ParamQuantity::setDisplayValue(float displayValue) { | |||
if (!module) | |||
return; | |||
float v = (displayValue - getParam()->displayOffset) / getParam()->displayMultiplier; | |||
float displayBase = getParam()->displayBase; | |||
float v = (displayValue - displayOffset) / displayMultiplier; | |||
if (displayBase == 0.f) { | |||
// Linear | |||
// v is unchanged | |||
} | |||
else if (displayBase < 0.f) { | |||
// Logarithmic | |||
@@ -107,15 +101,11 @@ void ParamQuantity::setDisplayValueString(std::string s) { | |||
} | |||
std::string ParamQuantity::getLabel() { | |||
if (!module) | |||
return Quantity::getLabel(); | |||
return getParam()->label; | |||
return label; | |||
} | |||
std::string ParamQuantity::getUnit() { | |||
if (!module) | |||
return Quantity::getUnit(); | |||
return getParam()->unit; | |||
return unit; | |||
} | |||