// This code written mainly from information provided at // http://www.borg.com/~jglatt/tech/midifile/table.htm // The program has accreted by exploring a few MIDI files. // It reports details as it reads files. // I expect it to accrete more mods. // In text output the construction "at 0x..." gives a hexadecimal location // within the input file where something is located. // I call the command "midipeek". // gcc -Wmost -O3 peek.c -o ~/bin/midipeek // midipeek #include #include typedef unsigned char uchar; typedef unsigned int ui; int cc = 0; uchar h[16]; FILE * in = 0; int ex(int d){printf("Bye %d, at 0x%X\n", d, cc); if (in) {int j=16; while(j--) printf("%02X ", h[(cc-1-j)&15]); // report the last 16 characters that were read. printf("\n"); // and upcomming 16 too. j=16; while(j--) printf("%02X ", getc(in));} printf("\n"); exit(4);} uchar nc(){char x = getc(in); if(x == EOF && feof(in)) {printf("Unexpected end of file at 0x%X\n", cc); ex(6);} h[cc++&15] = x; return x;} ui gvl(){ui x = 0; z: {uchar c = nc(); x = (x<<7) | (c & 0x7f); if(c & 0x80) goto z;} return x;} float tf = 480; static void expect(char * s){while (*s) if(*(s++) != nc()) ex(1);} short ec[128]; // verbosity attenuator. // Each element of this array counts some sort of event. // Only first 8 occurances of such each sort for a given track is reported. // .... is printed when this limit is exceeded. // yet, below, is to delay start of counting in order to get later detailed report. ui Time; // computed by accumulating timing bytes in track. int den; ui yet=0; void TS(){printf("B%d.%4.2f at 0x%x ", Time/(4*den), (Time % (4*den))/(float)den, cc);} int t(char s){short * x = ec+s; if(Time < yet) return 0; if((*x)-- >0) putchar(s); if(!*x) printf("....%c\n", s); {char b = *x>=0; if(b) TS(); return b;}} void Expl(char * m, char r) {char s = t(r); int tl = gvl(); if(s) printf("%s, %d: ", m, tl); while (tl--) {uchar t = nc(); if(s) putchar(('!' <= t && t <= 'z') ? t : '.');} if(s) putchar('\n');} ui gn(int l) {ui k = 0; while(l--) k = (k<<8) | nc(); return k;} int main(int argc, char * * argv){ if(argc < 3) {printf("type ./a.out initialtime\n"); ex(42);} in = fopen(argv[1], "r"); expect("MThd"); if(gn(4) != 6) ex(3); // Needs a MThd . {int format = gn(2), tc = gn(2), tcc = tc; tf = den = gn(2); yet = tf*atoi(argv[2]); if(!(format == 1 || (format == 0 && tc == 1))) ex(2); printf("%d tracks, %d ticks/quarternote\n", tc, den); while(tc--) {Time=0; {int j=128; while(j--) ec[j] = 12;} ec['n'] = ec['f'] = 3; expect("MTrk"); {int ts = gn(4), hstrt = cc; uchar cst=-1; // Running channel status unless -1 uchar cchan=0x80; char * dp[16]; {int j=16; while(j--) dp[j]=0;} printf("Beginning track %d with %d bytes at 0x%X\n", tcc-tc, ts, cc-8); while(1){int sa=0, Sb=-1; //status byte address and byte void warn(){printf("WARNING: "); TS();} Time += gvl(); zx: {uchar sc = nc(); // xxxxcccc; x is order and c+1 is channel number void mcm(int ch){if(!dp[ch]) {char * p = (char *)malloc(128); if(!p) ex(27); {int j=128; while(j--) p[j] = 0;} dp[ch] = p;} cchan = ch; cst = 9;} if(sc & 0x80) { // Status byte sa = cc; Sb = sc; if(sc == 0xFF) {uchar sbtp = nc(); printf("Sub-type 0x%02x at 0x%X -> \n", sbtp, cc); switch(sbtp){ // http://www.omega-art.com/midi/mfiles.html#meta case 1: Expl("Text Event",'u'); break; case 2: Expl("Copyright",'v'); break; case 3: Expl("Sequence or Track name", 'x'); break; case 4: Expl("Instrument name", 'x'); break; case 32: {if(1 != gvl()) ex(18); printf("channel prefix %d\n", nc()); break;} case 33: {if(1 != gvl()) ex(18); printf("port prefix %d\n", nc()); break;} case 47: if(t('z')) printf("End of Track\n\n"); if(gvl()) ex(13); if(cc != hstrt+ts) ex(14); goto endTrk; case 81: { if(3 != gvl()) ex(11); {int tm = gn(3); if(t('m')) printf("Set Tempo: %d microseconds/quarter-note\n", tm); break;}} case 84: printf("SMPTE Offset: "); if(5 != gvl()) ex(12); // http://www.jososoft.dk/yamaha/articles/MIDI_10.htm {int j=5; char * unt[] = {" frames", ".", " sec ", " min ", " hrs "}; while(j--) printf(j?"%d%s":"%02d%s", nc(), unt[j]); printf("\n");} break; case 88: printf("Time Signature: "); // http://www.jososoft.dk/yamaha/articles/MIDI_10.htm if(4 != gvl()) ex(10); {int m[4]; int j=4; while(j--) m[j]=nc(); printf("%d/%d %d %d\n", m[3], (1<>4){ // the actions in this switch statement only initiate running status case 9: if(t('n')) printf("channel %d notes on:\n", 1+(sc&15)); mcm(sc&15); // cchan = sc&15; cst = 9; break; case 8: if(t('f')) printf("channel %d notes off: \n", 1+(sc&15)); if(!dp[sc&15]) ex(24); cchan = sc&15; cst = 8; break; case 11: {int fun = nc(), v=nc(); void px(){printf("Channel %d controller %d to %d: ", 1+(sc&15), fun, v);} switch(fun){ case 0: printf("Chan %d, Bank Select to %d\n", 1+(sc&15), v); break; case 1: px(); printf("Modulation wheel <- %d\n", v); break; case 6: px(); printf("Data Entry(?) <- %d\n", v); break; case 7: printf("Channel %d Volume to %d\n", 1+(sc&15), v); break; case 10: px(); printf("Pan(?) <- %d\n", v); break; case 91: px(); printf("Effects 1 Depth(?) <- %d\n", v); break; case 93: px(); printf("Effects 3 Depth(?) <- %d\n", v); break; case 100: px(); printf("Registered Parameter Number LSB(?) <- %d\n", v); break; case 101: px(); printf("Registered Parameter Number MSB(?) <- %d\n", v); break; default: px(); printf(" Unknown effet\n"); break;} goto endMessage;} case 12: printf("Channel %d changes to program %d\n", 1+(sc&15), nc()); cst=-1; goto endMessage; case 13: printf("Channel %d Channel aftertouch %d\n", 1+(sc&15), nc()); cst=-1; goto endMessage; case 14: printf("Channel %d reports pitch wheel at %04X\n", 1+(sc&15), nc() | (nc()<<7)); goto endMessage; // See // for meanings. case 15: {void sysx(char * s) {int len = gvl(); printf ("%s Sysx message with %d octets\n", s, len); while(len--) nc();} if(sc == 0xF0) {sysx("initial"); goto endMessage;} else if(sc == 0xF7) {sysx ("subsequent"); goto endMessage;}} // note fall thru from case 15! default: printf("Status byte = %02X!\n", sc); ex(17); } goto zx;}} else {// channel message; presumably running status char sb = nc(); // get second byte if(cchan & 0x80) {warn(); if(sc == 10 && sb == 0x40 && Time == 0) { printf("Ignoring mysterious message 0x0a40 in file\n"); // This accomodates . goto endMessage;} if(sa) printf("Note message with no running status in effect " "since status byte 0x%02x at 0x%x in file.\n", Sb, sa); else printf("Note message with no preceding status byte in track.\n"); printf("Faking channel 1, running status \"note down\"\n"); mcm(0);} // cst is channel status [4 scopes out] // sb is 2nd byte of message ("velocity" I think) [1 scope out] // dp array of currently depressed notes [5 scopes out] // cchan channel number -1 extracted from stream, or 80 if none [4 scopes out] // sc first message byte [2 scopes out] // printf("cst=%x\n", cst); // .... if(!(((cst == 9) && (sb ? (!(dp[cchan][sc]++) || ({warn(); printf("Twice downx: note=%d, v=%d\n", sc, sb); 1;})): (!(--dp[cchan][sc]) || ({if(t('d')) printf("Twice down: note=%d\n", sc); 1;})))) || ((cst == 8) && (!(--dp[cchan][sc]) || ({warn(); printf("Twice up: note=%d, v=%d cst=%d c=%d\n", sc, sb, cst, cchan); 1;}))) || ((cst == 10) && (dp[cchan][sc] || ex(38))))) ex(39); if(cst == 9) {if (sb) {if(t('N')) printf("%d begins at %9.4f v=%d\n", sc, Time/tf, sb);} else {if(t('O')) printf("%d ends at %9.4f\n", sc, Time/tf);}} else if(cst == 8 && t('O')) printf("%d Ends at %9.4f\n", sc, Time/tf);}} endMessage: ;} endTrk: {int j=16; while(j--) if(dp[j]) {int k=128, c = 10; while(k--) if(dp[j][k]) if((c--)>0) printf("Note %d stuck on in channel %d\n", k, j+1); free(dp[j]);}} if(Time/den > 4*3600) {printf("Long(!) piece. %d quarter notes\n", Time/den); ex(31);} }} if(getc(in) != EOF || !feof(in)) ex(99); return 0;}} // http://www.borg.com/~jglatt/tech // http://www.ibiblio.org/emusic-l/info-docs-FAQs/MIDI-doc/MIDI-SMF.txt // http://www.borg.com/~jglatt/tech/midispec/pressure.htm // http://www.srm.com/qtma/davidsmidispec.html // http://crystal.apana.org.au/~ghansper/midi_introduction/midi_file_format.html // get http://www.usb.org/developers/data/usb_20.zip // http://www.usb.org/developers/data/devclass/midi10.pdf