From 4ccb746e5a4f08dec67f456f89704d35fd3f9789 Mon Sep 17 00:00:00 2001 From: ben Date: Sat, 7 Oct 2017 19:56:14 +0200 Subject: [PATCH] Restructure MidiInterface, add velocity and aftertouch to Midi-to-CV and add Midi-CC-to-CV --- src/core/MidiInterface.cpp | 639 ++++++++++++++++++++++++++----------- src/core/core.cpp | 3 +- src/core/core.hpp | 9 +- 3 files changed, 461 insertions(+), 190 deletions(-) diff --git a/src/core/MidiInterface.cpp b/src/core/MidiInterface.cpp index 74ef861a..8fd4e6a9 100644 --- a/src/core/MidiInterface.cpp +++ b/src/core/MidiInterface.cpp @@ -9,62 +9,51 @@ using namespace rack; -struct MidiInterface : Module { - enum ParamIds { - NUM_PARAMS - }; - enum InputIds { - NUM_INPUTS - }; - enum OutputIds { - PITCH_OUTPUT, - GATE_OUTPUT, - MOD_OUTPUT, - PITCHWHEEL_OUTPUT, - NUM_OUTPUTS - }; - +/** + * MidiIO implements the shared functionality of all midi modules, namely: + * + Channel Selection (including helper for storing json) + * + Interface Selection (including helper for storing json) + * + rtMidi initialisation (input or output) + */ +struct MidiIO { int portId = -1; - RtMidiIn *midiIn = NULL; - std::list notes; + RtMidi *rtMidi = NULL; + /** Filter MIDI channel -1 means all MIDI channels */ int channel = -1; - bool pedal = false; - int note = 60; // C4, most modules should use 261.626 Hz - int mod = 0; - int pitchWheel = 64; - bool retrigger = false; - bool retriggered = false; - MidiInterface() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS) { + /* + * If isOut is set to true, creates a RtMidiOut, RtMidiIn otherwise + */ + MidiIO(bool isOut = false) { try { - midiIn = new RtMidiIn(RtMidi::UNSPECIFIED, "VCVRack"); + if (isOut) { + rtMidi = new RtMidiOut(RtMidi::UNSPECIFIED, "VCVRack"); + } else { + rtMidi = new RtMidiIn(RtMidi::UNSPECIFIED, "VCVRack"); + } } - catch ( RtMidiError &error ) { + catch (RtMidiError &error) { fprintf(stderr, "Failed to create RtMidiIn: %s\n", error.getMessage().c_str()); } } - ~MidiInterface() { - setPortId(-1); - } - void step(); + ~MidiIO() {} int getPortCount(); + std::string getPortName(int portId); + // -1 will close the port void setPortId(int portId); + void setChannel(int channel) { this->channel = channel; } - void pressNote(int note); - void releaseNote(int note); - void processMidi(std::vector msg); - json_t *toJson() { - json_t *rootJ = json_object(); + json_t *addBaseJson(json_t *rootJ) { if (portId >= 0) { std::string portName = getPortName(portId); json_object_set_new(rootJ, "portName", json_string(portName.c_str())); @@ -73,7 +62,7 @@ struct MidiInterface : Module { return rootJ; } - void fromJson(json_t *rootJ) { + void baseFromJson(json_t *rootJ) { json_t *portNameJ = json_object_get(rootJ, "portName"); if (portNameJ) { std::string portName = json_string_value(portNameJ); @@ -91,185 +80,89 @@ struct MidiInterface : Module { } } - void initialize() { - setPortId(-1); - } }; - -void MidiInterface::step() { - if (midiIn->isPortOpen()) { - std::vector message; - - // midiIn->getMessage returns empty vector if there are no messages in the queue - double stamp = midiIn->getMessage( &message ); - while (message.size() > 0) { - processMidi(message); - stamp = midiIn->getMessage( &message ); - } - } - - outputs[PITCH_OUTPUT].value = ((note - 60)) / 12.0; - - bool gate = pedal || !notes.empty(); - if (retrigger && retriggered) { - gate = false; - retriggered = false; - } - outputs[GATE_OUTPUT].value = gate ? 10.0 : 0.0; - outputs[MOD_OUTPUT].value = mod / 127.0 * 10.0; - outputs[PITCHWHEEL_OUTPUT].value = (pitchWheel - 64) / 64.0 * 10.0; -} - -int MidiInterface::getPortCount() { - return midiIn->getPortCount(); +int MidiIO::getPortCount() { + return rtMidi->getPortCount(); } -std::string MidiInterface::getPortName(int portId) { +std::string MidiIO::getPortName(int portId) { std::string portName; try { - portName = midiIn->getPortName(portId); + portName = rtMidi->getPortName(portId); } - catch ( RtMidiError &error ) { + catch (RtMidiError &error) { fprintf(stderr, "Failed to get Port Name: %d, %s\n", portId, error.getMessage().c_str()); } return portName; } -void MidiInterface::setPortId(int portId) { +void MidiIO::setPortId(int portId) { // Close port if it was previously opened - if (midiIn->isPortOpen()) { - midiIn->closePort(); + if (rtMidi->isPortOpen()) { + rtMidi->closePort(); } this->portId = -1; // Open new port if (portId >= 0) { - midiIn->openPort(portId, "Midi Interface"); + rtMidi->openPort(portId, "Midi Interface"); } this->portId = portId; } -void MidiInterface::pressNote(int note) { - // Remove existing similar note - auto it = std::find(notes.begin(), notes.end(), note); - if (it != notes.end()) - notes.erase(it); - // Push note - notes.push_back(note); - this->note = note; - retriggered = true; -} - -void MidiInterface::releaseNote(int note) { - // Remove the note - auto it = std::find(notes.begin(), notes.end(), note); - if (it != notes.end()) - notes.erase(it); - - if (pedal) { - // Don't release if pedal is held - } - else if (!notes.empty()) { - // Play previous note - auto it2 = notes.end(); - it2--; - this->note = *it2; - retriggered = true; - } -} - -void MidiInterface::processMidi(std::vector msg) { - int channel = msg[0] & 0xf; - int status = (msg[0] >> 4) & 0xf; - int data1 = msg[1]; - int data2 = msg[2]; - - //fprintf(stderr, "channel %d status %d data1 %d data2 %d\n", channel, status, data1,data2); - - // Filter channels - if (this->channel >= 0 && this->channel != channel) - return; - - switch (status) { - // note off - case 0x8: { - releaseNote(data1); - } break; - case 0x9: // note on - if (data2 > 0) { - pressNote(data1); - } - else { - // For some reason, some keyboards send a "note on" event with a velocity of 0 to signal that the key has been released. - releaseNote(data1); - } - break; - case 0xb: // cc - switch (data1) { - case 0x01: // mod - this->mod = data2; - break; - case 0x40: // sustain - pedal = (data2 >= 64); - releaseNote(-1); - break; - } - break; - case 0xe: // pitch wheel - this->pitchWheel = data2; - break; - } -} - - struct MidiItem : MenuItem { - MidiInterface *midiInterface; + MidiIO *midiModule; int portId; + void onAction() { - midiInterface->setPortId(portId); + midiModule->setPortId(portId); } }; struct MidiChoice : ChoiceButton { - MidiInterface *midiInterface; + MidiIO *midiModule; + void onAction() { Menu *menu = gScene->createMenu(); menu->box.pos = getAbsolutePos().plus(Vec(0, box.size.y)); menu->box.size.x = box.size.x; - int portCount = midiInterface->getPortCount(); + int portCount = midiModule->getPortCount(); { MidiItem *midiItem = new MidiItem(); - midiItem->midiInterface = midiInterface; + midiItem->midiModule = midiModule; midiItem->portId = -1; midiItem->text = "No device"; menu->pushChild(midiItem); } for (int portId = 0; portId < portCount; portId++) { MidiItem *midiItem = new MidiItem(); - midiItem->midiInterface = midiInterface; + midiItem->midiModule = midiModule; midiItem->portId = portId; - midiItem->text = midiInterface->getPortName(portId); + midiItem->text = midiModule->getPortName(portId); menu->pushChild(midiItem); } } + void step() { - std::string name = midiInterface->getPortName(midiInterface->portId); - text = ellipsize(name, 8); + std::string name = midiModule->getPortName(midiModule->portId); + text = ellipsize(name, 15); } }; struct ChannelItem : MenuItem { - MidiInterface *midiInterface; + MidiIO *midiModule; int channel; + void onAction() { - midiInterface->setChannel(channel); + midiModule->setChannel(channel); } }; struct ChannelChoice : ChoiceButton { - MidiInterface *midiInterface; + MidiIO *midiModule; + void onAction() { Menu *menu = gScene->createMenu(); menu->box.pos = getAbsolutePos().plus(Vec(0, box.size.y)); @@ -277,29 +170,207 @@ struct ChannelChoice : ChoiceButton { { ChannelItem *channelItem = new ChannelItem(); - channelItem->midiInterface = midiInterface; + channelItem->midiModule = midiModule; channelItem->channel = -1; channelItem->text = "All"; menu->pushChild(channelItem); } for (int channel = 0; channel < 16; channel++) { ChannelItem *channelItem = new ChannelItem(); - channelItem->midiInterface = midiInterface; + channelItem->midiModule = midiModule; channelItem->channel = channel; channelItem->text = stringf("%d", channel + 1); menu->pushChild(channelItem); } } + void step() { - text = (midiInterface->channel >= 0) ? stringf("%d", midiInterface->channel + 1) : "All"; + text = (midiModule->channel >= 0) ? stringf("%d", midiModule->channel + 1) : "All"; + } +}; + +/* + * MIDIToCVInterface converts midi note on/off events, velocity , channel aftertouch, pitch wheel and mod wheel to + * CV + */ +struct MIDIToCVInterface : MidiIO, Module { + enum ParamIds { + NUM_PARAMS + }; + enum InputIds { + NUM_INPUTS + }; + enum OutputIds { + PITCH_OUTPUT = 0, + GATE_OUTPUT, + VELOCITY_OUTPUT, + MOD_OUTPUT, + PITCHWHEEL_OUTPUT, + CHANNEL_AFTERTOUCH_OUTPUT, + NUM_OUTPUTS + }; + + std::list notes; + bool pedal = false; + int note = 60; // C4, most modules should use 261.626 Hz + int mod = 0; + int vel = 0; + int afterTouch = 0; + int pitchWheel = 64; + bool retrigger = false; + bool retriggered = false; + float lights[NUM_OUTPUTS]; + + MIDIToCVInterface() : MidiIO(), Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS) { + + } + + ~MIDIToCVInterface() { + setPortId(-1); + }; + + void step(); + + void pressNote(int note); + + void releaseNote(int note); + + void processMidi(std::vector msg); + + virtual json_t *toJson() { + json_t *rootJ = json_object(); + addBaseJson(rootJ); + return rootJ; } + + virtual void fromJson(json_t *rootJ) { + baseFromJson(rootJ); + } + + virtual void initialize() { + setPortId(-1); + } + }; -MidiInterfaceWidget::MidiInterfaceWidget() { - MidiInterface *module = new MidiInterface(); +void MIDIToCVInterface::step() { + if (rtMidi->isPortOpen()) { + std::vector message; + + // midiIn->getMessage returns empty vector if there are no messages in the queue + + dynamic_cast(rtMidi)->getMessage(&message); + while (message.size() > 0) { + processMidi(message); + dynamic_cast(rtMidi)->getMessage(&message); + } + } + + outputs[PITCH_OUTPUT].value = ((note - 60)) / 12.0; + + bool gate = pedal || !notes.empty(); + if (retrigger && retriggered) { + gate = false; + retriggered = false; + } + outputs[GATE_OUTPUT].value = gate ? 10.0 : 0.0; + lights[GATE_OUTPUT] = gate ? 1.0 : 0.0; + + outputs[MOD_OUTPUT].value = mod / 127.0 * 10.0; + lights[MOD_OUTPUT] = mod / 127.0; + + outputs[PITCHWHEEL_OUTPUT].value = (pitchWheel - 64) / 64.0 * 10.0; + lights[MOD_OUTPUT] = pitchWheel / 127.0; + + outputs[CHANNEL_AFTERTOUCH_OUTPUT].value = afterTouch / 127.0 * 10.0; + lights[CHANNEL_AFTERTOUCH_OUTPUT] = afterTouch / 127.0; + + outputs[VELOCITY_OUTPUT].value = vel / 127.0 * 10.0; + lights[VELOCITY_OUTPUT] = vel / 127.0; +} + + +void MIDIToCVInterface::pressNote(int note) { + // Remove existing similar note + auto it = std::find(notes.begin(), notes.end(), note); + if (it != notes.end()) + notes.erase(it); + // Push note + notes.push_back(note); + this->note = note; + retriggered = true; +} + +void MIDIToCVInterface::releaseNote(int note) { + // Remove the note + auto it = std::find(notes.begin(), notes.end(), note); + if (it != notes.end()) + notes.erase(it); + + if (pedal) { + // Don't release if pedal is held + } else if (!notes.empty()) { + // Play previous note + auto it2 = notes.end(); + it2--; + this->note = *it2; + retriggered = true; + } +} + +void MIDIToCVInterface::processMidi(std::vector msg) { + int channel = msg[0] & 0xf; + int status = (msg[0] >> 4) & 0xf; + int data1 = msg[1]; + int data2 = msg[2]; + + //fprintf(stderr, "channel %d status %d data1 %d data2 %d\n", channel, status, data1,data2); + + // Filter channels + if (this->channel >= 0 && this->channel != channel) + return; + + switch (status) { + // note off + case 0x8: { + releaseNote(data1); + } + break; + case 0x9: // note on + if (data2 > 0) { + pressNote(data1); + this->vel = data2; + } else { + // For some reason, some keyboards send a "note on" event with a velocity of 0 to signal that the key has been released. + releaseNote(data1); + } + break; + case 0xb: // cc + switch (data1) { + case 0x01: // mod + this->mod = data2; + break; + case 0x40: // sustain + pedal = (data2 >= 64); + releaseNote(-1); + break; + } + break; + case 0xe: // pitch wheel + this->pitchWheel = data2; + break; + case 0xd: // channel aftertouch + this->afterTouch = data1; + break; + } +} + + +MidiToCVWidget::MidiToCVWidget() { + MIDIToCVInterface *module = new MIDIToCVInterface(); setModule(module); - box.size = Vec(15 * 6, 380); + box.size = Vec(15 * 9, 380); { Panel *panel = new LightPanel(); @@ -310,16 +381,17 @@ MidiInterfaceWidget::MidiInterfaceWidget() { float margin = 5; float labelHeight = 15; float yPos = margin; + float yGap = 40; { Label *label = new Label(); label->box.pos = Vec(margin, yPos); - label->text = "MIDI device"; + label->text = "MIDI to CV"; addChild(label); yPos += labelHeight + margin; MidiChoice *midiChoice = new MidiChoice(); - midiChoice->midiInterface = dynamic_cast(module); + midiChoice->midiModule = dynamic_cast(module); midiChoice->box.pos = Vec(margin, yPos); midiChoice->box.size.x = box.size.x - 10; addChild(midiChoice); @@ -334,59 +406,252 @@ MidiInterfaceWidget::MidiInterfaceWidget() { yPos += labelHeight + margin; ChannelChoice *channelChoice = new ChannelChoice(); - channelChoice->midiInterface = dynamic_cast(module); + channelChoice->midiModule = dynamic_cast(module); channelChoice->box.pos = Vec(margin, yPos); channelChoice->box.size.x = box.size.x - 10; addChild(channelChoice); - yPos += channelChoice->box.size.y + margin; + yPos += channelChoice->box.size.y + margin + 15; } - { + std::string labels[MIDIToCVInterface::NUM_OUTPUTS] = {"1V/oct", "Gate", "Velocity", "Mod Wheel", + "Pitch Wheel", "Aftertouch"}; + + for (int i = 0; i < MIDIToCVInterface::NUM_OUTPUTS; i++) { Label *label = new Label(); label->box.pos = Vec(margin, yPos); - label->text = "1V/oct"; + label->text = labels[i]; addChild(label); - yPos += labelHeight + margin; - addOutput(createOutput(Vec(28, yPos), module, MidiInterface::PITCH_OUTPUT)); - yPos += 37 + margin; + addOutput(createOutput(Vec(15 * 6, yPos - 5), module, i)); + if (i != MIDIToCVInterface::PITCH_OUTPUT) { + addChild(createValueLight>(Vec(15 * 7.5, yPos - 5), &module->lights[i])); + + } + yPos += yGap + margin; + } + + +} + +void MidiToCVWidget::step() { + // Assume QWERTY +#define MIDI_KEY(key, midi) if (glfwGetKey(gWindow, key)) printf("%d\n", midi); + + // MIDI_KEY(GLFW_KEY_Z, 48); + + ModuleWidget::step(); +} + + +/* + * MIDIToCVInterface converts midi note on/off events, velocity , channel aftertouch, pitch wheel and mod weel to + * CV + */ +struct MIDICCToCVInterface : MidiIO, Module { + enum ParamIds { + NUM_PARAMS + }; + enum InputIds { + NUM_INPUTS + }; + enum OutputIds { + NUM_OUTPUTS = 16 + }; + + int cc[NUM_OUTPUTS]; + int ccNum[NUM_OUTPUTS]; + float lights[NUM_OUTPUTS]; + + + MIDICCToCVInterface() : MidiIO(), Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS) { + for (int i = 0; i < NUM_OUTPUTS; i++) { + cc[i] = 0; + ccNum[i] = i; + } + } + + ~MIDICCToCVInterface() { + setPortId(-1); + } + + void step(); + + void processMidi(std::vector msg); + + virtual json_t *toJson() { + json_t *rootJ = json_object(); + addBaseJson(rootJ); + for (int i = 0; i < NUM_OUTPUTS; i++) { + json_object_set_new(rootJ, std::to_string(i).c_str(), json_integer(ccNum[i])); + } + return rootJ; + } + + virtual void fromJson(json_t *rootJ) { + baseFromJson(rootJ); + for (int i = 0; i < NUM_OUTPUTS; i++) { + json_t *ccNumJ = json_object_get(rootJ, std::to_string(i).c_str()); + if (ccNumJ) { + ccNum[i] = json_integer_value(ccNumJ); + } + + } + } + + virtual void initialize() { + setPortId(-1); + } + +}; + + +void MIDICCToCVInterface::step() { + if (rtMidi->isPortOpen()) { + std::vector message; + + // midiIn->getMessage returns empty vector if there are no messages in the queue + + dynamic_cast(rtMidi)->getMessage(&message); + while (message.size() > 0) { + processMidi(message); + dynamic_cast(rtMidi)->getMessage(&message); + } + } + + for (int i = 0; i < NUM_OUTPUTS; i++) { + outputs[i].value = cc[i] / 127.0 * 10.0; + lights[i] = 2.0 * outputs[i].value / 10.0 - 1.0; + } +} + + +void MIDICCToCVInterface::processMidi(std::vector msg) { + int channel = msg[0] & 0xf; + int status = (msg[0] >> 4) & 0xf; + int data1 = msg[1]; + int data2 = msg[2]; + + //fprintf(stderr, "channel %d status %d data1 %d data2 %d\n", channel, status, data1,data2); + + // Filter channels + if (this->channel >= 0 && this->channel != channel) + return; + + if (status == 0xb) { + for (int i = 0; i < NUM_OUTPUTS; i++) { + if (data1 == ccNum[i]) { + this->cc[i] = data2; + } + + } + } +} + + +struct CCNumItem : MenuItem { + MIDICCToCVInterface *midiModule; + int cc; + int num; + + void onAction() { + midiModule->ccNum[num] = cc; + } +}; + +struct CCNumChoice : ChoiceButton { + MIDICCToCVInterface *midiModule; + int num; + + void onAction() { + Menu *menu = gScene->createMenu(); + menu->box.pos = getAbsolutePos().plus(Vec(0, box.size.y)); + menu->box.size.x = box.size.x; + + + for (int cc = 0; cc < 128; cc++) { + CCNumItem *ccNumItem = new CCNumItem(); + ccNumItem->midiModule = midiModule; + ccNumItem->cc = cc; + ccNumItem->num = num; + ccNumItem->text = std::to_string(cc); + menu->pushChild(ccNumItem); + } + } + + void step() { + text = std::to_string(midiModule->ccNum[num]); + } +}; + +MIDICCToCVWidget::MIDICCToCVWidget() { + MIDICCToCVInterface *module = new MIDICCToCVInterface(); + setModule(module); + box.size = Vec(15 * 18, 380); + + { + Panel *panel = new LightPanel(); + panel->box.size = box.size; + addChild(panel); } + float margin = 5; + float labelHeight = 15; + float yPos = margin; + { Label *label = new Label(); label->box.pos = Vec(margin, yPos); - label->text = "Gate"; + label->text = "MIDI CC to CV"; addChild(label); yPos += labelHeight + margin; - addOutput(createOutput(Vec(28, yPos), module, MidiInterface::GATE_OUTPUT)); - yPos += 37 + margin; + MidiChoice *midiChoice = new MidiChoice(); + midiChoice->midiModule = dynamic_cast(module); + midiChoice->box.pos = Vec(margin, yPos); + midiChoice->box.size.x = box.size.x - 10; + addChild(midiChoice); + yPos += midiChoice->box.size.y + margin; } { Label *label = new Label(); label->box.pos = Vec(margin, yPos); - label->text = "Mod Wheel"; + label->text = "Channel"; addChild(label); yPos += labelHeight + margin; - addOutput(createOutput(Vec(28, yPos), module, MidiInterface::MOD_OUTPUT)); - yPos += 37 + margin; + ChannelChoice *channelChoice = new ChannelChoice(); + channelChoice->midiModule = dynamic_cast(module); + channelChoice->box.pos = Vec(margin, yPos); + channelChoice->box.size.x = box.size.x - 10; + addChild(channelChoice); + yPos += channelChoice->box.size.y + margin + 15; } - { - Label *label = new Label(); - label->box.pos = Vec(margin, yPos); - label->text = "Pitch Wheel"; - addChild(label); + for (int i = 0; i < MIDICCToCVInterface::NUM_OUTPUTS; i++) { + CCNumChoice *ccNumChoice = new CCNumChoice(); + ccNumChoice->midiModule = module; + ccNumChoice->num = module->ccNum[i]; + ccNumChoice->box.pos = Vec(10 + (i % 4) * (67), yPos); + ccNumChoice->box.size.x = 15 * 3; + + addChild(ccNumChoice); + yPos += labelHeight + margin; + addOutput(createOutput(Vec(10 + (i % 4) * (67), yPos + 5), module, i)); + addChild(createValueLight>(Vec((i % 4) * (67) + 32, yPos + 5), &module->lights[i])); - addOutput(createOutput(Vec(28, yPos), module, MidiInterface::PITCHWHEEL_OUTPUT)); - yPos += 37 + margin; + if ((i + 1) % 4 == 0) { + yPos += 40 + margin; + } else { + yPos -= labelHeight + margin; + } } + + } -void MidiInterfaceWidget::step() { +void MIDICCToCVWidget::step() { // Assume QWERTY #define MIDI_KEY(key, midi) if (glfwGetKey(gWindow, key)) printf("%d\n", midi); diff --git a/src/core/core.cpp b/src/core/core.cpp index 7fe507cc..9322ca31 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -6,5 +6,6 @@ void init(rack::Plugin *plugin) { plugin->name = "Core"; plugin->homepageUrl = "https://vcvrack.com/"; createModel(plugin, "AudioInterface", "Audio Interface"); - createModel(plugin, "MidiInterface", "MIDI Interface"); + createModel(plugin, "MIDIToCVInterface", "MIDI-to-CV Interface"); + createModel(plugin, "MIDICCToCVInterface", "MIDI CC-to-CV Interface"); } diff --git a/src/core/core.hpp b/src/core/core.hpp index be7939b3..c3b41f05 100644 --- a/src/core/core.hpp +++ b/src/core/core.hpp @@ -12,7 +12,12 @@ struct AudioInterfaceWidget : ModuleWidget { AudioInterfaceWidget(); }; -struct MidiInterfaceWidget : ModuleWidget { - MidiInterfaceWidget(); +struct MidiToCVWidget : ModuleWidget { + MidiToCVWidget(); + void step(); +}; + +struct MIDICCToCVWidget : ModuleWidget { + MIDICCToCVWidget(); void step(); };