diff --git a/src/core/MIDI_CC.cpp b/src/core/MIDI_CC.cpp index 99ed3d6b..d218df08 100644 --- a/src/core/MIDI_CC.cpp +++ b/src/core/MIDI_CC.cpp @@ -22,12 +22,17 @@ struct MIDI_CC : Module { midi::InputQueue midiInput; /** [cc][channel] */ - int8_t values[128][16]; + int8_t ccValues[128][16]; + /** When LSB is enabled for CC 0-31, the MSB is stored here until the LSB is received. + [cc][channel] + */ + int8_t msbValues[32][16]; int learningId; int learnedCcs[16]; /** [cell][channel] */ dsp::ExponentialFilter valueFilters[16][16]; bool mpeMode; + bool lsbEnabled; MIDI_CC() { config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); @@ -43,17 +48,23 @@ struct MIDI_CC : Module { } void onReset() override { - for (int i = 0; i < 128; i++) { + for (int cc = 0; cc < 128; cc++) { + for (int c = 0; c < 16; c++) { + ccValues[cc][c] = 0; + } + } + for (int cc = 0; cc < 32; cc++) { for (int c = 0; c < 16; c++) { - values[i][c] = 0; + msbValues[cc][c] = 0; } } + learningId = -1; for (int i = 0; i < 16; i++) { learnedCcs[i] = i; } - learningId = -1; midiInput.reset(); mpeMode = false; + lsbEnabled = false; } void process(const ProcessArgs& args) override { @@ -77,7 +88,13 @@ struct MIDI_CC : Module { int cc = learnedCcs[i]; for (int c = 0; c < channels; c++) { - float value = values[cc][c] / 127.f; + int16_t cellValue = ccValues[cc][c] * 128; + if (lsbEnabled && cc < 32) + cellValue += ccValues[cc + 32][c]; + // Maximum value for 14-bit CC should be MSB=127 LSB=0, not MSB=127 LSB=127. + float value = cellValue / float(128 * 127); + // Support negative values because the gamepad MIDI driver generates nonstandard 8-bit CC values. + value = clamp(value, -1.f, 1.f); // Detect behavior from MIDI buttons. if (std::fabs(valueFilters[i][c].out - value) >= 1.f) { @@ -93,7 +110,7 @@ struct MIDI_CC : Module { } } - void processMessage(const midi::Message &msg) { + void processMessage(const midi::Message& msg) { switch (msg.getStatus()) { // cc case 0xb: { @@ -103,22 +120,33 @@ 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(); + 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][c] != value) { + if (learningId >= 0 && ccValues[cc][c] != value) { learnedCcs[learningId] = cc; learningId = -1; } - values[cc][c] = value; + + if (lsbEnabled && cc < 32) { + // Don't set MSB yet. Wait for LSB to be received. + msbValues[cc][c] = value; + } + else if (lsbEnabled && 32 <= cc && cc < 64) { + // Apply MSB when LSB is received + ccValues[cc - 32][c] = msbValues[cc - 32][c]; + ccValues[cc][c] = value; + } + else { + ccValues[cc][c] = value; + } } json_t* dataToJson() override { @@ -134,13 +162,14 @@ struct MIDI_CC : Module { json_t* valuesJ = json_array(); for (int i = 0; i < 128; 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_array_append_new(valuesJ, json_integer(ccValues[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)); + json_object_set_new(rootJ, "lsbEnabled", json_boolean(lsbEnabled)); return rootJ; } @@ -159,7 +188,7 @@ struct MIDI_CC : Module { for (int i = 0; i < 128; i++) { json_t* valueJ = json_array_get(valuesJ, i); if (valueJ) { - values[i][0] = json_integer_value(valueJ); + ccValues[i][0] = json_integer_value(valueJ); } } } @@ -171,14 +200,10 @@ struct MIDI_CC : Module { 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; + json_t* lsbEnabledJ = json_object_get(rootJ, "lsbEnabled"); + if (lsbEnabledJ) + lsbEnabled = json_boolean_value(lsbEnabledJ); } }; @@ -223,11 +248,31 @@ struct MIDI_CCWidget : ModuleWidget { menu->addChild(new MenuSeparator); - MIDI_CCMpeModeItem* mpeModeItem = new MIDI_CCMpeModeItem; + struct MpeModeItem : MenuItem { + MIDI_CC* module; + void onAction(const event::Action& e) override { + module->mpeMode ^= true; + } + }; + + MpeModeItem* mpeModeItem = new MpeModeItem; mpeModeItem->text = "MPE mode"; mpeModeItem->rightText = CHECKMARK(module->mpeMode); mpeModeItem->module = module; menu->addChild(mpeModeItem); + + struct LSBItem : MenuItem { + MIDI_CC* module; + void onAction(const event::Action& e) override { + module->lsbEnabled ^= true; + } + }; + + LSBItem* highResolutionItem = new LSBItem; + highResolutionItem->text = "CC 0-31 controls are 14-bit"; + highResolutionItem->rightText = CHECKMARK(module->lsbEnabled); + highResolutionItem->module = module; + menu->addChild(highResolutionItem); } };