#include "plugin.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 dsp::BooleanTrigger tDejaVuTrigger; dsp::BooleanTrigger xDejaVuTrigger; dsp::BooleanTrigger tModeTrigger; dsp::BooleanTrigger xModeTrigger; dsp::BooleanTrigger tRangeTrigger; dsp::BooleanTrigger xRangeTrigger; dsp::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() { config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); configParam(T_DEJA_VU_PARAM, 0.0, 1.0, 0.0, "t deja vu"); configParam(X_DEJA_VU_PARAM, 0.0, 1.0, 0.0, "X deja vu"); configParam(DEJA_VU_PARAM, 0.0, 1.0, 0.5, "Deja vu probability"); configParam(T_RATE_PARAM, -1.0, 1.0, 0.0, "Clock rate"); configParam(X_SPREAD_PARAM, 0.0, 1.0, 0.5, "Probability distribution"); configParam(T_MODE_PARAM, 0.0, 1.0, 0.0, "t mode"); configParam(X_MODE_PARAM, 0.0, 1.0, 0.0, "X mode"); configParam(DEJA_VU_LENGTH_PARAM, 0.0, 1.0, 0.0, "Loop length"); configParam(T_BIAS_PARAM, 0.0, 1.0, 0.5, "Gate bias"); configParam(X_BIAS_PARAM, 0.0, 1.0, 0.5, "Distribution bias"); configParam(T_RANGE_PARAM, 0.0, 1.0, 0.0, "Clock range mode"); configParam(X_RANGE_PARAM, 0.0, 1.0, 0.0, "Output voltage range mode"); configParam(EXTERNAL_PARAM, 0.0, 1.0, 0.0, "External processing mode"); configParam(T_JITTER_PARAM, 0.0, 1.0, 0.0, "Randomness amount"); configParam(X_STEPS_PARAM, 0.0, 1.0, 0.5, "Smoothness"); 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 = random::u32() % 3; x_mode = random::u32() % 3; t_range = random::u32() % 3; x_range = random::u32() % 3; } void onSampleRateChange() override { float sampleRate = APP->engine->getSampleRate(); 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 *dataToJson() 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 dataFromJson(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 process(const ProcessArgs &args) override { // Buttons if (tDejaVuTrigger.process(params[T_DEJA_VU_PARAM].getValue() <= 0.f)) { t_deja_vu = !t_deja_vu; } if (xDejaVuTrigger.process(params[X_DEJA_VU_PARAM].getValue() <= 0.f)) { x_deja_vu = !x_deja_vu; } if (tModeTrigger.process(params[T_MODE_PARAM].getValue() <= 0.f)) { t_mode = (t_mode + 1) % 3; } if (xModeTrigger.process(params[X_MODE_PARAM].getValue() <= 0.f)) { x_mode = (x_mode + 1) % 3; } if (tRangeTrigger.process(params[T_RANGE_PARAM].getValue() <= 0.f)) { t_range = (t_range + 1) % 3; } if (xRangeTrigger.process(params[X_RANGE_PARAM].getValue() <= 0.f)) { x_range = (x_range + 1) % 3; } if (externalTrigger.process(params[EXTERNAL_PARAM].getValue() <= 0.f)) { external = !external; } // Clocks bool t_gate = (inputs[T_CLOCK_INPUT].getVoltage() >= 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].getVoltage() >= 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].setVoltage(gates[blockIndex*2 + 0] ? 10.f : 0.f); lights[T1_LIGHT].setSmoothBrightness(gates[blockIndex*2 + 0], args.sampleTime); outputs[T2_OUTPUT].setVoltage((ramp_master[blockIndex] < 0.5f) ? 10.f : 0.f); lights[T2_LIGHT].setSmoothBrightness(ramp_master[blockIndex] < 0.5f, args.sampleTime); outputs[T3_OUTPUT].setVoltage(gates[blockIndex*2 + 1] ? 10.f : 0.f); lights[T3_LIGHT].setSmoothBrightness(gates[blockIndex*2 + 1], args.sampleTime); outputs[X1_OUTPUT].setVoltage(voltages[blockIndex*4 + 0]); lights[X1_LIGHT].setSmoothBrightness(voltages[blockIndex*4 + 0], args.sampleTime); outputs[X2_OUTPUT].setVoltage(voltages[blockIndex*4 + 1]); lights[X2_LIGHT].setSmoothBrightness(voltages[blockIndex*4 + 1], args.sampleTime); outputs[X3_OUTPUT].setVoltage(voltages[blockIndex*4 + 2]); lights[X3_LIGHT].setSmoothBrightness(voltages[blockIndex*4 + 2], args.sampleTime); outputs[Y_OUTPUT].setVoltage(voltages[blockIndex*4 + 3]); lights[Y_LIGHT].setSmoothBrightness(voltages[blockIndex*4 + 3], args.sampleTime); } 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].getValue() + inputs[DEJA_VU_INPUT].getVoltage() / 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].getValue() * (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].isConnected(); 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].getValue() + inputs[T_RATE_INPUT].getVoltage() / 5.f); t_generator.set_rate(t_rate); float t_bias = clamp(params[T_BIAS_PARAM].getValue() + inputs[T_BIAS_INPUT].getVoltage() / 5.f, 0.f, 1.f); t_generator.set_bias(t_bias); float t_jitter = clamp(params[T_JITTER_PARAM].getValue() + inputs[T_JITTER_INPUT].getVoltage() / 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].isConnected()) 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].getValue() + inputs[X_SPREAD_INPUT].getVoltage() / 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].getValue() + inputs[X_SPREAD_INPUT].getVoltage() / 5.f, 0.f, 1.f); x.spread = x_spread; float x_bias = clamp(params[X_BIAS_PARAM].getValue() + inputs[X_BIAS_INPUT].getVoltage() / 5.f, 0.f, 1.f); x.bias = x_bias; float x_steps = clamp(params[X_STEPS_PARAM].getValue() + inputs[X_STEPS_INPUT].getVoltage() / 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) { setModule(module); setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "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)); addParam(createParamCentered(mm2px(Vec(74.845, 17.794)), module, Marbles::X_DEJA_VU_PARAM)); addParam(createParamCentered(mm2px(Vec(45.695, 22.244)), module, Marbles::DEJA_VU_PARAM)); addParam(createParamCentered(mm2px(Vec(23.467, 35.264)), module, Marbles::T_RATE_PARAM)); addParam(createParamCentered(mm2px(Vec(67.945, 35.243)), module, Marbles::X_SPREAD_PARAM)); addParam(createParamCentered(mm2px(Vec(6.945, 38.794)), module, Marbles::T_MODE_PARAM)); addParam(createParamCentered(mm2px(Vec(84.445, 38.793)), module, Marbles::X_MODE_PARAM)); addParam(createParamCentered(mm2px(Vec(45.695, 51.144)), module, Marbles::DEJA_VU_LENGTH_PARAM)); addParam(createParamCentered(mm2px(Vec(9.545, 58.394)), module, Marbles::T_BIAS_PARAM)); addParam(createParamCentered(mm2px(Vec(81.844, 58.394)), module, Marbles::X_BIAS_PARAM)); addParam(createParamCentered(mm2px(Vec(26.644, 59.694)), module, Marbles::T_RANGE_PARAM)); addParam(createParamCentered(mm2px(Vec(64.744, 59.694)), module, Marbles::X_RANGE_PARAM)); addParam(createParamCentered(mm2px(Vec(45.694, 67.294)), module, Marbles::EXTERNAL_PARAM)); addParam(createParamCentered(mm2px(Vec(31.544, 73.694)), module, Marbles::T_JITTER_PARAM)); addParam(createParamCentered(mm2px(Vec(59.845, 73.694)), module, Marbles::X_STEPS_PARAM)); 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(const event::Action &e) override { module->x_scale = scale; } }; menu->addChild(new MenuEntry); menu->addChild(createMenuLabel("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 = createMenuItem(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(const event::Action &e) override { module->x_clock_source_internal = source; } }; menu->addChild(new MenuEntry); menu->addChild(createMenuLabel("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 = createMenuItem(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(const event::Action &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 = createMenuItem(yDividerRatioLabels[i], CHECKMARK(module->y_divider_index == i)); item->module = module; item->index = i; menu->addChild(item); } return menu; } }; menu->addChild(new MenuEntry); YDividerItem *yDividerItem = createMenuItem("Y divider ratio"); yDividerItem->module = module; menu->addChild(yDividerItem); } }; Model *modelMarbles = createModel("Marbles");