#include #include #include #include #include "core.hpp" using namespace rack; void midiInit() { PmError err = Pm_Initialize(); if (err) { printf("Failed to initialize PortMidi: %s\n", Pm_GetErrorText(err)); return; } } struct MidiInterface : Module { enum ParamIds { NUM_PARAMS }; enum InputIds { NUM_INPUTS }; enum OutputIds { GATE_OUTPUT, PITCH_OUTPUT, NUM_OUTPUTS }; PortMidiStream *stream = NULL; std::list notes; bool pedal = false; int note = 64; // C4 int pitchWheel = 64; bool retrigger = true; bool retriggered = false; MidiInterface(); ~MidiInterface(); void step(); int getPortCount(); std::string getPortName(int portId); // -1 will close the port void openPort(int portId); void pressNote(int note); void releaseNote(int note); void processMidi(long msg); }; MidiInterface::MidiInterface() { params.resize(NUM_PARAMS); inputs.resize(NUM_INPUTS); outputs.resize(NUM_OUTPUTS); } MidiInterface::~MidiInterface() { openPort(-1); } void MidiInterface::step() { if (stream) { // Read MIDI events PmEvent event; while (Pm_Read(stream, &event, 1) > 0) { processMidi(event.message); } } if (outputs[GATE_OUTPUT]) { bool gate = pedal || !notes.empty(); if (retrigger && retriggered) { gate = false; retriggered = false; } *outputs[GATE_OUTPUT] = gate ? 5.0 : 0.0; } if (outputs[PITCH_OUTPUT]) { *outputs[PITCH_OUTPUT] = ((note - 64) + 2.0*(pitchWheel - 64) / 64.0) / 12.0; } } int MidiInterface::getPortCount() { return Pm_CountDevices(); } std::string MidiInterface::getPortName(int portId) { const PmDeviceInfo *info = Pm_GetDeviceInfo(portId); if (!info) return ""; char name[1024]; snprintf(name, sizeof(name), "%s: %s (%s)", info->interf, info->name, info->input ? "input" : "output"); return name; } void MidiInterface::openPort(int portId) { PmError err; // Close existing port if (stream) { err = Pm_Close(stream); if (err) { printf("Failed to close MIDI port: %s\n", Pm_GetErrorText(err)); } stream = NULL; } // Open new port if (portId >= 0) { err = Pm_OpenInput(&stream, portId, NULL, 128, NULL, NULL); if (err) { printf("Failed to open MIDI port: %s\n", Pm_GetErrorText(err)); return; } } } 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(long msg) { int channel = msg & 0xf; int status = (msg >> 4) & 0xf; int data1 = (msg >> 8) & 0xff; int data2 = (msg >> 16) & 0xff; if (channel != 0) return; printf("channel %d status %d data1 %d data2 %d\n", channel, status, data1, data2); 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 0x40: pedal = (data2 >= 64); releaseNote(-1); break; } break; case 0xe: // pitch wheel this->pitchWheel = data2; break; } } struct MidiItem : MenuItem { MidiInterface *midiInterface; int portId; void onAction() { midiInterface->openPort(portId); } }; struct MidiChoice : ChoiceButton { MidiInterface *midiInterface; void onAction() { MenuOverlay *overlay = new MenuOverlay(); Menu *menu = new Menu(); menu->box.pos = getAbsolutePos().plus(Vec(0, box.size.y)); int portCount = midiInterface->getPortCount(); { MidiItem *midiItem = new MidiItem(); midiItem->midiInterface = midiInterface; 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->portId = portId; midiItem->text = midiInterface->getPortName(portId); menu->pushChild(midiItem); } overlay->addChild(menu); gScene->setOverlay(overlay); } }; MidiInterfaceWidget::MidiInterfaceWidget() : ModuleWidget(new MidiInterface()) { box.size = Vec(15*8, 380); float margin = 5; float yPos = margin; { Label *label = new Label(); label->box.pos = Vec(margin, yPos); label->text = "MIDI Interface"; addChild(label); yPos += label->box.size.y + margin; } { MidiChoice *midiChoice = new MidiChoice(); midiChoice->midiInterface = dynamic_cast(module); midiChoice->text = "MIDI device"; midiChoice->box.pos = Vec(margin, yPos); midiChoice->box.size.x = box.size.x - 10; addChild(midiChoice); yPos += midiChoice->box.size.y + margin; } yPos += 5; addOutput(createOutput(Vec(25, yPos), module, MidiInterface::PITCH_OUTPUT)); addOutput(createOutput(Vec(75, yPos), module, MidiInterface::GATE_OUTPUT)); yPos += 25 + margin; { Label *pitchLabel = new Label(); pitchLabel->box.pos = Vec(25-12, yPos); pitchLabel->text = "Pitch"; addChild(pitchLabel); Label *gateLabel = new Label(); gateLabel->box.pos = Vec(75-12, yPos); gateLabel->text = "Gate"; addChild(gateLabel); yPos += pitchLabel->box.size.y + margin; } } void MidiInterfaceWidget::draw(NVGcontext *vg) { bndBackground(vg, box.pos.x, box.pos.y, box.size.x, box.size.y); ModuleWidget::draw(vg); }