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