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