-
Final Log
09/28/2017 at 05:09 • 0 commentsFinal Log
The processing code work just fine off the Arduino. Go figure?
I have written a tool that takes a MIDI file and converts it to an Arduino sketch:
- Midi2Ardunio.exe
I have not 100% tested everything so if something is not working lot me know and I will fix it. I have put it in my Files area along with a MIDI (Popcorn) and the Arduino sketch:
- SimpleMidiPlayer.ino
I have put a sample MIDI (popcorn), a recording of the Arduino playing it.
---
Spoke to soon. The very next file I tested crashed!
Took all afternoon until I realised that I MUST open binary files with "rd" and not "r", otherwise 0x0A is interpreted as a "CR" and the next char is dropped.
Okay, amended executable uploaded.
---
Regards AlanX
-
A Working MIDI Player
09/27/2017 at 02:33 • 0 commentsA 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
-
Using a Counter for the ISR
09/25/2017 at 15:27 • 0 commentsUsing a counter for the ISR
I received a suggestion to use a counter and trip method for the Interrupt Service Routine (ISR). While is was not keen as the method (Direct Digital Synthesis) is pretty standard and gives the best long term accuracy. But that is not the whole story as this accuracy comes at the cost of sub-harmonics on the higher frequencies. Anyway I decided to have a look at it. First the speadsheet calculations:
As you can see the DDS (magic) method is more accurate especially at high frequencies and around the frequencies of most interest (middle C and A), than the count and trip method. The greatest fault of the count and trip is it cannot play some keys above 1.1 kHz.
The ISR is running at 31275.5kHz which leaves 31 us for ISR code execution time. Not a lot! You can go faster which would push these problems into higher frequencies but really for most music that is going to be played on an Arduino, it does not matter.
The second test was to listen to the tones. In this case the Count and Trip method has a much cleaner sound. At high frequencies the sub-harmonics of the DDS method are quite obvious. Again, does not matter for most of the music that is going to be played on an Arduino.
Here is the code for the Count and Trip version:
/* A Concurrent Key/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 TickDuration 1 // 1 ms const unsigned int Key2Trip[128] = { 1913, 1805, 1704, 1608, 1518, 1433, 1352, 1277, 1205, 1137, 1073, 1013, 956, 903, 852, 804, 759, 716, 676, 638, 602, 569, 537, 507, 478, 451, 426, 402, 380, 358, 338, 319, 301, 284, 268, 253, 239, 226, 213, 201, 190, 179, 169, 160, 151, 142, 134, 127, 120, 113, 107, 101, 95, 90, 85, 80, 75, 71, 67, 63, 60, 56, 53, 50, 47, 45, 42, 40, 38, 36, 34, 32, 30, 28, 27, 25, 24, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 13, 12, 11, 11, 10, 9, 9, 8, 8, 7, 7, 7, 6, 6, 6, 5, 5, 5, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1 }; // 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 count=0; volatile unsigned int count0=0; volatile unsigned int count1=0; volatile unsigned int count2=0; volatile unsigned int count3=0; volatile unsigned int count4=0; volatile unsigned int count5=0; volatile unsigned int trip=31; volatile unsigned int trip0=0; volatile unsigned int trip1=0; volatile unsigned int trip2=0; volatile unsigned int trip3=0; volatile unsigned int trip4=0; volatile unsigned int trip5=0; volatile unsigned long master=0; ISR(TIMER2_OVF_vect) { count++; count0++; count1++; count2++; count3++; count4++; count5++; if (count>=trip) { master++; count=0; } if (count0>=trip0) { if (trip0>0) PINC|=B00000001; count0=0; } if (count1>=trip1) { if (trip1>0) PINC|=B00000010; count1=0; } if (count2>=trip2) { if (trip2>0) PINC|=B00000100; count2=0; } if (count3>=trip3) { if (trip3>0) PINC|=B00001000; count3=0; } if (count4>=trip4) { if (trip4>0) PINC|=B00010000; count4=0; } if (count5>=trip5) { if (trip5>0) PINC|=B00100000; count5=0; } } void SetKey(unsigned int Channel,unsigned int Key) { if (Channel==0) trip0=Key2Trip[Key]; if (Channel==1) trip1=Key2Trip[Key]; if (Channel==2) trip2=Key2Trip[Key]; if (Channel==3) trip3=Key2Trip[Key]; if (Channel==4) trip4=Key2Trip[Key]; if (Channel==5) trip5=Key2Trip[Key]; // Turn it off if (Key==0) { if (Channel==0) trip0=0; if (Channel==1) trip1=0; if (Channel==2) trip2=0; if (Channel==3) trip3=0; if (Channel==4) trip4=0; if (Channel==5) trip5=0; } } 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 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(); for (int i=0;i<128;i++) { SetKey(0,i); delay(100); } SetKey(0,0); } void loop() { delay(1000); digitalWrite(LED_BUILTIN,!digitalRead(LED_BUILTIN)); }
And here is the code for the DDS version:
/* A Concurrent Key/Tone Sketch Six channels: A0-A5 Procedure is SetTone(Pin,Key); Set Freq to zero to turn off tone Author: agp.cooper@gmail.com */ #define TickDuration 1 // 1 ms const unsigned int Key2Freq[128] = { 0, 9, 9, 10, 10, 11, 12, 12, 13, 14, 15, 15, 16, 17, 18, 19, 21, 22, 23, 24, 26, 28, 29, 31, 33, 35, 37, 39, 41, 44, 46, 49, 52, 55, 58, 62, 65, 69, 73, 78, 82, 87, 92, 98, 104, 110, 117, 123, 131, 139, 147, 156, 165, 175, 185, 196, 208, 220, 233, 247, 262, 277, 294, 311, 330, 349, 370, 392, 415, 440, 466, 494, 523, 554, 587, 622, 659, 698, 740, 784, 831, 880, 932, 988, 1047, 1109, 1175, 1245, 1319, 1397, 1480, 1568, 1661, 1760, 1865, 1976, 2093, 2217, 2349, 2489, 2637, 2794, 2960, 3136, 3322, 3520, 3729, 3951, 4186, 4435, 4699, 4978, 5274, 5588, 5920, 6272, 6645, 7040, 7459, 7902, 8372, 8870, 9397, 9956,10548,11175,11840,12544 }; // 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 Pin,unsigned int Freq) { if (Pin==0) magic0=Key2Freq[Freq]; if (Pin==1) magic1=Key2Freq[Freq]; if (Pin==2) magic2=Key2Freq[Freq]; if (Pin==3) magic3=Key2Freq[Freq]; if (Pin==4) magic4=Key2Freq[Freq]; if (Pin==5) magic5=Key2Freq[Freq]; // Turn it off if (Freq==0) { if (Pin==0) phase0=0; if (Pin==1) phase1=0; if (Pin==2) phase2=0; if (Pin==3) phase3=0; if (Pin==4) phase4=0; if (Pin==5) phase5=0; } } 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 // 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(); // Set tick duration (ms) magic=2095*TickDuration; for (int i=0;i<128;i++) { SetKey(0,i); delay(100); } SetKey(0,0); } void loop() { delay(1000); digitalWrite(LED_BUILTIN,!digitalRead(LED_BUILTIN)); }
Anyway, investigating the differences was interesting and worth while.Use the version of the code you prefer.
Regards AlanX
-
MIDI Coding
09/24/2017 at 08:41 • 2 commentsMIDI Coding
I can now generate concurrent audio tones on pins A0-5.
The Loading The MIDI source into the Arduino
The good way of doing this is to just load the binary as a C array directly into the Arduino sketch. It is quite compact allowing bigger MID files. I used "srec_cat" from https://sourceforge.net/projects/srecord/files/srecord-win32/.
Using "srec_cat.exe popcorn.mid -Binary -o popcorn.hex -C-Array MidiData", this creates an array:
/* http://srecord.sourceforge.net/ */ const unsigned char MidiData[] = { 0x4D, 0x54, 0x68, 0x64, 0x00, 0x00, 0x00, 0x06, 0x00, 0x01, 0x00, 0x04, ... 0x0A, 0x40, 0x00, 0x5D, 0x00, 0x00, 0x07, 0x64, 0x00, 0x5B, 0x00, 0x89, 0xEB, 0x7F, 0xFF, 0x2F, 0x00, }; const unsigned long MidiData_termination = 0x00000000; const unsigned long MidiData_start = 0x00000000; const unsigned long MidiData_finish = 0x00002A4D; const unsigned long MidiData_length = 0x00002A4D; #define MIDIDATA_TERMINATION 0x00000000 #define MIDIDATA_START 0x00000000 #define MIDIDATA_FINISH 0x00002A4D #define MIDIDATA_LENGTH 0x00002A4D
Which is stored in program space using PROGMEM:
/* PopCorn */ #include <avr/pgmspace.h> const byte PROGMEM MidiData[] = { 0x4D, 0x54, 0x68, 0x64, 0x00, 0x00, 0x00, 0x06, 0x00, 0x01, 0x00, 0x04, ...
Magic now I just have to process the code.
The Processing Code
Now I need to consider the MIDI processing system.
It may seem simple, just turn on or off tones with duration but the MIDI code has to be queued and executed at the right time (the event). To do that I will need a master or global "tick counter" and a "next event counter" for each channel.
The masker tick counter would be handled by the ISR. Instead of a frequency, a duration would be set. The ISR would increment a global or master tick counter upon timeout.
The next event counter would be incremented with duration as channel data is processed. Then no new data would be processed until the master tick equals or exceeds the next event counter.
Decoding MIDI Files
The MIDI code format is not that difficult but it will take time to write the decoder.
The webpage describes the format:
https://www.csie.ntu.edu.tw/~r92092/ref/midi/
The above MIDI file description is really good! As you read the description it answers the questions in your mind.
At first I was horrified by the variable data format but once I understood how simple it was I am converted!
Reading the Header
Here is the code so far, it reads the header and exits (I have clipped the MIDI data array to keep it short):
/* A Concurrent Tone Sketch Six channels: A0-A5 Maximum frequency is 5957 Hz Procedure is SetTone(Pin,Freq); Set Freq to zero to turn off tone Author: agp.cooper@gmail.com */ /* PopCorn */ #include <avr/pgmspace.h> const byte PROGMEM MidiData[] = { 0x4D, 0x54, 0x68, 0x64, 0x00, 0x00, 0x00, 0x06, 0x00, 0x01, 0x00, 0x04, ... 0x0A, 0x40, 0x00, 0x5D, 0x00, 0x00, 0x07, 0x64, 0x00, 0x5B, 0x00, 0x89, 0xEB, 0x7F, 0xFF, 0x2F, 0x00, }; const unsigned long MidiData_termination = 0x00000000; const unsigned long MidiData_start = 0x00000000; const unsigned long MidiData_finish = 0x00002A4D; const unsigned long MidiData_length = 0x00002A4D; #define MIDIDATA_TERMINATION 0x00000000 #define MIDIDATA_START 0x00000000 #define MIDIDATA_FINISH 0x00002A4D #define MIDIDATA_LENGTH 0x00002A4D // 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 */ #define TickDuration 1 // 1 ms 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 SetTone(unsigned int Pin,unsigned int Freq) { // Max Frequency is 5957 Hz if (Pin==0) magic0=4*(Freq*11/21); if (Pin==1) magic1=4*(Freq*11/21); if (Pin==2) magic2=4*(Freq*11/21); if (Pin==3) magic3=4*(Freq*11/21); if (Pin==4) magic4=4*(Freq*11/21); if (Pin==5) magic5=4*(Freq*11/21); // Turn it off if (Freq==0) { if (Pin==0) phase0=0; if (Pin==1) phase1=0; if (Pin==2) phase2=0; if (Pin==3) phase3=0; if (Pin==4) phase4=0; if (Pin==5) phase5=0; } } void setup() { // 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 byte Data0; byte Data1; byte Data2; byte Data3; // Decoding varaibles unsigned long DeltaTick=0; unsigned long TrackPtr[6]; 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 // 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(); // Set tick duration (ms) magic=2095*TickDuration; /* 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&0x80==0x80) BadHeader=true; if (!BadHeader) { // Set remaining track pointers for (trk=0;trk<6;trk++) { if (trk<nTrks) { 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; if (trk+1<6) { 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); TrackPtr[trk+1]=TrackPtr[trk]+((unsigned int)Data3<<24)+((unsigned int)Data2<<16)+((unsigned int)Data1<<8)+Data0+8; } } } } Serial.begin(9600); delay(100); if (!BadHeader) { Serial.println("Header Okay:"); } else { Serial.println("Header Bad?:"); } Serial.print(" Format "); Serial.println(format); Serial.print(" Tracks "); Serial.println(nTrks); Serial.print(" Division "); Serial.println(division); delay(100); if (!BadTrack) { Serial.println("Track Headers Okay:"); } else { Serial.println("Track Headers Bad?:"); } for (trk=0;trk<6;trk++) { if (trk<nTrks) { Serial.print(" TrackPtr"); Serial.print(trk); Serial.print(" "); Serial.println(TrackPtr[trk]); } } Serial.end(); } void loop() { delay(1000); digitalWrite(LED_BUILTIN,!digitalRead(LED_BUILTIN)); }
And this is what I get:
Header Okay: Format 1 Tracks 4 Division 480 Track Headers Okay: TrackPtr0 14 TrackPtr1 715 TrackPtr2 6511 TrackPtr3 10781
Magic.
I should probably point out that the MIDI standard for format "1" is that Track 1 contains the "Tempo Map", so there is only three music tracks in this file (Tracks 2-4).
The MIDI Standard
The track "data" format is:
[<Variable Length "Delta Time"><Variable Length "Event">]*
The MIDI Standard describes lots of interesting "Events" that can be done but I am only interested in tones, so I need to write code to recognise and skip non-tone events.
Variable Length Delta Time
Rather than describe it, here is a short code fragment:
{ unsigned long DeltaTime; byte Data; Data=NextByte(); DeltaTime=Data&0x7F; while (Data>=0x80) { Data=NextByte(); DeltaTime=DeltaTime<<7; DeltaTime+=Data&0x7F; } }
TBC ...
AlanX
-
The ISR
09/24/2017 at 02:39 • 0 commentsThe Interrupt Service Routine
This is the most important bit. Its code I originally wrote for a modem project.
The ISR is triggered 31372.5 times a second which is about as fast as the Arduino can sensibly go. At this speed you only have 31 us to do your stuff (e.i. the quick or the dead!). I may have to slow it down but lets try fast first.
Here is the basic code:
/* A Concurrent Tone Sketch Six channels: A0-A5 Maximum frequency is 5957 Hz Procedure is SetTone(Pin,Freq); Set Freq to zero to turn off tone Author: agp.cooper@gmail.com */ // 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 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 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; ISR(TIMER2_OVF_vect) { 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 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 // 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 (31.25 kHz), 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(); // Test tones SetTone(0,13); SetTone(1,51); SetTone(2,103); SetTone(3,171); SetTone(4,200); SetTone(5,300); delay(10000); // Turn off test SetTone(0,0); SetTone(1,0); SetTone(2,0); SetTone(3,0); SetTone(4,0); SetTone(5,0); } void SetTone(unsigned int Pin,unsigned int Freq) { // Max Frequency is 5957 Hz if (Pin==0) magic0=4*(Freq*11/21); if (Pin==1) magic1=4*(Freq*11/21); if (Pin==2) magic2=4*(Freq*11/21); if (Pin==3) magic3=4*(Freq*11/21); if (Pin==4) magic4=4*(Freq*11/21); if (Pin==5) magic5=4*(Freq*11/21); // Turn it off if (Freq==0) { if (Pin==0) phase0=0; if (Pin==1) phase1=0; if (Pin==2) phase2=0; if (Pin==3) phase3=0; if (Pin==4) phase4=0; if (Pin==5) phase5=0; } } void loop() { delay(1000); digitalWrite(LED_BUILTIN,!digitalRead(LED_BUILTIN)); }
Well, it works so a good start (it helps if you have existing code to hack).
The code sets different frequencies on A0-5 for 10 seconds and then turns off.
The LED flashes when done.
My multimeter show the frequency to be +/- 1 Hz for the frequencies tested.
The code has a limit of 5957 Hz because of a quick and dirty magic number calculations.
This can be fixed but it is pretty unlikely any music midi would go this high.
Next?
I think I have to add the note duration to the sketch.
AlanX