Browse Source

Merge branch 'bontric-2017-10-bontric-midiCC-fix'

tags/v0.5.0
Andrew Belt 7 years ago
parent
commit
a581412f0f
8 changed files with 435 additions and 266 deletions
  1. +1
    -1
      ext/osdialog
  2. +80
    -72
      src/core/MidiCCToCV.cpp
  3. +93
    -59
      src/core/MidiClockToCV.cpp
  4. +45
    -25
      src/core/MidiIO.cpp
  5. +91
    -18
      src/core/MidiIO.hpp
  6. +55
    -26
      src/core/MidiToCV.cpp
  7. +46
    -49
      src/core/MidiTriggerToCV.cpp
  8. +24
    -16
      src/core/QuadMidiToCV.cpp

+ 1
- 1
ext/osdialog

@@ -1 +1 @@
Subproject commit 4dd22f56d6b733c8de13d6e8b12f13390aa5782e
Subproject commit 015d020615e8169d2f227ad385c5f2aa1e091fd1

+ 80
- 72
src/core/MidiCCToCV.cpp View File

@@ -4,10 +4,22 @@
#include "core.hpp" #include "core.hpp"
#include "MidiIO.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 { struct MIDICCToCVInterface : MidiIO, Module {
enum ParamIds { enum ParamIds {
NUM_PARAMS NUM_PARAMS
@@ -22,27 +34,15 @@ struct MIDICCToCVInterface : MidiIO, Module {
NUM_LIGHTS = 16 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) { MIDICCToCVInterface() : MidiIO(), Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) {
for (int i = 0; i < NUM_OUTPUTS; i++) { 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; void step() override;


@@ -54,9 +54,9 @@ struct MIDICCToCVInterface : MidiIO, Module {
json_t *rootJ = json_object(); json_t *rootJ = json_object();
addBaseJson(rootJ); addBaseJson(rootJ);
for (int i = 0; i < NUM_OUTPUTS; i++) { 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) { 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; return rootJ;
@@ -67,13 +67,15 @@ struct MIDICCToCVInterface : MidiIO, Module {
for (int i = 0; i < NUM_OUTPUTS; i++) { for (int i = 0; i < NUM_OUTPUTS; i++) {
json_t *ccNumJ = json_object_get(rootJ, ("ccNum" + std::to_string(i)).c_str()); json_t *ccNumJ = json_object_get(rootJ, ("ccNum" + std::to_string(i)).c_str());
if (ccNumJ) { 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()); json_t *ccValJ = json_object_get(rootJ, ("ccVal" + std::to_string(i)).c_str());
if (ccValJ) { 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++) { 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() { void MIDICCToCVInterface::resetMidi() {
for (int i = 0; i < NUM_OUTPUTS; i++) { 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) { if (status == 0xb) {
for (int i = 0; i < NUM_OUTPUTS; i++) { 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 { } 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 draw(NVGcontext *vg) override;


void onMouseDown(EventMouseDown &e) override; void onMouseDown(EventMouseDown &e) override;

void onMouseUp(EventMouseUp &e) override; void onMouseUp(EventMouseUp &e) override;

void onMouseLeave(EventMouseLeave &e) override; void onMouseLeave(EventMouseLeave &e) override;


int num;
int outNum;


MIDICCToCVInterface *module; MIDICCToCVInterface *module;
}; };
@@ -173,59 +181,59 @@ struct CCTextField : TextField {
void CCTextField::draw(NVGcontext *vg) { void CCTextField::draw(NVGcontext *vg) {
/* This is necessary, since the save /* This is necessary, since the save
* file is loaded after constructing the widget*/ * 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); TextField::draw(vg);
} }


void CCTextField::onMouseDown(EventMouseDown &e) {
void CCTextField::onMouseUp(EventMouseUp &e) {
if (e.button == 1) { 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) { 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) { void CCTextField::onMouseLeave(EventMouseLeave &e) {
module->onFocus[num] = false;
module->cc[outNum].numSelected = false;
e.consumed = true;
} }




void CCTextField::onTextChange() { void CCTextField::onTextChange() {
int *ccNum = &module->ccNum[num];
if (text.size() > 0) { if (text.size() > 0) {
try { try {
*ccNum = std::stoi(text);
int num = std::stoi(text);
// Only allow valid cc numbers // Only allow valid cc numbers
if (*ccNum < 0 || *ccNum > 127 || text.size() > 3) {
if (num < 0 || num > 127 || text.size() > 3) {
text = ""; text = "";
begin = end = 0; 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 (...) { } catch (...) {
text = ""; text = "";
begin = end = 0; 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++) { for (int i = 0; i < MIDICCToCVInterface::NUM_OUTPUTS; i++) {
CCTextField *ccNumChoice = new CCTextField(); CCTextField *ccNumChoice = new CCTextField();
ccNumChoice->module = module; 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.pos = Vec(11 + (i % 4) * (63), yPos);
ccNumChoice->box.size.x = 29; ccNumChoice->box.size.x = 29;




+ 93
- 59
src/core/MidiClockToCV.cpp View File

@@ -19,7 +19,9 @@ struct MIDIClockToCVInterface : MidiIO, Module {
enum OutputIds { enum OutputIds {
CLOCK1_PULSE, CLOCK1_PULSE,
CLOCK2_PULSE, CLOCK2_PULSE,
RESET_PULSE,
CONTINUE_PULSE,
START_PULSE,
STOP_PULSE,
NUM_OUTPUTS NUM_OUTPUTS
}; };


@@ -28,10 +30,30 @@ struct MIDIClockToCVInterface : MidiIO, Module {


PulseGenerator clock1Pulse; PulseGenerator clock1Pulse;
PulseGenerator clock2Pulse; PulseGenerator clock2Pulse;
PulseGenerator resetPulse;
PulseGenerator continuePulse;
PulseGenerator startPulse;
PulseGenerator stopPulse;
bool tick = false; bool tick = false;
bool running = 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) { MIDIClockToCVInterface() : MidiIO(), Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS) {
@@ -72,19 +94,7 @@ struct MIDIClockToCVInterface : MidiIO, Module {
}; };


void MIDIClockToCVInterface::step() { 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()) { if (isPortOpen()) {
std::vector<unsigned char> message; 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); 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; 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) { if (tick) {
@@ -125,11 +144,11 @@ void MIDIClockToCVInterface::step() {
*/ */
if (running) { if (running) {
if (c_bar % ratios[clock1ratio] == 0) { if (c_bar % ratios[clock1ratio] == 0) {
clock1Pulse.trigger(trigger_length);
clock1Pulse.trigger(pulseTime);
} }


if (c_bar % ratios[clock2ratio] == 0) { 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); bool pulse = clock1Pulse.process(1.0 / sampleRate);
outputs[CLOCK1_PULSE].value = pulse ? 10.0 : 0.0; outputs[CLOCK1_PULSE].value = pulse ? 10.0 : 0.0;


pulse = clock2Pulse.process(1.0 / sampleRate); pulse = clock2Pulse.process(1.0 / sampleRate);
outputs[CLOCK2_PULSE].value = pulse ? 10.0 : 0.0; 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() { void MIDIClockToCVInterface::resetMidi() {
outputs[CLOCK1_PULSE].value = 0.0; outputs[CLOCK1_PULSE].value = 0.0;
outputs[CLOCK2_PULSE].value = 0.0;
} }


void MIDIClockToCVInterface::processMidi(std::vector<unsigned char> msg) { void MIDIClockToCVInterface::processMidi(std::vector<unsigned char> msg) {


switch (msg[0]) { switch (msg[0]) {
case 0xfa: case 0xfa:
reset = true;
running = true;
start = true;
break;
case 0xfb:
cont = true;
break; break;
case 0xfc: case 0xfc:
running = false;
stop = true;
break; break;
case 0xf8: case 0xf8:
tick = true; tick = true;
@@ -243,7 +270,7 @@ MIDIClockToCVWidget::MIDIClockToCVWidget() {
label->box.pos = Vec(box.size.x - margin - 7 * 15, margin); label->box.pos = Vec(box.size.x - margin - 7 * 15, margin);
label->text = "MIDI Clk-CV"; label->text = "MIDI Clk-CV";
addChild(label); addChild(label);
yPos = labelHeight * 2;
yPos = labelHeight*2;
} }


{ {
@@ -258,26 +285,46 @@ MIDIClockToCVWidget::MIDIClockToCVWidget() {
midiChoice->box.pos = Vec(margin, yPos); midiChoice->box.pos = Vec(margin, yPos);
midiChoice->box.size.x = box.size.x - 10; midiChoice->box.size.x = box.size.x - 10;
addChild(midiChoice); 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 *label = new Label();
label->box.pos = Vec(margin, yPos); label->box.pos = Vec(margin, yPos);
label->text = "C1 Ratio";
label->text = "Stop";
addChild(label); 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(); ClockRatioChoice *ratioChoice = new ClockRatioChoice();
ratioChoice->clockRatio = &module->clock1ratio; 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); 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(); ClockRatioChoice *ratioChoice = new ClockRatioChoice();
ratioChoice->clockRatio = &module->clock2ratio; 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); addChild(label);


addOutput(createOutput<PJ3410Port>(Vec(15 * 6, yPos - 5), module, MIDIClockToCVInterface::CLOCK2_PULSE)); 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() { void MIDIClockToCVWidget::step() {


+ 45
- 25
src/core/MidiIO.cpp View File

@@ -49,15 +49,28 @@ void MidiIO::baseFromJson(json_t *rootJ) {
} }


std::vector<std::string> MidiIO::getDevices() { 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 = {}; 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++) { for (unsigned int i = 0; i < m->getPortCount(); i++) {
names.push_back(m->getPortName(i)); names.push_back(m->getPortName(i));
} }


if (!isPortOpen())
delete (m);

return names; return names;
} }


@@ -81,6 +94,13 @@ void MidiIO::openDevice(std::string deviceName) {
break; 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) { catch (RtMidiError &error) {
fprintf(stderr, "Failed to create RtMidiIn: %s\n", error.getMessage().c_str()); 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) { void MidiIO::setIgnores(bool ignoreSysex, bool ignoreTime, bool ignoreSense) {
bool sy = true, ti = true, se = true; 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) { 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) { double MidiIO::getMessage(std::vector<unsigned char> *msg) {
std::vector<unsigned char> next_msg;
MidiMessage next_msg = MidiMessage();


MidiInWrapper *mw = midiInMap[deviceName]; MidiInWrapper *mw = midiInMap[deviceName];


@@ -128,24 +148,23 @@ double MidiIO::getMessage(std::vector<unsigned char> *msg) {
return 0; 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() { bool MidiIO::isPortOpen() {
@@ -165,9 +184,10 @@ void MidiIO::close() {


mw->erase(id); mw->erase(id);


if (mw->subscribers == 0) {
if (mw->idMessagesMap.size() == 0) {
mw->closePort(); mw->closePort();
midiInMap.erase(deviceName); midiInMap.erase(deviceName);
delete (mw);
} }


id = -1; id = -1;
@@ -238,4 +258,4 @@ void ChannelChoice::onAction(EventAction &e) {


void ChannelChoice::step() { void ChannelChoice::step() {
text = (midiModule->channel >= 0) ? stringf("%d", midiModule->channel + 1) : "All"; text = (midiModule->channel >= 0) ? stringf("%d", midiModule->channel + 1) : "All";
}
}

+ 91
- 18
src/core/MidiIO.hpp View File

@@ -5,50 +5,53 @@


using namespace rack; 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 * This class allows to use one instance of rtMidiIn with
* multiple modules. A MidiIn port will be opened only once while multiple * multiple modules. A MidiIn port will be opened only once while multiple
* instances can use it simultaniously, each receiving all its incoming messages. * instances can use it simultaniously, each receiving all its incoming messages.
*/ */

struct MidiInWrapper : RtMidiIn { 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 uid_c = 0;
int subscribers = 0;


MidiInWrapper() : RtMidiIn() { MidiInWrapper() : RtMidiIn() {
idMessagesMap = {}; idMessagesMap = {};
idStampsMap = {};
}; };


int add() { int add() {
int id = ++uid_c; int id = ++uid_c;
subscribers++;
idMessagesMap[id] = {}; idMessagesMap[id] = {};
idStampsMap[id] = {};

ignoresMap[id][0] = true;
ignoresMap[id][1] = true;
ignoresMap[id][2] = true;
ignoresMap[id] = IgnoreFlags();
return id; return id;
} }


void erase(int id) { void erase(int id) {
subscribers--;
idMessagesMap.erase(id); idMessagesMap.erase(id);
idStampsMap.erase(id);
ignoresMap.erase(id); ignoresMap.erase(id);
} }
}; };


/**
* Note: MidiIO is not thread safe which might become
* important in the future
*/
struct MidiIO { struct MidiIO {
private: private:
static std::unordered_map<std::string, MidiInWrapper *> midiInMap; static std::unordered_map<std::string, MidiInWrapper *> midiInMap;
@@ -90,10 +93,80 @@ public:
/* called when midi port is set */ /* called when midi port is set */
virtual void resetMidi() {} 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() {} 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 // MIDI module widgets
////////////////////// //////////////////////


+ 55
- 26
src/core/MidiToCV.cpp View File

@@ -9,6 +9,12 @@
* MIDIToCVInterface converts midi note on/off events, velocity , channel aftertouch, pitch wheel and mod wheel to * MIDIToCVInterface converts midi note on/off events, velocity , channel aftertouch, pitch wheel and mod wheel to
* CV * 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 { struct MIDIToCVInterface : MidiIO, Module {
enum ParamIds { enum ParamIds {
RESET_PARAM, RESET_PARAM,
@@ -34,17 +40,17 @@ struct MIDIToCVInterface : MidiIO, Module {
std::list<int> notes; std::list<int> notes;
bool pedal = false; bool pedal = false;
int note = 60; // C4, most modules should use 261.626 Hz int note = 60; // C4, most modules should use 261.626 Hz
int mod = 0;
int vel = 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; SchmittTrigger resetTrigger;


MIDIToCVInterface() : MidiIO(), Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) { MIDIToCVInterface() : MidiIO(), Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) {

pitchWheel.val = 64;
pitchWheel.tSmooth.set(0, 0);
} }


~MIDIToCVInterface() { ~MIDIToCVInterface() {
@@ -77,11 +83,14 @@ struct MIDIToCVInterface : MidiIO, Module {
}; };


void MIDIToCVInterface::resetMidi() { 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; vel = 0;
outputs[GATE_OUTPUT].value = 0.0;
gate = false;
notes.clear(); notes.clear();
} }


@@ -99,11 +108,6 @@ void MIDIToCVInterface::step() {


outputs[PITCH_OUTPUT].value = ((note - 60)) / 12.0; 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)) { if (resetTrigger.process(params[RESET_PARAM].value)) {
resetMidi(); resetMidi();
return; return;
@@ -112,11 +116,27 @@ void MIDIToCVInterface::step() {
lights[RESET_LIGHT].value -= lights[RESET_LIGHT].value / 0.55 / engineGetSampleRate(); // fade out light lights[RESET_LIGHT].value -= lights[RESET_LIGHT].value / 0.55 / engineGetSampleRate(); // fade out light


outputs[GATE_OUTPUT].value = gate ? 10.0 : 0.0; 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; 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) { void MIDIToCVInterface::pressNote(int note) {
@@ -127,7 +147,7 @@ void MIDIToCVInterface::pressNote(int note) {
// Push note // Push note
notes.push_back(note); notes.push_back(note);
this->note = note; this->note = note;
retriggered = true;
gate = true;
} }


void MIDIToCVInterface::releaseNote(int note) { void MIDIToCVInterface::releaseNote(int note) {
@@ -138,12 +158,15 @@ void MIDIToCVInterface::releaseNote(int note) {


if (pedal) { if (pedal) {
// Don't release if pedal is held // Don't release if pedal is held
gate = true;
} else if (!notes.empty()) { } else if (!notes.empty()) {
// Play previous note // Play previous note
auto it2 = notes.end(); auto it2 = notes.end();
it2--; it2--;
this->note = *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 data1 = msg[1];
int data2 = msg[2]; 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 // Filter channels
if (this->channel >= 0 && this->channel != channel) if (this->channel >= 0 && this->channel != channel)
@@ -177,19 +200,24 @@ void MIDIToCVInterface::processMidi(std::vector<unsigned char> msg) {
case 0xb: // cc case 0xb: // cc
switch (data1) { switch (data1) {
case 0x01: // mod case 0x01: // mod
this->mod = data2;
mod.val = data2;
mod.changed = true;
break; break;
case 0x40: // sustain case 0x40: // sustain
pedal = (data2 >= 64); pedal = (data2 >= 64);
releaseNote(-1);
if (!pedal) {
releaseNote(-1);
}
break; break;
} }
break; break;
case 0xe: // pitch wheel case 0xe: // pitch wheel
this->pitchWheel = data2;
pitchWheel.val = data2;
pitchWheel.changed = true;
break; break;
case 0xd: // channel aftertouch case 0xd: // channel aftertouch
this->afterTouch = data1;
afterTouch.val = data1;
afterTouch.changed = true;
break; break;
} }
} }
@@ -225,7 +253,8 @@ MidiToCVWidget::MidiToCVWidget() {
} }


addParam(createParam<LEDButton>(Vec(7 * 15, labelHeight), module, MIDIToCVInterface::RESET_PARAM, 0.0, 1.0, 0.0)); 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 *label = new Label();
label->box.pos = Vec(margin, yPos); label->box.pos = Vec(margin, yPos);


+ 46
- 49
src/core/MidiTriggerToCV.cpp View File

@@ -7,10 +7,13 @@


using namespace rack; 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 { struct MIDITriggerToCVInterface : MidiIO, Module {
enum ParamIds { enum ParamIds {
NUM_PARAMS NUM_PARAMS
@@ -22,16 +25,11 @@ struct MIDITriggerToCVInterface : MidiIO, Module {
NUM_OUTPUTS = 16 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) { MIDITriggerToCVInterface() : MidiIO(), Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS) {
for (int i = 0; i < NUM_OUTPUTS; i++) { 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(); json_t *rootJ = json_object();
addBaseJson(rootJ); addBaseJson(rootJ);
for (int i = 0; i < NUM_OUTPUTS; i++) { 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; return rootJ;
} }
@@ -58,8 +56,8 @@ struct MIDITriggerToCVInterface : MidiIO, Module {
for (int i = 0; i < NUM_OUTPUTS; i++) { for (int i = 0; i < NUM_OUTPUTS; i++) {
json_t *ccNumJ = json_object_get(rootJ, std::to_string(i).c_str()); json_t *ccNumJ = json_object_get(rootJ, std::to_string(i).c_str());
if (ccNumJ) { 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 { void reset() override {
resetMidi(); resetMidi();
} }

}; };




@@ -88,13 +85,13 @@ void MIDITriggerToCVInterface::step() {
// Note: Could have an option to select between gate and velocity // Note: Could have an option to select between gate and velocity
// but trigger seams more useful // but trigger seams more useful
// outputs[i].value = trigger[i] / 127.0 * 10; // 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() { void MIDITriggerToCVInterface::resetMidi() {
for (int i = 0; i < NUM_OUTPUTS; i++) { 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 if (status == 0x8) { // note off
for (int i = 0; i < NUM_OUTPUTS; i++) { 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; return;
@@ -121,14 +118,12 @@ void MIDITriggerToCVInterface::processMidi(std::vector<unsigned char> msg) {


if (status == 0x9) { // note on if (status == 0x9) { // note on
for (int i = 0; i < NUM_OUTPUTS; i++) { 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; void onMouseLeave(EventMouseLeave &e) override;



int *triggerNum;
bool *inited;
bool *onFocus;
int outNum;
MIDITriggerToCVInterface *module;
}; };


void TriggerTextField::draw(NVGcontext *vg) { void TriggerTextField::draw(NVGcontext *vg) {
/* This is necessary, since the save /* This is necessary, since the save
* file is loaded after constructing the widget*/ * 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); TextField::draw(vg);
@@ -170,37 +163,42 @@ void TriggerTextField::draw(NVGcontext *vg) {
void TriggerTextField::onTextChange() { void TriggerTextField::onTextChange() {
if (text.size() > 0) { if (text.size() > 0) {
try { try {
*triggerNum = std::stoi(text);
int num = std::stoi(text);
// Only allow valid cc numbers // Only allow valid cc numbers
if (*triggerNum < 0 || *triggerNum > 127 || text.size() > 3) {
if (num < 0 || num > 127 || text.size() > 3) {
text = ""; text = "";
begin = end = 0; begin = end = 0;
*triggerNum = -1;
module->trigger[outNum].num = -1;
}else {
module->trigger[outNum].num = num;
} }
} catch (...) { } catch (...) {
text = ""; text = "";
begin = end = 0; begin = end = 0;
*triggerNum = -1;
module->trigger[outNum].num = -1;
} }
}; };
} }


void TriggerTextField::onMouseDown(EventMouseDown &e) {
void TriggerTextField::onMouseUp(EventMouseUp &e) {
if (e.button == 1) { 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) { 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) { void TriggerTextField::onMouseLeave(EventMouseLeave &e) {
*onFocus = false;
module->trigger[outNum].onFocus = false;
e.consumed = true;
} }


MIDITriggerToCVWidget::MIDITriggerToCVWidget() { MIDITriggerToCVWidget::MIDITriggerToCVWidget() {
@@ -260,10 +258,9 @@ MIDITriggerToCVWidget::MIDITriggerToCVWidget() {


for (int i = 0; i < MIDITriggerToCVInterface::NUM_OUTPUTS; i++) { for (int i = 0; i < MIDITriggerToCVInterface::NUM_OUTPUTS; i++) {
TriggerTextField *triggerNumChoice = new TriggerTextField(); 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.pos = Vec(11 + (i % 4) * (63), yPos);
triggerNumChoice->box.size.x = 29; triggerNumChoice->box.size.x = 29;




+ 24
- 16
src/core/QuadMidiToCV.cpp View File

@@ -9,7 +9,6 @@ struct MidiKey {
int pitch = 60; int pitch = 60;
int at = 0; // aftertouch int at = 0; // aftertouch
int vel = 0; // velocity int vel = 0; // velocity
int retriggerC = 0;
bool gate = false; bool gate = false;
}; };


@@ -97,10 +96,9 @@ void QuadMIDIToCVInterface::resetMidi() {
} }


void QuadMIDIToCVInterface::step() { void QuadMIDIToCVInterface::step() {
static int msgsProcessed = 0;

if (isPortOpen()) { if (isPortOpen()) {
std::vector<unsigned char> message; std::vector<unsigned char> message;
int msgsProcessed = 0;


// 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
// NOTE: For the quadmidi we will process max 4 midi messages per step to avoid // NOTE: For the quadmidi we will process max 4 midi messages per step to avoid
@@ -111,7 +109,6 @@ void QuadMIDIToCVInterface::step() {
getMessage(&message); getMessage(&message);
msgsProcessed++; msgsProcessed++;
} }
msgsProcessed = 0;
} }




@@ -136,8 +133,7 @@ void QuadMIDIToCVInterface::processMidi(std::vector<unsigned char> msg) {
int status = (msg[0] >> 4) & 0xf; int status = (msg[0] >> 4) & 0xf;
int data1 = msg[1]; int data1 = msg[1];
int data2 = msg[2]; int data2 = msg[2];

static int gate;
bool gate;


// Filter channels // Filter channels
if (this->channel >= 0 && this->channel != channel) if (this->channel >= 0 && this->channel != channel)
@@ -168,6 +164,7 @@ void QuadMIDIToCVInterface::processMidi(std::vector<unsigned char> msg) {
if (data1 == 0x40) { // pedal if (data1 == 0x40) { // pedal
pedal = (data2 >= 64); pedal = (data2 >= 64);
if (!pedal) { if (!pedal) {
open.clear();
for (int i = 0; i < 4; i++) { for (int i = 0; i < 4; i++) {
activeKeys[i].gate = false; activeKeys[i].gate = false;
open.push_back(i); open.push_back(i);
@@ -179,7 +176,11 @@ void QuadMIDIToCVInterface::processMidi(std::vector<unsigned char> msg) {
return; return;
} }


if (!pedal && !gate) {
if (pedal && !gate) {
return;
}

if (!gate) {
for (int i = 0; i < 4; i++) { for (int i = 0; i < 4; i++) {
if (activeKeys[i].pitch == data1) { if (activeKeys[i].pitch == data1) {
activeKeys[i].gate = false; activeKeys[i].gate = false;
@@ -194,21 +195,20 @@ void QuadMIDIToCVInterface::processMidi(std::vector<unsigned char> msg) {
} }


if (open.empty()) { if (open.empty()) {
open.clear();
for (int i = 0; i < 4; i++) { for (int i = 0; i < 4; i++) {
open.push_back(i); open.push_back(i);
} }
} }



if (!activeKeys[0].gate && !activeKeys[1].gate && if (!activeKeys[0].gate && !activeKeys[1].gate &&
!activeKeys[2].gate && !activeKeys[3].gate) { !activeKeys[2].gate && !activeKeys[3].gate) {
open.sort(); open.sort();
} }



switch (mode) { switch (mode) {
case RESET: case RESET:
if (open.size() == 4 ) {
if (open.size() >= 4) {
for (int i = 0; i < 4; i++) { for (int i = 0; i < 4; i++) {
activeKeys[i].gate = false; activeKeys[i].gate = false;
open.push_back(i); open.push_back(i);
@@ -220,17 +220,25 @@ void QuadMIDIToCVInterface::processMidi(std::vector<unsigned char> msg) {
break; break;
case ROTATE: case ROTATE:
break; 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(); 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 { int QuadMIDIToCVInterface::getMode() const {


Loading…
Cancel
Save