| @@ -1,5 +1,6 @@ | |||
| #include "Core.hpp" | |||
| #include "midi.hpp" | |||
| #include "dsp/digital.hpp" | |||
| #include <algorithm> | |||
| @@ -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<uint8_t> cachedNotes; | |||
| // cachedNotes : UNISON_MODE and REASSIGN_MODE cache all played notes. The other polyModes cache stolen notes (after the 4th one). | |||
| std::vector<uint8_t> 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<int>(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<int>(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<std::string> polyModeNames = {"Rotate", "Reset", "Reassign", "Unison"}; | |||
| std::vector<std::string> polyModeNames = { | |||
| "Rotate", | |||
| "Reuse", | |||
| "Reset", | |||
| "Reassign", | |||
| "Unison" | |||
| }; | |||
| for (int i = 0; i < QuadMIDIToCVInterface::NUM_MODES; i++) { | |||
| PolyphonyItem *item = MenuItem::create<PolyphonyItem>(polyModeNames[i], CHECKMARK(module->polyMode == i)); | |||
| item->module = module; | |||