/*
Parallel-Wired Arduino Scanner ACTIVE HIGH for analog Allen organs
==========================
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 *****
Note **********that because the switching is active HIGH, the sketch will put out note ON messages for all inputs that are not connected
to the level matching circuitry or to ground when the program starts up. Any unused inputs should therefore be connected directly to
ground. Until qll the inputs are connected it is best to connect to ones computer only after the the Arduino has started up.
All switches connected to the digital inputs are to be connected to B+ voltage of around 15V, voltage is not critical.
A level matching circuit using a 10k resistor and a 1N4001 diode limits the voltage to 5V.
The output of the leel matching circuit 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
A MIDI shield is connected to the last Arduino in the chain. (Power, ground and TX0)
Equipment: Arduino Mega and MIDI shield
created 2026 Aug 08
modified 2026 Aug 08
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 oldOldExpression = 0, oldExpression = 0, newExpression = 0; // Expression controller values
// The following array represents the controller values output for analog device voltages
// in 0.31V increments ranging from 0 to 5V. Input must never exceed 5V.
// Array can be edited as desired, but must contain exactly 17 entries with values between 0 and 127 only.
byte controllerArray [17] = {0, 8, 16, 24, 32, 40, 48, 56, 64, 72, 80, 88, 96, 104, 112, 120, 127};
//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();
// scanExpressionPedal(); //remove comment slashes // to activateexpression pedal
processSerial();
Serial.flush();
}
//Scan inputs, 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 == LOW) //bit LOW, 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 == LOW) //bit LOW, 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 == LOW) //bit LOW, 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 64, yields an integer from 0 to 16. This is used as an index into the controllerArray
//to select the controller value to be output.
void scanExpressionPedal()
{
newExpression = analogRead (69); //0 to 1023
newExpression = (newExpression + 1) / 64; //0 to 16
if ((newExpression != oldExpression) && (newExpression != oldOldExpression)) // double check avoids jitter
{
oldOldExpression = oldExpression;
oldExpression = newExpression;
newExpression = controllerArray [newExpression]; //extract controller value from array
if (newExpression > 127)
{
newExpression = 127; //correct out of range controller value
}
Serial.write (0xB0); //controller (channel 1)
Serial.write (0); //number zero
Serial.write (newExpression);
}
}
//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);
}