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;