@@ -1 +1 @@ | |||
Subproject commit 4dd22f56d6b733c8de13d6e8b12f13390aa5782e | |||
Subproject commit 015d020615e8169d2f227ad385c5f2aa1e091fd1 |
@@ -4,10 +4,22 @@ | |||
#include "core.hpp" | |||
#include "MidiIO.hpp" | |||
/* | |||
* MIDIToCVInterface converts midi note on/off events, velocity , channel aftertouch, pitch wheel and mod weel to | |||
* CV | |||
*/ | |||
struct CCValue { | |||
int val = 0; // Controller value | |||
TransitionSmoother tSmooth; | |||
int num = 0; // Controller number | |||
bool numInited = false; // Num inited by config file | |||
bool numSelected = false; // Text field selected for midi learn | |||
bool changed = false; // Value has been changed by midi message (only if it is in sync!) | |||
int sync = 0; // Output value sync (implies diff) | |||
bool syncFirst = true; // First value after sync was reset | |||
void resetSync() { | |||
sync = 0; | |||
syncFirst = true; | |||
} | |||
}; | |||
struct MIDICCToCVInterface : MidiIO, Module { | |||
enum ParamIds { | |||
NUM_PARAMS | |||
@@ -22,27 +34,15 @@ struct MIDICCToCVInterface : MidiIO, Module { | |||
NUM_LIGHTS = 16 | |||
}; | |||
int cc[NUM_OUTPUTS]; | |||
int ccNum[NUM_OUTPUTS]; | |||
int ccSync[NUM_OUTPUTS]; | |||
bool ccSyncFirst[NUM_OUTPUTS]; | |||
bool ccNumInited[NUM_OUTPUTS]; | |||
bool onFocus[NUM_OUTPUTS]; | |||
CCValue cc[NUM_OUTPUTS]; | |||
MIDICCToCVInterface() : MidiIO(), Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) { | |||
for (int i = 0; i < NUM_OUTPUTS; i++) { | |||
cc[i] = 0; | |||
ccNum[i] = i; | |||
ccSync[i] = 0; | |||
ccSyncFirst[i] = true; | |||
onFocus[i] = false; | |||
cc[i].num = i; | |||
} | |||
} | |||
~MIDICCToCVInterface() { | |||
} | |||
~MIDICCToCVInterface() {} | |||
void step() override; | |||
@@ -54,9 +54,9 @@ struct MIDICCToCVInterface : MidiIO, Module { | |||
json_t *rootJ = json_object(); | |||
addBaseJson(rootJ); | |||
for (int i = 0; i < NUM_OUTPUTS; i++) { | |||
json_object_set_new(rootJ, ("ccNum" + std::to_string(i)).c_str(), json_integer(ccNum[i])); | |||
json_object_set_new(rootJ, ("ccNum" + std::to_string(i)).c_str(), json_integer(cc[i].num)); | |||
if (outputs[i].active) { | |||
json_object_set_new(rootJ, ("ccVal" + std::to_string(i)).c_str(), json_integer(cc[i])); | |||
json_object_set_new(rootJ, ("ccVal" + std::to_string(i)).c_str(), json_integer(cc[i].val)); | |||
} | |||
} | |||
return rootJ; | |||
@@ -67,13 +67,15 @@ struct MIDICCToCVInterface : MidiIO, Module { | |||
for (int i = 0; i < NUM_OUTPUTS; i++) { | |||
json_t *ccNumJ = json_object_get(rootJ, ("ccNum" + std::to_string(i)).c_str()); | |||
if (ccNumJ) { | |||
ccNum[i] = json_integer_value(ccNumJ); | |||
ccNumInited[i] = true; | |||
cc[i].num = json_integer_value(ccNumJ); | |||
cc[i].numInited = true; | |||
} | |||
json_t *ccValJ = json_object_get(rootJ, ("ccVal" + std::to_string(i)).c_str()); | |||
if (ccValJ) { | |||
cc[i] = json_integer_value(ccValJ); | |||
cc[i].val = json_integer_value(ccValJ); | |||
cc[i].tSmooth.set((cc[i].val / 127.0 * 10.0), (cc[i].val / 127.0 * 10.0)); | |||
cc[i].resetSync(); | |||
} | |||
} | |||
@@ -97,19 +99,25 @@ void MIDICCToCVInterface::step() { | |||
} | |||
} | |||
for (int i = 0; i < NUM_OUTPUTS; i++) { | |||
lights[i].setBrightness(ccSync[i] / 127.0); | |||
lights[i].setBrightness(cc[i].sync / 127.0); | |||
if (cc[i].changed) { | |||
cc[i].tSmooth.set(outputs[i].value, (cc[i].val / 127.0 * 10.0), int(engineGetSampleRate() / 32)); | |||
cc[i].changed = false; | |||
} | |||
outputs[i].value = cc[i] / 127.0 * 10.0; | |||
outputs[i].value = cc[i].tSmooth.next(); | |||
} | |||
} | |||
void MIDICCToCVInterface::resetMidi() { | |||
for (int i = 0; i < NUM_OUTPUTS; i++) { | |||
cc[i] = 0; | |||
ccSync[i] = 0; | |||
ccSyncFirst[i] = true; | |||
cc[i].val = 0; | |||
cc[i].resetSync(); | |||
cc[i].tSmooth.set(0,0); | |||
} | |||
}; | |||
@@ -127,27 +135,29 @@ void MIDICCToCVInterface::processMidi(std::vector<unsigned char> msg) { | |||
if (status == 0xb) { | |||
for (int i = 0; i < NUM_OUTPUTS; i++) { | |||
if (onFocus[i]) { | |||
ccSync[i] = true; | |||
ccSyncFirst[i] = true; | |||
ccNum[i] = data1; | |||
if (cc[i].numSelected) { | |||
cc[i].resetSync(); | |||
cc[i].num = data1; | |||
} | |||
if (data1 == ccNum[i]) { | |||
if (ccSyncFirst[i]) { | |||
ccSyncFirst[i] = false; | |||
if (data2 < cc[i] + 2 && data2 > cc[i] - 2) { | |||
ccSync[i] = 0; | |||
} else { | |||
ccSync[i] = absi(data2 - cc[i]); | |||
if (data1 == cc[i].num) { | |||
/* If the first value we received after sync was reset is +/- 1 of | |||
* the output value the values are in sync*/ | |||
if (cc[i].syncFirst) { | |||
cc[i].syncFirst = false; | |||
if (data2 < cc[i].val + 2 && data2 > cc[i].val - 2) { | |||
cc[i].sync = 0; | |||
}else { | |||
cc[i].sync = absi(data2 - cc[i].val); | |||
} | |||
return; | |||
} | |||
if (ccSync[i] == 0) { | |||
cc[i] = data2; | |||
if (cc[i].sync == 0) { | |||
cc[i].val = data2; | |||
cc[i].changed = true; | |||
} else { | |||
ccSync[i] = absi(data2 - cc[i]); | |||
cc[i].sync = absi(data2 - cc[i].val); | |||
} | |||
} | |||
} | |||
@@ -160,12 +170,10 @@ struct CCTextField : TextField { | |||
void draw(NVGcontext *vg) override; | |||
void onMouseDown(EventMouseDown &e) override; | |||
void onMouseUp(EventMouseUp &e) override; | |||
void onMouseLeave(EventMouseLeave &e) override; | |||
int num; | |||
int outNum; | |||
MIDICCToCVInterface *module; | |||
}; | |||
@@ -173,59 +181,59 @@ struct CCTextField : TextField { | |||
void CCTextField::draw(NVGcontext *vg) { | |||
/* This is necessary, since the save | |||
* file is loaded after constructing the widget*/ | |||
if (module->ccNumInited[num]) { | |||
module->ccNumInited[num] = false; | |||
text = std::to_string(module->ccNum[num]); | |||
if (module->cc[outNum].numInited) { | |||
module->cc[outNum].numInited = false; | |||
text = std::to_string(module->cc[outNum].num); | |||
} | |||
if (module->onFocus[num]) { | |||
text = std::to_string(module->ccNum[num]); | |||
/* If number is selected for midi learn*/ | |||
if (module->cc[outNum].numSelected) { | |||
text = std::to_string(module->cc[outNum].num); | |||
} | |||
TextField::draw(vg); | |||
} | |||
void CCTextField::onMouseDown(EventMouseDown &e) { | |||
void CCTextField::onMouseUp(EventMouseUp &e) { | |||
if (e.button == 1) { | |||
module->onFocus[num] = true; | |||
module->cc[outNum].numSelected = false; | |||
e.consumed = true; | |||
} | |||
e.consumed = true; | |||
TextField::onMouseUp(e); | |||
} | |||
void CCTextField::onMouseUp(EventMouseUp &e) { | |||
void CCTextField::onMouseDown(EventMouseDown &e) { | |||
if (e.button == 1) { | |||
module->onFocus[num] = false; | |||
module->cc[outNum].numSelected = true; | |||
e.consumed = true; | |||
} | |||
e.consumed = true; | |||
TextField::onMouseDown(e); | |||
} | |||
void CCTextField::onMouseLeave(EventMouseLeave &e) { | |||
module->onFocus[num] = false; | |||
module->cc[outNum].numSelected = false; | |||
e.consumed = true; | |||
} | |||
void CCTextField::onTextChange() { | |||
int *ccNum = &module->ccNum[num]; | |||
if (text.size() > 0) { | |||
try { | |||
*ccNum = std::stoi(text); | |||
int num = std::stoi(text); | |||
// Only allow valid cc numbers | |||
if (*ccNum < 0 || *ccNum > 127 || text.size() > 3) { | |||
if (num < 0 || num > 127 || text.size() > 3) { | |||
text = ""; | |||
begin = end = 0; | |||
*ccNum = -1; | |||
return; | |||
} | |||
if (!module->ccNumInited[num] && *ccNum != std::stoi(text)) { | |||
module->ccSync[num] = 0; | |||
module->ccSyncFirst[num] = true; | |||
module->cc[outNum].num = -1; | |||
} else { | |||
module->cc[outNum].num = num; | |||
module->cc[outNum].resetSync(); | |||
} | |||
} catch (...) { | |||
text = ""; | |||
begin = end = 0; | |||
*ccNum = -1; | |||
module->cc[outNum].num = -1; | |||
} | |||
}; | |||
} | |||
@@ -288,8 +296,8 @@ MIDICCToCVWidget::MIDICCToCVWidget() { | |||
for (int i = 0; i < MIDICCToCVInterface::NUM_OUTPUTS; i++) { | |||
CCTextField *ccNumChoice = new CCTextField(); | |||
ccNumChoice->module = module; | |||
ccNumChoice->num = i; | |||
ccNumChoice->text = std::to_string(module->ccNum[i]); | |||
ccNumChoice->outNum = i; | |||
ccNumChoice->text = std::to_string(module->cc[i].num); | |||
ccNumChoice->box.pos = Vec(11 + (i % 4) * (63), yPos); | |||
ccNumChoice->box.size.x = 29; | |||
@@ -19,7 +19,9 @@ struct MIDIClockToCVInterface : MidiIO, Module { | |||
enum OutputIds { | |||
CLOCK1_PULSE, | |||
CLOCK2_PULSE, | |||
RESET_PULSE, | |||
CONTINUE_PULSE, | |||
START_PULSE, | |||
STOP_PULSE, | |||
NUM_OUTPUTS | |||
}; | |||
@@ -28,10 +30,30 @@ struct MIDIClockToCVInterface : MidiIO, Module { | |||
PulseGenerator clock1Pulse; | |||
PulseGenerator clock2Pulse; | |||
PulseGenerator resetPulse; | |||
PulseGenerator continuePulse; | |||
PulseGenerator startPulse; | |||
PulseGenerator stopPulse; | |||
bool tick = false; | |||
bool running = false; | |||
bool reset = false; | |||
bool start = false; | |||
bool stop = false; | |||
bool cont = false; | |||
int c_bar = 0; | |||
/* Note this is in relation to the Midi clock's Tick (6x per 16th note). | |||
* Therefore, e.g. the 2:3 is calculated: | |||
* | |||
* 24 (Ticks per quarter note) * 2 / 3 = 16 | |||
* | |||
* Implying that every 16 midi clock ticks we need to send a pulse | |||
* */ | |||
const int ratios[9] = {6, 8, 12, 16, 24, 32, 48, 96, 192}; | |||
const int numratios = 9; | |||
/* | |||
* Length of clock pulse | |||
*/ | |||
const float pulseTime = 0.005; | |||
MIDIClockToCVInterface() : MidiIO(), Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS) { | |||
@@ -72,19 +94,7 @@ struct MIDIClockToCVInterface : MidiIO, Module { | |||
}; | |||
void MIDIClockToCVInterface::step() { | |||
static int c_bar = 0; | |||
static float trigger_length = 0.05; | |||
static float sampleRate = engineGetSampleRate(); | |||
/* Note this is in relation to the Midi clock's Tick (6x per 16th note). | |||
* Therefore, e.g. the 2:3 is calculated: | |||
* | |||
* 24 (Ticks per quarter note) * 2 / 3 = 16 | |||
* | |||
* Implying that every 16 midi clock ticks we need to send a pulse | |||
* */ | |||
static int ratios[] = {6, 8, 12, 16, 24, 32, 48, 96, 192}; | |||
static int numratios = sizeof(ratios) / sizeof(*ratios); | |||
float sampleRate = engineGetSampleRate(); | |||
if (isPortOpen()) { | |||
std::vector<unsigned char> message; | |||
@@ -105,14 +115,23 @@ void MIDIClockToCVInterface::step() { | |||
clock2ratio = int(clampf(inputs[CLOCK2_RATIO].value, 0.0, 10.0) * (numratios - 1) / 10); | |||
} | |||
if (reset) { | |||
resetPulse.trigger(trigger_length); | |||
reset = false; | |||
if (start) { | |||
start = false; | |||
running = true; | |||
startPulse.trigger(pulseTime); | |||
c_bar = 0; | |||
clock1Pulse.time = 0.0; | |||
clock1Pulse.pulseTime = 0.0; | |||
clock2Pulse.time = 0.0; | |||
clock2Pulse.pulseTime = 0.0; | |||
} | |||
if (stop) { | |||
stop = false; | |||
running = false; | |||
stopPulse.trigger(pulseTime); | |||
} | |||
if (cont) { | |||
cont = false; | |||
running = true; | |||
continuePulse.trigger(pulseTime); | |||
} | |||
if (tick) { | |||
@@ -125,11 +144,11 @@ void MIDIClockToCVInterface::step() { | |||
*/ | |||
if (running) { | |||
if (c_bar % ratios[clock1ratio] == 0) { | |||
clock1Pulse.trigger(trigger_length); | |||
clock1Pulse.trigger(pulseTime); | |||
} | |||
if (c_bar % ratios[clock2ratio] == 0) { | |||
clock2Pulse.trigger(trigger_length); | |||
clock2Pulse.trigger(pulseTime); | |||
} | |||
} | |||
@@ -141,31 +160,39 @@ void MIDIClockToCVInterface::step() { | |||
} | |||
} | |||
bool pulse = clock1Pulse.process(1.0 / sampleRate); | |||
outputs[CLOCK1_PULSE].value = pulse ? 10.0 : 0.0; | |||
pulse = clock2Pulse.process(1.0 / sampleRate); | |||
outputs[CLOCK2_PULSE].value = pulse ? 10.0 : 0.0; | |||
pulse = resetPulse.process(1.0 / sampleRate); | |||
outputs[RESET_PULSE].value = pulse ? 10.0 : 0.0; | |||
pulse = continuePulse.process(1.0 / sampleRate); | |||
outputs[CONTINUE_PULSE].value = pulse ? 10.0 : 0.0; | |||
pulse = startPulse.process(1.0 / sampleRate); | |||
outputs[START_PULSE].value = pulse ? 10.0 : 0.0; | |||
pulse = stopPulse.process(1.0 / sampleRate); | |||
outputs[STOP_PULSE].value = pulse ? 10.0 : 0.0; | |||
} | |||
void MIDIClockToCVInterface::resetMidi() { | |||
outputs[CLOCK1_PULSE].value = 0.0; | |||
outputs[CLOCK2_PULSE].value = 0.0; | |||
} | |||
void MIDIClockToCVInterface::processMidi(std::vector<unsigned char> msg) { | |||
switch (msg[0]) { | |||
case 0xfa: | |||
reset = true; | |||
running = true; | |||
start = true; | |||
break; | |||
case 0xfb: | |||
cont = true; | |||
break; | |||
case 0xfc: | |||
running = false; | |||
stop = true; | |||
break; | |||
case 0xf8: | |||
tick = true; | |||
@@ -243,7 +270,7 @@ MIDIClockToCVWidget::MIDIClockToCVWidget() { | |||
label->box.pos = Vec(box.size.x - margin - 7 * 15, margin); | |||
label->text = "MIDI Clk-CV"; | |||
addChild(label); | |||
yPos = labelHeight * 2; | |||
yPos = labelHeight*2; | |||
} | |||
{ | |||
@@ -258,26 +285,46 @@ MIDIClockToCVWidget::MIDIClockToCVWidget() { | |||
midiChoice->box.pos = Vec(margin, yPos); | |||
midiChoice->box.size.x = box.size.x - 10; | |||
addChild(midiChoice); | |||
yPos += midiChoice->box.size.y + margin * 6; | |||
yPos += midiChoice->box.size.y + margin * 4; | |||
} | |||
{ | |||
Label *label = new Label(); | |||
label->box.pos = Vec(margin, yPos); | |||
label->text = "Start"; | |||
addChild(label); | |||
addOutput(createOutput<PJ3410Port>(Vec(15 * 6, yPos - 5), module, MIDIClockToCVInterface::START_PULSE)); | |||
yPos += labelHeight + margin * 4; | |||
} | |||
{ | |||
Label *label = new Label(); | |||
label->box.pos = Vec(margin, yPos); | |||
label->text = "C1 Ratio"; | |||
label->text = "Stop"; | |||
addChild(label); | |||
addOutput(createOutput<PJ3410Port>(Vec(15 * 6, yPos - 5), module, MIDIClockToCVInterface::STOP_PULSE)); | |||
yPos += labelHeight + margin * 4; | |||
} | |||
addInput(createInput<PJ3410Port>(Vec(15 * 6, yPos - 5), module, MIDIClockToCVInterface::CLOCK1_RATIO)); | |||
{ | |||
Label *label = new Label(); | |||
label->box.pos = Vec(margin, yPos); | |||
label->text = "Continue"; | |||
addChild(label); | |||
addOutput(createOutput<PJ3410Port>(Vec(15 * 6, yPos - 5), module, MIDIClockToCVInterface::CONTINUE_PULSE)); | |||
yPos += labelHeight + margin * 6; | |||
} | |||
yPos += margin * 6; | |||
{ | |||
addInput(createInput<PJ3410Port>(Vec(margin, yPos - 5), module, MIDIClockToCVInterface::CLOCK1_RATIO)); | |||
ClockRatioChoice *ratioChoice = new ClockRatioChoice(); | |||
ratioChoice->clockRatio = &module->clock1ratio; | |||
ratioChoice->box.pos = Vec(margin, yPos); | |||
ratioChoice->box.size.x = box.size.x - 10; | |||
ratioChoice->box.pos = Vec(int(box.size.x/3), yPos); | |||
ratioChoice->box.size.x = int(box.size.x/1.5 - margin); | |||
addChild(ratioChoice); | |||
yPos += ratioChoice->box.size.y + margin * 2; | |||
yPos += ratioChoice->box.size.y + margin * 3; | |||
} | |||
@@ -293,22 +340,15 @@ MIDIClockToCVWidget::MIDIClockToCVWidget() { | |||
{ | |||
Label *label = new Label(); | |||
label->box.pos = Vec(margin, yPos); | |||
label->text = "C2 Ratio"; | |||
addChild(label); | |||
addInput(createInput<PJ3410Port>(Vec(15 * 6, yPos - 5), module, MIDIClockToCVInterface::CLOCK2_RATIO)); | |||
yPos += margin * 6; | |||
addInput(createInput<PJ3410Port>(Vec(margin, yPos - 5), module, MIDIClockToCVInterface::CLOCK2_RATIO)); | |||
ClockRatioChoice *ratioChoice = new ClockRatioChoice(); | |||
ratioChoice->clockRatio = &module->clock2ratio; | |||
ratioChoice->box.pos = Vec(margin, yPos); | |||
ratioChoice->box.size.x = box.size.x - 10; | |||
addChild(ratioChoice); | |||
yPos += ratioChoice->box.size.y + margin * 2; | |||
ratioChoice->box.pos = Vec(int(box.size.x/3), yPos); | |||
ratioChoice->box.size.x = int(box.size.x/1.5 - margin); | |||
addChild(ratioChoice); | |||
yPos += ratioChoice->box.size.y + margin * 3; | |||
} | |||
{ | |||
@@ -318,16 +358,10 @@ MIDIClockToCVWidget::MIDIClockToCVWidget() { | |||
addChild(label); | |||
addOutput(createOutput<PJ3410Port>(Vec(15 * 6, yPos - 5), module, MIDIClockToCVInterface::CLOCK2_PULSE)); | |||
yPos += labelHeight + margin * 7; | |||
yPos += labelHeight + margin * 3; | |||
} | |||
{ | |||
Label *label = new Label(); | |||
label->box.pos = Vec(margin, yPos); | |||
label->text = "Reset"; | |||
addChild(label); | |||
addOutput(createOutput<PJ3410Port>(Vec(15 * 6, yPos - 5), module, MIDIClockToCVInterface::RESET_PULSE)); | |||
} | |||
} | |||
void MIDIClockToCVWidget::step() { | |||
@@ -49,15 +49,28 @@ void MidiIO::baseFromJson(json_t *rootJ) { | |||
} | |||
std::vector<std::string> MidiIO::getDevices() { | |||
/* Note: we could also use an existing interface if one exists */ | |||
static RtMidiIn *m = new RtMidiIn(); | |||
std::vector<std::string> names = {}; | |||
if (isOut) { | |||
// TODO | |||
return names; | |||
} | |||
RtMidiIn *m; | |||
try { | |||
m = new RtMidiIn(); | |||
} catch (RtMidiError &error) { | |||
fprintf(stderr, "Failed to create RtMidiIn: %s\n", error.getMessage().c_str()); | |||
return names; | |||
} | |||
for (unsigned int i = 0; i < m->getPortCount(); i++) { | |||
names.push_back(m->getPortName(i)); | |||
} | |||
if (!isPortOpen()) | |||
delete (m); | |||
return names; | |||
} | |||
@@ -81,6 +94,13 @@ void MidiIO::openDevice(std::string deviceName) { | |||
break; | |||
} | |||
} | |||
if (!mw->isPortOpen()) { | |||
fprintf(stderr, "Failed to create RtMidiIn: No such device %s\n", deviceName.c_str()); | |||
this->deviceName = ""; | |||
this->id = -1; | |||
return; | |||
} | |||
} | |||
catch (RtMidiError &error) { | |||
fprintf(stderr, "Failed to create RtMidiIn: %s\n", error.getMessage().c_str()); | |||
@@ -99,17 +119,17 @@ void MidiIO::openDevice(std::string deviceName) { | |||
void MidiIO::setIgnores(bool ignoreSysex, bool ignoreTime, bool ignoreSense) { | |||
bool sy = true, ti = true, se = true; | |||
midiInMap[deviceName]->ignoresMap[id][0] = ignoreSysex; | |||
midiInMap[deviceName]->ignoresMap[id][1] = ignoreTime; | |||
midiInMap[deviceName]->ignoresMap[id][2] = ignoreSense; | |||
midiInMap[deviceName]->ignoresMap[id].midiSysex = ignoreSysex; | |||
midiInMap[deviceName]->ignoresMap[id].midiTime = ignoreTime; | |||
midiInMap[deviceName]->ignoresMap[id].midiSense = ignoreSense; | |||
for (auto kv : midiInMap[deviceName]->ignoresMap) { | |||
sy = sy && kv.second[0]; | |||
ti = ti && kv.second[1]; | |||
se = se && kv.second[2]; | |||
sy = sy && kv.second.midiSysex; | |||
ti = ti && kv.second.midiTime; | |||
se = se && kv.second.midiSense; | |||
} | |||
midiInMap[deviceName]->ignoreTypes(se,ti,se); | |||
midiInMap[deviceName]->ignoreTypes(se, ti, se); | |||
} | |||
@@ -119,7 +139,7 @@ std::string MidiIO::getDeviceName() { | |||
} | |||
double MidiIO::getMessage(std::vector<unsigned char> *msg) { | |||
std::vector<unsigned char> next_msg; | |||
MidiMessage next_msg = MidiMessage(); | |||
MidiInWrapper *mw = midiInMap[deviceName]; | |||
@@ -128,24 +148,23 @@ double MidiIO::getMessage(std::vector<unsigned char> *msg) { | |||
return 0; | |||
} | |||
double stamp = midiInMap[deviceName]->getMessage(&next_msg); | |||
next_msg.timeStamp = mw->getMessage(&next_msg.bytes); | |||
if (next_msg.bytes.size() > 0) { | |||
for (auto &kv : mw->idMessagesMap) { | |||
if (next_msg.size() > 0) { | |||
for (auto kv : mw->idMessagesMap) { | |||
mw->idMessagesMap[kv.first].push_back(next_msg); | |||
mw->idStampsMap[kv.first].push_back(stamp); | |||
kv.second.push_back(next_msg); | |||
} | |||
} | |||
if (mw->idMessagesMap[id].size() <= 0) { | |||
*msg = next_msg; | |||
return stamp; | |||
if (mw->idMessagesMap[id].size() > 0) { | |||
next_msg = mw->idMessagesMap[id].front(); | |||
mw->idMessagesMap[id].pop_front(); | |||
} | |||
*msg = mw->idMessagesMap[id].front(); | |||
stamp = mw->idStampsMap[id].front(); | |||
mw->idMessagesMap[id].pop_front(); | |||
return stamp; | |||
*msg = next_msg.bytes; | |||
return next_msg.timeStamp; | |||
} | |||
bool MidiIO::isPortOpen() { | |||
@@ -165,9 +184,10 @@ void MidiIO::close() { | |||
mw->erase(id); | |||
if (mw->subscribers == 0) { | |||
if (mw->idMessagesMap.size() == 0) { | |||
mw->closePort(); | |||
midiInMap.erase(deviceName); | |||
delete (mw); | |||
} | |||
id = -1; | |||
@@ -238,4 +258,4 @@ void ChannelChoice::onAction(EventAction &e) { | |||
void ChannelChoice::step() { | |||
text = (midiModule->channel >= 0) ? stringf("%d", midiModule->channel + 1) : "All"; | |||
} | |||
} |
@@ -5,50 +5,53 @@ | |||
using namespace rack; | |||
struct IgnoreFlags { | |||
bool midiSysex = true; | |||
bool midiTime = true; | |||
bool midiSense = true; | |||
}; | |||
struct MidiMessage { | |||
std::vector<unsigned char> bytes; | |||
double timeStamp; | |||
MidiMessage() : bytes(0), timeStamp(0.0) {}; | |||
}; | |||
/** | |||
* 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 MidiInWrapper : RtMidiIn { | |||
std::unordered_map<int, std::list<std::vector<unsigned char>>> idMessagesMap; | |||
std::unordered_map<int, std::list<double>> idStampsMap; | |||
/* Stores Ignore settings for each instance in the following order: | |||
* {ignore_midiSysex, ignore_midiTime, ignore_midiSense} | |||
*/ | |||
std::unordered_map<int, bool[3]> ignoresMap; | |||
std::unordered_map<int, std::list<MidiMessage>> idMessagesMap; | |||
std::unordered_map<int, IgnoreFlags> ignoresMap; | |||
int uid_c = 0; | |||
int subscribers = 0; | |||
MidiInWrapper() : RtMidiIn() { | |||
idMessagesMap = {}; | |||
idStampsMap = {}; | |||
}; | |||
int add() { | |||
int id = ++uid_c; | |||
subscribers++; | |||
idMessagesMap[id] = {}; | |||
idStampsMap[id] = {}; | |||
ignoresMap[id][0] = true; | |||
ignoresMap[id][1] = true; | |||
ignoresMap[id][2] = true; | |||
ignoresMap[id] = IgnoreFlags(); | |||
return id; | |||
} | |||
void erase(int id) { | |||
subscribers--; | |||
idMessagesMap.erase(id); | |||
idStampsMap.erase(id); | |||
ignoresMap.erase(id); | |||
} | |||
}; | |||
/** | |||
* Note: MidiIO is not thread safe which might become | |||
* important in the future | |||
*/ | |||
struct MidiIO { | |||
private: | |||
static std::unordered_map<std::string, MidiInWrapper *> midiInMap; | |||
@@ -90,10 +93,80 @@ public: | |||
/* called when midi port is set */ | |||
virtual void resetMidi() {} | |||
/* called if a user switches or sets the deivce (and after this device is initialised)*/ | |||
/* called if a user switches or sets the device (and after this device is initialised)*/ | |||
virtual void onDeviceChange() {} | |||
}; | |||
struct TransitionSmoother { | |||
enum TransitionFunction { | |||
SMOOTHSTEP, | |||
EXP, | |||
LIN, | |||
}; | |||
enum TransitionMode { | |||
DELTA, | |||
CONST, | |||
}; | |||
float start; | |||
float end; | |||
float x; | |||
float delta; | |||
float step; | |||
TransitionFunction t; | |||
void set(float start, float end, int l = 1500, TransitionFunction t = LIN, TransitionMode m = DELTA, bool reset = true) { | |||
this->start = start; | |||
this->end = end; | |||
this->delta = end - start; | |||
this->t = t; | |||
if (reset || x >= 1) { | |||
this->x = 0; | |||
} | |||
switch (m) { | |||
case DELTA: | |||
/* If the change is smaller, the transition phase is longer */ | |||
this->step = delta > 0 ? delta/l : -delta/l; | |||
break; | |||
case CONST: | |||
this->step = 1.0/l; | |||
break; | |||
} | |||
} | |||
float next() { | |||
float next = start; | |||
x += step; | |||
if (x >= 1) | |||
return end; | |||
switch (t) { | |||
case SMOOTHSTEP: | |||
next += delta*x*x*(3-2*x); | |||
break; | |||
case EXP: | |||
next += delta*x*x; | |||
break; | |||
case LIN: | |||
next += delta*x; | |||
break; | |||
} | |||
if ((delta > 0 && next > end) || (delta <= 0 && next < end)) | |||
return end; | |||
return next;; | |||
} | |||
}; | |||
////////////////////// | |||
// MIDI module widgets | |||
////////////////////// | |||
@@ -9,6 +9,12 @@ | |||
* MIDIToCVInterface converts midi note on/off events, velocity , channel aftertouch, pitch wheel and mod wheel to | |||
* CV | |||
*/ | |||
struct MidiValue { | |||
int val = 0; // Controller value | |||
TransitionSmoother tSmooth; | |||
bool changed = false; // Value has been changed by midi message (only if it is in sync!) | |||
}; | |||
struct MIDIToCVInterface : MidiIO, Module { | |||
enum ParamIds { | |||
RESET_PARAM, | |||
@@ -34,17 +40,17 @@ struct MIDIToCVInterface : MidiIO, Module { | |||
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; | |||
MidiValue mod; | |||
MidiValue afterTouch; | |||
MidiValue pitchWheel; | |||
bool gate = false; | |||
SchmittTrigger resetTrigger; | |||
MIDIToCVInterface() : MidiIO(), Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) { | |||
pitchWheel.val = 64; | |||
pitchWheel.tSmooth.set(0, 0); | |||
} | |||
~MIDIToCVInterface() { | |||
@@ -77,11 +83,14 @@ struct MIDIToCVInterface : MidiIO, Module { | |||
}; | |||
void MIDIToCVInterface::resetMidi() { | |||
mod = 0; | |||
pitchWheel = 64; | |||
afterTouch = 0; | |||
mod.val = 0; | |||
mod.tSmooth.set(0, 0); | |||
pitchWheel.val = 64; | |||
pitchWheel.tSmooth.set(0, 0); | |||
afterTouch.val = 0; | |||
afterTouch.tSmooth.set(0, 0); | |||
vel = 0; | |||
outputs[GATE_OUTPUT].value = 0.0; | |||
gate = false; | |||
notes.clear(); | |||
} | |||
@@ -99,11 +108,6 @@ void MIDIToCVInterface::step() { | |||
outputs[PITCH_OUTPUT].value = ((note - 60)) / 12.0; | |||
bool gate = pedal || !notes.empty(); | |||
if (retrigger && retriggered) { | |||
gate = false; | |||
retriggered = false; | |||
} | |||
if (resetTrigger.process(params[RESET_PARAM].value)) { | |||
resetMidi(); | |||
return; | |||
@@ -112,11 +116,27 @@ void MIDIToCVInterface::step() { | |||
lights[RESET_LIGHT].value -= lights[RESET_LIGHT].value / 0.55 / engineGetSampleRate(); // fade out light | |||
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; | |||
outputs[CHANNEL_AFTERTOUCH_OUTPUT].value = afterTouch / 127.0 * 10.0; | |||
outputs[VELOCITY_OUTPUT].value = vel / 127.0 * 10.0; | |||
int steps = int(engineGetSampleRate() / 32); | |||
if (mod.changed) { | |||
mod.tSmooth.set(outputs[MOD_OUTPUT].value, (mod.val / 127.0 * 10.0), steps); | |||
mod.changed = false; | |||
} | |||
outputs[MOD_OUTPUT].value = mod.tSmooth.next(); | |||
if (pitchWheel.changed) { | |||
pitchWheel.tSmooth.set(outputs[PITCHWHEEL_OUTPUT].value, (pitchWheel.val - 64) / 64.0 * 10.0, steps); | |||
pitchWheel.changed = false; | |||
} | |||
outputs[PITCHWHEEL_OUTPUT].value = pitchWheel.tSmooth.next(); | |||
/* NOTE: I'll leave out value smoothing for after touch for now. I currently don't | |||
* have an after touch capable device around and I assume it would require different | |||
* smoothing*/ | |||
outputs[CHANNEL_AFTERTOUCH_OUTPUT].value = afterTouch.val / 127.0 * 10.0; | |||
} | |||
void MIDIToCVInterface::pressNote(int note) { | |||
@@ -127,7 +147,7 @@ void MIDIToCVInterface::pressNote(int note) { | |||
// Push note | |||
notes.push_back(note); | |||
this->note = note; | |||
retriggered = true; | |||
gate = true; | |||
} | |||
void MIDIToCVInterface::releaseNote(int note) { | |||
@@ -138,12 +158,15 @@ void MIDIToCVInterface::releaseNote(int note) { | |||
if (pedal) { | |||
// Don't release if pedal is held | |||
gate = true; | |||
} else if (!notes.empty()) { | |||
// Play previous note | |||
auto it2 = notes.end(); | |||
it2--; | |||
this->note = *it2; | |||
retriggered = true; | |||
gate = true; | |||
} else { | |||
gate = false; | |||
} | |||
} | |||
@@ -153,7 +176,7 @@ void MIDIToCVInterface::processMidi(std::vector<unsigned char> msg) { | |||
int data1 = msg[1]; | |||
int data2 = msg[2]; | |||
//fprintf(stderr, "channel %d status %d data1 %d data2 %d\n", channel, status, data1,data2); | |||
// fprintf(stderr, "channel %d status %d data1 %d data2 %d\n", channel, status, data1, data2); | |||
// Filter channels | |||
if (this->channel >= 0 && this->channel != channel) | |||
@@ -177,19 +200,24 @@ void MIDIToCVInterface::processMidi(std::vector<unsigned char> msg) { | |||
case 0xb: // cc | |||
switch (data1) { | |||
case 0x01: // mod | |||
this->mod = data2; | |||
mod.val = data2; | |||
mod.changed = true; | |||
break; | |||
case 0x40: // sustain | |||
pedal = (data2 >= 64); | |||
releaseNote(-1); | |||
if (!pedal) { | |||
releaseNote(-1); | |||
} | |||
break; | |||
} | |||
break; | |||
case 0xe: // pitch wheel | |||
this->pitchWheel = data2; | |||
pitchWheel.val = data2; | |||
pitchWheel.changed = true; | |||
break; | |||
case 0xd: // channel aftertouch | |||
this->afterTouch = data1; | |||
afterTouch.val = data1; | |||
afterTouch.changed = true; | |||
break; | |||
} | |||
} | |||
@@ -225,7 +253,8 @@ MidiToCVWidget::MidiToCVWidget() { | |||
} | |||
addParam(createParam<LEDButton>(Vec(7 * 15, labelHeight), module, MIDIToCVInterface::RESET_PARAM, 0.0, 1.0, 0.0)); | |||
addChild(createLight<SmallLight<RedLight>>(Vec(7 * 15 + 5, labelHeight + 5), module, MIDIToCVInterface::RESET_LIGHT)); | |||
addChild(createLight<SmallLight<RedLight>>(Vec(7 * 15 + 5, labelHeight + 5), module, | |||
MIDIToCVInterface::RESET_LIGHT)); | |||
{ | |||
Label *label = new Label(); | |||
label->box.pos = Vec(margin, yPos); | |||
@@ -7,10 +7,13 @@ | |||
using namespace rack; | |||
/* | |||
* MIDIToCVInterface converts midi note on/off events, velocity , channel aftertouch, pitch wheel and mod weel to | |||
* CV | |||
*/ | |||
struct TriggerValue { | |||
int val = 0; | |||
int num; | |||
bool numInited = false; | |||
bool onFocus = false; | |||
}; | |||
struct MIDITriggerToCVInterface : MidiIO, Module { | |||
enum ParamIds { | |||
NUM_PARAMS | |||
@@ -22,16 +25,11 @@ struct MIDITriggerToCVInterface : MidiIO, Module { | |||
NUM_OUTPUTS = 16 | |||
}; | |||
int trigger[NUM_OUTPUTS]; | |||
int triggerNum[NUM_OUTPUTS]; | |||
bool triggerNumInited[NUM_OUTPUTS]; | |||
bool onFocus[NUM_OUTPUTS]; | |||
TriggerValue trigger[NUM_OUTPUTS]; | |||
MIDITriggerToCVInterface() : MidiIO(), Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS) { | |||
for (int i = 0; i < NUM_OUTPUTS; i++) { | |||
trigger[i] = 0; | |||
triggerNum[i] = i; | |||
onFocus[i] = false; | |||
trigger[i].num = i; | |||
} | |||
} | |||
@@ -48,7 +46,7 @@ struct MIDITriggerToCVInterface : MidiIO, Module { | |||
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(triggerNum[i])); | |||
json_object_set_new(rootJ, std::to_string(i).c_str(), json_integer(trigger[i].num)); | |||
} | |||
return rootJ; | |||
} | |||
@@ -58,8 +56,8 @@ struct MIDITriggerToCVInterface : MidiIO, Module { | |||
for (int i = 0; i < NUM_OUTPUTS; i++) { | |||
json_t *ccNumJ = json_object_get(rootJ, std::to_string(i).c_str()); | |||
if (ccNumJ) { | |||
triggerNum[i] = json_integer_value(ccNumJ); | |||
triggerNumInited[i] = true; | |||
trigger[i].num = json_integer_value(ccNumJ); | |||
trigger[i].numInited = true; | |||
} | |||
} | |||
@@ -68,7 +66,6 @@ struct MIDITriggerToCVInterface : MidiIO, Module { | |||
void reset() override { | |||
resetMidi(); | |||
} | |||
}; | |||
@@ -88,13 +85,13 @@ void MIDITriggerToCVInterface::step() { | |||
// Note: Could have an option to select between gate and velocity | |||
// but trigger seams more useful | |||
// outputs[i].value = trigger[i] / 127.0 * 10; | |||
outputs[i].value = trigger[i] > 0 ? 10.0 : 0.0; | |||
outputs[i].value = trigger[i].val > 0 ? 10.0 : 0.0; | |||
} | |||
} | |||
void MIDITriggerToCVInterface::resetMidi() { | |||
for (int i = 0; i < NUM_OUTPUTS; i++) { | |||
trigger[i] = 0; | |||
trigger[i].val = 0; | |||
} | |||
}; | |||
@@ -112,8 +109,8 @@ void MIDITriggerToCVInterface::processMidi(std::vector<unsigned char> msg) { | |||
if (status == 0x8) { // note off | |||
for (int i = 0; i < NUM_OUTPUTS; i++) { | |||
if (data1 == triggerNum[i]) { | |||
trigger[i] = data2; | |||
if (data1 == trigger[i].num) { | |||
trigger[i].val = data2; | |||
} | |||
} | |||
return; | |||
@@ -121,14 +118,12 @@ void MIDITriggerToCVInterface::processMidi(std::vector<unsigned char> msg) { | |||
if (status == 0x9) { // note on | |||
for (int i = 0; i < NUM_OUTPUTS; i++) { | |||
if (onFocus[i]) { | |||
this->triggerNum[i] = data1; | |||
if (trigger[i].onFocus && data2 > 0) { | |||
trigger[i].num = data1; | |||
} | |||
} | |||
for (int i = 0; i < NUM_OUTPUTS; i++) { | |||
if (data1 == triggerNum[i]) { | |||
trigger[i] = data2; | |||
if (data1 == trigger[i].num) { | |||
trigger[i].val = data2; | |||
} | |||
} | |||
} | |||
@@ -146,22 +141,20 @@ struct TriggerTextField : TextField { | |||
void onMouseLeave(EventMouseLeave &e) override; | |||
int *triggerNum; | |||
bool *inited; | |||
bool *onFocus; | |||
int outNum; | |||
MIDITriggerToCVInterface *module; | |||
}; | |||
void TriggerTextField::draw(NVGcontext *vg) { | |||
/* This is necessary, since the save | |||
* file is loaded after constructing the widget*/ | |||
if (*inited) { | |||
*inited = false; | |||
text = std::to_string(*triggerNum); | |||
if (module->trigger[outNum].numInited) { | |||
module->trigger[outNum].numInited = false; | |||
text = std::to_string(module->trigger[outNum].num); | |||
} | |||
if (*onFocus) { | |||
text = std::to_string(*triggerNum); | |||
if (module->trigger[outNum].onFocus) { | |||
text = std::to_string(module->trigger[outNum].num); | |||
} | |||
TextField::draw(vg); | |||
@@ -170,37 +163,42 @@ void TriggerTextField::draw(NVGcontext *vg) { | |||
void TriggerTextField::onTextChange() { | |||
if (text.size() > 0) { | |||
try { | |||
*triggerNum = std::stoi(text); | |||
int num = std::stoi(text); | |||
// Only allow valid cc numbers | |||
if (*triggerNum < 0 || *triggerNum > 127 || text.size() > 3) { | |||
if (num < 0 || num > 127 || text.size() > 3) { | |||
text = ""; | |||
begin = end = 0; | |||
*triggerNum = -1; | |||
module->trigger[outNum].num = -1; | |||
}else { | |||
module->trigger[outNum].num = num; | |||
} | |||
} catch (...) { | |||
text = ""; | |||
begin = end = 0; | |||
*triggerNum = -1; | |||
module->trigger[outNum].num = -1; | |||
} | |||
}; | |||
} | |||
void TriggerTextField::onMouseDown(EventMouseDown &e) { | |||
void TriggerTextField::onMouseUp(EventMouseUp &e) { | |||
if (e.button == 1) { | |||
*onFocus = true; | |||
module->trigger[outNum].onFocus = false; | |||
e.consumed = true; | |||
} | |||
e.consumed = true; | |||
TextField::onMouseUp(e); | |||
} | |||
void TriggerTextField::onMouseUp(EventMouseUp &e) { | |||
void TriggerTextField::onMouseDown(EventMouseDown &e) { | |||
if (e.button == 1) { | |||
*onFocus = false; | |||
module->trigger[outNum].onFocus = true; | |||
e.consumed = true; | |||
} | |||
e.consumed = true; | |||
TextField::onMouseDown(e); | |||
} | |||
void TriggerTextField::onMouseLeave(EventMouseLeave &e) { | |||
*onFocus = false; | |||
module->trigger[outNum].onFocus = false; | |||
e.consumed = true; | |||
} | |||
MIDITriggerToCVWidget::MIDITriggerToCVWidget() { | |||
@@ -260,10 +258,9 @@ MIDITriggerToCVWidget::MIDITriggerToCVWidget() { | |||
for (int i = 0; i < MIDITriggerToCVInterface::NUM_OUTPUTS; i++) { | |||
TriggerTextField *triggerNumChoice = new TriggerTextField(); | |||
triggerNumChoice->triggerNum = &module->triggerNum[i]; | |||
triggerNumChoice->inited = &module->triggerNumInited[i]; | |||
triggerNumChoice->onFocus = &module->onFocus[i]; | |||
triggerNumChoice->text = std::to_string(module->triggerNum[i]); | |||
triggerNumChoice->module = module; | |||
triggerNumChoice->outNum = i; | |||
triggerNumChoice->text = std::to_string(module->trigger[i].num); | |||
triggerNumChoice->box.pos = Vec(11 + (i % 4) * (63), yPos); | |||
triggerNumChoice->box.size.x = 29; | |||
@@ -9,7 +9,6 @@ struct MidiKey { | |||
int pitch = 60; | |||
int at = 0; // aftertouch | |||
int vel = 0; // velocity | |||
int retriggerC = 0; | |||
bool gate = false; | |||
}; | |||
@@ -97,10 +96,9 @@ void QuadMIDIToCVInterface::resetMidi() { | |||
} | |||
void QuadMIDIToCVInterface::step() { | |||
static int msgsProcessed = 0; | |||
if (isPortOpen()) { | |||
std::vector<unsigned char> message; | |||
int msgsProcessed = 0; | |||
// midiIn->getMessage returns empty vector if there are no messages in the queue | |||
// NOTE: For the quadmidi we will process max 4 midi messages per step to avoid | |||
@@ -111,7 +109,6 @@ void QuadMIDIToCVInterface::step() { | |||
getMessage(&message); | |||
msgsProcessed++; | |||
} | |||
msgsProcessed = 0; | |||
} | |||
@@ -136,8 +133,7 @@ void QuadMIDIToCVInterface::processMidi(std::vector<unsigned char> msg) { | |||
int status = (msg[0] >> 4) & 0xf; | |||
int data1 = msg[1]; | |||
int data2 = msg[2]; | |||
static int gate; | |||
bool gate; | |||
// Filter channels | |||
if (this->channel >= 0 && this->channel != channel) | |||
@@ -168,6 +164,7 @@ void QuadMIDIToCVInterface::processMidi(std::vector<unsigned char> msg) { | |||
if (data1 == 0x40) { // pedal | |||
pedal = (data2 >= 64); | |||
if (!pedal) { | |||
open.clear(); | |||
for (int i = 0; i < 4; i++) { | |||
activeKeys[i].gate = false; | |||
open.push_back(i); | |||
@@ -179,7 +176,11 @@ void QuadMIDIToCVInterface::processMidi(std::vector<unsigned char> msg) { | |||
return; | |||
} | |||
if (!pedal && !gate) { | |||
if (pedal && !gate) { | |||
return; | |||
} | |||
if (!gate) { | |||
for (int i = 0; i < 4; i++) { | |||
if (activeKeys[i].pitch == data1) { | |||
activeKeys[i].gate = false; | |||
@@ -194,21 +195,20 @@ void QuadMIDIToCVInterface::processMidi(std::vector<unsigned char> msg) { | |||
} | |||
if (open.empty()) { | |||
open.clear(); | |||
for (int i = 0; i < 4; i++) { | |||
open.push_back(i); | |||
} | |||
} | |||
if (!activeKeys[0].gate && !activeKeys[1].gate && | |||
!activeKeys[2].gate && !activeKeys[3].gate) { | |||
open.sort(); | |||
} | |||
switch (mode) { | |||
case RESET: | |||
if (open.size() == 4 ) { | |||
if (open.size() >= 4) { | |||
for (int i = 0; i < 4; i++) { | |||
activeKeys[i].gate = false; | |||
open.push_back(i); | |||
@@ -220,17 +220,25 @@ void QuadMIDIToCVInterface::processMidi(std::vector<unsigned char> msg) { | |||
break; | |||
case ROTATE: | |||
break; | |||
default: | |||
fprintf(stderr, "No mode selected?!\n"); | |||
} | |||
activeKeys[open.front()].gate = true; | |||
activeKeys[open.front()].pitch = data1; | |||
activeKeys[open.front()].vel = data2; | |||
int next = open.front(); | |||
open.pop_front(); | |||
return; | |||
for (int i = 0; i < 4; i++) { | |||
if (activeKeys[i].pitch == data1 && activeKeys[i].gate) { | |||
activeKeys[i].vel = data2; | |||
if (std::find(open.begin(), open.end(), i) != open.end()) | |||
open.remove(i); | |||
open.push_front(i); | |||
activeKeys[i].gate = false; | |||
} | |||
} | |||
activeKeys[next].gate = true; | |||
activeKeys[next].pitch = data1; | |||
activeKeys[next].vel = data2; | |||
} | |||
int QuadMIDIToCVInterface::getMode() const { | |||