@@ -1 +1 @@ | |||||
Subproject commit 4dd22f56d6b733c8de13d6e8b12f13390aa5782e | |||||
Subproject commit 015d020615e8169d2f227ad385c5f2aa1e091fd1 |
@@ -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; | ||||
@@ -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() { | ||||
@@ -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"; | ||||
} | |||||
} |
@@ -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 | ||||
////////////////////// | ////////////////////// | ||||
@@ -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); | ||||
@@ -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; | ||||
@@ -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 { | ||||