diff --git a/CHANGELOG.md b/CHANGELOG.md index 40b71bf..ad3de96 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +v1.3.1 (3-26-2021) +- Added new module RandRoute +- Added new module Collider +- Added volts offset knob for Orbitones +- Bug fix for Orbitones trails +- Updated docs + v1.2.4 (3-05-2021) - Bug fix in Orbitones - Added percentages on individual sliders for StochSeq and StochSeq4 diff --git a/README.md b/README.md index bc683b7..3a4521d 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ Add these modules to VCVRack through the [Rack library](https://library.vcvrack. ## Contents: +* [Collider](#collider) * [Cosmosis](#cosmosis) :tv: * [Neutrinode](#neutrinode) :tv: * [Orbitones](#orbitones) @@ -16,12 +17,41 @@ Add these modules to VCVRack through the [Rack library](https://library.vcvrack. * [PolyrhythmClock](#polyrhythm-clock) :tv: * [QubitCrusher](#qubit-crusher) * [RandGates](#randgates) :tv: +* [RandRoute](#randroute) * [StochSeq](#stochseq) :tv: * [StochSeq4](#stochseq4) :tv: * [Talea](#talea) :tv: --- +### Collider + +![Collider](/docs/Collider.png) + +*A physical model of various of shakers and wind chimes (maracas, sleigh bells, bamboo chimes, metallic chimes, etc).* + +##### RIGHT-CLICK MENU: +- Polyphony. +##### BUTTON: +- `SHAKE` shakes the particles. Hold down to continuously shake. +##### INPUTS: +- `SHAKE` gate controls the shake. +- `V/OCT` center frequency of the particles. +- `SPREAD` control voltage determines the amount of spread from the center frequency. +- `VEL` control voltage for the initial shake energy. +- `PARTICLES` control voltage for the number of particles. More particles = faster decay in system energy, less particles = slower decay. +##### KNOBS: +- `C` center frequency of the particles in Hz. +- `SPREAD` sets the amount of spread from the center frequency. +- `RND` sets the amount of random frequencydeviation on each particle collision. +- `PARTICLES` sets the number of particles. More particles = faster decay in system energy, less particles = slower decay. +##### OUTPUTS: +- `V/OCT` outputs ±5 volts. +- `GATE` outputs pulses. +- `VEL` outputs velocity of entire system. + +--- + ### Cosmosis ![Cosmosis](/docs/Cosmosis.png) @@ -130,6 +160,7 @@ Click on the attractors to move position them where you want. Click anywhere els - `ON` turns on/off individual attractors. - `G` scales the individual attractors' gravity. - `G` (big knob) main gravity control for all attractors. +- `OFFSET` offsets the voltage output. ##### INPUTS: - `MOVE` trigger turns on/off the random movement of attractors. - `G` takes a CV using this formula: `G` * 2V. @@ -257,6 +288,22 @@ Watch the tutorial: --- +### RandRoute + +![RandRoute](/docs/RandRoute.png) + +*Randomly routes one inputs to 4 possible outputs.* + +##### INPUTS: +- `TRG` randomizes the output +- `IN` any type of input, i.e. gates or ±5 volts. +##### KNOB: +- Gives a higher probability weight to the chosen input. All the way to the right is uniform randomness. +##### OUTPUT: +- `OUTS` (purple, blue, aqua, red) randomly chosen to output the input as either a pulse or ±5 volts. + +--- + ### StochSeq ![StochSeq](/docs/StochSeq.png) diff --git a/docs/Collider.png b/docs/Collider.png new file mode 100644 index 0000000..08e460d Binary files /dev/null and b/docs/Collider.png differ diff --git a/docs/RandRoute.png b/docs/RandRoute.png new file mode 100644 index 0000000..a7367d4 Binary files /dev/null and b/docs/RandRoute.png differ diff --git a/plugin.json b/plugin.json index 729b948..95cd7bf 100644 --- a/plugin.json +++ b/plugin.json @@ -1,7 +1,7 @@ { "slug": "Sha-Bang-Modules", "name": "Sha#Bang! Modules", - "version": "1.2.4", + "version": "1.3.1", "license": "GPL-3.0-only", "author": "Jeremy Muller", "authorEmail": "jeremy@jeremymuller.com", @@ -18,6 +18,12 @@ "description": "Hydrogen absorption spectrum", "tags": ["Blank", "Visual"] }, + { + "slug": "Collider", + "name": "Collider", + "description": "Physics-based shaker", + "tags": ["Granular", "Physical modeling", "Polyphonic"] + }, { "slug": "Cosmosis", "name": "Cosmosis", @@ -66,6 +72,12 @@ "description": "Randomly routes one of 4 inputs to the output", "tags": ["Utility", "Switch", "Polyphonic"] }, + { + "slug": "RandRoute", + "name": "Random Route Switch", + "description": "Randomly routes one input to 4 possible outputs", + "tags": ["Utility", "Switch", "Polyphonic"] + }, { "slug": "QubitCrusher", "name": "QubitCrusher", diff --git a/res/BigButtonDown.svg b/res/BigButtonDown.svg new file mode 100644 index 0000000..be7651c --- /dev/null +++ b/res/BigButtonDown.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/res/BigButtonUp.svg b/res/BigButtonUp.svg new file mode 100644 index 0000000..0e2b2c3 --- /dev/null +++ b/res/BigButtonUp.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/res/Collider.svg b/res/Collider.svg new file mode 100644 index 0000000..e17222a --- /dev/null +++ b/res/Collider.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/res/Orbitones.svg b/res/Orbitones.svg index 6c117b3..9bd6c22 100644 --- a/res/Orbitones.svg +++ b/res/Orbitones.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/res/RandRoute.svg b/res/RandRoute.svg new file mode 100644 index 0000000..4b70155 --- /dev/null +++ b/res/RandRoute.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Collider.cpp b/src/Collider.cpp new file mode 100644 index 0000000..9c8f255 --- /dev/null +++ b/src/Collider.cpp @@ -0,0 +1,254 @@ +#include "plugin.hpp" + +#define MIN_SHAKE_ENERGY 0.001 + +struct Collider : Module { + enum ParamIds { + SHAKE_PARAM, + PARTICLES_PARAM, + CENTER_FREQ_PARAM, + FREQ_RANGE_PARAM, + RANDOMIZE_PARAM, + NUM_PARAMS + }; + enum InputIds { + SHAKE_INPUT, + CENTER_FREQ_INPUT, + FREQ_RANGE_INPUT, + VEL_INPUT, + PARTICLES_INPUT, + NUM_INPUTS + }; + enum OutputIds { + VEL_OUTPUT, + GATE_OUTPUT, + VOLT_OUTPUT, + NUM_OUTPUTS + }; + enum LightIds { + PURPLE_LIGHT, + BLUE_LIGHT, + AQUA_LIGHT, + RED_LIGHT, + NUM_LIGHTS + }; + + dsp::SchmittTrigger shakeTrig; + dsp::PulseGenerator pulses[16]; + bool gates[16]; + bool isShaking = false; + float ampLevel = 0.0; + float centerFreq = 2800; // 2800 + float freqSweep = 1.001; + float centerVoltage = 0.0; + float freqRange = 0.2; + float notes[3]; + float freqs[3]; + float freqRandomize = 0.0; + int noteIndex = 0; + float shakeEnergy = 0.0; + float velocity = 1.0; + float systemDecay = 0.9999; + float soundDecay = 0.85; + int numOfParticles = 50; + float percentageObj = numOfParticles * 0.027; + int currentChannel = 0; + int channels = 1; + int checkParams = 0; + + Collider() { + config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); + configParam(SHAKE_PARAM, 0.0, 1.0, 0.0, "Shake"); + configParam(PARTICLES_PARAM, 1.0, 150.0, 50.0, "Number of Particles"); + configParam(CENTER_FREQ_PARAM, 100.0, 10000.0, 2000.0, "Frequency", " Hz"); + configParam(FREQ_RANGE_PARAM, 0.0, 1.0, 0.2, "Frequency Range", "x"); + configParam(RANDOMIZE_PARAM, 0.0, 1.0, 0.0, "Frequency randomization", "x"); + + initNotes(centerFreq); + } + + void initNotes(float center) { + centerFreq = center; + notes[0] = freqToMidi(centerFreq); + notes[1] = freqToMidi(centerFreq * (1.0 - freqRange)); + notes[2] = freqToMidi(centerFreq * (1.0 + freqRange)); + + freqs[0] = centerFreq; + freqs[1] = centerFreq * (1.0 - freqRange); + freqs[2] = centerFreq * (1.0 + freqRange); + } + + json_t *dataToJson() override { + json_t *rootJ = json_object(); + json_object_set_new(rootJ, "channels", json_integer(channels)); + + return rootJ; + } + + void dataFromJson(json_t *rootJ) override { + json_t *channelsJ = json_object_get(rootJ, "channels"); + if (channelsJ) channels = json_integer_value(channelsJ); + } + + void process(const ProcessArgs &args) override { + if (checkParams == 0) { + if (params[SHAKE_PARAM].getValue() + inputs[SHAKE_INPUT].getVoltage()) { + shakeEnergy = velocity; + } + + if (inputs[VEL_INPUT].isConnected()) { + float volts = inputs[VEL_INPUT].getVoltage() / 10.0; + if (volts != velocity) { + velocity = volts; + } + } else { + velocity = 1.0; + } + + freqRandomize = params[RANDOMIZE_PARAM].getValue(); + + if (inputs[PARTICLES_INPUT].isConnected()) { + float cv = inputs[PARTICLES_INPUT].getVoltage() / 10.0; + cv = cv * cv; + numOfParticles = (int)rescale(cv, 0.0, 1.0, 1.0, 150.0); + percentageObj = numOfParticles * 0.027; + } else if (params[PARTICLES_PARAM].getValue() != numOfParticles) { + numOfParticles = (int)params[PARTICLES_PARAM].getValue(); + percentageObj = numOfParticles * 0.027; + } + + if (inputs[CENTER_FREQ_INPUT].isConnected()) { + if (inputs[CENTER_FREQ_INPUT].getVoltage() != centerVoltage) { + centerVoltage = inputs[CENTER_FREQ_INPUT].getVoltage(); + centerFreq = dsp::FREQ_C4 * pow(2, centerVoltage); + initNotes(centerFreq); + } + } else if (params[CENTER_FREQ_PARAM].getValue() != centerFreq) { + initNotes(params[CENTER_FREQ_PARAM].getValue()); + } + + if (inputs[FREQ_RANGE_INPUT].isConnected()) { + float cv = clamp(inputs[FREQ_RANGE_INPUT].getVoltage() / 10.f, 0.0, 0.95); + cv = cv * cv; // square it so it's easy to get low values + if (cv != freqRange) { + freqRange = cv; + initNotes(centerFreq); + } + } else if (params[FREQ_RANGE_PARAM].getValue() != freqRange) { + freqRange = clamp(params[FREQ_RANGE_PARAM].getValue(), 0.0, 0.95); + initNotes(centerFreq); + } + } + checkParams = (checkParams + 1) % 4; + + // this algorithm inspired by Perry Cook's Phisem + + if (shakeEnergy > MIN_SHAKE_ENERGY) { + shakeEnergy *= systemDecay; + if (randRange(1024.f) < percentageObj) { + currentChannel = (currentChannel + 1) % channels; + ampLevel += shakeEnergy; + + pulses[currentChannel].trigger(1e-3f); + + float freq = freqs[noteIndex] * (1.0 + (freqRandomize * randRange(-1.0, 1.0))); + float volts = freqToVolts(freq); + noteIndex = (noteIndex + 1) % 3; + outputs[VOLT_OUTPUT].setVoltage(volts, currentChannel); + + outputs[VEL_OUTPUT].setVoltage(ampLevel * 10.0, currentChannel); + } + ampLevel *= soundDecay; + + bool pulse = pulses[currentChannel].process(args.sampleTime); + outputs[GATE_OUTPUT].setVoltage(pulse ? 10.0 : 0.0, currentChannel); + + } else { + for (int i = 0; i < channels; i++) { + bool pulse = pulses[i].process(args.sampleTime); + outputs[GATE_OUTPUT].setVoltage(pulse ? 10.0 : 0.0, i); + } + } + + outputs[VEL_OUTPUT].setChannels(channels); + outputs[GATE_OUTPUT].setChannels(channels); + outputs[VOLT_OUTPUT].setChannels(channels); + + } +}; + +namespace ColliderNS { + struct ChannelValueItem : MenuItem { + Collider *module; + int channels; + void onAction(const event::Action &e) override { + module->channels = channels; + } + }; + + struct ChannelItem : MenuItem { + Collider *module; + Menu *createChildMenu() override { + Menu *menu = new Menu; + for (int channels = 1; channels <= 16; channels++) { + ChannelValueItem *item = new ChannelValueItem; + if (channels == 1) + item->text = "Monophonic"; + else + item->text = string::f("%d", channels); + item->rightText = CHECKMARK(module->channels == channels); + item->module = module; + item->channels = channels; + menu->addChild(item); + } + return menu; + } + }; +} + +struct ColliderWidget : ModuleWidget { + ColliderWidget(Collider *module) { + setModule(module); + setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/Collider.svg"))); + + // screws + addChild(createWidget(Vec(17.3, 2))); + addChild(createWidget(Vec(17.3, box.size.y - 14))); + addChild(createWidget(Vec(90.7, 2))); + addChild(createWidget(Vec(90.7, box.size.y - 14))); + + // button + addParam(createParamCentered(Vec(60, 77.4), module, Collider::SHAKE_PARAM)); + + // knobs + addParam(createParamCentered(Vec(29.4, 184.3), module, Collider::CENTER_FREQ_PARAM)); + addParam(createParamCentered(Vec(61.5, 184.3), module, Collider::FREQ_RANGE_PARAM)); + addParam(createParamCentered(Vec(93.6, 184.3), module, Collider::RANDOMIZE_PARAM)); + addParam(createParamCentered(Vec(77.6, 245.8), module, Collider::PARTICLES_PARAM)); + + // inputs + addInput(createInputCentered(Vec(29.4, 106), module, Collider::SHAKE_INPUT)); + addInput(createInputCentered(Vec(29.4, 155.9), module, Collider::CENTER_FREQ_INPUT)); + addInput(createInputCentered(Vec(61.5, 155.9), module, Collider::FREQ_RANGE_INPUT)); + addInput(createInputCentered(Vec(93.6, 155.9), module, Collider::VEL_INPUT)); + addInput(createInputCentered(Vec(45.4, 245.8), module, Collider::PARTICLES_INPUT)); + + // outputs + addOutput(createOutputCentered(Vec(29.4, 300.8), module, Collider::VOLT_OUTPUT)); + addOutput(createOutputCentered(Vec(61.5, 300.8), module, Collider::GATE_OUTPUT)); + addOutput(createOutputCentered(Vec(93.6, 300.8), module, Collider::VEL_OUTPUT)); + } + + void appendContextMenu(Menu *menu) override { + Collider *module = dynamic_cast(this->module); + menu->addChild(new MenuEntry); + + ColliderNS::ChannelItem *channelItem = new ColliderNS::ChannelItem; + channelItem->text = "Polyphony channels"; + channelItem->rightText = string::f("%d", module->channels) + " " + RIGHT_ARROW; + channelItem->module = module; + menu->addChild(channelItem); + } +}; + +Model *modelCollider = createModel("Collider"); diff --git a/src/Orbitones.cpp b/src/Orbitones.cpp index 6d5499e..683a65b 100644 --- a/src/Orbitones.cpp +++ b/src/Orbitones.cpp @@ -3,17 +3,22 @@ #define DISPLAY_SIZE_WIDTH 427 #define DISPLAY_SIZE_HEIGHT 378 #define MAX_PARTICLES 16 +#define MAX_HISTORY 20 #define INTERNAL_SAMP_TIME 60.0 struct Trail { float x; float y; int alpha; - Trail() {} + bool visible; + Trail() { + visible = false; + } Trail(float _x, float _y, int _alpha) { x = _x; y = _y; alpha = _alpha; + visible = true; } }; @@ -27,7 +32,10 @@ struct Particle { float radius; bool visible; bool whiteTrails = true; - std::vector history; + // std::vector history; + Trail history[MAX_HISTORY]; + int currentHistorySize; + int historyIndex; Particle() { box.pos.x = randRange(0, DISPLAY_SIZE_WIDTH); @@ -38,6 +46,8 @@ struct Particle { radius = randRange(5, 12); mass = radius; visible = false; + currentHistorySize = 0; + historyIndex = 0; } void setPos(Vec _pos) { @@ -71,17 +81,22 @@ struct Particle { // trail Vec v = box.getCenter(); Trail t = Trail(v.x, v.y, 255); - history.insert(history.begin(), t); - // history.push_back(t); - if (history.size() > 20) { - history.pop_back(); - // history.erase(history.begin()); + + // shift elements to insert at beginning + for (int i = MAX_HISTORY-1; i > 0; i--) { + history[i] = history[i-1]; } + history[0] = t; + + currentHistorySize = std::min(++currentHistorySize, MAX_HISTORY); + historyIndex = (historyIndex + 1) % MAX_HISTORY; + return true; } void clearHistory() { - history.clear(); + currentHistorySize = 0; + historyIndex = 0; } }; @@ -138,6 +153,7 @@ struct Orbitones : Module { NUM_ATTRACTORS }; enum ParamIds { + OFFSET_PARAM, REMOVE_PARTICLE_PARAM, CLEAR_PARTICLES_PARAM, MOVE_ATTRACTORS_PARAM, @@ -173,6 +189,7 @@ struct Orbitones : Module { dsp::SchmittTrigger removeTrig, clearTrig, moveTrig; Attractor *attractors = new Attractor[NUM_ATTRACTORS]; Particle *particles = new Particle[MAX_PARTICLES]; + float voltsOffset = 0.0; bool movement = false; bool drawTrails = true; bool particleBoundary = false; @@ -184,6 +201,7 @@ struct Orbitones : Module { Orbitones() { config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); + configParam(OFFSET_PARAM, -5.0, 5.0, 0.0, "Offset", " V"); configParam(REMOVE_PARTICLE_PARAM, 0.0, 1.0, 0.0, "Remove previous particle"); configParam(CLEAR_PARTICLES_PARAM, 0.0, 1.0, 0.0, "Clear particles"); configParam(MOVE_ATTRACTORS_PARAM, 0.0, 1.0, 0.0, "Move attractors"); @@ -320,6 +338,7 @@ struct Orbitones : Module { } void process(const ProcessArgs &args) override { + voltsOffset = params[OFFSET_PARAM].getValue(); if (removeTrig.process(params[REMOVE_PARTICLE_PARAM].getValue())) { if (visibleParticles > 0) removeParticle(visibleParticles-1); } @@ -372,10 +391,11 @@ struct Orbitones : Module { } } particles[i].update(); - float voltsX = rescale(particles[i].box.pos.x, 0, DISPLAY_SIZE_WIDTH, -5.0, 5.0); - float voltsY = rescale(particles[i].box.pos.y, DISPLAY_SIZE_HEIGHT, 0, -5.0, 5.0); - float voltsVelX = rescale(particles[i].vel.x, -12.0, 12.0, -5.0, 5.0); - float voltsVelY = rescale(particles[i].vel.y, 12.0, -12.0, -5.0, 5.0); + + float voltsX = rescale(particles[i].box.pos.x, 0, DISPLAY_SIZE_WIDTH, -5.0 + voltsOffset, 5.0 + voltsOffset); + float voltsY = rescale(particles[i].box.pos.y, DISPLAY_SIZE_HEIGHT, 0, -5.0 + voltsOffset, 5.0 + voltsOffset); + float voltsVelX = rescale(particles[i].vel.x, -12.0, 12.0, -5.0 + voltsOffset, 5.0 + voltsOffset); + float voltsVelY = rescale(particles[i].vel.y, 12.0, -12.0, -5.0 + voltsOffset, 5.0 + voltsOffset); outputs[X_POLY_OUTPUT].setVoltage(voltsX, i); outputs[Y_POLY_OUTPUT].setVoltage(voltsY, i); outputs[NEG_X_POLY_OUTPUT].setVoltage(-voltsX, i); @@ -694,8 +714,7 @@ struct OrbitonesDisplay : Widget { if (module->drawTrails) { bool updated = module->particles[i].updateHistory(); if (!updated) break; - unsigned int length = std::min(module->particles[i].history.size(), 20); - for (unsigned int j = 1; j < length; j++) { + for (int j = 1; j < module->particles[i].currentHistorySize; j++) { Trail trailPos = module->particles[i].history[j]; Trail trailPosPrev = module->particles[i].history[j-1]; nvgBeginPath(args.vg); @@ -746,6 +765,7 @@ struct OrbitonesWidget : ModuleWidget { addChild(createWidget(Vec(505.5, 2))); addChild(createWidget(Vec(505.5, box.size.y - 14))); + addParam(createParamCentered(Vec(511.5, 201.5), module, Orbitones::OFFSET_PARAM)); addParam(createParamCentered(Vec(42, 56), module, Orbitones::REMOVE_PARTICLE_PARAM)); addParam(createParamCentered(Vec(42, 93.4), module, Orbitones::CLEAR_PARTICLES_PARAM)); addParam(createParamCentered(Vec(42, 130.9), module, Orbitones::MOVE_ATTRACTORS_PARAM)); diff --git a/src/RandRoute.cpp b/src/RandRoute.cpp new file mode 100644 index 0000000..3b34a94 --- /dev/null +++ b/src/RandRoute.cpp @@ -0,0 +1,90 @@ +#include "plugin.hpp" + +#define NUM_OF_OUTPUTS 4 + +struct RandRoute : Module { + enum ParamIds { + WEIGHTING_PARAM, + NUM_PARAMS + }; + enum InputIds { + TRIGGER_INPUT, + GATE_INPUT, + NUM_INPUTS + }; + enum OutputIds { + GATES_OUTPUT = TRIGGER_INPUT + NUM_OF_OUTPUTS, + NUM_OUTPUTS = GATES_OUTPUT + NUM_OF_OUTPUTS + }; + enum LightIds { + PURPLE_LIGHT, + BLUE_LIGHT, + AQUA_LIGHT, + RED_LIGHT, + NUM_LIGHTS + }; + + dsp::SchmittTrigger mainTrig; + int currentGate; + + RandRoute() { + config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); + configParam(WEIGHTING_PARAM, 0.0, 4.0, 4.0, "weight"); + + + setCurrentGate(); + } + + void setCurrentGate() { + if (params[WEIGHTING_PARAM].getValue() < 4) { + int weight = (int)params[WEIGHTING_PARAM].getValue(); + int r = static_cast(random::uniform() * (NUM_OF_OUTPUTS+1)); + currentGate = (r > 3) ? weight : r; // 40% chance of weighted choice + } else { + currentGate = static_cast(random::uniform() * NUM_OF_OUTPUTS); + } + } + + void process(const ProcessArgs &args) override { + // TODO: fix this routing shizzzz + if (mainTrig.process(inputs[TRIGGER_INPUT].getVoltage())) { + setCurrentGate(); + } + for (int i = 0; i < NUM_LIGHTS; i++) { + lights[i].setBrightness((i==currentGate) ? 1.0 : 0.0); + } + + int channels = inputs[GATE_INPUT].getChannels(); + for (int c = 0; c < channels; c++) { + float in = inputs[GATE_INPUT].getVoltage(c); + outputs[GATES_OUTPUT + currentGate].setVoltage(in, c); + } + outputs[GATES_OUTPUT].setChannels(channels); + } +}; + +struct RandRouteWidget : ModuleWidget { + RandRouteWidget(RandRoute *module) { + setModule(module); + setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/RandRoute.svg"))); + + addChild(createWidget(Vec(16.5, 2))); + addChild(createWidget(Vec(16.5, box.size.y - 14))); + + addInput(createInputCentered(Vec(22.5, 79.4), module, RandRoute::TRIGGER_INPUT)); + addParam(createParamCentered(Vec(22.5, 156.1), module, RandRoute::WEIGHTING_PARAM)); + + for (int i = 0; i < NUM_OF_OUTPUTS; i++) { + addOutput(createOutputCentered(Vec(22.5, 200.7 + (i * 40.5)), module, RandRoute::GATES_OUTPUT+i)); + } + addInput(createInputCentered(Vec(22.5, 119.8), module, RandRoute::GATE_INPUT)); + + // lights + addChild(createLight>(Vec(22.5 - 3.21, 340.9 - 3.21), module, RandRoute::PURPLE_LIGHT)); + addChild(createLight>(Vec(22.5 - 3.21, 340.9 - 3.21), module, RandRoute::BLUE_LIGHT)); + addChild(createLight>(Vec(22.5 - 3.21, 340.9 - 3.21), module, RandRoute::AQUA_LIGHT)); + addChild(createLight>(Vec(22.5 - 3.21, 340.9 - 3.21), module, RandRoute::RED_LIGHT)); + } +}; + +Model *modelRandRoute = createModel("RandRoute"); diff --git a/src/plugin.cpp b/src/plugin.cpp index a2349df..b060d61 100644 --- a/src/plugin.cpp +++ b/src/plugin.cpp @@ -12,6 +12,7 @@ void init(Plugin* p) { p->addModel(modelStochSeq4); p->addModel(modelPolyrhythmClock); p->addModel(modelRandGates); + p->addModel(modelRandRoute); p->addModel(modelNeutrinode); p->addModel(modelCosmosis); p->addModel(modelJeremyBlankPanel); @@ -21,6 +22,7 @@ void init(Plugin* p) { p->addModel(modelOrbitones); p->addModel(modelAbsorptionSpectrum); p->addModel(modelTalea); + p->addModel(modelCollider); // Any other plugin initialization may go here. // As an alternative, consider lazy-loading assets and lookup tables when your module is created to reduce startup times of Rack. diff --git a/src/plugin.hpp b/src/plugin.hpp index 6c5fbd6..44d5048 100644 --- a/src/plugin.hpp +++ b/src/plugin.hpp @@ -14,6 +14,7 @@ extern Model *modelStochSeq; extern Model *modelStochSeq4; extern Model *modelPolyrhythmClock; extern Model *modelRandGates; +extern Model *modelRandRoute; extern Model *modelNeutrinode; extern Model *modelCosmosis; extern Model *modelJeremyBlankPanel; @@ -23,11 +24,11 @@ extern Model *modelPhotronPanel; extern Model *modelOrbitones; extern Model *modelAbsorptionSpectrum; extern Model *modelTalea; +extern Model *modelCollider; /************************** INLINE FUNCTIONS **************************/ -inline float -dist(Vec a, Vec b) +inline float dist(Vec a, Vec b) { // returns distance between two points return std::sqrt(std::pow((a.x-b.x), 2) + std::pow((a.y-b.y), 2)); } @@ -45,6 +46,14 @@ inline float mag(Vec a) { // returns magnitude of vector return std::sqrt(a.x * a.x + a.y * a.y); } +inline float freqToMidi(float freq) { + return 69 + 12.0 * log2(freq / 440.0); +} + +inline float freqToVolts(float freq) { + return log2(freq / dsp::FREQ_C4); +} + inline float randRange(float max) { // returns random float up to max return random::uniform() * max; } @@ -53,6 +62,10 @@ inline float randRange(float min, float max) { // returns random float within mi return random::uniform() * fabs(max-min) + min; } +inline int randRange(int max) { + return static_cast(random::uniform() * max); +} + /************************** LABEL **************************/ struct LeftAlignedLabel : Widget { @@ -202,6 +215,14 @@ struct DefaultButton : SvgSwitch { } }; +struct BigButton : SvgSwitch { + BigButton() { + momentary = true; + addFrame(APP->window->loadSvg(asset::plugin(pluginInstance, "res/BigButtonUp.svg"))); + addFrame(APP->window->loadSvg(asset::plugin(pluginInstance, "res/BigButtonDown.svg"))); + } +}; + struct PauseButton : SvgSwitch { PauseButton() { // momentary = true;