(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(); | |||||
} | } | ||||
} | } | ||||