diff --git a/src/core/MidiCCToCV.cpp b/src/core/MidiCCToCV.cpp index e2f8f918..c3b82613 100644 --- a/src/core/MidiCCToCV.cpp +++ b/src/core/MidiCCToCV.cpp @@ -31,7 +31,7 @@ struct MIDICCToCVInterface : MidiIO, Module { } ~MIDICCToCVInterface() { - setPortId(-1); + } void step(); @@ -62,22 +62,20 @@ struct MIDICCToCVInterface : MidiIO, Module { } virtual void initialize() { - setPortId(-1); } }; void MIDICCToCVInterface::step() { - if (rtMidi->isPortOpen()) { + if (isPortOpen()) { std::vector message; // midiIn->getMessage returns empty vector if there are no messages in the queue - - dynamic_cast(rtMidi)->getMessage(&message); + message = getMessage(); while (message.size() > 0) { processMidi(message); - dynamic_cast(rtMidi)->getMessage(&message); + message = getMessage(); } } diff --git a/src/core/MidiClockToCV.cpp b/src/core/MidiClockToCV.cpp index 9f906823..212beb69 100644 --- a/src/core/MidiClockToCV.cpp +++ b/src/core/MidiClockToCV.cpp @@ -35,11 +35,9 @@ struct MIDIClockToCVInterface : MidiIO, Module { bool stop = false; MIDIClockToCVInterface() : MidiIO(), Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS) { - (dynamic_cast(rtMidi))->ignoreTypes(true, false); } ~MIDIClockToCVInterface() { - setPortId(-1); } void step(); @@ -70,7 +68,6 @@ struct MIDIClockToCVInterface : MidiIO, Module { } virtual void initialize() { - setPortId(-1); } }; @@ -88,17 +85,15 @@ void MIDIClockToCVInterface::step() { * */ static int ratios[] = {6, 8, 12, 16, 24, 32, 48, 96, 192}; - if (rtMidi->isPortOpen()) { + if (isPortOpen()) { std::vector message; // midiIn->getMessage returns empty vector if there are no messages in the queue - - dynamic_cast(rtMidi)->getMessage(&message); + message = getMessage(); while (message.size() > 0) { processMidi(message); - dynamic_cast(rtMidi)->getMessage(&message); + message = getMessage(); } - } if (start) { diff --git a/src/core/MidiInterface.cpp b/src/core/MidiInterface.cpp index 6d9f0991..e763a052 100644 --- a/src/core/MidiInterface.cpp +++ b/src/core/MidiInterface.cpp @@ -6,6 +6,72 @@ using namespace rack; +// note this is currently not thread safe but can be easily archieved by adding a mutex +RtMidiInSplitter::RtMidiInSplitter() { + midiInMap = {}; + deviceIdMessagesMap = {}; +} + +int RtMidiInSplitter::openDevice(std::string deviceName) { + int id; + + if (!midiInMap[deviceName]) { + try { + RtMidiIn *t = new RtMidiIn(RtMidi::UNSPECIFIED, "Rack"); + t->ignoreTypes(true, false); // TODO: make this optional! + midiInMap[deviceName] = t; + for (int i = 0; i < t->getPortCount(); i++) { + if (deviceName == t->getPortName(i)) { + t->openPort(i); + break; + } + } + } + catch (RtMidiError &error) { + fprintf(stderr, "Failed to create RtMidiIn: %s\n", error.getMessage().c_str()); + } + id = 0; + deviceIdMessagesMap[deviceName] = {}; + } else { + id = deviceIdMessagesMap[deviceName].size(); + } + + deviceIdMessagesMap[deviceName][id] = {}; + return id; +} + +std::vector RtMidiInSplitter::getMessage(std::string deviceName, int id) { + std::vector next_msg, ret; + midiInMap[deviceName]->getMessage(&next_msg); + + if (next_msg.size() > 0) { + for (int i = 0; i < deviceIdMessagesMap[deviceName].size(); i++) { + deviceIdMessagesMap[deviceName][i].push_back(next_msg); + } + } + + if (deviceIdMessagesMap[deviceName][id].size() == 0){ + return next_msg; + } + + ret = deviceIdMessagesMap[deviceName][id].front(); + deviceIdMessagesMap[deviceName][id].pop_front(); + return ret; +} + +std::vector RtMidiInSplitter::getDevices() { + /*This is a bit unneccessary */ + RtMidiIn *t = new RtMidiIn(RtMidi::UNSPECIFIED, "Rack"); + + std::vector names = {}; + + for (int i = 0; i < t->getPortCount(); i++) { + names.push_back(t->getPortName(i)); + } + + return names; +} + /** * MidiIO implements the shared functionality of all midi modules, namely: * + Channel Selection (including helper for storing json) @@ -13,48 +79,29 @@ using namespace rack; * + rtMidi initialisation (input or output) */ MidiIO::MidiIO(bool isOut) { - portId = -1; - rtMidi = NULL; channel = -1; - - /* - * If isOut is set to true, creates a RtMidiOut, RtMidiIn otherwise - */ - 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()); - } + this->isOut = isOut; }; +RtMidiInSplitter MidiIO::midiInSplitter = RtMidiInSplitter(); + void MidiIO::setChannel(int channel) { this->channel = channel; } json_t *MidiIO::addBaseJson(json_t *rootJ) { - if (portId >= 0) { - std::string portName = getPortName(portId); - json_object_set_new(rootJ, "portName", json_string(portName.c_str())); + if (deviceName != "") { + json_object_set_new(rootJ, "interfaceName", json_string(deviceName.c_str())); json_object_set_new(rootJ, "channel", json_integer(channel)); } return rootJ; } void MidiIO::baseFromJson(json_t *rootJ) { - json_t *portNameJ = json_object_get(rootJ, "portName"); + json_t *portNameJ = json_object_get(rootJ, "interfaceName"); if (portNameJ) { - std::string portName = json_string_value(portNameJ); - for (int i = 0; i < getPortCount(); i++) { - if (portName == getPortName(i)) { - setPortId(i); - break; - } - } + deviceName = json_string_value(portNameJ); + openDevice(deviceName); } json_t *channelJ = json_object_get(rootJ, "channel"); @@ -63,41 +110,35 @@ void MidiIO::baseFromJson(json_t *rootJ) { } } - -int MidiIO::getPortCount() { - return rtMidi->getPortCount(); +std::vector MidiIO::getDevices() { + return midiInSplitter.getDevices(); } -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::openDevice(std::string deviceName) { + id = midiInSplitter.openDevice(deviceName); + deviceName = deviceName; } -void MidiIO::setPortId(int portId) { +std::string MidiIO::getDeviceName() { + return deviceName; +} - // Close port if it was previously opened - if (rtMidi->isPortOpen()) { - rtMidi->closePort(); - } - this->portId = -1; +std::vector MidiIO::getMessage() { + return midiInSplitter.getMessage(deviceName, id); +} - // Open new port - if (portId >= 0) { - rtMidi->openPort(portId, "Midi Interface"); - } - this->portId = portId; +bool MidiIO::isPortOpen() { + return id > 0; } +void MidiIO::setDeviceName(const std::string &deviceName) { + MidiIO::deviceName = deviceName; +} void MidiItem::onAction() { midiModule->resetMidi(); // reset Midi values - midiModule->setPortId(portId); + midiModule->openDevice(text); + midiModule->setDeviceName(text); } void MidiChoice::onAction() { @@ -105,36 +146,35 @@ void MidiChoice::onAction() { 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"; + midiItem->text = ""; menu->pushChild(midiItem); } - for (int portId = 0; portId < portCount; portId++) { + + std::vector deviceNames = midiModule->getDevices(); + for (int i = 0; i < deviceNames.size(); i++) { MidiItem *midiItem = new MidiItem(); midiItem->midiModule = midiModule; - midiItem->portId = portId; - midiItem->text = midiModule->getPortName(portId); + midiItem->text = deviceNames[i]; menu->pushChild(midiItem); } } void MidiChoice::step() { - if (midiModule->portId < 0) { + if (midiModule->getDeviceName() == "") { text = "No Device"; return; } - std::string name = midiModule->getPortName(midiModule->portId); + std::string name = midiModule->getDeviceName(); text = ellipsize(name, 15); } void ChannelItem::onAction() { - midiModule->resetMidi(); // reset Midi values - midiModule->setChannel(channel); - } + midiModule->resetMidi(); // reset Midi values + midiModule->setChannel(channel); +} void ChannelChoice::onAction() { Menu *menu = gScene->createMenu(); diff --git a/src/core/MidiInterface.hpp b/src/core/MidiInterface.hpp index d4e24dde..c9753266 100644 --- a/src/core/MidiInterface.hpp +++ b/src/core/MidiInterface.hpp @@ -1,3 +1,4 @@ +#include #include "rack.hpp" #include "rtmidi/RtMidi.h" @@ -5,25 +6,64 @@ using namespace rack; -////////////////////// -// MIDI module widgets -////////////////////// +/** + * This class allows to use one instance of rtMidiIn with + * multiple modules. A MidiIn port will be opened only once while multiple + * instances can use it simultaniously, each receiving all its incoming messages. + */ +struct RtMidiInSplitter { +private: + std::unordered_map midiInMap; + std::unordered_map>>> deviceIdMessagesMap; +public: + RtMidiInSplitter(); + + /* Returns an Id which uniquely identifies the caller in combination with the interface name */ + int openDevice(std::string interfaceName); + + /* Returns the next message in queue for given device & id*/ + std::vector getMessage(std::string deviceName, int id); + + /* Returns Device names as string*/ + std::vector getDevices(); + +}; + +//struct RtMidiOutSplitter { +//private: +// std::unordered_map midiOuts; +//public: +// RtMidiOutSplitter(); +//}; struct MidiIO { - int portId; +private: + static RtMidiInSplitter midiInSplitter; + int id = -1; + std::string deviceName = ""; +public: + void setDeviceName(const std::string &deviceName); + +// static RtMidiOutSplitter MidiOutSlpitter = RtMidiOutSplitter(); + +public: int channel; - RtMidi *rtMidi; + bool isOut = false; - MidiIO(bool isOut=false); - int getPortCount(); + MidiIO(bool isOut = false); - std::string getPortName(int portId); + std::vector getDevices(); + void openDevice(std::string deviceName); - void setPortId(int portId); + std::string getDeviceName(); void setChannel(int channel); + std::vector getMessage(); + + bool isPortOpen(); + json_t *addBaseJson(json_t *rootJ); void baseFromJson(json_t *rootJ); @@ -32,9 +72,12 @@ struct MidiIO { virtual void resetMidi()=0; }; +////////////////////// +// MIDI module widgets +////////////////////// + struct MidiItem : MenuItem { MidiIO *midiModule; - int portId; void onAction(); }; @@ -63,13 +106,13 @@ struct ChannelChoice : ChoiceButton { }; -struct MidiToCVWidget : ModuleWidget{ +struct MidiToCVWidget : ModuleWidget { MidiToCVWidget(); void step(); }; -struct MIDICCToCVWidget : ModuleWidget{ +struct MIDICCToCVWidget : ModuleWidget { MIDICCToCVWidget(); void step(); diff --git a/src/core/MidiToCV.cpp b/src/core/MidiToCV.cpp index ee78b135..181052fc 100644 --- a/src/core/MidiToCV.cpp +++ b/src/core/MidiToCV.cpp @@ -45,7 +45,6 @@ struct MIDIToCVInterface : MidiIO, Module { } ~MIDIToCVInterface() { - setPortId(-1); }; void step(); @@ -67,7 +66,6 @@ struct MIDIToCVInterface : MidiIO, Module { } virtual void initialize() { - setPortId(-1); } virtual void resetMidi(); @@ -85,15 +83,14 @@ void MIDIToCVInterface::resetMidi() { } void MIDIToCVInterface::step() { - if (rtMidi->isPortOpen()) { + if (isPortOpen()) { std::vector message; // midiIn->getMessage returns empty vector if there are no messages in the queue - - dynamic_cast(rtMidi)->getMessage(&message); + message = getMessage(); while (message.size() > 0) { processMidi(message); - dynamic_cast(rtMidi)->getMessage(&message); + message = getMessage(); } } diff --git a/src/core/MidiTriggerToCV.cpp b/src/core/MidiTriggerToCV.cpp index f85040d0..1ac6c79b 100644 --- a/src/core/MidiTriggerToCV.cpp +++ b/src/core/MidiTriggerToCV.cpp @@ -34,7 +34,6 @@ struct MIDITriggerToCVInterface : MidiIO, Module { } ~MIDITriggerToCVInterface() { - setPortId(-1); } void step(); @@ -65,22 +64,20 @@ struct MIDITriggerToCVInterface : MidiIO, Module { } virtual void initialize() { - setPortId(-1); } }; void MIDITriggerToCVInterface::step() { - if (rtMidi->isPortOpen()) { + if (isPortOpen()) { std::vector message; // midiIn->getMessage returns empty vector if there are no messages in the queue - - dynamic_cast(rtMidi)->getMessage(&message); + message = getMessage(); while (message.size() > 0) { processMidi(message); - dynamic_cast(rtMidi)->getMessage(&message); + message = getMessage(); } }