|
| 1 | +#include "plugin.hpp" |
| 2 | +#include "musiclib.hpp" |
| 3 | + |
| 4 | +struct DiatonicCV : Module { |
| 5 | + enum ParamIds { |
| 6 | + OCTAVE_PARAM, |
| 7 | + CHORD_PARAM, |
| 8 | + TYPE_PARAM, |
| 9 | + INVERSION_PARAM, |
| 10 | + VOICING_PARAM, |
| 11 | + NUM_PARAMS |
| 12 | + }; |
| 13 | + enum InputIds { |
| 14 | + POLY_INPUT, |
| 15 | + OCTAVE_INPUT, |
| 16 | + CHORD_INPUT, |
| 17 | + TYPE_INPUT, |
| 18 | + INVERSION_INPUT, |
| 19 | + VOICING_INPUT, |
| 20 | + NUM_INPUTS |
| 21 | + }; |
| 22 | + enum OutputIds { |
| 23 | + POLY_OUTPUT, |
| 24 | + NUM_OUTPUTS |
| 25 | + }; |
| 26 | + enum LightIds { |
| 27 | + NUM_LIGHTS |
| 28 | + }; |
| 29 | + |
| 30 | + int octave = 4; |
| 31 | + int chord = 0; |
| 32 | + int chord_type = 0; |
| 33 | + bool inverted = false; |
| 34 | + bool hasPoly = true; |
| 35 | + int bass_note = 0; |
| 36 | + float polyNotes_v[16] = {0.0f,0.0f,0.0f,0.0f,0.0f,0.0f,0.0f,0.0f,0.0f,0.0f,0.0f,0.0f,0.0f,0.0f,0.0f,0.0f}; |
| 37 | + int polyNotes[16] = {48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48}; |
| 38 | + int polyChannels = 0; |
| 39 | + int inversion = 0; |
| 40 | + int voicing = 0; |
| 41 | + struct chord playing_chord; |
| 42 | + struct scale cmajor; |
| 43 | + |
| 44 | + RefreshCounter refresh; |
| 45 | + |
| 46 | + DiatonicCV() { |
| 47 | + config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); |
| 48 | + configParam(OCTAVE_PARAM, -4.0, 4.0, 0.0, "Octave"); |
| 49 | + configParam(CHORD_PARAM, 0, 6.0, 0.0, "Chord (I - VII)"); |
| 50 | + configParam(TYPE_PARAM, 0, 2.0, 0.0, "Chord Type"); |
| 51 | + configParam(INVERSION_PARAM, 0.0, 4.0, 0.0, "Inversion"); |
| 52 | + configParam(VOICING_PARAM, 0.0, 4.0, 0.0, "Voicing"); |
| 53 | + |
| 54 | + configInput(POLY_INPUT, "Polyphonic"); |
| 55 | + configInput(OCTAVE_INPUT, "Octave"); |
| 56 | + configInput(CHORD_INPUT, "Chord"); |
| 57 | + configInput(TYPE_INPUT, "Chord Type"); |
| 58 | + configInput(INVERSION_INPUT, "Inversion"); |
| 59 | + configInput(VOICING_INPUT, "Voicing"); |
| 60 | + |
| 61 | + configOutput(POLY_OUTPUT, "Polyphonic"); |
| 62 | + |
| 63 | + cmajor = get_scale(0,0); |
| 64 | + } |
| 65 | + |
| 66 | + void process(const ProcessArgs& args) override; |
| 67 | +}; |
| 68 | + |
| 69 | +void DiatonicCV::process(const ProcessArgs &args){ |
| 70 | + if (refresh.processInputs()) { |
| 71 | + if(inputs[POLY_INPUT].isConnected()){ |
| 72 | + polyChannels = inputs[POLY_INPUT].getChannels(); |
| 73 | + for (int c = 0; c < 16; c++) { |
| 74 | + float v = inputs[POLY_INPUT].getVoltage(c); |
| 75 | + polyNotes_v[c] = v; |
| 76 | + polyNotes[c] = voltage_to_note_int(v); |
| 77 | + } |
| 78 | + //sort the notes in ascending order |
| 79 | + std::sort(std::begin(polyNotes), polyNotes + polyChannels); |
| 80 | + }else{ |
| 81 | + //Just make it C Major |
| 82 | + polyChannels = 7; |
| 83 | + for(int t=0; t<7; t++){ |
| 84 | + polyNotes[t] = cmajor.notes[t]; |
| 85 | + } |
| 86 | + } |
| 87 | + |
| 88 | + float octave_v = params[OCTAVE_PARAM].getValue(); |
| 89 | + if(inputs[OCTAVE_INPUT].isConnected()){ |
| 90 | + octave_v = inputs[OCTAVE_INPUT].getVoltage(); |
| 91 | + } |
| 92 | + octave = (int)round(octave_v) + 4; |
| 93 | + |
| 94 | + float chord_v = params[CHORD_PARAM].getValue(); |
| 95 | + if(inputs[CHORD_INPUT].isConnected()){ |
| 96 | + chord_v = inputs[CHORD_INPUT].getVoltage(); |
| 97 | + if(chord_v < 0.0f) chord_v = 0.0f; |
| 98 | + if(chord_v > 7.0f) chord_v = 7.0f; |
| 99 | + } |
| 100 | + chord = (int)round(chord_v); |
| 101 | + |
| 102 | + float type_v = params[TYPE_PARAM].getValue(); |
| 103 | + if(inputs[TYPE_INPUT].isConnected()){ |
| 104 | + type_v = inputs[TYPE_INPUT].getVoltage(); |
| 105 | + if(type_v < 0.0f) type_v = 0.0f; |
| 106 | + if(type_v > 3.0f) type_v = 3.0f; |
| 107 | + } |
| 108 | + chord_type = (int)round(type_v); |
| 109 | + |
| 110 | + //inversion |
| 111 | + inversion = (int)round(params[INVERSION_PARAM].getValue()); |
| 112 | + if(inputs[INVERSION_PARAM].isConnected()){ |
| 113 | + inversion = (int)clamp(round(inputs[INVERSION_PARAM].getVoltage()),0.0f,3.0f); |
| 114 | + } |
| 115 | + |
| 116 | + //voicing |
| 117 | + voicing = (int)round(params[VOICING_PARAM].getValue()); |
| 118 | + if(inputs[VOICING_PARAM].isConnected()){ |
| 119 | + voicing = (int)clamp(round(inputs[VOICING_PARAM].getVoltage()),0.0f,4.0f); |
| 120 | + } |
| 121 | + |
| 122 | + //Make the chord |
| 123 | + playing_chord = get_diatonic_chord(polyNotes, polyChannels, octave, chord, chord_type, inversion, voicing); |
| 124 | + } |
| 125 | + |
| 126 | + if (refresh.processLights()) { |
| 127 | + |
| 128 | + } |
| 129 | + |
| 130 | + outputs[POLY_OUTPUT].setChannels(playing_chord.num_notes); |
| 131 | + for(int t=0; t<playing_chord.num_notes; t++){ |
| 132 | + outputs[POLY_OUTPUT].setVoltage(note_to_voltage(playing_chord.notes[t]),t); |
| 133 | + } |
| 134 | +} |
| 135 | + |
| 136 | + |
| 137 | +struct DiatonicCVWidget : ModuleWidget { |
| 138 | + struct ChordDisplayWidget : TransparentWidget { |
| 139 | + DiatonicCV* module; |
| 140 | + char text[10]; |
| 141 | + |
| 142 | + ChordDisplayWidget(Vec _pos, Vec _size, DiatonicCV* _module) { |
| 143 | + box.size = _size; |
| 144 | + box.pos = _pos.minus(_size.div(2)); |
| 145 | + module = _module; |
| 146 | + } |
| 147 | + |
| 148 | + void draw(const DrawArgs &args) override { |
| 149 | + std::shared_ptr<Font> font = APP->window->loadFont(asset::plugin(pluginInstance, "res/fonts/PixelOperator.ttf")); |
| 150 | + if(font){ |
| 151 | + NVGcolor textColor = prepareDisplay(args.vg, &box, 22); |
| 152 | + nvgFontFaceId(args.vg, font->handle); |
| 153 | + nvgTextLetterSpacing(args.vg, -1.5); |
| 154 | + nvgTextAlign(args.vg, NVG_ALIGN_CENTER); |
| 155 | + |
| 156 | + Vec textPos = Vec(box.size.x/2, 21.0f); |
| 157 | + nvgFillColor(args.vg, textColor); |
| 158 | + |
| 159 | + if (module != NULL && module->playing_chord.num_notes > 2){ |
| 160 | + detect_chord_name_simple(module->playing_chord,text); |
| 161 | + }else{ |
| 162 | + snprintf(text, 9, " "); |
| 163 | + } |
| 164 | + |
| 165 | + nvgText(args.vg, textPos.x, textPos.y, text, NULL); |
| 166 | + } |
| 167 | + } |
| 168 | + |
| 169 | + }; |
| 170 | + |
| 171 | + DiatonicCVWidget(DiatonicCV* module) { |
| 172 | + setModule(module); |
| 173 | + setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/DiatonicCV.svg"))); |
| 174 | + |
| 175 | + addChild(createWidget<ScrewSilver>(Vec(RACK_GRID_WIDTH, 0))); |
| 176 | + addChild(createWidget<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0))); |
| 177 | + addChild(createWidget<ScrewSilver>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); |
| 178 | + addChild(createWidget<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); |
| 179 | + |
| 180 | + const int centerX = box.size.x / 2; |
| 181 | + |
| 182 | + ChordDisplayWidget* display = new ChordDisplayWidget(Vec(centerX, 55), Vec(76, 29), module); |
| 183 | + addChild(display); |
| 184 | + |
| 185 | + addInput(createInputCentered<PJ301MPort>(Vec(centerX, 95), module, DiatonicCV::POLY_INPUT)); |
| 186 | + |
| 187 | + const int offsetXL = 40; |
| 188 | + const int spacingY = 45; |
| 189 | + const int y1 = 134; |
| 190 | + const int y2 = y1 + spacingY; |
| 191 | + const int y3 = y2 + spacingY; |
| 192 | + |
| 193 | + |
| 194 | + addParam(createParamCentered<Rogan2PWhite>(Vec(centerX,y1), module, DiatonicCV::OCTAVE_PARAM)); |
| 195 | + addInput(createInputCentered<PJ301MPort>(Vec(centerX - offsetXL, y1), module, DiatonicCV::OCTAVE_INPUT)); |
| 196 | + |
| 197 | + addParam(createParamCentered<Rogan2PWhite>(Vec(centerX,y2), module, DiatonicCV::CHORD_PARAM)); |
| 198 | + addInput(createInputCentered<PJ301MPort>(Vec(centerX - offsetXL, y2), module, DiatonicCV::CHORD_INPUT)); |
| 199 | + |
| 200 | + addParam(createParamCentered<Rogan2PWhite>(Vec(centerX,y3), module, DiatonicCV::TYPE_PARAM)); |
| 201 | + addInput(createInputCentered<PJ301MPort>(Vec(centerX - offsetXL, y3), module, DiatonicCV::TYPE_INPUT)); |
| 202 | + |
| 203 | + static const int offsetX2 = 14; |
| 204 | + static const int posY = 269; |
| 205 | + |
| 206 | + const int offsetXL2 = 42; |
| 207 | + |
| 208 | + addParam(createParamCentered<RoundSmallBlackKnob>(Vec(centerX - offsetX2,posY), module, DiatonicCV::INVERSION_PARAM)); |
| 209 | + addInput(createInputCentered<PJ301MPort>(Vec(centerX - offsetXL2, posY), module, DiatonicCV::INVERSION_INPUT)); |
| 210 | + |
| 211 | + addParam(createParamCentered<RoundSmallBlackKnob>(Vec(centerX + offsetX2,posY), module, DiatonicCV::VOICING_PARAM)); |
| 212 | + addInput(createInputCentered<PJ301MPort>(Vec(centerX + offsetXL2, posY), module, DiatonicCV::VOICING_INPUT)); |
| 213 | + |
| 214 | + addOutput(createOutputCentered<PJ301MPort>(Vec(centerX, 332), module, DiatonicCV::POLY_OUTPUT)); |
| 215 | + } |
| 216 | +}; |
| 217 | + |
| 218 | + |
| 219 | +Model* modelDiatonicCV = createModel<DiatonicCV, DiatonicCVWidget>("DiatonicCV"); |
0 commit comments