| @@ -1,6 +1,7 @@ | |||
| #pragma once | |||
| #include "app/common.hpp" | |||
| #include "app/MultiLightWidget.hpp" | |||
| #include "engine/Module.hpp" | |||
| namespace rack { | |||
| @@ -6,7 +6,7 @@ | |||
| #include "app/Port.hpp" | |||
| #include "app/ParamWidget.hpp" | |||
| #include "plugin.hpp" | |||
| #include "engine.hpp" | |||
| #include "engine/Module.hpp" | |||
| namespace rack { | |||
| @@ -1,6 +1,6 @@ | |||
| #pragma once | |||
| #include "ui/Quantity.hpp" | |||
| #include "engine.hpp" | |||
| #include "engine/Module.hpp" | |||
| namespace rack { | |||
| @@ -2,7 +2,6 @@ | |||
| #include "widgets/OpaqueWidget.hpp" | |||
| #include "app/ParamQuantity.hpp" | |||
| #include "app/common.hpp" | |||
| #include "engine.hpp" | |||
| namespace rack { | |||
| @@ -2,7 +2,6 @@ | |||
| #include "app/common.hpp" | |||
| #include "app/WireWidget.hpp" | |||
| #include "app/WireContainer.hpp" | |||
| #include "engine.hpp" | |||
| namespace rack { | |||
| @@ -1,6 +1,5 @@ | |||
| #pragma once | |||
| #include "app/common.hpp" | |||
| #include "engine.hpp" | |||
| namespace rack { | |||
| @@ -1,7 +1,6 @@ | |||
| #pragma once | |||
| #include "app/common.hpp" | |||
| #include "app/WireWidget.hpp" | |||
| #include "engine.hpp" | |||
| namespace rack { | |||
| @@ -1,5 +1,6 @@ | |||
| #pragma once | |||
| #include "app/common.hpp" | |||
| #include "engine/Wire.hpp" | |||
| namespace rack { | |||
| @@ -1,6 +1,6 @@ | |||
| #pragma once | |||
| // Include some of the C++ standard library for convenience | |||
| // Include most of the C stdlib for convenience | |||
| #include <cstdlib> | |||
| #include <cstdio> | |||
| #include <cstdint> | |||
| @@ -9,6 +9,8 @@ | |||
| #include <cmath> | |||
| #include <cstring> | |||
| #include <cassert> | |||
| // Include some of the C++ stdlib for convenience | |||
| #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 "engine.hpp" | |||
| #include "app.hpp" | |||
| #include "event.hpp" | |||
| #include "engine/Module.hpp" | |||
| namespace rack { | |||
| @@ -8,7 +8,7 @@ | |||
| #include "network.hpp" | |||
| #include "asset.hpp" | |||
| #include "plugin.hpp" | |||
| #include "engine.hpp" | |||
| #include "engine/Engine.hpp" | |||
| #include "window.hpp" | |||
| #include "widgets.hpp" | |||
| #include "app.hpp" | |||
| @@ -144,7 +144,7 @@ struct AudioInterface : Module { | |||
| void AudioInterface::step() { | |||
| // Update SRC states | |||
| int sampleRate = (int) engineGetSampleRate(); | |||
| int sampleRate = (int) gEngine->getSampleRate(); | |||
| inputSrc.setRates(audioIO.sampleRate, sampleRate); | |||
| outputSrc.setRates(sampleRate, audioIO.sampleRate); | |||
| @@ -47,7 +47,7 @@ struct MIDICCToCVInterface : Module { | |||
| processMessage(msg); | |||
| } | |||
| float lambda = 100.f * engineGetSampleTime(); | |||
| float lambda = 100.f * gEngine->getSampleTime(); | |||
| for (int i = 0; i < 16; i++) { | |||
| int learnedCc = learnedCcs[i]; | |||
| 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)) { | |||
| processMessage(msg); | |||
| } | |||
| float deltaTime = engineGetSampleTime(); | |||
| float deltaTime = gEngine->getSampleTime(); | |||
| outputs[CV_OUTPUT].value = (lastNote - 60) / 12.f; | |||
| outputs[GATE_OUTPUT].value = gate ? 10.f : 0.f; | |||
| @@ -1,5 +1,6 @@ | |||
| #include "Core.hpp" | |||
| #include "midi.hpp" | |||
| #include "engine/Engine.hpp" | |||
| #include "event.hpp" | |||
| @@ -70,7 +71,7 @@ struct MIDITriggerToCVInterface : Module { | |||
| while (midiInput.shift(&msg)) { | |||
| processMessage(msg); | |||
| } | |||
| float deltaTime = engineGetSampleTime(); | |||
| float deltaTime = gEngine->getSampleTime(); | |||
| for (int i = 0; i < 16; i++) { | |||
| if (gateTimes[i] > 0.f) { | |||
| @@ -1,5 +1,4 @@ | |||
| #include "app.hpp" | |||
| #include "engine.hpp" | |||
| namespace rack { | |||
| @@ -1,4 +1,5 @@ | |||
| #include "osdialog.h" | |||
| #include "engine/Engine.hpp" | |||
| #include "rack.hpp" | |||
| @@ -7,7 +8,7 @@ namespace rack { | |||
| ModuleWidget::ModuleWidget(Module *module) { | |||
| if (module) { | |||
| engineAddModule(module); | |||
| gEngine->addModule(module); | |||
| } | |||
| this->module = module; | |||
| } | |||
| @@ -17,7 +18,7 @@ ModuleWidget::~ModuleWidget() { | |||
| disconnect(); | |||
| // Remove and delete the Module instance | |||
| if (module) { | |||
| engineRemoveModule(module); | |||
| gEngine->removeModule(module); | |||
| delete module; | |||
| module = NULL; | |||
| } | |||
| @@ -229,7 +230,7 @@ void ModuleWidget::reset() { | |||
| param->reset(); | |||
| } | |||
| if (module) { | |||
| engineResetModule(module); | |||
| gEngine->resetModule(module); | |||
| } | |||
| } | |||
| @@ -238,7 +239,7 @@ void ModuleWidget::randomize() { | |||
| param->randomize(); | |||
| } | |||
| if (module) { | |||
| engineRandomizeModule(module); | |||
| gEngine->randomizeModule(module); | |||
| } | |||
| } | |||
| @@ -247,7 +248,7 @@ void ModuleWidget::draw(NVGcontext *vg) { | |||
| Widget::draw(vg); | |||
| // Power meter | |||
| if (module && gPowerMeter) { | |||
| if (module && gEngine->powerMeter) { | |||
| nvgBeginPath(vg); | |||
| nvgRect(vg, | |||
| 0, box.size.y - 20, | |||
| @@ -1,5 +1,4 @@ | |||
| #include "app/ParamWidget.hpp" | |||
| #include "engine.hpp" | |||
| #include "random.hpp" | |||
| @@ -1,6 +1,5 @@ | |||
| #include "app.hpp" | |||
| #include "window.hpp" | |||
| #include "engine.hpp" | |||
| #include "componentlibrary.hpp" | |||
| @@ -1,6 +1,6 @@ | |||
| #include "app.hpp" | |||
| #include "window.hpp" | |||
| #include "engine.hpp" | |||
| #include "engine/Engine.hpp" | |||
| #include "asset.hpp" | |||
| #include "helpers.hpp" | |||
| @@ -96,21 +96,21 @@ struct PowerMeterButton : TooltipIconButton { | |||
| } | |||
| std::string getTooltipText() override {return "Toggle power meter (see manual for explanation)";} | |||
| void onAction(event::Action &e) override { | |||
| gPowerMeter ^= true; | |||
| gEngine->powerMeter ^= true; | |||
| } | |||
| }; | |||
| struct EnginePauseItem : MenuItem { | |||
| void onAction(event::Action &e) override { | |||
| gPaused ^= true; | |||
| gEngine->paused ^= true; | |||
| } | |||
| }; | |||
| struct SampleRateItem : MenuItem { | |||
| float sampleRate; | |||
| 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")); | |||
| EnginePauseItem *pauseItem = new EnginePauseItem; | |||
| pauseItem->text = gPaused ? "Resume engine" : "Pause engine"; | |||
| pauseItem->text = gEngine->paused ? "Resume engine" : "Pause engine"; | |||
| menu->addChild(pauseItem); | |||
| std::vector<float> sampleRates = {44100, 48000, 88200, 96000, 176400, 192000}; | |||
| for (float sampleRate : sampleRates) { | |||
| SampleRateItem *item = new SampleRateItem; | |||
| item->text = string::f("%.0f Hz", sampleRate); | |||
| item->rightText = CHECKMARK(engineGetSampleRate() == sampleRate); | |||
| item->rightText = CHECKMARK(gEngine->getSampleRate() == sampleRate); | |||
| item->sampleRate = sampleRate; | |||
| menu->addChild(item); | |||
| } | |||
| @@ -1,5 +1,5 @@ | |||
| #include "app.hpp" | |||
| #include "engine.hpp" | |||
| #include "engine/Engine.hpp" | |||
| #include "componentlibrary.hpp" | |||
| #include "window.hpp" | |||
| #include "event.hpp" | |||
| @@ -107,12 +107,12 @@ void WireWidget::updateWire() { | |||
| wire->outputId = outputPort->portId; | |||
| wire->inputModule = inputPort->module; | |||
| wire->inputId = inputPort->portId; | |||
| engineAddWire(wire); | |||
| gEngine->addWire(wire); | |||
| } | |||
| } | |||
| else { | |||
| if (wire) { | |||
| engineRemoveWire(wire); | |||
| gEngine->removeWire(wire); | |||
| delete wire; | |||
| 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 "bridge.hpp" | |||
| #include "settings.hpp" | |||
| #include "engine/Engine.hpp" | |||
| #ifdef ARCH_WIN | |||
| #include <Windows.h> | |||
| @@ -64,7 +65,7 @@ int main(int argc, char* argv[]) { | |||
| // Initialize app | |||
| pluginInit(devMode); | |||
| engineInit(); | |||
| gEngine = new Engine; | |||
| rtmidiInit(); | |||
| bridgeInit(); | |||
| keyboard::init(); | |||
| @@ -95,9 +96,9 @@ int main(int argc, char* argv[]) { | |||
| gRackWidget->lastPath = patchFile; | |||
| } | |||
| engineStart(); | |||
| gEngine->start(); | |||
| windowRun(); | |||
| engineStop(); | |||
| gEngine->stop(); | |||
| // Destroy namespaces | |||
| gRackWidget->save(asset::local("autosave.vcv")); | |||
| @@ -105,7 +106,7 @@ int main(int argc, char* argv[]) { | |||
| appDestroy(); | |||
| windowDestroy(); | |||
| bridgeDestroy(); | |||
| engineDestroy(); | |||
| delete gEngine; | |||
| midiDestroy(); | |||
| pluginDestroy(); | |||
| logger::destroy(); | |||
| @@ -1,6 +1,10 @@ | |||
| #include <jansson.h> | |||
| #include "rack.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 { | |||
| @@ -50,7 +54,7 @@ static json_t *settingsToJson() { | |||
| json_object_set_new(rootJ, "allowCursorLock", allowCursorLockJ); | |||
| // sampleRate | |||
| json_t *sampleRateJ = json_real(engineGetSampleRate()); | |||
| json_t *sampleRateJ = json_real(gEngine->getSampleRate()); | |||
| json_object_set_new(rootJ, "sampleRate", sampleRateJ); | |||
| // lastPath | |||
| @@ -66,7 +70,7 @@ static json_t *settingsToJson() { | |||
| json_object_set_new(rootJ, "moduleBrowser", appModuleBrowserToJson()); | |||
| // powerMeter | |||
| json_object_set_new(rootJ, "powerMeter", json_boolean(gPowerMeter)); | |||
| json_object_set_new(rootJ, "powerMeter", json_boolean(gEngine->powerMeter)); | |||
| // checkVersion | |||
| 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"); | |||
| if (sampleRateJ) { | |||
| float sampleRate = json_number_value(sampleRateJ); | |||
| engineSetSampleRate(sampleRate); | |||
| gEngine->setSampleRate(sampleRate); | |||
| } | |||
| // lastPath | |||
| @@ -142,7 +146,7 @@ static void settingsFromJson(json_t *rootJ) { | |||
| // powerMeter | |||
| json_t *powerMeterJ = json_object_get(rootJ, "powerMeter"); | |||
| if (powerMeterJ) | |||
| gPowerMeter = json_boolean_value(powerMeterJ); | |||
| gEngine->powerMeter = json_boolean_value(powerMeterJ); | |||
| // checkVersion | |||
| json_t *checkVersionJ = json_object_get(rootJ, "checkVersion"); | |||