(Note: This misses some checks and proper error handling, but should be good enough to test in principle)tags/v0.5.0
| @@ -31,7 +31,7 @@ struct MIDICCToCVInterface : MidiIO, Module { | |||||
| } | } | ||||
| ~MIDICCToCVInterface() { | ~MIDICCToCVInterface() { | ||||
| setPortId(-1); | |||||
| } | } | ||||
| void step(); | void step(); | ||||
| @@ -62,22 +62,20 @@ struct MIDICCToCVInterface : MidiIO, Module { | |||||
| } | } | ||||
| virtual void initialize() { | virtual void initialize() { | ||||
| setPortId(-1); | |||||
| } | } | ||||
| }; | }; | ||||
| void MIDICCToCVInterface::step() { | void MIDICCToCVInterface::step() { | ||||
| if (rtMidi->isPortOpen()) { | |||||
| if (isPortOpen()) { | |||||
| std::vector<unsigned char> message; | std::vector<unsigned char> message; | ||||
| // midiIn->getMessage returns empty vector if there are no messages in the queue | // midiIn->getMessage returns empty vector if there are no messages in the queue | ||||
| dynamic_cast<RtMidiIn *>(rtMidi)->getMessage(&message); | |||||
| message = getMessage(); | |||||
| while (message.size() > 0) { | while (message.size() > 0) { | ||||
| processMidi(message); | processMidi(message); | ||||
| dynamic_cast<RtMidiIn *>(rtMidi)->getMessage(&message); | |||||
| message = getMessage(); | |||||
| } | } | ||||
| } | } | ||||
| @@ -35,11 +35,9 @@ struct MIDIClockToCVInterface : MidiIO, Module { | |||||
| bool stop = false; | bool stop = false; | ||||
| MIDIClockToCVInterface() : MidiIO(), Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS) { | MIDIClockToCVInterface() : MidiIO(), Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS) { | ||||
| (dynamic_cast<RtMidiIn *>(rtMidi))->ignoreTypes(true, false); | |||||
| } | } | ||||
| ~MIDIClockToCVInterface() { | ~MIDIClockToCVInterface() { | ||||
| setPortId(-1); | |||||
| } | } | ||||
| void step(); | void step(); | ||||
| @@ -70,7 +68,6 @@ struct MIDIClockToCVInterface : MidiIO, Module { | |||||
| } | } | ||||
| virtual void initialize() { | virtual void initialize() { | ||||
| setPortId(-1); | |||||
| } | } | ||||
| }; | }; | ||||
| @@ -88,17 +85,15 @@ void MIDIClockToCVInterface::step() { | |||||
| * */ | * */ | ||||
| static int ratios[] = {6, 8, 12, 16, 24, 32, 48, 96, 192}; | static int ratios[] = {6, 8, 12, 16, 24, 32, 48, 96, 192}; | ||||
| if (rtMidi->isPortOpen()) { | |||||
| if (isPortOpen()) { | |||||
| std::vector<unsigned char> message; | std::vector<unsigned char> message; | ||||
| // midiIn->getMessage returns empty vector if there are no messages in the queue | // midiIn->getMessage returns empty vector if there are no messages in the queue | ||||
| dynamic_cast<RtMidiIn *>(rtMidi)->getMessage(&message); | |||||
| message = getMessage(); | |||||
| while (message.size() > 0) { | while (message.size() > 0) { | ||||
| processMidi(message); | processMidi(message); | ||||
| dynamic_cast<RtMidiIn *>(rtMidi)->getMessage(&message); | |||||
| message = getMessage(); | |||||
| } | } | ||||
| } | } | ||||
| if (start) { | if (start) { | ||||
| @@ -6,6 +6,72 @@ | |||||
| using namespace rack; | 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<unsigned char> RtMidiInSplitter::getMessage(std::string deviceName, int id) { | |||||
| std::vector<unsigned char> 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<std::string> RtMidiInSplitter::getDevices() { | |||||
| /*This is a bit unneccessary */ | |||||
| RtMidiIn *t = new RtMidiIn(RtMidi::UNSPECIFIED, "Rack"); | |||||
| std::vector<std::string> 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: | * MidiIO implements the shared functionality of all midi modules, namely: | ||||
| * + Channel Selection (including helper for storing json) | * + Channel Selection (including helper for storing json) | ||||
| @@ -13,48 +79,29 @@ using namespace rack; | |||||
| * + rtMidi initialisation (input or output) | * + rtMidi initialisation (input or output) | ||||
| */ | */ | ||||
| MidiIO::MidiIO(bool isOut) { | MidiIO::MidiIO(bool isOut) { | ||||
| portId = -1; | |||||
| rtMidi = NULL; | |||||
| channel = -1; | 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) { | void MidiIO::setChannel(int channel) { | ||||
| this->channel = channel; | this->channel = channel; | ||||
| } | } | ||||
| json_t *MidiIO::addBaseJson(json_t *rootJ) { | 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)); | json_object_set_new(rootJ, "channel", json_integer(channel)); | ||||
| } | } | ||||
| return rootJ; | return rootJ; | ||||
| } | } | ||||
| void MidiIO::baseFromJson(json_t *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) { | 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"); | 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<std::string> 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<unsigned char> 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() { | void MidiItem::onAction() { | ||||
| midiModule->resetMidi(); // reset Midi values | midiModule->resetMidi(); // reset Midi values | ||||
| midiModule->setPortId(portId); | |||||
| midiModule->openDevice(text); | |||||
| midiModule->setDeviceName(text); | |||||
| } | } | ||||
| void MidiChoice::onAction() { | void MidiChoice::onAction() { | ||||
| @@ -105,36 +146,35 @@ void MidiChoice::onAction() { | |||||
| menu->box.pos = getAbsolutePos().plus(Vec(0, box.size.y)); | menu->box.pos = getAbsolutePos().plus(Vec(0, box.size.y)); | ||||
| menu->box.size.x = box.size.x; | menu->box.size.x = box.size.x; | ||||
| int portCount = midiModule->getPortCount(); | |||||
| { | { | ||||
| MidiItem *midiItem = new MidiItem(); | MidiItem *midiItem = new MidiItem(); | ||||
| midiItem->midiModule = midiModule; | midiItem->midiModule = midiModule; | ||||
| midiItem->portId = -1; | |||||
| midiItem->text = "No device"; | |||||
| midiItem->text = ""; | |||||
| menu->pushChild(midiItem); | menu->pushChild(midiItem); | ||||
| } | } | ||||
| for (int portId = 0; portId < portCount; portId++) { | |||||
| std::vector<std::string> deviceNames = midiModule->getDevices(); | |||||
| for (int i = 0; i < deviceNames.size(); i++) { | |||||
| MidiItem *midiItem = new MidiItem(); | MidiItem *midiItem = new MidiItem(); | ||||
| midiItem->midiModule = midiModule; | midiItem->midiModule = midiModule; | ||||
| midiItem->portId = portId; | |||||
| midiItem->text = midiModule->getPortName(portId); | |||||
| midiItem->text = deviceNames[i]; | |||||
| menu->pushChild(midiItem); | menu->pushChild(midiItem); | ||||
| } | } | ||||
| } | } | ||||
| void MidiChoice::step() { | void MidiChoice::step() { | ||||
| if (midiModule->portId < 0) { | |||||
| if (midiModule->getDeviceName() == "") { | |||||
| text = "No Device"; | text = "No Device"; | ||||
| return; | return; | ||||
| } | } | ||||
| std::string name = midiModule->getPortName(midiModule->portId); | |||||
| std::string name = midiModule->getDeviceName(); | |||||
| text = ellipsize(name, 15); | text = ellipsize(name, 15); | ||||
| } | } | ||||
| void ChannelItem::onAction() { | void ChannelItem::onAction() { | ||||
| midiModule->resetMidi(); // reset Midi values | |||||
| midiModule->setChannel(channel); | |||||
| } | |||||
| midiModule->resetMidi(); // reset Midi values | |||||
| midiModule->setChannel(channel); | |||||
| } | |||||
| void ChannelChoice::onAction() { | void ChannelChoice::onAction() { | ||||
| Menu *menu = gScene->createMenu(); | Menu *menu = gScene->createMenu(); | ||||
| @@ -1,3 +1,4 @@ | |||||
| #include <unordered_map> | |||||
| #include "rack.hpp" | #include "rack.hpp" | ||||
| #include "rtmidi/RtMidi.h" | #include "rtmidi/RtMidi.h" | ||||
| @@ -5,25 +6,64 @@ | |||||
| using namespace rack; | 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<std::string, RtMidiIn*> midiInMap; | |||||
| std::unordered_map<std::string, std::unordered_map<int, std::list<std::vector<unsigned char>>>> 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<unsigned char> getMessage(std::string deviceName, int id); | |||||
| /* Returns Device names as string*/ | |||||
| std::vector<std::string> getDevices(); | |||||
| }; | |||||
| //struct RtMidiOutSplitter { | |||||
| //private: | |||||
| // std::unordered_map<std::string, RtMidiOut> midiOuts; | |||||
| //public: | |||||
| // RtMidiOutSplitter(); | |||||
| //}; | |||||
| struct MidiIO { | 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; | int channel; | ||||
| RtMidi *rtMidi; | |||||
| bool isOut = false; | |||||
| MidiIO(bool isOut=false); | |||||
| int getPortCount(); | |||||
| MidiIO(bool isOut = false); | |||||
| std::string getPortName(int portId); | |||||
| std::vector<std::string> getDevices(); | |||||
| void openDevice(std::string deviceName); | |||||
| void setPortId(int portId); | |||||
| std::string getDeviceName(); | |||||
| void setChannel(int channel); | void setChannel(int channel); | ||||
| std::vector<unsigned char> getMessage(); | |||||
| bool isPortOpen(); | |||||
| json_t *addBaseJson(json_t *rootJ); | json_t *addBaseJson(json_t *rootJ); | ||||
| void baseFromJson(json_t *rootJ); | void baseFromJson(json_t *rootJ); | ||||
| @@ -32,9 +72,12 @@ struct MidiIO { | |||||
| virtual void resetMidi()=0; | virtual void resetMidi()=0; | ||||
| }; | }; | ||||
| ////////////////////// | |||||
| // MIDI module widgets | |||||
| ////////////////////// | |||||
| struct MidiItem : MenuItem { | struct MidiItem : MenuItem { | ||||
| MidiIO *midiModule; | MidiIO *midiModule; | ||||
| int portId; | |||||
| void onAction(); | void onAction(); | ||||
| }; | }; | ||||
| @@ -63,13 +106,13 @@ struct ChannelChoice : ChoiceButton { | |||||
| }; | }; | ||||
| struct MidiToCVWidget : ModuleWidget{ | |||||
| struct MidiToCVWidget : ModuleWidget { | |||||
| MidiToCVWidget(); | MidiToCVWidget(); | ||||
| void step(); | void step(); | ||||
| }; | }; | ||||
| struct MIDICCToCVWidget : ModuleWidget{ | |||||
| struct MIDICCToCVWidget : ModuleWidget { | |||||
| MIDICCToCVWidget(); | MIDICCToCVWidget(); | ||||
| void step(); | void step(); | ||||
| @@ -45,7 +45,6 @@ struct MIDIToCVInterface : MidiIO, Module { | |||||
| } | } | ||||
| ~MIDIToCVInterface() { | ~MIDIToCVInterface() { | ||||
| setPortId(-1); | |||||
| }; | }; | ||||
| void step(); | void step(); | ||||
| @@ -67,7 +66,6 @@ struct MIDIToCVInterface : MidiIO, Module { | |||||
| } | } | ||||
| virtual void initialize() { | virtual void initialize() { | ||||
| setPortId(-1); | |||||
| } | } | ||||
| virtual void resetMidi(); | virtual void resetMidi(); | ||||
| @@ -85,15 +83,14 @@ void MIDIToCVInterface::resetMidi() { | |||||
| } | } | ||||
| void MIDIToCVInterface::step() { | void MIDIToCVInterface::step() { | ||||
| if (rtMidi->isPortOpen()) { | |||||
| if (isPortOpen()) { | |||||
| std::vector<unsigned char> message; | std::vector<unsigned char> message; | ||||
| // midiIn->getMessage returns empty vector if there are no messages in the queue | // midiIn->getMessage returns empty vector if there are no messages in the queue | ||||
| dynamic_cast<RtMidiIn *>(rtMidi)->getMessage(&message); | |||||
| message = getMessage(); | |||||
| while (message.size() > 0) { | while (message.size() > 0) { | ||||
| processMidi(message); | processMidi(message); | ||||
| dynamic_cast<RtMidiIn *>(rtMidi)->getMessage(&message); | |||||
| message = getMessage(); | |||||
| } | } | ||||
| } | } | ||||
| @@ -34,7 +34,6 @@ struct MIDITriggerToCVInterface : MidiIO, Module { | |||||
| } | } | ||||
| ~MIDITriggerToCVInterface() { | ~MIDITriggerToCVInterface() { | ||||
| setPortId(-1); | |||||
| } | } | ||||
| void step(); | void step(); | ||||
| @@ -65,22 +64,20 @@ struct MIDITriggerToCVInterface : MidiIO, Module { | |||||
| } | } | ||||
| virtual void initialize() { | virtual void initialize() { | ||||
| setPortId(-1); | |||||
| } | } | ||||
| }; | }; | ||||
| void MIDITriggerToCVInterface::step() { | void MIDITriggerToCVInterface::step() { | ||||
| if (rtMidi->isPortOpen()) { | |||||
| if (isPortOpen()) { | |||||
| std::vector<unsigned char> message; | std::vector<unsigned char> message; | ||||
| // midiIn->getMessage returns empty vector if there are no messages in the queue | // midiIn->getMessage returns empty vector if there are no messages in the queue | ||||
| dynamic_cast<RtMidiIn *>(rtMidi)->getMessage(&message); | |||||
| message = getMessage(); | |||||
| while (message.size() > 0) { | while (message.size() > 0) { | ||||
| processMidi(message); | processMidi(message); | ||||
| dynamic_cast<RtMidiIn *>(rtMidi)->getMessage(&message); | |||||
| message = getMessage(); | |||||
| } | } | ||||
| } | } | ||||