/*  Modified to read SysEx and text messages > 64K                    */
/*  Super fast extraction by reading the whole MIDI file into memory  */
/*  before processing                                                 */


#define NULLFUNC 0

#include <stdio.h>
#include <stdlib.h>
#include <dos.h>
#include <conio.h>
#include <io.h>
#include <midifile.h>

char *strcpy(), *strcat();
void exit(), free();

extern unsigned int midf;

/* public stuff */

/* Functions to be called while processing the MIDI file. */
int (*mf_error)() = NULLFUNC;
int (*mf_noteon)() = NULLFUNC;
int (*mf_sysex)() = NULLFUNC;
int (*mf_arbitrary)() = NULLFUNC;
int (*mf_text)() = NULLFUNC;

int mf_nomerge = 0;                /* 1 => continue'ed system exclusives are */
                                                                       /* not collapsed. */
long mf_currtime = 0L;          /* current time in delta-time units */

/* private stuff */
unsigned long mf_toberead = 0L;
int lastbyte;

static long readvarinum();
static long read32bit();
static long to32bit();
static int read16bit();
static int to16bit();

unsigned char huge *mid_buff = NULL;
unsigned char huge *buff_ptr = NULL;
unsigned char huge *end_of_mf = NULL;
unsigned char far *msg_start = NULL;
unsigned long msg_len = 0L;
unsigned int bufsegp;
unsigned long mf_size;


mfread()
{
       unsigned int max_para;

       mf_size = filelength(midf);

       if(allocmem((unsigned)((mf_size + 15) >> 4), &bufsegp) != -1)
               (*mf_error)("not enough memory to load MIDI file");

       mid_buff = (unsigned char huge*) MK_FP(bufsegp, 0);

       mid_read(mid_buff, mf_size);           /* Read the whole MIDI file */
       buff_ptr = mid_buff;                      /* Buffer position pointer  */
       end_of_mf = mid_buff + mf_size;        /* End of file pointer      */

       readheader();
       while (readtrack());

       freemem(bufsegp);
}


static
readmt(s)               /* read through the "MThd" or "MTrk" header string */
char *s;
{
       int n = 0;
       char *p = s;
       int c;

       while ( n++<4 && (c = virt_getc()) != EOF ) {
               if ( c != *p++ ) {
                       char buff[32];
                       (void) strcpy(buff,"expecting ");
                       (void) strcat(buff,s);
                       (*mf_error)(buff);
               }
       }
       return(c);
}


static
egetc()                 /* read a single character and abort on EOF */
{
       int c = virt_getc();

       if ( c == EOF )
               (*mf_error)("premature EOF");

       mf_toberead--;
       return(c);
}

static
readheader()            /* read a header chunk */
{
       int format, ntrks, division;

       if ( readmt("MThd") == EOF )
               return;

       mf_toberead = read32bit();
       format = read16bit();
       ntrks = read16bit();
       division = read16bit();

       /* flush any extra stuff, in case the length of header is not 6 */
       while ( mf_toberead > 0 )
               (void) egetc();
}

static
readtrack()              /* read a track chunk */
{
       /* This array is indexed by the high half of a status byte.  It's */
       /* value is either the number of bytes needed (1 or 2) for a channel */
       /* message, or 0 (meaning it's not  a channel message). */
       static int chantype[] = {
               0, 0, 0, 0, 0, 0, 0, 0,         /* 0x00 through 0x70 */
               2, 2, 2, 2, 1, 1, 2, 0          /* 0x80 through 0xf0 */
       };
       unsigned long lookfor;
       int c, c1, type;
       int sysexcontinue = 0;   /* 1 if last message was an unfinished sysex */
       int running = 0;               /* 1 when running status used */
       int status = 0;             /* status value (e.g. 0x90==note-on) */
       int needed;

       if ( readmt("MTrk") == EOF )
               return(0);

       mf_toberead = read32bit();
       mf_currtime = 0L;

       while ( mf_toberead > 0L ) {

               mf_currtime += readvarinum();   /* delta time */

               c = egetc();

               if ( sysexcontinue && c != 0xf7 )
                       (*mf_error)("didn't find expected continuation of a sysex");

               if ( (c & 0x80) == 0 ) {         /* running status? */
                       if ( status == 0 )
                               (*mf_error)("unexpected running status");
                       running = 1;
               }
               else {
                       status = c;
                       running = 0;
               }

               needed = chantype[ (status>>4) & 0xf ];

               if ( needed ) {         /* ie. is it a channel message? */

                       if ( running )
                               c1 = c;
                       else
                               c1 = egetc();
                       chanmessage( status, c1, (needed>1) ? egetc() : 0 );
                       continue;;
               }

               switch ( c ) {

               case 0xff:                      /* meta event */

                       type = egetc();
                       lookfor = readvarinum();
                       msg_start = (unsigned char far*) buff_ptr;
                       buff_ptr += lookfor;
                       mf_toberead -= lookfor;
                       msg_len = lookfor;

                       metaevent(type);
                       break;

               case 0xf0:              /* start of system exclusive */

                       lookfor = readvarinum();
                       msg_start = (unsigned char far*) buff_ptr;
                       buff_ptr += (lookfor - 1L);
                       lastbyte = virt_getc();
                       mf_toberead -= lookfor;
                       msg_len = lookfor;

                       if ( lastbyte == 0xf7 || mf_nomerge == 0 )
                               (*mf_sysex)(msg_len, msg_start);
                       else
                               sysexcontinue = 1;  /* merge into next msg */
                       break;

               case 0xf7:      /* sysex continuation or arbitrary stuff */

                       lookfor = readvarinum();

                       if ( ! sysexcontinue )
                       {
                               msg_len = 0L;
                               msg_start = (unsigned char far*) buff_ptr;
                       }

                       buff_ptr += (lookfor - 1L);
                       lastbyte = virt_getc();
                       mf_toberead -= lookfor;
                       msg_len += lookfor;

                       if ( ! sysexcontinue ) {
                               if ( mf_arbitrary )
                                       (*mf_arbitrary)(msg_len, msg_start);
                       }

                       else if ( lastbyte == 0xf7 ) {
                               (*mf_sysex)(msg_len, msg_start);
                               sysexcontinue = 0;
                       }
                       break;
               default:
                       badbyte(c);
                       break;
               }
       }
       return(1);
}

static
badbyte(c)
int c;
{
       char buff[32];

       (void) sprintf(buff,"unexpected byte: 0x%02x",c);
       (*mf_error)(buff);
}

static
metaevent(type)
{
       switch  ( type ) {
               case 0x01:      /* Text event */
               case 0x02:      /* Copyright notice */
               case 0x03:      /* Sequence/Track name */
               case 0x04:      /* Instrument name */
               case 0x05:      /* Lyric */
               case 0x06:      /* Marker */
               case 0x07:      /* Cue point */
               case 0x08:
               case 0x09:
               case 0x0a:
               case 0x0b:
               case 0x0c:
               case 0x0d:
               case 0x0e:
               case 0x0f: (*mf_text)(type, msg_len, msg_start);
       }
}

chanmessage(int status, int c1, int c2)
{
       int chan = status & 0xf;

       switch ( status & 0xf0 )
       {
               case 0x90:
                       (*mf_noteon)(chan,c1,c2);
       }
}

/* readvarinum - read a varying-length number, and return the */
/* number of characters it took. */

static long
readvarinum()
{
       long value;
       int c;

       c = egetc();
       value = c;
       if ( c & 0x80 ) {
               value &= 0x7f;
               do {
                       c = egetc();
                       value = (value << 7) + (c & 0x7f);
               } while (c & 0x80);
       }
       return (value);
}

static long
to32bit(c1,c2,c3,c4)
{
       long value = 0L;

       value = (c1 & 0xff);
       value = (value<<8) + (c2 & 0xff);
       value = (value<<8) + (c3 & 0xff);
       value = (value<<8) + (c4 & 0xff);
       return (value);
}

static
to16bit(c1,c2)
int c1, c2;
{
       return ((c1 & 0xff ) << 8) + (c2 & 0xff);
}

static long
read32bit()
{
       int c1, c2, c3, c4;

       c1 = egetc();
       c2 = egetc();
       c3 = egetc();
       c4 = egetc();
       return to32bit(c1,c2,c3,c4);
}

static
read16bit()
{
       int c1, c2;
       c1 = egetc();
       c2 = egetc();
       return to16bit(c1,c2);
}


mid_read(unsigned char huge *buf, unsigned long len)
{
       union REGS regs;
       struct SREGS segregs;
       unsigned int bytes_to_read;

       segregs.ds = FP_SEG(buf);

       while(1)
       {
               regs.h.ah = 0x3f ;
               regs.x.bx = midf;
               regs.x.dx = 0;

               if(len >= 0x8000)
                       bytes_to_read = 0x8000;
               else
                       bytes_to_read = (unsigned int)len;

               regs.x.cx = bytes_to_read;
               intdosx(&regs, &regs, &segregs);
               if(bytes_to_read < 0x8000) break;
               len -= 0x8000L;
               if(len == 0L) break;
               segregs.ds += 0x0800;
       }
}


virt_getc()
{
       if(buff_ptr >= end_of_mf)
               return EOF;

       return (int) (*buff_ptr++);
}