diff --git a/plugin.json b/plugin.json index c0295d7..27e2d23 100644 --- a/plugin.json +++ b/plugin.json @@ -359,6 +359,7 @@ "name": "Mult", "description": "Copies a signal to 8 outputs", "tags": [ + "Multiple", "Utility", "Polyphonic" ] @@ -368,6 +369,17 @@ "name": "Rescale", "description": "Rescales voltages with gain and offset", "tags": [ + "Attenuator", + "Utility", + "Polyphonic" + ] + }, + { + "slug": "RandomValues", + "name": "Random Values", + "description": "Generates random fixed voltages", + "tags": [ + "Random", "Utility", "Polyphonic" ] diff --git a/res/RandomValues.svg b/res/RandomValues.svg new file mode 100644 index 0000000..80581f2 --- /dev/null +++ b/res/RandomValues.svg @@ -0,0 +1,240 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/RandomValues.cpp b/src/RandomValues.cpp new file mode 100644 index 0000000..9828744 --- /dev/null +++ b/src/RandomValues.cpp @@ -0,0 +1,159 @@ +#include "plugin.hpp" + + +using simd::float_4; + + +struct RandomValues : Module { + enum ParamId { + PUSH_PARAM, + PARAMS_LEN + }; + enum InputId { + TRIG_INPUT, + INPUTS_LEN + }; + enum OutputId { + ENUMS(RND_OUTPUTS, 7), + OUTPUTS_LEN + }; + enum LightId { + PUSH_LIGHT, + LIGHTS_LEN + }; + + dsp::BooleanTrigger pushTrigger; + dsp::TSchmittTrigger trigTriggers[4]; + + struct Range { + float gain; + float offset; + + bool operator==(const Range& other) const { + return gain == other.gain && offset == other.offset; + } + }; + Range range = {10.f, 0.f}; + + RandomValues() { + config(PARAMS_LEN, INPUTS_LEN, OUTPUTS_LEN, LIGHTS_LEN); + configButton(PUSH_PARAM, "Push"); + configInput(TRIG_INPUT, "Trigger"); + for (int i = 0; i < 7; i++) + configOutput(RND_OUTPUTS + i, string::f("Random %d", i + 1)); + } + + void onReset(const ResetEvent& e) override { + Module::onReset(e); + range = {10.f, 0.f}; + } + + void process(const ProcessArgs& args) override { + int channels = std::max(1, inputs[TRIG_INPUT].getChannels()); + + bool pushed = pushTrigger.process(params[PUSH_PARAM].getValue()); + bool light = false; + + for (int c = 0; c < channels; c += 4) { + float_4 triggered = trigTriggers[c / 4].process(inputs[TRIG_INPUT].getVoltageSimd(c), 0.1f, 1.f); + int triggeredMask = simd::movemask(triggered); + + // This branch is infrequent so we don't need to use SIMD. + if (pushed || triggeredMask) { + light = true; + + for (int c2 = 0; c2 < std::min(4, channels - c); c2++) { + if (pushed || (triggeredMask & (1 << c2))) { + for (int i = 0; i < 7; i++) { + float r = random::get(); + r = r * range.gain + range.offset; + outputs[RND_OUTPUTS + i].setVoltage(r, c + c2); + } + } + } + } + } + + for (int i = 0; i < 7; i++) { + outputs[RND_OUTPUTS + i].setChannels(channels); + } + lights[PUSH_LIGHT].setBrightnessSmooth(light, args.sampleTime); + } + + json_t* dataToJson() override { + json_t* rootJ = json_object(); + json_object_set_new(rootJ, "gain", json_real(range.gain)); + json_object_set_new(rootJ, "offset", json_real(range.offset)); + return rootJ; + } + + void dataFromJson(json_t* rootJ) override { + json_t* gainJ = json_object_get(rootJ, "gain"); + if (gainJ) + range.gain = json_number_value(gainJ); + + json_t* offsetJ = json_object_get(rootJ, "offset"); + if (offsetJ) + range.offset = json_number_value(offsetJ); + } +}; + + +struct RandomValuesWidget : ModuleWidget { + RandomValuesWidget(RandomValues* module) { + setModule(module); + setPanel(createPanel(asset::plugin(pluginInstance, "res/RandomValues.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(createLightParamCentered>(mm2px(Vec(7.62, 21.968)), module, RandomValues::PUSH_PARAM, RandomValues::PUSH_LIGHT)); + + addInput(createInputCentered(mm2px(Vec(7.622, 38.225)), module, RandomValues::TRIG_INPUT)); + + addOutput(createOutputCentered(mm2px(Vec(7.622, 52.35)), module, RandomValues::RND_OUTPUTS + 0)); + addOutput(createOutputCentered(mm2px(Vec(7.622, 62.477)), module, RandomValues::RND_OUTPUTS + 1)); + addOutput(createOutputCentered(mm2px(Vec(7.622, 72.605)), module, RandomValues::RND_OUTPUTS + 2)); + addOutput(createOutputCentered(mm2px(Vec(7.622, 82.732)), module, RandomValues::RND_OUTPUTS + 3)); + addOutput(createOutputCentered(mm2px(Vec(7.622, 92.86)), module, RandomValues::RND_OUTPUTS + 4)); + addOutput(createOutputCentered(mm2px(Vec(7.622, 102.987)), module, RandomValues::RND_OUTPUTS + 5)); + addOutput(createOutputCentered(mm2px(Vec(7.622, 113.013)), module, RandomValues::RND_OUTPUTS + 6)); + } + + void appendContextMenu(Menu* menu) override { + RandomValues* module = getModule(); + + menu->addChild(new MenuSeparator); + + static const std::vector ranges = { + {10.f, 0.f}, + {5.f, 0.f}, + {1.f, 0.f}, + {20.f, -10.f}, + {10.f, -5.f}, + {2.f, -1.f}, + }; + static const std::vector labels = { + "0V to 10V", + "0V to 5V", + "0V to 1V", + "-10V to 10V", + "-5V to 5V", + "-1V to 1V", + }; + menu->addChild(createIndexSubmenuItem("Voltage range", labels, + [=]() { + auto it = std::find(ranges.begin(), ranges.end(), module->range); + return std::distance(ranges.begin(), it); + }, + [=](int i) { + module->range = ranges[i]; + } + )); + } +}; + + +Model* modelRandomValues = createModel("RandomValues"); \ No newline at end of file diff --git a/src/plugin.cpp b/src/plugin.cpp index 095f460..731b4af 100644 --- a/src/plugin.cpp +++ b/src/plugin.cpp @@ -42,4 +42,5 @@ void init(Plugin* p) { p->addModel(modelProcess); p->addModel(modelMult); p->addModel(modelRescale); + p->addModel(modelRandomValues); } diff --git a/src/plugin.hpp b/src/plugin.hpp index cc754a5..dd0a9fc 100644 --- a/src/plugin.hpp +++ b/src/plugin.hpp @@ -42,6 +42,7 @@ extern Model* modelGates; extern Model* modelProcess; extern Model* modelMult; extern Model* modelRescale; +extern Model* modelRandomValues; struct DigitalDisplay : Widget {