|
- #pragma once
- #include <vector>
-
- #include <jansson.h>
-
- #include <common.hpp>
- #include <string.hpp>
- #include <plugin/Model.hpp>
- #include <engine/Param.hpp>
- #include <engine/Port.hpp>
- #include <engine/Light.hpp>
- #include <engine/ParamQuantity.hpp>
- #include <engine/PortInfo.hpp>
- #include <engine/LightInfo.hpp>
-
-
- namespace rack {
-
-
- namespace plugin {
- struct Model;
- }
-
-
- namespace engine {
-
-
- /** DSP processor instance for your module. */
- struct Module {
- struct Internal;
- Internal* internal;
-
- /** Not owned. */
- plugin::Model* model = NULL;
-
- /** Unique ID for referring to the module in the engine.
- Between 0 and 2^53-1 since the number is serialized with JSON.
- Assigned when added to the engine.
- */
- int64_t id = -1;
-
- /** Arrays of components.
- Initialized using config().
-
- It is recommended to call getParam(), getInput(), etc. instead of accessing these directly.
- */
- std::vector<Param> params;
- std::vector<Input> inputs;
- std::vector<Output> outputs;
- std::vector<Light> lights;
-
- /** Arrays of component metadata.
- Initialized using configParam(), configInput(), configOutput(), and configLight().
- LightInfos are initialized to null unless configLight() is called.
-
- It is recommended to call getParamQuantity(), getInputInfo(), etc. instead of accessing these directly.
- */
- std::vector<ParamQuantity*> paramQuantities;
- std::vector<PortInfo*> inputInfos;
- std::vector<PortInfo*> outputInfos;
- std::vector<LightInfo*> lightInfos;
-
- /** Represents a message-passing channel for an adjacent module. */
- struct Expander {
- /** ID of the expander module, or -1 if nonexistent. */
- int64_t 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;
-
- void requestMessageFlip() {
- messageFlipRequested = true;
- }
- };
-
- Expander leftExpander;
- Expander rightExpander;
-
- struct BypassRoute {
- int inputId = -1;
- int outputId = -1;
- };
- std::vector<BypassRoute> 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.
- Should only be called from a Module subclass's constructor.
- */
- 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.
- Should only be called from a Module subclass's constructor.
- */
- template <class TParamQuantity = ParamQuantity>
- 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->ParamQuantity::module = this;
- q->ParamQuantity::paramId = paramId;
- q->ParamQuantity::minValue = minValue;
- q->ParamQuantity::maxValue = maxValue;
- q->ParamQuantity::defaultValue = defaultValue;
- q->ParamQuantity::name = name;
- q->ParamQuantity::unit = unit;
- q->ParamQuantity::displayBase = displayBase;
- q->ParamQuantity::displayMultiplier = displayMultiplier;
- q->ParamQuantity::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.
- Should only be called from a Module subclass's constructor.
- */
- template <class TSwitchQuantity = SwitchQuantity>
- TSwitchQuantity* configSwitch(int paramId, float minValue, float maxValue, float defaultValue, std::string name = "", std::vector<std::string> labels = {}) {
- TSwitchQuantity* sq = configParam<TSwitchQuantity>(paramId, minValue, maxValue, defaultValue, name);
- sq->ParamQuantity::snapEnabled = true;
- sq->ParamQuantity::smoothEnabled = false;
- sq->SwitchQuantity::labels = labels;
- return sq;
- }
-
- /** Helper for creating a SwitchQuantity with no label.
- Should only be called from a Module subclass's constructor.
- */
- template <class TSwitchQuantity = SwitchQuantity>
- TSwitchQuantity* configButton(int paramId, std::string name = "") {
- TSwitchQuantity* sq = configParam<TSwitchQuantity>(paramId, 0.f, 1.f, 0.f, name);
- sq->ParamQuantity::snapEnabled = true;
- sq->ParamQuantity::smoothEnabled = false;
- sq->ParamQuantity::randomizeEnabled = false;
- return sq;
- }
-
- /** Helper for creating a PortInfo for an input port and setting its properties.
- See PortInfo for documentation of arguments.
- Should only be called from a Module subclass's constructor.
- */
- template <class TPortInfo = PortInfo>
- TPortInfo* configInput(int portId, std::string name = "") {
- assert(portId < (int) inputs.size() && portId < (int) inputInfos.size());
- if (inputInfos[portId])
- delete inputInfos[portId];
-
- TPortInfo* info = new TPortInfo;
- info->PortInfo::module = this;
- info->PortInfo::type = Port::INPUT;
- info->PortInfo::portId = portId;
- info->PortInfo::name = name;
- inputInfos[portId] = info;
- return info;
- }
-
- /** Helper for creating a PortInfo for an output port and setting its properties.
- See PortInfo for documentation of arguments.
- Should only be called from a Module subclass's constructor.
- */
- template <class TPortInfo = PortInfo>
- TPortInfo* configOutput(int portId, std::string name = "") {
- assert(portId < (int) outputs.size() && portId < (int) outputInfos.size());
- if (outputInfos[portId])
- delete outputInfos[portId];
-
- TPortInfo* info = new TPortInfo;
- info->PortInfo::module = this;
- info->PortInfo::type = Port::OUTPUT;
- info->PortInfo::portId = portId;
- info->PortInfo::name = name;
- outputInfos[portId] = info;
- return info;
- }
-
- /** Helper for creating a LightInfo and setting its properties.
- For multi-colored lights, use the first lightId.
- See LightInfo for documentation of arguments.
- Should only be called from a Module subclass's constructor.
- */
- template <class TLightInfo = LightInfo>
- TLightInfo* configLight(int lightId, std::string name = "") {
- assert(lightId < (int) lights.size() && lightId < (int) lightInfos.size());
- if (lightInfos[lightId])
- delete lightInfos[lightId];
-
- TLightInfo* info = new TLightInfo;
- info->LightInfo::module = this;
- info->LightInfo::lightId = lightId;
- info->LightInfo::name = name;
- lightInfos[lightId] = info;
- return info;
- }
-
- /** Adds a direct route from an input to an output when the module is bypassed.
- Should only be called from a Module subclass's constructor.
- */
- 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) {
- // Prevent unused variable warning for compilers that ignore assert()
- (void) br;
- assert(br.outputId != outputId);
- }
-
- BypassRoute br;
- br.inputId = inputId;
- br.outputId = outputId;
- bypassRoutes.push_back(br);
- }
-
- /** Creates and returns the module's patch storage directory path.
- Do not call this method in process() since filesystem operations block the audio thread.
-
- Throws an Exception if Module is not yet added to the Engine.
- Therefore, you may not call these methods in your Module constructor.
- Instead, load patch storage files in onAdd() and save them in onSave().
-
- Patch storage files of deleted modules are garbage collected when user saves the patch.
- To allow the Undo feature to restore patch storage if the module is accidentally deleted, it is recommended to not delete patch storage in onRemove().
- */
- std::string createPatchStorageDirectory();
- std::string getPatchStorageDirectory();
-
- /** Getters for members */
- plugin::Model* getModel() {
- return model;
- }
- int64_t getId() {
- return id;
- }
- int getNumParams() {
- return params.size();
- }
- Param& getParam(int index) {
- return params[index];
- }
- int getNumInputs() {
- return inputs.size();
- }
- Input& getInput(int index) {
- return inputs[index];
- }
- int getNumOutputs() {
- return outputs.size();
- }
- Output& getOutput(int index) {
- return outputs[index];
- }
- int getNumLights() {
- return lights.size();
- }
- Light& getLight(int index) {
- return lights[index];
- }
- ParamQuantity* getParamQuantity(int index) {
- return paramQuantities[index];
- }
- PortInfo* getInputInfo(int index) {
- return inputInfos[index];
- }
- PortInfo* getOutputInfo(int index) {
- return outputInfos[index];
- }
- LightInfo* getLightInfo(int index) {
- return lightInfos[index];
- }
- Expander& getLeftExpander() {
- return leftExpander;
- }
- Expander& getRightExpander() {
- return rightExpander;
- }
- /** Returns the left Expander for `side = 0` and the right Expander for `side = 1`. */
- Expander& getExpander(uint8_t side) {
- return side ? rightExpander : leftExpander;
- }
-
- // Virtual methods
-
- struct ProcessArgs {
- /** The current sample rate in Hz. */
- float sampleRate;
- /** The timestep of process() in seconds.
- Defined by `1 / sampleRate`.
- */
- float sampleTime;
- /** Number of audio samples since the Engine's first sample. */
- int64_t frame;
- };
- /** 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);
-
- /** Serializes the "params" object. */
- 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;
- }
- /** Override to load internal data from the "data" property of the module's JSON object.
- Not called if "data" property is not present.
- */
- virtual void dataFromJson(json_t* rootJ) {}
-
- ///////////////////////
- // 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 from 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::INPUT or Port::OUTPUT */
- 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 when the Engine sample rate changes, and when the Module is added to the Engine.
- */
- virtual void onSampleRateChange(const SampleRateChangeEvent& e) {
- // Call deprecated event method by default
- onSampleRateChange();
- }
-
- struct ExpanderChangeEvent {
- /** 0 for left, 1 for right. */
- uint8_t side;
- };
- /** Called after an expander is added, removed, or changed on either the left or right side of the Module.
- */
- 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);
-
- struct SaveEvent {};
- /** Called when the user saves the patch to a file.
- If your module uses patch asset storage, make sure all files are saved in this event.
- */
- virtual void onSave(const SaveEvent& e) {}
-
- struct SetMasterEvent {};
- virtual void onSetMaster(const SetMasterEvent& e) {}
-
- struct UnsetMasterEvent {};
- virtual void onUnsetMaster(const UnsetMasterEvent& 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() {}
-
- bool isBypassed();
- PRIVATE void setBypassed(bool bypassed);
- PRIVATE const float* meterBuffer();
- PRIVATE int meterLength();
- PRIVATE int meterIndex();
- PRIVATE void doProcess(const ProcessArgs& args);
- PRIVATE static void jsonStripIds(json_t* rootJ);
- /** Sets module of expander and dispatches ExpanderChangeEvent if changed. */
- PRIVATE void setExpanderModule(Module* module, uint8_t side);
- };
-
-
- } // namespace engine
- } // namespace rack
|