|  |  | @@ -39,13 +39,13 @@ struct HostMIDICC : Module { | 
		
	
		
			
			|  |  |  | }; | 
		
	
		
			
			|  |  |  | enum InputIds { | 
		
	
		
			
			|  |  |  | ENUMS(CC_INPUTS, 16), | 
		
	
		
			
			|  |  |  | CC_INPUT_CHANNEL_PRESSURE, | 
		
	
		
			
			|  |  |  | CC_INPUT_CH_PRESSURE, | 
		
	
		
			
			|  |  |  | CC_INPUT_PITCHBEND, | 
		
	
		
			
			|  |  |  | NUM_INPUTS | 
		
	
		
			
			|  |  |  | }; | 
		
	
		
			
			|  |  |  | enum OutputIds { | 
		
	
		
			
			|  |  |  | ENUMS(CC_OUTPUT, 16), | 
		
	
		
			
			|  |  |  | CC_OUTPUT_CHANNEL_PRESSURE, | 
		
	
		
			
			|  |  |  | CC_OUTPUT_CH_PRESSURE, | 
		
	
		
			
			|  |  |  | CC_OUTPUT_PITCHBEND, | 
		
	
		
			
			|  |  |  | NUM_OUTPUTS | 
		
	
		
			
			|  |  |  | }; | 
		
	
	
		
			
				|  |  | @@ -64,16 +64,19 @@ struct HostMIDICC : Module { | 
		
	
		
			
			|  |  |  | int64_t lastBlockFrame; | 
		
	
		
			
			|  |  |  | uint8_t channel; | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | uint8_t chPressure[16]; | 
		
	
		
			
			|  |  |  | uint16_t pitchbend[16]; | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | // stuff from Rack | 
		
	
		
			
			|  |  |  | /** [cc][channel] */ | 
		
	
		
			
			|  |  |  | int8_t ccValues[128][16]; | 
		
	
		
			
			|  |  |  | uint8_t ccValues[128][16]; | 
		
	
		
			
			|  |  |  | /** When LSB is enabled for CC 0-31, the MSB is stored here until the LSB is received. | 
		
	
		
			
			|  |  |  | [cc][channel] | 
		
	
		
			
			|  |  |  | */ | 
		
	
		
			
			|  |  |  | int8_t msbValues[32][16]; | 
		
	
		
			
			|  |  |  | uint8_t msbValues[32][16]; | 
		
	
		
			
			|  |  |  | int learningId; | 
		
	
		
			
			|  |  |  | /** [cell][channel] */ | 
		
	
		
			
			|  |  |  | dsp::ExponentialFilter valueFilters[16][16]; | 
		
	
		
			
			|  |  |  | dsp::ExponentialFilter valueFilters[NUM_OUTPUTS][16]; | 
		
	
		
			
			|  |  |  | bool smooth; | 
		
	
		
			
			|  |  |  | bool mpeMode; | 
		
	
		
			
			|  |  |  | bool lsbMode; | 
		
	
	
		
			
				|  |  | @@ -81,7 +84,7 @@ struct HostMIDICC : Module { | 
		
	
		
			
			|  |  |  | MidiInput(CardinalPluginContext* const pc) | 
		
	
		
			
			|  |  |  | : pcontext(pc) | 
		
	
		
			
			|  |  |  | { | 
		
	
		
			
			|  |  |  | for (int i = 0; i < 16; i++) { | 
		
	
		
			
			|  |  |  | for (int i = 0; i < NUM_OUTPUTS; i++) { | 
		
	
		
			
			|  |  |  | for (int c = 0; c < 16; c++) { | 
		
	
		
			
			|  |  |  | valueFilters[i][c].setTau(1 / 30.f); | 
		
	
		
			
			|  |  |  | } | 
		
	
	
		
			
				|  |  | @@ -107,6 +110,10 @@ struct HostMIDICC : Module { | 
		
	
		
			
			|  |  |  | msbValues[cc][c] = 0; | 
		
	
		
			
			|  |  |  | } | 
		
	
		
			
			|  |  |  | } | 
		
	
		
			
			|  |  |  | for (int c = 0; c < 16; c++) { | 
		
	
		
			
			|  |  |  | chPressure[c] = 0; | 
		
	
		
			
			|  |  |  | pitchbend[c] = 8192; | 
		
	
		
			
			|  |  |  | } | 
		
	
		
			
			|  |  |  | learningId = -1; | 
		
	
		
			
			|  |  |  | smooth = true; | 
		
	
		
			
			|  |  |  | mpeMode = false; | 
		
	
	
		
			
				|  |  | @@ -148,33 +155,47 @@ struct HostMIDICC : Module { | 
		
	
		
			
			|  |  |  | continue; | 
		
	
		
			
			|  |  |  | } | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | // adapted from Rack | 
		
	
		
			
			|  |  |  | if ((data[0] & 0xF0) != 0xB0) | 
		
	
		
			
			|  |  |  | const uint8_t status = data[0] & 0xF0; | 
		
	
		
			
			|  |  |  | const uint8_t chan = data[0] & 0x0F; | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | /**/ if (status == 0xD0) | 
		
	
		
			
			|  |  |  | { | 
		
	
		
			
			|  |  |  | chPressure[chan] = data[1]; | 
		
	
		
			
			|  |  |  | } | 
		
	
		
			
			|  |  |  | else if (status == 0xE0) | 
		
	
		
			
			|  |  |  | { | 
		
	
		
			
			|  |  |  | pitchbend[chan] = (data[2] << 7) | data[1]; | 
		
	
		
			
			|  |  |  | } | 
		
	
		
			
			|  |  |  | else if (status != 0xB0) | 
		
	
		
			
			|  |  |  | { | 
		
	
		
			
			|  |  |  | continue; | 
		
	
		
			
			|  |  |  | } | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | uint8_t c = mpeMode ? (data[0] & 0x0F) : 0; | 
		
	
		
			
			|  |  |  | uint8_t cc = data[1]; | 
		
	
		
			
			|  |  |  | // adapted from Rack | 
		
	
		
			
			|  |  |  | const uint8_t c = mpeMode ? chan : 0; | 
		
	
		
			
			|  |  |  | const uint8_t cc = data[1]; | 
		
	
		
			
			|  |  |  | const uint8_t value = data[2]; | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | // Allow CC to be negative if the 8th bit is set. | 
		
	
		
			
			|  |  |  | // The gamepad driver abuses this, for example. | 
		
	
		
			
			|  |  |  | // Cast uint8_t to int8_t | 
		
	
		
			
			|  |  |  | int8_t value = data[2]; | 
		
	
		
			
			|  |  |  | // Learn | 
		
	
		
			
			|  |  |  | if (learningId >= 0 && ccValues[cc][c] != value) { | 
		
	
		
			
			|  |  |  | if (learningId >= 0 && ccValues[cc][c] != value) | 
		
	
		
			
			|  |  |  | { | 
		
	
		
			
			|  |  |  | learnedCcs[learningId] = cc; | 
		
	
		
			
			|  |  |  | learningId = -1; | 
		
	
		
			
			|  |  |  | } | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | if (lsbMode && cc < 32) { | 
		
	
		
			
			|  |  |  | if (lsbMode && cc < 32) | 
		
	
		
			
			|  |  |  | { | 
		
	
		
			
			|  |  |  | // Don't set MSB yet. Wait for LSB to be received. | 
		
	
		
			
			|  |  |  | msbValues[cc][c] = value; | 
		
	
		
			
			|  |  |  | } | 
		
	
		
			
			|  |  |  | else if (lsbMode && 32 <= cc && cc < 64) { | 
		
	
		
			
			|  |  |  | else if (lsbMode && 32 <= cc && cc < 64) | 
		
	
		
			
			|  |  |  | { | 
		
	
		
			
			|  |  |  | // Apply MSB when LSB is received | 
		
	
		
			
			|  |  |  | ccValues[cc - 32][c] = msbValues[cc - 32][c]; | 
		
	
		
			
			|  |  |  | ccValues[cc][c] = value; | 
		
	
		
			
			|  |  |  | } | 
		
	
		
			
			|  |  |  | else { | 
		
	
		
			
			|  |  |  | else | 
		
	
		
			
			|  |  |  | { | 
		
	
		
			
			|  |  |  | ccValues[cc][c] = value; | 
		
	
		
			
			|  |  |  | } | 
		
	
		
			
			|  |  |  | } | 
		
	
	
		
			
				|  |  | @@ -184,35 +205,87 @@ struct HostMIDICC : Module { | 
		
	
		
			
			|  |  |  | // Rack stuff | 
		
	
		
			
			|  |  |  | const int channels = mpeMode ? 16 : 1; | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | for (int i = 0; i < 16; i++) { | 
		
	
		
			
			|  |  |  | for (int i = 0; i < 16; i++) | 
		
	
		
			
			|  |  |  | { | 
		
	
		
			
			|  |  |  | if (!outputs[CC_OUTPUT + i].isConnected()) | 
		
	
		
			
			|  |  |  | continue; | 
		
	
		
			
			|  |  |  | outputs[CC_OUTPUT + i].setChannels(channels); | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | int cc = learnedCcs[i]; | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | for (int c = 0; c < channels; c++) { | 
		
	
		
			
			|  |  |  | for (int c = 0; c < channels; c++) | 
		
	
		
			
			|  |  |  | { | 
		
	
		
			
			|  |  |  | int16_t cellValue = int16_t(ccValues[cc][c]) * 128; | 
		
	
		
			
			|  |  |  | if (lsbMode && cc < 32) | 
		
	
		
			
			|  |  |  | cellValue += ccValues[cc + 32][c]; | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | // Maximum value for 14-bit CC should be MSB=127 LSB=0, not MSB=127 LSB=127, because this is the maximum value that 7-bit controllers can send. | 
		
	
		
			
			|  |  |  | float value = float(cellValue) / (128 * 127); | 
		
	
		
			
			|  |  |  | // Support negative values because the gamepad MIDI driver generates nonstandard 8-bit CC values. | 
		
	
		
			
			|  |  |  | value = clamp(value, -1.f, 1.f); | 
		
	
		
			
			|  |  |  | const float value = static_cast<float>(cellValue) / (128.0f * 127.0f); | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | // Detect behavior from MIDI buttons. | 
		
	
		
			
			|  |  |  | if (smooth && std::fabs(valueFilters[i][c].out - value) < 1.f) { | 
		
	
		
			
			|  |  |  | if (smooth && std::fabs(valueFilters[i][c].out - value) < 1.f) | 
		
	
		
			
			|  |  |  | { | 
		
	
		
			
			|  |  |  | // Smooth value with filter | 
		
	
		
			
			|  |  |  | valueFilters[i][c].process(args.sampleTime, value); | 
		
	
		
			
			|  |  |  | } | 
		
	
		
			
			|  |  |  | else { | 
		
	
		
			
			|  |  |  | else | 
		
	
		
			
			|  |  |  | { | 
		
	
		
			
			|  |  |  | // Jump value | 
		
	
		
			
			|  |  |  | valueFilters[i][c].out = value; | 
		
	
		
			
			|  |  |  | } | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | outputs[CC_OUTPUT + i].setVoltage(valueFilters[i][c].out * 10.f, c); | 
		
	
		
			
			|  |  |  | } | 
		
	
		
			
			|  |  |  | } | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | if (outputs[CC_OUTPUT_CH_PRESSURE].isConnected()) | 
		
	
		
			
			|  |  |  | { | 
		
	
		
			
			|  |  |  | outputs[CC_OUTPUT_CH_PRESSURE].setChannels(channels); | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | for (int c = 0; c < channels; c++) | 
		
	
		
			
			|  |  |  | { | 
		
	
		
			
			|  |  |  | const float value = static_cast<float>(chPressure[c]) / 128.0f; | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | // Detect behavior from MIDI buttons. | 
		
	
		
			
			|  |  |  | if (smooth && std::fabs(valueFilters[CC_OUTPUT_CH_PRESSURE][c].out - value) < 1.f) | 
		
	
		
			
			|  |  |  | { | 
		
	
		
			
			|  |  |  | // Smooth value with filter | 
		
	
		
			
			|  |  |  | valueFilters[CC_OUTPUT_CH_PRESSURE][c].process(args.sampleTime, value); | 
		
	
		
			
			|  |  |  | } | 
		
	
		
			
			|  |  |  | else | 
		
	
		
			
			|  |  |  | { | 
		
	
		
			
			|  |  |  | // Jump value | 
		
	
		
			
			|  |  |  | valueFilters[CC_OUTPUT_CH_PRESSURE][c].out = value; | 
		
	
		
			
			|  |  |  | } | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | outputs[CC_OUTPUT_CH_PRESSURE].setVoltage(valueFilters[CC_OUTPUT_CH_PRESSURE][c].out * 10.f, c); | 
		
	
		
			
			|  |  |  | } | 
		
	
		
			
			|  |  |  | } | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | if (outputs[CC_OUTPUT_PITCHBEND].isConnected()) | 
		
	
		
			
			|  |  |  | { | 
		
	
		
			
			|  |  |  | outputs[CC_OUTPUT_PITCHBEND].setChannels(channels); | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | for (int c = 0; c < channels; c++) | 
		
	
		
			
			|  |  |  | { | 
		
	
		
			
			|  |  |  | const float value = static_cast<float>(pitchbend[c]) / 16384.0f; | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | // Detect behavior from MIDI buttons. | 
		
	
		
			
			|  |  |  | if (smooth && std::fabs(valueFilters[CC_OUTPUT_PITCHBEND][c].out - value) < 1.f) | 
		
	
		
			
			|  |  |  | { | 
		
	
		
			
			|  |  |  | // Smooth value with filter | 
		
	
		
			
			|  |  |  | valueFilters[CC_OUTPUT_PITCHBEND][c].process(args.sampleTime, value); | 
		
	
		
			
			|  |  |  | } | 
		
	
		
			
			|  |  |  | else | 
		
	
		
			
			|  |  |  | { | 
		
	
		
			
			|  |  |  | // Jump value | 
		
	
		
			
			|  |  |  | valueFilters[CC_OUTPUT_PITCHBEND][c].out = value; | 
		
	
		
			
			|  |  |  | } | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | outputs[CC_OUTPUT_CH_PRESSURE].setVoltage(valueFilters[CC_OUTPUT_PITCHBEND][c].out * 10.f, c); | 
		
	
		
			
			|  |  |  | } | 
		
	
		
			
			|  |  |  | } | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | return blockFrameChanged; | 
		
	
		
			
			|  |  |  | } | 
		
	
		
			
			|  |  |  | 
 | 
		
	
	
		
			
				|  |  | @@ -224,8 +297,7 @@ struct HostMIDICC : Module { | 
		
	
		
			
			|  |  |  | uint8_t channel = 0; | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | // from Rack | 
		
	
		
			
			|  |  |  | dsp::Timer rateLimiterTimer; | 
		
	
		
			
			|  |  |  | int lastValues[128]; | 
		
	
		
			
			|  |  |  | int lastValues[130]; | 
		
	
		
			
			|  |  |  | int64_t frame = 0; | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | MidiOutput(CardinalPluginContext* const pc) | 
		
	
	
		
			
				|  |  | @@ -236,16 +308,15 @@ struct HostMIDICC : Module { | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | void reset() | 
		
	
		
			
			|  |  |  | { | 
		
	
		
			
			|  |  |  | for (int n = 0; n < 128; n++) | 
		
	
		
			
			|  |  |  | for (int n = 0; n < 130; ++n) | 
		
	
		
			
			|  |  |  | lastValues[n] = -1; | 
		
	
		
			
			|  |  |  | } | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | void setValue(int value, int cc) | 
		
	
		
			
			|  |  |  | void sendCC(const int cc, const int value) | 
		
	
		
			
			|  |  |  | { | 
		
	
		
			
			|  |  |  | if (value == lastValues[cc]) | 
		
	
		
			
			|  |  |  | if (lastValues[cc] == value) | 
		
	
		
			
			|  |  |  | return; | 
		
	
		
			
			|  |  |  | lastValues[cc] = value; | 
		
	
		
			
			|  |  |  | // CC | 
		
	
		
			
			|  |  |  | midi::Message m; | 
		
	
		
			
			|  |  |  | m.setStatus(0xb); | 
		
	
		
			
			|  |  |  | m.setNote(cc); | 
		
	
	
		
			
				|  |  | @@ -254,6 +325,31 @@ struct HostMIDICC : Module { | 
		
	
		
			
			|  |  |  | sendMessage(m); | 
		
	
		
			
			|  |  |  | } | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | void sendChanPressure(const int pressure) | 
		
	
		
			
			|  |  |  | { | 
		
	
		
			
			|  |  |  | if (lastValues[128] == pressure) | 
		
	
		
			
			|  |  |  | return; | 
		
	
		
			
			|  |  |  | lastValues[128] = pressure; | 
		
	
		
			
			|  |  |  | midi::Message m; | 
		
	
		
			
			|  |  |  | m.setStatus(0xd); | 
		
	
		
			
			|  |  |  | m.setNote(pressure); | 
		
	
		
			
			|  |  |  | m.setFrame(frame); | 
		
	
		
			
			|  |  |  | sendMessage(m); | 
		
	
		
			
			|  |  |  | } | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | void sendPitchbend(const int pitchbend) | 
		
	
		
			
			|  |  |  | { | 
		
	
		
			
			|  |  |  | if (lastValues[129] == pitchbend) | 
		
	
		
			
			|  |  |  | return; | 
		
	
		
			
			|  |  |  | lastValues[129] = pitchbend; | 
		
	
		
			
			|  |  |  | midi::Message m; | 
		
	
		
			
			|  |  |  | m.setStatus(0xe); | 
		
	
		
			
			|  |  |  | m.setNote(pitchbend & 0x7F); | 
		
	
		
			
			|  |  |  | m.setValue(pitchbend >> 7); | 
		
	
		
			
			|  |  |  | m.setFrame(frame); | 
		
	
		
			
			|  |  |  | sendMessage(m); | 
		
	
		
			
			|  |  |  | } | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | void sendMessage(const midi::Message& message) | 
		
	
		
			
			|  |  |  | { | 
		
	
		
			
			|  |  |  | pcontext->writeMidiMessage(message, channel); | 
		
	
	
		
			
				|  |  | @@ -276,13 +372,13 @@ struct HostMIDICC : Module { | 
		
	
		
			
			|  |  |  | for (int i = 0; i < 16; i++) | 
		
	
		
			
			|  |  |  | configInput(CC_INPUTS + i, string::f("Cell %d", i + 1)); | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | configInput(CC_INPUT_CHANNEL_PRESSURE, "Channel pressure"); | 
		
	
		
			
			|  |  |  | configInput(CC_INPUT_CH_PRESSURE, "Channel pressure"); | 
		
	
		
			
			|  |  |  | configInput(CC_INPUT_PITCHBEND, "Pitchbend"); | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | for (int i = 0; i < 16; i++) | 
		
	
		
			
			|  |  |  | configOutput(CC_OUTPUT + i, string::f("Cell %d", i + 1)); | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | configOutput(CC_OUTPUT_CHANNEL_PRESSURE, "Channel pressure"); | 
		
	
		
			
			|  |  |  | configOutput(CC_OUTPUT_CH_PRESSURE, "Channel pressure"); | 
		
	
		
			
			|  |  |  | configOutput(CC_OUTPUT_PITCHBEND, "Pitchbend"); | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | onReset(); | 
		
	
	
		
			
				|  |  | @@ -304,18 +400,23 @@ struct HostMIDICC : Module { | 
		
	
		
			
			|  |  |  | else | 
		
	
		
			
			|  |  |  | ++midiOutput.frame; | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | const float rateLimiterPeriod = 1 / 200.f; | 
		
	
		
			
			|  |  |  | bool rateLimiterTriggered = (midiOutput.rateLimiterTimer.process(args.sampleTime) >= rateLimiterPeriod); | 
		
	
		
			
			|  |  |  | if (rateLimiterTriggered) | 
		
	
		
			
			|  |  |  | midiOutput.rateLimiterTimer.time -= rateLimiterPeriod; | 
		
	
		
			
			|  |  |  | else | 
		
	
		
			
			|  |  |  | return; | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | for (int i = 0; i < 16; i++) | 
		
	
		
			
			|  |  |  | { | 
		
	
		
			
			|  |  |  | int value = (int) std::round(inputs[CC_INPUTS + i].getVoltage() / 10.f * 127); | 
		
	
		
			
			|  |  |  | value = clamp(value, 0, 127); | 
		
	
		
			
			|  |  |  | midiOutput.setValue(value, learnedCcs[i]); | 
		
	
		
			
			|  |  |  | midiOutput.sendCC(learnedCcs[i], value); | 
		
	
		
			
			|  |  |  | } | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | { | 
		
	
		
			
			|  |  |  | int value = (int) std::round(inputs[CC_INPUT_CH_PRESSURE].getVoltage() / 10.f * 127); | 
		
	
		
			
			|  |  |  | value = clamp(value, 0, 127); | 
		
	
		
			
			|  |  |  | midiOutput.sendChanPressure(value); | 
		
	
		
			
			|  |  |  | } | 
		
	
		
			
			|  |  |  | 
 | 
		
	
		
			
			|  |  |  | { | 
		
	
		
			
			|  |  |  | int value = (int) std::round(inputs[CC_INPUT_PITCHBEND].getVoltage() / 10.f * 16383); | 
		
	
		
			
			|  |  |  | value = clamp(value, 0, 16383); | 
		
	
		
			
			|  |  |  | midiOutput.sendPitchbend(value); | 
		
	
		
			
			|  |  |  | } | 
		
	
		
			
			|  |  |  | } | 
		
	
		
			
			|  |  |  | 
 | 
		
	
	
		
			
				|  |  | 
 |