diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4b896e5..d6cb682 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,4 +1,8 @@
+### 0.6.2 (2018-10-09)
+
+- Added Random Sampler from Audible Instruments Preview
+
### 0.6.1 (2018-09-12)
- Added Macro Oscillator 2 from Audible Instruments Preview
diff --git a/Makefile b/Makefile
index f4e0c43..ebdd4ef 100644
--- a/Makefile
+++ b/Makefile
@@ -1,5 +1,5 @@
SLUG = AudibleInstruments
-VERSION = 0.6.1
+VERSION = 0.6.2
FLAGS += \
-DTEST \
@@ -76,9 +76,20 @@ SOURCES += eurorack/peaks/number_station/number_station.cc
SOURCES += eurorack/stages/segment_generator.cc
SOURCES += eurorack/stages/ramp_extractor.cc
-# SOURCES += eurorack/stages/chain_state.cc
SOURCES += eurorack/stages/resources.cc
+SOURCES += eurorack/stmlib/utils/random.cc
+SOURCES += eurorack/stmlib/dsp/atan.cc
+SOURCES += eurorack/stmlib/dsp/units.cc
+SOURCES += eurorack/marbles/random/t_generator.cc
+SOURCES += eurorack/marbles/random/x_y_generator.cc
+SOURCES += eurorack/marbles/random/output_channel.cc
+SOURCES += eurorack/marbles/random/lag_processor.cc
+SOURCES += eurorack/marbles/random/quantizer.cc
+SOURCES += eurorack/marbles/ramp/ramp_extractor.cc
+SOURCES += eurorack/marbles/resources.cc
+
+
DISTRIBUTABLES += $(wildcard LICENSE*) res
RACK_DIR ?= ../..
diff --git a/res/Marbles.svg b/res/Marbles.svg
new file mode 100644
index 0000000..1d38f71
--- /dev/null
+++ b/res/Marbles.svg
@@ -0,0 +1,2474 @@
+
+
+
+
diff --git a/src/AudibleInstruments.cpp b/src/AudibleInstruments.cpp
index b6f9ccb..b8f19c9 100644
--- a/src/AudibleInstruments.cpp
+++ b/src/AudibleInstruments.cpp
@@ -24,4 +24,5 @@ void init(rack::Plugin *p) {
p->addModel(modelFrames);
// p->addModel(modelPeaks);
// p->addModel(modelStages);
+ p->addModel(modelMarbles);
}
diff --git a/src/AudibleInstruments.hpp b/src/AudibleInstruments.hpp
index 0383a0a..9539be0 100644
--- a/src/AudibleInstruments.hpp
+++ b/src/AudibleInstruments.hpp
@@ -22,3 +22,4 @@ extern Model *modelVeils;
extern Model *modelFrames;
extern Model *modelPeaks;
extern Model *modelStages;
+extern Model *modelMarbles;
diff --git a/src/Marbles.cpp b/src/Marbles.cpp
new file mode 100644
index 0000000..5c1e835
--- /dev/null
+++ b/src/Marbles.cpp
@@ -0,0 +1,667 @@
+#include "AudibleInstruments.hpp"
+#include "dsp/digital.hpp"
+#include "marbles/random/random_generator.h"
+#include "marbles/random/random_stream.h"
+#include "marbles/random/t_generator.h"
+#include "marbles/random/x_y_generator.h"
+#include "marbles/note_filter.h"
+
+
+static const int BLOCK_SIZE = 5;
+
+
+static const marbles::Scale preset_scales[6] = {
+ // C major
+ {
+ 1.0f,
+ 12,
+ {
+ { 0.0000f, 255 }, // C
+ { 0.0833f, 16 }, // C#
+ { 0.1667f, 96 }, // D
+ { 0.2500f, 24 }, // D#
+ { 0.3333f, 128 }, // E
+ { 0.4167f, 64 }, // F
+ { 0.5000f, 8 }, // F#
+ { 0.5833f, 192 }, // G
+ { 0.6667f, 16 }, // G#
+ { 0.7500f, 96 }, // A
+ { 0.8333f, 24 }, // A#
+ { 0.9167f, 128 }, // B
+ }
+ },
+
+ // C minor
+ {
+ 1.0f,
+ 12,
+ {
+ { 0.0000f, 255 }, // C
+ { 0.0833f, 16 }, // C#
+ { 0.1667f, 96 }, // D
+ { 0.2500f, 128 }, // Eb
+ { 0.3333f, 8 }, // E
+ { 0.4167f, 64 }, // F
+ { 0.5000f, 4 }, // F#
+ { 0.5833f, 192 }, // G
+ { 0.6667f, 16 }, // G#
+ { 0.7500f, 96 }, // A
+ { 0.8333f, 128 }, // Bb
+ { 0.9167f, 16 }, // B
+ }
+ },
+
+ // Pentatonic
+ {
+ 1.0f,
+ 12,
+ {
+ { 0.0000f, 255 }, // C
+ { 0.0833f, 4 }, // C#
+ { 0.1667f, 96 }, // D
+ { 0.2500f, 4 }, // Eb
+ { 0.3333f, 4 }, // E
+ { 0.4167f, 140 }, // F
+ { 0.5000f, 4 }, // F#
+ { 0.5833f, 192 }, // G
+ { 0.6667f, 4 }, // G#
+ { 0.7500f, 96 }, // A
+ { 0.8333f, 4 }, // Bb
+ { 0.9167f, 4 }, // B
+ }
+ },
+
+ // Pelog
+ {
+ 1.0f,
+ 7,
+ {
+ { 0.0000f, 255 }, // C
+ { 0.1275f, 128 }, // Db+
+ { 0.2625f, 32 }, // Eb-
+ { 0.4600f, 8 }, // F#-
+ { 0.5883f, 192 }, // G
+ { 0.7067f, 64 }, // Ab
+ { 0.8817f, 16 }, // Bb+
+ }
+ },
+
+ // Raag Bhairav That
+ {
+ 1.0f,
+ 12,
+ {
+ { 0.0000f, 255 }, // ** Sa
+ { 0.0752f, 128 }, // ** Komal Re
+ { 0.1699f, 4 }, // Re
+ { 0.2630f, 4 }, // Komal Ga
+ { 0.3219f, 128 }, // ** Ga
+ { 0.4150f, 64 }, // ** Ma
+ { 0.4918f, 4 }, // Tivre Ma
+ { 0.5850f, 192 }, // ** Pa
+ { 0.6601f, 64 }, // ** Komal Dha
+ { 0.7549f, 4 }, // Dha
+ { 0.8479f, 4 }, // Komal Ni
+ { 0.9069f, 64 }, // ** Ni
+ }
+ },
+
+ // Raag Shri
+ {
+ 1.0f,
+ 12,
+ {
+ { 0.0000f, 255 }, // ** Sa
+ { 0.0752f, 4 }, // Komal Re
+ { 0.1699f, 128 }, // ** Re
+ { 0.2630f, 64 }, // ** Komal Ga
+ { 0.3219f, 4 }, // Ga
+ { 0.4150f, 128 }, // ** Ma
+ { 0.4918f, 4 }, // Tivre Ma
+ { 0.5850f, 192 }, // ** Pa
+ { 0.6601f, 4 }, // Komal Dha
+ { 0.7549f, 64 }, // ** Dha
+ { 0.8479f, 128 }, // ** Komal Ni
+ { 0.9069f, 4 }, // Ni
+ }
+ },
+};
+
+
+struct Marbles : Module {
+ enum ParamIds {
+ T_DEJA_VU_PARAM,
+ X_DEJA_VU_PARAM,
+ DEJA_VU_PARAM,
+ T_RATE_PARAM,
+ X_SPREAD_PARAM,
+ T_MODE_PARAM,
+ X_MODE_PARAM,
+ DEJA_VU_LENGTH_PARAM,
+ T_BIAS_PARAM,
+ X_BIAS_PARAM,
+ T_RANGE_PARAM,
+ X_RANGE_PARAM,
+ EXTERNAL_PARAM,
+ T_JITTER_PARAM,
+ X_STEPS_PARAM,
+ NUM_PARAMS
+ };
+ enum InputIds {
+ T_BIAS_INPUT,
+ X_BIAS_INPUT,
+ T_CLOCK_INPUT,
+ T_RATE_INPUT,
+ T_JITTER_INPUT,
+ DEJA_VU_INPUT,
+ X_STEPS_INPUT,
+ X_SPREAD_INPUT,
+ X_CLOCK_INPUT,
+ NUM_INPUTS
+ };
+ enum OutputIds {
+ T1_OUTPUT,
+ T2_OUTPUT,
+ T3_OUTPUT,
+ Y_OUTPUT,
+ X1_OUTPUT,
+ X2_OUTPUT,
+ X3_OUTPUT,
+ NUM_OUTPUTS
+ };
+ enum LightIds {
+ T_DEJA_VU_LIGHT,
+ X_DEJA_VU_LIGHT,
+ ENUMS(T_MODE_LIGHTS, 2),
+ ENUMS(X_MODE_LIGHTS, 2),
+ ENUMS(T_RANGE_LIGHTS, 2),
+ ENUMS(X_RANGE_LIGHTS, 2),
+ EXTERNAL_LIGHT,
+ T1_LIGHT,
+ T2_LIGHT,
+ T3_LIGHT,
+ Y_LIGHT,
+ X1_LIGHT,
+ X2_LIGHT,
+ X3_LIGHT,
+ NUM_LIGHTS
+ };
+
+ marbles::RandomGenerator random_generator;
+ marbles::RandomStream random_stream;
+ marbles::TGenerator t_generator;
+ marbles::XYGenerator xy_generator;
+ marbles::NoteFilter note_filter;
+
+ // State
+ BooleanTrigger tDejaVuTrigger;
+ BooleanTrigger xDejaVuTrigger;
+ BooleanTrigger tModeTrigger;
+ BooleanTrigger xModeTrigger;
+ BooleanTrigger tRangeTrigger;
+ BooleanTrigger xRangeTrigger;
+ BooleanTrigger externalTrigger;
+ bool t_deja_vu;
+ bool x_deja_vu;
+ int t_mode;
+ int x_mode;
+ int t_range;
+ int x_range;
+ bool external;
+ int x_scale;
+ int y_divider_index;
+ int x_clock_source_internal;
+
+ // Buffers
+ stmlib::GateFlags t_clocks[BLOCK_SIZE] = {};
+ stmlib::GateFlags last_t_clock = 0;
+ stmlib::GateFlags xy_clocks[BLOCK_SIZE] = {};
+ stmlib::GateFlags last_xy_clock = 0;
+ float ramp_master[BLOCK_SIZE] = {};
+ float ramp_external[BLOCK_SIZE] = {};
+ float ramp_slave[2][BLOCK_SIZE] = {};
+ bool gates[BLOCK_SIZE * 2] = {};
+ float voltages[BLOCK_SIZE * 4] = {};
+ int blockIndex = 0;
+
+ Marbles() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) {
+ random_generator.Init(1);
+ random_stream.Init(&random_generator);
+ note_filter.Init();
+ onSampleRateChange();
+ onReset();
+ }
+
+ void onReset() override {
+ t_deja_vu = false;
+ x_deja_vu = false;
+ t_mode = 0;
+ x_mode = 0;
+ t_range = 1;
+ x_range = 1;
+ external = false;
+ x_scale = 0;
+ y_divider_index = 8;
+ x_clock_source_internal = 0;
+ }
+
+ void onRandomize() override {
+ t_mode = randomu32() % 3;
+ x_mode = randomu32() % 3;
+ t_range = randomu32() % 3;
+ x_range = randomu32() % 3;
+ }
+
+ void onSampleRateChange() override {
+ float sampleRate = engineGetSampleRate();
+ t_generator.Init(&random_stream, sampleRate);
+ xy_generator.Init(&random_stream, sampleRate);
+
+ // Set scales
+ for (int i = 0; i < 6; i++) {
+ xy_generator.LoadScale(i, preset_scales[i]);
+ }
+ }
+
+ json_t *toJson() override {
+ json_t *rootJ = json_object();
+
+ json_object_set_new(rootJ, "t_deja_vu", json_boolean(t_deja_vu));
+ json_object_set_new(rootJ, "x_deja_vu", json_boolean(x_deja_vu));
+ json_object_set_new(rootJ, "t_mode", json_integer(t_mode));
+ json_object_set_new(rootJ, "x_mode", json_integer(x_mode));
+ json_object_set_new(rootJ, "t_range", json_integer(t_range));
+ json_object_set_new(rootJ, "x_range", json_integer(x_range));
+ json_object_set_new(rootJ, "external", json_boolean(external));
+ json_object_set_new(rootJ, "x_scale", json_integer(x_scale));
+ json_object_set_new(rootJ, "y_divider_index", json_integer(y_divider_index));
+ json_object_set_new(rootJ, "x_clock_source_internal", json_integer(x_clock_source_internal));
+
+ return rootJ;
+ }
+
+ void fromJson(json_t *rootJ) override {
+ json_t *t_deja_vuJ = json_object_get(rootJ, "t_deja_vu");
+ if (t_deja_vuJ)
+ t_deja_vu = json_boolean_value(t_deja_vuJ);
+
+ json_t *x_deja_vuJ = json_object_get(rootJ, "x_deja_vu");
+ if (x_deja_vuJ)
+ x_deja_vu = json_boolean_value(x_deja_vuJ);
+
+ json_t *t_modeJ = json_object_get(rootJ, "t_mode");
+ if (t_modeJ)
+ t_mode = json_integer_value(t_modeJ);
+
+ json_t *x_modeJ = json_object_get(rootJ, "x_mode");
+ if (x_modeJ)
+ x_mode = json_integer_value(x_modeJ);
+
+ json_t *t_rangeJ = json_object_get(rootJ, "t_range");
+ if (t_rangeJ)
+ t_range = json_integer_value(t_rangeJ);
+
+ json_t *x_rangeJ = json_object_get(rootJ, "x_range");
+ if (x_rangeJ)
+ x_range = json_integer_value(x_rangeJ);
+
+ json_t *externalJ = json_object_get(rootJ, "external");
+ if (externalJ)
+ external = json_boolean_value(externalJ);
+
+ json_t *x_scaleJ = json_object_get(rootJ, "x_scale");
+ if (x_scaleJ)
+ x_scale = json_integer_value(x_scaleJ);
+
+ json_t *y_divider_indexJ = json_object_get(rootJ, "y_divider_index");
+ if (y_divider_indexJ)
+ y_divider_index = json_integer_value(y_divider_indexJ);
+
+ json_t *x_clock_source_internalJ = json_object_get(rootJ, "x_clock_source_internal");
+ if (x_clock_source_internalJ)
+ x_clock_source_internal = json_integer_value(x_clock_source_internalJ);
+ }
+
+ void step() override {
+ // Buttons
+ if (tDejaVuTrigger.process(params[T_DEJA_VU_PARAM].value <= 0.f)) {
+ t_deja_vu = !t_deja_vu;
+ }
+ if (xDejaVuTrigger.process(params[X_DEJA_VU_PARAM].value <= 0.f)) {
+ x_deja_vu = !x_deja_vu;
+ }
+ if (tModeTrigger.process(params[T_MODE_PARAM].value <= 0.f)) {
+ t_mode = (t_mode + 1) % 3;
+ }
+ if (xModeTrigger.process(params[X_MODE_PARAM].value <= 0.f)) {
+ x_mode = (x_mode + 1) % 3;
+ }
+ if (tRangeTrigger.process(params[T_RANGE_PARAM].value <= 0.f)) {
+ t_range = (t_range + 1) % 3;
+ }
+ if (xRangeTrigger.process(params[X_RANGE_PARAM].value <= 0.f)) {
+ x_range = (x_range + 1) % 3;
+ }
+ if (externalTrigger.process(params[EXTERNAL_PARAM].value <= 0.f)) {
+ external = !external;
+ }
+
+ // Clocks
+ bool t_gate = (inputs[T_CLOCK_INPUT].value >= 1.7f);
+ last_t_clock = stmlib::ExtractGateFlags(last_t_clock, t_gate);
+ t_clocks[blockIndex] = last_t_clock;
+
+ bool x_gate = (inputs[X_CLOCK_INPUT].value >= 1.7f);
+ last_xy_clock = stmlib::ExtractGateFlags(last_xy_clock, x_gate);
+ xy_clocks[blockIndex] = last_xy_clock;
+
+ // Process block
+ if (++blockIndex >= BLOCK_SIZE) {
+ blockIndex = 0;
+ stepBlock();
+ }
+
+ // Lights and outputs
+
+ lights[T_DEJA_VU_LIGHT].setBrightness(t_deja_vu);
+ lights[X_DEJA_VU_LIGHT].setBrightness(x_deja_vu);
+
+ lights[T_MODE_LIGHTS + 0].setBrightness(t_mode == 0 || t_mode == 1);
+ lights[T_MODE_LIGHTS + 1].setBrightness(t_mode == 1 || t_mode == 2);
+
+ lights[X_MODE_LIGHTS + 0].setBrightness(x_mode == 0 || x_mode == 1);
+ lights[X_MODE_LIGHTS + 1].setBrightness(x_mode == 1 || x_mode == 2);
+
+ lights[T_RANGE_LIGHTS + 0].setBrightness(t_range == 0 || t_range == 1);
+ lights[T_RANGE_LIGHTS + 1].setBrightness(t_range == 1 || t_range == 2);
+
+ lights[X_RANGE_LIGHTS + 0].setBrightness(x_range == 0 || x_range == 1);
+ lights[X_RANGE_LIGHTS + 1].setBrightness(x_range == 1 || x_range == 2);
+
+ lights[EXTERNAL_LIGHT].setBrightness(external);
+
+ outputs[T1_OUTPUT].value = gates[blockIndex*2 + 0] ? 10.f : 0.f;
+ lights[T1_LIGHT].setBrightnessSmooth(gates[blockIndex*2 + 0]);
+ outputs[T2_OUTPUT].value = (ramp_master[blockIndex] < 0.5f) ? 10.f : 0.f;
+ lights[T2_LIGHT].setBrightnessSmooth(ramp_master[blockIndex] < 0.5f);
+ outputs[T3_OUTPUT].value = gates[blockIndex*2 + 1] ? 10.f : 0.f;
+ lights[T3_LIGHT].setBrightnessSmooth(gates[blockIndex*2 + 1]);
+
+ outputs[X1_OUTPUT].value = voltages[blockIndex*4 + 0];
+ lights[X1_LIGHT].setBrightnessSmooth(voltages[blockIndex*4 + 0]);
+ outputs[X2_OUTPUT].value = voltages[blockIndex*4 + 1];
+ lights[X2_LIGHT].setBrightnessSmooth(voltages[blockIndex*4 + 1]);
+ outputs[X3_OUTPUT].value = voltages[blockIndex*4 + 2];
+ lights[X3_LIGHT].setBrightnessSmooth(voltages[blockIndex*4 + 2]);
+ outputs[Y_OUTPUT].value = voltages[blockIndex*4 + 3];
+ lights[Y_LIGHT].setBrightnessSmooth(voltages[blockIndex*4 + 3]);
+ }
+
+ void stepBlock() {
+ // Ramps
+
+ marbles::Ramps ramps;
+ ramps.master = ramp_master;
+ ramps.external = ramp_external;
+ ramps.slave[0] = ramp_slave[0];
+ ramps.slave[1] = ramp_slave[1];
+
+ float deja_vu = clamp(params[DEJA_VU_PARAM].value + inputs[DEJA_VU_INPUT].value / 5.f, 0.f, 1.f);
+ static const int loop_length[] = {
+ 1, 1, 1, 2, 2,
+ 2, 2, 2, 3, 3,
+ 3, 3, 4, 4, 4,
+ 4, 4, 5, 5, 6,
+ 6, 6, 7, 7, 8,
+ 8, 8, 10, 10, 12,
+ 12, 12, 14, 14, 16,
+ 16
+ };
+ float deja_vu_length_index = params[DEJA_VU_LENGTH_PARAM].value * (LENGTHOF(loop_length) - 1);
+ int deja_vu_length = loop_length[(int) roundf(deja_vu_length_index)];
+
+ // Set up TGenerator
+
+ bool t_external_clock = inputs[T_CLOCK_INPUT].active;
+
+ t_generator.set_model((marbles::TGeneratorModel) t_mode);
+ t_generator.set_range((marbles::TGeneratorRange) t_range);
+ float t_rate = 60.f * (params[T_RATE_PARAM].value + inputs[T_RATE_INPUT].value / 5.f);
+ t_generator.set_rate(t_rate);
+ float t_bias = clamp(params[T_BIAS_PARAM].value + inputs[T_BIAS_INPUT].value / 5.f, 0.f, 1.f);
+ t_generator.set_bias(t_bias);
+ float t_jitter = clamp(params[T_JITTER_PARAM].value + inputs[T_JITTER_INPUT].value / 5.f, 0.f, 1.f);
+ t_generator.set_jitter(t_jitter);
+ t_generator.set_deja_vu(t_deja_vu ? deja_vu : 0.f);
+ t_generator.set_length(deja_vu_length);
+ // TODO
+ t_generator.set_pulse_width_mean(0.f);
+ t_generator.set_pulse_width_std(0.f);
+
+ t_generator.Process(t_external_clock, t_clocks, ramps, gates, BLOCK_SIZE);
+
+ // Set up XYGenerator
+
+ marbles::ClockSource x_clock_source = (marbles::ClockSource) x_clock_source_internal;
+ if (inputs[X_CLOCK_INPUT].active)
+ x_clock_source = marbles::CLOCK_SOURCE_EXTERNAL;
+
+ marbles::GroupSettings x;
+ x.control_mode = (marbles::ControlMode) x_mode;
+ x.voltage_range = (marbles::VoltageRange) x_range;
+ // TODO Fix the scaling
+ float note_cv = 0.5f * (params[X_SPREAD_PARAM].value + inputs[X_SPREAD_INPUT].value / 5.f);
+ float u = note_filter.Process(0.5f * (note_cv + 1.f));
+ x.register_mode = external;
+ x.register_value = u;
+
+ float x_spread = clamp(params[X_SPREAD_PARAM].value + inputs[X_SPREAD_INPUT].value / 5.f, 0.f, 1.f);
+ x.spread = x_spread;
+ float x_bias = clamp(params[X_BIAS_PARAM].value + inputs[X_BIAS_INPUT].value / 5.f, 0.f, 1.f);
+ x.bias = x_bias;
+ float x_steps = clamp(params[X_STEPS_PARAM].value + inputs[X_STEPS_INPUT].value / 5.f, 0.f, 1.f);
+ x.steps = x_steps;
+ x.deja_vu = x_deja_vu ? deja_vu : 0.f;
+ x.length = deja_vu_length;
+ x.ratio.p = 1;
+ x.ratio.q = 1;
+ x.scale_index = x_scale;
+
+ marbles::GroupSettings y;
+ y.control_mode = marbles::CONTROL_MODE_IDENTICAL;
+ // TODO
+ y.voltage_range = (marbles::VoltageRange) x_range;
+ y.register_mode = false;
+ y.register_value = 0.0f;
+ // TODO
+ y.spread = x_spread;
+ y.bias = x_bias;
+ y.steps = x_steps;
+ y.deja_vu = 0.0f;
+ y.length = 1;
+ static const marbles::Ratio y_divider_ratios[] = {
+ { 1, 64 },
+ { 1, 48 },
+ { 1, 32 },
+ { 1, 24 },
+ { 1, 16 },
+ { 1, 12 },
+ { 1, 8 },
+ { 1, 6 },
+ { 1, 4 },
+ { 1, 3 },
+ { 1, 2 },
+ { 1, 1 },
+ };
+ y.ratio = y_divider_ratios[y_divider_index];
+ y.scale_index = x_scale;
+
+ xy_generator.Process(x_clock_source, x, y, xy_clocks, ramps, voltages, BLOCK_SIZE);
+ }
+};
+
+
+template
+struct CKD6Light : BASE {
+ CKD6Light() {
+ this->box.size = Vec(22, 22);
+ }
+};
+
+
+struct MarblesWidget : ModuleWidget {
+ MarblesWidget(Marbles *module) : ModuleWidget(module) {
+ setPanel(SVG::load(assetPlugin(plugin, "res/Marbles.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(16.545, 17.794)), module, Marbles::T_DEJA_VU_PARAM, 0.0, 1.0, 0.0));
+ addParam(createParamCentered(mm2px(Vec(74.845, 17.794)), module, Marbles::X_DEJA_VU_PARAM, 0.0, 1.0, 0.0));
+ addParam(createParamCentered(mm2px(Vec(45.695, 22.244)), module, Marbles::DEJA_VU_PARAM, 0.0, 1.0, 0.5));
+ addParam(createParamCentered(mm2px(Vec(23.467, 35.264)), module, Marbles::T_RATE_PARAM, -1.0, 1.0, 0.0));
+ addParam(createParamCentered(mm2px(Vec(67.945, 35.243)), module, Marbles::X_SPREAD_PARAM, 0.0, 1.0, 0.5));
+ addParam(createParamCentered(mm2px(Vec(6.945, 38.794)), module, Marbles::T_MODE_PARAM, 0.0, 1.0, 0.0));
+ addParam(createParamCentered(mm2px(Vec(84.445, 38.793)), module, Marbles::X_MODE_PARAM, 0.0, 1.0, 0.0));
+ addParam(createParamCentered(mm2px(Vec(45.695, 51.144)), module, Marbles::DEJA_VU_LENGTH_PARAM, 0.0, 1.0, 0.0));
+ addParam(createParamCentered(mm2px(Vec(9.545, 58.394)), module, Marbles::T_BIAS_PARAM, 0.0, 1.0, 0.5));
+ addParam(createParamCentered(mm2px(Vec(81.844, 58.394)), module, Marbles::X_BIAS_PARAM, 0.0, 1.0, 0.5));
+ addParam(createParamCentered(mm2px(Vec(26.644, 59.694)), module, Marbles::T_RANGE_PARAM, 0.0, 1.0, 0.0));
+ addParam(createParamCentered(mm2px(Vec(64.744, 59.694)), module, Marbles::X_RANGE_PARAM, 0.0, 1.0, 0.0));
+ addParam(createParamCentered(mm2px(Vec(45.694, 67.294)), module, Marbles::EXTERNAL_PARAM, 0.0, 1.0, 0.0));
+ addParam(createParamCentered(mm2px(Vec(31.544, 73.694)), module, Marbles::T_JITTER_PARAM, 0.0, 1.0, 0.0));
+ addParam(createParamCentered(mm2px(Vec(59.845, 73.694)), module, Marbles::X_STEPS_PARAM, 0.0, 1.0, 0.5));
+
+ addInput(createInputCentered(mm2px(Vec(9.545, 81.944)), module, Marbles::T_BIAS_INPUT));
+ addInput(createInputCentered(mm2px(Vec(81.844, 81.944)), module, Marbles::X_BIAS_INPUT));
+ addInput(createInputCentered(mm2px(Vec(9.545, 96.544)), module, Marbles::T_CLOCK_INPUT));
+ addInput(createInputCentered(mm2px(Vec(21.595, 96.544)), module, Marbles::T_RATE_INPUT));
+ addInput(createInputCentered(mm2px(Vec(33.644, 96.544)), module, Marbles::T_JITTER_INPUT));
+ addInput(createInputCentered(mm2px(Vec(45.695, 96.544)), module, Marbles::DEJA_VU_INPUT));
+ addInput(createInputCentered(mm2px(Vec(57.745, 96.544)), module, Marbles::X_STEPS_INPUT));
+ addInput(createInputCentered(mm2px(Vec(69.795, 96.544)), module, Marbles::X_SPREAD_INPUT));
+ addInput(createInputCentered(mm2px(Vec(81.844, 96.544)), module, Marbles::X_CLOCK_INPUT));
+
+ addOutput(createOutputCentered(mm2px(Vec(9.545, 111.144)), module, Marbles::T1_OUTPUT));
+ addOutput(createOutputCentered(mm2px(Vec(21.595, 111.144)), module, Marbles::T2_OUTPUT));
+ addOutput(createOutputCentered(mm2px(Vec(33.644, 111.144)), module, Marbles::T3_OUTPUT));
+ addOutput(createOutputCentered(mm2px(Vec(45.695, 111.144)), module, Marbles::Y_OUTPUT));
+ addOutput(createOutputCentered(mm2px(Vec(57.745, 111.144)), module, Marbles::X1_OUTPUT));
+ addOutput(createOutputCentered(mm2px(Vec(69.795, 111.144)), module, Marbles::X2_OUTPUT));
+ addOutput(createOutputCentered(mm2px(Vec(81.844, 111.144)), module, Marbles::X3_OUTPUT));
+
+ addChild(createLightCentered>(mm2px(Vec(16.545, 17.794)), module, Marbles::T_DEJA_VU_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(74.845, 17.794)), module, Marbles::X_DEJA_VU_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(6.944, 29.894)), module, Marbles::T_MODE_LIGHTS));
+ addChild(createLightCentered>(mm2px(Vec(84.444, 29.894)), module, Marbles::X_MODE_LIGHTS));
+ addChild(createLightCentered>(mm2px(Vec(26.644, 53.994)), module, Marbles::T_RANGE_LIGHTS));
+ addChild(createLightCentered>(mm2px(Vec(64.744, 53.994)), module, Marbles::X_RANGE_LIGHTS));
+ addChild(createLightCentered>(mm2px(Vec(45.695, 76.194)), module, Marbles::EXTERNAL_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(6.044, 104.794)), module, Marbles::T1_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(18.094, 104.794)), module, Marbles::T2_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(30.145, 104.794)), module, Marbles::T3_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(42.194, 104.794)), module, Marbles::Y_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(54.244, 104.794)), module, Marbles::X1_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(66.294, 104.794)), module, Marbles::X2_LIGHT));
+ addChild(createLightCentered>(mm2px(Vec(78.344, 104.794)), module, Marbles::X3_LIGHT));
+ }
+
+ void appendContextMenu(Menu *menu) override {
+ Marbles *module = dynamic_cast(this->module);
+
+ struct ScaleItem : MenuItem {
+ Marbles *module;
+ int scale;
+ void onAction(EventAction &e) override {
+ module->x_scale = scale;
+ }
+ };
+
+ menu->addChild(MenuEntry::create());
+ menu->addChild(MenuLabel::create("Scales"));
+ const std::string scaleLabels[] = {
+ "Major",
+ "Minor",
+ "Pentatonic",
+ "Pelog",
+ "Raag Bhairav That",
+ "Raag Shri",
+ };
+ for (int i = 0; i < (int) LENGTHOF(scaleLabels); i++) {
+ ScaleItem *item = MenuItem::create(scaleLabels[i], CHECKMARK(module->x_scale == i));
+ item->module = module;
+ item->scale = i;
+ menu->addChild(item);
+ }
+
+ struct XClockSourceInternal : MenuItem {
+ Marbles *module;
+ int source;
+ void onAction(EventAction &e) override {
+ module->x_clock_source_internal = source;
+ }
+ };
+
+ menu->addChild(MenuEntry::create());
+ menu->addChild(MenuLabel::create("Internal X clock source"));
+ const std::string sourceLabels[] = {
+ "T₁ → X₁, T₂ → X₂, T₃ → X₃",
+ "T₁ → X₁, X₂, X₃",
+ "T₂ → X₁, X₂, X₃",
+ "T₃ → X₁, X₂, X₃",
+ };
+ for (int i = 0; i < (int) LENGTHOF(sourceLabels); i++) {
+ XClockSourceInternal *item = MenuItem::create(sourceLabels[i], CHECKMARK(module->x_clock_source_internal == i));
+ item->module = module;
+ item->source = i;
+ menu->addChild(item);
+ }
+
+ struct YDividerIndexItem : MenuItem {
+ Marbles *module;
+ int index;
+ void onAction(EventAction &e) override {
+ module->y_divider_index = index;
+ }
+ };
+
+ struct YDividerItem : MenuItem {
+ Marbles *module;
+ Menu *createChildMenu() override {
+ Menu *menu = new Menu();
+ const std::string yDividerRatioLabels[] = {
+ "1/64",
+ "1/48",
+ "1/32",
+ "1/24",
+ "1/16",
+ "1/12",
+ "1/8",
+ "1/6",
+ "1/4",
+ "1/3",
+ "1/2",
+ "1",
+ };
+ for (int i = 0; i < (int) LENGTHOF(yDividerRatioLabels); i++) {
+ YDividerIndexItem *item = MenuItem::create(yDividerRatioLabels[i], CHECKMARK(module->y_divider_index == i));
+ item->module = module;
+ item->index = i;
+ menu->addChild(item);
+ }
+ return menu;
+ }
+ };
+
+ menu->addChild(MenuEntry::create());
+ YDividerItem *yDividerItem = MenuItem::create("Y divider ratio");
+ yDividerItem->module = module;
+ menu->addChild(yDividerItem);
+ }
+};
+
+
+Model *modelMarbles = createModel("Audible Instruments", "Marbles", "Random Sampler");
+