- 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
@@ -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()); | |||
} | |||
@@ -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; | |||
@@ -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(); | |||
@@ -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() {} | |||
@@ -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); | |||
}; | |||
@@ -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) {} | |||
}; | |||
@@ -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); | |||
}; | |||
@@ -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 |
@@ -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() {} | |||
}; | |||
@@ -19,6 +19,9 @@ struct Param { | |||
void setValue(float value) { | |||
this->value = value; | |||
} | |||
json_t* toJson(); | |||
void fromJson(json_t* rootJ); | |||
}; | |||
@@ -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 */ | |||
@@ -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); | |||
@@ -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; | |||
} | |||
@@ -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; | |||
@@ -33,6 +33,7 @@ struct PatchManager { | |||
json_t* toJson(); | |||
void fromJson(json_t* rootJ); | |||
bool isLegacy(int level); | |||
void log(std::string msg); | |||
}; | |||
@@ -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; | |||
} | |||
@@ -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; | |||
} | |||
@@ -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; | |||
@@ -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; | |||
@@ -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"; | |||
@@ -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(); | |||
@@ -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(); | |||
} | |||
} | |||
@@ -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; | |||
} | |||
@@ -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 | |||
@@ -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); | |||
} | |||
}; | |||
@@ -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); | |||
} | |||
}; | |||
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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() { | |||
@@ -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 |
@@ -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); | |||
} | |||
@@ -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) { | |||
@@ -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 |
@@ -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; | |||
@@ -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) | |||
@@ -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); | |||
@@ -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); | |||
@@ -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); | |||