/*
 * ptmidinp.c: MIDI input module for for ptmid. Reads a MIDI file and
 * creates a structure representing it.
 *
 * Author: Andrew Scott  (c)opyright 1994
 *
 * Date: 17/11/1993 ver 0.0
 *       8/1/1994   ver 0.1
 */

#include <stdio.h>
#include <stdlib.h>
#include "ptmid.h"

#define ODD(x) (x & 1)
#define MYROUTINE

typedef unsigned long VLQ; /** VLQ is a variable length quantity **/

int rgbPatch[16], wDivision;
unsigned wQuant;
NRL *rgpnrl[128];
Tune *ptuneMain, *ptuneCurr;

/*
 * Init: Yes.. you guessed it. Initializes variables and stuff.
 */
static void Init()
{
	int i;

	for (i = 16; i--; ) /** Clear current instrument array **/
		rgbPatch[i] = 0;
	for (i = 128; i--; ) /** Clear hanging-note array **/
		rgpnrl[i] = NULL;
}

/*
 * ValidquantSz: Takes a lower-case string and checks to see if it is a
 * legal quantize fraction. If not, zero is returned, else value of
 * string is returned (+1 if a triplet case).
 */
int ValidquantSz(Sz szQuant)
{
	int bFrac;
	Sz szEnd;

	if ((bFrac = strtol(szQuant, &szEnd, 10))) /** If a number **/
		if ('t' == *szEnd) /** possibly followed by a 't', then valid **/
			bFrac++;
	return bFrac;
}

/*
 * VlqFromPfile: Reads bytes from given file until a variable length
 * quantity is decoded. Returns that vlq.
 */
VLQ VlqFromPfile(FILE *pfile)
{
	int b;
	VLQ vlqRead;

#ifdef MYROUTINE

	vlqRead = 0;
	while ((b = getc(pfile)) & 128 && EOF != b)
		vlqRead = (vlqRead << 7) + (b & 127);
	return (vlqRead << 7) + b;

#else

	if ((vlqRead = getc(pfile)) & 128) {
		vlqRead &= 127;
		do
			vlqRead = (vlqRead << 7) + ((b = getc(pfile)) & 127);
		while (b & 128 && EOF != b);
	}
	return vlqRead;

#endif
}

/*
 * Addnote: Given pitch, instrument, and volume will add that note to the
 * array of playing notes.
 */
void Addnote(int pitch, int inst, int vol)
{
	NRL *pnrlT;

	if (0 > pitch || 127 < pitch)
		return;
	pnrlT = (NRL *) malloc(sizeof(NRL)); /** Allocate space **/
	pnrlT->pnrl = rgpnrl[pitch];
	rgpnrl[pitch] = pnrlT; /** Attach to front of list in hanging-note array **/
	pnrlT->inst = inst;
	pnrlT->vol = vol;
	pnrlT->ptuneNow = ptuneCurr;
}

/*
 * PeiRequestPtune: Returns a pointer to a new event structure at the
 * position in the tune specified.
 */
EI *PeiRequestPtune(Tune *ptune)
{
	EI *pei;

	pei = (EI *) malloc(sizeof(EI)); /** Allocate space for event **/
	pei->peiNext = ptune->pei;
	ptune->pei = pei; /** Attach to front of event list at tune position **/
	return pei;
}

/*
 * PeiLocatePtune: Given a duration and a position in the tune, will search
 * through for chord events at that position with matching duration.
 */
EI *PeiLocatePtune(Tune *ptune, unsigned long durat)
{
	EI *pei;

	for (pei = ptune->pei; NULL != pei && !(pei->cni && pei->effect == durat); )
		pei = pei->peiNext;
	return pei;
}

/*
 * Endnote: Given a pitch and instrument, will remove a note from array
 * of playing notes and put it in a chord in the tune.
 */
void Endnote(int pitch, int inst)
{
	NRL *pnrlT, *pnrlOld;
	unsigned long durat;
	EI *peiT;
	NI *pniT;

	if (0 > pitch || 127 < pitch)
		return;
	for (pnrlT = rgpnrl[pitch]; NULL != pnrlT && pnrlT->inst != inst; ) {
		pnrlOld = pnrlT;
		pnrlT = pnrlT->pnrl;
	} /** Find instrument in hanging-note array **/
	if (NULL == pnrlT)
		return;

	if ((durat = ptuneCurr->count - pnrlT->ptuneNow->count) == 0)
		durat = wQuant; /** Calculate its (quantized) duration **/
	if ((peiT = PeiLocatePtune(pnrlT->ptuneNow, durat)) == NULL) {
		peiT = PeiRequestPtune(pnrlT->ptuneNow); /** If must start a new chord **/
		peiT->effect = durat;
		peiT->cni = 1;
		pniT = (NI *) malloc(sizeof(NI)); /** start new note list **/
		peiT->pni = pniT;
	} else { /** Else **/
		peiT->pni = (NI *) realloc(peiT->pni, sizeof(NI) * ++(peiT->cni));
		pniT = peiT->pni + peiT->cni - 1; /** add to old note list **/
	}
	pniT->inst = inst; /** Update note info **/
	pniT->pitch = pitch;
	pniT->vol = pnrlT->vol;

	if (rgpnrl[pitch] == pnrlT) /** Remove note from hanging-note array **/
		rgpnrl[pitch] = pnrlT->pnrl;
	else
		pnrlOld->pnrl = pnrlT->pnrl;
	free(pnrlT);
}

/*
 * VlqInterpPpfile: Reads a file pointer and gathers all notes at this
 * instant, storing them on the note stack. Returns ticks until next
 * collection of notes. A running status of current channel is maintained.
 */
VLQ VlqInterpPpfile(FILE **ppfile, unsigned *pbStat)
{
	unsigned bEvent;
	VLQ vlqT = 0;

	while (0 == vlqT) { /** While doing simultaneous events.. **/
		bEvent = (unsigned) getc(*ppfile); /** Get first data byte **/
		if (0x80 <= bEvent && 0xEF >= bEvent) { /** If a command **/
			*pbStat = bEvent; /** update running-status byte **/
			bEvent = (unsigned) getc(*ppfile); /** and get next data byte **/
		}
		if (0xF0 == bEvent || 0xF7 == bEvent) /** If a sys-exclusive message **/
			fseek(*ppfile, VlqFromPfile(*ppfile), SEEK_CUR); /** skip it **/
		else if (0xFF == bEvent) { /** Else if a meta event **/
			bEvent = (unsigned) getc(*ppfile); /** get type of event **/
			vlqT = VlqFromPfile(*ppfile); /** and length **/
			if (0x2F == bEvent) { /*** If termination event ***/
				fclose(*ppfile); /*** close handle ***/
				*ppfile = NULL;
				vlqT = 0;
				break; /*** and terminate ***/
			}
			if (0x51 == bEvent) { /*** Else if tempo event ***/
				unsigned long t;
				EI *pei;

				t = (unsigned long) getc(*ppfile) << 16; /*** get value ***/
				t += (unsigned long) getc(*ppfile) << 8;
				t += (unsigned long) getc(*ppfile);
				pei = PeiRequestPtune(ptuneCurr);
				pei->effect = 60000000L / wQuant * wDivision / t; /*** and convert ***/
				pei->cni = 0;
			} else
				fseek(*ppfile, vlqT, SEEK_CUR); /*** Else skip event ***/
		} else
			switch (*pbStat & 0xF0) { /** Else must be a midi event.. **/
				case 0x80:
				case 0x90: { /** Note on/off **/
					unsigned bVol, bChan;

					bVol = getc(*ppfile);
					bChan = *pbStat & 0x0F;
					if (0 < bVol && 0x90 <= *pbStat)
						if (bChan != bDrumch)
							Addnote(bEvent, rgbPatch[bChan], bVol);
						else
							Addnote(0, -1 - bEvent, bVol);
					else
						if (bChan != bDrumch)
							Endnote(bEvent, rgbPatch[bChan]);
						else
							Endnote(0, -1 - bEvent);
					break;
				}
				case 0xA0: /** Polyphonic Key Pressure **/
				case 0xB0: /** Controller change **/
				case 0xE0: /** Pitch Wheel change **/
					getc(*ppfile);
					break;
				case 0xC0: /** Program change **/
					rgbPatch[*pbStat - 0xC0] = bEvent;
					break;
				case 0xD0: /** Channel Pressure **/
					break;
			}
		vlqT = VlqFromPfile(*ppfile);
	}
	return vlqT;
}

/*
 * Freearray: Performs a free on all structures left in rgpnrl array.
 * Assume these notes are rebels.
 */
void Freearray()
{
	int i = 128;
	NRL *pnrlT, *pnrlT2;

	while (i--) /** Go through hanging-note array **/
		for (pnrlT = rgpnrl[i]; NULL != pnrlT; ) { /** freeing each list **/
			pnrlT2 = pnrlT->pnrl;
			free(pnrlT);
			pnrlT = pnrlT2;
		}
}

/*
 * PtuneLoadPfile: Given the filename of a MIDI file, parse it and return
 * a pointer to a collection of chords (a Tune structure). If MIDI file
 * cannot be processed, NULL is returned.
 */
Tune *PtuneLoadFn(Sz FnMIDI)
{
	FILE *pfile, **ppfile;
	unsigned long cb, *pvlqWait, vlqMin = -1, vlqT, wCount, wNcount;
	unsigned ippfile, ippfileMax, *pbStatus, wQuant2;
	int cppfile;

	Init();
	pfile = fopen(FnMIDI, "rb");
	if ('M' != getc(pfile) || 'T' != getc(pfile) || 'h' != getc(pfile) ||
	 'd' != getc(pfile) || 0 != getc(pfile) || 0 != getc(pfile) ||
	 0 != getc(pfile) || 6 != getc(pfile) || 0 != getc(pfile) ||
	 1 < (unsigned) getc(pfile)) {
		fclose(pfile);
		return NULL; /** Only process type 0 or type 1 general MIDI files **/
	}

	ippfileMax = (unsigned) getc(pfile) << 8;
	ippfileMax += (unsigned) getc(pfile); /** Get # tracks **/
	wDivision = (unsigned) getc(pfile) << 8;
	wDivision += (unsigned) getc(pfile); /** Get ticks for a beat **/

	if (fseek(pfile, 23, SEEK_SET)) {
		fclose(pfile);
		return NULL; /** Error if MIDI file is smaller than 23 bytes **/
	}
	if (fNocopy && getc(pfile) == 0xFF && getc(pfile) == 0x02) {
		fclose(pfile);
		if (!fQuiet)
			printf("** NOCOPY option set and copyright notice found in file **\n");
		return NULL; /** Error if Nocopy and copyright notice exists **/
	}
	if (32767 < wDivision) {
		if (!fQuiet)
			printf("** Slack programmer error -- SMPTE frames not supported **\n");
		fclose(pfile);
		return NULL;
	}

	if (ODD(wQuantval)) /** Calculate quantize ticks from quantize fraction **/
		wQuant = wDivision * 8 / (3 * (wQuantval - 1));
	else
		wQuant = wDivision * 4 / wQuantval;
	wQuant2 = wQuant/2;
	if (!fQuiet) {
		printf("Ticks to quantize: %u\n", wQuant);
		printf("Number of tracks: %d\n", ippfileMax);
	}

	ppfile = (FILE **) malloc(sizeof(FILE *) * ippfileMax);
	pvlqWait = (VLQ *) malloc(sizeof(VLQ) * ippfileMax);
	pbStatus = (unsigned *) malloc(sizeof(unsigned) * ippfileMax);

	fseek(pfile, 18, SEEK_SET);
	/** Put file pointers at the start of each track in file **/
	for (ippfile = 0; !feof(pfile) && ippfile < ippfileMax; ippfile++) {
		cb = (unsigned long) getc(pfile) << 24;
		cb += (unsigned long) getc(pfile) << 16;
		cb += (unsigned long) getc(pfile) << 8;
		cb += (unsigned long) getc(pfile);
		if ((ppfile[ippfile] = fopen(FnMIDI, "rb")) == NULL) {
			fprintf(stderr, "ptmid: No more files can be opened\n");
			exit(1);
		}
		fseek(ppfile[ippfile], ftell(pfile), SEEK_SET);
		if ((pvlqWait[ippfile] = VlqFromPfile(ppfile[ippfile])) < vlqMin)
			vlqMin = pvlqWait[ippfile]; /** Find minimum time to first event **/
		pbStatus[ippfile] = 0x90;
		fseek(pfile, cb + 4, SEEK_CUR);
	}
	fclose(pfile);
	if (ippfile != ippfileMax) {
		printf("** MIDI file ends prematurely **\n");
		free(ppfile);
		free(pvlqWait);
		free(pbStatus);
		return NULL;
	}

	ptuneMain = ptuneCurr = (Tune *) malloc(sizeof(Tune));
	wCount = vlqMin; /** Start from first event **/
	wNcount = (wCount + wQuant2) % wQuant;
	ptuneMain->count = wCount + wQuant2 - wNcount;
	wNcount = wQuant - wNcount;
	ptuneMain->ptune = NULL;
	ptuneMain->pei = NULL;
	cppfile = 1;
	while (0 < cppfile) { /** While still tracks in file to process **/
		cppfile = 0;
		vlqT = -1;
		for (ippfile = 0; ippfile < ippfileMax; ippfile++) /** With each track.. **/
			if (NULL != ppfile[ippfile]) { /** If not finished **/
				cppfile++;
				/**
				 ** Must keep all tracks in sync, if events occurring on this track
				 ** at this instant, then interpret them. Also note when next event
				 ** will occur (ie. minimum ticks to next event)
				 **/
				if ((pvlqWait[ippfile] -= vlqMin) == 0)
					pvlqWait[ippfile] = VlqInterpPpfile(&ppfile[ippfile], pbStatus +
						ippfile);
				if (pvlqWait[ippfile] < vlqT)
					vlqT = pvlqWait[ippfile];
			}

		vlqMin = vlqT;
		wCount += vlqMin;
		if (wNcount <= vlqMin) { /** If need to advance to new quanta **/
			if ((ptuneCurr->ptune = (Tune *) malloc(sizeof(Tune))) == NULL) {
				fprintf(stderr, "ptmid: Cannot allocate any more memory\n");
				exit(1);
			} /** allocate **/
			ptuneCurr = ptuneCurr->ptune; /** and initialize **/
			wNcount = (wCount + wQuant2) % wQuant;
			ptuneCurr->count = wCount + wQuant2 - wNcount;
			wNcount = wQuant - wNcount;
			ptuneCurr->ptune = NULL;
			ptuneCurr->pei = NULL;
		} else
			wNcount -= vlqMin; /** Else decrememnt "new quanta" count **/
	}

	Freearray();
	free(pvlqWait);
	free(pbStatus);
	free(ppfile);

	return ptuneMain;
}
