#pragma once #include #include #include #include #include #include #include #include #include #include namespace rack { namespace plugin { struct Model; } namespace engine { /** DSP processor instance for your module. */ struct Module { struct Internal; Internal* internal; plugin::Model* model = NULL; /** Unique ID for referring to the module in the engine. Assigned when added to the engine. */ int id = -1; /** Arrays of components. Initialized with config(). */ std::vector params; std::vector inputs; std::vector outputs; std::vector lights; std::vector paramQuantities; std::vector inputInfos; std::vector outputInfos; /** Represents a message-passing channel for an adjacent module. */ struct Expander { /** ID of the expander module, or -1 if nonexistent. */ int moduleId = -1; /** Pointer to the expander Module, or NULL if nonexistent. */ Module* module = NULL; /** Double buffer for receiving messages from the expander module. If you intend to receive messages from an expander, allocate both message buffers with identical blocks of memory (arrays, structs, etc). Remember to free the buffer in the Module destructor. Example: rightExpander.producerMessage = new MyExpanderMessage; rightExpander.consumerMessage = new MyExpanderMessage; You must check the expander module's `model` before attempting to write its message buffer. Once the module is checked, you can reinterpret_cast its producerMessage at no performance cost. Producer messages are intended to be write-only. Consumer messages are intended to be read-only. Once you write a message, set messageFlipRequested to true to request that the messages are flipped at the end of the timestep. This means that message-passing has 1-sample latency. You may choose for your Module to instead write to its own message buffer for consumption by other modules, i.e. the expander "pulls" rather than this module "pushing". As long as this convention is followed by the other module, this is fine. */ void* producerMessage = NULL; void* consumerMessage = NULL; bool messageFlipRequested = false; }; Expander leftExpander; Expander rightExpander; struct BypassRoute { int inputId = -1; int outputId = -1; }; std::vector bypassRoutes; /** Constructs a Module with no params, inputs, outputs, and lights. */ Module(); /** Use config() instead. */ DEPRECATED Module(int numParams, int numInputs, int numOutputs, int numLights = 0) : Module() { config(numParams, numInputs, numOutputs, numLights); } virtual ~Module(); /** Configures the number of Params, Outputs, Inputs, and Lights. */ void config(int numParams, int numInputs, int numOutputs, int numLights = 0); /** Helper for creating a ParamQuantity and setting its properties. See ParamQuantity for documentation of arguments. */ template TParamQuantity* configParam(int paramId, float minValue, float maxValue, float defaultValue, std::string name = "", std::string unit = "", float displayBase = 0.f, float displayMultiplier = 1.f, float displayOffset = 0.f) { assert(paramId < (int) params.size() && paramId < (int) paramQuantities.size()); if (paramQuantities[paramId]) delete paramQuantities[paramId]; TParamQuantity* q = new TParamQuantity; q->module = this; q->paramId = paramId; q->minValue = minValue; q->maxValue = maxValue; q->defaultValue = defaultValue; q->name = name; q->unit = unit; q->displayBase = displayBase; q->displayMultiplier = displayMultiplier; q->displayOffset = displayOffset; paramQuantities[paramId] = q; Param* p = ¶ms[paramId]; p->value = q->getDefaultValue(); return q; } /** Helper for creating a SwitchQuantity and setting its label strings. See ParamQuantity and SwitchQuantity for documentation of arguments. */ template TSwitchQuantity* configSwitch(int paramId, float minValue, float maxValue, float defaultValue, std::string name = "", std::vector labels = {}) { TSwitchQuantity* sq = configParam(paramId, minValue, maxValue, defaultValue, name); sq->labels = labels; return sq; } template TSwitchQuantity* configButton(int paramId, std::string name = "") { TSwitchQuantity* sq = configParam(paramId, 0.f, 1.f, 0.f, name); return sq; } /** Helper for creating a PortInfo for an input port and setting its properties. See PortInfo for documentation of arguments. */ template TPortInfo* configInput(int portId, std::string name = "") { assert(portId < (int) inputs.size() && portId < (int) inputInfos.size()); if (inputInfos[portId]) delete inputInfos[portId]; TPortInfo* p = new TPortInfo; p->module = this; p->type = Port::INPUT; p->portId = portId; p->name = name; inputInfos[portId] = p; return p; } /** Helper for creating a PortInfo for an output port and setting its properties. See PortInfo for documentation of arguments. */ template TPortInfo* configOutput(int portId, std::string name = "") { assert(portId < (int) outputs.size() && portId < (int) outputInfos.size()); if (outputInfos[portId]) delete outputInfos[portId]; TPortInfo* p = new TPortInfo; p->module = this; p->type = Port::OUTPUT; p->portId = portId; p->name = name; outputInfos[portId] = p; return p; } /** Adds a direct route from an input to an output when the module is bypassed. */ void configBypass(int inputId, int outputId) { assert(inputId < (int) inputs.size()); assert(outputId < (int) outputs.size()); // Check that output is not yet routed for (BypassRoute& br : bypassRoutes) { assert(br.outputId != outputId); } BypassRoute br; br.inputId = inputId; br.outputId = outputId; bypassRoutes.push_back(br); } struct ProcessArgs { 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) { step(); } /** DEPRECATED. Override `process(const ProcessArgs& args)` instead. */ virtual void step() {} /** Called instead of process() when Module is bypassed. Typically you do not need to override this. Use configBypass() instead. If you do override it, avoid reading param values, since the state of the module should have no effect on routing. */ virtual void processBypass(const ProcessArgs& args); /** Usually you should override dataToJson() instead. There are very few reasons you should override this (perhaps to lock a mutex while serialization is occurring). */ virtual json_t* toJson(); /** 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); virtual json_t* paramsToJson(); virtual void paramsFromJson(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 BypassEvent {}; /** Called after bypassing the module. */ virtual void onBypass(const BypassEvent& e) {} struct UnBypassEvent {}; /** Called after enabling the module. */ virtual void onUnBypass(const UnBypassEvent& 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::onReset(e)` in your overridden method 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)` in your overridden method 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() {} /** private */ float& cpuTime(); /** private */ bool& bypassed(); }; } // namespace engine } // namespace rack