#include #include #include #include "rtmidi/RtMidi.h" #include "core.hpp" #include "gui.hpp" #include "../../include/engine.hpp" using namespace rack; /** * 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; RtMidi *rtMidi = NULL; /** Filter MIDI channel -1 means all MIDI channels */ int channel = -1; /* * If isOut is set to true, creates a RtMidiOut, RtMidiIn otherwise */ MidiIO(bool isOut = false) { try { if (isOut) { rtMidi = new RtMidiOut(RtMidi::UNSPECIFIED, "Rack"); } else { rtMidi = new RtMidiIn(RtMidi::UNSPECIFIED, "Rack"); } } catch (RtMidiError &error) { fprintf(stderr, "Failed to create RtMidiIn: %s\n", error.getMessage().c_str()); } } ~MidiIO() {} int getPortCount(); std::string getPortName(int portId); // -1 will close the port void setPortId(int portId); void setChannel(int channel) { this->channel = channel; } 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())); json_object_set_new(rootJ, "channel", json_integer(channel)); } return rootJ; } void baseFromJson(json_t *rootJ) { json_t *portNameJ = json_object_get(rootJ, "portName"); if (portNameJ) { std::string portName = json_string_value(portNameJ); for (int i = 0; i < getPortCount(); i++) { if (portName == getPortName(i)) { setPortId(i); break; } } } json_t *channelJ = json_object_get(rootJ, "channel"); if (channelJ) { setChannel(json_integer_value(channelJ)); } } virtual void resetMidi()=0; // called when midi port is set }; int MidiIO::getPortCount() { return rtMidi->getPortCount(); } std::string MidiIO::getPortName(int portId) { std::string portName; try { portName = rtMidi->getPortName(portId); } catch (RtMidiError &error) { fprintf(stderr, "Failed to get Port Name: %d, %s\n", portId, error.getMessage().c_str()); } return portName; } void MidiIO::setPortId(int portId) { resetMidi(); // reset Midi values // Close port if it was previously opened if (rtMidi->isPortOpen()) { rtMidi->closePort(); } this->portId = -1; // Open new port if (portId >= 0) { rtMidi->openPort(portId, "Midi Interface"); } this->portId = portId; } struct MidiItem : MenuItem { MidiIO *midiModule; int portId; void onAction() { midiModule->setPortId(portId); } }; struct MidiChoice : ChoiceButton { 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 = midiModule->getPortCount(); { MidiItem *midiItem = new MidiItem(); 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->midiModule = midiModule; midiItem->portId = portId; midiItem->text = midiModule->getPortName(portId); menu->pushChild(midiItem); } } void step() { if (midiModule->portId < 0) { text = "No Device"; return; } std::string name = midiModule->getPortName(midiModule->portId); text = ellipsize(name, 15); } }; struct ChannelItem : MenuItem { MidiIO *midiModule; int channel; void onAction() { midiModule->setChannel(channel); } }; struct ChannelChoice : ChoiceButton { 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; { ChannelItem *channelItem = new ChannelItem(); channelItem->midiModule = midiModule; channelItem->channel = -1; channelItem->text = "All"; menu->pushChild(channelItem); } for (int channel = 0; channel < 16; channel++) { ChannelItem *channelItem = new ChannelItem(); channelItem->midiModule = midiModule; channelItem->channel = channel; channelItem->text = stringf("%d", channel + 1); menu->pushChild(channelItem); } } void step() { 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 { RESET_PARAM, 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]; SchmittTrigger resetTrigger; float resetLight = 0.0; 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); } virtual void resetMidi(); void updateLights(); }; 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::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; // 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; } if (resetTrigger.process(params[RESET_PARAM].value)) { resetMidi(); return; } if (resetLight > 0) { resetLight -= resetLight/0.55/gSampleRate; // fade out light } 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; outputs[VELOCITY_OUTPUT].value = vel / 127.0 * 10.0; updateLights(); } 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 * 9, 380); { Panel *panel = new LightPanel(); panel->box.size = box.size; addChild(panel); } float margin = 5; float labelHeight = 15; float yPos = margin; 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(box.size.x - margin - 7 * 15, margin); label->text = "MIDI to CV"; addChild(label); yPos = labelHeight * 2; } 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); label->text = "MIDI Interface"; addChild(label); yPos += labelHeight + 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 = "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; addChild(channelChoice); 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 = labels[i]; addChild(label); 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]; bool ccNumInited[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 void resetMidi(); 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); ccNumInited[i] = true; } } } 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::resetMidi() { for (int i =0 ; i< NUM_OUTPUTS; i++){ cc[i] = 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 CCTextField : TextField { void draw(NVGcontext *vg); 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 if (text.size() > 0) { if (*inited) { *inited = false; text = std::to_string(*ccNum); } 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(16 * 15, 380); { Panel *panel = new LightPanel(); panel->box.size = box.size; addChild(panel); } float margin = 5; 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(box.size.x - margin - 11 * 15, margin); label->text = "MIDI CC to CV"; addChild(label); 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((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; } { Label *label = new Label(); label->box.pos = Vec(margin, yPos); label->text = "Channel"; addChild(label); ChannelChoice *channelChoice = new ChannelChoice(); channelChoice->midiModule = dynamic_cast(module); 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 * 3; } for (int i = 0; i < MIDICCToCVInterface::NUM_OUTPUTS; i++) { 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(11 + (i % 4) * (63), yPos); ccNumChoice->box.size.x = 29; addChild(ccNumChoice); yPos += labelHeight + margin; 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 += 47 + margin; } else { yPos -= labelHeight + margin; } } } void MIDICCToCVWidget::step() { // Assume QWERTY #define MIDI_KEY(key, midi) if (glfwGetKey(gWindow, key)) printf("%d\n", midi); // MIDI_KEY(GLFW_KEY_Z, 48); ModuleWidget::step(); }