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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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"); +