#include #include #include "JWModules.hpp" #include "dsp/digital.hpp" namespace rack_plugin_JW_Modules { #define ROWS 32 #define COLS 32 #define CELLS 1024 #define POLY 16 #define HW 11.75 //cell height and width struct NoteSeq : Module,QuantizeUtils { enum ParamIds { STEP_BTN_PARAM, LENGTH_KNOB_PARAM, PLAY_MODE_KNOB_PARAM, RESET_BTN_PARAM, CLEAR_BTN_PARAM, RND_MODE_KNOB_PARAM, RND_TRIG_BTN_PARAM, RND_AMT_KNOB_PARAM, ROT_RIGHT_BTN_PARAM, ROT_LEFT_BTN_PARAM, FLIP_HORIZ_BTN_PARAM, FLIP_VERT_BTN_PARAM, SHIFT_UP_BTN_PARAM, SHIFT_DOWN_BTN_PARAM, LIFE_ON_SWITCH_PARAM, LIFE_SPEED_KNOB_PARAM, SCALE_KNOB_PARAM, NOTE_KNOB_PARAM, OCTAVE_KNOB_PARAM, LOW_HIGH_SWITCH_PARAM, HIGHEST_NOTE_PARAM, LOWEST_NOTE_PARAM, INCLUDE_INACTIVE_PARAM, NUM_PARAMS }; enum InputIds { CLOCK_INPUT, RESET_INPUT, CLEAR_INPUT, RND_TRIG_INPUT, RND_AMT_INPUT, ROT_RIGHT_INPUT, ROT_LEFT_INPUT, FLIP_HORIZ_INPUT, FLIP_VERT_INPUT, SHIFT_UP_INPUT, SHIFT_DOWN_INPUT, HIGHEST_NOTE_INPUT, LOWEST_NOTE_INPUT, NUM_INPUTS }; enum OutputIds { VOCT_MAIN_OUTPUT, GATE_MAIN_OUTPUT = VOCT_MAIN_OUTPUT + POLY, MIN_VOCT_OUTPUT = GATE_MAIN_OUTPUT + POLY, MIN_GATE_OUTPUT, MID_VOCT_OUTPUT, MID_GATE_OUTPUT, MAX_VOCT_OUTPUT, MAX_GATE_OUTPUT, RND_VOCT_OUTPUT, RND_GATE_OUTPUT, NUM_OUTPUTS }; enum LightIds { GATES_LIGHT, NUM_LIGHTS = GATES_LIGHT + POLY, }; enum PlayMode { PM_FWD_LOOP, PM_BWD_LOOP, PM_FWD_BWD_LOOP, PM_BWD_FWD_LOOP, PM_RANDOM_POS, NUM_PLAY_MODES }; enum RndMode { RND_BASIC, RND_EUCLID, RND_SIN_WAVE, RND_LIFE_GLIDERS, NUM_RND_MODES }; enum ShiftDirection { DIR_UP, DIR_DOWN }; enum RotateDirection { DIR_LEFT, DIR_RIGHT }; enum FlipDirection { DIR_HORIZ, DIR_VERT }; float displayWidth = 0, displayHeight = 0; float rate = 1.0 / engineGetSampleRate(); float lifeRate = 0.5 * engineGetSampleRate(); long lifeCounter = 0; int seqPos = 0; float rndFloat0to1AtClockStep = randomUniform(); bool goingForward = true; bool *cells = new bool[CELLS]; SchmittTrigger clockTrig, resetTrig, clearTrig; SchmittTrigger rndTrig, shiftUpTrig, shiftDownTrig; SchmittTrigger rotateRightTrig, rotateLeftTrig, flipHorizTrig, flipVertTrig; PulseGenerator gatePulse; NoteSeq() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) { reset(); } ~NoteSeq() { delete [] cells; } void randomize() override { randomizeCells(); } void reset() override { resetSeq(); clearCells(); } void onSampleRateChange() override { rate = 1.0 / engineGetSampleRate(); } json_t *toJson() override { json_t *rootJ = json_object(); json_t *cellsJ = json_array(); for (int i = 0; i < CELLS; i++) { json_t *cellJ = json_integer((int) cells[i]); json_array_append_new(cellsJ, cellJ); } json_object_set_new(rootJ, "cells", cellsJ); return rootJ; } void fromJson(json_t *rootJ) override { json_t *cellsJ = json_object_get(rootJ, "cells"); if (cellsJ) { for (int i = 0; i < CELLS; i++) { json_t *cellJ = json_array_get(cellsJ, i); if (cellJ) cells[i] = json_integer_value(cellJ); } } } //TODO maybe add length input //TODO maybe add start pos knob and input void step() override { if(params[LIFE_ON_SWITCH_PARAM].value){ if(lifeCounter % int(params[LIFE_SPEED_KNOB_PARAM].value) == 0){ stepLife(); lifeCounter = 1; } } if (clearTrig.process(params[CLEAR_BTN_PARAM].value + inputs[CLEAR_INPUT].value)) { clearCells(); } if (rndTrig.process(params[RND_TRIG_BTN_PARAM].value + inputs[RND_TRIG_INPUT].value)) { randomizeCells(); } if (rotateRightTrig.process(params[ROT_RIGHT_BTN_PARAM].value + inputs[ROT_RIGHT_INPUT].value)) { rotateCells(DIR_RIGHT); } if (rotateLeftTrig.process(params[ROT_LEFT_BTN_PARAM].value + inputs[ROT_LEFT_INPUT].value)) { rotateCells(DIR_LEFT); } if (flipHorizTrig.process(params[FLIP_HORIZ_BTN_PARAM].value + inputs[FLIP_HORIZ_INPUT].value)) { flipCells(DIR_HORIZ); } if (flipVertTrig.process(params[FLIP_VERT_BTN_PARAM].value + inputs[FLIP_VERT_INPUT].value)) { flipCells(DIR_VERT); } if (shiftUpTrig.process(params[SHIFT_UP_BTN_PARAM].value + inputs[SHIFT_UP_INPUT].value)) { shiftCells(DIR_UP); } if (shiftDownTrig.process(params[SHIFT_DOWN_BTN_PARAM].value + inputs[SHIFT_DOWN_INPUT].value)) { shiftCells(DIR_DOWN); } if (resetTrig.process(params[RESET_BTN_PARAM].value + inputs[RESET_INPUT].value)) { resetSeq(); } if (clockTrig.process(inputs[CLOCK_INPUT].value + params[STEP_BTN_PARAM].value)) { clockStep(); } bool pulse = gatePulse.process(1.0 / engineGetSampleRate()); ////////////////////////////////////////////// POLY OUTPUTS ////////////////////////////////////////////// std::vector polyYVals = getYValsFromBottomAtSeqPos(params[INCLUDE_INACTIVE_PARAM].value, pulse); for(int i=0;i monoYVals = getYValsFromBottomAtSeqPos(false, pulse); bool atLeastOne = int(monoYVals.size()) > 0; if(outputs[MIN_VOCT_OUTPUT].active && atLeastOne){ std::vector::iterator pos = std::min_element(monoYVals.begin(), monoYVals.end()); outputs[MIN_VOCT_OUTPUT].value = closestVoltageForRow(*pos); } if(outputs[MIN_GATE_OUTPUT].active){ outputs[MIN_GATE_OUTPUT].value = (pulse && atLeastOne) ? 10.0 : 0.0; } if(outputs[MID_VOCT_OUTPUT].active && atLeastOne){ std::vector::iterator minPos = std::min_element(monoYVals.begin(), monoYVals.end()); std::vector::iterator maxPos = std::max_element(monoYVals.begin(), monoYVals.end()); outputs[MID_VOCT_OUTPUT].value = closestVoltageForRow((*minPos + *maxPos) * 0.5); } if(outputs[MID_GATE_OUTPUT].active){ outputs[MID_GATE_OUTPUT].value = (pulse && atLeastOne) ? 10.0 : 0.0; } if(outputs[MAX_VOCT_OUTPUT].active && atLeastOne){ std::vector::iterator pos = std::max_element(monoYVals.begin(), monoYVals.end()); outputs[MAX_VOCT_OUTPUT].value = closestVoltageForRow(*pos); } if(outputs[MAX_GATE_OUTPUT].active){ outputs[MAX_GATE_OUTPUT].value = (pulse && atLeastOne) ? 10.0 : 0.0; } if(outputs[RND_VOCT_OUTPUT].active && atLeastOne){ outputs[RND_VOCT_OUTPUT].value = closestVoltageForRow(monoYVals[int(rndFloat0to1AtClockStep * monoYVals.size())]); } if(outputs[RND_GATE_OUTPUT].active){ outputs[RND_GATE_OUTPUT].value = (pulse && atLeastOne) ? 10.0 : 0.0; } } std::vector getYValsFromBottomAtSeqPos(bool includeInactive, bool pulse){ std::vector v; for(int i=CELLS-1;i>=0;i--){ int x = xFromI(i); if(x == seqPos && (cells[i] || includeInactive)){ int y = yFromI(i); int yFromBottom = ROWS - 1 - y; if(yFromBottom <= getFinalHighestNote1to32()-1 && yFromBottom >= getFinalLowestNote1to32()-1){ v.push_back(yFromBottom); } } } // if(pulse){ // printf("getActiveYValsAtSeqPos "); // for(int n : v) { printf("%i ", n); } // printf("\n\n"); // } return v; } int getFinalHighestNote1to32(){ int inputOffset = inputs[HIGHEST_NOTE_INPUT].active ? clampijw(int(rescalefjw(inputs[HIGHEST_NOTE_INPUT].value, -5, 5, -16, 16)), 1, 32) : 0; return params[HIGHEST_NOTE_PARAM].value + inputOffset; } int getFinalLowestNote1to32(){ int inputOffset = inputs[LOWEST_NOTE_INPUT].active ? clampijw(int(rescalefjw(inputs[LOWEST_NOTE_INPUT].value, -5, 5, -16, 16)), 1, 32) : 0; return params[LOWEST_NOTE_PARAM].value + inputOffset; } float closestVoltageForRow(int cellYFromBottom){ int octave = params[OCTAVE_KNOB_PARAM].value; int rootNote = params[NOTE_KNOB_PARAM].value; int scale = params[SCALE_KNOB_PARAM].value; return closestVoltageInScale(octave + (cellYFromBottom * 0.0833), rootNote, scale); } void clockStep(){ gatePulse.trigger(1e-3); lifeCounter++; rndFloat0to1AtClockStep = randomUniform(); //iterate seq pos int curPlayMode = int(params[PLAY_MODE_KNOB_PARAM].value); int seqLen = int(params[NoteSeq::LENGTH_KNOB_PARAM].value); if(curPlayMode == PM_FWD_LOOP){ seqPos = (seqPos + 1) % seqLen; goingForward = true; } else if(curPlayMode == PM_BWD_LOOP){ seqPos = seqPos > 0 ? seqPos - 1 : seqLen - 1; goingForward = false; } else if(curPlayMode == PM_FWD_BWD_LOOP || curPlayMode == PM_BWD_FWD_LOOP){ if(goingForward){ if(seqPos < seqLen - 1){ seqPos++; } else { seqPos--; goingForward = false; } } else { if(seqPos > 0){ seqPos--; } else { seqPos++; goingForward = true; } } } else if(curPlayMode == PM_RANDOM_POS){ seqPos = int(randomUniform() * seqLen); } } void resetSeq(){ int curPlayMode = int(params[PLAY_MODE_KNOB_PARAM].value); if(curPlayMode == PM_FWD_LOOP || curPlayMode == PM_FWD_BWD_LOOP || curPlayMode == PM_RANDOM_POS){ seqPos = 0; } else if(curPlayMode == PM_BWD_LOOP || curPlayMode == PM_BWD_FWD_LOOP){ seqPos = int(params[NoteSeq::LENGTH_KNOB_PARAM].value) - 1; } } void rotateCells(RotateDirection dir){ bool *newCells = new bool[CELLS]; for(int x=0; x < COLS; x++){ for(int y=0; y < ROWS; y++){ switch(dir){ case DIR_RIGHT: newCells[iFromXY(x, y)] = cells[iFromXY(y, COLS - x - 1)]; break; case DIR_LEFT: newCells[iFromXY(x, y)] = cells[iFromXY(COLS - y - 1, x)]; break; } } } cells = newCells; } void flipCells(FlipDirection dir){ bool *newCells = new bool[CELLS]; for(int x=0; x < COLS; x++){ for(int y=0; y < ROWS; y++){ switch(dir){ case DIR_HORIZ: newCells[iFromXY(x, y)] = cells[iFromXY(COLS - 1 - x, y)]; break; case DIR_VERT: newCells[iFromXY(x, y)] = cells[iFromXY(x, ROWS - 1 - y)]; break; } } } cells = newCells; } void shiftCells(ShiftDirection dir){ bool *newCells = new bool[CELLS]; for(int x=0; x < COLS; x++){ for(int y=0; y < ROWS; y++){ switch(dir){ case DIR_UP: newCells[iFromXY(x, y)] = cells[iFromXY(x, y==ROWS-1 ? 0 : y + 1)]; break; case DIR_DOWN: newCells[iFromXY(x, y)] = cells[iFromXY(x, y==0 ? ROWS-1 : y - 1)]; break; } } } cells = newCells; } void clearCells() { for(int i=0;i3) { newCells[cellIdx] = false; } else if (neighbors==3 && !cells[cellIdx]) { newCells[cellIdx] = true; } } } cells = newCells; } int getNeighborCount(int x, int y){ int result = 0; for(int i=-1; i<=1; i++){ for(int j=-1; j<=1; j++){ int newX = x + i; int newY = y + j; if(newX==x && newY==y)continue; if(newX>=0 && newY>=0 && newX= 0 && cellX < COLS && cellY >=0 && cellY < ROWS){ cells[iFromXY(cellX, cellY)] = on; } } bool isCellOnByDisplayPos(float displayX, float displayY){ return isCellOn(int(displayX / HW), int(displayY / HW)); } bool isCellOn(int cellX, int cellY){ return cells[iFromXY(cellX, cellY)]; } int iFromXY(int cellX, int cellY){ return cellX + cellY * ROWS; } int xFromI(int cellI){ return cellI % COLS; } int yFromI(int cellI){ return cellI / COLS; } }; struct NoteSeqDisplay : Widget { NoteSeq *module; bool currentlyTurningOn = false; float initX = 0; float initY = 0; float dragX = 0; float dragY = 0; NoteSeqDisplay(){} void onMouseDown(EventMouseDown &e) override { if (e.button == 0) { e.consumed = true; e.target = this; initX = e.pos.x; initY = e.pos.y; currentlyTurningOn = !module->isCellOnByDisplayPos(e.pos.x, e.pos.y); module->setCellOnByDisplayPos(e.pos.x, e.pos.y, currentlyTurningOn); } } void onMouseMove(EventMouseMove &e) override {} void onMouseUp(EventMouseUp &e) override {} void onDragStart(EventDragStart &e) override { dragX = RACK_PLUGIN_UI_RACKWIDGET->lastMousePos.x; dragY = RACK_PLUGIN_UI_RACKWIDGET->lastMousePos.y; } void onDragEnd(EventDragEnd &e) override {} void onDragMove(EventDragMove &e) override { float newDragX = RACK_PLUGIN_UI_RACKWIDGET->lastMousePos.x; float newDragY = RACK_PLUGIN_UI_RACKWIDGET->lastMousePos.y; module->setCellOnByDisplayPos(initX+(newDragX-dragX), initY+(newDragY-dragY), currentlyTurningOn); } void draw(NVGcontext *vg) override { //background nvgFillColor(vg, nvgRGB(20, 30, 33)); nvgBeginPath(vg); nvgRect(vg, 0, 0, box.size.x, box.size.y); nvgFill(vg); //grid nvgStrokeColor(vg, nvgRGB(60, 70, 73)); for(int i=1;icells[i]){ int y = i / ROWS; int x = i % COLS; nvgBeginPath(vg); nvgRect(vg, x * HW, y * HW, HW, HW); nvgFill(vg); } } nvgStrokeWidth(vg, 2); //highest note line float rowHighLimitY = (32-module->getFinalHighestNote1to32()) * HW; nvgStrokeColor(vg, nvgRGB(255, 151, 9));//orange nvgBeginPath(vg); nvgMoveTo(vg, 0, rowHighLimitY); nvgLineTo(vg, box.size.x, rowHighLimitY); nvgStroke(vg); //lowest note line float rowLowLimitY = (33-module->getFinalLowestNote1to32()) * HW; nvgStrokeColor(vg, nvgRGB(255, 243, 9));//yellow nvgBeginPath(vg); nvgMoveTo(vg, 0, rowLowLimitY); nvgLineTo(vg, box.size.x, rowLowLimitY); nvgStroke(vg); //seq length line float colLimitX = module->params[NoteSeq::LENGTH_KNOB_PARAM].value * HW; nvgStrokeColor(vg, nvgRGB(144, 26, 252));//purple nvgBeginPath(vg); nvgMoveTo(vg, colLimitX, 0); nvgLineTo(vg, colLimitX, box.size.y); nvgStroke(vg); //seq pos nvgStrokeColor(vg, nvgRGB(255, 255, 255)); nvgBeginPath(vg); nvgRect(vg, module->seqPos * HW, 0, HW, box.size.y); nvgStroke(vg); } }; struct PlayModeKnob : JwSmallSnapKnob { PlayModeKnob(){} std::string formatCurrentValue() override { switch(int(value)){ case NoteSeq::PM_FWD_LOOP:return "→"; case NoteSeq::PM_BWD_LOOP:return "←"; case NoteSeq::PM_FWD_BWD_LOOP:return "→←"; case NoteSeq::PM_BWD_FWD_LOOP:return "←→"; case NoteSeq::PM_RANDOM_POS:return "*"; } return ""; } }; struct RndModeKnob : JwSmallSnapKnob { RndModeKnob(){} std::string formatCurrentValue() override { switch(int(value)){ case NoteSeq::RND_BASIC:return "Basic"; case NoteSeq::RND_EUCLID:return "Euclid"; case NoteSeq::RND_SIN_WAVE:return "Sine"; case NoteSeq::RND_LIFE_GLIDERS:return "Gliders"; } return ""; } }; struct NoteSeqWidget : ModuleWidget { NoteSeqWidget(NoteSeq *module); }; NoteSeqWidget::NoteSeqWidget(NoteSeq *module) : ModuleWidget(module) { box.size = Vec(RACK_GRID_WIDTH*48, RACK_GRID_HEIGHT); SVGPanel *panel = new SVGPanel(); panel->box.size = box.size; panel->setBackground(SVG::load(assetPlugin(plugin, "res/NoteSeq.svg"))); addChild(panel); NoteSeqDisplay *display = new NoteSeqDisplay(); display->module = module; display->box.pos = Vec(172, 2); display->box.size = Vec(376, RACK_GRID_HEIGHT - 4); addChild(display); module->displayWidth = display->box.size.x; module->displayHeight = display->box.size.y; addChild(Widget::create(Vec(16, 1))); addChild(Widget::create(Vec(16, 365))); addChild(Widget::create(Vec(box.size.x-29, 1))); addChild(Widget::create(Vec(box.size.x-29, 365))); ///////////////////////////////////////////////////// LEFT SIDE ///////////////////////////////////////////////////// //row 1 addInput(Port::create(Vec(25, 40), Port::INPUT, module, NoteSeq::CLOCK_INPUT)); addParam(ParamWidget::create(Vec(58, 35), module, NoteSeq::STEP_BTN_PARAM, 0.0, 1.0, 0.0)); addParam(ParamWidget::create(Vec(92, 35), module, NoteSeq::LENGTH_KNOB_PARAM, 1.0, 32.0, 32.0)); PlayModeKnob *playModeKnob = dynamic_cast(ParamWidget::create(Vec(126, 35), module, NoteSeq::PLAY_MODE_KNOB_PARAM, 0.0, NoteSeq::NUM_PLAY_MODES - 1, 0.0)); CenteredLabel* const playModeLabel = new CenteredLabel; playModeLabel->box.pos = Vec(69.5, 35); playModeLabel->text = ""; playModeKnob->connectLabel(playModeLabel); addChild(playModeLabel); addParam(playModeKnob); //row 2 addInput(Port::create(Vec(10, 92), Port::INPUT, module, NoteSeq::RESET_INPUT)); addParam(ParamWidget::create(Vec(30, 87), module, NoteSeq::RESET_BTN_PARAM, 0.0, 1.0, 0.0)); addInput(Port::create(Vec(60, 92), Port::INPUT, module, NoteSeq::CLEAR_INPUT)); addParam(ParamWidget::create(Vec(80, 87), module, NoteSeq::CLEAR_BTN_PARAM, 0.0, 1.0, 0.0)); RndModeKnob *rndModeKnob = dynamic_cast(ParamWidget::create(Vec(120, 87), module, NoteSeq::RND_MODE_KNOB_PARAM, 0.0, NoteSeq::NUM_RND_MODES - 1, 0.0)); CenteredLabel* const rndModeLabel = new CenteredLabel; rndModeLabel->box.pos = Vec(67, 61); rndModeLabel->text = ""; rndModeKnob->connectLabel(rndModeLabel); addChild(rndModeLabel); addParam(rndModeKnob); //row 3 addInput(Port::create(Vec(60, 150), Port::INPUT, module, NoteSeq::RND_TRIG_INPUT)); addParam(ParamWidget::create(Vec(80, 145), module, NoteSeq::RND_TRIG_BTN_PARAM, 0.0, 1.0, 0.0)); addInput(Port::create(Vec(118, 150), Port::INPUT, module, NoteSeq::RND_AMT_INPUT)); addParam(ParamWidget::create(Vec(138, 145), module, NoteSeq::RND_AMT_KNOB_PARAM, 0.0, 1.0, 0.1)); addInput(Port::create(Vec(60, 201), Port::INPUT, module, NoteSeq::SHIFT_UP_INPUT)); addParam(ParamWidget::create(Vec(80, 196), module, NoteSeq::SHIFT_UP_BTN_PARAM, 0.0, 1.0, 0.0)); addInput(Port::create(Vec(118, 201), Port::INPUT, module, NoteSeq::SHIFT_DOWN_INPUT)); addParam(ParamWidget::create(Vec(138, 196), module, NoteSeq::SHIFT_DOWN_BTN_PARAM, 0.0, 1.0, 0.0)); addInput(Port::create(Vec(60, 252), Port::INPUT, module, NoteSeq::ROT_RIGHT_INPUT)); addParam(ParamWidget::create(Vec(80, 247), module, NoteSeq::ROT_RIGHT_BTN_PARAM, 0.0, 1.0, 0.0)); addInput(Port::create(Vec(118, 252), Port::INPUT, module, NoteSeq::ROT_LEFT_INPUT)); addParam(ParamWidget::create(Vec(138, 247), module, NoteSeq::ROT_LEFT_BTN_PARAM, 0.0, 1.0, 0.0)); addInput(Port::create(Vec(60, 304), Port::INPUT, module, NoteSeq::FLIP_HORIZ_INPUT)); addParam(ParamWidget::create(Vec(80, 299), module, NoteSeq::FLIP_HORIZ_BTN_PARAM, 0.0, 1.0, 0.0)); addInput(Port::create(Vec(118, 304), Port::INPUT, module, NoteSeq::FLIP_VERT_INPUT)); addParam(ParamWidget::create(Vec(138, 299), module, NoteSeq::FLIP_VERT_BTN_PARAM, 0.0, 1.0, 0.0)); addParam(ParamWidget::create(Vec(68, 345), module, NoteSeq::LIFE_ON_SWITCH_PARAM, 0.0, 1.0, 0.0)); addParam(ParamWidget::create(Vec(125, 345), module, NoteSeq::LIFE_SPEED_KNOB_PARAM, 16.0, 1.0, 4.0)); ///////////////////////////////////////////////////// RIGHT SIDE ///////////////////////////////////////////////////// float outputRowTop = 35.0; float outputRowDist = 21.0; for(int i=0;i(Vec(559.081, outputRowTop + i * outputRowDist), Port::OUTPUT, module, NoteSeq::VOCT_MAIN_OUTPUT + paramIdx)); //param # from bottom up addChild(ModuleLightWidget::create>(Vec(580, (outputRowTop+3) + i * outputRowDist), module, NoteSeq::GATES_LIGHT + paramIdx)); addOutput(Port::create(Vec(591.858, outputRowTop + i * outputRowDist), Port::OUTPUT, module, NoteSeq::GATE_MAIN_OUTPUT + paramIdx)); //param # from bottom up } addOutput(Port::create(Vec(656.303, outputRowTop), Port::OUTPUT, module, NoteSeq::MIN_VOCT_OUTPUT)); addOutput(Port::create(Vec(689.081, outputRowTop), Port::OUTPUT, module, NoteSeq::MIN_GATE_OUTPUT)); addOutput(Port::create(Vec(656.303, outputRowTop + outputRowDist), Port::OUTPUT, module, NoteSeq::MID_VOCT_OUTPUT)); addOutput(Port::create(Vec(689.081, outputRowTop + outputRowDist), Port::OUTPUT, module, NoteSeq::MID_GATE_OUTPUT)); addOutput(Port::create(Vec(656.303, outputRowTop + 2*outputRowDist), Port::OUTPUT, module, NoteSeq::MAX_VOCT_OUTPUT)); addOutput(Port::create(Vec(689.081, outputRowTop + 2*outputRowDist), Port::OUTPUT, module, NoteSeq::MAX_GATE_OUTPUT)); addOutput(Port::create(Vec(656.303, outputRowTop + 3*outputRowDist), Port::OUTPUT, module, NoteSeq::RND_VOCT_OUTPUT)); addOutput(Port::create(Vec(689.081, outputRowTop + 3*outputRowDist), Port::OUTPUT, module, NoteSeq::RND_GATE_OUTPUT)); addInput(Port::create(Vec(643, 152), Port::INPUT, module, NoteSeq::HIGHEST_NOTE_INPUT)); addParam(ParamWidget::create(Vec(663, 147), module, NoteSeq::HIGHEST_NOTE_PARAM, 1.0, 32.0, 32.0)); addInput(Port::create(Vec(643, 195), Port::INPUT, module, NoteSeq::LOWEST_NOTE_INPUT)); addParam(ParamWidget::create(Vec(663, 190), module, NoteSeq::LOWEST_NOTE_PARAM, 1.0, 32.0, 1.0)); addParam(ParamWidget::create(Vec(654, 236), module, NoteSeq::INCLUDE_INACTIVE_PARAM, 0.0, 1.0, 0.0)); addParam(ParamWidget::create(Vec(652, 276), module, NoteSeq::OCTAVE_KNOB_PARAM, -5.0, 7.0, 2.0)); ///// NOTE AND SCALE CONTROLS ///// float pitchParamYVal = 320; float labelY = 178; NoteKnob *noteKnob = dynamic_cast(ParamWidget::create(Vec(626, pitchParamYVal), module, NoteSeq::NOTE_KNOB_PARAM, 0.0, QuantizeUtils::NUM_NOTES-1, QuantizeUtils::NOTE_C)); CenteredLabel* const noteLabel = new CenteredLabel; noteLabel->box.pos = Vec(319, labelY); noteLabel->text = ""; noteKnob->connectLabel(noteLabel); addChild(noteLabel); addParam(noteKnob); ScaleKnob *scaleKnob = dynamic_cast(ParamWidget::create(Vec(677, pitchParamYVal), module, NoteSeq::SCALE_KNOB_PARAM, 0.0, QuantizeUtils::NUM_SCALES-1, QuantizeUtils::MINOR)); CenteredLabel* const scaleLabel = new CenteredLabel; scaleLabel->box.pos = Vec(345, labelY); scaleLabel->text = ""; scaleKnob->connectLabel(scaleLabel); addChild(scaleLabel); addParam(scaleKnob); } } // namespace rack_plugin_JW_Modules using namespace rack_plugin_JW_Modules; RACK_PLUGIN_MODEL_INIT(JW_Modules, NoteSeq) { Model *modelNoteSeq = Model::create("JW-Modules", "NoteSeq", "NoteSeq", SEQUENCER_TAG); return modelNoteSeq; }