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