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] 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: {