Browse Source

Wrap engine state into class Engine

tags/v1.0.0
Andrew Belt 5 years ago
parent
commit
316518a794
37 changed files with 723 additions and 623 deletions
  1. +1
    -0
      include/app/ModuleLightWidget.hpp
  2. +1
    -1
      include/app/ModuleWidget.hpp
  3. +1
    -1
      include/app/ParamQuantity.hpp
  4. +0
    -1
      include/app/ParamWidget.hpp
  5. +0
    -1
      include/app/RackWidget.hpp
  6. +0
    -1
      include/app/SVGButton.hpp
  7. +0
    -1
      include/app/WireContainer.hpp
  8. +1
    -0
      include/app/WireWidget.hpp
  9. +3
    -1
      include/common.hpp
  10. +0
    -171
      include/engine.hpp
  11. +51
    -0
      include/engine/Engine.hpp
  12. +23
    -0
      include/engine/Input.hpp
  13. +21
    -0
      include/engine/Light.hpp
  14. +58
    -0
      include/engine/Module.hpp
  15. +18
    -0
      include/engine/Output.hpp
  16. +38
    -0
      include/engine/Param.hpp
  17. +18
    -0
      include/engine/Wire.hpp
  18. +1
    -1
      include/helpers.hpp
  19. +1
    -1
      include/rack.hpp
  20. +1
    -1
      src/Core/AudioInterface.cpp
  21. +1
    -1
      src/Core/MIDICCToCVInterface.cpp
  22. +1
    -1
      src/Core/MIDIToCVInterface.cpp
  23. +2
    -1
      src/Core/MIDITriggerToCVInterface.cpp
  24. +0
    -1
      src/app/ModuleLightWidget.cpp
  25. +6
    -5
      src/app/ModuleWidget.cpp
  26. +0
    -1
      src/app/ParamWidget.cpp
  27. +0
    -1
      src/app/Port.cpp
  28. +7
    -7
      src/app/Toolbar.cpp
  29. +3
    -3
      src/app/WireWidget.cpp
  30. +0
    -411
      src/engine.cpp
  31. +332
    -0
      src/engine/Engine.cpp
  32. +27
    -0
      src/engine/Light.cpp
  33. +54
    -0
      src/engine/Module.cpp
  34. +24
    -0
      src/engine/Param.cpp
  35. +14
    -0
      src/engine/Wire.cpp
  36. +5
    -4
      src/main.cpp
  37. +10
    -6
      src/settings.cpp

+ 1
- 0
include/app/ModuleLightWidget.hpp View File

@@ -1,6 +1,7 @@
#pragma once
#include "app/common.hpp"
#include "app/MultiLightWidget.hpp"
#include "engine/Module.hpp"


namespace rack {


+ 1
- 1
include/app/ModuleWidget.hpp View File

@@ -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
- 1
include/app/ParamQuantity.hpp View File

@@ -1,6 +1,6 @@
#pragma once
#include "ui/Quantity.hpp"
#include "engine.hpp"
#include "engine/Module.hpp"


namespace rack {


+ 0
- 1
include/app/ParamWidget.hpp View File

@@ -2,7 +2,6 @@
#include "widgets/OpaqueWidget.hpp"
#include "app/ParamQuantity.hpp"
#include "app/common.hpp"
#include "engine.hpp"


namespace rack {


+ 0
- 1
include/app/RackWidget.hpp View File

@@ -2,7 +2,6 @@
#include "app/common.hpp"
#include "app/WireWidget.hpp"
#include "app/WireContainer.hpp"
#include "engine.hpp"


namespace rack {


+ 0
- 1
include/app/SVGButton.hpp View File

@@ -1,6 +1,5 @@
#pragma once
#include "app/common.hpp"
#include "engine.hpp"


namespace rack {


+ 0
- 1
include/app/WireContainer.hpp View File

@@ -1,7 +1,6 @@
#pragma once
#include "app/common.hpp"
#include "app/WireWidget.hpp"
#include "engine.hpp"


namespace rack {


+ 1
- 0
include/app/WireWidget.hpp View File

@@ -1,5 +1,6 @@
#pragma once
#include "app/common.hpp"
#include "engine/Wire.hpp"


namespace rack {


+ 3
- 1
include/common.hpp View File

@@ -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>




+ 0
- 171
include/engine.hpp View File

@@ -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

+ 51
- 0
include/engine/Engine.hpp View File

@@ -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

+ 23
- 0
include/engine/Input.hpp View File

@@ -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

+ 21
- 0
include/engine/Light.hpp View File

@@ -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

+ 58
- 0
include/engine/Module.hpp View File

@@ -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

+ 18
- 0
include/engine/Output.hpp View File

@@ -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

+ 38
- 0
include/engine/Param.hpp View File

@@ -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

+ 18
- 0
include/engine/Wire.hpp View File

@@ -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
- 1
include/helpers.hpp View File

@@ -1,7 +1,7 @@
#include "plugin.hpp"
#include "engine.hpp"
#include "app.hpp"
#include "event.hpp"
#include "engine/Module.hpp"


namespace rack {


+ 1
- 1
include/rack.hpp View File

@@ -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"


+ 1
- 1
src/Core/AudioInterface.cpp View File

@@ -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);



+ 1
- 1
src/Core/MIDICCToCVInterface.cpp View File

@@ -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);


+ 1
- 1
src/Core/MIDIToCVInterface.cpp View File

@@ -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;


+ 2
- 1
src/Core/MIDITriggerToCVInterface.cpp View File

@@ -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) {


+ 0
- 1
src/app/ModuleLightWidget.cpp View File

@@ -1,5 +1,4 @@
#include "app.hpp"
#include "engine.hpp"


namespace rack {


+ 6
- 5
src/app/ModuleWidget.cpp View File

@@ -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,


+ 0
- 1
src/app/ParamWidget.cpp View File

@@ -1,5 +1,4 @@
#include "app/ParamWidget.hpp"
#include "engine.hpp"
#include "random.hpp"




+ 0
- 1
src/app/Port.cpp View File

@@ -1,6 +1,5 @@
#include "app.hpp"
#include "window.hpp"
#include "engine.hpp"
#include "componentlibrary.hpp"




+ 7
- 7
src/app/Toolbar.cpp View File

@@ -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);
}


+ 3
- 3
src/app/WireWidget.cpp View File

@@ -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;
}


+ 0
- 411
src/engine.cpp View File

@@ -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 &param : 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

+ 332
- 0
src/engine/Engine.cpp View File

@@ -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

+ 27
- 0
src/engine/Light.cpp View File

@@ -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

+ 54
- 0
src/engine/Module.cpp View File

@@ -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 &param : 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

+ 24
- 0
src/engine/Param.cpp View File

@@ -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

+ 14
- 0
src/engine/Wire.cpp View File

@@ -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

+ 5
- 4
src/main.cpp View File

@@ -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();


+ 10
- 6
src/settings.cpp View File

@@ -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");


Loading…
Cancel
Save