/////////////////////////////////////////////////////////////////// // // dBiz revisited version of // // Cartesian Sequencer Module for VCV // many thx to // Strum 2017 // strum@softhome.net // /////////////////////////////////////////////////////////////////// #include "dBiz.hpp" #include "dsp/digital.hpp" using namespace std; namespace rack_plugin_dBiz { struct Bene : Module { enum ParamIds { ROOT_NOTE_PARAM, SCALE_PARAM, // QUANT_PARAM, KNOB_PARAM, NUM_PARAMS = KNOB_PARAM + 16 }; enum InputIds { ROOT_NOTE_INPUT, SCALE_INPUT, UP, DOWN, LEFT, RIGHT, X_PAD, Y_PAD, G_PAD, RESET, X_RESET, Y_RESET, NUM_INPUTS }; enum OutputIds { UNQUANT_OUT, QUANT_OUT, ROW_OUT, COLUMN_OUT = ROW_OUT + 4, NUM_OUTPUTS = COLUMN_OUT + 4 }; enum LightIds { GRID_LIGHTS, NUM_LIGHTS = GRID_LIGHTS + 16 }; //copied & fixed these scales http://www.grantmuller.com/MidiReference/doc/midiReference/ScaleReference.html int SCALE_AEOLIAN [7] = {0, 2, 3, 5, 7, 8, 10}; int SCALE_BLUES [6] = {0, 3, 5, 6, 7, 10}; //FIXED! int SCALE_CHROMATIC [12]= {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}; int SCALE_DIATONIC_MINOR [7] = {0, 2, 3, 5, 7, 8, 10}; int SCALE_DORIAN [7] = {0, 2, 3, 5, 7, 9, 10}; int SCALE_HARMONIC_MINOR [7] = {0, 2, 3, 5, 7, 8, 11}; int SCALE_INDIAN [7] = {0, 1, 1, 4, 5, 8, 10}; int SCALE_LOCRIAN [7] = {0, 1, 3, 5, 6, 8, 10}; int SCALE_LYDIAN [7] = {0, 2, 4, 6, 7, 9, 10}; int SCALE_MAJOR [7] = {0, 2, 4, 5, 7, 9, 11}; int SCALE_MELODIC_MINOR [9] = {0, 2, 3, 5, 7, 8, 9, 10, 11}; int SCALE_MINOR [7] = {0, 2, 3, 5, 7, 8, 10}; int SCALE_MIXOLYDIAN [7] = {0, 2, 4, 5, 7, 9, 10}; int SCALE_NATURAL_MINOR [7] = {0, 2, 3, 5, 7, 8, 10}; int SCALE_PENTATONIC [5] = {0, 2, 4, 7, 9}; int SCALE_PHRYGIAN [7] = {0, 1, 3, 5, 7, 8, 10}; int SCALE_TURKISH [7] = {0, 1, 3, 5, 7, 10, 11}; enum Notes { NOTE_C, NOTE_C_SHARP, NOTE_D, NOTE_D_SHARP, NOTE_E, NOTE_F, NOTE_F_SHARP, NOTE_G, NOTE_G_SHARP, NOTE_A, NOTE_A_SHARP, NOTE_B, NUM_NOTES }; enum Scales { AEOLIAN, BLUES, CHROMATIC, DIATONIC_MINOR, DORIAN, HARMONIC_MINOR, INDIAN, LOCRIAN, LYDIAN, MAJOR, MELODIC_MINOR, MINOR, MIXOLYDIAN, NATURAL_MINOR, PENTATONIC, PHRYGIAN, TURKISH, NONE, NUM_SCALES }; SchmittTrigger leftTrigger; SchmittTrigger rightTrigger; SchmittTrigger upTrigger; SchmittTrigger downTrigger; SchmittTrigger resetTrigger; SchmittTrigger x_resetTrigger; SchmittTrigger y_resetTrigger; SchmittTrigger button_triggers[4][4]; float row_outs[4] = {0.0,0.0,0.0,0.0}; float column_outs[4] = {0.0,0.0,0.0,0.0}; int x_position = 0; int y_position = 0; int rootNote = 0; int curScaleVal = 0; float pitch = 0; float previousPitch = 0; Bene() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) {} void step() override; // Quantization based on JW quantizer module!!! float closestVoltageInScale(float voltsIn) { rootNote = params[ROOT_NOTE_PARAM].value + rescale(inputs[ROOT_NOTE_INPUT].value, 0,10,0, Bene::NUM_NOTES - 1); curScaleVal =params[SCALE_PARAM].value + rescale(inputs[SCALE_INPUT].value, 0,10,0, Bene::NUM_SCALES - 1); int *curScaleArr; int notesInScale = 0; switch (curScaleVal) { case AEOLIAN: curScaleArr = SCALE_AEOLIAN; notesInScale = LENGTHOF(SCALE_AEOLIAN); break; case BLUES: curScaleArr = SCALE_BLUES; notesInScale = LENGTHOF(SCALE_BLUES); break; case CHROMATIC: curScaleArr = SCALE_CHROMATIC; notesInScale = LENGTHOF(SCALE_CHROMATIC); break; case DIATONIC_MINOR: curScaleArr = SCALE_DIATONIC_MINOR; notesInScale = LENGTHOF(SCALE_DIATONIC_MINOR); break; case DORIAN: curScaleArr = SCALE_DORIAN; notesInScale = LENGTHOF(SCALE_DORIAN); break; case HARMONIC_MINOR: curScaleArr = SCALE_HARMONIC_MINOR; notesInScale = LENGTHOF(SCALE_HARMONIC_MINOR); break; case INDIAN: curScaleArr = SCALE_INDIAN; notesInScale = LENGTHOF(SCALE_INDIAN); break; case LOCRIAN: curScaleArr = SCALE_LOCRIAN; notesInScale = LENGTHOF(SCALE_LOCRIAN); break; case LYDIAN: curScaleArr = SCALE_LYDIAN; notesInScale = LENGTHOF(SCALE_LYDIAN); break; case MAJOR: curScaleArr = SCALE_MAJOR; notesInScale = LENGTHOF(SCALE_MAJOR); break; case MELODIC_MINOR: curScaleArr = SCALE_MELODIC_MINOR; notesInScale = LENGTHOF(SCALE_MELODIC_MINOR); break; case MINOR: curScaleArr = SCALE_MINOR; notesInScale = LENGTHOF(SCALE_MINOR); break; case MIXOLYDIAN: curScaleArr = SCALE_MIXOLYDIAN; notesInScale = LENGTHOF(SCALE_MIXOLYDIAN); break; case NATURAL_MINOR: curScaleArr = SCALE_NATURAL_MINOR; notesInScale = LENGTHOF(SCALE_NATURAL_MINOR); break; case PENTATONIC: curScaleArr = SCALE_PENTATONIC; notesInScale = LENGTHOF(SCALE_PENTATONIC); break; case PHRYGIAN: curScaleArr = SCALE_PHRYGIAN; notesInScale = LENGTHOF(SCALE_PHRYGIAN); break; case TURKISH: curScaleArr = SCALE_TURKISH; notesInScale = LENGTHOF(SCALE_TURKISH); break; case NONE: return voltsIn; } float closestVal = 10.0; float closestDist = 10.0; float scaleNoteInVolts = 0; float distAway = 0; int octaveInVolts = int(floorf(voltsIn)); float voltMinusOct = voltsIn - octaveInVolts; for (int i=0; i < notesInScale; i++) { scaleNoteInVolts = curScaleArr[i] / 12.0; distAway = fabs(voltMinusOct - scaleNoteInVolts); if(distAway < closestDist){ closestVal = scaleNoteInVolts; closestDist = distAway; } } return octaveInVolts + rootNote/12.0 + closestVal; } }; void Bene::step() { bool step_right = false; bool step_left = false; bool step_up = false; bool step_down = false; lights[GRID_LIGHTS+x_position+y_position*4].value =1.0; // handle clock inputs if (inputs[RIGHT].active) { if (rightTrigger.process(inputs[RIGHT].value)) { step_right = true; } } if (inputs[LEFT].active) { if (leftTrigger.process(inputs[LEFT].value)) { step_left = true; } } if (inputs[DOWN].active) { if (downTrigger.process(inputs[DOWN].value)) { step_down = true; } } if (inputs[UP].active) { if (upTrigger.process(inputs[UP].value)) { step_up = true; } } // resets if (resetTrigger.process(inputs[RESET].value)) { lights[GRID_LIGHTS + x_position + y_position*4].value = 0.0; x_position = 0; y_position = 0; lights[GRID_LIGHTS + x_position + y_position*4].value = 1.0; step_right = false; step_left = false; step_up = false; step_down = false; } if (x_resetTrigger.process(inputs[X_RESET].value)) { lights[GRID_LIGHTS + x_position + y_position*4].value = 0.0; x_position = 0; lights[GRID_LIGHTS + x_position + y_position*4].value = 1.0; step_right = false; step_left = false; step_up = false; step_down = false; } if (y_resetTrigger.process(inputs[Y_RESET].value)) { lights[GRID_LIGHTS + x_position + y_position*4].value = 0.0; y_position = 0; lights[GRID_LIGHTS + x_position + y_position*4].value = 1.0; step_right = false; step_left = false; step_up = false; step_down = false; } // handle button triggers int xpad = round(inputs[X_PAD].value); int ypad = round(inputs[Y_PAD].value); bool gated = inputs[G_PAD].value > 0.0; if (gated) { for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { lights[GRID_LIGHTS + x_position + y_position*4].value = 0.0; x_position = xpad-1; y_position = ypad-1; lights[GRID_LIGHTS + x_position + y_position*4].value = 1.0; } } } // change x and y if (step_right) { lights[GRID_LIGHTS + x_position + y_position*4].value = 0.0; x_position += 1; if (x_position > 3) x_position = 0; lights[GRID_LIGHTS + x_position + y_position*4].value = 1.0; } if (step_left) { lights[GRID_LIGHTS + x_position + y_position*4].value = 0.0; x_position -= 1; if (x_position < 0) x_position = 3; lights[GRID_LIGHTS + x_position + y_position*4].value = 1.0; } if (step_down) { lights[GRID_LIGHTS + x_position + y_position*4].value = 0.0; y_position += 1; if (y_position > 3) y_position = 0; lights[GRID_LIGHTS + x_position + y_position*4].value = 1.0; } if (step_up) { lights[GRID_LIGHTS + x_position + y_position*4].value = 0.0; y_position -= 1; if (y_position < 0) y_position = 3; lights[GRID_LIGHTS + x_position + y_position*4].value = 1.0; } /// set outputs int which_knob = y_position * 4 + x_position; //float main_out = params[KNOB_PARAM + which_knob].value; // int oct = round(main_out); // float left = main_out - oct; // int semi = round(left * 12); // float quant_out = oct + semi/12.0; // for (int i = 0 ; i < 4 ; i++) { float main_out = params[KNOB_PARAM + which_knob].value; float quant_out = closestVoltageInScale(params[KNOB_PARAM + which_knob].value); row_outs[i] = closestVoltageInScale(params[KNOB_PARAM + y_position * 4 + i].value); column_outs[i] = closestVoltageInScale(params[KNOB_PARAM + x_position + i * 4].value); /* roct[i] = round(row_outs[i]); rleft[i] = row_outs[i] - roct[i]; rsemi[i] = round(rleft[i] * 12); rquant_out[i] = roct[i] + rsemi[i] / 12.0; coct[i] = round(column_outs[i]); cleft[i] = column_outs[i] - roct[i]; csemi[i] = round(cleft[i] * 12); cquant_out[i] = coct[i] + csemi[i] / 12.0; */ outputs[ROW_OUT + i].value = row_outs[i]; outputs[COLUMN_OUT + i].value = column_outs[i]; outputs[UNQUANT_OUT].value = main_out; outputs[QUANT_OUT].value = quant_out; } } //////////////////////////////////// Display --- Based on DTROY by Bidoo struct BeneDisplay : TransparentWidget { Bene *module; int frame = 0; shared_ptr font; string note, scale; BeneDisplay() { font = Font::load(assetPlugin(plugin, "res/DejaVuSansMono.ttf")); } void drawMessage(NVGcontext *vg, Vec pos, string note,string scale) { nvgFontSize(vg, 18); nvgFontFaceId(vg, font->handle); nvgTextLetterSpacing(vg, -2); nvgFillColor(vg, nvgRGBA(75, 199, 75, 0xff)); nvgFontSize(vg, 14); nvgFillColor(vg, nvgRGBA(0xff, 0xff, 0xff, 0xff)); nvgText(vg, pos.x + 8, pos.y + 23, note.c_str(), NULL); nvgText(vg, pos.x + 30, pos.y + 23, scale.c_str(), NULL); } string displayRootNote(int value) { switch (value) { case Bene::NOTE_C: return "C"; case Bene::NOTE_C_SHARP: return "C#"; case Bene::NOTE_D: return "D"; case Bene::NOTE_D_SHARP: return "D#"; case Bene::NOTE_E: return "E"; case Bene::NOTE_F: return "F"; case Bene::NOTE_F_SHARP: return "F#"; case Bene::NOTE_G: return "G"; case Bene::NOTE_G_SHARP: return "G#"; case Bene::NOTE_A: return "A"; case Bene::NOTE_A_SHARP: return "A#"; case Bene::NOTE_B: return "B"; default: return ""; } } string displayScale(int value) { switch (value) { case Bene::AEOLIAN: return "Aeolian"; case Bene::BLUES: return "Blues"; case Bene::CHROMATIC: return "Chromatic"; case Bene::DIATONIC_MINOR: return "Diat. Min."; case Bene::DORIAN: return "Dorian"; case Bene::HARMONIC_MINOR: return "Harm. Min."; case Bene::INDIAN: return "Indian"; case Bene::LOCRIAN: return "Locrian"; case Bene::LYDIAN: return "Lydian"; case Bene::MAJOR: return "Major"; case Bene::MELODIC_MINOR: return "Melo. Min."; case Bene::MINOR: return "Minor"; case Bene::MIXOLYDIAN: return "Mixolydian"; case Bene::NATURAL_MINOR: return "Nat. Min."; case Bene::PENTATONIC: return "Pentatonic"; case Bene::PHRYGIAN: return "Phrygian"; case Bene::TURKISH: return "Turkish"; case Bene::NONE: return "None"; default: return ""; } } void draw(NVGcontext *vg) override { if (++frame >= 4) { frame = 0; note = displayRootNote(module->rootNote); scale = displayScale(module->curScaleVal); } drawMessage(vg, Vec(0, 20), note, scale); } }; //////////////////////////////// struct BeneWidget : ModuleWidget { BeneWidget(Bene *module) : ModuleWidget(module) { box.size = Vec(15*13, 380); int top = 20; int top2 = 35; int left = 8; int column_spacing = 35; int row_spacing = 35; { SVGPanel *panel = new SVGPanel(); panel->box.size = box.size; panel->setBackground(SVG::load(assetPlugin(plugin,"res/Bene.svg"))); addChild(panel); } { BeneDisplay *display = new BeneDisplay(); display->module = module; display->box.pos = Vec(left, top + 105); display->box.size = Vec(250, 60); addChild(display); } addInput(Port::create(Vec(left, top), Port::INPUT, module, Bene::LEFT)); addInput(Port::create(Vec(left+column_spacing, top), Port::INPUT, module, Bene::RIGHT)); addInput(Port::create(Vec(left, top + 40), Port::INPUT, module, Bene::UP)); addInput(Port::create(Vec(left + column_spacing, top + 40), Port::INPUT, module, Bene::DOWN)); addInput(Port::create(Vec(left+column_spacing * 2, top), Port::INPUT, module, Bene::X_RESET)); addInput(Port::create(Vec(left + column_spacing * 2, top + 40), Port::INPUT, module, Bene::Y_RESET)); addInput(Port::create(Vec(left , top+85), Port::INPUT, module, Bene::X_PAD)); addInput(Port::create(Vec(left + column_spacing , top + 85), Port::INPUT, module, Bene::Y_PAD)); addInput(Port::create(Vec(left + column_spacing * 2, top + 85), Port::INPUT, module, Bene::G_PAD)); addInput(Port::create(Vec(left + column_spacing * 3, top ), Port::INPUT, module, Bene::RESET)); addOutput(Port::create(Vec(left + column_spacing * 5-20, top), Port::OUTPUT, module, Bene::UNQUANT_OUT)); addOutput(Port::create(Vec(left + column_spacing * 5-20, top+30), Port::OUTPUT, module, Bene::QUANT_OUT)); for ( int i = 0 ; i < 4 ; i++) { for ( int j = 0 ; j < 4 ; j++) { addParam(ParamWidget::create(Vec(left+column_spacing * i, top2 + row_spacing * j + 150 ), module, Bene::KNOB_PARAM + i + j * 4, 0.0, 2.0, 1.0)); addChild(GrayModuleLightWidget::create>(Vec(left + column_spacing * i + 8, top2 + row_spacing * j + 150 + 8), module, Bene::GRID_LIGHTS + i + j * 4)); } addOutput(Port::create(Vec(left+column_spacing * i+5, top2 + row_spacing * 4 + 155 ), Port::OUTPUT, module, Bene::ROW_OUT + i)); addOutput(Port::create(Vec(left+column_spacing * 4+5, top2 + row_spacing * i + 155 ), Port::OUTPUT, module, Bene::COLUMN_OUT + i)); } addParam(ParamWidget::create(Vec(left + column_spacing*3-5, top + 85 + row_spacing), module, Bene::ROOT_NOTE_PARAM, 0.0, Bene::NUM_NOTES - 1 + 0.1, 0)); addParam(ParamWidget::create(Vec(left + column_spacing*4 , top + 85 + row_spacing), module, Bene::SCALE_PARAM, 0.0, Bene::NUM_SCALES - 1 + 0.1, 0)); addInput(Port::create(Vec(column_spacing * 4-25, top + 85), Port::INPUT, module, Bene::ROOT_NOTE_INPUT)); addInput(Port::create(Vec(column_spacing * 4 +15, top + 85), Port::INPUT, module, Bene::SCALE_INPUT)); addChild(Widget::create(Vec(15, 0))); addChild(Widget::create(Vec(box.size.x-30, 0))); addChild(Widget::create(Vec(15, 365))); addChild(Widget::create(Vec(box.size.x-30, 365))); } }; } // namespace rack_plugin_dBiz using namespace rack_plugin_dBiz; RACK_PLUGIN_MODEL_INIT(dBiz, Bene) { Model *modelBene = Model::create("dBiz", "Bene", "Bene", SEQUENCER_TAG); return modelBene; }