From 4ccb746e5a4f08dec67f456f89704d35fd3f9789 Mon Sep 17 00:00:00 2001 From: ben Date: Sat, 7 Oct 2017 19:56:14 +0200 Subject: [PATCH 01/12] 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(); }; From 1e395ae27c97a4cc9b4306120d3f133153a4c11b Mon Sep 17 00:00:00 2001 From: ben Date: Sun, 8 Oct 2017 16:16:55 +0200 Subject: [PATCH 02/12] polish MIDI-to-CV and add label to selector --- src/core/MidiInterface.cpp | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/core/MidiInterface.cpp b/src/core/MidiInterface.cpp index 9df282d8..26489ae9 100644 --- a/src/core/MidiInterface.cpp +++ b/src/core/MidiInterface.cpp @@ -381,13 +381,27 @@ MidiToCVWidget::MidiToCVWidget() { float margin = 5; float labelHeight = 15; float yPos = margin; - float yGap = 40; + float yGap = 35; + + addChild(createScrew(Vec(margin, 0))); + addChild(createScrew(Vec(box.size.x - 15 - margin, 0))); + addChild(createScrew(Vec(margin, 365))); + addChild(createScrew(Vec(box.size.x - 15 - margin, 365))); { Label *label = new Label(); - label->box.pos = Vec(margin, yPos); + label->box.pos = Vec(box.size.x - margin - 7 * 15, margin); label->text = "MIDI to CV"; addChild(label); + yPos = labelHeight * 2; + + } + + { + Label *label = new Label(); + label->box.pos = Vec(margin, yPos); + label->text = "MIDI Interface"; + addChild(label); yPos += labelHeight + margin; MidiChoice *midiChoice = new MidiChoice(); From f633de164a033721e16734d682d4b258b4f1ddad Mon Sep 17 00:00:00 2001 From: ben Date: Sun, 8 Oct 2017 16:17:44 +0200 Subject: [PATCH 03/12] Use TextField for channel selection (this is still a bit hacky) --- src/core/MidiInterface.cpp | 105 +++++++++++++++++++++---------------- 1 file changed, 59 insertions(+), 46 deletions(-) diff --git a/src/core/MidiInterface.cpp b/src/core/MidiInterface.cpp index 26489ae9..b6eb0170 100644 --- a/src/core/MidiInterface.cpp +++ b/src/core/MidiInterface.cpp @@ -474,6 +474,7 @@ struct MIDICCToCVInterface : MidiIO, Module { int cc[NUM_OUTPUTS]; int ccNum[NUM_OUTPUTS]; + bool ccNumInited[NUM_OUTPUTS]; float lights[NUM_OUTPUTS]; @@ -507,6 +508,7 @@ struct MIDICCToCVInterface : MidiIO, Module { json_t *ccNumJ = json_object_get(rootJ, std::to_string(i).c_str()); if (ccNumJ) { ccNum[i] = json_integer_value(ccNumJ); + ccNumInited[i] = true; } } @@ -562,45 +564,44 @@ void MIDICCToCVInterface::processMidi(std::vector msg) { } -struct CCNumItem : MenuItem { - MIDICCToCVInterface *midiModule; - int cc; - int num; +struct CCTextField : TextField { + void draw(NVGcontext *vg); - void onAction() { - midiModule->ccNum[num] = cc; - } + int *ccNum; + bool *inited; }; -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 CCTextField::draw(NVGcontext *vg) { + // Note: this might not be the best way to do this. + // Text field should have a virtual "onTextChange" function or something. + // draw() is triggered way more frequently + if (text.size() > 0) { + if (*inited) { + *inited = false; + text = std::to_string(*ccNum); } - } - - void step() { - text = std::to_string(midiModule->ccNum[num]); - } -}; + try { + *ccNum = std::stoi(text, NULL, 10); + // Only allow valid cc numbers + if (*ccNum < 0 || *ccNum > 127) { + text = ""; + begin = 0; + end = text.size(); + } + } catch (...) { + text = ""; + begin = 0; + end = text.size(); + } + }; + TextField::draw(vg); +} MIDICCToCVWidget::MIDICCToCVWidget() { MIDICCToCVInterface *module = new MIDICCToCVInterface(); setModule(module); - box.size = Vec(15 * 18, 380); + box.size = Vec(16 * 15, 380); { Panel *panel = new LightPanel(); @@ -612,17 +613,29 @@ MIDICCToCVWidget::MIDICCToCVWidget() { float labelHeight = 15; float yPos = margin; + addChild(createScrew(Vec(margin, 0))); + addChild(createScrew(Vec(box.size.x - 15 - margin, 0))); + addChild(createScrew(Vec(margin, 365))); + addChild(createScrew(Vec(box.size.x - 15 - margin, 365))); { Label *label = new Label(); - label->box.pos = Vec(margin, yPos); + label->box.pos = Vec(box.size.x - margin - 11 * 15, margin); label->text = "MIDI CC to CV"; addChild(label); - yPos += labelHeight + margin; + yPos = labelHeight * 2; + + } + + { + Label *label = new Label(); + label->box.pos = Vec(margin, yPos); + label->text = "MIDI Interface"; + addChild(label); MidiChoice *midiChoice = new MidiChoice(); midiChoice->midiModule = dynamic_cast(module); - midiChoice->box.pos = Vec(margin, yPos); - midiChoice->box.size.x = box.size.x - 10; + midiChoice->box.pos = Vec((box.size.x - 10) / 2 + margin, yPos); + midiChoice->box.size.x = (box.size.x / 2.0) - margin; addChild(midiChoice); yPos += midiChoice->box.size.y + margin; } @@ -632,31 +645,31 @@ MIDICCToCVWidget::MIDICCToCVWidget() { label->box.pos = Vec(margin, yPos); label->text = "Channel"; addChild(label); - yPos += labelHeight + margin; ChannelChoice *channelChoice = new ChannelChoice(); channelChoice->midiModule = dynamic_cast(module); - channelChoice->box.pos = Vec(margin, yPos); - channelChoice->box.size.x = box.size.x - 10; + channelChoice->box.pos = Vec((box.size.x - 10) / 2 + margin, yPos); + channelChoice->box.size.x = (box.size.x / 2.0) - margin; addChild(channelChoice); - yPos += channelChoice->box.size.y + margin + 15; + yPos += channelChoice->box.size.y + margin * 2; } 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; + CCTextField *ccNumChoice = new CCTextField(); + ccNumChoice->ccNum = &module->ccNum[i]; + ccNumChoice->inited = &module->ccNumInited[i]; + ccNumChoice->text = std::to_string(module->ccNum[i]); + ccNumChoice->box.pos = Vec(10 + (i % 4) * (63), yPos); + ccNumChoice->box.size.x = 15 * 2; 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((i % 4) * (63) + 10, yPos + 5), module, i)); + addChild(createValueLight>(Vec((i % 4) * (63) + 32, yPos + 5), &module->lights[i])); if ((i + 1) % 4 == 0) { - yPos += 40 + margin; + yPos += 50 + margin; } else { yPos -= labelHeight + margin; } From 0314a723dfdfb37dce7a79b0ee23ac4045d4e738 Mon Sep 17 00:00:00 2001 From: ben Date: Sun, 8 Oct 2017 19:50:52 +0200 Subject: [PATCH 04/12] Add resetMidi function to MidiIO and implement for Midi Modules --- src/core/MidiInterface.cpp | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/core/MidiInterface.cpp b/src/core/MidiInterface.cpp index b6eb0170..ec8b4dd6 100644 --- a/src/core/MidiInterface.cpp +++ b/src/core/MidiInterface.cpp @@ -80,6 +80,7 @@ struct MidiIO { } } + virtual void resetMidi()=0; // called when midi port is set }; int MidiIO::getPortCount() { @@ -98,6 +99,9 @@ std::string MidiIO::getPortName(int portId) { } void MidiIO::setPortId(int portId) { + + resetMidi(); // reset Midi values + // Close port if it was previously opened if (rtMidi->isPortOpen()) { rtMidi->closePort(); @@ -251,8 +255,19 @@ struct MIDIToCVInterface : MidiIO, Module { setPortId(-1); } + virtual void resetMidi(); }; +void MIDIToCVInterface::resetMidi(){ + mod = 0; + pitchWheel = 64; + afterTouch = 0; + vel = 0; + resetLight = 1.0; + outputs[GATE_OUTPUT].value = 0.0; + notes.clear(); + updateLights(); +} void MIDIToCVInterface::step() { if (rtMidi->isPortOpen()) { @@ -540,6 +555,11 @@ void MIDICCToCVInterface::step() { } } +void MIDICCToCVInterface::resetMidi() { + for (int i =0 ; i< NUM_OUTPUTS; i++){ + cc[i] = 0; + } +}; void MIDICCToCVInterface::processMidi(std::vector msg) { int channel = msg[0] & 0xf; From b24ebf9fc805d3df4403405f25c0fd19f57aca65 Mon Sep 17 00:00:00 2001 From: ben Date: Sun, 8 Oct 2017 19:51:30 +0200 Subject: [PATCH 05/12] Fix Wrong text on midi device selector if no device is selected and add missing function declaration --- src/core/MidiInterface.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/core/MidiInterface.cpp b/src/core/MidiInterface.cpp index ec8b4dd6..a113a131 100644 --- a/src/core/MidiInterface.cpp +++ b/src/core/MidiInterface.cpp @@ -150,6 +150,10 @@ struct MidiChoice : ChoiceButton { } void step() { + if (midiModule->portId < 0) { + text = "No Device"; + return; + } std::string name = midiModule->getPortName(midiModule->portId); text = ellipsize(name, 15); } @@ -508,6 +512,8 @@ struct MIDICCToCVInterface : MidiIO, Module { void processMidi(std::vector msg); + virtual void resetMidi(); + virtual json_t *toJson() { json_t *rootJ = json_object(); addBaseJson(rootJ); From 8fa51f8ceb4b388c02bb4c1e5f826ce91d369175 Mon Sep 17 00:00:00 2001 From: ben Date: Sun, 8 Oct 2017 19:54:37 +0200 Subject: [PATCH 06/12] add reset button to Midi-to-CV module --- src/core/MidiInterface.cpp | 39 ++++++++++++++++++++++++++++---------- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/src/core/MidiInterface.cpp b/src/core/MidiInterface.cpp index a113a131..c7e3b4fc 100644 --- a/src/core/MidiInterface.cpp +++ b/src/core/MidiInterface.cpp @@ -203,6 +203,7 @@ struct ChannelChoice : ChoiceButton { */ struct MIDIToCVInterface : MidiIO, Module { enum ParamIds { + RESET_PARAM, NUM_PARAMS }; enum InputIds { @@ -229,6 +230,9 @@ struct MIDIToCVInterface : MidiIO, Module { bool retriggered = false; float lights[NUM_OUTPUTS]; + SchmittTrigger resetTrigger; + float resetLight = 0.0; + MIDIToCVInterface() : MidiIO(), Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS) { } @@ -260,6 +264,8 @@ struct MIDIToCVInterface : MidiIO, Module { } virtual void resetMidi(); + void updateLights(); + }; void MIDIToCVInterface::resetMidi(){ @@ -273,6 +279,15 @@ void MIDIToCVInterface::resetMidi(){ updateLights(); } +void MIDIToCVInterface::updateLights() { + lights[GATE_OUTPUT] = outputs[GATE_OUTPUT].value/10; + lights[MOD_OUTPUT] = mod / 127.0; + lights[PITCHWHEEL_OUTPUT] = pitchWheel / 127.0; + lights[CHANNEL_AFTERTOUCH_OUTPUT] = afterTouch / 127.0; + lights[VELOCITY_OUTPUT] = vel / 127.0; + +} + void MIDIToCVInterface::step() { if (rtMidi->isPortOpen()) { std::vector message; @@ -293,22 +308,24 @@ void MIDIToCVInterface::step() { gate = false; retriggered = false; } - outputs[GATE_OUTPUT].value = gate ? 10.0 : 0.0; - lights[GATE_OUTPUT] = gate ? 1.0 : 0.0; + if (resetTrigger.process(params[RESET_PARAM].value)) { + resetMidi(); + return; + } - outputs[MOD_OUTPUT].value = mod / 127.0 * 10.0; - lights[MOD_OUTPUT] = mod / 127.0; + if (resetLight > 0) { + resetLight -= resetLight/0.55/gSampleRate; // fade out light + } - outputs[PITCHWHEEL_OUTPUT].value = (pitchWheel - 64) / 64.0 * 10.0; - lights[MOD_OUTPUT] = pitchWheel / 127.0; + 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; 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; -} + updateLights(); +} void MIDIToCVInterface::pressNote(int note) { // Remove existing similar note @@ -416,6 +433,8 @@ MidiToCVWidget::MidiToCVWidget() { } + addParam(createParam(Vec(7 * 15, labelHeight), module, MIDIToCVInterface::RESET_PARAM, 0.0, 1.0, 0.0)); + addChild(createValueLight>(Vec(7*15+5, labelHeight+5), &module->resetLight)); { Label *label = new Label(); label->box.pos = Vec(margin, yPos); From 8b484e877259250478320f5dc7227b9187a5aed4 Mon Sep 17 00:00:00 2001 From: ben Date: Sun, 8 Oct 2017 19:55:11 +0200 Subject: [PATCH 07/12] polish cc-to-cv module --- src/core/MidiInterface.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/core/MidiInterface.cpp b/src/core/MidiInterface.cpp index c7e3b4fc..ea9767cd 100644 --- a/src/core/MidiInterface.cpp +++ b/src/core/MidiInterface.cpp @@ -696,7 +696,7 @@ MIDICCToCVWidget::MIDICCToCVWidget() { channelChoice->box.pos = Vec((box.size.x - 10) / 2 + margin, yPos); channelChoice->box.size.x = (box.size.x / 2.0) - margin; addChild(channelChoice); - yPos += channelChoice->box.size.y + margin * 2; + yPos += channelChoice->box.size.y + margin * 3; } for (int i = 0; i < MIDICCToCVInterface::NUM_OUTPUTS; i++) { @@ -704,8 +704,8 @@ MIDICCToCVWidget::MIDICCToCVWidget() { ccNumChoice->ccNum = &module->ccNum[i]; ccNumChoice->inited = &module->ccNumInited[i]; ccNumChoice->text = std::to_string(module->ccNum[i]); - ccNumChoice->box.pos = Vec(10 + (i % 4) * (63), yPos); - ccNumChoice->box.size.x = 15 * 2; + ccNumChoice->box.pos = Vec(11 + (i % 4) * (63), yPos); + ccNumChoice->box.size.x = 29; addChild(ccNumChoice); @@ -714,7 +714,7 @@ MIDICCToCVWidget::MIDICCToCVWidget() { addChild(createValueLight>(Vec((i % 4) * (63) + 32, yPos + 5), &module->lights[i])); if ((i + 1) % 4 == 0) { - yPos += 50 + margin; + yPos += 47 + margin; } else { yPos -= labelHeight + margin; } From 2515311aad7857be4e38e97c1015f4d83a779dac Mon Sep 17 00:00:00 2001 From: ben Date: Mon, 9 Oct 2017 11:04:18 +0200 Subject: [PATCH 08/12] Clean up code for TextField --- src/core/MidiInterface.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/core/MidiInterface.cpp b/src/core/MidiInterface.cpp index ea9767cd..b68aeefe 100644 --- a/src/core/MidiInterface.cpp +++ b/src/core/MidiInterface.cpp @@ -627,17 +627,15 @@ void CCTextField::draw(NVGcontext *vg) { text = std::to_string(*ccNum); } try { - *ccNum = std::stoi(text, NULL, 10); + *ccNum = std::stoi(text); // Only allow valid cc numbers if (*ccNum < 0 || *ccNum > 127) { text = ""; - begin = 0; - end = text.size(); + begin = end = 0; } } catch (...) { text = ""; - begin = 0; - end = text.size(); + begin = end = 0; } }; TextField::draw(vg); From 5e16c84720c53bff6238630928a36d63106da617 Mon Sep 17 00:00:00 2001 From: ben Date: Mon, 9 Oct 2017 11:35:56 +0200 Subject: [PATCH 09/12] Add another check for invalid input (std::stoi ignores subsequent symbols) --- src/core/MidiInterface.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/MidiInterface.cpp b/src/core/MidiInterface.cpp index b68aeefe..90082ad0 100644 --- a/src/core/MidiInterface.cpp +++ b/src/core/MidiInterface.cpp @@ -629,7 +629,7 @@ void CCTextField::draw(NVGcontext *vg) { try { *ccNum = std::stoi(text); // Only allow valid cc numbers - if (*ccNum < 0 || *ccNum > 127) { + if (*ccNum < 0 || *ccNum > 127 || text.size() > 3) { text = ""; begin = end = 0; } From b49ce246943ee23264dcbdd6d7f8375ba1b87664 Mon Sep 17 00:00:00 2001 From: ben Date: Mon, 9 Oct 2017 11:59:33 +0200 Subject: [PATCH 10/12] Implement onTextChange and set ccNum to -1 for invalid input --- src/core/MidiInterface.cpp | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/core/MidiInterface.cpp b/src/core/MidiInterface.cpp index 90082ad0..63585bbd 100644 --- a/src/core/MidiInterface.cpp +++ b/src/core/MidiInterface.cpp @@ -610,35 +610,33 @@ void MIDICCToCVInterface::processMidi(std::vector msg) { struct CCTextField : TextField { - void draw(NVGcontext *vg); - + void onTextChange(); int *ccNum; bool *inited; }; -void CCTextField::draw(NVGcontext *vg) { - // Note: this might not be the best way to do this. - // Text field should have a virtual "onTextChange" function or something. - // draw() is triggered way more frequently +void CCTextField::onTextChange() { if (text.size() > 0) { if (*inited) { *inited = false; text = std::to_string(*ccNum); } + try { *ccNum = std::stoi(text); // Only allow valid cc numbers if (*ccNum < 0 || *ccNum > 127 || text.size() > 3) { text = ""; begin = end = 0; + *ccNum = -1; } } catch (...) { text = ""; begin = end = 0; + *ccNum = -1; } }; - TextField::draw(vg); } MIDICCToCVWidget::MIDICCToCVWidget() { From b421dd8471ef63e6e6158bcd6648aee1f010e536 Mon Sep 17 00:00:00 2001 From: ben Date: Mon, 9 Oct 2017 12:21:33 +0200 Subject: [PATCH 11/12] fix initialisation --- src/core/MidiInterface.cpp | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/core/MidiInterface.cpp b/src/core/MidiInterface.cpp index 63585bbd..d6d5f46a 100644 --- a/src/core/MidiInterface.cpp +++ b/src/core/MidiInterface.cpp @@ -611,18 +611,24 @@ void MIDICCToCVInterface::processMidi(std::vector msg) { struct CCTextField : TextField { void onTextChange(); + void draw(NVGcontext *vg); int *ccNum; bool *inited; }; +void CCTextField::draw(NVGcontext *vg) { + /* This is necessary, since the save + * file is loaded after constructing the widget*/ + if (*inited) { + *inited = false; + text = std::to_string(*ccNum); + } + + TextField::draw(vg); +} void CCTextField::onTextChange() { if (text.size() > 0) { - if (*inited) { - *inited = false; - text = std::to_string(*ccNum); - } - try { *ccNum = std::stoi(text); // Only allow valid cc numbers From 38f7c22b009f4712bf49aab062901bd6380a2535 Mon Sep 17 00:00:00 2001 From: ben Date: Mon, 9 Oct 2017 12:22:20 +0200 Subject: [PATCH 12/12] formatting --- src/core/MidiInterface.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/core/MidiInterface.cpp b/src/core/MidiInterface.cpp index d6d5f46a..e35f4b9f 100644 --- a/src/core/MidiInterface.cpp +++ b/src/core/MidiInterface.cpp @@ -264,11 +264,12 @@ struct MIDIToCVInterface : MidiIO, Module { } virtual void resetMidi(); + void updateLights(); }; -void MIDIToCVInterface::resetMidi(){ +void MIDIToCVInterface::resetMidi() { mod = 0; pitchWheel = 64; afterTouch = 0; @@ -280,7 +281,7 @@ void MIDIToCVInterface::resetMidi(){ } void MIDIToCVInterface::updateLights() { - lights[GATE_OUTPUT] = outputs[GATE_OUTPUT].value/10; + lights[GATE_OUTPUT] = outputs[GATE_OUTPUT].value / 10; lights[MOD_OUTPUT] = mod / 127.0; lights[PITCHWHEEL_OUTPUT] = pitchWheel / 127.0; lights[CHANNEL_AFTERTOUCH_OUTPUT] = afterTouch / 127.0; @@ -314,7 +315,7 @@ void MIDIToCVInterface::step() { } if (resetLight > 0) { - resetLight -= resetLight/0.55/gSampleRate; // fade out light + resetLight -= resetLight / 0.55 / gSampleRate; // fade out light } @@ -434,7 +435,7 @@ MidiToCVWidget::MidiToCVWidget() { } addParam(createParam(Vec(7 * 15, labelHeight), module, MIDIToCVInterface::RESET_PARAM, 0.0, 1.0, 0.0)); - addChild(createValueLight>(Vec(7*15+5, labelHeight+5), &module->resetLight)); + addChild(createValueLight>(Vec(7 * 15 + 5, labelHeight + 5), &module->resetLight)); { Label *label = new Label(); label->box.pos = Vec(margin, yPos); @@ -581,7 +582,7 @@ void MIDICCToCVInterface::step() { } void MIDICCToCVInterface::resetMidi() { - for (int i =0 ; i< NUM_OUTPUTS; i++){ + for (int i = 0; i < NUM_OUTPUTS; i++) { cc[i] = 0; } }; @@ -611,7 +612,9 @@ void MIDICCToCVInterface::processMidi(std::vector msg) { struct CCTextField : TextField { void onTextChange(); + void draw(NVGcontext *vg); + int *ccNum; bool *inited; };