/* This program 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. */
/* This program is unbelievably badly written. */
/* Warning : this program assumes that short int is 2 bytes
   and long int is 4. As far as I know, it's always the case,
   but you never know. */

/* The MIDI format stores (almost) all numbers under the
   big endian format (LSB first). Intel-based machines store
   numbers under the little endian format (MSB first).
   This program needs to know what endian format is used.
   If little endian format is used, comment the following line. */
#define BIGENDIAN

/* List of include files may depend on which computer is used.
   The following is (probably) valid for UNIX based machines.
   If using Turbo C on a PC, uncomment io.h, and comment unistd.h. */
#include <stdio.h>
/*#include <io.h>*/
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>

/* Some compilers may have already defined this (ex. Turbo C). */
#define EXIT_SUCCESS 0
#define EXIT_FAILURE 1

int midifile;
FILE *psfile;

/* Increase this to read more tracks. */
#define maxnbtracks 20

unsigned long int absolutetime; /* In ticks */
unsigned long int absoluteclock; /* In microseconds */
/* Change this number to adjust time scale. */
#define ticksperpage 3000
unsigned long int pagestart; /* In ticks */
short int subdiv;
unsigned long int thedelay;
unsigned long int tempo;
char alldone;

struct atrack {
  char on;
  long int start; /* In midi file */
  unsigned long int whereami; /* From track start */
  unsigned long int length;
  unsigned long int delay; /* Delta time remaining */
  unsigned char rstatus; /* Running status */
} tracks[maxnbtracks];
short int nbtracks;

struct achannel {
  unsigned char patch;
  char notes[128];
  short int wheel;
} channels[16];

#define garbagesize 50
char garbage[garbagesize];

void error(char *errstr)
{
  fprintf(stderr,errstr);
  exit(EXIT_FAILURE);
}

void openthemidifile(void)
{
  /* The O_BINARY may be needed on some systems. */
  if ((midifile=open("zefile.mid",O_RDONLY/*|O_BINARY*/))==-1)
    error("Error while opening midi file.\n");
}

void closethemidifile(void)
{
  close(midifile);
}

void openthepsfile(void)
{
  if ((psfile=fopen("zefile.ps","wt"))==NULL)
    error("Error while opening PostScript file.\n");
}

void closethepsfile(void)
{
  if (fclose(psfile)) error("Error while closing PostScript file.\n");
}

void writepsheader(void)
{
  fprintf(psfile,"%% Midi file dump\n");
  fprintf(psfile,"%% Begin error handler\n");
  fprintf(psfile,"statusdict /jobname (Midi dump) put\n");
  fprintf(psfile,"errordict begin\n");
  fprintf(psfile,"/errhelpdict 12 dict def\n");
  fprintf(psfile,"errhelpdict begin\n");
  fprintf(psfile,"/stackunderflow (operand stack underflow) def\n");
  fprintf(psfile,"/undefined (undefined object) def\n");
  fprintf(psfile,"/typecheck (wrong argument type) def\n");
  fprintf(psfile,"/nocurrentpoint (no current point) def\n");
  fprintf(psfile,"/dictstackunderflow (extra end encountered) def\n");
  fprintf(psfile,"end\n");
  fprintf(psfile,"/handleerror {\n");
  fprintf(psfile,"  $error begin\n");
  fprintf(psfile,"  newerror {\n");
  fprintf(psfile,"    /newerror false def\n");
  fprintf(psfile,"    showpage\n");
  fprintf(psfile,"    /x 18 def /y 691.2 def\n");
  fprintf(psfile,"    /Helvetica 14.4 selectfont\n");
  fprintf(psfile,"    x y moveto\n");
  fprintf(psfile,"    (Offending command = ) show\n");
  fprintf(psfile,"    /command load\n");
  fprintf(psfile,"    {\n");
  fprintf(psfile,"      dup type\n");
  fprintf(psfile,"      /stringtype ne {\n");
  fprintf(psfile,"        (Max error string) cvs\n");
  fprintf(psfile,"      } if\n");
  fprintf(psfile,"      show\n");
  fprintf(psfile,"    } exec\n");
  fprintf(psfile,"    /y y 14.4 sub def\n");
  fprintf(psfile,"    x y moveto\n");
  fprintf(psfile,"    (Error = ) show\n");
  fprintf(psfile,"    errorname\n");
  fprintf(psfile,"    {\n");
  fprintf(psfile,"      dup type dup\n");
  fprintf(psfile,"      (Max error string) cvs\n");
  fprintf(psfile,"      show\n");
  fprintf(psfile,"      ( : ) show\n");
  fprintf(psfile,"      /stringtype ne {\n");
  fprintf(psfile,"        (Max error string) cvs\n");
  fprintf(psfile,"      } if\n");
  fprintf(psfile,"      show\n");
  fprintf(psfile,"    } exec\n");
  fprintf(psfile,"    errordict begin\n");
  fprintf(psfile,"    errhelpdict errorname known {\n");
  fprintf(psfile,"      x 72 add y 14.4 sub moveto\n");
  fprintf(psfile,"      errhelpdict errorname get\n");
  fprintf(psfile,"      show\n");
  fprintf(psfile,"    } if\n");
  fprintf(psfile,"    end\n");
  fprintf(psfile,"    /y y 28.8 sub def\n");
  fprintf(psfile,"    x y moveto\n");
  fprintf(psfile,"    (Stack =) show\n");
  fprintf(psfile,"    ostack {\n");
  fprintf(psfile,"      /y y 14.4 sub def\n");
  fprintf(psfile,"      x 72 add y moveto\n");
  fprintf(psfile,"      dup type\n");
  fprintf(psfile,"      /stringtype ne {\n");
  fprintf(psfile,"        (Max error string) cvs\n");
  fprintf(psfile,"      } if\n");
  fprintf(psfile,"      show\n");
  fprintf(psfile,"    } forall\n");
  fprintf(psfile,"    showpage\n");
  fprintf(psfile,"  } if\n");
  fprintf(psfile,"  end\n");
  fprintf(psfile,"} def\n");
  fprintf(psfile,"end\n");
  fprintf(psfile,"%% End error handler\n");
  fprintf(psfile,"\n");
  fprintf(psfile,"%% Begin macros, constants & page layoyt\n");
  fprintf(psfile,"/choosefont {\n");
  fprintf(psfile,"  dup /h exch def selectfont\n");
  fprintf(psfile,"} bind def\n");
  fprintf(psfile,"/justify {\n");
  fprintf(psfile,"  /s exch def\n");
  fprintf(psfile,"  /hy exch 1 add 2 div def /hx exch 1 add 2 div def\n");
  fprintf(psfile,"  /w s stringwidth pop def\n");
  fprintf(psfile,"  hy h mul sub\n");
  fprintf(psfile,"  exch hx w mul sub exch\n");
  fprintf(psfile,"  moveto\n");
  fprintf(psfile,"  s show\n");
  fprintf(psfile,"} bind def\n");
  fprintf(psfile,"/cm 72 2.54 div def\n");
  fprintf(psfile,"/totwidth 21 def\n");
  fprintf(psfile,"/totheigth 29.7 def\n");
  fprintf(psfile,"/formatset {\n");
  fprintf(psfile,"  initgraphics\n");
  fprintf(psfile,"  cm cm scale\n");
  fprintf(psfile,"  90 rotate\n");
  fprintf(psfile,"  0 totwidth neg translate\n");
  fprintf(psfile,"} bind def\n");
  fprintf(psfile,"/bandwidth 19 def\n");
  fprintf(psfile,"/bandbottom 0.5 def\n");
  fprintf(psfile,"/bandtop bandbottom bandwidth add def\n");
  fprintf(psfile,"/bandnotes 72 def\n");
  fprintf(psfile,"/bottomnote 30 def\n");
  fprintf(psfile,"/halftonewidth bandwidth bandnotes div def\n");
  fprintf(psfile,"/scrollleft 1.5 def\n");
  fprintf(psfile,"/scrollright 0.5 def\n");
  fprintf(psfile,"/scrollsize totheigth scrollleft sub scrollright sub def\n");
  fprintf(psfile,"/notetoheigth {\n");
  fprintf(psfile,"  bottomnote sub halftonewidth mul bandbottom add\n");
  fprintf(psfile,"} bind def\n");
  fprintf(psfile,"/notemark {\n");
  fprintf(psfile,"  /name exch def /note exch def\n");
  fprintf(psfile,"  /hgt note notetoheigth def\n");
  fprintf(psfile,"  /Helvetica 0.3 choosefont\n");
  fprintf(psfile,"  scrollleft hgt 1.0 0.0 name justify\n");
  fprintf(psfile,"  0 setlinewidth\n");
  fprintf(psfile,"  scrollleft hgt moveto scrollsize 0 rlineto stroke\n");
  fprintf(psfile,"} bind def\n");
  fprintf(psfile,"/notescale {\n");
  fprintf(psfile,"  36 (C1) notemark\n");
  fprintf(psfile,"  48 (C2) notemark\n");
  fprintf(psfile,"  60 (C3) notemark\n");
  fprintf(psfile,"  72 (C4) notemark\n");
  fprintf(psfile,"  84 (C5) notemark\n");
  fprintf(psfile,"  96 (C6) notemark\n");
  fprintf(psfile,"} bind def\n");
  fprintf(psfile,"/scrolltime %d def\n",ticksperpage);
  fprintf(psfile,"/tickwidth scrollsize scrolltime div def\n");
  fprintf(psfile,"/ticktoplace {\n");
  fprintf(psfile,"  tickwidth mul scrollleft add\n");
  fprintf(psfile,"} bind def\n");
  fprintf(psfile,"/timescale {\n");
  fprintf(psfile,"  /name exch def /tick exch def\n");
  fprintf(psfile,"  /place tick ticktoplace def\n");
  fprintf(psfile,"  /Helvetica 0.15 choosefont\n");
  fprintf(psfile,"  place bandtop 0.0 -1.0 name justify\n");
  fprintf(psfile,"  0 setlinewidth\n");
  fprintf(psfile,"  place bandtop moveto place bandbottom lineto stroke\n");
  fprintf(psfile,"} bind def\n");
  fprintf(psfile,"/anote {\n");
  fprintf(psfile,"  /tick2 exch def /tick1 exch def /note exch def\n");
  fprintf(psfile,"  /chan exch def\n");
  fprintf(psfile,"  /hgta note 0.3 sub notetoheigth def\n");
  fprintf(psfile,"  /hgtb note 0.3 add notetoheigth def\n");
  fprintf(psfile,"  /place1 tick1 ticktoplace def\n");
  fprintf(psfile,"  /place2 tick2 ticktoplace def\n");
  fprintf(psfile,"  0 setlinewidth\n");
  fprintf(psfile,"  place1 hgta moveto place2 hgta lineto\n");
  fprintf(psfile,"  place2 hgtb lineto place1 hgtb lineto\n");
  fprintf(psfile,"  closepath\n");
  fprintf(psfile,"  gsave chan 15 div setgray fill grestore\n");
  fprintf(psfile,"  newpath\n");
  fprintf(psfile,"  place1 hgta moveto place2 hgta lineto stroke\n");
  fprintf(psfile,"  place1 hgtb moveto place2 hgtb lineto stroke\n");
  fprintf(psfile,"} bind def\n");
  fprintf(psfile,"/anoteon {\n");
  fprintf(psfile,"  /tick exch def /note exch def\n");
  fprintf(psfile,"  /chan exch def\n");
  fprintf(psfile,"  /hgta note 0.45 sub notetoheigth def\n");
  fprintf(psfile,"  /hgtb note 0.45 add notetoheigth def\n");
  fprintf(psfile,"  /place tick ticktoplace def\n");
  fprintf(psfile,"  0.04 setlinewidth\n");
  fprintf(psfile,"  place hgta moveto place hgtb lineto stroke\n");
  fprintf(psfile,"} bind def\n");
  fprintf(psfile,"%% End\n");
  fprintf(psfile,"\n");
  fprintf(psfile,"%% Begin body\n");
  fprintf(psfile,"formatset\n");
  fprintf(psfile,"notescale\n");
}

void writepsfooter(void)
{
  fprintf(psfile,"showpage\n");
  fprintf(psfile,"quit\n");
  fprintf(psfile,"%% The end!\n");
}

void initmidichannels(void)
{
  unsigned char chn,i;
  for (chn=0;chn<16;chn++) {
    channels[chn].patch=0;
    for (i=0;i<128;i++) {
      channels[chn].notes[i]=0;
    }
    channels[chn].wheel=0;
  }
}

void readfromtrack(short int trk,void *buf,unsigned len)
{
  lseek(midifile,tracks[trk].start+tracks[trk].whereami,SEEK_SET);
  read(midifile,buf,len);
  tracks[trk].whereami+=len;
}

char readabyte(short int trk)
{
  char c;
  readfromtrack(trk,&c,1);
  return c;
}

void readdtime(short int trk,unsigned long *val)
{
  char c;
  *val=0L;
  do *val=(*val<<7)|((c=readabyte(trk))&0x7F); while (c>>7);
}

/* The following function reads an integer in
big endian format on a little endian system.
If BIGENDIAN is defined, read is used instead. */
void readlittleendian(char it[],unsigned len)
{
  unsigned i;
  for (i=0;i<len;i++) {
    read(midifile,&it[len-i-1],1);
  }
}

void readmidiheader(void)
{
  unsigned long headerlength,headerstart;
  short int trk;
  read(midifile,&garbage,4);
  #ifdef BIGENDIAN
  read(midifile,&headerlength,4);
  #else
  readlittleendian(&headerlength,4);
  #endif
  headerstart=lseek(midifile,0L,SEEK_CUR);
  read(midifile,&garbage,2);
  #ifdef BIGENDIAN
  read(midifile,&nbtracks,2);
  #else
  readlittleendian(&nbtracks,2);
  #endif
  if (nbtracks>=maxnbtracks) error("Too many tracks.\n");
  #ifdef BIGENDIAN
  read(midifile,&subdiv,2);
  #else
  readlittleendian(&subdiv,2);
  #endif
  lseek(midifile,headerstart+headerlength,SEEK_SET);
  for (trk=0;trk<nbtracks;trk++) {
    read(midifile,&garbage,4);
    #ifdef BIGENDIAN
    read(midifile,&tracks[trk].length,4);
    #else
    readlittleendian(&tracks[trk].length,4);
    #endif
    tracks[trk].start=lseek(midifile,0L,SEEK_CUR);
    tracks[trk].on=1;
    tracks[trk].whereami=0L;
    tracks[trk].rstatus=0x90;
    lseek(midifile,tracks[trk].start+tracks[trk].length,SEEK_SET);
  }
  for (trk=0;trk<nbtracks;trk++) {
    readdtime(trk,&tracks[trk].delay);
  }
  tempo=500000L;
}

void testalldone(void)
{
  short int trk;
  alldone=1;
  for (trk=0;trk<nbtracks;trk++) {
    alldone=alldone&&!tracks[trk].on;
  }
}

void finddelay(void)
{
  short int trk;
  thedelay=(unsigned long int)subdiv-absolutetime%(unsigned long int)subdiv;
  for (trk=0;trk<nbtracks;trk++) {
    if ((tracks[trk].on)&&(tracks[trk].delay<thedelay))
      thedelay=tracks[trk].delay;
  }
}

void dodelay(void)
{
  short int trk;
  unsigned char chn,i;
  for (chn=0;chn<16;chn++) {
    for (i=0;i<128;i++) {
      if (channels[chn].notes[i]) {
	fprintf(psfile,"%d %.3f %ld %ld anote\n",
	  chn,(float)i+((float)channels[chn].wheel)/4096,
	  absolutetime-pagestart,absolutetime-pagestart+thedelay);
      }
    }
  }
  absolutetime+=thedelay;
  absoluteclock+=thedelay*(tempo/subdiv);
  for (trk=0;trk<nbtracks;trk++) if (tracks[trk].on) {
    tracks[trk].delay-=thedelay;
  }
  if (absolutetime-pagestart>=ticksperpage) {
    pagestart+=ticksperpage;
    fprintf(psfile,"showpage\n");
    fprintf(psfile,"formatset\n");
    fprintf(psfile,"notescale\n");
  }
  if (absolutetime%subdiv==0) {
    fprintf(psfile,"%ld (%ld) timescale\n",
      absolutetime-pagestart,absolutetime);
  }
}

void midinoteoff(unsigned char chn,unsigned char nte,unsigned char vel)
{
  channels[chn].notes[nte]=0;
}

void midinoteon(unsigned char chn,unsigned char nte,unsigned char vel)
{
  channels[chn].notes[nte]=vel;
  if (vel) fprintf(psfile,"%d %.3f %ld anoteon\n",
    chn,(float)nte+((float)channels[chn].wheel)/4096,absolutetime-pagestart);
}

void midicontroler(unsigned char chn,unsigned char ctr,unsigned char val)
{
}

void midipatch(unsigned char chn,unsigned char pat)
{
  channels[chn].patch=pat;
}

void midipitchwheel(unsigned char chn,unsigned char low,unsigned char hig)
{
  if (hig<0x80) channels[chn].wheel=(((short int)hig)<<7)|low;
  else channels[chn].wheel=(((short int)hig)<<7)|low|0xC000;
}

void docommands(void)
{
  short int trk;
  unsigned char c,c2,cmd;
  unsigned long mpos,mlen;
  for (trk=0;trk<nbtracks;trk++) {
    while (tracks[trk].on&&(tracks[trk].delay==0)) {
      if ((c=readabyte(trk))>=0x80) {
	tracks[trk].rstatus=c;
	c=readabyte(trk);
      }
      cmd=tracks[trk].rstatus;
      /* Uncomment the following to show time flow as ps file is produced. */
/*      printf("@%3ld:%2ld.%3ld  ",
	absoluteclock/60000000L,(absoluteclock/1000000L)%60L,
	(absoluteclock/1000L)%1000L);
      printf("(%5ld)  ",absolutetime);
      printf("trk %2d: cmd %2X %2X\n",trk,cmd,c); */
      if ((cmd>=0x80)&&(cmd<0x90)) {
	c2=readabyte(trk);
	midinoteoff(cmd&0x0F,c,c2);
      }
      else if ((cmd>=0x90)&&(cmd<0xA0)) {
	c2=readabyte(trk);
	midinoteon(cmd&0x0F,c,c2);
      }
      else if ((cmd>=0xB0)&&(cmd<0xC0)) {
	c2=readabyte(trk);
	midicontroler(cmd&0x0F,c,c2);
      }
      else if ((cmd>=0xC0)&&(cmd<0xD0)) {
	midipatch(cmd&0x0F,c);
      }
      else if ((cmd>=0xE0)&&(cmd<0xF0)) {
	c2=readabyte(trk);
	midipitchwheel(cmd&0x0F,c,c2);
      }
      else if (cmd==0xFF) {
	readdtime(trk,&mlen);
	mpos=tracks[trk].whereami;
	switch (c) {
	  case 0x2F:
	    tracks[trk].on=0;
	    break;
	  case 0x51:
	    tempo=0;
	    readfromtrack(trk,&tempo,mlen<4?mlen:4);
	    break;
	}
	tracks[trk].whereami=mpos+mlen;
      }
      else error("Unknown MIDI command.\n");
      if (tracks[trk].on) readdtime(trk,&tracks[trk].delay);
    }
  }
}

int main(int argc,char *argv[])
{
  openthemidifile();
  openthepsfile();
  writepsheader();
  readmidiheader();
  initmidichannels();
  while (1) {
    testalldone();
    if (alldone) break;
    finddelay();
    dodelay();
    docommands();
  }
  writepsfooter();
  closethemidifile();
  closethepsfile();
  return EXIT_SUCCESS;
}
