Close

A Working MIDI Player

A project log for Simple Concurrent Tones for the Arduino

The Arduino tone library does not support concurrent multiple tones. Now I am sure someone has done this before but here is my take.

agpcooperagp.cooper 09/27/2017 at 02:330 Comments

A Working MIDI Player

Not quite, I had to hack Track 5 (noise) to get it to work properly, something is upsetting Channel 0?

So hook up two 100R speaker to A1 and A2 (Channel 1 and 2) and listen to Popcorn.

Source? http://www.mediafire.com/file/pocsc984peahdod/popcorn-GXSCC_version.mid

Hint: I uploaded the full Arduino sketch to Files.

Here is the code (with most of the MIDI removed):

/*
   A Concurrent Tone Sketch
   Six channels: A0-A5
   Procedure is SetKey(Pin,Key);
   Setq Key to 0 to turn off sound
   Middle C is Key 60   
   Author: agp.cooper@gmail.com
*/
#define MaxChannels 6  // A0-5
#define TickDuration 1 // 1 ms 
#include <avr/pgmspace.h>
/* popcorn-GXSCC_version.mid */
const byte PROGMEM MidiData[] = {
0x4D, 0x54, 0x68, 0x64, 0x00, 0x00, 0x00, 0x06, 0x00, 0x01, 0x00, 0x05,
...
0x40, 0x8D, 0x0F, 0xFF, 0x2F, 0x00,
};
const unsigned long MidiData_length = 0x00004752;
const unsigned int Key2Magic[128] = {
    0,     18,    19,    20,    22,    23,    24,    26,
   27,     29,    31,    32,    34,    36,    38,    41,
   43,     46,    48,    51,    54,    58,    61,    65,
   69,     73,    77,    81,    86,    91,    97,   103,
   109,   115,   122,   129,   137,   145,   154,   163,
   173,   183,   194,   205,   218,   230,   244,   259,
   274,   290,   308,   326,   345,   366,   388,   411,
   435,   461,   488,   517,   548,   581,   615,   652,
   691,   732,   775,   821,   870,   922,   977,  1035,
  1096,  1162,  1231,  1304,  1381,  1464,  1551,  1643,
  1740,  1844,  1954,  2070,  2193,  2323,  2461,  2608,
  2763,  2927,  3101,  3286,  3481,  3688,  3907,  4140,
  4386,  4647,  4923,  5216,  5526,  5854,  6202,  6571,
  6962,  7376,  7815,  8279,  8772,  9293,  9846, 10431,
 11051, 11709, 12405, 13142, 13924, 14752, 15629, 16558,
 17543, 18586, 19691, 20862, 22103, 23417, 24810, 26285
};
// Define various ADC prescaler
const byte PS_16=(1<<ADPS2);
const byte PS_32=(1<<ADPS2)|(1<<ADPS0);
const byte PS_64=(1<<ADPS2)|(1<<ADPS1);
const byte PS_128=(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0);
/* ISR: Tone on A0-5 */
volatile unsigned int magic=0;
volatile unsigned int magic0=0;
volatile unsigned int magic1=0;
volatile unsigned int magic2=0;
volatile unsigned int magic3=0;
volatile unsigned int magic4=0;
volatile unsigned int magic5=0;
volatile unsigned int phase=0;
volatile unsigned int phase0=0;
volatile unsigned int phase1=0;
volatile unsigned int phase2=0;
volatile unsigned int phase3=0;
volatile unsigned int phase4=0;
volatile unsigned int phase5=0;
volatile unsigned long master=0;
ISR(TIMER2_OVF_vect)
{
  if (phase<0x8000) {
    phase+=magic;
    if (phase>=0x8000) master++;
  } else {
    phase+=magic;
  }
  phase0+=magic0;
  phase1+=magic1;
  phase2+=magic2;
  phase3+=magic3;
  phase4+=magic4;
  phase5+=magic5;
  if (phase0<0x8000) PORTC&=B11111110; else PORTC|=B00000001;
  if (phase1<0x8000) PORTC&=B11111101; else PORTC|=B00000010;
  if (phase2<0x8000) PORTC&=B11111011; else PORTC|=B00000100;
  if (phase3<0x8000) PORTC&=B11110111; else PORTC|=B00001000;
  if (phase4<0x8000) PORTC&=B11101111; else PORTC|=B00010000;
  if (phase5<0x8000) PORTC&=B11011111; else PORTC|=B00100000;
}
void SetKey(unsigned int Channel,unsigned int Key)
{
  if (Channel==0) magic0=Key2Magic[Key];
  if (Channel==1) magic1=Key2Magic[Key];
  if (Channel==2) magic2=Key2Magic[Key];
  if (Channel==3) magic3=Key2Magic[Key];
  if (Channel==4) magic4=Key2Magic[Key];
  if (Channel==5) magic5=Key2Magic[Key];
  // Turn it off
  if (Key==0) {
    if (Channel==0) phase0=0;
    if (Channel==1) phase1=0;
    if (Channel==2) phase2=0;
    if (Channel==3) phase3=0;
    if (Channel==4) phase4=0;
    if (Channel==5) phase5=0;
  }
}
void PlayMIDI(void)
{
  // Header Variables
  unsigned int format=0;
  unsigned int nTrks=0;
  unsigned int trk;
  unsigned int division=0;
  bool BadHeader=false;
  bool BadTrack=false;
  // Temporary data variables
  unsigned char Data0;
  unsigned char Data1;
  unsigned char Data2;
  unsigned char Data3;
  // Decoding varaibles
  bool MoreData;
  unsigned char Data;
  unsigned int Key;
  unsigned long DeltaTime;
  unsigned long TrackPtr[128];
  unsigned long TrackLen[128];
  unsigned long TrackEnd[128];
  unsigned long TrackTime[128];
  unsigned char MIDIMsg[128];
  unsigned char Channel[128];
  bool TrackWait[128];
  /* Read MIDI Headers */
  // Header Id
  Data0=pgm_read_byte(MidiData+3);
  Data1=pgm_read_byte(MidiData+2);
  Data2=pgm_read_byte(MidiData+1);
  Data3=pgm_read_byte(MidiData+0);
  if ((Data3!='M')&&(Data2!='T')&&(Data1!='h')&&(Data0!='d')) BadHeader=true;
  // Header Records
  Data0=pgm_read_byte(MidiData+7);
  Data1=pgm_read_byte(MidiData+6);
  Data2=pgm_read_byte(MidiData+5);
  Data3=pgm_read_byte(MidiData+4);
  // Set the first track pointer
  TrackPtr[0]=Data0+8;
  // MIDI format
  Data0=pgm_read_byte(MidiData+9);
  Data1=pgm_read_byte(MidiData+8);
  format=((unsigned int)Data1<<8)+Data0;
  if ((format!=1)&&(format!=2)&&(format!=3)) BadHeader=true;
  // MIDI tracks
  Data0=pgm_read_byte(MidiData+11);
  Data1=pgm_read_byte(MidiData+10);
  nTrks=((unsigned int)Data1<<8)+Data0;
  // MIDI division (ticks per quarter staff)
  Data0=pgm_read_byte(MidiData+13);
  Data1=pgm_read_byte(MidiData+12);
  division=((unsigned int)Data1<<8)+Data0;
  // Only ticks per quarter staff accepted
  if ((division&0x8000)==0x8000) BadHeader=true; 
  for (trk=0;trk<nTrks;trk++) {
    Data0=pgm_read_byte(MidiData+TrackPtr[trk]+3);
    Data1=pgm_read_byte(MidiData+TrackPtr[trk]+2);
    Data2=pgm_read_byte(MidiData+TrackPtr[trk]+1);
    Data3=pgm_read_byte(MidiData+TrackPtr[trk]+0);
    if ((Data3!='M')&&(Data2!='T')&&(Data1!='r')&&(Data0!='k')) BadTrack=true;
    Data0=pgm_read_byte(MidiData+TrackPtr[trk]+7);
    Data1=pgm_read_byte(MidiData+TrackPtr[trk]+6);
    Data2=pgm_read_byte(MidiData+TrackPtr[trk]+5);
    Data3=pgm_read_byte(MidiData+TrackPtr[trk]+4);
    TrackLen[trk]=((unsigned int)Data3<<24)+((unsigned int)Data2<<16)+((unsigned int)Data1<<8)+Data0;
    if (trk+1<nTrks) TrackPtr[trk+1]=TrackPtr[trk]+TrackLen[trk]+8;
  }
  // Advance to start of data
  for (trk=0;trk<nTrks;trk++) {
    TrackPtr[trk]+=8;
    TrackEnd[trk]=TrackPtr[trk]+TrackLen[trk];
    TrackTime[trk]=0;
    TrackWait[trk]=false;
    MIDIMsg[trk]=0xF7;
    Channel[trk]=0;
  }
  
  if ((!BadHeader)&&(!BadTrack)) {
    Serial.begin(9600);
    Serial.print("Format ");
    Serial.println(format);
    Serial.print("Tracks ");
    Serial.println(nTrks);
    Serial.print("Division ");  
    Serial.println(division);    
    Serial.end();
    
    // Process the data
    master=0;
    MoreData=true;
    while (MoreData) {
      MoreData=false;
      for (trk=0;trk<nTrks;trk++) {
        if (TrackPtr[trk]<TrackEnd[trk]) {
          MoreData=true;
          if (!TrackWait[trk]) {
            // Read Delta Time (delay before Event)
            Data=pgm_read_byte(MidiData+TrackPtr[trk]++);
            DeltaTime=Data&0x7F;
            while (Data>=0x80) {
              Data=pgm_read_byte(MidiData+TrackPtr[trk]++);
              DeltaTime=DeltaTime<<7;
              DeltaTime+=Data&0x7F;
            }
            // Start time of the Event
            TrackTime[trk]+=500*DeltaTime/division; // Convert to milli-seconds
            if (DeltaTime>0) TrackWait[trk]=true;
          }
          // Test if Track Wait is over
          if (TrackTime[trk]<=master) TrackWait[trk]=false;
          if (!TrackWait[trk]) {
            // Read and execute Event
            Data=pgm_read_byte(MidiData+TrackPtr[trk]++);
            if (Data>=0x80) {
              MIDIMsg[trk]=Data&0xF0;
              Channel[trk]=Data&0x0F;
              Data=pgm_read_byte(MidiData+TrackPtr[trk]++);
            }
            if (MIDIMsg[trk]==0x80) {
              // Channel Voice Messages - Note off
              Key=0;
              if (Channel[trk]<MaxChannels) SetKey(Channel[trk],Key);
              TrackPtr[trk]++;                 // Skip Data
              
            } else if (MIDIMsg[trk]==0x90) {
              // Channel Voice Messages - Note on
              Key=Data;
              Data=pgm_read_byte(MidiData+TrackPtr[trk]++);
              if (Data==0) Key=0;
              if (Channel[trk]<MaxChannels) SetKey(Channel[trk],Key);
              
            } else if (MIDIMsg[trk]==0xA0) {
              // Channel Voice Messages - Polyphonic Key Pressure
              TrackPtr[trk]++;                 // Skip Data
              
            } else if (MIDIMsg[trk]==0xB0) {
              // Channel Voice Messages - Controller Change
              TrackPtr[trk]++;                 // Skip Data
              
            } else if (MIDIMsg[trk]==0xC0) {
              // Channel Voice Messages - Program Change
              
            } else if (MIDIMsg[trk]==0xD0) {
              // Channel Voice Messages - Channel Key Pressure
              
            } else if (MIDIMsg[trk]==0xE0) {
              // Channel Voice Messages - Pitch Bend
              TrackPtr[trk]++;                 // Skip Data
              
            } else if (MIDIMsg[trk]==0xF0) {
              if (Channel[trk]==0x00) {
                // Sysex Events(sysex data)
                TrackPtr[trk]+=Data;              // Skip Data
                
              } else if (Channel[trk]==0x07) {
                // Sysex Events (any data)
                TrackPtr[trk]+=Data;              // Skip Data
                
              } else if (Channel[trk]==0x0F) {
                // Meta Event
                Data=pgm_read_byte(MidiData+TrackPtr[trk]++); // Length
                TrackPtr[trk]+=Data;              // Skip Data
                
              }
            }
          }
        }
      }
    }
  }
}
void setup()
{
  pinMode(LED_BUILTIN,OUTPUT);
  pinMode(A0,OUTPUT);
  pinMode(A1,OUTPUT);
  pinMode(A2,OUTPUT);
  pinMode(A3,OUTPUT);
  pinMode(A4,OUTPUT);
  pinMode(A5,OUTPUT);
  pinMode(11,OUTPUT); // Used as a frequency check
    
  /* Start ISR */
  // Set tick duration (ms)
  magic=2095*TickDuration;
  
  // Set ADC prescaler (assume a 16 MHz clock)
  ADCSRA&=~PS_128;                                 // Remove bits set by Arduino library
  ADCSRA|=PS_16;                                   // 16 prescaler (1 MHz)
  // Disable interrupts
  cli();
  
  // Use Timer 2 for ISR (Output on D11 for frequency check)
  // Good for ATmega48A/PA/88A/PA/168A/PA/328/P
  TIMSK2 = 0;                                      // Timer interrupts off
  TCCR2A = (2 << COM2A0)|(1 << WGM20);             // Phase correct PWM (31275.5 Hz), toggle output on OC2A (PB3/D11)
  TCCR2B = (0 << WGM22)|(1 << CS20);               // 16 MHz clock (no pre-scaler)
  OCR2A = 128;                                     // Set 50% duty
  TIMSK2 = (1<<TOIE2);                             // Set interrupt on overflow (=BOTTOM)
  // Enable interrupts 
  sei();   
  PlayMIDI();
}
void loop()
{
  delay(1000);
  digitalWrite(LED_BUILTIN,!digitalRead(LED_BUILTIN));
}

Minor Update

I can't find the problem with track 5 (i.e. trk 4). It uses channel 9 which is ignored as the maximum channel number is 5  (i.e. 0-5). If I block the track (i.e. trk!=4) it works really well.

It's not processor overload as the problem persists with trk!=3. I moved a division out of the MIDI processing loop, still no improvement.

The Keys being received by Key2Magic are within range for the file (31-81) but some "chirps" are generated. This means the ISR is not 100% working as expected.

It may be the ISR missing updates??? Perhaps a volatile issue??

I do know that if I use "too many" Serial.print() in the program will not play (does not matter if they are at the beginning or the end of the file, even if I close the Serial. Again very strange, a compiler problem?

I am not even close to using all the memory (static or program)?

The MIDI processor itself looks good. The delta-times look okay and the events used look okay. The tracks finish okay. Manual checks of the read look okay.

---

Anyway, I have uploaded the 3 speaker version (A0-2). It is pretty impressive still (compared to a monotone).

Fixed

I pre-processed the MIDI file (using basically the same processing code!), the resulting file is actually smaller and works perfectly.

The processing code may have a timing error but I am not sure. For the new code I generated byte size delays but had to exclude events not exported to the Arduino.

Further Work

All more you can do particularly on the software side, but I have achieved my objective.

Project closed!

AlanX


Discussions