/* This is the source such as it is.  It compiled under several different
   compilers, but use at your own risk.  This program released to the public
   domain (author: Tom Zerucha) [to satisfy legal types].

    This is not my best work, but because several people asked for it,
    here is my midi format 1 to format 0 converter.  Note that there are
    hooks for editing things such as patch changes.  It was adapted from
    a player I am working on so some of the comments aren't fixed yet.

*/
#include <string.h>
#include <malloc.h>
#include <stdio.h>
#include <process.h> /* for exit() */

#ifndef SEEK_SET
#define SEEK_SET 0
#endif

#define FBSZ 4096
unsigned char inbuf[FBSZ];
#define NTRK 256

/* standard midi header after fixing for byte differences */
struct mthd
{
	char mthd [4];
	unsigned long hdlen;
	unsigned short int format; /* 0 - sgl multichan trk, 1 - 1+
					simul, 2 - 1+ indep */
	unsigned short int ntrks;  /* number of track chunks */
	unsigned short int division;   /* time div of a quarter note (or if
					<0, frm/sec,res */
} hdr;

/* standard track header - left unfixed*/
struct mtrkh
{
	char mtrk[4];
	unsigned char trln[4];
} mthdmy;

unsigned long trklen[NTRK];     /* real track lengths */
unsigned char *trkbuf[NTRK];  /* pointers to malloced track buffers */
unsigned int trkptr[NTRK];   /* displacement into track */
unsigned int trk;	    /* current track */

/* input and out files */
FILE *f, *of;

int optr;       /* output pointer */

/* write character out (buffered) */
/* on a PC this is faster than simply using fputc(of) */
void mputc(x)
unsigned char x;
{
	inbuf[optr++] = x;
	if( optr == FBSZ ) {
		fwrite( inbuf, 1, FBSZ, of );
		optr = 0;
	}
}

/* this is fast, but unprotected from buffer overrun */
/* should verify against trklen[trk] */
#define mgetc() (trkbuf[trk][trkptr[trk]++])

/* this gets a standard midifile variable length value */
unsigned long getvar()
{
	unsigned long reclen;
	unsigned char c;

	reclen = 0;
	do {
		c = mgetc();
		reclen <<= 7;
		reclen |= c & 0x7f;
	} while( c & 0x80 );
	return( reclen );
}

/* this puts a standard midifile variable length value */
void putvar(val)
unsigned long val;
{
	char subs[6];
	int i;

	i = 0;
	while( val > 127 ) {
		subs[i++] = 0x80 | ( val & 127 );
		val >>= 7;
	}
	subs[i++] = 0x80 | ( val & 127 );
	subs[0] &= 0x7f;
	while( i-- )
		mputc( subs[i] );
}

/*---------------------------------------------------------------------------*/

int doneflg[NTRK];  /* set when EOT is encountered */
unsigned int trksleft;			/* count of tracks remaining */

void dometa(c, deltime)
unsigned char c;
unsigned long deltime;
{
	unsigned char d;
	unsigned long reclen;

	d = mgetc();
	reclen = getvar();
	if( c == 0xff && d == 0x2f ) {   /* end of track */
		doneflg[trk] = 1;
		trksleft--;
		trkptr[trk]+=reclen;
/*
        while( reclen-- )
			mgetc();
*/
		return;
	}
/* but don't do end of track */
	putvar( deltime );
	mputc( c );
	mputc( d );
	putvar( reclen );
	while( reclen-- )
		mputc( mgetc() );
}
/*---------------------------------------------------------------------------*/

main(argc, argv)
int argc;
char *argv[];
{
	unsigned char c, d;
	unsigned char mtype;
	unsigned long reclen;
	unsigned int maxtrk;
	int gotevt[NTRK];
	int laststs[NTRK];
	unsigned long lastmstime;
	unsigned long currtime;
	unsigned long deltime;
	unsigned long nextevt[NTRK];
	unsigned long tptr;

	optr = 0;
/* read in file */
	of = fopen( argv[2], "wb" );
	if( !of )
		exit( -1 );
	f = fopen( argv[1], "rb" );
	if( !f )
		exit( -1 );

/* get header */
	fread( inbuf, 1, 14, f );
	strncpy( hdr.mthd, inbuf, 4 );
/* portable bigendian to header */
	hdr.hdlen = inbuf[4];
	hdr.hdlen <<= 8;
	hdr.hdlen |= inbuf[5];
	hdr.hdlen <<= 8;
	hdr.hdlen |= inbuf[6];
	hdr.hdlen <<= 8;
	hdr.hdlen |= inbuf[7];
	hdr.format = inbuf[8];
	hdr.format <<= 8;
	hdr.format |= inbuf[9];
	hdr.ntrks = inbuf[10];
	hdr.ntrks <<= 8;
	hdr.ntrks |= inbuf[11];
	hdr.division = inbuf[12];
	hdr.division <<= 8;
	hdr.division |= inbuf[13];
	printf( "Header, len=%ld format=%d #tracks=%d clk/qtr=%d\n", hdr.hdlen,
		hdr.format, hdr.ntrks, hdr.division );
/* change and write new header */
	inbuf[8] = 0;
	inbuf[9] = 0;
	inbuf[10] = 0;
	inbuf[11] = 1;
	fwrite( inbuf, 1, 14, of );
	maxtrk = hdr.ntrks;
/**/
	if( maxtrk > NTRK ) {
		printf( "Too many tracks (%d > %d)\n", maxtrk, NTRK );
		exit( -1 );
	}

/* read in track headers, allocate buffers, and read in track data */
	trk = 0;
	while( trk < maxtrk && !feof( f ) ) {

/* reset flags while we are at it */
		gotevt[trk] = 0;
		doneflg[trk] = 0;
		trkptr[trk] = 0;
		nextevt[trk] = 0;
		fread( &mthdmy, 1, sizeof( mthdmy ), f );
		trklen[trk] = mthdmy.trln[0];
		trklen[trk] <<= 8;
		trklen[trk] |= mthdmy.trln[1] & 0xff;
		trklen[trk] <<= 8;
		trklen[trk] |= mthdmy.trln[2] & 0xff;
		trklen[trk] <<= 8;
		trklen[trk] |= mthdmy.trln[3] & 0xff;
		printf( "Track %d Len:%ld\n", trk, trklen[trk] );
		if( !trklen[trk] )
			doneflg[trk] = 1;
		else {
			trkbuf[trk] = malloc( trklen[trk] );
			if( !trkbuf[trk] ) {
				printf( "Out of Memory\n" );
				exit( -1 );
			}
			fread( trkbuf[trk], 1, trklen[trk], f );
		}
		trk++;
	}
	fclose( f );
	tptr = ftell( of );
	fwrite( &mthdmy, 1, sizeof( mthdmy ), of );
/* need to fix .trklen at offset later */
#if 0
	if( trk != maxtrk )      /* sometimes they aren't all there */
		maxtrk = trk;
#endif
	trksleft = maxtrk;
	lastmstime = 0;
	currtime = 0;
	while( trksleft ) {

/* scan tracks for ready event */
		for( trk = 0; trk < maxtrk; trk++ ) {

/* process track while events can be done */
			while( !doneflg[trk] ) {

/* get time of next event */
				if( !gotevt[trk] )
					nextevt[trk] += getvar();

/* check if next event is later */
				if( currtime < nextevt[trk] ) {
					gotevt[trk] = 1;
					break;
				}
				else
					gotevt[trk] = 0;
				deltime = currtime - lastmstime;
				lastmstime = currtime;
/* get status byte and dispatch by type */
				c = mgetc();
				if( c > 0x7f )
					laststs[trk] = c;
				else {
					trkptr[trk]--;
					c = laststs[trk];
				}
				switch( c >> 4 ) {
					case 0x0f:
						switch( c & 0x0f ) {
							case 0x00:
							case 0x07:
							case 0x0f:
								dometa( c,
									deltime
									);
							break;
							default:
								printf(
							"UNKNOWN %02x   \n", c
							);
							break;
						}
					break;
/* these are split out so you can play with the settings if desired */
					case 0x08: /* note off */
					case 0x09: /* note on */
					case 0x0a: /* poly aftch */
					case 0x0e: /* pitch bend */
					case 0x0b: /* controller */
						putvar( deltime );
					mtype = c;
					mputc( c );
					c = mgetc();
					mputc( c );
					d = mgetc();
					mputc( d );
					break;
					case 0x0d: /* channel aftch */
					case 0x0c: /* program (patch) */
						putvar( deltime );
					mputc( c );
					d = mgetc();
					mputc( d );
					break;
					default:
						break;
				}
			}
		}
		currtime++;
		if( !( currtime % 1000 ) ) {
			printf( "%6ld delta time\r", currtime );
			fflush( stdout );
		}
	}
	mputc( 0x00 );
	mputc( 0xff );
	mputc( 0x2f );
	mputc( 0x00 );
	fwrite( inbuf, 1, optr, of );
	reclen = ftell( of ) - tptr - 8;
/* backpatch track length */
    fseek( of, tptr, 0 );   /* 0 - from BOF */
	mthdmy.trln[3] = reclen & 0xff;
	reclen >>= 8;
	mthdmy.trln[2] = reclen & 0xff;
	reclen >>= 8;
	mthdmy.trln[1] = reclen & 0xff;
	reclen >>= 8;
	mthdmy.trln[0] = reclen & 0xff;
	fwrite( &mthdmy, 1, sizeof( mthdmy ), of );
	fclose( of );
}
