/*----------------------------------------------------------------------------
 *
 * File: 
 * eas_smf.c
 *
 * Contents and purpose:
 * SMF Type 0 and 1 File Parser
 *
 * For SMF timebase analysis, see "MIDI Sequencer Analysis.xls". 
 *
 * Copyright Sonic Network Inc. 2005

 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *----------------------------------------------------------------------------
 * Revision Control:
 *	 $Revision: 803 $
 *	 $Date: 2007-08-01 09:57:00 -0700 (Wed, 01 Aug 2007) $
 *----------------------------------------------------------------------------
*/

#include "eas_data.h"
#include "eas_miditypes.h"
#include "eas_parser.h"
#include "eas_report.h"
#include "eas_host.h"
#include "eas_midi.h"
#include "eas_config.h"
#include "eas_vm_protos.h"
#include "eas_smfdata.h"
#include "eas_smf.h"

#ifdef JET_INTERFACE
#include "jet_data.h"
#endif

//3 dls: The timebase for this module is adequate to keep MIDI and
//3 digital audio synchronized for only a few minutes. It should be
//3 sufficient for most mobile applications. If better accuracy is
//3 required, more fractional bits should be added to the timebase.

static const EAS_U8 smfHeader[] = { 'M', 'T', 'h', 'd' };

/* local prototypes */
static EAS_RESULT SMF_GetVarLenData (EAS_HW_DATA_HANDLE hwInstData, EAS_FILE_HANDLE fileHandle, EAS_U32 *pData);
static EAS_RESULT SMF_ParseMetaEvent (S_EAS_DATA *pEASData, S_SMF_DATA *pSMFData, S_SMF_STREAM *pSMFStream);
static EAS_RESULT SMF_ParseSysEx (S_EAS_DATA *pEASData, S_SMF_DATA *pSMFData, S_SMF_STREAM *pSMFStream, EAS_U8 f0, EAS_INT parserMode);
static EAS_RESULT SMF_ParseEvent (S_EAS_DATA *pEASData, S_SMF_DATA *pSMFData, S_SMF_STREAM *pSMFStream, EAS_INT parserMode);
static EAS_RESULT SMF_GetDeltaTime (EAS_HW_DATA_HANDLE hwInstData, S_SMF_STREAM *pSMFStream);
static void SMF_UpdateTime (S_SMF_DATA *pSMFData, EAS_U32 ticks);


/*----------------------------------------------------------------------------
 *
 * SMF_Parser
 *
 * This structure contains the functional interface for the SMF parser 
 *----------------------------------------------------------------------------
*/
const S_FILE_PARSER_INTERFACE EAS_SMF_Parser =
{
	SMF_CheckFileType,
	SMF_Prepare,
	SMF_Time,
	SMF_Event,
	SMF_State,
	SMF_Close,
	SMF_Reset,
	SMF_Pause,
	SMF_Resume,
	NULL,
	SMF_SetData,
	SMF_GetData,
	NULL
};

/*----------------------------------------------------------------------------
 * SMF_CheckFileType()
 *----------------------------------------------------------------------------
 * Purpose: 
 * Check the file type to see if we can parse it
 *
 * Inputs:
 * pEASData			- pointer to overall EAS data structure
 * handle			- pointer to file handle
 *		
 * Outputs:
 * 
 *
 * Side Effects:
 *
 *----------------------------------------------------------------------------
*/
EAS_RESULT SMF_CheckFileType (S_EAS_DATA *pEASData, EAS_FILE_HANDLE fileHandle, EAS_VOID_PTR *ppHandle, EAS_I32 offset)
{
	S_SMF_DATA* pSMFData;
	EAS_RESULT result;

	/* seek to starting offset - usually 0 */
	*ppHandle = NULL;
	if ((result = EAS_HWFileSeek(pEASData->hwInstData, fileHandle, offset)) != EAS_SUCCESS)
		return result;

	/* search through file for header - slow method */
	if (pEASData->searchHeaderFlag)
	{
		result = EAS_SearchFile(pEASData, fileHandle, smfHeader, sizeof(smfHeader), &offset);
		if (result != EAS_SUCCESS)
			return (result == EAS_EOF) ? EAS_SUCCESS : result;
	}
	
	/* read the first 4 bytes of the file - quick method */
	else {
		EAS_U8 header[4];
		EAS_I32 count;
		if ((result = EAS_HWReadFile(pEASData->hwInstData, fileHandle, header, sizeof(header), &count)) != EAS_SUCCESS)
			return result;
		
		/* check for 'MTrk' - return if no match */
		if ((header[0] != 'M') || (header[1] != 'T') || (header[2] != 'h') || (header[3] != 'd'))
			return EAS_SUCCESS;
	}

	/* check for static memory allocation */
	if (pEASData->staticMemoryModel)
		pSMFData = EAS_CMEnumData(EAS_CM_SMF_DATA);
	else
	{
		pSMFData = EAS_HWMalloc(pEASData->hwInstData, sizeof(S_SMF_DATA));
		EAS_HWMemSet((void *)pSMFData,0, sizeof(S_SMF_DATA));
	}
	if (!pSMFData)
		return EAS_ERROR_MALLOC_FAILED;

	/* initialize some critical data */
	pSMFData->fileHandle = fileHandle;
	pSMFData->fileOffset = offset;
	pSMFData->pSynth = NULL;
	pSMFData->time = 0;
	pSMFData->state = EAS_STATE_OPEN;
	*ppHandle = pSMFData;
	
	return EAS_SUCCESS;
}

/*----------------------------------------------------------------------------
 * SMF_Prepare()
 *----------------------------------------------------------------------------
 * Purpose: 
 * Prepare to parse the file. Allocates instance data (or uses static allocation for
 * static memory model). 
 *
 * Inputs:
 * pEASData			- pointer to overall EAS data structure
 * handle			- pointer to file handle
 *		
 * Outputs:
 * 
 *
 * Side Effects:
 *
 *----------------------------------------------------------------------------
*/
EAS_RESULT SMF_Prepare (S_EAS_DATA *pEASData, EAS_VOID_PTR pInstData)
{
	S_SMF_DATA* pSMFData;
	EAS_RESULT result;

	/* check for valid state */
	pSMFData = (S_SMF_DATA *) pInstData;
	if (pSMFData->state != EAS_STATE_OPEN)
		return EAS_ERROR_NOT_VALID_IN_THIS_STATE;

	/* instantiate a synthesizer */
	if ((result = VMInitMIDI(pEASData, &pSMFData->pSynth)) != EAS_SUCCESS)
	{
		{ /* dpp: EAS_ReportEx(_EAS_SEVERITY_ERROR, "VMInitMIDI returned %d\n", result); */ }
		return result;
	}

	/* parse the file header and setup the individual stream parsers */
	if ((result = SMF_ParseHeader(pEASData->hwInstData, pSMFData)) != EAS_SUCCESS)
		return result;

	/* ready to play */
	pSMFData->state = EAS_STATE_READY;
	return EAS_SUCCESS;
}

/*----------------------------------------------------------------------------
 * SMF_Time()
 *----------------------------------------------------------------------------
 * Purpose: 
 * Returns the time of the next event in msecs
 *
 * Inputs:
 * pEASData			- pointer to overall EAS data structure
 * handle			- pointer to file handle
 * pTime			- pointer to variable to hold time of next event (in msecs)
 *		
 * Outputs:
 * 
 *
 * Side Effects:
 *
 *----------------------------------------------------------------------------
*/
/*lint -esym(715, pEASData) reserved for future use */
EAS_RESULT SMF_Time (S_EAS_DATA *pEASData, EAS_VOID_PTR pInstData, EAS_U32 *pTime)
{
	S_SMF_DATA *pSMFData;
	
	pSMFData = (S_SMF_DATA*) pInstData;

	/* sanity check */
#ifdef _CHECKED_BUILD
	if (pSMFData->state == EAS_STATE_STOPPED)
	{
		{ /* dpp: EAS_ReportEx(_EAS_SEVERITY_ERROR, "Can't ask for time on a stopped stream\n"); */ }
	}

	if (pSMFData->nextStream == NULL)
	{
		{ /* dpp: EAS_ReportEx( _EAS_SEVERITY_ERROR, "no is NULL\n"); */ }
	}
#endif

#if 0
	/* return time in milliseconds */
	/* if chase mode, lie about time */
	if (pSMFData->flags & SMF_FLAGS_CHASE_MODE)
		*pTime = 0;

	else
#endif	

		/*lint -e{704} use shift instead of division */
		*pTime = pSMFData->time >> 8;

	*pTime = pSMFData->time >> 8;
	return EAS_SUCCESS;
}

/*----------------------------------------------------------------------------
 * SMF_Event()
 *----------------------------------------------------------------------------
 * Purpose: 
 * Parse the next event in the file
 *
 * Inputs:
 * pEASData			- pointer to overall EAS data structure
 * handle			- pointer to file handle
 *		
 * Outputs:
 * 
 *
 * Side Effects:
 *
 *----------------------------------------------------------------------------
*/
EAS_RESULT SMF_Event (S_EAS_DATA *pEASData, EAS_VOID_PTR pInstData, EAS_INT parserMode)
{
	S_SMF_DATA* pSMFData;
	EAS_RESULT result;
	EAS_I32 i;
	EAS_U32 ticks;
	EAS_U32 temp;

	/* establish pointer to instance data */
	pSMFData = (S_SMF_DATA*) pInstData;
	if (pSMFData->state >= EAS_STATE_OPEN)
		return EAS_SUCCESS;

	/* get current ticks */
	ticks = pSMFData->nextStream->ticks;

	/* assume that an error occurred */
	pSMFData->state = EAS_STATE_ERROR;

#ifdef JET_INTERFACE
	/* if JET has track muted, set parser mode to mute */
	if (pSMFData->nextStream->midiStream.jetData & MIDI_FLAGS_JET_MUTE)
		parserMode = eParserModeMute;
#endif	

	/* parse the next event from all the streams */
	if ((result = SMF_ParseEvent(pEASData, pSMFData, pSMFData->nextStream, parserMode)) != EAS_SUCCESS)
	{
		/* check for unexpected end-of-file */
		if (result != EAS_EOF)
			return result;

		/* indicate end of track for this stream */
		pSMFData->nextStream->ticks = SMF_END_OF_TRACK;
	}
	
	/* get next delta time, unless already at end of track */
	else if (pSMFData->nextStream->ticks != SMF_END_OF_TRACK)
	{
		if ((result = SMF_GetDeltaTime(pEASData->hwInstData, pSMFData->nextStream)) != EAS_SUCCESS)
		{
			/* check for unexpected end-of-file */
			if (result != EAS_EOF)
				return result;

			/* indicate end of track for this stream */
			pSMFData->nextStream->ticks = SMF_END_OF_TRACK;
		}

		/* if zero delta to next event, stay with this stream */
		else if (pSMFData->nextStream->ticks == ticks)
		{
			pSMFData->state = EAS_STATE_PLAY;
			return EAS_SUCCESS;
		}
	}
	
	/* find next event in all streams */
	temp = 0x7ffffff;
	pSMFData->nextStream = NULL;
	for (i = 0; i < pSMFData->numStreams; i++)
	{
		if (pSMFData->streams[i].ticks < temp)
		{
			temp = pSMFData->streams[i].ticks;
			pSMFData->nextStream = &pSMFData->streams[i];
		}
	}
	
	/* are there any more events to parse? */
	if (pSMFData->nextStream)
	{
		pSMFData->state = EAS_STATE_PLAY;
		
		/* update the time of the next event */
		SMF_UpdateTime(pSMFData, pSMFData->nextStream->ticks - ticks);
	}
	else
	{
		pSMFData->state = EAS_STATE_STOPPING;
		VMReleaseAllVoices(pEASData->pVoiceMgr, pSMFData->pSynth);
	}

	return EAS_SUCCESS;
}

/*----------------------------------------------------------------------------
 * SMF_State()
 *----------------------------------------------------------------------------
 * Purpose: 
 * Returns the current state of the stream
 *
 * Inputs:
 * pEASData			- pointer to overall EAS data structure
 * handle			- pointer to file handle
 * pState			- pointer to variable to store state
 *		
 * Outputs:
 * 
 *
 * Side Effects:
 *
 *----------------------------------------------------------------------------
*/
/*lint -esym(715, pEASData) reserved for future use */
EAS_RESULT SMF_State (S_EAS_DATA *pEASData, EAS_VOID_PTR pInstData, EAS_I32 *pState)
{
	S_SMF_DATA* pSMFData;

	/* establish pointer to instance data */
	pSMFData = (S_SMF_DATA*) pInstData;

	/* if stopping, check to see if synth voices are active */
	if (pSMFData->state == EAS_STATE_STOPPING)
	{
		if (VMActiveVoices(pSMFData->pSynth) == 0)
			pSMFData->state = EAS_STATE_STOPPED;
	}
	
	if (pSMFData->state == EAS_STATE_PAUSING)
	{
		if (VMActiveVoices(pSMFData->pSynth) == 0)
			pSMFData->state = EAS_STATE_PAUSED;
	}
	
	/* return current state */
	*pState = pSMFData->state;
	return EAS_SUCCESS;
}

/*----------------------------------------------------------------------------
 * SMF_Close()
 *----------------------------------------------------------------------------
 * Purpose: 
 * Close the file and clean up
 *
 * Inputs:
 * pEASData			- pointer to overall EAS data structure
 * handle			- pointer to file handle
 *		
 * Outputs:
 * 
 *
 * Side Effects:
 *
 *----------------------------------------------------------------------------
*/
EAS_RESULT SMF_Close (S_EAS_DATA *pEASData, EAS_VOID_PTR pInstData)
{
	S_SMF_DATA* pSMFData;
	EAS_I32 i;
	EAS_RESULT result;

	pSMFData = (S_SMF_DATA*) pInstData;

	/* close all the streams */
	for (i = 0; i < pSMFData->numStreams; i++)
	{
		if (pSMFData->streams[i].fileHandle != NULL)
		{
			if ((result = EAS_HWCloseFile(pEASData->hwInstData, pSMFData->streams[i].fileHandle)) != EAS_SUCCESS)
				return result;
		}
	}
	if (pSMFData->fileHandle != NULL)
		if ((result = EAS_HWCloseFile(pEASData->hwInstData, pSMFData->fileHandle)) != EAS_SUCCESS)
			return result;

	/* free the synth */
	if (pSMFData->pSynth != NULL)
		VMMIDIShutdown(pEASData, pSMFData->pSynth);

	/* if using dynamic memory, free it */
	if (!pEASData->staticMemoryModel)
	{
		if (pSMFData->streams)
			EAS_HWFree(pEASData->hwInstData, pSMFData->streams);

		/* free the instance data */			
		EAS_HWFree(pEASData->hwInstData, pSMFData);
	}

	return EAS_SUCCESS;
}

/*----------------------------------------------------------------------------
 * SMF_Reset()
 *----------------------------------------------------------------------------
 * Purpose: 
 * Reset the sequencer. Used for locating backwards in the file.
 *
 * Inputs:
 * pEASData			- pointer to overall EAS data structure
 * handle			- pointer to file handle
 *		
 * Outputs:
 * 
 *
 * Side Effects:
 *
 *----------------------------------------------------------------------------
*/
EAS_RESULT SMF_Reset (S_EAS_DATA *pEASData, EAS_VOID_PTR pInstData)
{
	S_SMF_DATA* pSMFData;
	EAS_I32 i;
	EAS_RESULT result;
	EAS_U32 ticks;

	pSMFData = (S_SMF_DATA*) pInstData;

	/* reset time to zero */
	pSMFData->time = 0;

	/* reset the synth */
	VMReset(pEASData->pVoiceMgr, pSMFData->pSynth, EAS_TRUE);

	/* find the start of each track */
	ticks = 0x7fffffffL;
	pSMFData->nextStream = NULL;
	for (i = 0; i < pSMFData->numStreams; i++)
	{

		/* reset file position to first byte of data in track */
		if ((result = EAS_HWFileSeek(pEASData->hwInstData, pSMFData->streams[i].fileHandle, pSMFData->streams[i].startFilePos)) != EAS_SUCCESS)
			return result;

		/* initalize some data */
		pSMFData->streams[i].ticks = 0;

		/* initalize the MIDI parser data */
		EAS_InitMIDIStream(&pSMFData->streams[i].midiStream);
		
		/* parse the first delta time in each stream */
		if ((result = SMF_GetDeltaTime(pEASData->hwInstData,&pSMFData->streams[i])) != EAS_SUCCESS)
			return result;
		if (pSMFData->streams[i].ticks < ticks)
		{
			ticks = pSMFData->streams[i].ticks;
			pSMFData->nextStream = &pSMFData->streams[i];
		}
	}

	
	pSMFData->state = EAS_STATE_READY;
	return EAS_SUCCESS;
}

/*----------------------------------------------------------------------------
 * SMF_Pause()
 *----------------------------------------------------------------------------
 * Purpose: 
 * Pauses the sequencer. Mutes all voices and sets state to pause.
 *
 * Inputs:
 * pEASData			- pointer to overall EAS data structure
 * handle			- pointer to file handle
 *		
 * Outputs:
 * 
 *
 * Side Effects:
 *
 *----------------------------------------------------------------------------
*/
EAS_RESULT SMF_Pause (S_EAS_DATA *pEASData, EAS_VOID_PTR pInstData)
{
	S_SMF_DATA *pSMFData;

	/* can't pause a stopped stream */
	pSMFData = (S_SMF_DATA*) pInstData;
	if (pSMFData->state == EAS_STATE_STOPPED)
		return EAS_ERROR_ALREADY_STOPPED;

	/* mute the synthesizer */
	VMMuteAllVoices(pEASData->pVoiceMgr, pSMFData->pSynth);
	pSMFData->state = EAS_STATE_PAUSING;	
	return EAS_SUCCESS;
}

/*----------------------------------------------------------------------------
 * SMF_Resume()
 *----------------------------------------------------------------------------
 * Purpose: 
 * Resume playing after a pause, sets state back to playing.
 *
 * Inputs:
 * pEASData			- pointer to overall EAS data structure
 * handle			- pointer to file handle
 *		
 * Outputs:
 * 
 *
 * Side Effects:
 *
 *----------------------------------------------------------------------------
*/
/*lint -esym(715, pEASData) reserved for future use */
EAS_RESULT SMF_Resume (S_EAS_DATA *pEASData, EAS_VOID_PTR pInstData)
{
	S_SMF_DATA *pSMFData;

	/* can't resume a stopped stream */
	pSMFData = (S_SMF_DATA*) pInstData;
	if (pSMFData->state == EAS_STATE_STOPPED)
		return EAS_ERROR_ALREADY_STOPPED;

	/* nothing to do but resume playback */
	pSMFData->state = EAS_STATE_PLAY;	
	return EAS_SUCCESS;
}

/*----------------------------------------------------------------------------
 * SMF_SetData()
 *----------------------------------------------------------------------------
 * Purpose: 
 * Sets parser parameters
 *
 * Inputs:
 * pEASData			- pointer to overall EAS data structure
 * handle			- pointer to file handle
 *		
 * Outputs:
 * 
 *
 * Side Effects:
 *
 *----------------------------------------------------------------------------
*/
/*lint -esym(715, pEASData) reserved for future use */
EAS_RESULT SMF_SetData (S_EAS_DATA *pEASData, EAS_VOID_PTR pInstData, EAS_I32 param, EAS_I32 value)
{
	S_SMF_DATA *pSMFData;

	pSMFData = (S_SMF_DATA*) pInstData;
	switch (param)
	{
		
		/* set metadata callback */
		case PARSER_DATA_METADATA_CB:
			EAS_HWMemCpy(&pSMFData->metadata, (void*) value, sizeof(S_METADATA_CB));
			break;

#ifdef JET_INTERFACE
		/* set jet segment and track ID of all tracks for callback function */
		case PARSER_DATA_JET_CB:
			{
				EAS_U32 i;
				EAS_U32 bit = (EAS_U32) value;
				bit = (bit << JET_EVENT_SEG_SHIFT) & JET_EVENT_SEG_MASK;
				for (i = 0; i < pSMFData->numStreams; i++)
					pSMFData->streams[i].midiStream.jetData =
						(pSMFData->streams[i].midiStream.jetData &
						~(JET_EVENT_TRACK_MASK | JET_EVENT_SEG_MASK)) |
						i << JET_EVENT_TRACK_SHIFT | bit | MIDI_FLAGS_JET_CB;
				pSMFData->flags |= SMF_FLAGS_JET_STREAM;
			}
			break;
			
		/* set state of all mute flags at once */
		case PARSER_DATA_MUTE_FLAGS:
			{
				EAS_INT i;
				EAS_U32 bit = (EAS_U32) value;
				for (i = 0; i < pSMFData->numStreams; i++)
				{
					if (bit & 1)
						pSMFData->streams[i].midiStream.jetData |= MIDI_FLAGS_JET_MUTE;
					else
						pSMFData->streams[i].midiStream.jetData &= ~MIDI_FLAGS_JET_MUTE;
					bit >>= 1;
				}
			}
			break;

		/* set track mute */
		case PARSER_DATA_SET_MUTE:
			if (value < pSMFData->numStreams)
				pSMFData->streams[value].midiStream.jetData |= MIDI_FLAGS_JET_MUTE;
			else
				return EAS_ERROR_PARAMETER_RANGE;
			break;
			
		/* clear track mute */
		case PARSER_DATA_CLEAR_MUTE:
			if (value < pSMFData->numStreams)
				pSMFData->streams[value].midiStream.jetData &= ~MIDI_FLAGS_JET_MUTE;
			else
				return EAS_ERROR_PARAMETER_RANGE;
			break;
#endif			

		default:
			return EAS_ERROR_INVALID_PARAMETER;
	}
	
	return EAS_SUCCESS;
}

/*----------------------------------------------------------------------------
 * SMF_GetData()
 *----------------------------------------------------------------------------
 * Purpose: 
 * Retrieves parser parameters
 *
 * Inputs:
 * pEASData			- pointer to overall EAS data structure
 * handle			- pointer to file handle
 *		
 * Outputs:
 * 
 *
 * Side Effects:
 *
 *----------------------------------------------------------------------------
*/
/*lint -esym(715, pEASData) reserved for future use */
EAS_RESULT SMF_GetData (S_EAS_DATA *pEASData, EAS_VOID_PTR pInstData, EAS_I32 param, EAS_I32 *pValue)
{
	S_SMF_DATA *pSMFData;

	pSMFData = (S_SMF_DATA*) pInstData;
	switch (param)
	{
		/* return file type */
		case PARSER_DATA_FILE_TYPE:
			if (pSMFData->numStreams == 1)
				*pValue = EAS_FILE_SMF0;
			else
				*pValue = EAS_FILE_SMF1;
			break;

/* now handled in eas_public.c */
#if 0			
		case PARSER_DATA_POLYPHONY:
			if (pSMFData->pSynth)
				VMGetPolyphony(pEASData->pVoiceMgr, pSMFData->pSynth, pValue);
			else
				return EAS_ERROR_NOT_VALID_IN_THIS_STATE;
			break;
			
		case PARSER_DATA_PRIORITY:
			if (pSMFData->pSynth)
				VMGetPriority(pEASData->pVoiceMgr, pSMFData->pSynth, pValue);
			break;
			
		/* set transposition */
		case PARSER_DATA_TRANSPOSITION:
			*pValue = pSMFData->transposition;
			break;
#endif

		case PARSER_DATA_SYNTH_HANDLE:
			*pValue = (EAS_I32) pSMFData->pSynth;
			break;
			
		default:
			return EAS_ERROR_INVALID_PARAMETER;
	}
	
	return EAS_SUCCESS;
}

/*----------------------------------------------------------------------------
 * SMF_GetVarLenData()
 *----------------------------------------------------------------------------
 * Purpose: 
 * Reads a varible length quantity from an SMF file
 *
 * Inputs:
 * 
 *		
 * Outputs:
 * 
 *
 * Side Effects:
 *
 *----------------------------------------------------------------------------
*/
static EAS_RESULT SMF_GetVarLenData (EAS_HW_DATA_HANDLE hwInstData, EAS_FILE_HANDLE fileHandle, EAS_U32 *pData)
{
	EAS_RESULT result;
	EAS_U32 data;
	EAS_U8 c;

	/* read until bit 7 is zero */
	data = 0;
	do
	{
		if ((result = EAS_HWGetByte(hwInstData, fileHandle,&c)) != EAS_SUCCESS)
			return result;
		data = (data << 7) | (c & 0x7f);
	} while (c & 0x80);
	*pData = data;
	return EAS_SUCCESS;
}

/*----------------------------------------------------------------------------
 * SMF_GetDeltaTime()
 *----------------------------------------------------------------------------
 * Purpose: 
 * Reads a varible length quantity from an SMF file
 *
 * Inputs:
 * 
 *		
 * Outputs:
 * 
 *
 * Side Effects:
 *
 *----------------------------------------------------------------------------
*/
static EAS_RESULT SMF_GetDeltaTime (EAS_HW_DATA_HANDLE hwInstData, S_SMF_STREAM *pSMFStream)
{
	EAS_RESULT result;
	EAS_U32 ticks;

	if ((result = SMF_GetVarLenData(hwInstData, pSMFStream->fileHandle, &ticks)) != EAS_SUCCESS)
		return result;

	pSMFStream->ticks += ticks;
	return EAS_SUCCESS;	
}

/*----------------------------------------------------------------------------
 * SMF_ParseMetaEvent()
 *----------------------------------------------------------------------------
 * Purpose: 
 * Reads a varible length quantity from an SMF file
 *
 * Inputs:
 * 
 *		
 * Outputs:
 * 
 *
 * Side Effects:
 *
 *----------------------------------------------------------------------------
*/
static EAS_RESULT SMF_ParseMetaEvent (S_EAS_DATA *pEASData, S_SMF_DATA *pSMFData, S_SMF_STREAM *pSMFStream)
{
	EAS_RESULT result;
	EAS_U32 len;
	EAS_I32 pos;
	EAS_U32 temp;
	EAS_U8 c;

	/* get the meta-event type */
	if ((result = EAS_HWGetByte(pEASData->hwInstData, pSMFStream->fileHandle, &c)) != EAS_SUCCESS)
		return result;
	
	/* get the length */
	if ((result = SMF_GetVarLenData(pEASData->hwInstData, pSMFStream->fileHandle, &len)) != EAS_SUCCESS)
		return result;

	/* get the current file position so we can skip the event */
	if ((result = EAS_HWFilePos(pEASData->hwInstData, pSMFStream->fileHandle, &pos)) != EAS_SUCCESS)
		return result;
	pos += (EAS_I32) len;

	/* end of track? */
	if (c == SMF_META_END_OF_TRACK)
	{
		{ /* dpp: EAS_ReportEx(_EAS_SEVERITY_DETAIL, "Meta-event: end of track\n", c, len); */ }
		pSMFStream->ticks = SMF_END_OF_TRACK;
	}

	/* tempo event? */
	else if (c == SMF_META_TEMPO)
	{
		/* read the 3-byte timebase value */
		temp = 0;
		while (len--)
		{
			if ((result = EAS_HWGetByte(pEASData->hwInstData, pSMFStream->fileHandle, &c)) != EAS_SUCCESS)
				return result;
			temp = (temp << 8) | c;
		}

		pSMFData->tickConv = (EAS_U16) (((temp * 1024) / pSMFData->ppqn + 500) / 1000);
		pSMFData->flags |= SMF_FLAGS_HAS_TEMPO;
	}

	/* check for time signature - see iMelody spec V1.4 section 4.1.2.2.3.6 */
	else if (c == SMF_META_TIME_SIGNATURE)
	{
		pSMFData->flags |= SMF_FLAGS_HAS_TIME_SIG;
	}

	/* if the host has registered a metadata callback return the metadata */
	else if (pSMFData->metadata.callback)
	{
		EAS_I32 readLen;
		E_EAS_METADATA_TYPE metaType;

		metaType = EAS_METADATA_UNKNOWN;

		/* only process title on the first track */
		if (c == SMF_META_SEQTRK_NAME)
			metaType = EAS_METADATA_TITLE;
		else if (c == SMF_META_TEXT)
			metaType = EAS_METADATA_TEXT;
		else if (c == SMF_META_COPYRIGHT)
			metaType = EAS_METADATA_COPYRIGHT;
		else if (c == SMF_META_LYRIC)
			metaType = EAS_METADATA_LYRIC;

		if (metaType != EAS_METADATA_UNKNOWN)
		{
			readLen = pSMFData->metadata.bufferSize - 1;
			if ((EAS_I32) len < readLen)
				readLen = (EAS_I32) len;
			if ((result = EAS_HWReadFile(pEASData->hwInstData, pSMFStream->fileHandle, pSMFData->metadata.buffer, readLen, &readLen)) != EAS_SUCCESS)
				return result;
			pSMFData->metadata.buffer[readLen] = 0;
			pSMFData->metadata.callback(metaType, pSMFData->metadata.buffer, pSMFData->metadata.pUserData);
		}
	}

	/* position file to next event - in case we ignored all or part of the meta-event */
	if ((result = EAS_HWFileSeek(pEASData->hwInstData, pSMFStream->fileHandle, pos)) != EAS_SUCCESS)
		return result;

	{ /* dpp: EAS_ReportEx(_EAS_SEVERITY_DETAIL, "Meta-event: type=%02x, len=%d\n", c, len); */ }
	return EAS_SUCCESS;	
}

/*----------------------------------------------------------------------------
 * SMF_ParseSysEx()
 *----------------------------------------------------------------------------
 * Purpose: 
 * Reads a varible length quantity from an SMF file
 *
 * Inputs:
 * 
 *		
 * Outputs:
 * 
 *
 * Side Effects:
 *
 *----------------------------------------------------------------------------
*/
static EAS_RESULT SMF_ParseSysEx (S_EAS_DATA *pEASData, S_SMF_DATA *pSMFData, S_SMF_STREAM *pSMFStream, EAS_U8 f0, EAS_INT parserMode)
{
	EAS_RESULT result;
	EAS_U32 len;
	EAS_U8 c;

	/* get the length */
	if ((result = SMF_GetVarLenData(pEASData->hwInstData, pSMFStream->fileHandle, &len)) != EAS_SUCCESS)
		return result;

	/* start of SysEx message? */
	if (f0 == 0xf0)
	{
		if ((result = EAS_ParseMIDIStream(pEASData, pSMFData->pSynth, &pSMFStream->midiStream, f0, parserMode)) != EAS_SUCCESS)
			return result;
	}
	
	/* feed the SysEx to the stream parser */
	while (len--)
	{
		if ((result = EAS_HWGetByte(pEASData->hwInstData, pSMFStream->fileHandle, &c)) != EAS_SUCCESS)
			return result;
		if ((result = EAS_ParseMIDIStream(pEASData, pSMFData->pSynth, &pSMFStream->midiStream, c, parserMode)) != EAS_SUCCESS)
			return result;

		/* check for GM system ON */
		if (pSMFStream->midiStream.flags & MIDI_FLAG_GM_ON)
			pSMFData->flags |= SMF_FLAGS_HAS_GM_ON;
	}

	return EAS_SUCCESS;	
}

/*----------------------------------------------------------------------------
 * SMF_ParseEvent()
 *----------------------------------------------------------------------------
 * Purpose: 
 * Reads a varible length quantity from an SMF file
 *
 * Inputs:
 * 
 *		
 * Outputs:
 * 
 *
 * Side Effects:
 *
 *----------------------------------------------------------------------------
*/
static EAS_RESULT SMF_ParseEvent (S_EAS_DATA *pEASData, S_SMF_DATA *pSMFData, S_SMF_STREAM *pSMFStream, EAS_INT parserMode)
{
	EAS_RESULT result;
	EAS_U8 c;

	/* get the event type */
	if ((result = EAS_HWGetByte(pEASData->hwInstData, pSMFStream->fileHandle, &c)) != EAS_SUCCESS)
		return result;

	/* parse meta-event */
	if (c == 0xff)
	{
		if ((result = SMF_ParseMetaEvent(pEASData, pSMFData, pSMFStream)) != EAS_SUCCESS)
			return result;
	}

	/* parse SysEx */
	else if ((c == 0xf0) || (c == 0xf7))
	{
		if ((result = SMF_ParseSysEx(pEASData, pSMFData, pSMFStream, c, parserMode)) != EAS_SUCCESS)
			return result;
	}

	/* parse MIDI message */
	else
	{
		if ((result = EAS_ParseMIDIStream(pEASData, pSMFData->pSynth, &pSMFStream->midiStream, c, parserMode)) != EAS_SUCCESS)
			return result;

		/* keep streaming data to the MIDI parser until the message is complete */
		while (pSMFStream->midiStream.pending)
		{
			if ((result = EAS_HWGetByte(pEASData->hwInstData, pSMFStream->fileHandle, &c)) != EAS_SUCCESS)
				return result;
			if ((result = EAS_ParseMIDIStream(pEASData, pSMFData->pSynth, &pSMFStream->midiStream, c, parserMode)) != EAS_SUCCESS)
				return result;
		}

	}

	/* chase mode logic */
	if (pSMFData->time == 0)
	{
		if (pSMFData->flags & SMF_FLAGS_CHASE_MODE)
		{
			if (pSMFStream->midiStream.flags & MIDI_FLAG_FIRST_NOTE)
				pSMFData->flags &= ~SMF_FLAGS_CHASE_MODE;
		}
		else if ((pSMFData->flags & SMF_FLAGS_SETUP_BAR) == SMF_FLAGS_SETUP_BAR)
			pSMFData->flags = (pSMFData->flags & ~SMF_FLAGS_SETUP_BAR) | SMF_FLAGS_CHASE_MODE;
	}
	
	return EAS_SUCCESS;	
}

/*----------------------------------------------------------------------------
 * SMF_ParseHeader()
 *----------------------------------------------------------------------------
 * Purpose: 
 * Parses the header of an SMF file, allocates memory the stream parsers and initializes the
 * stream parsers.
 *
 * Inputs:
 * pEASData			- pointer to overall EAS data structure
 * pSMFData			- pointer to parser instance data
 * fileHandle		- file handle
 * fileOffset		- offset in the file where the header data starts, usually 0
 * 
 *		
 * Outputs:
 * 
 *
 * Side Effects:
 *
 *----------------------------------------------------------------------------
*/
/*lint -e{801} we know that 'goto' is deprecated - but it's cleaner in this case */
EAS_RESULT SMF_ParseHeader (EAS_HW_DATA_HANDLE hwInstData, S_SMF_DATA *pSMFData)
{
	EAS_RESULT result;
	EAS_I32 i;
	EAS_U16 division;
	EAS_U32 chunkSize;
	EAS_U32 chunkStart;
	EAS_U32 temp;
	EAS_U32 ticks;

	/* rewind the file and find the end of the header chunk */
	if ((result = EAS_HWFileSeek(hwInstData, pSMFData->fileHandle, pSMFData->fileOffset + SMF_OFS_HEADER_SIZE)) != EAS_SUCCESS)
		goto ReadError;
	if ((result = EAS_HWGetDWord(hwInstData, pSMFData->fileHandle, &chunkSize, EAS_TRUE)) != EAS_SUCCESS)
		goto ReadError;

	/* determine the number of tracks */
	if ((result = EAS_HWFileSeek(hwInstData, pSMFData->fileHandle, pSMFData->fileOffset + SMF_OFS_NUM_TRACKS)) != EAS_SUCCESS)
		goto ReadError;
	if ((result = EAS_HWGetWord(hwInstData, pSMFData->fileHandle, &pSMFData->numStreams, EAS_TRUE)) != EAS_SUCCESS)
		goto ReadError;

	/* limit the number of tracks */
	if (pSMFData->numStreams > MAX_SMF_STREAMS)
	{
		{ /* dpp: EAS_ReportEx(_EAS_SEVERITY_WARNING, "SMF file contains %u tracks, playing %d tracks\n", pSMFData->numStreams, MAX_SMF_STREAMS); */ }
		pSMFData->numStreams = MAX_SMF_STREAMS;
	} else if (pSMFData->numStreams == 0)
	{
		/* avoid 0 sized allocation */
		return EAS_ERROR_PARAMETER_RANGE;
	}

	/* get the time division */
	if ((result = EAS_HWGetWord(hwInstData, pSMFData->fileHandle, &division, EAS_TRUE)) != EAS_SUCCESS)
		goto ReadError;

	/* setup default timebase for 120 bpm */
	pSMFData->ppqn = 192;
	if (!division || division & 0x8000)
		{ /* dpp: EAS_ReportEx(_EAS_SEVERITY_WARNING,"No support for SMPTE code timebase\n"); */ }
	else
		pSMFData->ppqn = (division & 0x7fff);
	pSMFData->tickConv = (EAS_U16) (((SMF_DEFAULT_TIMEBASE * 1024) / pSMFData->ppqn + 500) / 1000);

	/* dynamic memory allocation, allocate memory for streams */
	if (pSMFData->streams == NULL)
	{
		pSMFData->streams = EAS_HWMalloc(hwInstData,sizeof(S_SMF_STREAM) * pSMFData->numStreams);
		if (pSMFData->streams == NULL)
			return EAS_ERROR_MALLOC_FAILED;

		/* zero the memory to insure complete initialization */
		EAS_HWMemSet((void *)(pSMFData->streams), 0, sizeof(S_SMF_STREAM) * pSMFData->numStreams);
	}

	/* find the start of each track */
	chunkStart = (EAS_U32) pSMFData->fileOffset;
	ticks = 0x7fffffffL;
	pSMFData->nextStream = NULL;
	for (i = 0; i < pSMFData->numStreams; i++)
	{

		for (;;)
		{

			/* calculate start of next chunk - checking for errors */
			temp = chunkStart + SMF_CHUNK_INFO_SIZE + chunkSize;
			if (temp <= chunkStart)
			{
				{ /* dpp: EAS_ReportEx(_EAS_SEVERITY_WARNING,"Error in chunk size at offset %d\n", chunkStart); */ }
				return EAS_ERROR_FILE_FORMAT;
			}
			chunkStart = temp;

			/* seek to the start of the next chunk */
			if ((result = EAS_HWFileSeek(hwInstData, pSMFData->fileHandle, (EAS_I32) chunkStart)) != EAS_SUCCESS)
				goto ReadError;
			
			/* read the chunk identifier */
			if ((result = EAS_HWGetDWord(hwInstData, pSMFData->fileHandle, &temp, EAS_TRUE)) != EAS_SUCCESS)
				goto ReadError;

			/* read the chunk size */
			if ((result = EAS_HWGetDWord(hwInstData, pSMFData->fileHandle, &chunkSize, EAS_TRUE)) != EAS_SUCCESS)
				goto ReadError;

			/* make sure this is an 'MTrk' chunk */
			if (temp == SMF_CHUNK_TYPE_TRACK)
				break;

			{ /* dpp: EAS_ReportEx(_EAS_SEVERITY_WARNING,"Unexpected chunk type: 0x%08x\n", temp); */ }
		}

		/* initalize some data */
		pSMFData->streams[i].ticks = 0;
		pSMFData->streams[i].fileHandle = pSMFData->fileHandle;

		/* NULL the file handle so we don't try to close it twice */
		pSMFData->fileHandle = NULL;

		/* save this file position as the start of the track */
		pSMFData->streams[i].startFilePos = (EAS_I32) chunkStart + SMF_CHUNK_INFO_SIZE;
		
		/* initalize the MIDI parser data */
		EAS_InitMIDIStream(&pSMFData->streams[i].midiStream);
		
		/* parse the first delta time in each stream */
		if ((result = SMF_GetDeltaTime(hwInstData, &pSMFData->streams[i])) != EAS_SUCCESS)
				goto ReadError;
		
		if (pSMFData->streams[i].ticks < ticks)
		{
			ticks = pSMFData->streams[i].ticks;
			pSMFData->nextStream = &pSMFData->streams[i];
		}

		/* more tracks to do, create a duplicate file handle */
		if (i < (pSMFData->numStreams - 1))
		{
			if ((result = EAS_HWDupHandle(hwInstData, pSMFData->streams[i].fileHandle, &pSMFData->fileHandle)) != EAS_SUCCESS)
				goto ReadError;
		}
	}

	/* update the time of the next event */
	if (pSMFData->nextStream)
		SMF_UpdateTime(pSMFData, pSMFData->nextStream->ticks);

	return EAS_SUCCESS;

	/* ugly goto: but simpler than structured */
	ReadError:
		if (result == EAS_EOF)
			return EAS_ERROR_FILE_FORMAT;
		return result;
}

/*----------------------------------------------------------------------------
 * SMF_UpdateTime()
 *----------------------------------------------------------------------------
 * Purpose: 
 * Update the millisecond time base by converting the ticks into millieconds
 *
 * Inputs:
 * 
 *		
 * Outputs:
 * 
 *
 * Side Effects:
 *
 *----------------------------------------------------------------------------
*/
static void SMF_UpdateTime (S_SMF_DATA *pSMFData, EAS_U32 ticks)
{
	EAS_U32 temp1, temp2;

	if (pSMFData->flags & SMF_FLAGS_CHASE_MODE)
		return;
	
	temp1 = (ticks >> 10) * pSMFData->tickConv;
	temp2 = (ticks & 0x3ff) * pSMFData->tickConv;
	pSMFData->time += (EAS_I32)((temp1 << 8) + (temp2 >> 2));
}