Browse Source

Massive WIP v2 overhaul:

- Add Engine::fromJson which can be called without the GUI at all.
- Make RackWidget::fromJson attach itself to existing Engine state. However, once attached, they own their Engine objects (Module, Cable).
- Remove Engine thread. Now Engine must be stepped by other threads, such as the audio thread. This is fantastic because there is no longer a mutex lock every audio buffer.
	- Add concept of the "primary module", which is allowed to call Engine::step().
- Add many Module events.
tags/v2.0.0
Andrew Belt 5 years ago
parent
commit
e4b6056ded
40 changed files with 1392 additions and 1009 deletions
  1. +30
    -24
      include/Quantity.hpp
  2. +10
    -6
      include/app/CableWidget.hpp
  3. +4
    -8
      include/app/ModuleWidget.hpp
  4. +0
    -2
      include/app/ParamWidget.hpp
  5. +8
    -8
      include/app/RackWidget.hpp
  6. +3
    -3
      include/common.hpp
  7. +5
    -2
      include/engine/Cable.hpp
  8. +24
    -6
      include/engine/Engine.hpp
  9. +91
    -20
      include/engine/Module.hpp
  10. +3
    -0
      include/engine/Param.hpp
  11. +8
    -0
      include/engine/ParamQuantity.hpp
  12. +5
    -2
      include/engine/Port.hpp
  13. +7
    -9
      include/helpers.hpp
  14. +6
    -6
      include/history.hpp
  15. +1
    -0
      include/patch.hpp
  16. +5
    -7
      include/plugin/Model.hpp
  17. +2
    -1
      src/Quantity.cpp
  18. +98
    -150
      src/app/CableWidget.cpp
  19. +7
    -4
      src/app/ModuleBrowser.cpp
  20. +25
    -28
      src/app/ModuleWidget.cpp
  21. +0
    -8
      src/app/ParamWidget.cpp
  22. +13
    -7
      src/app/PortWidget.cpp
  23. +106
    -121
      src/app/RackWidget.cpp
  24. +326
    -261
      src/core/AudioInterface.cpp
  25. +27
    -18
      src/core/Blank.cpp
  26. +26
    -18
      src/core/Notes.cpp
  27. +64
    -0
      src/engine/Cable.cpp
  28. +345
    -171
      src/engine/Engine.cpp
  29. +37
    -11
      src/engine/Module.cpp
  30. +23
    -0
      src/engine/Param.cpp
  31. +2
    -1
      src/engine/ParamQuantity.cpp
  32. +0
    -35
      src/engine/Port.cpp
  33. +44
    -41
      src/history.cpp
  34. +1
    -6
      src/main.cpp
  35. +17
    -6
      src/patch.cpp
  36. +8
    -8
      src/plugin.cpp
  37. +1
    -1
      src/plugin/Model.cpp
  38. +8
    -8
      src/plugin/Plugin.cpp
  39. +1
    -1
      src/settings.cpp
  40. +1
    -1
      src/window.cpp

+ 30
- 24
include/Quantity.hpp View File

@@ -1,5 +1,6 @@
#pragma once
#include <math.hpp>
#include <random.hpp>


namespace rack {
@@ -11,122 +12,127 @@ Often used as a decorator component for widget::Widgets that read or write a qua
struct Quantity {
virtual ~Quantity() {}

/** Sets the value directly
/** Sets the value directly.
Override this to change the state of your subclass to represent the new value.
*/
virtual void setValue(float value) {}

/** Returns the value
/** Returns the value.
Override this to return the state of your subclass.
*/
virtual float getValue() {
return 0.f;
}

/** Returns the minimum allowed value */
/** Returns the minimum allowed value. */
virtual float getMinValue() {
return 0.f;
}

/** Returns the maximum allowed value */
/** Returns the maximum allowed value. */
virtual float getMaxValue() {
return 1.f;
}

/** Returns the default value, for resetting */
/** Returns the default value, for resetting. */
virtual float getDefaultValue() {
return 0.f;
}

/** Returns the value, possibly transformed for displaying
/** Returns the value, possibly transformed for displaying.
Useful for logarithmic scaling, multiplying by 100 for percentages, etc.
*/
virtual float getDisplayValue() {
return getValue();
}

/** Inversely transforms the display value and sets the value */
/** Inversely transforms the display value and sets the value. */
virtual void setDisplayValue(float displayValue) {
setValue(displayValue);
}

/** The number of total decimal places for generating the display value string
*/
/** The number of total decimal places for generating the display value string. */
virtual int getDisplayPrecision();

/** Returns a string representation of the display value */
/** Returns a string representation of the display value. */
virtual std::string getDisplayValueString();

virtual void setDisplayValueString(std::string s);

/** The name of the quantity */
/** The name of the quantity. */
virtual std::string getLabel() {
return "";
}

/** The unit abbreviation of the quantity
/** The unit abbreviation of the quantity.
Include an initial space character if you want a space after the number, e.g. "440 Hz". This allows space-less units, like "100%".
*/
virtual std::string getUnit() {
return "";
}

/** Returns a string representation of the quantity */
/** Returns a string representation of the quantity. */
virtual std::string getString();

// Helper methods

/** Resets the value to the default value */
/** Resets the value to the default value. */
void reset() {
setValue(getDefaultValue());
}

/** Checks whether the value is at the min value */
/** Sets the value to a uniform random value between the bounds. */
void randomize() {
if (isBounded())
setScaledValue(random::uniform());
}

/** Checks whether the value is at the min value. */
bool isMin() {
return getValue() <= getMinValue();
}

/** Checks whether the value is at the max value */
/** Checks whether the value is at the max value. */
bool isMax() {
return getValue() >= getMaxValue();
}

/** Sets the value to the min value */
/** Sets the value to the min value. */
void setMin() {
setValue(getMinValue());
}

/** Sets the value to the max value */
/** Sets the value to the max value. */
void setMax() {
setValue(getMaxValue());
}

/** Sets value from the range 0 to 1 */
/** Sets value from the range 0 to 1. */
void setScaledValue(float scaledValue) {
setValue(math::rescale(scaledValue, 0.f, 1.f, getMinValue(), getMaxValue()));
}

/** Returns the value rescaled to the range 0 to 1 */
/** Returns the value rescaled to the range 0 to 1. */
float getScaledValue() {
return math::rescale(getValue(), getMinValue(), getMaxValue(), 0.f, 1.f);
}

/** The difference between the max and min values */
/** The difference between the max and min values. */
float getRange() {
return getMaxValue() - getMinValue();
}

/** Checks whether the bounds are finite */
/** Checks whether the bounds are finite. */
bool isBounded() {
return std::isfinite(getMinValue()) && std::isfinite(getMaxValue());
}

/** Adds an amount to the value */
/** Adds an amount to the value. */
void moveValue(float deltaValue) {
setValue(getValue() + deltaValue);
}

/** Adds an amount to the value scaled to the range 0 to 1 */
/** Adds an amount to the value scaled to the range 0 to 1. */
void moveScaledValue(float deltaScaledValue) {
moveValue(deltaScaledValue * getRange());
}


+ 10
- 6
include/app/CableWidget.hpp View File

@@ -12,21 +12,25 @@ namespace app {


struct CableWidget : widget::OpaqueWidget {
PortWidget* outputPort = NULL;
PortWidget* inputPort = NULL;
PortWidget* hoveredOutputPort = NULL;
PortWidget* outputPort = NULL;
PortWidget* hoveredInputPort = NULL;
PortWidget* hoveredOutputPort = NULL;
/** Owned. */
engine::Cable* cable;
engine::Cable* cable = NULL;
NVGcolor color;

CableWidget();
~CableWidget();
bool isComplete();
void setOutput(PortWidget* outputPort);
void setInput(PortWidget* inputPort);
math::Vec getOutputPos();
/** From input/output ports, re-creates a cable and adds it to the Engine. */
void updateCable();
/** From a cable, sets the input/output ports.
Cable must already be added to the Engine.
*/
void setCable(engine::Cable* cable);
math::Vec getInputPos();
math::Vec getOutputPos();
json_t* toJson();
void fromJson(json_t* rootJ);
void draw(const DrawArgs& args) override;


+ 4
- 8
include/app/ModuleWidget.hpp View File

@@ -22,8 +22,8 @@ struct ModuleWidget : widget::OpaqueWidget {
/** Note that the indexes of these vectors do not necessarily correspond with the indexes of `Module::params` etc.
*/
std::vector<ParamWidget*> params;
std::vector<PortWidget*> outputs;
std::vector<PortWidget*> inputs;
std::vector<PortWidget*> outputs;
/** For RackWidget dragging */
math::Vec dragPos;
math::Vec oldPos;
@@ -57,13 +57,9 @@ struct ModuleWidget : widget::OpaqueWidget {
PortWidget* getOutput(int outputId);
PortWidget* getInput(int inputId);

/** Overriding these is deprecated.
Use Module::dataToJson() and dataFromJson() instead
*/
virtual json_t* toJson();
virtual void fromJson(json_t* rootJ);

/** Serializes/unserializes the module state */
json_t* toJson();
void fromJson(json_t* rootJ);
void copyClipboard();
void pasteClipboardAction();
void loadAction(std::string filename);
@@ -86,7 +82,7 @@ struct ModuleWidget : widget::OpaqueWidget {
void randomizeAction();
void disconnectAction();
void cloneAction();
void bypassAction();
void disableAction();
/** Deletes `this` */
void removeAction();
void createContextMenu();


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

@@ -24,8 +24,6 @@ struct ParamWidget : widget::OpaqueWidget {
void onEnter(const event::Enter& e) override;
void onLeave(const event::Leave& e) override;

/** For legacy patch loading */
void fromJson(json_t* rootJ);
void createContextMenu();
void resetAction();
virtual void reset() {}


+ 8
- 8
include/app/RackWidget.hpp View File

@@ -39,7 +39,7 @@ struct RackWidget : widget::OpaqueWidget {

/** Completely clear the rack's modules and cables */
void clear();
json_t* toJson();
void mergeJson(json_t* rootJ);
void fromJson(json_t* rootJ);
void pastePresetClipboardAction();

@@ -68,18 +68,18 @@ struct RackWidget : widget::OpaqueWidget {
void clearCablesAction();
/** Removes all complete cables connected to the port */
void clearCablesOnPort(PortWidget* port);
/** Adds a complete cable and adds it to the Engine.
/** Adds a complete cable.
Ownership rules work like add/removeChild()
*/
void addCable(CableWidget* w);
void removeCable(CableWidget* w);
/** Takes ownership of `w` and adds it as a child if it isn't already */
void setIncompleteCable(CableWidget* w);
void addCable(CableWidget* cw);
void removeCable(CableWidget* cw);
/** Takes ownership of `cw` and adds it as a child if it isn't already. */
void setIncompleteCable(CableWidget* cw);
CableWidget* releaseIncompleteCable();
/** Returns the most recently added complete cable connected to the given Port, i.e. the top of the stack */
/** Returns the most recently added complete cable connected to the given Port, i.e. the top of the stack. */
CableWidget* getTopCable(PortWidget* port);
CableWidget* getCable(int cableId);
/** Returns all cables attached to port, complete or not */
/** Returns all cables attached to port, complete or not. */
std::list<CableWidget*> getCablesOnPort(PortWidget* port);
};



+ 3
- 3
include/common.hpp View File

@@ -157,9 +157,9 @@ DeferWrapper<F> deferWrapper(F f) {
#define DEFER(code) auto CONCAT(_defer_, __COUNTER__) = rack::deferWrapper([&]() code)


/** An exception meant to be shown to the user */
struct UserException : std::runtime_error {
UserException(const std::string& msg) : std::runtime_error(msg) {}
/** An exception explicitly thrown by Rack. */
struct Exception : std::runtime_error {
Exception(const std::string& msg) : std::runtime_error(msg) {}
};




+ 5
- 2
include/engine/Cable.hpp View File

@@ -9,10 +9,13 @@ namespace engine {

struct Cable {
int id = -1;
Module* outputModule = NULL;
int outputId;
Module* inputModule = NULL;
int inputId;
Module* outputModule = NULL;
int outputId;

json_t* toJson();
void fromJson(json_t* rootJ);
};




+ 24
- 6
include/engine/Engine.hpp View File

@@ -16,19 +16,27 @@ struct Engine {

Engine();
~Engine();
/** Starts engine thread. */
void start();
/** Stops engine thread. */
void stop();

void clear();
/** Advances the engine by `frames` frames.
Only call this method from the primary module.
*/
void step(int frames);
void setPrimaryModule(Module* module);
Module* getPrimaryModule();

void setPaused(bool paused);
bool isPaused();
float getSampleRate();
/** Returns the inverse of the current sample rate. */
/** Returns the inverse of the current sample rate.
*/
float getSampleTime();
/** Causes worker threads to block on a mutex instead of spinlock.
Call this in your Module::step() method to hint that the operation will take more than ~0.1 ms.
*/
void yieldWorkers();
/** Returns the number of audio samples since the Engine's first sample.
*/
uint64_t getFrame();

// Modules
@@ -42,7 +50,7 @@ struct Engine {
Module* getModule(int moduleId);
void resetModule(Module* module);
void randomizeModule(Module* module);
void bypassModule(Module* module, bool bypass);
void disableModule(Module* module, bool disabled);

// Cables
/** Adds a cable to the rack engine.
@@ -52,6 +60,7 @@ struct Engine {
*/
void addCable(Cable* cable);
void removeCable(Cable* cable);
Cable* getCable(int cableId);

// Params
void setParam(Module* module, int paramId, float value);
@@ -70,8 +79,17 @@ struct Engine {
If `overwrite` is true and another ParamHandle points to the same param, unsets that one and replaces it with the given handle.
*/
void updateParamHandle(ParamHandle* paramHandle, int moduleId, int paramId, bool overwrite = true);

json_t* toJson();
void fromJson(json_t* rootJ);
};


/** Creates a Module from a JSON module object.
Throws an Exception if the model is not found.
*/
Module* moduleFromJson(json_t* moduleJ);


} // namespace engine
} // namespace rack

+ 91
- 20
include/engine/Module.hpp View File

@@ -32,8 +32,8 @@ struct Module {
Initialized with config().
*/
std::vector<Param> params;
std::vector<Output> outputs;
std::vector<Input> inputs;
std::vector<Output> outputs;
std::vector<Light> lights;
std::vector<ParamQuantity*> paramQuantities;

@@ -78,7 +78,7 @@ struct Module {
/** Whether the Module is skipped from stepping by the engine.
Module subclasses should not read/write this variable.
*/
bool bypass = false;
bool disabled = false;

/** Constructs a Module with no params, inputs, outputs, and lights. */
Module();
@@ -121,38 +121,109 @@ struct Module {
float sampleRate;
float sampleTime;
};

/** Advances the module by one audio sample.
Override this method to read Inputs and Params and to write Outputs and Lights.
*/
virtual void process(const ProcessArgs& args) {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
step();
#pragma GCC diagnostic pop
}
/** Override process(const ProcessArgs &args) instead. */
DEPRECATED 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() {}
/** Called when the Module is added to the Engine */
virtual void onAdd() {}
/** Called when the Module is removed from the Engine */
virtual void onRemove() {}
/** DEPRECATED. Override `process(const ProcessArgs& args)` instead. */
virtual void step() {}

json_t* toJson();
void fromJson(json_t* rootJ);
/** This is virtual only for the purpose of unserializing legacy data when you could set properties of the `.modules[]` object itself.
Normally you should override dataFromJson().
Remember to call `Module::fromJson(rootJ)` within your overridden method.
*/
virtual void fromJson(json_t* rootJ);

/** Override 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) {}

///////////////////////
// Events
///////////////////////

// All of these events are thread-safe with process().

struct AddEvent {};
/** Called after adding the module to the Engine.
*/
virtual void onAdd(const AddEvent& e) {
// Call deprecated event method by default
onAdd();
}

struct RemoveEvent {};
/** Called before removing the module to the Engine.
*/
virtual void onRemove(const RemoveEvent& e) {
// Call deprecated event method by default
onRemove();
}

struct EnableEvent {};
/** Called after enabling the module.
*/
virtual void onEnable(const EnableEvent& e) {}

struct DisableEvent {};
/** Called after disabling the module.
*/
virtual void onDisable(const DisableEvent& e) {}

struct PortChangeEvent {
/** True if connecting, false if disconnecting. */
bool connecting;
Port::Type type;
int portId;
};
/** Called after a cable connects to or disconnects from a port.
This event is not called for output ports if a stackable cable was added/removed and did not change the port's connected state.
*/
virtual void onPortChange(const PortChangeEvent& e) {}

struct SampleRateChangeEvent {
float sampleRate;
float sampleTime;
};
/** Called after the Engine sample rate changes.
*/
virtual void onSampleRateChange(const SampleRateChangeEvent& e) {
// Call deprecated event method by default
onSampleRateChange();
}

struct ExpanderChangeEvent {};
/** Called after the Engine sample rate changes.
*/
virtual void onExpanderChange(const ExpanderChangeEvent& e) {}

struct ResetEvent {};
/** Called when the user resets (initializes) the module.
The default implementation resets all parameters to their default value, so you must call `Module::onRandomize(e)` if you want to keep this behavior.
*/
virtual void onReset(const ResetEvent& e);

struct RandomizeEvent {};
/** Called when the user randomizes the module.
The default implementation randomizes all parameters by default, so you must call `Module::onRandomize(e)` if you want to keep this behavior.
*/
virtual void onRandomize(const RandomizeEvent& e);

/** DEPRECATED. Override `onAdd(e)` instead. */
virtual void onAdd() {}
/** DEPRECATED. Override `onRemove(e)` instead. */
virtual void onRemove() {}
/** DEPRECATED. Override `onReset(e)` instead. */
virtual void onReset() {}
/** DEPRECATED. Override `onRandomize(e)` instead. */
virtual void onRandomize() {}
/** DEPRECATED. Override `onSampleRateChange(e)` instead. */
virtual void onSampleRateChange() {}
};




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

@@ -19,6 +19,9 @@ struct Param {
void setValue(float value) {
this->value = value;
}

json_t* toJson();
void fromJson(json_t* rootJ);
};




+ 8
- 0
include/engine/ParamQuantity.hpp View File

@@ -35,8 +35,16 @@ struct ParamQuantity : Quantity {
float displayBase = 0.f;
float displayMultiplier = 1.f;
float displayOffset = 0.f;
int displayPrecision = 5;
/** An optional one-sentence description of the parameter. */
std::string description;
/** Enables parameter resetting when the module is reset.
*/
bool resetEnabled = true;
/** Enables parameter randomization when the module is randomized.
Unbounded (infinite) parameters are not randomizable, regardless of this setting.
*/
bool randomizeEnabled = true;

Param* getParam();
/** Request to the engine to smoothly set the value */


+ 5
- 2
include/engine/Port.hpp View File

@@ -33,6 +33,11 @@ struct alignas(32) Port {
*/
Light plugLights[3];

enum Type {
INPUT,
OUTPUT,
};

/** Sets the voltage of the given channel. */
void setVoltage(float voltage, int channel = 0) {
voltages[channel] = voltage;
@@ -168,8 +173,6 @@ struct alignas(32) Port {
return channels > 1;
}

void process(float deltaTime);

/** Use getNormalVoltage() instead. */
DEPRECATED float normalize(float normalVoltage) {
return getNormalVoltage(normalVoltage);


+ 7
- 9
include/helpers.hpp View File

@@ -24,15 +24,13 @@ plugin::Model* createModel(const std::string& slug) {
m->model = this;
return m;
}
app::ModuleWidget* createModuleWidget() override {
TModule* m = new TModule;
m->engine::Module::model = this;
app::ModuleWidget* mw = new TModuleWidget(m);
mw->model = this;
return mw;
}
app::ModuleWidget* createModuleWidgetNull() override {
app::ModuleWidget* mw = new TModuleWidget(NULL);
app::ModuleWidget* createModuleWidget(engine::Module* m) override {
TModule *tm = NULL;
if (m) {
assert(m->model == this);
tm = dynamic_cast<TModule*>(m);
}
app::ModuleWidget* mw = new TModuleWidget(tm);
mw->model = this;
return mw;
}


+ 6
- 6
include/history.hpp View File

@@ -97,12 +97,12 @@ struct ModuleMove : ModuleAction {
};


struct ModuleBypass : ModuleAction {
bool bypass;
struct ModuleDisable : ModuleAction {
bool disabled;
void undo() override;
void redo() override;
ModuleBypass() {
name = "bypass module";
ModuleDisable() {
name = "disable module";
}
};

@@ -133,10 +133,10 @@ struct ParamChange : ModuleAction {

struct CableAdd : Action {
int cableId;
int outputModuleId;
int outputId;
int inputModuleId;
int inputId;
int outputModuleId;
int outputId;
NVGcolor color;
void setCable(app::CableWidget* cw);
void undo() override;


+ 1
- 0
include/patch.hpp View File

@@ -33,6 +33,7 @@ struct PatchManager {
json_t* toJson();
void fromJson(json_t* rootJ);
bool isLegacy(int level);
void log(std::string msg);
};




+ 5
- 7
include/plugin/Model.hpp View File

@@ -39,16 +39,14 @@ struct Model {
std::string description;

virtual ~Model() {}
/** Creates a headless Module */
/** Creates a Module. */
virtual engine::Module* createModule() {
return NULL;
}
/** Creates a ModuleWidget with a Module attached */
virtual app::ModuleWidget* createModuleWidget() {
return NULL;
}
/** Creates a ModuleWidget with no Module, useful for previews */
virtual app::ModuleWidget* createModuleWidgetNull() {
/** Creates a ModuleWidget with a Module attached.
Module may be NULL.
*/
virtual app::ModuleWidget* createModuleWidget(engine::Module* m) {
return NULL;
}



+ 2
- 1
src/Quantity.cpp View File

@@ -38,7 +38,8 @@ std::string Quantity::getString() {
std::string label = getLabel();
if (!label.empty())
s += label + ": ";
s += getDisplayValueString() + getUnit();
s += getDisplayValueString();
s += getUnit();
return s;
}



+ 98
- 150
src/app/CableWidget.cpp View File

@@ -5,12 +5,110 @@
#include <app.hpp>
#include <patch.hpp>
#include <settings.hpp>
#include <engine/Engine.hpp>
#include <engine/Port.hpp>


namespace rack {
namespace app {

CableWidget::CableWidget() {
color = color::BLACK_TRANSPARENT;
if (!settings::cableColors.empty()) {
int id = APP->scene->rack->nextCableColorId++;
APP->scene->rack->nextCableColorId %= settings::cableColors.size();
color = settings::cableColors[id];
}
}

CableWidget::~CableWidget() {
setCable(NULL);
}

bool CableWidget::isComplete() {
return outputPort && inputPort;
}

void CableWidget::updateCable() {
if (cable) {
APP->engine->removeCable(cable);
delete cable;
cable = NULL;
}
if (inputPort && outputPort) {
cable = new engine::Cable;
cable->inputModule = inputPort->module;
cable->inputId = inputPort->portId;
cable->outputModule = outputPort->module;
cable->outputId = outputPort->portId;
APP->engine->addCable(cable);
}
}

void CableWidget::setCable(engine::Cable* cable) {
if (this->cable) {
APP->engine->removeCable(this->cable);
delete this->cable;
this->cable = NULL;
}
this->cable = cable;
if (cable) {
app::ModuleWidget* outputModule = APP->scene->rack->getModule(cable->outputModule->id);
assert(outputModule);
outputPort = outputModule->getOutput(cable->outputId);

app::ModuleWidget* inputModule = APP->scene->rack->getModule(cable->inputModule->id);
assert(inputModule);
inputPort = inputModule->getInput(cable->inputId);
}
else {
outputPort = NULL;
inputPort = NULL;
}
}

math::Vec CableWidget::getInputPos() {
if (inputPort) {
return inputPort->getRelativeOffset(inputPort->box.zeroPos().getCenter(), APP->scene->rack);
}
else if (hoveredInputPort) {
return hoveredInputPort->getRelativeOffset(hoveredInputPort->box.zeroPos().getCenter(), APP->scene->rack);
}
else {
return APP->scene->rack->mousePos;
}
}

math::Vec CableWidget::getOutputPos() {
if (outputPort) {
return outputPort->getRelativeOffset(outputPort->box.zeroPos().getCenter(), APP->scene->rack);
}
else if (hoveredOutputPort) {
return hoveredOutputPort->getRelativeOffset(hoveredOutputPort->box.zeroPos().getCenter(), APP->scene->rack);
}
else {
return APP->scene->rack->mousePos;
}
}

json_t* CableWidget::toJson() {
json_t* rootJ = json_object();

std::string s = color::toHexString(color);
json_object_set_new(rootJ, "color", json_string(s.c_str()));

return rootJ;
}

void CableWidget::fromJson(json_t* rootJ) {
json_t* colorJ = json_object_get(rootJ, "color");
if (colorJ) {
// v0.6.0 and earlier patches use JSON objects. Just ignore them if so and use the existing cable color.
if (json_is_string(colorJ))
color = color::fromHexString(json_string_value(colorJ));
}
}

static void drawPlug(NVGcontext* vg, math::Vec pos, NVGcolor color) {
NVGcolor colorOutline = nvgLerpRGBA(color, nvgRGBf(0.0, 0.0, 0.0), 0.5);

@@ -79,156 +177,6 @@ static void drawCable(NVGcontext* vg, math::Vec pos1, math::Vec pos2, NVGcolor c
}
}


CableWidget::CableWidget() {
cable = new engine::Cable;

color = color::BLACK_TRANSPARENT;
if (!settings::cableColors.empty()) {
int id = APP->scene->rack->nextCableColorId++;
APP->scene->rack->nextCableColorId %= settings::cableColors.size();
color = settings::cableColors[id];
}
}

CableWidget::~CableWidget() {
delete cable;
}

bool CableWidget::isComplete() {
return outputPort && inputPort;
}

void CableWidget::setOutput(PortWidget* outputPort) {
this->outputPort = outputPort;
if (outputPort) {
assert(outputPort->type == PortWidget::OUTPUT);
cable->outputModule = outputPort->module;
cable->outputId = outputPort->portId;
}
}

void CableWidget::setInput(PortWidget* inputPort) {
this->inputPort = inputPort;
if (inputPort) {
assert(inputPort->type == PortWidget::INPUT);
cable->inputModule = inputPort->module;
cable->inputId = inputPort->portId;
}
}

math::Vec CableWidget::getOutputPos() {
if (outputPort) {
return outputPort->getRelativeOffset(outputPort->box.zeroPos().getCenter(), APP->scene->rack);
}
else if (hoveredOutputPort) {
return hoveredOutputPort->getRelativeOffset(hoveredOutputPort->box.zeroPos().getCenter(), APP->scene->rack);
}
else {
return APP->scene->rack->mousePos;
}
}

math::Vec CableWidget::getInputPos() {
if (inputPort) {
return inputPort->getRelativeOffset(inputPort->box.zeroPos().getCenter(), APP->scene->rack);
}
else if (hoveredInputPort) {
return hoveredInputPort->getRelativeOffset(hoveredInputPort->box.zeroPos().getCenter(), APP->scene->rack);
}
else {
return APP->scene->rack->mousePos;
}
}

json_t* CableWidget::toJson() {
assert(isComplete());
json_t* rootJ = json_object();

json_object_set_new(rootJ, "id", json_integer(cable->id));
json_object_set_new(rootJ, "outputModuleId", json_integer(cable->outputModule->id));
json_object_set_new(rootJ, "outputId", json_integer(cable->outputId));
json_object_set_new(rootJ, "inputModuleId", json_integer(cable->inputModule->id));
json_object_set_new(rootJ, "inputId", json_integer(cable->inputId));

std::string s = color::toHexString(color);
json_object_set_new(rootJ, "color", json_string(s.c_str()));

return rootJ;
}

void CableWidget::fromJson(json_t* rootJ) {
// outputModuleId
json_t* outputModuleIdJ = json_object_get(rootJ, "outputModuleId");
if (!outputModuleIdJ)
return;
int outputModuleId = json_integer_value(outputModuleIdJ);
ModuleWidget* outputModule = APP->scene->rack->getModule(outputModuleId);
if (!outputModule)
return;

// inputModuleId
json_t* inputModuleIdJ = json_object_get(rootJ, "inputModuleId");
if (!inputModuleIdJ)
return;
int inputModuleId = json_integer_value(inputModuleIdJ);
ModuleWidget* inputModule = APP->scene->rack->getModule(inputModuleId);
if (!inputModule)
return;

// outputId
json_t* outputIdJ = json_object_get(rootJ, "outputId");
if (!outputIdJ)
return;
int outputId = json_integer_value(outputIdJ);

// inputId
json_t* inputIdJ = json_object_get(rootJ, "inputId");
if (!inputIdJ)
return;
int inputId = json_integer_value(inputIdJ);

// Only set ID if unset
if (cable->id < 0) {
// id
json_t* idJ = json_object_get(rootJ, "id");
// Before 1.0, cables IDs were not used, so just leave it as default and Engine will assign one automatically.
if (idJ) {
cable->id = json_integer_value(idJ);
}
}

// Set ports
if (APP->patch->isLegacy(1)) {
// Before 0.6, the index of the "ports" array was the index of the PortWidget in the `outputs` and `inputs` vector.
setOutput(outputModule->outputs[outputId]);
setInput(inputModule->inputs[inputId]);
}
else {
for (PortWidget* port : outputModule->outputs) {
if (port->portId == outputId) {
setOutput(port);
break;
}
}
for (PortWidget* port : inputModule->inputs) {
if (port->portId == inputId) {
setInput(port);
break;
}
}
}
if (!isComplete())
return;

json_t* colorJ = json_object_get(rootJ, "color");
if (colorJ) {
// v0.6.0 and earlier patches use JSON objects. Just ignore them if so and use the existing cable color.
if (json_is_string(colorJ))
color = color::fromHexString(json_string_value(colorJ));
}
}

void CableWidget::draw(const DrawArgs& args) {
float opacity = settings::cableOpacity;
float tension = settings::cableTension;


+ 7
- 4
src/app/ModuleBrowser.cpp View File

@@ -18,6 +18,7 @@
#include <app/Scene.hpp>
#include <plugin.hpp>
#include <app.hpp>
#include <engine/Engine.hpp>
#include <plugin/Model.hpp>
#include <string.hpp>
#include <history.hpp>
@@ -82,9 +83,11 @@ static bool isModelVisible(plugin::Model* model, const std::string& search, cons
}

static ModuleWidget* chooseModel(plugin::Model* model) {
// Create module
ModuleWidget* moduleWidget = model->createModuleWidget();
assert(moduleWidget);
// Create Module and ModuleWidget
engine::Module* module = model->createModule();
APP->engine->addModule(module);

ModuleWidget* moduleWidget = model->createModuleWidget(module);
APP->scene->rack->addModuleAtMouse(moduleWidget);

// Push ModuleAdd history action
@@ -170,7 +173,7 @@ struct ModelBox : widget::OpaqueWidget {
zoomWidget->setZoom(MODEL_BOX_ZOOM);
previewFb->addChild(zoomWidget);

ModuleWidget* moduleWidget = model->createModuleWidgetNull();
ModuleWidget* moduleWidget = model->createModuleWidget(NULL);
zoomWidget->addChild(moduleWidget);

zoomWidget->box.size.x = moduleWidget->box.size.x * MODEL_BOX_ZOOM;


+ 25
- 28
src/app/ModuleWidget.cpp View File

@@ -225,10 +225,10 @@ struct ModuleCloneItem : ui::MenuItem {
};


struct ModuleBypassItem : ui::MenuItem {
struct ModuleDisableItem : ui::MenuItem {
ModuleWidget* moduleWidget;
void onAction(const event::Action& e) override {
moduleWidget->bypassAction();
moduleWidget->disableAction();
}
};

@@ -253,14 +253,14 @@ ModuleWidget::~ModuleWidget() {
void ModuleWidget::draw(const DrawArgs& args) {
nvgScissor(args.vg, RECT_ARGS(args.clipBox));

if (module && module->bypass) {
if (module && module->disabled) {
nvgGlobalAlpha(args.vg, 0.33);
}

Widget::draw(args);

// Power meter
if (module && settings::cpuMeter && !module->bypass) {
if (module && settings::cpuMeter && !module->disabled) {
nvgBeginPath(args.vg);
nvgRect(args.vg,
0, box.size.y - 35,
@@ -363,7 +363,7 @@ void ModuleWidget::onHoverKey(const event::HoverKey& e) {
} break;
case GLFW_KEY_E: {
if ((e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) {
bypassAction();
disableAction();
e.consume(this);
}
} break;
@@ -419,7 +419,9 @@ void ModuleWidget::onDragMove(const event::DragMove& e) {

void ModuleWidget::setModule(engine::Module* module) {
if (this->module) {
APP->engine->removeModule(this->module);
delete this->module;
this->module = NULL;
}
this->module = module;
}
@@ -496,16 +498,11 @@ PortWidget* ModuleWidget::getInput(int inputId) {
}

json_t* ModuleWidget::toJson() {
if (!module)
return NULL;

json_t* rootJ = module->toJson();
return rootJ;
json_t* moduleJ = module->toJson();
return moduleJ;
}

void ModuleWidget::fromJson(json_t* rootJ) {
if (!module)
return;
module->fromJson(rootJ);
}

@@ -736,16 +733,16 @@ void ModuleWidget::disconnectAction() {
}

void ModuleWidget::cloneAction() {
ModuleWidget* clonedModuleWidget = model->createModuleWidget();
assert(clonedModuleWidget);
engine::Module* clonedModule = model->createModule();
// JSON serialization is the obvious way to do this
json_t* moduleJ = toJson();
clonedModuleWidget->fromJson(moduleJ);
clonedModule->fromJson(moduleJ);
json_decref(moduleJ);

// Reset ID so the Engine automatically assigns a new one
clonedModuleWidget->module->id = -1;
clonedModule->id = -1;
APP->engine->addModule(clonedModule);

ModuleWidget* clonedModuleWidget = model->createModuleWidget(clonedModule);
APP->scene->rack->addModuleAtMouse(clonedModuleWidget);

// history::ModuleAdd
@@ -755,12 +752,12 @@ void ModuleWidget::cloneAction() {
APP->history->push(h);
}

void ModuleWidget::bypassAction() {
void ModuleWidget::disableAction() {
assert(module);
// history::ModuleBypass
history::ModuleBypass* h = new history::ModuleBypass;
// history::ModuleDisable
history::ModuleDisable* h = new history::ModuleDisable;
h->moduleId = module->id;
h->bypass = !module->bypass;
h->disabled = !module->disabled;
APP->history->push(h);
h->redo();
}
@@ -826,13 +823,13 @@ void ModuleWidget::createContextMenu() {
cloneItem->moduleWidget = this;
menu->addChild(cloneItem);

ModuleBypassItem* bypassItem = new ModuleBypassItem;
bypassItem->text = "Disable";
bypassItem->rightText = RACK_MOD_CTRL_NAME "+E";
if (module && module->bypass)
bypassItem->rightText = CHECKMARK_STRING " " + bypassItem->rightText;
bypassItem->moduleWidget = this;
menu->addChild(bypassItem);
ModuleDisableItem* disableItem = new ModuleDisableItem;
disableItem->text = "Disable";
disableItem->rightText = RACK_MOD_CTRL_NAME "+E";
if (module && module->disabled)
disableItem->rightText = CHECKMARK_STRING " " + disableItem->rightText;
disableItem->moduleWidget = this;
menu->addChild(disableItem);

ModuleDeleteItem* deleteItem = new ModuleDeleteItem;
deleteItem->text = "Delete";


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

@@ -183,14 +183,6 @@ void ParamWidget::onLeave(const event::Leave& e) {
}
}

void ParamWidget::fromJson(json_t* rootJ) {
json_t* valueJ = json_object_get(rootJ, "value");
if (valueJ) {
if (paramQuantity)
paramQuantity->setValue(json_number_value(valueJ));
}
}

void ParamWidget::createContextMenu() {
ui::Menu* menu = createMenu();



+ 13
- 7
src/app/PortWidget.cpp View File

@@ -93,13 +93,16 @@ void PortWidget::onDragStart(const event::DragStart& e) {
CableWidget* cw = NULL;
if ((APP->window->getMods() & RACK_MOD_MASK) == RACK_MOD_CTRL) {
if (type == OUTPUT) {
// Ctrl-clicking an output creates a new cable.
// Keep cable NULL. Will be created below
}
else {
// Ctrl-clicking an input clones the cable already patched to it.
CableWidget* topCw = APP->scene->rack->getTopCable(this);
if (topCw) {
cw = new CableWidget;
cw->setOutput(topCw->outputPort);
cw->outputPort = topCw->outputPort;
cw->updateCable();
}
}
}
@@ -116,9 +119,10 @@ void PortWidget::onDragStart(const event::DragStart& e) {
// Disconnect and reuse existing cable
APP->scene->rack->removeCable(cw);
if (type == OUTPUT)
cw->setOutput(NULL);
cw->outputPort = NULL;
else
cw->setInput(NULL);
cw->inputPort = NULL;
cw->updateCable();
}
}

@@ -126,9 +130,10 @@ void PortWidget::onDragStart(const event::DragStart& e) {
// Create a new cable
cw = new CableWidget;
if (type == OUTPUT)
cw->setOutput(this);
cw->outputPort = this;
else
cw->setInput(this);
cw->inputPort = this;
cw->updateCable();
}

APP->scene->rack->setIncompleteCable(cw);
@@ -169,9 +174,10 @@ void PortWidget::onDragDrop(const event::DragDrop& e) {
if (cw) {
cw->hoveredOutputPort = cw->hoveredInputPort = NULL;
if (type == OUTPUT)
cw->setOutput(this);
cw->outputPort = this;
else
cw->setInput(this);
cw->inputPort = this;
cw->updateCable();
}
}



+ 106
- 121
src/app/RackWidget.cpp View File

@@ -19,29 +19,14 @@ namespace rack {
namespace app {


static ModuleWidget* moduleFromJson(json_t* moduleJ) {
// Get slugs
json_t* pluginSlugJ = json_object_get(moduleJ, "plugin");
if (!pluginSlugJ)
return NULL;
std::string pluginSlug = json_string_value(pluginSlugJ);
pluginSlug = plugin::normalizeSlug(pluginSlug);

json_t* modelSlugJ = json_object_get(moduleJ, "model");
if (!modelSlugJ)
return NULL;
std::string modelSlug = json_string_value(modelSlugJ);
modelSlug = plugin::normalizeSlug(modelSlug);

// Get Model
plugin::Model* model = plugin::getModel(pluginSlug, modelSlug);
if (!model)
return NULL;
/** Creates a new Module and ModuleWidget */
ModuleWidget* moduleWidgetFromJson(json_t* moduleJ) {
engine::Module* module = engine::moduleFromJson(moduleJ);
assert(module);

// Create ModuleWidget
ModuleWidget* moduleWidget = model->createModuleWidget();
ModuleWidget* moduleWidget = module->model->createModuleWidget(module);
assert(moduleWidget);
moduleWidget->fromJson(moduleJ);
return moduleWidget;
}

@@ -174,10 +159,7 @@ void RackWidget::clear() {
}
}

json_t* RackWidget::toJson() {
// root
json_t* rootJ = json_object();

void RackWidget::mergeJson(json_t* rootJ) {
// Get module offset so modules are aligned to (0, 0) when the patch is loaded.
math::Vec moduleOffset = math::Vec(INFINITY, INFINITY);
for (widget::Widget* w : moduleContainer->children) {
@@ -188,39 +170,46 @@ json_t* RackWidget::toJson() {
}

// modules
json_t* modulesJ = json_array();
for (widget::Widget* w : moduleContainer->children) {
ModuleWidget* moduleWidget = dynamic_cast<ModuleWidget*>(w);
assert(moduleWidget);
json_t* modulesJ = json_object_get(rootJ, "modules");
assert(modulesJ);
size_t moduleIndex;
json_t* moduleJ;
json_array_foreach(modulesJ, moduleIndex, moduleJ) {
// module
json_t* moduleJ = moduleWidget->toJson();
{
// pos
math::Vec pos = moduleWidget->box.pos.minus(moduleOffset);
pos = pos.div(RACK_GRID_SIZE).round();
json_t* posJ = json_pack("[i, i]", (int) pos.x, (int) pos.y);
json_object_set_new(moduleJ, "pos", posJ);
}
json_array_append_new(modulesJ, moduleJ);
json_t* idJ = json_object_get(moduleJ, "id");
if (!idJ)
continue;
int id = json_integer_value(idJ);
// TODO Legacy v0.6?
ModuleWidget* moduleWidget = getModule(id);
assert(moduleWidget);

// pos
math::Vec pos = moduleWidget->box.pos.minus(moduleOffset);
pos = pos.div(RACK_GRID_SIZE).round();
json_t* posJ = json_pack("[i, i]", (int) pos.x, (int) pos.y);
json_object_set_new(moduleJ, "pos", posJ);
}
json_object_set_new(rootJ, "modules", modulesJ);

// cables
json_t* cablesJ = json_array();
for (widget::Widget* w : cableContainer->children) {
CableWidget* cw = dynamic_cast<CableWidget*>(w);
assert(cw);

// Only serialize complete cables
if (!cw->isComplete())
json_t* cablesJ = json_object_get(rootJ, "cables");
assert(cablesJ);
size_t cableIndex;
json_t* cableJ;
json_array_foreach(cablesJ, cableIndex, cableJ) {
// cable
json_t* idJ = json_object_get(cableJ, "id");
if (!idJ)
continue;
int id = json_integer_value(idJ);
CableWidget* cw = getCable(id);
assert(cw);

json_t* cableJ = cw->toJson();
json_array_append_new(cablesJ, cableJ);
json_t* cwJ = cw->toJson();
// Merge cable JSON object
json_object_update(cableJ, cwJ);
json_decref(cwJ);
}
json_object_set_new(rootJ, "cables", cablesJ);

return rootJ;
}

void RackWidget::fromJson(json_t* rootJ) {
@@ -231,55 +220,62 @@ void RackWidget::fromJson(json_t* rootJ) {
size_t moduleIndex;
json_t* moduleJ;
json_array_foreach(modulesJ, moduleIndex, moduleJ) {
ModuleWidget* moduleWidget = moduleFromJson(moduleJ);
// module
// Create ModuleWidget and attach it to existing Module from Engine.
json_t* idJ = json_object_get(moduleJ, "id");
if (!idJ)
continue;
int id = json_integer_value(idJ);
engine::Module* module = APP->engine->getModule(id);
if (!module)
continue;

if (moduleWidget) {
// Before 1.0, the module ID was the index in the "modules" array
if (APP->patch->isLegacy(2)) {
moduleWidget->module->id = moduleIndex;
}
ModuleWidget* moduleWidget = module->model->createModuleWidget(module);

// pos
json_t* posJ = json_object_get(moduleJ, "pos");
double x, y;
json_unpack(posJ, "[F, F]", &x, &y);
math::Vec pos = math::Vec(x, y);
if (APP->patch->isLegacy(1)) {
// Before 0.6, positions were in pixel units
moduleWidget->box.pos = pos;
}
else {
moduleWidget->box.pos = pos.mult(RACK_GRID_SIZE);
}
moduleWidget->box.pos = moduleWidget->box.pos.plus(RACK_OFFSET);
// Before 1.0, the module ID was the index in the "modules" array
if (APP->patch->isLegacy(2)) {
module->id = moduleIndex;
}

addModule(moduleWidget);
// pos
json_t* posJ = json_object_get(moduleJ, "pos");
double x, y;
json_unpack(posJ, "[F, F]", &x, &y);
math::Vec pos = math::Vec(x, y);
if (APP->patch->isLegacy(1)) {
// In <=v0.5, positions were in pixel units
moduleWidget->box.pos = pos;
}
else {
json_t* pluginSlugJ = json_object_get(moduleJ, "plugin");
json_t* modelSlugJ = json_object_get(moduleJ, "model");
std::string pluginSlug = json_string_value(pluginSlugJ);
std::string modelSlug = json_string_value(modelSlugJ);
APP->patch->warningLog += string::f("Could not find module \"%s\" of plugin \"%s\"\n", modelSlug.c_str(), pluginSlug.c_str());
moduleWidget->box.pos = pos.mult(RACK_GRID_SIZE);
}
moduleWidget->box.pos = moduleWidget->box.pos.plus(RACK_OFFSET);

addModule(moduleWidget);
}

// cables
json_t* cablesJ = json_object_get(rootJ, "cables");
// Before 1.0, cables were called wires
// In <=v0.6, cables were called wires
if (!cablesJ)
cablesJ = json_object_get(rootJ, "wires");
assert(cablesJ);
size_t cableIndex;
json_t* cableJ;
json_array_foreach(cablesJ, cableIndex, cableJ) {
// Create a unserialize cable
// cable
// Get Cable from Engine
json_t* idJ = json_object_get(cableJ, "id");
if (!idJ)
continue;
int id = json_integer_value(idJ);
engine::Cable* cable = APP->engine->getCable(id);
if (!cable)
continue;

CableWidget* cw = new CableWidget;
cw->setCable(cable);
cw->fromJson(cableJ);
if (!cw->isComplete()) {
delete cw;
continue;
}
addCable(cw);
}
}
@@ -293,13 +289,21 @@ void RackWidget::pastePresetClipboardAction() {

json_error_t error;
json_t* moduleJ = json_loads(moduleJson, 0, &error);
if (moduleJ) {
ModuleWidget* mw = moduleFromJson(moduleJ);
if (!moduleJ) {
WARN("JSON parsing error at %s %d:%d %s", error.source, error.line, error.column, error.text);
return;
}
DEFER({
json_decref(moduleJ);
});

try {
engine::Module* module = engine::moduleFromJson(moduleJ);
// Reset ID so the Engine automatically assigns a new one
mw->module->id = -1;
module->id = -1;
APP->engine->addModule(module);

ModuleWidget* mw = module->model->createModuleWidget(module);
addModuleAtMouse(mw);

// history::ModuleAdd
@@ -307,8 +311,9 @@ void RackWidget::pastePresetClipboardAction() {
h->setModule(mw);
APP->history->push(h);
}
else {
WARN("JSON parsing error at %s %d:%d %s", error.source, error.line, error.column, error.text);
catch (Exception& e) {
WARN("%s", e.what());
return;
}
}

@@ -352,10 +357,6 @@ void RackWidget::addModule(ModuleWidget* m) {
assert(m->box.size.y == RACK_GRID_HEIGHT);
moduleContainer->addChild(m);

if (m->module) {
// Add module to Engine
APP->engine->addModule(m->module);
}
RackWidget_updateAdjacent(this);
}

@@ -378,11 +379,6 @@ void RackWidget::removeModule(ModuleWidget* m) {
// Disconnect cables
m->disconnect();

if (m->module) {
// Remove module from Engine
APP->engine->removeModule(m->module);
}

// Remove module from ModuleContainer
moduleContainer->removeChild(m);
}
@@ -556,14 +552,6 @@ history::ComplexAction* RackWidget::getModuleDragAction() {


void RackWidget::clearCables() {
for (widget::Widget* w : cableContainer->children) {
CableWidget* cw = dynamic_cast<CableWidget*>(w);
assert(cw);
if (!cw->isComplete())
continue;

APP->engine->removeCable(cw->cable);
}
incompleteCable = NULL;
cableContainer->clearChildren();
}
@@ -603,27 +591,25 @@ void RackWidget::clearCablesOnPort(PortWidget* port) {
}
}

void RackWidget::addCable(CableWidget* w) {
assert(w->isComplete());
APP->engine->addCable(w->cable);
cableContainer->addChild(w);
void RackWidget::addCable(CableWidget* cw) {
assert(cw->isComplete());
cableContainer->addChild(cw);
}

void RackWidget::removeCable(CableWidget* w) {
assert(w->isComplete());
APP->engine->removeCable(w->cable);
cableContainer->removeChild(w);
void RackWidget::removeCable(CableWidget* cw) {
assert(cw->isComplete());
cableContainer->removeChild(cw);
}

void RackWidget::setIncompleteCable(CableWidget* w) {
void RackWidget::setIncompleteCable(CableWidget* cw) {
if (incompleteCable) {
cableContainer->removeChild(incompleteCable);
delete incompleteCable;
incompleteCable = NULL;
}
if (w) {
cableContainer->addChild(w);
incompleteCable = w;
if (cw) {
cableContainer->addChild(cw);
incompleteCable = cw;
}
}

@@ -641,9 +627,6 @@ CableWidget* RackWidget::getTopCable(PortWidget* port) {
for (auto it = cableContainer->children.rbegin(); it != cableContainer->children.rend(); it++) {
CableWidget* cw = dynamic_cast<CableWidget*>(*it);
assert(cw);
// Ignore incomplete cables
if (!cw->isComplete())
continue;
if (cw->inputPort == port || cw->outputPort == port)
return cw;
}
@@ -654,6 +637,8 @@ CableWidget* RackWidget::getCable(int cableId) {
for (widget::Widget* w : cableContainer->children) {
CableWidget* cw = dynamic_cast<CableWidget*>(w);
assert(cw);
if (!cw->cable)
continue;
if (cw->cable->id == cableId)
return cw;
}
@@ -662,15 +647,15 @@ CableWidget* RackWidget::getCable(int cableId) {

std::list<CableWidget*> RackWidget::getCablesOnPort(PortWidget* port) {
assert(port);
std::list<CableWidget*> cables;
std::list<CableWidget*> cws;
for (widget::Widget* w : cableContainer->children) {
CableWidget* cw = dynamic_cast<CableWidget*>(w);
assert(cw);
if (cw->inputPort == port || cw->outputPort == port) {
cables.push_back(cw);
cws.push_back(cw);
}
}
return cables;
return cws;
}




+ 326
- 261
src/core/AudioInterface.cpp View File

@@ -12,17 +12,24 @@ namespace rack {
namespace core {


template <int AUDIO_OUTPUTS, int AUDIO_INPUTS>
template <int NUM_AUDIO_OUTPUTS, int NUM_AUDIO_INPUTS>
struct AudioInterfacePort : audio::Port {
std::mutex engineMutex;
std::condition_variable engineCv;
std::mutex audioMutex;
std::condition_variable audioCv;
// Audio thread produces, engine thread consumes
dsp::DoubleRingBuffer < dsp::Frame<AUDIO_INPUTS>, (1 << 15) > inputBuffer;
// Audio thread consumes, engine thread produces
dsp::DoubleRingBuffer < dsp::Frame<AUDIO_OUTPUTS>, (1 << 15) > outputBuffer;
bool active = false;
// std::mutex engineMutex;
// std::condition_variable engineCv;
// std::mutex audioMutex;
// std::condition_variable audioCv;
// // Audio thread produces, engine thread consumes
// dsp::DoubleRingBuffer < dsp::Frame<NUM_AUDIO_INPUTS>, (1 << 15) > inputBuffer;
// // Audio thread consumes, engine thread produces
// dsp::DoubleRingBuffer < dsp::Frame<NUM_AUDIO_OUTPUTS>, (1 << 15) > outputBuffer;
// bool active = false;

// For checking getPrimaryModule()
Module* module = NULL;
const float* input = NULL;
float* output = NULL;
int frame = 0;
int numFrames = 0;

~AudioInterfacePort() {
// Close stream here before destructing AudioInterfacePort, so the mutexes are still valid when waiting to close.
@@ -30,54 +37,67 @@ struct AudioInterfacePort : audio::Port {
}

void processStream(const float* input, float* output, int frames) override {
// Reactivate idle stream
if (!active) {
active = true;
inputBuffer.clear();
outputBuffer.clear();
if (APP->engine->getPrimaryModule() != module) {
// TEMP
std::memset(output, 0, sizeof(float) * frames * numOutputs);
return;
}

if (numInputs > 0) {
// TODO Do we need to wait on the input to be consumed here? Experimentally, it works fine if we don't.
for (int i = 0; i < frames; i++) {
if (inputBuffer.full())
break;
dsp::Frame<AUDIO_INPUTS> inputFrame;
std::memset(&inputFrame, 0, sizeof(inputFrame));
std::memcpy(&inputFrame, &input[numInputs * i], numInputs * sizeof(float));
inputBuffer.push(inputFrame);
}
}

if (numOutputs > 0) {
std::unique_lock<std::mutex> lock(audioMutex);
auto cond = [&] {
return (outputBuffer.size() >= (size_t) frames);
};
auto timeout = std::chrono::milliseconds(100);
if (audioCv.wait_for(lock, timeout, cond)) {
// Consume audio block
for (int i = 0; i < frames; i++) {
dsp::Frame<AUDIO_OUTPUTS> f = outputBuffer.shift();
for (int j = 0; j < numOutputs; j++) {
output[numOutputs * i + j] = clamp(f.samples[j], -1.f, 1.f);
}
}
}
else {
// Timed out, fill output with zeros
std::memset(output, 0, frames * numOutputs * sizeof(float));
// DEBUG("Audio Interface Port underflow");
}
}

// Notify engine when finished processing
engineCv.notify_one();
frame = 0;
numFrames = frames;
this->input = input;
this->output = output;

APP->engine->step(frames);

// // Reactivate idle stream
// if (!active) {
// active = true;
// inputBuffer.clear();
// outputBuffer.clear();
// }

// if (numInputs > 0) {
// // TODO Do we need to wait on the input to be consumed here? Experimentally, it works fine if we don't.
// for (int i = 0; i < frames; i++) {
// if (inputBuffer.full())
// break;
// dsp::Frame<NUM_AUDIO_INPUTS> inputFrame;
// std::memset(&inputFrame, 0, sizeof(inputFrame));
// std::memcpy(&inputFrame, &input[numInputs * i], numInputs * sizeof(float));
// inputBuffer.push(inputFrame);
// }
// }

// if (numOutputs > 0) {
// std::unique_lock<std::mutex> lock(audioMutex);
// auto cond = [&] {
// return (outputBuffer.size() >= (size_t) frames);
// };
// auto timeout = std::chrono::milliseconds(100);
// if (audioCv.wait_for(lock, timeout, cond)) {
// // Consume audio block
// for (int i = 0; i < frames; i++) {
// dsp::Frame<NUM_AUDIO_OUTPUTS> f = outputBuffer.shift();
// for (int j = 0; j < numOutputs; j++) {
// output[numOutputs * i + j] = clamp(f.samples[j], -1.f, 1.f);
// }
// }
// }
// else {
// // Timed out, fill output with zeros
// std::memset(output, 0, frames * numOutputs * sizeof(float));
// // DEBUG("Audio Interface Port underflow");
// }
// }

// // Notify engine when finished processing
// engineCv.notify_one();
}

void onCloseStream() override {
inputBuffer.clear();
outputBuffer.clear();
// inputBuffer.clear();
// outputBuffer.clear();
}

void onChannelsChange() override {
@@ -85,136 +105,161 @@ struct AudioInterfacePort : audio::Port {
};


template <int AUDIO_OUTPUTS, int AUDIO_INPUTS>
template <int NUM_AUDIO_OUTPUTS, int NUM_AUDIO_INPUTS>
struct AudioInterface : Module {
enum ParamIds {
NUM_PARAMS
};
enum InputIds {
ENUMS(AUDIO_INPUT, AUDIO_INPUTS),
ENUMS(AUDIO_INPUTS, NUM_AUDIO_INPUTS),
NUM_INPUTS
};
enum OutputIds {
ENUMS(AUDIO_OUTPUT, AUDIO_OUTPUTS),
ENUMS(AUDIO_OUTPUTS, NUM_AUDIO_OUTPUTS),
NUM_OUTPUTS
};
enum LightIds {
ENUMS(INPUT_LIGHT, AUDIO_INPUTS / 2),
ENUMS(OUTPUT_LIGHT, AUDIO_OUTPUTS / 2),
ENUMS(INPUT_LIGHTS, NUM_AUDIO_INPUTS / 2),
ENUMS(OUTPUT_LIGHTS, NUM_AUDIO_OUTPUTS / 2),
NUM_LIGHTS
};

AudioInterfacePort<AUDIO_OUTPUTS, AUDIO_INPUTS> port;
int lastSampleRate = 0;
int lastNumOutputs = -1;
int lastNumInputs = -1;
AudioInterfacePort<NUM_AUDIO_OUTPUTS, NUM_AUDIO_INPUTS> port;
// int lastSampleRate = 0;
// int lastNumOutputs = -1;
// int lastNumInputs = -1;

dsp::SampleRateConverter<AUDIO_INPUTS> inputSrc;
dsp::SampleRateConverter<AUDIO_OUTPUTS> outputSrc;
// dsp::SampleRateConverter<NUM_AUDIO_INPUTS> inputSrc;
// dsp::SampleRateConverter<NUM_AUDIO_OUTPUTS> outputSrc;

// in rack's sample rate
dsp::DoubleRingBuffer<dsp::Frame<AUDIO_INPUTS>, 16> inputBuffer;
dsp::DoubleRingBuffer<dsp::Frame<AUDIO_OUTPUTS>, 16> outputBuffer;
// // in rack's sample rate
// dsp::DoubleRingBuffer<dsp::Frame<NUM_AUDIO_INPUTS>, 16> inputBuffer;
// dsp::DoubleRingBuffer<dsp::Frame<NUM_AUDIO_OUTPUTS>, 16> outputBuffer;

AudioInterface() {
config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
port.maxChannels = std::max(AUDIO_OUTPUTS, AUDIO_INPUTS);
port.maxChannels = std::max(NUM_AUDIO_OUTPUTS, NUM_AUDIO_INPUTS);
port.module = this;
onSampleRateChange();
}

void process(const ProcessArgs& args) override {
// Update SRC states
inputSrc.setRates(port.sampleRate, args.sampleRate);
outputSrc.setRates(args.sampleRate, port.sampleRate);

inputSrc.setChannels(port.numInputs);
outputSrc.setChannels(port.numOutputs);

// Inputs: audio engine -> rack engine
if (port.active && port.numInputs > 0) {
// Wait until inputs are present
// Give up after a timeout in case the audio device is being unresponsive.
std::unique_lock<std::mutex> lock(port.engineMutex);
auto cond = [&] {
return (!port.inputBuffer.empty());
};
auto timeout = std::chrono::milliseconds(200);
if (port.engineCv.wait_for(lock, timeout, cond)) {
// Convert inputs
int inLen = port.inputBuffer.size();
int outLen = inputBuffer.capacity();
inputSrc.process(port.inputBuffer.startData(), &inLen, inputBuffer.endData(), &outLen);
port.inputBuffer.startIncr(inLen);
inputBuffer.endIncr(outLen);
}
else {
// Give up on pulling input
port.active = false;
// DEBUG("Audio Interface underflow");
}
// Claim primary module if there is none
if (!APP->engine->getPrimaryModule()) {
APP->engine->setPrimaryModule(this);
}

// Take input from buffer
dsp::Frame<AUDIO_INPUTS> inputFrame;
if (!inputBuffer.empty()) {
inputFrame = inputBuffer.shift();
}
else {
std::memset(&inputFrame, 0, sizeof(inputFrame));
// Get inputs
for (int i = 0; i < port.numOutputs; i++) {
float v = inputs[AUDIO_INPUTS + i].getVoltage() / 10.f;
port.output[port.frame * port.numOutputs + i] = v;
}

// Set outputs
for (int i = 0; i < port.numInputs; i++) {
outputs[AUDIO_OUTPUT + i].setVoltage(10.f * inputFrame.samples[i]);
float v = port.input[port.frame * port.numInputs + i];
outputs[AUDIO_OUTPUTS + i].setVoltage(10.f * v);
}
for (int i = port.numInputs; i < AUDIO_INPUTS; i++) {
outputs[AUDIO_OUTPUT + i].setVoltage(0.f);
for (int i = port.numInputs; i < NUM_AUDIO_INPUTS; i++) {
outputs[AUDIO_OUTPUTS + i].setVoltage(0.f);
}

// Outputs: rack engine -> audio engine
if (port.active && port.numOutputs > 0) {
// Get and push output SRC frame
if (!outputBuffer.full()) {
dsp::Frame<AUDIO_OUTPUTS> outputFrame;
for (int i = 0; i < AUDIO_OUTPUTS; i++) {
outputFrame.samples[i] = inputs[AUDIO_INPUT + i].getVoltageSum() / 10.f;
}
outputBuffer.push(outputFrame);
}

if (outputBuffer.full()) {
// Wait until enough outputs are consumed
// Give up after a timeout in case the audio device is being unresponsive.
auto cond = [&] {
return (port.outputBuffer.size() < (size_t) port.blockSize);
};
if (!cond())
APP->engine->yieldWorkers();
std::unique_lock<std::mutex> lock(port.engineMutex);
auto timeout = std::chrono::milliseconds(200);
if (port.engineCv.wait_for(lock, timeout, cond)) {
// Push converted output
int inLen = outputBuffer.size();
int outLen = port.outputBuffer.capacity();
outputSrc.process(outputBuffer.startData(), &inLen, port.outputBuffer.endData(), &outLen);
outputBuffer.startIncr(inLen);
port.outputBuffer.endIncr(outLen);
}
else {
// Give up on pushing output
port.active = false;
outputBuffer.clear();
// DEBUG("Audio Interface underflow");
}
}

// Notify audio thread that an output is potentially ready
port.audioCv.notify_one();
}
port.frame++;

// // Update SRC states
// inputSrc.setRates(port.sampleRate, args.sampleRate);
// outputSrc.setRates(args.sampleRate, port.sampleRate);

// inputSrc.setChannels(port.numInputs);
// outputSrc.setChannels(port.numOutputs);

// // Inputs: audio engine -> rack engine
// if (port.active && port.numInputs > 0) {
// // Wait until inputs are present
// // Give up after a timeout in case the audio device is being unresponsive.
// std::unique_lock<std::mutex> lock(port.engineMutex);
// auto cond = [&] {
// return (!port.inputBuffer.empty());
// };
// auto timeout = std::chrono::milliseconds(200);
// if (port.engineCv.wait_for(lock, timeout, cond)) {
// // Convert inputs
// int inLen = port.inputBuffer.size();
// int outLen = inputBuffer.capacity();
// inputSrc.process(port.inputBuffer.startData(), &inLen, inputBuffer.endData(), &outLen);
// port.inputBuffer.startIncr(inLen);
// inputBuffer.endIncr(outLen);
// }
// else {
// // Give up on pulling input
// port.active = false;
// // DEBUG("Audio Interface underflow");
// }
// }

// // Take input from buffer
// dsp::Frame<NUM_AUDIO_INPUTS> inputFrame;
// if (!inputBuffer.empty()) {
// inputFrame = inputBuffer.shift();
// }
// else {
// std::memset(&inputFrame, 0, sizeof(inputFrame));
// }
// for (int i = 0; i < port.numInputs; i++) {
// outputs[AUDIO_OUTPUTS + i].setVoltage(10.f * inputFrame.samples[i]);
// }
// for (int i = port.numInputs; i < NUM_AUDIO_INPUTS; i++) {
// outputs[AUDIO_OUTPUTS + i].setVoltage(0.f);
// }

// // Outputs: rack engine -> audio engine
// if (port.active && port.numOutputs > 0) {
// // Get and push output SRC frame
// if (!outputBuffer.full()) {
// dsp::Frame<NUM_AUDIO_OUTPUTS> outputFrame;
// for (int i = 0; i < NUM_AUDIO_OUTPUTS; i++) {
// outputFrame.samples[i] = inputs[AUDIO_INPUTS + i].getVoltageSum() / 10.f;
// }
// outputBuffer.push(outputFrame);
// }

// if (outputBuffer.full()) {
// // Wait until enough outputs are consumed
// // Give up after a timeout in case the audio device is being unresponsive.
// auto cond = [&] {
// return (port.outputBuffer.size() < (size_t) port.blockSize);
// };
// if (!cond())
// APP->engine->yieldWorkers();
// std::unique_lock<std::mutex> lock(port.engineMutex);
// auto timeout = std::chrono::milliseconds(200);
// if (port.engineCv.wait_for(lock, timeout, cond)) {
// // Push converted output
// int inLen = outputBuffer.size();
// int outLen = port.outputBuffer.capacity();
// outputSrc.process(outputBuffer.startData(), &inLen, port.outputBuffer.endData(), &outLen);
// outputBuffer.startIncr(inLen);
// port.outputBuffer.endIncr(outLen);
// }
// else {
// // Give up on pushing output
// port.active = false;
// outputBuffer.clear();
// // DEBUG("Audio Interface underflow");
// }
// }

// // Notify audio thread that an output is potentially ready
// port.audioCv.notify_one();
// }

// Turn on light if at least one port is enabled in the nearby pair
for (int i = 0; i < AUDIO_INPUTS / 2; i++)
lights[INPUT_LIGHT + i].setBrightness(port.active && port.numOutputs >= 2 * i + 1);
for (int i = 0; i < AUDIO_OUTPUTS / 2; i++)
lights[OUTPUT_LIGHT + i].setBrightness(port.active && port.numInputs >= 2 * i + 1);
for (int i = 0; i < NUM_AUDIO_INPUTS / 2; i++) {
lights[INPUT_LIGHTS + i].setBrightness(port.numOutputs >= 2 * i + 1);
}
for (int i = 0; i < NUM_AUDIO_OUTPUTS / 2; i++) {
lights[OUTPUT_LIGHTS + i].setBrightness(port.numInputs >= 2 * i + 1);
}
}

json_t* dataToJson() override {
@@ -225,7 +270,8 @@ struct AudioInterface : Module {

void dataFromJson(json_t* rootJ) override {
json_t* audioJ = json_object_get(rootJ, "audio");
port.fromJson(audioJ);
if (audioJ)
port.fromJson(audioJ);
}

void onReset() override {
@@ -234,126 +280,145 @@ struct AudioInterface : Module {
};


struct AudioInterface8Widget : ModuleWidget {
typedef AudioInterface<8, 8> TAudioInterface;
template <typename TAudioInterface>
struct PrimaryModuleItem : MenuItem {
TAudioInterface* module;

AudioInterface8Widget(TAudioInterface* module) {
setModule(module);
setPanel(APP->window->loadSvg(asset::system("res/Core/AudioInterface.svg")));

addChild(createWidget<ScrewSilver>(Vec(RACK_GRID_WIDTH, 0)));
addChild(createWidget<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0)));
addChild(createWidget<ScrewSilver>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
addChild(createWidget<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));

addInput(createInput<PJ301MPort>(mm2px(Vec(3.7069211, 55.530807)), module, TAudioInterface::AUDIO_INPUT + 0));
addInput(createInput<PJ301MPort>(mm2px(Vec(15.307249, 55.530807)), module, TAudioInterface::AUDIO_INPUT + 1));
addInput(createInput<PJ301MPort>(mm2px(Vec(26.906193, 55.530807)), module, TAudioInterface::AUDIO_INPUT + 2));
addInput(createInput<PJ301MPort>(mm2px(Vec(38.506519, 55.530807)), module, TAudioInterface::AUDIO_INPUT + 3));
addInput(createInput<PJ301MPort>(mm2px(Vec(3.7069209, 70.144905)), module, TAudioInterface::AUDIO_INPUT + 4));
addInput(createInput<PJ301MPort>(mm2px(Vec(15.307249, 70.144905)), module, TAudioInterface::AUDIO_INPUT + 5));
addInput(createInput<PJ301MPort>(mm2px(Vec(26.906193, 70.144905)), module, TAudioInterface::AUDIO_INPUT + 6));
addInput(createInput<PJ301MPort>(mm2px(Vec(38.506519, 70.144905)), module, TAudioInterface::AUDIO_INPUT + 7));

addOutput(createOutput<PJ301MPort>(mm2px(Vec(3.7069209, 92.143906)), module, TAudioInterface::AUDIO_OUTPUT + 0));
addOutput(createOutput<PJ301MPort>(mm2px(Vec(15.307249, 92.143906)), module, TAudioInterface::AUDIO_OUTPUT + 1));
addOutput(createOutput<PJ301MPort>(mm2px(Vec(26.906193, 92.143906)), module, TAudioInterface::AUDIO_OUTPUT + 2));
addOutput(createOutput<PJ301MPort>(mm2px(Vec(38.506519, 92.143906)), module, TAudioInterface::AUDIO_OUTPUT + 3));
addOutput(createOutput<PJ301MPort>(mm2px(Vec(3.7069209, 108.1443)), module, TAudioInterface::AUDIO_OUTPUT + 4));
addOutput(createOutput<PJ301MPort>(mm2px(Vec(15.307249, 108.1443)), module, TAudioInterface::AUDIO_OUTPUT + 5));
addOutput(createOutput<PJ301MPort>(mm2px(Vec(26.906193, 108.1443)), module, TAudioInterface::AUDIO_OUTPUT + 6));
addOutput(createOutput<PJ301MPort>(mm2px(Vec(38.506523, 108.1443)), module, TAudioInterface::AUDIO_OUTPUT + 7));

addChild(createLight<SmallLight<GreenLight>>(mm2px(Vec(12.524985, 54.577202)), module, TAudioInterface::INPUT_LIGHT + 0));
addChild(createLight<SmallLight<GreenLight>>(mm2px(Vec(35.725647, 54.577202)), module, TAudioInterface::INPUT_LIGHT + 1));
addChild(createLight<SmallLight<GreenLight>>(mm2px(Vec(12.524985, 69.158226)), module, TAudioInterface::INPUT_LIGHT + 2));
addChild(createLight<SmallLight<GreenLight>>(mm2px(Vec(35.725647, 69.158226)), module, TAudioInterface::INPUT_LIGHT + 3));
addChild(createLight<SmallLight<GreenLight>>(mm2px(Vec(12.524985, 91.147583)), module, TAudioInterface::OUTPUT_LIGHT + 0));
addChild(createLight<SmallLight<GreenLight>>(mm2px(Vec(35.725647, 91.147583)), module, TAudioInterface::OUTPUT_LIGHT + 1));
addChild(createLight<SmallLight<GreenLight>>(mm2px(Vec(12.524985, 107.17003)), module, TAudioInterface::OUTPUT_LIGHT + 2));
addChild(createLight<SmallLight<GreenLight>>(mm2px(Vec(35.725647, 107.17003)), module, TAudioInterface::OUTPUT_LIGHT + 3));

AudioWidget* audioWidget = createWidget<AudioWidget>(mm2px(Vec(3.2122073, 14.837339)));
audioWidget->box.size = mm2px(Vec(44, 28));
audioWidget->setAudioPort(module ? &module->port : NULL);
addChild(audioWidget);
void onAction(const event::Action& e) override {
APP->engine->setPrimaryModule(module);
}
};


struct AudioInterface16Widget : ModuleWidget {
typedef AudioInterface<16, 16> TAudioInterface;
template <int NUM_AUDIO_OUTPUTS, int NUM_AUDIO_INPUTS>
struct AudioInterfaceWidget : ModuleWidget {
typedef AudioInterface<NUM_AUDIO_OUTPUTS, NUM_AUDIO_INPUTS> TAudioInterface;

AudioInterface16Widget(TAudioInterface* module) {
AudioInterfaceWidget(TAudioInterface* module) {
setModule(module);
setPanel(APP->window->loadSvg(asset::system("res/Core/AudioInterface16.svg")));

addChild(createWidget<ScrewSilver>(Vec(RACK_GRID_WIDTH, 0)));
addChild(createWidget<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0)));
addChild(createWidget<ScrewSilver>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
addChild(createWidget<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));

addInput(createInputCentered<PJ301MPort>(mm2px(Vec(7.661, 59.638)), module, TAudioInterface::AUDIO_INPUT + 0));
addInput(createInputCentered<PJ301MPort>(mm2px(Vec(19.26, 59.638)), module, TAudioInterface::AUDIO_INPUT + 1));
addInput(createInputCentered<PJ301MPort>(mm2px(Vec(30.86, 59.638)), module, TAudioInterface::AUDIO_INPUT + 2));
addInput(createInputCentered<PJ301MPort>(mm2px(Vec(42.461, 59.638)), module, TAudioInterface::AUDIO_INPUT + 3));
addInput(createInputCentered<PJ301MPort>(mm2px(Vec(54.06, 59.638)), module, TAudioInterface::AUDIO_INPUT + 4));
addInput(createInputCentered<PJ301MPort>(mm2px(Vec(65.661, 59.638)), module, TAudioInterface::AUDIO_INPUT + 5));
addInput(createInputCentered<PJ301MPort>(mm2px(Vec(77.26, 59.638)), module, TAudioInterface::AUDIO_INPUT + 6));
addInput(createInputCentered<PJ301MPort>(mm2px(Vec(88.86, 59.638)), module, TAudioInterface::AUDIO_INPUT + 7));
addInput(createInputCentered<PJ301MPort>(mm2px(Vec(7.661, 74.251)), module, TAudioInterface::AUDIO_INPUT + 8));
addInput(createInputCentered<PJ301MPort>(mm2px(Vec(19.26, 74.251)), module, TAudioInterface::AUDIO_INPUT + 9));
addInput(createInputCentered<PJ301MPort>(mm2px(Vec(30.86, 74.251)), module, TAudioInterface::AUDIO_INPUT + 10));
addInput(createInputCentered<PJ301MPort>(mm2px(Vec(42.461, 74.251)), module, TAudioInterface::AUDIO_INPUT + 11));
addInput(createInputCentered<PJ301MPort>(mm2px(Vec(54.06, 74.251)), module, TAudioInterface::AUDIO_INPUT + 12));
addInput(createInputCentered<PJ301MPort>(mm2px(Vec(65.661, 74.251)), module, TAudioInterface::AUDIO_INPUT + 13));
addInput(createInputCentered<PJ301MPort>(mm2px(Vec(77.26, 74.251)), module, TAudioInterface::AUDIO_INPUT + 14));
addInput(createInputCentered<PJ301MPort>(mm2px(Vec(88.86, 74.251)), module, TAudioInterface::AUDIO_INPUT + 15));

addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(7.661, 96.251)), module, TAudioInterface::AUDIO_OUTPUT + 0));
addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(19.26, 96.251)), module, TAudioInterface::AUDIO_OUTPUT + 1));
addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(30.86, 96.251)), module, TAudioInterface::AUDIO_OUTPUT + 2));
addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(42.461, 96.251)), module, TAudioInterface::AUDIO_OUTPUT + 3));
addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(54.06, 96.251)), module, TAudioInterface::AUDIO_OUTPUT + 4));
addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(65.661, 96.251)), module, TAudioInterface::AUDIO_OUTPUT + 5));
addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(77.26, 96.251)), module, TAudioInterface::AUDIO_OUTPUT + 6));
addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(88.86, 96.251)), module, TAudioInterface::AUDIO_OUTPUT + 7));
addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(7.661, 112.252)), module, TAudioInterface::AUDIO_OUTPUT + 8));
addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(19.26, 112.252)), module, TAudioInterface::AUDIO_OUTPUT + 9));
addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(30.86, 112.252)), module, TAudioInterface::AUDIO_OUTPUT + 10));
addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(42.461, 112.252)), module, TAudioInterface::AUDIO_OUTPUT + 11));
addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(54.06, 112.252)), module, TAudioInterface::AUDIO_OUTPUT + 12));
addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(65.661, 112.252)), module, TAudioInterface::AUDIO_OUTPUT + 13));
addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(77.26, 112.252)), module, TAudioInterface::AUDIO_OUTPUT + 14));
addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(88.86, 112.252)), module, TAudioInterface::AUDIO_OUTPUT + 15));

addChild(createLightCentered<SmallLight<GreenLight>>(mm2px(Vec(13.46, 55.667)), module, TAudioInterface::INPUT_LIGHT + 0));
addChild(createLightCentered<SmallLight<GreenLight>>(mm2px(Vec(36.661, 55.667)), module, TAudioInterface::INPUT_LIGHT + 1));
addChild(createLightCentered<SmallLight<GreenLight>>(mm2px(Vec(59.861, 55.667)), module, TAudioInterface::INPUT_LIGHT + 2));
addChild(createLightCentered<SmallLight<GreenLight>>(mm2px(Vec(83.061, 55.667)), module, TAudioInterface::INPUT_LIGHT + 3));
addChild(createLightCentered<SmallLight<GreenLight>>(mm2px(Vec(13.46, 70.248)), module, TAudioInterface::INPUT_LIGHT + 4));
addChild(createLightCentered<SmallLight<GreenLight>>(mm2px(Vec(36.661, 70.248)), module, TAudioInterface::INPUT_LIGHT + 5));
addChild(createLightCentered<SmallLight<GreenLight>>(mm2px(Vec(59.861, 70.248)), module, TAudioInterface::INPUT_LIGHT + 6));
addChild(createLightCentered<SmallLight<GreenLight>>(mm2px(Vec(83.061, 70.248)), module, TAudioInterface::INPUT_LIGHT + 7));
addChild(createLightCentered<SmallLight<GreenLight>>(mm2px(Vec(13.46, 92.238)), module, TAudioInterface::OUTPUT_LIGHT + 0));
addChild(createLightCentered<SmallLight<GreenLight>>(mm2px(Vec(36.661, 92.238)), module, TAudioInterface::OUTPUT_LIGHT + 1));
addChild(createLightCentered<SmallLight<GreenLight>>(mm2px(Vec(59.861, 92.238)), module, TAudioInterface::OUTPUT_LIGHT + 2));
addChild(createLightCentered<SmallLight<GreenLight>>(mm2px(Vec(83.061, 92.238)), module, TAudioInterface::OUTPUT_LIGHT + 3));
addChild(createLightCentered<SmallLight<GreenLight>>(mm2px(Vec(13.46, 108.259)), module, TAudioInterface::OUTPUT_LIGHT + 4));
addChild(createLightCentered<SmallLight<GreenLight>>(mm2px(Vec(36.661, 108.259)), module, TAudioInterface::OUTPUT_LIGHT + 5));
addChild(createLightCentered<SmallLight<GreenLight>>(mm2px(Vec(59.861, 108.259)), module, TAudioInterface::OUTPUT_LIGHT + 6));
addChild(createLightCentered<SmallLight<GreenLight>>(mm2px(Vec(83.061, 108.259)), module, TAudioInterface::OUTPUT_LIGHT + 7));

AudioWidget* audioWidget = createWidget<AudioWidget>(mm2px(Vec(2.57, 14.839)));
audioWidget->box.size = mm2px(Vec(91.382, 28.0));
audioWidget->setAudioPort(module ? &module->port : NULL);
addChild(audioWidget);

if (NUM_AUDIO_OUTPUTS == 8 && NUM_AUDIO_INPUTS == 8) {
setPanel(APP->window->loadSvg(asset::system("res/Core/AudioInterface.svg")));

addChild(createWidget<ScrewSilver>(Vec(RACK_GRID_WIDTH, 0)));
addChild(createWidget<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0)));
addChild(createWidget<ScrewSilver>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
addChild(createWidget<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));

addInput(createInput<PJ301MPort>(mm2px(Vec(3.7069211, 55.530807)), module, TAudioInterface::AUDIO_INPUTS + 0));
addInput(createInput<PJ301MPort>(mm2px(Vec(15.307249, 55.530807)), module, TAudioInterface::AUDIO_INPUTS + 1));
addInput(createInput<PJ301MPort>(mm2px(Vec(26.906193, 55.530807)), module, TAudioInterface::AUDIO_INPUTS + 2));
addInput(createInput<PJ301MPort>(mm2px(Vec(38.506519, 55.530807)), module, TAudioInterface::AUDIO_INPUTS + 3));
addInput(createInput<PJ301MPort>(mm2px(Vec(3.7069209, 70.144905)), module, TAudioInterface::AUDIO_INPUTS + 4));
addInput(createInput<PJ301MPort>(mm2px(Vec(15.307249, 70.144905)), module, TAudioInterface::AUDIO_INPUTS + 5));
addInput(createInput<PJ301MPort>(mm2px(Vec(26.906193, 70.144905)), module, TAudioInterface::AUDIO_INPUTS + 6));
addInput(createInput<PJ301MPort>(mm2px(Vec(38.506519, 70.144905)), module, TAudioInterface::AUDIO_INPUTS + 7));

addOutput(createOutput<PJ301MPort>(mm2px(Vec(3.7069209, 92.143906)), module, TAudioInterface::AUDIO_OUTPUTS + 0));
addOutput(createOutput<PJ301MPort>(mm2px(Vec(15.307249, 92.143906)), module, TAudioInterface::AUDIO_OUTPUTS + 1));
addOutput(createOutput<PJ301MPort>(mm2px(Vec(26.906193, 92.143906)), module, TAudioInterface::AUDIO_OUTPUTS + 2));
addOutput(createOutput<PJ301MPort>(mm2px(Vec(38.506519, 92.143906)), module, TAudioInterface::AUDIO_OUTPUTS + 3));
addOutput(createOutput<PJ301MPort>(mm2px(Vec(3.7069209, 108.1443)), module, TAudioInterface::AUDIO_OUTPUTS + 4));
addOutput(createOutput<PJ301MPort>(mm2px(Vec(15.307249, 108.1443)), module, TAudioInterface::AUDIO_OUTPUTS + 5));
addOutput(createOutput<PJ301MPort>(mm2px(Vec(26.906193, 108.1443)), module, TAudioInterface::AUDIO_OUTPUTS + 6));
addOutput(createOutput<PJ301MPort>(mm2px(Vec(38.506523, 108.1443)), module, TAudioInterface::AUDIO_OUTPUTS + 7));

addChild(createLight<SmallLight<GreenLight>>(mm2px(Vec(12.524985, 54.577202)), module, TAudioInterface::INPUT_LIGHTS + 0));
addChild(createLight<SmallLight<GreenLight>>(mm2px(Vec(35.725647, 54.577202)), module, TAudioInterface::INPUT_LIGHTS + 1));
addChild(createLight<SmallLight<GreenLight>>(mm2px(Vec(12.524985, 69.158226)), module, TAudioInterface::INPUT_LIGHTS + 2));
addChild(createLight<SmallLight<GreenLight>>(mm2px(Vec(35.725647, 69.158226)), module, TAudioInterface::INPUT_LIGHTS + 3));
addChild(createLight<SmallLight<GreenLight>>(mm2px(Vec(12.524985, 91.147583)), module, TAudioInterface::OUTPUT_LIGHTS + 0));
addChild(createLight<SmallLight<GreenLight>>(mm2px(Vec(35.725647, 91.147583)), module, TAudioInterface::OUTPUT_LIGHTS + 1));
addChild(createLight<SmallLight<GreenLight>>(mm2px(Vec(12.524985, 107.17003)), module, TAudioInterface::OUTPUT_LIGHTS + 2));
addChild(createLight<SmallLight<GreenLight>>(mm2px(Vec(35.725647, 107.17003)), module, TAudioInterface::OUTPUT_LIGHTS + 3));

AudioWidget* audioWidget = createWidget<AudioWidget>(mm2px(Vec(3.2122073, 14.837339)));
audioWidget->box.size = mm2px(Vec(44, 28));
audioWidget->setAudioPort(module ? &module->port : NULL);
addChild(audioWidget);
}
else if (NUM_AUDIO_OUTPUTS == 16 && NUM_AUDIO_INPUTS == 16) {
setPanel(APP->window->loadSvg(asset::system("res/Core/AudioInterface16.svg")));

addChild(createWidget<ScrewSilver>(Vec(RACK_GRID_WIDTH, 0)));
addChild(createWidget<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0)));
addChild(createWidget<ScrewSilver>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
addChild(createWidget<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));

addInput(createInputCentered<PJ301MPort>(mm2px(Vec(7.661, 59.638)), module, TAudioInterface::AUDIO_INPUTS + 0));
addInput(createInputCentered<PJ301MPort>(mm2px(Vec(19.26, 59.638)), module, TAudioInterface::AUDIO_INPUTS + 1));
addInput(createInputCentered<PJ301MPort>(mm2px(Vec(30.86, 59.638)), module, TAudioInterface::AUDIO_INPUTS + 2));
addInput(createInputCentered<PJ301MPort>(mm2px(Vec(42.461, 59.638)), module, TAudioInterface::AUDIO_INPUTS + 3));
addInput(createInputCentered<PJ301MPort>(mm2px(Vec(54.06, 59.638)), module, TAudioInterface::AUDIO_INPUTS + 4));
addInput(createInputCentered<PJ301MPort>(mm2px(Vec(65.661, 59.638)), module, TAudioInterface::AUDIO_INPUTS + 5));
addInput(createInputCentered<PJ301MPort>(mm2px(Vec(77.26, 59.638)), module, TAudioInterface::AUDIO_INPUTS + 6));
addInput(createInputCentered<PJ301MPort>(mm2px(Vec(88.86, 59.638)), module, TAudioInterface::AUDIO_INPUTS + 7));
addInput(createInputCentered<PJ301MPort>(mm2px(Vec(7.661, 74.251)), module, TAudioInterface::AUDIO_INPUTS + 8));
addInput(createInputCentered<PJ301MPort>(mm2px(Vec(19.26, 74.251)), module, TAudioInterface::AUDIO_INPUTS + 9));
addInput(createInputCentered<PJ301MPort>(mm2px(Vec(30.86, 74.251)), module, TAudioInterface::AUDIO_INPUTS + 10));
addInput(createInputCentered<PJ301MPort>(mm2px(Vec(42.461, 74.251)), module, TAudioInterface::AUDIO_INPUTS + 11));
addInput(createInputCentered<PJ301MPort>(mm2px(Vec(54.06, 74.251)), module, TAudioInterface::AUDIO_INPUTS + 12));
addInput(createInputCentered<PJ301MPort>(mm2px(Vec(65.661, 74.251)), module, TAudioInterface::AUDIO_INPUTS + 13));
addInput(createInputCentered<PJ301MPort>(mm2px(Vec(77.26, 74.251)), module, TAudioInterface::AUDIO_INPUTS + 14));
addInput(createInputCentered<PJ301MPort>(mm2px(Vec(88.86, 74.251)), module, TAudioInterface::AUDIO_INPUTS + 15));

addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(7.661, 96.251)), module, TAudioInterface::AUDIO_OUTPUTS + 0));
addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(19.26, 96.251)), module, TAudioInterface::AUDIO_OUTPUTS + 1));
addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(30.86, 96.251)), module, TAudioInterface::AUDIO_OUTPUTS + 2));
addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(42.461, 96.251)), module, TAudioInterface::AUDIO_OUTPUTS + 3));
addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(54.06, 96.251)), module, TAudioInterface::AUDIO_OUTPUTS + 4));
addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(65.661, 96.251)), module, TAudioInterface::AUDIO_OUTPUTS + 5));
addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(77.26, 96.251)), module, TAudioInterface::AUDIO_OUTPUTS + 6));
addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(88.86, 96.251)), module, TAudioInterface::AUDIO_OUTPUTS + 7));
addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(7.661, 112.252)), module, TAudioInterface::AUDIO_OUTPUTS + 8));
addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(19.26, 112.252)), module, TAudioInterface::AUDIO_OUTPUTS + 9));
addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(30.86, 112.252)), module, TAudioInterface::AUDIO_OUTPUTS + 10));
addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(42.461, 112.252)), module, TAudioInterface::AUDIO_OUTPUTS + 11));
addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(54.06, 112.252)), module, TAudioInterface::AUDIO_OUTPUTS + 12));
addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(65.661, 112.252)), module, TAudioInterface::AUDIO_OUTPUTS + 13));
addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(77.26, 112.252)), module, TAudioInterface::AUDIO_OUTPUTS + 14));
addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(88.86, 112.252)), module, TAudioInterface::AUDIO_OUTPUTS + 15));

addChild(createLightCentered<SmallLight<GreenLight>>(mm2px(Vec(13.46, 55.667)), module, TAudioInterface::INPUT_LIGHTS + 0));
addChild(createLightCentered<SmallLight<GreenLight>>(mm2px(Vec(36.661, 55.667)), module, TAudioInterface::INPUT_LIGHTS + 1));
addChild(createLightCentered<SmallLight<GreenLight>>(mm2px(Vec(59.861, 55.667)), module, TAudioInterface::INPUT_LIGHTS + 2));
addChild(createLightCentered<SmallLight<GreenLight>>(mm2px(Vec(83.061, 55.667)), module, TAudioInterface::INPUT_LIGHTS + 3));
addChild(createLightCentered<SmallLight<GreenLight>>(mm2px(Vec(13.46, 70.248)), module, TAudioInterface::INPUT_LIGHTS + 4));
addChild(createLightCentered<SmallLight<GreenLight>>(mm2px(Vec(36.661, 70.248)), module, TAudioInterface::INPUT_LIGHTS + 5));
addChild(createLightCentered<SmallLight<GreenLight>>(mm2px(Vec(59.861, 70.248)), module, TAudioInterface::INPUT_LIGHTS + 6));
addChild(createLightCentered<SmallLight<GreenLight>>(mm2px(Vec(83.061, 70.248)), module, TAudioInterface::INPUT_LIGHTS + 7));
addChild(createLightCentered<SmallLight<GreenLight>>(mm2px(Vec(13.46, 92.238)), module, TAudioInterface::OUTPUT_LIGHTS + 0));
addChild(createLightCentered<SmallLight<GreenLight>>(mm2px(Vec(36.661, 92.238)), module, TAudioInterface::OUTPUT_LIGHTS + 1));
addChild(createLightCentered<SmallLight<GreenLight>>(mm2px(Vec(59.861, 92.238)), module, TAudioInterface::OUTPUT_LIGHTS + 2));
addChild(createLightCentered<SmallLight<GreenLight>>(mm2px(Vec(83.061, 92.238)), module, TAudioInterface::OUTPUT_LIGHTS + 3));
addChild(createLightCentered<SmallLight<GreenLight>>(mm2px(Vec(13.46, 108.259)), module, TAudioInterface::OUTPUT_LIGHTS + 4));
addChild(createLightCentered<SmallLight<GreenLight>>(mm2px(Vec(36.661, 108.259)), module, TAudioInterface::OUTPUT_LIGHTS + 5));
addChild(createLightCentered<SmallLight<GreenLight>>(mm2px(Vec(59.861, 108.259)), module, TAudioInterface::OUTPUT_LIGHTS + 6));
addChild(createLightCentered<SmallLight<GreenLight>>(mm2px(Vec(83.061, 108.259)), module, TAudioInterface::OUTPUT_LIGHTS + 7));

AudioWidget* audioWidget = createWidget<AudioWidget>(mm2px(Vec(2.57, 14.839)));
audioWidget->box.size = mm2px(Vec(91.382, 28.0));
audioWidget->setAudioPort(module ? &module->port : NULL);
addChild(audioWidget);
}
}

void appendContextMenu(Menu* menu) override {
TAudioInterface* module = dynamic_cast<TAudioInterface*>(this->module);

menu->addChild(new MenuEntry);

PrimaryModuleItem<TAudioInterface>* primaryModuleItem = new PrimaryModuleItem<TAudioInterface>;
primaryModuleItem->text = "Primary audio module";
primaryModuleItem->rightText = CHECKMARK(APP->engine->getPrimaryModule() == module);
primaryModuleItem->module = module;
menu->addChild(primaryModuleItem);
}
};


Model* modelAudioInterface = createModel<AudioInterface<8, 8>, AudioInterface8Widget>("AudioInterface");
Model* modelAudioInterface16 = createModel<AudioInterface<16, 16>, AudioInterface16Widget>("AudioInterface16");
Model* modelAudioInterface = createModel<AudioInterface<8, 8>, AudioInterfaceWidget<8, 8>>("AudioInterface");
Model* modelAudioInterface16 = createModel<AudioInterface<16, 16>, AudioInterfaceWidget<16, 16>>("AudioInterface16");


} // namespace core


+ 27
- 18
src/core/Blank.cpp View File

@@ -6,6 +6,31 @@ namespace rack {
namespace core {


struct BlankModule : Module {
int width = 4;

/** Legacy for <=v1 patches */
void fromJson(json_t* rootJ) override {
Module::fromJson(rootJ);
json_t* widthJ = json_object_get(rootJ, "width");
if (widthJ)
width = json_number_value(widthJ) / RACK_GRID_WIDTH;
}

json_t* dataToJson() override {
json_t* rootJ = json_object();
json_object_set_new(rootJ, "width", json_integer(width));
return rootJ;
}

void dataFromJson(json_t* rootJ) override {
json_t* widthJ = json_object_get(rootJ, "width");
if (widthJ)
width = json_integer_value(widthJ);
}
};


struct BlankPanel : Widget {
Widget* panelBorder;

@@ -120,6 +145,8 @@ struct BlankWidget : ModuleWidget {
}

void step() override {
// TODO Update from module

blankPanel->box.size = box.size;
topRightScrew->box.pos.x = box.size.x - 30;
bottomRightScrew->box.pos.x = box.size.x - 30;
@@ -132,24 +159,6 @@ struct BlankWidget : ModuleWidget {
rightHandle->box.pos.x = box.size.x - rightHandle->box.size.x;
ModuleWidget::step();
}

json_t* toJson() override {
json_t* rootJ = ModuleWidget::toJson();

// width
json_object_set_new(rootJ, "width", json_real(box.size.x));

return rootJ;
}

void fromJson(json_t* rootJ) override {
ModuleWidget::fromJson(rootJ);

// width
json_t* widthJ = json_object_get(rootJ, "width");
if (widthJ)
box.size.x = json_number_value(widthJ);
}
};




+ 26
- 18
src/core/Notes.cpp View File

@@ -5,7 +5,33 @@ namespace rack {
namespace core {


struct NotesModule : Module {
std::string text;

/** Legacy for <=v1 patches */
void fromJson(json_t* rootJ) override {
Module::fromJson(rootJ);
json_t* textJ = json_object_get(rootJ, "text");
if (textJ)
text = json_string_value(textJ);
}

json_t* dataToJson() override {
json_t* rootJ = json_object();
json_object_set_new(rootJ, "text", json_string(text.c_str()));
return rootJ;
}

void dataFromJson(json_t* rootJ) override {
json_t* textJ = json_object_get(rootJ, "text");
if (textJ)
text = json_string_value(textJ);
}
};


struct NotesWidget : ModuleWidget {
// TODO Subclass this or something and keep `module->text` in sync with the text field's string.
TextField* textField;

NotesWidget(Module* module) {
@@ -22,24 +48,6 @@ struct NotesWidget : ModuleWidget {
textField->multiline = true;
addChild(textField);
}

json_t* toJson() override {
json_t* rootJ = ModuleWidget::toJson();

// text
json_object_set_new(rootJ, "text", json_string(textField->text.c_str()));

return rootJ;
}

void fromJson(json_t* rootJ) override {
ModuleWidget::fromJson(rootJ);

// text
json_t* textJ = json_object_get(rootJ, "text");
if (textJ)
textField->text = json_string_value(textJ);
}
};




+ 64
- 0
src/engine/Cable.cpp View File

@@ -0,0 +1,64 @@
#include <engine/Cable.hpp>
#include <engine/Engine.hpp>
#include <app.hpp>


namespace rack {
namespace engine {


json_t* Cable::toJson() {
json_t* rootJ = json_object();
json_object_set_new(rootJ, "id", json_integer(id));
json_object_set_new(rootJ, "outputModuleId", json_integer(outputModule->id));
json_object_set_new(rootJ, "outputId", json_integer(outputId));
json_object_set_new(rootJ, "inputModuleId", json_integer(inputModule->id));
json_object_set_new(rootJ, "inputId", json_integer(inputId));
return rootJ;
}


void Cable::fromJson(json_t* rootJ) {
// inputModuleId
json_t* inputModuleIdJ = json_object_get(rootJ, "inputModuleId");
if (!inputModuleIdJ)
throw Exception("inputModuleId not found for cable");
int inputModuleId = json_integer_value(inputModuleIdJ);
inputModule = APP->engine->getModule(inputModuleId);
if (!inputModule)
throw Exception("inputModule not found for cable");

// inputId
json_t* inputIdJ = json_object_get(rootJ, "inputId");
if (!inputIdJ)
throw Exception("inputId not found for cable");
inputId = json_integer_value(inputIdJ);

// outputModuleId
json_t* outputModuleIdJ = json_object_get(rootJ, "outputModuleId");
if (!outputModuleIdJ)
throw Exception("outputModuleId not found for cable");
int outputModuleId = json_integer_value(outputModuleIdJ);
outputModule = APP->engine->getModule(outputModuleId);
if (!outputModule)
throw Exception("outputModule not found for cable");

// outputId
json_t* outputIdJ = json_object_get(rootJ, "outputId");
if (!outputIdJ)
throw Exception("outputId not found for cable");
outputId = json_integer_value(outputIdJ);

// Only set ID if unset
if (id < 0) {
// id
json_t* idJ = json_object_get(rootJ, "id");
// Before 1.0, cables IDs were not used, so just leave it as default and Engine will assign one automatically.
if (idJ)
id = json_integer_value(idJ);
}
}


} // namespace engine
} // namespace rack

+ 345
- 171
src/engine/Engine.cpp View File

@@ -2,6 +2,9 @@
#include <settings.hpp>
#include <system.hpp>
#include <random.hpp>
#include <app.hpp>
#include <patch.hpp>
#include <plugin.hpp>

#include <algorithm>
#include <chrono>
@@ -27,38 +30,6 @@ static void initMXCSR() {
}


/** 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 Barrier {
std::mutex mutex;
std::condition_variable cv;
@@ -155,7 +126,6 @@ struct EngineWorker {
assert(!running);
running = true;
thread = std::thread([&] {
random::init();
run();
});
}
@@ -180,22 +150,20 @@ struct Engine::Internal {
std::map<std::tuple<int, int>, ParamHandle*> paramHandleCache;
bool paused = false;

bool running = false;
float sampleRate;
float sampleTime;
float sampleRate = 0.f;
float sampleTime = 0.f;
uint64_t frame = 0;
Module* primaryModule = NULL;

int nextModuleId = 0;
int nextCableId = 0;

// Parameter smoothing
Module* smoothModule = NULL;
int smoothParamId;
float smoothValue;
int smoothParamId = 0;
float smoothValue = 0.f;

std::recursive_mutex mutex;
std::thread thread;
VIPMutex vipMutex;

bool realTime = false;
int threadCount = 0;
@@ -206,25 +174,31 @@ struct Engine::Internal {
};


Engine::Engine() {
internal = new Internal;

internal->sampleRate = 44100.f;
internal->sampleTime = 1 / internal->sampleRate;

system::setThreadRealTime(false);
static void Port_step(Port* that, float deltaTime) {
// Set plug lights
if (that->channels == 0) {
that->plugLights[0].setBrightness(0.f);
that->plugLights[1].setBrightness(0.f);
that->plugLights[2].setBrightness(0.f);
}
else if (that->channels == 1) {
float v = that->getVoltage() / 10.f;
that->plugLights[0].setSmoothBrightness(v, deltaTime);
that->plugLights[1].setSmoothBrightness(-v, deltaTime);
that->plugLights[2].setBrightness(0.f);
}
else {
float v2 = 0.f;
for (int c = 0; c < that->channels; c++) {
v2 += std::pow(that->getVoltage(c), 2);
}
float v = std::sqrt(v2) / 10.f;
that->plugLights[0].setBrightness(0.f);
that->plugLights[1].setBrightness(0.f);
that->plugLights[2].setSmoothBrightness(v, deltaTime);
}
}

Engine::~Engine() {
// Make sure there are no cables or modules in the rack on destruction.
// If this happens, a module must have failed to remove itself before the RackWidget was destroyed.
assert(internal->cables.empty());
assert(internal->modules.empty());
assert(internal->paramHandles.empty());
assert(internal->paramHandleCache.empty());

delete internal;
}

static void Engine_stepModules(Engine* that, int threadId) {
Engine::Internal* internal = that->internal;
@@ -236,37 +210,28 @@ static void Engine_stepModules(Engine* that, int threadId) {
processArgs.sampleRate = internal->sampleRate;
processArgs.sampleTime = internal->sampleTime;

// Set up CPU meter
// Prime number to avoid synchronizing with power-of-2 buffers
const int timerDivider = 7;
bool timerEnabled = settings::cpuMeter && (internal->frame % timerDivider) == 0;
double timerOverhead = 0.f;
if (timerEnabled) {
double startTime = system::getThreadTime();
double stopTime = system::getThreadTime();
timerOverhead = stopTime - startTime;
}
bool cpuMeter = settings::cpuMeter;

// Step each module
// for (int i = threadId; i < modulesLen; i += threadCount) {
while (true) {
// Choose next module
// First-come-first serve module-to-thread allocation algorithm
int i = internal->workerModuleIndex++;
if (i >= modulesLen)
break;

Module* module = internal->modules[i];
if (!module->bypass) {
if (!module->disabled) {
// Step module
if (timerEnabled) {
double startTime = system::getThreadTime();
if (cpuMeter) {
auto beginTime = std::chrono::high_resolution_clock::now();
module->process(processArgs);
double stopTime = system::getThreadTime();
auto endTime = std::chrono::high_resolution_clock::now();
float duration = std::chrono::duration<float>(endTime - beginTime).count();

float cpuTime = std::fmax(0.f, stopTime - startTime - timerOverhead);
// Smooth CPU time
const float cpuTau = 2.f /* seconds */;
module->cpuTime += (cpuTime - module->cpuTime) * timerDivider * processArgs.sampleTime / cpuTau;
module->cpuTime += (duration - module->cpuTime) * processArgs.sampleTime / cpuTau;
}
else {
module->process(processArgs);
@@ -274,15 +239,20 @@ static void Engine_stepModules(Engine* that, int threadId) {
}

// Iterate ports to step plug lights
for (Input& input : module->inputs) {
input.process(processArgs.sampleTime);
}
for (Output& output : module->outputs) {
output.process(processArgs.sampleTime);
const int portDivider = 8;
if (internal->frame % portDivider == 0) {
float portTime = processArgs.sampleTime * portDivider;
for (Input& input : module->inputs) {
Port_step(&input, portTime);
}
for (Output& output : module->outputs) {
Port_step(&output, portTime);
}
}
}
}


static void Cable_step(Cable* that) {
Output* output = &that->outputModule->outputs[that->outputId];
Input* input = &that->inputModule->inputs[that->inputId];
@@ -299,6 +269,7 @@ static void Cable_step(Cable* that) {
}
}


static void Engine_step(Engine* that) {
Engine::Internal* internal = that->internal;

@@ -349,6 +320,7 @@ static void Engine_step(Engine* that) {
internal->frame++;
}


static void Engine_updateExpander(Engine* that, Module::Expander* expander) {
if (expander->moduleId >= 0) {
if (!expander->module || expander->module->id != expander->moduleId) {
@@ -362,6 +334,7 @@ static void Engine_updateExpander(Engine* that, Module::Expander* expander) {
}
}


static void Engine_relaunchWorkers(Engine* that, int threadCount, bool realTime) {
Engine::Internal* internal = that->internal;

@@ -402,122 +375,141 @@ static void Engine_relaunchWorkers(Engine* that, int threadCount, bool realTime)
}
}

static void Engine_run(Engine* that) {
Engine::Internal* internal = that->internal;
// Set up thread
system::setThreadName("Engine");
// system::setThreadRealTime();
initMXCSR();

internal->frame = 0;
// Every time the that waits and locks a mutex, it steps this many frames
const int mutexSteps = 128;
// Time in seconds that the that is rushing ahead of the estimated clock time
double aheadTime = 0.0;
auto lastTime = std::chrono::high_resolution_clock::now();

while (internal->running) {
internal->vipMutex.wait();

// Set sample rate
if (internal->sampleRate != settings::sampleRate) {
internal->sampleRate = settings::sampleRate;
internal->sampleTime = 1 / internal->sampleRate;
for (Module* module : internal->modules) {
module->onSampleRateChange();
}
aheadTime = 0.0;
}
Engine::Engine() {
internal = new Internal;

if (!internal->paused) {
// Launch workers
if (internal->threadCount != settings::threadCount || internal->realTime != settings::realTime) {
Engine_relaunchWorkers(that, settings::threadCount, settings::realTime);
}
internal->sampleRate = 44100.f;
internal->sampleTime = 1 / internal->sampleRate;
}

std::lock_guard<std::recursive_mutex> lock(internal->mutex);

// Update expander pointers
for (Module* module : internal->modules) {
Engine_updateExpander(that, &module->leftExpander);
Engine_updateExpander(that, &module->rightExpander);
}
Engine::~Engine() {
Engine_relaunchWorkers(this, 0, false);
clear();

// Step modules
for (int i = 0; i < mutexSteps; i++) {
Engine_step(that);
}
// Make sure there are no cables or modules in the rack on destruction.
// If this happens, a module must have failed to remove itself before the RackWidget was destroyed.
assert(internal->cables.empty());
assert(internal->modules.empty());
assert(internal->paramHandles.empty());
assert(internal->paramHandleCache.empty());

delete internal;
}


void Engine::clear() {
std::lock_guard<std::recursive_mutex> lock(internal->mutex);
// Copy lists because we'll be removing while iterating
std::set<ParamHandle*> paramHandles = internal->paramHandles;
for (ParamHandle* paramHandle : paramHandles) {
removeParamHandle(paramHandle);
}
std::vector<Cable*> cables = internal->cables;
for (Cable* cable : cables) {
removeCable(cable);
}
std::vector<Module*> modules = internal->modules;
for (Module* module : modules) {
removeModule(module);
}
// Reset engine state
internal->nextModuleId = 0;
internal->nextCableId = 0;
}


void Engine::step(int frames) {
initMXCSR();

// Set sample rate
if (internal->sampleRate != settings::sampleRate) {
internal->sampleRate = settings::sampleRate;
internal->sampleTime = 1 / internal->sampleRate;
Module::SampleRateChangeEvent e;
e.sampleRate = internal->sampleRate;
e.sampleTime = internal->sampleTime;
for (Module* module : internal->modules) {
module->onSampleRateChange(e);
}
else {
// Stop workers while closed
if (internal->threadCount != 1) {
Engine_relaunchWorkers(that, 1, settings::realTime);
}
}

if (!internal->paused) {
// Launch workers
if (internal->threadCount != settings::threadCount || internal->realTime != settings::realTime) {
Engine_relaunchWorkers(this, settings::threadCount, settings::realTime);
}

double stepTime = mutexSteps * internal->sampleTime;
aheadTime += stepTime;
auto currTime = std::chrono::high_resolution_clock::now();
const double aheadFactor = 2.0;
aheadTime -= aheadFactor * std::chrono::duration<double>(currTime - lastTime).count();
lastTime = currTime;
aheadTime = std::fmax(aheadTime, 0.0);
std::lock_guard<std::recursive_mutex> lock(internal->mutex);
// Update expander pointers
for (Module* module : internal->modules) {
Engine_updateExpander(this, &module->leftExpander);
Engine_updateExpander(this, &module->rightExpander);
}

// 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 (aheadTime > aheadMax) {
std::this_thread::sleep_for(std::chrono::duration<double>(stepTime));
// Step modules
for (int i = 0; i < frames; i++) {
Engine_step(this);
}
}
else {
// Stop workers while paused
if (internal->threadCount != 1) {
Engine_relaunchWorkers(this, 1, settings::realTime);
}
}

// Stop workers
Engine_relaunchWorkers(that, 0, false);
yieldWorkers();
}

void Engine::start() {
internal->running = true;
internal->thread = std::thread([&] {
random::init();
Engine_run(this);
});

void Engine::setPrimaryModule(Module* module) {
std::lock_guard<std::recursive_mutex> lock(internal->mutex);
internal->primaryModule = module;
}

void Engine::stop() {
internal->running = false;
internal->thread.join();
Module* Engine::getPrimaryModule() {
return internal->primaryModule;
}


void Engine::setPaused(bool paused) {
VIPLock vipLock(internal->vipMutex);
std::lock_guard<std::recursive_mutex> lock(internal->mutex);
internal->paused = paused;
}


bool Engine::isPaused() {
// No lock
return internal->paused;
}


float Engine::getSampleRate() {
return internal->sampleRate;
}


float Engine::getSampleTime() {
return internal->sampleTime;
}


void Engine::yieldWorkers() {
internal->workerBarrier.yield = true;
}


uint64_t Engine::getFrame() {
return internal->frame;
}


void Engine::addModule(Module* module) {
assert(module);
VIPLock vipLock(internal->vipMutex);
std::lock_guard<std::recursive_mutex> lock(internal->mutex);
// Check that the module is not already added
auto it = std::find(internal->modules.begin(), internal->modules.end(), module);
@@ -540,17 +532,19 @@ void Engine::addModule(Module* module) {
// Add module
internal->modules.push_back(module);
// Trigger Add event
module->onAdd();
Module::AddEvent eAdd;
module->onAdd(eAdd);
// Update ParamHandles' module pointers
for (ParamHandle* paramHandle : internal->paramHandles) {
if (paramHandle->moduleId == module->id)
paramHandle->module = module;
}
DEBUG("Added module %d to engine", module->id);
}


void Engine::removeModule(Module* module) {
assert(module);
VIPLock vipLock(internal->vipMutex);
std::lock_guard<std::recursive_mutex> lock(internal->mutex);
// Check that the module actually exists
auto it = std::find(internal->modules.begin(), internal->modules.end(), module);
@@ -581,13 +575,18 @@ void Engine::removeModule(Module* module) {
}
}
// Trigger Remove event
module->onRemove();
Module::RemoveEvent eRemove;
module->onRemove(eRemove);
// Unset primary module
if (internal->primaryModule == module)
internal->primaryModule = NULL;
// Remove module
internal->modules.erase(it);
DEBUG("Removed module %d to engine", module->id);
}


Module* Engine::getModule(int moduleId) {
VIPLock vipLock(internal->vipMutex);
std::lock_guard<std::recursive_mutex> lock(internal->mutex);
// Find module
for (Module* module : internal->modules) {
@@ -597,36 +596,48 @@ Module* Engine::getModule(int moduleId) {
return NULL;
}


void Engine::resetModule(Module* module) {
assert(module);
VIPLock vipLock(internal->vipMutex);
std::lock_guard<std::recursive_mutex> lock(internal->mutex);

module->onReset();
Module::ResetEvent eReset;
module->onReset(eReset);
}


void Engine::randomizeModule(Module* module) {
assert(module);
VIPLock vipLock(internal->vipMutex);
std::lock_guard<std::recursive_mutex> lock(internal->mutex);

module->onRandomize();
Module::RandomizeEvent eRandomize;
module->onRandomize(eRandomize);
}

void Engine::bypassModule(Module* module, bool bypass) {

void Engine::disableModule(Module* module, bool disabled) {
assert(module);
VIPLock vipLock(internal->vipMutex);
std::lock_guard<std::recursive_mutex> lock(internal->mutex);
if (module->bypass == bypass)
if (module->disabled == disabled)
return;
// Clear outputs and set to 1 channel
for (Output& output : module->outputs) {
// This zeros all voltages, but the channel is set to 1 if connected
output.setChannels(0);
}
module->bypass = bypass;
module->disabled = disabled;
// Trigger event
if (disabled) {
Module::DisableEvent eDisable;
module->onDisable(eDisable);
}
else {
Module::EnableEvent eEnable;
module->onEnable(eEnable);
}
}


static void Port_setDisconnected(Port* that) {
that->channels = 0;
for (int c = 0; c < PORT_MAX_CHANNELS; c++) {
@@ -634,12 +645,14 @@ static void Port_setDisconnected(Port* that) {
}
}


static void Port_setConnected(Port* that) {
if (that->channels > 0)
return;
that->channels = 1;
}


static void Engine_updateConnected(Engine* that) {
// Find disconnected ports
std::set<Port*> disconnectedPorts;
@@ -671,17 +684,23 @@ static void Engine_updateConnected(Engine* that) {
}
}


void Engine::addCable(Cable* cable) {
assert(cable);
VIPLock vipLock(internal->vipMutex);
std::lock_guard<std::recursive_mutex> lock(internal->mutex);
// Check cable properties
assert(cable->outputModule);
assert(cable->inputModule);
// Check that the cable is not already added, and that the input is not already used by another cable
assert(cable->outputModule);
bool outputWasConnected = false;
for (Cable* cable2 : internal->cables) {
// Check that the cable is not already added
assert(cable2 != cable);
// Check that the input is not already used by another cable
assert(!(cable2->inputModule == cable->inputModule && cable2->inputId == cable->inputId));
// Get connected status of output, to decide whether we need to call a PortChangeEvent.
// It's best to not trust `cable->outputModule->outputs[cable->outputId]->isConnected()`
if (cable2->outputModule == cable->outputModule && cable2->outputId == cable->outputId)
outputWasConnected = true;
}
// Set ID
if (cable->id < 0) {
@@ -701,11 +720,28 @@ void Engine::addCable(Cable* cable) {
// Add the cable
internal->cables.push_back(cable);
Engine_updateConnected(this);
// Trigger input port event
{
Module::PortChangeEvent e;
e.connecting = true;
e.type = Port::INPUT;
e.portId = cable->inputId;
cable->inputModule->onPortChange(e);
}
// Trigger output port event if its state went from disconnected to connected.
if (!outputWasConnected) {
Module::PortChangeEvent e;
e.connecting = true;
e.type = Port::OUTPUT;
e.portId = cable->outputId;
cable->outputModule->onPortChange(e);
}
DEBUG("Added cable %d to engine", cable->id);
}


void Engine::removeCable(Cable* cable) {
assert(cable);
VIPLock vipLock(internal->vipMutex);
std::lock_guard<std::recursive_mutex> lock(internal->mutex);
// Check that the cable is already added
auto it = std::find(internal->cables.begin(), internal->cables.end(), cable);
@@ -713,6 +749,41 @@ void Engine::removeCable(Cable* cable) {
// Remove the cable
internal->cables.erase(it);
Engine_updateConnected(this);
bool outputIsConnected = false;
for (Cable* cable2 : internal->cables) {
// Get connected status of output, to decide whether we need to call a PortChangeEvent.
// It's best to not trust `cable->outputModule->outputs[cable->outputId]->isConnected()`
if (cable2->outputModule == cable->outputModule && cable2->outputId == cable->outputId)
outputIsConnected = true;
}
// Trigger input port event
{
Module::PortChangeEvent e;
e.connecting = false;
e.type = Port::INPUT;
e.portId = cable->inputId;
cable->inputModule->onPortChange(e);
}
// Trigger output port event if its state went from connected to disconnected.
if (!outputIsConnected) {
Module::PortChangeEvent e;
e.connecting = false;
e.type = Port::OUTPUT;
e.portId = cable->outputId;
cable->outputModule->onPortChange(e);
}
DEBUG("Removed cable %d to engine", cable->id);
}


Cable* Engine::getCable(int cableId) {
std::lock_guard<std::recursive_mutex> lock(internal->mutex);
// Find Cable
for (Cable* cable : internal->cables) {
if (cable->id == cableId)
return cable;
}
return NULL;
}

void Engine::setParam(Module* module, int paramId, float value) {
@@ -725,10 +796,12 @@ void Engine::setParam(Module* module, int paramId, float value) {
module->params[paramId].value = value;
}


float Engine::getParam(Module* module, int paramId) {
return module->params[paramId].value;
}


void Engine::setSmoothParam(Module* module, int paramId, float value) {
// If another param is being smoothed, jump value
if (internal->smoothModule && !(internal->smoothModule == module && internal->smoothParamId == paramId)) {
@@ -740,12 +813,14 @@ void Engine::setSmoothParam(Module* module, int paramId, float value) {
internal->smoothModule = module;
}


float Engine::getSmoothParam(Module* module, int paramId) {
if (internal->smoothModule == module && internal->smoothParamId == paramId)
return internal->smoothValue;
return getParam(module, paramId);
}


static void Engine_refreshParamHandleCache(Engine* that) {
// Clear cache
that->internal->paramHandleCache.clear();
@@ -757,8 +832,8 @@ static void Engine_refreshParamHandleCache(Engine* that) {
}
}


void Engine::addParamHandle(ParamHandle* paramHandle) {
VIPLock vipLock(internal->vipMutex);
std::lock_guard<std::recursive_mutex> lock(internal->mutex);

// New ParamHandles must be blank.
@@ -773,8 +848,8 @@ void Engine::addParamHandle(ParamHandle* paramHandle) {
internal->paramHandles.insert(paramHandle);
}


void Engine::removeParamHandle(ParamHandle* paramHandle) {
VIPLock vipLock(internal->vipMutex);
std::lock_guard<std::recursive_mutex> lock(internal->mutex);

// Check that the ParamHandle is already added
@@ -787,6 +862,7 @@ void Engine::removeParamHandle(ParamHandle* paramHandle) {
Engine_refreshParamHandleCache(this);
}


ParamHandle* Engine::getParamHandle(int moduleId, int paramId) {
// Don't lock because this method is called potentially thousands of times per screen frame.

@@ -796,12 +872,13 @@ ParamHandle* Engine::getParamHandle(int moduleId, int paramId) {
return it->second;
}


ParamHandle* Engine::getParamHandle(Module* module, int paramId) {
return getParamHandle(module->id, paramId);
}


void Engine::updateParamHandle(ParamHandle* paramHandle, int moduleId, int paramId, bool overwrite) {
VIPLock vipLock(internal->vipMutex);
std::lock_guard<std::recursive_mutex> lock(internal->mutex);

// Check that it exists
@@ -840,12 +917,85 @@ void Engine::updateParamHandle(ParamHandle* paramHandle, int moduleId, int param
}


json_t* Engine::toJson() {
json_t* rootJ = json_object();

// modules
json_t* modulesJ = json_array();
for (Module* module : internal->modules) {
// module
json_t* moduleJ = module->toJson();
json_array_append_new(modulesJ, moduleJ);
}
json_object_set_new(rootJ, "modules", modulesJ);

// cables
json_t* cablesJ = json_array();
for (Cable* cable : internal->cables) {
// cable
json_t* cableJ = cable->toJson();
json_array_append_new(cablesJ, cableJ);
}
json_object_set_new(rootJ, "cables", cablesJ);

return rootJ;
}


void Engine::fromJson(json_t* rootJ) {
// modules
json_t* modulesJ = json_object_get(rootJ, "modules");
if (!modulesJ)
return;
size_t moduleIndex;
json_t* moduleJ;
json_array_foreach(modulesJ, moduleIndex, moduleJ) {
try {
Module* module = moduleFromJson(moduleJ);

// Before 1.0, the module ID was the index in the "modules" array
if (APP->patch->isLegacy(2)) {
module->id = moduleIndex;
}

addModule(module);
}
catch (Exception& e) {
APP->patch->log(e.what());
}
}

// cables
json_t* cablesJ = json_object_get(rootJ, "cables");
// Before 1.0, cables were called wires
if (!cablesJ)
cablesJ = json_object_get(rootJ, "wires");
if (!cablesJ)
return;
size_t cableIndex;
json_t* cableJ;
json_array_foreach(cablesJ, cableIndex, cableJ) {
// cable
Cable* cable = new Cable;
try {
cable->fromJson(cableJ);
// DEBUG("%p %d %p %d", cable->inputModule, cable->inputId, cable->)
addCable(cable);
}
catch (Exception& e) {
delete cable;
// Don't log exceptions because missing modules create unnecessary complaining when cables try to connect to them.
}
}
}


void EngineWorker::run() {
system::setThreadName("Engine worker");
system::setThreadRealTime(engine->internal->realTime);
system::setThreadName(string::f("Worker %d", id));
initMXCSR();
random::init();

while (1) {
while (true) {
engine->internal->engineBarrier.wait();
if (!running)
return;
@@ -855,5 +1005,29 @@ void EngineWorker::run() {
}


Module* moduleFromJson(json_t* moduleJ) {
// Get slugs
json_t* pluginSlugJ = json_object_get(moduleJ, "plugin");
if (!pluginSlugJ)
throw Exception("\"plugin\" property not found in module JSON");
json_t* modelSlugJ = json_object_get(moduleJ, "model");
if (!modelSlugJ)
throw Exception("\"model\" property not found in module JSON");
std::string pluginSlug = json_string_value(pluginSlugJ);
std::string modelSlug = json_string_value(modelSlugJ);

// Get Model
plugin::Model* model = plugin::getModel(pluginSlug, modelSlug);
if (!model)
throw Exception(string::f("Could not find module \"%s\" of plugin \"%s\"", modelSlug.c_str(), pluginSlug.c_str()));

// Create Module
Module* module = model->createModule();
assert(module);
module->fromJson(moduleJ);
return module;
}


} // namespace engine
} // namespace rack

+ 37
- 11
src/engine/Module.cpp View File

@@ -52,20 +52,17 @@ json_t* Module::toJson() {
if (!paramQuantities[paramId]->isBounded())
continue;

json_t* paramJ = json_object();
json_t* paramJ = params[paramId].toJson();

json_object_set_new(paramJ, "id", json_integer(paramId));

float value = params[paramId].getValue();
json_object_set_new(paramJ, "value", json_real(value));

json_array_append(paramsJ, paramJ);
}
json_object_set_new(rootJ, "params", paramsJ);

// bypass
if (bypass)
json_object_set_new(rootJ, "bypass", json_boolean(bypass));
// disabled
if (disabled)
json_object_set_new(rootJ, "disabled", json_boolean(disabled));

// leftModuleId
if (leftExpander.moduleId >= 0)
@@ -155,10 +152,13 @@ void Module::fromJson(json_t* rootJ) {
params[paramId].setValue(json_number_value(valueJ));
}

// bypass
json_t* bypassJ = json_object_get(rootJ, "bypass");
if (bypassJ)
bypass = json_boolean_value(bypassJ);
// disabled
json_t* disabledJ = json_object_get(rootJ, "disabled");
// legacy bypass
if (!disabledJ)
disabledJ = json_object_get(rootJ, "bypass");
if (disabledJ)
disabled = json_boolean_value(disabledJ);

// These do not need to be deserialized, since the module positions will set them correctly when added to the rack.
// // leftModuleId
@@ -178,5 +178,31 @@ void Module::fromJson(json_t* rootJ) {
}


void Module::onReset(const ResetEvent& e) {
// Reset all parameters
assert(params.size() == paramQuantities.size());
for (int i = 0; i < (int) params.size(); i++) {
if (!paramQuantities[i]->resetEnabled)
continue;
paramQuantities[i]->reset();
}
// Call deprecated event
onReset();
}


void Module::onRandomize(const RandomizeEvent& e) {
// Randomize all parameters
assert(params.size() == paramQuantities.size());
for (int i = 0; i < (int) params.size(); i++) {
if (!paramQuantities[i]->randomizeEnabled)
continue;
paramQuantities[i]->randomize();
}
// Call deprecated event
onRandomize();
}


} // namespace engine
} // namespace rack

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

@@ -0,0 +1,23 @@
#include <engine/Param.hpp>


namespace rack {
namespace engine {


json_t* Param::toJson() {
json_t* rootJ = json_object();
json_object_set_new(rootJ, "value", json_real(value));
return rootJ;
}


void Param::fromJson(json_t* rootJ) {
json_t* valueJ = json_object_get(rootJ, "value");
if (valueJ)
value = json_number_value(valueJ);
}


} // namespace engine
} // namespace rack

+ 2
- 1
src/engine/ParamQuantity.cpp View File

@@ -9,6 +9,7 @@ namespace engine {

engine::Param* ParamQuantity::getParam() {
assert(module);
assert(paramId < (int) module->params.size());
return &module->params[paramId];
}

@@ -93,7 +94,7 @@ void ParamQuantity::setDisplayValue(float displayValue) {
}

int ParamQuantity::getDisplayPrecision() {
return Quantity::getDisplayPrecision();
return displayPrecision;
}

std::string ParamQuantity::getDisplayValueString() {


+ 0
- 35
src/engine/Port.cpp View File

@@ -1,35 +0,0 @@
#include <engine/Port.hpp>


namespace rack {
namespace engine {


void Port::process(float deltaTime) {
// Set plug lights
if (channels == 0) {
plugLights[0].setBrightness(0.f);
plugLights[1].setBrightness(0.f);
plugLights[2].setBrightness(0.f);
}
else if (channels == 1) {
float v = getVoltage() / 10.f;
plugLights[0].setSmoothBrightness(v, deltaTime);
plugLights[1].setSmoothBrightness(-v, deltaTime);
plugLights[2].setBrightness(0.f);
}
else {
float v2 = 0.f;
for (int c = 0; c < channels; c++) {
v2 += std::pow(getVoltage(c), 2);
}
float v = std::sqrt(v2) / 10.f;
plugLights[0].setBrightness(0.f);
plugLights[1].setBrightness(0.f);
plugLights[2].setSmoothBrightness(v, deltaTime);
}
}


} // namespace engine
} // namespace rack

+ 44
- 41
src/history.cpp View File

@@ -48,23 +48,26 @@ void ModuleAdd::setModule(app::ModuleWidget* mw) {
pos = mw->box.pos;
// ModuleAdd doesn't *really* need the state to be serialized, although ModuleRemove certainly does.
// However, creating a module may give it a nondeterministic initial state for whatever reason, so serialize anyway.
moduleJ = mw->toJson();
moduleJ = mw->module->toJson();
}

void ModuleAdd::undo() {
app::ModuleWidget* mw = APP->scene->rack->getModule(moduleId);
assert(mw);
engine::Module* module = mw->module;
APP->scene->rack->removeModule(mw);
delete mw;
APP->engine->removeModule(module);
delete module;
}

void ModuleAdd::redo() {
app::ModuleWidget* mw = model->createModuleWidget();
assert(mw);
assert(mw->module);
mw->module->id = moduleId;
engine::Module* module = model->createModule();
module->id = moduleId;
module->fromJson(moduleJ);
APP->engine->addModule(module);
app::ModuleWidget* mw = model->createModuleWidget(module);
mw->box.pos = pos;
mw->fromJson(moduleJ);
APP->scene->rack->addModule(mw);
}

@@ -82,16 +85,16 @@ void ModuleMove::redo() {
}


void ModuleBypass::undo() {
app::ModuleWidget* mw = APP->scene->rack->getModule(moduleId);
assert(mw);
APP->engine->bypassModule(mw->module, !bypass);
void ModuleDisable::undo() {
engine::Module* module = APP->engine->getModule(moduleId);
assert(module);
APP->engine->disableModule(module, !disabled);
}

void ModuleBypass::redo() {
app::ModuleWidget* mw = APP->scene->rack->getModule(moduleId);
assert(mw);
APP->engine->bypassModule(mw->module, bypass);
void ModuleDisable::redo() {
engine::Module* module = APP->engine->getModule(moduleId);
assert(module);
APP->engine->disableModule(module, disabled);
}


@@ -101,28 +104,28 @@ ModuleChange::~ModuleChange() {
}

void ModuleChange::undo() {
app::ModuleWidget* mw = APP->scene->rack->getModule(moduleId);
assert(mw);
mw->fromJson(oldModuleJ);
engine::Module* module = APP->engine->getModule(moduleId);
assert(module);
module->fromJson(oldModuleJ);
}

void ModuleChange::redo() {
app::ModuleWidget* mw = APP->scene->rack->getModule(moduleId);
assert(mw);
mw->fromJson(newModuleJ);
engine::Module* module = APP->engine->getModule(moduleId);
assert(module);
module->fromJson(newModuleJ);
}


void ParamChange::undo() {
app::ModuleWidget* mw = APP->scene->rack->getModule(moduleId);
assert(mw);
mw->module->params[paramId].value = oldValue;
engine::Module* module = APP->engine->getModule(moduleId);
assert(module);
APP->engine->setParam(module, paramId, oldValue);
}

void ParamChange::redo() {
app::ModuleWidget* mw = APP->scene->rack->getModule(moduleId);
assert(mw);
mw->module->params[paramId].value = newValue;
engine::Module* module = APP->engine->getModule(moduleId);
assert(module);
APP->engine->setParam(module, paramId, newValue);
}


@@ -141,28 +144,28 @@ void CableAdd::setCable(app::CableWidget* cw) {

void CableAdd::undo() {
app::CableWidget* cw = APP->scene->rack->getCable(cableId);
assert(cw);
APP->scene->rack->removeCable(cw);
delete cw;

engine::Cable* cable = APP->engine->getCable(cableId);
assert(cable);
APP->engine->removeCable(cable);
delete cable;
}

void CableAdd::redo() {
app::CableWidget* cw = new app::CableWidget;
cw->cable->id = cableId;

app::ModuleWidget* outputModule = APP->scene->rack->getModule(outputModuleId);
assert(outputModule);
app::PortWidget* outputPort = outputModule->getOutput(outputId);
assert(outputPort);
cw->setOutput(outputPort);

app::ModuleWidget* inputModule = APP->scene->rack->getModule(inputModuleId);
assert(inputModule);
app::PortWidget* inputPort = inputModule->getInput(inputId);
assert(inputPort);
cw->setInput(inputPort);
engine::Cable* cable = new engine::Cable;
cable->id = cableId;
cable->inputModule = APP->engine->getModule(inputModuleId);
cable->inputId = inputId;
cable->outputModule = APP->engine->getModule(outputModuleId);
cable->outputId = outputId;
APP->engine->addCable(cable);

app::CableWidget* cw = new app::CableWidget;
cw->setCable(cable);
cw->color = color;

APP->scene->rack->addCable(cw);
}



+ 1
- 6
src/main.cpp View File

@@ -137,7 +137,7 @@ int main(int argc, char* argv[]) {
try {
settings::load(asset::settingsPath);
}
catch (UserException& e) {
catch (Exception& e) {
std::string msg = e.what();
msg += "\n\nReset settings to default?";
if (!osdialog_message(OSDIALOG_WARNING, OSDIALOG_OK_CANCEL, msg.c_str())) {
@@ -187,9 +187,6 @@ int main(int argc, char* argv[]) {
APP->patch->init(patchPath);
}

INFO("Starting engine");
APP->engine->start();

if (settings::headless) {
// TEMP Prove that the app doesn't crash
std::this_thread::sleep_for(std::chrono::seconds(2));
@@ -203,8 +200,6 @@ int main(int argc, char* argv[]) {
APP->window->run();
INFO("Stopped window");
}
INFO("Stopping engine");
APP->engine->stop();

// Destroy app
if (!settings::headless) {


+ 17
- 6
src/patch.cpp View File

@@ -1,6 +1,7 @@
#include <patch.hpp>
#include <asset.hpp>
#include <system.hpp>
#include <engine/Engine.hpp>
#include <app.hpp>
#include <app/common.hpp>
#include <app/Scene.hpp>
@@ -57,6 +58,7 @@ void PatchManager::reset() {
APP->history->clear();
APP->scene->rack->clear();
APP->scene->rackScroll->reset();
APP->engine->clear();

path = "";
if (load(asset::templatePath)) {
@@ -184,7 +186,7 @@ bool PatchManager::load(std::string path) {
APP->history->clear();
APP->scene->rack->clear();
APP->scene->rackScroll->reset();
legacy = 0;
APP->engine->clear();
fromJson(rootJ);
return true;
}
@@ -252,11 +254,12 @@ json_t* PatchManager::toJson() {
json_t* versionJ = json_string(app::APP_VERSION.c_str());
json_object_set_new(rootJ, "version", versionJ);

// Merge with RackWidget JSON
json_t* rackJ = APP->scene->rack->toJson();
json_t* engineJ = APP->engine->toJson();
APP->scene->rack->mergeJson(engineJ);

// Merge with rootJ
json_object_update(rootJ, rackJ);
json_decref(rackJ);
json_object_update(rootJ, engineJ);
json_decref(engineJ);

return rootJ;
}
@@ -274,8 +277,8 @@ void PatchManager::fromJson(json_t* rootJ) {
}

// Detect old patches with ModuleWidget::params/inputs/outputs indices.
// (We now use Module::params/inputs/outputs indices.)
if (string::startsWith(version, "0.3.") || string::startsWith(version, "0.4.") || string::startsWith(version, "0.5.") || version == "" || version == "dev") {
// Use ModuleWidget::params/inputs/outputs indices instead of Module.
legacy = 1;
}
else if (string::startsWith(version, "0.6.")) {
@@ -285,7 +288,10 @@ void PatchManager::fromJson(json_t* rootJ) {
INFO("Loading patch using legacy mode %d", legacy);
}

APP->engine->fromJson(rootJ);
APP->scene->rack->fromJson(rootJ);
// At this point, ModuleWidgets and CableWidgets should own all Modules and Cables.
// TODO Assert this

// Display a message if we have something to say
if (!warningLog.empty()) {
@@ -298,5 +304,10 @@ bool PatchManager::isLegacy(int level) {
return legacy && legacy <= level;
}

void PatchManager::log(std::string msg) {
warningLog += msg;
warningLog += "\n";
}


} // namespace rack

+ 8
- 8
src/plugin.cpp View File

@@ -58,7 +58,7 @@ static InitCallback loadLibrary(Plugin* plugin) {

// Check file existence
if (!system::isFile(libraryFilename)) {
throw UserException(string::f("Library %s does not exist", libraryFilename.c_str()));
throw Exception(string::f("Library %s does not exist", libraryFilename.c_str()));
}

// Load dynamic/shared library
@@ -68,12 +68,12 @@ static InitCallback loadLibrary(Plugin* plugin) {
SetErrorMode(0);
if (!handle) {
int error = GetLastError();
throw UserException(string::f("Failed to load library %s: code %d", libraryFilename.c_str(), error));
throw Exception(string::f("Failed to load library %s: code %d", libraryFilename.c_str(), error));
}
#else
void* handle = dlopen(libraryFilename.c_str(), RTLD_NOW | RTLD_LOCAL);
if (!handle) {
throw UserException(string::f("Failed to load library %s: %s", libraryFilename.c_str(), dlerror()));
throw Exception(string::f("Failed to load library %s: %s", libraryFilename.c_str(), dlerror()));
}
#endif
plugin->handle = handle;
@@ -86,7 +86,7 @@ static InitCallback loadLibrary(Plugin* plugin) {
initCallback = (InitCallback) dlsym(handle, "init");
#endif
if (!initCallback) {
throw UserException(string::f("Failed to read init() symbol in %s", libraryFilename.c_str()));
throw Exception(string::f("Failed to read init() symbol in %s", libraryFilename.c_str()));
}

return initCallback;
@@ -117,7 +117,7 @@ static Plugin* loadPlugin(std::string path) {
std::string manifestFilename = (path == "") ? asset::system("Core.json") : (path + "/plugin.json");
FILE* file = fopen(manifestFilename.c_str(), "r");
if (!file) {
throw UserException(string::f("Manifest file %s does not exist", manifestFilename.c_str()));
throw Exception(string::f("Manifest file %s does not exist", manifestFilename.c_str()));
}
DEFER({
fclose(file);
@@ -126,7 +126,7 @@ static Plugin* loadPlugin(std::string path) {
json_error_t error;
json_t* rootJ = json_loadf(file, 0, &error);
if (!rootJ) {
throw UserException(string::f("JSON parsing error at %s %d:%d %s", manifestFilename.c_str(), error.line, error.column, error.text));
throw Exception(string::f("JSON parsing error at %s %d:%d %s", manifestFilename.c_str(), error.line, error.column, error.text));
}
DEFER({
json_decref(rootJ);
@@ -148,13 +148,13 @@ static Plugin* loadPlugin(std::string path) {
// Reject plugin if slug already exists
Plugin* oldPlugin = getPlugin(plugin->slug);
if (oldPlugin) {
throw UserException(string::f("Plugin %s is already loaded, not attempting to load it again", plugin->slug.c_str()));
throw Exception(string::f("Plugin %s is already loaded, not attempting to load it again", plugin->slug.c_str()));
}

INFO("Loaded plugin %s v%s from %s", plugin->slug.c_str(), plugin->version.c_str(), path.c_str());
plugins.push_back(plugin);
}
catch (UserException& e) {
catch (Exception& e) {
WARN("Could not load plugin %s: %s", path.c_str(), e.what());
delete plugin;
plugin = NULL;


+ 1
- 1
src/plugin/Model.cpp View File

@@ -17,7 +17,7 @@ void Model::fromJson(json_t* rootJ) {
if (nameJ)
name = json_string_value(nameJ);
if (name == "")
throw UserException(string::f("No module name for slug %s", slug.c_str()));
throw Exception(string::f("No module name for slug %s", slug.c_str()));

json_t* descriptionJ = json_object_get(rootJ, "description");
if (descriptionJ)


+ 8
- 8
src/plugin/Plugin.cpp View File

@@ -37,25 +37,25 @@ void Plugin::fromJson(json_t* rootJ) {
if (slugJ)
slug = json_string_value(slugJ);
if (slug == "")
throw UserException("No plugin slug");
throw Exception("No plugin slug");
if (!isSlugValid(slug))
throw UserException(string::f("Plugin slug \"%s\" is invalid", slug.c_str()));
throw Exception(string::f("Plugin slug \"%s\" is invalid", slug.c_str()));

// Version
json_t* versionJ = json_object_get(rootJ, "version");
if (versionJ)
version = json_string_value(versionJ);
if (!string::startsWith(version, app::ABI_VERSION + "."))
throw UserException(string::f("Plugin version %s does not match Rack ABI version %s", version.c_str(), app::ABI_VERSION.c_str()));
throw Exception(string::f("Plugin version %s does not match Rack ABI version %s", version.c_str(), app::ABI_VERSION.c_str()));
if (version == "")
throw UserException("No plugin version");
throw Exception("No plugin version");

// Name
json_t* nameJ = json_object_get(rootJ, "name");
if (nameJ)
name = json_string_value(nameJ);
if (name == "")
throw UserException("No plugin name");
throw Exception("No plugin name");

// Brand
json_t* brandJ = json_object_get(rootJ, "brand");
@@ -112,19 +112,19 @@ void Plugin::fromJson(json_t* rootJ) {
// Get model slug
json_t* modelSlugJ = json_object_get(moduleJ, "slug");
if (!modelSlugJ) {
throw UserException(string::f("No slug found for module entry %d", moduleId));
throw Exception(string::f("No slug found for module entry %d", moduleId));
}
std::string modelSlug = json_string_value(modelSlugJ);

// Check model slug
if (!isSlugValid(modelSlug)) {
throw UserException(string::f("Module slug \"%s\" is invalid", modelSlug.c_str()));
throw Exception(string::f("Module slug \"%s\" is invalid", modelSlug.c_str()));
}

// Get model
Model* model = getModel(modelSlug);
if (!model) {
throw UserException(string::f("Manifest contains module %s but it is not defined in the plugin", modelSlug.c_str()));
throw Exception(string::f("Manifest contains module %s but it is not defined in the plugin", modelSlug.c_str()));
}

model->fromJson(moduleJ);


+ 1
- 1
src/settings.cpp View File

@@ -219,7 +219,7 @@ void load(const std::string& path) {
json_error_t error;
json_t* rootJ = json_loadf(file, 0, &error);
if (!rootJ)
throw UserException(string::f("Settings file has invalid JSON at %d:%d %s", error.line, error.column, error.text));
throw Exception(string::f("Settings file has invalid JSON at %d:%d %s", error.line, error.column, error.text));

fromJson(rootJ);
json_decref(rootJ);


+ 1
- 1
src/window.cpp View File

@@ -433,7 +433,7 @@ void Window::screenshot(float zoom) {
INFO("Screenshotting %s %s to %s", p->slug.c_str(), model->slug.c_str(), filename.c_str());

// Create widgets
app::ModuleWidget* mw = model->createModuleWidgetNull();
app::ModuleWidget* mw = model->createModuleWidget(NULL);
widget::FramebufferWidget* fb = new widget::FramebufferWidget;
fb->oversample = 2;
fb->addChild(mw);


Loading…
Cancel
Save