#include #include #include "dsp/digital.hpp" #include "moDllz.hpp" #include "midi.hpp" #include "dsp/filter.hpp" /* * MIDIdualCV converts upper/lower midi note on/off events, velocity , channel aftertouch, pitch wheel, mod wheel breath cc and expression to CV */ namespace rack_plugin_moDllz { struct MidiNoteData { uint8_t velocity = 0; uint8_t aftertouch = 0; }; struct MIDIdualCV : Module { enum ParamIds { RESETMIDI_PARAM, LWRRETRGGMODE_PARAM, UPRRETRGGMODE_PARAM, PBDUALPOLARITY_PARAM, SUSTAINHOLD_PARAM, NUM_PARAMS }; enum InputIds { NUM_INPUTS }; enum OutputIds { PITCH_OUTPUT_Lwr, PITCH_OUTPUT_Upr, VELOCITY_OUTPUT_Lwr, VELOCITY_OUTPUT_Upr, RETRIGGATE_OUTPUT_Lwr, RETRIGGATE_OUTPUT_Upr, GATE_OUTPUT, PBEND_OUTPUT, MOD_OUTPUT, EXPRESSION_OUTPUT, BREATH_OUTPUT, SUSTAIN_OUTPUT, PRESSURE_OUTPUT, NUM_OUTPUTS }; enum LightIds { RESETMIDI_LIGHT, NUM_LIGHTS }; MidiInputQueue midiInput; uint8_t mod = 0; ExponentialFilter modFilter; uint8_t breath = 0; ExponentialFilter breathFilter; uint8_t expression = 0; ExponentialFilter exprFilter; uint16_t pitch = 8192; ExponentialFilter pitchFilter; uint8_t sustain = 0; ExponentialFilter sustainFilter; uint8_t pressure = 0; ExponentialFilter pressureFilter; MidiNoteData noteData[128]; struct noteLive{ int note = 0; uint8_t vel = 0; bool trig = false; }; noteLive lowerNote; noteLive upperNote; bool anynoteGate = false; bool sustpedal = false; PulseGenerator gatePulse; SchmittTrigger resetMidiTrigger; MIDIdualCV() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) {} bool noteupdated = false; /////////////////// //// //// //// ///////////////////// ///////////////// /////////////// ///////// //////////// ////// //////////////////// ///////////////// //////// ///////// /////// ////////////////////// /////////////////////// /////// ///////// //////////// //////////////////////////// ////////////// ///////// ///////// ///// //////////////////////////// void step() override { MidiMessage msg; while (midiInput.shift(&msg)) { processMessage(msg); } ///reset triggers... lowerNote.trig = false; upperNote.trig = false; /////////////////////// if (noteupdated){ anynoteGate = false; noteupdated = false; ///LOWER/// for (int i = 0; i < 128; i++){ if (noteData[i].velocity > 0){ anynoteGate = true; /////// trigger !! gatePulse.trigger(1e-3); ///////// if (i < lowerNote.note) lowerNote.trig = true; lowerNote.note = i; lowerNote.vel = noteData[i].velocity; break; } } if (anynoteGate){ ///UPPER/// for (int i = 127; i > -1; i--){ if (noteData[i].velocity > 0){ if (i > upperNote.note) upperNote.trig = true; upperNote.note = i; upperNote.vel = noteData[i].velocity; break; } } }else{ lowerNote.note = 128; upperNote.note = -1; } if (lowerNote.note < 128) outputs[PITCH_OUTPUT_Lwr].value = static_cast(lowerNote.note - 60) / 12.0f; if (upperNote.note > -1) outputs[PITCH_OUTPUT_Upr].value = static_cast(upperNote.note - 60) / 12.0f; if (lowerNote.vel > 0) outputs[VELOCITY_OUTPUT_Lwr].value = static_cast(lowerNote.vel) / 127.0f * 10.0f; if (upperNote.vel > 0) outputs[VELOCITY_OUTPUT_Upr].value = static_cast(upperNote.vel) / 127.0 * 10.0; } bool retriggLwr = lowerNote.trig || params[LWRRETRGGMODE_PARAM].value > 0.5; bool retriggUpr = upperNote.trig || params[UPRRETRGGMODE_PARAM].value > 0.5; bool pulse = gatePulse.process(1.0 / engineGetSampleRate()); bool gateout = anynoteGate || sustpedal; outputs[RETRIGGATE_OUTPUT_Lwr].value = gateout && !(retriggLwr && pulse)? 10.0f : 0.0f ; outputs[RETRIGGATE_OUTPUT_Upr].value = gateout && !(retriggUpr && pulse)? 10.0f : 0.0f ; outputs[GATE_OUTPUT].value = gateout ? 10.0f : 0.0f ; pitchFilter.lambda = 100.f * engineGetSampleTime(); if (params[PBDUALPOLARITY_PARAM].value < 0.5f) outputs[PBEND_OUTPUT].value = pitchFilter.process(rescale(pitch, 0, 16384, -5.f, 5.f)); else outputs[PBEND_OUTPUT].value = pitchFilter.process(rescale(pitch, 0, 16384, 0.f, 10.f)); modFilter.lambda = 100.f * engineGetSampleTime(); outputs[MOD_OUTPUT].value = modFilter.process(rescale(mod, 0, 127, 0.f, 10.f)); breathFilter.lambda = 100.f * engineGetSampleTime(); outputs[BREATH_OUTPUT].value = breathFilter.process(rescale(breath, 0, 127, 0.f, 10.f)); exprFilter.lambda = 100.f * engineGetSampleTime(); outputs[EXPRESSION_OUTPUT].value = exprFilter.process(rescale(expression, 0, 127, 0.f, 10.f)); sustainFilter.lambda = 100.f * engineGetSampleTime(); outputs[SUSTAIN_OUTPUT].value = sustainFilter.process(rescale(sustain, 0, 127, 0.f, 10.f)); pressureFilter.lambda = 100.f * engineGetSampleTime(); outputs[PRESSURE_OUTPUT].value = pressureFilter.process(rescale(pressure, 0, 127, 0.f, 10.f)); ///// RESET MIDI LIGHT if (resetMidiTrigger.process(params[RESETMIDI_PARAM].value)) { lights[RESETMIDI_LIGHT].value= 1.0f; MidiPanic(); return; } if (lights[RESETMIDI_LIGHT].value > 0.0001f){ lights[RESETMIDI_LIGHT].value -= 0.0001f ; // fade out light } } /////////////////////// * * * ///////////////////////////////////////////////// * * * // * * * E N D O F S T E P * * * /////////////////////// * * * ///////////////////////////////////////////////// * * * void processMessage(MidiMessage msg) { // debug("MIDI: %01x %01x %02x %02x", msg.status(), msg.channel(), msg.data1, msg.data2); if ((midiInput.channel < 0) || (midiInput.channel == msg.channel())){ switch (msg.status()) { case 0x8: { // note off uint8_t note = msg.data1 & 0x7f; noteData[note].velocity = 0; noteData[note].aftertouch = 0; noteupdated = true; } break; case 0x9: { // note on uint8_t note = msg.data1 & 0x7f; noteData[note].velocity = msg.data2; noteData[note].aftertouch = 0; noteupdated = true; } break; case 0xb: // cc processCC(msg); break; case 0xe: // pitch wheel pitch = msg.data2 * 128 + msg.data1; break; case 0xd: // channel aftertouch pressure = msg.data1; break; // case 0xf: ///realtime clock etc // processSystem(msg); // break; default: break; } } } void processCC(MidiMessage msg) { switch (msg.data1) { case 0x01: // mod mod = msg.data2; break; case 0x02: // breath breath = msg.data2; break; case 0x0B: // Expression expression = msg.data2; break; case 0x40: { // sustain sustain = msg.data2; if ((params[SUSTAINHOLD_PARAM].value > 0.5) && anynoteGate) sustpedal = (msg.data2 >= 64); else sustpedal = false; } break; default: break; } } //void processSystem(MidiMessage msg) { // switch (msg.channel()) { // case 0x8: { // debug("timing clock"); // } break; // case 0xa: { // debug("start"); // } break; // case 0xb: { // debug("continue"); // } break; // case 0xc: { // debug("stop"); // } break; // default: break; // } //} void MidiPanic() { pitch = 8192; outputs[PBEND_OUTPUT].value = 0.0f; mod = 0; outputs[MOD_OUTPUT].value = 0.0f; breath = 0; outputs[BREATH_OUTPUT].value = 0.0f; expression = 0; outputs[EXPRESSION_OUTPUT].value = 0.0f; pressure = 0; outputs[PRESSURE_OUTPUT].value = 0.0f; sustain = 0; outputs[SUSTAIN_OUTPUT].value = 0.0f; sustpedal = false; } json_t *toJson() override { json_t *rootJ = json_object(); json_object_set_new(rootJ, "midi", midiInput.toJson()); return rootJ; } void fromJson(json_t *rootJ) override { json_t *midiJ = json_object_get(rootJ, "midi"); midiInput.fromJson(midiJ); } }; struct overMidiDisplay : TransparentWidget { void draw(NVGcontext* vg) { NVGcolor backgroundColor = nvgRGBA(0x80, 0x80, 0x80, 0x24); nvgBeginPath(vg); nvgRoundedRect(vg, 0, 0, 113, 48, 5.0f); nvgFillColor(vg, backgroundColor); nvgFill(vg); nvgBeginPath(vg); NVGcolor linecolor = nvgRGB(0x50, 0x50, 0x50); nvgRect(vg, 61, 0, 1, 16); nvgFillColor(vg, linecolor); nvgFill(vg); } }; struct MIDIdualCVWidget : ModuleWidget { MIDIdualCVWidget(MIDIdualCV *module): ModuleWidget(module){ setPanel(SVG::load(assetPlugin(plugin, "res/MIDIdualCV.svg"))); //Screws addChild(Widget::create(Vec(0, 0))); addChild(Widget::create(Vec(box.size.x - 15, 0))); addChild(Widget::create(Vec(0, 365))); addChild(Widget::create(Vec(box.size.x - 15, 365))); ///MIDI float yPos = 21.0f; float xPos = 11.0f; MidiWidget *midiWidget = Widget::create(Vec(xPos,yPos)); midiWidget->box.size = Vec(113,48); midiWidget->midiIO = &module->midiInput; midiWidget->driverChoice->box.size = Vec(56,16); midiWidget->deviceChoice->box.size = Vec(113,16); midiWidget->channelChoice->box.size = Vec(113,16); midiWidget->driverChoice->box.pos = Vec(0, 0); midiWidget->deviceChoice->box.pos = Vec(0, 16); midiWidget->channelChoice->box.pos = Vec(0, 32); midiWidget->driverSeparator->box.pos = Vec(0, 16); midiWidget->deviceSeparator->box.pos = Vec(0, 32); midiWidget->driverChoice->font = Font::load(mFONT_FILE); midiWidget->deviceChoice->font = Font::load(mFONT_FILE); midiWidget->channelChoice->font = Font::load(mFONT_FILE); midiWidget->driverChoice->textOffset = Vec(4,12); midiWidget->deviceChoice->textOffset = Vec(4,12); midiWidget->channelChoice->textOffset = Vec(4,12); midiWidget->driverChoice->color = nvgRGB(0xdd, 0xdd, 0xdd); midiWidget->deviceChoice->color = nvgRGB(0xdd, 0xdd, 0xdd); midiWidget->channelChoice->color = nvgRGB(0xdd, 0xdd, 0xdd); addChild(midiWidget); overMidiDisplay *overmididisplay = Widget::create(Vec(xPos,yPos)); addChild(overmididisplay); // //reset button xPos = 77; yPos = 22; addParam(ParamWidget::create(Vec(xPos, yPos), module, MIDIdualCV::RESETMIDI_PARAM, 0.0f, 1.0f, 0.0f)); addChild(ModuleLightWidget::create>(Vec(xPos+35.f, yPos+4.f), module, MIDIdualCV::RESETMIDI_LIGHT)); yPos = 94.0f; //Lower-Upper Outputs for (int i = 0; i < 3; i++) { addOutput(Port::create(Vec(20, yPos), Port::OUTPUT, module, i * 2)); addOutput(Port::create(Vec(93, yPos), Port::OUTPUT, module, i * 2 + 1)); yPos += 24; } //Retrig Switches addParam(ParamWidget::create(Vec(21, 168), module, MIDIdualCV::LWRRETRGGMODE_PARAM, 0.0, 1.0, 0.0)); addParam(ParamWidget::create(Vec(105, 168), module, MIDIdualCV::UPRRETRGGMODE_PARAM, 0.0, 1.0, 0.0)); yPos = 200; xPos = 72; //Common Outputs for (int i = 6; i < MIDIdualCV::NUM_OUTPUTS; i++) { addOutput(Port::create(Vec(xPos, yPos), Port::OUTPUT, module, i)); yPos += 23; } ///PitchWheel +- or + addParam(ParamWidget::create(Vec(105, 225), module, MIDIdualCV::PBDUALPOLARITY_PARAM, 0.0, 1.0, 0.0)); ///Sustain hold notes addParam(ParamWidget::create(Vec(105, 318), module, MIDIdualCV::SUSTAINHOLD_PARAM, 0.0, 1.0, 1.0)); } }; } // namespace rack_plugin_moDllz using namespace rack_plugin_moDllz; RACK_PLUGIN_MODEL_INIT(moDllz, MIDIdualCV) { Model *modelMIDIdualCV = Model::create("moDllz", "MIDIdualCV", "MIDI to dual CV interface", MIDI_TAG, DUAL_TAG, EXTERNAL_TAG); return modelMIDIdualCV; }