computerscare-vcv-modules

ComputerScare modules for VCV Rack
Log | Files | Refs

ComputerscareLaundrySoup.cpp (20714B)


      1 #include <string>
      2 #include <sstream>
      3 #include <iomanip>
      4 
      5 #include "Computerscare.hpp"
      6 #include "dtpulse.hpp"
      7 
      8 
      9 struct ComputerscareLaundrySoup;
     10 struct LaundryTextField;
     11 struct LaundryTF2;
     12 struct LaundrySmallDisplay;
     13 struct ComputerscareLaundrySoupWidget;
     14 
     15 const int numFields = 6;
     16 
     17 std::string randomFormula() {
     18   std::string mainlookup = "111111111111111111111111112222333333344444444444444445556667778888888888888999";
     19   std::string string = "";
     20   std::string randchar = "";
     21   std::vector<std::string> atLookup = {"4", "8", "12", "16", "24", "32", "36", "48", "60", "64", "120", "128"};
     22   int length = 0;
     23 
     24   length = floor(random::uniform() * 12) + 1;
     25   string = "";
     26   for (int j = 0; j < length; j++) {
     27     randchar = mainlookup[floor(random::uniform() * mainlookup.size())];
     28     string = string + randchar;
     29     if (random::uniform() < 0.2) {
     30       string += "?";
     31     }
     32   }
     33   if (random::uniform() < 0.5) {
     34     if (random::uniform() < 0.8) {
     35       string = "[" + string.insert(floor(random::uniform() * (string.size() - 1) + 1), ",") + "]";
     36     }
     37     string += "@" + atLookup[floor(random::uniform() * atLookup.size())];
     38   }
     39   return string;
     40 }
     41 
     42 struct ComputerscareLaundrySoup : Module {
     43   enum ParamIds {
     44     MANUAL_CLOCK_PARAM,
     45     MANUAL_RESET_PARAM,
     46     INDIVIDUAL_RESET_PARAM,
     47     NUM_PARAMS = INDIVIDUAL_RESET_PARAM + numFields
     48   };
     49   enum InputIds {
     50     GLOBAL_CLOCK_INPUT,
     51     GLOBAL_RESET_INPUT,
     52     CLOCK_INPUT,
     53     RESET_INPUT = CLOCK_INPUT + numFields,
     54     NUM_INPUTS = RESET_INPUT + numFields
     55   };
     56   enum OutputIds {
     57     TRG_OUTPUT,
     58     FIRST_STEP_OUTPUT = TRG_OUTPUT + numFields,
     59     NUM_OUTPUTS = FIRST_STEP_OUTPUT + numFields
     60   };
     61   enum LightIds {
     62     SWITCH_LIGHTS,
     63     NUM_LIGHTS
     64   };
     65 
     66   rack::dsp::SchmittTrigger globalClockTrigger;
     67   rack::dsp::SchmittTrigger globalResetTriggerInput;
     68 
     69   rack::dsp::SchmittTrigger globalManualClockTrigger;
     70   rack::dsp::SchmittTrigger globalManualResetTrigger;
     71 
     72   rack::dsp::SchmittTrigger clockTriggers[numFields];
     73   rack::dsp::SchmittTrigger resetTriggers[numFields];
     74 
     75   LaundryTF2* textFields[numFields];
     76   LaundrySmallDisplay* smallLetterDisplays[numFields];
     77 
     78   std::string currentFormula[numFields];
     79   std::string currentTextFieldValue[numFields];
     80   std::string upcomingFormula[numFields];
     81 
     82   rack::dsp::SchmittTrigger manualResetTriggers[numFields];
     83 
     84   LaundryPoly laundryPoly[numFields];
     85 
     86   int checkCounter = 0;
     87   int checkCounterLimit = 10000;
     88 
     89   int channelCountEnum[numFields];
     90   int channelCount[numFields];
     91 
     92   bool activePolyStep[numFields][16] = {{false}};
     93 
     94 
     95   bool shouldChange[numFields] = {false};
     96   bool changeImminent[numFields] = {false};
     97   bool manualSet[numFields];
     98   bool inError[numFields];
     99 
    100   bool jsonLoaded = false;
    101   bool laundryInitialized = false;
    102   int sampleCounter = 0;
    103 
    104 
    105 
    106   ComputerscareLaundrySoup() {
    107     config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
    108     for (int i = 0; i < numFields; i++) {
    109       manualSet[i] = false;
    110       inError[i] = false;
    111 
    112       setNextAbsoluteSequence(i);
    113       checkIfShouldChange(i);
    114       resetOneOfThem(i);
    115 
    116       std::string rowi = std::to_string(i + 1);
    117 
    118       configButton(INDIVIDUAL_RESET_PARAM + i, "Reset Row " + rowi );
    119 
    120       configInput(CLOCK_INPUT + i, "Row " + rowi + " Clock");
    121       configInput(RESET_INPUT + i, "Row " + rowi + " Reset");
    122 
    123       configOutput(TRG_OUTPUT + i, "Row " + rowi + " Trigger");
    124       configOutput(FIRST_STEP_OUTPUT + i, "Row " + rowi + " End of Cycle");
    125 
    126       LaundryPoly lp = LaundryPoly(currentFormula[i]);
    127       laundryPoly[i] = lp;
    128       channelCountEnum[i] = -1;
    129       channelCount[i] = 1;
    130     }
    131 
    132     configButton(MANUAL_CLOCK_PARAM, "Manual Clock Advance");
    133     configButton(MANUAL_RESET_PARAM, "Manual Reset");
    134 
    135     configInput(GLOBAL_CLOCK_INPUT, "Global Clock");
    136     configInput(GLOBAL_RESET_INPUT, "Global Reset");
    137   }
    138   json_t *dataToJson() override {
    139     json_t *rootJ = json_object();
    140 
    141     json_t *sequencesJ = json_array();
    142     json_t *channelCountJ = json_array();
    143     for (int i = 0; i < numFields; i++) {
    144       json_t *sequenceJ = json_string(currentTextFieldValue[i].c_str());
    145       json_array_append_new(sequencesJ, sequenceJ);
    146       json_t *channelJ = json_integer(channelCountEnum[i]);
    147       json_array_append_new(channelCountJ, channelJ);
    148     }
    149     json_object_set_new(rootJ, "sequences", sequencesJ);
    150     json_object_set_new(rootJ, "channelCount", channelCountJ);
    151 
    152     return rootJ;
    153   }
    154 
    155   void dataFromJson(json_t *rootJ) override {
    156     std::string val;
    157     int count;
    158     json_t *sequencesJ = json_object_get(rootJ, "sequences");
    159     if (sequencesJ) {
    160       for (int i = 0; i < numFields; i++) {
    161 
    162         json_t *sequenceJ = json_array_get(sequencesJ, i);
    163         if (sequenceJ)
    164           val = json_string_value(sequenceJ);
    165         // currentFormula[i] = val;
    166         //currentTextFieldValue[i] = val;
    167         currentTextFieldValue[i] = val;
    168         // upcomingFormula[i]=val;
    169         manualSet[i] = true;
    170       }
    171     }
    172     else {
    173       json_t *textJLegacy = json_object_get(rootJ, "data");
    174       if (textJLegacy) {
    175         json_t *seqJLegacy = json_object_get(textJLegacy, "sequences");
    176 
    177         if (seqJLegacy) {
    178           for (int i = 0; i < numFields; i++) {
    179             json_t *sequenceJ = json_array_get(seqJLegacy, i);
    180             if (sequenceJ)
    181               val = json_string_value(sequenceJ);
    182             // currentFormula[i] = val;
    183             //lastValue[i] = val;
    184             currentTextFieldValue[i] = val;
    185             //upcomingFormula[i]=val;
    186             manualSet[i] = true;
    187 
    188           }
    189         }
    190       }
    191     }
    192     json_t *channelCountEnumJ = json_object_get(rootJ, "channelCount");
    193     if (channelCountEnumJ) {
    194       for (int i = 0; i < numFields; i++) {
    195         json_t *countJ = json_array_get(channelCountEnumJ, i);
    196         if (countJ) {
    197           count = json_integer_value(countJ);
    198           channelCountEnum[i] = count;
    199         }
    200       }
    201     }
    202     jsonLoaded = true;
    203 
    204   }
    205   void process(const ProcessArgs &args) override;
    206 
    207   void onAdd() override {
    208   }
    209 
    210   void checkTextField(int channel) {
    211     std::string textFieldValue = currentTextFieldValue[channel];
    212 
    213     if (textFieldValue != currentFormula[channel] && textFieldValue != upcomingFormula[channel]) {
    214 
    215       LaundryPoly lp = LaundryPoly(textFieldValue);
    216       if (!lp.inError && matchParens(textFieldValue)) {
    217         upcomingFormula[channel] = textFieldValue;
    218         setNextAbsoluteSequence(channel);
    219         inError[channel] = false;
    220       }
    221       else {
    222         DEBUG("Channel %i in error", channel);
    223         inError[channel] = true;
    224       }
    225     }
    226 
    227   }
    228 
    229   void onRandomize() override {
    230     randomizeAllFields();
    231   }
    232   void randomizeAllFields() {
    233     for (int i = 0; i < numFields; i++) {
    234       randomizeAField(i);
    235     }
    236   }
    237 
    238   void randomizeAField(int i) {
    239     currentTextFieldValue[i] = randomFormula();
    240     manualSet[i] = true;
    241     setNextAbsoluteSequence(i);
    242 
    243 
    244   }
    245 
    246   void setNextAbsoluteSequence(int index) {
    247     shouldChange[index] = true;
    248   }
    249   void checkChannelCount(int index) {
    250     if (channelCountEnum[index] == -1) {
    251       if (currentFormula[index].find("#") != std::string::npos) {
    252         channelCount[index] = 16;
    253       } else {
    254         size_t n = std::count(currentFormula[index].begin(), currentFormula[index].end(), ';');
    255         channelCount[index] = std::min((int)n + 1, 16);
    256       }
    257     } else {
    258       channelCount[index] = channelCountEnum[index];
    259     }
    260   }
    261   void setAbsoluteSequenceFromQueue(int index) {
    262     laundryPoly[index] = LaundryPoly(upcomingFormula[index]);
    263     currentFormula[index] = upcomingFormula[index];
    264     checkChannelCount(index);
    265     if (laundryPoly[index].inError) {
    266       DEBUG("ERROR ch:%i", index);
    267     }
    268   }
    269   void checkIfShouldChange(int index) {
    270     if (shouldChange[index]) {
    271       setAbsoluteSequenceFromQueue(index);
    272       shouldChange[index] = false;
    273     }
    274   }
    275 
    276   /*
    277   lets say the sequence "332" is entered in the 0th (first)
    278   numSteps[0] would then be 8 (3 + 3 + 2)
    279   absoluteSequences[0] would be the vector (1,0,0,1,0,0,1,0)
    280   absoluteStep[0] would run from 0 to 7
    281 
    282   332-4 (332 offset by 4)
    283 
    284   */
    285   void incrementInternalStep(int i) {
    286     for (int ch = 0; ch < 16; ch++) {
    287       laundryPoly[i].lss[ch].incrementAndCheck();
    288       if (laundryPoly[i].lss[laundryPoly[i].maxIndex].readHead == 0) {
    289         this->setChangeImminent(i, false);
    290       }
    291     }
    292   }
    293   std::string getDisplayString(int index) {
    294     LaundryPoly loopy = this->laundryPoly[index];
    295 
    296     std::string lhs = std::to_string(loopy.lss[loopy.maxIndex].readHead + 1);
    297     std::string rhs =  std::to_string(loopy.lss[loopy.maxIndex].numSteps);
    298 
    299     padTo(lhs, 3, ' ');
    300     padTo(rhs, 3, ' ');
    301 
    302     std::string val = lhs + "\n" + rhs;
    303 
    304     return val;
    305   }
    306   void setChangeImminent(int i, bool value) {
    307     changeImminent[i] = value;
    308   }
    309   void resetOneOfThem(int i) {
    310     for (int ch = 0; ch < 16; ch++) {
    311       laundryPoly[i].lss[ch].readHead = -1;
    312     }
    313   }
    314 };
    315 
    316 
    317 void ComputerscareLaundrySoup::process(const ProcessArgs &args) {
    318 
    319 
    320   bool globalGateIn = globalClockTrigger.isHigh();
    321   bool atFirstStep = false;
    322   bool atFirstStepPoly[16];
    323   bool atLastStepAfterIncrement = false;
    324   bool clocked = globalClockTrigger.process(inputs[GLOBAL_CLOCK_INPUT].getVoltage());
    325   bool currentTriggerIsHigh = false;
    326   bool currentTriggerClocked = false;
    327 
    328   bool globalManualResetClicked = globalManualResetTrigger.process(params[MANUAL_RESET_PARAM].getValue());
    329   bool globalManualClockClicked = globalManualClockTrigger.process(params[MANUAL_CLOCK_PARAM].getValue());
    330 
    331   bool globalResetTriggered = globalResetTriggerInput.process(inputs[GLOBAL_RESET_INPUT].getVoltage() / 2.f);
    332   bool currentResetActive = false;
    333   bool currentResetTriggered = false;
    334   bool currentManualResetClicked;
    335 
    336   int numOutputChannels;
    337   sampleCounter++;
    338   if (sampleCounter > 10000) {
    339     sampleCounter = 0;
    340   }
    341 
    342 
    343   if (checkCounter > checkCounterLimit) {
    344     if (!jsonLoaded) {
    345       for (int i = 0; i < numFields; i++) {
    346         currentTextFieldValue[i] = i < numFields - 1 ? std::to_string(i + 1) : randomFormula();
    347         manualSet[i] = true;
    348       }
    349       for (int i = 0; i < numFields; i++) {
    350         checkTextField(i);
    351         checkChannelCount(i);
    352         checkIfShouldChange(i);
    353       }
    354       jsonLoaded = true;
    355     }
    356     else {
    357       for (int i = 0; i < numFields; i++) {
    358         checkTextField(i);
    359         checkChannelCount(i);
    360       }
    361     }
    362     checkCounter = 0;
    363   }
    364   checkCounter++;
    365 
    366   for (int i = 0; i < numFields; i++) {
    367     numOutputChannels = channelCount[i];
    368     currentResetActive = inputs[RESET_INPUT + i].isConnected();
    369     currentResetTriggered = resetTriggers[i].process(inputs[RESET_INPUT + i].getVoltage() / 2.f);
    370     currentTriggerIsHigh = clockTriggers[i].isHigh();
    371     currentTriggerClocked = clockTriggers[i].process(inputs[CLOCK_INPUT + i].getVoltage());
    372 
    373     currentManualResetClicked = manualResetTriggers[i].process(params[INDIVIDUAL_RESET_PARAM + i].getValue());
    374 
    375     if (this->laundryPoly[i].maxSteps > 0) {
    376       if (inputs[CLOCK_INPUT + i].isConnected()) {
    377         if (currentTriggerClocked || globalManualClockClicked) {
    378           incrementInternalStep(i);
    379           for (int ch = 0; ch < numOutputChannels; ch++) {
    380             activePolyStep[i][ch] = (this->laundryPoly[i].lss[ch].peekWorkingStep() == 1);
    381           }
    382           atLastStepAfterIncrement = this->laundryPoly[i].maxChannelAtLastStep();
    383           if (atLastStepAfterIncrement) checkIfShouldChange(i);
    384         }
    385       }
    386       else {
    387         if ((inputs[GLOBAL_CLOCK_INPUT].isConnected() && clocked) || globalManualClockClicked) {
    388           incrementInternalStep(i);
    389           for (int ch = 0; ch < numOutputChannels; ch++) {
    390             activePolyStep[i][ch] = (this->laundryPoly[i].lss[ch].peekWorkingStep() == 1);
    391           }
    392           atLastStepAfterIncrement = this->laundryPoly[i].maxChannelAtLastStep();
    393           if (atLastStepAfterIncrement) checkIfShouldChange(i);
    394         }
    395       }
    396 
    397       for (int ch = 0; ch < numOutputChannels; ch++) {
    398 
    399         atFirstStepPoly[ch] =  (this->laundryPoly[i].lss[ch].readHead == 0);
    400       }
    401 
    402       atFirstStep = (this->laundryPoly[i].lss[this->laundryPoly[i].maxIndex].readHead == 0);
    403       if (globalManualResetClicked || currentManualResetClicked) {
    404         setChangeImminent(i, true);
    405         checkIfShouldChange(i);
    406         resetOneOfThem(i);
    407       }
    408       if ( (currentResetActive && currentResetTriggered) || (!currentResetActive && globalResetTriggered)) {
    409 
    410         setChangeImminent(i, false);
    411         checkIfShouldChange(i);
    412         resetOneOfThem(i);
    413 
    414       }
    415       else {
    416         if (atFirstStep && !currentResetActive && !inputs[GLOBAL_RESET_INPUT].isConnected()) {
    417           //checkIfShouldChange(i);
    418           //not sure why this was commented out but i think its important :)
    419         }
    420       }
    421     }
    422     //this always assumes 16 channel poly output.  It is a waste if the user doesnt want poly
    423     outputs[TRG_OUTPUT + i].setChannels(numOutputChannels);
    424     outputs[FIRST_STEP_OUTPUT + i].setChannels(numOutputChannels);
    425 
    426     if (inputs[CLOCK_INPUT + i].isConnected()) {
    427       for (int ch = 0; ch < numOutputChannels; ch++) {
    428         outputs[TRG_OUTPUT + i].setVoltage((currentTriggerIsHigh && activePolyStep[i][ch]) ? 10.0f : 0.0f, ch);
    429         outputs[FIRST_STEP_OUTPUT + i].setVoltage((currentTriggerIsHigh && atFirstStepPoly[ch]) ? 10.f : 0.0f, ch);
    430       }
    431     }
    432     else {
    433       for (int ch = 0; ch < numOutputChannels; ch++) {
    434         outputs[TRG_OUTPUT + i].setVoltage((globalGateIn && activePolyStep[i][ch]) ? 10.0f : 0.0f, ch);
    435         outputs[FIRST_STEP_OUTPUT + i].setVoltage((globalGateIn && atFirstStepPoly[ch]) ? 10.f : 0.0f, ch);
    436       }
    437     }
    438   }
    439 }
    440 
    441 struct LaundryTF2 : ComputerscareTextField
    442 {
    443   ComputerscareLaundrySoup *module;
    444   //int fontSize = 16;
    445   int rowIndex = 0;
    446 
    447   LaundryTF2(int i)
    448   {
    449     rowIndex = i;
    450     ComputerscareTextField();
    451   };
    452 
    453 
    454   void draw(const DrawArgs &args) override
    455   {
    456     if (module)
    457     {
    458       if (module->manualSet[rowIndex]) {
    459 
    460         text = module->currentTextFieldValue[rowIndex];
    461         module->manualSet[rowIndex] = false;
    462       }
    463       std::string value = text.c_str();
    464       module->currentTextFieldValue[rowIndex] = value;
    465       inError = module->inError[rowIndex];
    466       /* if (value != module->currentFormula[rowIndex] )
    467        {
    468          module->currentTextFieldValue[rowIndex] = value;
    469          inError = true;
    470        }
    471        else {
    472          inError = false;
    473        }*/
    474     }
    475     else {
    476       text = randomFormula();
    477     }
    478 
    479     ComputerscareTextField::draw(args);
    480   }
    481 
    482 
    483   //void draw(const DrawArgs &args) override;
    484   //int getTextPosition(math::Vec mousePos) override;
    485 };
    486 
    487 struct LaundrySmallDisplay : SmallLetterDisplay
    488 {
    489   ComputerscareLaundrySoup *module;
    490   int type;
    491   int index;
    492   LaundrySmallDisplay(int i)
    493   {
    494     index = i;
    495     SmallLetterDisplay();
    496   };
    497   void draw(const DrawArgs &args)
    498   {
    499     //this->setNumDivisionsString();
    500     if (module)
    501     {
    502       value = module->getDisplayString(index);
    503       blink = module->shouldChange[index];
    504       doubleblink = module->changeImminent[index];
    505       SmallLetterDisplay::draw(args);
    506     }
    507   }
    508 
    509 };
    510 
    511 struct LaundryChannelItem : MenuItem {
    512   ComputerscareLaundrySoup *module;
    513   int channels;
    514   int row;
    515   void onAction(const event::Action &e) override {
    516     if (row > -1) {
    517       module->channelCountEnum[row] = channels;
    518     } else {
    519       for (int i = 0; i < numFields; i++) {
    520         module->channelCountEnum[i] = channels;
    521       }
    522     }
    523   }
    524 };
    525 
    526 
    527 struct LaundryChannelsItem : MenuItem {
    528   ComputerscareLaundrySoup *module;
    529   int row;
    530   Menu *createChildMenu() override {
    531     Menu *menu = new Menu;
    532     for (int channels = -1; channels <= 16; channels++) {
    533       LaundryChannelItem *item = new LaundryChannelItem;
    534       item->row = row;
    535       if (channels < 0)
    536         item->text = "Automatic";
    537       else
    538         item->text = string::f("%d", channels);
    539       if (row > -1) {
    540         item->rightText = CHECKMARK(module->channelCountEnum[row] == channels);
    541       }
    542       item->module = module;
    543       item->channels = channels;
    544       menu->addChild(item);
    545     }
    546     return menu;
    547   }
    548 };
    549 
    550 
    551 struct ComputerscareLaundrySoupWidget : ModuleWidget {
    552 
    553   double verticalSpacing = 18.4;
    554   int verticalStart = 22;
    555   ComputerscareLaundrySoupWidget(ComputerscareLaundrySoup *module) {
    556     setModule(module);
    557     setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/ComputerscareLaundrySoupPanel.svg")));
    558 
    559     //global clock input
    560     addInput(createInput<InPort>(mm2px(Vec(2 , 0)),  module, ComputerscareLaundrySoup::GLOBAL_CLOCK_INPUT));
    561 
    562     //global reset input
    563     addInput(createInput<InPort>(mm2px(Vec(12 , 0)),  module, ComputerscareLaundrySoup::GLOBAL_RESET_INPUT));
    564 
    565     //momentary clock and reset buttons
    566     addParam(createParam<ComputerscareClockButton>(mm2px(Vec(2 , 8)), module, ComputerscareLaundrySoup::MANUAL_CLOCK_PARAM));
    567     addParam(createParam<ComputerscareResetButton>(mm2px(Vec(12 , 8)), module, ComputerscareLaundrySoup::MANUAL_RESET_PARAM));
    568 
    569     for (int i = 0; i < numFields; i++) {
    570       //first-step output
    571       addOutput(createOutput<OutPort>(mm2px(Vec(42 , verticalStart + verticalSpacing * i - 11)),  module, ComputerscareLaundrySoup::FIRST_STEP_OUTPUT + i));
    572 
    573       //individual output
    574       addOutput(createOutput<OutPort>(mm2px(Vec(54 , verticalStart + verticalSpacing * i - 11)),  module, ComputerscareLaundrySoup::TRG_OUTPUT + i));
    575 
    576       //individual clock input
    577       addInput(createInput<InPort>(mm2px(Vec(2, verticalStart + verticalSpacing * i - 10)),  module, ComputerscareLaundrySoup::CLOCK_INPUT + i));
    578 
    579       //individual reset input
    580       addInput(createInput<InPort>(mm2px(Vec(12, verticalStart + verticalSpacing * i - 10)),  module, ComputerscareLaundrySoup::RESET_INPUT + i));
    581 
    582       textFieldTemp = new LaundryTF2(i);
    583       textFieldTemp->box.pos = mm2px(Vec(1, verticalStart + verticalSpacing * i));
    584       textFieldTemp->module = module;
    585       textFieldTemp->box.size = mm2px(Vec(64, 7));
    586       textFieldTemp->multiline = false;
    587       textFieldTemp->color = nvgRGB(0xC0, 0xE7, 0xDE);
    588       textFieldTemp->text = "";
    589 
    590       laundryTextFields[i] = textFieldTemp;
    591       addChild(textFieldTemp);
    592 
    593 
    594       // active / total steps display
    595       smallLetterDisplay = new LaundrySmallDisplay(i);
    596       smallLetterDisplay->box.pos = mm2px(Vec(22, verticalStart - 9.2 + verticalSpacing * i));
    597       smallLetterDisplay->box.size = Vec(60, 30);
    598       smallLetterDisplay->value = std::to_string(3);
    599       smallLetterDisplay->baseColor = COLOR_COMPUTERSCARE_LIGHT_GREEN;
    600       smallLetterDisplay->module = module;
    601       addChild(smallLetterDisplay);
    602       laundrySmallDisplays[i] = smallLetterDisplay;
    603 
    604       addParam(createParam<ComputerscareInvisibleButton>(mm2px(Vec(20, verticalStart - 9.2 + verticalSpacing * i)), module, ComputerscareLaundrySoup::INDIVIDUAL_RESET_PARAM + i));
    605 
    606     }
    607     laundry = module;
    608   }
    609 
    610   void appendContextMenu(Menu *menu) override {
    611     ComputerscareLaundrySoup *module = dynamic_cast<ComputerscareLaundrySoup*>(this->laundry);
    612 
    613     menu->addChild(new MenuEntry);
    614 
    615 
    616 
    617     for (int i = -1; i < numFields; i++) {
    618       LaundryChannelsItem *channelsItem = new LaundryChannelsItem;
    619       channelsItem->text = i == -1 ? "Set All Channels Polyphony" : string::f("Channel %d Polyphony", i + 1);;
    620       channelsItem->rightText = RIGHT_ARROW;
    621       channelsItem->module = module;
    622       channelsItem->row = i;
    623       menu->addChild(channelsItem);
    624 
    625       if (i == -1) {
    626         MenuLabel *spacerLabel = new MenuLabel();
    627         menu->addChild(spacerLabel);
    628       }
    629     }
    630   }
    631   /*This is a deprecated method, but since I used ModuleWidget::toJson to save the custom sequences,
    632       old patches have "sequences" at the root of the JSON serialization.  Module::dataFromJSON does not provide
    633       the root object, just the "data" key, so this is the only way to get the sequences from patches prior to v1.2
    634 
    635       */
    636   /* void fromJson(json_t *rootJ) override
    637    {
    638 
    639      std::string val;
    640      ModuleWidget::fromJson(rootJ);
    641 
    642      json_t *seqJLegacy = json_object_get(rootJ, "sequences");
    643      if (seqJLegacy) {
    644        for (int i = 0; i < numFields; i++) {
    645          json_t *sequenceJ = json_array_get(seqJLegacy, i);
    646          if (sequenceJ) {
    647            val = json_string_value(sequenceJ);
    648            laundry->currentTextFieldValue[i] = val;
    649            laundry->manualSet[i] = true;
    650          }
    651 
    652        }
    653        laundry->jsonLoaded = true;
    654      }
    655 
    656 
    657    }*/
    658   ComputerscareLaundrySoup *laundry;
    659 
    660   LaundryTF2 *textFieldTemp;
    661   LaundryTF2 *laundryTextFields[numFields];
    662   LaundrySmallDisplay* smallLetterDisplay;
    663   LaundrySmallDisplay* laundrySmallDisplays[numFields];
    664 
    665 };
    666 
    667 Model *modelComputerscareLaundrySoup = createModel<ComputerscareLaundrySoup, ComputerscareLaundrySoupWidget>("computerscare-laundry-soup");