@@ -2,6 +2,7 @@ | |||||
#include "ui/Quantity.hpp" | #include "ui/Quantity.hpp" | ||||
#include "engine/Module.hpp" | #include "engine/Module.hpp" | ||||
#include "engine/Param.hpp" | #include "engine/Param.hpp" | ||||
#include "engine/ParamInfo.hpp" | |||||
namespace rack { | namespace rack { | ||||
@@ -18,6 +19,7 @@ struct ParamQuantity : Quantity { | |||||
float snapValue = 0.f; | float snapValue = 0.f; | ||||
Param *getParam(); | Param *getParam(); | ||||
ParamInfo *getParamInfo(); | |||||
void commitSnap(); | void commitSnap(); | ||||
void setValue(float value) override; | void setValue(float value) override; | ||||
@@ -5,6 +5,7 @@ | |||||
#include "engine/Input.hpp" | #include "engine/Input.hpp" | ||||
#include "engine/Output.hpp" | #include "engine/Output.hpp" | ||||
#include "engine/Light.hpp" | #include "engine/Light.hpp" | ||||
#include "engine/ParamInfo.hpp" | |||||
#include <vector> | #include <vector> | ||||
#include <jansson.h> | #include <jansson.h> | ||||
@@ -18,19 +19,20 @@ struct Module { | |||||
std::vector<Input> inputs; | std::vector<Input> inputs; | ||||
std::vector<Output> outputs; | std::vector<Output> outputs; | ||||
std::vector<Light> lights; | std::vector<Light> lights; | ||||
std::vector<ParamInfo> paramInfos; | |||||
/** For power meter */ | /** For power meter */ | ||||
float cpuTime = 0.f; | float cpuTime = 0.f; | ||||
bool bypass = false; | bool bypass = false; | ||||
/** Constructs a Module with no params, inputs, outputs, and lights */ | /** Constructs a Module with no params, inputs, outputs, and lights */ | ||||
Module(); | 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() {} | 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(); | json_t *toJson(); | ||||
void fromJson(json_t *rootJ); | void fromJson(json_t *rootJ); | ||||
void reset(); | void reset(); | ||||
@@ -12,23 +12,11 @@ struct Param { | |||||
float maxValue = 1.f; | float maxValue = 1.f; | ||||
float defaultValue = 0.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->value = defaultValue; | ||||
this->minValue = minValue; | this->minValue = minValue; | ||||
this->maxValue = maxValue; | this->maxValue = maxValue; | ||||
this->defaultValue = defaultValue; | this->defaultValue = defaultValue; | ||||
this->label = label; | |||||
this->unit = unit; | |||||
this->displayBase = displayBase; | |||||
this->displayMultiplier = displayMultiplier; | |||||
} | } | ||||
json_t *toJson(); | 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 *createParam(math::Vec pos, Module *module, int paramId) { | ||||
TParamWidget *o = new TParamWidget; | TParamWidget *o = new TParamWidget; | ||||
o->box.pos = pos; | 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; | return o; | ||||
} | } | ||||
template <class TParamWidget> | template <class TParamWidget> | ||||
TParamWidget *createParamCentered(math::Vec pos, Module *module, int paramId) { | 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; | 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 | /** An action operating on a module | ||||
Subclass this to create your own custom actions for your module. | Subclass this to create your own custom actions for your module. | ||||
*/ | */ | ||||
@@ -37,16 +48,6 @@ struct ModuleRemove : ModuleAction { | |||||
Model *model; | Model *model; | ||||
math::Vec pos; | math::Vec pos; | ||||
json_t *moduleJ; | json_t *moduleJ; | ||||
struct CableInfo { | |||||
int cableId; | |||||
int outputModuleId; | |||||
int outputId; | |||||
int inputModuleId; | |||||
int inputId; | |||||
}; | |||||
std::vector<CableInfo> cableInfos; | |||||
~ModuleRemove(); | ~ModuleRemove(); | ||||
void undo() override; | void undo() override; | ||||
void redo() 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 { | struct State { | ||||
std::vector<Action*> actions; | std::vector<Action*> actions; | ||||
int actionIndex = 0; | int actionIndex = 0; | ||||
@@ -118,7 +118,7 @@ struct AudioInterface : Module { | |||||
dsp::DoubleRingBuffer<dsp::Frame<AUDIO_OUTPUTS>, 16> outputBuffer; | dsp::DoubleRingBuffer<dsp::Frame<AUDIO_OUTPUTS>, 16> outputBuffer; | ||||
AudioInterface() { | AudioInterface() { | ||||
setup(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); | |||||
config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); | |||||
onSampleRateChange(); | onSampleRateChange(); | ||||
} | } | ||||
@@ -25,7 +25,7 @@ struct MIDICCToCVInterface : Module { | |||||
int learnedCcs[16] = {}; | int learnedCcs[16] = {}; | ||||
MIDICCToCVInterface() { | MIDICCToCVInterface() { | ||||
setup(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); | |||||
config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); | |||||
onReset(); | onReset(); | ||||
} | } | ||||
@@ -56,7 +56,7 @@ struct MIDIToCVInterface : Module { | |||||
bool gate; | bool gate; | ||||
MIDIToCVInterface() { | MIDIToCVInterface() { | ||||
setup(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); | |||||
config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); | |||||
heldNotes.resize(128, 0); | heldNotes.resize(128, 0); | ||||
onReset(); | onReset(); | ||||
} | } | ||||
@@ -28,7 +28,7 @@ struct MIDITriggerToCVInterface : Module { | |||||
bool velocity = false; | bool velocity = false; | ||||
MIDITriggerToCVInterface() { | MIDITriggerToCVInterface() { | ||||
setup(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); | |||||
config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); | |||||
onReset(); | onReset(); | ||||
} | } | ||||
@@ -51,7 +51,7 @@ struct QuadMIDIToCVInterface : Module { | |||||
int stealIndex; | int stealIndex; | ||||
QuadMIDIToCVInterface() { | QuadMIDIToCVInterface() { | ||||
setup(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); | |||||
config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); | |||||
cachedNotes.resize(128, 0); | cachedNotes.resize(128, 0); | ||||
onReset(); | onReset(); | ||||
} | } | ||||
@@ -282,18 +282,14 @@ void ModuleWidget::draw(NVGcontext *vg) { | |||||
nvgBeginPath(vg); | nvgBeginPath(vg); | ||||
nvgRect(vg, | nvgRect(vg, | ||||
0, box.size.y - 20, | 0, box.size.y - 20, | ||||
55, 20); | |||||
65, 20); | |||||
nvgFillColor(vg, nvgRGBAf(0, 0, 0, 0.5)); | nvgFillColor(vg, nvgRGBAf(0, 0, 0, 0.5)); | ||||
nvgFill(vg); | 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); | nvgBeginPath(vg); | ||||
nvgRect(vg, | nvgRect(vg, | ||||
0, (1.f - p) * box.size.y, | 0, (1.f - p) * box.size.y, | ||||
@@ -318,13 +314,17 @@ void ModuleWidget::drawShadow(NVGcontext *vg) { | |||||
} | } | ||||
static void ModuleWidget_removeAction(ModuleWidget *moduleWidget) { | static void ModuleWidget_removeAction(ModuleWidget *moduleWidget) { | ||||
history::ComplexAction *complexAction = new history::ComplexAction; | |||||
// Push ModuleRemove history action | // 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); | app()->scene->rackWidget->removeModule(moduleWidget); | ||||
delete moduleWidget; | delete moduleWidget; | ||||
@@ -9,6 +9,11 @@ Param *ParamQuantity::getParam() { | |||||
return &module->params[paramId]; | return &module->params[paramId]; | ||||
} | } | ||||
ParamInfo *ParamQuantity::getParamInfo() { | |||||
assert(module); | |||||
return &module->paramInfos[paramId]; | |||||
} | |||||
void ParamQuantity::commitSnap() { | void ParamQuantity::commitSnap() { | ||||
// TODO | // TODO | ||||
} | } | ||||
@@ -49,34 +54,34 @@ float ParamQuantity::getDefaultValue() { | |||||
float ParamQuantity::getDisplayValue() { | float ParamQuantity::getDisplayValue() { | ||||
if (!module) | if (!module) | ||||
return Quantity::getDisplayValue(); | return Quantity::getDisplayValue(); | ||||
if (getParam()->displayBase == 0.f) { | |||||
if (getParamInfo()->displayBase == 0.f) { | |||||
// Linear | // 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) | // Fixed (special case of exponential) | ||||
return getParam()->displayMultiplier; | |||||
return getParamInfo()->displayMultiplier; | |||||
} | } | ||||
else { | else { | ||||
// Exponential | // Exponential | ||||
return std::pow(getParam()->displayBase, getValue()) * getParam()->displayMultiplier; | |||||
return std::pow(getParamInfo()->displayBase, getValue()) * getParamInfo()->displayMultiplier; | |||||
} | } | ||||
} | } | ||||
void ParamQuantity::setDisplayValue(float displayValue) { | void ParamQuantity::setDisplayValue(float displayValue) { | ||||
if (!module) | if (!module) | ||||
return; | return; | ||||
if (getParam()->displayBase == 0.f) { | |||||
if (getParamInfo()->displayBase == 0.f) { | |||||
// Linear | // Linear | ||||
setValue(displayValue / getParam()->displayMultiplier); | |||||
setValue(displayValue / getParamInfo()->displayMultiplier); | |||||
} | } | ||||
else if (getParam()->displayBase == 1.f) { | |||||
else if (getParamInfo()->displayBase == 1.f) { | |||||
// Fixed | // Fixed | ||||
setValue(getParam()->displayMultiplier); | |||||
setValue(getParamInfo()->displayMultiplier); | |||||
} | } | ||||
else { | else { | ||||
// Exponential | // 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() { | std::string ParamQuantity::getLabel() { | ||||
if (!module) | if (!module) | ||||
return Quantity::getLabel(); | return Quantity::getLabel(); | ||||
return getParam()->label; | |||||
return getParamInfo()->label; | |||||
} | } | ||||
std::string ParamQuantity::getUnit() { | std::string ParamQuantity::getUnit() { | ||||
if (!module) | if (!module) | ||||
return Quantity::getUnit(); | return Quantity::getUnit(); | ||||
return getParam()->unit; | |||||
return getParamInfo()->unit; | |||||
} | } | ||||
@@ -82,7 +82,7 @@ void ParamWidget::step() { | |||||
// Quantity string | // Quantity string | ||||
tooltip->text = paramQuantity->getString(); | tooltip->text = paramQuantity->getString(); | ||||
// Param description | // Param description | ||||
std::string description = paramQuantity->getParam()->description; | |||||
std::string description = paramQuantity->getParamInfo()->description; | |||||
if (!description.empty()) | if (!description.empty()) | ||||
tooltip->text += "\n" + description; | tooltip->text += "\n" + description; | ||||
} | } | ||||
@@ -144,10 +144,10 @@ static void Engine_step(Engine *engine) { | |||||
module->step(); | module->step(); | ||||
auto stopTime = std::chrono::high_resolution_clock::now(); | 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 | // 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 { | else { | ||||
module->step(); | module->step(); | ||||
@@ -240,6 +240,7 @@ void Engine::addModule(Module *module) { | |||||
} | } | ||||
else { | else { | ||||
// Manual ID | // Manual ID | ||||
assert(module->id < internal->nextModuleId); | |||||
// Check that the ID is not already taken | // Check that the ID is not already taken | ||||
for (Module *m : modules) { | for (Module *m : modules) { | ||||
assert(module->id != m->id); | assert(module->id != m->id); | ||||
@@ -315,6 +316,7 @@ void Engine::addCable(Cable *cable) { | |||||
} | } | ||||
else { | else { | ||||
// Manual ID | // Manual ID | ||||
assert(cable->id < internal->nextCableId); | |||||
// Check that the ID is not already taken | // Check that the ID is not already taken | ||||
for (Cable *w : cables) { | for (Cable *w : cables) { | ||||
assert(cable->id != w->id); | assert(cable->id != w->id); | ||||
@@ -7,15 +7,16 @@ namespace rack { | |||||
Module::Module() { | 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); | 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); | ||||
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() { | json_t *Module::toJson() { | ||||
@@ -9,8 +9,10 @@ namespace rack { | |||||
json_t *Param::toJson() { | json_t *Param::toJson() { | ||||
json_t *rootJ = json_object(); | json_t *rootJ = json_object(); | ||||
float v = 0.f; | |||||
// Infinite params should serialize to 0 | // 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)); | json_object_set_new(rootJ, "value", json_real(v)); | ||||
return rootJ; | return rootJ; | ||||
@@ -7,6 +7,30 @@ namespace rack { | |||||
namespace history { | 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() { | void ModuleAdd::undo() { | ||||
ModuleWidget *moduleWidget = app()->scene->rackWidget->getModule(moduleId); | ModuleWidget *moduleWidget = app()->scene->rackWidget->getModule(moduleId); | ||||
assert(moduleWidget); | assert(moduleWidget); | ||||
@@ -36,11 +60,6 @@ void ModuleRemove::undo() { | |||||
moduleWidget->box.pos = pos; | moduleWidget->box.pos = pos; | ||||
moduleWidget->fromJson(moduleJ); | moduleWidget->fromJson(moduleJ); | ||||
app()->scene->rackWidget->addModule(moduleWidget); | app()->scene->rackWidget->addModule(moduleWidget); | ||||
// Add cables | |||||
for (CableInfo &cableInfo : cableInfos) { | |||||
// TODO Add cable | |||||
} | |||||
} | } | ||||
void ModuleRemove::redo() { | void ModuleRemove::redo() { | ||||