Browse Source

added unison and gate force gap options

pull/1939/head
Andrew Simper 1 year ago
parent
commit
5c30b560a4
1 changed files with 109 additions and 25 deletions
  1. +109
    -25
      src/core/MIDI_CV.cpp

+ 109
- 25
src/core/MIDI_CV.cpp View File

@@ -1,4 +1,5 @@
#include <algorithm> #include <algorithm>
#include <array>


#include "plugin.hpp" #include "plugin.hpp"


@@ -40,6 +41,7 @@ struct MIDI_CV : Module {
bool smooth; bool smooth;
int clockDivision; int clockDivision;
int channels; int channels;
int channelsUnison;
enum PolyMode { enum PolyMode {
ROTATE_MODE, ROTATE_MODE,
REUSE_MODE, REUSE_MODE,
@@ -54,7 +56,10 @@ struct MIDI_CV : Module {
bool pedal; bool pedal;
// Indexed by channel // Indexed by channel
uint8_t notes[16]; uint8_t notes[16];
uint8_t unisonIndecies[16];
bool gates[16]; bool gates[16];
bool gatesForceGap[16];
bool gateForceGaps;
uint8_t velocities[16]; uint8_t velocities[16];
uint8_t aftertouches[16]; uint8_t aftertouches[16];
std::vector<uint8_t> heldNotes; std::vector<uint8_t> heldNotes;
@@ -90,8 +95,10 @@ struct MIDI_CV : Module {
configOutput(CLOCK_OUTPUT, "Clock"); configOutput(CLOCK_OUTPUT, "Clock");
configOutput(CLOCK_DIV_OUTPUT, "Clock divider"); configOutput(CLOCK_DIV_OUTPUT, "Clock divider");
configOutput(START_OUTPUT, "Start trigger"); configOutput(START_OUTPUT, "Start trigger");
configOutput(STOP_OUTPUT, "Stop trigger");
configOutput(CONTINUE_OUTPUT, "Continue trigger");
configOutput(STOP_OUTPUT, "Unison CV");
configOutput(CONTINUE_OUTPUT, "Voice CV");
// configOutput(STOP_OUTPUT, "Stop trigger");
// configOutput(CONTINUE_OUTPUT, "Continue trigger");
heldNotes.reserve(128); heldNotes.reserve(128);
for (int c = 0; c < 16; c++) { for (int c = 0; c < 16; c++) {
pwFilters[c].setTau(1 / 30.f); pwFilters[c].setTau(1 / 30.f);
@@ -103,18 +110,22 @@ struct MIDI_CV : Module {
void onReset() override { void onReset() override {
smooth = true; smooth = true;
channels = 1; channels = 1;
channelsUnison = 1;
polyMode = ROTATE_MODE; polyMode = ROTATE_MODE;
pwRange = 2; pwRange = 2;
clockDivision = 24; clockDivision = 24;
panic(); panic();
midiInput.reset(); midiInput.reset();
gateForceGaps = true;
} }


/** Resets performance state */ /** Resets performance state */
void panic() { void panic() {
for (int c = 0; c < 16; c++) { for (int c = 0; c < 16; c++) {
notes[c] = 60; notes[c] = 60;
unisonIndecies[c] = 0;
gates[c] = false; gates[c] = false;
gatesForceGap[c] = false;
velocities[c] = 0; velocities[c] = 0;
aftertouches[c] = 0; aftertouches[c] = 0;
pws[c] = 8192; pws[c] = 8192;
@@ -127,6 +138,17 @@ struct MIDI_CV : Module {
heldNotes.clear(); heldNotes.clear();
} }


float channelUnisonTransformBi(int index, float scale)
{
if (channelsUnison == 1) return 0;
return 10.f*float(index)*scale - 5.f;
}
float channelTransformBi(int index, float scale)
{
if (channels == 1) return 0;
return 10.f*float(index)*scale - 5.f;
}

void process(const ProcessArgs& args) override { void process(const ProcessArgs& args) override {
midi::Message msg; midi::Message msg;
while (midiInput.tryPop(&msg, args.frame)) { while (midiInput.tryPop(&msg, args.frame)) {
@@ -163,22 +185,29 @@ struct MIDI_CV : Module {
outputs[VELOCITY_OUTPUT].setChannels(channels); outputs[VELOCITY_OUTPUT].setChannels(channels);
outputs[AFTERTOUCH_OUTPUT].setChannels(channels); outputs[AFTERTOUCH_OUTPUT].setChannels(channels);
outputs[RETRIGGER_OUTPUT].setChannels(channels); outputs[RETRIGGER_OUTPUT].setChannels(channels);
outputs[STOP_OUTPUT].setChannels(channels);
outputs[CONTINUE_OUTPUT].setChannels(channels);
const float channelUnisonScale = 1.f/float(channelsUnison == 1 ? 1 : channelsUnison - 1);
const float channelScale = 1.f/float(channels == 1 ? 1 : channels - 1);
for (int c = 0; c < channels; c++) { for (int c = 0; c < channels; c++) {
float pw = pwValues[(polyMode == MPE_MODE) ? c : 0]; float pw = pwValues[(polyMode == MPE_MODE) ? c : 0];
float pitch = (notes[c] - 60.f + pw * pwRange) / 12.f; float pitch = (notes[c] - 60.f + pw * pwRange) / 12.f;
outputs[PITCH_OUTPUT].setVoltage(pitch, c); outputs[PITCH_OUTPUT].setVoltage(pitch, c);
outputs[GATE_OUTPUT].setVoltage(gates[c] ? 10.f : 0.f, c);
outputs[GATE_OUTPUT].setVoltage(gates[c] && !gatesForceGap[c] ? 10.f : 0.f, c);
outputs[VELOCITY_OUTPUT].setVoltage(rescale(velocities[c], 0, 127, 0.f, 10.f), c); outputs[VELOCITY_OUTPUT].setVoltage(rescale(velocities[c], 0, 127, 0.f, 10.f), c);
outputs[AFTERTOUCH_OUTPUT].setVoltage(rescale(aftertouches[c], 0, 127, 0.f, 10.f), c); outputs[AFTERTOUCH_OUTPUT].setVoltage(rescale(aftertouches[c], 0, 127, 0.f, 10.f), c);
outputs[RETRIGGER_OUTPUT].setVoltage(retriggerPulses[c].process(args.sampleTime) ? 10.f : 0.f, c); outputs[RETRIGGER_OUTPUT].setVoltage(retriggerPulses[c].process(args.sampleTime) ? 10.f : 0.f, c);
outputs[STOP_OUTPUT].setVoltage(channelUnisonTransformBi(unisonIndecies[c], channelUnisonScale), c);
outputs[CONTINUE_OUTPUT].setVoltage(channelTransformBi(c, channelScale), c);
gatesForceGap[c] = false;
} }


// Set clock and transport outputs // Set clock and transport outputs
outputs[CLOCK_OUTPUT].setVoltage(clockPulse.process(args.sampleTime) ? 10.f : 0.f); outputs[CLOCK_OUTPUT].setVoltage(clockPulse.process(args.sampleTime) ? 10.f : 0.f);
outputs[CLOCK_DIV_OUTPUT].setVoltage(clockDividerPulse.process(args.sampleTime) ? 10.f : 0.f); outputs[CLOCK_DIV_OUTPUT].setVoltage(clockDividerPulse.process(args.sampleTime) ? 10.f : 0.f);
outputs[START_OUTPUT].setVoltage(startPulse.process(args.sampleTime) ? 10.f : 0.f); outputs[START_OUTPUT].setVoltage(startPulse.process(args.sampleTime) ? 10.f : 0.f);
outputs[STOP_OUTPUT].setVoltage(stopPulse.process(args.sampleTime) ? 10.f : 0.f);
outputs[CONTINUE_OUTPUT].setVoltage(continuePulse.process(args.sampleTime) ? 10.f : 0.f);
//outputs[STOP_OUTPUT].setVoltage(stopPulse.process(args.sampleTime) ? 10.f : 0.f);
//outputs[CONTINUE_OUTPUT].setVoltage(continuePulse.process(args.sampleTime) ? 10.f : 0.f);
} }


void processMessage(const midi::Message& msg) { void processMessage(const midi::Message& msg) {
@@ -192,9 +221,13 @@ struct MIDI_CV : Module {
// note on // note on
case 0x9: { case 0x9: {
if (msg.getValue() > 0) { if (msg.getValue() > 0) {
int c = msg.getChannel();
pressNote(msg.getNote(), &c);
velocities[c] = msg.getValue();
int midiChannel = msg.getChannel();
auto indicies = pressNote(msg.getNote(), midiChannel);
for (int u=0; (u < channelsUnison) && (indicies[u] >= 0); u++)
{
uint8_t c = indicies[u];
velocities[c] = msg.getValue();
}
} }
else { else {
// For some reason, some keyboards send a "note on" event with a velocity of 0 to signal that the key has been released. // For some reason, some keyboards send a "note on" event with a velocity of 0 to signal that the key has been released.
@@ -295,15 +328,15 @@ struct MIDI_CV : Module {
} }
} }


int assignChannel(uint8_t note) {
if (channels == 1)
return 0;
int assignChannel(uint8_t note, uint8_t unisonIndex) {
if (channels == channelsUnison)
return unisonIndex;


switch (polyMode) { switch (polyMode) {
case REUSE_MODE: { case REUSE_MODE: {
// Find channel with the same note // Find channel with the same note
for (int c = 0; c < channels; c++) { for (int c = 0; c < channels; c++) {
if (notes[c] == note)
if ((notes[c] == note) && (unisonIndecies[c] == unisonIndex))
return c; return c;
} }
} // fallthrough } // fallthrough
@@ -341,7 +374,9 @@ struct MIDI_CV : Module {
} }
} }


void pressNote(uint8_t note, int* channel) {
std::array<uint8_t, 17> pressNote(uint8_t note, int midiChannel) {
std::array<uint8_t, 17> indicies;
indicies[0] = -1;
// Remove existing similar note // Remove existing similar note
auto it = std::find(heldNotes.begin(), heldNotes.end(), note); auto it = std::find(heldNotes.begin(), heldNotes.end(), note);
if (it != heldNotes.end()) if (it != heldNotes.end())
@@ -350,15 +385,32 @@ struct MIDI_CV : Module {
heldNotes.push_back(note); heldNotes.push_back(note);
// Determine actual channel // Determine actual channel
if (polyMode == MPE_MODE) { if (polyMode == MPE_MODE) {
// need to handle this case as well to allow for unison
// Channel is already decided for us // Channel is already decided for us
// Set note
uint8_t c = midiChannel;
indicies[0] = c;
indicies[1] = -1;
notes[c] = note;
gates[c] = true;
unisonIndecies[c] = c % channelsUnison;
retriggerPulses[c].trigger(1e-3);
} }
else { else {
*channel = assignChannel(note);
for (int u = 0; u < channelsUnison; u++)
{
uint8_t c = assignChannel(note, u);
//uint8_t c = u;
indicies[u] = c;
// Set note
notes[c] = note;
gates[c] = true;
unisonIndecies[c] = u;
retriggerPulses[c].trigger(1e-3);
}
indicies[channelsUnison] = -1;
} }
// Set note
notes[*channel] = note;
gates[*channel] = true;
retriggerPulses[*channel].trigger(1e-3);
return indicies;
} }


void releaseNote(uint8_t note) { void releaseNote(uint8_t note) {
@@ -373,15 +425,21 @@ struct MIDI_CV : Module {
for (int c = 0; c < channels; c++) { for (int c = 0; c < channels; c++) {
if (notes[c] == note) { if (notes[c] == note) {
gates[c] = false; gates[c] = false;
// this will stay low even when gates[c] = true
// is set by a note on before the gate is sent as low
gatesForceGap[c] = gateForceGaps;
} }
} }
// Set last note if monophonic // Set last note if monophonic
if (channels == 1) {
if (channels == channelsUnison) {
if (note == notes[0] && !heldNotes.empty()) { if (note == notes[0] && !heldNotes.empty()) {
uint8_t lastNote = heldNotes.back(); uint8_t lastNote = heldNotes.back();
notes[0] = lastNote;
gates[0] = true;
return;
for (int u = 0; u < channelsUnison; u++)
{
uint8_t c = unisonIndecies[u];
notes[c] = lastNote;
gates[c] = true;
}
} }
} }
} }
@@ -397,10 +455,14 @@ struct MIDI_CV : Module {
return; return;
pedal = false; pedal = false;
// Set last note if monophonic // Set last note if monophonic
if (channels == 1) {
if (channels == channelsUnison) {
if (!heldNotes.empty()) { if (!heldNotes.empty()) {
uint8_t lastNote = heldNotes.back(); uint8_t lastNote = heldNotes.back();
notes[0] = lastNote;
for (int u = 0; u < channelsUnison; u++)
{
uint8_t c = unisonIndecies[u];
notes[c] = lastNote;
}
} }
} }
// Clear notes that are not held if polyphonic // Clear notes that are not held if polyphonic
@@ -412,13 +474,19 @@ struct MIDI_CV : Module {
for (uint8_t note : heldNotes) { for (uint8_t note : heldNotes) {
if (notes[c] == note) { if (notes[c] == note) {
gates[c] = true; gates[c] = true;
break;
} }
} }
} }
} }
} }


void setUnisonChannels(int channelsUnison) {
if (channelsUnison == this->channelsUnison)
return;
this->channelsUnison = channelsUnison;
panic();
}

void setChannels(int channels) { void setChannels(int channels) {
if (channels == this->channels) if (channels == this->channels)
return; return;
@@ -438,6 +506,7 @@ struct MIDI_CV : Module {
json_object_set_new(rootJ, "pwRange", json_real(pwRange)); json_object_set_new(rootJ, "pwRange", json_real(pwRange));
json_object_set_new(rootJ, "smooth", json_boolean(smooth)); json_object_set_new(rootJ, "smooth", json_boolean(smooth));
json_object_set_new(rootJ, "channels", json_integer(channels)); json_object_set_new(rootJ, "channels", json_integer(channels));
json_object_set_new(rootJ, "channelsUnison", json_integer(channelsUnison));
json_object_set_new(rootJ, "polyMode", json_integer(polyMode)); json_object_set_new(rootJ, "polyMode", json_integer(polyMode));
json_object_set_new(rootJ, "clockDivision", json_integer(clockDivision)); json_object_set_new(rootJ, "clockDivision", json_integer(clockDivision));
// Saving/restoring pitch and mod doesn't make much sense for MPE. // Saving/restoring pitch and mod doesn't make much sense for MPE.
@@ -465,6 +534,10 @@ struct MIDI_CV : Module {
if (channelsJ) if (channelsJ)
setChannels(json_integer_value(channelsJ)); setChannels(json_integer_value(channelsJ));


json_t* channelsUnisonJ = json_object_get(rootJ, "channelsUnison");
if (channelsUnisonJ)
setChannels(json_integer_value(channelsUnisonJ));

json_t* polyModeJ = json_object_get(rootJ, "polyMode"); json_t* polyModeJ = json_object_get(rootJ, "polyMode");
if (polyModeJ) if (polyModeJ)
polyMode = (PolyMode) json_integer_value(polyModeJ); polyMode = (PolyMode) json_integer_value(polyModeJ);
@@ -560,6 +633,15 @@ struct MIDI_CVWidget : ModuleWidget {
} }
})); }));


menu->addChild(createSubmenuItem("Unison channels", string::f("%d", module->channelsUnison), [=](Menu* menu) {
for (int u = 1; u <= 16; u++) {
menu->addChild(createCheckMenuItem((u == 1) ? "1 (Off)" : string::f("%d", u), "",
[=]() {return module->channelsUnison == u;},
[=]() {module->setUnisonChannels(u);}
));
}
}));

menu->addChild(createSubmenuItem("Polyphony channels", string::f("%d", module->channels), [=](Menu* menu) { menu->addChild(createSubmenuItem("Polyphony channels", string::f("%d", module->channels), [=](Menu* menu) {
for (int c = 1; c <= 16; c++) { for (int c = 1; c <= 16; c++) {
menu->addChild(createCheckMenuItem((c == 1) ? "Monophonic" : string::f("%d", c), "", menu->addChild(createCheckMenuItem((c == 1) ? "Monophonic" : string::f("%d", c), "",
@@ -576,6 +658,8 @@ struct MIDI_CVWidget : ModuleWidget {
"MPE", "MPE",
}, &module->polyMode)); }, &module->polyMode));


menu->addChild(createBoolPtrMenuItem("Gate force gaps", "", &module->gateForceGaps));

menu->addChild(createMenuItem("Panic", "", menu->addChild(createMenuItem("Panic", "",
[=]() {module->panic();} [=]() {module->panic();}
)); ));


Loading…
Cancel
Save