"TOY" Sequencer

/*

 "Toy" MIDI Sequencer

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

 Connected (DIN MIDI) between the output of a VPO and the input of a computer (HW), this little Arduino

 Mega based sequencer is always ON recording all MIDI messages. 

 By pressing Top B, what has been recorded will be played back.

 Pressing Top C clears what has been recorded and sets pointers back to the beginning.

 While the recording is being played, anything being played on the keyboard will continue to be recorded

 but at the end of the current file.

 It also records and "plays back" pistons and stops if they are hard wired.

 It will record up to 1800 MIDI events.

 Note that pauses in the recording longer than 4 seconds get elided. I.e., You can play a Tocatta, go have a

 coffee and come back to play the Fugue leaving everything running while gone. This is a side effect of using

 only a single byte to store the time.

 

 This sketch records and plays back a series of note ON/OFF MIDI events. With each event

 is stored the timeCount at which the event occurs. timeCount is a one byte counter running from 

 0 to 255 which is clocked by an overflow interrupt derived from the 8-bit timer2.

 period = (1/16E6)*256*1024 = 16 ms, i.e. freq. = 61 Hz

 16E6 = Arduino clock frequency

 256 =  8-bit timer2 counts from 0 to 255 with a preset of 0

 1024 = prescale factor

 Thus timeCount has a period of 256 * 16 ms = 4.17 s. Therefore pauses (or chords) lasting longer than

that get elided.


Equipment: Arduino Mega with MIDI shield (256 kB Flash, 8 kB SRAM)


 created 2017 Jan. 9

 modified 2023 Mar. 5 testing successful. (Expanded to all channels)

 by John Coenraads 

 */

 

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


#include <avr/interrupt.h>  

#include <avr/io.h>


byte i,j,k;        //counters, Old Fortran habit


// data storage array when recording and the pointers into it.

//column 0 = timeCount, 1 = noteStatus, 2 = noteNumber, 3 = noteVelocity

byte midiData [1802][4];

volatile int playPointer = 1;        // values 1 to 1000

int recordPointer = 1;


// timeCount times MIDI events. Volatile because it changes in Interrupt Service Routine.

volatile byte timeCount = 0;


//Transmit variables

volatile byte noteStatusTx;

volatile byte noteNumberTx;

volatile byte noteVelocityTx;


//Receive variables

byte noteStatusRx;

byte noteNumberRx;

byte noteVelocityRx;


//Flags set when Play or Record is selected 

byte recordMode = 1;               //record mode is always ON

volatile byte playMode = 0;        //set when Top A#,  Note # 94 is pressed = Play


//Flag to keep track of last valid status (90, 80)

byte fetchByte = 0;


//Timer2 Overflow Interrupt Vector, called every 16 ms

ISR(TIMER2_OVF_vect) 

{

  timeCount++;               //Increment the interrupt counter

  TCNT2 = 0;                 //Reset Timer to 0 out of 255

  TIFR2 = 0x00;              //Timer2 INT Flag Reg: Clear Timer Overflow Flag

  

  if (playMode != 0)          //play data in data file

  {

      while (timeCount == midiData [playPointer][0])

      {

          noteStatusTx = midiData [playPointer][1];

          noteNumberTx = midiData [playPointer][2];

          noteVelocityTx = midiData [playPointer][3];

          Serial.write (noteStatusTx);

          Serial.write (noteNumberTx);

          Serial.write (noteVelocityTx);

          

          playPointer++;

          if (playPointer >= recordPointer)

          {

              playPointer = 1;

              playMode = 0;

              break;

          }

      }

   }

}  


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

void setup() 

{

    //  Set MIDI baud rate:

    Serial.begin(31250);

  

    //Perform a general clear

    genClear();


  //Set up Timer2 to fire every 16 ms

  TCCR2B = 0x00;        //Disable Timer2 while we set it up

  TCNT2  = 0;           //Reset Timer Count to 0 out of 255

  TIFR2  = 0x00;        //Timer2 INT Flag Reg: Clear Timer Overflow Flag

  TIMSK2 = 0x01;        //Timer2 INT Reg: Timer2 Overflow Interrupt Enable

  TCCR2A = 0x00;        //Timer2 Control Reg A: Normal port operation, Wave Gen Mode normal

  TCCR2B = 0x07;        //Timer2 Control Reg B: Timer Prescaler set to 1024

}


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

//Main loop waits for note ON or OFF messages and collects noteStatus, noteNumber and noteVelocity


void loop() 

{

    fetchByte = Serial.read ();

    if (fetchByte < 0xA0)                //is status byte or data byte

    {

        if (fetchByte > 0x7F)           //is Status byte

        {

            noteStatusRx = fetchByte;

            while (!Serial.available()) {}                //wait for serial data, port 0

            noteNumberRx = Serial.read ();

            while (!Serial.available()) {}                //wait for serial data, port 0

            noteVelocityRx = Serial.read ();

            processDataRx();

         }   

         else                            //data byte

         {                         

            noteNumberRx = fetchByte;

            while (!Serial.available()) {}                //wait for serial data, port 0

            noteVelocityRx = Serial.read ();

            processDataRx();

         }

     }    

}


//General Clear Procedure ===============================================

void genClear() 

{

    /*temporary trace**************************************************************************

    trace(100);

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

        {trace (i);trace (midiData [i][0]);trace (midiData [i][1]);trace (midiData [i][2]);trace (midiData [i][3]);}

    temporary trace**************************************************************************

    */

    

    // Send out an "All Notes Off" on all channels 

    turnNotesOff(); 

    

    //Clear serial input buffer.

    while (Serial.available() > 0)

    {

        Serial.read();

    }

    

    //Clear status buffer

    fetchByte = 0;

    

    //Reset Record and Play flags

    recordMode = 1;      //Always ON

    playMode = 0;       

   

   //Reset midiData array pointers

    playPointer = 1;        

    recordPointer = 1; 

}


//Process Data Received ===================================

void processDataRx() 

{

    switch (noteNumberRx)

    {

        case 96:                   // C 

          genClear();        break;

        case 95:                   // B 

          turnNotesOff();

          playMode = 1;   

          playPointer = 1;   break;

        default:  

            //Transmit data        

            Serial.write (noteStatusRx);

            Serial.write (noteNumberRx);

            Serial.write (noteVelocityRx);

            

            //Record data

            if (recordMode != 0)

            {

                midiData [recordPointer] [0] = timeCount;

                midiData [recordPointer] [1] = noteStatusRx;

                midiData [recordPointer] [2] = noteNumberRx;

                midiData [recordPointer] [3] = noteVelocityRx;

                recordPointer++;

                if (recordPointer > 1800)

               {

                  recordMode = 0;

                  recordPointer = 1800;

               }

            } 

        break;    

    }

}         


//Turn all notes off, all channels ===================================

void turnNotesOff() 

{

    for (i = 0; i < 16; i++)

        {Serial.write(0xB0 + i);

        Serial.write(0x7B);

        Serial.write(0x00);}

}        


// trace procedure 


void trace (byte info)

{

    Serial.write (0xF3);

    Serial.write (info);

}