diff --git a/src/faderbank/FaderbankModule.cpp b/src/faderbank/FaderbankModule.cpp index f1b4815..667f1d5 100644 --- a/src/faderbank/FaderbankModule.cpp +++ b/src/faderbank/FaderbankModule.cpp @@ -56,29 +56,35 @@ void FaderbankModule::processMIDIMessages(const ProcessArgs& args) // Combine channel and CC number into a lookup key uint8_t ccNum = msg.getNote(); uint16_t key = (msg.getChannel() << 8) | ccNum; + auto iter = inputMap.find(key); if (iter != inputMap.end()) { - uint8_t index = iter->second; - if (index < NUM_FADERS) - { - records[index].highValue = msg.getValue(); - records[index].lastHighValue = msg.getValue(); - records[index].lastHighValueFrame = args.frame; + auto faderDestinations = iter->second; + for (auto index : faderDestinations) { + if (index < NUM_FADERS) + { + records[index].highValue = msg.getValue(); + records[index].lastHighValue = msg.getValue(); + records[index].lastHighValueFrame = args.frame; + } } } - else if (use14bitCCs && ccNum >= 32 && ccNum < 64) + else if (ccNum >= 32 && ccNum < 64) { // look for potential LSB CC of 14-bit CC 0-31 key = (msg.getChannel() << 8) | (ccNum - 32); iter = inputMap.find(key); if (iter != inputMap.end()) { - uint8_t index = iter->second; - if (index < NUM_FADERS) + auto faderDestinations = iter->second; + for (auto index : faderDestinations) { - records[index].lowValue = msg.getValue(); - records[index].lastLowValueFrame = args.frame; + if (index < NUM_FADERS && records[index].faderMode == FaderMode14bitCC) + { + records[index].lowValue = msg.getValue(); + records[index].lastLowValueFrame = args.frame; + } } } } @@ -90,16 +96,21 @@ void FaderbankModule::processMIDIMessages(const ProcessArgs& args) msg.bytes[2] == 0x00 && msg.bytes[3] == 0x00 && msg.bytes[4] == 0x0F && // sysex config response ID - msg.bytes.size() > (9 + 48 + NUM_FADERS)) + msg.bytes.size() >= (9 + 80)) { - inputMap.clear(); for (int i = 0; i < NUM_FADERS; i++) { - uint8_t channel = msg.bytes[9 + 16 + i] - 1; - uint8_t ccNum = msg.bytes[9 + 48 + i]; - inputMap[(channel << 8) | ccNum] = i; + uint8_t channel = ((msg.bytes[9 + 16 + i]) & 0xF) - 1; + uint8_t ccNum = msg.bytes[9 + 48 + i] & 0x7F; records[i].ccNum = ccNum; + records[i].channel = channel; + if (msg.bytes.size() >= 9 + 82) + { + uint16_t ccMode = msg.bytes[9 + 80] << 8 | msg.bytes[9 + 81]; + records[i].faderMode = (ccMode & (1 << i)) == 0 ? FaderMode14bitCC : FaderModeCC; + } } + updateInputMap(); } } break; @@ -112,7 +123,7 @@ void FaderbankModule::processMIDIMessages(const ProcessArgs& args) { uint16_t value; bool updateable = false; - bool expect14bit = use14bitCCs && records[i].ccNum < 32; + bool expect14bit = records[i].faderMode == FaderMode14bitCC && records[i].ccNum < 32; if (records[i].highValue != 0xFF) { @@ -132,7 +143,7 @@ void FaderbankModule::processMIDIMessages(const ProcessArgs& args) } else { - value = records[i].highValue & 0x7F; + value = (records[i].highValue & 0x7F) << 7; updateable = true; } } @@ -148,7 +159,7 @@ void FaderbankModule::processMIDIMessages(const ProcessArgs& args) auto param = getParamQuantity(i); if (param) { - param->setScaledValue((value * 1.0f) / ((expect14bit ? 0x3FFF : 0x7F) * 1.0f)); + param->setScaledValue((value * 1.0f) / (0x3FFF * 1.0f)); } records[i].highValue = 0xFF; @@ -159,12 +170,30 @@ void FaderbankModule::processMIDIMessages(const ProcessArgs& args) void FaderbankModule::resetConfig() { - inputMap.clear(); for (int i = 0; i < NUM_FADERS; i++) { // by default, assign CC faders starting with 32, all on channel 1 - inputMap[32 + i] = i; records[i].ccNum = 32 + i; + records[i].channel = 0; + records[i].faderMode = FaderModeCC; + } + + updateInputMap(); +} + +void FaderbankModule::updateInputMap() +{ + inputMap.clear(); + + for (int i = 0; i < NUM_FADERS; i++) + { + uint16_t key = (records[i].channel << 8) | records[i].ccNum; + if (inputMap.find(key) == inputMap.end()) + { + inputMap.insert(make_pair(key, std::vector())); + } + + inputMap[key].push_back(i); } } @@ -204,6 +233,47 @@ void FaderbankModule::updateFaderRanges() } } +void FaderbankModule::autodetectConfig() +{ + resetConfig(); + + midiInput.setDriverId(rack::midi::getDriverIds()[0]); + if (midiInput.deviceId == -1) + { + for (int deviceId : midiInput.getDeviceIds()) + { + if (midiInput.getDeviceName(deviceId).substr(0, 3).find("16n") != std::string::npos) + { + midiInput.setDeviceId(deviceId); + break; + } + } + } + + midiOutput.setDriverId(rack::midi::getDriverIds()[0]); + if (midiOutput.deviceId == -1) + { + for (int deviceId : midiOutput.getDeviceIds()) + { + if (midiOutput.getDeviceName(deviceId).find("16n") != std::string::npos) + { + midiOutput.setDeviceId(deviceId); + break; + } + } + } + + // Send a sysex message to request device channel/CC config. + if (midiOutput.deviceId != -1) + { + rack::midi::Message msg; + msg.setSize(6); + msg.bytes = { 0xF0, 0x7d, 0x00, 0x00, 0x1F, 0xF7 }; + + midiOutput.sendMessage(msg); + } +} + json_t* FaderbankModule::dataToJson() { json_t* rootJ = json_object(); @@ -211,17 +281,21 @@ json_t* FaderbankModule::dataToJson() json_object_set_new(rootJ, "faderRange", json_integer(faderRange)); json_object_set_new(rootJ, "faderSize", json_integer(faderSize)); json_object_set_new(rootJ, "polyphonicMode", json_boolean(polyphonicMode)); - json_object_set_new(rootJ, "use14bitCCs", json_boolean(use14bitCCs)); json_object_set_new(rootJ, "midi", midiInput.toJson()); json_object_set_new(rootJ, "midiOutput", midiOutput.toJson()); - json_t* configJ = json_object(); - for (auto& entry : inputMap) + json_t* configJ = json_array(); + for (auto& entry : records) { - json_object_set_new(configJ, std::to_string(entry.first).c_str(), json_integer(entry.second)); + json_t* faderRecord = json_object(); + json_object_set_new(faderRecord, "channel", json_integer(entry.channel)); + json_object_set_new(faderRecord, "faderMode", json_integer(entry.faderMode)); + json_object_set_new(faderRecord, "ccNum", json_integer(entry.ccNum)); + + json_array_append(configJ, faderRecord); } - json_object_set_new(rootJ, "16n_config", configJ); + json_object_set_new(rootJ, "fader_config", configJ); return rootJ; } @@ -242,10 +316,6 @@ void FaderbankModule::dataFromJson(json_t* rootJ) if (polyphonicModeJ) polyphonicMode = json_boolean_value(polyphonicModeJ); - json_t* use14bitCCsJ = json_object_get(rootJ, "use14bitCCs"); - if (use14bitCCsJ) - use14bitCCs = json_boolean_value(use14bitCCsJ); - json_t* midiJ = json_object_get(rootJ, "midi"); if (midiJ) midiInput.fromJson(midiJ); @@ -254,20 +324,51 @@ void FaderbankModule::dataFromJson(json_t* rootJ) if (midiOutputJ) midiOutput.fromJson(midiOutputJ); - json_t* configJ = json_object_get(rootJ, "16n_config"); - if (configJ) + // backwards compatibility for patches with older config structure + json_t* oldConfigJ = json_object_get(rootJ, "16n_config"); + if (oldConfigJ) { - inputMap.clear(); json_t* dataJ; const char* key; - json_object_foreach(configJ, key, dataJ) + json_object_foreach(oldConfigJ, key, dataJ) { int16_t val = std::stoi(key); int8_t fader = json_integer_value(dataJ); - inputMap[val] = fader; - records[fader].ccNum = val & 0xFF; + records[fader].ccNum = val & 0x7F; + records[fader].channel = val >> 8; } } + + // current format for config + json_t* configJ = json_object_get(rootJ, "fader_config"); + if (configJ) + { + json_t* dataJ; + size_t key; + json_array_foreach(configJ, key, dataJ) + { + if (key < NUM_FADERS) + { + json_t* channelJ = json_object_get(dataJ, "channel"); + if (channelJ) + { + records[key].channel = json_integer_value(channelJ) & 0xF; + } + json_t* modeJ = json_object_get(dataJ, "faderMode"); + if (modeJ) + { + records[key].faderMode = static_cast(json_integer_value(modeJ)); + } + json_t* ccJ = json_object_get(dataJ, "ccNum"); + if (ccJ) + { + records[key].ccNum = json_integer_value(ccJ) & 0x7F; + } + } + } + } + + updateInputMap(); } void FaderbankModule::fromJson(json_t* rootJ) @@ -278,4 +379,14 @@ void FaderbankModule::fromJson(json_t* rootJ) dataFromJson(dataJ); Module::fromJson(rootJ); +} + +FaderbankModule::ControllerRecord::ControllerRecord() +{ + highValue = 0xFF; + lowValue = 0xFF; + lastHighValue = 0; + channel = 0; + ccNum = 0; + faderMode = FaderModeCC; } \ No newline at end of file diff --git a/src/faderbank/FaderbankModule.hpp b/src/faderbank/FaderbankModule.hpp index a29fc53..e5ffa6f 100644 --- a/src/faderbank/FaderbankModule.hpp +++ b/src/faderbank/FaderbankModule.hpp @@ -5,22 +5,6 @@ #define NUM_FADERS 16 -struct ControllerRecord -{ - uint8_t highValue; - uint8_t lowValue; - uint8_t lastHighValue; - int64_t lastHighValueFrame; - int64_t lastLowValueFrame; - uint8_t ccNum; - - ControllerRecord() - { - highValue = 0xFF; - lowValue = 0xFF; - lastHighValue = 0; - } -}; struct FaderbankModule : rack::Module { @@ -31,14 +15,16 @@ struct FaderbankModule : rack::Module void processMIDIMessages(const ProcessArgs& args); void resetConfig(); + void updateInputMap(); void updateFaderRanges(); + void autodetectConfig(); json_t* dataToJson() override; void dataFromJson(json_t* rootJ) override; // override fromJson to deserialize data before params void fromJson(json_t* rootJ) override; - std::map inputMap; + std::map > inputMap; rack::midi::InputQueue midiInput; rack::midi::Output midiOutput; @@ -56,11 +42,29 @@ struct FaderbankModule : rack::Module FaderRangeBipolar } FaderRange; + typedef enum + { + FaderModeCC, + FaderMode14bitCC + } FaderMode; + + struct ControllerRecord + { + uint8_t highValue; + uint8_t lowValue; + uint8_t lastHighValue; + int64_t lastHighValueFrame; + int64_t lastLowValueFrame; + uint8_t ccNum; + uint8_t channel; + FaderMode faderMode; + + ControllerRecord(); + }; + FaderSize faderSize = FaderSize90mm; FaderRange faderRange = FaderRange10V; bool polyphonicMode = false; - bool use14bitCCs = false; -protected: ControllerRecord records[NUM_FADERS]; }; diff --git a/src/faderbank/FaderbankWidget.cpp b/src/faderbank/FaderbankWidget.cpp index d01ef33..1d61eaf 100644 --- a/src/faderbank/FaderbankWidget.cpp +++ b/src/faderbank/FaderbankWidget.cpp @@ -107,7 +107,6 @@ struct FaderbankSliderYellow : LightSlider NUM_FADERS) + { + return; + } + + FaderbankModule::ControllerRecord record = fb->records[faderIndex]; + + std::vector modeNames { "CC", "CC (14-bit)" }; + + std::vector channelNames; + for (auto i = 0; i < 16; i++) + { + std::ostringstream ss; + ss << (i + 1); + channelNames.push_back(ss.str()); + } + + std::vector ccNames; + for (auto i = 0; i < 128; i++) + { + std::ostringstream ss; + ss << i; + ccNames.push_back(ss.str()); + } + + std::ostringstream faderName; + faderName << faderIndex + 1; + + std::ostringstream faderDesc; + faderDesc << "Ch " << (int)(record.channel + 1) << " " << modeNames[record.faderMode] << " "; + if (record.faderMode == FaderbankModule::FaderModeCC) + { + faderDesc << (int)record.ccNum; + } + else if (record.faderMode == FaderbankModule::FaderMode14bitCC) + { + faderDesc << (int)record.ccNum << "/" << (int)(record.ccNum + 32); + } + + menu->addChild(createSubmenuItem(faderName.str(), faderDesc.str(), + [=](Menu* childMenu) + { + childMenu->addChild(createIndexSubmenuItem("Channel", channelNames, + [=]() + { + return fb->records[faderIndex].channel; + }, + [=](int index) + { + fb->records[faderIndex].channel = index & 0xF; + fb->updateInputMap(); + } + )); + + childMenu->addChild(createIndexSubmenuItem("Mode", modeNames, + [=]() + { + return fb->records[faderIndex].faderMode; + }, + [=](int index) + { + fb->records[faderIndex].faderMode = static_cast(index); + fb->updateInputMap(); + } + )); + + childMenu->addChild(createIndexSubmenuItem("CC Number", ccNames, + [=]() + { + return fb->records[faderIndex].ccNum; + }, + [=](int index) + { + fb->records[faderIndex].ccNum = index & 0x7F; + fb->updateInputMap(); + } + )); + } + )); +} + void FaderbankWidget::appendContextMenu(Menu* menu) { auto fb = dynamic_cast(module); @@ -200,47 +282,61 @@ void FaderbankWidget::appendContextMenu(Menu* menu) menu->addChild(new MenuSeparator()); - menu->addChild(createSubmenuItem("MIDI input", fb->midiInput.getDeviceName(fb->midiInput.getDeviceId()), - [=](Menu* childMenu) - { - appendMidiMenu(childMenu, &fb->midiInput); - // remove channel selection - auto last = childMenu->children.back(); - childMenu->removeChild(last); - delete last; - })); - - menu->addChild(createSubmenuItem("MIDI output", fb->midiOutput.getDeviceName(fb->midiOutput.getDeviceId()), - [=](Menu* childMenu) - { - appendMidiMenu(childMenu, &fb->midiOutput); - // remove channel selection - auto last = childMenu->children.back(); - childMenu->removeChild(last); - delete last; - })); - - menu->addChild(createCheckMenuItem( - "Use 14-bit MIDI CCs", "", - [=]() - { - return fb->use14bitCCs; - }, + menu->addChild(createMenuItem("Autodetect 16n hardware", "", [=]() { - fb->use14bitCCs = !fb->use14bitCCs; + fb->autodetectConfig(); })); - menu->addChild(createMenuItem("Autodetect 16n configuration", "", - [=]() + menu->addChild(createSubmenuItem("MIDI Configuration", "", + [=](Menu* configMenu) { - fb->resetConfig(); - - // Send a sysex message to request device channel/CC config. - midi::Message msg; - msg.setSize(6); - msg.bytes = { 0xF0, 0x7d, 0x00, 0x00, 0x1F, 0xF7 }; + configMenu->addChild(createSubmenuItem("Input device", fb->midiInput.getDeviceName(fb->midiInput.getDeviceId()), + [=](Menu* childMenu) + { + appendMidiMenu(childMenu, &fb->midiInput); + // remove channel selection + auto last = childMenu->children.back(); + childMenu->removeChild(last); + delete last; + // and separator + last = childMenu->children.back(); + childMenu->removeChild(last); + delete last; + })); + + configMenu->addChild(createSubmenuItem("Output device", fb->midiOutput.getDeviceName(fb->midiOutput.getDeviceId()), + [=](Menu* childMenu) + { + appendMidiMenu(childMenu, &fb->midiOutput); + // remove channel selection + auto last = childMenu->children.back(); + childMenu->removeChild(last); + delete last; + // and separator + last = childMenu->children.back(); + childMenu->removeChild(last); + delete last; + })); + + configMenu->addChild(createSubmenuItem("Fader settings", "", + [=](Menu* childMenu) + { + for (int i = 0; i < NUM_FADERS; i++) + { + appendFaderConfigMenu(fb, childMenu, i); + } + })); + + configMenu->addChild(new MenuSeparator()); + + configMenu->addChild(createMenuItem("Write configuration to 16n hardware", "", + [=]() + { + // TODO + } + )); + } + )); +} - fb->midiOutput.sendMessage(msg); - })); -} \ No newline at end of file