Browse Source

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

tags/v2.0.0
Andrew Belt 4 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;
int8_t values[128];
/** [cc][channel] */
int8_t values[128][16];
int learningId;
int learnedCcs[16];
dsp::ExponentialFilter valueFilters[16];
/** [cell][channel] */
dsp::ExponentialFilter valueFilters[16][16];
bool mpeMode;

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

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

void onReset() override {
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++) {
learnedCcs[i] = i;
}
learningId = -1;
midiInput.reset();
mpeMode = false;
}

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

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

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

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
json_t* valuesJ = json_array();
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, "midi", midiInput.toJson());

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

@@ -141,7 +159,7 @@ struct MIDI_CC : Module {
for (int i = 0; i < 128; i++) {
json_t* valueJ = json_array_get(valuesJ, i);
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");
if (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);
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;

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;
uint8_t learnedNotes[16] = {};
bool velocityMode = false;
bool mpeMode = false;

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

onReset();
}

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

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

int channels = mpeMode ? 16 : 1;

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()) {
// note off
case 0x8: {
releaseNote(msg.getNote());
releaseNote(msg.getChannel(), msg.getNote());
} break;
// note on
case 0x9: {
if (msg.getValue() > 0) {
pressNote(msg.getNote(), msg.getValue());
pressNote(msg.getChannel(), msg.getNote(), msg.getValue());
}
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;
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
if (learningId >= 0) {
learnedNotes[learningId] = note;
@@ -116,18 +123,19 @@ struct MIDI_Gate : Module {
// Find id
for (int i = 0; i < 16; i++) {
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
for (int i = 0; i < 16; i++) {
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, "midi", midiInput.toJson());

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

@@ -165,6 +175,10 @@ struct MIDI_Gate : Module {
json_t* midiJ = json_object_get(rootJ, "midi");
if (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 {
MIDI_Gate* module;
void onAction(const event::Action& e) override {
@@ -228,6 +250,12 @@ struct MIDI_GateWidget : ModuleWidget {
velocityItem->module = module;
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;
panicItem->text = "Panic";
panicItem->module = module;


Loading…
Cancel
Save