From 2862703f4c54bffcf9b5322ab8e9272880e36167 Mon Sep 17 00:00:00 2001 From: Andrew Belt Date: Fri, 18 Jan 2019 02:03:12 -0500 Subject: [PATCH] Add CV-MIDI to Core. Move arrow key scrolling from ScrollWidget to RackWidget. --- include/engine/Input.hpp | 4 +- include/engine/Port.hpp | 15 +- include/rack.hpp | 1 + res/Core/CV-MIDI.svg | 471 +++++++++++++++++++++++++++++++++++++++ src/Core/CV_MIDI.cpp | 319 ++++++++++++++++++++++++++ src/Core/Core.cpp | 5 + src/Core/Core.hpp | 1 + src/app/CableWidget.cpp | 2 +- src/app/RackWidget.cpp | 23 ++ src/engine/Cable.cpp | 4 +- src/engine/Engine.cpp | 2 +- src/midi.cpp | 1 + src/ui/ScrollWidget.cpp | 24 -- 13 files changed, 836 insertions(+), 36 deletions(-) create mode 100644 res/Core/CV-MIDI.svg create mode 100644 src/Core/CV_MIDI.cpp diff --git a/include/engine/Input.hpp b/include/engine/Input.hpp index 30923b15..9d8f4845 100644 --- a/include/engine/Input.hpp +++ b/include/engine/Input.hpp @@ -8,8 +8,8 @@ namespace rack { struct Input : Port { /** Returns the value if a cable is plugged in, otherwise returns the given default value */ - float normalize(float normalVoltage, int index = 0) { - return active ? getVoltage(index) : normalVoltage; + float normalize(float normalVoltage, int channel = 0) { + return active ? getVoltage(channel) : normalVoltage; } }; diff --git a/include/engine/Port.hpp b/include/engine/Port.hpp index d1fc3d2c..bd8fe168 100644 --- a/include/engine/Port.hpp +++ b/include/engine/Port.hpp @@ -18,17 +18,20 @@ struct Port { float value; float values[PORT_MAX_CHANNELS] = {}; }; - /** Number of polyphonic channels */ - int numChannels = 1; + /** Number of polyphonic channels + May be 0 to PORT_MAX_CHANNELS. + */ + int channels = 1; /** Whether a cable is plugged in */ bool active = false; Light plugLights[2]; - float getVoltage(int index = 0) { - return values[index]; + float getVoltage(int channel = 0) { + return values[channel]; } - void setVoltage(float voltage, int index = 0) { - values[index] = voltage; + + void setVoltage(float voltage, int channel = 0) { + values[channel] = voltage; } }; diff --git a/include/rack.hpp b/include/rack.hpp index 86e8b570..4876c96c 100644 --- a/include/rack.hpp +++ b/include/rack.hpp @@ -9,6 +9,7 @@ #include "asset.hpp" #include "window.hpp" #include "app.hpp" +#include "midi.hpp" #include "helpers.hpp" #include "widgets/Widget.hpp" diff --git a/res/Core/CV-MIDI.svg b/res/Core/CV-MIDI.svg new file mode 100644 index 00000000..918d9bef --- /dev/null +++ b/res/Core/CV-MIDI.svg @@ -0,0 +1,471 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Core/CV_MIDI.cpp b/src/Core/CV_MIDI.cpp new file mode 100644 index 00000000..5ea1e6e7 --- /dev/null +++ b/src/Core/CV_MIDI.cpp @@ -0,0 +1,319 @@ +#include "Core.hpp" + + +template +struct PolyphonicMidiOutput : midi::Output { + int vels[C]; + int lastNotes[C]; + int notes[C]; + bool lastGates[C]; + bool gates[C]; + int lastAfts[C]; + int lastPw; + int lastMw; + bool lastClk; + int lastVol; + int lastPan; + bool lastStart; + bool lastStop; + bool lastCont; + + PolyphonicMidiOutput() { + reset(); + } + + void reset() { + for (int c = 0; c < C; c++) { + vels[c] = 100; + lastNotes[c] = notes[c] = 60; + lastGates[c] = gates[c] = false; + lastAfts[c] = -1; + } + lastPw = 0x2000; + lastMw = 0; + lastClk = false; + lastVol = 127; + lastPan = 64; + lastStart = false; + lastStop = false; + lastCont = false; + } + + void panic() { + reset(); + // Send all note off commands + for (int note = 0; note <= 127; note++) { + // Note off + midi::Message m; + m.cmd = 0x8; + m.data1 = note; + m.data2 = 0; + sendMessage(m); + } + } + + void setVel(int vel, int c) { + vels[c] = vel; + } + + void setNote(int note, int c) { + notes[c] = note; + } + + void setGate(bool gate, int c) { + gates[c] = gate; + } + + void stepChannel(int c) { + bool changedNote = gates[c] && lastGates[c] && notes[c] != lastNotes[c]; + bool enabledGate = gates[c] && !lastGates[c]; + bool disabledGate = !gates[c] && lastGates[c]; + if (changedNote || enabledGate) { + // Note on + midi::Message m; + m.cmd = 0x9; + m.data1 = notes[c]; + m.data2 = vels[c]; + sendMessage(m); + } + if (changedNote || disabledGate) { + // Note off + midi::Message m; + m.cmd = 0x8; + m.data1 = lastNotes[c]; + m.data2 = vels[c]; + sendMessage(m); + } + lastNotes[c] = notes[c]; + lastGates[c] = gates[c]; + } + + void setAftertouch(int aft, int c) { + if (lastAfts[c] == aft) + return; + lastAfts[c] = aft; + // Polyphonic key pressure + midi::Message m; + m.cmd = 0xa; + m.data1 = notes[c]; + m.data2 = aft; + sendMessage(m); + } + + void setPitchWheel(int pw) { + if (lastPw == pw) + return; + lastPw = pw; + // Pitch wheel + midi::Message m; + m.cmd = 0xe; + m.data1 = pw & 0x7f; + m.data2 = (pw >> 7) & 0x7f; + sendMessage(m); + } + + void setModWheel(int mw) { + if (lastMw == mw) + return; + lastMw = mw; + // CC Mod wheel + midi::Message m; + m.cmd = 0xb; + m.data1 = 0x01; + m.data2 = mw; + sendMessage(m); + } + + void setClock(bool clk) { + if (lastClk == clk) + return; + lastClk = clk; + if (clk) { + // Timing clock + midi::Message m; + m.cmd = 0xf; + m.data1 = 0x8; + sendMessage(m); + } + } + + void setVolume(int vol) { + if (lastVol == vol) + return; + lastVol = vol; + // CC Volume + midi::Message m; + m.cmd = 0xb; + m.data1 = 0x07; + m.data2 = vol; + sendMessage(m); + } + + void setPan(int pan) { + if (lastPan == pan) + return; + lastPan = pan; + // CC Pan + midi::Message m; + m.cmd = 0xb; + m.data1 = 0x0a; + m.data2 = pan; + sendMessage(m); + } + + void setStart(bool start) { + if (lastStart == start) + return; + lastStart = start; + if (start) { + // Start + midi::Message m; + m.cmd = 0xf; + m.data1 = 0xa; + sendMessage(m); + } + } + + void setStop(bool stop) { + if (lastStop == stop) + return; + lastStop = stop; + if (stop) { + // Stop + midi::Message m; + m.cmd = 0xf; + m.data1 = 0xb; + sendMessage(m); + } + } + + void setContinue(bool cont) { + if (lastCont == cont) + return; + lastCont = cont; + if (cont) { + // Continue + midi::Message m; + m.cmd = 0xf; + m.data1 = 0xc; + sendMessage(m); + } + } +}; + + +struct CV_MIDI : Module { + enum ParamIds { + NUM_PARAMS + }; + enum InputIds { + PITCH_INPUT, + GATE_INPUT, + VEL_INPUT, + AFT_INPUT, + PW_INPUT, + MW_INPUT, + CLK_INPUT, + VOL_INPUT, + PAN_INPUT, + START_INPUT, + STOP_INPUT, + CONTINUE_INPUT, + NUM_INPUTS + }; + enum OutputIds { + NUM_OUTPUTS + }; + enum LightIds { + NUM_LIGHTS + }; + + PolyphonicMidiOutput midiOutput; + + CV_MIDI() { + config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); + } + + void step() override { + for (int c = 0; c < inputs[PITCH_INPUT].channels; c++) { + int vel = (int) std::round(inputs[VEL_INPUT].normalize(10.f * 100 / 127, c) / 10.f * 127); + vel = clamp(vel, 0, 127); + midiOutput.setVel(vel, c); + + int note = (int) std::round(inputs[PITCH_INPUT].getVoltage(c) * 12.f + 60.f); + note = clamp(note, 0, 127); + midiOutput.setNote(note, c); + + bool gate = inputs[GATE_INPUT].getVoltage(c) >= 1.f; + midiOutput.setGate(gate, c); + + midiOutput.stepChannel(c); + + int aft = (int) std::round(inputs[AFT_INPUT].getVoltage(c) / 10.f * 127); + aft = clamp(aft, 0, 127); + midiOutput.setAftertouch(aft, c); + } + + int pw = (int) std::round((inputs[PW_INPUT].getVoltage() + 5.f) / 10.f * 0x4000); + pw = clamp(pw, 0, 0x3fff); + midiOutput.setPitchWheel(pw); + + int mw = (int) std::round(inputs[MW_INPUT].getVoltage() / 10.f * 127); + mw = clamp(mw, 0, 127); + midiOutput.setModWheel(mw); + + bool clk = inputs[CLK_INPUT].value >= 1.f; + midiOutput.setClock(clk); + + int vol = (int) std::round(inputs[VOL_INPUT].normalize(10.f) / 10.f * 127); + vol = clamp(vol, 0, 127); + midiOutput.setVolume(vol); + + int pan = (int) std::round((inputs[PAN_INPUT].getVoltage() + 5.f) / 10.f * 127); + pan = clamp(pan, 0, 127); + midiOutput.setPan(pan); + + bool start = inputs[START_INPUT].value >= 1.f; + midiOutput.setStart(start); + + bool stop = inputs[STOP_INPUT].value >= 1.f; + midiOutput.setStop(stop); + + bool cont = inputs[CONTINUE_INPUT].value >= 1.f; + midiOutput.setContinue(cont); + } +}; + + +struct CV_MIDIWidget : ModuleWidget { + CV_MIDIWidget(CV_MIDI *module) : ModuleWidget(module) { + setPanel(SVG::load(asset::system("res/Core/CV-MIDI.svg"))); + + addChild(createWidget(Vec(RACK_GRID_WIDTH, 0))); + addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0))); + addChild(createWidget(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); + addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); + + addInput(createInputCentered(mm2px(Vec(9, 64)), module, CV_MIDI::PITCH_INPUT)); + addInput(createInputCentered(mm2px(Vec(20, 64)), module, CV_MIDI::GATE_INPUT)); + addInput(createInputCentered(mm2px(Vec(32, 64)), module, CV_MIDI::VEL_INPUT)); + addInput(createInputCentered(mm2px(Vec(9, 80)), module, CV_MIDI::AFT_INPUT)); + addInput(createInputCentered(mm2px(Vec(20, 80)), module, CV_MIDI::PW_INPUT)); + addInput(createInputCentered(mm2px(Vec(32, 80)), module, CV_MIDI::MW_INPUT)); + addInput(createInputCentered(mm2px(Vec(9, 96)), module, CV_MIDI::CLK_INPUT)); + addInput(createInputCentered(mm2px(Vec(20, 96)), module, CV_MIDI::VOL_INPUT)); + addInput(createInputCentered(mm2px(Vec(32, 96)), module, CV_MIDI::PAN_INPUT)); + addInput(createInputCentered(mm2px(Vec(9, 112)), module, CV_MIDI::START_INPUT)); + addInput(createInputCentered(mm2px(Vec(20, 112)), module, CV_MIDI::STOP_INPUT)); + addInput(createInputCentered(mm2px(Vec(32, 112)), module, CV_MIDI::CONTINUE_INPUT)); + + MidiWidget *midiWidget = createWidget(mm2px(Vec(3.41891, 14.8373))); + midiWidget->box.size = mm2px(Vec(33.840, 28)); + if (module) + midiWidget->midiIO = &module->midiOutput; + addChild(midiWidget); + } +}; + + +Model *modelCV_MIDI = createModel("CV-MIDI"); diff --git a/src/Core/Core.cpp b/src/Core/Core.cpp index ba7d790b..740bc894 100644 --- a/src/Core/Core.cpp +++ b/src/Core/Core.cpp @@ -38,6 +38,11 @@ void init(rack::Plugin *p) { modelMIDITriggerToCVInterface->tags = {"External", "MIDI"}; p->addModel(modelMIDITriggerToCVInterface); + modelCV_MIDI->name = "CV-MIDI"; + modelCV_MIDI->description = ""; + modelCV_MIDI->tags = {"External", "MIDI"}; + p->addModel(modelCV_MIDI); + modelBlank->name = "Blank"; modelBlank->description = ""; modelBlank->tags = {"Blank"}; diff --git a/src/Core/Core.hpp b/src/Core/Core.hpp index 5dd364b9..c72e1f05 100644 --- a/src/Core/Core.hpp +++ b/src/Core/Core.hpp @@ -10,6 +10,7 @@ extern Model *modelMIDIToCVInterface; extern Model *modelQuadMIDIToCVInterface; extern Model *modelMIDICCToCVInterface; extern Model *modelMIDITriggerToCVInterface; +extern Model *modelCV_MIDI; extern Model *modelBlank; extern Model *modelNotes; diff --git a/src/app/CableWidget.cpp b/src/app/CableWidget.cpp index c627c230..bf0b77ad 100644 --- a/src/app/CableWidget.cpp +++ b/src/app/CableWidget.cpp @@ -182,7 +182,7 @@ void CableWidget::draw(NVGcontext *vg) { float thickness = 5; if (cable && cable->outputModule) { Output *output = &cable->outputModule->outputs[cable->outputId]; - if (output->numChannels != 1) { + if (output->channels != 1) { thickness = 7; } } diff --git a/src/app/RackWidget.cpp b/src/app/RackWidget.cpp index 9b9cd3a9..64deab0e 100644 --- a/src/app/RackWidget.cpp +++ b/src/app/RackWidget.cpp @@ -589,6 +589,29 @@ void RackWidget::draw(NVGcontext *vg) { } void RackWidget::onHover(const event::Hover &e) { + // Scroll with arrow keys + float arrowSpeed = 30.0; + if (app()->window->isShiftPressed() && app()->window->isModPressed()) + arrowSpeed /= 16.0; + else if (app()->window->isShiftPressed()) + arrowSpeed *= 4.0; + else if (app()->window->isModPressed()) + arrowSpeed /= 4.0; + + ScrollWidget *scrollWidget = app()->scene->scrollWidget; + if (glfwGetKey(app()->window->win, GLFW_KEY_LEFT) == GLFW_PRESS) { + scrollWidget->offset.x -= arrowSpeed; + } + if (glfwGetKey(app()->window->win, GLFW_KEY_RIGHT) == GLFW_PRESS) { + scrollWidget->offset.x += arrowSpeed; + } + if (glfwGetKey(app()->window->win, GLFW_KEY_UP) == GLFW_PRESS) { + scrollWidget->offset.y -= arrowSpeed; + } + if (glfwGetKey(app()->window->win, GLFW_KEY_DOWN) == GLFW_PRESS) { + scrollWidget->offset.y += arrowSpeed; + } + OpaqueWidget::onHover(e); lastMousePos = e.pos; } diff --git a/src/engine/Cable.cpp b/src/engine/Cable.cpp index 20de979b..9660526e 100644 --- a/src/engine/Cable.cpp +++ b/src/engine/Cable.cpp @@ -8,9 +8,9 @@ void Cable::step() { Output *output = &outputModule->outputs[outputId]; Input *input = &inputModule->inputs[inputId]; // Match number of polyphonic channels to output port - input->numChannels = output->numChannels; + input->channels = output->channels; // Copy values from output to input - for (int i = 0; i < output->numChannels; i++) { + for (int i = 0; i < output->channels; i++) { input->values[i] = output->values[i]; } } diff --git a/src/engine/Engine.cpp b/src/engine/Engine.cpp index 9ca53c53..6d048212 100644 --- a/src/engine/Engine.cpp +++ b/src/engine/Engine.cpp @@ -131,7 +131,7 @@ static void Engine_step(Engine *engine) { if (module->bypass) { // Bypass module for (Output &output : module->outputs) { - output.numChannels = 1; + output.channels = 1; output.setVoltage(0.f); } module->cpuTime = 0.f; diff --git a/src/midi.cpp b/src/midi.cpp index 18abdf41..051a62f0 100644 --- a/src/midi.cpp +++ b/src/midi.cpp @@ -228,6 +228,7 @@ void Output::setDeviceId(int deviceId) { } void Output::sendMessage(Message message) { + // DEBUG("sendMessage %02x %02x %02x", message.cmd, message.data1, message.data2); if (outputDevice) { outputDevice->sendMessage(message); } diff --git a/src/ui/ScrollWidget.cpp b/src/ui/ScrollWidget.cpp index 803ba159..8f181fdd 100644 --- a/src/ui/ScrollWidget.cpp +++ b/src/ui/ScrollWidget.cpp @@ -69,30 +69,6 @@ void ScrollWidget::step() { } void ScrollWidget::onHover(const event::Hover &e) { - // Scroll with arrow keys - if (!app()->event->selectedWidget) { - float arrowSpeed = 30.0; - if (app()->window->isShiftPressed() && app()->window->isModPressed()) - arrowSpeed /= 16.0; - else if (app()->window->isShiftPressed()) - arrowSpeed *= 4.0; - else if (app()->window->isModPressed()) - arrowSpeed /= 4.0; - - if (glfwGetKey(app()->window->win, GLFW_KEY_LEFT) == GLFW_PRESS) { - offset.x -= arrowSpeed; - } - if (glfwGetKey(app()->window->win, GLFW_KEY_RIGHT) == GLFW_PRESS) { - offset.x += arrowSpeed; - } - if (glfwGetKey(app()->window->win, GLFW_KEY_UP) == GLFW_PRESS) { - offset.y -= arrowSpeed; - } - if (glfwGetKey(app()->window->win, GLFW_KEY_DOWN) == GLFW_PRESS) { - offset.y += arrowSpeed; - } - } - OpaqueWidget::onHover(e); }