|
- #include "plugin.hpp"
-
-
- /*! \brief Decode System Exclusive messages.
- SysEx messages are encoded to guarantee transmission of data bytes higher than
- 127 without breaking the MIDI protocol. Use this static method to reassemble
- your received message.
- \param inSysEx The SysEx data received from MIDI in.
- \param outData The output buffer where to store the decrypted message.
- \param inLength The length of the input buffer.
- \param inFlipHeaderBits True for Korg and other who store MSB in reverse order
- \return The length of the output buffer.
- @see encodeSysEx @see getSysExArrayLength
- Code inspired from Ruin & Wesen's SysEx encoder/decoder - http://ruinwesen.com
- */
- unsigned decodeSysEx(const uint8_t* inSysEx,
- uint8_t* outData,
- unsigned inLength,
- bool inFlipHeaderBits) {
- unsigned count = 0;
- uint8_t msbStorage = 0;
- uint8_t byteIndex = 0;
-
- for (unsigned i = 0; i < inLength; ++i) {
- if ((i % 8) == 0) {
- msbStorage = inSysEx[i];
- byteIndex = 6;
- }
- else {
- const uint8_t body = inSysEx[i];
- const uint8_t shift = inFlipHeaderBits ? 6 - byteIndex : byteIndex;
- const uint8_t msb = uint8_t(((msbStorage >> shift) & 1) << 7);
- byteIndex--;
- outData[count++] = msb | body;
- }
- }
- return count;
- }
-
- struct RoundRobinProcessor {
- // if a channel (0 - 11) should be updated, return it's index, otherwise return -1
- int process(float sampleTime, float period, int numActiveChannels) {
-
- if (numActiveChannels == 0 || period <= 0) {
- return -1;
- }
-
- time += sampleTime;
-
- if (time > period) {
- time -= period;
-
- // special case: when there's only one channel, the below logic (which looks for when active channel changes)
- // wont fire. as we've completed a period, return an "update channel 0" value
- if (numActiveChannels == 1) {
- return 0;
- }
- }
-
- int currentActiveChannel = numActiveChannels * time / period;
-
- if (currentActiveChannel != previousActiveChannel) {
- previousActiveChannel = currentActiveChannel;
- return currentActiveChannel;
- }
-
- // if we've got this far, no updates needed (-1)
- return -1;
- }
- private:
- float time = 0.f;
- int previousActiveChannel = -1;
- };
-
-
- struct MidiThing : Module {
- enum ParamId {
- REFRESH_PARAM,
- PARAMS_LEN
- };
- enum InputId {
- A1_INPUT,
- B1_INPUT,
- C1_INPUT,
- A2_INPUT,
- B2_INPUT,
- C2_INPUT,
- A3_INPUT,
- B3_INPUT,
- C3_INPUT,
- A4_INPUT,
- B4_INPUT,
- C4_INPUT,
- INPUTS_LEN
- };
- enum OutputId {
- OUTPUTS_LEN
- };
- enum LightId {
- LIGHTS_LEN
- };
- /// Port mode
- enum PORTMODE_t {
- NOPORTMODE = 0,
- MODE10V,
- MODEPN5V,
- MODENEG10V,
- MODE8V,
- MODE5V,
-
- LASTPORTMODE
- };
-
- const char* cfgPortModeNames[7] = {
- "No Mode",
- "0/10v",
- "-5/5v",
- "-10/0v",
- "0/8v",
- "0/5v",
- ""
- };
-
- const std::vector<float> updateRates = {250., 500., 1000., 2000., 4000., 8000.};
- const std::vector<std::string> updateRateNames = {"250 Hz (fewest active channels, slowest, lowest-cpu)", "500 Hz", "1 kHz", "2 kHz", "4 kHz",
- "8 kHz (most active channels, fast, highest-cpu)"
- };
- int updateRateIdx = 2;
-
- // use Pre-def 4 for bridge mode
- const static int VCV_BRIDGE_PREDEF = 4;
-
- midi::Output midiOut;
- RoundRobinProcessor roundRobinProcessor;
-
- MidiThing() {
- config(PARAMS_LEN, INPUTS_LEN, OUTPUTS_LEN, LIGHTS_LEN);
- configButton(REFRESH_PARAM, "");
-
- for (int i = 0; i < NUM_INPUTS; ++i) {
- portModes[i] = MODE10V;
- configInput(A1_INPUT + i, string::f("Port %d", i + 1));
- }
- }
-
- void onReset() override {
- midiOut.reset();
-
- }
-
- void requestAllChannelsParamsOverSysex() {
- for (int row = 0; row < 4; ++row) {
- for (int col = 0; col < 3; ++col) {
- const int PORT_CONFIG = 2;
- requestParamOverSysex(row, col, PORT_CONFIG);
- }
- }
- }
-
- // request that MidiThing loads a pre-defined template, 1-4
- void setPredef(uint8_t predef) {
- predef = clamp(predef, 1, 4);
- midi::Message msg;
- msg.bytes.resize(8);
- // Midi spec is zeroo indexed
- uint8_t predefToSend = predef - 1;
- msg.bytes = {0xF0, 0x7D, 0x17, 0x00, 0x00, 0x02, 0x00, predefToSend, 0xF7};
- midiOut.setChannel(0);
- midiOut.sendMessage(msg);
- // DEBUG("Predef %d msg request sent: %s", predef, msg.toString().c_str());
- }
-
- void setMidiMergeViaSysEx(bool mergeOn) {
- midi::Message msg;
- msg.bytes.resize(8);
-
- msg.bytes = {0xF0, 0x7D, 0x19, 0x00, 0x05, 0x02, 0x00, (uint8_t) mergeOn, 0xF7};
- midiOut.setChannel(0);
- midiOut.sendMessage(msg);
- // DEBUG("Predef %d msg request sent: %s", mergeOn, msg.toString().c_str());
- }
-
-
- void setVoltageModeOnHardware(uint8_t row, uint8_t col, PORTMODE_t outputMode_) {
- uint8_t port = 3 * row + col;
- portModes[port] = outputMode_;
-
- midi::Message msg;
- msg.bytes.resize(8);
- // F0 7D 17 2n 02 02 00 0m F7
- // Where n = 0 based port number
- // and m is the volt output mode to select from:
- msg.bytes = {0xF0, 0x7D, 0x17, static_cast<unsigned char>(32 + port), 0x02, 0x02, 0x00, (uint8_t) portModes[port], 0xF7};
- midiOut.sendMessage(msg);
- // DEBUG("Voltage mode msg sent: port %d (%d), mode %d", port, static_cast<unsigned char>(32 + port), portModes[port]);
- }
-
- void setVoltageModeOnHardware(uint8_t row, uint8_t col) {
- setVoltageModeOnHardware(row, col, portModes[3 * row + col]);
- }
-
- void syncVcvStateToHardware() {
- for (int row = 0; row < 4; ++row) {
- for (int col = 0; col < 3; ++col) {
- setVoltageModeOnHardware(row, col);
- }
- }
- }
-
-
- midi::InputQueue inputQueue;
- void requestParamOverSysex(uint8_t row, uint8_t col, uint8_t mode) {
-
- midi::Message msg;
- msg.bytes.resize(8);
- // F0 7D 17 00 01 03 00 nm pp F7
- uint8_t port = 3 * row + col;
- //Where n is:
- // 0 = Full configuration request. The module will send only pre def, port functions and modified parameters
- // 2 = Send Port configuration
- // 4 = Send MIDI Channel configuration
- // 6 = Send Voice Configuration
-
- uint8_t n = mode * 16;
- uint8_t m = port; // element number: 0-11 port number, 1-16 channel or voice number
- uint8_t pp = 2;
- msg.bytes = {0xF0, 0x7D, 0x17, 0x00, 0x01, 0x03, 0x00, static_cast<uint8_t>(n + m), pp, 0xF7};
- midiOut.sendMessage(msg);
- // DEBUG("API request mode msg sent: port %d, pp %s", port, msg.toString().c_str());
- }
-
- int getVoltageMode(uint8_t row, uint8_t col) {
- // -1 because menu is zero indexed but enum is not
- int channel = clamp(3 * row + col, 0, NUM_INPUTS - 1);
- return portModes[channel] - 1;
- }
-
- const static int NUM_INPUTS = 12;
- bool isClipping[NUM_INPUTS] = {};
-
- bool checkIsVoltageWithinRange(uint8_t channel, float voltage) {
- const float tol = 0.001;
- switch (portModes[channel]) {
- case MODE10V: return 0 - tol < voltage && voltage < 10 + tol;
- case MODEPN5V: return -5 - tol < voltage && voltage < 5 + tol;
- case MODENEG10V: return -10 - tol < voltage && voltage < 0 + tol;
- case MODE8V: return 0 - tol < voltage && voltage < 8 + tol;
- case MODE5V: return 0 - tol < voltage && voltage < 5 + tol;
- default: return false;
- }
- }
-
- uint16_t rescaleVoltageForChannel(uint8_t channel, float voltage) {
- switch (portModes[channel]) {
- case MODE10V: return rescale(clamp(voltage, 0.f, 10.f), 0.f, +10.f, 0, 16383);
- case MODEPN5V: return rescale(clamp(voltage, -5.f, 5.f), -5.f, +5.f, 0, 16383);
- case MODENEG10V: return rescale(clamp(voltage, -10.f, 0.f), -10.f, +0.f, 0, 16383);
- case MODE8V: return rescale(clamp(voltage, 0.f, 8.f), 0.f, +8.f, 0, 16383);
- case MODE5V: return rescale(clamp(voltage, 0.f, 5.f), 0.f, +5.f, 0, 16383);
- default: return 0;
- }
- }
-
- // one way sync (VCV -> hardware) for now
- void doSync() {
- // switch to VCV template (predef 4)
- setPredef(4);
-
- // disable MIDI merge (otherwise large sample rates will not work)
- setMidiMergeViaSysEx(false);
-
- // send full VCV config
- syncVcvStateToHardware();
-
- // disabled for now, but this would request what state the hardware is in
- if (parseSysExMessagesFromHardware) {
- requestAllChannelsParamsOverSysex();
- }
- }
-
- // debug only
- bool parseSysExMessagesFromHardware = false;
- int numActiveChannels = 0;
- dsp::BooleanTrigger buttonTrigger;
- dsp::Timer rateLimiterTimer;
- PORTMODE_t portModes[NUM_INPUTS] = {};
- void process(const ProcessArgs& args) override {
-
- if (buttonTrigger.process(params[REFRESH_PARAM].getValue())) {
- doSync();
- }
-
- // disabled for now, but this is how VCV would read SysEx coming from the hardware (if requested above)
- if (parseSysExMessagesFromHardware) {
- midi::Message msg;
- uint8_t outData[32] = {};
- while (inputQueue.tryPop(&msg, args.frame)) {
-
- uint8_t outLen = decodeSysEx(&msg.bytes[0], outData, msg.bytes.size(), false);
- if (outLen > 3) {
-
- int channel = (outData[2] & 0x0f) >> 0;
-
- if (channel >= 0 && channel < NUM_INPUTS) {
- if (outData[outLen - 1] < LASTPORTMODE) {
- portModes[channel] = (PORTMODE_t) outData[outLen - 1];
- }
- }
- }
- }
- }
-
- std::vector<int> activeChannels;
- for (int c = 0; c < NUM_INPUTS; ++c) {
- if (inputs[A1_INPUT + c].isConnected()) {
- activeChannels.push_back(c);
- }
- }
- numActiveChannels = activeChannels.size();
- // we're done if no channels are active
- if (numActiveChannels == 0) {
- return;
- }
-
- //DEBUG("updateRateIdx: %d", updateRateIdx);
- const float updateRateHz = updateRates[updateRateIdx];
- //DEBUG("updateRateHz: %f", updateRateHz);
- const int maxCCMessagesPerSecondPerChannel = updateRateHz / numActiveChannels;
-
- // MIDI baud rate is 31250 b/s, or 3125 B/s.
- // CC messages are 3 bytes, so we can send a maximum of 1041 CC messages per second.
- // The refresh rate period (i.e. how often we can send X channels of data is:
- const float rateLimiterPeriod = 1.f / maxCCMessagesPerSecondPerChannel;
-
- // this returns -1 if no channel should be updated, or the index of the channel that should be updated
- // it distributes update times in a round robin fashion
- int channelIdxToUpdate = roundRobinProcessor.process(args.sampleTime, rateLimiterPeriod, numActiveChannels);
-
- if (channelIdxToUpdate >= 0 && channelIdxToUpdate < numActiveChannels) {
- int c = activeChannels[channelIdxToUpdate];
-
- const float channelVoltage = inputs[A1_INPUT + c].getVoltage();
- uint16_t pw = rescaleVoltageForChannel(c, channelVoltage);
- isClipping[c] = !checkIsVoltageWithinRange(c, channelVoltage);
- midi::Message m;
- m.setStatus(0xe);
- m.setNote(pw & 0x7f);
- m.setValue((pw >> 7) & 0x7f);
- m.setFrame(args.frame);
-
- midiOut.setChannel(c);
- midiOut.sendMessage(m);
- }
- }
-
-
- json_t* dataToJson() override {
- json_t* rootJ = json_object();
- json_object_set_new(rootJ, "midiOutput", midiOut.toJson());
- json_object_set_new(rootJ, "inputQueue", inputQueue.toJson());
- json_object_set_new(rootJ, "updateRateIdx", json_integer(updateRateIdx));
-
- for (int c = 0; c < NUM_INPUTS; ++c) {
- json_object_set_new(rootJ, string::f("portMode%d", c).c_str(), json_integer(portModes[c]));
- }
-
- return rootJ;
- }
-
- void dataFromJson(json_t* rootJ) override {
- json_t* midiOutputJ = json_object_get(rootJ, "midiOutput");
- if (midiOutputJ) {
- midiOut.fromJson(midiOutputJ);
- }
-
- json_t* midiInputQueueJ = json_object_get(rootJ, "inputQueue");
- if (midiInputQueueJ) {
- inputQueue.fromJson(midiInputQueueJ);
- }
-
- json_t* updateRateIdxJ = json_object_get(rootJ, "updateRateIdx");
- if (updateRateIdxJ) {
- updateRateIdx = json_integer_value(updateRateIdxJ);
- }
-
- for (int c = 0; c < NUM_INPUTS; ++c) {
- json_t* portModeJ = json_object_get(rootJ, string::f("portMode%d", c).c_str());
- if (portModeJ) {
- portModes[c] = (PORTMODE_t)json_integer_value(portModeJ);
- }
- }
-
- // requestAllChannelsParamsOverSysex();
- syncVcvStateToHardware();
- }
- };
-
- struct MidiThingPort : BefacoInputPort {
- int row = 0, col = 0;
- MidiThing* module;
-
- void appendContextMenu(Menu* menu) override {
-
- menu->addChild(new MenuSeparator());
- std::string label = string::f("Voltage Mode Port %d", 3 * row + col + 1);
-
- menu->addChild(createIndexSubmenuItem(label,
- {"0 to 10v", "-5 to 5v", "-10 to 0v", "0 to 8v", "0 to 5v"},
- [ = ]() {
- return module->getVoltageMode(row, col);
- },
- [ = ](int modeIdx) {
- MidiThing::PORTMODE_t mode = (MidiThing::PORTMODE_t)(modeIdx + 1);
- module->setVoltageModeOnHardware(row, col, mode);
- }
- ));
-
- /*
- menu->addChild(createIndexSubmenuItem("Get Port Info",
- {"Full", "Port", "MIDI", "Voice"},
- [ = ]() {
- return -1;
- },
- [ = ](int mode) {
- module->requestParamOverSysex(row, col, 2 * mode);
- }
- ));
- */
- }
- };
-
- // dervied from https://github.com/countmodula/VCVRackPlugins/blob/v2.0.0/src/components/CountModulaLEDDisplay.hpp
- struct LEDDisplay : LightWidget {
- float fontSize = 9;
- Vec textPos = Vec(1, 13);
- int numChars = 7;
- int row = 0, col = 0;
- MidiThing* module;
-
- LEDDisplay() {
- box.size = mm2px(Vec(9.298, 5.116));
- }
-
- void setCentredPos(Vec pos) {
- box.pos.x = pos.x - box.size.x / 2;
- box.pos.y = pos.y - box.size.y / 2;
- }
-
- void drawBackground(const DrawArgs& args) override {
- // Background
- NVGcolor backgroundColor = nvgRGB(0x20, 0x20, 0x20);
- NVGcolor borderColor = nvgRGB(0x10, 0x10, 0x10);
- nvgBeginPath(args.vg);
- nvgRoundedRect(args.vg, 0.0, 0.0, box.size.x, box.size.y, 2.0);
- nvgFillColor(args.vg, backgroundColor);
- nvgFill(args.vg);
- nvgStrokeWidth(args.vg, 1.0);
- nvgStrokeColor(args.vg, borderColor);
- nvgStroke(args.vg);
- }
-
- void drawLight(const DrawArgs& args) override {
- // Background
- NVGcolor backgroundColor = nvgRGB(0x20, 0x20, 0x20);
- NVGcolor borderColor = nvgRGB(0x10, 0x10, 0x10);
- NVGcolor textColor = nvgRGB(0xff, 0x10, 0x10);
-
- nvgBeginPath(args.vg);
- nvgRoundedRect(args.vg, 0.0, 0.0, box.size.x, box.size.y, 2.0);
- nvgFillColor(args.vg, backgroundColor);
- nvgFill(args.vg);
- nvgStrokeWidth(args.vg, 1.0);
-
- if (module) {
- const bool isClipping = module->isClipping[col + row * 3];
- if (isClipping) {
- borderColor = nvgRGB(0xff, 0x20, 0x20);
- }
- }
-
- nvgStrokeColor(args.vg, borderColor);
- nvgStroke(args.vg);
-
- std::shared_ptr<Font> font = APP->window->loadFont(asset::plugin(pluginInstance, "res/fonts/miso.otf"));
-
- if (font && font->handle >= 0) {
-
- std::string text = "?-?v"; // fallback if module not yet defined
- if (module) {
- text = module->cfgPortModeNames[module->getVoltageMode(row, col) + 1];
- }
- char buffer[numChars + 1];
- int l = text.size();
- if (l > numChars)
- l = numChars;
-
- nvgGlobalTint(args.vg, color::WHITE);
-
- text.copy(buffer, l);
- buffer[l] = '\0';
-
- nvgFontSize(args.vg, fontSize);
- nvgFontFaceId(args.vg, font->handle);
- nvgFillColor(args.vg, textColor);
- nvgTextAlign(args.vg, NVG_ALIGN_CENTER | NVG_ALIGN_BOTTOM);
- NVGtextRow textRow;
- nvgTextBreakLines(args.vg, text.c_str(), NULL, box.size.x, &textRow, 1);
- nvgTextBox(args.vg, textPos.x, textPos.y, box.size.x, textRow.start, textRow.end);
- }
- }
-
- void onButton(const ButtonEvent& e) override {
- if (e.button == GLFW_MOUSE_BUTTON_RIGHT && e.action == GLFW_PRESS) {
- ui::Menu* menu = createMenu();
-
- menu->addChild(createMenuLabel(string::f("Voltage mode port %d:", col + 3 * row + 1)));
-
- const std::string labels[5] = {"0 to 10v", "-5 to 5v", "-10 to 0v", "0 to 8v", "0 to 5v"};
-
- for (int i = 0; i < 5; ++i) {
- menu->addChild(createCheckMenuItem(labels[i], "",
- [ = ]() {
- return module->getVoltageMode(row, col) == i;
- },
- [ = ]() {
- MidiThing::PORTMODE_t mode = (MidiThing::PORTMODE_t)(i + 1);
- module->setVoltageModeOnHardware(row, col, mode);
- }
- ));
- }
-
- e.consume(this);
- return;
- }
-
- LightWidget::onButton(e);
- }
-
- };
-
-
- struct MidiThingWidget : ModuleWidget {
-
- struct LedDisplayCenterChoiceEx : LedDisplayChoice {
- LedDisplayCenterChoiceEx() {
- box.size = mm2px(math::Vec(0, 8.0));
- color = nvgRGB(0xf0, 0xf0, 0xf0);
- bgColor = nvgRGBAf(0, 0, 0, 0);
- textOffset = math::Vec(0, 16);
- }
-
- void drawLayer(const DrawArgs& args, int layer) override {
- nvgScissor(args.vg, RECT_ARGS(args.clipBox));
- if (layer == 1) {
- if (bgColor.a > 0.0) {
- nvgBeginPath(args.vg);
- nvgRect(args.vg, 0, 0, box.size.x, box.size.y);
- nvgFillColor(args.vg, bgColor);
- nvgFill(args.vg);
- }
-
- std::shared_ptr<window::Font> font = APP->window->loadFont(asset::plugin(pluginInstance, "res/fonts/miso.otf"));
-
- if (font && font->handle >= 0 && !text.empty()) {
- nvgFillColor(args.vg, color);
- nvgFontFaceId(args.vg, font->handle);
- nvgTextLetterSpacing(args.vg, -0.6f);
- nvgFontSize(args.vg, 10);
- nvgTextAlign(args.vg, NVG_ALIGN_CENTER | NVG_ALIGN_BOTTOM);
- NVGtextRow textRow;
- nvgTextBreakLines(args.vg, text.c_str(), NULL, box.size.x, &textRow, 1);
- nvgTextBox(args.vg, textOffset.x, textOffset.y, box.size.x, textRow.start, textRow.end);
- }
- }
- nvgResetScissor(args.vg);
- }
- };
-
-
- struct MidiDriverItem : ui::MenuItem {
- midi::Port* port;
- int driverId;
- void onAction(const event::Action& e) override {
- port->setDriverId(driverId);
- }
- };
-
- struct MidiDriverChoice : LedDisplayCenterChoiceEx {
- midi::Port* port;
- void onAction(const event::Action& e) override {
- if (!port)
- return;
- createContextMenu();
- }
-
- virtual ui::Menu* createContextMenu() {
- ui::Menu* menu = createMenu();
- menu->addChild(createMenuLabel("MIDI driver"));
- for (int driverId : midi::getDriverIds()) {
- MidiDriverItem* item = new MidiDriverItem;
- item->port = port;
- item->driverId = driverId;
- item->text = midi::getDriver(driverId)->getName();
- item->rightText = CHECKMARK(item->driverId == port->driverId);
- menu->addChild(item);
- }
- return menu;
- }
-
- void step() override {
- text = port ? port->getDriver()->getName() : "";
- if (text.empty()) {
- text = "(No driver)";
- color.a = 0.5f;
- }
- else {
- color.a = 1.f;
- }
- }
- };
-
- struct MidiDeviceItem : ui::MenuItem {
- midi::Port* outPort, *inPort;
- int deviceId;
- void onAction(const event::Action& e) override {
- outPort->setDeviceId(deviceId);
- inPort->setDeviceId(deviceId);
- }
- };
-
- struct MidiDeviceChoice : LedDisplayCenterChoiceEx {
- midi::Port* outPort, *inPort;
- void onAction(const event::Action& e) override {
- if (!outPort || !inPort)
- return;
- createContextMenu();
- }
-
- virtual ui::Menu* createContextMenu() {
- ui::Menu* menu = createMenu();
- menu->addChild(createMenuLabel("MIDI device"));
- {
- MidiDeviceItem* item = new MidiDeviceItem;
- item->outPort = outPort;
- item->inPort = inPort;
- item->deviceId = -1;
- item->text = "(No device)";
- item->rightText = CHECKMARK(item->deviceId == outPort->deviceId);
- menu->addChild(item);
- }
- for (int deviceId : outPort->getDeviceIds()) {
- MidiDeviceItem* item = new MidiDeviceItem;
- item->outPort = outPort;
- item->inPort = inPort;
- item->deviceId = deviceId;
- item->text = outPort->getDeviceName(deviceId);
- item->rightText = CHECKMARK(item->deviceId == outPort->deviceId);
- menu->addChild(item);
- }
- return menu;
- }
-
- void step() override {
- text = outPort ? outPort->getDeviceName(outPort->deviceId) : "";
- if (text.empty()) {
- text = "(No device)";
- color.a = 0.5f;
- }
- else {
- color.a = 1.f;
- }
- }
- };
-
- struct MidiWidget : LedDisplay {
- MidiDriverChoice* driverChoice;
- LedDisplaySeparator* driverSeparator;
- MidiDeviceChoice* deviceChoice;
- LedDisplaySeparator* deviceSeparator;
-
- void setMidiPorts(midi::Port* outPort, midi::Port* inPort) {
-
- clearChildren();
- math::Vec pos;
-
- MidiDriverChoice* driverChoice = createWidget<MidiDriverChoice>(pos);
- driverChoice->box.size = Vec(box.size.x, 20.f);
- //driverChoice->textOffset = Vec(6.f, 14.7f);
- driverChoice->color = nvgRGB(0xf0, 0xf0, 0xf0);
- driverChoice->port = outPort;
-
- addChild(driverChoice);
- pos = driverChoice->box.getBottomLeft();
- this->driverChoice = driverChoice;
-
- this->driverSeparator = createWidget<LedDisplaySeparator>(pos);
- this->driverSeparator->box.size.x = box.size.x;
- addChild(this->driverSeparator);
-
- MidiDeviceChoice* deviceChoice = createWidget<MidiDeviceChoice>(pos);
- deviceChoice->box.size = Vec(box.size.x, 21.f);
- //deviceChoice->textOffset = Vec(6.f, 14.7f);
- deviceChoice->color = nvgRGB(0xf0, 0xf0, 0xf0);
- deviceChoice->outPort = outPort;
- deviceChoice->inPort = inPort;
- addChild(deviceChoice);
- pos = deviceChoice->box.getBottomLeft();
- this->deviceChoice = deviceChoice;
- }
- };
-
-
- MidiThingWidget(MidiThing* module) {
- setModule(module);
- setPanel(createPanel(asset::plugin(pluginInstance, "res/panels/MidiThing.svg")));
-
- addChild(createWidget<Knurlie>(Vec(RACK_GRID_WIDTH, 0)));
- addChild(createWidget<Knurlie>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
-
- MidiWidget* midiInputWidget = createWidget<MidiWidget>(Vec(1.5f, 36.4f)); //mm2px(Vec(0.5f, 10.f)));
- midiInputWidget->box.size = mm2px(Vec(5.08 * 6 - 1, 13.5f));
- if (module) {
- midiInputWidget->setMidiPorts(&module->midiOut, &module->inputQueue);
- }
- else {
- midiInputWidget->setMidiPorts(nullptr, nullptr);
- }
- addChild(midiInputWidget);
-
- addParam(createParamCentered<BefacoButton>(mm2px(Vec(21.12, 57.32)), module, MidiThing::REFRESH_PARAM));
-
- const float xStartLed = 0.2 + 0.628;
- const float yStartLed = 28.019;
-
- for (int row = 0; row < 4; row++) {
- for (int col = 0; col < 3; col++) {
-
- LEDDisplay* display = createWidget<LEDDisplay>(mm2px(Vec(xStartLed + 9.751 * col, yStartLed + 5.796 * row)));
- display->module = module;
- display->row = row;
- display->col = col;
- addChild(display);
-
- auto input = createInputCentered<MidiThingPort>(mm2px(Vec(5.08 + 10 * col, 69.77 + 14.225 * row)), module, MidiThing::A1_INPUT + 3 * row + col);
- input->row = row;
- input->col = col;
- input->module = module;
- addInput(input);
-
-
- }
- }
- }
-
- void appendContextMenu(Menu* menu) override {
- MidiThing* module = dynamic_cast<MidiThing*>(this->module);
- assert(module);
-
- menu->addChild(new MenuSeparator());
-
- menu->addChild(createSubmenuItem("Select MIDI Device", "",
- [ = ](Menu * menu) {
-
- for (auto driverId : rack::midi::getDriverIds()) {
- midi::Driver* driver = midi::getDriver(driverId);
- const bool activeDriver = module->midiOut.getDriverId() == driverId;
-
- menu->addChild(createSubmenuItem(driver->getName(), CHECKMARK(activeDriver),
- [ = ](Menu * menu) {
-
- for (auto deviceId : driver->getOutputDeviceIds()) {
- const bool activeDevice = activeDriver && module->midiOut.getDeviceId() == deviceId;
-
- menu->addChild(createMenuItem(driver->getOutputDeviceName(deviceId),
- CHECKMARK(activeDevice),
- [ = ]() {
- module->midiOut.setDriverId(driverId);
- module->midiOut.setDeviceId(deviceId);
-
- module->inputQueue.setDriverId(driverId);
- module->inputQueue.setDeviceId(deviceId);
- module->inputQueue.setChannel(0); // TODO update
-
- module->doSync();
-
- // DEBUG("Updating Output MIDI settings - driver: %s, device: %s",
- // driver->getName().c_str(), driver->getOutputDeviceName(deviceId).c_str());
- }));
- }
- }));
- }
- }));
-
- menu->addChild(createIndexPtrSubmenuItem("All channels MIDI update rate",
- module->updateRateNames,
- &module->updateRateIdx));
-
- float updateRate = module->updateRates[module->updateRateIdx] / module->numActiveChannels;
- menu->addChild(createMenuLabel(string::f("Per-channel MIDI update rate: %.3g Hz", updateRate)));
- }
- };
-
-
- Model* modelMidiThing = createModel<MidiThing, MidiThingWidget>("MidiThingV2");
|