/** SGX

The   sss    gggg x   x   
     s      g      x x    Engine
      sss   g  gg   x
         s  g   g  x x          Copyright 1995-2009.
     ssss    gggg x   x         Steven Goodwin. All Rights Reserved.
    
This version has been released under the GNU Public License v2.0.

Please see the license files for details.

File: sgx/src/domains/midi/midifile.cpp

*/

/*
 * midiFile.c - A general purpose midi file handling library. This code
 *				can read and write MIDI files in formats 0 and 1.
 * Version 1.1
 *
 *  AUTHOR: Steven Goodwin (StevenGoodwin@gmail.com)
 *          Copyright 1998-2008, Steven Goodwin
 *
 *  This program is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU General Public License as
 *  published by the Free Software Foundation; either version 2 of
 *  the License,or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
#define _CRT_SECURE_NO_DEPRECATE

#include "sgx/core/core.hpp"
#include "sgx/filesystem/filesystem.hpp"


#include "domains/midi/midi.hpp"

/*
** Internal Data Structures
*/
typedef struct 	{
				BYTE note, chn;
				BYTE valid, p2;
				DWORD end_pos;
				} MIDI_LAST_NOTE;

typedef struct 	{
				BYTE *ptr;
				BYTE *pBase;
				BYTE *pEnd;
				
				DWORD pos;
				DWORD dt;
				/* For Reading MIDI Files */
				DWORD sz;						/* size of whole iTrack */
				/* For Writing MIDI Files */
				DWORD iBlockSize;				/* max size of track */
				BYTE iDefaultChannel;			/* use for write only */
				BYTE last_status;				/* used for running status */
				
				MIDI_LAST_NOTE LastNote[MAX_TRACK_POLYPHONY];
				} MIDI_FILE_TRACK;

typedef struct 	{
				DWORD	iHeaderSize;
				/**/
				WORD	iVersion;		/* 0, 1 or 2 */
				WORD	iNumTracks;		/* number of tracks... (will be 1 for MIDI type 0) */
				WORD	PPQN;			/* pulses per quarter note */
				} MIDI_HEADER;

typedef struct {
				CSGXFile *			pFile;
				BOOL				bOpenForWriting;
				
				MIDI_HEADER			Header;
				BYTE *ptr;			/* to whole data block */
				DWORD file_sz;
				
				MIDI_FILE_TRACK		Track[MAX_MIDI_TRACKS];
				} _MIDI_FILE;


/*
** Internal Functions
*/
#define DT_DEF				32			/* assume maximum delta-time + msg is no more than 32 bytes */
#if SGX_ENDIAN_LITTLE
#define SWAP_WORD(w)		(WORD)(((w)>>8)|((w)<<8))
#define SWAP_DWORD(d)		(DWORD)((d)>>24)|(((d)>>8)&0xff00)|(((d)<<8)&0xff0000)|(((d)<<24))
#else
#define SWAP_WORD(w)		w
#define SWAP_DWORD(d)		d
#endif

#define _VAR_CAST				_MIDI_FILE *pMF = (_MIDI_FILE *)_pMF
#define IsFilePtrValid(pMF)		(pMF)
#define IsTrackValid(_x)		(_midiValidateTrack(pMF, _x))
#define IsChannelValid(_x)		((_x)>=1 && (_x)<=16)
#define IsNoteValid(_x)			((_x)>=0 && (_x)<128)
#define IsMessageValid(_x)		((_x)>=msgNoteOff && (_x)<=msgMetaEvent)


static BOOL _midiValidateTrack(const _MIDI_FILE *pMF, int iTrack)
{
	if (!IsFilePtrValid(pMF))	return FALSE;
	
	if (pMF->bOpenForWriting)
		{
		if (iTrack < 0 || iTrack >= MAX_MIDI_TRACKS)
			return FALSE;
		}
	else	/* open for reading */
		{
		if (!pMF->ptr)
			return FALSE;
		
		if (iTrack < 0 || iTrack>=pMF->Header.iNumTracks)
			return FALSE;
		}
	
	return TRUE;
}

static BYTE *_midiWriteVarLen(BYTE *ptr, int n)
{
long buffer;
long value=n;
int bContinue = 1;

	buffer = value & 0x7f;
	while ((value >>= 7) > 0)
		{
		buffer <<= 8;
		buffer |= 0x80;
		buffer += (value & 0x7f);
		}

	while (bContinue) {
		*ptr++ = (BYTE)buffer;
		if (buffer & 0x80) {
			buffer >>= 8;
		} else {
			bContinue = 0;
		}
	}
	
	return(ptr);
}

/* Return a ptr to valid block of memory to store a message
** of up to sz_reqd bytes 
*/
static BYTE *_midiGetPtr(_MIDI_FILE *pMF, int iTrack, int sz_reqd)
{
const DWORD mem_sz_inc = 8092;	/* arbitary */
BYTE *ptr;
int curr_offset;
MIDI_FILE_TRACK *pTrack = &pMF->Track[iTrack];

	ptr = pTrack->ptr;
	if (ptr == NULL || ptr+sz_reqd > pTrack->pEnd)		/* need more RAM! */
		{
		curr_offset = ptr-pTrack->pBase;
		if ((ptr = (BYTE *)realloc(pTrack->pBase, mem_sz_inc+pTrack->iBlockSize)) != 0)
			{
			pTrack->pBase = ptr;
			pTrack->iBlockSize += mem_sz_inc;
			pTrack->pEnd = ptr+pTrack->iBlockSize;
			/* Move new ptr to continue data entry: */
			pTrack->ptr = ptr+curr_offset;
			ptr += curr_offset;
			}
		else
			{
			/* NO MEMORY LEFT */
			return NULL;
			}
		}

	return ptr;
}


static int _midiGetLength(int ppqn, int iNoteLen, BOOL bOverride)
{
int length = ppqn;
	
	if (bOverride)
		{
		length = iNoteLen;
		}
	else
		{
		switch(iNoteLen)
			{
			case	MIDI_NOTE_DOTTED_MINIM:
						length *= 3;
						break;

			case	MIDI_NOTE_DOTTED_CROCHET:
						length *= 3;
						length /= 2;
						break;

			case	MIDI_NOTE_DOTTED_QUAVER:
						length *= 3;
						length /= 4;
						break;

			case	MIDI_NOTE_DOTTED_SEMIQUAVER:
						length *= 3;
						length /= 8;
						break;

			case	MIDI_NOTE_DOTTED_SEMIDEMIQUAVER:
						length *= 3;
						length /= 16;
						break;

			case	MIDI_NOTE_BREVE:
						length *= 4;
						break;

			case	MIDI_NOTE_MINIM:
						length *= 2;
						break;

			case	MIDI_NOTE_QUAVER:
						length /= 2;
						break;

			case	MIDI_NOTE_SEMIQUAVER:
						length /= 4;
						break;

			case	MIDI_NOTE_SEMIDEMIQUAVER:
						length /= 8;
						break;
			
			case	MIDI_NOTE_TRIPLE_CROCHET:
						length *= 2;
						length /= 3;
						break;			
			}
		}
	
	return length;
}

/*
** midiFile* Functions
*/
MIDI_FILE  *midiFileCreate(const char *pFilename, BOOL bOverwriteIfExists)
{
_MIDI_FILE *pMF = new _MIDI_FILE();
int i;

	if (!pMF)							return NULL;
	
	if (!bOverwriteIfExists) {
		if (CSGXFileSystem::get()->exists(pFilename)) {
			return NULL;
		}
	}

	pMF->pFile = new CSGXFile(pFilename, "rw");
	if (pMF->pFile->open()) {
		/*empty*/
	} else {
		delete pMF->pFile;
		delete pMF;
		return NULL;
	}
	
	pMF->bOpenForWriting = TRUE;
	pMF->Header.PPQN = MIDI_PPQN_DEFAULT;
	pMF->Header.iVersion = MIDI_VERSION_DEFAULT;
	
	for(i=0;i<MAX_MIDI_TRACKS;++i)
		{
		pMF->Track[i].pos = 0;
		pMF->Track[i].ptr = NULL;
		pMF->Track[i].pBase = NULL;
		pMF->Track[i].pEnd = NULL;
		pMF->Track[i].iBlockSize = 0;
		pMF->Track[i].dt = 0;
		pMF->Track[i].iDefaultChannel = (BYTE)(i & 0xf);
		
		sgxMemset(pMF->Track[i].LastNote, '\0', sizeof(pMF->Track[i].LastNote));
		}
	
	return (MIDI_FILE *)pMF;
}

int		midiFileSetTracksDefaultChannel(MIDI_FILE *_pMF, int iTrack, int iChannel)
{
int prev;

	_VAR_CAST;
	if (!IsFilePtrValid(pMF))				return 0;
	if (!IsTrackValid(iTrack))				return 0;
	if (!IsChannelValid(iChannel))			return 0;

	/* For programmer each, iChannel is between 1 & 16 - but MIDI uses
	** 0-15. Thus, the fudge factor of 1 :)
	*/
	prev = pMF->Track[iTrack].iDefaultChannel+1;
	pMF->Track[iTrack].iDefaultChannel = (BYTE)(iChannel-1);
	return prev;
}

int		midiFileGetTracksDefaultChannel(const MIDI_FILE *_pMF, int iTrack)
{
	_VAR_CAST;
	if (!IsFilePtrValid(pMF))				return 0;
	if (!IsTrackValid(iTrack))				return 0;

	return pMF->Track[iTrack].iDefaultChannel+1;
}

int		midiFileSetPPQN(MIDI_FILE *_pMF, int PPQN)
{
int prev;

	_VAR_CAST;
	if (!IsFilePtrValid(pMF))				return MIDI_PPQN_DEFAULT;
	prev = pMF->Header.PPQN;
	pMF->Header.PPQN = (WORD)PPQN;
	return prev;
}

int		midiFileGetPPQN(const MIDI_FILE *_pMF)
{
	_VAR_CAST;
	if (!IsFilePtrValid(pMF))				return MIDI_PPQN_DEFAULT;
	return (int)pMF->Header.PPQN;
}

int		midiFileSetVersion(MIDI_FILE *_pMF, int iVersion)
{
int prev;

	_VAR_CAST;
	if (!IsFilePtrValid(pMF))				return MIDI_VERSION_DEFAULT;
	if (iVersion<0 || iVersion>2)			return MIDI_VERSION_DEFAULT;
	prev = pMF->Header.iVersion;
	pMF->Header.iVersion = (WORD)iVersion;
	return prev;
}

int			midiFileGetVersion(const MIDI_FILE *_pMF)
{
	_VAR_CAST;
	if (!IsFilePtrValid(pMF))				return MIDI_VERSION_DEFAULT;
	return pMF->Header.iVersion;
}

MIDI_FILE  *midiFileOpen(const char *pFilename)
{
	CSGXFile fp(pFilename);

_MIDI_FILE *pMF = NULL;
BYTE *ptr;
BOOL bValidFile=FALSE;
long size;

	if (fp.open()) 
		{
		if ((pMF = new _MIDI_FILE()) != 0)
			{
				size = fp.getFileSize();

			if ((pMF->ptr = new BYTE[size]) != 0)
				{
				fp.read(pMF->ptr, size);
				/* Is this a valid MIDI file ? */
				ptr = pMF->ptr;
				if (*(ptr+0) == 'M' && *(ptr+1) == 'T' && 
					*(ptr+2) == 'h' && *(ptr+3) == 'd')
					{
					DWORD dwData;
					WORD wData;
					int i;

					dwData = *((DWORD *)(ptr+4));
					pMF->Header.iHeaderSize = SWAP_DWORD(dwData);
					
					wData = *((WORD *)(ptr+8));
					pMF->Header.iVersion = (WORD)SWAP_WORD(wData);
					
					wData = *((WORD *)(ptr+10));
					pMF->Header.iNumTracks = (WORD)SWAP_WORD(wData);
					
					wData = *((WORD *)(ptr+12));
					pMF->Header.PPQN = (WORD)SWAP_WORD(wData);
					
					ptr += pMF->Header.iHeaderSize+8;
					/*
					**	 get all tracks
					*/
					for(i=0;i<MAX_MIDI_TRACKS;++i)
						{
						pMF->Track[i].pos = 0;
						pMF->Track[i].last_status = 0;
						}
					
					for(i=0;i<pMF->Header.iNumTracks;++i)
						{
						pMF->Track[i].pBase = ptr;
						pMF->Track[i].ptr = ptr+8;
						dwData = *((DWORD *)(ptr+4));
						pMF->Track[i].sz = SWAP_DWORD(dwData);
						pMF->Track[i].pEnd = ptr+pMF->Track[i].sz+8;
						ptr += pMF->Track[i].sz+8;
						}
						   
					pMF->bOpenForWriting = FALSE;
					pMF->pFile = NULL;
					bValidFile = TRUE;
					}
				}
			}

		fp.close();
		}
	
	if (!bValidFile)
		{
		if (pMF)	   delete pMF;
		return NULL;
		}
	
	return (MIDI_FILE *)pMF;
}

typedef struct {
		int	iIdx;
		int	iEndPos;
		} MIDI_END_POINT;

static int qs_cmp_pEndPoints(const void *e1, const void *e2)
{
MIDI_END_POINT *p1 = (MIDI_END_POINT *)e1;
MIDI_END_POINT *p2 = (MIDI_END_POINT *)e2;

	return p1->iEndPos-p2->iEndPos;
}

BOOL	midiFileFlushTrack(MIDI_FILE *_pMF, int iTrack, BOOL bFlushToEnd, DWORD dwEndTimePos)
{
int sz;
BYTE *ptr;
MIDI_END_POINT *pEndPoints;
int num, i, mx_pts;
BOOL bNoChanges = TRUE;

	_VAR_CAST;
	if (!IsFilePtrValid(pMF))				return FALSE;
	if (!_midiValidateTrack(pMF, iTrack))	return FALSE;
	sz = sizeof(pMF->Track[0].LastNote)/sizeof(pMF->Track[0].LastNote[0]);

	/*
	** flush all 
	*/
	pEndPoints = new MIDI_END_POINT[sz];
	mx_pts = 0;
	for(i=0;i<sz;++i)
		if (pMF->Track[iTrack].LastNote[i].valid)
			{
			pEndPoints[mx_pts].iIdx = i;
			pEndPoints[mx_pts].iEndPos = pMF->Track[iTrack].LastNote[i].end_pos;
			mx_pts++;
			}
	
	if (bFlushToEnd)
		{
		if (mx_pts)
			dwEndTimePos = pEndPoints[mx_pts-1].iEndPos;
		else
			dwEndTimePos = pMF->Track[iTrack].pos;
		}
	
	if (mx_pts)
		{
		/* Sort, smallest first, and add the note off msgs */
		qsort(pEndPoints, mx_pts, sizeof(MIDI_END_POINT), qs_cmp_pEndPoints);
		
		i = 0;
		while ((dwEndTimePos >= (DWORD)pEndPoints[i].iEndPos || bFlushToEnd) && i<mx_pts)
			{
			ptr = _midiGetPtr(pMF, iTrack, DT_DEF);
			if (!ptr)
				return FALSE;
			
			num = pEndPoints[i].iIdx;		/* get 'LastNote' index */
			
			ptr = _midiWriteVarLen(ptr, pMF->Track[iTrack].LastNote[num].end_pos - pMF->Track[iTrack].pos);
			/* msgNoteOn  msgNoteOff */
			*ptr++ = (BYTE)(msgNoteOff | pMF->Track[iTrack].LastNote[num].chn);
			*ptr++ = pMF->Track[iTrack].LastNote[num].note;
			*ptr++ = 0;
			
			pMF->Track[iTrack].LastNote[num].valid = FALSE;
			pMF->Track[iTrack].pos = pMF->Track[iTrack].LastNote[num].end_pos;
			
			pMF->Track[iTrack].ptr = ptr;
			
			++i;
			bNoChanges = FALSE;
			}
		}
	
	delete pEndPoints;
	/*
	** Re-calc current position
	*/
	pMF->Track[iTrack].dt = dwEndTimePos - pMF->Track[iTrack].pos;
	
	return TRUE;
}

BOOL	midiFileSyncTracks(MIDI_FILE *_pMF, int iTrack1, int iTrack2)
{
int p1, p2;

	_VAR_CAST;
	if (!IsFilePtrValid(pMF))			return FALSE;
	if (!IsTrackValid(iTrack1))			return FALSE;
	if (!IsTrackValid(iTrack2))			return FALSE;

	p1 = pMF->Track[iTrack1].pos + pMF->Track[iTrack1].dt;
	p2 = pMF->Track[iTrack2].pos + pMF->Track[iTrack2].dt;
	
	if (p1 < p2)		midiTrackIncTime(pMF, iTrack1, p2-p1, TRUE);
	else if (p2 < p1)	midiTrackIncTime(pMF, iTrack2, p1-p2, TRUE);
	
	return TRUE;
}


BOOL	midiFileClose(MIDI_FILE *_pMF)
{
	_VAR_CAST;
	if (!IsFilePtrValid(pMF))			return FALSE;
	
	if (pMF->bOpenForWriting)	
		{
		WORD iNumTracks = 0;
		WORD wTest = 256;
		BOOL bSwap = FALSE;
		int i;

		/* Intel processor style-endians need byte swap :( */
		if (*((BYTE *)&wTest) == 0)
			bSwap = TRUE;

		/* flush our buffers  */
		for(i=0;i<MAX_MIDI_TRACKS;++i)
			{
			if (pMF->Track[i].ptr)
				{
				midiSongAddEndSequence(pMF, i);
				midiFileFlushTrack(pMF, i, TRUE, 0);
				iNumTracks++;
				}
			}
		/* 
		** Header 
		*/
		{
		const tBYTE mthd[4] = {'M', 'T', 'h', 'd'};
		DWORD dwData;
		WORD wData;
		WORD version, PPQN;

			pMF->pFile->write(mthd, 4);
			dwData = 6;
			if (bSwap)	dwData = SWAP_DWORD(dwData);
			pMF->pFile->write((tBYTE *)&dwData, sizeof(DWORD));

			wData = (WORD)(iNumTracks==1?pMF->Header.iVersion:1);
			if (bSwap)	version = SWAP_WORD(wData); else version = (WORD)wData;
			if (bSwap)	iNumTracks = SWAP_WORD(iNumTracks);
			wData = pMF->Header.PPQN;
			if (bSwap)	PPQN = SWAP_WORD(wData); else PPQN = wData;
			pMF->pFile->write((tBYTE *)&version, sizeof(WORD));
			pMF->pFile->write((tBYTE *)&iNumTracks, sizeof(WORD));
			pMF->pFile->write((tBYTE *)&PPQN, sizeof(WORD));
		}
		/*
		** Track data
		*/
		for(i=0;i<MAX_MIDI_TRACKS;++i)
			if (pMF->Track[i].ptr)
				{
				const tBYTE mtrk[4] = {'M', 'T', 'r', 'k'};
				DWORD sz, dwData;

				/* write track header */
				pMF->pFile->write((tBYTE *)&mtrk, 4);

				/* write data size */
				sz = dwData = (int)(pMF->Track[i].ptr - pMF->Track[i].pBase);
				if (bSwap)	sz = SWAP_DWORD(sz);
				pMF->pFile->write((tBYTE *)&sz, sizeof(DWORD));

				/* write data */
				pMF->pFile->write((tBYTE *)pMF->Track[i].pBase, dwData);
				
				/* Free memory */
				delete pMF->Track[i].pBase;
				}

		}

	if (pMF->pFile) {
		pMF->pFile->close();
		delete pMF->pFile;
		pMF->pFile = NULL;
	}
	delete pMF;
	return TRUE;
}


/*
** midiSong* Functions
*/
BOOL	midiSongAddSMPTEOffset(MIDI_FILE *_pMF, int iTrack, int iHours, int iMins, int iSecs, int iFrames, int iFFrames)
{
static BYTE tmp[] = {msgMetaEvent, metaSMPTEOffset, 0x05, 0,0,0,0,0};

	_VAR_CAST;
	if (!IsFilePtrValid(pMF))				return FALSE;
	if (!IsTrackValid(iTrack))				return FALSE;

	if (iMins<0 || iMins>59)		iMins=0;
	if (iSecs<0 || iSecs>59)		iSecs=0;
	if (iFrames<0 || iFrames>24)	iFrames=0;

	tmp[3] = (BYTE)iHours;
	tmp[4] = (BYTE)iMins;
	tmp[5] = (BYTE)iSecs;
	tmp[6] = (BYTE)iFrames;
	tmp[7] = (BYTE)iFFrames;
	return midiTrackAddRaw(pMF, iTrack, sizeof(tmp), tmp, FALSE, 0);
}


BOOL	midiSongAddSimpleTimeSig(MIDI_FILE *_pMF, int iTrack, int iNom, int iDenom)
{
	return midiSongAddTimeSig(_pMF, iTrack, iNom, iDenom, 24, 8);
}

BOOL	midiSongAddTimeSig(MIDI_FILE *_pMF, int iTrack, int iNom, int iDenom, int iClockInMetroTick, int iNotated32nds)
{
static BYTE tmp[] = {msgMetaEvent, metaTimeSig, 0x04, 0,0,0,0};

	_VAR_CAST;
	if (!IsFilePtrValid(pMF))				return FALSE;
	if (!IsTrackValid(iTrack))				return FALSE;

	tmp[3] = (BYTE)iNom;
	tmp[4] = (BYTE)(MIDI_NOTE_MINIM/iDenom);
	tmp[5] = (BYTE)iClockInMetroTick;
	tmp[6] = (BYTE)iNotated32nds;
	return midiTrackAddRaw(pMF, iTrack, sizeof(tmp), tmp, FALSE, 0);
}

BOOL	midiSongAddKeySig(MIDI_FILE *_pMF, int iTrack, tMIDI_KEYSIG iKey)
{
static BYTE tmp[] = {msgMetaEvent, metaKeySig, 0x02, 0, 0};

	_VAR_CAST;
	if (!IsFilePtrValid(pMF))				return FALSE;
	if (!IsTrackValid(iTrack))				return FALSE;

	tmp[3] = (BYTE)((iKey&keyMaskKey)*((iKey&keyMaskNeg)?-1:1));
	tmp[4] = (BYTE)((iKey&keyMaskMin)?1:0);
	return midiTrackAddRaw(pMF, iTrack, sizeof(tmp), tmp, FALSE, 0);
}

BOOL	midiSongAddTempo(MIDI_FILE *_pMF, int iTrack, int iTempo)
{
static BYTE tmp[] = {msgMetaEvent, metaSetTempo, 0x03, 0,0,0};
int us;	/* micro-seconds per qn */

	_VAR_CAST;
	if (!IsFilePtrValid(pMF))				return FALSE;
	if (!IsTrackValid(iTrack))				return FALSE;

	us = 60000000L/iTempo;
	tmp[3] = (BYTE)((us>>16)&0xff);
	tmp[4] = (BYTE)((us>>8)&0xff);
	tmp[5] = (BYTE)((us>>0)&0xff);
	return midiTrackAddRaw(pMF, iTrack, sizeof(tmp), tmp, FALSE, 0);
}

BOOL	midiSongAddMIDIPort(MIDI_FILE *_pMF, int iTrack, int iPort)
{
static BYTE tmp[] = {msgMetaEvent, metaMIDIPort, 1, 0};

	_VAR_CAST;
	if (!IsFilePtrValid(pMF))				return FALSE;
	if (!IsTrackValid(iTrack))				return FALSE;
	tmp[3] = (BYTE)iPort;
	return midiTrackAddRaw(pMF, iTrack, sizeof(tmp), tmp, FALSE, 0);
}

BOOL	midiSongAddEndSequence(MIDI_FILE *_pMF, int iTrack)
{
static BYTE tmp[] = {msgMetaEvent, metaEndSequence, 0};

	_VAR_CAST;
	if (!IsFilePtrValid(pMF))				return FALSE;
	if (!IsTrackValid(iTrack))				return FALSE;

	return midiTrackAddRaw(pMF, iTrack, sizeof(tmp), tmp, FALSE, 0);
}


/*
** midiTrack* Functions
*/
BOOL	midiTrackAddRaw(MIDI_FILE *_pMF, int iTrack, int data_sz, const BYTE *pData, BOOL bMovePtr, int dt)
{
MIDI_FILE_TRACK *pTrk;
BYTE *ptr;
int dtime;

	_VAR_CAST;
	if (!IsFilePtrValid(pMF))			return FALSE;
	if (!IsTrackValid(iTrack))			return FALSE;
	
	pTrk = &pMF->Track[iTrack];
	ptr = _midiGetPtr(pMF, iTrack, data_sz+DT_DEF);
	if (!ptr)
		return FALSE;
	
	dtime = pTrk->dt;
	if (bMovePtr)
		dtime += dt;
	
	ptr = _midiWriteVarLen(ptr, dtime);
	sgxMemcpy(ptr, pData, data_sz);
	
	pTrk->pos += dtime;
	pTrk->dt = 0;
	pTrk->ptr = ptr+data_sz;
	
	return TRUE;
}


BOOL	midiTrackIncTime(MIDI_FILE *_pMF, int iTrack, int iDeltaTime, BOOL bOverridePPQN)
{
DWORD will_end_at;

	_VAR_CAST;
	if (!IsFilePtrValid(pMF))				return FALSE;
	if (!IsTrackValid(iTrack))				return FALSE;
	
	will_end_at = _midiGetLength(pMF->Header.PPQN, iDeltaTime, bOverridePPQN);
	will_end_at += pMF->Track[iTrack].pos + pMF->Track[iTrack].dt;
	
	midiFileFlushTrack(pMF, iTrack, FALSE, will_end_at);
	
	return TRUE;
}

BOOL	midiTrackAddText(MIDI_FILE *_pMF, int iTrack, tMIDI_TEXT iType, const char *pTxt)
{
BYTE *ptr;
int sz;

	_VAR_CAST;
	if (!IsFilePtrValid(pMF))				return FALSE;
	if (!IsTrackValid(iTrack))				return FALSE;

	sz = sgxStrlen(pTxt);
	if ((ptr = _midiGetPtr(pMF, iTrack, sz+DT_DEF)) != 0)
		{
		*ptr++ = 0;		/* delta-time=0 */
		*ptr++ = msgMetaEvent;
		*ptr++ = (BYTE)iType;
		ptr = _midiWriteVarLen((BYTE *)ptr, sz);
		sgxStrcpy((tCHAR *)ptr, (const tCHAR *)pTxt);
		pMF->Track[iTrack].ptr = ptr+sz;
		return TRUE;
		}
	else		 
		{
		return FALSE;
		}
}

BOOL	midiTrackSetKeyPressure(MIDI_FILE *pMF, int iTrack, int iNote, int iAftertouch)
{
	return midiTrackAddMsg(pMF, iTrack, msgNoteKeyPressure, iNote, iAftertouch);
}

BOOL	midiTrackAddControlChange(MIDI_FILE *pMF, int iTrack, tMIDI_CC iCCType, int iParam)
{
	return midiTrackAddMsg(pMF, iTrack, msgControlChange, iCCType, iParam);
}

BOOL	midiTrackAddProgramChange(MIDI_FILE *pMF, int iTrack, int iInstrPatch)
{
	return midiTrackAddMsg(pMF, iTrack, msgSetProgram, iInstrPatch, 0);
}

BOOL	midiTrackChangeKeyPressure(MIDI_FILE *pMF, int iTrack, int iDeltaPressure)
{
	return midiTrackAddMsg(pMF, iTrack, msgChangePressure, iDeltaPressure&0x7f, 0);
}

BOOL	midiTrackSetPitchWheel(MIDI_FILE *pMF, int iTrack, int iWheelPos)
{
WORD wheel = (WORD)iWheelPos;

	/* bitshift 7 instead of eight because we're dealing with 7 bit numbers */
	wheel += MIDI_WHEEL_CENTRE;
	return midiTrackAddMsg(pMF, iTrack, msgSetPitchWheel, wheel&0x7f, (wheel>>7)&0x7f);
}

BOOL	midiTrackAddMsg(MIDI_FILE *_pMF, int iTrack, tMIDI_MSG iMsg, int iParam1, int iParam2)
{
BYTE *ptr;
BYTE data[3];
int sz;

	_VAR_CAST;
	if (!IsFilePtrValid(pMF))				return FALSE;
	if (!IsTrackValid(iTrack))				return FALSE;
	if (!IsMessageValid(iMsg))				return FALSE;

	ptr = _midiGetPtr(pMF, iTrack, DT_DEF);
	if (!ptr)
		return FALSE;
	
	data[0] = (BYTE)(iMsg | pMF->Track[iTrack].iDefaultChannel);
	data[1] = (BYTE)(iParam1 & 0x7f); 
	data[2] = (BYTE)(iParam2 & 0x7f); 
	/*
	** Is this msg a single, or double BYTE, prm?
	*/
 	switch(iMsg)
		{
		case	msgSetProgram:			/* only one byte required for these msgs */
		case	msgChangePressure:
									sz = 2;
									break;

		default:						/* double byte messages */
									sz = 3;
									break;
		}
	
	return midiTrackAddRaw(pMF, iTrack, sz, data, FALSE, 0);

}

BOOL	midiTrackAddNote(MIDI_FILE *_pMF, int iTrack, int iNote, int iLength, int iVol, BOOL bAutoInc, BOOL bOverrideLength)
{
MIDI_FILE_TRACK *pTrk;
BYTE *ptr;
BOOL bSuccess = FALSE;
int i, chn;

	_VAR_CAST;
	if (!IsFilePtrValid(pMF))				return FALSE;
	if (!IsTrackValid(iTrack))				return FALSE;
	if (!IsNoteValid(iNote))				return FALSE;
	
	pTrk = &pMF->Track[iTrack];
	ptr = _midiGetPtr(pMF, iTrack, DT_DEF);
	if (!ptr)
		return FALSE;
	
	chn = pTrk->iDefaultChannel;
	iLength = _midiGetLength(pMF->Header.PPQN, iLength, bOverrideLength);
	
	for(i=0;i<sizeof(pTrk->LastNote)/sizeof(pTrk->LastNote[0]);++i)
		if (pTrk->LastNote[i].valid == FALSE)
			{
			pTrk->LastNote[i].note = (BYTE)iNote;
			pTrk->LastNote[i].chn = (BYTE)chn;
			pTrk->LastNote[i].end_pos = pTrk->pos+pTrk->dt+iLength;
			pTrk->LastNote[i].valid = TRUE;
			bSuccess = TRUE;
			
			ptr = _midiWriteVarLen(ptr, pTrk->dt);		/* delta-time */
			*ptr++ = (BYTE)(msgNoteOn | chn);
			*ptr++ = (BYTE)iNote;
			*ptr++ = (BYTE)iVol;
			break;
			}
	
	if (!bSuccess)
		return FALSE;
	
	pTrk->ptr = ptr;
	
	pTrk->pos += pTrk->dt;
	pTrk->dt = 0;
	
	if (bAutoInc)
		return midiTrackIncTime(pMF, iTrack, iLength, bOverrideLength);
	
	return TRUE;
}

BOOL	midiTrackAddRest(MIDI_FILE *_pMF, int iTrack, int iLength, BOOL bOverridePPQN)
{
	_VAR_CAST;
	if (!IsFilePtrValid(pMF))				return FALSE;
	if (!IsTrackValid(iTrack))				return FALSE;

	iLength = _midiGetLength(pMF->Header.PPQN, iLength, bOverridePPQN);
	return midiTrackIncTime(pMF, iTrack, iLength, bOverridePPQN);
}

int		midiTrackGetEndPos(MIDI_FILE *_pMF, int iTrack)
{
	_VAR_CAST;
	if (!IsFilePtrValid(pMF))				return FALSE;
	if (!IsTrackValid(iTrack))				return FALSE;

	return pMF->Track[iTrack].pos;
}

/*
** midiRead* Functions
*/
static BYTE *_midiReadVarLen(BYTE *ptr, DWORD *num)
{
DWORD value;
BYTE c;

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


static BOOL _midiReadTrackCopyData(MIDI_MSG *pMsg, BYTE *ptr, DWORD sz, BOOL bCopyPtrData)
{
	if (sz > pMsg->data_sz)
		{
		pMsg->data = (BYTE *)realloc(pMsg->data, sz);
		pMsg->data_sz = sz;
		}
	
	if (!pMsg->data)
		return FALSE;
	
	if (bCopyPtrData && ptr)
		sgxMemcpy(pMsg->data, ptr, sz);
	
	return TRUE;
}

int midiReadGetNumTracks(const MIDI_FILE *_pMF)
{
	_VAR_CAST;
	return pMF->Header.iNumTracks;
}

DWORD midiReadGetPositionHandle(const MIDI_FILE *_pMF, int iTrack) {
	_VAR_CAST;
	if (!IsTrackValid(iTrack))			return 0;

	return (DWORD)pMF->Track[iTrack].ptr;
}

void midiReadSetPosition(const MIDI_FILE *_pMF, int iTrack, DWORD handle) {
	_VAR_CAST;
	if (!IsTrackValid(iTrack))			return;

	pMF->Track[iTrack].ptr = (BYTE *)handle;
}

BOOL midiReadGetNextMessage(const MIDI_FILE *_pMF, int iTrack, MIDI_MSG *pMsg)
{
MIDI_FILE_TRACK *pTrack;
BYTE *bptr, *pMsgDataPtr;
int sz;

	_VAR_CAST;
	if (!IsTrackValid(iTrack))			return FALSE;
	
	pTrack = &pMF->Track[iTrack];
	/* FIXME: Check if there is data on this track first!!!	*/
	if (pTrack->ptr >= pTrack->pEnd)
		return FALSE;
	
	pTrack->ptr = _midiReadVarLen(pTrack->ptr, &pMsg->dt);
	pTrack->pos += pMsg->dt;

	pMsg->dwAbsPos = pTrack->pos;

	if (*pTrack->ptr & 0x80)	/* Is this is sys message */
		{
		pMsg->iType = (tMIDI_MSG)((*pTrack->ptr) & 0xf0);
		pMsgDataPtr = pTrack->ptr+1;

		/* SysEx & Meta events don't carry channel info, but something
		** important in their lower bits that we must keep */
		if (pMsg->iType == 0xf0)
			pMsg->iType = (tMIDI_MSG)(*pTrack->ptr);
		}
	else						/* just data - so use the last msg type */
		{
		pMsg->iType = pMsg->iLastMsgType;
		pMsgDataPtr = pTrack->ptr;
		}
	
	pMsg->iLastMsgType = (tMIDI_MSG)pMsg->iType;
	pMsg->iLastMsgChnl = (BYTE)((*pTrack->ptr) & 0x0f)+1;

	switch(pMsg->iType)
		{
		case	msgNoteOn:
							pMsg->MsgData.NoteOn.iChannel = pMsg->iLastMsgChnl;
							pMsg->MsgData.NoteOn.iNote = *(pMsgDataPtr);
							pMsg->MsgData.NoteOn.iVolume = *(pMsgDataPtr+1);
							pMsg->iMsgSize = 3;
							break;

		case	msgNoteOff:
							pMsg->MsgData.NoteOff.iChannel = pMsg->iLastMsgChnl;
							pMsg->MsgData.NoteOff.iNote = *(pMsgDataPtr);
							pMsg->iMsgSize = 3;
							break;

		case	msgNoteKeyPressure:
							pMsg->MsgData.NoteKeyPressure.iChannel = pMsg->iLastMsgChnl;
							pMsg->MsgData.NoteKeyPressure.iNote = *(pMsgDataPtr);
							pMsg->MsgData.NoteKeyPressure.iPressure = *(pMsgDataPtr+1);
							pMsg->iMsgSize = 3;
							break;

		case	msgSetParameter:
							pMsg->MsgData.NoteParameter.iChannel = pMsg->iLastMsgChnl;
							pMsg->MsgData.NoteParameter.iControl = (tMIDI_CC)*(pMsgDataPtr);
							pMsg->MsgData.NoteParameter.iParam = *(pMsgDataPtr+1);
							pMsg->iMsgSize = 3;
							break;

		case	msgSetProgram:
							pMsg->MsgData.ChangeProgram.iChannel = pMsg->iLastMsgChnl;
							pMsg->MsgData.ChangeProgram.iProgram = *(pMsgDataPtr);
							pMsg->iMsgSize = 2;
							break;

		case	msgChangePressure:
							pMsg->MsgData.ChangePressure.iChannel = pMsg->iLastMsgChnl;
							pMsg->MsgData.ChangePressure.iPressure = *(pMsgDataPtr);
							pMsg->iMsgSize = 2;
							break;

		case	msgSetPitchWheel:
							pMsg->MsgData.PitchWheel.iChannel = pMsg->iLastMsgChnl;
							pMsg->MsgData.PitchWheel.iPitch = *(pMsgDataPtr) | (*(pMsgDataPtr+1) << 7);
							pMsg->MsgData.PitchWheel.iPitch -= MIDI_WHEEL_CENTRE;
							pMsg->iMsgSize = 3;
							break;

		case	msgMetaEvent:
							/* We can use 'pTrack->ptr' from now on, since meta events
							** always have bit 7 set */
							bptr = pTrack->ptr;
							pMsg->MsgData.MetaEvent.iType = (tMIDI_META)*(pTrack->ptr+1);
							pTrack->ptr = _midiReadVarLen(pTrack->ptr+2, &pMsg->iMsgSize);
							sz = (pTrack->ptr-bptr)+pMsg->iMsgSize;
							
							if (_midiReadTrackCopyData(pMsg, pTrack->ptr, sz, FALSE) == FALSE)
								return FALSE;

							/* Now copy the data...*/
							sgxMemcpy(pMsg->data, bptr, sz);

							/* Now place it in a neat structure */
							switch(pMsg->MsgData.MetaEvent.iType)
								{
								case	metaMIDIPort:
										pMsg->MsgData.MetaEvent.Data.iMIDIPort = *(pTrack->ptr+0);
										break;
								case	metaSequenceNumber:
										pMsg->MsgData.MetaEvent.Data.iSequenceNumber = *(pTrack->ptr+0);
										break;
								case	metaTextEvent:
								case	metaCopyright:
								case	metaTrackName:
								case	metaInstrument:
								case	metaLyric:
								case	metaMarker:
								case	metaCuePoint:
										/* TODO - add NULL terminator ??? */
										pMsg->MsgData.MetaEvent.Data.Text.pData = pTrack->ptr;
										break;
								case	metaEndSequence:
										/* NO DATA */
										break;
								case	metaSetTempo:
										{
										DWORD us = ((*(pTrack->ptr+0))<<16)|((*(pTrack->ptr+1))<<8)|(*(pTrack->ptr+2));
										pMsg->MsgData.MetaEvent.Data.Tempo.iBPM = 60000000L/us;
										}
										break;
								case	metaSMPTEOffset:
										pMsg->MsgData.MetaEvent.Data.SMPTE.iHours = *(pTrack->ptr+0);
										pMsg->MsgData.MetaEvent.Data.SMPTE.iMins= *(pTrack->ptr+1);
										pMsg->MsgData.MetaEvent.Data.SMPTE.iSecs = *(pTrack->ptr+2);
										pMsg->MsgData.MetaEvent.Data.SMPTE.iFrames = *(pTrack->ptr+3);
										pMsg->MsgData.MetaEvent.Data.SMPTE.iFF = *(pTrack->ptr+4);
										break;
								case	metaTimeSig:
										pMsg->MsgData.MetaEvent.Data.TimeSig.iNom = *(pTrack->ptr+0);
										pMsg->MsgData.MetaEvent.Data.TimeSig.iDenom = *(pTrack->ptr+1) * MIDI_NOTE_MINIM;
										/* TODO: Variations without 24 & 8 */
										break;
								case	metaKeySig: {
										tUINT32 v = 0;
										if (*pTrack->ptr & 0x80)
											{
											/* Do some trendy sign extending in reverse :) */
											v |= ((256-*pTrack->ptr)&keyMaskKey);
											v |= keyMaskNeg;
											}
										else
											{
											v =( *pTrack->ptr&keyMaskKey);
											}
										if (*(pTrack->ptr+1)) {
											v |= keyMaskMin;
										}
										pMsg->MsgData.MetaEvent.Data.KeySig.iKey = (tMIDI_KEYSIG)v;
										} break;
								case	metaSequencerSpecific:
										pMsg->MsgData.MetaEvent.Data.Sequencer.iSize = pMsg->iMsgSize;
										pMsg->MsgData.MetaEvent.Data.Sequencer.pData = pTrack->ptr;
										break;
								}

							pTrack->ptr += pMsg->iMsgSize;
							pMsg->iMsgSize = sz;
							break;

		case	msgSysEx1:
		case	msgSysEx2:
							bptr = pTrack->ptr;
							pTrack->ptr = _midiReadVarLen(pTrack->ptr+1, &pMsg->iMsgSize);
							sz = (pTrack->ptr-bptr)+pMsg->iMsgSize;
							
							if (_midiReadTrackCopyData(pMsg, pTrack->ptr, sz, FALSE) == FALSE)
								return FALSE;

							/* Now copy the data... */
							sgxMemcpy(pMsg->data, bptr, sz);
							pTrack->ptr += pMsg->iMsgSize;
							pMsg->iMsgSize = sz;
							pMsg->MsgData.SysEx.pData = pMsg->data;
							pMsg->MsgData.SysEx.iSize = sz;
							break;
		}
	/*
	** Standard MIDI messages use a common copy routine
	*/
	pMsg->bImpliedMsg = FALSE;
	if ((pMsg->iType&0xf0) != 0xf0)
		{
		if (*pTrack->ptr & 0x80) 
			{
			}
		else 
			{
			pMsg->bImpliedMsg = TRUE;
			pMsg->iImpliedMsg = pMsg->iLastMsgType;
			pMsg->iMsgSize--;
			}
		_midiReadTrackCopyData(pMsg, pTrack->ptr, pMsg->iMsgSize, TRUE);
		pTrack->ptr+=pMsg->iMsgSize;
		}
	return TRUE;
}

void midiReadInitMessage(MIDI_MSG *pMsg)
{
	pMsg->data = NULL;
	pMsg->data_sz = 0;
	pMsg->bImpliedMsg = FALSE;
}

void midiReadFreeMessage(MIDI_MSG *pMsg)
{
	if (pMsg->data)	delete pMsg->data;
	pMsg->data = NULL;
}
