#include "Computerscare.hpp" #include "dsp/digital.hpp" #include "dsp/filter.hpp" #include #include #include namespace rack_plugin_computerscare { struct ComputerscarePatchSequencer : Module { enum ParamIds { STEPS_PARAM, MANUAL_CLOCK_PARAM, EDIT_PARAM, EDIT_PREV_PARAM, ENUMS(SWITCHES,100), NUM_PARAMS }; enum InputIds { TRG_INPUT, ENUMS(INPUT_JACKS, 10), RANDOMIZE_INPUT, NUM_INPUTS }; enum OutputIds { OUTPUTS, NUM_OUTPUTS = OUTPUTS + 10 }; enum LightIds { SWITCH_LIGHTS, NUM_LIGHTS = SWITCH_LIGHTS + 200 }; SchmittTrigger switch_triggers[10][10]; SchmittTrigger nextAddressRead; SchmittTrigger nextAddressEdit; SchmittTrigger prevAddressEdit; SchmittTrigger clockTrigger; SchmittTrigger randomizeTrigger; int address = 0; int editAddress = 0; int addressPlusOne = 1; int editAddressPlusOne = 1; int numAddresses = 2; bool switch_states[16][10][10] = {{{0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}},{{0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}},{{0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}},{{0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}},{{0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}},{{0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}},{{0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}},{{0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}},{{0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}},{{0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}},{{0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}},{{0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}},{{0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}},{{0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}},{{0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}},{{0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}} }; float input_values[10] = {0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0}; float sums[10] = {0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0}; ComputerscarePatchSequencer() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) {} void step() override; json_t *toJson() override { json_t *rootJ = json_object(); // button states json_t *button_statesJ = json_array(); for(int k = 0; k < 16; k++) { for (int i = 0; i < 10; i++) { for (int j = 0; j < 10; j++) { json_t *button_stateJ = json_integer((int) switch_states[k][i][j]); json_array_append_new(button_statesJ, button_stateJ); } } } json_object_set_new(rootJ, "buttons", button_statesJ); return rootJ; } void fromJson(json_t *rootJ) override { // button states json_t *button_statesJ = json_object_get(rootJ, "buttons"); if (button_statesJ) { for(int k = 0; k < 16; k++) { for (int i = 0; i < 10; i++) { for (int j = 0; j < 10; j++) { json_t *button_stateJ = json_array_get(button_statesJ, k*100+i*10 + j); if (button_stateJ) switch_states[k][i][j] = !!json_integer_value(button_stateJ); } } } } } void onRandomize() override { randomizePatchMatrix(); } // For more advanced Module features, read Rack's engine.hpp header file // - toJson, fromJson: serialization of internal data // - onSampleRateChange: event triggered by a change of sample rate // - onReset, onRandomize, onCreate, onDelete: implements special behavior when user clicks these from the context menu void randomizePatchMatrix() { int randomIndex; for (int i = 0; i < 10; i++) { randomIndex = floor(randomUniform()*10); for (int j = 0; j < 10; j++) { if(j==randomIndex) switch_states[editAddress][j][i] = 1; else switch_states[editAddress][j][i]=0; //fully randomize everything. a bit intense for its use in patching //switch_states[editAddress][i][j] = (randomUniform() > 0.5f); } } }; // end randomize() void onReset() override { for(int k =0; k < 16; k++) { for (int i = 0; i < 10; i++) { for (int j = 0; j < 10; j++) { switch_states[k][i][j] = 0; } } } }; // end randomize() }; void ComputerscarePatchSequencer::step() { int numStepsKnobPosition = (int) clamp(roundf(params[STEPS_PARAM].value), 1.0f, 16.0f); for ( int i = 0 ; i < 10 ; i++) { sums[i] = 0.0; } for (int i = 0 ; i < 10 ; i++) { for (int j = 0 ; j < 10 ; j++) { if (switch_triggers[i][j].process(params[SWITCHES+j*10 + i].value)) { // handle button clicks in the patch matrix switch_states[editAddress][i][j] = !switch_states[editAddress][i][j]; } // update the green lights (step you are editing) and the red lights (current active step) lights[SWITCH_LIGHTS + i + j * 10].value = (switch_states[editAddress][i][j]) ? 1.0 : 0.0; lights[SWITCH_LIGHTS + i + j * 10+100].value = (switch_states[address][i][j]) ? 1.0 : 0.0; } } if(numStepsKnobPosition != numAddresses) { numAddresses = numStepsKnobPosition; } if(randomizeTrigger.process(inputs[RANDOMIZE_INPUT].value / 2.f)) { randomizePatchMatrix(); } if(nextAddressEdit.process(params[EDIT_PARAM].value) ) { editAddress = editAddress + 1; editAddress = editAddress % 16; } if(prevAddressEdit.process(params[EDIT_PREV_PARAM].value) ) { editAddress = editAddress - 1; editAddress = editAddress + 16; editAddress = editAddress % 16; } if(nextAddressRead.process(params[MANUAL_CLOCK_PARAM].value) || clockTrigger.process(inputs[TRG_INPUT].value / 2.f)) { numAddresses = (int) clamp(roundf(params[STEPS_PARAM].value /*+ inputs[STEPS_INPUT].value*/), 1.0f, 16.0f); address = address + 1; address = address % numAddresses; } addressPlusOne = address + 1; editAddressPlusOne = editAddress + 1; for (int i = 0 ; i < 10 ; i++) { input_values[i] = inputs[INPUT_JACKS + i].value; } for (int i = 0 ; i < 10 ; i++) { for (int j = 0 ; j < 10 ; j++) { // todo: toggle for each output of how to combine multiple active signals in a column // sum, average, and, or etc if (switch_states[address][j][i]) sums[i] += input_values[j]; } } /// outputs for (int i = 0 ; i < 10 ; i++) { outputs[OUTPUTS + i].value = sums[i]; } } //////////////////////////////////// struct NumberDisplayWidget3 : TransparentWidget { int *value; std::shared_ptr font; NumberDisplayWidget3() { font = Font::load(assetPlugin(plugin, "res/digital-7.ttf")); }; void draw(NVGcontext *vg) override { // Background NVGcolor backgroundColor = nvgRGB(0x00, 0x00, 0x00); nvgBeginPath(vg); nvgRoundedRect(vg, 0.0, 0.0, box.size.x, box.size.y, 4.0); nvgFillColor(vg, backgroundColor); nvgFill(vg); // text nvgFontSize(vg, 13); nvgFontFaceId(vg, font->handle); nvgTextLetterSpacing(vg, 2.5); std::stringstream to_display; to_display << std::setw(3) << *value; Vec textPos = Vec(6.0f, 17.0f); NVGcolor textColor = nvgRGB(0xC0, 0xE7, 0xDE); nvgFillColor(vg, textColor); nvgText(vg, textPos.x, textPos.y, to_display.str().c_str(), NULL); } }; struct ComputerscarePatchSequencerWidget : ModuleWidget { ComputerscarePatchSequencerWidget(ComputerscarePatchSequencer *module) : ModuleWidget(module) { setPanel(SVG::load(assetPlugin(plugin, "res/ComputerscarePatchSequencerPanel.svg"))); int top_row = 70; int row_spacing = 26; int column_spacing = 26; for (int i = 0 ; i < 10 ; i++) { addInput(Port::create(Vec(3, i * row_spacing + top_row), Port::INPUT, module, ComputerscarePatchSequencer::INPUT_JACKS + i)); addOutput(Port::create(Vec(33 + i * column_spacing , top_row + 10 * row_spacing), Port::OUTPUT, module, ComputerscarePatchSequencer::OUTPUTS + i)); for(int j = 0 ; j < 10 ; j++ ) { // the part you click addParam(ParamWidget::create(Vec(35 + column_spacing * j, top_row + row_spacing * i), module, ComputerscarePatchSequencer::SWITCHES + i + j * 10, 0.0, 1.0, 0.0)); // green light indicates the state of the matrix that is being edited addChild(ModuleLightWidget::create>(Vec(35 + column_spacing * j +1.4, top_row + row_spacing * i +1.4 ), module, ComputerscarePatchSequencer::SWITCH_LIGHTS + i + j * 10)); // red light indicates the state of the matrix that is the active step addChild(ModuleLightWidget::create>(Vec(35 + column_spacing * j + 4.3, top_row + row_spacing * i + 4.3), module, ComputerscarePatchSequencer::SWITCH_LIGHTS + i + j * 10+100)); } } //clock input addInput(Port::create(Vec(3, 0), Port::INPUT, module, ComputerscarePatchSequencer::TRG_INPUT)); //manual clock button addParam(ParamWidget::create(Vec(7 , 41), module, ComputerscarePatchSequencer::MANUAL_CLOCK_PARAM, 0.0, 1.0, 0.0)); //randomize input addInput(Port::create(Vec(270, 0), Port::INPUT, module, ComputerscarePatchSequencer::RANDOMIZE_INPUT)); //active step display NumberDisplayWidget3 *display = new NumberDisplayWidget3(); display->box.pos = Vec(30,40); display->box.size = Vec(50, 20); display->value = &module->addressPlusOne; addChild(display); // number of steps display NumberDisplayWidget3 *stepsDisplay = new NumberDisplayWidget3(); stepsDisplay->box.pos = Vec(150,40); stepsDisplay->box.size = Vec(50, 20); stepsDisplay->value = &module->numAddresses; addChild(stepsDisplay); //number-of-steps dial. Discrete, 16 positions ParamWidget* stepsKnob = ParamWidget::create(Vec(108,30), module, ComputerscarePatchSequencer::STEPS_PARAM, 1.0f, 16.0f, 2.0f); addParam(stepsKnob); //editAddressNext button addParam(ParamWidget::create(Vec(227 , 41), module, ComputerscarePatchSequencer::EDIT_PARAM, 0.0, 1.0, 0.0)); //editAddressPrevious button addParam(ParamWidget::create(Vec(208 , 41), module, ComputerscarePatchSequencer::EDIT_PREV_PARAM, 0.0, 1.0, 0.0)); // currently editing step #: NumberDisplayWidget3 *displayEdit = new NumberDisplayWidget3(); displayEdit->box.pos = Vec(245,40); displayEdit->box.size = Vec(50, 20); displayEdit->value = &module->editAddressPlusOne; addChild(displayEdit); } }; } // namespace rack_plugin_computerscare using namespace rack_plugin_computerscare; RACK_PLUGIN_MODEL_INIT(computerscare, ComputerscarePatchSequencer) { Model *modelComputerscarePatchSequencer = Model::create("computerscare", "computerscare-patch-sequencer", "Father & Son Patch Sequencer", UTILITY_TAG,SEQUENCER_TAG); return modelComputerscarePatchSequencer; }