//*********************************************************************************************** //Three channel 32-step writable sequencer module for VCV Rack by Marc Boulé // //Based on code from the Fundamental and AudibleInstruments plugins by Andrew Belt //and graphics from the Component Library by Wes Milholen //See ./LICENSE.txt for all licenses //See ./res/fonts/ for font licenses //*********************************************************************************************** #include "ImpromptuModular.hpp" #include "PhraseSeqUtil.hpp" namespace rack_plugin_ImpromptuModular { struct WriteSeq32 : Module { enum ParamIds { SHARP_PARAM, ENUMS(WINDOW_PARAM, 4), QUANTIZE_PARAM, ENUMS(GATE_PARAM, 8), CHANNEL_PARAM, COPY_PARAM, PASTE_PARAM, RUN_PARAM, WRITE_PARAM, STEPL_PARAM, MONITOR_PARAM, STEPR_PARAM, STEPS_PARAM, AUTOSTEP_PARAM, PASTESYNC_PARAM, NUM_PARAMS }; enum InputIds { CHANNEL_INPUT,// no longer used CV_INPUT, GATE_INPUT, WRITE_INPUT, STEPL_INPUT, STEPR_INPUT, CLOCK_INPUT, RESET_INPUT, // -- 0.6.2 RUNCV_INPUT, NUM_INPUTS }; enum OutputIds { ENUMS(CV_OUTPUTS, 3), ENUMS(GATE_OUTPUTS, 3), NUM_OUTPUTS }; enum LightIds { ENUMS(WINDOW_LIGHTS, 4), ENUMS(STEP_LIGHTS, 8), ENUMS(GATE_LIGHTS, 8), ENUMS(CHANNEL_LIGHTS, 4), RUN_LIGHT, ENUMS(WRITE_LIGHT, 2),// room for GreenRed PENDING_LIGHT, NUM_LIGHTS }; // Need to save int panelTheme = 0; bool running; int indexStep; int indexStepStage; int indexChannel; float cv[4][32]; bool gates[4][32]; bool resetOnRun; // No need to save int notesPos[8]; // used for rendering notes in LCD_24, 8 gate and 8 step LEDs float cvCPbuffer[32];// copy paste buffer for CVs bool gateCPbuffer[32];// copy paste buffer for gates long infoCopyPaste;// 0 when no info, positive downward step counter timer when copy, negative upward when paste int pendingPaste;// 0 = nothing to paste, 1 = paste on clk, 2 = paste on seq, destination channel in next msbits long clockIgnoreOnReset; const float clockIgnoreOnResetDuration = 0.001f;// disable clock on powerup and reset for 1 ms (so that the first step plays) int lightRefreshCounter; SchmittTrigger clockTrigger; SchmittTrigger resetTrigger; SchmittTrigger runningTrigger; SchmittTrigger channelTrigger; SchmittTrigger stepLTrigger; SchmittTrigger stepRTrigger; SchmittTrigger copyTrigger; SchmittTrigger pasteTrigger; SchmittTrigger writeTrigger; SchmittTrigger gateTriggers[8]; SchmittTrigger windowTriggers[4]; WriteSeq32() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) { onReset(); } void onReset() override { running = true; indexStep = 0; indexStepStage = 0; indexChannel = 0; for (int s = 0; s < 32; s++) { for (int c = 0; c < 4; c++) { cv[c][s] = 0.0f; gates[c][s] = true; } cvCPbuffer[s] = 0.0f; gateCPbuffer[s] = true; } infoCopyPaste = 0l; pendingPaste = 0; clockIgnoreOnReset = (long) (clockIgnoreOnResetDuration * engineGetSampleRate()); resetOnRun = false; lightRefreshCounter = 0; } void onRandomize() override { running = true; indexStep = 0; indexStepStage = 0; indexChannel = 0; for (int s = 0; s < 32; s++) { for (int c = 0; c < 4; c++) { cv[c][s] = quantize((randomUniform() *10.0f) - 4.0f, params[QUANTIZE_PARAM].value > 0.5f); gates[c][s] = (randomUniform() > 0.5f); } cvCPbuffer[s] = 0.0f; gateCPbuffer[s] = true; } infoCopyPaste = 0l; pendingPaste = 0; clockIgnoreOnReset = (long) (clockIgnoreOnResetDuration * engineGetSampleRate()); resetOnRun = false; } json_t *toJson() override { json_t *rootJ = json_object(); // panelTheme json_object_set_new(rootJ, "panelTheme", json_integer(panelTheme)); // running json_object_set_new(rootJ, "running", json_boolean(running)); // indexStep json_object_set_new(rootJ, "indexStep", json_integer(indexStep)); // indexStepStage json_object_set_new(rootJ, "indexStepStage", json_integer(indexStepStage)); // indexChannel json_object_set_new(rootJ, "indexChannel", json_integer(indexChannel)); // CV json_t *cvJ = json_array(); for (int c = 0; c < 4; c++) for (int s = 0; s < 32; s++) { json_array_insert_new(cvJ, s + (c<<5), json_real(cv[c][s])); } json_object_set_new(rootJ, "cv", cvJ); // Gates json_t *gatesJ = json_array(); for (int c = 0; c < 4; c++) for (int s = 0; s < 32; s++) { json_array_insert_new(gatesJ, s + (c<<5), json_integer((int) gates[c][s]));// json_boolean wil break patches } json_object_set_new(rootJ, "gates", gatesJ); // resetOnRun json_object_set_new(rootJ, "resetOnRun", json_boolean(resetOnRun)); return rootJ; } void fromJson(json_t *rootJ) override { // panelTheme json_t *panelThemeJ = json_object_get(rootJ, "panelTheme"); if (panelThemeJ) panelTheme = json_integer_value(panelThemeJ); // running json_t *runningJ = json_object_get(rootJ, "running"); if (runningJ) running = json_is_true(runningJ); // indexStep json_t *indexStepJ = json_object_get(rootJ, "indexStep"); if (indexStepJ) indexStep = json_integer_value(indexStepJ); // indexStepStage json_t *indexStepStageJ = json_object_get(rootJ, "indexStepStage"); if (indexStepStageJ) indexStepStage = json_integer_value(indexStepStageJ); // indexChannel json_t *indexChannelJ = json_object_get(rootJ, "indexChannel"); if (indexChannelJ) indexChannel = json_integer_value(indexChannelJ); // CV json_t *cvJ = json_object_get(rootJ, "cv"); if (cvJ) { for (int c = 0; c < 4; c++) for (int s = 0; s < 32; s++) { json_t *cvArrayJ = json_array_get(cvJ, s + (c<<5)); if (cvArrayJ) cv[c][s] = json_real_value(cvArrayJ); } } // Gates json_t *gatesJ = json_object_get(rootJ, "gates"); if (gatesJ) { for (int c = 0; c < 4; c++) for (int s = 0; s < 32; s++) { json_t *gateJ = json_array_get(gatesJ, s + (c<<5)); if (gateJ) gates[c][s] = !!json_integer_value(gateJ);// json_is_true() will break patches } } // resetOnRun json_t *resetOnRunJ = json_object_get(rootJ, "resetOnRun"); if (resetOnRunJ) resetOnRun = json_is_true(resetOnRunJ); } inline float quantize(float cv, bool enable) { return enable ? (roundf(cv * 12.0f) / 12.0f) : cv; } // Advances the module by 1 audio frame with duration 1.0 / engineGetSampleRate() void step() override { static const float copyPasteInfoTime = 0.5f;// seconds //********** Buttons, knobs, switches and inputs ********** int numSteps = (int) clamp(roundf(params[STEPS_PARAM].value), 1.0f, 32.0f); // Run state button if (runningTrigger.process(params[RUN_PARAM].value + inputs[RUNCV_INPUT].value)) { running = !running; //pendingPaste = 0;// no pending pastes across run state toggles if (running && resetOnRun) { indexStep = 0; indexStepStage = 0; } clockIgnoreOnReset = (long) (clockIgnoreOnResetDuration * engineGetSampleRate()); } // Copy button if (copyTrigger.process(params[COPY_PARAM].value)) { infoCopyPaste = (long) (copyPasteInfoTime * engineGetSampleRate() / displayRefreshStepSkips); for (int s = 0; s < 32; s++) { cvCPbuffer[s] = cv[indexChannel][s]; gateCPbuffer[s] = gates[indexChannel][s]; } pendingPaste = 0; } // Paste button if (pasteTrigger.process(params[PASTE_PARAM].value)) { if (params[PASTESYNC_PARAM].value < 0.5f || indexChannel == 3) { // Paste realtime, no pending to schedule infoCopyPaste = (long) (-1 * copyPasteInfoTime * engineGetSampleRate() / displayRefreshStepSkips); for (int s = 0; s < 32; s++) { cv[indexChannel][s] = cvCPbuffer[s]; gates[indexChannel][s] = gateCPbuffer[s]; } pendingPaste = 0; } else { pendingPaste = params[PASTESYNC_PARAM].value > 1.5f ? 2 : 1; pendingPaste |= indexChannel<<2; // add paste destination channel into pendingPaste } } // Channel selection button if (channelTrigger.process(params[CHANNEL_PARAM].value)) { indexChannel++; if (indexChannel >= 4) indexChannel = 0; } // Gate buttons for (int index8 = 0, iGate = 0; index8 < 8; index8++) { if (gateTriggers[index8].process(params[GATE_PARAM + index8].value)) { iGate = ( (indexChannel == 3 ? indexStepStage : indexStep) & 0x18) | index8; if (iGate < numSteps)// don't toggle gates beyond steps gates[indexChannel][iGate] = !gates[indexChannel][iGate]; } } bool canEdit = !running || (indexChannel == 3); // Steps knob will not trigger anything in step(), and if user goes lower than current step, lower the index accordingly if (indexStep >= numSteps) indexStep = numSteps - 1; if (indexStepStage >= numSteps) indexStepStage = numSteps - 1; // Write button (must be before StepL and StepR in case route gate simultaneously to Step R and Write for example) // (write must be to correct step) if (writeTrigger.process(params[WRITE_PARAM].value + inputs[WRITE_INPUT].value)) { if (canEdit) { int index = (indexChannel == 3 ? indexStepStage : indexStep); // CV cv[indexChannel][index] = quantize(inputs[CV_INPUT].value, params[QUANTIZE_PARAM].value > 0.5f); // Gate if (inputs[GATE_INPUT].active) gates[indexChannel][index] = (inputs[GATE_INPUT].value >= 1.0f) ? true : false; // Autostep if (params[AUTOSTEP_PARAM].value > 0.5f) { if (indexChannel == 3) indexStepStage = moveIndex(indexStepStage, indexStepStage + 1, numSteps); else indexStep = moveIndex(indexStep, indexStep + 1, numSteps); } } } // Step L button if (stepLTrigger.process(params[STEPL_PARAM].value + inputs[STEPL_INPUT].value)) { if (canEdit) { if (indexChannel == 3) indexStepStage = moveIndex(indexStepStage, indexStepStage - 1, numSteps); else indexStep = moveIndex(indexStep, indexStep - 1, numSteps); } } // Step R button if (stepRTrigger.process(params[STEPR_PARAM].value + inputs[STEPR_INPUT].value)) { if (canEdit) { if (indexChannel == 3) indexStepStage = moveIndex(indexStepStage, indexStepStage + 1, numSteps); else indexStep = moveIndex(indexStep, indexStep + 1, numSteps); } } // Window buttons for (int i = 0; i < 4; i++) { if (windowTriggers[i].process(params[WINDOW_PARAM+i].value)) { if (canEdit) { if (indexChannel == 3) indexStepStage = (i<<3) | (indexStepStage&0x7); else indexStep = (i<<3) | (indexStep&0x7); } } } //********** Clock and reset ********** // Clock if (clockTrigger.process(inputs[CLOCK_INPUT].value)) { if (running && clockIgnoreOnReset == 0l) { indexStep = moveIndex(indexStep, indexStep + 1, numSteps); // Pending paste on clock or end of seq if ( ((pendingPaste&0x3) == 1) || ((pendingPaste&0x3) == 2 && indexStep == 0) ) { int pasteChannel = pendingPaste>>2; infoCopyPaste = (long) (-1 * copyPasteInfoTime * engineGetSampleRate() / displayRefreshStepSkips); for (int s = 0; s < 32; s++) { cv[pasteChannel][s] = cvCPbuffer[s]; gates[pasteChannel][s] = gateCPbuffer[s]; } pendingPaste = 0; } } } // Reset if (resetTrigger.process(inputs[RESET_INPUT].value)) { indexStep = 0; indexStepStage = 0; pendingPaste = 0; //indexChannel = 0; clockTrigger.reset(); clockIgnoreOnReset = (long) (clockIgnoreOnResetDuration * engineGetSampleRate()); } //********** Outputs and lights ********** // CV and gate outputs (staging area not used) if (running) { for (int i = 0; i < 3; i++) { outputs[CV_OUTPUTS + i].value = cv[i][indexStep]; outputs[GATE_OUTPUTS + i].value = (clockTrigger.isHigh() && gates[i][indexStep]) ? 10.0f : 0.0f; } } else { bool muteGate = false;// (params[WRITE_PARAM].value + params[STEPL_PARAM].value + params[STEPR_PARAM].value) > 0.5f; // set to false if don't want mute gate on button push for (int i = 0; i < 3; i++) { // CV if (params[MONITOR_PARAM].value > 0.5f) outputs[CV_OUTPUTS + i].value = cv[i][indexStep];// each CV out monitors the current step CV of that channel else outputs[CV_OUTPUTS + i].value = quantize(inputs[CV_INPUT].value, params[QUANTIZE_PARAM].value > 0.5f);// all CV outs monitor the CV in (only current channel will have a gate though) // Gate outputs[GATE_OUTPUTS + i].value = ((i == indexChannel) && !muteGate) ? 10.0f : 0.0f; } } lightRefreshCounter++; if (lightRefreshCounter > displayRefreshStepSkips) { lightRefreshCounter = 0; int index = (indexChannel == 3 ? indexStepStage : indexStep); // Window lights for (int i = 0; i < 4; i++) { lights[WINDOW_LIGHTS + i].value = ((i == (index >> 3))?1.0f:0.0f); } // Step and gate lights for (int index8 = 0, iGate = 0; index8 < 8; index8++) { lights[STEP_LIGHTS + index8].value = (index8 == (index&0x7)) ? 1.0f : 0.0f; iGate = (index&0x18) | index8; lights[GATE_LIGHTS + index8].value = (gates[indexChannel][iGate] && iGate < numSteps) ? 1.0f : 0.0f; } // Channel lights lights[CHANNEL_LIGHTS + 0].value = (indexChannel == 0) ? 1.0f : 0.0f;// green lights[CHANNEL_LIGHTS + 1].value = (indexChannel == 1) ? 1.0f : 0.0f;// yellow lights[CHANNEL_LIGHTS + 2].value = (indexChannel == 2) ? 1.0f : 0.0f;// orange lights[CHANNEL_LIGHTS + 3].value = (indexChannel == 3) ? 1.0f : 0.0f;// blue // Run light lights[RUN_LIGHT].value = running ? 1.0f : 0.0f; // Write allowed light lights[WRITE_LIGHT + 0].value = (canEdit)?1.0f:0.0f; lights[WRITE_LIGHT + 1].value = (canEdit)?0.0f:1.0f; // Pending paste light lights[PENDING_LIGHT].value = (pendingPaste == 0 ? 0.0f : 1.0f); if (infoCopyPaste != 0l) { if (infoCopyPaste > 0l) infoCopyPaste --; if (infoCopyPaste < 0l) infoCopyPaste ++; } }// lightRefreshCounter if (clockIgnoreOnReset > 0l) clockIgnoreOnReset--; } }; struct WriteSeq32Widget : ModuleWidget { struct NotesDisplayWidget : TransparentWidget { WriteSeq32 *module; std::shared_ptr font; char text[4]; NotesDisplayWidget() { font = Font::load(assetPlugin(plugin, "res/fonts/Segment14.ttf")); } void cvToStr(int index8) { if (module->infoCopyPaste != 0l) { if (index8 == 0) { if (module->infoCopyPaste > 0l) snprintf(text, 4, "COP"); else snprintf(text, 4, "PAS"); } else if (index8 == 1) { if (module->infoCopyPaste > 0l) snprintf(text, 4, "Y "); else snprintf(text, 4, "TE "); } else { snprintf(text, 4, " "); } } else { int index = (module->indexChannel == 3 ? module->indexStepStage : module->indexStep); if ( ( (index&0x18) |index8) >= (int) clamp(roundf(module->params[WriteSeq32::STEPS_PARAM].value), 1.0f, 32.0f) ) { text[0] = ' '; text[1] = ' '; text[2] = ' '; } else { float cvVal = module->cv[module->indexChannel][index8|(index&0x18)]; float cvValOffset = cvVal +10.0f;//to properly handle negative note voltages int indexNote = (int) clamp( roundf( (cvValOffset-floor(cvValOffset)) * 12.0f ), 0.0f, 11.0f); bool sharp = (module->params[WriteSeq32::SHARP_PARAM].value > 0.5f) ? true : false; // note letter text[0] = sharp ? noteLettersSharp[indexNote] : noteLettersFlat[indexNote]; // octave number int octave = (int) roundf(floorf(cvVal)+4.0f); if (octave < 0 || octave > 9) text[1] = (octave > 9) ? ':' : '_'; else text[1] = (char) ( 0x30 + octave); // sharp/flat text[2] = ' '; if (isBlackKey[indexNote] == 1) text[2] = (sharp ? '\"' : '^' ); } } // end of string text[3] = 0; } void draw(NVGcontext *vg) override { NVGcolor textColor = prepareDisplay(vg, &box); nvgFontFaceId(vg, font->handle); nvgTextLetterSpacing(vg, -1.5); for (int i = 0; i < 8; i++) { Vec textPos = Vec(module->notesPos[i], 24); nvgFillColor(vg, nvgTransRGBA(textColor, 16)); nvgText(vg, textPos.x, textPos.y, "~~~", NULL); nvgFillColor(vg, textColor); cvToStr(i); nvgText(vg, textPos.x, textPos.y, text, NULL); } } }; struct StepsDisplayWidget : TransparentWidget { float *valueKnob; std::shared_ptr font; StepsDisplayWidget() { font = Font::load(assetPlugin(plugin, "res/fonts/Segment14.ttf")); } void draw(NVGcontext *vg) override { NVGcolor textColor = prepareDisplay(vg, &box); nvgFontFaceId(vg, font->handle); //nvgTextLetterSpacing(vg, 2.5); Vec textPos = Vec(6, 24); nvgFillColor(vg, nvgTransRGBA(textColor, 16)); nvgText(vg, textPos.x, textPos.y, "~~", NULL); nvgFillColor(vg, textColor); char displayStr[3]; snprintf(displayStr, 3, "%2u", (unsigned) clamp(roundf(*valueKnob), 1.0f, 32.0f) ); nvgText(vg, textPos.x, textPos.y, displayStr, NULL); } }; struct PanelThemeItem : MenuItem { WriteSeq32 *module; int theme; void onAction(EventAction &e) override { module->panelTheme = theme; } void step() override { rightText = (module->panelTheme == theme) ? "✔" : ""; } }; struct ResetOnRunItem : MenuItem { WriteSeq32 *module; void onAction(EventAction &e) override { module->resetOnRun = !module->resetOnRun; } }; Menu *createContextMenu() override { Menu *menu = ModuleWidget::createContextMenu(); MenuLabel *spacerLabel = new MenuLabel(); menu->addChild(spacerLabel); WriteSeq32 *module = dynamic_cast(this->module); assert(module); MenuLabel *themeLabel = new MenuLabel(); themeLabel->text = "Panel Theme"; menu->addChild(themeLabel); PanelThemeItem *lightItem = new PanelThemeItem(); lightItem->text = lightPanelID;// ImpromptuModular.hpp lightItem->module = module; lightItem->theme = 0; menu->addChild(lightItem); PanelThemeItem *darkItem = new PanelThemeItem(); darkItem->text = darkPanelID;// ImpromptuModular.hpp darkItem->module = module; darkItem->theme = 1; menu->addChild(darkItem); menu->addChild(new MenuLabel());// empty line MenuLabel *settingsLabel = new MenuLabel(); settingsLabel->text = "Settings"; menu->addChild(settingsLabel); ResetOnRunItem *rorItem = MenuItem::create("Reset on Run", CHECKMARK(module->resetOnRun)); rorItem->module = module; menu->addChild(rorItem); return menu; } WriteSeq32Widget(WriteSeq32 *module) : ModuleWidget(module) { // Main panel from Inkscape DynamicSVGPanel *panel = new DynamicSVGPanel(); panel->addPanel(SVG::load(assetPlugin(plugin, "res/light/WriteSeq32.svg"))); panel->addPanel(SVG::load(assetPlugin(plugin, "res/dark/WriteSeq32_dark.svg"))); box.size = panel->box.size; panel->mode = &module->panelTheme; addChild(panel); // Screws addChild(createDynamicScrew(Vec(15, 0), &module->panelTheme)); addChild(createDynamicScrew(Vec(box.size.x-30, 0), &module->panelTheme)); addChild(createDynamicScrew(Vec(15, 365), &module->panelTheme)); addChild(createDynamicScrew(Vec(box.size.x-30, 365), &module->panelTheme)); // Column rulers (horizontal positions) static const int columnRuler0 = 25; static const int columnnRulerStep = 69; static const int columnRuler1 = columnRuler0 + columnnRulerStep; static const int columnRuler2 = columnRuler1 + columnnRulerStep; static const int columnRuler3 = columnRuler2 + columnnRulerStep; static const int columnRuler4 = columnRuler3 + columnnRulerStep; static const int columnRuler5 = columnRuler4 + columnnRulerStep - 15; // Row rulers (vertical positions) static const int rowRuler0 = 172; static const int rowRulerStep = 49; static const int rowRuler1 = rowRuler0 + rowRulerStep; static const int rowRuler2 = rowRuler1 + rowRulerStep; static const int rowRuler3 = rowRuler2 + rowRulerStep + 4; // ****** Top portion ****** static const int yRulerTopLEDs = 42; static const int yRulerTopSwitches = yRulerTopLEDs-11; // Autostep, sharp/flat and quantize switches // Autostep addParam(ParamWidget::create(Vec(columnRuler0+3+hOffsetCKSS, yRulerTopSwitches+vOffsetCKSS), module, WriteSeq32::AUTOSTEP_PARAM, 0.0f, 1.0f, 1.0f)); // Sharp/flat addParam(ParamWidget::create(Vec(columnRuler4+hOffsetCKSS, yRulerTopSwitches+vOffsetCKSS), module, WriteSeq32::SHARP_PARAM, 0.0f, 1.0f, 1.0f)); // Quantize addParam(ParamWidget::create(Vec(columnRuler5+hOffsetCKSS, yRulerTopSwitches+vOffsetCKSS), module, WriteSeq32::QUANTIZE_PARAM, 0.0f, 1.0f, 1.0f)); // Window LED buttons static const float wLightsPosX = 140.0f; static const float wLightsIntX = 35.0f; for (int i = 0; i < 4; i++) { addParam(ParamWidget::create(Vec(wLightsPosX + i * wLightsIntX, yRulerTopLEDs - 4.4f), module, WriteSeq32::WINDOW_PARAM + i, 0.0f, 1.0f, 0.0f)); addChild(ModuleLightWidget::create>(Vec(wLightsPosX + 4.4f + i * wLightsIntX, yRulerTopLEDs), module, WriteSeq32::WINDOW_LIGHTS + i)); } // Prepare 8 positions for step lights, gate lights and notes display module->notesPos[0] = 9;// this is also used to help line up LCD digits with LEDbuttons and avoid bad horizontal scaling with long str in display for (int i = 1; i < 8; i++) { module->notesPos[i] = module->notesPos[i-1] + 46; } // Notes display NotesDisplayWidget *displayNotes = new NotesDisplayWidget(); displayNotes->box.pos = Vec(12, 76); displayNotes->box.size = Vec(381, 30); displayNotes->module = module; addChild(displayNotes); // Step LEDs (must be done after Notes display such that LED glow will overlay the notes display static const int yRulerStepLEDs = 65; for (int i = 0; i < 8; i++) { addChild(ModuleLightWidget::create>(Vec(module->notesPos[i]+25.0f+1.5f, yRulerStepLEDs), module, WriteSeq32::STEP_LIGHTS + i)); } // Gates LED buttons static const int yRulerT2 = 119.0f; for (int i = 0; i < 8; i++) { addParam(ParamWidget::create(Vec(module->notesPos[i]+25.0f-4.4f, yRulerT2-4.4f), module, WriteSeq32::GATE_PARAM + i, 0.0f, 1.0f, 0.0f)); addChild(ModuleLightWidget::create>(Vec(module->notesPos[i]+25.0f, yRulerT2), module, WriteSeq32::GATE_LIGHTS + i)); } // ****** Bottom portion ****** // Column 0 // Channel button addParam(createDynamicParam(Vec(columnRuler0+offsetCKD6b, rowRuler0+offsetCKD6b), module, WriteSeq32::CHANNEL_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme)); // Channel LEDS static const int chanLEDoffsetX = 25; static const int chanLEDoffsetY[4] = {-20, -8, 4, 16}; addChild(ModuleLightWidget::create>(Vec(columnRuler0 + chanLEDoffsetX + offsetMediumLight, rowRuler0 - 4 + chanLEDoffsetY[0] + offsetMediumLight), module, WriteSeq32::CHANNEL_LIGHTS + 0)); addChild(ModuleLightWidget::create>(Vec(columnRuler0 + chanLEDoffsetX + offsetMediumLight, rowRuler0 - 4 + chanLEDoffsetY[1] + offsetMediumLight), module, WriteSeq32::CHANNEL_LIGHTS + 1)); addChild(ModuleLightWidget::create>(Vec(columnRuler0 + chanLEDoffsetX + offsetMediumLight, rowRuler0 - 4 + chanLEDoffsetY[2] + offsetMediumLight), module, WriteSeq32::CHANNEL_LIGHTS + 2)); addChild(ModuleLightWidget::create>(Vec(columnRuler0 + chanLEDoffsetX + offsetMediumLight, rowRuler0 - 4 + chanLEDoffsetY[3] + offsetMediumLight), module, WriteSeq32::CHANNEL_LIGHTS + 3)); // Copy/paste switches addParam(ParamWidget::create(Vec(columnRuler0-10, rowRuler1+offsetTL1105), module, WriteSeq32::COPY_PARAM, 0.0f, 1.0f, 0.0f)); addParam(ParamWidget::create(Vec(columnRuler0+20, rowRuler1+offsetTL1105), module, WriteSeq32::PASTE_PARAM, 0.0f, 1.0f, 0.0f)); // Paste sync (and light) addParam(ParamWidget::create(Vec(columnRuler0+hOffsetCKSS, rowRuler2+vOffsetCKSSThree), module, WriteSeq32::PASTESYNC_PARAM, 0.0f, 2.0f, 0.0f)); addChild(ModuleLightWidget::create>(Vec(columnRuler0 + 41, rowRuler2 + 14), module, WriteSeq32::PENDING_LIGHT)); // Run CV input addInput(createDynamicPort(Vec(columnRuler0, rowRuler3), Port::INPUT, module, WriteSeq32::RUNCV_INPUT, &module->panelTheme)); // Column 1 // Step L button addParam(createDynamicParam(Vec(columnRuler1+offsetCKD6b, rowRuler0+offsetCKD6b), module, WriteSeq32::STEPL_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme)); // Run LED bezel and light addParam(ParamWidget::create(Vec(columnRuler1+offsetLEDbezel, rowRuler1+offsetLEDbezel), module, WriteSeq32::RUN_PARAM, 0.0f, 1.0f, 0.0f)); addChild(ModuleLightWidget::create>(Vec(columnRuler1+offsetLEDbezel+offsetLEDbezelLight, rowRuler1+offsetLEDbezel+offsetLEDbezelLight), module, WriteSeq32::RUN_LIGHT)); // Gate input addInput(createDynamicPort(Vec(columnRuler1, rowRuler2), Port::INPUT, module, WriteSeq32::GATE_INPUT, &module->panelTheme)); // Step L input addInput(createDynamicPort(Vec(columnRuler1, rowRuler3), Port::INPUT, module, WriteSeq32::STEPL_INPUT, &module->panelTheme)); // Column 2 // Step R button addParam(createDynamicParam(Vec(columnRuler2+offsetCKD6b, rowRuler0+offsetCKD6b), module, WriteSeq32::STEPR_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme)); // Write button and light addParam(createDynamicParam(Vec(columnRuler2+offsetCKD6b, rowRuler1+offsetCKD6b), module, WriteSeq32::WRITE_PARAM, 0.0f, 1.0f, 0.0f, &module->panelTheme)); addChild(ModuleLightWidget::create>(Vec(columnRuler2 -12, rowRuler1 - 12), module, WriteSeq32::WRITE_LIGHT)); // CV input addInput(createDynamicPort(Vec(columnRuler2, rowRuler2), Port::INPUT, module, WriteSeq32::CV_INPUT, &module->panelTheme)); // Step R input addInput(createDynamicPort(Vec(columnRuler2, rowRuler3), Port::INPUT, module, WriteSeq32::STEPR_INPUT, &module->panelTheme)); // Column 3 // Steps display StepsDisplayWidget *displaySteps = new StepsDisplayWidget(); displaySteps->box.pos = Vec(columnRuler3-7, rowRuler0+vOffsetDisplay); displaySteps->box.size = Vec(40, 30);// 2 characters displaySteps->valueKnob = &module->params[WriteSeq32::STEPS_PARAM].value; addChild(displaySteps); // Steps knob addParam(createDynamicParam(Vec(columnRuler3+offsetIMBigKnob, rowRuler1+offsetIMBigKnob), module, WriteSeq32::STEPS_PARAM, 1.0f, 32.0f, 32.0f, &module->panelTheme)); // Monitor addParam(ParamWidget::create(Vec(columnRuler3+hOffsetCKSSH, rowRuler2+vOffsetCKSSH), module, WriteSeq32::MONITOR_PARAM, 0.0f, 1.0f, 0.0f)); // Write input addInput(createDynamicPort(Vec(columnRuler3, rowRuler3), Port::INPUT, module, WriteSeq32::WRITE_INPUT, &module->panelTheme)); // Column 4 // Outputs addOutput(createDynamicPort(Vec(columnRuler4, rowRuler0), Port::OUTPUT, module, WriteSeq32::CV_OUTPUTS + 0, &module->panelTheme)); addOutput(createDynamicPort(Vec(columnRuler4, rowRuler1), Port::OUTPUT, module, WriteSeq32::CV_OUTPUTS + 1, &module->panelTheme)); addOutput(createDynamicPort(Vec(columnRuler4, rowRuler2), Port::OUTPUT, module, WriteSeq32::CV_OUTPUTS + 2, &module->panelTheme)); // Reset addInput(createDynamicPort(Vec(columnRuler4, rowRuler3), Port::INPUT, module, WriteSeq32::RESET_INPUT, &module->panelTheme)); // Column 5 // Gates addOutput(createDynamicPort(Vec(columnRuler5, rowRuler0), Port::OUTPUT, module, WriteSeq32::GATE_OUTPUTS + 0, &module->panelTheme)); addOutput(createDynamicPort(Vec(columnRuler5, rowRuler1), Port::OUTPUT, module, WriteSeq32::GATE_OUTPUTS + 1, &module->panelTheme)); addOutput(createDynamicPort(Vec(columnRuler5, rowRuler2), Port::OUTPUT, module, WriteSeq32::GATE_OUTPUTS + 2, &module->panelTheme)); // Clock addInput(createDynamicPort(Vec(columnRuler5, rowRuler3), Port::INPUT, module, WriteSeq32::CLOCK_INPUT, &module->panelTheme)); } }; } // namespace rack_plugin_ImpromptuModular using namespace rack_plugin_ImpromptuModular; RACK_PLUGIN_MODEL_INIT(ImpromptuModular, WriteSeq32) { Model *modelWriteSeq32 = Model::create("Impromptu Modular", "Write-Seq-32", "SEQ - Write-Seq-32", SEQUENCER_TAG); return modelWriteSeq32; } /*CHANGE LOG 0.6.7: no reset on run by default, with switch added in context menu reset does not revert chan number to 1 */