Parallel Scanner: 32 step expression
/*
Parallel-Wired Arduino Scanner with 32 step swell pedal
==========================
This sketch scans 65 inputs (64 digital and 1 analog) and outputs MIDI messages on Channel 1.
It also accepts serial input so that units can be daisy chained together.Each Arduino is
automatically assigned a unique channel number.
Pins 0 - 1: reserved for serial IO duty.
Pins 2 - 12: 11 digital inputs
Pin 13: not used because of attached LED
Pins 14 - 19: serial 1,2,3 IO pins reset for 6 digital inputs
Pins 22-53: 32 digital inputs
Pins 54 - 68: Analog pins used as 15 digital inputs
Pins 69: Reserved for analog input. The controllerArray values will have to be edited to
reflect the voltage range put out by device device. Connect 15k (+/-) pot across Arduino's
5V and Ground. Centre tap goes to pin 69.Input must never exceed 5V.
Note: ***** Unused analog inputs must be grounded to avoid spurious control messages *****
All switches connected to the digital inputs are to be connected to a common ground.
The other side of each switch is connected directly to its digital input pin (no diodes needed).
MIDI messages assume bottom C connects to pin 2. Subsequent pins follow the
order: 2 - 12, 14 - 19, 22 - 68.
Daisy chained boards communicate through Serial Port 0
No MIDI shield is needed (and in fact blocks access to the pins) since the last board in the chain can
communicate directly with a MIDI device by connecting two 220 ohm resistors to +5 and Tx
in the usual output configuration.
Equipment: Arduino Mega
created 2024 APR 02
modified 2024 APR 02
by John Coenraads
*/
// Declarations==========================================
//Counters (old Fortran habit)
int i, j, k;
byte inputBit; //input data bit
byte pinCount; //current pin
byte noteNumber; //noteNumber for keyboard scan, low C = 36
const byte debounceCount = 4; //Note ON if count = 4, OFF if count = 0
byte debounceArray [110]; //holds debounce count for 61 inputs
byte noteOnArray [110]; //tracks which notes are turned ON
int oldOldExpression1 = 0, oldExpression1 = 0, newExpression1 = 0; // Expression controller values for expression pedal 1
// The following array represents the controller values output for analog device voltages
// in 0.15V increments ranging from 0 to 5V. Input must never exceed 5V.
// Array can be edited as desired, but must contain exactly 33 entries with values between 0 and 127 only.
byte controllerArray [33] = {32,32,32,32,32,32,32,32,32,39,46,54,61,68,76,83,90,97,104,111,119,127,127,127,127,127,127,127,127,127,127,127,127};
//In this example, the potentiometer outputs voltages from 1.35 volts to 3.3 volts.
//The controller values start at 32 to ensure that swell pedal volume does not drop to zero.
//Since 1.35/0.15 = 9, it is with the 10th entry that we start incrementing the controller values e.g., 39,46,54,61 ...
//Since 3.3/0.15 = 22, it is with the 22nd entry that we make sure to reach 127, (the max. value) e.g., ...90,97,104,111,119,127,127
//Even though the voltage range is only 2 volts, this still gives us 13 steps of swell volume control
//Receive variables
byte noteStatusRx;
byte noteNumberRx;
byte noteVelocityRx;
void setup()
//Initialize =========================================================
{
// Set MIDI baud rate:
Serial.begin(31250);
//Clear serial input buffer
while (Serial.available() > 0)
{
Serial.read();
}
//Relieve ports 1,2,3 of serial IO duty
Serial1.end();
Serial2.end();
Serial3.end();
//Initialize 64 pins for input. Normally high (via internal pullups)
for (i = 2; i < 13; i++)
{
pinMode (i, INPUT_PULLUP);
}
for (i = 14; i < 20; i++)
{
pinMode (i, INPUT_PULLUP);
}
for (i = 22; i < 69; i++)
{
pinMode (i, INPUT_PULLUP);
}
//Initialize debounce count array to zero
for (i = 0; i < 110; i++)
{
debounceArray[i] = 0;
}
//Initialize noteOn arrays to zero (note off)
for (i = 0; i < 110; i++)
{
noteOnArray [i] = 0;
}
}
//Main Loop ===========================================================
void loop()
{
scanInputs();
scanExpressionPedal1();
processSerial();
Serial.flush();
}
//Scan keyboard input, convert to MIDI and output via port 0, channel 1 (0)
void scanInputs ()
{
noteNumber = 36; //start at low C
for (pinCount = 2; pinCount < 13; pinCount++)
{
inputBit = digitalRead(pinCount); //read bit
if (inputBit == HIGH) //bit HIGH, switch open
{
turnNoteOFF();
}
else //bit LOW, switch closed
{
turnNoteON();
}
noteNumber = noteNumber + 1; //move on to next note
}
for (pinCount = 14; pinCount < 20; pinCount++)
{
inputBit = digitalRead(pinCount); //read bit
if (inputBit == HIGH) //bit HIGH, switch open
{
turnNoteOFF();
}
else //bit LOW, switch closed
{
turnNoteON();
}
noteNumber = noteNumber + 1; //move on to next note
}
for (pinCount = 22; pinCount < 69; pinCount++)
{
inputBit = digitalRead(pinCount); //read bit
if (inputBit == HIGH) //bit HIGH, switch open
{
turnNoteOFF();
}
else //bit LOW, switch closed
{
turnNoteON();
}
noteNumber = noteNumber + 1; //move on to next note
}
}
//Turn note on. Debouncing is achieved by requiring that several turnNoteON requests
//are received before sending out noteOn MIDI message
void turnNoteON ()
{
if (debounceArray[noteNumber] < debounceCount)
{
debounceArray [noteNumber] = debounceArray [noteNumber] + 1;
if ((debounceArray[noteNumber] == debounceCount) && ( !noteOnArray[noteNumber]))
{
Serial.write (0x90); //note ON, channel 1,
Serial.write (noteNumber);
Serial.write (0x7f); //medium velocity
noteOnArray[noteNumber] = 1; //note now ON
}
}
}
//Turn note off. Debouncing is achieved by requiring that several turnNoteOFF requests
//are received before sending out noteOff MIDI message
void turnNoteOFF ()
{
if (debounceArray[noteNumber] > 0)
{
debounceArray [noteNumber] = debounceArray [noteNumber] - 1;
if ((debounceArray[noteNumber] == 0) && (noteOnArray[noteNumber]))
{
Serial.write (0x90); //note ON, channel 1,
Serial.write (noteNumber);
Serial.write (0); //zero velocity = turn OFF note
noteOnArray[noteNumber] = 0; //note now OFF
}
}
}
//Expression pedal input:
//Voltage inputs from 0 to 5V are converted to a 10 bit integer from 0 to 1023 by analog to digital converter.
//Adding 1 and dividing by 32, yields an integer from 0 to 32. This is used as an index into the controllerArray
//to select the controller value to be output.
void scanExpressionPedal1()
{
newExpression1 = analogRead (69); //0 to 1023
newExpression1 = (newExpression1 + 1) / 32; //0 to 32
if ((newExpression1 != oldExpression1) && (newExpression1 != oldOldExpression1)) // double check avoids jitter
{
oldOldExpression1 = oldExpression1;
oldExpression1 = newExpression1;
newExpression1 = controllerArray [newExpression1]; //extract controller value from array
if (newExpression1 > 127)
{
newExpression1 = 127; //correct out of range controller value
}
Serial.write (0xB0); //controller (channel 1)
Serial.write (0); //control number zero
Serial.write (newExpression1);
}
}
//Process data received for serial port 0 (from preceding Arduino): status, note number, velocity
//Note: this bare bones version does not handle running status.
void processSerial ()
{
if (Serial.available())
{
noteStatusRx = Serial.read ();
if (noteStatusRx > 0x7F) //is status byte
{
while (!Serial.available()) {} //wait for serial data, port 0
noteNumberRx = Serial.read ();
while (!Serial.available()) {} //wait for serial data, port 0
noteVelocityRx = Serial.read ();
Serial.write (noteStatusRx + 1); //output on port 0, one channel up
Serial.write (noteNumberRx);
Serial.write (noteVelocityRx);
}
}
}
void trace (byte info) //used during debugging
{
Serial.write (0xF3);
Serial.write (info);
}