Browse Source

Add MPE mode to MIDI-CC and MIDI-Gate.

tags/v2.0.0
Andrew Belt 5 years ago
parent
commit
f5898fe148
2 changed files with 120 additions and 50 deletions
  1. +62
    -20
      src/core/MIDI_CC.cpp
  2. +58
    -30
      src/core/MIDI_Gate.cpp

+ 62
- 20
src/core/MIDI_CC.cpp View File

@@ -21,30 +21,39 @@ struct MIDI_CC : Module {
}; };


midi::InputQueue midiInput; midi::InputQueue midiInput;
int8_t values[128];
/** [cc][channel] */
int8_t values[128][16];
int learningId; int learningId;
int learnedCcs[16]; int learnedCcs[16];
dsp::ExponentialFilter valueFilters[16];
/** [cell][channel] */
dsp::ExponentialFilter valueFilters[16][16];
bool mpeMode;


MIDI_CC() { MIDI_CC() {
config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
for (int i = 0; i < 16; i++) for (int i = 0; i < 16; i++)
configOutput(CC_OUTPUT + i, string::f("Cell %d", i + 1)); configOutput(CC_OUTPUT + i, string::f("Cell %d", i + 1));

for (int i = 0; i < 16; i++) { for (int i = 0; i < 16; i++) {
valueFilters[i].setTau(1 / 30.f);
for (int c = 0; c < 16; c++) {
valueFilters[i][c].setTau(1 / 30.f);
}
} }
onReset(); onReset();
} }


void onReset() override { void onReset() override {
for (int i = 0; i < 128; i++) { for (int i = 0; i < 128; i++) {
values[i] = 0;
for (int c = 0; c < 16; c++) {
values[i][c] = 0;
}
} }
for (int i = 0; i < 16; i++) { for (int i = 0; i < 16; i++) {
learnedCcs[i] = i; learnedCcs[i] = i;
} }
learningId = -1; learningId = -1;
midiInput.reset(); midiInput.reset();
mpeMode = false;
} }


void process(const ProcessArgs& args) override { void process(const ProcessArgs& args) override {
@@ -59,23 +68,28 @@ struct MIDI_CC : Module {
midiInput.queue.pop(); midiInput.queue.pop();
} }


int channels = mpeMode ? 16 : 1;
for (int i = 0; i < 16; i++) { for (int i = 0; i < 16; i++) {
if (!outputs[CC_OUTPUT + i].isConnected()) if (!outputs[CC_OUTPUT + i].isConnected())
continue; continue;
outputs[CC_OUTPUT + i].setChannels(channels);


int cc = learnedCcs[i]; int cc = learnedCcs[i];
float value = values[cc] / 127.f;


// Detect behavior from MIDI buttons.
if (std::fabs(valueFilters[i].out - value) >= 1.f) {
// Jump value
valueFilters[i].out = value;
}
else {
// Smooth value with filter
valueFilters[i].process(args.sampleTime, value);
for (int c = 0; c < channels; c++) {
float value = values[cc][c] / 127.f;

// Detect behavior from MIDI buttons.
if (std::fabs(valueFilters[i][c].out - value) >= 1.f) {
// Jump value
valueFilters[i][c].out = value;
}
else {
// Smooth value with filter
valueFilters[i][c].process(args.sampleTime, value);
}
outputs[CC_OUTPUT + i].setVoltage(valueFilters[i][c].out * 10.f, c);
} }
outputs[CC_OUTPUT + i].setVoltage(valueFilters[i].out * 10.f);
} }
} }


@@ -90,20 +104,21 @@ struct MIDI_CC : Module {
} }


void processCC(const midi::Message &msg) { void processCC(const midi::Message &msg) {
uint8_t c = mpeMode ? msg.getChannel() : 0;
uint8_t cc = msg.getNote(); uint8_t cc = msg.getNote();
if (msg.bytes.size() < 2)
return;
// Allow CC to be negative if the 8th bit is set. // Allow CC to be negative if the 8th bit is set.
// The gamepad driver abuses this, for example. // The gamepad driver abuses this, for example.
// Cast uint8_t to int8_t // Cast uint8_t to int8_t
if (msg.bytes.size() < 2)
return;
int8_t value = msg.bytes[2]; int8_t value = msg.bytes[2];
value = clamp(value, -127, 127); value = clamp(value, -127, 127);
// Learn // Learn
if (learningId >= 0 && values[cc] != value) {
if (learningId >= 0 && values[cc][c] != value) {
learnedCcs[learningId] = cc; learnedCcs[learningId] = cc;
learningId = -1; learningId = -1;
} }
values[cc] = value;
values[cc][c] = value;
} }


json_t* dataToJson() override { json_t* dataToJson() override {
@@ -118,11 +133,14 @@ struct MIDI_CC : Module {
// Remember values so users don't have to touch MIDI controller knobs when restarting Rack // Remember values so users don't have to touch MIDI controller knobs when restarting Rack
json_t* valuesJ = json_array(); json_t* valuesJ = json_array();
for (int i = 0; i < 128; i++) { for (int i = 0; i < 128; i++) {
json_array_append_new(valuesJ, json_integer(values[i]));
// Note: Only save channel 0. Since MPE mode won't be commonly used, it's pointless to save all 16 channels.
json_array_append_new(valuesJ, json_integer(values[i][0]));
} }
json_object_set_new(rootJ, "values", valuesJ); json_object_set_new(rootJ, "values", valuesJ);


json_object_set_new(rootJ, "midi", midiInput.toJson()); json_object_set_new(rootJ, "midi", midiInput.toJson());

json_object_set_new(rootJ, "mpeMode", json_boolean(mpeMode));
return rootJ; return rootJ;
} }


@@ -141,7 +159,7 @@ struct MIDI_CC : Module {
for (int i = 0; i < 128; i++) { for (int i = 0; i < 128; i++) {
json_t* valueJ = json_array_get(valuesJ, i); json_t* valueJ = json_array_get(valuesJ, i);
if (valueJ) { if (valueJ) {
values[i] = json_integer_value(valueJ);
values[i][0] = json_integer_value(valueJ);
} }
} }
} }
@@ -149,6 +167,18 @@ struct MIDI_CC : Module {
json_t* midiJ = json_object_get(rootJ, "midi"); json_t* midiJ = json_object_get(rootJ, "midi");
if (midiJ) if (midiJ)
midiInput.fromJson(midiJ); midiInput.fromJson(midiJ);

json_t* mpeModeJ = json_object_get(rootJ, "mpeMode");
if (mpeModeJ)
mpeMode = json_boolean_value(mpeModeJ);
}
};


struct MIDI_CCMpeModeItem : MenuItem {
MIDI_CC* module;
void onAction(const event::Action& e) override {
module->mpeMode ^= true;
} }
}; };


@@ -187,6 +217,18 @@ struct MIDI_CCWidget : ModuleWidget {
midiWidget->setModule(module); midiWidget->setModule(module);
addChild(midiWidget); addChild(midiWidget);
} }

void appendContextMenu(Menu* menu) override {
MIDI_CC* module = dynamic_cast<MIDI_CC*>(this->module);

menu->addChild(new MenuSeparator);

MIDI_CCMpeModeItem* mpeModeItem = new MIDI_CCMpeModeItem;
mpeModeItem->text = "MPE mode";
mpeModeItem->rightText = CHECKMARK(module->mpeMode);
mpeModeItem->module = module;
menu->addChild(mpeModeItem);
}
}; };






+ 58
- 30
src/core/MIDI_Gate.cpp View File

@@ -22,17 +22,22 @@ struct MIDI_Gate : Module {


midi::InputQueue midiInput; midi::InputQueue midiInput;


bool gates[16];
float gateTimes[16];
uint8_t velocities[16];
/** [cell][c] */
bool gates[16][16];
/** [cell][c] */
float gateTimes[16][16];
/** [cell][c] */
uint8_t velocities[16][16];
int learningId = -1; int learningId = -1;
uint8_t learnedNotes[16] = {}; uint8_t learnedNotes[16] = {};
bool velocityMode = false; bool velocityMode = false;
bool mpeMode = false;


MIDI_Gate() { MIDI_Gate() {
config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
for (int i = 0; i < 16; i++) for (int i = 0; i < 16; i++)
configOutput(TRIG_OUTPUT + i, string::f("Cell %d", i + 1)); configOutput(TRIG_OUTPUT + i, string::f("Cell %d", i + 1));

onReset(); onReset();
} }


@@ -45,12 +50,16 @@ struct MIDI_Gate : Module {
learningId = -1; learningId = -1;
panic(); panic();
midiInput.reset(); midiInput.reset();
velocityMode = false;
mpeMode = false;
} }


void panic() { void panic() {
for (int i = 0; i < 16; i++) { for (int i = 0; i < 16; i++) {
gates[i] = false;
gateTimes[i] = 0.f;
for (int c = 0; c < 16; c++) {
gates[i][c] = false;
gateTimes[i][c] = 0.f;
}
} }
} }


@@ -66,17 +75,20 @@ struct MIDI_Gate : Module {
midiInput.queue.pop(); midiInput.queue.pop();
} }


int channels = mpeMode ? 16 : 1;

for (int i = 0; i < 16; i++) { for (int i = 0; i < 16; i++) {
if (gateTimes[i] > 0.f) {
outputs[TRIG_OUTPUT + i].setVoltage(velocityMode ? rescale(velocities[i], 0, 127, 0.f, 10.f) : 10.f);
// If the gate is off, wait 1 ms before turning the pulse off.
// This avoids drum controllers sending a pulse with 0 ms duration.
if (!gates[i]) {
gateTimes[i] -= args.sampleTime;
outputs[TRIG_OUTPUT + i].setChannels(channels);
for (int c = 0; c < channels; c++) {
// Make sure all pulses last longer than 1ms
if (gates[i][c] || gateTimes[i][c] > 0.f) {
float velocity = velocityMode ? (velocities[i][c] / 127.f) : 1.f;
outputs[TRIG_OUTPUT + i].setVoltage(velocity * 10.f, c);
gateTimes[i][c] -= args.sampleTime;
}
else {
outputs[TRIG_OUTPUT + i].setVoltage(0.f, c);
} }
}
else {
outputs[TRIG_OUTPUT + i].setVoltage(0.f);
} }
} }
} }
@@ -85,29 +97,24 @@ struct MIDI_Gate : Module {
switch (msg.getStatus()) { switch (msg.getStatus()) {
// note off // note off
case 0x8: { case 0x8: {
releaseNote(msg.getNote());
releaseNote(msg.getChannel(), msg.getNote());
} break; } break;
// note on // note on
case 0x9: { case 0x9: {
if (msg.getValue() > 0) { if (msg.getValue() > 0) {
pressNote(msg.getNote(), msg.getValue());
pressNote(msg.getChannel(), msg.getNote(), msg.getValue());
} }
else { else {
// I don't know why, but many keyboards send a "note on" command with 0 velocity to mean "note release"
releaseNote(msg.getNote());
}
} break;
// all notes off (panic)
case 0x7b: {
if (msg.getValue() == 0) {
panic();
// Many stupid keyboards send a "note on" command with 0 velocity to mean "note release"
releaseNote(msg.getChannel(), msg.getNote());
} }
} break; } break;
default: break; default: break;
} }
} }


void pressNote(uint8_t note, uint8_t vel) {
void pressNote(uint8_t channel, uint8_t note, uint8_t vel) {
int c = mpeMode ? channel : 0;
// Learn // Learn
if (learningId >= 0) { if (learningId >= 0) {
learnedNotes[learningId] = note; learnedNotes[learningId] = note;
@@ -116,18 +123,19 @@ struct MIDI_Gate : Module {
// Find id // Find id
for (int i = 0; i < 16; i++) { for (int i = 0; i < 16; i++) {
if (learnedNotes[i] == note) { if (learnedNotes[i] == note) {
gates[i] = true;
gateTimes[i] = 1e-3f;
velocities[i] = vel;
gates[i][c] = true;
gateTimes[i][c] = 1e-3f;
velocities[i][c] = vel;
} }
} }
} }


void releaseNote(uint8_t note) {
void releaseNote(uint8_t channel, uint8_t note) {
int c = mpeMode ? channel : 0;
// Find id // Find id
for (int i = 0; i < 16; i++) { for (int i = 0; i < 16; i++) {
if (learnedNotes[i] == note) { if (learnedNotes[i] == note) {
gates[i] = false;
gates[i][c] = false;
} }
} }
} }
@@ -145,6 +153,8 @@ struct MIDI_Gate : Module {
json_object_set_new(rootJ, "velocity", json_boolean(velocityMode)); json_object_set_new(rootJ, "velocity", json_boolean(velocityMode));


json_object_set_new(rootJ, "midi", midiInput.toJson()); json_object_set_new(rootJ, "midi", midiInput.toJson());

json_object_set_new(rootJ, "mpeMode", json_boolean(mpeMode));
return rootJ; return rootJ;
} }


@@ -165,6 +175,10 @@ struct MIDI_Gate : Module {
json_t* midiJ = json_object_get(rootJ, "midi"); json_t* midiJ = json_object_get(rootJ, "midi");
if (midiJ) if (midiJ)
midiInput.fromJson(midiJ); midiInput.fromJson(midiJ);

json_t* mpeModeJ = json_object_get(rootJ, "mpeMode");
if (mpeModeJ)
mpeMode = json_boolean_value(mpeModeJ);
} }
}; };


@@ -177,6 +191,14 @@ struct MIDI_GateVelocityItem : MenuItem {
}; };




struct MIDI_GateMpeModeItem : MenuItem {
MIDI_Gate* module;
void onAction(const event::Action& e) override {
module->mpeMode ^= true;
}
};


struct MIDI_GatePanicItem : MenuItem { struct MIDI_GatePanicItem : MenuItem {
MIDI_Gate* module; MIDI_Gate* module;
void onAction(const event::Action& e) override { void onAction(const event::Action& e) override {
@@ -228,6 +250,12 @@ struct MIDI_GateWidget : ModuleWidget {
velocityItem->module = module; velocityItem->module = module;
menu->addChild(velocityItem); menu->addChild(velocityItem);


MIDI_GateMpeModeItem* mpeModeItem = new MIDI_GateMpeModeItem;
mpeModeItem->text = "MPE mode";
mpeModeItem->rightText = CHECKMARK(module->mpeMode);
mpeModeItem->module = module;
menu->addChild(mpeModeItem);

MIDI_GatePanicItem* panicItem = new MIDI_GatePanicItem; MIDI_GatePanicItem* panicItem = new MIDI_GatePanicItem;
panicItem->text = "Panic"; panicItem->text = "Panic";
panicItem->module = module; panicItem->module = module;


Loading…
Cancel
Save