|
|
@@ -9,62 +9,51 @@ |
|
|
|
|
|
|
|
using namespace rack; |
|
|
|
|
|
|
|
struct MidiInterface : Module { |
|
|
|
enum ParamIds { |
|
|
|
NUM_PARAMS |
|
|
|
}; |
|
|
|
enum InputIds { |
|
|
|
NUM_INPUTS |
|
|
|
}; |
|
|
|
enum OutputIds { |
|
|
|
PITCH_OUTPUT, |
|
|
|
GATE_OUTPUT, |
|
|
|
MOD_OUTPUT, |
|
|
|
PITCHWHEEL_OUTPUT, |
|
|
|
NUM_OUTPUTS |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
|
* 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; |
|
|
|
RtMidiIn *midiIn = NULL; |
|
|
|
std::list<int> notes; |
|
|
|
RtMidi *rtMidi = NULL; |
|
|
|
|
|
|
|
/** Filter MIDI channel |
|
|
|
-1 means all MIDI channels |
|
|
|
*/ |
|
|
|
int channel = -1; |
|
|
|
bool pedal = false; |
|
|
|
int note = 60; // C4, most modules should use 261.626 Hz |
|
|
|
int mod = 0; |
|
|
|
int pitchWheel = 64; |
|
|
|
bool retrigger = false; |
|
|
|
bool retriggered = false; |
|
|
|
|
|
|
|
MidiInterface() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS) { |
|
|
|
/* |
|
|
|
* If isOut is set to true, creates a RtMidiOut, RtMidiIn otherwise |
|
|
|
*/ |
|
|
|
MidiIO(bool isOut = false) { |
|
|
|
try { |
|
|
|
midiIn = new RtMidiIn(RtMidi::UNSPECIFIED, "VCVRack"); |
|
|
|
if (isOut) { |
|
|
|
rtMidi = new RtMidiOut(RtMidi::UNSPECIFIED, "VCVRack"); |
|
|
|
} else { |
|
|
|
rtMidi = new RtMidiIn(RtMidi::UNSPECIFIED, "VCVRack"); |
|
|
|
} |
|
|
|
} |
|
|
|
catch ( RtMidiError &error ) { |
|
|
|
catch (RtMidiError &error) { |
|
|
|
fprintf(stderr, "Failed to create RtMidiIn: %s\n", error.getMessage().c_str()); |
|
|
|
} |
|
|
|
} |
|
|
|
~MidiInterface() { |
|
|
|
setPortId(-1); |
|
|
|
} |
|
|
|
|
|
|
|
void step(); |
|
|
|
~MidiIO() {} |
|
|
|
|
|
|
|
int getPortCount(); |
|
|
|
|
|
|
|
std::string getPortName(int portId); |
|
|
|
|
|
|
|
// -1 will close the port |
|
|
|
void setPortId(int portId); |
|
|
|
|
|
|
|
void setChannel(int channel) { |
|
|
|
this->channel = channel; |
|
|
|
} |
|
|
|
void pressNote(int note); |
|
|
|
void releaseNote(int note); |
|
|
|
void processMidi(std::vector<unsigned char> msg); |
|
|
|
|
|
|
|
json_t *toJson() { |
|
|
|
json_t *rootJ = json_object(); |
|
|
|
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())); |
|
|
@@ -73,7 +62,7 @@ struct MidiInterface : Module { |
|
|
|
return rootJ; |
|
|
|
} |
|
|
|
|
|
|
|
void fromJson(json_t *rootJ) { |
|
|
|
void baseFromJson(json_t *rootJ) { |
|
|
|
json_t *portNameJ = json_object_get(rootJ, "portName"); |
|
|
|
if (portNameJ) { |
|
|
|
std::string portName = json_string_value(portNameJ); |
|
|
@@ -91,185 +80,89 @@ struct MidiInterface : Module { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
void initialize() { |
|
|
|
setPortId(-1); |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
void MidiInterface::step() { |
|
|
|
if (midiIn->isPortOpen()) { |
|
|
|
std::vector<unsigned char> message; |
|
|
|
|
|
|
|
// midiIn->getMessage returns empty vector if there are no messages in the queue |
|
|
|
double stamp = midiIn->getMessage( &message ); |
|
|
|
while (message.size() > 0) { |
|
|
|
processMidi(message); |
|
|
|
stamp = midiIn->getMessage( &message ); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
outputs[PITCH_OUTPUT].value = ((note - 60)) / 12.0; |
|
|
|
|
|
|
|
bool gate = pedal || !notes.empty(); |
|
|
|
if (retrigger && retriggered) { |
|
|
|
gate = false; |
|
|
|
retriggered = false; |
|
|
|
} |
|
|
|
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; |
|
|
|
} |
|
|
|
|
|
|
|
int MidiInterface::getPortCount() { |
|
|
|
return midiIn->getPortCount(); |
|
|
|
int MidiIO::getPortCount() { |
|
|
|
return rtMidi->getPortCount(); |
|
|
|
} |
|
|
|
|
|
|
|
std::string MidiInterface::getPortName(int portId) { |
|
|
|
std::string MidiIO::getPortName(int portId) { |
|
|
|
std::string portName; |
|
|
|
try { |
|
|
|
portName = midiIn->getPortName(portId); |
|
|
|
portName = rtMidi->getPortName(portId); |
|
|
|
} |
|
|
|
catch ( RtMidiError &error ) { |
|
|
|
catch (RtMidiError &error) { |
|
|
|
fprintf(stderr, "Failed to get Port Name: %d, %s\n", portId, error.getMessage().c_str()); |
|
|
|
} |
|
|
|
return portName; |
|
|
|
} |
|
|
|
|
|
|
|
void MidiInterface::setPortId(int portId) { |
|
|
|
void MidiIO::setPortId(int portId) { |
|
|
|
// Close port if it was previously opened |
|
|
|
if (midiIn->isPortOpen()) { |
|
|
|
midiIn->closePort(); |
|
|
|
if (rtMidi->isPortOpen()) { |
|
|
|
rtMidi->closePort(); |
|
|
|
} |
|
|
|
this->portId = -1; |
|
|
|
|
|
|
|
// Open new port |
|
|
|
if (portId >= 0) { |
|
|
|
midiIn->openPort(portId, "Midi Interface"); |
|
|
|
rtMidi->openPort(portId, "Midi Interface"); |
|
|
|
} |
|
|
|
this->portId = portId; |
|
|
|
} |
|
|
|
|
|
|
|
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(std::vector<unsigned char> 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); |
|
|
|
} |
|
|
|
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; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
struct MidiItem : MenuItem { |
|
|
|
MidiInterface *midiInterface; |
|
|
|
MidiIO *midiModule; |
|
|
|
int portId; |
|
|
|
|
|
|
|
void onAction() { |
|
|
|
midiInterface->setPortId(portId); |
|
|
|
midiModule->setPortId(portId); |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
struct MidiChoice : ChoiceButton { |
|
|
|
MidiInterface *midiInterface; |
|
|
|
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 = midiInterface->getPortCount(); |
|
|
|
int portCount = midiModule->getPortCount(); |
|
|
|
{ |
|
|
|
MidiItem *midiItem = new MidiItem(); |
|
|
|
midiItem->midiInterface = midiInterface; |
|
|
|
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->midiInterface = midiInterface; |
|
|
|
midiItem->midiModule = midiModule; |
|
|
|
midiItem->portId = portId; |
|
|
|
midiItem->text = midiInterface->getPortName(portId); |
|
|
|
midiItem->text = midiModule->getPortName(portId); |
|
|
|
menu->pushChild(midiItem); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
void step() { |
|
|
|
std::string name = midiInterface->getPortName(midiInterface->portId); |
|
|
|
text = ellipsize(name, 8); |
|
|
|
std::string name = midiModule->getPortName(midiModule->portId); |
|
|
|
text = ellipsize(name, 15); |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
struct ChannelItem : MenuItem { |
|
|
|
MidiInterface *midiInterface; |
|
|
|
MidiIO *midiModule; |
|
|
|
int channel; |
|
|
|
|
|
|
|
void onAction() { |
|
|
|
midiInterface->setChannel(channel); |
|
|
|
midiModule->setChannel(channel); |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
struct ChannelChoice : ChoiceButton { |
|
|
|
MidiInterface *midiInterface; |
|
|
|
MidiIO *midiModule; |
|
|
|
|
|
|
|
void onAction() { |
|
|
|
Menu *menu = gScene->createMenu(); |
|
|
|
menu->box.pos = getAbsolutePos().plus(Vec(0, box.size.y)); |
|
|
@@ -277,29 +170,207 @@ struct ChannelChoice : ChoiceButton { |
|
|
|
|
|
|
|
{ |
|
|
|
ChannelItem *channelItem = new ChannelItem(); |
|
|
|
channelItem->midiInterface = midiInterface; |
|
|
|
channelItem->midiModule = midiModule; |
|
|
|
channelItem->channel = -1; |
|
|
|
channelItem->text = "All"; |
|
|
|
menu->pushChild(channelItem); |
|
|
|
} |
|
|
|
for (int channel = 0; channel < 16; channel++) { |
|
|
|
ChannelItem *channelItem = new ChannelItem(); |
|
|
|
channelItem->midiInterface = midiInterface; |
|
|
|
channelItem->midiModule = midiModule; |
|
|
|
channelItem->channel = channel; |
|
|
|
channelItem->text = stringf("%d", channel + 1); |
|
|
|
menu->pushChild(channelItem); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
void step() { |
|
|
|
text = (midiInterface->channel >= 0) ? stringf("%d", midiInterface->channel + 1) : "All"; |
|
|
|
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 { |
|
|
|
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<int> 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]; |
|
|
|
|
|
|
|
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<unsigned char> 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); |
|
|
|
} |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
MidiInterfaceWidget::MidiInterfaceWidget() { |
|
|
|
MidiInterface *module = new MidiInterface(); |
|
|
|
void MIDIToCVInterface::step() { |
|
|
|
if (rtMidi->isPortOpen()) { |
|
|
|
std::vector<unsigned char> message; |
|
|
|
|
|
|
|
// midiIn->getMessage returns empty vector if there are no messages in the queue |
|
|
|
|
|
|
|
dynamic_cast<RtMidiIn *>(rtMidi)->getMessage(&message); |
|
|
|
while (message.size() > 0) { |
|
|
|
processMidi(message); |
|
|
|
dynamic_cast<RtMidiIn *>(rtMidi)->getMessage(&message); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
outputs[PITCH_OUTPUT].value = ((note - 60)) / 12.0; |
|
|
|
|
|
|
|
bool gate = pedal || !notes.empty(); |
|
|
|
if (retrigger && retriggered) { |
|
|
|
gate = false; |
|
|
|
retriggered = false; |
|
|
|
} |
|
|
|
outputs[GATE_OUTPUT].value = gate ? 10.0 : 0.0; |
|
|
|
lights[GATE_OUTPUT] = gate ? 1.0 : 0.0; |
|
|
|
|
|
|
|
outputs[MOD_OUTPUT].value = mod / 127.0 * 10.0; |
|
|
|
lights[MOD_OUTPUT] = mod / 127.0; |
|
|
|
|
|
|
|
outputs[PITCHWHEEL_OUTPUT].value = (pitchWheel - 64) / 64.0 * 10.0; |
|
|
|
lights[MOD_OUTPUT] = pitchWheel / 127.0; |
|
|
|
|
|
|
|
outputs[CHANNEL_AFTERTOUCH_OUTPUT].value = afterTouch / 127.0 * 10.0; |
|
|
|
lights[CHANNEL_AFTERTOUCH_OUTPUT] = afterTouch / 127.0; |
|
|
|
|
|
|
|
outputs[VELOCITY_OUTPUT].value = vel / 127.0 * 10.0; |
|
|
|
lights[VELOCITY_OUTPUT] = vel / 127.0; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
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<unsigned char> 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 * 6, 380); |
|
|
|
box.size = Vec(15 * 9, 380); |
|
|
|
|
|
|
|
{ |
|
|
|
Panel *panel = new LightPanel(); |
|
|
@@ -310,16 +381,17 @@ MidiInterfaceWidget::MidiInterfaceWidget() { |
|
|
|
float margin = 5; |
|
|
|
float labelHeight = 15; |
|
|
|
float yPos = margin; |
|
|
|
float yGap = 40; |
|
|
|
|
|
|
|
{ |
|
|
|
Label *label = new Label(); |
|
|
|
label->box.pos = Vec(margin, yPos); |
|
|
|
label->text = "MIDI device"; |
|
|
|
label->text = "MIDI to CV"; |
|
|
|
addChild(label); |
|
|
|
yPos += labelHeight + margin; |
|
|
|
|
|
|
|
MidiChoice *midiChoice = new MidiChoice(); |
|
|
|
midiChoice->midiInterface = dynamic_cast<MidiInterface*>(module); |
|
|
|
midiChoice->midiModule = dynamic_cast<MidiIO *>(module); |
|
|
|
midiChoice->box.pos = Vec(margin, yPos); |
|
|
|
midiChoice->box.size.x = box.size.x - 10; |
|
|
|
addChild(midiChoice); |
|
|
@@ -334,59 +406,252 @@ MidiInterfaceWidget::MidiInterfaceWidget() { |
|
|
|
yPos += labelHeight + margin; |
|
|
|
|
|
|
|
ChannelChoice *channelChoice = new ChannelChoice(); |
|
|
|
channelChoice->midiInterface = dynamic_cast<MidiInterface*>(module); |
|
|
|
channelChoice->midiModule = dynamic_cast<MidiIO *>(module); |
|
|
|
channelChoice->box.pos = Vec(margin, yPos); |
|
|
|
channelChoice->box.size.x = box.size.x - 10; |
|
|
|
addChild(channelChoice); |
|
|
|
yPos += channelChoice->box.size.y + margin; |
|
|
|
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 = "1V/oct"; |
|
|
|
label->text = labels[i]; |
|
|
|
addChild(label); |
|
|
|
yPos += labelHeight + margin; |
|
|
|
|
|
|
|
addOutput(createOutput<PJ3410Port>(Vec(28, yPos), module, MidiInterface::PITCH_OUTPUT)); |
|
|
|
yPos += 37 + margin; |
|
|
|
addOutput(createOutput<PJ3410Port>(Vec(15 * 6, yPos - 5), module, i)); |
|
|
|
if (i != MIDIToCVInterface::PITCH_OUTPUT) { |
|
|
|
addChild(createValueLight<SmallLight<GreenValueLight>>(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]; |
|
|
|
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<unsigned char> msg); |
|
|
|
|
|
|
|
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); |
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
virtual void initialize() { |
|
|
|
setPortId(-1); |
|
|
|
} |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
void MIDICCToCVInterface::step() { |
|
|
|
if (rtMidi->isPortOpen()) { |
|
|
|
std::vector<unsigned char> message; |
|
|
|
|
|
|
|
// midiIn->getMessage returns empty vector if there are no messages in the queue |
|
|
|
|
|
|
|
dynamic_cast<RtMidiIn *>(rtMidi)->getMessage(&message); |
|
|
|
while (message.size() > 0) { |
|
|
|
processMidi(message); |
|
|
|
dynamic_cast<RtMidiIn *>(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::processMidi(std::vector<unsigned char> 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 CCNumItem : MenuItem { |
|
|
|
MIDICCToCVInterface *midiModule; |
|
|
|
int cc; |
|
|
|
int num; |
|
|
|
|
|
|
|
void onAction() { |
|
|
|
midiModule->ccNum[num] = cc; |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
struct CCNumChoice : ChoiceButton { |
|
|
|
MIDICCToCVInterface *midiModule; |
|
|
|
int num; |
|
|
|
|
|
|
|
void onAction() { |
|
|
|
Menu *menu = gScene->createMenu(); |
|
|
|
menu->box.pos = getAbsolutePos().plus(Vec(0, box.size.y)); |
|
|
|
menu->box.size.x = box.size.x; |
|
|
|
|
|
|
|
|
|
|
|
for (int cc = 0; cc < 128; cc++) { |
|
|
|
CCNumItem *ccNumItem = new CCNumItem(); |
|
|
|
ccNumItem->midiModule = midiModule; |
|
|
|
ccNumItem->cc = cc; |
|
|
|
ccNumItem->num = num; |
|
|
|
ccNumItem->text = std::to_string(cc); |
|
|
|
menu->pushChild(ccNumItem); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
void step() { |
|
|
|
text = std::to_string(midiModule->ccNum[num]); |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
MIDICCToCVWidget::MIDICCToCVWidget() { |
|
|
|
MIDICCToCVInterface *module = new MIDICCToCVInterface(); |
|
|
|
setModule(module); |
|
|
|
box.size = Vec(15 * 18, 380); |
|
|
|
|
|
|
|
{ |
|
|
|
Panel *panel = new LightPanel(); |
|
|
|
panel->box.size = box.size; |
|
|
|
addChild(panel); |
|
|
|
} |
|
|
|
|
|
|
|
float margin = 5; |
|
|
|
float labelHeight = 15; |
|
|
|
float yPos = margin; |
|
|
|
|
|
|
|
{ |
|
|
|
Label *label = new Label(); |
|
|
|
label->box.pos = Vec(margin, yPos); |
|
|
|
label->text = "Gate"; |
|
|
|
label->text = "MIDI CC to CV"; |
|
|
|
addChild(label); |
|
|
|
yPos += labelHeight + margin; |
|
|
|
|
|
|
|
addOutput(createOutput<PJ3410Port>(Vec(28, yPos), module, MidiInterface::GATE_OUTPUT)); |
|
|
|
yPos += 37 + margin; |
|
|
|
MidiChoice *midiChoice = new MidiChoice(); |
|
|
|
midiChoice->midiModule = dynamic_cast<MidiIO *>(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 = "Mod Wheel"; |
|
|
|
label->text = "Channel"; |
|
|
|
addChild(label); |
|
|
|
yPos += labelHeight + margin; |
|
|
|
|
|
|
|
addOutput(createOutput<PJ3410Port>(Vec(28, yPos), module, MidiInterface::MOD_OUTPUT)); |
|
|
|
yPos += 37 + margin; |
|
|
|
ChannelChoice *channelChoice = new ChannelChoice(); |
|
|
|
channelChoice->midiModule = dynamic_cast<MidiIO *>(module); |
|
|
|
channelChoice->box.pos = Vec(margin, yPos); |
|
|
|
channelChoice->box.size.x = box.size.x - 10; |
|
|
|
addChild(channelChoice); |
|
|
|
yPos += channelChoice->box.size.y + margin + 15; |
|
|
|
} |
|
|
|
|
|
|
|
{ |
|
|
|
Label *label = new Label(); |
|
|
|
label->box.pos = Vec(margin, yPos); |
|
|
|
label->text = "Pitch Wheel"; |
|
|
|
addChild(label); |
|
|
|
for (int i = 0; i < MIDICCToCVInterface::NUM_OUTPUTS; i++) { |
|
|
|
CCNumChoice *ccNumChoice = new CCNumChoice(); |
|
|
|
ccNumChoice->midiModule = module; |
|
|
|
ccNumChoice->num = module->ccNum[i]; |
|
|
|
ccNumChoice->box.pos = Vec(10 + (i % 4) * (67), yPos); |
|
|
|
ccNumChoice->box.size.x = 15 * 3; |
|
|
|
|
|
|
|
addChild(ccNumChoice); |
|
|
|
|
|
|
|
yPos += labelHeight + margin; |
|
|
|
addOutput(createOutput<PJ3410Port>(Vec(10 + (i % 4) * (67), yPos + 5), module, i)); |
|
|
|
addChild(createValueLight<SmallLight<GreenValueLight>>(Vec((i % 4) * (67) + 32, yPos + 5), &module->lights[i])); |
|
|
|
|
|
|
|
addOutput(createOutput<PJ3410Port>(Vec(28, yPos), module, MidiInterface::PITCHWHEEL_OUTPUT)); |
|
|
|
yPos += 37 + margin; |
|
|
|
if ((i + 1) % 4 == 0) { |
|
|
|
yPos += 40 + margin; |
|
|
|
} else { |
|
|
|
yPos -= labelHeight + margin; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
void MidiInterfaceWidget::step() { |
|
|
|
void MIDICCToCVWidget::step() { |
|
|
|
// Assume QWERTY |
|
|
|
#define MIDI_KEY(key, midi) if (glfwGetKey(gWindow, key)) printf("%d\n", midi); |
|
|
|
|
|
|
|