From 0e40a86ba3d467d28529ec2e4d5364bb748f37b2 Mon Sep 17 00:00:00 2001 From: dllmusic <34119160+dllmusic@users.noreply.github.com> Date: Fri, 23 Mar 2018 23:32:19 -0400 Subject: [PATCH 1/3] QuadMIDIToCV with all poly modes Overview of functionality: ROTATE > Incoming note always takes the next available ch. REUSE > If incoming note is repeated (already assigned to one ch) takes that ch, if not, it takes the next available ch. RESET > Incoming note always takes the lowest available ch. REASSIGN > Similar to RESET but when releasing notes they are reassigned continuously from ch 0 (keeping the order) UNISON > Incoming note takes all 4 ch. When receiving more than 4 notes "stealing" occurs, always taking the next ch. When keys are released, still pressed notes that where stolen are recovered. In modes other than REUSE (or UNISON), repeating a note with sustain pedal rotates it over the channels producing "unison". When using sustain pedal and playing and releasing many notes, still pressed notes are stolen, but they are recovered when sustain pedal is off. I tested every possible combination of note(s) on off / sustain on off. also added a pulseGen for re-triggering notes when necessary (I also added the MIDI channel if on MidiMessage) --- src/Core/QuadMIDIToCVInterface.cpp | 207 +++++++++++++++++++++-------- 1 file changed, 151 insertions(+), 56 deletions(-) diff --git a/src/Core/QuadMIDIToCVInterface.cpp b/src/Core/QuadMIDIToCVInterface.cpp index dca10b66..be036661 100644 --- a/src/Core/QuadMIDIToCVInterface.cpp +++ b/src/Core/QuadMIDIToCVInterface.cpp @@ -26,7 +26,10 @@ struct QuadMIDIToCVInterface : Module { enum PolyMode { ROTATE_MODE, - RESET_MODE, + /* Added REUSE option that reuses a channel when receiving the same note. + Good when using sustain pedal so it doesn't "stack" unisons ... not sure this is the best name but it is descriptive...*/ + REUSE_MODE, + RESET_MODE, REASSIGN_MODE, UNISON_MODE, NUM_MODES @@ -39,13 +42,20 @@ struct QuadMIDIToCVInterface : Module { }; NoteData noteData[128]; - std::vector heldNotes; + // cachedNotes : UNISON_MODE and REASSIGN_MODE cache all played notes. The other polyModes cache stealed notes (after 4th one). + std::vector cachedNotes; uint8_t notes[4]; bool gates[4]; + // gates set to TRUE by pedal and current gate. FALSE by pedal. + bool pedalgates[4]; bool pedal; int rotateIndex; + int stealIndex; + + // retrigger for stolen notes (when gates already open) + PulseGenerator reTrigger[4]; - QuadMIDIToCVInterface() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS), heldNotes(128) { + QuadMIDIToCVInterface() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS), cachedNotes(128) { onReset(); } @@ -70,93 +80,174 @@ struct QuadMIDIToCVInterface : Module { for (int i = 0; i < 4; i++) { notes[i] = 60; gates[i] = false; + pedalgates[i] = false; } pedal = false; - rotateIndex = 0; + rotateIndex = -1; + cachedNotes.clear(); + } + + int getPolyIndex (int nowIndex) { + for (int i = 0; i < 4; i++) { + nowIndex ++; + if (nowIndex > 3) + nowIndex = 0; + if (!(gates[nowIndex] || pedalgates[nowIndex])) { + stealIndex = nowIndex; + return nowIndex; + } + } + // All taken = steal (stealIndex always rotate) + stealIndex ++; + if (stealIndex > 3) + stealIndex = 0; + if ((polyMode < REASSIGN_MODE) && (gates[stealIndex])) + cachedNotes.push_back(notes[stealIndex]); + return stealIndex; } void pressNote(uint8_t note) { - // Remove existing similar note - auto it = std::find(heldNotes.begin(), heldNotes.end(), note); - if (it != heldNotes.end()) - heldNotes.erase(it); - // Push note - heldNotes.push_back(note); - - // Set notes and gates + // Set notes and gates switch (polyMode) { case ROTATE_MODE: { + rotateIndex = getPolyIndex(rotateIndex); } break; - case RESET_MODE: { + case REUSE_MODE: { + bool reuse = false; + for (int i = 0; i < 4; i++) { + if (notes[i] == note) { + rotateIndex = i; + reuse = true; + break; + } + } + if (!reuse) + rotateIndex = getPolyIndex(rotateIndex); + } break; + case RESET_MODE: { + rotateIndex = getPolyIndex(-1); } break; case REASSIGN_MODE: { - + cachedNotes.push_back(note); + rotateIndex = getPolyIndex(-1); } break; case UNISON_MODE: { + cachedNotes.push_back(note); for (int i = 0; i < 4; i++) { notes[i] = note; gates[i] = true; + pedalgates[i] = pedal; + //...it could be just "legato" for Unison mode without this... + reTrigger[i].trigger(1e-3); } + return; } break; default: break; } - } + // Set notes and gates + if (gates[rotateIndex] || pedalgates[rotateIndex]) + reTrigger[rotateIndex].trigger(1e-3); + notes[rotateIndex] = note; + gates[rotateIndex] = true; + pedalgates[rotateIndex] = pedal; + } void releaseNote(uint8_t note) { - // Remove the note - auto it = std::find(heldNotes.begin(), heldNotes.end(), note); - if (it != heldNotes.end()) - heldNotes.erase(it); - // Hold note if pedal is pressed - if (pedal) - return; - // Set last note + // Remove the note + auto it = std::find(cachedNotes.begin(), cachedNotes.end(), note); + if (it != cachedNotes.end()) + cachedNotes.erase(it); + switch (polyMode) { - case ROTATE_MODE: { - - } break; - - case RESET_MODE: { - - } break; - - case REASSIGN_MODE: { - - } break; - + case REASSIGN_MODE: { + int held = static_cast(cachedNotes.size()); + if (held > 4) + held = 4; + for (int i = 0; i < held; i++) { + if (!pedalgates[i]) + notes[i] = cachedNotes.at(i); + pedalgates[i] = pedal; + } + for (int i = held; i < 4; i++) { + gates[i] = false; + } + } break; + case UNISON_MODE: { - if (!heldNotes.empty()) { - auto it2 = heldNotes.end(); - it2--; - for (int i = 0; i < 4; i++) { - notes[i] = *it2; - gates[i] = true; - } - } - else { - for (int i = 0; i < 4; i++) { - gates[i] = false; - } - } + if (!cachedNotes.empty()) { + uint8_t backnote = cachedNotes.back(); + for (int i = 0; i < 4; i++) { + notes[i] = backnote; + gates[i] = true; + } + } + else { + for (int i = 0; i < 4; i++) { + gates[i] = false; + } + } } break; - - default: break; + + // default ROTATE_MODE REUSE_MODE RESET_MODE + default: { + for (int i = 0; i < 4; i++) { + if (notes[i] == note) { + if (pedalgates[i]){ + gates[i] = false; + } + else if (!cachedNotes.empty()) { + notes[i] = cachedNotes.back(); + cachedNotes.pop_back(); + } + else { + gates[i] = false; + } + } + } + } break; } - - } - + } + void pressPedal() { pedal = true; + for (int i = 0; i < 4; i++) { + pedalgates[i] = gates[i]; + } } void releasePedal() { - pedal = false; - releaseNote(255); + pedal = false; + /* When pedal is off: Recover notes for still-pressed keys (if any), + ...after they were already being "cycled" out by pedal-sustained notes + */ + + for (int i = 0; i < 4; i++) { + pedalgates[i] = false; + if (!cachedNotes.empty()) { + if (polyMode < REASSIGN_MODE) { + notes[i] = cachedNotes.back(); + cachedNotes.pop_back(); + gates[i] = true; + } + } + } + if (polyMode == REASSIGN_MODE) { + int held = static_cast(cachedNotes.size()); + if (held > 4) + held = 4; + for (int i = 0; i < held; i++) { + notes[i] = cachedNotes.at(i); + gates[i] = true; + } + for (int i = held; i < 4; i++) { + gates[i] = false; + } + } } void step() override { @@ -170,11 +261,15 @@ struct QuadMIDIToCVInterface : Module { outputs[CV_OUTPUT + i].value = (lastNote - 60) / 12.f; outputs[GATE_OUTPUT + i].value = gates[i] ? 10.f : 0.f; outputs[VELOCITY_OUTPUT + i].value = rescale(noteData[lastNote].velocity, 0, 127, 0.f, 10.f); - outputs[VELOCITY_OUTPUT + i].value = rescale(noteData[lastNote].aftertouch, 0, 127, 0.f, 10.f); + outputs[AFTERTOUCH_OUTPUT + i].value = rescale(noteData[lastNote].aftertouch, 0, 127, 0.f, 10.f); } } void processMessage(MidiMessage msg) { + // filter MIDI channel + if ((midiInput.channel > -1) && (midiInput.channel != msg.channel())) + return; + switch (msg.status()) { // note off case 0x8: { From 2162c0836609340391d4d3bce47ba73185b0f2e0 Mon Sep 17 00:00:00 2001 From: dllmusic <34119160+dllmusic@users.noreply.github.com> Date: Fri, 23 Mar 2018 23:47:21 -0400 Subject: [PATCH 2/3] Update QuadMIDIToCVInterface.cpp --- src/Core/QuadMIDIToCVInterface.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Core/QuadMIDIToCVInterface.cpp b/src/Core/QuadMIDIToCVInterface.cpp index be036661..b7e27c05 100644 --- a/src/Core/QuadMIDIToCVInterface.cpp +++ b/src/Core/QuadMIDIToCVInterface.cpp @@ -258,8 +258,9 @@ struct QuadMIDIToCVInterface : Module { for (int i = 0; i < 4; i++) { uint8_t lastNote = notes[i]; + uint8_t lastGate = ((gates[i] || pedalgates[i]) && (!(reTrigger[i].process(engineGetSampleTime())))); outputs[CV_OUTPUT + i].value = (lastNote - 60) / 12.f; - outputs[GATE_OUTPUT + i].value = gates[i] ? 10.f : 0.f; + outputs[GATE_OUTPUT + i].value = lastGate ? 10.f : 0.f; outputs[VELOCITY_OUTPUT + i].value = rescale(noteData[lastNote].velocity, 0, 127, 0.f, 10.f); outputs[AFTERTOUCH_OUTPUT + i].value = rescale(noteData[lastNote].aftertouch, 0, 127, 0.f, 10.f); } From 9b9f2a9f6fc23e122270914f00d5b69477754ba8 Mon Sep 17 00:00:00 2001 From: Andrew Belt Date: Sat, 24 Mar 2018 06:16:19 -0400 Subject: [PATCH 3/3] Refactor MIDI-4, remove retriggering on GATE output --- src/Core/QuadMIDIToCVInterface.cpp | 228 ++++++++++++++--------------- 1 file changed, 113 insertions(+), 115 deletions(-) diff --git a/src/Core/QuadMIDIToCVInterface.cpp b/src/Core/QuadMIDIToCVInterface.cpp index b7e27c05..ff04101d 100644 --- a/src/Core/QuadMIDIToCVInterface.cpp +++ b/src/Core/QuadMIDIToCVInterface.cpp @@ -1,5 +1,6 @@ #include "Core.hpp" #include "midi.hpp" +#include "dsp/digital.hpp" #include @@ -26,10 +27,10 @@ struct QuadMIDIToCVInterface : Module { enum PolyMode { ROTATE_MODE, - /* Added REUSE option that reuses a channel when receiving the same note. - Good when using sustain pedal so it doesn't "stack" unisons ... not sure this is the best name but it is descriptive...*/ + // Added REUSE option that reuses a channel when receiving the same note. + // Good when using sustain pedal so it doesn't "stack" unisons ... not sure this is the best name but it is descriptive... REUSE_MODE, - RESET_MODE, + RESET_MODE, REASSIGN_MODE, UNISON_MODE, NUM_MODES @@ -42,18 +43,15 @@ struct QuadMIDIToCVInterface : Module { }; NoteData noteData[128]; - // cachedNotes : UNISON_MODE and REASSIGN_MODE cache all played notes. The other polyModes cache stealed notes (after 4th one). - std::vector cachedNotes; + // cachedNotes : UNISON_MODE and REASSIGN_MODE cache all played notes. The other polyModes cache stolen notes (after the 4th one). + std::vector cachedNotes; uint8_t notes[4]; bool gates[4]; - // gates set to TRUE by pedal and current gate. FALSE by pedal. - bool pedalgates[4]; + // gates set to TRUE by pedal and current gate. FALSE by pedal. + bool pedalgates[4]; bool pedal; int rotateIndex; - int stealIndex; - - // retrigger for stolen notes (when gates already open) - PulseGenerator reTrigger[4]; + int stealIndex; QuadMIDIToCVInterface() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS), cachedNotes(128) { onReset(); @@ -80,34 +78,34 @@ struct QuadMIDIToCVInterface : Module { for (int i = 0; i < 4; i++) { notes[i] = 60; gates[i] = false; - pedalgates[i] = false; + pedalgates[i] = false; } pedal = false; rotateIndex = -1; - cachedNotes.clear(); + cachedNotes.clear(); } - - int getPolyIndex (int nowIndex) { - for (int i = 0; i < 4; i++) { - nowIndex ++; - if (nowIndex > 3) + + int getPolyIndex(int nowIndex) { + for (int i = 0; i < 4; i++) { + nowIndex++; + if (nowIndex > 3) nowIndex = 0; - if (!(gates[nowIndex] || pedalgates[nowIndex])) { + if (!(gates[nowIndex] || pedalgates[nowIndex])) { stealIndex = nowIndex; return nowIndex; - } + } } - // All taken = steal (stealIndex always rotate) - stealIndex ++; + // All taken = steal (stealIndex always rotates) + stealIndex++; if (stealIndex > 3) - stealIndex = 0; + stealIndex = 0; if ((polyMode < REASSIGN_MODE) && (gates[stealIndex])) cachedNotes.push_back(notes[stealIndex]); return stealIndex; } void pressNote(uint8_t note) { - // Set notes and gates + // Set notes and gates switch (polyMode) { case ROTATE_MODE: { rotateIndex = getPolyIndex(rotateIndex); @@ -141,8 +139,7 @@ struct QuadMIDIToCVInterface : Module { notes[i] = note; gates[i] = true; pedalgates[i] = pedal; - //...it could be just "legato" for Unison mode without this... - reTrigger[i].trigger(1e-3); + // reTrigger[i].trigger(1e-3); } return; } break; @@ -150,104 +147,99 @@ struct QuadMIDIToCVInterface : Module { default: break; } // Set notes and gates - if (gates[rotateIndex] || pedalgates[rotateIndex]) - reTrigger[rotateIndex].trigger(1e-3); + // if (gates[rotateIndex] || pedalgates[rotateIndex]) + // reTrigger[rotateIndex].trigger(1e-3); notes[rotateIndex] = note; gates[rotateIndex] = true; pedalgates[rotateIndex] = pedal; - } + } void releaseNote(uint8_t note) { - // Remove the note - auto it = std::find(cachedNotes.begin(), cachedNotes.end(), note); - if (it != cachedNotes.end()) - cachedNotes.erase(it); - + // Remove the note + auto it = std::find(cachedNotes.begin(), cachedNotes.end(), note); + if (it != cachedNotes.end()) + cachedNotes.erase(it); + switch (polyMode) { - case REASSIGN_MODE: { - int held = static_cast(cachedNotes.size()); - if (held > 4) - held = 4; - for (int i = 0; i < held; i++) { - if (!pedalgates[i]) - notes[i] = cachedNotes.at(i); - pedalgates[i] = pedal; - } - for (int i = held; i < 4; i++) { - gates[i] = false; - } - } break; - + case REASSIGN_MODE: { + for (int i = 0; i < 4; i++) { + if (i < (int) cachedNotes.size()) { + if (!pedalgates[i]) + notes[i] = cachedNotes[i]; + pedalgates[i] = pedal; + } + else { + gates[i] = false; + } + } + } break; + case UNISON_MODE: { - if (!cachedNotes.empty()) { - uint8_t backnote = cachedNotes.back(); - for (int i = 0; i < 4; i++) { - notes[i] = backnote; - gates[i] = true; - } - } - else { - for (int i = 0; i < 4; i++) { - gates[i] = false; - } - } + if (!cachedNotes.empty()) { + uint8_t backnote = cachedNotes.back(); + for (int i = 0; i < 4; i++) { + notes[i] = backnote; + gates[i] = true; + } + } + else { + for (int i = 0; i < 4; i++) { + gates[i] = false; + } + } + } break; + + // default ROTATE_MODE REUSE_MODE RESET_MODE + default: { + for (int i = 0; i < 4; i++) { + if (notes[i] == note) { + if (pedalgates[i]) { + gates[i] = false; + } + else if (!cachedNotes.empty()) { + notes[i] = cachedNotes.back(); + cachedNotes.pop_back(); + } + else { + gates[i] = false; + } + } + } } break; - - // default ROTATE_MODE REUSE_MODE RESET_MODE - default: { - for (int i = 0; i < 4; i++) { - if (notes[i] == note) { - if (pedalgates[i]){ - gates[i] = false; - } - else if (!cachedNotes.empty()) { - notes[i] = cachedNotes.back(); - cachedNotes.pop_back(); - } - else { - gates[i] = false; - } - } - } - } break; } - } - + } + void pressPedal() { pedal = true; - for (int i = 0; i < 4; i++) { - pedalgates[i] = gates[i]; - } + for (int i = 0; i < 4; i++) { + pedalgates[i] = gates[i]; + } } void releasePedal() { - pedal = false; - /* When pedal is off: Recover notes for still-pressed keys (if any), - ...after they were already being "cycled" out by pedal-sustained notes - */ - - for (int i = 0; i < 4; i++) { - pedalgates[i] = false; - if (!cachedNotes.empty()) { - if (polyMode < REASSIGN_MODE) { - notes[i] = cachedNotes.back(); - cachedNotes.pop_back(); - gates[i] = true; - } - } - } - if (polyMode == REASSIGN_MODE) { - int held = static_cast(cachedNotes.size()); - if (held > 4) - held = 4; - for (int i = 0; i < held; i++) { - notes[i] = cachedNotes.at(i); - gates[i] = true; - } - for (int i = held; i < 4; i++) { - gates[i] = false; - } - } + pedal = false; + // When pedal is off, recover notes for pressed keys (if any) after they were already being "cycled" out by pedal-sustained notes. + for (int i = 0; i < 4; i++) { + pedalgates[i] = false; + if (!cachedNotes.empty()) { + if (polyMode < REASSIGN_MODE) { + notes[i] = cachedNotes.back(); + cachedNotes.pop_back(); + gates[i] = true; + } + } + } + if (polyMode == REASSIGN_MODE) { + for (int i = 0; i < 4; i++) { + if (i < (int) cachedNotes.size()) { + notes[i] = cachedNotes[i]; + gates[i] = true; + } + else { + gates[i] = false; + } + } + } } void step() override { @@ -258,7 +250,7 @@ struct QuadMIDIToCVInterface : Module { for (int i = 0; i < 4; i++) { uint8_t lastNote = notes[i]; - uint8_t lastGate = ((gates[i] || pedalgates[i]) && (!(reTrigger[i].process(engineGetSampleTime())))); + uint8_t lastGate = (gates[i] || pedalgates[i]); outputs[CV_OUTPUT + i].value = (lastNote - 60) / 12.f; outputs[GATE_OUTPUT + i].value = lastGate ? 10.f : 0.f; outputs[VELOCITY_OUTPUT + i].value = rescale(noteData[lastNote].velocity, 0, 127, 0.f, 10.f); @@ -268,9 +260,9 @@ struct QuadMIDIToCVInterface : Module { void processMessage(MidiMessage msg) { // filter MIDI channel - if ((midiInput.channel > -1) && (midiInput.channel != msg.channel())) - return; - + if ((midiInput.channel > -1) && (midiInput.channel != msg.channel())) + return; + switch (msg.status()) { // note off case 0x8: { @@ -358,7 +350,13 @@ struct QuadMIDIToCVInterfaceWidget : ModuleWidget { menu->addChild(MenuEntry::create()); menu->addChild(MenuLabel::create("Polyphony mode")); - std::vector polyModeNames = {"Rotate", "Reset", "Reassign", "Unison"}; + std::vector polyModeNames = { + "Rotate", + "Reuse", + "Reset", + "Reassign", + "Unison" + }; for (int i = 0; i < QuadMIDIToCVInterface::NUM_MODES; i++) { PolyphonyItem *item = MenuItem::create(polyModeNames[i], CHECKMARK(module->polyMode == i)); item->module = module;