Code:
/* Works on T3.2, Usb type = Midi
* Based on Many_Buttons_Knobs example by Leif Oddson
* https://forum.pjrc.com/threads/45376
* and some bits nicked from Notes and Volts
*
* With some additions by MatrixRat to provide some Per-element definitions
* Pots (or other analog sensors) can send CCs, MSB/LSB CCs, or NRPNs
* Analog inputs scalable for sensors that do not emit full 0v - 3.3v range.
*
* Buttons can send NoteOn/NoteOff, CC# on press, CC# on press/release or Toggle CC#.
* Button Pressed or Released values configurable per element
* No NRPN support for buttons as have not needed 'em.
*/
#include <ResponsiveAnalogRead.h>
#include <Bounce.h>
const int NUM_POTS = 4; // number of Analog PINS
const int POT_PINS [NUM_POTS] = { A2, A0, A1, A3}; // Specify which pins are connected to Pots
const uint16_t POT_CONTROLLER_NUMBER[NUM_POTS] = { 1, 4, 15, 1234}; // CC#, Note# or we might be sending NRPN and need a 14-bit value here.
const uint16_t POT_INPUT_CENTER [NUM_POTS] = { 400, 511, 511, 511}; // Useful for Center detent pots were detent spot is not at electrical center.
const uint16_t POT_DEADZONE_WIDTH [NUM_POTS] = { 40, 0, 0, 0}; // Allows a dead zone to give us room to climb out of the detent before value changes.
const uint16_t POT_INPUT_MINIMUM [NUM_POTS] = { 0, 0, 0, 0}; // Useful for pots where wiper does not reach track Min.
const uint16_t POT_INPUT_MAXIMUM [NUM_POTS] = {1023, 1023, 1023, 1020}; // " " " " " " " " " Max.
const uint16_t POT_OUTPUT_MINIMUM [NUM_POTS] = { 0, 0, 0, 0}; // Specify Output range Minimum.
const uint16_t POT_OUTPUT_MAXIMUM [NUM_POTS] = { 127, 127, 255, 1023}; // Specify output range Maximum
const byte POT_OUTPUT_TYPE [NUM_POTS] = { 1, 1, 2, 3}; // 0=Muted, 1=7-bitCC, 2=MSB LSB CC, 3= NRPN.
const byte POT_OUTPUT_CHANNEL [NUM_POTS] = { 16, 16, 16, 16};
//******VARIABLES***********
uint16_t data [NUM_POTS];
uint16_t dataLag[NUM_POTS]; // when lag and new are not the same then update MIDI CC value
ResponsiveAnalogRead analog[] {
{POT_PINS[0], true},
{POT_PINS[1], true},
{POT_PINS[2], true},
{POT_PINS[3], true}
};
//************************************************************
const int BOUNCE_TIME = 7; // 5 ms is usually sufficient
const int NUM_BUTTONS = 4; // number of Buttons connected to digital PINS
const int BUTTON_PINS [NUM_BUTTONS] = { 8, 11, 12, 3};// Which Pin is a Button on?
const byte BUTTON_CHANNEL [NUM_BUTTONS] = { 16, 16, 16, 16};// What channel is the button sending on?
const int BUTTON_CONTROLLER_NUMBER[NUM_BUTTONS] = {104, 103, 102, 105};// Could be CC# or Note#
const int BUTTON_ON_VALUE [NUM_BUTTONS] = {127, 127, 127, 127};// Specify a button pressed value
const int BUTTON_OFF_VALUE [NUM_BUTTONS] = { 0, 0, 0, 0};// Specify a button released value
const byte BUTTON_OUTPUT_TYPE [NUM_BUTTONS] = { 1, 2, 4, 3};// 0=Muted, 1=Note On/Off, 2=CC Press, 3=CC Press/Release, 4=Toggle
int BUTTON_TOGGLED [NUM_BUTTONS]; //Array to hold toggled state
// initialize the bounce objects
Bounce digital[] = {
Bounce(BUTTON_PINS[0], BOUNCE_TIME),
Bounce(BUTTON_PINS[1], BOUNCE_TIME),
Bounce(BUTTON_PINS[2], BOUNCE_TIME),
Bounce(BUTTON_PINS[3], BOUNCE_TIME),
};
//*******************************************************************
void setup() {
// loop to configure input pins and internal pullup resisters for digital section
for (int i = 0; i < NUM_BUTTONS; i++) {
pinMode(BUTTON_PINS[i], INPUT_PULLUP);
}
}
//**********************************************
void loop() {
while (usbMIDI.read()) {
// controllers must call .read() to keep the queue clear even if they are not responding to MIDI
}
getDigitalData();
getAnalogData();
}
//************ANALOG SECTION**************
void getAnalogData()
{
for (int i = 0; i < NUM_POTS; i++) {
analog[i].update();
if (!analog[i].hasChanged())
continue;
//With some help from @NominalAnimal
const int32_t OUTPUT_CENTER = (POT_OUTPUT_MINIMUM[i] + POT_OUTPUT_MAXIMUM[i]) / 2;
const int32_t input = analog[i].getValue();
if (input <= POT_INPUT_MINIMUM[i]) {
data[i] = POT_OUTPUT_MINIMUM[i];
} else if (input < POT_INPUT_CENTER[i] - POT_DEADZONE_WIDTH[i]) {
data[i] = map(input, POT_INPUT_MINIMUM[i], POT_INPUT_CENTER[i] - POT_DEADZONE_WIDTH[i], POT_OUTPUT_MINIMUM[i], OUTPUT_CENTER);
} else if (input <= POT_INPUT_CENTER[i] + POT_DEADZONE_WIDTH[i]) {
data[i] = OUTPUT_CENTER;
} else if (input < POT_INPUT_MAXIMUM[i]) {
data[i] = map(input, POT_INPUT_CENTER[i] + POT_DEADZONE_WIDTH[i], POT_INPUT_MAXIMUM[i], OUTPUT_CENTER, POT_OUTPUT_MAXIMUM[i]);
} else {
data[i] = POT_OUTPUT_MAXIMUM[i];
}
if (data[i] != dataLag[i]) {
dataLag[i] = data[i];
uint16_t potmessage = data[i]; //has what the pot has just given us
// Note we're using a uint16_t because we might be sending values higher than 127 with MSB/LSB CCs or NRPNs
// and the following dance pulls out MSB/LSB and tosses out bits 14 and 15
byte valueLSB = (potmessage & 0b0000000001111111);
byte valueMSB = (potmessage & 0b0011111110000000) >> 7;
// Similarly, we might be sending an NRPN 14-bit Controller number
byte controlLSB = (POT_CONTROLLER_NUMBER[i] & 0b0000000001111111);// POT_CONTROLLER_NUMBER[i], a uint16_t holds Control#
byte controlMSB = (POT_CONTROLLER_NUMBER[i] & 0b0011111110000000) >> 7;
switch (POT_OUTPUT_TYPE[i])// from POT_OUTPUT_TYPE definition
{
case 0:
//muted, do nothing
break;
case 1: // Sending 7-bit CC
usbMIDI.sendControlChange (controlLSB, valueLSB, (POT_OUTPUT_CHANNEL[i]));
break;
case 2:
usbMIDI.sendControlChange (controlLSB, valueMSB, (POT_OUTPUT_CHANNEL[i]));
usbMIDI.sendControlChange (controlLSB + 32, valueLSB, (POT_OUTPUT_CHANNEL[i]));
break;
case 3:
usbMIDI.sendControlChange(99, controlMSB, (POT_OUTPUT_CHANNEL[i]));
usbMIDI.sendControlChange(98, controlLSB, (POT_OUTPUT_CHANNEL[i]));
usbMIDI.sendNrpnValue(data[i], (POT_OUTPUT_CHANNEL[i]));
break;
}
}
}
}
//************DIGITAL SECTION**************
void getDigitalData() {
for (int i = 0; i < NUM_BUTTONS; i++) {
digital[i].update();
if (digital[i].fallingEdge()) {
// Button is pressed
switch (BUTTON_OUTPUT_TYPE[i]) {
case 0:
//Do nothing, MUTED
break;
case 1: //Note
usbMIDI.sendNoteOn (BUTTON_CONTROLLER_NUMBER[i], (BUTTON_ON_VALUE[i]), (BUTTON_CHANNEL[i]));
break;
case 2: //PRESS
usbMIDI.sendControlChange (BUTTON_CONTROLLER_NUMBER[i], (BUTTON_ON_VALUE[i]), (BUTTON_CHANNEL[i]));
break;
case 3: //CC PRESS - RELEASE
usbMIDI.sendControlChange (BUTTON_CONTROLLER_NUMBER[i], (BUTTON_ON_VALUE[i]), (BUTTON_CHANNEL[i]));
break;
case 4: //Toggle
if (BUTTON_TOGGLED[i] == 0) {
usbMIDI.sendControlChange (BUTTON_CONTROLLER_NUMBER[i], (BUTTON_ON_VALUE[i]), (BUTTON_CHANNEL[i]));
BUTTON_TOGGLED[i] = 1;
}
else if (BUTTON_TOGGLED[i] == 1)
{
usbMIDI.sendControlChange (BUTTON_CONTROLLER_NUMBER[i], (BUTTON_OFF_VALUE[i]), (BUTTON_CHANNEL[i]));
BUTTON_TOGGLED[i] = 0;
}
break;
}
}
if (digital[i].risingEdge()) {
switch (BUTTON_OUTPUT_TYPE[i]) {
case 0:
//Do nothing - MUTED
break;
case 1:// Send Note Off
usbMIDI.sendNoteOff (BUTTON_CONTROLLER_NUMBER[i], (BUTTON_OFF_VALUE[i]), (BUTTON_CHANNEL[i]));
break;
case 2: // Don't Send CC Release
// MUTED
break;
case 3: // Send CC - RELEASE
usbMIDI.sendControlChange (BUTTON_CONTROLLER_NUMBER[i], (BUTTON_OFF_VALUE[i]), (BUTTON_CHANNEL[i]));
break;
case 4: // Dummy for TOGGLE
break;
}
}
}
}