Browse Source

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)
tags/v0.6.0
dllmusic GitHub 7 years ago
parent
commit
0e40a86ba3
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 151 additions and 56 deletions
  1. +151
    -56
      src/Core/QuadMIDIToCVInterface.cpp

+ 151
- 56
src/Core/QuadMIDIToCVInterface.cpp View File

@@ -26,7 +26,10 @@ struct QuadMIDIToCVInterface : Module {


enum PolyMode { enum PolyMode {
ROTATE_MODE, 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, REASSIGN_MODE,
UNISON_MODE, UNISON_MODE,
NUM_MODES NUM_MODES
@@ -39,13 +42,20 @@ struct QuadMIDIToCVInterface : Module {
}; };


NoteData noteData[128]; NoteData noteData[128];
std::vector<uint8_t> heldNotes;
// cachedNotes : UNISON_MODE and REASSIGN_MODE cache all played notes. The other polyModes cache stealed notes (after 4th one).
std::vector<uint8_t> cachedNotes;
uint8_t notes[4]; uint8_t notes[4];
bool gates[4]; bool gates[4];
// gates set to TRUE by pedal and current gate. FALSE by pedal.
bool pedalgates[4];
bool pedal; bool pedal;
int rotateIndex; 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(); onReset();
} }


@@ -70,93 +80,174 @@ struct QuadMIDIToCVInterface : Module {
for (int i = 0; i < 4; i++) { for (int i = 0; i < 4; i++) {
notes[i] = 60; notes[i] = 60;
gates[i] = false; gates[i] = false;
pedalgates[i] = false;
} }
pedal = 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) { 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) { switch (polyMode) {
case ROTATE_MODE: { case ROTATE_MODE: {
rotateIndex = getPolyIndex(rotateIndex);
} break; } 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; } break;


case REASSIGN_MODE: { case REASSIGN_MODE: {

cachedNotes.push_back(note);
rotateIndex = getPolyIndex(-1);
} break; } break;


case UNISON_MODE: { case UNISON_MODE: {
cachedNotes.push_back(note);
for (int i = 0; i < 4; i++) { for (int i = 0; i < 4; i++) {
notes[i] = note; notes[i] = note;
gates[i] = true; gates[i] = true;
pedalgates[i] = pedal;
//...it could be just "legato" for Unison mode without this...
reTrigger[i].trigger(1e-3);
} }
return;
} break; } break;


default: 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) { 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) { switch (polyMode) {
case ROTATE_MODE: {

} break;

case RESET_MODE: {

} break;

case REASSIGN_MODE: {

} break;

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 UNISON_MODE: { 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; } 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() { void pressPedal() {
pedal = true; pedal = true;
for (int i = 0; i < 4; i++) {
pedalgates[i] = gates[i];
}
} }


void releasePedal() { 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<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;
}
}
} }


void step() override { void step() override {
@@ -170,11 +261,15 @@ struct QuadMIDIToCVInterface : Module {
outputs[CV_OUTPUT + i].value = (lastNote - 60) / 12.f; 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 = 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].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) { void processMessage(MidiMessage msg) {
// filter MIDI channel
if ((midiInput.channel > -1) && (midiInput.channel != msg.channel()))
return;
switch (msg.status()) { switch (msg.status()) {
// note off // note off
case 0x8: { case 0x8: {


Loading…
Cancel
Save