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
Become a Hackaday.io Member
Create an account to leave a comment. Already have an account? Log In.