diff --git a/plugin.json b/plugin.json index f2f4f03..f333793 100644 --- a/plugin.json +++ b/plugin.json @@ -290,6 +290,16 @@ "Random", "Sample and hold" ] + }, + { + "slug": "CVMix", + "name": "CV Mix", + "description": "Mixes 3 CV signals with attenuverters", + "tags": [ + "Utility", + "Polyphonic", + "Mixer" + ] } ] } \ No newline at end of file diff --git a/res/CVMix.svg b/res/CVMix.svg new file mode 100644 index 0000000..aa175de --- /dev/null +++ b/res/CVMix.svg @@ -0,0 +1,367 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/CVMix.cpp b/src/CVMix.cpp new file mode 100644 index 0000000..499ae79 --- /dev/null +++ b/src/CVMix.cpp @@ -0,0 +1,86 @@ +#include "plugin.hpp" + + +struct CVMix : Module { + enum ParamId { + ENUMS(LEVEL_PARAMS, 3), + PARAMS_LEN + }; + enum InputId { + ENUMS(CV_INPUTS, 3), + INPUTS_LEN + }; + enum OutputId { + MIX_OUTPUT, + OUTPUTS_LEN + }; + enum LightId { + LIGHTS_LEN + }; + + CVMix() { + config(PARAMS_LEN, INPUTS_LEN, OUTPUTS_LEN, LIGHTS_LEN); + for (int i = 0; i < 3; i++) + configParam(LEVEL_PARAMS + i, -1.f, 1.f, 0.f, string::f("Level %d", i + 1), "%", 0, 100); + for (int i = 0; i < 3; i++) + configInput(CV_INPUTS + i, string::f("CV %d", i + 1)); + configOutput(MIX_OUTPUT, "Mix"); + } + + void process(const ProcessArgs& args) override { + if (!outputs[MIX_OUTPUT].isConnected()) + return; + + // Get number of channels + int channels = 1; + for (int i = 0; i < 3; i++) + channels = std::max(channels, inputs[CV_INPUTS + i].getChannels()); + + for (int c = 0; c < channels; c++) { + // Sum CV inputs + float mix = 0.f; + for (int i = 0; i < 3; i++) { + float cv; + // Normalize first input to 10V + if (i == 0) + cv = inputs[CV_INPUTS + i].getNormalVoltage(10.f, c); + else + cv = inputs[CV_INPUTS + i].getVoltage(c); + + // Apply gain + cv *= params[LEVEL_PARAMS + i].getValue(); + mix += cv; + } + + // Set mix output + outputs[MIX_OUTPUT].setVoltage(mix, c); + } + outputs[MIX_OUTPUT].setChannels(channels); + } +}; + + +struct CVMixWidget : ModuleWidget { + CVMixWidget(CVMix* module) { + setModule(module); + setPanel(createPanel(asset::plugin(pluginInstance, "res/CVMix.svg"))); + + addChild(createWidget(Vec(RACK_GRID_WIDTH, 0))); + addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0))); + addChild(createWidget(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); + addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); + + addParam(createParamCentered(mm2px(Vec(7.62, 24.723)), module, CVMix::LEVEL_PARAMS + 0)); + addParam(createParamCentered(mm2px(Vec(7.62, 41.327)), module, CVMix::LEVEL_PARAMS + 1)); + addParam(createParamCentered(mm2px(Vec(7.62, 57.922)), module, CVMix::LEVEL_PARAMS + 2)); + + addInput(createInputCentered(mm2px(Vec(7.62, 76.539)), module, CVMix::CV_INPUTS + 0)); + addInput(createInputCentered(mm2px(Vec(7.62, 86.699)), module, CVMix::CV_INPUTS + 1)); + addInput(createInputCentered(mm2px(Vec(7.62, 96.859)), module, CVMix::CV_INPUTS + 2)); + + addOutput(createOutputCentered(mm2px(Vec(7.62, 113.115)), module, CVMix::MIX_OUTPUT)); + } +}; + + +Model* modelCVMix = createModel("CVMix"); \ No newline at end of file diff --git a/src/plugin.cpp b/src/plugin.cpp index 07d5017..4816544 100644 --- a/src/plugin.cpp +++ b/src/plugin.cpp @@ -34,4 +34,5 @@ void init(Plugin* p) { p->addModel(modelMidSide); p->addModel(modelNoise); p->addModel(modelRandom); + p->addModel(modelCVMix); } diff --git a/src/plugin.hpp b/src/plugin.hpp index fa5bd9c..09e4ba2 100644 --- a/src/plugin.hpp +++ b/src/plugin.hpp @@ -34,6 +34,7 @@ extern Model* modelViz; extern Model* modelMidSide; extern Model* modelNoise; extern Model* modelRandom; +extern Model* modelCVMix; struct DigitalDisplay : Widget {