- 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 | #pragma once | ||||
| #include <math.hpp> | #include <math.hpp> | ||||
| #include <random.hpp> | |||||
| namespace rack { | namespace rack { | ||||
| @@ -11,122 +12,127 @@ Often used as a decorator component for widget::Widgets that read or write a qua | |||||
| struct Quantity { | struct Quantity { | ||||
| virtual ~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. | Override this to change the state of your subclass to represent the new value. | ||||
| */ | */ | ||||
| virtual void setValue(float value) {} | virtual void setValue(float value) {} | ||||
| /** Returns the value | |||||
| /** Returns the value. | |||||
| Override this to return the state of your subclass. | Override this to return the state of your subclass. | ||||
| */ | */ | ||||
| virtual float getValue() { | virtual float getValue() { | ||||
| return 0.f; | return 0.f; | ||||
| } | } | ||||
| /** Returns the minimum allowed value */ | |||||
| /** Returns the minimum allowed value. */ | |||||
| virtual float getMinValue() { | virtual float getMinValue() { | ||||
| return 0.f; | return 0.f; | ||||
| } | } | ||||
| /** Returns the maximum allowed value */ | |||||
| /** Returns the maximum allowed value. */ | |||||
| virtual float getMaxValue() { | virtual float getMaxValue() { | ||||
| return 1.f; | return 1.f; | ||||
| } | } | ||||
| /** Returns the default value, for resetting */ | |||||
| /** Returns the default value, for resetting. */ | |||||
| virtual float getDefaultValue() { | virtual float getDefaultValue() { | ||||
| return 0.f; | 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. | Useful for logarithmic scaling, multiplying by 100 for percentages, etc. | ||||
| */ | */ | ||||
| virtual float getDisplayValue() { | virtual float getDisplayValue() { | ||||
| return getValue(); | 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) { | virtual void setDisplayValue(float displayValue) { | ||||
| setValue(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(); | virtual int getDisplayPrecision(); | ||||
| /** Returns a string representation of the display value */ | |||||
| /** Returns a string representation of the display value. */ | |||||
| virtual std::string getDisplayValueString(); | virtual std::string getDisplayValueString(); | ||||
| virtual void setDisplayValueString(std::string s); | virtual void setDisplayValueString(std::string s); | ||||
| /** The name of the quantity */ | |||||
| /** The name of the quantity. */ | |||||
| virtual std::string getLabel() { | virtual std::string getLabel() { | ||||
| return ""; | 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%". | 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() { | virtual std::string getUnit() { | ||||
| return ""; | return ""; | ||||
| } | } | ||||
| /** Returns a string representation of the quantity */ | |||||
| /** Returns a string representation of the quantity. */ | |||||
| virtual std::string getString(); | virtual std::string getString(); | ||||
| // Helper methods | // Helper methods | ||||
| /** Resets the value to the default value */ | |||||
| /** Resets the value to the default value. */ | |||||
| void reset() { | void reset() { | ||||
| setValue(getDefaultValue()); | 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() { | bool isMin() { | ||||
| return getValue() <= getMinValue(); | return getValue() <= getMinValue(); | ||||
| } | } | ||||
| /** Checks whether the value is at the max value */ | |||||
| /** Checks whether the value is at the max value. */ | |||||
| bool isMax() { | bool isMax() { | ||||
| return getValue() >= getMaxValue(); | return getValue() >= getMaxValue(); | ||||
| } | } | ||||
| /** Sets the value to the min value */ | |||||
| /** Sets the value to the min value. */ | |||||
| void setMin() { | void setMin() { | ||||
| setValue(getMinValue()); | setValue(getMinValue()); | ||||
| } | } | ||||
| /** Sets the value to the max value */ | |||||
| /** Sets the value to the max value. */ | |||||
| void setMax() { | void setMax() { | ||||
| setValue(getMaxValue()); | setValue(getMaxValue()); | ||||
| } | } | ||||
| /** Sets value from the range 0 to 1 */ | |||||
| /** Sets value from the range 0 to 1. */ | |||||
| void setScaledValue(float scaledValue) { | void setScaledValue(float scaledValue) { | ||||
| setValue(math::rescale(scaledValue, 0.f, 1.f, getMinValue(), getMaxValue())); | 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() { | float getScaledValue() { | ||||
| return math::rescale(getValue(), getMinValue(), getMaxValue(), 0.f, 1.f); | 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() { | float getRange() { | ||||
| return getMaxValue() - getMinValue(); | return getMaxValue() - getMinValue(); | ||||
| } | } | ||||
| /** Checks whether the bounds are finite */ | |||||
| /** Checks whether the bounds are finite. */ | |||||
| bool isBounded() { | bool isBounded() { | ||||
| return std::isfinite(getMinValue()) && std::isfinite(getMaxValue()); | return std::isfinite(getMinValue()) && std::isfinite(getMaxValue()); | ||||
| } | } | ||||
| /** Adds an amount to the value */ | |||||
| /** Adds an amount to the value. */ | |||||
| void moveValue(float deltaValue) { | void moveValue(float deltaValue) { | ||||
| setValue(getValue() + 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) { | void moveScaledValue(float deltaScaledValue) { | ||||
| moveValue(deltaScaledValue * getRange()); | moveValue(deltaScaledValue * getRange()); | ||||
| } | } | ||||
| @@ -12,21 +12,25 @@ namespace app { | |||||
| struct CableWidget : widget::OpaqueWidget { | struct CableWidget : widget::OpaqueWidget { | ||||
| PortWidget* outputPort = NULL; | |||||
| PortWidget* inputPort = NULL; | PortWidget* inputPort = NULL; | ||||
| PortWidget* hoveredOutputPort = NULL; | |||||
| PortWidget* outputPort = NULL; | |||||
| PortWidget* hoveredInputPort = NULL; | PortWidget* hoveredInputPort = NULL; | ||||
| PortWidget* hoveredOutputPort = NULL; | |||||
| /** Owned. */ | /** Owned. */ | ||||
| engine::Cable* cable; | |||||
| engine::Cable* cable = NULL; | |||||
| NVGcolor color; | NVGcolor color; | ||||
| CableWidget(); | CableWidget(); | ||||
| ~CableWidget(); | ~CableWidget(); | ||||
| bool isComplete(); | 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 getInputPos(); | ||||
| math::Vec getOutputPos(); | |||||
| json_t* toJson(); | json_t* toJson(); | ||||
| void fromJson(json_t* rootJ); | void fromJson(json_t* rootJ); | ||||
| void draw(const DrawArgs& args) override; | 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. | /** Note that the indexes of these vectors do not necessarily correspond with the indexes of `Module::params` etc. | ||||
| */ | */ | ||||
| std::vector<ParamWidget*> params; | std::vector<ParamWidget*> params; | ||||
| std::vector<PortWidget*> outputs; | |||||
| std::vector<PortWidget*> inputs; | std::vector<PortWidget*> inputs; | ||||
| std::vector<PortWidget*> outputs; | |||||
| /** For RackWidget dragging */ | /** For RackWidget dragging */ | ||||
| math::Vec dragPos; | math::Vec dragPos; | ||||
| math::Vec oldPos; | math::Vec oldPos; | ||||
| @@ -57,13 +57,9 @@ struct ModuleWidget : widget::OpaqueWidget { | |||||
| PortWidget* getOutput(int outputId); | PortWidget* getOutput(int outputId); | ||||
| PortWidget* getInput(int inputId); | 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 */ | /** Serializes/unserializes the module state */ | ||||
| json_t* toJson(); | |||||
| void fromJson(json_t* rootJ); | |||||
| void copyClipboard(); | void copyClipboard(); | ||||
| void pasteClipboardAction(); | void pasteClipboardAction(); | ||||
| void loadAction(std::string filename); | void loadAction(std::string filename); | ||||
| @@ -86,7 +82,7 @@ struct ModuleWidget : widget::OpaqueWidget { | |||||
| void randomizeAction(); | void randomizeAction(); | ||||
| void disconnectAction(); | void disconnectAction(); | ||||
| void cloneAction(); | void cloneAction(); | ||||
| void bypassAction(); | |||||
| void disableAction(); | |||||
| /** Deletes `this` */ | /** Deletes `this` */ | ||||
| void removeAction(); | void removeAction(); | ||||
| void createContextMenu(); | void createContextMenu(); | ||||
| @@ -24,8 +24,6 @@ struct ParamWidget : widget::OpaqueWidget { | |||||
| void onEnter(const event::Enter& e) override; | void onEnter(const event::Enter& e) override; | ||||
| void onLeave(const event::Leave& e) override; | void onLeave(const event::Leave& e) override; | ||||
| /** For legacy patch loading */ | |||||
| void fromJson(json_t* rootJ); | |||||
| void createContextMenu(); | void createContextMenu(); | ||||
| void resetAction(); | void resetAction(); | ||||
| virtual void reset() {} | virtual void reset() {} | ||||
| @@ -39,7 +39,7 @@ struct RackWidget : widget::OpaqueWidget { | |||||
| /** Completely clear the rack's modules and cables */ | /** Completely clear the rack's modules and cables */ | ||||
| void clear(); | void clear(); | ||||
| json_t* toJson(); | |||||
| void mergeJson(json_t* rootJ); | |||||
| void fromJson(json_t* rootJ); | void fromJson(json_t* rootJ); | ||||
| void pastePresetClipboardAction(); | void pastePresetClipboardAction(); | ||||
| @@ -68,18 +68,18 @@ struct RackWidget : widget::OpaqueWidget { | |||||
| void clearCablesAction(); | void clearCablesAction(); | ||||
| /** Removes all complete cables connected to the port */ | /** Removes all complete cables connected to the port */ | ||||
| void clearCablesOnPort(PortWidget* 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() | 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(); | 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* getTopCable(PortWidget* port); | ||||
| CableWidget* getCable(int cableId); | 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); | 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) | #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 { | struct Cable { | ||||
| int id = -1; | int id = -1; | ||||
| Module* outputModule = NULL; | |||||
| int outputId; | |||||
| Module* inputModule = NULL; | Module* inputModule = NULL; | ||||
| int inputId; | int inputId; | ||||
| Module* outputModule = NULL; | |||||
| int outputId; | |||||
| json_t* toJson(); | |||||
| void fromJson(json_t* rootJ); | |||||
| }; | }; | ||||
| @@ -16,19 +16,27 @@ struct Engine { | |||||
| Engine(); | 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); | void setPaused(bool paused); | ||||
| bool isPaused(); | bool isPaused(); | ||||
| float getSampleRate(); | float getSampleRate(); | ||||
| /** Returns the inverse of the current sample rate. */ | |||||
| /** Returns the inverse of the current sample rate. | |||||
| */ | |||||
| float getSampleTime(); | float getSampleTime(); | ||||
| /** Causes worker threads to block on a mutex instead of spinlock. | /** 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. | Call this in your Module::step() method to hint that the operation will take more than ~0.1 ms. | ||||
| */ | */ | ||||
| void yieldWorkers(); | void yieldWorkers(); | ||||
| /** Returns the number of audio samples since the Engine's first sample. | |||||
| */ | |||||
| uint64_t getFrame(); | uint64_t getFrame(); | ||||
| // Modules | // Modules | ||||
| @@ -42,7 +50,7 @@ struct Engine { | |||||
| Module* getModule(int moduleId); | Module* getModule(int moduleId); | ||||
| void resetModule(Module* module); | void resetModule(Module* module); | ||||
| void randomizeModule(Module* module); | void randomizeModule(Module* module); | ||||
| void bypassModule(Module* module, bool bypass); | |||||
| void disableModule(Module* module, bool disabled); | |||||
| // Cables | // Cables | ||||
| /** Adds a cable to the rack engine. | /** Adds a cable to the rack engine. | ||||
| @@ -52,6 +60,7 @@ struct Engine { | |||||
| */ | */ | ||||
| void addCable(Cable* cable); | void addCable(Cable* cable); | ||||
| void removeCable(Cable* cable); | void removeCable(Cable* cable); | ||||
| Cable* getCable(int cableId); | |||||
| // Params | // Params | ||||
| void setParam(Module* module, int paramId, float value); | 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. | 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); | 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 engine | ||||
| } // namespace rack | } // namespace rack | ||||
| @@ -32,8 +32,8 @@ struct Module { | |||||
| Initialized with config(). | Initialized with config(). | ||||
| */ | */ | ||||
| std::vector<Param> params; | std::vector<Param> params; | ||||
| std::vector<Output> outputs; | |||||
| std::vector<Input> inputs; | std::vector<Input> inputs; | ||||
| std::vector<Output> outputs; | |||||
| std::vector<Light> lights; | std::vector<Light> lights; | ||||
| std::vector<ParamQuantity*> paramQuantities; | std::vector<ParamQuantity*> paramQuantities; | ||||
| @@ -78,7 +78,7 @@ struct Module { | |||||
| /** Whether the Module is skipped from stepping by the engine. | /** Whether the Module is skipped from stepping by the engine. | ||||
| Module subclasses should not read/write this variable. | Module subclasses should not read/write this variable. | ||||
| */ | */ | ||||
| bool bypass = false; | |||||
| bool disabled = false; | |||||
| /** Constructs a Module with no params, inputs, outputs, and lights. */ | /** Constructs a Module with no params, inputs, outputs, and lights. */ | ||||
| Module(); | Module(); | ||||
| @@ -121,38 +121,109 @@ struct Module { | |||||
| float sampleRate; | float sampleRate; | ||||
| float sampleTime; | float sampleTime; | ||||
| }; | }; | ||||
| /** Advances the module by one audio sample. | /** Advances the module by one audio sample. | ||||
| Override this method to read Inputs and Params and to write Outputs and Lights. | Override this method to read Inputs and Params and to write Outputs and Lights. | ||||
| */ | */ | ||||
| virtual void process(const ProcessArgs& args) { | virtual void process(const ProcessArgs& args) { | ||||
| #pragma GCC diagnostic push | |||||
| #pragma GCC diagnostic ignored "-Wdeprecated-declarations" | |||||
| step(); | 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(); | 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. */ | /** Override to store extra internal data in the "data" property of the module's JSON object. */ | ||||
| virtual json_t* dataToJson() { | virtual json_t* dataToJson() { | ||||
| return NULL; | return NULL; | ||||
| } | } | ||||
| virtual void dataFromJson(json_t* root) {} | 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) { | void setValue(float value) { | ||||
| this->value = value; | this->value = value; | ||||
| } | } | ||||
| json_t* toJson(); | |||||
| void fromJson(json_t* rootJ); | |||||
| }; | }; | ||||
| @@ -35,8 +35,16 @@ struct ParamQuantity : Quantity { | |||||
| float displayBase = 0.f; | float displayBase = 0.f; | ||||
| float displayMultiplier = 1.f; | float displayMultiplier = 1.f; | ||||
| float displayOffset = 0.f; | float displayOffset = 0.f; | ||||
| int displayPrecision = 5; | |||||
| /** An optional one-sentence description of the parameter. */ | /** An optional one-sentence description of the parameter. */ | ||||
| std::string description; | 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(); | Param* getParam(); | ||||
| /** Request to the engine to smoothly set the value */ | /** Request to the engine to smoothly set the value */ | ||||
| @@ -33,6 +33,11 @@ struct alignas(32) Port { | |||||
| */ | */ | ||||
| Light plugLights[3]; | Light plugLights[3]; | ||||
| enum Type { | |||||
| INPUT, | |||||
| OUTPUT, | |||||
| }; | |||||
| /** Sets the voltage of the given channel. */ | /** Sets the voltage of the given channel. */ | ||||
| void setVoltage(float voltage, int channel = 0) { | void setVoltage(float voltage, int channel = 0) { | ||||
| voltages[channel] = voltage; | voltages[channel] = voltage; | ||||
| @@ -168,8 +173,6 @@ struct alignas(32) Port { | |||||
| return channels > 1; | return channels > 1; | ||||
| } | } | ||||
| void process(float deltaTime); | |||||
| /** Use getNormalVoltage() instead. */ | /** Use getNormalVoltage() instead. */ | ||||
| DEPRECATED float normalize(float normalVoltage) { | DEPRECATED float normalize(float normalVoltage) { | ||||
| return getNormalVoltage(normalVoltage); | return getNormalVoltage(normalVoltage); | ||||
| @@ -24,15 +24,13 @@ plugin::Model* createModel(const std::string& slug) { | |||||
| m->model = this; | m->model = this; | ||||
| return m; | 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; | mw->model = this; | ||||
| return mw; | return mw; | ||||
| } | } | ||||
| @@ -97,12 +97,12 @@ struct ModuleMove : ModuleAction { | |||||
| }; | }; | ||||
| struct ModuleBypass : ModuleAction { | |||||
| bool bypass; | |||||
| struct ModuleDisable : ModuleAction { | |||||
| bool disabled; | |||||
| void undo() override; | void undo() override; | ||||
| void redo() override; | void redo() override; | ||||
| ModuleBypass() { | |||||
| name = "bypass module"; | |||||
| ModuleDisable() { | |||||
| name = "disable module"; | |||||
| } | } | ||||
| }; | }; | ||||
| @@ -133,10 +133,10 @@ struct ParamChange : ModuleAction { | |||||
| struct CableAdd : Action { | struct CableAdd : Action { | ||||
| int cableId; | int cableId; | ||||
| int outputModuleId; | |||||
| int outputId; | |||||
| int inputModuleId; | int inputModuleId; | ||||
| int inputId; | int inputId; | ||||
| int outputModuleId; | |||||
| int outputId; | |||||
| NVGcolor color; | NVGcolor color; | ||||
| void setCable(app::CableWidget* cw); | void setCable(app::CableWidget* cw); | ||||
| void undo() override; | void undo() override; | ||||
| @@ -33,6 +33,7 @@ struct PatchManager { | |||||
| json_t* toJson(); | json_t* toJson(); | ||||
| void fromJson(json_t* rootJ); | void fromJson(json_t* rootJ); | ||||
| bool isLegacy(int level); | bool isLegacy(int level); | ||||
| void log(std::string msg); | |||||
| }; | }; | ||||
| @@ -39,16 +39,14 @@ struct Model { | |||||
| std::string description; | std::string description; | ||||
| virtual ~Model() {} | virtual ~Model() {} | ||||
| /** Creates a headless Module */ | |||||
| /** Creates a Module. */ | |||||
| virtual engine::Module* createModule() { | virtual engine::Module* createModule() { | ||||
| return NULL; | 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; | return NULL; | ||||
| } | } | ||||
| @@ -38,7 +38,8 @@ std::string Quantity::getString() { | |||||
| std::string label = getLabel(); | std::string label = getLabel(); | ||||
| if (!label.empty()) | if (!label.empty()) | ||||
| s += label + ": "; | s += label + ": "; | ||||
| s += getDisplayValueString() + getUnit(); | |||||
| s += getDisplayValueString(); | |||||
| s += getUnit(); | |||||
| return s; | return s; | ||||
| } | } | ||||
| @@ -5,12 +5,110 @@ | |||||
| #include <app.hpp> | #include <app.hpp> | ||||
| #include <patch.hpp> | #include <patch.hpp> | ||||
| #include <settings.hpp> | #include <settings.hpp> | ||||
| #include <engine/Engine.hpp> | |||||
| #include <engine/Port.hpp> | #include <engine/Port.hpp> | ||||
| namespace rack { | namespace rack { | ||||
| namespace app { | 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) { | static void drawPlug(NVGcontext* vg, math::Vec pos, NVGcolor color) { | ||||
| NVGcolor colorOutline = nvgLerpRGBA(color, nvgRGBf(0.0, 0.0, 0.0), 0.5); | 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) { | void CableWidget::draw(const DrawArgs& args) { | ||||
| float opacity = settings::cableOpacity; | float opacity = settings::cableOpacity; | ||||
| float tension = settings::cableTension; | float tension = settings::cableTension; | ||||
| @@ -18,6 +18,7 @@ | |||||
| #include <app/Scene.hpp> | #include <app/Scene.hpp> | ||||
| #include <plugin.hpp> | #include <plugin.hpp> | ||||
| #include <app.hpp> | #include <app.hpp> | ||||
| #include <engine/Engine.hpp> | |||||
| #include <plugin/Model.hpp> | #include <plugin/Model.hpp> | ||||
| #include <string.hpp> | #include <string.hpp> | ||||
| #include <history.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) { | 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); | APP->scene->rack->addModuleAtMouse(moduleWidget); | ||||
| // Push ModuleAdd history action | // Push ModuleAdd history action | ||||
| @@ -170,7 +173,7 @@ struct ModelBox : widget::OpaqueWidget { | |||||
| zoomWidget->setZoom(MODEL_BOX_ZOOM); | zoomWidget->setZoom(MODEL_BOX_ZOOM); | ||||
| previewFb->addChild(zoomWidget); | previewFb->addChild(zoomWidget); | ||||
| ModuleWidget* moduleWidget = model->createModuleWidgetNull(); | |||||
| ModuleWidget* moduleWidget = model->createModuleWidget(NULL); | |||||
| zoomWidget->addChild(moduleWidget); | zoomWidget->addChild(moduleWidget); | ||||
| zoomWidget->box.size.x = moduleWidget->box.size.x * MODEL_BOX_ZOOM; | 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; | ModuleWidget* moduleWidget; | ||||
| void onAction(const event::Action& e) override { | void onAction(const event::Action& e) override { | ||||
| moduleWidget->bypassAction(); | |||||
| moduleWidget->disableAction(); | |||||
| } | } | ||||
| }; | }; | ||||
| @@ -253,14 +253,14 @@ ModuleWidget::~ModuleWidget() { | |||||
| void ModuleWidget::draw(const DrawArgs& args) { | void ModuleWidget::draw(const DrawArgs& args) { | ||||
| nvgScissor(args.vg, RECT_ARGS(args.clipBox)); | nvgScissor(args.vg, RECT_ARGS(args.clipBox)); | ||||
| if (module && module->bypass) { | |||||
| if (module && module->disabled) { | |||||
| nvgGlobalAlpha(args.vg, 0.33); | nvgGlobalAlpha(args.vg, 0.33); | ||||
| } | } | ||||
| Widget::draw(args); | Widget::draw(args); | ||||
| // Power meter | // Power meter | ||||
| if (module && settings::cpuMeter && !module->bypass) { | |||||
| if (module && settings::cpuMeter && !module->disabled) { | |||||
| nvgBeginPath(args.vg); | nvgBeginPath(args.vg); | ||||
| nvgRect(args.vg, | nvgRect(args.vg, | ||||
| 0, box.size.y - 35, | 0, box.size.y - 35, | ||||
| @@ -363,7 +363,7 @@ void ModuleWidget::onHoverKey(const event::HoverKey& e) { | |||||
| } break; | } break; | ||||
| case GLFW_KEY_E: { | case GLFW_KEY_E: { | ||||
| if ((e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { | if ((e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { | ||||
| bypassAction(); | |||||
| disableAction(); | |||||
| e.consume(this); | e.consume(this); | ||||
| } | } | ||||
| } break; | } break; | ||||
| @@ -419,7 +419,9 @@ void ModuleWidget::onDragMove(const event::DragMove& e) { | |||||
| void ModuleWidget::setModule(engine::Module* module) { | void ModuleWidget::setModule(engine::Module* module) { | ||||
| if (this->module) { | if (this->module) { | ||||
| APP->engine->removeModule(this->module); | |||||
| delete this->module; | delete this->module; | ||||
| this->module = NULL; | |||||
| } | } | ||||
| this->module = module; | this->module = module; | ||||
| } | } | ||||
| @@ -496,16 +498,11 @@ PortWidget* ModuleWidget::getInput(int inputId) { | |||||
| } | } | ||||
| json_t* ModuleWidget::toJson() { | 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) { | void ModuleWidget::fromJson(json_t* rootJ) { | ||||
| if (!module) | |||||
| return; | |||||
| module->fromJson(rootJ); | module->fromJson(rootJ); | ||||
| } | } | ||||
| @@ -736,16 +733,16 @@ void ModuleWidget::disconnectAction() { | |||||
| } | } | ||||
| void ModuleWidget::cloneAction() { | void ModuleWidget::cloneAction() { | ||||
| ModuleWidget* clonedModuleWidget = model->createModuleWidget(); | |||||
| assert(clonedModuleWidget); | |||||
| engine::Module* clonedModule = model->createModule(); | |||||
| // JSON serialization is the obvious way to do this | // JSON serialization is the obvious way to do this | ||||
| json_t* moduleJ = toJson(); | json_t* moduleJ = toJson(); | ||||
| clonedModuleWidget->fromJson(moduleJ); | |||||
| clonedModule->fromJson(moduleJ); | |||||
| json_decref(moduleJ); | json_decref(moduleJ); | ||||
| // Reset ID so the Engine automatically assigns a new one | // 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); | APP->scene->rack->addModuleAtMouse(clonedModuleWidget); | ||||
| // history::ModuleAdd | // history::ModuleAdd | ||||
| @@ -755,12 +752,12 @@ void ModuleWidget::cloneAction() { | |||||
| APP->history->push(h); | APP->history->push(h); | ||||
| } | } | ||||
| void ModuleWidget::bypassAction() { | |||||
| void ModuleWidget::disableAction() { | |||||
| assert(module); | assert(module); | ||||
| // history::ModuleBypass | |||||
| history::ModuleBypass* h = new history::ModuleBypass; | |||||
| // history::ModuleDisable | |||||
| history::ModuleDisable* h = new history::ModuleDisable; | |||||
| h->moduleId = module->id; | h->moduleId = module->id; | ||||
| h->bypass = !module->bypass; | |||||
| h->disabled = !module->disabled; | |||||
| APP->history->push(h); | APP->history->push(h); | ||||
| h->redo(); | h->redo(); | ||||
| } | } | ||||
| @@ -826,13 +823,13 @@ void ModuleWidget::createContextMenu() { | |||||
| cloneItem->moduleWidget = this; | cloneItem->moduleWidget = this; | ||||
| menu->addChild(cloneItem); | 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; | ModuleDeleteItem* deleteItem = new ModuleDeleteItem; | ||||
| deleteItem->text = "Delete"; | 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() { | void ParamWidget::createContextMenu() { | ||||
| ui::Menu* menu = createMenu(); | ui::Menu* menu = createMenu(); | ||||
| @@ -93,13 +93,16 @@ void PortWidget::onDragStart(const event::DragStart& e) { | |||||
| CableWidget* cw = NULL; | CableWidget* cw = NULL; | ||||
| if ((APP->window->getMods() & RACK_MOD_MASK) == RACK_MOD_CTRL) { | if ((APP->window->getMods() & RACK_MOD_MASK) == RACK_MOD_CTRL) { | ||||
| if (type == OUTPUT) { | if (type == OUTPUT) { | ||||
| // Ctrl-clicking an output creates a new cable. | |||||
| // Keep cable NULL. Will be created below | // Keep cable NULL. Will be created below | ||||
| } | } | ||||
| else { | else { | ||||
| // Ctrl-clicking an input clones the cable already patched to it. | |||||
| CableWidget* topCw = APP->scene->rack->getTopCable(this); | CableWidget* topCw = APP->scene->rack->getTopCable(this); | ||||
| if (topCw) { | if (topCw) { | ||||
| cw = new CableWidget; | 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 | // Disconnect and reuse existing cable | ||||
| APP->scene->rack->removeCable(cw); | APP->scene->rack->removeCable(cw); | ||||
| if (type == OUTPUT) | if (type == OUTPUT) | ||||
| cw->setOutput(NULL); | |||||
| cw->outputPort = NULL; | |||||
| else | else | ||||
| cw->setInput(NULL); | |||||
| cw->inputPort = NULL; | |||||
| cw->updateCable(); | |||||
| } | } | ||||
| } | } | ||||
| @@ -126,9 +130,10 @@ void PortWidget::onDragStart(const event::DragStart& e) { | |||||
| // Create a new cable | // Create a new cable | ||||
| cw = new CableWidget; | cw = new CableWidget; | ||||
| if (type == OUTPUT) | if (type == OUTPUT) | ||||
| cw->setOutput(this); | |||||
| cw->outputPort = this; | |||||
| else | else | ||||
| cw->setInput(this); | |||||
| cw->inputPort = this; | |||||
| cw->updateCable(); | |||||
| } | } | ||||
| APP->scene->rack->setIncompleteCable(cw); | APP->scene->rack->setIncompleteCable(cw); | ||||
| @@ -169,9 +174,10 @@ void PortWidget::onDragDrop(const event::DragDrop& e) { | |||||
| if (cw) { | if (cw) { | ||||
| cw->hoveredOutputPort = cw->hoveredInputPort = NULL; | cw->hoveredOutputPort = cw->hoveredInputPort = NULL; | ||||
| if (type == OUTPUT) | if (type == OUTPUT) | ||||
| cw->setOutput(this); | |||||
| cw->outputPort = this; | |||||
| else | else | ||||
| cw->setInput(this); | |||||
| cw->inputPort = this; | |||||
| cw->updateCable(); | |||||
| } | } | ||||
| } | } | ||||
| @@ -19,29 +19,14 @@ namespace rack { | |||||
| namespace app { | 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 | // Create ModuleWidget | ||||
| ModuleWidget* moduleWidget = model->createModuleWidget(); | |||||
| ModuleWidget* moduleWidget = module->model->createModuleWidget(module); | |||||
| assert(moduleWidget); | assert(moduleWidget); | ||||
| moduleWidget->fromJson(moduleJ); | |||||
| return moduleWidget; | 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. | // Get module offset so modules are aligned to (0, 0) when the patch is loaded. | ||||
| math::Vec moduleOffset = math::Vec(INFINITY, INFINITY); | math::Vec moduleOffset = math::Vec(INFINITY, INFINITY); | ||||
| for (widget::Widget* w : moduleContainer->children) { | for (widget::Widget* w : moduleContainer->children) { | ||||
| @@ -188,39 +170,46 @@ json_t* RackWidget::toJson() { | |||||
| } | } | ||||
| // modules | // 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 | // 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 | // 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; | 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) { | void RackWidget::fromJson(json_t* rootJ) { | ||||
| @@ -231,55 +220,62 @@ void RackWidget::fromJson(json_t* rootJ) { | |||||
| size_t moduleIndex; | size_t moduleIndex; | ||||
| json_t* moduleJ; | json_t* moduleJ; | ||||
| json_array_foreach(modulesJ, moduleIndex, 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 { | 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 | // cables | ||||
| json_t* cablesJ = json_object_get(rootJ, "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) | if (!cablesJ) | ||||
| cablesJ = json_object_get(rootJ, "wires"); | cablesJ = json_object_get(rootJ, "wires"); | ||||
| assert(cablesJ); | assert(cablesJ); | ||||
| size_t cableIndex; | size_t cableIndex; | ||||
| json_t* cableJ; | json_t* cableJ; | ||||
| json_array_foreach(cablesJ, cableIndex, 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; | CableWidget* cw = new CableWidget; | ||||
| cw->setCable(cable); | |||||
| cw->fromJson(cableJ); | cw->fromJson(cableJ); | ||||
| if (!cw->isComplete()) { | |||||
| delete cw; | |||||
| continue; | |||||
| } | |||||
| addCable(cw); | addCable(cw); | ||||
| } | } | ||||
| } | } | ||||
| @@ -293,13 +289,21 @@ void RackWidget::pastePresetClipboardAction() { | |||||
| json_error_t error; | json_error_t error; | ||||
| json_t* moduleJ = json_loads(moduleJson, 0, &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); | json_decref(moduleJ); | ||||
| }); | |||||
| try { | |||||
| engine::Module* module = engine::moduleFromJson(moduleJ); | |||||
| // Reset ID so the Engine automatically assigns a new one | // 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); | addModuleAtMouse(mw); | ||||
| // history::ModuleAdd | // history::ModuleAdd | ||||
| @@ -307,8 +311,9 @@ void RackWidget::pastePresetClipboardAction() { | |||||
| h->setModule(mw); | h->setModule(mw); | ||||
| APP->history->push(h); | 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); | assert(m->box.size.y == RACK_GRID_HEIGHT); | ||||
| moduleContainer->addChild(m); | moduleContainer->addChild(m); | ||||
| if (m->module) { | |||||
| // Add module to Engine | |||||
| APP->engine->addModule(m->module); | |||||
| } | |||||
| RackWidget_updateAdjacent(this); | RackWidget_updateAdjacent(this); | ||||
| } | } | ||||
| @@ -378,11 +379,6 @@ void RackWidget::removeModule(ModuleWidget* m) { | |||||
| // Disconnect cables | // Disconnect cables | ||||
| m->disconnect(); | m->disconnect(); | ||||
| if (m->module) { | |||||
| // Remove module from Engine | |||||
| APP->engine->removeModule(m->module); | |||||
| } | |||||
| // Remove module from ModuleContainer | // Remove module from ModuleContainer | ||||
| moduleContainer->removeChild(m); | moduleContainer->removeChild(m); | ||||
| } | } | ||||
| @@ -556,14 +552,6 @@ history::ComplexAction* RackWidget::getModuleDragAction() { | |||||
| void RackWidget::clearCables() { | 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; | incompleteCable = NULL; | ||||
| cableContainer->clearChildren(); | 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) { | if (incompleteCable) { | ||||
| cableContainer->removeChild(incompleteCable); | cableContainer->removeChild(incompleteCable); | ||||
| delete incompleteCable; | delete incompleteCable; | ||||
| incompleteCable = NULL; | 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++) { | for (auto it = cableContainer->children.rbegin(); it != cableContainer->children.rend(); it++) { | ||||
| CableWidget* cw = dynamic_cast<CableWidget*>(*it); | CableWidget* cw = dynamic_cast<CableWidget*>(*it); | ||||
| assert(cw); | assert(cw); | ||||
| // Ignore incomplete cables | |||||
| if (!cw->isComplete()) | |||||
| continue; | |||||
| if (cw->inputPort == port || cw->outputPort == port) | if (cw->inputPort == port || cw->outputPort == port) | ||||
| return cw; | return cw; | ||||
| } | } | ||||
| @@ -654,6 +637,8 @@ CableWidget* RackWidget::getCable(int cableId) { | |||||
| for (widget::Widget* w : cableContainer->children) { | for (widget::Widget* w : cableContainer->children) { | ||||
| CableWidget* cw = dynamic_cast<CableWidget*>(w); | CableWidget* cw = dynamic_cast<CableWidget*>(w); | ||||
| assert(cw); | assert(cw); | ||||
| if (!cw->cable) | |||||
| continue; | |||||
| if (cw->cable->id == cableId) | if (cw->cable->id == cableId) | ||||
| return cw; | return cw; | ||||
| } | } | ||||
| @@ -662,15 +647,15 @@ CableWidget* RackWidget::getCable(int cableId) { | |||||
| std::list<CableWidget*> RackWidget::getCablesOnPort(PortWidget* port) { | std::list<CableWidget*> RackWidget::getCablesOnPort(PortWidget* port) { | ||||
| assert(port); | assert(port); | ||||
| std::list<CableWidget*> cables; | |||||
| std::list<CableWidget*> cws; | |||||
| for (widget::Widget* w : cableContainer->children) { | for (widget::Widget* w : cableContainer->children) { | ||||
| CableWidget* cw = dynamic_cast<CableWidget*>(w); | CableWidget* cw = dynamic_cast<CableWidget*>(w); | ||||
| assert(cw); | assert(cw); | ||||
| if (cw->inputPort == port || cw->outputPort == port) { | 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 { | namespace core { | ||||
| template <int AUDIO_OUTPUTS, int AUDIO_INPUTS> | |||||
| template <int NUM_AUDIO_OUTPUTS, int NUM_AUDIO_INPUTS> | |||||
| struct AudioInterfacePort : audio::Port { | 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() { | ~AudioInterfacePort() { | ||||
| // Close stream here before destructing AudioInterfacePort, so the mutexes are still valid when waiting to close. | // 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 { | 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 { | void onCloseStream() override { | ||||
| inputBuffer.clear(); | |||||
| outputBuffer.clear(); | |||||
| // inputBuffer.clear(); | |||||
| // outputBuffer.clear(); | |||||
| } | } | ||||
| void onChannelsChange() override { | 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 { | struct AudioInterface : Module { | ||||
| enum ParamIds { | enum ParamIds { | ||||
| NUM_PARAMS | NUM_PARAMS | ||||
| }; | }; | ||||
| enum InputIds { | enum InputIds { | ||||
| ENUMS(AUDIO_INPUT, AUDIO_INPUTS), | |||||
| ENUMS(AUDIO_INPUTS, NUM_AUDIO_INPUTS), | |||||
| NUM_INPUTS | NUM_INPUTS | ||||
| }; | }; | ||||
| enum OutputIds { | enum OutputIds { | ||||
| ENUMS(AUDIO_OUTPUT, AUDIO_OUTPUTS), | |||||
| ENUMS(AUDIO_OUTPUTS, NUM_AUDIO_OUTPUTS), | |||||
| NUM_OUTPUTS | NUM_OUTPUTS | ||||
| }; | }; | ||||
| enum LightIds { | 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 | 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() { | AudioInterface() { | ||||
| config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); | 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(); | onSampleRateChange(); | ||||
| } | } | ||||
| void process(const ProcessArgs& args) override { | 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++) { | 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 | // 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 { | json_t* dataToJson() override { | ||||
| @@ -225,7 +270,8 @@ struct AudioInterface : Module { | |||||
| void dataFromJson(json_t* rootJ) override { | void dataFromJson(json_t* rootJ) override { | ||||
| json_t* audioJ = json_object_get(rootJ, "audio"); | json_t* audioJ = json_object_get(rootJ, "audio"); | ||||
| port.fromJson(audioJ); | |||||
| if (audioJ) | |||||
| port.fromJson(audioJ); | |||||
| } | } | ||||
| void onReset() override { | 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); | 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 | } // namespace core | ||||
| @@ -6,6 +6,31 @@ namespace rack { | |||||
| namespace core { | 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 { | struct BlankPanel : Widget { | ||||
| Widget* panelBorder; | Widget* panelBorder; | ||||
| @@ -120,6 +145,8 @@ struct BlankWidget : ModuleWidget { | |||||
| } | } | ||||
| void step() override { | void step() override { | ||||
| // TODO Update from module | |||||
| blankPanel->box.size = box.size; | blankPanel->box.size = box.size; | ||||
| topRightScrew->box.pos.x = box.size.x - 30; | topRightScrew->box.pos.x = box.size.x - 30; | ||||
| bottomRightScrew->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; | rightHandle->box.pos.x = box.size.x - rightHandle->box.size.x; | ||||
| ModuleWidget::step(); | 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 { | 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 { | struct NotesWidget : ModuleWidget { | ||||
| // TODO Subclass this or something and keep `module->text` in sync with the text field's string. | |||||
| TextField* textField; | TextField* textField; | ||||
| NotesWidget(Module* module) { | NotesWidget(Module* module) { | ||||
| @@ -22,24 +48,6 @@ struct NotesWidget : ModuleWidget { | |||||
| textField->multiline = true; | textField->multiline = true; | ||||
| addChild(textField); | 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 <settings.hpp> | ||||
| #include <system.hpp> | #include <system.hpp> | ||||
| #include <random.hpp> | #include <random.hpp> | ||||
| #include <app.hpp> | |||||
| #include <patch.hpp> | |||||
| #include <plugin.hpp> | |||||
| #include <algorithm> | #include <algorithm> | ||||
| #include <chrono> | #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 { | struct Barrier { | ||||
| std::mutex mutex; | std::mutex mutex; | ||||
| std::condition_variable cv; | std::condition_variable cv; | ||||
| @@ -155,7 +126,6 @@ struct EngineWorker { | |||||
| assert(!running); | assert(!running); | ||||
| running = true; | running = true; | ||||
| thread = std::thread([&] { | thread = std::thread([&] { | ||||
| random::init(); | |||||
| run(); | run(); | ||||
| }); | }); | ||||
| } | } | ||||
| @@ -180,22 +150,20 @@ struct Engine::Internal { | |||||
| std::map<std::tuple<int, int>, ParamHandle*> paramHandleCache; | std::map<std::tuple<int, int>, ParamHandle*> paramHandleCache; | ||||
| bool paused = false; | bool paused = false; | ||||
| bool running = false; | |||||
| float sampleRate; | |||||
| float sampleTime; | |||||
| float sampleRate = 0.f; | |||||
| float sampleTime = 0.f; | |||||
| uint64_t frame = 0; | uint64_t frame = 0; | ||||
| Module* primaryModule = NULL; | |||||
| int nextModuleId = 0; | int nextModuleId = 0; | ||||
| int nextCableId = 0; | int nextCableId = 0; | ||||
| // Parameter smoothing | // Parameter smoothing | ||||
| Module* smoothModule = NULL; | Module* smoothModule = NULL; | ||||
| int smoothParamId; | |||||
| float smoothValue; | |||||
| int smoothParamId = 0; | |||||
| float smoothValue = 0.f; | |||||
| std::recursive_mutex mutex; | std::recursive_mutex mutex; | ||||
| std::thread thread; | |||||
| VIPMutex vipMutex; | |||||
| bool realTime = false; | bool realTime = false; | ||||
| int threadCount = 0; | 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) { | static void Engine_stepModules(Engine* that, int threadId) { | ||||
| Engine::Internal* internal = that->internal; | Engine::Internal* internal = that->internal; | ||||
| @@ -236,37 +210,28 @@ static void Engine_stepModules(Engine* that, int threadId) { | |||||
| processArgs.sampleRate = internal->sampleRate; | processArgs.sampleRate = internal->sampleRate; | ||||
| processArgs.sampleTime = internal->sampleTime; | 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 | // Step each module | ||||
| // for (int i = threadId; i < modulesLen; i += threadCount) { | |||||
| while (true) { | while (true) { | ||||
| // Choose next module | // Choose next module | ||||
| // First-come-first serve module-to-thread allocation algorithm | |||||
| int i = internal->workerModuleIndex++; | int i = internal->workerModuleIndex++; | ||||
| if (i >= modulesLen) | if (i >= modulesLen) | ||||
| break; | break; | ||||
| Module* module = internal->modules[i]; | Module* module = internal->modules[i]; | ||||
| if (!module->bypass) { | |||||
| if (!module->disabled) { | |||||
| // Step module | // Step module | ||||
| if (timerEnabled) { | |||||
| double startTime = system::getThreadTime(); | |||||
| if (cpuMeter) { | |||||
| auto beginTime = std::chrono::high_resolution_clock::now(); | |||||
| module->process(processArgs); | 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 | // Smooth CPU time | ||||
| const float cpuTau = 2.f /* seconds */; | const float cpuTau = 2.f /* seconds */; | ||||
| module->cpuTime += (cpuTime - module->cpuTime) * timerDivider * processArgs.sampleTime / cpuTau; | |||||
| module->cpuTime += (duration - module->cpuTime) * processArgs.sampleTime / cpuTau; | |||||
| } | } | ||||
| else { | else { | ||||
| module->process(processArgs); | module->process(processArgs); | ||||
| @@ -274,15 +239,20 @@ static void Engine_stepModules(Engine* that, int threadId) { | |||||
| } | } | ||||
| // Iterate ports to step plug lights | // 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) { | static void Cable_step(Cable* that) { | ||||
| Output* output = &that->outputModule->outputs[that->outputId]; | Output* output = &that->outputModule->outputs[that->outputId]; | ||||
| Input* input = &that->inputModule->inputs[that->inputId]; | Input* input = &that->inputModule->inputs[that->inputId]; | ||||
| @@ -299,6 +269,7 @@ static void Cable_step(Cable* that) { | |||||
| } | } | ||||
| } | } | ||||
| static void Engine_step(Engine* that) { | static void Engine_step(Engine* that) { | ||||
| Engine::Internal* internal = that->internal; | Engine::Internal* internal = that->internal; | ||||
| @@ -349,6 +320,7 @@ static void Engine_step(Engine* that) { | |||||
| internal->frame++; | internal->frame++; | ||||
| } | } | ||||
| static void Engine_updateExpander(Engine* that, Module::Expander* expander) { | static void Engine_updateExpander(Engine* that, Module::Expander* expander) { | ||||
| if (expander->moduleId >= 0) { | if (expander->moduleId >= 0) { | ||||
| if (!expander->module || expander->module->id != expander->moduleId) { | 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) { | static void Engine_relaunchWorkers(Engine* that, int threadCount, bool realTime) { | ||||
| Engine::Internal* internal = that->internal; | 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) { | void Engine::setPaused(bool paused) { | ||||
| VIPLock vipLock(internal->vipMutex); | |||||
| std::lock_guard<std::recursive_mutex> lock(internal->mutex); | std::lock_guard<std::recursive_mutex> lock(internal->mutex); | ||||
| internal->paused = paused; | internal->paused = paused; | ||||
| } | } | ||||
| bool Engine::isPaused() { | bool Engine::isPaused() { | ||||
| // No lock | // No lock | ||||
| return internal->paused; | return internal->paused; | ||||
| } | } | ||||
| float Engine::getSampleRate() { | float Engine::getSampleRate() { | ||||
| return internal->sampleRate; | return internal->sampleRate; | ||||
| } | } | ||||
| float Engine::getSampleTime() { | float Engine::getSampleTime() { | ||||
| return internal->sampleTime; | return internal->sampleTime; | ||||
| } | } | ||||
| void Engine::yieldWorkers() { | void Engine::yieldWorkers() { | ||||
| internal->workerBarrier.yield = true; | internal->workerBarrier.yield = true; | ||||
| } | } | ||||
| uint64_t Engine::getFrame() { | uint64_t Engine::getFrame() { | ||||
| return internal->frame; | return internal->frame; | ||||
| } | } | ||||
| void Engine::addModule(Module* module) { | void Engine::addModule(Module* module) { | ||||
| assert(module); | assert(module); | ||||
| VIPLock vipLock(internal->vipMutex); | |||||
| std::lock_guard<std::recursive_mutex> lock(internal->mutex); | std::lock_guard<std::recursive_mutex> lock(internal->mutex); | ||||
| // Check that the module is not already added | // Check that the module is not already added | ||||
| auto it = std::find(internal->modules.begin(), internal->modules.end(), module); | auto it = std::find(internal->modules.begin(), internal->modules.end(), module); | ||||
| @@ -540,17 +532,19 @@ void Engine::addModule(Module* module) { | |||||
| // Add module | // Add module | ||||
| internal->modules.push_back(module); | internal->modules.push_back(module); | ||||
| // Trigger Add event | // Trigger Add event | ||||
| module->onAdd(); | |||||
| Module::AddEvent eAdd; | |||||
| module->onAdd(eAdd); | |||||
| // Update ParamHandles' module pointers | // Update ParamHandles' module pointers | ||||
| for (ParamHandle* paramHandle : internal->paramHandles) { | for (ParamHandle* paramHandle : internal->paramHandles) { | ||||
| if (paramHandle->moduleId == module->id) | if (paramHandle->moduleId == module->id) | ||||
| paramHandle->module = module; | paramHandle->module = module; | ||||
| } | } | ||||
| DEBUG("Added module %d to engine", module->id); | |||||
| } | } | ||||
| void Engine::removeModule(Module* module) { | void Engine::removeModule(Module* module) { | ||||
| assert(module); | assert(module); | ||||
| VIPLock vipLock(internal->vipMutex); | |||||
| std::lock_guard<std::recursive_mutex> lock(internal->mutex); | std::lock_guard<std::recursive_mutex> lock(internal->mutex); | ||||
| // Check that the module actually exists | // Check that the module actually exists | ||||
| auto it = std::find(internal->modules.begin(), internal->modules.end(), module); | auto it = std::find(internal->modules.begin(), internal->modules.end(), module); | ||||
| @@ -581,13 +575,18 @@ void Engine::removeModule(Module* module) { | |||||
| } | } | ||||
| } | } | ||||
| // Trigger Remove event | // Trigger Remove event | ||||
| module->onRemove(); | |||||
| Module::RemoveEvent eRemove; | |||||
| module->onRemove(eRemove); | |||||
| // Unset primary module | |||||
| if (internal->primaryModule == module) | |||||
| internal->primaryModule = NULL; | |||||
| // Remove module | // Remove module | ||||
| internal->modules.erase(it); | internal->modules.erase(it); | ||||
| DEBUG("Removed module %d to engine", module->id); | |||||
| } | } | ||||
| Module* Engine::getModule(int moduleId) { | Module* Engine::getModule(int moduleId) { | ||||
| VIPLock vipLock(internal->vipMutex); | |||||
| std::lock_guard<std::recursive_mutex> lock(internal->mutex); | std::lock_guard<std::recursive_mutex> lock(internal->mutex); | ||||
| // Find module | // Find module | ||||
| for (Module* module : internal->modules) { | for (Module* module : internal->modules) { | ||||
| @@ -597,36 +596,48 @@ Module* Engine::getModule(int moduleId) { | |||||
| return NULL; | return NULL; | ||||
| } | } | ||||
| void Engine::resetModule(Module* module) { | void Engine::resetModule(Module* module) { | ||||
| assert(module); | assert(module); | ||||
| VIPLock vipLock(internal->vipMutex); | |||||
| std::lock_guard<std::recursive_mutex> lock(internal->mutex); | std::lock_guard<std::recursive_mutex> lock(internal->mutex); | ||||
| module->onReset(); | |||||
| Module::ResetEvent eReset; | |||||
| module->onReset(eReset); | |||||
| } | } | ||||
| void Engine::randomizeModule(Module* module) { | void Engine::randomizeModule(Module* module) { | ||||
| assert(module); | assert(module); | ||||
| VIPLock vipLock(internal->vipMutex); | |||||
| std::lock_guard<std::recursive_mutex> lock(internal->mutex); | 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); | assert(module); | ||||
| VIPLock vipLock(internal->vipMutex); | |||||
| std::lock_guard<std::recursive_mutex> lock(internal->mutex); | std::lock_guard<std::recursive_mutex> lock(internal->mutex); | ||||
| if (module->bypass == bypass) | |||||
| if (module->disabled == disabled) | |||||
| return; | return; | ||||
| // Clear outputs and set to 1 channel | // Clear outputs and set to 1 channel | ||||
| for (Output& output : module->outputs) { | for (Output& output : module->outputs) { | ||||
| // This zeros all voltages, but the channel is set to 1 if connected | // This zeros all voltages, but the channel is set to 1 if connected | ||||
| output.setChannels(0); | 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) { | static void Port_setDisconnected(Port* that) { | ||||
| that->channels = 0; | that->channels = 0; | ||||
| for (int c = 0; c < PORT_MAX_CHANNELS; c++) { | 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) { | static void Port_setConnected(Port* that) { | ||||
| if (that->channels > 0) | if (that->channels > 0) | ||||
| return; | return; | ||||
| that->channels = 1; | that->channels = 1; | ||||
| } | } | ||||
| static void Engine_updateConnected(Engine* that) { | static void Engine_updateConnected(Engine* that) { | ||||
| // Find disconnected ports | // Find disconnected ports | ||||
| std::set<Port*> disconnectedPorts; | std::set<Port*> disconnectedPorts; | ||||
| @@ -671,17 +684,23 @@ static void Engine_updateConnected(Engine* that) { | |||||
| } | } | ||||
| } | } | ||||
| void Engine::addCable(Cable* cable) { | void Engine::addCable(Cable* cable) { | ||||
| assert(cable); | assert(cable); | ||||
| VIPLock vipLock(internal->vipMutex); | |||||
| std::lock_guard<std::recursive_mutex> lock(internal->mutex); | std::lock_guard<std::recursive_mutex> lock(internal->mutex); | ||||
| // Check cable properties | // Check cable properties | ||||
| assert(cable->outputModule); | |||||
| assert(cable->inputModule); | 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) { | for (Cable* cable2 : internal->cables) { | ||||
| // Check that the cable is not already added | |||||
| assert(cable2 != cable); | assert(cable2 != cable); | ||||
| // Check that the input is not already used by another cable | |||||
| assert(!(cable2->inputModule == cable->inputModule && cable2->inputId == cable->inputId)); | 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 | // Set ID | ||||
| if (cable->id < 0) { | if (cable->id < 0) { | ||||
| @@ -701,11 +720,28 @@ void Engine::addCable(Cable* cable) { | |||||
| // Add the cable | // Add the cable | ||||
| internal->cables.push_back(cable); | internal->cables.push_back(cable); | ||||
| Engine_updateConnected(this); | 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) { | void Engine::removeCable(Cable* cable) { | ||||
| assert(cable); | assert(cable); | ||||
| VIPLock vipLock(internal->vipMutex); | |||||
| std::lock_guard<std::recursive_mutex> lock(internal->mutex); | std::lock_guard<std::recursive_mutex> lock(internal->mutex); | ||||
| // Check that the cable is already added | // Check that the cable is already added | ||||
| auto it = std::find(internal->cables.begin(), internal->cables.end(), cable); | auto it = std::find(internal->cables.begin(), internal->cables.end(), cable); | ||||
| @@ -713,6 +749,41 @@ void Engine::removeCable(Cable* cable) { | |||||
| // Remove the cable | // Remove the cable | ||||
| internal->cables.erase(it); | internal->cables.erase(it); | ||||
| Engine_updateConnected(this); | 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) { | 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; | module->params[paramId].value = value; | ||||
| } | } | ||||
| float Engine::getParam(Module* module, int paramId) { | float Engine::getParam(Module* module, int paramId) { | ||||
| return module->params[paramId].value; | return module->params[paramId].value; | ||||
| } | } | ||||
| void Engine::setSmoothParam(Module* module, int paramId, float value) { | void Engine::setSmoothParam(Module* module, int paramId, float value) { | ||||
| // If another param is being smoothed, jump value | // If another param is being smoothed, jump value | ||||
| if (internal->smoothModule && !(internal->smoothModule == module && internal->smoothParamId == paramId)) { | 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; | internal->smoothModule = module; | ||||
| } | } | ||||
| float Engine::getSmoothParam(Module* module, int paramId) { | float Engine::getSmoothParam(Module* module, int paramId) { | ||||
| if (internal->smoothModule == module && internal->smoothParamId == paramId) | if (internal->smoothModule == module && internal->smoothParamId == paramId) | ||||
| return internal->smoothValue; | return internal->smoothValue; | ||||
| return getParam(module, paramId); | return getParam(module, paramId); | ||||
| } | } | ||||
| static void Engine_refreshParamHandleCache(Engine* that) { | static void Engine_refreshParamHandleCache(Engine* that) { | ||||
| // Clear cache | // Clear cache | ||||
| that->internal->paramHandleCache.clear(); | that->internal->paramHandleCache.clear(); | ||||
| @@ -757,8 +832,8 @@ static void Engine_refreshParamHandleCache(Engine* that) { | |||||
| } | } | ||||
| } | } | ||||
| void Engine::addParamHandle(ParamHandle* paramHandle) { | void Engine::addParamHandle(ParamHandle* paramHandle) { | ||||
| VIPLock vipLock(internal->vipMutex); | |||||
| std::lock_guard<std::recursive_mutex> lock(internal->mutex); | std::lock_guard<std::recursive_mutex> lock(internal->mutex); | ||||
| // New ParamHandles must be blank. | // New ParamHandles must be blank. | ||||
| @@ -773,8 +848,8 @@ void Engine::addParamHandle(ParamHandle* paramHandle) { | |||||
| internal->paramHandles.insert(paramHandle); | internal->paramHandles.insert(paramHandle); | ||||
| } | } | ||||
| void Engine::removeParamHandle(ParamHandle* paramHandle) { | void Engine::removeParamHandle(ParamHandle* paramHandle) { | ||||
| VIPLock vipLock(internal->vipMutex); | |||||
| std::lock_guard<std::recursive_mutex> lock(internal->mutex); | std::lock_guard<std::recursive_mutex> lock(internal->mutex); | ||||
| // Check that the ParamHandle is already added | // Check that the ParamHandle is already added | ||||
| @@ -787,6 +862,7 @@ void Engine::removeParamHandle(ParamHandle* paramHandle) { | |||||
| Engine_refreshParamHandleCache(this); | Engine_refreshParamHandleCache(this); | ||||
| } | } | ||||
| ParamHandle* Engine::getParamHandle(int moduleId, int paramId) { | ParamHandle* Engine::getParamHandle(int moduleId, int paramId) { | ||||
| // Don't lock because this method is called potentially thousands of times per screen frame. | // 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; | return it->second; | ||||
| } | } | ||||
| ParamHandle* Engine::getParamHandle(Module* module, int paramId) { | ParamHandle* Engine::getParamHandle(Module* module, int paramId) { | ||||
| return getParamHandle(module->id, paramId); | return getParamHandle(module->id, paramId); | ||||
| } | } | ||||
| void Engine::updateParamHandle(ParamHandle* paramHandle, int moduleId, int paramId, bool overwrite) { | void Engine::updateParamHandle(ParamHandle* paramHandle, int moduleId, int paramId, bool overwrite) { | ||||
| VIPLock vipLock(internal->vipMutex); | |||||
| std::lock_guard<std::recursive_mutex> lock(internal->mutex); | std::lock_guard<std::recursive_mutex> lock(internal->mutex); | ||||
| // Check that it exists | // 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() { | void EngineWorker::run() { | ||||
| system::setThreadName("Engine worker"); | |||||
| system::setThreadRealTime(engine->internal->realTime); | |||||
| system::setThreadName(string::f("Worker %d", id)); | |||||
| initMXCSR(); | initMXCSR(); | ||||
| random::init(); | |||||
| while (1) { | |||||
| while (true) { | |||||
| engine->internal->engineBarrier.wait(); | engine->internal->engineBarrier.wait(); | ||||
| if (!running) | if (!running) | ||||
| return; | 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 engine | ||||
| } // namespace rack | } // namespace rack | ||||
| @@ -52,20 +52,17 @@ json_t* Module::toJson() { | |||||
| if (!paramQuantities[paramId]->isBounded()) | if (!paramQuantities[paramId]->isBounded()) | ||||
| continue; | continue; | ||||
| json_t* paramJ = json_object(); | |||||
| json_t* paramJ = params[paramId].toJson(); | |||||
| json_object_set_new(paramJ, "id", json_integer(paramId)); | 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_array_append(paramsJ, paramJ); | ||||
| } | } | ||||
| json_object_set_new(rootJ, "params", paramsJ); | 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 | // leftModuleId | ||||
| if (leftExpander.moduleId >= 0) | if (leftExpander.moduleId >= 0) | ||||
| @@ -155,10 +152,13 @@ void Module::fromJson(json_t* rootJ) { | |||||
| params[paramId].setValue(json_number_value(valueJ)); | 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. | // These do not need to be deserialized, since the module positions will set them correctly when added to the rack. | ||||
| // // leftModuleId | // // 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 engine | ||||
| } // namespace rack | } // 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() { | engine::Param* ParamQuantity::getParam() { | ||||
| assert(module); | assert(module); | ||||
| assert(paramId < (int) module->params.size()); | |||||
| return &module->params[paramId]; | return &module->params[paramId]; | ||||
| } | } | ||||
| @@ -93,7 +94,7 @@ void ParamQuantity::setDisplayValue(float displayValue) { | |||||
| } | } | ||||
| int ParamQuantity::getDisplayPrecision() { | int ParamQuantity::getDisplayPrecision() { | ||||
| return Quantity::getDisplayPrecision(); | |||||
| return displayPrecision; | |||||
| } | } | ||||
| std::string ParamQuantity::getDisplayValueString() { | 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; | pos = mw->box.pos; | ||||
| // ModuleAdd doesn't *really* need the state to be serialized, although ModuleRemove certainly does. | // 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. | // 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() { | void ModuleAdd::undo() { | ||||
| app::ModuleWidget* mw = APP->scene->rack->getModule(moduleId); | app::ModuleWidget* mw = APP->scene->rack->getModule(moduleId); | ||||
| assert(mw); | assert(mw); | ||||
| engine::Module* module = mw->module; | |||||
| APP->scene->rack->removeModule(mw); | APP->scene->rack->removeModule(mw); | ||||
| delete mw; | delete mw; | ||||
| APP->engine->removeModule(module); | |||||
| delete module; | |||||
| } | } | ||||
| void ModuleAdd::redo() { | 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->box.pos = pos; | ||||
| mw->fromJson(moduleJ); | |||||
| APP->scene->rack->addModule(mw); | 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() { | 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() { | 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() { | 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() { | 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() { | void CableAdd::undo() { | ||||
| app::CableWidget* cw = APP->scene->rack->getCable(cableId); | app::CableWidget* cw = APP->scene->rack->getCable(cableId); | ||||
| assert(cw); | |||||
| APP->scene->rack->removeCable(cw); | APP->scene->rack->removeCable(cw); | ||||
| delete cw; | delete cw; | ||||
| engine::Cable* cable = APP->engine->getCable(cableId); | |||||
| assert(cable); | |||||
| APP->engine->removeCable(cable); | |||||
| delete cable; | |||||
| } | } | ||||
| void CableAdd::redo() { | 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; | cw->color = color; | ||||
| APP->scene->rack->addCable(cw); | APP->scene->rack->addCable(cw); | ||||
| } | } | ||||
| @@ -137,7 +137,7 @@ int main(int argc, char* argv[]) { | |||||
| try { | try { | ||||
| settings::load(asset::settingsPath); | settings::load(asset::settingsPath); | ||||
| } | } | ||||
| catch (UserException& e) { | |||||
| catch (Exception& e) { | |||||
| std::string msg = e.what(); | std::string msg = e.what(); | ||||
| msg += "\n\nReset settings to default?"; | msg += "\n\nReset settings to default?"; | ||||
| if (!osdialog_message(OSDIALOG_WARNING, OSDIALOG_OK_CANCEL, msg.c_str())) { | 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); | APP->patch->init(patchPath); | ||||
| } | } | ||||
| INFO("Starting engine"); | |||||
| APP->engine->start(); | |||||
| if (settings::headless) { | if (settings::headless) { | ||||
| // TEMP Prove that the app doesn't crash | // TEMP Prove that the app doesn't crash | ||||
| std::this_thread::sleep_for(std::chrono::seconds(2)); | std::this_thread::sleep_for(std::chrono::seconds(2)); | ||||
| @@ -203,8 +200,6 @@ int main(int argc, char* argv[]) { | |||||
| APP->window->run(); | APP->window->run(); | ||||
| INFO("Stopped window"); | INFO("Stopped window"); | ||||
| } | } | ||||
| INFO("Stopping engine"); | |||||
| APP->engine->stop(); | |||||
| // Destroy app | // Destroy app | ||||
| if (!settings::headless) { | if (!settings::headless) { | ||||
| @@ -1,6 +1,7 @@ | |||||
| #include <patch.hpp> | #include <patch.hpp> | ||||
| #include <asset.hpp> | #include <asset.hpp> | ||||
| #include <system.hpp> | #include <system.hpp> | ||||
| #include <engine/Engine.hpp> | |||||
| #include <app.hpp> | #include <app.hpp> | ||||
| #include <app/common.hpp> | #include <app/common.hpp> | ||||
| #include <app/Scene.hpp> | #include <app/Scene.hpp> | ||||
| @@ -57,6 +58,7 @@ void PatchManager::reset() { | |||||
| APP->history->clear(); | APP->history->clear(); | ||||
| APP->scene->rack->clear(); | APP->scene->rack->clear(); | ||||
| APP->scene->rackScroll->reset(); | APP->scene->rackScroll->reset(); | ||||
| APP->engine->clear(); | |||||
| path = ""; | path = ""; | ||||
| if (load(asset::templatePath)) { | if (load(asset::templatePath)) { | ||||
| @@ -184,7 +186,7 @@ bool PatchManager::load(std::string path) { | |||||
| APP->history->clear(); | APP->history->clear(); | ||||
| APP->scene->rack->clear(); | APP->scene->rack->clear(); | ||||
| APP->scene->rackScroll->reset(); | APP->scene->rackScroll->reset(); | ||||
| legacy = 0; | |||||
| APP->engine->clear(); | |||||
| fromJson(rootJ); | fromJson(rootJ); | ||||
| return true; | return true; | ||||
| } | } | ||||
| @@ -252,11 +254,12 @@ json_t* PatchManager::toJson() { | |||||
| json_t* versionJ = json_string(app::APP_VERSION.c_str()); | json_t* versionJ = json_string(app::APP_VERSION.c_str()); | ||||
| json_object_set_new(rootJ, "version", versionJ); | 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 | // Merge with rootJ | ||||
| json_object_update(rootJ, rackJ); | |||||
| json_decref(rackJ); | |||||
| json_object_update(rootJ, engineJ); | |||||
| json_decref(engineJ); | |||||
| return rootJ; | return rootJ; | ||||
| } | } | ||||
| @@ -274,8 +277,8 @@ void PatchManager::fromJson(json_t* rootJ) { | |||||
| } | } | ||||
| // Detect old patches with ModuleWidget::params/inputs/outputs indices. | // 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") { | 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; | legacy = 1; | ||||
| } | } | ||||
| else if (string::startsWith(version, "0.6.")) { | 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); | INFO("Loading patch using legacy mode %d", legacy); | ||||
| } | } | ||||
| APP->engine->fromJson(rootJ); | |||||
| APP->scene->rack->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 | // Display a message if we have something to say | ||||
| if (!warningLog.empty()) { | if (!warningLog.empty()) { | ||||
| @@ -298,5 +304,10 @@ bool PatchManager::isLegacy(int level) { | |||||
| return legacy && legacy <= level; | return legacy && legacy <= level; | ||||
| } | } | ||||
| void PatchManager::log(std::string msg) { | |||||
| warningLog += msg; | |||||
| warningLog += "\n"; | |||||
| } | |||||
| } // namespace rack | } // namespace rack | ||||
| @@ -58,7 +58,7 @@ static InitCallback loadLibrary(Plugin* plugin) { | |||||
| // Check file existence | // Check file existence | ||||
| if (!system::isFile(libraryFilename)) { | 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 | // Load dynamic/shared library | ||||
| @@ -68,12 +68,12 @@ static InitCallback loadLibrary(Plugin* plugin) { | |||||
| SetErrorMode(0); | SetErrorMode(0); | ||||
| if (!handle) { | if (!handle) { | ||||
| int error = GetLastError(); | 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 | #else | ||||
| void* handle = dlopen(libraryFilename.c_str(), RTLD_NOW | RTLD_LOCAL); | void* handle = dlopen(libraryFilename.c_str(), RTLD_NOW | RTLD_LOCAL); | ||||
| if (!handle) { | 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 | #endif | ||||
| plugin->handle = handle; | plugin->handle = handle; | ||||
| @@ -86,7 +86,7 @@ static InitCallback loadLibrary(Plugin* plugin) { | |||||
| initCallback = (InitCallback) dlsym(handle, "init"); | initCallback = (InitCallback) dlsym(handle, "init"); | ||||
| #endif | #endif | ||||
| if (!initCallback) { | 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; | return initCallback; | ||||
| @@ -117,7 +117,7 @@ static Plugin* loadPlugin(std::string path) { | |||||
| std::string manifestFilename = (path == "") ? asset::system("Core.json") : (path + "/plugin.json"); | std::string manifestFilename = (path == "") ? asset::system("Core.json") : (path + "/plugin.json"); | ||||
| FILE* file = fopen(manifestFilename.c_str(), "r"); | FILE* file = fopen(manifestFilename.c_str(), "r"); | ||||
| if (!file) { | 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({ | DEFER({ | ||||
| fclose(file); | fclose(file); | ||||
| @@ -126,7 +126,7 @@ static Plugin* loadPlugin(std::string path) { | |||||
| json_error_t error; | json_error_t error; | ||||
| json_t* rootJ = json_loadf(file, 0, &error); | json_t* rootJ = json_loadf(file, 0, &error); | ||||
| if (!rootJ) { | 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({ | DEFER({ | ||||
| json_decref(rootJ); | json_decref(rootJ); | ||||
| @@ -148,13 +148,13 @@ static Plugin* loadPlugin(std::string path) { | |||||
| // Reject plugin if slug already exists | // Reject plugin if slug already exists | ||||
| Plugin* oldPlugin = getPlugin(plugin->slug); | Plugin* oldPlugin = getPlugin(plugin->slug); | ||||
| if (oldPlugin) { | 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()); | INFO("Loaded plugin %s v%s from %s", plugin->slug.c_str(), plugin->version.c_str(), path.c_str()); | ||||
| plugins.push_back(plugin); | plugins.push_back(plugin); | ||||
| } | } | ||||
| catch (UserException& e) { | |||||
| catch (Exception& e) { | |||||
| WARN("Could not load plugin %s: %s", path.c_str(), e.what()); | WARN("Could not load plugin %s: %s", path.c_str(), e.what()); | ||||
| delete plugin; | delete plugin; | ||||
| plugin = NULL; | plugin = NULL; | ||||
| @@ -17,7 +17,7 @@ void Model::fromJson(json_t* rootJ) { | |||||
| if (nameJ) | if (nameJ) | ||||
| name = json_string_value(nameJ); | name = json_string_value(nameJ); | ||||
| if (name == "") | 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"); | json_t* descriptionJ = json_object_get(rootJ, "description"); | ||||
| if (descriptionJ) | if (descriptionJ) | ||||
| @@ -37,25 +37,25 @@ void Plugin::fromJson(json_t* rootJ) { | |||||
| if (slugJ) | if (slugJ) | ||||
| slug = json_string_value(slugJ); | slug = json_string_value(slugJ); | ||||
| if (slug == "") | if (slug == "") | ||||
| throw UserException("No plugin slug"); | |||||
| throw Exception("No plugin slug"); | |||||
| if (!isSlugValid(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 | // Version | ||||
| json_t* versionJ = json_object_get(rootJ, "version"); | json_t* versionJ = json_object_get(rootJ, "version"); | ||||
| if (versionJ) | if (versionJ) | ||||
| version = json_string_value(versionJ); | version = json_string_value(versionJ); | ||||
| if (!string::startsWith(version, app::ABI_VERSION + ".")) | 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 == "") | if (version == "") | ||||
| throw UserException("No plugin version"); | |||||
| throw Exception("No plugin version"); | |||||
| // Name | // Name | ||||
| json_t* nameJ = json_object_get(rootJ, "name"); | json_t* nameJ = json_object_get(rootJ, "name"); | ||||
| if (nameJ) | if (nameJ) | ||||
| name = json_string_value(nameJ); | name = json_string_value(nameJ); | ||||
| if (name == "") | if (name == "") | ||||
| throw UserException("No plugin name"); | |||||
| throw Exception("No plugin name"); | |||||
| // Brand | // Brand | ||||
| json_t* brandJ = json_object_get(rootJ, "brand"); | json_t* brandJ = json_object_get(rootJ, "brand"); | ||||
| @@ -112,19 +112,19 @@ void Plugin::fromJson(json_t* rootJ) { | |||||
| // Get model slug | // Get model slug | ||||
| json_t* modelSlugJ = json_object_get(moduleJ, "slug"); | json_t* modelSlugJ = json_object_get(moduleJ, "slug"); | ||||
| if (!modelSlugJ) { | 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); | std::string modelSlug = json_string_value(modelSlugJ); | ||||
| // Check model slug | // Check model slug | ||||
| if (!isSlugValid(modelSlug)) { | 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 | // Get model | ||||
| Model* model = getModel(modelSlug); | Model* model = getModel(modelSlug); | ||||
| if (!model) { | 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); | model->fromJson(moduleJ); | ||||
| @@ -219,7 +219,7 @@ void load(const std::string& path) { | |||||
| json_error_t error; | json_error_t error; | ||||
| json_t* rootJ = json_loadf(file, 0, &error); | json_t* rootJ = json_loadf(file, 0, &error); | ||||
| if (!rootJ) | 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); | fromJson(rootJ); | ||||
| json_decref(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()); | INFO("Screenshotting %s %s to %s", p->slug.c_str(), model->slug.c_str(), filename.c_str()); | ||||
| // Create widgets | // Create widgets | ||||
| app::ModuleWidget* mw = model->createModuleWidgetNull(); | |||||
| app::ModuleWidget* mw = model->createModuleWidget(NULL); | |||||
| widget::FramebufferWidget* fb = new widget::FramebufferWidget; | widget::FramebufferWidget* fb = new widget::FramebufferWidget; | ||||
| fb->oversample = 2; | fb->oversample = 2; | ||||
| fb->addChild(mw); | fb->addChild(mw); | ||||