|  |  | @@ -20,6 +20,16 @@ | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | // -------------------------------------------------------------------------------------------------------------------- | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | /** | 
		
	
		
			
			|  |  |  | * This class contains a substantial amount of code from VCVRack's dsp/midi.hpp | 
		
	
		
			
			|  |  |  | * Copyright (C) 2016-2021 VCV. | 
		
	
		
			
			|  |  |  | * | 
		
	
		
			
			|  |  |  | * This program is free software: you can redistribute it and/or | 
		
	
		
			
			|  |  |  | * modify it under the terms of the GNU General Public License as | 
		
	
		
			
			|  |  |  | * published by the Free Software Foundation; either version 3 of | 
		
	
		
			
			|  |  |  | * the License, or (at your option) any later version. | 
		
	
		
			
			|  |  |  | */ | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | struct CardinalExpanderForInputMIDI : CardinalExpanderFromCVToCarlaMIDI { | 
		
	
		
			
			|  |  |  | enum ParamIds { | 
		
	
		
			
			|  |  |  | NUM_PARAMS | 
		
	
	
		
			
				|  |  | @@ -27,10 +37,10 @@ struct CardinalExpanderForInputMIDI : CardinalExpanderFromCVToCarlaMIDI { | 
		
	
		
			
			|  |  |  | enum InputIds { | 
		
	
		
			
			|  |  |  | PITCH_INPUT, | 
		
	
		
			
			|  |  |  | GATE_INPUT, | 
		
	
		
			
			|  |  |  | VEL_INPUT, | 
		
	
		
			
			|  |  |  | AFT_INPUT, | 
		
	
		
			
			|  |  |  | PW_INPUT, | 
		
	
		
			
			|  |  |  | MW_INPUT, | 
		
	
		
			
			|  |  |  | VELOCITY_INPUT, | 
		
	
		
			
			|  |  |  | AFTERTOUCH_INPUT, | 
		
	
		
			
			|  |  |  | PITCHBEND_INPUT, | 
		
	
		
			
			|  |  |  | MODWHEEL_INPUT, | 
		
	
		
			
			|  |  |  | NUM_INPUTS | 
		
	
		
			
			|  |  |  | }; | 
		
	
		
			
			|  |  |  | enum OutputIds { | 
		
	
	
		
			
				|  |  | @@ -45,112 +55,170 @@ struct CardinalExpanderForInputMIDI : CardinalExpanderFromCVToCarlaMIDI { | 
		
	
		
			
			|  |  |  | int8_t notes[CHANNELS]; | 
		
	
		
			
			|  |  |  | bool gates[CHANNELS]; | 
		
	
		
			
			|  |  |  | int8_t keyPressures[CHANNELS]; | 
		
	
		
			
			|  |  |  | int8_t mw; | 
		
	
		
			
			|  |  |  | int16_t pw; | 
		
	
		
			
			|  |  |  | int8_t modwheel; | 
		
	
		
			
			|  |  |  | int16_t pitchbend; | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | uint8_t channel = 0; | 
		
	
		
			
			|  |  |  | Module* lastConnectedModule = nullptr; | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | CardinalExpanderForInputMIDI() { | 
		
	
		
			
			|  |  |  | CardinalExpanderForInputMIDI() | 
		
	
		
			
			|  |  |  | { | 
		
	
		
			
			|  |  |  | static_assert(NUM_INPUTS == kNumInputs, "Invalid input configuration"); | 
		
	
		
			
			|  |  |  | config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); | 
		
	
		
			
			|  |  |  | configInput(PITCH_INPUT, "1V/octave pitch"); | 
		
	
		
			
			|  |  |  | configInput(GATE_INPUT, "Gate"); | 
		
	
		
			
			|  |  |  | configInput(VEL_INPUT, "Velocity"); | 
		
	
		
			
			|  |  |  | configInput(AFT_INPUT, "Aftertouch"); | 
		
	
		
			
			|  |  |  | configInput(PW_INPUT, "Pitchbend"); | 
		
	
		
			
			|  |  |  | configInput(MW_INPUT, "Mod wheel"); | 
		
	
		
			
			|  |  |  | configInput(VELOCITY_INPUT, "Velocity"); | 
		
	
		
			
			|  |  |  | configInput(AFTERTOUCH_INPUT, "Aftertouch"); | 
		
	
		
			
			|  |  |  | configInput(PITCHBEND_INPUT, "Pitchbend"); | 
		
	
		
			
			|  |  |  | configInput(MODWHEEL_INPUT, "Mod wheel"); | 
		
	
		
			
			|  |  |  | onReset(); | 
		
	
		
			
			|  |  |  | } | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | /** Must be called before setNoteGate(). */ | 
		
	
		
			
			|  |  |  | void setVelocity(int8_t vel, int c) { | 
		
	
		
			
			|  |  |  | void setVelocity(int8_t vel, int c) | 
		
	
		
			
			|  |  |  | { | 
		
	
		
			
			|  |  |  | vels[c] = vel; | 
		
	
		
			
			|  |  |  | } | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | void setNoteGate(int8_t note, bool gate, int c) { | 
		
	
		
			
			|  |  |  | if (midiEventCount == MAX_MIDI_EVENTS) | 
		
	
		
			
			|  |  |  | void setNoteGate(int8_t note, bool gate, int c) | 
		
	
		
			
			|  |  |  | { | 
		
	
		
			
			|  |  |  | if (midiEventCount == MAX_MIDI_EVENTS || frame == UINT_MAX) | 
		
	
		
			
			|  |  |  | return; | 
		
	
		
			
			|  |  |  | bool changedNote = gate && gates[c] && (note != notes[c]); | 
		
	
		
			
			|  |  |  | bool enabledGate = gate && !gates[c]; | 
		
	
		
			
			|  |  |  | bool disabledGate = !gate && gates[c]; | 
		
	
		
			
			|  |  |  | if (changedNote || disabledGate) { | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | const bool changedNote = gate && gates[c] && (note != notes[c]); | 
		
	
		
			
			|  |  |  | const bool enabledGate = gate && !gates[c]; | 
		
	
		
			
			|  |  |  | const bool disabledGate = !gate && gates[c]; | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | if (changedNote || disabledGate) | 
		
	
		
			
			|  |  |  | { | 
		
	
		
			
			|  |  |  | // Note off | 
		
	
		
			
			|  |  |  | NativeMidiEvent& m(midiEvents[midiEventCount++]); | 
		
	
		
			
			|  |  |  | m.time = frame; | 
		
	
		
			
			|  |  |  | m.port = 0; | 
		
	
		
			
			|  |  |  | m.size = 3; | 
		
	
		
			
			|  |  |  | m.data[0] = 0x80; | 
		
	
		
			
			|  |  |  | m.data[0] = 0x80 | channel; | 
		
	
		
			
			|  |  |  | m.data[1] = notes[c]; | 
		
	
		
			
			|  |  |  | m.data[2] = vels[c]; | 
		
	
		
			
			|  |  |  | } | 
		
	
		
			
			|  |  |  | if (changedNote || enabledGate) { | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | if (changedNote || enabledGate) | 
		
	
		
			
			|  |  |  | { | 
		
	
		
			
			|  |  |  | // Note on | 
		
	
		
			
			|  |  |  | NativeMidiEvent& m(midiEvents[midiEventCount++]); | 
		
	
		
			
			|  |  |  | m.time = frame; | 
		
	
		
			
			|  |  |  | m.port = 0; | 
		
	
		
			
			|  |  |  | m.size = 3; | 
		
	
		
			
			|  |  |  | m.data[0] = 0x90; | 
		
	
		
			
			|  |  |  | m.data[0] = 0x90 | channel; | 
		
	
		
			
			|  |  |  | m.data[1] = note; | 
		
	
		
			
			|  |  |  | m.data[2] = vels[c]; | 
		
	
		
			
			|  |  |  | } | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | notes[c] = note; | 
		
	
		
			
			|  |  |  | gates[c] = gate; | 
		
	
		
			
			|  |  |  | } | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | void setKeyPressure(int8_t val, int c) { | 
		
	
		
			
			|  |  |  | if (keyPressures[c] == val || midiEventCount == MAX_MIDI_EVENTS) | 
		
	
		
			
			|  |  |  | void setKeyPressure(int8_t val, int c) | 
		
	
		
			
			|  |  |  | { | 
		
	
		
			
			|  |  |  | if (keyPressures[c] == val) | 
		
	
		
			
			|  |  |  | return; | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | keyPressures[c] = val; | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | if (midiEventCount == MAX_MIDI_EVENTS || frame == UINT_MAX) | 
		
	
		
			
			|  |  |  | return; | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | // Polyphonic key pressure | 
		
	
		
			
			|  |  |  | NativeMidiEvent& m(midiEvents[midiEventCount++]); | 
		
	
		
			
			|  |  |  | m.time = frame; | 
		
	
		
			
			|  |  |  | m.port = 0; | 
		
	
		
			
			|  |  |  | m.size = 3; | 
		
	
		
			
			|  |  |  | m.data[0] = 0xa0; | 
		
	
		
			
			|  |  |  | m.data[0] = 0xa0 | channel; | 
		
	
		
			
			|  |  |  | m.data[1] = notes[c]; | 
		
	
		
			
			|  |  |  | m.data[2] = val; | 
		
	
		
			
			|  |  |  | } | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | void setModWheel(int8_t mw) { | 
		
	
		
			
			|  |  |  | if (this->mw == mw || midiEventCount == MAX_MIDI_EVENTS) | 
		
	
		
			
			|  |  |  | void setModWheel(int8_t modwheel) | 
		
	
		
			
			|  |  |  | { | 
		
	
		
			
			|  |  |  | if (this->modwheel == modwheel) | 
		
	
		
			
			|  |  |  | return; | 
		
	
		
			
			|  |  |  | this->mw = mw; | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | this->modwheel = modwheel; | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | if (midiEventCount == MAX_MIDI_EVENTS || frame == UINT_MAX) | 
		
	
		
			
			|  |  |  | return; | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | // Modulation Wheel (CC1) | 
		
	
		
			
			|  |  |  | NativeMidiEvent& m(midiEvents[midiEventCount++]); | 
		
	
		
			
			|  |  |  | m.time = frame; | 
		
	
		
			
			|  |  |  | m.port = 0; | 
		
	
		
			
			|  |  |  | m.size = 3; | 
		
	
		
			
			|  |  |  | m.data[0] = 0xb0; | 
		
	
		
			
			|  |  |  | m.data[0] = 0xb0 | channel; | 
		
	
		
			
			|  |  |  | m.data[1] = 1; | 
		
	
		
			
			|  |  |  | m.data[2] = mw; | 
		
	
		
			
			|  |  |  | m.data[2] = modwheel; | 
		
	
		
			
			|  |  |  | } | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | void setPitchWheel(int16_t pw) { | 
		
	
		
			
			|  |  |  | if (this->pw == pw || midiEventCount == MAX_MIDI_EVENTS) | 
		
	
		
			
			|  |  |  | void setPitchbend(int16_t pitchbend) | 
		
	
		
			
			|  |  |  | { | 
		
	
		
			
			|  |  |  | if (this->pitchbend == pitchbend) | 
		
	
		
			
			|  |  |  | return; | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | this->pitchbend = pitchbend; | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | if (midiEventCount == MAX_MIDI_EVENTS || frame == UINT_MAX) | 
		
	
		
			
			|  |  |  | return; | 
		
	
		
			
			|  |  |  | this->pw = pw; | 
		
	
		
			
			|  |  |  |  | 
		
	
		
			
			|  |  |  | // Pitch Wheel | 
		
	
		
			
			|  |  |  | NativeMidiEvent& m(midiEvents[midiEventCount++]); | 
		
	
		
			
			|  |  |  | m.time = frame; | 
		
	
		
			
			|  |  |  | m.port = 0; | 
		
	
		
			
			|  |  |  | m.size = 3; | 
		
	
		
			
			|  |  |  | m.data[0] = 0xe0; | 
		
	
		
			
			|  |  |  | m.data[1] = pw & 0x7f; | 
		
	
		
			
			|  |  |  | m.data[2] = (pw >> 7) & 0x7f; | 
		
	
		
			
			|  |  |  | m.data[0] = 0xe0 | channel; | 
		
	
		
			
			|  |  |  | m.data[1] = pitchbend & 0x7f; | 
		
	
		
			
			|  |  |  | m.data[2] = (pitchbend >> 7) & 0x7f; | 
		
	
		
			
			|  |  |  | } | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | void onReset() override | 
		
	
		
			
			|  |  |  | void panic() | 
		
	
		
			
			|  |  |  | { | 
		
	
		
			
			|  |  |  | for (uint c = 0; c < CHANNELS; c++) { | 
		
	
		
			
			|  |  |  | if (frame != UINT_MAX) | 
		
	
		
			
			|  |  |  | { | 
		
	
		
			
			|  |  |  | // Send all note off commands | 
		
	
		
			
			|  |  |  | for (int note = 0; note <= 127; ++note) | 
		
	
		
			
			|  |  |  | { | 
		
	
		
			
			|  |  |  | if (midiEventCount == MAX_MIDI_EVENTS) | 
		
	
		
			
			|  |  |  | break; | 
		
	
		
			
			|  |  |  | // Note off | 
		
	
		
			
			|  |  |  | NativeMidiEvent& m(midiEvents[midiEventCount++]); | 
		
	
		
			
			|  |  |  | m.time = frame; | 
		
	
		
			
			|  |  |  | m.port = 0; | 
		
	
		
			
			|  |  |  | m.size = 3; | 
		
	
		
			
			|  |  |  | m.data[0] = 0x80 | channel; | 
		
	
		
			
			|  |  |  | m.data[1] = note; | 
		
	
		
			
			|  |  |  | m.data[2] = 0; | 
		
	
		
			
			|  |  |  | } | 
		
	
		
			
			|  |  |  | } | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | reset(); | 
		
	
		
			
			|  |  |  | } | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | void reset() | 
		
	
		
			
			|  |  |  | { | 
		
	
		
			
			|  |  |  | for (uint c = 0; c < CHANNELS; ++c) | 
		
	
		
			
			|  |  |  | { | 
		
	
		
			
			|  |  |  | vels[c] = 100; | 
		
	
		
			
			|  |  |  | notes[c] = 60; | 
		
	
		
			
			|  |  |  | gates[c] = false; | 
		
	
		
			
			|  |  |  | keyPressures[c] = -1; | 
		
	
		
			
			|  |  |  | } | 
		
	
		
			
			|  |  |  | mw = -1; | 
		
	
		
			
			|  |  |  | pw = 0x2000; | 
		
	
		
			
			|  |  |  | modwheel = -1; | 
		
	
		
			
			|  |  |  | pitchbend = 0x2000; | 
		
	
		
			
			|  |  |  | midiEventCount = 0; | 
		
	
		
			
			|  |  |  | frame = UINT_MAX; | 
		
	
		
			
			|  |  |  | } | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | void onReset() override | 
		
	
		
			
			|  |  |  | { | 
		
	
		
			
			|  |  |  | reset(); | 
		
	
		
			
			|  |  |  | channel = 0; | 
		
	
		
			
			|  |  |  | lastConnectedModule = nullptr; | 
		
	
		
			
			|  |  |  | } | 
		
	
		
			
			|  |  |  | 
 | 
		
	
	
		
			
				|  |  | @@ -178,7 +246,7 @@ struct CardinalExpanderForInputMIDI : CardinalExpanderFromCVToCarlaMIDI { | 
		
	
		
			
			|  |  |  | return; | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | for (int c = 0; c < inputs[PITCH_INPUT].getChannels(); c++) { | 
		
	
		
			
			|  |  |  | int vel = (int) std::round(inputs[VEL_INPUT].getNormalPolyVoltage(10.f * 100 / 127, c) / 10.f * 127); | 
		
	
		
			
			|  |  |  | int vel = (int) std::round(inputs[VELOCITY_INPUT].getNormalPolyVoltage(10.f * 100 / 127, c) / 10.f * 127); | 
		
	
		
			
			|  |  |  | vel = clamp(vel, 0, 127); | 
		
	
		
			
			|  |  |  | setVelocity(vel, c); | 
		
	
		
			
			|  |  |  | 
 | 
		
	
	
		
			
				|  |  | @@ -187,21 +255,36 @@ struct CardinalExpanderForInputMIDI : CardinalExpanderFromCVToCarlaMIDI { | 
		
	
		
			
			|  |  |  | bool gate = inputs[GATE_INPUT].getPolyVoltage(c) >= 1.f; | 
		
	
		
			
			|  |  |  | setNoteGate(note, gate, c); | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | int aft = (int) std::round(inputs[AFT_INPUT].getPolyVoltage(c) / 10.f * 127); | 
		
	
		
			
			|  |  |  | int aft = (int) std::round(inputs[AFTERTOUCH_INPUT].getPolyVoltage(c) / 10.f * 127); | 
		
	
		
			
			|  |  |  | aft = clamp(aft, 0, 127); | 
		
	
		
			
			|  |  |  | setKeyPressure(aft, c); | 
		
	
		
			
			|  |  |  | } | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | int pw = (int) std::round((inputs[PW_INPUT].getVoltage() + 5.f) / 10.f * 0x4000); | 
		
	
		
			
			|  |  |  | pw = clamp(pw, 0, 0x3fff); | 
		
	
		
			
			|  |  |  | setPitchWheel(pw); | 
		
	
		
			
			|  |  |  | int pitchbend = (int) std::round((inputs[PITCHBEND_INPUT].getVoltage() + 5.f) / 10.f * 16383); | 
		
	
		
			
			|  |  |  | pitchbend = clamp(pitchbend, 0, 16383); | 
		
	
		
			
			|  |  |  | setPitchbend(pitchbend); | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | int mw = (int) std::round(inputs[MW_INPUT].getVoltage() / 10.f * 127); | 
		
	
		
			
			|  |  |  | mw = clamp(mw, 0, 127); | 
		
	
		
			
			|  |  |  | setModWheel(mw); | 
		
	
		
			
			|  |  |  | int modwheel = (int) std::round(inputs[MODWHEEL_INPUT].getVoltage() / 10.f * 127); | 
		
	
		
			
			|  |  |  | modwheel = clamp(modwheel, 0, 127); | 
		
	
		
			
			|  |  |  | setModWheel(modwheel); | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | ++frame; | 
		
	
		
			
			|  |  |  | } | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | json_t* dataToJson() override | 
		
	
		
			
			|  |  |  | { | 
		
	
		
			
			|  |  |  | json_t* const rootJ = json_object(); | 
		
	
		
			
			|  |  |  | DISTRHO_SAFE_ASSERT_RETURN(rootJ != nullptr, nullptr); | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | json_object_set_new(rootJ, "channel", json_integer(channel)); | 
		
	
		
			
			|  |  |  | return rootJ; | 
		
	
		
			
			|  |  |  | } | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | void dataFromJson(json_t* const rootJ) override | 
		
	
		
			
			|  |  |  | { | 
		
	
		
			
			|  |  |  | if (json_t* const channelJ = json_object_get(rootJ, "channel")) | 
		
	
		
			
			|  |  |  | channel = json_integer_value(channelJ) & 0x0F; | 
		
	
		
			
			|  |  |  | } | 
		
	
		
			
			|  |  |  | }; | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | // -------------------------------------------------------------------------------------------------------------------- | 
		
	
	
		
			
				|  |  | @@ -211,16 +294,19 @@ struct CardinalExpanderForInputMIDIWidget : ModuleWidgetWith3HP { | 
		
	
		
			
			|  |  |  | static constexpr const float startY = 90.0f; | 
		
	
		
			
			|  |  |  | static constexpr const float padding = 49.0f; | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | CardinalExpanderForInputMIDIWidget(CardinalExpanderForInputMIDI* const module) | 
		
	
		
			
			|  |  |  | CardinalExpanderForInputMIDI* const module; | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | CardinalExpanderForInputMIDIWidget(CardinalExpanderForInputMIDI* const m) | 
		
	
		
			
			|  |  |  | : module(m) | 
		
	
		
			
			|  |  |  | { | 
		
	
		
			
			|  |  |  | setModule(module); | 
		
	
		
			
			|  |  |  | setModule(m); | 
		
	
		
			
			|  |  |  | setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/ExpanderMIDI.svg"))); | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | addChild(createWidget<ScrewBlack>(Vec(RACK_GRID_WIDTH, 0))); | 
		
	
		
			
			|  |  |  | addChild(createWidget<ScrewBlack>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | for (int i=0; i<CardinalExpanderForInputMIDI::NUM_INPUTS; ++i) | 
		
	
		
			
			|  |  |  | addInput(createInput<PJ301MPort>(Vec(startX + 4.0f, startY + padding * i), module, i)); | 
		
	
		
			
			|  |  |  | addInput(createInput<PJ301MPort>(Vec(startX + 4.0f, startY + padding * i), m, i)); | 
		
	
		
			
			|  |  |  | } | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | void draw(const DrawArgs& args) override | 
		
	
	
		
			
				|  |  | @@ -261,7 +347,35 @@ struct CardinalExpanderForInputMIDIWidget : ModuleWidgetWith3HP { | 
		
	
		
			
			|  |  |  | nvgText(args.vg, box.size.x * 0.666f, startY + padding * 4 - 4.0f, "Pb", nullptr); | 
		
	
		
			
			|  |  |  | nvgText(args.vg, box.size.x * 0.666f, startY + padding * 5 - 4.0f, "MW", nullptr); | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | ModuleWidgetWithSideScrews::draw(args); | 
		
	
		
			
			|  |  |  | ModuleWidgetWith3HP::draw(args); | 
		
	
		
			
			|  |  |  | } | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | void appendContextMenu(Menu* const menu) override | 
		
	
		
			
			|  |  |  | { | 
		
	
		
			
			|  |  |  | menu->addChild(new MenuSeparator); | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | struct ChannelItem : MenuItem { | 
		
	
		
			
			|  |  |  | CardinalExpanderForInputMIDI* module; | 
		
	
		
			
			|  |  |  | Menu* createChildMenu() override { | 
		
	
		
			
			|  |  |  | Menu* menu = new Menu; | 
		
	
		
			
			|  |  |  | for (uint8_t c = 0; c < 16; c++) { | 
		
	
		
			
			|  |  |  | menu->addChild(createCheckMenuItem(string::f("%d", c+1), "", | 
		
	
		
			
			|  |  |  | [=]() {return module->channel == c;}, | 
		
	
		
			
			|  |  |  | [=]() {module->channel = c;} | 
		
	
		
			
			|  |  |  | )); | 
		
	
		
			
			|  |  |  | } | 
		
	
		
			
			|  |  |  | return menu; | 
		
	
		
			
			|  |  |  | } | 
		
	
		
			
			|  |  |  | }; | 
		
	
		
			
			|  |  |  | ChannelItem* const channelItem = new ChannelItem; | 
		
	
		
			
			|  |  |  | channelItem->text = "MIDI channel"; | 
		
	
		
			
			|  |  |  | channelItem->rightText = string::f("%d", module->channel+1) + "  " + RIGHT_ARROW; | 
		
	
		
			
			|  |  |  | channelItem->module = module; | 
		
	
		
			
			|  |  |  | menu->addChild(channelItem); | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | menu->addChild(createMenuItem("Panic", "", | 
		
	
		
			
			|  |  |  | [=]() { module->panic(); } | 
		
	
		
			
			|  |  |  | )); | 
		
	
		
			
			|  |  |  | } | 
		
	
		
			
			|  |  |  | }; | 
		
	
		
			
			|  |  |  | 
 |