computerscare-vcv-modules

ComputerScare modules for VCV Rack
Log | Files | Refs

ComputerscarePatchSequencer.cpp (23165B)


      1 #include "Computerscare.hpp"
      2 
      3 #include <string>
      4 #include <sstream>
      5 #include <iomanip>
      6 
      7 const int maxSteps = 16;
      8 const int numInputs = 10;
      9 const int numOutputs = 10;
     10 
     11 struct ComputerscareDebug;
     12 
     13 struct ComputerscarePatchSequencer : Module {
     14   enum ParamIds {
     15     STEPS_PARAM,
     16     MANUAL_CLOCK_PARAM,
     17     EDIT_PARAM,
     18     EDIT_PREV_PARAM,
     19     ENUMS(SWITCHES, 100),
     20     RESET_PARAM,
     21     NUM_PARAMS
     22   };
     23   enum InputIds {
     24     TRG_INPUT,
     25     ENUMS(INPUT_JACKS, 10),
     26     RANDOMIZE_INPUT,
     27     RESET_INPUT,
     28     NUM_INPUTS
     29   };
     30   enum OutputIds {
     31     OUTPUTS,
     32     NUM_OUTPUTS = OUTPUTS + 10
     33   };
     34   enum LightIds {
     35     SWITCH_LIGHTS,
     36     NUM_LIGHTS = SWITCH_LIGHTS + 200
     37   };
     38 
     39   rack::dsp::SchmittTrigger switch_triggers[10][10];
     40 
     41   rack::dsp::SchmittTrigger nextAddressRead;
     42   rack::dsp::SchmittTrigger nextAddressEdit;
     43   rack::dsp::SchmittTrigger prevAddressEdit;
     44   rack::dsp::SchmittTrigger clockTrigger;
     45   rack::dsp::SchmittTrigger randomizeTrigger;
     46   rack::dsp::SchmittTrigger resetTriggerInput;
     47   rack::dsp::SchmittTrigger resetTriggerButton;
     48 
     49   int address = 0;
     50   int editAddress = 0;
     51   int addressPlusOne = 1;
     52   int editAddressPlusOne = 1;
     53   int counter = 513;
     54 
     55   int numAddresses = 2;
     56   bool switch_states[maxSteps][10][10] = {};
     57 
     58   bool onlyRandomizeActive = true;
     59 
     60   float input_values[numInputs * 16] = {0.0};
     61   float sums[numOutputs * 16] = {0.0};
     62 
     63   int randomizationStepEnum = 0; //0: edit step, 1: active step, 2: all steps
     64   int randomizationOutputBoundsEnum = 1; //0: randomize exactly one per output, 1: randomize exactly one per output, 2: randomize 1 or more, 3: randomize 0 or more
     65 
     66   int channelCount[numOutputs];
     67   int channelCountEnum = -1;
     68 
     69   ComputerscarePatchSequencer() {
     70     config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
     71     configParam(STEPS_PARAM, 1.f, 16.f, 2.0f, "Number of Steps");
     72     for (int i = 0; i < numOutputs; i++) {
     73       channelCount[i] = 0;
     74       configInput(INPUT_JACKS + i, "Row " + std::to_string(i + 1));
     75       configOutput(OUTPUTS + i, "Column " + std::to_string(i + 1));
     76     }
     77 
     78     for (int inRow = 0; inRow < numInputs; inRow++) {
     79       for (int outCol = 0; outCol < numOutputs; outCol++) {
     80         configButton(SWITCHES + outCol * numInputs + inRow, "Toggle Input Row " + std::to_string(inRow + 1) + ",Output Column " + std::to_string(outCol + 1));
     81       }
     82     }
     83     getParamQuantity(STEPS_PARAM)->randomizeEnabled = false;
     84 
     85     configButton(MANUAL_CLOCK_PARAM, "Manual Scene Advance");
     86     configButton(RESET_PARAM, "Reset To Scene 1");
     87 
     88     configButton(EDIT_PARAM, "Edit Next Scene");
     89     configButton(EDIT_PREV_PARAM, "Edit Previous Scene");
     90 
     91     configInput(TRG_INPUT, "Clock");
     92     configInput(RESET_INPUT, "Reset Trigger");
     93     configInput(RANDOMIZE_INPUT, "Randomize Trigger");
     94 
     95   }
     96   void process(const ProcessArgs &args) override;
     97 
     98 
     99   void updateChannelCount() {
    100     int currentMax;
    101 
    102     for (int j = 0; j < numOutputs; j++) {
    103       if (channelCountEnum == -1) {
    104         currentMax = 0;
    105         for (int i = 0; i < numInputs; i++) {
    106           if (switch_states[address][i][j] && inputs[INPUT_JACKS + i].isConnected()) {
    107             currentMax = std::max(currentMax, inputs[INPUT_JACKS + i].getChannels());
    108           }
    109         }
    110       }
    111       else {
    112         currentMax = channelCountEnum;
    113       }
    114       channelCount[j] = currentMax;
    115       outputs[OUTPUTS + j].setChannels(currentMax);
    116     }
    117   }
    118 
    119   int getRandomizationStepEnum() {
    120     return randomizationStepEnum;
    121   }
    122 
    123   int getRandomizationOutputBoundsEnum() {
    124     return randomizationOutputBoundsEnum;
    125   }
    126 
    127   void setRandomizationStepEnum(int randomizationStep) {
    128     randomizationStepEnum = randomizationStep;
    129   }
    130   void setRandomizationOutputBoundsEnum(int randomizationOutputBounds) {
    131     randomizationOutputBoundsEnum = randomizationOutputBounds;
    132   }
    133 
    134   void onRandomize() override {
    135     randomizePatchMatrix();
    136   }
    137 
    138   void randomizePatchMatrix()
    139   {
    140     if (onlyRandomizeActive) {
    141       randomizeMatrixOnlyActive();
    142     }
    143     else {
    144       randomizeMatrixIncludingDisconnected();
    145     }
    146   };
    147 
    148 
    149   // For more advanced Module features, read Rack's engine.hpp header file
    150   // - toJson, fromJson: serialization of internal data
    151   // - onSampleRateChange: event triggered by a change of sample rate
    152   // - onReset, onRandomize, onCreate, onDelete: implements special behavior when user clicks these from the context menu
    153 
    154   void randomizeMatrixOnlyActive() {
    155     int randomIndex;
    156 
    157     bool connectedInputs[10];
    158     bool connectedOutputs[10];
    159     int numConnectedInputs = 0;
    160 
    161     std::vector<int> connectedInputIndices;
    162 
    163     for (int i = 0; i < 10; i++)
    164     {
    165       if (inputs[INPUT_JACKS + i].isConnected()) {
    166         numConnectedInputs++;
    167         connectedInputIndices.push_back(i);
    168       }
    169 
    170       connectedInputs[i] = inputs[INPUT_JACKS + i].isConnected();
    171       connectedOutputs[i] = outputs[OUTPUTS + i].isConnected();
    172     }
    173     for (int k = 0; k < maxSteps; k++) {
    174       if ((randomizationStepEnum == 0 && k == editAddress) || (randomizationStepEnum == 1 && k == address) || randomizationStepEnum == 2) {
    175         for (int i = 0; i < 10; i++) {
    176           randomIndex = numConnectedInputs > 0 ? connectedInputIndices[floor(random::uniform() * numConnectedInputs)] : 0;
    177           if (connectedOutputs[i]) {
    178             for (int j = 0; j < 10; j++) {
    179               if (j == randomIndex)
    180                 switch_states[k][j][i] = 1;
    181               else
    182                 switch_states[k][j][i] = 0;
    183             }
    184           }
    185         }
    186       }
    187     }
    188 
    189   }
    190 
    191   void randomizeMatrixIncludingDisconnected() {
    192     int randomIndex;
    193     for (int k = 0; k < maxSteps; k++) {
    194       if ((randomizationStepEnum == 0 && k == editAddress) || (randomizationStepEnum == 1 && k == address) || randomizationStepEnum == 2) {
    195         for (int i = 0; i < 10; i++)
    196         {
    197           randomIndex = floor(random::uniform() * 10);
    198 
    199           for (int j = 0; j < 10; j++)
    200           {
    201             if (randomizationOutputBoundsEnum == 3) {
    202               switch_states[k][j][i] = (j == randomIndex || random::uniform() < 0.2) ? 1 : 0;
    203             }
    204             else if (randomizationOutputBoundsEnum == 2) {
    205               switch_states[k][j][i] = random::uniform() < 0.2 ? 1 : 0;
    206             }
    207             else if (randomizationOutputBoundsEnum == 0) {
    208               switch_states[k][j][i] = (j == randomIndex && random::uniform() < 0.7) ? 1 : 0;
    209             }
    210             else {
    211               switch_states[k][j][i] = j == randomIndex ? 1 : 0;
    212             }
    213           }
    214         }
    215       }
    216     }
    217 
    218   }
    219 
    220   void onReset() override
    221   {
    222     for (int k = 0; k < maxSteps; k++) {
    223 
    224 
    225       for (int i = 0; i < 10; i++)
    226       {
    227         for (int j = 0; j < 10; j++)
    228         {
    229           switch_states[k][i][j] =  0;
    230         }
    231       }
    232     }
    233   }; // end randomize()
    234 
    235 
    236   void dataFromJson(json_t *rootJ) override {
    237     // button states
    238     json_t *button_statesJ = json_object_get(rootJ, "buttons");
    239     if (button_statesJ)
    240     {
    241       for (int k = 0; k < maxSteps; k++) {
    242 
    243         for (int i = 0; i < 10; i++) {
    244           for (int j = 0; j < 10; j++) {
    245             json_t *button_stateJ = json_array_get(button_statesJ, k * 100 + i * 10 + j);
    246             if (button_stateJ)
    247               switch_states[k][i][j] = !!json_integer_value(button_stateJ);
    248           }
    249         }
    250       }
    251     }
    252     json_t *onlyRandomizeActiveJ = json_object_get(rootJ, "onlyRandomizeActive");
    253     if (onlyRandomizeActiveJ) { onlyRandomizeActive = json_is_true(onlyRandomizeActiveJ); }
    254 
    255     json_t *randomizationStepEnumJ = json_object_get(rootJ, "randomizationStepEnum");
    256     if (randomizationStepEnumJ) { setRandomizationStepEnum(json_integer_value(randomizationStepEnumJ)); }
    257 
    258     json_t *channelCountEnumJ = json_object_get(rootJ, "channelCountEnum");
    259     if (channelCountEnumJ) { channelCountEnum = json_integer_value(channelCountEnumJ); }
    260 
    261     json_t *randomizationOutputBoundsEnumJ = json_object_get(rootJ, "randomizationOutputBoundsEnum");
    262     if (randomizationOutputBoundsEnumJ) { setRandomizationOutputBoundsEnum(json_integer_value(randomizationOutputBoundsEnumJ)); }
    263 
    264   }
    265   json_t *dataToJson() override
    266   {
    267 
    268     json_t *rootJ = json_object();
    269     // button states
    270     json_t *button_statesJ = json_array();
    271     for (int k = 0; k < maxSteps; k++) {
    272       for (int i = 0; i < 10; i++)
    273       {
    274         for (int j = 0; j < 10; j++)
    275         {
    276           json_t *button_stateJ = json_integer((int) switch_states[k][i][j]);
    277           json_array_append_new(button_statesJ, button_stateJ);
    278         }
    279       }
    280     }
    281     json_object_set_new(rootJ, "buttons", button_statesJ);
    282     json_object_set_new(rootJ, "onlyRandomizeActive", json_boolean(onlyRandomizeActive));
    283     json_object_set_new(rootJ, "channelCountEnum", json_integer(channelCountEnum));
    284     json_object_set_new(rootJ, "randomizationStepEnum", json_integer(getRandomizationStepEnum()));
    285     json_object_set_new(rootJ, "randomizationOutputBoundsEnum", json_integer(getRandomizationOutputBoundsEnum()));
    286     return rootJ;
    287   }
    288 };
    289 
    290 
    291 void ComputerscarePatchSequencer::process(const ProcessArgs &args) {
    292 
    293   int numStepsKnobPosition = (int) clamp(roundf(params[STEPS_PARAM].getValue()), 1.0f, 16.0f);
    294   //int channels[10] = {0};
    295 
    296   for ( int j = 0 ; j < 10 ; j++)
    297   {
    298     //channels[i] = inputs[INPUT_JACKS + i].getChannels();
    299     for (int c = 0; c < 16; c++) {
    300       sums[j * 16 + c] = 0.0;
    301     }
    302 
    303 
    304   }
    305 
    306   for (int i = 0 ; i < 10 ; i++)
    307   {
    308     for (int j = 0 ; j < 10 ; j++)
    309     {
    310       if (switch_triggers[i][j].process(params[SWITCHES + j * 10 + i].getValue()))
    311       {
    312         // handle button clicks in the patch matrix
    313         switch_states[editAddress][i][j] = !switch_states[editAddress][i][j];
    314       }
    315 
    316 
    317     }
    318   }
    319   if (counter > 512) {
    320     updateChannelCount();
    321     for (int i = 0 ; i < 10 ; i++)
    322     {
    323       for (int j = 0 ; j < 10 ; j++)
    324       {
    325 
    326         // update the green lights (step you are editing) and the red lights (current active step)
    327         lights[SWITCH_LIGHTS + i + j * 10].value  = (switch_states[editAddress][i][j]) ? 1.0 : 0.0;
    328         lights[SWITCH_LIGHTS + i + j * 10 + 100].value  = (switch_states[address][i][j]) ? 1.0 : 0.0;
    329       }
    330     }
    331     counter = 0;
    332   }
    333   counter++;
    334 
    335 
    336   if (numStepsKnobPosition != numAddresses) {
    337     numAddresses = numStepsKnobPosition;
    338   }
    339 
    340   if (randomizeTrigger.process(inputs[RANDOMIZE_INPUT].getVoltage() / 2.f)) {
    341     randomizePatchMatrix();
    342   }
    343   if (nextAddressEdit.process(params[EDIT_PARAM].getValue()) ) {
    344     editAddress = editAddress + 1;
    345     editAddress = editAddress % maxSteps;
    346   }
    347   if (prevAddressEdit.process(params[EDIT_PREV_PARAM].getValue()) ) {
    348     editAddress = editAddress - 1;
    349     editAddress = editAddress + maxSteps;
    350     editAddress = editAddress % maxSteps;
    351   }
    352 
    353   if (nextAddressRead.process(params[MANUAL_CLOCK_PARAM].getValue()) || clockTrigger.process(inputs[TRG_INPUT].getVoltage() / 2.f)) {
    354     numAddresses =  (int) clamp(roundf(params[STEPS_PARAM].getValue() /*+ inputs[STEPS_INPUT].getVoltage()*/), 1.0f, 16.0f);
    355 
    356     address = address + 1;
    357     address = address % numAddresses;
    358   }
    359 
    360   if (resetTriggerButton.process(params[RESET_PARAM].getValue()) || resetTriggerInput.process(inputs[RESET_INPUT].getVoltage() / 2.f)) {
    361     numAddresses =  (int) clamp(roundf(params[STEPS_PARAM].getValue()), 1.0f, 16.0f);
    362 
    363     address = 0;
    364   }
    365 
    366   addressPlusOne = address + 1;
    367   editAddressPlusOne = editAddress + 1;
    368 
    369   for (int i = 0 ; i < 10 ; i++)
    370   {
    371     for (int c = 0; c < 16; c++) {
    372       input_values[i * 16 + c] = inputs[INPUT_JACKS + i].getVoltage(c);
    373     }
    374   }
    375 
    376   for (int i = 0 ; i < 10 ; i++)
    377   {
    378     for (int j = 0 ; j < 10 ; j++)
    379     {
    380       // todo: toggle for each output of how to combine multiple active signals in a column
    381       // sum, average, and, or etc
    382       if (switch_states[address][i][j]) {
    383         for (int c = 0; c < channelCount[j]; c++) {
    384           sums[j * 16 + c] += input_values[i * 16 + c];
    385         }
    386 
    387       }
    388     }
    389   }
    390   /// outputs
    391   for (int j = 0 ; j < 10 ; j++)
    392   {
    393     //outputs[OUTPUTS + j].setChannels(16);
    394     for (int c = 0; c < channelCount[j]; c++) {
    395       outputs[OUTPUTS + j].setVoltage(sums[j * 16 + c], c);
    396     }
    397   }
    398 }
    399 
    400 ////////////////////////////////////
    401 struct NumberDisplayWidget3 : TransparentWidget {
    402 
    403   int *value;
    404   ComputerscarePatchSequencer *module;
    405   std::string fontPath = "res/Segment7Standard.ttf";
    406 
    407   NumberDisplayWidget3() {
    408 
    409   };
    410 
    411   void draw(const DrawArgs &args) override
    412   {
    413     // Background
    414     NVGcolor backgroundColor = nvgRGB(0x00, 0x00, 0x00);
    415 
    416     nvgBeginPath(args.vg);
    417     nvgRoundedRect(args.vg, 0.0, 0.0, box.size.x, box.size.y, 4.0);
    418     nvgFillColor(args.vg, backgroundColor);
    419     nvgFill(args.vg);
    420 
    421   }
    422   void drawLayer(const BGPanel::DrawArgs& args, int layer) override {
    423     if (layer == 1) {
    424       drawText(args);
    425     }
    426     Widget::drawLayer(args, layer);
    427   }
    428   void drawText(const BGPanel::DrawArgs& args) {
    429     std::shared_ptr<Font> font = APP->window->loadFont(asset::plugin(pluginInstance, fontPath));
    430     if (font) {
    431       // text
    432       nvgFontSize(args.vg, 18);
    433       nvgFontFaceId(args.vg, font->handle);
    434       nvgTextLetterSpacing(args.vg, 2.5);
    435 
    436       std::stringstream to_display;
    437       if (module) {
    438         to_display << std::setw(3) << *value;
    439       }
    440       else {
    441         to_display << std::setw(3) << "16";
    442       }
    443 
    444       Vec textPos = Vec(6.0f, 17.0f);
    445       NVGcolor textColor = nvgRGB(0xC0, 0xE7, 0xDE);
    446       nvgFillColor(args.vg, textColor);
    447       nvgText(args.vg, textPos.x, textPos.y, to_display.str().c_str(), NULL);
    448     }
    449   }
    450 };
    451 
    452 
    453 
    454 struct ComputerscarePatchSequencerWidget : ModuleWidget {
    455 
    456   ComputerscarePatchSequencerWidget(ComputerscarePatchSequencer *module) {
    457     setModule(module);
    458     setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/ComputerscarePatchSequencerPanel.svg")));
    459 
    460     int top_row = 70;
    461     int row_spacing = 26;
    462     int column_spacing = 26;
    463 
    464     int rdx = rand() % 8;
    465     int rdy  = rand() % 8;
    466 
    467     for (int i = 0 ; i < 10 ; i++)
    468     {
    469 
    470 
    471       for (int j = 0 ; j < 10 ; j++ )
    472       {
    473         // the part you click
    474         addParam(createParam<LEDButton>(Vec(35 + column_spacing * j + 2, top_row + row_spacing * i + 4), module, ComputerscarePatchSequencer::SWITCHES + i + j * 10));
    475 
    476 
    477 
    478         // green light indicates the state of the matrix that is being edited
    479         //ModuleLightWidget *bigOne = ModuleLightWidget::create<ComputerscareHugeLight<ComputerscareGreenLight>>(Vec(35 + column_spacing * j +0.4, top_row + row_spacing * i +2.4 ), module, ComputerscarePatchSequencer::SWITCH_LIGHTS  + i + j * 10);
    480         addChild(createLight<ComputerscareHugeLight<ComputerscareGreenLight>>(Vec(35 + column_spacing * j + 0.4, top_row + row_spacing * i + 2.4 ), module, ComputerscarePatchSequencer::SWITCH_LIGHTS  + i + j * 10));
    481 
    482 
    483 
    484         //addParam(createParam<LEDButton>(Vec(35 + column_spacing * j+2, top_row + row_spacing * i+4), module, ComputerscarePatchSequencer::SWITCHES + i + j * 10));
    485 
    486 
    487 
    488         //addChild(bigOne);
    489 
    490         double xpos = 35 + column_spacing * j + 6.3 + rand() % 8 - 4;
    491         double ypos = top_row + row_spacing * i + 8.3 + rand() % 8 - 4;
    492         // red light indicates the state of the matrix that is the active step
    493         //computerscarered
    494         //addParam(createParam<ComputerscareSmallLight>(Vec(xpos, ypos), module, ComputerscarePatchSequencer::SWITCH_LIGHTS  + i + j * 10 + 100));
    495         addChild(createLight<ComputerscareSmallLight<ComputerscareRedLight>>(Vec(xpos - rdy, ypos + rdx), module, ComputerscarePatchSequencer::SWITCH_LIGHTS  + i + j * 10 + 100));
    496 
    497         addChild(createLight<ComputerscareSmallLight<ComputerscareRedLight>>(Vec(xpos + rdx, ypos + rdy), module, ComputerscarePatchSequencer::SWITCH_LIGHTS  + i + j * 10 + 100));
    498 
    499       }
    500 
    501       addInput(createInput<InPort>(Vec(3, i * row_spacing + top_row), module, ComputerscarePatchSequencer::INPUT_JACKS + i));
    502 
    503       if (i % 2) {
    504         addOutput(createOutput<PointingUpPentagonPort>(Vec(33 + i * column_spacing , top_row + 10 * row_spacing), module, ComputerscarePatchSequencer::OUTPUTS + i));
    505       }
    506       else {
    507         addOutput(createOutput<InPort>(Vec(33 + i * column_spacing , top_row + 10 * row_spacing), module, ComputerscarePatchSequencer::OUTPUTS + i));
    508       }
    509     }
    510 
    511     //clock input
    512     addInput(createInput<InPort>(Vec(24, 37), module, ComputerscarePatchSequencer::TRG_INPUT));
    513 
    514     //reset input
    515     addInput(createInput<InPort>(Vec(24, 3),  module, ComputerscarePatchSequencer::RESET_INPUT));
    516 
    517     //manual clock button
    518     addParam(createParam<LEDButton>(Vec(7 , 37), module, ComputerscarePatchSequencer::MANUAL_CLOCK_PARAM));
    519 
    520     //reset button
    521     addParam(createParam<LEDButton>(Vec(7 , 3), module, ComputerscarePatchSequencer::RESET_PARAM));
    522 
    523     //randomize input
    524     addInput(createInput<InPort>(Vec(270, 0), module, ComputerscarePatchSequencer::RANDOMIZE_INPUT));
    525 
    526     //active step display
    527     NumberDisplayWidget3 *display = new NumberDisplayWidget3();
    528     display->box.pos = Vec(56, 40);
    529     display->box.size = Vec(50, 20);
    530     display->value = &module->addressPlusOne;
    531     display->module = module;
    532     addChild(display);
    533 
    534     // number of steps display
    535     NumberDisplayWidget3 *stepsDisplay = new NumberDisplayWidget3();
    536     stepsDisplay->box.pos = Vec(150, 40);
    537     stepsDisplay->box.size = Vec(50, 20);
    538     stepsDisplay->module = module;
    539     stepsDisplay->value = &module->numAddresses;
    540     addChild(stepsDisplay);
    541 
    542     //number-of-steps dial.   Discrete, 16 positions
    543     ParamWidget* stepsKnob =  createParam<LrgKnob>(Vec(108, 30), module, ComputerscarePatchSequencer::STEPS_PARAM);
    544     addParam(stepsKnob);
    545 
    546     //editAddressNext button
    547     addParam(createParam<LEDButton>(Vec(227 , 41), module, ComputerscarePatchSequencer::EDIT_PARAM));
    548 
    549     //editAddressPrevious button
    550     addParam(createParam<LEDButton>(Vec(208 , 41), module, ComputerscarePatchSequencer::EDIT_PREV_PARAM));
    551 
    552     // currently editing step #:
    553     NumberDisplayWidget3 *displayEdit = new NumberDisplayWidget3();
    554     displayEdit->box.pos = Vec(246, 40);
    555     displayEdit->box.size = Vec(50, 20);
    556     displayEdit->module = module;
    557     displayEdit->value = &module->editAddressPlusOne;
    558     addChild(displayEdit);
    559     fatherSon = module;
    560   }
    561 
    562 
    563   /* void fromJson(json_t *rootJ) override
    564    {
    565      ModuleWidget::fromJson(rootJ);
    566      json_t *button_statesJ = json_object_get(rootJ, "buttons");
    567      if (button_statesJ) {
    568        //there be legacy JSON
    569        fatherSon->dataFromJson(rootJ);
    570      }
    571    }*/
    572   void appendContextMenu(Menu *menu) override;
    573 
    574   ComputerscarePatchSequencer *fatherSon;
    575 };
    576 struct OnlyRandomizeActiveMenuItem : MenuItem {
    577   ComputerscarePatchSequencer *patchSequencer;
    578   OnlyRandomizeActiveMenuItem() {
    579 
    580   }
    581   void onAction(const event::Action &e) override {
    582     patchSequencer->onlyRandomizeActive = !patchSequencer->onlyRandomizeActive;
    583   }
    584   void step() override {
    585     rightText = patchSequencer->onlyRandomizeActive ? "✔" : "";
    586     MenuItem::step();
    587   }
    588 };
    589 struct WhichStepToRandomizeItem : MenuItem {
    590   ComputerscarePatchSequencer *patchSequencer;
    591   int stepEnum;
    592   void onAction(const event::Action &e) override {
    593     patchSequencer->setRandomizationStepEnum(stepEnum);
    594   }
    595   void step() override {
    596     rightText = CHECKMARK(patchSequencer->getRandomizationStepEnum() == stepEnum);
    597     MenuItem::step();
    598   }
    599 };
    600 
    601 struct WhichRandomizationOutputBoundsItem : MenuItem {
    602   ComputerscarePatchSequencer *patchSequencer;
    603   int boundsEnum;
    604   void onAction(const event::Action &e) override {
    605     patchSequencer->setRandomizationOutputBoundsEnum(boundsEnum);
    606   }
    607   void step() override {
    608     rightText = CHECKMARK(patchSequencer->getRandomizationOutputBoundsEnum() == boundsEnum);
    609     MenuItem::step();
    610   }
    611 };
    612 
    613 struct FatherSonChannelItem : MenuItem {
    614   ComputerscarePatchSequencer *module;
    615   int channels;
    616   void onAction(const event::Action &e) override {
    617     module->channelCountEnum = channels;
    618   }
    619 };
    620 
    621 
    622 struct FatherSonChannelsItem : MenuItem {
    623   ComputerscarePatchSequencer *module;
    624   Menu *createChildMenu() override {
    625     Menu *menu = new Menu;
    626     for (int channels = -1; channels <= 16; channels++) {
    627       FatherSonChannelItem *item = new FatherSonChannelItem;
    628       if (channels < 0) {
    629         item->text = "Automatic";
    630       }
    631       else {
    632         item->text = string::f("%d", channels);
    633       }
    634       item->rightText = CHECKMARK(module->channelCountEnum == channels);
    635       item->module = module;
    636       item->channels = channels;
    637       menu->addChild(item);
    638     }
    639     return menu;
    640   }
    641 };
    642 
    643 void ComputerscarePatchSequencerWidget::appendContextMenu(Menu *menu)
    644 {
    645   ComputerscarePatchSequencer *patchSequencer = dynamic_cast<ComputerscarePatchSequencer *>(this->module);
    646 
    647   MenuLabel *spacerLabel = new MenuLabel();
    648   menu->addChild(spacerLabel);
    649 
    650   FatherSonChannelsItem *channelsItem = new FatherSonChannelsItem;
    651   channelsItem->text = "Output Polyphony";
    652   channelsItem->rightText = RIGHT_ARROW;
    653   channelsItem->module = patchSequencer;
    654   menu->addChild(channelsItem);
    655 
    656   menu->addChild(new MenuEntry);
    657 
    658 
    659   MenuLabel *modeLabel = new MenuLabel();
    660   modeLabel->text = "Randomization Options";
    661   menu->addChild(modeLabel);
    662 
    663 
    664   OnlyRandomizeActiveMenuItem *onlyRandomizeActiveMenuItem = new OnlyRandomizeActiveMenuItem();
    665   onlyRandomizeActiveMenuItem->text = "Only Randomize Active Connections";
    666   onlyRandomizeActiveMenuItem->patchSequencer = patchSequencer;
    667   menu->addChild(onlyRandomizeActiveMenuItem);
    668 
    669   menu->addChild(construct<MenuLabel>());
    670   menu->addChild(construct<MenuLabel>(&MenuLabel::text, "Which Step to Randomize"));
    671   menu->addChild(construct<WhichStepToRandomizeItem>(&MenuItem::text, "Edit step", &WhichStepToRandomizeItem::patchSequencer, patchSequencer, &WhichStepToRandomizeItem::stepEnum, 0));
    672   menu->addChild(construct<WhichStepToRandomizeItem>(&MenuItem::text, "Active step", &WhichStepToRandomizeItem::patchSequencer, patchSequencer, &WhichStepToRandomizeItem::stepEnum, 1));
    673   menu->addChild(construct<WhichStepToRandomizeItem>(&MenuItem::text, "All steps", &WhichStepToRandomizeItem::patchSequencer, patchSequencer, &WhichStepToRandomizeItem::stepEnum, 2));
    674 
    675 
    676   // randomization output bounds
    677   menu->addChild(construct<MenuLabel>());
    678   menu->addChild(construct<MenuLabel>(&MenuLabel::text, "Output Row Randomization Method"));
    679   menu->addChild(construct<WhichRandomizationOutputBoundsItem>(&MenuItem::text, "One or none", &WhichRandomizationOutputBoundsItem::patchSequencer, patchSequencer, &WhichRandomizationOutputBoundsItem::boundsEnum, 0));
    680   menu->addChild(construct<WhichRandomizationOutputBoundsItem>(&MenuItem::text, "Exactly one", &WhichRandomizationOutputBoundsItem::patchSequencer, patchSequencer, &WhichRandomizationOutputBoundsItem::boundsEnum, 1));
    681   menu->addChild(construct<WhichRandomizationOutputBoundsItem>(&MenuItem::text, "Zero or more", &WhichRandomizationOutputBoundsItem::patchSequencer, patchSequencer, &WhichRandomizationOutputBoundsItem::boundsEnum, 2));
    682   menu->addChild(construct<WhichRandomizationOutputBoundsItem>(&MenuItem::text, "One or more", &WhichRandomizationOutputBoundsItem::patchSequencer, patchSequencer, &WhichRandomizationOutputBoundsItem::boundsEnum, 3));
    683 
    684 }
    685 
    686 Model *modelComputerscarePatchSequencer = createModel<ComputerscarePatchSequencer, ComputerscarePatchSequencerWidget>("computerscare-fatherandson");
    687