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