/* Midi to postscript converter. The original was written by David Madore, found it in the autumn of 2003 on http://www.eleves.ens.fr:8080/home/madore/programs/programs-1.8.html . C code (object oriented, globals, cross-platform, endianness) improved by Pieter Suurmond, october 17, 2003. Reads midi file "zefile.mid" and produces a kind of score of it in PostScript format under the name "zefile.ps". Limitations: a lot of them. Only the noteon, noteoff, patch, controller, pitchwheel MIDI events are recognized, and any other event produces an error. This is easy to correct, of course. Page format is A4 (european standard). To change this, modify the MIDI totwidth and totheigth variables below. More about the postscript language can be found on http://www.cs.indiana.edu/docproject/programming/postscript/postscript.html . */ #include #include #define kTICKS_PER_PAGE (3000) /* Change this number to adjust time scale. */ /* Increase to zoom out. */ #define kTIMESCALE_FONTSIZE (0.30) /* Was 0.15. */ #define kFILL_GREY_VALUE (0.40) /* 0.0=black; 1.0=white. */ typedef struct { char on; long int start; /* In midi file */ unsigned long whereami; /* From track start */ unsigned long length; unsigned long delay; /* Delta time remaining */ unsigned char rstatus; /* Running status */ } MIDI_TRACK; typedef struct { unsigned char patch; char notes[128]; short wheel; } MIDI_CHANNEL; /* Memory structure for opening, parsing and closing a midi file. Object is created by open_midi_file() and cleaned up by close_midi_file(). */ typedef struct { FILE* fp; /* File pointer. */ unsigned short nt; /* Number of tracks. */ MIDI_TRACK* tracks; MIDI_CHANNEL channels[16]; short subdiv; /* Not sure whether this belongs to midifile! */ unsigned long tempo; unsigned long thedelay; } MIDI_FILE; /* Memory structure for creating, writing and closing a postscript file. Object is created by open_ps_file() and cleaned up by close_ps_file(). */ typedef struct { FILE* fp; /* File pointer. */ unsigned long pagestart; /* In ticks */ unsigned long absolutetime; /* In ticks */ unsigned long absoluteclock; /* In microseconds (only for debugging). */ } PS_FILE; static void write_ps_header(FILE* f) { /* Starting with "%!PS-..." makes desktop recognise the postscript file. */ fprintf(f,"\ %%!PS-Adobe-2.0\n\ %% Midi file dump\n\ %% Begin error handler\n\ statusdict /jobname (Midi dump) put\n\ errordict begin\n\ /errhelpdict 12 dict def\n\ errhelpdict begin\n\ /stackunderflow (operand stack underflow) def\n\ /undefined (undefined object) def\n\ /typecheck (wrong argument type) def\n\ /nocurrentpoint (no current point) def\n\ /dictstackunderflow (extra end encountered) def\n\ end\n\ /handleerror {\n\ $error begin\n\ newerror {\n\ /newerror false def\n\ showpage\n\ /x 18 def /y 691.2 def\n\ /Helvetica 14.4 selectfont\n\ x y moveto\n\ (Offending command = ) show\n\ /command load\n\ {\n\ dup type\n\ /stringtype ne {\n\ (Max error string) cvs\n\ } if\n\ show\n\ } exec\n\ /y y 14.4 sub def\n\ x y moveto\n\ (Error = ) show\n\ errorname\n\ {\n\ dup type dup\n\ (Max error string) cvs\n\ show\n\ ( : ) show\n\ /stringtype ne {\n\ (Max error string) cvs\n\ } if\n\ show\n\ } exec\n\ errordict begin\n\ errhelpdict errorname known {\n\ x 72 add y 14.4 sub moveto\n\ errhelpdict errorname get\n\ show\n\ } if\n\ end\n\ /y y 28.8 sub def\n\ x y moveto\n\ (Stack =) show\n\ ostack {\n\ /y y 14.4 sub def\n\ x 72 add y moveto\n\ dup type\n\ /stringtype ne {\n\ (Max error string) cvs\n\ } if\n\ show\n\ } forall\n\ showpage\n\ } if\n\ end\n\ } def\n\ end\n\ %% End error handler\n\n"); fprintf(f,"\ %% Begin macros, constants & page layout\n\ /choosefont {\n\ dup /h exch def selectfont\n\ } bind def\n\ /justify {\n\ /s exch def\n\ /hy exch 1 add 2 div def /hx exch 1 add 2 div def\n\ /w s stringwidth pop def\n\ hy h mul sub\n\ exch hx w mul sub exch\n\ moveto\n\ s show\n\ } bind def\n\ /cm 72 2.54 div def\n\ /totwidth 21 def\n\ /totheigth 29.7 def\n\ /formatset {\n\ initgraphics\n\ cm cm scale\n\ 90 rotate\n\ 0 totwidth neg translate\n\ } bind def\n"); fprintf(f,"\ /bandwidth 19 def\n\ /bandbottom 0.5 def\n\ /bandtop bandbottom bandwidth add def\n\ /bandnotes 72 def\n\ /bottomnote 30 def\n\ /halftonewidth bandwidth bandnotes div def\n\ /scrollleft 1.5 def\n\ /scrollright 0.5 def\n\ /scrollsize totheigth scrollleft sub scrollright sub def\n\ /notetoheigth {\n\ bottomnote sub halftonewidth mul bandbottom add\n\ } bind def\n\ /notemark {\n\ /name exch def /note exch def\n\ /hgt note notetoheigth def\n\ /Helvetica 0.3 choosefont\n\ scrollleft hgt 1.0 0.0 name justify\n\ 0 setlinewidth\n\ scrollleft hgt moveto scrollsize 0 rlineto stroke\n\ } bind def\n"); fprintf(f,"\ /notescale {\n\ 36 (C1) notemark\n\ 48 (C2) notemark\n\ 60 (C3) notemark\n\ 72 (C4) notemark\n\ 84 (C5) notemark\n\ 96 (C6) notemark\n\ } bind def\n"); fprintf(f,"\ /scrolltime %d def\n\ /tickwidth scrollsize scrolltime div def\n\ /ticktoplace {\n\ tickwidth mul scrollleft add\n\ } bind def\n", kTICKS_PER_PAGE); fprintf(f,"\ /timescale {\n\ /name exch def /tick exch def\n\ /place tick ticktoplace def\n\ /Helvetica %.2f choosefont\n\ place bandtop 0.0 -1.0 name justify\n\ 0 setlinewidth\n\ place bandtop moveto place bandbottom lineto stroke\n\ } bind def\n", kTIMESCALE_FONTSIZE); /* Fontsize of timescale. */ fprintf(f,"\ /anote {\n\ /tick2 exch def /tick1 exch def /note exch def\n\ /chan exch def\n\ /hgta note 0.3 sub notetoheigth def\n\ /hgtb note 0.3 add notetoheigth def\n\ /place1 tick1 ticktoplace def\n\ /place2 tick2 ticktoplace def\n\ 0 setlinewidth\n\ place1 hgta moveto place2 hgta lineto\n\ place2 hgtb lineto place1 hgtb lineto\n\ closepath\n\ gsave chan 15 div %.6f setgray fill grestore\n\ newpath\n\ place1 hgta moveto place2 hgta lineto stroke\n\ place1 hgtb moveto place2 hgtb lineto stroke\n\ } bind def\n", kFILL_GREY_VALUE); fprintf(f,"\ /anoteon {\n\ /tick exch def /note exch def\n\ /chan exch def\n\ /hgta note 0.45 sub notetoheigth def\n\ /hgtb note 0.45 add notetoheigth def\n\ /place tick ticktoplace def\n\ 0.04 setlinewidth\n\ place hgta moveto place hgtb lineto stroke\n\ } bind def\n\ %% End\n\n"); fprintf(f,"\ %% Begin body\n\ formatset\n\ notescale\n"); } static void write_ps_footer(FILE* f) { fprintf(f,"\ showpage\n\ quit\n\ %% The end!\n"); } static char readfromtrack(MIDI_FILE* mf, short int trk, void *buf, unsigned len) { fseek(mf->fp, mf->tracks[trk].start + mf->tracks[trk].whereami, SEEK_SET); if (len != fread(buf, 1, len, mf->fp)) return 1; mf->tracks[trk].whereami += len; return 0; } static char readabyte(MIDI_FILE* mf, short int trk) { char c; readfromtrack(mf, trk, &c, 1); return c; } static void readdtime(MIDI_FILE* mf, short int trk, unsigned long *val) { char c; *val = 0L; do { *val=(*val<<7)|((c=readabyte(mf, trk))&0x7F); } while (c>>7); } /* Reads 2-byte unsigned short, MSB first. Returns 0 on success and leaves the 16 bit number in *dest. Endian-safe. */ static char read_unsigned_16(FILE* fp, unsigned short* dest) { unsigned char buffer[2]; if (2 != fread(buffer, 1, 2, fp)) return 1; /* Error! */ *dest = (buffer[0] << 8) | buffer[1]; return 0; } /* Reads 4-byte unsigned short, MSB first. Returns 0 on success and leaves the 32 bit number in *dest. Endian-safe. */ static char read_unsigned_32(FILE* fp, unsigned long* dest) { unsigned char buffer[4]; if (4 != fread(buffer, 1, 4, fp)) return 1; /* Error! */ *dest = (buffer[0] << 24) | (buffer[1] << 16) | (buffer[2] << 8) | buffer[3]; return 0; } static void initmidichannels(MIDI_CHANNEL* mc) { unsigned char chn, i; for (chn=0; chn<16; chn++) { mc[chn].patch = 0; for (i=0; i<128; i++) mc[chn].notes[i] = 0; mc[chn].wheel = 0; } } /* Opens an existing MIDI file for reading. Returns a nonzero pointer to a newly created MIDI_FILE structure on success. Returns NULL on failures. */ static MIDI_FILE* open_midi_file(char* filename) { MIDI_FILE* result; unsigned long headerlength, headerstart; unsigned short trk; char garbage[16]; /* Never read more than 16 bytes! */ result = malloc(sizeof(MIDI_FILE)); if (result) { result->tempo = 500000L; initmidichannels(result->channels); result->fp = fopen(filename, "rb"); if (result->fp) { fread(&garbage, 1, 4, result->fp); /* Skip 4 bytes. */ read_unsigned_32(result->fp, &headerlength); /* Read header length (MSB B B LSB). */ headerstart = ftell(result->fp); fread(&garbage, 1, 2, result->fp); /* Skip 2 bytes. */ read_unsigned_16(result->fp, &result->nt); /* Read number of tracks (MSB LSB). */ result->tracks = malloc(result->nt * sizeof(MIDI_TRACK)); if (result->tracks) { read_unsigned_16(result->fp, &result->subdiv); /* Read subdivisions (MSB LSB). */ fseek(result->fp, headerstart + headerlength, SEEK_SET); for (trk=0; trknt; trk++) { fread(&garbage, 1, 4, result->fp); /* Skip 4 bytes. */ read_unsigned_32(result->fp, /* Read track length (MSB B B LSB). */ &result->tracks[trk].length); result->tracks[trk].start = ftell(result->fp); result->tracks[trk].on = 1; result->tracks[trk].whereami = 0L; result->tracks[trk].rstatus = 0x90; fseek(result->fp, result->tracks[trk].start + result->tracks[trk].length, SEEK_SET); } for (trk=0; trknt; trk++) readdtime(result, trk, &result->tracks[trk].delay); } else { printf("Not enough memory for %d tracks!\n", result->nt); fclose(result->fp); free(result); result = NULL; } } else { printf("Could not open midi file '%s'!\n", filename); free(result); result = NULL; } } return result; } static void close_midi_file(MIDI_FILE** mf) { fclose((*mf)->fp); /* Sure open. */ free((*mf)->tracks); /* Sure allocated. */ free(*mf); *mf = (MIDI_FILE*)NULL; } static char testalldone(MIDI_FILE* mf) { short int trk; char alldone = 1; for (trk=0; trknt; trk++) alldone = alldone && !mf->tracks[trk].on; return alldone; } static void finddelay(MIDI_FILE* mf, unsigned long absolutetime) /* In ticks. */ { unsigned short trk; mf->thedelay = (unsigned long int)mf->subdiv - absolutetime % (unsigned long int)mf->subdiv; for (trk=0; trknt; trk++) { if ((mf->tracks[trk].on) && (mf->tracks[trk].delay < mf->thedelay)) mf->thedelay = mf->tracks[trk].delay; } } static void dodelay(MIDI_FILE* mf, PS_FILE* pf) { unsigned short trk; unsigned char chn, i; for (chn=0; chn<16; chn++) { for (i=0; i<128; i++) { if (mf->channels[chn].notes[i]) { fprintf(pf->fp,"\ %d %.3f %ld %ld anote\n", chn,(double)i+((double)mf->channels[chn].wheel)/4096, pf->absolutetime - pf->pagestart, pf->absolutetime - pf->pagestart + mf->thedelay); } } } pf->absolutetime += mf->thedelay; pf->absoluteclock += mf->thedelay * (mf->tempo / mf->subdiv); for (trk=0; trknt; trk++) { if (mf->tracks[trk].on) mf->tracks[trk].delay -= mf->thedelay; } if (pf->absolutetime - pf->pagestart >= kTICKS_PER_PAGE) { pf->pagestart += kTICKS_PER_PAGE; fprintf(pf->fp, "\ showpage\n\ formatset\n\ notescale\n"); } if (pf->absolutetime % mf->subdiv == 0) fprintf(pf->fp, "\ %ld (%ld) timescale\n", pf->absolutetime - pf->pagestart, pf->absolutetime); } static void midinoteoff(MIDI_CHANNEL* mc, unsigned char chn, unsigned char nte, unsigned char vel) { mc[chn].notes[nte] = 0; } static void midinoteon(MIDI_CHANNEL* mc, PS_FILE* pf, unsigned char chn, unsigned char nte, unsigned char vel) { mc[chn].notes[nte] = vel; if (vel) fprintf(pf->fp, "%d %.3f %ld anoteon\n", chn, (double)nte + ((double)mc[chn].wheel) / 4096, pf->absolutetime - pf->pagestart); } static void midicontroller(unsigned char chn,unsigned char ctr,unsigned char val) { } static void midipatch(MIDI_CHANNEL* mc, unsigned char chn,unsigned char pat) { mc[chn].patch = pat; } static void midipitchwheel(MIDI_CHANNEL* mc, unsigned char chn, unsigned char low, unsigned char high) { if (high < 0x80) mc[chn].wheel = (((short int)high)<<7) | low; else mc[chn].wheel = (((short int)high)<<7) | low | 0xC000; } static void docommands(MIDI_FILE* mf, PS_FILE* pf) { short int trk; unsigned char c, c2, cmd; unsigned long mpos, mlen; for (trk=0; trknt; trk++) { while (mf->tracks[trk].on && (mf->tracks[trk].delay == 0)) { if ((c=readabyte(mf, trk)) >= 0x80) { mf->tracks[trk].rstatus=c; c=readabyte(mf, trk); } cmd=mf->tracks[trk].rstatus; #if (1) /* Make (0) to surpress printing. */ printf("@%3ld:%2ld.%3ld ", pf->absoluteclock/60000000L, (pf->absoluteclock/1000000L)%60L, (pf->absoluteclock/1000L)%1000L); printf("(%5ld) ", pf->absolutetime); printf("trk %2d: cmd %2X %2X\n", trk, cmd, c); #endif if ((cmd>=0x80) && (cmd<0x90)) { c2=readabyte(mf, trk); midinoteoff(mf->channels, cmd&0x0F, c, c2); } else if ((cmd>=0x90) && (cmd<0xA0)) { c2=readabyte(mf, trk); midinoteon(mf->channels, pf, cmd&0x0F, c, c2); } else if ((cmd>=0xB0) && (cmd<0xC0)) { c2=readabyte(mf, trk); midicontroller(cmd&0x0F, c, c2); } else if ((cmd>=0xC0) && (cmd<0xD0)) { midipatch(mf->channels, cmd&0x0F, c); } else if ((cmd>=0xE0) && (cmd<0xF0)) { c2=readabyte(mf, trk); midipitchwheel(mf->channels, cmd&0x0F, c, c2); } else if (cmd==0xFF) { readdtime(mf, trk, &mlen); mpos = mf->tracks[trk].whereami; switch (c) { case 0x2F: mf->tracks[trk].on=0; break; case 0x51: mf->tempo = 0; readfromtrack(mf, trk, &mf->tempo, mlen<4?mlen:4); break; } mf->tracks[trk].whereami=mpos+mlen; } else printf("Unknown MIDI command.\n"); if (mf->tracks[trk].on) readdtime(mf, trk, &mf->tracks[trk].delay); } } } static PS_FILE* open_ps_file(char* filename) { PS_FILE* result; result = malloc(sizeof(PS_FILE)); if (result) { result->fp = fopen(filename, "wt"); if (result->fp) { result->pagestart = 0L; result->absolutetime = 0L; /* In ticks */ result->absoluteclock = 0L; /* In microseconds. */ } else { printf("Could not open postscript file '%s'!\n", filename); free(result); result = NULL; } } return result; } static void close_ps_file(PS_FILE** pf) { if (fclose((*pf)->fp)) /* Sure open. */ printf("Error while closing postscript file.\n"); free(*pf); /* Sure allocated. */ *pf = (PS_FILE*)NULL; } int main(int argc,char *argv[]) { MIDI_FILE* mf = NULL; PS_FILE* pf = NULL; if ((sizeof(short) < 2) || (sizeof(long) < 4)) { printf("Error: datatypes too small!\n"); return 1; /* Better check this at compile time. */ } mf = open_midi_file("zefile.mid"); if (mf) { pf = open_ps_file("zefile.ps"); if (pf) { write_ps_header(pf->fp); while (!testalldone(mf)) { finddelay(mf, pf->absolutetime); /* writes mf->thedelay. */ dodelay(mf, pf); docommands(mf, pf); } write_ps_footer(pf->fp); close_ps_file(&pf); } close_midi_file(&mf); } return 0; /* Zero means succes. */ }