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);

}