Pipe Organ "Front End"
/*
MIDI Pipe Organ Controller
==========================
Custom software to operate DC four rank unit organ.
Runs on Arduino Mega 2560.
This sketch accepts MIDI input from any keyboard (channel 1).
Depending on the stops selected, the Arduino performs
unification and duplexing and then outputs the data onto
channels 4 (Blockflote 2), 5 (Quintadena 8), 6 (Principal 4)and 7 (Gedeckt 8)
for transmission to four MIDI driver boards (64 outputs each).
Top C acts as a general cancel after which the user selects stops
by pressing keys in the top octave. Playing a note outside this octave
causes the top octave to revert back to its normal playing function.
Equipment: Arduino Mega with MIDI shield
created 2016 Oct. 12
modified 2016 Dec. 18
by John Coenraads
*/
// Declarations==========================================
//Counter (old Fortran habit)
int i, j, k;
//Transmit variables
byte noteStatusTx;
byte noteNumberTx;
byte noteVelocityTx;
//Receive variables
byte noteStatusRx;
byte noteNumberRx;
byte noteVelocityRx;
//Flag set when general cancel (Top C, Note # 96) is pressed.
byte setStops = 0;
const byte genCan = 96;
//Flag to keep track of last valid status (90, 80)
byte statusBuffer = 0;
//Array to keep track of which pipes are on or off. Zero = OFF.
//Values are incremented when a note ON message is received.
//Values are decremented when a note OFF message is received.
//Col. 0 = Gedeckt, 1 = Principal, 2 = Quintaton, 3 = Blockflote.
//
byte pipeArray [96][4];
//Array to track which stops are selected.
//Col. 0 = Gedeckt, 1 = Principal, 2 = Quintaton, 3 = Blockflote.
//Up to four stops per rank.
//Each cell contains offset, e.g., -5, 0, 7, 12, 19, etc, to be added
// to noteNumber. But if 1, no stop selected.
//
byte manStops [4][4];
int turnOnOff; // 1 = turn on, -1 = turn off
//Initialize =========================================================
void setup()
{
// Set MIDI baud rate:
Serial.begin(31250);
//Perform a general cancel
genCancel();
}
//Main Loop ===========================================================
void loop()
{
if (Serial.available())
{
noteStatusRx = Serial.read ();
if (noteStatusRx > 0x7F) //is status byte
{
if ((noteStatusRx == 0x90) || //channel 1, note ON
(noteStatusRx == 0x80)) //channel 1, note OFF
{
statusBuffer = noteStatusRx;
while (!Serial.available()) {} //wait for serial data
noteNumberRx = Serial.read ();
while (!Serial.available()) {} //wait for serial data
noteVelocityRx = Serial.read ();
processDataRx();
}
else //not 90 or 80
{
statusBuffer = 0;
}
}
else //is data byte
{
if (statusBuffer != 0)
{
noteNumberRx = noteStatusRx;
noteStatusRx = statusBuffer;
while (!Serial.available()) {} //wait for serial data
noteVelocityRx = Serial.read ();
processDataRx();
}
}
}
}
//Process data received: status, note number, velocity
void processDataRx ()
{
if (noteNumberRx == genCan)
{
setStops = 1;
genCancel(); //Perform a general cancel
}
else
{
if (setStops == 1) processStop();
if (setStops == 0) processNote();
}
}
//General Cancel Procedure ===============================================
void genCancel()
{
// Send out an "All Notes Off" on channels 4,5,6,7
Serial.write(0xB3);
Serial.write(0x7B);
Serial.write(0x00);
Serial.write(0xB4);
Serial.write(0x7B);
Serial.write(0x00);
Serial.write(0xB5);
Serial.write(0x7B);
Serial.write(0x00);
Serial.write(0xB6);
Serial.write(0x7B);
Serial.write(0x00);
//Turn all stops off.
//1 = Off. Any other number is offset to
//be added to noteNumber
for (i = 0; i < 4; i++)
{
manStops[i][0] = 1;
manStops[i][1] = 1;
manStops[i][2] = 1;
manStops[i][3] = 1;
}
//Clear pipe array: 0 = note is off
for (i = 0; i < 96; i++)
{
pipeArray[i] [0] = 0;
pipeArray[i] [1] = 0;
pipeArray[i] [2] = 0;
pipeArray[i] [3] = 0;
}
//Clear serial input buffer.
while (Serial.available() > 0)
{
Serial.read();
}
//Clear status buffer
statusBuffer = 0;
}
//Turn stops on as per top octave keys pressed ===================================
void processStop()
{
switch (noteNumberRx)
{
case 84: // C Gedeckt not used
manStops [3][0] = 1; break;
case 85: // C# Principal 2 2/3
manStops [2][1] = 7; break;
case 86: // D Principal not used
manStops [3][1] = 1; break;
case 87: // D# Blockflote 2 2/3
manStops [2][3] = -5; break;
case 88: // E Quintaton not used
manStops [3][2] = 1; break;
case 89: // F Gedeckt 8
manStops [0][0] = 0; break;
case 90: // F# Gedeckt 4
manStops [1][0] = 12; break;
case 91: // G Quintaton 8
manStops [0][2] = 0; break;
case 92: // G# Quintaton 4
manStops [1][2] = 12; break;
case 93: // A Principal 4
manStops [0][1] = 0; break;
case 94: // A# Principal 2
manStops [1][1] = 12; break;
case 95: // B Blockflote 2
manStops [0][3] = 0; break;
default: //Revert back to normal playing
setStops = 0;
}
}
//Turn note received On or Off ===========================================
void processNote ()
{
if ((noteStatusRx == 0x90) && // if 90
(noteVelocityRx != 0)) // and with non-zero note velocity
{
turnOnOff = 1; // turn on
noteVelocityTx = 64; // with medium velocity
}
else
{
turnOnOff = -1; // turn off (was 80, or 90 with zero velocity)
noteVelocityTx = 0; //by setting velocity to zero
}
for (j = 0; j < 4; j++)
{
if (manStops [j] [0] != 1) //Gedeckt
{
noteStatusTx = 0x96;
noteNumberTx = noteNumberRx + manStops [j][0];
if (noteNumberTx > 91) //If top note exceeded
{
noteNumberTx = noteNumberTx - 12; //play octave lower
}
pipeArray [noteNumberTx][0] = pipeArray [noteNumberTx][0] + turnOnOff;
if (((pipeArray [noteNumberTx][0] == 1) && (turnOnOff == 1)) ||
((pipeArray [noteNumberTx][0] == 0) && (turnOnOff == -1)))
{
Serial.write (noteStatusTx);
Serial.write (noteNumberTx);
Serial.write (noteVelocityTx);
}
}
if (manStops [j] [1] != 1) //Principal
{
noteStatusTx = 0x95;
noteNumberTx = noteNumberRx + manStops [j][1];
if (noteNumberTx > 91) //If top note exceeded
{
noteNumberTx = noteNumberTx - 12; //play octave lower
}
pipeArray [noteNumberTx][1] = pipeArray [noteNumberTx][1] + turnOnOff;
if (((pipeArray [noteNumberTx][1] == 1) && (turnOnOff == 1)) ||
((pipeArray [noteNumberTx][1] == 0) && (turnOnOff == -1)))
{
Serial.write (noteStatusTx);
Serial.write (noteNumberTx);
Serial.write (noteVelocityTx);
}
}
if (manStops [j] [2] != 1) //Quintaton
{
noteStatusTx = 0x94;
noteNumberTx = noteNumberRx + manStops [j][2];
if (noteNumberTx > 91) //If top note exceeded
{
noteNumberTx = noteNumberTx - 12; //play octave lower
}
if (noteNumberTx < 48) //Quintaton lacks bottom octave
{
noteStatusTx = 0x96; //Borrow bottom octave from Gedeckt
}
pipeArray [noteNumberTx][2] = pipeArray [noteNumberTx][2] + turnOnOff;
if (((pipeArray [noteNumberTx][2] == 1) && (turnOnOff == 1)) ||
((pipeArray [noteNumberTx][2] == 0) && (turnOnOff == -1)))
{
Serial.write (noteStatusTx);
Serial.write (noteNumberTx);
Serial.write (noteVelocityTx);
}
}
if (manStops [j] [3] != 1) //Blockflote
{
noteStatusTx = 0x93;
noteNumberTx = noteNumberRx + manStops [j][3];
if (noteNumberTx > 91) //If top note exceeded
{
noteNumberTx = noteNumberTx - 12; //play octave lower
}
pipeArray [noteNumberTx][3] = pipeArray [noteNumberTx][3] + turnOnOff;
if (((pipeArray [noteNumberTx][3] == 1) && (turnOnOff == 1)) ||
((pipeArray [noteNumberTx][3] == 0) && (turnOnOff == -1)))
{
Serial.write (noteStatusTx);
Serial.write (noteNumberTx);
Serial.write (noteVelocityTx);
}
}
}
}
// trace procedure
void trace (byte info)
{
Serial.write (0xF3);
Serial.write (info);
}