@@ -1,6 +1,7 @@ | |||||
#pragma once | #pragma once | ||||
#include "app/common.hpp" | #include "app/common.hpp" | ||||
#include "app/MultiLightWidget.hpp" | #include "app/MultiLightWidget.hpp" | ||||
#include "engine/Module.hpp" | |||||
namespace rack { | namespace rack { | ||||
@@ -6,7 +6,7 @@ | |||||
#include "app/Port.hpp" | #include "app/Port.hpp" | ||||
#include "app/ParamWidget.hpp" | #include "app/ParamWidget.hpp" | ||||
#include "plugin.hpp" | #include "plugin.hpp" | ||||
#include "engine.hpp" | |||||
#include "engine/Module.hpp" | |||||
namespace rack { | namespace rack { | ||||
@@ -1,6 +1,6 @@ | |||||
#pragma once | #pragma once | ||||
#include "ui/Quantity.hpp" | #include "ui/Quantity.hpp" | ||||
#include "engine.hpp" | |||||
#include "engine/Module.hpp" | |||||
namespace rack { | namespace rack { | ||||
@@ -2,7 +2,6 @@ | |||||
#include "widgets/OpaqueWidget.hpp" | #include "widgets/OpaqueWidget.hpp" | ||||
#include "app/ParamQuantity.hpp" | #include "app/ParamQuantity.hpp" | ||||
#include "app/common.hpp" | #include "app/common.hpp" | ||||
#include "engine.hpp" | |||||
namespace rack { | namespace rack { | ||||
@@ -2,7 +2,6 @@ | |||||
#include "app/common.hpp" | #include "app/common.hpp" | ||||
#include "app/WireWidget.hpp" | #include "app/WireWidget.hpp" | ||||
#include "app/WireContainer.hpp" | #include "app/WireContainer.hpp" | ||||
#include "engine.hpp" | |||||
namespace rack { | namespace rack { | ||||
@@ -1,6 +1,5 @@ | |||||
#pragma once | #pragma once | ||||
#include "app/common.hpp" | #include "app/common.hpp" | ||||
#include "engine.hpp" | |||||
namespace rack { | namespace rack { | ||||
@@ -1,7 +1,6 @@ | |||||
#pragma once | #pragma once | ||||
#include "app/common.hpp" | #include "app/common.hpp" | ||||
#include "app/WireWidget.hpp" | #include "app/WireWidget.hpp" | ||||
#include "engine.hpp" | |||||
namespace rack { | namespace rack { | ||||
@@ -1,5 +1,6 @@ | |||||
#pragma once | #pragma once | ||||
#include "app/common.hpp" | #include "app/common.hpp" | ||||
#include "engine/Wire.hpp" | |||||
namespace rack { | namespace rack { | ||||
@@ -1,6 +1,6 @@ | |||||
#pragma once | #pragma once | ||||
// Include some of the C++ standard library for convenience | |||||
// Include most of the C stdlib for convenience | |||||
#include <cstdlib> | #include <cstdlib> | ||||
#include <cstdio> | #include <cstdio> | ||||
#include <cstdint> | #include <cstdint> | ||||
@@ -9,6 +9,8 @@ | |||||
#include <cmath> | #include <cmath> | ||||
#include <cstring> | #include <cstring> | ||||
#include <cassert> | #include <cassert> | ||||
// Include some of the C++ stdlib for convenience | |||||
#include <string> | #include <string> | ||||
@@ -1,171 +0,0 @@ | |||||
#pragma once | |||||
#include <vector> | |||||
#include "common.hpp" | |||||
#include <jansson.h> | |||||
namespace rack { | |||||
struct Param { | |||||
float value = 0.f; | |||||
float minValue = 0.f; | |||||
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; | |||||
int displayPrecision = 2; | |||||
std::string label; | |||||
std::string unit; | |||||
// TODO Change this horrible method name | |||||
void setup(float minValue, float maxValue, float defaultValue, std::string label = "", std::string unit = "", int displayPrecision = 2) { | |||||
this->value = defaultValue; | |||||
this->minValue = minValue; | |||||
this->maxValue = maxValue; | |||||
this->defaultValue = defaultValue; | |||||
this->label = label; | |||||
this->unit = unit; | |||||
this->displayPrecision = displayPrecision; | |||||
} | |||||
json_t *toJson(); | |||||
void fromJson(json_t *rootJ); | |||||
}; | |||||
struct Light { | |||||
/** The square of the brightness value */ | |||||
float value = 0.0; | |||||
float getBrightness(); | |||||
void setBrightness(float brightness) { | |||||
value = (brightness > 0.f) ? brightness * brightness : 0.f; | |||||
} | |||||
/** Emulates slow fall (but immediate rise) of LED brightness. | |||||
`frames` rescales the timestep. For example, if your module calls this method every 16 frames, use 16.0. | |||||
*/ | |||||
void setBrightnessSmooth(float brightness, float frames = 1.f); | |||||
}; | |||||
struct Input { | |||||
/** Voltage of the port, zero if not plugged in. Read-only by Module */ | |||||
float value = 0.0; | |||||
/** Whether a wire is plugged in */ | |||||
bool active = false; | |||||
Light plugLights[2]; | |||||
/** Returns the value if a wire is plugged in, otherwise returns the given default value */ | |||||
float normalize(float normalValue) { | |||||
return active ? value : normalValue; | |||||
} | |||||
}; | |||||
struct Output { | |||||
/** Voltage of the port. Write-only by Module */ | |||||
float value = 0.0; | |||||
/** Whether a wire is plugged in */ | |||||
bool active = false; | |||||
Light plugLights[2]; | |||||
}; | |||||
struct Module { | |||||
std::vector<Param> params; | |||||
std::vector<Input> inputs; | |||||
std::vector<Output> outputs; | |||||
std::vector<Light> lights; | |||||
/** For CPU usage meter */ | |||||
float cpuTime = 0.0; | |||||
/** Constructs a Module with no params, inputs, outputs, and lights */ | |||||
Module() {} | |||||
/** Constructs a Module with a fixed number of params, inputs, outputs, and lights */ | |||||
Module(int numParams, int numInputs, int numOutputs, int numLights = 0) { | |||||
params.resize(numParams); | |||||
inputs.resize(numInputs); | |||||
outputs.resize(numOutputs); | |||||
lights.resize(numLights); | |||||
} | |||||
virtual ~Module() {} | |||||
/** Advances the module by 1 audio frame with duration 1.0 / gSampleRate | |||||
Override this method to read inputs and params, and to write outputs and lights. | |||||
*/ | |||||
virtual void step() {} | |||||
/** Called when the engine sample rate is changed | |||||
*/ | |||||
virtual void onSampleRateChange() {} | |||||
/** Deprecated */ | |||||
virtual void onCreate() {} | |||||
/** Deprecated */ | |||||
virtual void onDelete() {} | |||||
/** Called when user clicks Initialize in the module context menu */ | |||||
virtual void onReset() { | |||||
// Call deprecated method | |||||
reset(); | |||||
} | |||||
/** Called when user clicks Randomize in the module context menu */ | |||||
virtual void onRandomize() { | |||||
// Call deprecated method | |||||
randomize(); | |||||
} | |||||
json_t *toJson(); | |||||
void fromJson(json_t *rootJ); | |||||
/** Override these 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) {} | |||||
/** Deprecated */ | |||||
virtual void reset() {} | |||||
/** Deprecated */ | |||||
virtual void randomize() {} | |||||
}; | |||||
struct Wire { | |||||
Module *outputModule = NULL; | |||||
int outputId; | |||||
Module *inputModule = NULL; | |||||
int inputId; | |||||
void step(); | |||||
}; | |||||
void engineInit(); | |||||
void engineDestroy(); | |||||
/** Launches engine thread */ | |||||
void engineStart(); | |||||
void engineStop(); | |||||
/** Does not transfer pointer ownership */ | |||||
void engineAddModule(Module *module); | |||||
void engineRemoveModule(Module *module); | |||||
void engineResetModule(Module *module); | |||||
void engineRandomizeModule(Module *module); | |||||
/** Does not transfer pointer ownership */ | |||||
void engineAddWire(Wire *wire); | |||||
void engineRemoveWire(Wire *wire); | |||||
void engineSetParam(Module *module, int paramId, float value); | |||||
void engineSetParamSmooth(Module *module, int paramId, float value); | |||||
void engineSetSampleRate(float sampleRate); | |||||
float engineGetSampleRate(); | |||||
/** Returns the inverse of the current sample rate */ | |||||
float engineGetSampleTime(); | |||||
extern bool gPaused; | |||||
/** Plugins should not manipulate other modules or wires unless that is the entire purpose of the module. | |||||
Your plugin needs to have a clear purpose for manipulating other modules and wires and must be done with a good UX. | |||||
*/ | |||||
extern std::vector<Module*> gModules; | |||||
extern std::vector<Wire*> gWires; | |||||
extern bool gPowerMeter; | |||||
} // namespace rack |
@@ -0,0 +1,51 @@ | |||||
#pragma once | |||||
#include <vector> | |||||
#include "common.hpp" | |||||
#include "engine/Module.hpp" | |||||
#include "engine/Wire.hpp" | |||||
namespace rack { | |||||
struct Engine { | |||||
/** Plugins should not manipulate other modules or wires unless that is the entire purpose of the module. | |||||
Your plugin needs to have a clear purpose for manipulating other modules and wires and must be done with a good UX. | |||||
*/ | |||||
std::vector<Module*> modules; | |||||
std::vector<Wire*> wires; | |||||
bool paused = false; | |||||
bool powerMeter = false; | |||||
struct Internal; | |||||
Internal *internal; | |||||
Engine(); | |||||
~Engine(); | |||||
/** Starts engine thread */ | |||||
void start(); | |||||
/** Stops engine thread */ | |||||
void stop(); | |||||
/** Does not transfer pointer ownership */ | |||||
void addModule(Module *module); | |||||
void removeModule(Module *module); | |||||
void resetModule(Module *module); | |||||
void randomizeModule(Module *module); | |||||
/** Does not transfer pointer ownership */ | |||||
void addWire(Wire *wire); | |||||
void removeWire(Wire *wire); | |||||
void setParam(Module *module, int paramId, float value); | |||||
void setParamSmooth(Module *module, int paramId, float value); | |||||
void setSampleRate(float sampleRate); | |||||
float getSampleRate(); | |||||
/** Returns the inverse of the current sample rate */ | |||||
float getSampleTime(); | |||||
}; | |||||
// TODO Move to global state header | |||||
extern Engine *gEngine; | |||||
} // namespace rack |
@@ -0,0 +1,23 @@ | |||||
#pragma once | |||||
#include "common.hpp" | |||||
#include "engine/Light.hpp" | |||||
namespace rack { | |||||
struct Input { | |||||
/** Voltage of the port, zero if not plugged in. Read-only by Module */ | |||||
float value = 0.f; | |||||
/** Whether a wire is plugged in */ | |||||
bool active = false; | |||||
Light plugLights[2]; | |||||
/** Returns the value if a wire is plugged in, otherwise returns the given default value */ | |||||
float normalize(float normalValue) { | |||||
return active ? value : normalValue; | |||||
} | |||||
}; | |||||
} // namespace rack |
@@ -0,0 +1,21 @@ | |||||
#pragma once | |||||
#include "common.hpp" | |||||
namespace rack { | |||||
struct Light { | |||||
float value = 0.f; | |||||
float getBrightness(); | |||||
void setBrightness(float brightness) { | |||||
value = (brightness > 0.f) ? std::pow(brightness, 2) : 0.f; | |||||
} | |||||
/** Emulates slow fall (but immediate rise) of LED brightness. | |||||
`frames` rescales the timestep. For example, if your module calls this method every 16 frames, use 16.f. | |||||
*/ | |||||
void setBrightnessSmooth(float brightness, float frames = 1.f); | |||||
}; | |||||
} // namespace rack |
@@ -0,0 +1,58 @@ | |||||
#pragma once | |||||
#include <vector> | |||||
#include "common.hpp" | |||||
#include "engine/Param.hpp" | |||||
#include "engine/Input.hpp" | |||||
#include "engine/Output.hpp" | |||||
#include "engine/Light.hpp" | |||||
#include <jansson.h> | |||||
namespace rack { | |||||
struct Module { | |||||
std::vector<Param> params; | |||||
std::vector<Input> inputs; | |||||
std::vector<Output> outputs; | |||||
std::vector<Light> lights; | |||||
/** For CPU usage meter */ | |||||
float cpuTime = 0.f; | |||||
/** Constructs a Module with no params, inputs, outputs, and lights */ | |||||
Module() {} | |||||
/** Constructs a Module with a fixed number of params, inputs, outputs, and lights */ | |||||
Module(int numParams, int numInputs, int numOutputs, int numLights = 0) { | |||||
setup(numParams, numInputs, numOutputs, numLights); | |||||
} | |||||
virtual ~Module() {} | |||||
void setup(int numParams, int numInputs, int numOutputs, int numLights = 0) { | |||||
params.resize(numParams); | |||||
inputs.resize(numInputs); | |||||
outputs.resize(numOutputs); | |||||
lights.resize(numLights); | |||||
} | |||||
/** Advances the module by 1 audio frame with duration 1.0 / gSampleRate | |||||
Override this method to read inputs and params, and to write outputs and lights. | |||||
*/ | |||||
virtual void step() {} | |||||
/** Called when the engine sample rate is changed */ | |||||
virtual void onSampleRateChange() {} | |||||
/** Called when user clicks Initialize in the module context menu */ | |||||
virtual void onReset() {} | |||||
/** Called when user clicks Randomize in the module context menu */ | |||||
virtual void onRandomize() {} | |||||
json_t *toJson(); | |||||
void fromJson(json_t *rootJ); | |||||
/** Override these 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) {} | |||||
}; | |||||
} // namespace rack |
@@ -0,0 +1,18 @@ | |||||
#pragma once | |||||
#include "common.hpp" | |||||
#include "engine/Light.hpp" | |||||
namespace rack { | |||||
struct Output { | |||||
/** Voltage of the port. Write-only by Module */ | |||||
float value = 0.f; | |||||
/** Whether a wire is plugged in */ | |||||
bool active = false; | |||||
Light plugLights[2]; | |||||
}; | |||||
} // namespace rack |
@@ -0,0 +1,38 @@ | |||||
#pragma once | |||||
#include "common.hpp" | |||||
#include <jansson.h> | |||||
namespace rack { | |||||
struct Param { | |||||
float value = 0.f; | |||||
float minValue = 0.f; | |||||
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; | |||||
int displayPrecision = 2; | |||||
std::string label; | |||||
std::string unit; | |||||
void setup(float minValue, float maxValue, float defaultValue, std::string label = "", std::string unit = "", int displayPrecision = 2) { | |||||
this->value = defaultValue; | |||||
this->minValue = minValue; | |||||
this->maxValue = maxValue; | |||||
this->defaultValue = defaultValue; | |||||
this->label = label; | |||||
this->unit = unit; | |||||
this->displayPrecision = displayPrecision; | |||||
} | |||||
json_t *toJson(); | |||||
void fromJson(json_t *rootJ); | |||||
}; | |||||
} // namespace rack |
@@ -0,0 +1,18 @@ | |||||
#pragma once | |||||
#include "common.hpp" | |||||
#include "engine/Module.hpp" | |||||
namespace rack { | |||||
struct Wire { | |||||
Module *outputModule = NULL; | |||||
int outputId; | |||||
Module *inputModule = NULL; | |||||
int inputId; | |||||
void step(); | |||||
}; | |||||
} // namespace rack |
@@ -1,7 +1,7 @@ | |||||
#include "plugin.hpp" | #include "plugin.hpp" | ||||
#include "engine.hpp" | |||||
#include "app.hpp" | #include "app.hpp" | ||||
#include "event.hpp" | #include "event.hpp" | ||||
#include "engine/Module.hpp" | |||||
namespace rack { | namespace rack { | ||||
@@ -8,7 +8,7 @@ | |||||
#include "network.hpp" | #include "network.hpp" | ||||
#include "asset.hpp" | #include "asset.hpp" | ||||
#include "plugin.hpp" | #include "plugin.hpp" | ||||
#include "engine.hpp" | |||||
#include "engine/Engine.hpp" | |||||
#include "window.hpp" | #include "window.hpp" | ||||
#include "widgets.hpp" | #include "widgets.hpp" | ||||
#include "app.hpp" | #include "app.hpp" | ||||
@@ -144,7 +144,7 @@ struct AudioInterface : Module { | |||||
void AudioInterface::step() { | void AudioInterface::step() { | ||||
// Update SRC states | // Update SRC states | ||||
int sampleRate = (int) engineGetSampleRate(); | |||||
int sampleRate = (int) gEngine->getSampleRate(); | |||||
inputSrc.setRates(audioIO.sampleRate, sampleRate); | inputSrc.setRates(audioIO.sampleRate, sampleRate); | ||||
outputSrc.setRates(sampleRate, audioIO.sampleRate); | outputSrc.setRates(sampleRate, audioIO.sampleRate); | ||||
@@ -47,7 +47,7 @@ struct MIDICCToCVInterface : Module { | |||||
processMessage(msg); | processMessage(msg); | ||||
} | } | ||||
float lambda = 100.f * engineGetSampleTime(); | |||||
float lambda = 100.f * gEngine->getSampleTime(); | |||||
for (int i = 0; i < 16; i++) { | for (int i = 0; i < 16; i++) { | ||||
int learnedCc = learnedCcs[i]; | int learnedCc = learnedCcs[i]; | ||||
float value = rescale(clamp(ccs[learnedCc], -127, 127), 0, 127, 0.f, 10.f); | float value = rescale(clamp(ccs[learnedCc], -127, 127), 0, 127, 0.f, 10.f); | ||||
@@ -144,7 +144,7 @@ struct MIDIToCVInterface : Module { | |||||
while (midiInput.shift(&msg)) { | while (midiInput.shift(&msg)) { | ||||
processMessage(msg); | processMessage(msg); | ||||
} | } | ||||
float deltaTime = engineGetSampleTime(); | |||||
float deltaTime = gEngine->getSampleTime(); | |||||
outputs[CV_OUTPUT].value = (lastNote - 60) / 12.f; | outputs[CV_OUTPUT].value = (lastNote - 60) / 12.f; | ||||
outputs[GATE_OUTPUT].value = gate ? 10.f : 0.f; | outputs[GATE_OUTPUT].value = gate ? 10.f : 0.f; | ||||
@@ -1,5 +1,6 @@ | |||||
#include "Core.hpp" | #include "Core.hpp" | ||||
#include "midi.hpp" | #include "midi.hpp" | ||||
#include "engine/Engine.hpp" | |||||
#include "event.hpp" | #include "event.hpp" | ||||
@@ -70,7 +71,7 @@ struct MIDITriggerToCVInterface : Module { | |||||
while (midiInput.shift(&msg)) { | while (midiInput.shift(&msg)) { | ||||
processMessage(msg); | processMessage(msg); | ||||
} | } | ||||
float deltaTime = engineGetSampleTime(); | |||||
float deltaTime = gEngine->getSampleTime(); | |||||
for (int i = 0; i < 16; i++) { | for (int i = 0; i < 16; i++) { | ||||
if (gateTimes[i] > 0.f) { | if (gateTimes[i] > 0.f) { | ||||
@@ -1,5 +1,4 @@ | |||||
#include "app.hpp" | #include "app.hpp" | ||||
#include "engine.hpp" | |||||
namespace rack { | namespace rack { | ||||
@@ -1,4 +1,5 @@ | |||||
#include "osdialog.h" | #include "osdialog.h" | ||||
#include "engine/Engine.hpp" | |||||
#include "rack.hpp" | #include "rack.hpp" | ||||
@@ -7,7 +8,7 @@ namespace rack { | |||||
ModuleWidget::ModuleWidget(Module *module) { | ModuleWidget::ModuleWidget(Module *module) { | ||||
if (module) { | if (module) { | ||||
engineAddModule(module); | |||||
gEngine->addModule(module); | |||||
} | } | ||||
this->module = module; | this->module = module; | ||||
} | } | ||||
@@ -17,7 +18,7 @@ ModuleWidget::~ModuleWidget() { | |||||
disconnect(); | disconnect(); | ||||
// Remove and delete the Module instance | // Remove and delete the Module instance | ||||
if (module) { | if (module) { | ||||
engineRemoveModule(module); | |||||
gEngine->removeModule(module); | |||||
delete module; | delete module; | ||||
module = NULL; | module = NULL; | ||||
} | } | ||||
@@ -229,7 +230,7 @@ void ModuleWidget::reset() { | |||||
param->reset(); | param->reset(); | ||||
} | } | ||||
if (module) { | if (module) { | ||||
engineResetModule(module); | |||||
gEngine->resetModule(module); | |||||
} | } | ||||
} | } | ||||
@@ -238,7 +239,7 @@ void ModuleWidget::randomize() { | |||||
param->randomize(); | param->randomize(); | ||||
} | } | ||||
if (module) { | if (module) { | ||||
engineRandomizeModule(module); | |||||
gEngine->randomizeModule(module); | |||||
} | } | ||||
} | } | ||||
@@ -247,7 +248,7 @@ void ModuleWidget::draw(NVGcontext *vg) { | |||||
Widget::draw(vg); | Widget::draw(vg); | ||||
// Power meter | // Power meter | ||||
if (module && gPowerMeter) { | |||||
if (module && gEngine->powerMeter) { | |||||
nvgBeginPath(vg); | nvgBeginPath(vg); | ||||
nvgRect(vg, | nvgRect(vg, | ||||
0, box.size.y - 20, | 0, box.size.y - 20, | ||||
@@ -1,5 +1,4 @@ | |||||
#include "app/ParamWidget.hpp" | #include "app/ParamWidget.hpp" | ||||
#include "engine.hpp" | |||||
#include "random.hpp" | #include "random.hpp" | ||||
@@ -1,6 +1,5 @@ | |||||
#include "app.hpp" | #include "app.hpp" | ||||
#include "window.hpp" | #include "window.hpp" | ||||
#include "engine.hpp" | |||||
#include "componentlibrary.hpp" | #include "componentlibrary.hpp" | ||||
@@ -1,6 +1,6 @@ | |||||
#include "app.hpp" | #include "app.hpp" | ||||
#include "window.hpp" | #include "window.hpp" | ||||
#include "engine.hpp" | |||||
#include "engine/Engine.hpp" | |||||
#include "asset.hpp" | #include "asset.hpp" | ||||
#include "helpers.hpp" | #include "helpers.hpp" | ||||
@@ -96,21 +96,21 @@ struct PowerMeterButton : TooltipIconButton { | |||||
} | } | ||||
std::string getTooltipText() override {return "Toggle power meter (see manual for explanation)";} | std::string getTooltipText() override {return "Toggle power meter (see manual for explanation)";} | ||||
void onAction(event::Action &e) override { | void onAction(event::Action &e) override { | ||||
gPowerMeter ^= true; | |||||
gEngine->powerMeter ^= true; | |||||
} | } | ||||
}; | }; | ||||
struct EnginePauseItem : MenuItem { | struct EnginePauseItem : MenuItem { | ||||
void onAction(event::Action &e) override { | void onAction(event::Action &e) override { | ||||
gPaused ^= true; | |||||
gEngine->paused ^= true; | |||||
} | } | ||||
}; | }; | ||||
struct SampleRateItem : MenuItem { | struct SampleRateItem : MenuItem { | ||||
float sampleRate; | float sampleRate; | ||||
void onAction(event::Action &e) override { | void onAction(event::Action &e) override { | ||||
engineSetSampleRate(sampleRate); | |||||
gPaused = false; | |||||
gEngine->setSampleRate(sampleRate); | |||||
gEngine->paused = false; | |||||
} | } | ||||
}; | }; | ||||
@@ -127,14 +127,14 @@ struct SampleRateButton : TooltipIconButton { | |||||
menu->addChild(createMenuLabel("Engine sample rate")); | menu->addChild(createMenuLabel("Engine sample rate")); | ||||
EnginePauseItem *pauseItem = new EnginePauseItem; | EnginePauseItem *pauseItem = new EnginePauseItem; | ||||
pauseItem->text = gPaused ? "Resume engine" : "Pause engine"; | |||||
pauseItem->text = gEngine->paused ? "Resume engine" : "Pause engine"; | |||||
menu->addChild(pauseItem); | menu->addChild(pauseItem); | ||||
std::vector<float> sampleRates = {44100, 48000, 88200, 96000, 176400, 192000}; | std::vector<float> sampleRates = {44100, 48000, 88200, 96000, 176400, 192000}; | ||||
for (float sampleRate : sampleRates) { | for (float sampleRate : sampleRates) { | ||||
SampleRateItem *item = new SampleRateItem; | SampleRateItem *item = new SampleRateItem; | ||||
item->text = string::f("%.0f Hz", sampleRate); | item->text = string::f("%.0f Hz", sampleRate); | ||||
item->rightText = CHECKMARK(engineGetSampleRate() == sampleRate); | |||||
item->rightText = CHECKMARK(gEngine->getSampleRate() == sampleRate); | |||||
item->sampleRate = sampleRate; | item->sampleRate = sampleRate; | ||||
menu->addChild(item); | menu->addChild(item); | ||||
} | } | ||||
@@ -1,5 +1,5 @@ | |||||
#include "app.hpp" | #include "app.hpp" | ||||
#include "engine.hpp" | |||||
#include "engine/Engine.hpp" | |||||
#include "componentlibrary.hpp" | #include "componentlibrary.hpp" | ||||
#include "window.hpp" | #include "window.hpp" | ||||
#include "event.hpp" | #include "event.hpp" | ||||
@@ -107,12 +107,12 @@ void WireWidget::updateWire() { | |||||
wire->outputId = outputPort->portId; | wire->outputId = outputPort->portId; | ||||
wire->inputModule = inputPort->module; | wire->inputModule = inputPort->module; | ||||
wire->inputId = inputPort->portId; | wire->inputId = inputPort->portId; | ||||
engineAddWire(wire); | |||||
gEngine->addWire(wire); | |||||
} | } | ||||
} | } | ||||
else { | else { | ||||
if (wire) { | if (wire) { | ||||
engineRemoveWire(wire); | |||||
gEngine->removeWire(wire); | |||||
delete wire; | delete wire; | ||||
wire = NULL; | wire = NULL; | ||||
} | } | ||||
@@ -1,411 +0,0 @@ | |||||
#include <vector> | |||||
#include <algorithm> | |||||
#include <chrono> | |||||
#include <thread> | |||||
#include <condition_variable> | |||||
#include <mutex> | |||||
#include <xmmintrin.h> | |||||
#include <pmmintrin.h> | |||||
#include "rack.hpp" | |||||
#include "engine.hpp" | |||||
namespace rack { | |||||
bool gPaused = false; | |||||
std::vector<Module*> gModules; | |||||
std::vector<Wire*> gWires; | |||||
bool gPowerMeter = false; | |||||
static bool running = false; | |||||
static float sampleRate = 44100.f; | |||||
static float sampleTime = 1.f / sampleRate; | |||||
static float sampleRateRequested = sampleRate; | |||||
static Module *resetModule = NULL; | |||||
static Module *randomizeModule = NULL; | |||||
/** Threads which obtain a VIPLock will cause wait() to block for other less important threads. | |||||
This does not provide the VIPs with an exclusive lock. That should be left up to another mutex shared between the less important thread. | |||||
*/ | |||||
struct VIPMutex { | |||||
int count = 0; | |||||
std::condition_variable cv; | |||||
std::mutex countMutex; | |||||
/** Blocks until there are no remaining VIPLocks */ | |||||
void wait() { | |||||
std::unique_lock<std::mutex> lock(countMutex); | |||||
while (count > 0) | |||||
cv.wait(lock); | |||||
} | |||||
}; | |||||
struct VIPLock { | |||||
VIPMutex &m; | |||||
VIPLock(VIPMutex &m) : m(m) { | |||||
std::unique_lock<std::mutex> lock(m.countMutex); | |||||
m.count++; | |||||
} | |||||
~VIPLock() { | |||||
std::unique_lock<std::mutex> lock(m.countMutex); | |||||
m.count--; | |||||
lock.unlock(); | |||||
m.cv.notify_all(); | |||||
} | |||||
}; | |||||
static std::mutex mutex; | |||||
static std::thread thread; | |||||
static VIPMutex vipMutex; | |||||
// Parameter interpolation | |||||
static Module *smoothModule = NULL; | |||||
static int smoothParamId; | |||||
static float smoothValue; | |||||
json_t *Param::toJson() { | |||||
json_t *rootJ = json_object(); | |||||
// Infinite params should serialize to 0 | |||||
float v = (std::isfinite(minValue) && std::isfinite(maxValue)) ? value : 0.f; | |||||
json_object_set_new(rootJ, "value", json_real(v)); | |||||
return rootJ; | |||||
} | |||||
void Param::fromJson(json_t *rootJ) { | |||||
json_t *valueJ = json_object_get(rootJ, "value"); | |||||
if (valueJ) | |||||
value = json_number_value(valueJ); | |||||
} | |||||
float Light::getBrightness() { | |||||
// LEDs are diodes, so don't allow reverse current. | |||||
// For some reason, instead of the RMS, the sqrt of RMS looks better | |||||
return std::pow(std::fmaxf(0.f, value), 0.25f); | |||||
} | |||||
void Light::setBrightnessSmooth(float brightness, float frames) { | |||||
float v = (brightness > 0.f) ? brightness * brightness : 0.f; | |||||
if (v < value) { | |||||
// Fade out light with lambda = framerate | |||||
value += (v - value) * sampleTime * frames * 60.f; | |||||
} | |||||
else { | |||||
// Immediately illuminate light | |||||
value = v; | |||||
} | |||||
} | |||||
json_t *Module::toJson() { | |||||
json_t *rootJ = json_object(); | |||||
// params | |||||
json_t *paramsJ = json_array(); | |||||
for (Param ¶m : params) { | |||||
json_t *paramJ = param.toJson(); | |||||
json_array_append_new(paramsJ, paramJ); | |||||
} | |||||
json_object_set_new(rootJ, "params", paramsJ); | |||||
// data | |||||
json_t *dataJ = dataToJson(); | |||||
if (dataJ) { | |||||
json_object_set_new(rootJ, "data", dataJ); | |||||
} | |||||
return rootJ; | |||||
} | |||||
void Module::fromJson(json_t *rootJ) { | |||||
// params | |||||
json_t *paramsJ = json_object_get(rootJ, "params"); | |||||
size_t i; | |||||
json_t *paramJ; | |||||
json_array_foreach(paramsJ, i, paramJ) { | |||||
uint32_t paramId = i; | |||||
// Get paramId | |||||
json_t *paramIdJ = json_object_get(paramJ, "paramId"); | |||||
if (paramIdJ) { | |||||
// Legacy v0.6.0 to <v1.0 | |||||
paramId = json_integer_value(paramIdJ); | |||||
} | |||||
if (paramId < params.size()) { | |||||
params[paramId].fromJson(paramJ); | |||||
} | |||||
} | |||||
// data | |||||
json_t *dataJ = json_object_get(rootJ, "data"); | |||||
if (dataJ) { | |||||
dataFromJson(dataJ); | |||||
} | |||||
} | |||||
void Wire::step() { | |||||
float value = outputModule->outputs[outputId].value; | |||||
inputModule->inputs[inputId].value = value; | |||||
} | |||||
void engineInit() { | |||||
} | |||||
void engineDestroy() { | |||||
// Make sure there are no wires or modules in the rack on destruction. This suggests that a module failed to remove itself before the RackWidget was destroyed. | |||||
assert(gWires.empty()); | |||||
assert(gModules.empty()); | |||||
} | |||||
static void engineStep() { | |||||
// Sample rate | |||||
if (sampleRateRequested != sampleRate) { | |||||
sampleRate = sampleRateRequested; | |||||
sampleTime = 1.f / sampleRate; | |||||
for (Module *module : gModules) { | |||||
module->onSampleRateChange(); | |||||
} | |||||
} | |||||
// Events | |||||
if (resetModule) { | |||||
resetModule->onReset(); | |||||
resetModule = NULL; | |||||
} | |||||
if (randomizeModule) { | |||||
randomizeModule->onRandomize(); | |||||
randomizeModule = NULL; | |||||
} | |||||
// Param smoothing | |||||
{ | |||||
Module *localSmoothModule = smoothModule; | |||||
int localSmoothParamId = smoothParamId; | |||||
float localSmoothValue = smoothValue; | |||||
if (localSmoothModule) { | |||||
float value = localSmoothModule->params[localSmoothParamId].value; | |||||
const float lambda = 60.0; // decay rate is 1 graphics frame | |||||
float delta = localSmoothValue - value; | |||||
float newValue = value + delta * lambda * sampleTime; | |||||
if (value == newValue) { | |||||
// Snap to actual smooth value if the value doesn't change enough (due to the granularity of floats) | |||||
localSmoothModule->params[localSmoothParamId].value = localSmoothValue; | |||||
smoothModule = NULL; | |||||
} | |||||
else { | |||||
localSmoothModule->params[localSmoothParamId].value = newValue; | |||||
} | |||||
} | |||||
} | |||||
// Step modules | |||||
for (Module *module : gModules) { | |||||
std::chrono::high_resolution_clock::time_point startTime; | |||||
if (gPowerMeter) { | |||||
startTime = std::chrono::high_resolution_clock::now(); | |||||
module->step(); | |||||
auto stopTime = std::chrono::high_resolution_clock::now(); | |||||
float cpuTime = std::chrono::duration<float>(stopTime - startTime).count() * sampleRate; | |||||
module->cpuTime += (cpuTime - module->cpuTime) * sampleTime / 0.5f; | |||||
} | |||||
else { | |||||
module->step(); | |||||
} | |||||
// Step ports | |||||
for (Input &input : module->inputs) { | |||||
if (input.active) { | |||||
float value = input.value / 5.f; | |||||
input.plugLights[0].setBrightnessSmooth(value); | |||||
input.plugLights[1].setBrightnessSmooth(-value); | |||||
} | |||||
} | |||||
for (Output &output : module->outputs) { | |||||
if (output.active) { | |||||
float value = output.value / 5.f; | |||||
output.plugLights[0].setBrightnessSmooth(value); | |||||
output.plugLights[1].setBrightnessSmooth(-value); | |||||
} | |||||
} | |||||
} | |||||
// Step cables by moving their output values to inputs | |||||
for (Wire *wire : gWires) { | |||||
wire->step(); | |||||
} | |||||
} | |||||
static void engineRun() { | |||||
// Set CPU to flush-to-zero (FTZ) and denormals-are-zero (DAZ) mode | |||||
// https://software.intel.com/en-us/node/682949 | |||||
_MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON); | |||||
_MM_SET_DENORMALS_ZERO_MODE(_MM_DENORMALS_ZERO_ON); | |||||
// Every time the engine waits and locks a mutex, it steps this many frames | |||||
const int mutexSteps = 64; | |||||
// Time in seconds that the engine is rushing ahead of the estimated clock time | |||||
double ahead = 0.0; | |||||
auto lastTime = std::chrono::high_resolution_clock::now(); | |||||
while (running) { | |||||
vipMutex.wait(); | |||||
if (!gPaused) { | |||||
std::lock_guard<std::mutex> lock(mutex); | |||||
for (int i = 0; i < mutexSteps; i++) { | |||||
engineStep(); | |||||
} | |||||
} | |||||
double stepTime = mutexSteps * sampleTime; | |||||
ahead += stepTime; | |||||
auto currTime = std::chrono::high_resolution_clock::now(); | |||||
const double aheadFactor = 2.0; | |||||
ahead -= aheadFactor * std::chrono::duration<double>(currTime - lastTime).count(); | |||||
lastTime = currTime; | |||||
ahead = fmaxf(ahead, 0.0); | |||||
// Avoid pegging the CPU at 100% when there are no "blocking" modules like AudioInterface, but still step audio at a reasonable rate | |||||
// The number of steps to wait before possibly sleeping | |||||
const double aheadMax = 1.0; // seconds | |||||
if (ahead > aheadMax) { | |||||
std::this_thread::sleep_for(std::chrono::duration<double>(stepTime)); | |||||
} | |||||
} | |||||
} | |||||
void engineStart() { | |||||
running = true; | |||||
thread = std::thread(engineRun); | |||||
} | |||||
void engineStop() { | |||||
running = false; | |||||
thread.join(); | |||||
} | |||||
void engineAddModule(Module *module) { | |||||
assert(module); | |||||
VIPLock vipLock(vipMutex); | |||||
std::lock_guard<std::mutex> lock(mutex); | |||||
// Check that the module is not already added | |||||
auto it = std::find(gModules.begin(), gModules.end(), module); | |||||
assert(it == gModules.end()); | |||||
gModules.push_back(module); | |||||
} | |||||
void engineRemoveModule(Module *module) { | |||||
assert(module); | |||||
VIPLock vipLock(vipMutex); | |||||
std::lock_guard<std::mutex> lock(mutex); | |||||
// If a param is being smoothed on this module, stop smoothing it immediately | |||||
if (module == smoothModule) { | |||||
smoothModule = NULL; | |||||
} | |||||
// Check that all wires are disconnected | |||||
for (Wire *wire : gWires) { | |||||
assert(wire->outputModule != module); | |||||
assert(wire->inputModule != module); | |||||
} | |||||
// Check that the module actually exists | |||||
auto it = std::find(gModules.begin(), gModules.end(), module); | |||||
assert(it != gModules.end()); | |||||
// Remove it | |||||
gModules.erase(it); | |||||
} | |||||
void engineResetModule(Module *module) { | |||||
resetModule = module; | |||||
} | |||||
void engineRandomizeModule(Module *module) { | |||||
randomizeModule = module; | |||||
} | |||||
static void updateActive() { | |||||
// Set everything to inactive | |||||
for (Module *module : gModules) { | |||||
for (Input &input : module->inputs) { | |||||
input.active = false; | |||||
} | |||||
for (Output &output : module->outputs) { | |||||
output.active = false; | |||||
} | |||||
} | |||||
// Set inputs/outputs to active | |||||
for (Wire *wire : gWires) { | |||||
wire->outputModule->outputs[wire->outputId].active = true; | |||||
wire->inputModule->inputs[wire->inputId].active = true; | |||||
} | |||||
} | |||||
void engineAddWire(Wire *wire) { | |||||
assert(wire); | |||||
VIPLock vipLock(vipMutex); | |||||
std::lock_guard<std::mutex> lock(mutex); | |||||
// Check wire properties | |||||
assert(wire->outputModule); | |||||
assert(wire->inputModule); | |||||
// Check that the wire is not already added, and that the input is not already used by another cable | |||||
for (Wire *wire2 : gWires) { | |||||
assert(wire2 != wire); | |||||
assert(!(wire2->inputModule == wire->inputModule && wire2->inputId == wire->inputId)); | |||||
} | |||||
// Add the wire | |||||
gWires.push_back(wire); | |||||
updateActive(); | |||||
} | |||||
void engineRemoveWire(Wire *wire) { | |||||
assert(wire); | |||||
VIPLock vipLock(vipMutex); | |||||
std::lock_guard<std::mutex> lock(mutex); | |||||
// Check that the wire is already added | |||||
auto it = std::find(gWires.begin(), gWires.end(), wire); | |||||
assert(it != gWires.end()); | |||||
// Set input to 0V | |||||
wire->inputModule->inputs[wire->inputId].value = 0.0; | |||||
// Remove the wire | |||||
gWires.erase(it); | |||||
updateActive(); | |||||
} | |||||
void engineSetParam(Module *module, int paramId, float value) { | |||||
module->params[paramId].value = value; | |||||
} | |||||
void engineSetParamSmooth(Module *module, int paramId, float value) { | |||||
// If another param is being smoothed, jump value | |||||
if (smoothModule && !(smoothModule == module && smoothParamId == paramId)) { | |||||
smoothModule->params[smoothParamId].value = smoothValue; | |||||
} | |||||
smoothParamId = paramId; | |||||
smoothValue = value; | |||||
smoothModule = module; | |||||
} | |||||
void engineSetSampleRate(float newSampleRate) { | |||||
sampleRateRequested = newSampleRate; | |||||
} | |||||
float engineGetSampleRate() { | |||||
return sampleRate; | |||||
} | |||||
float engineGetSampleTime() { | |||||
return sampleTime; | |||||
} | |||||
} // namespace rack |
@@ -0,0 +1,332 @@ | |||||
#include <algorithm> | |||||
#include <chrono> | |||||
#include <thread> | |||||
#include <condition_variable> | |||||
#include <mutex> | |||||
#include <xmmintrin.h> | |||||
#include <pmmintrin.h> | |||||
#include "engine/Engine.hpp" | |||||
namespace rack { | |||||
/** Threads which obtain a VIPLock will cause wait() to block for other less important threads. | |||||
This does not provide the VIPs with an exclusive lock. That should be left up to another mutex shared between the less important thread. | |||||
*/ | |||||
struct VIPMutex { | |||||
int count = 0; | |||||
std::condition_variable cv; | |||||
std::mutex countMutex; | |||||
/** Blocks until there are no remaining VIPLocks */ | |||||
void wait() { | |||||
std::unique_lock<std::mutex> lock(countMutex); | |||||
while (count > 0) | |||||
cv.wait(lock); | |||||
} | |||||
}; | |||||
struct VIPLock { | |||||
VIPMutex &m; | |||||
VIPLock(VIPMutex &m) : m(m) { | |||||
std::unique_lock<std::mutex> lock(m.countMutex); | |||||
m.count++; | |||||
} | |||||
~VIPLock() { | |||||
std::unique_lock<std::mutex> lock(m.countMutex); | |||||
m.count--; | |||||
lock.unlock(); | |||||
m.cv.notify_all(); | |||||
} | |||||
}; | |||||
struct Engine::Internal { | |||||
bool running = false; | |||||
float sampleRate; | |||||
float sampleTime; | |||||
float sampleRateRequested; | |||||
Module *resetModule = NULL; | |||||
Module *randomizeModule = NULL; | |||||
// Parameter smoothing | |||||
Module *smoothModule = NULL; | |||||
int smoothParamId; | |||||
float smoothValue; | |||||
std::mutex mutex; | |||||
std::thread thread; | |||||
VIPMutex vipMutex; | |||||
}; | |||||
Engine::Engine() { | |||||
internal = new Internal; | |||||
float sampleRate = 44100.f; | |||||
internal->sampleRate = sampleRate; | |||||
internal->sampleTime = 1 / sampleRate; | |||||
internal->sampleRateRequested = sampleRate; | |||||
} | |||||
Engine::~Engine() { | |||||
// Make sure there are no wires or modules in the rack on destruction. This suggests that a module failed to remove itself before the RackWidget was destroyed. | |||||
assert(wires.empty()); | |||||
assert(modules.empty()); | |||||
delete internal; | |||||
} | |||||
static void Engine_step(Engine *engine) { | |||||
// Sample rate | |||||
if (engine->internal->sampleRateRequested != engine->internal->sampleRate) { | |||||
engine->internal->sampleRate = engine->internal->sampleRateRequested; | |||||
engine->internal->sampleTime = 1 / engine->internal->sampleRate; | |||||
for (Module *module : engine->modules) { | |||||
module->onSampleRateChange(); | |||||
} | |||||
} | |||||
// Events | |||||
if (engine->internal->resetModule) { | |||||
engine->internal->resetModule->onReset(); | |||||
engine->internal->resetModule = NULL; | |||||
} | |||||
if (engine->internal->randomizeModule) { | |||||
engine->internal->randomizeModule->onRandomize(); | |||||
engine->internal->randomizeModule = NULL; | |||||
} | |||||
// Param smoothing | |||||
{ | |||||
// Store in local variables for thread safety | |||||
Module *localSmoothModule = engine->internal->smoothModule; | |||||
int localSmoothParamId = engine->internal->smoothParamId; | |||||
float localSmoothValue = engine->internal->smoothValue; | |||||
if (localSmoothModule) { | |||||
float value = localSmoothModule->params[localSmoothParamId].value; | |||||
// decay rate is 1 graphics frame | |||||
const float lambda = 60.f; | |||||
float delta = localSmoothValue - value; | |||||
float newValue = value + delta * lambda * engine->internal->sampleTime; | |||||
if (value == newValue) { | |||||
// Snap to actual smooth value if the value doesn't change enough (due to the granularity of floats) | |||||
localSmoothModule->params[localSmoothParamId].value = localSmoothValue; | |||||
engine->internal->smoothModule = NULL; | |||||
} | |||||
else { | |||||
localSmoothModule->params[localSmoothParamId].value = newValue; | |||||
} | |||||
} | |||||
} | |||||
// Step modules | |||||
for (Module *module : engine->modules) { | |||||
if (engine->powerMeter) { | |||||
auto startTime = std::chrono::high_resolution_clock::now(); | |||||
module->step(); | |||||
auto stopTime = std::chrono::high_resolution_clock::now(); | |||||
float cpuTime = std::chrono::duration<float>(stopTime - startTime).count() * engine->internal->sampleRate; | |||||
// Smooth cpu time | |||||
module->cpuTime += (cpuTime - module->cpuTime) * engine->internal->sampleTime / 0.5f; | |||||
} | |||||
else { | |||||
module->step(); | |||||
} | |||||
// Step ports | |||||
for (Input &input : module->inputs) { | |||||
if (input.active) { | |||||
float value = input.value / 5.f; | |||||
input.plugLights[0].setBrightnessSmooth(value); | |||||
input.plugLights[1].setBrightnessSmooth(-value); | |||||
} | |||||
} | |||||
for (Output &output : module->outputs) { | |||||
if (output.active) { | |||||
float value = output.value / 5.f; | |||||
output.plugLights[0].setBrightnessSmooth(value); | |||||
output.plugLights[1].setBrightnessSmooth(-value); | |||||
} | |||||
} | |||||
} | |||||
// Step cables by moving their output values to inputs | |||||
for (Wire *wire : engine->wires) { | |||||
wire->step(); | |||||
} | |||||
} | |||||
static void Engine_run(Engine *engine) { | |||||
// Set CPU to flush-to-zero (FTZ) and denormals-are-zero (DAZ) mode | |||||
// https://software.intel.com/en-us/node/682949 | |||||
_MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON); | |||||
_MM_SET_DENORMALS_ZERO_MODE(_MM_DENORMALS_ZERO_ON); | |||||
// Every time the engine waits and locks a mutex, it steps this many frames | |||||
const int mutexSteps = 64; | |||||
// Time in seconds that the engine is rushing ahead of the estimated clock time | |||||
double ahead = 0.0; | |||||
auto lastTime = std::chrono::high_resolution_clock::now(); | |||||
while (engine->internal->running) { | |||||
engine->internal->vipMutex.wait(); | |||||
if (!engine->paused) { | |||||
std::lock_guard<std::mutex> lock(engine->internal->mutex); | |||||
for (int i = 0; i < mutexSteps; i++) { | |||||
Engine_step(engine); | |||||
} | |||||
} | |||||
double stepTime = mutexSteps * engine->internal->sampleTime; | |||||
ahead += stepTime; | |||||
auto currTime = std::chrono::high_resolution_clock::now(); | |||||
const double aheadFactor = 2.0; | |||||
ahead -= aheadFactor * std::chrono::duration<double>(currTime - lastTime).count(); | |||||
lastTime = currTime; | |||||
ahead = std::fmax(ahead, 0.0); | |||||
// Avoid pegging the CPU at 100% when there are no "blocking" modules like AudioInterface, but still step audio at a reasonable rate | |||||
// The number of steps to wait before possibly sleeping | |||||
const double aheadMax = 1.0; // seconds | |||||
if (ahead > aheadMax) { | |||||
std::this_thread::sleep_for(std::chrono::duration<double>(stepTime)); | |||||
} | |||||
} | |||||
} | |||||
void Engine::start() { | |||||
internal->running = true; | |||||
internal->thread = std::thread(Engine_run, this); | |||||
} | |||||
void Engine::stop() { | |||||
internal->running = false; | |||||
internal->thread.join(); | |||||
} | |||||
void Engine::addModule(Module *module) { | |||||
assert(module); | |||||
VIPLock vipLock(internal->vipMutex); | |||||
std::lock_guard<std::mutex> lock(internal->mutex); | |||||
// Check that the module is not already added | |||||
auto it = std::find(modules.begin(), modules.end(), module); | |||||
assert(it == modules.end()); | |||||
modules.push_back(module); | |||||
} | |||||
void Engine::removeModule(Module *module) { | |||||
assert(module); | |||||
VIPLock vipLock(internal->vipMutex); | |||||
std::lock_guard<std::mutex> lock(internal->mutex); | |||||
// If a param is being smoothed on this module, stop smoothing it immediately | |||||
if (module == internal->smoothModule) { | |||||
internal->smoothModule = NULL; | |||||
} | |||||
// Check that all wires are disconnected | |||||
for (Wire *wire : wires) { | |||||
assert(wire->outputModule != module); | |||||
assert(wire->inputModule != module); | |||||
} | |||||
// Check that the module actually exists | |||||
auto it = std::find(modules.begin(), modules.end(), module); | |||||
assert(it != modules.end()); | |||||
// Remove it | |||||
modules.erase(it); | |||||
} | |||||
void Engine::resetModule(Module *module) { | |||||
internal->resetModule = module; | |||||
} | |||||
void Engine::randomizeModule(Module *module) { | |||||
internal->randomizeModule = module; | |||||
} | |||||
static void Engine_updateActive(Engine *engine) { | |||||
// Set everything to inactive | |||||
for (Module *module : engine->modules) { | |||||
for (Input &input : module->inputs) { | |||||
input.active = false; | |||||
} | |||||
for (Output &output : module->outputs) { | |||||
output.active = false; | |||||
} | |||||
} | |||||
// Set inputs/outputs to active | |||||
for (Wire *wire : engine->wires) { | |||||
wire->outputModule->outputs[wire->outputId].active = true; | |||||
wire->inputModule->inputs[wire->inputId].active = true; | |||||
} | |||||
} | |||||
void Engine::addWire(Wire *wire) { | |||||
assert(wire); | |||||
VIPLock vipLock(internal->vipMutex); | |||||
std::lock_guard<std::mutex> lock(internal->mutex); | |||||
// Check wire properties | |||||
assert(wire->outputModule); | |||||
assert(wire->inputModule); | |||||
// Check that the wire is not already added, and that the input is not already used by another cable | |||||
for (Wire *wire2 : wires) { | |||||
assert(wire2 != wire); | |||||
assert(!(wire2->inputModule == wire->inputModule && wire2->inputId == wire->inputId)); | |||||
} | |||||
// Add the wire | |||||
wires.push_back(wire); | |||||
Engine_updateActive(this); | |||||
} | |||||
void Engine::removeWire(Wire *wire) { | |||||
assert(wire); | |||||
VIPLock vipLock(internal->vipMutex); | |||||
std::lock_guard<std::mutex> lock(internal->mutex); | |||||
// Check that the wire is already added | |||||
auto it = std::find(wires.begin(), wires.end(), wire); | |||||
assert(it != wires.end()); | |||||
// Set input to 0V | |||||
wire->inputModule->inputs[wire->inputId].value = 0.f; | |||||
// Remove the wire | |||||
wires.erase(it); | |||||
Engine_updateActive(this); | |||||
} | |||||
void Engine::setParam(Module *module, int paramId, float value) { | |||||
// TODO Make thread safe | |||||
module->params[paramId].value = value; | |||||
} | |||||
void Engine::setParamSmooth(Module *module, int paramId, float value) { | |||||
// If another param is being smoothed, jump value | |||||
if (internal->smoothModule && !(internal->smoothModule == module && internal->smoothParamId == paramId)) { | |||||
internal->smoothModule->params[internal->smoothParamId].value = internal->smoothValue; | |||||
} | |||||
internal->smoothParamId = paramId; | |||||
internal->smoothValue = value; | |||||
internal->smoothModule = module; | |||||
} | |||||
void Engine::setSampleRate(float newSampleRate) { | |||||
internal->sampleRateRequested = newSampleRate; | |||||
} | |||||
float Engine::getSampleRate() { | |||||
return internal->sampleRate; | |||||
} | |||||
float Engine::getSampleTime() { | |||||
return internal->sampleTime; | |||||
} | |||||
Engine *gEngine = NULL; | |||||
} // namespace rack |
@@ -0,0 +1,27 @@ | |||||
#include "engine/Light.hpp" | |||||
#include "engine/Engine.hpp" | |||||
namespace rack { | |||||
float Light::getBrightness() { | |||||
// LEDs are diodes, so don't allow reverse current. | |||||
// For some reason, instead of the RMS, the sqrt of RMS looks better | |||||
return std::pow(std::fmax(0.f, value), 0.25f); | |||||
} | |||||
void Light::setBrightnessSmooth(float brightness, float frames) { | |||||
float v = (brightness > 0.f) ? std::pow(brightness, 2) : 0.f; | |||||
if (v < value) { | |||||
// Fade out light with lambda = framerate | |||||
value += (v - value) * gEngine->getSampleTime() * frames * 60.f; | |||||
} | |||||
else { | |||||
// Immediately illuminate light | |||||
value = v; | |||||
} | |||||
} | |||||
} // namespace rack |
@@ -0,0 +1,54 @@ | |||||
#include "engine/Module.hpp" | |||||
namespace rack { | |||||
json_t *Module::toJson() { | |||||
json_t *rootJ = json_object(); | |||||
// params | |||||
json_t *paramsJ = json_array(); | |||||
for (Param ¶m : params) { | |||||
json_t *paramJ = param.toJson(); | |||||
json_array_append_new(paramsJ, paramJ); | |||||
} | |||||
json_object_set_new(rootJ, "params", paramsJ); | |||||
// data | |||||
json_t *dataJ = dataToJson(); | |||||
if (dataJ) { | |||||
json_object_set_new(rootJ, "data", dataJ); | |||||
} | |||||
return rootJ; | |||||
} | |||||
void Module::fromJson(json_t *rootJ) { | |||||
// params | |||||
json_t *paramsJ = json_object_get(rootJ, "params"); | |||||
size_t i; | |||||
json_t *paramJ; | |||||
json_array_foreach(paramsJ, i, paramJ) { | |||||
uint32_t paramId = i; | |||||
// Get paramId | |||||
json_t *paramIdJ = json_object_get(paramJ, "paramId"); | |||||
if (paramIdJ) { | |||||
// Legacy v0.6.0 to <v1.0 | |||||
paramId = json_integer_value(paramIdJ); | |||||
} | |||||
if (paramId < params.size()) { | |||||
params[paramId].fromJson(paramJ); | |||||
} | |||||
} | |||||
// data | |||||
json_t *dataJ = json_object_get(rootJ, "data"); | |||||
if (dataJ) { | |||||
dataFromJson(dataJ); | |||||
} | |||||
} | |||||
} // namespace rack |
@@ -0,0 +1,24 @@ | |||||
#include "engine/Param.hpp" | |||||
namespace rack { | |||||
json_t *Param::toJson() { | |||||
json_t *rootJ = json_object(); | |||||
// Infinite params should serialize to 0 | |||||
float v = (std::isfinite(minValue) && std::isfinite(maxValue)) ? value : 0.f; | |||||
json_object_set_new(rootJ, "value", json_real(v)); | |||||
return rootJ; | |||||
} | |||||
void Param::fromJson(json_t *rootJ) { | |||||
json_t *valueJ = json_object_get(rootJ, "value"); | |||||
if (valueJ) | |||||
value = json_number_value(valueJ); | |||||
} | |||||
} // namespace rack |
@@ -0,0 +1,14 @@ | |||||
#include "engine/Wire.hpp" | |||||
namespace rack { | |||||
void Wire::step() { | |||||
// Copy output to input | |||||
float value = outputModule->outputs[outputId].value; | |||||
inputModule->inputs[inputId].value = value; | |||||
} | |||||
} // namespace rack |
@@ -6,6 +6,7 @@ | |||||
#include "gamepad.hpp" | #include "gamepad.hpp" | ||||
#include "bridge.hpp" | #include "bridge.hpp" | ||||
#include "settings.hpp" | #include "settings.hpp" | ||||
#include "engine/Engine.hpp" | |||||
#ifdef ARCH_WIN | #ifdef ARCH_WIN | ||||
#include <Windows.h> | #include <Windows.h> | ||||
@@ -64,7 +65,7 @@ int main(int argc, char* argv[]) { | |||||
// Initialize app | // Initialize app | ||||
pluginInit(devMode); | pluginInit(devMode); | ||||
engineInit(); | |||||
gEngine = new Engine; | |||||
rtmidiInit(); | rtmidiInit(); | ||||
bridgeInit(); | bridgeInit(); | ||||
keyboard::init(); | keyboard::init(); | ||||
@@ -95,9 +96,9 @@ int main(int argc, char* argv[]) { | |||||
gRackWidget->lastPath = patchFile; | gRackWidget->lastPath = patchFile; | ||||
} | } | ||||
engineStart(); | |||||
gEngine->start(); | |||||
windowRun(); | windowRun(); | ||||
engineStop(); | |||||
gEngine->stop(); | |||||
// Destroy namespaces | // Destroy namespaces | ||||
gRackWidget->save(asset::local("autosave.vcv")); | gRackWidget->save(asset::local("autosave.vcv")); | ||||
@@ -105,7 +106,7 @@ int main(int argc, char* argv[]) { | |||||
appDestroy(); | appDestroy(); | ||||
windowDestroy(); | windowDestroy(); | ||||
bridgeDestroy(); | bridgeDestroy(); | ||||
engineDestroy(); | |||||
delete gEngine; | |||||
midiDestroy(); | midiDestroy(); | ||||
pluginDestroy(); | pluginDestroy(); | ||||
logger::destroy(); | logger::destroy(); | ||||
@@ -1,6 +1,10 @@ | |||||
#include <jansson.h> | |||||
#include "rack.hpp" | |||||
#include "settings.hpp" | #include "settings.hpp" | ||||
#include "logger.hpp" | |||||
#include "window.hpp" | |||||
#include "plugin.hpp" | |||||
#include "app.hpp" | |||||
#include "engine/Engine.hpp" | |||||
#include <jansson.h> | |||||
namespace rack { | namespace rack { | ||||
@@ -50,7 +54,7 @@ static json_t *settingsToJson() { | |||||
json_object_set_new(rootJ, "allowCursorLock", allowCursorLockJ); | json_object_set_new(rootJ, "allowCursorLock", allowCursorLockJ); | ||||
// sampleRate | // sampleRate | ||||
json_t *sampleRateJ = json_real(engineGetSampleRate()); | |||||
json_t *sampleRateJ = json_real(gEngine->getSampleRate()); | |||||
json_object_set_new(rootJ, "sampleRate", sampleRateJ); | json_object_set_new(rootJ, "sampleRate", sampleRateJ); | ||||
// lastPath | // lastPath | ||||
@@ -66,7 +70,7 @@ static json_t *settingsToJson() { | |||||
json_object_set_new(rootJ, "moduleBrowser", appModuleBrowserToJson()); | json_object_set_new(rootJ, "moduleBrowser", appModuleBrowserToJson()); | ||||
// powerMeter | // powerMeter | ||||
json_object_set_new(rootJ, "powerMeter", json_boolean(gPowerMeter)); | |||||
json_object_set_new(rootJ, "powerMeter", json_boolean(gEngine->powerMeter)); | |||||
// checkVersion | // checkVersion | ||||
json_object_set_new(rootJ, "checkVersion", json_boolean(gCheckVersion)); | json_object_set_new(rootJ, "checkVersion", json_boolean(gCheckVersion)); | ||||
@@ -121,7 +125,7 @@ static void settingsFromJson(json_t *rootJ) { | |||||
json_t *sampleRateJ = json_object_get(rootJ, "sampleRate"); | json_t *sampleRateJ = json_object_get(rootJ, "sampleRate"); | ||||
if (sampleRateJ) { | if (sampleRateJ) { | ||||
float sampleRate = json_number_value(sampleRateJ); | float sampleRate = json_number_value(sampleRateJ); | ||||
engineSetSampleRate(sampleRate); | |||||
gEngine->setSampleRate(sampleRate); | |||||
} | } | ||||
// lastPath | // lastPath | ||||
@@ -142,7 +146,7 @@ static void settingsFromJson(json_t *rootJ) { | |||||
// powerMeter | // powerMeter | ||||
json_t *powerMeterJ = json_object_get(rootJ, "powerMeter"); | json_t *powerMeterJ = json_object_get(rootJ, "powerMeter"); | ||||
if (powerMeterJ) | if (powerMeterJ) | ||||
gPowerMeter = json_boolean_value(powerMeterJ); | |||||
gEngine->powerMeter = json_boolean_value(powerMeterJ); | |||||
// checkVersion | // checkVersion | ||||
json_t *checkVersionJ = json_object_get(rootJ, "checkVersion"); | json_t *checkVersionJ = json_object_get(rootJ, "checkVersion"); | ||||