ContentEncoder

/*

Content organ Stop Rail Encoder  

 ==============================================

This sketch is designed to scan 14 stops each with 14 LEDs, 14 momentary ON buttons, 14 momentary OFF buttons,

one analog input for expression pedal and multiple piston inputs.

Three Arduinos running this identical code are daisy chained through Port 0 to handle three divisions.


Content pins D0, D1, ... D7 map onto Arduino pins 22, 23, to 29.

These pins are sequentially brought LOW


The following pins are then scanned to determine which switches are closed. LOW = switch closed.

Content pins S4, S6, S0 and S2 map onto Arduino pins 32, 33, 34, 35.


Output is on channel 1 + baseChannel for the first Arduino and incremented by one for subsequent Arduinos.

 Stopswitches use note numbers 36 to 49

                        

LEDs connect to Arduino pins 38, 39, ... to 51


 Arduino pins 2 - 12,  14 - 21, 54 - 68, are piston inputs and uses note numbers starting at 60.

 Pistons are parallel wired and the switches share a common ground to the Arduino.


Arduino Pin 69 is for 1 analog input (expression pedal). 


Connect 15k (+/-) pot across Arduino's +5V and Ground. Centre tap goes to pin 69. Input must never exceed 5V. 

Note: ***** Active, but unused analog inputs, must be grounded to avoid spurious control messages *****

Alternatively, the call to the expression pedal procedures can just be left commented out as in the code below. 

Remove the comment (//) in front of the procedure call in the main loop to activate the input.


Note 1: ONLY the stops should reflect their messages back through MIDI OUT from HW. Do NOT reflect back anything else, e.g., the pistons

since this will create confusion.


Note 2: For testing start with one Arduino and hook up one stop rail. Mount the MIDI shield on the Arduino and conect to HW IN and OUT.

Connect LEDs + resistors to the LED output pins.


Note 3: If the initial test works, daisy chain another Arduino (call it A, making the original Arduino B) and change "numberArduinos" 

from 0 to 1. At this point you will have to remove the MIDI shield from B. Connect power and ground. Then Tx0 on B goes to TX on the

shield (output to HW), and Rx on the shield to Rx0 on Arduino A (Input from HW) thus creating a loop.


A single Arduino worked on the bench, I have not tried daisy chaining, so this needs to be beta tested.

                

Equipment: Arduino Mega with one MIDI shield mounted on the last Arduino. The MIDI shield IN and OUT ports

connect to the computer and HW.


 created 2025 JAN 24

 modified 2025 JAN 27    Adjusted piston channel numbers

 by John Coenraads 


*/

 

// Declarations==========================================


//Counters (old Fortran habit)

int i, j, k;

byte noteStatus;

byte noteNumber;        // Stop 1 = 36 ON and OFF

byte noteVelocity;

   

//Receive variables

byte noteStatusRx;

byte noteNumberRx;

byte noteVelocityRx;


const byte debounceCount = 4;    //Note ON if count reaches decounceCount, OFF if count reaches  0

byte pistonDebounceArray [100];          //holds debounce count for each piston switch


const byte baseChannel = 4;           //Channel number increment. Channel 1 + 4 = Channel 5, leaving 1, 2 and 3 for the keyboards

const byte numberArduinos = 0;        //Number of additional daisy chained Arduinos


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 controllerArray1 [33] = {1,4,8,12,16,20,24,28,32,36,40,44,48,52,56,60,64,68,72,76,80,84,88,92,96,100,104,108,112,116,120,124,127};     


//Initialize =========================================================

void setup() 

{

    //  Set MIDI baud rate:

    Serial.begin(31250); 

    //Serial.begin (9600);

    

    //Initialize input. Normally high (via internal pullups)

    for (i = 2; i < 69; i++)

        { pinMode (i, INPUT_PULLUP);}

        

       //Initialize  output (normally high) for scanning

    for (i = 22; i < 30; i++)

        {

           pinMode (i, OUTPUT);  

           digitalWrite (i, HIGH);

        }

        //Initialize  output (normally low) for LEDs

    for (i = 38; i < 52; i++)

        {

        pinMode (i, OUTPUT);  

        digitalWrite (i, LOW);

        }

}


//Main Loop ===========================================================

void loop() 

{

    scanStops();

    scanPistons();

    //scanExpressionPedal1();         // Arduino pin 69

    processSerial ();

}


//Scan 14 stops, convert to MIDI and output via port 0, channel 1.

void scanStops ()

     {

       for (i = 1; i < 9; i++)

          {

             digitalWrite (i + 21, LOW); 

             if  (digitalRead(32) == LOW) {turnOFF (35 +i); while (digitalRead(32) == LOW) {};delay (200);}

             if  (digitalRead(33) == LOW) {turnOFF (43 +i); while (digitalRead(33) == LOW) {};delay (200);}

             if  (digitalRead(34) == LOW) {turnON  (35 +i); while (digitalRead(34) == LOW) {};delay (200);}

             if  (digitalRead(35) == LOW) {turnON  (43 +i); while (digitalRead(35) == LOW) {};delay (200);}             

             digitalWrite (i + 21, HIGH);              

          } 

     } 

  

//MIDI ON message is sent turning stop ON

void turnON (byte stopNumber)

{

            Serial.write (0x90 + baseChannel);          //note ON, 

            Serial.write (stopNumber);

            Serial.write (0x7f);        

}


//MIDI OFF message is sent turning stop Off

void turnOFF (byte stopNumber)

{

            Serial.write (0x90 + baseChannel);          //note ON,

            Serial.write (stopNumber);

            Serial.write (0);          //zero velocity = OFF       

}


//*****************************************************************************



//Scan Pistons, convert to MIDI and output via port 0, channel 4.

//Piston feed is permanently wired to ground

void scanPistons ()

 {

    noteNumber = 60;    //starting note number

    

    for (i = 2; i < 13; i++)

        { if  (digitalRead(i) == LOW) {turnONpiston ();} else {turnOFFpiston ();}

             noteNumber++; }

        

    for (i = 14; i < 23; i++)

        { if  (digitalRead(i) == LOW) {turnONpiston ();} else {turnOFFpiston ();}

             noteNumber++; }

        

     for (i = 54; i < 69; i++)

        { if  (digitalRead(i) == LOW) {turnONpiston ();} else {turnOFFpiston ();}

             noteNumber++; }

 }

     

   

//MIDI ON message is sent only if note is not already ON.

void turnONpiston ()

{

    if (pistonDebounceArray[noteNumber] == 0)

        {

            Serial.write (0x90 + baseChannel);          //note ON, 

            Serial.write (noteNumber);

            Serial.write (0x7f);          //medium velocity

            pistonDebounceArray[noteNumber] = debounceCount;

        }   

}


//MIDI OFF message is sent only if note is not already OFF.

void turnOFFpiston ()

{

    if (pistonDebounceArray[noteNumber] == 1)

        {

           Serial.write (0x90 + baseChannel);          //note ON,   

           Serial.write (noteNumber);

           Serial.write (0);          //zero velocity = OFF

        }  

        if (pistonDebounceArray[noteNumber] > 0)  {pistonDebounceArray[noteNumber] -- ;}        

}


//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 (66);                      //0 to 1023

  newExpression1 = (newExpression1 + 1) / 32;             //0 to 32

  

  if ((newExpression1 != oldExpression1) && (newExpression1 != oldOldExpression1))       // double check avoids jitter

  {

    oldOldExpression1 = oldExpression1;

    oldExpression1 = newExpression1;

    newExpression1 = controllerArray1 [newExpression1];   //extract controller value from array

    

    if (newExpression1 > 127)

    {

      newExpression1 = 127;                             //correct out of range controller value

    }


    Serial.write (0xB0 + baseChannel);                               //controller (channel 4)

    Serial.write (0);                                  //control number 1

    Serial.write (newExpression1);

  }

}



  

//Process data received for serial port 0 (from preceding keyboard): 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 ();

      

      if ((noteStatusRx == 0x90 + baseChannel + numberArduinos)

          ||(noteStatusRx == 0x80 + baseChannel + numberArduinos))     

      {

        switchLED();

      }

      else

      {

        Serial.write (noteStatusRx + 1);              //output on port 0, one channels up

        Serial.write (noteNumberRx);

        Serial.write (noteVelocityRx);

      }

    }

  }

}


void switchLED()

{  if (noteVelocityRx > 0)

  {

     digitalWrite (noteNumberRx + 2, HIGH);    //first stop notenumber = 36, firsts LED on pin 38

  }

  else

  {

     digitalWrite (noteNumberRx + 2, LOW);

  }  

  if (noteStatusRx == 0x80 + baseChannel + numberArduinos)

  {

    digitalWrite (noteNumberRx + 2, LOW);

  }

}