diff src/midifile.c @ 0:785057719d9b

Import.
author Matti Hamalainen <ccr@tnsp.org>
date Mon, 05 Aug 2013 12:25:43 +0300
parents
children 4df6d9714314
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/midifile.c	Mon Aug 05 12:25:43 2013 +0300
@@ -0,0 +1,1178 @@
+/*
+ * midiFile.c - A general purpose midi file handling library. This code
+ *				can read and write MIDI files in formats 0 and 1.
+ * Version 1.4
+ *
+ *  AUTHOR: Steven Goodwin (StevenGoodwin@gmail.com)
+ *          Copyright 1998-2010, 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.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifndef  __APPLE__
+#include <malloc.h>
+#endif
+#include "midifile.h"
+
+/*
+** 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 {
+				FILE				*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 */
+#define SWAP_WORD(w)		(WORD)(((w)>>8)|((w)<<8))
+#define SWAP_DWORD(d)		(DWORD)((d)>>24)|(((d)>>8)&0xff00)|(((d)<<8)&0xff0000)|(((d)<<24))
+
+#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)
+{
+register long buffer;
+register long value=n;
+
+	buffer = value & 0x7f;
+	while ((value >>= 7) > 0)
+		{
+		buffer <<= 8;
+		buffer |= 0x80;
+		buffer += (value & 0x7f);
+		}
+
+	while (TRUE)
+		{
+		*ptr++ = (BYTE)buffer;
+		if (buffer & 0x80)
+			buffer >>= 8;
+		else
+			break;
+		}
+	
+	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)))
+			{
+			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 = (_MIDI_FILE *)malloc(sizeof(_MIDI_FILE));
+int i;
+
+	if (!pMF)							return NULL;
+	
+	if (!bOverwriteIfExists)
+		{
+		if ((pMF->pFile = fopen(pFilename, "r")))
+			{
+			fclose(pMF->pFile);
+			free(pMF);
+			return NULL;
+			}
+		}
+	
+	if ((pMF->pFile = fopen(pFilename, "wb+")))
+		{/*empty*/}
+	else
+		{
+		free((void *)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);
+		
+		memset(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)
+{
+FILE *fp = fopen(pFilename, "rb");
+_MIDI_FILE *pMF = NULL;
+BYTE *ptr;
+BOOL bValidFile=FALSE;
+long size;
+
+	if (fp)
+		{
+		if ((pMF = (_MIDI_FILE *)malloc(sizeof(_MIDI_FILE))))
+			{
+			fseek(fp, 0L, SEEK_END);
+			size = ftell(fp);
+			if ((pMF->ptr = (BYTE *)malloc(size)))
+				{
+				fseek(fp, 0L, SEEK_SET);
+				fread(pMF->ptr, sizeof(BYTE), size, fp);
+				/* 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;
+					}
+				}
+			}
+
+		fclose(fp);
+		}
+	
+	if (!bValidFile)
+		{
+		if (pMF)		free((void *)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 = (MIDI_END_POINT *)malloc(sz * sizeof(MIDI_END_POINT));
+	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;
+			}
+		}
+	
+	free((void *)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 BYTE mthd[4] = {'M', 'T', 'h', 'd'};
+		DWORD dwData;
+		WORD wData;
+		WORD version, PPQN;
+
+			fwrite(mthd, sizeof(BYTE), 4, pMF->pFile);
+			dwData = 6;
+			if (bSwap)	dwData = SWAP_DWORD(dwData);
+			fwrite(&dwData, sizeof(DWORD), 1, pMF->pFile);
+
+			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;
+			fwrite(&version, sizeof(WORD), 1, pMF->pFile);
+			fwrite(&iNumTracks, sizeof(WORD), 1, pMF->pFile);
+			fwrite(&PPQN, sizeof(WORD), 1, pMF->pFile);
+		}
+		/*
+		** Track data
+		*/
+		for(i=0;i<MAX_MIDI_TRACKS;++i)
+			if (pMF->Track[i].ptr)
+				{
+				const BYTE mtrk[4] = {'M', 'T', 'r', 'k'};
+				DWORD sz, dwData;
+
+				/* Write track header */
+				fwrite(&mtrk, sizeof(BYTE), 4, pMF->pFile);
+
+				/* Write data size */
+				sz = dwData = (int)(pMF->Track[i].ptr - pMF->Track[i].pBase);
+				if (bSwap)	sz = SWAP_DWORD(sz);
+				fwrite(&sz, sizeof(DWORD), 1, pMF->pFile);
+
+				/* Write data */
+				fwrite(pMF->Track[i].pBase, sizeof(BYTE), dwData, pMF->pFile);
+				
+				/* Free memory */
+				free((void *)pMF->Track[i].pBase);
+				}
+
+		}
+
+	if (pMF->pFile)
+		return fclose(pMF->pFile)?FALSE:TRUE;
+	free((void *)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);
+	memcpy(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 = strlen(pTxt);
+	if ((ptr = _midiGetPtr(pMF, iTrack, sz+DT_DEF)))
+		{
+		*ptr++ = 0;		/* delta-time=0 */
+		*ptr++ = msgMetaEvent;
+		*ptr++ = (BYTE)iType;
+		ptr = _midiWriteVarLen((BYTE *)ptr, sz);
+		strcpy((char *)ptr, 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)
+{
+register DWORD value;
+register 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)
+		memcpy(pMsg->data, ptr, sz);
+	
+	return TRUE;
+}
+
+int midiReadGetNumTracks(const MIDI_FILE *_pMF)
+{
+	_VAR_CAST;
+	return pMF->Header.iNumTracks;
+}
+
+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...*/
+							memcpy(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:
+										if (*pTrack->ptr & 0x80)
+											{
+											/* Do some trendy sign extending in reverse :) */
+											pMsg->MsgData.MetaEvent.Data.KeySig.iKey = ((256-*pTrack->ptr)&keyMaskKey);
+											pMsg->MsgData.MetaEvent.Data.KeySig.iKey |= keyMaskNeg;
+											}
+										else
+											{
+											pMsg->MsgData.MetaEvent.Data.KeySig.iKey = (tMIDI_KEYSIG)(*pTrack->ptr&keyMaskKey);
+											}
+										if (*(pTrack->ptr+1)) 
+											pMsg->MsgData.MetaEvent.Data.KeySig.iKey |= keyMaskMin;
+										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... */
+							memcpy(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)	free((void *)pMsg->data);
+	pMsg->data = NULL;
+}