diff --git a/src/Core/QuadMIDIToCVInterface.cpp b/src/Core/QuadMIDIToCVInterface.cpp index dca10b66..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,6 +27,9 @@ 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... + REUSE_MODE, RESET_MODE, REASSIGN_MODE, UNISON_MODE, @@ -39,13 +43,17 @@ struct QuadMIDIToCVInterface : Module { }; NoteData noteData[128]; - std::vector heldNotes; + // 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]; bool pedal; int rotateIndex; + int stealIndex; - 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,71 +78,107 @@ 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(); } - 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); + 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 rotates) + stealIndex++; + if (stealIndex > 3) + stealIndex = 0; + if ((polyMode < REASSIGN_MODE) && (gates[stealIndex])) + cachedNotes.push_back(notes[stealIndex]); + return stealIndex; + } + void pressNote(uint8_t note) { // 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; + // 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 - switch (polyMode) { - case ROTATE_MODE: { - - } break; - - case RESET_MODE: { - - } break; + auto it = std::find(cachedNotes.begin(), cachedNotes.end(), note); + if (it != cachedNotes.end()) + cachedNotes.erase(it); + switch (polyMode) { 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 (!heldNotes.empty()) { - auto it2 = heldNotes.end(); - it2--; + if (!cachedNotes.empty()) { + uint8_t backnote = cachedNotes.back(); for (int i = 0; i < 4; i++) { - notes[i] = *it2; + notes[i] = backnote; gates[i] = true; } } @@ -145,18 +189,57 @@ struct QuadMIDIToCVInterface : Module { } } 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); + // 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 { @@ -167,14 +250,19 @@ struct QuadMIDIToCVInterface : Module { for (int i = 0; i < 4; i++) { uint8_t lastNote = notes[i]; + uint8_t lastGate = (gates[i] || pedalgates[i]); 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[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: { @@ -262,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;