- Add Engine::fromJson which can be called without the GUI at all. - Make RackWidget::fromJson attach itself to existing Engine state. However, once attached, they own their Engine objects (Module, Cable). - Remove Engine thread. Now Engine must be stepped by other threads, such as the audio thread. This is fantastic because there is no longer a mutex lock every audio buffer. - Add concept of the "primary module", which is allowed to call Engine::step(). - Add many Module events.tags/v2.0.0
| @@ -1,5 +1,6 @@ | |||
| #pragma once | |||
| #include <math.hpp> | |||
| #include <random.hpp> | |||
| namespace rack { | |||
| @@ -11,122 +12,127 @@ Often used as a decorator component for widget::Widgets that read or write a qua | |||
| struct Quantity { | |||
| virtual ~Quantity() {} | |||
| /** Sets the value directly | |||
| /** Sets the value directly. | |||
| Override this to change the state of your subclass to represent the new value. | |||
| */ | |||
| virtual void setValue(float value) {} | |||
| /** Returns the value | |||
| /** Returns the value. | |||
| Override this to return the state of your subclass. | |||
| */ | |||
| virtual float getValue() { | |||
| return 0.f; | |||
| } | |||
| /** Returns the minimum allowed value */ | |||
| /** Returns the minimum allowed value. */ | |||
| virtual float getMinValue() { | |||
| return 0.f; | |||
| } | |||
| /** Returns the maximum allowed value */ | |||
| /** Returns the maximum allowed value. */ | |||
| virtual float getMaxValue() { | |||
| return 1.f; | |||
| } | |||
| /** Returns the default value, for resetting */ | |||
| /** Returns the default value, for resetting. */ | |||
| virtual float getDefaultValue() { | |||
| return 0.f; | |||
| } | |||
| /** Returns the value, possibly transformed for displaying | |||
| /** Returns the value, possibly transformed for displaying. | |||
| Useful for logarithmic scaling, multiplying by 100 for percentages, etc. | |||
| */ | |||
| virtual float getDisplayValue() { | |||
| return getValue(); | |||
| } | |||
| /** Inversely transforms the display value and sets the value */ | |||
| /** Inversely transforms the display value and sets the value. */ | |||
| virtual void setDisplayValue(float displayValue) { | |||
| setValue(displayValue); | |||
| } | |||
| /** The number of total decimal places for generating the display value string | |||
| */ | |||
| /** The number of total decimal places for generating the display value string. */ | |||
| virtual int getDisplayPrecision(); | |||
| /** Returns a string representation of the display value */ | |||
| /** Returns a string representation of the display value. */ | |||
| virtual std::string getDisplayValueString(); | |||
| virtual void setDisplayValueString(std::string s); | |||
| /** The name of the quantity */ | |||
| /** The name of the quantity. */ | |||
| virtual std::string getLabel() { | |||
| return ""; | |||
| } | |||
| /** The unit abbreviation of the quantity | |||
| /** The unit abbreviation of the quantity. | |||
| Include an initial space character if you want a space after the number, e.g. "440 Hz". This allows space-less units, like "100%". | |||
| */ | |||
| virtual std::string getUnit() { | |||
| return ""; | |||
| } | |||
| /** Returns a string representation of the quantity */ | |||
| /** Returns a string representation of the quantity. */ | |||
| virtual std::string getString(); | |||
| // Helper methods | |||
| /** Resets the value to the default value */ | |||
| /** Resets the value to the default value. */ | |||
| void reset() { | |||
| setValue(getDefaultValue()); | |||
| } | |||
| /** Checks whether the value is at the min value */ | |||
| /** Sets the value to a uniform random value between the bounds. */ | |||
| void randomize() { | |||
| if (isBounded()) | |||
| setScaledValue(random::uniform()); | |||
| } | |||
| /** Checks whether the value is at the min value. */ | |||
| bool isMin() { | |||
| return getValue() <= getMinValue(); | |||
| } | |||
| /** Checks whether the value is at the max value */ | |||
| /** Checks whether the value is at the max value. */ | |||
| bool isMax() { | |||
| return getValue() >= getMaxValue(); | |||
| } | |||
| /** Sets the value to the min value */ | |||
| /** Sets the value to the min value. */ | |||
| void setMin() { | |||
| setValue(getMinValue()); | |||
| } | |||
| /** Sets the value to the max value */ | |||
| /** Sets the value to the max value. */ | |||
| void setMax() { | |||
| setValue(getMaxValue()); | |||
| } | |||
| /** Sets value from the range 0 to 1 */ | |||
| /** Sets value from the range 0 to 1. */ | |||
| void setScaledValue(float scaledValue) { | |||
| setValue(math::rescale(scaledValue, 0.f, 1.f, getMinValue(), getMaxValue())); | |||
| } | |||
| /** Returns the value rescaled to the range 0 to 1 */ | |||
| /** Returns the value rescaled to the range 0 to 1. */ | |||
| float getScaledValue() { | |||
| return math::rescale(getValue(), getMinValue(), getMaxValue(), 0.f, 1.f); | |||
| } | |||
| /** The difference between the max and min values */ | |||
| /** The difference between the max and min values. */ | |||
| float getRange() { | |||
| return getMaxValue() - getMinValue(); | |||
| } | |||
| /** Checks whether the bounds are finite */ | |||
| /** Checks whether the bounds are finite. */ | |||
| bool isBounded() { | |||
| return std::isfinite(getMinValue()) && std::isfinite(getMaxValue()); | |||
| } | |||
| /** Adds an amount to the value */ | |||
| /** Adds an amount to the value. */ | |||
| void moveValue(float deltaValue) { | |||
| setValue(getValue() + deltaValue); | |||
| } | |||
| /** Adds an amount to the value scaled to the range 0 to 1 */ | |||
| /** Adds an amount to the value scaled to the range 0 to 1. */ | |||
| void moveScaledValue(float deltaScaledValue) { | |||
| moveValue(deltaScaledValue * getRange()); | |||
| } | |||
| @@ -12,21 +12,25 @@ namespace app { | |||
| struct CableWidget : widget::OpaqueWidget { | |||
| PortWidget* outputPort = NULL; | |||
| PortWidget* inputPort = NULL; | |||
| PortWidget* hoveredOutputPort = NULL; | |||
| PortWidget* outputPort = NULL; | |||
| PortWidget* hoveredInputPort = NULL; | |||
| PortWidget* hoveredOutputPort = NULL; | |||
| /** Owned. */ | |||
| engine::Cable* cable; | |||
| engine::Cable* cable = NULL; | |||
| NVGcolor color; | |||
| CableWidget(); | |||
| ~CableWidget(); | |||
| bool isComplete(); | |||
| void setOutput(PortWidget* outputPort); | |||
| void setInput(PortWidget* inputPort); | |||
| math::Vec getOutputPos(); | |||
| /** From input/output ports, re-creates a cable and adds it to the Engine. */ | |||
| void updateCable(); | |||
| /** From a cable, sets the input/output ports. | |||
| Cable must already be added to the Engine. | |||
| */ | |||
| void setCable(engine::Cable* cable); | |||
| math::Vec getInputPos(); | |||
| math::Vec getOutputPos(); | |||
| json_t* toJson(); | |||
| void fromJson(json_t* rootJ); | |||
| void draw(const DrawArgs& args) override; | |||
| @@ -22,8 +22,8 @@ struct ModuleWidget : widget::OpaqueWidget { | |||
| /** Note that the indexes of these vectors do not necessarily correspond with the indexes of `Module::params` etc. | |||
| */ | |||
| std::vector<ParamWidget*> params; | |||
| std::vector<PortWidget*> outputs; | |||
| std::vector<PortWidget*> inputs; | |||
| std::vector<PortWidget*> outputs; | |||
| /** For RackWidget dragging */ | |||
| math::Vec dragPos; | |||
| math::Vec oldPos; | |||
| @@ -57,13 +57,9 @@ struct ModuleWidget : widget::OpaqueWidget { | |||
| PortWidget* getOutput(int outputId); | |||
| PortWidget* getInput(int inputId); | |||
| /** Overriding these is deprecated. | |||
| Use Module::dataToJson() and dataFromJson() instead | |||
| */ | |||
| virtual json_t* toJson(); | |||
| virtual void fromJson(json_t* rootJ); | |||
| /** Serializes/unserializes the module state */ | |||
| json_t* toJson(); | |||
| void fromJson(json_t* rootJ); | |||
| void copyClipboard(); | |||
| void pasteClipboardAction(); | |||
| void loadAction(std::string filename); | |||
| @@ -86,7 +82,7 @@ struct ModuleWidget : widget::OpaqueWidget { | |||
| void randomizeAction(); | |||
| void disconnectAction(); | |||
| void cloneAction(); | |||
| void bypassAction(); | |||
| void disableAction(); | |||
| /** Deletes `this` */ | |||
| void removeAction(); | |||
| void createContextMenu(); | |||
| @@ -24,8 +24,6 @@ struct ParamWidget : widget::OpaqueWidget { | |||
| void onEnter(const event::Enter& e) override; | |||
| void onLeave(const event::Leave& e) override; | |||
| /** For legacy patch loading */ | |||
| void fromJson(json_t* rootJ); | |||
| void createContextMenu(); | |||
| void resetAction(); | |||
| virtual void reset() {} | |||
| @@ -39,7 +39,7 @@ struct RackWidget : widget::OpaqueWidget { | |||
| /** Completely clear the rack's modules and cables */ | |||
| void clear(); | |||
| json_t* toJson(); | |||
| void mergeJson(json_t* rootJ); | |||
| void fromJson(json_t* rootJ); | |||
| void pastePresetClipboardAction(); | |||
| @@ -68,18 +68,18 @@ struct RackWidget : widget::OpaqueWidget { | |||
| void clearCablesAction(); | |||
| /** Removes all complete cables connected to the port */ | |||
| void clearCablesOnPort(PortWidget* port); | |||
| /** Adds a complete cable and adds it to the Engine. | |||
| /** Adds a complete cable. | |||
| Ownership rules work like add/removeChild() | |||
| */ | |||
| void addCable(CableWidget* w); | |||
| void removeCable(CableWidget* w); | |||
| /** Takes ownership of `w` and adds it as a child if it isn't already */ | |||
| void setIncompleteCable(CableWidget* w); | |||
| void addCable(CableWidget* cw); | |||
| void removeCable(CableWidget* cw); | |||
| /** Takes ownership of `cw` and adds it as a child if it isn't already. */ | |||
| void setIncompleteCable(CableWidget* cw); | |||
| CableWidget* releaseIncompleteCable(); | |||
| /** Returns the most recently added complete cable connected to the given Port, i.e. the top of the stack */ | |||
| /** Returns the most recently added complete cable connected to the given Port, i.e. the top of the stack. */ | |||
| CableWidget* getTopCable(PortWidget* port); | |||
| CableWidget* getCable(int cableId); | |||
| /** Returns all cables attached to port, complete or not */ | |||
| /** Returns all cables attached to port, complete or not. */ | |||
| std::list<CableWidget*> getCablesOnPort(PortWidget* port); | |||
| }; | |||
| @@ -157,9 +157,9 @@ DeferWrapper<F> deferWrapper(F f) { | |||
| #define DEFER(code) auto CONCAT(_defer_, __COUNTER__) = rack::deferWrapper([&]() code) | |||
| /** An exception meant to be shown to the user */ | |||
| struct UserException : std::runtime_error { | |||
| UserException(const std::string& msg) : std::runtime_error(msg) {} | |||
| /** An exception explicitly thrown by Rack. */ | |||
| struct Exception : std::runtime_error { | |||
| Exception(const std::string& msg) : std::runtime_error(msg) {} | |||
| }; | |||
| @@ -9,10 +9,13 @@ namespace engine { | |||
| struct Cable { | |||
| int id = -1; | |||
| Module* outputModule = NULL; | |||
| int outputId; | |||
| Module* inputModule = NULL; | |||
| int inputId; | |||
| Module* outputModule = NULL; | |||
| int outputId; | |||
| json_t* toJson(); | |||
| void fromJson(json_t* rootJ); | |||
| }; | |||
| @@ -16,19 +16,27 @@ struct Engine { | |||
| Engine(); | |||
| ~Engine(); | |||
| /** Starts engine thread. */ | |||
| void start(); | |||
| /** Stops engine thread. */ | |||
| void stop(); | |||
| void clear(); | |||
| /** Advances the engine by `frames` frames. | |||
| Only call this method from the primary module. | |||
| */ | |||
| void step(int frames); | |||
| void setPrimaryModule(Module* module); | |||
| Module* getPrimaryModule(); | |||
| void setPaused(bool paused); | |||
| bool isPaused(); | |||
| float getSampleRate(); | |||
| /** Returns the inverse of the current sample rate. */ | |||
| /** Returns the inverse of the current sample rate. | |||
| */ | |||
| float getSampleTime(); | |||
| /** Causes worker threads to block on a mutex instead of spinlock. | |||
| Call this in your Module::step() method to hint that the operation will take more than ~0.1 ms. | |||
| */ | |||
| void yieldWorkers(); | |||
| /** Returns the number of audio samples since the Engine's first sample. | |||
| */ | |||
| uint64_t getFrame(); | |||
| // Modules | |||
| @@ -42,7 +50,7 @@ struct Engine { | |||
| Module* getModule(int moduleId); | |||
| void resetModule(Module* module); | |||
| void randomizeModule(Module* module); | |||
| void bypassModule(Module* module, bool bypass); | |||
| void disableModule(Module* module, bool disabled); | |||
| // Cables | |||
| /** Adds a cable to the rack engine. | |||
| @@ -52,6 +60,7 @@ struct Engine { | |||
| */ | |||
| void addCable(Cable* cable); | |||
| void removeCable(Cable* cable); | |||
| Cable* getCable(int cableId); | |||
| // Params | |||
| void setParam(Module* module, int paramId, float value); | |||
| @@ -70,8 +79,17 @@ struct Engine { | |||
| If `overwrite` is true and another ParamHandle points to the same param, unsets that one and replaces it with the given handle. | |||
| */ | |||
| void updateParamHandle(ParamHandle* paramHandle, int moduleId, int paramId, bool overwrite = true); | |||
| json_t* toJson(); | |||
| void fromJson(json_t* rootJ); | |||
| }; | |||
| /** Creates a Module from a JSON module object. | |||
| Throws an Exception if the model is not found. | |||
| */ | |||
| Module* moduleFromJson(json_t* moduleJ); | |||
| } // namespace engine | |||
| } // namespace rack | |||
| @@ -32,8 +32,8 @@ struct Module { | |||
| Initialized with config(). | |||
| */ | |||
| std::vector<Param> params; | |||
| std::vector<Output> outputs; | |||
| std::vector<Input> inputs; | |||
| std::vector<Output> outputs; | |||
| std::vector<Light> lights; | |||
| std::vector<ParamQuantity*> paramQuantities; | |||
| @@ -78,7 +78,7 @@ struct Module { | |||
| /** Whether the Module is skipped from stepping by the engine. | |||
| Module subclasses should not read/write this variable. | |||
| */ | |||
| bool bypass = false; | |||
| bool disabled = false; | |||
| /** Constructs a Module with no params, inputs, outputs, and lights. */ | |||
| Module(); | |||
| @@ -121,38 +121,109 @@ struct Module { | |||
| float sampleRate; | |||
| float sampleTime; | |||
| }; | |||
| /** Advances the module by one audio sample. | |||
| Override this method to read Inputs and Params and to write Outputs and Lights. | |||
| */ | |||
| virtual void process(const ProcessArgs& args) { | |||
| #pragma GCC diagnostic push | |||
| #pragma GCC diagnostic ignored "-Wdeprecated-declarations" | |||
| step(); | |||
| #pragma GCC diagnostic pop | |||
| } | |||
| /** Override process(const ProcessArgs &args) instead. */ | |||
| DEPRECATED virtual void step() {} | |||
| /** Called when the engine sample rate is changed. */ | |||
| virtual void onSampleRateChange() {} | |||
| /** Called when user clicks Initialize in the module context menu. */ | |||
| virtual void onReset() {} | |||
| /** Called when user clicks Randomize in the module context menu. */ | |||
| virtual void onRandomize() {} | |||
| /** Called when the Module is added to the Engine */ | |||
| virtual void onAdd() {} | |||
| /** Called when the Module is removed from the Engine */ | |||
| virtual void onRemove() {} | |||
| /** DEPRECATED. Override `process(const ProcessArgs& args)` instead. */ | |||
| virtual void step() {} | |||
| json_t* toJson(); | |||
| void fromJson(json_t* rootJ); | |||
| /** This is virtual only for the purpose of unserializing legacy data when you could set properties of the `.modules[]` object itself. | |||
| Normally you should override dataFromJson(). | |||
| Remember to call `Module::fromJson(rootJ)` within your overridden method. | |||
| */ | |||
| virtual void fromJson(json_t* rootJ); | |||
| /** Override to store extra internal data in the "data" property of the module's JSON object. */ | |||
| virtual json_t* dataToJson() { | |||
| return NULL; | |||
| } | |||
| virtual void dataFromJson(json_t* root) {} | |||
| /////////////////////// | |||
| // Events | |||
| /////////////////////// | |||
| // All of these events are thread-safe with process(). | |||
| struct AddEvent {}; | |||
| /** Called after adding the module to the Engine. | |||
| */ | |||
| virtual void onAdd(const AddEvent& e) { | |||
| // Call deprecated event method by default | |||
| onAdd(); | |||
| } | |||
| struct RemoveEvent {}; | |||
| /** Called before removing the module to the Engine. | |||
| */ | |||
| virtual void onRemove(const RemoveEvent& e) { | |||
| // Call deprecated event method by default | |||
| onRemove(); | |||
| } | |||
| struct EnableEvent {}; | |||
| /** Called after enabling the module. | |||
| */ | |||
| virtual void onEnable(const EnableEvent& e) {} | |||
| struct DisableEvent {}; | |||
| /** Called after disabling the module. | |||
| */ | |||
| virtual void onDisable(const DisableEvent& e) {} | |||
| struct PortChangeEvent { | |||
| /** True if connecting, false if disconnecting. */ | |||
| bool connecting; | |||
| Port::Type type; | |||
| int portId; | |||
| }; | |||
| /** Called after a cable connects to or disconnects from a port. | |||
| This event is not called for output ports if a stackable cable was added/removed and did not change the port's connected state. | |||
| */ | |||
| virtual void onPortChange(const PortChangeEvent& e) {} | |||
| struct SampleRateChangeEvent { | |||
| float sampleRate; | |||
| float sampleTime; | |||
| }; | |||
| /** Called after the Engine sample rate changes. | |||
| */ | |||
| virtual void onSampleRateChange(const SampleRateChangeEvent& e) { | |||
| // Call deprecated event method by default | |||
| onSampleRateChange(); | |||
| } | |||
| struct ExpanderChangeEvent {}; | |||
| /** Called after the Engine sample rate changes. | |||
| */ | |||
| virtual void onExpanderChange(const ExpanderChangeEvent& e) {} | |||
| struct ResetEvent {}; | |||
| /** Called when the user resets (initializes) the module. | |||
| The default implementation resets all parameters to their default value, so you must call `Module::onRandomize(e)` if you want to keep this behavior. | |||
| */ | |||
| virtual void onReset(const ResetEvent& e); | |||
| struct RandomizeEvent {}; | |||
| /** Called when the user randomizes the module. | |||
| The default implementation randomizes all parameters by default, so you must call `Module::onRandomize(e)` if you want to keep this behavior. | |||
| */ | |||
| virtual void onRandomize(const RandomizeEvent& e); | |||
| /** DEPRECATED. Override `onAdd(e)` instead. */ | |||
| virtual void onAdd() {} | |||
| /** DEPRECATED. Override `onRemove(e)` instead. */ | |||
| virtual void onRemove() {} | |||
| /** DEPRECATED. Override `onReset(e)` instead. */ | |||
| virtual void onReset() {} | |||
| /** DEPRECATED. Override `onRandomize(e)` instead. */ | |||
| virtual void onRandomize() {} | |||
| /** DEPRECATED. Override `onSampleRateChange(e)` instead. */ | |||
| virtual void onSampleRateChange() {} | |||
| }; | |||
| @@ -19,6 +19,9 @@ struct Param { | |||
| void setValue(float value) { | |||
| this->value = value; | |||
| } | |||
| json_t* toJson(); | |||
| void fromJson(json_t* rootJ); | |||
| }; | |||
| @@ -35,8 +35,16 @@ struct ParamQuantity : Quantity { | |||
| float displayBase = 0.f; | |||
| float displayMultiplier = 1.f; | |||
| float displayOffset = 0.f; | |||
| int displayPrecision = 5; | |||
| /** An optional one-sentence description of the parameter. */ | |||
| std::string description; | |||
| /** Enables parameter resetting when the module is reset. | |||
| */ | |||
| bool resetEnabled = true; | |||
| /** Enables parameter randomization when the module is randomized. | |||
| Unbounded (infinite) parameters are not randomizable, regardless of this setting. | |||
| */ | |||
| bool randomizeEnabled = true; | |||
| Param* getParam(); | |||
| /** Request to the engine to smoothly set the value */ | |||
| @@ -33,6 +33,11 @@ struct alignas(32) Port { | |||
| */ | |||
| Light plugLights[3]; | |||
| enum Type { | |||
| INPUT, | |||
| OUTPUT, | |||
| }; | |||
| /** Sets the voltage of the given channel. */ | |||
| void setVoltage(float voltage, int channel = 0) { | |||
| voltages[channel] = voltage; | |||
| @@ -168,8 +173,6 @@ struct alignas(32) Port { | |||
| return channels > 1; | |||
| } | |||
| void process(float deltaTime); | |||
| /** Use getNormalVoltage() instead. */ | |||
| DEPRECATED float normalize(float normalVoltage) { | |||
| return getNormalVoltage(normalVoltage); | |||
| @@ -24,15 +24,13 @@ plugin::Model* createModel(const std::string& slug) { | |||
| m->model = this; | |||
| return m; | |||
| } | |||
| app::ModuleWidget* createModuleWidget() override { | |||
| TModule* m = new TModule; | |||
| m->engine::Module::model = this; | |||
| app::ModuleWidget* mw = new TModuleWidget(m); | |||
| mw->model = this; | |||
| return mw; | |||
| } | |||
| app::ModuleWidget* createModuleWidgetNull() override { | |||
| app::ModuleWidget* mw = new TModuleWidget(NULL); | |||
| app::ModuleWidget* createModuleWidget(engine::Module* m) override { | |||
| TModule *tm = NULL; | |||
| if (m) { | |||
| assert(m->model == this); | |||
| tm = dynamic_cast<TModule*>(m); | |||
| } | |||
| app::ModuleWidget* mw = new TModuleWidget(tm); | |||
| mw->model = this; | |||
| return mw; | |||
| } | |||
| @@ -97,12 +97,12 @@ struct ModuleMove : ModuleAction { | |||
| }; | |||
| struct ModuleBypass : ModuleAction { | |||
| bool bypass; | |||
| struct ModuleDisable : ModuleAction { | |||
| bool disabled; | |||
| void undo() override; | |||
| void redo() override; | |||
| ModuleBypass() { | |||
| name = "bypass module"; | |||
| ModuleDisable() { | |||
| name = "disable module"; | |||
| } | |||
| }; | |||
| @@ -133,10 +133,10 @@ struct ParamChange : ModuleAction { | |||
| struct CableAdd : Action { | |||
| int cableId; | |||
| int outputModuleId; | |||
| int outputId; | |||
| int inputModuleId; | |||
| int inputId; | |||
| int outputModuleId; | |||
| int outputId; | |||
| NVGcolor color; | |||
| void setCable(app::CableWidget* cw); | |||
| void undo() override; | |||
| @@ -33,6 +33,7 @@ struct PatchManager { | |||
| json_t* toJson(); | |||
| void fromJson(json_t* rootJ); | |||
| bool isLegacy(int level); | |||
| void log(std::string msg); | |||
| }; | |||
| @@ -39,16 +39,14 @@ struct Model { | |||
| std::string description; | |||
| virtual ~Model() {} | |||
| /** Creates a headless Module */ | |||
| /** Creates a Module. */ | |||
| virtual engine::Module* createModule() { | |||
| return NULL; | |||
| } | |||
| /** Creates a ModuleWidget with a Module attached */ | |||
| virtual app::ModuleWidget* createModuleWidget() { | |||
| return NULL; | |||
| } | |||
| /** Creates a ModuleWidget with no Module, useful for previews */ | |||
| virtual app::ModuleWidget* createModuleWidgetNull() { | |||
| /** Creates a ModuleWidget with a Module attached. | |||
| Module may be NULL. | |||
| */ | |||
| virtual app::ModuleWidget* createModuleWidget(engine::Module* m) { | |||
| return NULL; | |||
| } | |||
| @@ -38,7 +38,8 @@ std::string Quantity::getString() { | |||
| std::string label = getLabel(); | |||
| if (!label.empty()) | |||
| s += label + ": "; | |||
| s += getDisplayValueString() + getUnit(); | |||
| s += getDisplayValueString(); | |||
| s += getUnit(); | |||
| return s; | |||
| } | |||
| @@ -5,12 +5,110 @@ | |||
| #include <app.hpp> | |||
| #include <patch.hpp> | |||
| #include <settings.hpp> | |||
| #include <engine/Engine.hpp> | |||
| #include <engine/Port.hpp> | |||
| namespace rack { | |||
| namespace app { | |||
| CableWidget::CableWidget() { | |||
| color = color::BLACK_TRANSPARENT; | |||
| if (!settings::cableColors.empty()) { | |||
| int id = APP->scene->rack->nextCableColorId++; | |||
| APP->scene->rack->nextCableColorId %= settings::cableColors.size(); | |||
| color = settings::cableColors[id]; | |||
| } | |||
| } | |||
| CableWidget::~CableWidget() { | |||
| setCable(NULL); | |||
| } | |||
| bool CableWidget::isComplete() { | |||
| return outputPort && inputPort; | |||
| } | |||
| void CableWidget::updateCable() { | |||
| if (cable) { | |||
| APP->engine->removeCable(cable); | |||
| delete cable; | |||
| cable = NULL; | |||
| } | |||
| if (inputPort && outputPort) { | |||
| cable = new engine::Cable; | |||
| cable->inputModule = inputPort->module; | |||
| cable->inputId = inputPort->portId; | |||
| cable->outputModule = outputPort->module; | |||
| cable->outputId = outputPort->portId; | |||
| APP->engine->addCable(cable); | |||
| } | |||
| } | |||
| void CableWidget::setCable(engine::Cable* cable) { | |||
| if (this->cable) { | |||
| APP->engine->removeCable(this->cable); | |||
| delete this->cable; | |||
| this->cable = NULL; | |||
| } | |||
| this->cable = cable; | |||
| if (cable) { | |||
| app::ModuleWidget* outputModule = APP->scene->rack->getModule(cable->outputModule->id); | |||
| assert(outputModule); | |||
| outputPort = outputModule->getOutput(cable->outputId); | |||
| app::ModuleWidget* inputModule = APP->scene->rack->getModule(cable->inputModule->id); | |||
| assert(inputModule); | |||
| inputPort = inputModule->getInput(cable->inputId); | |||
| } | |||
| else { | |||
| outputPort = NULL; | |||
| inputPort = NULL; | |||
| } | |||
| } | |||
| math::Vec CableWidget::getInputPos() { | |||
| if (inputPort) { | |||
| return inputPort->getRelativeOffset(inputPort->box.zeroPos().getCenter(), APP->scene->rack); | |||
| } | |||
| else if (hoveredInputPort) { | |||
| return hoveredInputPort->getRelativeOffset(hoveredInputPort->box.zeroPos().getCenter(), APP->scene->rack); | |||
| } | |||
| else { | |||
| return APP->scene->rack->mousePos; | |||
| } | |||
| } | |||
| math::Vec CableWidget::getOutputPos() { | |||
| if (outputPort) { | |||
| return outputPort->getRelativeOffset(outputPort->box.zeroPos().getCenter(), APP->scene->rack); | |||
| } | |||
| else if (hoveredOutputPort) { | |||
| return hoveredOutputPort->getRelativeOffset(hoveredOutputPort->box.zeroPos().getCenter(), APP->scene->rack); | |||
| } | |||
| else { | |||
| return APP->scene->rack->mousePos; | |||
| } | |||
| } | |||
| json_t* CableWidget::toJson() { | |||
| json_t* rootJ = json_object(); | |||
| std::string s = color::toHexString(color); | |||
| json_object_set_new(rootJ, "color", json_string(s.c_str())); | |||
| return rootJ; | |||
| } | |||
| void CableWidget::fromJson(json_t* rootJ) { | |||
| json_t* colorJ = json_object_get(rootJ, "color"); | |||
| if (colorJ) { | |||
| // v0.6.0 and earlier patches use JSON objects. Just ignore them if so and use the existing cable color. | |||
| if (json_is_string(colorJ)) | |||
| color = color::fromHexString(json_string_value(colorJ)); | |||
| } | |||
| } | |||
| static void drawPlug(NVGcontext* vg, math::Vec pos, NVGcolor color) { | |||
| NVGcolor colorOutline = nvgLerpRGBA(color, nvgRGBf(0.0, 0.0, 0.0), 0.5); | |||
| @@ -79,156 +177,6 @@ static void drawCable(NVGcontext* vg, math::Vec pos1, math::Vec pos2, NVGcolor c | |||
| } | |||
| } | |||
| CableWidget::CableWidget() { | |||
| cable = new engine::Cable; | |||
| color = color::BLACK_TRANSPARENT; | |||
| if (!settings::cableColors.empty()) { | |||
| int id = APP->scene->rack->nextCableColorId++; | |||
| APP->scene->rack->nextCableColorId %= settings::cableColors.size(); | |||
| color = settings::cableColors[id]; | |||
| } | |||
| } | |||
| CableWidget::~CableWidget() { | |||
| delete cable; | |||
| } | |||
| bool CableWidget::isComplete() { | |||
| return outputPort && inputPort; | |||
| } | |||
| void CableWidget::setOutput(PortWidget* outputPort) { | |||
| this->outputPort = outputPort; | |||
| if (outputPort) { | |||
| assert(outputPort->type == PortWidget::OUTPUT); | |||
| cable->outputModule = outputPort->module; | |||
| cable->outputId = outputPort->portId; | |||
| } | |||
| } | |||
| void CableWidget::setInput(PortWidget* inputPort) { | |||
| this->inputPort = inputPort; | |||
| if (inputPort) { | |||
| assert(inputPort->type == PortWidget::INPUT); | |||
| cable->inputModule = inputPort->module; | |||
| cable->inputId = inputPort->portId; | |||
| } | |||
| } | |||
| math::Vec CableWidget::getOutputPos() { | |||
| if (outputPort) { | |||
| return outputPort->getRelativeOffset(outputPort->box.zeroPos().getCenter(), APP->scene->rack); | |||
| } | |||
| else if (hoveredOutputPort) { | |||
| return hoveredOutputPort->getRelativeOffset(hoveredOutputPort->box.zeroPos().getCenter(), APP->scene->rack); | |||
| } | |||
| else { | |||
| return APP->scene->rack->mousePos; | |||
| } | |||
| } | |||
| math::Vec CableWidget::getInputPos() { | |||
| if (inputPort) { | |||
| return inputPort->getRelativeOffset(inputPort->box.zeroPos().getCenter(), APP->scene->rack); | |||
| } | |||
| else if (hoveredInputPort) { | |||
| return hoveredInputPort->getRelativeOffset(hoveredInputPort->box.zeroPos().getCenter(), APP->scene->rack); | |||
| } | |||
| else { | |||
| return APP->scene->rack->mousePos; | |||
| } | |||
| } | |||
| json_t* CableWidget::toJson() { | |||
| assert(isComplete()); | |||
| json_t* rootJ = json_object(); | |||
| json_object_set_new(rootJ, "id", json_integer(cable->id)); | |||
| json_object_set_new(rootJ, "outputModuleId", json_integer(cable->outputModule->id)); | |||
| json_object_set_new(rootJ, "outputId", json_integer(cable->outputId)); | |||
| json_object_set_new(rootJ, "inputModuleId", json_integer(cable->inputModule->id)); | |||
| json_object_set_new(rootJ, "inputId", json_integer(cable->inputId)); | |||
| std::string s = color::toHexString(color); | |||
| json_object_set_new(rootJ, "color", json_string(s.c_str())); | |||
| return rootJ; | |||
| } | |||
| void CableWidget::fromJson(json_t* rootJ) { | |||
| // outputModuleId | |||
| json_t* outputModuleIdJ = json_object_get(rootJ, "outputModuleId"); | |||
| if (!outputModuleIdJ) | |||
| return; | |||
| int outputModuleId = json_integer_value(outputModuleIdJ); | |||
| ModuleWidget* outputModule = APP->scene->rack->getModule(outputModuleId); | |||
| if (!outputModule) | |||
| return; | |||
| // inputModuleId | |||
| json_t* inputModuleIdJ = json_object_get(rootJ, "inputModuleId"); | |||
| if (!inputModuleIdJ) | |||
| return; | |||
| int inputModuleId = json_integer_value(inputModuleIdJ); | |||
| ModuleWidget* inputModule = APP->scene->rack->getModule(inputModuleId); | |||
| if (!inputModule) | |||
| return; | |||
| // outputId | |||
| json_t* outputIdJ = json_object_get(rootJ, "outputId"); | |||
| if (!outputIdJ) | |||
| return; | |||
| int outputId = json_integer_value(outputIdJ); | |||
| // inputId | |||
| json_t* inputIdJ = json_object_get(rootJ, "inputId"); | |||
| if (!inputIdJ) | |||
| return; | |||
| int inputId = json_integer_value(inputIdJ); | |||
| // Only set ID if unset | |||
| if (cable->id < 0) { | |||
| // id | |||
| json_t* idJ = json_object_get(rootJ, "id"); | |||
| // Before 1.0, cables IDs were not used, so just leave it as default and Engine will assign one automatically. | |||
| if (idJ) { | |||
| cable->id = json_integer_value(idJ); | |||
| } | |||
| } | |||
| // Set ports | |||
| if (APP->patch->isLegacy(1)) { | |||
| // Before 0.6, the index of the "ports" array was the index of the PortWidget in the `outputs` and `inputs` vector. | |||
| setOutput(outputModule->outputs[outputId]); | |||
| setInput(inputModule->inputs[inputId]); | |||
| } | |||
| else { | |||
| for (PortWidget* port : outputModule->outputs) { | |||
| if (port->portId == outputId) { | |||
| setOutput(port); | |||
| break; | |||
| } | |||
| } | |||
| for (PortWidget* port : inputModule->inputs) { | |||
| if (port->portId == inputId) { | |||
| setInput(port); | |||
| break; | |||
| } | |||
| } | |||
| } | |||
| if (!isComplete()) | |||
| return; | |||
| json_t* colorJ = json_object_get(rootJ, "color"); | |||
| if (colorJ) { | |||
| // v0.6.0 and earlier patches use JSON objects. Just ignore them if so and use the existing cable color. | |||
| if (json_is_string(colorJ)) | |||
| color = color::fromHexString(json_string_value(colorJ)); | |||
| } | |||
| } | |||
| void CableWidget::draw(const DrawArgs& args) { | |||
| float opacity = settings::cableOpacity; | |||
| float tension = settings::cableTension; | |||
| @@ -18,6 +18,7 @@ | |||
| #include <app/Scene.hpp> | |||
| #include <plugin.hpp> | |||
| #include <app.hpp> | |||
| #include <engine/Engine.hpp> | |||
| #include <plugin/Model.hpp> | |||
| #include <string.hpp> | |||
| #include <history.hpp> | |||
| @@ -82,9 +83,11 @@ static bool isModelVisible(plugin::Model* model, const std::string& search, cons | |||
| } | |||
| static ModuleWidget* chooseModel(plugin::Model* model) { | |||
| // Create module | |||
| ModuleWidget* moduleWidget = model->createModuleWidget(); | |||
| assert(moduleWidget); | |||
| // Create Module and ModuleWidget | |||
| engine::Module* module = model->createModule(); | |||
| APP->engine->addModule(module); | |||
| ModuleWidget* moduleWidget = model->createModuleWidget(module); | |||
| APP->scene->rack->addModuleAtMouse(moduleWidget); | |||
| // Push ModuleAdd history action | |||
| @@ -170,7 +173,7 @@ struct ModelBox : widget::OpaqueWidget { | |||
| zoomWidget->setZoom(MODEL_BOX_ZOOM); | |||
| previewFb->addChild(zoomWidget); | |||
| ModuleWidget* moduleWidget = model->createModuleWidgetNull(); | |||
| ModuleWidget* moduleWidget = model->createModuleWidget(NULL); | |||
| zoomWidget->addChild(moduleWidget); | |||
| zoomWidget->box.size.x = moduleWidget->box.size.x * MODEL_BOX_ZOOM; | |||
| @@ -225,10 +225,10 @@ struct ModuleCloneItem : ui::MenuItem { | |||
| }; | |||
| struct ModuleBypassItem : ui::MenuItem { | |||
| struct ModuleDisableItem : ui::MenuItem { | |||
| ModuleWidget* moduleWidget; | |||
| void onAction(const event::Action& e) override { | |||
| moduleWidget->bypassAction(); | |||
| moduleWidget->disableAction(); | |||
| } | |||
| }; | |||
| @@ -253,14 +253,14 @@ ModuleWidget::~ModuleWidget() { | |||
| void ModuleWidget::draw(const DrawArgs& args) { | |||
| nvgScissor(args.vg, RECT_ARGS(args.clipBox)); | |||
| if (module && module->bypass) { | |||
| if (module && module->disabled) { | |||
| nvgGlobalAlpha(args.vg, 0.33); | |||
| } | |||
| Widget::draw(args); | |||
| // Power meter | |||
| if (module && settings::cpuMeter && !module->bypass) { | |||
| if (module && settings::cpuMeter && !module->disabled) { | |||
| nvgBeginPath(args.vg); | |||
| nvgRect(args.vg, | |||
| 0, box.size.y - 35, | |||
| @@ -363,7 +363,7 @@ void ModuleWidget::onHoverKey(const event::HoverKey& e) { | |||
| } break; | |||
| case GLFW_KEY_E: { | |||
| if ((e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { | |||
| bypassAction(); | |||
| disableAction(); | |||
| e.consume(this); | |||
| } | |||
| } break; | |||
| @@ -419,7 +419,9 @@ void ModuleWidget::onDragMove(const event::DragMove& e) { | |||
| void ModuleWidget::setModule(engine::Module* module) { | |||
| if (this->module) { | |||
| APP->engine->removeModule(this->module); | |||
| delete this->module; | |||
| this->module = NULL; | |||
| } | |||
| this->module = module; | |||
| } | |||
| @@ -496,16 +498,11 @@ PortWidget* ModuleWidget::getInput(int inputId) { | |||
| } | |||
| json_t* ModuleWidget::toJson() { | |||
| if (!module) | |||
| return NULL; | |||
| json_t* rootJ = module->toJson(); | |||
| return rootJ; | |||
| json_t* moduleJ = module->toJson(); | |||
| return moduleJ; | |||
| } | |||
| void ModuleWidget::fromJson(json_t* rootJ) { | |||
| if (!module) | |||
| return; | |||
| module->fromJson(rootJ); | |||
| } | |||
| @@ -736,16 +733,16 @@ void ModuleWidget::disconnectAction() { | |||
| } | |||
| void ModuleWidget::cloneAction() { | |||
| ModuleWidget* clonedModuleWidget = model->createModuleWidget(); | |||
| assert(clonedModuleWidget); | |||
| engine::Module* clonedModule = model->createModule(); | |||
| // JSON serialization is the obvious way to do this | |||
| json_t* moduleJ = toJson(); | |||
| clonedModuleWidget->fromJson(moduleJ); | |||
| clonedModule->fromJson(moduleJ); | |||
| json_decref(moduleJ); | |||
| // Reset ID so the Engine automatically assigns a new one | |||
| clonedModuleWidget->module->id = -1; | |||
| clonedModule->id = -1; | |||
| APP->engine->addModule(clonedModule); | |||
| ModuleWidget* clonedModuleWidget = model->createModuleWidget(clonedModule); | |||
| APP->scene->rack->addModuleAtMouse(clonedModuleWidget); | |||
| // history::ModuleAdd | |||
| @@ -755,12 +752,12 @@ void ModuleWidget::cloneAction() { | |||
| APP->history->push(h); | |||
| } | |||
| void ModuleWidget::bypassAction() { | |||
| void ModuleWidget::disableAction() { | |||
| assert(module); | |||
| // history::ModuleBypass | |||
| history::ModuleBypass* h = new history::ModuleBypass; | |||
| // history::ModuleDisable | |||
| history::ModuleDisable* h = new history::ModuleDisable; | |||
| h->moduleId = module->id; | |||
| h->bypass = !module->bypass; | |||
| h->disabled = !module->disabled; | |||
| APP->history->push(h); | |||
| h->redo(); | |||
| } | |||
| @@ -826,13 +823,13 @@ void ModuleWidget::createContextMenu() { | |||
| cloneItem->moduleWidget = this; | |||
| menu->addChild(cloneItem); | |||
| ModuleBypassItem* bypassItem = new ModuleBypassItem; | |||
| bypassItem->text = "Disable"; | |||
| bypassItem->rightText = RACK_MOD_CTRL_NAME "+E"; | |||
| if (module && module->bypass) | |||
| bypassItem->rightText = CHECKMARK_STRING " " + bypassItem->rightText; | |||
| bypassItem->moduleWidget = this; | |||
| menu->addChild(bypassItem); | |||
| ModuleDisableItem* disableItem = new ModuleDisableItem; | |||
| disableItem->text = "Disable"; | |||
| disableItem->rightText = RACK_MOD_CTRL_NAME "+E"; | |||
| if (module && module->disabled) | |||
| disableItem->rightText = CHECKMARK_STRING " " + disableItem->rightText; | |||
| disableItem->moduleWidget = this; | |||
| menu->addChild(disableItem); | |||
| ModuleDeleteItem* deleteItem = new ModuleDeleteItem; | |||
| deleteItem->text = "Delete"; | |||
| @@ -183,14 +183,6 @@ void ParamWidget::onLeave(const event::Leave& e) { | |||
| } | |||
| } | |||
| void ParamWidget::fromJson(json_t* rootJ) { | |||
| json_t* valueJ = json_object_get(rootJ, "value"); | |||
| if (valueJ) { | |||
| if (paramQuantity) | |||
| paramQuantity->setValue(json_number_value(valueJ)); | |||
| } | |||
| } | |||
| void ParamWidget::createContextMenu() { | |||
| ui::Menu* menu = createMenu(); | |||
| @@ -93,13 +93,16 @@ void PortWidget::onDragStart(const event::DragStart& e) { | |||
| CableWidget* cw = NULL; | |||
| if ((APP->window->getMods() & RACK_MOD_MASK) == RACK_MOD_CTRL) { | |||
| if (type == OUTPUT) { | |||
| // Ctrl-clicking an output creates a new cable. | |||
| // Keep cable NULL. Will be created below | |||
| } | |||
| else { | |||
| // Ctrl-clicking an input clones the cable already patched to it. | |||
| CableWidget* topCw = APP->scene->rack->getTopCable(this); | |||
| if (topCw) { | |||
| cw = new CableWidget; | |||
| cw->setOutput(topCw->outputPort); | |||
| cw->outputPort = topCw->outputPort; | |||
| cw->updateCable(); | |||
| } | |||
| } | |||
| } | |||
| @@ -116,9 +119,10 @@ void PortWidget::onDragStart(const event::DragStart& e) { | |||
| // Disconnect and reuse existing cable | |||
| APP->scene->rack->removeCable(cw); | |||
| if (type == OUTPUT) | |||
| cw->setOutput(NULL); | |||
| cw->outputPort = NULL; | |||
| else | |||
| cw->setInput(NULL); | |||
| cw->inputPort = NULL; | |||
| cw->updateCable(); | |||
| } | |||
| } | |||
| @@ -126,9 +130,10 @@ void PortWidget::onDragStart(const event::DragStart& e) { | |||
| // Create a new cable | |||
| cw = new CableWidget; | |||
| if (type == OUTPUT) | |||
| cw->setOutput(this); | |||
| cw->outputPort = this; | |||
| else | |||
| cw->setInput(this); | |||
| cw->inputPort = this; | |||
| cw->updateCable(); | |||
| } | |||
| APP->scene->rack->setIncompleteCable(cw); | |||
| @@ -169,9 +174,10 @@ void PortWidget::onDragDrop(const event::DragDrop& e) { | |||
| if (cw) { | |||
| cw->hoveredOutputPort = cw->hoveredInputPort = NULL; | |||
| if (type == OUTPUT) | |||
| cw->setOutput(this); | |||
| cw->outputPort = this; | |||
| else | |||
| cw->setInput(this); | |||
| cw->inputPort = this; | |||
| cw->updateCable(); | |||
| } | |||
| } | |||
| @@ -19,29 +19,14 @@ namespace rack { | |||
| namespace app { | |||
| static ModuleWidget* moduleFromJson(json_t* moduleJ) { | |||
| // Get slugs | |||
| json_t* pluginSlugJ = json_object_get(moduleJ, "plugin"); | |||
| if (!pluginSlugJ) | |||
| return NULL; | |||
| std::string pluginSlug = json_string_value(pluginSlugJ); | |||
| pluginSlug = plugin::normalizeSlug(pluginSlug); | |||
| json_t* modelSlugJ = json_object_get(moduleJ, "model"); | |||
| if (!modelSlugJ) | |||
| return NULL; | |||
| std::string modelSlug = json_string_value(modelSlugJ); | |||
| modelSlug = plugin::normalizeSlug(modelSlug); | |||
| // Get Model | |||
| plugin::Model* model = plugin::getModel(pluginSlug, modelSlug); | |||
| if (!model) | |||
| return NULL; | |||
| /** Creates a new Module and ModuleWidget */ | |||
| ModuleWidget* moduleWidgetFromJson(json_t* moduleJ) { | |||
| engine::Module* module = engine::moduleFromJson(moduleJ); | |||
| assert(module); | |||
| // Create ModuleWidget | |||
| ModuleWidget* moduleWidget = model->createModuleWidget(); | |||
| ModuleWidget* moduleWidget = module->model->createModuleWidget(module); | |||
| assert(moduleWidget); | |||
| moduleWidget->fromJson(moduleJ); | |||
| return moduleWidget; | |||
| } | |||
| @@ -174,10 +159,7 @@ void RackWidget::clear() { | |||
| } | |||
| } | |||
| json_t* RackWidget::toJson() { | |||
| // root | |||
| json_t* rootJ = json_object(); | |||
| void RackWidget::mergeJson(json_t* rootJ) { | |||
| // Get module offset so modules are aligned to (0, 0) when the patch is loaded. | |||
| math::Vec moduleOffset = math::Vec(INFINITY, INFINITY); | |||
| for (widget::Widget* w : moduleContainer->children) { | |||
| @@ -188,39 +170,46 @@ json_t* RackWidget::toJson() { | |||
| } | |||
| // modules | |||
| json_t* modulesJ = json_array(); | |||
| for (widget::Widget* w : moduleContainer->children) { | |||
| ModuleWidget* moduleWidget = dynamic_cast<ModuleWidget*>(w); | |||
| assert(moduleWidget); | |||
| json_t* modulesJ = json_object_get(rootJ, "modules"); | |||
| assert(modulesJ); | |||
| size_t moduleIndex; | |||
| json_t* moduleJ; | |||
| json_array_foreach(modulesJ, moduleIndex, moduleJ) { | |||
| // module | |||
| json_t* moduleJ = moduleWidget->toJson(); | |||
| { | |||
| // pos | |||
| math::Vec pos = moduleWidget->box.pos.minus(moduleOffset); | |||
| pos = pos.div(RACK_GRID_SIZE).round(); | |||
| json_t* posJ = json_pack("[i, i]", (int) pos.x, (int) pos.y); | |||
| json_object_set_new(moduleJ, "pos", posJ); | |||
| } | |||
| json_array_append_new(modulesJ, moduleJ); | |||
| json_t* idJ = json_object_get(moduleJ, "id"); | |||
| if (!idJ) | |||
| continue; | |||
| int id = json_integer_value(idJ); | |||
| // TODO Legacy v0.6? | |||
| ModuleWidget* moduleWidget = getModule(id); | |||
| assert(moduleWidget); | |||
| // pos | |||
| math::Vec pos = moduleWidget->box.pos.minus(moduleOffset); | |||
| pos = pos.div(RACK_GRID_SIZE).round(); | |||
| json_t* posJ = json_pack("[i, i]", (int) pos.x, (int) pos.y); | |||
| json_object_set_new(moduleJ, "pos", posJ); | |||
| } | |||
| json_object_set_new(rootJ, "modules", modulesJ); | |||
| // cables | |||
| json_t* cablesJ = json_array(); | |||
| for (widget::Widget* w : cableContainer->children) { | |||
| CableWidget* cw = dynamic_cast<CableWidget*>(w); | |||
| assert(cw); | |||
| // Only serialize complete cables | |||
| if (!cw->isComplete()) | |||
| json_t* cablesJ = json_object_get(rootJ, "cables"); | |||
| assert(cablesJ); | |||
| size_t cableIndex; | |||
| json_t* cableJ; | |||
| json_array_foreach(cablesJ, cableIndex, cableJ) { | |||
| // cable | |||
| json_t* idJ = json_object_get(cableJ, "id"); | |||
| if (!idJ) | |||
| continue; | |||
| int id = json_integer_value(idJ); | |||
| CableWidget* cw = getCable(id); | |||
| assert(cw); | |||
| json_t* cableJ = cw->toJson(); | |||
| json_array_append_new(cablesJ, cableJ); | |||
| json_t* cwJ = cw->toJson(); | |||
| // Merge cable JSON object | |||
| json_object_update(cableJ, cwJ); | |||
| json_decref(cwJ); | |||
| } | |||
| json_object_set_new(rootJ, "cables", cablesJ); | |||
| return rootJ; | |||
| } | |||
| void RackWidget::fromJson(json_t* rootJ) { | |||
| @@ -231,55 +220,62 @@ void RackWidget::fromJson(json_t* rootJ) { | |||
| size_t moduleIndex; | |||
| json_t* moduleJ; | |||
| json_array_foreach(modulesJ, moduleIndex, moduleJ) { | |||
| ModuleWidget* moduleWidget = moduleFromJson(moduleJ); | |||
| // module | |||
| // Create ModuleWidget and attach it to existing Module from Engine. | |||
| json_t* idJ = json_object_get(moduleJ, "id"); | |||
| if (!idJ) | |||
| continue; | |||
| int id = json_integer_value(idJ); | |||
| engine::Module* module = APP->engine->getModule(id); | |||
| if (!module) | |||
| continue; | |||
| if (moduleWidget) { | |||
| // Before 1.0, the module ID was the index in the "modules" array | |||
| if (APP->patch->isLegacy(2)) { | |||
| moduleWidget->module->id = moduleIndex; | |||
| } | |||
| ModuleWidget* moduleWidget = module->model->createModuleWidget(module); | |||
| // pos | |||
| json_t* posJ = json_object_get(moduleJ, "pos"); | |||
| double x, y; | |||
| json_unpack(posJ, "[F, F]", &x, &y); | |||
| math::Vec pos = math::Vec(x, y); | |||
| if (APP->patch->isLegacy(1)) { | |||
| // Before 0.6, positions were in pixel units | |||
| moduleWidget->box.pos = pos; | |||
| } | |||
| else { | |||
| moduleWidget->box.pos = pos.mult(RACK_GRID_SIZE); | |||
| } | |||
| moduleWidget->box.pos = moduleWidget->box.pos.plus(RACK_OFFSET); | |||
| // Before 1.0, the module ID was the index in the "modules" array | |||
| if (APP->patch->isLegacy(2)) { | |||
| module->id = moduleIndex; | |||
| } | |||
| addModule(moduleWidget); | |||
| // pos | |||
| json_t* posJ = json_object_get(moduleJ, "pos"); | |||
| double x, y; | |||
| json_unpack(posJ, "[F, F]", &x, &y); | |||
| math::Vec pos = math::Vec(x, y); | |||
| if (APP->patch->isLegacy(1)) { | |||
| // In <=v0.5, positions were in pixel units | |||
| moduleWidget->box.pos = pos; | |||
| } | |||
| else { | |||
| json_t* pluginSlugJ = json_object_get(moduleJ, "plugin"); | |||
| json_t* modelSlugJ = json_object_get(moduleJ, "model"); | |||
| std::string pluginSlug = json_string_value(pluginSlugJ); | |||
| std::string modelSlug = json_string_value(modelSlugJ); | |||
| APP->patch->warningLog += string::f("Could not find module \"%s\" of plugin \"%s\"\n", modelSlug.c_str(), pluginSlug.c_str()); | |||
| moduleWidget->box.pos = pos.mult(RACK_GRID_SIZE); | |||
| } | |||
| moduleWidget->box.pos = moduleWidget->box.pos.plus(RACK_OFFSET); | |||
| addModule(moduleWidget); | |||
| } | |||
| // cables | |||
| json_t* cablesJ = json_object_get(rootJ, "cables"); | |||
| // Before 1.0, cables were called wires | |||
| // In <=v0.6, cables were called wires | |||
| if (!cablesJ) | |||
| cablesJ = json_object_get(rootJ, "wires"); | |||
| assert(cablesJ); | |||
| size_t cableIndex; | |||
| json_t* cableJ; | |||
| json_array_foreach(cablesJ, cableIndex, cableJ) { | |||
| // Create a unserialize cable | |||
| // cable | |||
| // Get Cable from Engine | |||
| json_t* idJ = json_object_get(cableJ, "id"); | |||
| if (!idJ) | |||
| continue; | |||
| int id = json_integer_value(idJ); | |||
| engine::Cable* cable = APP->engine->getCable(id); | |||
| if (!cable) | |||
| continue; | |||
| CableWidget* cw = new CableWidget; | |||
| cw->setCable(cable); | |||
| cw->fromJson(cableJ); | |||
| if (!cw->isComplete()) { | |||
| delete cw; | |||
| continue; | |||
| } | |||
| addCable(cw); | |||
| } | |||
| } | |||
| @@ -293,13 +289,21 @@ void RackWidget::pastePresetClipboardAction() { | |||
| json_error_t error; | |||
| json_t* moduleJ = json_loads(moduleJson, 0, &error); | |||
| if (moduleJ) { | |||
| ModuleWidget* mw = moduleFromJson(moduleJ); | |||
| if (!moduleJ) { | |||
| WARN("JSON parsing error at %s %d:%d %s", error.source, error.line, error.column, error.text); | |||
| return; | |||
| } | |||
| DEFER({ | |||
| json_decref(moduleJ); | |||
| }); | |||
| try { | |||
| engine::Module* module = engine::moduleFromJson(moduleJ); | |||
| // Reset ID so the Engine automatically assigns a new one | |||
| mw->module->id = -1; | |||
| module->id = -1; | |||
| APP->engine->addModule(module); | |||
| ModuleWidget* mw = module->model->createModuleWidget(module); | |||
| addModuleAtMouse(mw); | |||
| // history::ModuleAdd | |||
| @@ -307,8 +311,9 @@ void RackWidget::pastePresetClipboardAction() { | |||
| h->setModule(mw); | |||
| APP->history->push(h); | |||
| } | |||
| else { | |||
| WARN("JSON parsing error at %s %d:%d %s", error.source, error.line, error.column, error.text); | |||
| catch (Exception& e) { | |||
| WARN("%s", e.what()); | |||
| return; | |||
| } | |||
| } | |||
| @@ -352,10 +357,6 @@ void RackWidget::addModule(ModuleWidget* m) { | |||
| assert(m->box.size.y == RACK_GRID_HEIGHT); | |||
| moduleContainer->addChild(m); | |||
| if (m->module) { | |||
| // Add module to Engine | |||
| APP->engine->addModule(m->module); | |||
| } | |||
| RackWidget_updateAdjacent(this); | |||
| } | |||
| @@ -378,11 +379,6 @@ void RackWidget::removeModule(ModuleWidget* m) { | |||
| // Disconnect cables | |||
| m->disconnect(); | |||
| if (m->module) { | |||
| // Remove module from Engine | |||
| APP->engine->removeModule(m->module); | |||
| } | |||
| // Remove module from ModuleContainer | |||
| moduleContainer->removeChild(m); | |||
| } | |||
| @@ -556,14 +552,6 @@ history::ComplexAction* RackWidget::getModuleDragAction() { | |||
| void RackWidget::clearCables() { | |||
| for (widget::Widget* w : cableContainer->children) { | |||
| CableWidget* cw = dynamic_cast<CableWidget*>(w); | |||
| assert(cw); | |||
| if (!cw->isComplete()) | |||
| continue; | |||
| APP->engine->removeCable(cw->cable); | |||
| } | |||
| incompleteCable = NULL; | |||
| cableContainer->clearChildren(); | |||
| } | |||
| @@ -603,27 +591,25 @@ void RackWidget::clearCablesOnPort(PortWidget* port) { | |||
| } | |||
| } | |||
| void RackWidget::addCable(CableWidget* w) { | |||
| assert(w->isComplete()); | |||
| APP->engine->addCable(w->cable); | |||
| cableContainer->addChild(w); | |||
| void RackWidget::addCable(CableWidget* cw) { | |||
| assert(cw->isComplete()); | |||
| cableContainer->addChild(cw); | |||
| } | |||
| void RackWidget::removeCable(CableWidget* w) { | |||
| assert(w->isComplete()); | |||
| APP->engine->removeCable(w->cable); | |||
| cableContainer->removeChild(w); | |||
| void RackWidget::removeCable(CableWidget* cw) { | |||
| assert(cw->isComplete()); | |||
| cableContainer->removeChild(cw); | |||
| } | |||
| void RackWidget::setIncompleteCable(CableWidget* w) { | |||
| void RackWidget::setIncompleteCable(CableWidget* cw) { | |||
| if (incompleteCable) { | |||
| cableContainer->removeChild(incompleteCable); | |||
| delete incompleteCable; | |||
| incompleteCable = NULL; | |||
| } | |||
| if (w) { | |||
| cableContainer->addChild(w); | |||
| incompleteCable = w; | |||
| if (cw) { | |||
| cableContainer->addChild(cw); | |||
| incompleteCable = cw; | |||
| } | |||
| } | |||
| @@ -641,9 +627,6 @@ CableWidget* RackWidget::getTopCable(PortWidget* port) { | |||
| for (auto it = cableContainer->children.rbegin(); it != cableContainer->children.rend(); it++) { | |||
| CableWidget* cw = dynamic_cast<CableWidget*>(*it); | |||
| assert(cw); | |||
| // Ignore incomplete cables | |||
| if (!cw->isComplete()) | |||
| continue; | |||
| if (cw->inputPort == port || cw->outputPort == port) | |||
| return cw; | |||
| } | |||
| @@ -654,6 +637,8 @@ CableWidget* RackWidget::getCable(int cableId) { | |||
| for (widget::Widget* w : cableContainer->children) { | |||
| CableWidget* cw = dynamic_cast<CableWidget*>(w); | |||
| assert(cw); | |||
| if (!cw->cable) | |||
| continue; | |||
| if (cw->cable->id == cableId) | |||
| return cw; | |||
| } | |||
| @@ -662,15 +647,15 @@ CableWidget* RackWidget::getCable(int cableId) { | |||
| std::list<CableWidget*> RackWidget::getCablesOnPort(PortWidget* port) { | |||
| assert(port); | |||
| std::list<CableWidget*> cables; | |||
| std::list<CableWidget*> cws; | |||
| for (widget::Widget* w : cableContainer->children) { | |||
| CableWidget* cw = dynamic_cast<CableWidget*>(w); | |||
| assert(cw); | |||
| if (cw->inputPort == port || cw->outputPort == port) { | |||
| cables.push_back(cw); | |||
| cws.push_back(cw); | |||
| } | |||
| } | |||
| return cables; | |||
| return cws; | |||
| } | |||
| @@ -12,17 +12,24 @@ namespace rack { | |||
| namespace core { | |||
| template <int AUDIO_OUTPUTS, int AUDIO_INPUTS> | |||
| template <int NUM_AUDIO_OUTPUTS, int NUM_AUDIO_INPUTS> | |||
| struct AudioInterfacePort : audio::Port { | |||
| std::mutex engineMutex; | |||
| std::condition_variable engineCv; | |||
| std::mutex audioMutex; | |||
| std::condition_variable audioCv; | |||
| // Audio thread produces, engine thread consumes | |||
| dsp::DoubleRingBuffer < dsp::Frame<AUDIO_INPUTS>, (1 << 15) > inputBuffer; | |||
| // Audio thread consumes, engine thread produces | |||
| dsp::DoubleRingBuffer < dsp::Frame<AUDIO_OUTPUTS>, (1 << 15) > outputBuffer; | |||
| bool active = false; | |||
| // std::mutex engineMutex; | |||
| // std::condition_variable engineCv; | |||
| // std::mutex audioMutex; | |||
| // std::condition_variable audioCv; | |||
| // // Audio thread produces, engine thread consumes | |||
| // dsp::DoubleRingBuffer < dsp::Frame<NUM_AUDIO_INPUTS>, (1 << 15) > inputBuffer; | |||
| // // Audio thread consumes, engine thread produces | |||
| // dsp::DoubleRingBuffer < dsp::Frame<NUM_AUDIO_OUTPUTS>, (1 << 15) > outputBuffer; | |||
| // bool active = false; | |||
| // For checking getPrimaryModule() | |||
| Module* module = NULL; | |||
| const float* input = NULL; | |||
| float* output = NULL; | |||
| int frame = 0; | |||
| int numFrames = 0; | |||
| ~AudioInterfacePort() { | |||
| // Close stream here before destructing AudioInterfacePort, so the mutexes are still valid when waiting to close. | |||
| @@ -30,54 +37,67 @@ struct AudioInterfacePort : audio::Port { | |||
| } | |||
| void processStream(const float* input, float* output, int frames) override { | |||
| // Reactivate idle stream | |||
| if (!active) { | |||
| active = true; | |||
| inputBuffer.clear(); | |||
| outputBuffer.clear(); | |||
| if (APP->engine->getPrimaryModule() != module) { | |||
| // TEMP | |||
| std::memset(output, 0, sizeof(float) * frames * numOutputs); | |||
| return; | |||
| } | |||
| if (numInputs > 0) { | |||
| // TODO Do we need to wait on the input to be consumed here? Experimentally, it works fine if we don't. | |||
| for (int i = 0; i < frames; i++) { | |||
| if (inputBuffer.full()) | |||
| break; | |||
| dsp::Frame<AUDIO_INPUTS> inputFrame; | |||
| std::memset(&inputFrame, 0, sizeof(inputFrame)); | |||
| std::memcpy(&inputFrame, &input[numInputs * i], numInputs * sizeof(float)); | |||
| inputBuffer.push(inputFrame); | |||
| } | |||
| } | |||
| if (numOutputs > 0) { | |||
| std::unique_lock<std::mutex> lock(audioMutex); | |||
| auto cond = [&] { | |||
| return (outputBuffer.size() >= (size_t) frames); | |||
| }; | |||
| auto timeout = std::chrono::milliseconds(100); | |||
| if (audioCv.wait_for(lock, timeout, cond)) { | |||
| // Consume audio block | |||
| for (int i = 0; i < frames; i++) { | |||
| dsp::Frame<AUDIO_OUTPUTS> f = outputBuffer.shift(); | |||
| for (int j = 0; j < numOutputs; j++) { | |||
| output[numOutputs * i + j] = clamp(f.samples[j], -1.f, 1.f); | |||
| } | |||
| } | |||
| } | |||
| else { | |||
| // Timed out, fill output with zeros | |||
| std::memset(output, 0, frames * numOutputs * sizeof(float)); | |||
| // DEBUG("Audio Interface Port underflow"); | |||
| } | |||
| } | |||
| // Notify engine when finished processing | |||
| engineCv.notify_one(); | |||
| frame = 0; | |||
| numFrames = frames; | |||
| this->input = input; | |||
| this->output = output; | |||
| APP->engine->step(frames); | |||
| // // Reactivate idle stream | |||
| // if (!active) { | |||
| // active = true; | |||
| // inputBuffer.clear(); | |||
| // outputBuffer.clear(); | |||
| // } | |||
| // if (numInputs > 0) { | |||
| // // TODO Do we need to wait on the input to be consumed here? Experimentally, it works fine if we don't. | |||
| // for (int i = 0; i < frames; i++) { | |||
| // if (inputBuffer.full()) | |||
| // break; | |||
| // dsp::Frame<NUM_AUDIO_INPUTS> inputFrame; | |||
| // std::memset(&inputFrame, 0, sizeof(inputFrame)); | |||
| // std::memcpy(&inputFrame, &input[numInputs * i], numInputs * sizeof(float)); | |||
| // inputBuffer.push(inputFrame); | |||
| // } | |||
| // } | |||
| // if (numOutputs > 0) { | |||
| // std::unique_lock<std::mutex> lock(audioMutex); | |||
| // auto cond = [&] { | |||
| // return (outputBuffer.size() >= (size_t) frames); | |||
| // }; | |||
| // auto timeout = std::chrono::milliseconds(100); | |||
| // if (audioCv.wait_for(lock, timeout, cond)) { | |||
| // // Consume audio block | |||
| // for (int i = 0; i < frames; i++) { | |||
| // dsp::Frame<NUM_AUDIO_OUTPUTS> f = outputBuffer.shift(); | |||
| // for (int j = 0; j < numOutputs; j++) { | |||
| // output[numOutputs * i + j] = clamp(f.samples[j], -1.f, 1.f); | |||
| // } | |||
| // } | |||
| // } | |||
| // else { | |||
| // // Timed out, fill output with zeros | |||
| // std::memset(output, 0, frames * numOutputs * sizeof(float)); | |||
| // // DEBUG("Audio Interface Port underflow"); | |||
| // } | |||
| // } | |||
| // // Notify engine when finished processing | |||
| // engineCv.notify_one(); | |||
| } | |||
| void onCloseStream() override { | |||
| inputBuffer.clear(); | |||
| outputBuffer.clear(); | |||
| // inputBuffer.clear(); | |||
| // outputBuffer.clear(); | |||
| } | |||
| void onChannelsChange() override { | |||
| @@ -85,136 +105,161 @@ struct AudioInterfacePort : audio::Port { | |||
| }; | |||
| template <int AUDIO_OUTPUTS, int AUDIO_INPUTS> | |||
| template <int NUM_AUDIO_OUTPUTS, int NUM_AUDIO_INPUTS> | |||
| struct AudioInterface : Module { | |||
| enum ParamIds { | |||
| NUM_PARAMS | |||
| }; | |||
| enum InputIds { | |||
| ENUMS(AUDIO_INPUT, AUDIO_INPUTS), | |||
| ENUMS(AUDIO_INPUTS, NUM_AUDIO_INPUTS), | |||
| NUM_INPUTS | |||
| }; | |||
| enum OutputIds { | |||
| ENUMS(AUDIO_OUTPUT, AUDIO_OUTPUTS), | |||
| ENUMS(AUDIO_OUTPUTS, NUM_AUDIO_OUTPUTS), | |||
| NUM_OUTPUTS | |||
| }; | |||
| enum LightIds { | |||
| ENUMS(INPUT_LIGHT, AUDIO_INPUTS / 2), | |||
| ENUMS(OUTPUT_LIGHT, AUDIO_OUTPUTS / 2), | |||
| ENUMS(INPUT_LIGHTS, NUM_AUDIO_INPUTS / 2), | |||
| ENUMS(OUTPUT_LIGHTS, NUM_AUDIO_OUTPUTS / 2), | |||
| NUM_LIGHTS | |||
| }; | |||
| AudioInterfacePort<AUDIO_OUTPUTS, AUDIO_INPUTS> port; | |||
| int lastSampleRate = 0; | |||
| int lastNumOutputs = -1; | |||
| int lastNumInputs = -1; | |||
| AudioInterfacePort<NUM_AUDIO_OUTPUTS, NUM_AUDIO_INPUTS> port; | |||
| // int lastSampleRate = 0; | |||
| // int lastNumOutputs = -1; | |||
| // int lastNumInputs = -1; | |||
| dsp::SampleRateConverter<AUDIO_INPUTS> inputSrc; | |||
| dsp::SampleRateConverter<AUDIO_OUTPUTS> outputSrc; | |||
| // dsp::SampleRateConverter<NUM_AUDIO_INPUTS> inputSrc; | |||
| // dsp::SampleRateConverter<NUM_AUDIO_OUTPUTS> outputSrc; | |||
| // in rack's sample rate | |||
| dsp::DoubleRingBuffer<dsp::Frame<AUDIO_INPUTS>, 16> inputBuffer; | |||
| dsp::DoubleRingBuffer<dsp::Frame<AUDIO_OUTPUTS>, 16> outputBuffer; | |||
| // // in rack's sample rate | |||
| // dsp::DoubleRingBuffer<dsp::Frame<NUM_AUDIO_INPUTS>, 16> inputBuffer; | |||
| // dsp::DoubleRingBuffer<dsp::Frame<NUM_AUDIO_OUTPUTS>, 16> outputBuffer; | |||
| AudioInterface() { | |||
| config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); | |||
| port.maxChannels = std::max(AUDIO_OUTPUTS, AUDIO_INPUTS); | |||
| port.maxChannels = std::max(NUM_AUDIO_OUTPUTS, NUM_AUDIO_INPUTS); | |||
| port.module = this; | |||
| onSampleRateChange(); | |||
| } | |||
| void process(const ProcessArgs& args) override { | |||
| // Update SRC states | |||
| inputSrc.setRates(port.sampleRate, args.sampleRate); | |||
| outputSrc.setRates(args.sampleRate, port.sampleRate); | |||
| inputSrc.setChannels(port.numInputs); | |||
| outputSrc.setChannels(port.numOutputs); | |||
| // Inputs: audio engine -> rack engine | |||
| if (port.active && port.numInputs > 0) { | |||
| // Wait until inputs are present | |||
| // Give up after a timeout in case the audio device is being unresponsive. | |||
| std::unique_lock<std::mutex> lock(port.engineMutex); | |||
| auto cond = [&] { | |||
| return (!port.inputBuffer.empty()); | |||
| }; | |||
| auto timeout = std::chrono::milliseconds(200); | |||
| if (port.engineCv.wait_for(lock, timeout, cond)) { | |||
| // Convert inputs | |||
| int inLen = port.inputBuffer.size(); | |||
| int outLen = inputBuffer.capacity(); | |||
| inputSrc.process(port.inputBuffer.startData(), &inLen, inputBuffer.endData(), &outLen); | |||
| port.inputBuffer.startIncr(inLen); | |||
| inputBuffer.endIncr(outLen); | |||
| } | |||
| else { | |||
| // Give up on pulling input | |||
| port.active = false; | |||
| // DEBUG("Audio Interface underflow"); | |||
| } | |||
| // Claim primary module if there is none | |||
| if (!APP->engine->getPrimaryModule()) { | |||
| APP->engine->setPrimaryModule(this); | |||
| } | |||
| // Take input from buffer | |||
| dsp::Frame<AUDIO_INPUTS> inputFrame; | |||
| if (!inputBuffer.empty()) { | |||
| inputFrame = inputBuffer.shift(); | |||
| } | |||
| else { | |||
| std::memset(&inputFrame, 0, sizeof(inputFrame)); | |||
| // Get inputs | |||
| for (int i = 0; i < port.numOutputs; i++) { | |||
| float v = inputs[AUDIO_INPUTS + i].getVoltage() / 10.f; | |||
| port.output[port.frame * port.numOutputs + i] = v; | |||
| } | |||
| // Set outputs | |||
| for (int i = 0; i < port.numInputs; i++) { | |||
| outputs[AUDIO_OUTPUT + i].setVoltage(10.f * inputFrame.samples[i]); | |||
| float v = port.input[port.frame * port.numInputs + i]; | |||
| outputs[AUDIO_OUTPUTS + i].setVoltage(10.f * v); | |||
| } | |||
| for (int i = port.numInputs; i < AUDIO_INPUTS; i++) { | |||
| outputs[AUDIO_OUTPUT + i].setVoltage(0.f); | |||
| for (int i = port.numInputs; i < NUM_AUDIO_INPUTS; i++) { | |||
| outputs[AUDIO_OUTPUTS + i].setVoltage(0.f); | |||
| } | |||
| // Outputs: rack engine -> audio engine | |||
| if (port.active && port.numOutputs > 0) { | |||
| // Get and push output SRC frame | |||
| if (!outputBuffer.full()) { | |||
| dsp::Frame<AUDIO_OUTPUTS> outputFrame; | |||
| for (int i = 0; i < AUDIO_OUTPUTS; i++) { | |||
| outputFrame.samples[i] = inputs[AUDIO_INPUT + i].getVoltageSum() / 10.f; | |||
| } | |||
| outputBuffer.push(outputFrame); | |||
| } | |||
| if (outputBuffer.full()) { | |||
| // Wait until enough outputs are consumed | |||
| // Give up after a timeout in case the audio device is being unresponsive. | |||
| auto cond = [&] { | |||
| return (port.outputBuffer.size() < (size_t) port.blockSize); | |||
| }; | |||
| if (!cond()) | |||
| APP->engine->yieldWorkers(); | |||
| std::unique_lock<std::mutex> lock(port.engineMutex); | |||
| auto timeout = std::chrono::milliseconds(200); | |||
| if (port.engineCv.wait_for(lock, timeout, cond)) { | |||
| // Push converted output | |||
| int inLen = outputBuffer.size(); | |||
| int outLen = port.outputBuffer.capacity(); | |||
| outputSrc.process(outputBuffer.startData(), &inLen, port.outputBuffer.endData(), &outLen); | |||
| outputBuffer.startIncr(inLen); | |||
| port.outputBuffer.endIncr(outLen); | |||
| } | |||
| else { | |||
| // Give up on pushing output | |||
| port.active = false; | |||
| outputBuffer.clear(); | |||
| // DEBUG("Audio Interface underflow"); | |||
| } | |||
| } | |||
| // Notify audio thread that an output is potentially ready | |||
| port.audioCv.notify_one(); | |||
| } | |||
| port.frame++; | |||
| // // Update SRC states | |||
| // inputSrc.setRates(port.sampleRate, args.sampleRate); | |||
| // outputSrc.setRates(args.sampleRate, port.sampleRate); | |||
| // inputSrc.setChannels(port.numInputs); | |||
| // outputSrc.setChannels(port.numOutputs); | |||
| // // Inputs: audio engine -> rack engine | |||
| // if (port.active && port.numInputs > 0) { | |||
| // // Wait until inputs are present | |||
| // // Give up after a timeout in case the audio device is being unresponsive. | |||
| // std::unique_lock<std::mutex> lock(port.engineMutex); | |||
| // auto cond = [&] { | |||
| // return (!port.inputBuffer.empty()); | |||
| // }; | |||
| // auto timeout = std::chrono::milliseconds(200); | |||
| // if (port.engineCv.wait_for(lock, timeout, cond)) { | |||
| // // Convert inputs | |||
| // int inLen = port.inputBuffer.size(); | |||
| // int outLen = inputBuffer.capacity(); | |||
| // inputSrc.process(port.inputBuffer.startData(), &inLen, inputBuffer.endData(), &outLen); | |||
| // port.inputBuffer.startIncr(inLen); | |||
| // inputBuffer.endIncr(outLen); | |||
| // } | |||
| // else { | |||
| // // Give up on pulling input | |||
| // port.active = false; | |||
| // // DEBUG("Audio Interface underflow"); | |||
| // } | |||
| // } | |||
| // // Take input from buffer | |||
| // dsp::Frame<NUM_AUDIO_INPUTS> inputFrame; | |||
| // if (!inputBuffer.empty()) { | |||
| // inputFrame = inputBuffer.shift(); | |||
| // } | |||
| // else { | |||
| // std::memset(&inputFrame, 0, sizeof(inputFrame)); | |||
| // } | |||
| // for (int i = 0; i < port.numInputs; i++) { | |||
| // outputs[AUDIO_OUTPUTS + i].setVoltage(10.f * inputFrame.samples[i]); | |||
| // } | |||
| // for (int i = port.numInputs; i < NUM_AUDIO_INPUTS; i++) { | |||
| // outputs[AUDIO_OUTPUTS + i].setVoltage(0.f); | |||
| // } | |||
| // // Outputs: rack engine -> audio engine | |||
| // if (port.active && port.numOutputs > 0) { | |||
| // // Get and push output SRC frame | |||
| // if (!outputBuffer.full()) { | |||
| // dsp::Frame<NUM_AUDIO_OUTPUTS> outputFrame; | |||
| // for (int i = 0; i < NUM_AUDIO_OUTPUTS; i++) { | |||
| // outputFrame.samples[i] = inputs[AUDIO_INPUTS + i].getVoltageSum() / 10.f; | |||
| // } | |||
| // outputBuffer.push(outputFrame); | |||
| // } | |||
| // if (outputBuffer.full()) { | |||
| // // Wait until enough outputs are consumed | |||
| // // Give up after a timeout in case the audio device is being unresponsive. | |||
| // auto cond = [&] { | |||
| // return (port.outputBuffer.size() < (size_t) port.blockSize); | |||
| // }; | |||
| // if (!cond()) | |||
| // APP->engine->yieldWorkers(); | |||
| // std::unique_lock<std::mutex> lock(port.engineMutex); | |||
| // auto timeout = std::chrono::milliseconds(200); | |||
| // if (port.engineCv.wait_for(lock, timeout, cond)) { | |||
| // // Push converted output | |||
| // int inLen = outputBuffer.size(); | |||
| // int outLen = port.outputBuffer.capacity(); | |||
| // outputSrc.process(outputBuffer.startData(), &inLen, port.outputBuffer.endData(), &outLen); | |||
| // outputBuffer.startIncr(inLen); | |||
| // port.outputBuffer.endIncr(outLen); | |||
| // } | |||
| // else { | |||
| // // Give up on pushing output | |||
| // port.active = false; | |||
| // outputBuffer.clear(); | |||
| // // DEBUG("Audio Interface underflow"); | |||
| // } | |||
| // } | |||
| // // Notify audio thread that an output is potentially ready | |||
| // port.audioCv.notify_one(); | |||
| // } | |||
| // Turn on light if at least one port is enabled in the nearby pair | |||
| for (int i = 0; i < AUDIO_INPUTS / 2; i++) | |||
| lights[INPUT_LIGHT + i].setBrightness(port.active && port.numOutputs >= 2 * i + 1); | |||
| for (int i = 0; i < AUDIO_OUTPUTS / 2; i++) | |||
| lights[OUTPUT_LIGHT + i].setBrightness(port.active && port.numInputs >= 2 * i + 1); | |||
| for (int i = 0; i < NUM_AUDIO_INPUTS / 2; i++) { | |||
| lights[INPUT_LIGHTS + i].setBrightness(port.numOutputs >= 2 * i + 1); | |||
| } | |||
| for (int i = 0; i < NUM_AUDIO_OUTPUTS / 2; i++) { | |||
| lights[OUTPUT_LIGHTS + i].setBrightness(port.numInputs >= 2 * i + 1); | |||
| } | |||
| } | |||
| json_t* dataToJson() override { | |||
| @@ -225,7 +270,8 @@ struct AudioInterface : Module { | |||
| void dataFromJson(json_t* rootJ) override { | |||
| json_t* audioJ = json_object_get(rootJ, "audio"); | |||
| port.fromJson(audioJ); | |||
| if (audioJ) | |||
| port.fromJson(audioJ); | |||
| } | |||
| void onReset() override { | |||
| @@ -234,126 +280,145 @@ struct AudioInterface : Module { | |||
| }; | |||
| struct AudioInterface8Widget : ModuleWidget { | |||
| typedef AudioInterface<8, 8> TAudioInterface; | |||
| template <typename TAudioInterface> | |||
| struct PrimaryModuleItem : MenuItem { | |||
| TAudioInterface* module; | |||
| AudioInterface8Widget(TAudioInterface* module) { | |||
| setModule(module); | |||
| setPanel(APP->window->loadSvg(asset::system("res/Core/AudioInterface.svg"))); | |||
| addChild(createWidget<ScrewSilver>(Vec(RACK_GRID_WIDTH, 0))); | |||
| addChild(createWidget<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0))); | |||
| addChild(createWidget<ScrewSilver>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); | |||
| addChild(createWidget<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); | |||
| addInput(createInput<PJ301MPort>(mm2px(Vec(3.7069211, 55.530807)), module, TAudioInterface::AUDIO_INPUT + 0)); | |||
| addInput(createInput<PJ301MPort>(mm2px(Vec(15.307249, 55.530807)), module, TAudioInterface::AUDIO_INPUT + 1)); | |||
| addInput(createInput<PJ301MPort>(mm2px(Vec(26.906193, 55.530807)), module, TAudioInterface::AUDIO_INPUT + 2)); | |||
| addInput(createInput<PJ301MPort>(mm2px(Vec(38.506519, 55.530807)), module, TAudioInterface::AUDIO_INPUT + 3)); | |||
| addInput(createInput<PJ301MPort>(mm2px(Vec(3.7069209, 70.144905)), module, TAudioInterface::AUDIO_INPUT + 4)); | |||
| addInput(createInput<PJ301MPort>(mm2px(Vec(15.307249, 70.144905)), module, TAudioInterface::AUDIO_INPUT + 5)); | |||
| addInput(createInput<PJ301MPort>(mm2px(Vec(26.906193, 70.144905)), module, TAudioInterface::AUDIO_INPUT + 6)); | |||
| addInput(createInput<PJ301MPort>(mm2px(Vec(38.506519, 70.144905)), module, TAudioInterface::AUDIO_INPUT + 7)); | |||
| addOutput(createOutput<PJ301MPort>(mm2px(Vec(3.7069209, 92.143906)), module, TAudioInterface::AUDIO_OUTPUT + 0)); | |||
| addOutput(createOutput<PJ301MPort>(mm2px(Vec(15.307249, 92.143906)), module, TAudioInterface::AUDIO_OUTPUT + 1)); | |||
| addOutput(createOutput<PJ301MPort>(mm2px(Vec(26.906193, 92.143906)), module, TAudioInterface::AUDIO_OUTPUT + 2)); | |||
| addOutput(createOutput<PJ301MPort>(mm2px(Vec(38.506519, 92.143906)), module, TAudioInterface::AUDIO_OUTPUT + 3)); | |||
| addOutput(createOutput<PJ301MPort>(mm2px(Vec(3.7069209, 108.1443)), module, TAudioInterface::AUDIO_OUTPUT + 4)); | |||
| addOutput(createOutput<PJ301MPort>(mm2px(Vec(15.307249, 108.1443)), module, TAudioInterface::AUDIO_OUTPUT + 5)); | |||
| addOutput(createOutput<PJ301MPort>(mm2px(Vec(26.906193, 108.1443)), module, TAudioInterface::AUDIO_OUTPUT + 6)); | |||
| addOutput(createOutput<PJ301MPort>(mm2px(Vec(38.506523, 108.1443)), module, TAudioInterface::AUDIO_OUTPUT + 7)); | |||
| addChild(createLight<SmallLight<GreenLight>>(mm2px(Vec(12.524985, 54.577202)), module, TAudioInterface::INPUT_LIGHT + 0)); | |||
| addChild(createLight<SmallLight<GreenLight>>(mm2px(Vec(35.725647, 54.577202)), module, TAudioInterface::INPUT_LIGHT + 1)); | |||
| addChild(createLight<SmallLight<GreenLight>>(mm2px(Vec(12.524985, 69.158226)), module, TAudioInterface::INPUT_LIGHT + 2)); | |||
| addChild(createLight<SmallLight<GreenLight>>(mm2px(Vec(35.725647, 69.158226)), module, TAudioInterface::INPUT_LIGHT + 3)); | |||
| addChild(createLight<SmallLight<GreenLight>>(mm2px(Vec(12.524985, 91.147583)), module, TAudioInterface::OUTPUT_LIGHT + 0)); | |||
| addChild(createLight<SmallLight<GreenLight>>(mm2px(Vec(35.725647, 91.147583)), module, TAudioInterface::OUTPUT_LIGHT + 1)); | |||
| addChild(createLight<SmallLight<GreenLight>>(mm2px(Vec(12.524985, 107.17003)), module, TAudioInterface::OUTPUT_LIGHT + 2)); | |||
| addChild(createLight<SmallLight<GreenLight>>(mm2px(Vec(35.725647, 107.17003)), module, TAudioInterface::OUTPUT_LIGHT + 3)); | |||
| AudioWidget* audioWidget = createWidget<AudioWidget>(mm2px(Vec(3.2122073, 14.837339))); | |||
| audioWidget->box.size = mm2px(Vec(44, 28)); | |||
| audioWidget->setAudioPort(module ? &module->port : NULL); | |||
| addChild(audioWidget); | |||
| void onAction(const event::Action& e) override { | |||
| APP->engine->setPrimaryModule(module); | |||
| } | |||
| }; | |||
| struct AudioInterface16Widget : ModuleWidget { | |||
| typedef AudioInterface<16, 16> TAudioInterface; | |||
| template <int NUM_AUDIO_OUTPUTS, int NUM_AUDIO_INPUTS> | |||
| struct AudioInterfaceWidget : ModuleWidget { | |||
| typedef AudioInterface<NUM_AUDIO_OUTPUTS, NUM_AUDIO_INPUTS> TAudioInterface; | |||
| AudioInterface16Widget(TAudioInterface* module) { | |||
| AudioInterfaceWidget(TAudioInterface* module) { | |||
| setModule(module); | |||
| setPanel(APP->window->loadSvg(asset::system("res/Core/AudioInterface16.svg"))); | |||
| addChild(createWidget<ScrewSilver>(Vec(RACK_GRID_WIDTH, 0))); | |||
| addChild(createWidget<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0))); | |||
| addChild(createWidget<ScrewSilver>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); | |||
| addChild(createWidget<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); | |||
| addInput(createInputCentered<PJ301MPort>(mm2px(Vec(7.661, 59.638)), module, TAudioInterface::AUDIO_INPUT + 0)); | |||
| addInput(createInputCentered<PJ301MPort>(mm2px(Vec(19.26, 59.638)), module, TAudioInterface::AUDIO_INPUT + 1)); | |||
| addInput(createInputCentered<PJ301MPort>(mm2px(Vec(30.86, 59.638)), module, TAudioInterface::AUDIO_INPUT + 2)); | |||
| addInput(createInputCentered<PJ301MPort>(mm2px(Vec(42.461, 59.638)), module, TAudioInterface::AUDIO_INPUT + 3)); | |||
| addInput(createInputCentered<PJ301MPort>(mm2px(Vec(54.06, 59.638)), module, TAudioInterface::AUDIO_INPUT + 4)); | |||
| addInput(createInputCentered<PJ301MPort>(mm2px(Vec(65.661, 59.638)), module, TAudioInterface::AUDIO_INPUT + 5)); | |||
| addInput(createInputCentered<PJ301MPort>(mm2px(Vec(77.26, 59.638)), module, TAudioInterface::AUDIO_INPUT + 6)); | |||
| addInput(createInputCentered<PJ301MPort>(mm2px(Vec(88.86, 59.638)), module, TAudioInterface::AUDIO_INPUT + 7)); | |||
| addInput(createInputCentered<PJ301MPort>(mm2px(Vec(7.661, 74.251)), module, TAudioInterface::AUDIO_INPUT + 8)); | |||
| addInput(createInputCentered<PJ301MPort>(mm2px(Vec(19.26, 74.251)), module, TAudioInterface::AUDIO_INPUT + 9)); | |||
| addInput(createInputCentered<PJ301MPort>(mm2px(Vec(30.86, 74.251)), module, TAudioInterface::AUDIO_INPUT + 10)); | |||
| addInput(createInputCentered<PJ301MPort>(mm2px(Vec(42.461, 74.251)), module, TAudioInterface::AUDIO_INPUT + 11)); | |||
| addInput(createInputCentered<PJ301MPort>(mm2px(Vec(54.06, 74.251)), module, TAudioInterface::AUDIO_INPUT + 12)); | |||
| addInput(createInputCentered<PJ301MPort>(mm2px(Vec(65.661, 74.251)), module, TAudioInterface::AUDIO_INPUT + 13)); | |||
| addInput(createInputCentered<PJ301MPort>(mm2px(Vec(77.26, 74.251)), module, TAudioInterface::AUDIO_INPUT + 14)); | |||
| addInput(createInputCentered<PJ301MPort>(mm2px(Vec(88.86, 74.251)), module, TAudioInterface::AUDIO_INPUT + 15)); | |||
| addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(7.661, 96.251)), module, TAudioInterface::AUDIO_OUTPUT + 0)); | |||
| addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(19.26, 96.251)), module, TAudioInterface::AUDIO_OUTPUT + 1)); | |||
| addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(30.86, 96.251)), module, TAudioInterface::AUDIO_OUTPUT + 2)); | |||
| addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(42.461, 96.251)), module, TAudioInterface::AUDIO_OUTPUT + 3)); | |||
| addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(54.06, 96.251)), module, TAudioInterface::AUDIO_OUTPUT + 4)); | |||
| addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(65.661, 96.251)), module, TAudioInterface::AUDIO_OUTPUT + 5)); | |||
| addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(77.26, 96.251)), module, TAudioInterface::AUDIO_OUTPUT + 6)); | |||
| addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(88.86, 96.251)), module, TAudioInterface::AUDIO_OUTPUT + 7)); | |||
| addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(7.661, 112.252)), module, TAudioInterface::AUDIO_OUTPUT + 8)); | |||
| addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(19.26, 112.252)), module, TAudioInterface::AUDIO_OUTPUT + 9)); | |||
| addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(30.86, 112.252)), module, TAudioInterface::AUDIO_OUTPUT + 10)); | |||
| addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(42.461, 112.252)), module, TAudioInterface::AUDIO_OUTPUT + 11)); | |||
| addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(54.06, 112.252)), module, TAudioInterface::AUDIO_OUTPUT + 12)); | |||
| addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(65.661, 112.252)), module, TAudioInterface::AUDIO_OUTPUT + 13)); | |||
| addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(77.26, 112.252)), module, TAudioInterface::AUDIO_OUTPUT + 14)); | |||
| addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(88.86, 112.252)), module, TAudioInterface::AUDIO_OUTPUT + 15)); | |||
| addChild(createLightCentered<SmallLight<GreenLight>>(mm2px(Vec(13.46, 55.667)), module, TAudioInterface::INPUT_LIGHT + 0)); | |||
| addChild(createLightCentered<SmallLight<GreenLight>>(mm2px(Vec(36.661, 55.667)), module, TAudioInterface::INPUT_LIGHT + 1)); | |||
| addChild(createLightCentered<SmallLight<GreenLight>>(mm2px(Vec(59.861, 55.667)), module, TAudioInterface::INPUT_LIGHT + 2)); | |||
| addChild(createLightCentered<SmallLight<GreenLight>>(mm2px(Vec(83.061, 55.667)), module, TAudioInterface::INPUT_LIGHT + 3)); | |||
| addChild(createLightCentered<SmallLight<GreenLight>>(mm2px(Vec(13.46, 70.248)), module, TAudioInterface::INPUT_LIGHT + 4)); | |||
| addChild(createLightCentered<SmallLight<GreenLight>>(mm2px(Vec(36.661, 70.248)), module, TAudioInterface::INPUT_LIGHT + 5)); | |||
| addChild(createLightCentered<SmallLight<GreenLight>>(mm2px(Vec(59.861, 70.248)), module, TAudioInterface::INPUT_LIGHT + 6)); | |||
| addChild(createLightCentered<SmallLight<GreenLight>>(mm2px(Vec(83.061, 70.248)), module, TAudioInterface::INPUT_LIGHT + 7)); | |||
| addChild(createLightCentered<SmallLight<GreenLight>>(mm2px(Vec(13.46, 92.238)), module, TAudioInterface::OUTPUT_LIGHT + 0)); | |||
| addChild(createLightCentered<SmallLight<GreenLight>>(mm2px(Vec(36.661, 92.238)), module, TAudioInterface::OUTPUT_LIGHT + 1)); | |||
| addChild(createLightCentered<SmallLight<GreenLight>>(mm2px(Vec(59.861, 92.238)), module, TAudioInterface::OUTPUT_LIGHT + 2)); | |||
| addChild(createLightCentered<SmallLight<GreenLight>>(mm2px(Vec(83.061, 92.238)), module, TAudioInterface::OUTPUT_LIGHT + 3)); | |||
| addChild(createLightCentered<SmallLight<GreenLight>>(mm2px(Vec(13.46, 108.259)), module, TAudioInterface::OUTPUT_LIGHT + 4)); | |||
| addChild(createLightCentered<SmallLight<GreenLight>>(mm2px(Vec(36.661, 108.259)), module, TAudioInterface::OUTPUT_LIGHT + 5)); | |||
| addChild(createLightCentered<SmallLight<GreenLight>>(mm2px(Vec(59.861, 108.259)), module, TAudioInterface::OUTPUT_LIGHT + 6)); | |||
| addChild(createLightCentered<SmallLight<GreenLight>>(mm2px(Vec(83.061, 108.259)), module, TAudioInterface::OUTPUT_LIGHT + 7)); | |||
| AudioWidget* audioWidget = createWidget<AudioWidget>(mm2px(Vec(2.57, 14.839))); | |||
| audioWidget->box.size = mm2px(Vec(91.382, 28.0)); | |||
| audioWidget->setAudioPort(module ? &module->port : NULL); | |||
| addChild(audioWidget); | |||
| if (NUM_AUDIO_OUTPUTS == 8 && NUM_AUDIO_INPUTS == 8) { | |||
| setPanel(APP->window->loadSvg(asset::system("res/Core/AudioInterface.svg"))); | |||
| addChild(createWidget<ScrewSilver>(Vec(RACK_GRID_WIDTH, 0))); | |||
| addChild(createWidget<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0))); | |||
| addChild(createWidget<ScrewSilver>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); | |||
| addChild(createWidget<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); | |||
| addInput(createInput<PJ301MPort>(mm2px(Vec(3.7069211, 55.530807)), module, TAudioInterface::AUDIO_INPUTS + 0)); | |||
| addInput(createInput<PJ301MPort>(mm2px(Vec(15.307249, 55.530807)), module, TAudioInterface::AUDIO_INPUTS + 1)); | |||
| addInput(createInput<PJ301MPort>(mm2px(Vec(26.906193, 55.530807)), module, TAudioInterface::AUDIO_INPUTS + 2)); | |||
| addInput(createInput<PJ301MPort>(mm2px(Vec(38.506519, 55.530807)), module, TAudioInterface::AUDIO_INPUTS + 3)); | |||
| addInput(createInput<PJ301MPort>(mm2px(Vec(3.7069209, 70.144905)), module, TAudioInterface::AUDIO_INPUTS + 4)); | |||
| addInput(createInput<PJ301MPort>(mm2px(Vec(15.307249, 70.144905)), module, TAudioInterface::AUDIO_INPUTS + 5)); | |||
| addInput(createInput<PJ301MPort>(mm2px(Vec(26.906193, 70.144905)), module, TAudioInterface::AUDIO_INPUTS + 6)); | |||
| addInput(createInput<PJ301MPort>(mm2px(Vec(38.506519, 70.144905)), module, TAudioInterface::AUDIO_INPUTS + 7)); | |||
| addOutput(createOutput<PJ301MPort>(mm2px(Vec(3.7069209, 92.143906)), module, TAudioInterface::AUDIO_OUTPUTS + 0)); | |||
| addOutput(createOutput<PJ301MPort>(mm2px(Vec(15.307249, 92.143906)), module, TAudioInterface::AUDIO_OUTPUTS + 1)); | |||
| addOutput(createOutput<PJ301MPort>(mm2px(Vec(26.906193, 92.143906)), module, TAudioInterface::AUDIO_OUTPUTS + 2)); | |||
| addOutput(createOutput<PJ301MPort>(mm2px(Vec(38.506519, 92.143906)), module, TAudioInterface::AUDIO_OUTPUTS + 3)); | |||
| addOutput(createOutput<PJ301MPort>(mm2px(Vec(3.7069209, 108.1443)), module, TAudioInterface::AUDIO_OUTPUTS + 4)); | |||
| addOutput(createOutput<PJ301MPort>(mm2px(Vec(15.307249, 108.1443)), module, TAudioInterface::AUDIO_OUTPUTS + 5)); | |||
| addOutput(createOutput<PJ301MPort>(mm2px(Vec(26.906193, 108.1443)), module, TAudioInterface::AUDIO_OUTPUTS + 6)); | |||
| addOutput(createOutput<PJ301MPort>(mm2px(Vec(38.506523, 108.1443)), module, TAudioInterface::AUDIO_OUTPUTS + 7)); | |||
| addChild(createLight<SmallLight<GreenLight>>(mm2px(Vec(12.524985, 54.577202)), module, TAudioInterface::INPUT_LIGHTS + 0)); | |||
| addChild(createLight<SmallLight<GreenLight>>(mm2px(Vec(35.725647, 54.577202)), module, TAudioInterface::INPUT_LIGHTS + 1)); | |||
| addChild(createLight<SmallLight<GreenLight>>(mm2px(Vec(12.524985, 69.158226)), module, TAudioInterface::INPUT_LIGHTS + 2)); | |||
| addChild(createLight<SmallLight<GreenLight>>(mm2px(Vec(35.725647, 69.158226)), module, TAudioInterface::INPUT_LIGHTS + 3)); | |||
| addChild(createLight<SmallLight<GreenLight>>(mm2px(Vec(12.524985, 91.147583)), module, TAudioInterface::OUTPUT_LIGHTS + 0)); | |||
| addChild(createLight<SmallLight<GreenLight>>(mm2px(Vec(35.725647, 91.147583)), module, TAudioInterface::OUTPUT_LIGHTS + 1)); | |||
| addChild(createLight<SmallLight<GreenLight>>(mm2px(Vec(12.524985, 107.17003)), module, TAudioInterface::OUTPUT_LIGHTS + 2)); | |||
| addChild(createLight<SmallLight<GreenLight>>(mm2px(Vec(35.725647, 107.17003)), module, TAudioInterface::OUTPUT_LIGHTS + 3)); | |||
| AudioWidget* audioWidget = createWidget<AudioWidget>(mm2px(Vec(3.2122073, 14.837339))); | |||
| audioWidget->box.size = mm2px(Vec(44, 28)); | |||
| audioWidget->setAudioPort(module ? &module->port : NULL); | |||
| addChild(audioWidget); | |||
| } | |||
| else if (NUM_AUDIO_OUTPUTS == 16 && NUM_AUDIO_INPUTS == 16) { | |||
| setPanel(APP->window->loadSvg(asset::system("res/Core/AudioInterface16.svg"))); | |||
| addChild(createWidget<ScrewSilver>(Vec(RACK_GRID_WIDTH, 0))); | |||
| addChild(createWidget<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0))); | |||
| addChild(createWidget<ScrewSilver>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); | |||
| addChild(createWidget<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); | |||
| addInput(createInputCentered<PJ301MPort>(mm2px(Vec(7.661, 59.638)), module, TAudioInterface::AUDIO_INPUTS + 0)); | |||
| addInput(createInputCentered<PJ301MPort>(mm2px(Vec(19.26, 59.638)), module, TAudioInterface::AUDIO_INPUTS + 1)); | |||
| addInput(createInputCentered<PJ301MPort>(mm2px(Vec(30.86, 59.638)), module, TAudioInterface::AUDIO_INPUTS + 2)); | |||
| addInput(createInputCentered<PJ301MPort>(mm2px(Vec(42.461, 59.638)), module, TAudioInterface::AUDIO_INPUTS + 3)); | |||
| addInput(createInputCentered<PJ301MPort>(mm2px(Vec(54.06, 59.638)), module, TAudioInterface::AUDIO_INPUTS + 4)); | |||
| addInput(createInputCentered<PJ301MPort>(mm2px(Vec(65.661, 59.638)), module, TAudioInterface::AUDIO_INPUTS + 5)); | |||
| addInput(createInputCentered<PJ301MPort>(mm2px(Vec(77.26, 59.638)), module, TAudioInterface::AUDIO_INPUTS + 6)); | |||
| addInput(createInputCentered<PJ301MPort>(mm2px(Vec(88.86, 59.638)), module, TAudioInterface::AUDIO_INPUTS + 7)); | |||
| addInput(createInputCentered<PJ301MPort>(mm2px(Vec(7.661, 74.251)), module, TAudioInterface::AUDIO_INPUTS + 8)); | |||
| addInput(createInputCentered<PJ301MPort>(mm2px(Vec(19.26, 74.251)), module, TAudioInterface::AUDIO_INPUTS + 9)); | |||
| addInput(createInputCentered<PJ301MPort>(mm2px(Vec(30.86, 74.251)), module, TAudioInterface::AUDIO_INPUTS + 10)); | |||
| addInput(createInputCentered<PJ301MPort>(mm2px(Vec(42.461, 74.251)), module, TAudioInterface::AUDIO_INPUTS + 11)); | |||
| addInput(createInputCentered<PJ301MPort>(mm2px(Vec(54.06, 74.251)), module, TAudioInterface::AUDIO_INPUTS + 12)); | |||
| addInput(createInputCentered<PJ301MPort>(mm2px(Vec(65.661, 74.251)), module, TAudioInterface::AUDIO_INPUTS + 13)); | |||
| addInput(createInputCentered<PJ301MPort>(mm2px(Vec(77.26, 74.251)), module, TAudioInterface::AUDIO_INPUTS + 14)); | |||
| addInput(createInputCentered<PJ301MPort>(mm2px(Vec(88.86, 74.251)), module, TAudioInterface::AUDIO_INPUTS + 15)); | |||
| addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(7.661, 96.251)), module, TAudioInterface::AUDIO_OUTPUTS + 0)); | |||
| addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(19.26, 96.251)), module, TAudioInterface::AUDIO_OUTPUTS + 1)); | |||
| addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(30.86, 96.251)), module, TAudioInterface::AUDIO_OUTPUTS + 2)); | |||
| addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(42.461, 96.251)), module, TAudioInterface::AUDIO_OUTPUTS + 3)); | |||
| addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(54.06, 96.251)), module, TAudioInterface::AUDIO_OUTPUTS + 4)); | |||
| addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(65.661, 96.251)), module, TAudioInterface::AUDIO_OUTPUTS + 5)); | |||
| addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(77.26, 96.251)), module, TAudioInterface::AUDIO_OUTPUTS + 6)); | |||
| addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(88.86, 96.251)), module, TAudioInterface::AUDIO_OUTPUTS + 7)); | |||
| addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(7.661, 112.252)), module, TAudioInterface::AUDIO_OUTPUTS + 8)); | |||
| addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(19.26, 112.252)), module, TAudioInterface::AUDIO_OUTPUTS + 9)); | |||
| addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(30.86, 112.252)), module, TAudioInterface::AUDIO_OUTPUTS + 10)); | |||
| addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(42.461, 112.252)), module, TAudioInterface::AUDIO_OUTPUTS + 11)); | |||
| addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(54.06, 112.252)), module, TAudioInterface::AUDIO_OUTPUTS + 12)); | |||
| addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(65.661, 112.252)), module, TAudioInterface::AUDIO_OUTPUTS + 13)); | |||
| addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(77.26, 112.252)), module, TAudioInterface::AUDIO_OUTPUTS + 14)); | |||
| addOutput(createOutputCentered<PJ301MPort>(mm2px(Vec(88.86, 112.252)), module, TAudioInterface::AUDIO_OUTPUTS + 15)); | |||
| addChild(createLightCentered<SmallLight<GreenLight>>(mm2px(Vec(13.46, 55.667)), module, TAudioInterface::INPUT_LIGHTS + 0)); | |||
| addChild(createLightCentered<SmallLight<GreenLight>>(mm2px(Vec(36.661, 55.667)), module, TAudioInterface::INPUT_LIGHTS + 1)); | |||
| addChild(createLightCentered<SmallLight<GreenLight>>(mm2px(Vec(59.861, 55.667)), module, TAudioInterface::INPUT_LIGHTS + 2)); | |||
| addChild(createLightCentered<SmallLight<GreenLight>>(mm2px(Vec(83.061, 55.667)), module, TAudioInterface::INPUT_LIGHTS + 3)); | |||
| addChild(createLightCentered<SmallLight<GreenLight>>(mm2px(Vec(13.46, 70.248)), module, TAudioInterface::INPUT_LIGHTS + 4)); | |||
| addChild(createLightCentered<SmallLight<GreenLight>>(mm2px(Vec(36.661, 70.248)), module, TAudioInterface::INPUT_LIGHTS + 5)); | |||
| addChild(createLightCentered<SmallLight<GreenLight>>(mm2px(Vec(59.861, 70.248)), module, TAudioInterface::INPUT_LIGHTS + 6)); | |||
| addChild(createLightCentered<SmallLight<GreenLight>>(mm2px(Vec(83.061, 70.248)), module, TAudioInterface::INPUT_LIGHTS + 7)); | |||
| addChild(createLightCentered<SmallLight<GreenLight>>(mm2px(Vec(13.46, 92.238)), module, TAudioInterface::OUTPUT_LIGHTS + 0)); | |||
| addChild(createLightCentered<SmallLight<GreenLight>>(mm2px(Vec(36.661, 92.238)), module, TAudioInterface::OUTPUT_LIGHTS + 1)); | |||
| addChild(createLightCentered<SmallLight<GreenLight>>(mm2px(Vec(59.861, 92.238)), module, TAudioInterface::OUTPUT_LIGHTS + 2)); | |||
| addChild(createLightCentered<SmallLight<GreenLight>>(mm2px(Vec(83.061, 92.238)), module, TAudioInterface::OUTPUT_LIGHTS + 3)); | |||
| addChild(createLightCentered<SmallLight<GreenLight>>(mm2px(Vec(13.46, 108.259)), module, TAudioInterface::OUTPUT_LIGHTS + 4)); | |||
| addChild(createLightCentered<SmallLight<GreenLight>>(mm2px(Vec(36.661, 108.259)), module, TAudioInterface::OUTPUT_LIGHTS + 5)); | |||
| addChild(createLightCentered<SmallLight<GreenLight>>(mm2px(Vec(59.861, 108.259)), module, TAudioInterface::OUTPUT_LIGHTS + 6)); | |||
| addChild(createLightCentered<SmallLight<GreenLight>>(mm2px(Vec(83.061, 108.259)), module, TAudioInterface::OUTPUT_LIGHTS + 7)); | |||
| AudioWidget* audioWidget = createWidget<AudioWidget>(mm2px(Vec(2.57, 14.839))); | |||
| audioWidget->box.size = mm2px(Vec(91.382, 28.0)); | |||
| audioWidget->setAudioPort(module ? &module->port : NULL); | |||
| addChild(audioWidget); | |||
| } | |||
| } | |||
| void appendContextMenu(Menu* menu) override { | |||
| TAudioInterface* module = dynamic_cast<TAudioInterface*>(this->module); | |||
| menu->addChild(new MenuEntry); | |||
| PrimaryModuleItem<TAudioInterface>* primaryModuleItem = new PrimaryModuleItem<TAudioInterface>; | |||
| primaryModuleItem->text = "Primary audio module"; | |||
| primaryModuleItem->rightText = CHECKMARK(APP->engine->getPrimaryModule() == module); | |||
| primaryModuleItem->module = module; | |||
| menu->addChild(primaryModuleItem); | |||
| } | |||
| }; | |||
| Model* modelAudioInterface = createModel<AudioInterface<8, 8>, AudioInterface8Widget>("AudioInterface"); | |||
| Model* modelAudioInterface16 = createModel<AudioInterface<16, 16>, AudioInterface16Widget>("AudioInterface16"); | |||
| Model* modelAudioInterface = createModel<AudioInterface<8, 8>, AudioInterfaceWidget<8, 8>>("AudioInterface"); | |||
| Model* modelAudioInterface16 = createModel<AudioInterface<16, 16>, AudioInterfaceWidget<16, 16>>("AudioInterface16"); | |||
| } // namespace core | |||
| @@ -6,6 +6,31 @@ namespace rack { | |||
| namespace core { | |||
| struct BlankModule : Module { | |||
| int width = 4; | |||
| /** Legacy for <=v1 patches */ | |||
| void fromJson(json_t* rootJ) override { | |||
| Module::fromJson(rootJ); | |||
| json_t* widthJ = json_object_get(rootJ, "width"); | |||
| if (widthJ) | |||
| width = json_number_value(widthJ) / RACK_GRID_WIDTH; | |||
| } | |||
| json_t* dataToJson() override { | |||
| json_t* rootJ = json_object(); | |||
| json_object_set_new(rootJ, "width", json_integer(width)); | |||
| return rootJ; | |||
| } | |||
| void dataFromJson(json_t* rootJ) override { | |||
| json_t* widthJ = json_object_get(rootJ, "width"); | |||
| if (widthJ) | |||
| width = json_integer_value(widthJ); | |||
| } | |||
| }; | |||
| struct BlankPanel : Widget { | |||
| Widget* panelBorder; | |||
| @@ -120,6 +145,8 @@ struct BlankWidget : ModuleWidget { | |||
| } | |||
| void step() override { | |||
| // TODO Update from module | |||
| blankPanel->box.size = box.size; | |||
| topRightScrew->box.pos.x = box.size.x - 30; | |||
| bottomRightScrew->box.pos.x = box.size.x - 30; | |||
| @@ -132,24 +159,6 @@ struct BlankWidget : ModuleWidget { | |||
| rightHandle->box.pos.x = box.size.x - rightHandle->box.size.x; | |||
| ModuleWidget::step(); | |||
| } | |||
| json_t* toJson() override { | |||
| json_t* rootJ = ModuleWidget::toJson(); | |||
| // width | |||
| json_object_set_new(rootJ, "width", json_real(box.size.x)); | |||
| return rootJ; | |||
| } | |||
| void fromJson(json_t* rootJ) override { | |||
| ModuleWidget::fromJson(rootJ); | |||
| // width | |||
| json_t* widthJ = json_object_get(rootJ, "width"); | |||
| if (widthJ) | |||
| box.size.x = json_number_value(widthJ); | |||
| } | |||
| }; | |||
| @@ -5,7 +5,33 @@ namespace rack { | |||
| namespace core { | |||
| struct NotesModule : Module { | |||
| std::string text; | |||
| /** Legacy for <=v1 patches */ | |||
| void fromJson(json_t* rootJ) override { | |||
| Module::fromJson(rootJ); | |||
| json_t* textJ = json_object_get(rootJ, "text"); | |||
| if (textJ) | |||
| text = json_string_value(textJ); | |||
| } | |||
| json_t* dataToJson() override { | |||
| json_t* rootJ = json_object(); | |||
| json_object_set_new(rootJ, "text", json_string(text.c_str())); | |||
| return rootJ; | |||
| } | |||
| void dataFromJson(json_t* rootJ) override { | |||
| json_t* textJ = json_object_get(rootJ, "text"); | |||
| if (textJ) | |||
| text = json_string_value(textJ); | |||
| } | |||
| }; | |||
| struct NotesWidget : ModuleWidget { | |||
| // TODO Subclass this or something and keep `module->text` in sync with the text field's string. | |||
| TextField* textField; | |||
| NotesWidget(Module* module) { | |||
| @@ -22,24 +48,6 @@ struct NotesWidget : ModuleWidget { | |||
| textField->multiline = true; | |||
| addChild(textField); | |||
| } | |||
| json_t* toJson() override { | |||
| json_t* rootJ = ModuleWidget::toJson(); | |||
| // text | |||
| json_object_set_new(rootJ, "text", json_string(textField->text.c_str())); | |||
| return rootJ; | |||
| } | |||
| void fromJson(json_t* rootJ) override { | |||
| ModuleWidget::fromJson(rootJ); | |||
| // text | |||
| json_t* textJ = json_object_get(rootJ, "text"); | |||
| if (textJ) | |||
| textField->text = json_string_value(textJ); | |||
| } | |||
| }; | |||
| @@ -0,0 +1,64 @@ | |||
| #include <engine/Cable.hpp> | |||
| #include <engine/Engine.hpp> | |||
| #include <app.hpp> | |||
| namespace rack { | |||
| namespace engine { | |||
| json_t* Cable::toJson() { | |||
| json_t* rootJ = json_object(); | |||
| json_object_set_new(rootJ, "id", json_integer(id)); | |||
| json_object_set_new(rootJ, "outputModuleId", json_integer(outputModule->id)); | |||
| json_object_set_new(rootJ, "outputId", json_integer(outputId)); | |||
| json_object_set_new(rootJ, "inputModuleId", json_integer(inputModule->id)); | |||
| json_object_set_new(rootJ, "inputId", json_integer(inputId)); | |||
| return rootJ; | |||
| } | |||
| void Cable::fromJson(json_t* rootJ) { | |||
| // inputModuleId | |||
| json_t* inputModuleIdJ = json_object_get(rootJ, "inputModuleId"); | |||
| if (!inputModuleIdJ) | |||
| throw Exception("inputModuleId not found for cable"); | |||
| int inputModuleId = json_integer_value(inputModuleIdJ); | |||
| inputModule = APP->engine->getModule(inputModuleId); | |||
| if (!inputModule) | |||
| throw Exception("inputModule not found for cable"); | |||
| // inputId | |||
| json_t* inputIdJ = json_object_get(rootJ, "inputId"); | |||
| if (!inputIdJ) | |||
| throw Exception("inputId not found for cable"); | |||
| inputId = json_integer_value(inputIdJ); | |||
| // outputModuleId | |||
| json_t* outputModuleIdJ = json_object_get(rootJ, "outputModuleId"); | |||
| if (!outputModuleIdJ) | |||
| throw Exception("outputModuleId not found for cable"); | |||
| int outputModuleId = json_integer_value(outputModuleIdJ); | |||
| outputModule = APP->engine->getModule(outputModuleId); | |||
| if (!outputModule) | |||
| throw Exception("outputModule not found for cable"); | |||
| // outputId | |||
| json_t* outputIdJ = json_object_get(rootJ, "outputId"); | |||
| if (!outputIdJ) | |||
| throw Exception("outputId not found for cable"); | |||
| outputId = json_integer_value(outputIdJ); | |||
| // Only set ID if unset | |||
| if (id < 0) { | |||
| // id | |||
| json_t* idJ = json_object_get(rootJ, "id"); | |||
| // Before 1.0, cables IDs were not used, so just leave it as default and Engine will assign one automatically. | |||
| if (idJ) | |||
| id = json_integer_value(idJ); | |||
| } | |||
| } | |||
| } // namespace engine | |||
| } // namespace rack | |||
| @@ -2,6 +2,9 @@ | |||
| #include <settings.hpp> | |||
| #include <system.hpp> | |||
| #include <random.hpp> | |||
| #include <app.hpp> | |||
| #include <patch.hpp> | |||
| #include <plugin.hpp> | |||
| #include <algorithm> | |||
| #include <chrono> | |||
| @@ -27,38 +30,6 @@ static void initMXCSR() { | |||
| } | |||
| /** Threads which obtain a VIPLock will cause wait() to block for other less important threads. | |||
| This does not provide the VIPs with an exclusive lock. That should be left up to another mutex shared between the less important thread. | |||
| */ | |||
| struct VIPMutex { | |||
| int count = 0; | |||
| std::condition_variable cv; | |||
| std::mutex countMutex; | |||
| /** Blocks until there are no remaining VIPLocks */ | |||
| void wait() { | |||
| std::unique_lock<std::mutex> lock(countMutex); | |||
| while (count > 0) | |||
| cv.wait(lock); | |||
| } | |||
| }; | |||
| struct VIPLock { | |||
| VIPMutex& m; | |||
| VIPLock(VIPMutex& m) : m(m) { | |||
| std::unique_lock<std::mutex> lock(m.countMutex); | |||
| m.count++; | |||
| } | |||
| ~VIPLock() { | |||
| std::unique_lock<std::mutex> lock(m.countMutex); | |||
| m.count--; | |||
| lock.unlock(); | |||
| m.cv.notify_all(); | |||
| } | |||
| }; | |||
| struct Barrier { | |||
| std::mutex mutex; | |||
| std::condition_variable cv; | |||
| @@ -155,7 +126,6 @@ struct EngineWorker { | |||
| assert(!running); | |||
| running = true; | |||
| thread = std::thread([&] { | |||
| random::init(); | |||
| run(); | |||
| }); | |||
| } | |||
| @@ -180,22 +150,20 @@ struct Engine::Internal { | |||
| std::map<std::tuple<int, int>, ParamHandle*> paramHandleCache; | |||
| bool paused = false; | |||
| bool running = false; | |||
| float sampleRate; | |||
| float sampleTime; | |||
| float sampleRate = 0.f; | |||
| float sampleTime = 0.f; | |||
| uint64_t frame = 0; | |||
| Module* primaryModule = NULL; | |||
| int nextModuleId = 0; | |||
| int nextCableId = 0; | |||
| // Parameter smoothing | |||
| Module* smoothModule = NULL; | |||
| int smoothParamId; | |||
| float smoothValue; | |||
| int smoothParamId = 0; | |||
| float smoothValue = 0.f; | |||
| std::recursive_mutex mutex; | |||
| std::thread thread; | |||
| VIPMutex vipMutex; | |||
| bool realTime = false; | |||
| int threadCount = 0; | |||
| @@ -206,25 +174,31 @@ struct Engine::Internal { | |||
| }; | |||
| Engine::Engine() { | |||
| internal = new Internal; | |||
| internal->sampleRate = 44100.f; | |||
| internal->sampleTime = 1 / internal->sampleRate; | |||
| system::setThreadRealTime(false); | |||
| static void Port_step(Port* that, float deltaTime) { | |||
| // Set plug lights | |||
| if (that->channels == 0) { | |||
| that->plugLights[0].setBrightness(0.f); | |||
| that->plugLights[1].setBrightness(0.f); | |||
| that->plugLights[2].setBrightness(0.f); | |||
| } | |||
| else if (that->channels == 1) { | |||
| float v = that->getVoltage() / 10.f; | |||
| that->plugLights[0].setSmoothBrightness(v, deltaTime); | |||
| that->plugLights[1].setSmoothBrightness(-v, deltaTime); | |||
| that->plugLights[2].setBrightness(0.f); | |||
| } | |||
| else { | |||
| float v2 = 0.f; | |||
| for (int c = 0; c < that->channels; c++) { | |||
| v2 += std::pow(that->getVoltage(c), 2); | |||
| } | |||
| float v = std::sqrt(v2) / 10.f; | |||
| that->plugLights[0].setBrightness(0.f); | |||
| that->plugLights[1].setBrightness(0.f); | |||
| that->plugLights[2].setSmoothBrightness(v, deltaTime); | |||
| } | |||
| } | |||
| Engine::~Engine() { | |||
| // Make sure there are no cables or modules in the rack on destruction. | |||
| // If this happens, a module must have failed to remove itself before the RackWidget was destroyed. | |||
| assert(internal->cables.empty()); | |||
| assert(internal->modules.empty()); | |||
| assert(internal->paramHandles.empty()); | |||
| assert(internal->paramHandleCache.empty()); | |||
| delete internal; | |||
| } | |||
| static void Engine_stepModules(Engine* that, int threadId) { | |||
| Engine::Internal* internal = that->internal; | |||
| @@ -236,37 +210,28 @@ static void Engine_stepModules(Engine* that, int threadId) { | |||
| processArgs.sampleRate = internal->sampleRate; | |||
| processArgs.sampleTime = internal->sampleTime; | |||
| // Set up CPU meter | |||
| // Prime number to avoid synchronizing with power-of-2 buffers | |||
| const int timerDivider = 7; | |||
| bool timerEnabled = settings::cpuMeter && (internal->frame % timerDivider) == 0; | |||
| double timerOverhead = 0.f; | |||
| if (timerEnabled) { | |||
| double startTime = system::getThreadTime(); | |||
| double stopTime = system::getThreadTime(); | |||
| timerOverhead = stopTime - startTime; | |||
| } | |||
| bool cpuMeter = settings::cpuMeter; | |||
| // Step each module | |||
| // for (int i = threadId; i < modulesLen; i += threadCount) { | |||
| while (true) { | |||
| // Choose next module | |||
| // First-come-first serve module-to-thread allocation algorithm | |||
| int i = internal->workerModuleIndex++; | |||
| if (i >= modulesLen) | |||
| break; | |||
| Module* module = internal->modules[i]; | |||
| if (!module->bypass) { | |||
| if (!module->disabled) { | |||
| // Step module | |||
| if (timerEnabled) { | |||
| double startTime = system::getThreadTime(); | |||
| if (cpuMeter) { | |||
| auto beginTime = std::chrono::high_resolution_clock::now(); | |||
| module->process(processArgs); | |||
| double stopTime = system::getThreadTime(); | |||
| auto endTime = std::chrono::high_resolution_clock::now(); | |||
| float duration = std::chrono::duration<float>(endTime - beginTime).count(); | |||
| float cpuTime = std::fmax(0.f, stopTime - startTime - timerOverhead); | |||
| // Smooth CPU time | |||
| const float cpuTau = 2.f /* seconds */; | |||
| module->cpuTime += (cpuTime - module->cpuTime) * timerDivider * processArgs.sampleTime / cpuTau; | |||
| module->cpuTime += (duration - module->cpuTime) * processArgs.sampleTime / cpuTau; | |||
| } | |||
| else { | |||
| module->process(processArgs); | |||
| @@ -274,15 +239,20 @@ static void Engine_stepModules(Engine* that, int threadId) { | |||
| } | |||
| // Iterate ports to step plug lights | |||
| for (Input& input : module->inputs) { | |||
| input.process(processArgs.sampleTime); | |||
| } | |||
| for (Output& output : module->outputs) { | |||
| output.process(processArgs.sampleTime); | |||
| const int portDivider = 8; | |||
| if (internal->frame % portDivider == 0) { | |||
| float portTime = processArgs.sampleTime * portDivider; | |||
| for (Input& input : module->inputs) { | |||
| Port_step(&input, portTime); | |||
| } | |||
| for (Output& output : module->outputs) { | |||
| Port_step(&output, portTime); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| static void Cable_step(Cable* that) { | |||
| Output* output = &that->outputModule->outputs[that->outputId]; | |||
| Input* input = &that->inputModule->inputs[that->inputId]; | |||
| @@ -299,6 +269,7 @@ static void Cable_step(Cable* that) { | |||
| } | |||
| } | |||
| static void Engine_step(Engine* that) { | |||
| Engine::Internal* internal = that->internal; | |||
| @@ -349,6 +320,7 @@ static void Engine_step(Engine* that) { | |||
| internal->frame++; | |||
| } | |||
| static void Engine_updateExpander(Engine* that, Module::Expander* expander) { | |||
| if (expander->moduleId >= 0) { | |||
| if (!expander->module || expander->module->id != expander->moduleId) { | |||
| @@ -362,6 +334,7 @@ static void Engine_updateExpander(Engine* that, Module::Expander* expander) { | |||
| } | |||
| } | |||
| static void Engine_relaunchWorkers(Engine* that, int threadCount, bool realTime) { | |||
| Engine::Internal* internal = that->internal; | |||
| @@ -402,122 +375,141 @@ static void Engine_relaunchWorkers(Engine* that, int threadCount, bool realTime) | |||
| } | |||
| } | |||
| static void Engine_run(Engine* that) { | |||
| Engine::Internal* internal = that->internal; | |||
| // Set up thread | |||
| system::setThreadName("Engine"); | |||
| // system::setThreadRealTime(); | |||
| initMXCSR(); | |||
| internal->frame = 0; | |||
| // Every time the that waits and locks a mutex, it steps this many frames | |||
| const int mutexSteps = 128; | |||
| // Time in seconds that the that is rushing ahead of the estimated clock time | |||
| double aheadTime = 0.0; | |||
| auto lastTime = std::chrono::high_resolution_clock::now(); | |||
| while (internal->running) { | |||
| internal->vipMutex.wait(); | |||
| // Set sample rate | |||
| if (internal->sampleRate != settings::sampleRate) { | |||
| internal->sampleRate = settings::sampleRate; | |||
| internal->sampleTime = 1 / internal->sampleRate; | |||
| for (Module* module : internal->modules) { | |||
| module->onSampleRateChange(); | |||
| } | |||
| aheadTime = 0.0; | |||
| } | |||
| Engine::Engine() { | |||
| internal = new Internal; | |||
| if (!internal->paused) { | |||
| // Launch workers | |||
| if (internal->threadCount != settings::threadCount || internal->realTime != settings::realTime) { | |||
| Engine_relaunchWorkers(that, settings::threadCount, settings::realTime); | |||
| } | |||
| internal->sampleRate = 44100.f; | |||
| internal->sampleTime = 1 / internal->sampleRate; | |||
| } | |||
| std::lock_guard<std::recursive_mutex> lock(internal->mutex); | |||
| // Update expander pointers | |||
| for (Module* module : internal->modules) { | |||
| Engine_updateExpander(that, &module->leftExpander); | |||
| Engine_updateExpander(that, &module->rightExpander); | |||
| } | |||
| Engine::~Engine() { | |||
| Engine_relaunchWorkers(this, 0, false); | |||
| clear(); | |||
| // Step modules | |||
| for (int i = 0; i < mutexSteps; i++) { | |||
| Engine_step(that); | |||
| } | |||
| // Make sure there are no cables or modules in the rack on destruction. | |||
| // If this happens, a module must have failed to remove itself before the RackWidget was destroyed. | |||
| assert(internal->cables.empty()); | |||
| assert(internal->modules.empty()); | |||
| assert(internal->paramHandles.empty()); | |||
| assert(internal->paramHandleCache.empty()); | |||
| delete internal; | |||
| } | |||
| void Engine::clear() { | |||
| std::lock_guard<std::recursive_mutex> lock(internal->mutex); | |||
| // Copy lists because we'll be removing while iterating | |||
| std::set<ParamHandle*> paramHandles = internal->paramHandles; | |||
| for (ParamHandle* paramHandle : paramHandles) { | |||
| removeParamHandle(paramHandle); | |||
| } | |||
| std::vector<Cable*> cables = internal->cables; | |||
| for (Cable* cable : cables) { | |||
| removeCable(cable); | |||
| } | |||
| std::vector<Module*> modules = internal->modules; | |||
| for (Module* module : modules) { | |||
| removeModule(module); | |||
| } | |||
| // Reset engine state | |||
| internal->nextModuleId = 0; | |||
| internal->nextCableId = 0; | |||
| } | |||
| void Engine::step(int frames) { | |||
| initMXCSR(); | |||
| // Set sample rate | |||
| if (internal->sampleRate != settings::sampleRate) { | |||
| internal->sampleRate = settings::sampleRate; | |||
| internal->sampleTime = 1 / internal->sampleRate; | |||
| Module::SampleRateChangeEvent e; | |||
| e.sampleRate = internal->sampleRate; | |||
| e.sampleTime = internal->sampleTime; | |||
| for (Module* module : internal->modules) { | |||
| module->onSampleRateChange(e); | |||
| } | |||
| else { | |||
| // Stop workers while closed | |||
| if (internal->threadCount != 1) { | |||
| Engine_relaunchWorkers(that, 1, settings::realTime); | |||
| } | |||
| } | |||
| if (!internal->paused) { | |||
| // Launch workers | |||
| if (internal->threadCount != settings::threadCount || internal->realTime != settings::realTime) { | |||
| Engine_relaunchWorkers(this, settings::threadCount, settings::realTime); | |||
| } | |||
| double stepTime = mutexSteps * internal->sampleTime; | |||
| aheadTime += stepTime; | |||
| auto currTime = std::chrono::high_resolution_clock::now(); | |||
| const double aheadFactor = 2.0; | |||
| aheadTime -= aheadFactor * std::chrono::duration<double>(currTime - lastTime).count(); | |||
| lastTime = currTime; | |||
| aheadTime = std::fmax(aheadTime, 0.0); | |||
| std::lock_guard<std::recursive_mutex> lock(internal->mutex); | |||
| // Update expander pointers | |||
| for (Module* module : internal->modules) { | |||
| Engine_updateExpander(this, &module->leftExpander); | |||
| Engine_updateExpander(this, &module->rightExpander); | |||
| } | |||
| // Avoid pegging the CPU at 100% when there are no "blocking" modules like AudioInterface, but still step audio at a reasonable rate | |||
| // The number of steps to wait before possibly sleeping | |||
| const double aheadMax = 1.0; // seconds | |||
| if (aheadTime > aheadMax) { | |||
| std::this_thread::sleep_for(std::chrono::duration<double>(stepTime)); | |||
| // Step modules | |||
| for (int i = 0; i < frames; i++) { | |||
| Engine_step(this); | |||
| } | |||
| } | |||
| else { | |||
| // Stop workers while paused | |||
| if (internal->threadCount != 1) { | |||
| Engine_relaunchWorkers(this, 1, settings::realTime); | |||
| } | |||
| } | |||
| // Stop workers | |||
| Engine_relaunchWorkers(that, 0, false); | |||
| yieldWorkers(); | |||
| } | |||
| void Engine::start() { | |||
| internal->running = true; | |||
| internal->thread = std::thread([&] { | |||
| random::init(); | |||
| Engine_run(this); | |||
| }); | |||
| void Engine::setPrimaryModule(Module* module) { | |||
| std::lock_guard<std::recursive_mutex> lock(internal->mutex); | |||
| internal->primaryModule = module; | |||
| } | |||
| void Engine::stop() { | |||
| internal->running = false; | |||
| internal->thread.join(); | |||
| Module* Engine::getPrimaryModule() { | |||
| return internal->primaryModule; | |||
| } | |||
| void Engine::setPaused(bool paused) { | |||
| VIPLock vipLock(internal->vipMutex); | |||
| std::lock_guard<std::recursive_mutex> lock(internal->mutex); | |||
| internal->paused = paused; | |||
| } | |||
| bool Engine::isPaused() { | |||
| // No lock | |||
| return internal->paused; | |||
| } | |||
| float Engine::getSampleRate() { | |||
| return internal->sampleRate; | |||
| } | |||
| float Engine::getSampleTime() { | |||
| return internal->sampleTime; | |||
| } | |||
| void Engine::yieldWorkers() { | |||
| internal->workerBarrier.yield = true; | |||
| } | |||
| uint64_t Engine::getFrame() { | |||
| return internal->frame; | |||
| } | |||
| void Engine::addModule(Module* module) { | |||
| assert(module); | |||
| VIPLock vipLock(internal->vipMutex); | |||
| std::lock_guard<std::recursive_mutex> lock(internal->mutex); | |||
| // Check that the module is not already added | |||
| auto it = std::find(internal->modules.begin(), internal->modules.end(), module); | |||
| @@ -540,17 +532,19 @@ void Engine::addModule(Module* module) { | |||
| // Add module | |||
| internal->modules.push_back(module); | |||
| // Trigger Add event | |||
| module->onAdd(); | |||
| Module::AddEvent eAdd; | |||
| module->onAdd(eAdd); | |||
| // Update ParamHandles' module pointers | |||
| for (ParamHandle* paramHandle : internal->paramHandles) { | |||
| if (paramHandle->moduleId == module->id) | |||
| paramHandle->module = module; | |||
| } | |||
| DEBUG("Added module %d to engine", module->id); | |||
| } | |||
| void Engine::removeModule(Module* module) { | |||
| assert(module); | |||
| VIPLock vipLock(internal->vipMutex); | |||
| std::lock_guard<std::recursive_mutex> lock(internal->mutex); | |||
| // Check that the module actually exists | |||
| auto it = std::find(internal->modules.begin(), internal->modules.end(), module); | |||
| @@ -581,13 +575,18 @@ void Engine::removeModule(Module* module) { | |||
| } | |||
| } | |||
| // Trigger Remove event | |||
| module->onRemove(); | |||
| Module::RemoveEvent eRemove; | |||
| module->onRemove(eRemove); | |||
| // Unset primary module | |||
| if (internal->primaryModule == module) | |||
| internal->primaryModule = NULL; | |||
| // Remove module | |||
| internal->modules.erase(it); | |||
| DEBUG("Removed module %d to engine", module->id); | |||
| } | |||
| Module* Engine::getModule(int moduleId) { | |||
| VIPLock vipLock(internal->vipMutex); | |||
| std::lock_guard<std::recursive_mutex> lock(internal->mutex); | |||
| // Find module | |||
| for (Module* module : internal->modules) { | |||
| @@ -597,36 +596,48 @@ Module* Engine::getModule(int moduleId) { | |||
| return NULL; | |||
| } | |||
| void Engine::resetModule(Module* module) { | |||
| assert(module); | |||
| VIPLock vipLock(internal->vipMutex); | |||
| std::lock_guard<std::recursive_mutex> lock(internal->mutex); | |||
| module->onReset(); | |||
| Module::ResetEvent eReset; | |||
| module->onReset(eReset); | |||
| } | |||
| void Engine::randomizeModule(Module* module) { | |||
| assert(module); | |||
| VIPLock vipLock(internal->vipMutex); | |||
| std::lock_guard<std::recursive_mutex> lock(internal->mutex); | |||
| module->onRandomize(); | |||
| Module::RandomizeEvent eRandomize; | |||
| module->onRandomize(eRandomize); | |||
| } | |||
| void Engine::bypassModule(Module* module, bool bypass) { | |||
| void Engine::disableModule(Module* module, bool disabled) { | |||
| assert(module); | |||
| VIPLock vipLock(internal->vipMutex); | |||
| std::lock_guard<std::recursive_mutex> lock(internal->mutex); | |||
| if (module->bypass == bypass) | |||
| if (module->disabled == disabled) | |||
| return; | |||
| // Clear outputs and set to 1 channel | |||
| for (Output& output : module->outputs) { | |||
| // This zeros all voltages, but the channel is set to 1 if connected | |||
| output.setChannels(0); | |||
| } | |||
| module->bypass = bypass; | |||
| module->disabled = disabled; | |||
| // Trigger event | |||
| if (disabled) { | |||
| Module::DisableEvent eDisable; | |||
| module->onDisable(eDisable); | |||
| } | |||
| else { | |||
| Module::EnableEvent eEnable; | |||
| module->onEnable(eEnable); | |||
| } | |||
| } | |||
| static void Port_setDisconnected(Port* that) { | |||
| that->channels = 0; | |||
| for (int c = 0; c < PORT_MAX_CHANNELS; c++) { | |||
| @@ -634,12 +645,14 @@ static void Port_setDisconnected(Port* that) { | |||
| } | |||
| } | |||
| static void Port_setConnected(Port* that) { | |||
| if (that->channels > 0) | |||
| return; | |||
| that->channels = 1; | |||
| } | |||
| static void Engine_updateConnected(Engine* that) { | |||
| // Find disconnected ports | |||
| std::set<Port*> disconnectedPorts; | |||
| @@ -671,17 +684,23 @@ static void Engine_updateConnected(Engine* that) { | |||
| } | |||
| } | |||
| void Engine::addCable(Cable* cable) { | |||
| assert(cable); | |||
| VIPLock vipLock(internal->vipMutex); | |||
| std::lock_guard<std::recursive_mutex> lock(internal->mutex); | |||
| // Check cable properties | |||
| assert(cable->outputModule); | |||
| assert(cable->inputModule); | |||
| // Check that the cable is not already added, and that the input is not already used by another cable | |||
| assert(cable->outputModule); | |||
| bool outputWasConnected = false; | |||
| for (Cable* cable2 : internal->cables) { | |||
| // Check that the cable is not already added | |||
| assert(cable2 != cable); | |||
| // Check that the input is not already used by another cable | |||
| assert(!(cable2->inputModule == cable->inputModule && cable2->inputId == cable->inputId)); | |||
| // Get connected status of output, to decide whether we need to call a PortChangeEvent. | |||
| // It's best to not trust `cable->outputModule->outputs[cable->outputId]->isConnected()` | |||
| if (cable2->outputModule == cable->outputModule && cable2->outputId == cable->outputId) | |||
| outputWasConnected = true; | |||
| } | |||
| // Set ID | |||
| if (cable->id < 0) { | |||
| @@ -701,11 +720,28 @@ void Engine::addCable(Cable* cable) { | |||
| // Add the cable | |||
| internal->cables.push_back(cable); | |||
| Engine_updateConnected(this); | |||
| // Trigger input port event | |||
| { | |||
| Module::PortChangeEvent e; | |||
| e.connecting = true; | |||
| e.type = Port::INPUT; | |||
| e.portId = cable->inputId; | |||
| cable->inputModule->onPortChange(e); | |||
| } | |||
| // Trigger output port event if its state went from disconnected to connected. | |||
| if (!outputWasConnected) { | |||
| Module::PortChangeEvent e; | |||
| e.connecting = true; | |||
| e.type = Port::OUTPUT; | |||
| e.portId = cable->outputId; | |||
| cable->outputModule->onPortChange(e); | |||
| } | |||
| DEBUG("Added cable %d to engine", cable->id); | |||
| } | |||
| void Engine::removeCable(Cable* cable) { | |||
| assert(cable); | |||
| VIPLock vipLock(internal->vipMutex); | |||
| std::lock_guard<std::recursive_mutex> lock(internal->mutex); | |||
| // Check that the cable is already added | |||
| auto it = std::find(internal->cables.begin(), internal->cables.end(), cable); | |||
| @@ -713,6 +749,41 @@ void Engine::removeCable(Cable* cable) { | |||
| // Remove the cable | |||
| internal->cables.erase(it); | |||
| Engine_updateConnected(this); | |||
| bool outputIsConnected = false; | |||
| for (Cable* cable2 : internal->cables) { | |||
| // Get connected status of output, to decide whether we need to call a PortChangeEvent. | |||
| // It's best to not trust `cable->outputModule->outputs[cable->outputId]->isConnected()` | |||
| if (cable2->outputModule == cable->outputModule && cable2->outputId == cable->outputId) | |||
| outputIsConnected = true; | |||
| } | |||
| // Trigger input port event | |||
| { | |||
| Module::PortChangeEvent e; | |||
| e.connecting = false; | |||
| e.type = Port::INPUT; | |||
| e.portId = cable->inputId; | |||
| cable->inputModule->onPortChange(e); | |||
| } | |||
| // Trigger output port event if its state went from connected to disconnected. | |||
| if (!outputIsConnected) { | |||
| Module::PortChangeEvent e; | |||
| e.connecting = false; | |||
| e.type = Port::OUTPUT; | |||
| e.portId = cable->outputId; | |||
| cable->outputModule->onPortChange(e); | |||
| } | |||
| DEBUG("Removed cable %d to engine", cable->id); | |||
| } | |||
| Cable* Engine::getCable(int cableId) { | |||
| std::lock_guard<std::recursive_mutex> lock(internal->mutex); | |||
| // Find Cable | |||
| for (Cable* cable : internal->cables) { | |||
| if (cable->id == cableId) | |||
| return cable; | |||
| } | |||
| return NULL; | |||
| } | |||
| void Engine::setParam(Module* module, int paramId, float value) { | |||
| @@ -725,10 +796,12 @@ void Engine::setParam(Module* module, int paramId, float value) { | |||
| module->params[paramId].value = value; | |||
| } | |||
| float Engine::getParam(Module* module, int paramId) { | |||
| return module->params[paramId].value; | |||
| } | |||
| void Engine::setSmoothParam(Module* module, int paramId, float value) { | |||
| // If another param is being smoothed, jump value | |||
| if (internal->smoothModule && !(internal->smoothModule == module && internal->smoothParamId == paramId)) { | |||
| @@ -740,12 +813,14 @@ void Engine::setSmoothParam(Module* module, int paramId, float value) { | |||
| internal->smoothModule = module; | |||
| } | |||
| float Engine::getSmoothParam(Module* module, int paramId) { | |||
| if (internal->smoothModule == module && internal->smoothParamId == paramId) | |||
| return internal->smoothValue; | |||
| return getParam(module, paramId); | |||
| } | |||
| static void Engine_refreshParamHandleCache(Engine* that) { | |||
| // Clear cache | |||
| that->internal->paramHandleCache.clear(); | |||
| @@ -757,8 +832,8 @@ static void Engine_refreshParamHandleCache(Engine* that) { | |||
| } | |||
| } | |||
| void Engine::addParamHandle(ParamHandle* paramHandle) { | |||
| VIPLock vipLock(internal->vipMutex); | |||
| std::lock_guard<std::recursive_mutex> lock(internal->mutex); | |||
| // New ParamHandles must be blank. | |||
| @@ -773,8 +848,8 @@ void Engine::addParamHandle(ParamHandle* paramHandle) { | |||
| internal->paramHandles.insert(paramHandle); | |||
| } | |||
| void Engine::removeParamHandle(ParamHandle* paramHandle) { | |||
| VIPLock vipLock(internal->vipMutex); | |||
| std::lock_guard<std::recursive_mutex> lock(internal->mutex); | |||
| // Check that the ParamHandle is already added | |||
| @@ -787,6 +862,7 @@ void Engine::removeParamHandle(ParamHandle* paramHandle) { | |||
| Engine_refreshParamHandleCache(this); | |||
| } | |||
| ParamHandle* Engine::getParamHandle(int moduleId, int paramId) { | |||
| // Don't lock because this method is called potentially thousands of times per screen frame. | |||
| @@ -796,12 +872,13 @@ ParamHandle* Engine::getParamHandle(int moduleId, int paramId) { | |||
| return it->second; | |||
| } | |||
| ParamHandle* Engine::getParamHandle(Module* module, int paramId) { | |||
| return getParamHandle(module->id, paramId); | |||
| } | |||
| void Engine::updateParamHandle(ParamHandle* paramHandle, int moduleId, int paramId, bool overwrite) { | |||
| VIPLock vipLock(internal->vipMutex); | |||
| std::lock_guard<std::recursive_mutex> lock(internal->mutex); | |||
| // Check that it exists | |||
| @@ -840,12 +917,85 @@ void Engine::updateParamHandle(ParamHandle* paramHandle, int moduleId, int param | |||
| } | |||
| json_t* Engine::toJson() { | |||
| json_t* rootJ = json_object(); | |||
| // modules | |||
| json_t* modulesJ = json_array(); | |||
| for (Module* module : internal->modules) { | |||
| // module | |||
| json_t* moduleJ = module->toJson(); | |||
| json_array_append_new(modulesJ, moduleJ); | |||
| } | |||
| json_object_set_new(rootJ, "modules", modulesJ); | |||
| // cables | |||
| json_t* cablesJ = json_array(); | |||
| for (Cable* cable : internal->cables) { | |||
| // cable | |||
| json_t* cableJ = cable->toJson(); | |||
| json_array_append_new(cablesJ, cableJ); | |||
| } | |||
| json_object_set_new(rootJ, "cables", cablesJ); | |||
| return rootJ; | |||
| } | |||
| void Engine::fromJson(json_t* rootJ) { | |||
| // modules | |||
| json_t* modulesJ = json_object_get(rootJ, "modules"); | |||
| if (!modulesJ) | |||
| return; | |||
| size_t moduleIndex; | |||
| json_t* moduleJ; | |||
| json_array_foreach(modulesJ, moduleIndex, moduleJ) { | |||
| try { | |||
| Module* module = moduleFromJson(moduleJ); | |||
| // Before 1.0, the module ID was the index in the "modules" array | |||
| if (APP->patch->isLegacy(2)) { | |||
| module->id = moduleIndex; | |||
| } | |||
| addModule(module); | |||
| } | |||
| catch (Exception& e) { | |||
| APP->patch->log(e.what()); | |||
| } | |||
| } | |||
| // cables | |||
| json_t* cablesJ = json_object_get(rootJ, "cables"); | |||
| // Before 1.0, cables were called wires | |||
| if (!cablesJ) | |||
| cablesJ = json_object_get(rootJ, "wires"); | |||
| if (!cablesJ) | |||
| return; | |||
| size_t cableIndex; | |||
| json_t* cableJ; | |||
| json_array_foreach(cablesJ, cableIndex, cableJ) { | |||
| // cable | |||
| Cable* cable = new Cable; | |||
| try { | |||
| cable->fromJson(cableJ); | |||
| // DEBUG("%p %d %p %d", cable->inputModule, cable->inputId, cable->) | |||
| addCable(cable); | |||
| } | |||
| catch (Exception& e) { | |||
| delete cable; | |||
| // Don't log exceptions because missing modules create unnecessary complaining when cables try to connect to them. | |||
| } | |||
| } | |||
| } | |||
| void EngineWorker::run() { | |||
| system::setThreadName("Engine worker"); | |||
| system::setThreadRealTime(engine->internal->realTime); | |||
| system::setThreadName(string::f("Worker %d", id)); | |||
| initMXCSR(); | |||
| random::init(); | |||
| while (1) { | |||
| while (true) { | |||
| engine->internal->engineBarrier.wait(); | |||
| if (!running) | |||
| return; | |||
| @@ -855,5 +1005,29 @@ void EngineWorker::run() { | |||
| } | |||
| Module* moduleFromJson(json_t* moduleJ) { | |||
| // Get slugs | |||
| json_t* pluginSlugJ = json_object_get(moduleJ, "plugin"); | |||
| if (!pluginSlugJ) | |||
| throw Exception("\"plugin\" property not found in module JSON"); | |||
| json_t* modelSlugJ = json_object_get(moduleJ, "model"); | |||
| if (!modelSlugJ) | |||
| throw Exception("\"model\" property not found in module JSON"); | |||
| std::string pluginSlug = json_string_value(pluginSlugJ); | |||
| std::string modelSlug = json_string_value(modelSlugJ); | |||
| // Get Model | |||
| plugin::Model* model = plugin::getModel(pluginSlug, modelSlug); | |||
| if (!model) | |||
| throw Exception(string::f("Could not find module \"%s\" of plugin \"%s\"", modelSlug.c_str(), pluginSlug.c_str())); | |||
| // Create Module | |||
| Module* module = model->createModule(); | |||
| assert(module); | |||
| module->fromJson(moduleJ); | |||
| return module; | |||
| } | |||
| } // namespace engine | |||
| } // namespace rack | |||
| @@ -52,20 +52,17 @@ json_t* Module::toJson() { | |||
| if (!paramQuantities[paramId]->isBounded()) | |||
| continue; | |||
| json_t* paramJ = json_object(); | |||
| json_t* paramJ = params[paramId].toJson(); | |||
| json_object_set_new(paramJ, "id", json_integer(paramId)); | |||
| float value = params[paramId].getValue(); | |||
| json_object_set_new(paramJ, "value", json_real(value)); | |||
| json_array_append(paramsJ, paramJ); | |||
| } | |||
| json_object_set_new(rootJ, "params", paramsJ); | |||
| // bypass | |||
| if (bypass) | |||
| json_object_set_new(rootJ, "bypass", json_boolean(bypass)); | |||
| // disabled | |||
| if (disabled) | |||
| json_object_set_new(rootJ, "disabled", json_boolean(disabled)); | |||
| // leftModuleId | |||
| if (leftExpander.moduleId >= 0) | |||
| @@ -155,10 +152,13 @@ void Module::fromJson(json_t* rootJ) { | |||
| params[paramId].setValue(json_number_value(valueJ)); | |||
| } | |||
| // bypass | |||
| json_t* bypassJ = json_object_get(rootJ, "bypass"); | |||
| if (bypassJ) | |||
| bypass = json_boolean_value(bypassJ); | |||
| // disabled | |||
| json_t* disabledJ = json_object_get(rootJ, "disabled"); | |||
| // legacy bypass | |||
| if (!disabledJ) | |||
| disabledJ = json_object_get(rootJ, "bypass"); | |||
| if (disabledJ) | |||
| disabled = json_boolean_value(disabledJ); | |||
| // These do not need to be deserialized, since the module positions will set them correctly when added to the rack. | |||
| // // leftModuleId | |||
| @@ -178,5 +178,31 @@ void Module::fromJson(json_t* rootJ) { | |||
| } | |||
| void Module::onReset(const ResetEvent& e) { | |||
| // Reset all parameters | |||
| assert(params.size() == paramQuantities.size()); | |||
| for (int i = 0; i < (int) params.size(); i++) { | |||
| if (!paramQuantities[i]->resetEnabled) | |||
| continue; | |||
| paramQuantities[i]->reset(); | |||
| } | |||
| // Call deprecated event | |||
| onReset(); | |||
| } | |||
| void Module::onRandomize(const RandomizeEvent& e) { | |||
| // Randomize all parameters | |||
| assert(params.size() == paramQuantities.size()); | |||
| for (int i = 0; i < (int) params.size(); i++) { | |||
| if (!paramQuantities[i]->randomizeEnabled) | |||
| continue; | |||
| paramQuantities[i]->randomize(); | |||
| } | |||
| // Call deprecated event | |||
| onRandomize(); | |||
| } | |||
| } // namespace engine | |||
| } // namespace rack | |||
| @@ -0,0 +1,23 @@ | |||
| #include <engine/Param.hpp> | |||
| namespace rack { | |||
| namespace engine { | |||
| json_t* Param::toJson() { | |||
| json_t* rootJ = json_object(); | |||
| json_object_set_new(rootJ, "value", json_real(value)); | |||
| return rootJ; | |||
| } | |||
| void Param::fromJson(json_t* rootJ) { | |||
| json_t* valueJ = json_object_get(rootJ, "value"); | |||
| if (valueJ) | |||
| value = json_number_value(valueJ); | |||
| } | |||
| } // namespace engine | |||
| } // namespace rack | |||
| @@ -9,6 +9,7 @@ namespace engine { | |||
| engine::Param* ParamQuantity::getParam() { | |||
| assert(module); | |||
| assert(paramId < (int) module->params.size()); | |||
| return &module->params[paramId]; | |||
| } | |||
| @@ -93,7 +94,7 @@ void ParamQuantity::setDisplayValue(float displayValue) { | |||
| } | |||
| int ParamQuantity::getDisplayPrecision() { | |||
| return Quantity::getDisplayPrecision(); | |||
| return displayPrecision; | |||
| } | |||
| std::string ParamQuantity::getDisplayValueString() { | |||
| @@ -1,35 +0,0 @@ | |||
| #include <engine/Port.hpp> | |||
| namespace rack { | |||
| namespace engine { | |||
| void Port::process(float deltaTime) { | |||
| // Set plug lights | |||
| if (channels == 0) { | |||
| plugLights[0].setBrightness(0.f); | |||
| plugLights[1].setBrightness(0.f); | |||
| plugLights[2].setBrightness(0.f); | |||
| } | |||
| else if (channels == 1) { | |||
| float v = getVoltage() / 10.f; | |||
| plugLights[0].setSmoothBrightness(v, deltaTime); | |||
| plugLights[1].setSmoothBrightness(-v, deltaTime); | |||
| plugLights[2].setBrightness(0.f); | |||
| } | |||
| else { | |||
| float v2 = 0.f; | |||
| for (int c = 0; c < channels; c++) { | |||
| v2 += std::pow(getVoltage(c), 2); | |||
| } | |||
| float v = std::sqrt(v2) / 10.f; | |||
| plugLights[0].setBrightness(0.f); | |||
| plugLights[1].setBrightness(0.f); | |||
| plugLights[2].setSmoothBrightness(v, deltaTime); | |||
| } | |||
| } | |||
| } // namespace engine | |||
| } // namespace rack | |||
| @@ -48,23 +48,26 @@ void ModuleAdd::setModule(app::ModuleWidget* mw) { | |||
| pos = mw->box.pos; | |||
| // ModuleAdd doesn't *really* need the state to be serialized, although ModuleRemove certainly does. | |||
| // However, creating a module may give it a nondeterministic initial state for whatever reason, so serialize anyway. | |||
| moduleJ = mw->toJson(); | |||
| moduleJ = mw->module->toJson(); | |||
| } | |||
| void ModuleAdd::undo() { | |||
| app::ModuleWidget* mw = APP->scene->rack->getModule(moduleId); | |||
| assert(mw); | |||
| engine::Module* module = mw->module; | |||
| APP->scene->rack->removeModule(mw); | |||
| delete mw; | |||
| APP->engine->removeModule(module); | |||
| delete module; | |||
| } | |||
| void ModuleAdd::redo() { | |||
| app::ModuleWidget* mw = model->createModuleWidget(); | |||
| assert(mw); | |||
| assert(mw->module); | |||
| mw->module->id = moduleId; | |||
| engine::Module* module = model->createModule(); | |||
| module->id = moduleId; | |||
| module->fromJson(moduleJ); | |||
| APP->engine->addModule(module); | |||
| app::ModuleWidget* mw = model->createModuleWidget(module); | |||
| mw->box.pos = pos; | |||
| mw->fromJson(moduleJ); | |||
| APP->scene->rack->addModule(mw); | |||
| } | |||
| @@ -82,16 +85,16 @@ void ModuleMove::redo() { | |||
| } | |||
| void ModuleBypass::undo() { | |||
| app::ModuleWidget* mw = APP->scene->rack->getModule(moduleId); | |||
| assert(mw); | |||
| APP->engine->bypassModule(mw->module, !bypass); | |||
| void ModuleDisable::undo() { | |||
| engine::Module* module = APP->engine->getModule(moduleId); | |||
| assert(module); | |||
| APP->engine->disableModule(module, !disabled); | |||
| } | |||
| void ModuleBypass::redo() { | |||
| app::ModuleWidget* mw = APP->scene->rack->getModule(moduleId); | |||
| assert(mw); | |||
| APP->engine->bypassModule(mw->module, bypass); | |||
| void ModuleDisable::redo() { | |||
| engine::Module* module = APP->engine->getModule(moduleId); | |||
| assert(module); | |||
| APP->engine->disableModule(module, disabled); | |||
| } | |||
| @@ -101,28 +104,28 @@ ModuleChange::~ModuleChange() { | |||
| } | |||
| void ModuleChange::undo() { | |||
| app::ModuleWidget* mw = APP->scene->rack->getModule(moduleId); | |||
| assert(mw); | |||
| mw->fromJson(oldModuleJ); | |||
| engine::Module* module = APP->engine->getModule(moduleId); | |||
| assert(module); | |||
| module->fromJson(oldModuleJ); | |||
| } | |||
| void ModuleChange::redo() { | |||
| app::ModuleWidget* mw = APP->scene->rack->getModule(moduleId); | |||
| assert(mw); | |||
| mw->fromJson(newModuleJ); | |||
| engine::Module* module = APP->engine->getModule(moduleId); | |||
| assert(module); | |||
| module->fromJson(newModuleJ); | |||
| } | |||
| void ParamChange::undo() { | |||
| app::ModuleWidget* mw = APP->scene->rack->getModule(moduleId); | |||
| assert(mw); | |||
| mw->module->params[paramId].value = oldValue; | |||
| engine::Module* module = APP->engine->getModule(moduleId); | |||
| assert(module); | |||
| APP->engine->setParam(module, paramId, oldValue); | |||
| } | |||
| void ParamChange::redo() { | |||
| app::ModuleWidget* mw = APP->scene->rack->getModule(moduleId); | |||
| assert(mw); | |||
| mw->module->params[paramId].value = newValue; | |||
| engine::Module* module = APP->engine->getModule(moduleId); | |||
| assert(module); | |||
| APP->engine->setParam(module, paramId, newValue); | |||
| } | |||
| @@ -141,28 +144,28 @@ void CableAdd::setCable(app::CableWidget* cw) { | |||
| void CableAdd::undo() { | |||
| app::CableWidget* cw = APP->scene->rack->getCable(cableId); | |||
| assert(cw); | |||
| APP->scene->rack->removeCable(cw); | |||
| delete cw; | |||
| engine::Cable* cable = APP->engine->getCable(cableId); | |||
| assert(cable); | |||
| APP->engine->removeCable(cable); | |||
| delete cable; | |||
| } | |||
| void CableAdd::redo() { | |||
| app::CableWidget* cw = new app::CableWidget; | |||
| cw->cable->id = cableId; | |||
| app::ModuleWidget* outputModule = APP->scene->rack->getModule(outputModuleId); | |||
| assert(outputModule); | |||
| app::PortWidget* outputPort = outputModule->getOutput(outputId); | |||
| assert(outputPort); | |||
| cw->setOutput(outputPort); | |||
| app::ModuleWidget* inputModule = APP->scene->rack->getModule(inputModuleId); | |||
| assert(inputModule); | |||
| app::PortWidget* inputPort = inputModule->getInput(inputId); | |||
| assert(inputPort); | |||
| cw->setInput(inputPort); | |||
| engine::Cable* cable = new engine::Cable; | |||
| cable->id = cableId; | |||
| cable->inputModule = APP->engine->getModule(inputModuleId); | |||
| cable->inputId = inputId; | |||
| cable->outputModule = APP->engine->getModule(outputModuleId); | |||
| cable->outputId = outputId; | |||
| APP->engine->addCable(cable); | |||
| app::CableWidget* cw = new app::CableWidget; | |||
| cw->setCable(cable); | |||
| cw->color = color; | |||
| APP->scene->rack->addCable(cw); | |||
| } | |||
| @@ -137,7 +137,7 @@ int main(int argc, char* argv[]) { | |||
| try { | |||
| settings::load(asset::settingsPath); | |||
| } | |||
| catch (UserException& e) { | |||
| catch (Exception& e) { | |||
| std::string msg = e.what(); | |||
| msg += "\n\nReset settings to default?"; | |||
| if (!osdialog_message(OSDIALOG_WARNING, OSDIALOG_OK_CANCEL, msg.c_str())) { | |||
| @@ -187,9 +187,6 @@ int main(int argc, char* argv[]) { | |||
| APP->patch->init(patchPath); | |||
| } | |||
| INFO("Starting engine"); | |||
| APP->engine->start(); | |||
| if (settings::headless) { | |||
| // TEMP Prove that the app doesn't crash | |||
| std::this_thread::sleep_for(std::chrono::seconds(2)); | |||
| @@ -203,8 +200,6 @@ int main(int argc, char* argv[]) { | |||
| APP->window->run(); | |||
| INFO("Stopped window"); | |||
| } | |||
| INFO("Stopping engine"); | |||
| APP->engine->stop(); | |||
| // Destroy app | |||
| if (!settings::headless) { | |||
| @@ -1,6 +1,7 @@ | |||
| #include <patch.hpp> | |||
| #include <asset.hpp> | |||
| #include <system.hpp> | |||
| #include <engine/Engine.hpp> | |||
| #include <app.hpp> | |||
| #include <app/common.hpp> | |||
| #include <app/Scene.hpp> | |||
| @@ -57,6 +58,7 @@ void PatchManager::reset() { | |||
| APP->history->clear(); | |||
| APP->scene->rack->clear(); | |||
| APP->scene->rackScroll->reset(); | |||
| APP->engine->clear(); | |||
| path = ""; | |||
| if (load(asset::templatePath)) { | |||
| @@ -184,7 +186,7 @@ bool PatchManager::load(std::string path) { | |||
| APP->history->clear(); | |||
| APP->scene->rack->clear(); | |||
| APP->scene->rackScroll->reset(); | |||
| legacy = 0; | |||
| APP->engine->clear(); | |||
| fromJson(rootJ); | |||
| return true; | |||
| } | |||
| @@ -252,11 +254,12 @@ json_t* PatchManager::toJson() { | |||
| json_t* versionJ = json_string(app::APP_VERSION.c_str()); | |||
| json_object_set_new(rootJ, "version", versionJ); | |||
| // Merge with RackWidget JSON | |||
| json_t* rackJ = APP->scene->rack->toJson(); | |||
| json_t* engineJ = APP->engine->toJson(); | |||
| APP->scene->rack->mergeJson(engineJ); | |||
| // Merge with rootJ | |||
| json_object_update(rootJ, rackJ); | |||
| json_decref(rackJ); | |||
| json_object_update(rootJ, engineJ); | |||
| json_decref(engineJ); | |||
| return rootJ; | |||
| } | |||
| @@ -274,8 +277,8 @@ void PatchManager::fromJson(json_t* rootJ) { | |||
| } | |||
| // Detect old patches with ModuleWidget::params/inputs/outputs indices. | |||
| // (We now use Module::params/inputs/outputs indices.) | |||
| if (string::startsWith(version, "0.3.") || string::startsWith(version, "0.4.") || string::startsWith(version, "0.5.") || version == "" || version == "dev") { | |||
| // Use ModuleWidget::params/inputs/outputs indices instead of Module. | |||
| legacy = 1; | |||
| } | |||
| else if (string::startsWith(version, "0.6.")) { | |||
| @@ -285,7 +288,10 @@ void PatchManager::fromJson(json_t* rootJ) { | |||
| INFO("Loading patch using legacy mode %d", legacy); | |||
| } | |||
| APP->engine->fromJson(rootJ); | |||
| APP->scene->rack->fromJson(rootJ); | |||
| // At this point, ModuleWidgets and CableWidgets should own all Modules and Cables. | |||
| // TODO Assert this | |||
| // Display a message if we have something to say | |||
| if (!warningLog.empty()) { | |||
| @@ -298,5 +304,10 @@ bool PatchManager::isLegacy(int level) { | |||
| return legacy && legacy <= level; | |||
| } | |||
| void PatchManager::log(std::string msg) { | |||
| warningLog += msg; | |||
| warningLog += "\n"; | |||
| } | |||
| } // namespace rack | |||
| @@ -58,7 +58,7 @@ static InitCallback loadLibrary(Plugin* plugin) { | |||
| // Check file existence | |||
| if (!system::isFile(libraryFilename)) { | |||
| throw UserException(string::f("Library %s does not exist", libraryFilename.c_str())); | |||
| throw Exception(string::f("Library %s does not exist", libraryFilename.c_str())); | |||
| } | |||
| // Load dynamic/shared library | |||
| @@ -68,12 +68,12 @@ static InitCallback loadLibrary(Plugin* plugin) { | |||
| SetErrorMode(0); | |||
| if (!handle) { | |||
| int error = GetLastError(); | |||
| throw UserException(string::f("Failed to load library %s: code %d", libraryFilename.c_str(), error)); | |||
| throw Exception(string::f("Failed to load library %s: code %d", libraryFilename.c_str(), error)); | |||
| } | |||
| #else | |||
| void* handle = dlopen(libraryFilename.c_str(), RTLD_NOW | RTLD_LOCAL); | |||
| if (!handle) { | |||
| throw UserException(string::f("Failed to load library %s: %s", libraryFilename.c_str(), dlerror())); | |||
| throw Exception(string::f("Failed to load library %s: %s", libraryFilename.c_str(), dlerror())); | |||
| } | |||
| #endif | |||
| plugin->handle = handle; | |||
| @@ -86,7 +86,7 @@ static InitCallback loadLibrary(Plugin* plugin) { | |||
| initCallback = (InitCallback) dlsym(handle, "init"); | |||
| #endif | |||
| if (!initCallback) { | |||
| throw UserException(string::f("Failed to read init() symbol in %s", libraryFilename.c_str())); | |||
| throw Exception(string::f("Failed to read init() symbol in %s", libraryFilename.c_str())); | |||
| } | |||
| return initCallback; | |||
| @@ -117,7 +117,7 @@ static Plugin* loadPlugin(std::string path) { | |||
| std::string manifestFilename = (path == "") ? asset::system("Core.json") : (path + "/plugin.json"); | |||
| FILE* file = fopen(manifestFilename.c_str(), "r"); | |||
| if (!file) { | |||
| throw UserException(string::f("Manifest file %s does not exist", manifestFilename.c_str())); | |||
| throw Exception(string::f("Manifest file %s does not exist", manifestFilename.c_str())); | |||
| } | |||
| DEFER({ | |||
| fclose(file); | |||
| @@ -126,7 +126,7 @@ static Plugin* loadPlugin(std::string path) { | |||
| json_error_t error; | |||
| json_t* rootJ = json_loadf(file, 0, &error); | |||
| if (!rootJ) { | |||
| throw UserException(string::f("JSON parsing error at %s %d:%d %s", manifestFilename.c_str(), error.line, error.column, error.text)); | |||
| throw Exception(string::f("JSON parsing error at %s %d:%d %s", manifestFilename.c_str(), error.line, error.column, error.text)); | |||
| } | |||
| DEFER({ | |||
| json_decref(rootJ); | |||
| @@ -148,13 +148,13 @@ static Plugin* loadPlugin(std::string path) { | |||
| // Reject plugin if slug already exists | |||
| Plugin* oldPlugin = getPlugin(plugin->slug); | |||
| if (oldPlugin) { | |||
| throw UserException(string::f("Plugin %s is already loaded, not attempting to load it again", plugin->slug.c_str())); | |||
| throw Exception(string::f("Plugin %s is already loaded, not attempting to load it again", plugin->slug.c_str())); | |||
| } | |||
| INFO("Loaded plugin %s v%s from %s", plugin->slug.c_str(), plugin->version.c_str(), path.c_str()); | |||
| plugins.push_back(plugin); | |||
| } | |||
| catch (UserException& e) { | |||
| catch (Exception& e) { | |||
| WARN("Could not load plugin %s: %s", path.c_str(), e.what()); | |||
| delete plugin; | |||
| plugin = NULL; | |||
| @@ -17,7 +17,7 @@ void Model::fromJson(json_t* rootJ) { | |||
| if (nameJ) | |||
| name = json_string_value(nameJ); | |||
| if (name == "") | |||
| throw UserException(string::f("No module name for slug %s", slug.c_str())); | |||
| throw Exception(string::f("No module name for slug %s", slug.c_str())); | |||
| json_t* descriptionJ = json_object_get(rootJ, "description"); | |||
| if (descriptionJ) | |||
| @@ -37,25 +37,25 @@ void Plugin::fromJson(json_t* rootJ) { | |||
| if (slugJ) | |||
| slug = json_string_value(slugJ); | |||
| if (slug == "") | |||
| throw UserException("No plugin slug"); | |||
| throw Exception("No plugin slug"); | |||
| if (!isSlugValid(slug)) | |||
| throw UserException(string::f("Plugin slug \"%s\" is invalid", slug.c_str())); | |||
| throw Exception(string::f("Plugin slug \"%s\" is invalid", slug.c_str())); | |||
| // Version | |||
| json_t* versionJ = json_object_get(rootJ, "version"); | |||
| if (versionJ) | |||
| version = json_string_value(versionJ); | |||
| if (!string::startsWith(version, app::ABI_VERSION + ".")) | |||
| throw UserException(string::f("Plugin version %s does not match Rack ABI version %s", version.c_str(), app::ABI_VERSION.c_str())); | |||
| throw Exception(string::f("Plugin version %s does not match Rack ABI version %s", version.c_str(), app::ABI_VERSION.c_str())); | |||
| if (version == "") | |||
| throw UserException("No plugin version"); | |||
| throw Exception("No plugin version"); | |||
| // Name | |||
| json_t* nameJ = json_object_get(rootJ, "name"); | |||
| if (nameJ) | |||
| name = json_string_value(nameJ); | |||
| if (name == "") | |||
| throw UserException("No plugin name"); | |||
| throw Exception("No plugin name"); | |||
| // Brand | |||
| json_t* brandJ = json_object_get(rootJ, "brand"); | |||
| @@ -112,19 +112,19 @@ void Plugin::fromJson(json_t* rootJ) { | |||
| // Get model slug | |||
| json_t* modelSlugJ = json_object_get(moduleJ, "slug"); | |||
| if (!modelSlugJ) { | |||
| throw UserException(string::f("No slug found for module entry %d", moduleId)); | |||
| throw Exception(string::f("No slug found for module entry %d", moduleId)); | |||
| } | |||
| std::string modelSlug = json_string_value(modelSlugJ); | |||
| // Check model slug | |||
| if (!isSlugValid(modelSlug)) { | |||
| throw UserException(string::f("Module slug \"%s\" is invalid", modelSlug.c_str())); | |||
| throw Exception(string::f("Module slug \"%s\" is invalid", modelSlug.c_str())); | |||
| } | |||
| // Get model | |||
| Model* model = getModel(modelSlug); | |||
| if (!model) { | |||
| throw UserException(string::f("Manifest contains module %s but it is not defined in the plugin", modelSlug.c_str())); | |||
| throw Exception(string::f("Manifest contains module %s but it is not defined in the plugin", modelSlug.c_str())); | |||
| } | |||
| model->fromJson(moduleJ); | |||
| @@ -219,7 +219,7 @@ void load(const std::string& path) { | |||
| json_error_t error; | |||
| json_t* rootJ = json_loadf(file, 0, &error); | |||
| if (!rootJ) | |||
| throw UserException(string::f("Settings file has invalid JSON at %d:%d %s", error.line, error.column, error.text)); | |||
| throw Exception(string::f("Settings file has invalid JSON at %d:%d %s", error.line, error.column, error.text)); | |||
| fromJson(rootJ); | |||
| json_decref(rootJ); | |||
| @@ -433,7 +433,7 @@ void Window::screenshot(float zoom) { | |||
| INFO("Screenshotting %s %s to %s", p->slug.c_str(), model->slug.c_str(), filename.c_str()); | |||
| // Create widgets | |||
| app::ModuleWidget* mw = model->createModuleWidgetNull(); | |||
| app::ModuleWidget* mw = model->createModuleWidget(NULL); | |||
| widget::FramebufferWidget* fb = new widget::FramebufferWidget; | |||
| fb->oversample = 2; | |||
| fb->addChild(mw); | |||