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 <array>

#include "plugin.hpp"

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

/** Resets performance state */
void panic() {
for (int c = 0; c < 16; c++) {
notes[c] = 60;
unisonIndecies[c] = 0;
gates[c] = false;
gatesForceGap[c] = false;
velocities[c] = 0;
aftertouches[c] = 0;
pws[c] = 8192;
@@ -127,6 +138,17 @@ struct MIDI_CV : Module {
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 {
midi::Message msg;
while (midiInput.tryPop(&msg, args.frame)) {
@@ -163,22 +185,29 @@ struct MIDI_CV : Module {
outputs[VELOCITY_OUTPUT].setChannels(channels);
outputs[AFTERTOUCH_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++) {
float pw = pwValues[(polyMode == MPE_MODE) ? c : 0];
float pitch = (notes[c] - 60.f + pw * pwRange) / 12.f;
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[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[STOP_OUTPUT].setVoltage(channelUnisonTransformBi(unisonIndecies[c], channelUnisonScale), c);
outputs[CONTINUE_OUTPUT].setVoltage(channelTransformBi(c, channelScale), c);
gatesForceGap[c] = false;
}

// Set clock and transport outputs
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[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) {
@@ -192,9 +221,13 @@ struct MIDI_CV : Module {
// note on
case 0x9: {
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 {
// 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) {
case REUSE_MODE: {
// Find channel with the same note
for (int c = 0; c < channels; c++) {
if (notes[c] == note)
if ((notes[c] == note) && (unisonIndecies[c] == unisonIndex))
return c;
}
} // 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
auto it = std::find(heldNotes.begin(), heldNotes.end(), note);
if (it != heldNotes.end())
@@ -350,15 +385,32 @@ struct MIDI_CV : Module {
heldNotes.push_back(note);
// Determine actual channel
if (polyMode == MPE_MODE) {
// need to handle this case as well to allow for unison
// 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 {
*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) {
@@ -373,15 +425,21 @@ struct MIDI_CV : Module {
for (int c = 0; c < channels; c++) {
if (notes[c] == note) {
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
if (channels == 1) {
if (channels == channelsUnison) {
if (note == notes[0] && !heldNotes.empty()) {
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;
pedal = false;
// Set last note if monophonic
if (channels == 1) {
if (channels == channelsUnison) {
if (!heldNotes.empty()) {
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
@@ -412,13 +474,19 @@ struct MIDI_CV : Module {
for (uint8_t note : heldNotes) {
if (notes[c] == note) {
gates[c] = true;
break;
}
}
}
}
}

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

void setChannels(int channels) {
if (channels == this->channels)
return;
@@ -438,6 +506,7 @@ struct MIDI_CV : Module {
json_object_set_new(rootJ, "pwRange", json_real(pwRange));
json_object_set_new(rootJ, "smooth", json_boolean(smooth));
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, "clockDivision", json_integer(clockDivision));
// Saving/restoring pitch and mod doesn't make much sense for MPE.
@@ -465,6 +534,10 @@ struct MIDI_CV : Module {
if (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");
if (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) {
for (int c = 1; c <= 16; c++) {
menu->addChild(createCheckMenuItem((c == 1) ? "Monophonic" : string::f("%d", c), "",
@@ -576,6 +658,8 @@ struct MIDI_CVWidget : ModuleWidget {
"MPE",
}, &module->polyMode));

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

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


Loading…
Cancel
Save