| 
				
				
				
				 | 
			
			 | 
			@@ -0,0 +1,804 @@ | 
		
		
	
		
			
			 | 
			 | 
			
			 | 
			#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"); |