/*----------------------------------------------------------------------------
 *
 * File:
 * eas_tonecontrol.c
 *
 * Contents and purpose:
 * MMAPI ToneControl parser
 *
 * Copyright Sonic Network Inc. 2006

 * 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: 795 $
 *   $Date: 2007-08-01 00:14:45 -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_tcdata.h"


/* default channel and program for TC playback */
#define TC_CHANNEL              0
#define TC_PROGRAM              80
#define TC_VELOCITY             127

#define TC_FIELD_SILENCE        -1
#define TC_FIELD_VERSION        -2
#define TC_FIELD_TEMPO          -3
#define TC_FIELD_RESOLUTION     -4
#define TC_FIELD_BLOCK_START    -5
#define TC_FIELD_BLOCK_END      -6
#define TC_FIELD_PLAY_BLOCK     -7
#define TC_FIELD_SET_VOLUME     -8
#define TC_FIELD_REPEAT         -9
#define TC_FIELD_INVALID        -10

/* convert 0-100 volume to 0-127 velocity using fixed point */
#define TC_VOLUME_CONV          21307064
#define TC_VOLUME_SHIFT         24


/* local prototypes */
static EAS_RESULT TC_CheckFileType (S_EAS_DATA *pEASData, EAS_FILE_HANDLE fileHandle, EAS_VOID_PTR *ppHandle, EAS_I32 offset);
static EAS_RESULT TC_Prepare (S_EAS_DATA *pEASData, EAS_VOID_PTR pInstData);
static EAS_RESULT TC_Time (S_EAS_DATA *pEASData, EAS_VOID_PTR pInstData, EAS_U32 *pTime);
static EAS_RESULT TC_Event (S_EAS_DATA *pEASData, EAS_VOID_PTR pInstData, EAS_INT parserMode);
static EAS_RESULT TC_State (S_EAS_DATA *pEASData, EAS_VOID_PTR pInstData, EAS_STATE *pState);
static EAS_RESULT TC_Close (S_EAS_DATA *pEASData, EAS_VOID_PTR pInstData);
static EAS_RESULT TC_Reset (S_EAS_DATA *pEASData, EAS_VOID_PTR pInstData);
static EAS_RESULT TC_Pause (S_EAS_DATA *pEASData, EAS_VOID_PTR pInstData);
static EAS_RESULT TC_Resume (S_EAS_DATA *pEASData, EAS_VOID_PTR pInstData);
static EAS_RESULT TC_SetData (S_EAS_DATA *pEASData, EAS_VOID_PTR pInstData, EAS_I32 param, EAS_I32 value);
static EAS_RESULT TC_GetData (S_EAS_DATA *pEASData, EAS_VOID_PTR pInstData, EAS_I32 param, EAS_I32 *pValue);
static EAS_RESULT TC_ParseHeader (S_EAS_DATA *pEASData, S_TC_DATA* pData);
static EAS_RESULT TC_StartNote (S_EAS_DATA *pEASData, S_TC_DATA* pData, EAS_INT parserMode, EAS_I8 note);
static EAS_RESULT TC_GetRepeat (S_EAS_DATA *pEASData, S_TC_DATA* pData, EAS_INT parserMode);
static EAS_RESULT TC_PlayBlock (S_EAS_DATA *pEASData, S_TC_DATA* pData);
static EAS_RESULT TC_BlockEnd (S_EAS_DATA *pEASData, S_TC_DATA* pData);
static EAS_RESULT TC_GetVolume (S_EAS_DATA *pEASData, S_TC_DATA* pData);
static EAS_RESULT TC_GetTempo (S_EAS_DATA *pEASData, S_TC_DATA* pData);
static EAS_RESULT TC_GetResolution (S_EAS_DATA *pEASData, S_TC_DATA* pData);
static EAS_RESULT TC_GetNextChar (EAS_HW_DATA_HANDLE hwInstData, S_TC_DATA *pData, EAS_I8 *pValue);
static void TC_PutBackChar (S_TC_DATA *pData, EAS_I8 value);

/* calculate a new tick time based on resolution & tempo */
EAS_INLINE void TC_CalcTimeBase (S_TC_DATA *pData)
{

    /* ticks in 256ths of a millisecond */
    pData->tick = ((60 * 1000) << 8) / (pData->tempo * pData->resolution);
}

/*----------------------------------------------------------------------------
 *
 * EAS_TC_Parser
 *
 * This structure contains the functional interface for the iMelody parser
 *----------------------------------------------------------------------------
*/
const S_FILE_PARSER_INTERFACE EAS_TC_Parser =
{
    TC_CheckFileType,
    TC_Prepare,
    TC_Time,
    TC_Event,
    TC_State,
    TC_Close,
    TC_Reset,
    TC_Pause,
    TC_Resume,
    NULL,
    TC_SetData,
    TC_GetData,
    NULL
};

/*----------------------------------------------------------------------------
 * TC_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:
 *
 *----------------------------------------------------------------------------
*/
static EAS_RESULT TC_CheckFileType (S_EAS_DATA *pEASData, EAS_FILE_HANDLE fileHandle, EAS_VOID_PTR *ppHandle, EAS_I32 offset)
{
    S_TC_DATA data;
    S_TC_DATA *pData;

    /* init data */
    EAS_HWMemSet(&data, 0, sizeof(S_TC_DATA));
    data.fileHandle = fileHandle;
    data.fileOffset = offset;
    *ppHandle= NULL;

    /* see if we can parse the header */
    if (TC_ParseHeader(pEASData, &data) == EAS_SUCCESS)
    {

        /* check for static memory allocation */
        if (pEASData->staticMemoryModel)
            pData = EAS_CMEnumOptData(EAS_MODULE_MMAPI_TONE_CONTROL);
        else
            pData = EAS_HWMalloc(pEASData->hwInstData, sizeof(S_TC_DATA));
        if (!pData)
            return EAS_ERROR_MALLOC_FAILED;

        /* copy data to persistent storage */
        EAS_HWMemCpy(pData, &data, sizeof(S_TC_DATA));

        /* return a pointer to the instance data */
        pData->state = EAS_STATE_OPEN;
        *ppHandle = pData;
    }

    return EAS_SUCCESS;
}

/*----------------------------------------------------------------------------
 * TC_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:
 *
 *----------------------------------------------------------------------------
*/
static EAS_RESULT TC_Prepare (S_EAS_DATA *pEASData, EAS_VOID_PTR pInstData)
{
    S_TC_DATA* pData;
    EAS_RESULT result;

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

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

    /* set to ready state */
    pData->state = EAS_STATE_READY;
    return EAS_SUCCESS;
}

/*----------------------------------------------------------------------------
 * TC_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 */
static EAS_RESULT TC_Time (S_EAS_DATA *pEASData, EAS_VOID_PTR pInstData, EAS_U32 *pTime)
{
    S_TC_DATA *pData;

    pData = (S_TC_DATA*) pInstData;

    /* return time in milliseconds */
    /*lint -e{704} use shift instead of division */
    *pTime = pData->time >> 8;
    return EAS_SUCCESS;
}

/*----------------------------------------------------------------------------
 * TC_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:
 *
 *----------------------------------------------------------------------------
*/
static EAS_RESULT TC_Event (S_EAS_DATA *pEASData, EAS_VOID_PTR pInstData, EAS_INT parserMode)
{
    S_TC_DATA* pData;
    EAS_RESULT result;
    EAS_I8 temp;

    pData = (S_TC_DATA*) pInstData;
    if (pData->state >= EAS_STATE_OPEN)
        return EAS_SUCCESS;

    /* initialize MIDI channel when the track starts playing */
    if (pData->time == 0)
    {
        /* set program to square lead */
        VMProgramChange(pEASData->pVoiceMgr, pData->pSynth, TC_CHANNEL, TC_PROGRAM);

        /* set channel volume to max */
        VMControlChange(pEASData->pVoiceMgr, pData->pSynth, TC_CHANNEL, 7, 127);
    }

    /* check for end of note */
    if (pData->note >= 0)
    {
        /* stop the note */
        VMStopNote(pEASData->pVoiceMgr, pData->pSynth, TC_CHANNEL, (EAS_U8) pData->note, 0);

        /* check for repeat note */
        if (pData->repeatCount)
        {
            pData->repeatCount--;
            pData->time += pData->length;
            if ((pData->note >= 0) && (parserMode == eParserModePlay))
                VMStartNote(pEASData->pVoiceMgr, pData->pSynth, TC_CHANNEL, (EAS_U8) pData->note, pData->volume);
            return EAS_SUCCESS;
        }

        pData->note = TC_FIELD_SILENCE;
    }

    /* parse stream until we get a note or rest */
    for (;;)
    {

        /* get next byte from stream */
        if ((result = TC_GetNextChar(pEASData->hwInstData, pData, &temp)) != EAS_SUCCESS)
        {
            if (result == EAS_EOF)
            {
                pData->state = EAS_STATE_STOPPING;
                return EAS_SUCCESS;
            }
            break;
        }

        /* check for musical events */
        if (temp >= TC_FIELD_SILENCE)
        {
            result = TC_StartNote(pEASData, pData, parserMode, temp);
            break;
        }

        /* must be a control field */
        switch (temp)
        {
            case TC_FIELD_TEMPO:
                result = TC_GetTempo(pEASData, pData);
                break;

            case TC_FIELD_RESOLUTION:
                result = TC_GetResolution(pEASData, pData);
                break;

            case TC_FIELD_SET_VOLUME:
                result = TC_GetVolume(pEASData, pData);
                break;

            case TC_FIELD_REPEAT:
                result = TC_GetRepeat(pEASData, pData, parserMode);
                break;

            case TC_FIELD_PLAY_BLOCK:
                result = TC_PlayBlock(pEASData, pData);
                break;

            case TC_FIELD_BLOCK_START:
                result = TC_GetNextChar(pEASData->hwInstData, pData, &temp);
                break;

            case TC_FIELD_BLOCK_END:
                result = TC_BlockEnd(pEASData, pData);
                break;

            default:
                { /* dpp: EAS_ReportEx(_EAS_SEVERITY_ERROR, "Unexpected byte 0x%02x in ToneControl stream\n", temp); */ }
                result = EAS_ERROR_FILE_FORMAT;
        }

        /* check for error */
        if (result != EAS_SUCCESS)
            break;
    }

    /* check for error */
    if (result != EAS_SUCCESS)
    {
        if (result == EAS_EOF)
            result = EAS_ERROR_FILE_FORMAT;
        pData->state = EAS_STATE_ERROR;
    }
    else
        pData->state = EAS_STATE_PLAY;
    return result;
}

/*----------------------------------------------------------------------------
 * TC_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 */
static EAS_RESULT TC_State (S_EAS_DATA *pEASData, EAS_VOID_PTR pInstData, EAS_I32 *pState)
{
    S_TC_DATA* pData;

    /* establish pointer to instance data */
    pData = (S_TC_DATA*) pInstData;

    /* if stopping, check to see if synth voices are active */
    if (pData->state == EAS_STATE_STOPPING)
    {
        if (VMActiveVoices(pData->pSynth) == 0)
            pData->state = EAS_STATE_STOPPED;
    }

    if (pData->state == EAS_STATE_PAUSING)
    {
        if (VMActiveVoices(pData->pSynth) == 0)
            pData->state = EAS_STATE_PAUSED;
    }

    /* return current state */
    *pState = pData->state;
    return EAS_SUCCESS;
}

/*----------------------------------------------------------------------------
 * TC_Close()
 *----------------------------------------------------------------------------
 * Purpose:
 * Close the file and clean up
 *
 * Inputs:
 * pEASData         - pointer to overall EAS data structure
 * handle           - pointer to file handle
 *
 * Outputs:
 *
 *
 * Side Effects:
 *
 *----------------------------------------------------------------------------
*/
static EAS_RESULT TC_Close (S_EAS_DATA *pEASData, EAS_VOID_PTR pInstData)
{
    S_TC_DATA* pData;
    EAS_RESULT result;

    pData = (S_TC_DATA*) pInstData;

    /* close the file */
    if ((result = EAS_HWCloseFile(pEASData->hwInstData, pData->fileHandle)) != EAS_SUCCESS)
            return result;

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

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

    return EAS_SUCCESS;
}

/*----------------------------------------------------------------------------
 * TC_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:
 *
 *----------------------------------------------------------------------------
*/
static EAS_RESULT TC_Reset (S_EAS_DATA *pEASData, EAS_VOID_PTR pInstData)
{
    S_TC_DATA* pData;
    EAS_RESULT result;

    pData = (S_TC_DATA*) pInstData;

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

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

    /* reset file position and re-parse header */
    pData->state = EAS_STATE_ERROR;
    if ((result = EAS_HWFileSeek(pEASData->hwInstData, pData->fileHandle, pData->fileOffset)) != EAS_SUCCESS)
        return result;
    if ((result = TC_ParseHeader (pEASData,  pData)) != EAS_SUCCESS)
        return result;

    pData->state = EAS_STATE_READY;
    return EAS_SUCCESS;
}

/*----------------------------------------------------------------------------
 * TC_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:
 *
 *----------------------------------------------------------------------------
*/
static EAS_RESULT TC_Pause (S_EAS_DATA *pEASData, EAS_VOID_PTR pInstData)
{
    S_TC_DATA *pData;

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

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

/*----------------------------------------------------------------------------
 * TC_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 */
static EAS_RESULT TC_Resume (S_EAS_DATA *pEASData, EAS_VOID_PTR pInstData)
{
    S_TC_DATA *pData;

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

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

/*----------------------------------------------------------------------------
 * TC_SetData()
 *----------------------------------------------------------------------------
 * Purpose:
 * Return file type
 *
 * Inputs:
 * pEASData         - pointer to overall EAS data structure
 * handle           - pointer to file handle
 *
 * Outputs:
 *
 *
 * Side Effects:
 *
 *----------------------------------------------------------------------------
*/
/*lint -esym(715, pEASData, pInstData, value) reserved for future use */
static EAS_RESULT TC_SetData (S_EAS_DATA *pEASData, EAS_VOID_PTR pInstData, EAS_I32 param, EAS_I32 value)
{
    /* we don't parse any metadata, but we need to return success here */
    if (param == PARSER_DATA_METADATA_CB)
        return EAS_SUCCESS;

    return EAS_ERROR_INVALID_PARAMETER;
}

/*----------------------------------------------------------------------------
 * TC_GetData()
 *----------------------------------------------------------------------------
 * Purpose:
 * Return file type
 *
 * Inputs:
 * pEASData         - pointer to overall EAS data structure
 * handle           - pointer to file handle
 *
 * Outputs:
 *
 *
 * Side Effects:
 *
 *----------------------------------------------------------------------------
*/
/*lint -e{715} common with other parsers */
static EAS_RESULT TC_GetData (S_EAS_DATA *pEASData, EAS_VOID_PTR pInstData, EAS_I32 param, EAS_I32 *pValue)
{
    S_TC_DATA *pData;

    pData = (S_TC_DATA *) pInstData;
    switch (param)
    {
        /* return file type as TC */
        case PARSER_DATA_FILE_TYPE:
            *pValue = EAS_FILE_MMAPI_TONE_CONTROL;
            break;

        case PARSER_DATA_SYNTH_HANDLE:
            *pValue = (EAS_I32) pData->pSynth;
            break;

    default:
            return EAS_ERROR_INVALID_PARAMETER;
    }
    return EAS_SUCCESS;
}

/*----------------------------------------------------------------------------
 * TC_ParseHeader()
 *----------------------------------------------------------------------------
 * 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:
 *
 *----------------------------------------------------------------------------
*/
static EAS_RESULT TC_ParseHeader (S_EAS_DATA *pEASData, S_TC_DATA* pData)
{
    EAS_RESULT result;
    EAS_I8 temp;

    /* initialize some defaults */
    pData->time = 0;
    pData->tempo = 120;
    pData->resolution = 64;
    pData->volume = 127;
    pData->repeatCount = 0;
    pData->note = TC_FIELD_SILENCE;
    pData->byteAvail = EAS_FALSE;

    /* set default timebase */
    TC_CalcTimeBase(pData);

    /* seek to start of data */
    if ((result = EAS_HWFileSeek(pEASData->hwInstData, pData->fileHandle, pData->fileOffset)) != EAS_SUCCESS)
        return result;

    /* get version */
    if ((result = TC_GetNextChar(pEASData->hwInstData, pData, &temp)) != EAS_SUCCESS)
        return result;

    /* check for version number */
    if (temp == TC_FIELD_VERSION)
    {
        TC_GetNextChar(pEASData->hwInstData, pData, &temp);
//      { /* dpp: EAS_ReportEx(_EAS_SEVERITY_INFO, "ToneControl sequence version %d\n", temp); */ }
    }
    else
        return EAS_ERROR_FILE_FORMAT;

    /* parse the header data until we find the first note or block */
    for (;;)
    {

        /* get next byte from stream */
        if ((result = TC_GetNextChar(pEASData->hwInstData, pData, &temp)) != EAS_SUCCESS)
            return result;

        /* check for tempo */
        if (temp == TC_FIELD_TEMPO)
        {
            if ((result = TC_GetTempo(pEASData, pData)) != EAS_SUCCESS)
                return result;
        }

        /* or resolution */
        else if (temp == TC_FIELD_TEMPO)
        {
            if ((result = TC_GetResolution(pEASData, pData)) != EAS_SUCCESS)
                return result;
        }

        /* must be music data */
        else if (temp > TC_FIELD_INVALID)
        {
            TC_PutBackChar(pData, temp);
            return EAS_SUCCESS;
        }

        /* unknown codes */
        else
        {
            { /* dpp: EAS_ReportEx(_EAS_SEVERITY_ERROR, "Unexpected byte 0x%02x in ToneControl stream\n", temp); */ }
            return EAS_ERROR_FILE_FORMAT;
        }
    }
}

/*----------------------------------------------------------------------------
 * TC_StartNote()
 *----------------------------------------------------------------------------
 * Process a note or silence event
 *----------------------------------------------------------------------------
*/
static EAS_RESULT TC_StartNote (S_EAS_DATA *pEASData, S_TC_DATA* pData, EAS_INT parserMode, EAS_I8 note)
{
    EAS_I8 duration;

    /* get the duration */
    if (TC_GetNextChar(pEASData->hwInstData, pData, &duration) != EAS_SUCCESS)
        return EAS_ERROR_FILE_FORMAT;

    /* calculate time of next event */
    pData->length = (EAS_I32) duration * pData->tick;
    pData->time += pData->length;

    /* start the note */
    if ((note >= 0) && (parserMode == eParserModePlay))
    {
        VMStartNote(pEASData->pVoiceMgr, pData->pSynth, TC_CHANNEL, (EAS_U8) note, pData->volume);
        pData->note = note;
    }

    return EAS_SUCCESS;
}

/*----------------------------------------------------------------------------
 * TC_GetRepeat()
 *----------------------------------------------------------------------------
 * Process a repeat code
 *----------------------------------------------------------------------------
*/
static EAS_RESULT TC_GetRepeat (S_EAS_DATA *pEASData, S_TC_DATA* pData, EAS_INT parserMode)
{
    EAS_I8 count;

    /* get the repeat count */
    if (TC_GetNextChar(pEASData->hwInstData, pData, &count) != EAS_SUCCESS)
        return EAS_ERROR_FILE_FORMAT;

    /* validiate it */
    if (count < 2)
        return EAS_ERROR_FILE_FORMAT;

    /* calculate time of next event */
    pData->time += pData->length;
    pData->repeatCount = count - 2;

    /* start the note */
    if ((pData->note >= 0) && (parserMode == eParserModePlay))
        VMStartNote(pEASData->pVoiceMgr, pData->pSynth, TC_CHANNEL, (EAS_U8) pData->note, pData->volume);

    return EAS_SUCCESS;
}

/*----------------------------------------------------------------------------
 * TC_PlayBlock()
 *----------------------------------------------------------------------------
 * Play a block of notes
 *----------------------------------------------------------------------------
*/
static EAS_RESULT TC_PlayBlock (S_EAS_DATA *pEASData, S_TC_DATA* pData)
{
    EAS_RESULT result;
    EAS_I8 blockNum;
    EAS_I8 temp;
    EAS_I8 temp2;

    /* get the block number */
    if (TC_GetNextChar(pEASData->hwInstData, pData, &blockNum) != EAS_SUCCESS)
        return EAS_ERROR_FILE_FORMAT;

    /* validiate it */
    if (blockNum < 0)
        return EAS_ERROR_FILE_FORMAT;

    /* save the current position */
    if ((result = EAS_HWFilePos(pEASData->hwInstData, pData->fileHandle, &pData->restorePos)) != EAS_SUCCESS)
        return result;

    /* return to start of file */
    pData->byteAvail = EAS_FALSE;
    if ((result = EAS_HWFileSeek(pEASData->hwInstData, pData->fileHandle, pData->fileOffset)) != EAS_SUCCESS)
        return result;

    /* find the block */
    for (;;)
    {
        if (TC_GetNextChar(pEASData->hwInstData, pData, &temp) != EAS_SUCCESS)
            return EAS_ERROR_FILE_FORMAT;

        if (TC_GetNextChar(pEASData->hwInstData, pData, &temp2) != EAS_SUCCESS)
            return EAS_ERROR_FILE_FORMAT;

        if ((temp == TC_FIELD_BLOCK_START) && (temp2 == blockNum))
            return EAS_SUCCESS;
    }
}

/*----------------------------------------------------------------------------
 * TC_BlockEnd()
 *----------------------------------------------------------------------------
 * Handle end of block
 *----------------------------------------------------------------------------
*/
static EAS_RESULT TC_BlockEnd (S_EAS_DATA *pEASData, S_TC_DATA* pData)
{
    EAS_I8 blockNum;

    /* get the block number */
    if (TC_GetNextChar(pEASData->hwInstData, pData, &blockNum) != EAS_SUCCESS)
        return EAS_ERROR_FILE_FORMAT;

    /* validiate it */
    if (blockNum < 0)
        return EAS_ERROR_FILE_FORMAT;

    /* if we were playing this block, restore to previous position */
    pData->byteAvail = EAS_FALSE;
    return EAS_HWFileSeek(pEASData->hwInstData, pData->fileHandle, pData->restorePos);
}

/*----------------------------------------------------------------------------
 * TC_GetVolume()
 *----------------------------------------------------------------------------
 * Get the volume field and process it
 *----------------------------------------------------------------------------
*/
static EAS_RESULT TC_GetVolume (S_EAS_DATA *pEASData, S_TC_DATA* pData)
{
    EAS_I8 volume;

    /* get volume */
    if (TC_GetNextChar(pEASData->hwInstData, pData, &volume) != EAS_SUCCESS)
        return EAS_ERROR_FILE_FORMAT;
    if ((volume < 0) || (volume > 100))
        return EAS_ERROR_FILE_FORMAT;

    /* save volume */
    pData->volume = (EAS_U8) ((EAS_I32) (volume * TC_VOLUME_CONV + 1) >> TC_VOLUME_SHIFT);
    return EAS_SUCCESS;
}

/*----------------------------------------------------------------------------
 * TC_GetTempo()
 *----------------------------------------------------------------------------
 * Get the tempo field and process it
 *----------------------------------------------------------------------------
*/
static EAS_RESULT TC_GetTempo (S_EAS_DATA *pEASData, S_TC_DATA* pData)
{
    EAS_I8 tempo;

    /* get tempo */
    if (TC_GetNextChar(pEASData->hwInstData, pData, &tempo) != EAS_SUCCESS)
        return EAS_ERROR_FILE_FORMAT;
    if (tempo < 5)
        return EAS_ERROR_FILE_FORMAT;

    /* save tempo */
    pData->tempo = tempo;

    /* calculate new timebase */
    TC_CalcTimeBase(pData);
    return EAS_SUCCESS;
}

/*----------------------------------------------------------------------------
 * TC_GetResolution()
 *----------------------------------------------------------------------------
 * Get the resolution field and process it
 *----------------------------------------------------------------------------
*/
static EAS_RESULT TC_GetResolution (S_EAS_DATA *pEASData, S_TC_DATA* pData)
{
    EAS_I8 resolution;

    /* get resolution */
    if (TC_GetNextChar(pEASData->hwInstData, pData, &resolution) != EAS_SUCCESS)
        return EAS_ERROR_FILE_FORMAT;
    if (resolution < 0)
        return EAS_ERROR_FILE_FORMAT;

    /* save tempo */
    pData->resolution = resolution;

    /* calculate new timebase */
    TC_CalcTimeBase(pData);
    return EAS_SUCCESS;
}

/*----------------------------------------------------------------------------
 * TC_GetNextChar()
 *----------------------------------------------------------------------------
 * Fetch the next character from the stream
 *----------------------------------------------------------------------------
*/
static EAS_RESULT TC_GetNextChar (EAS_HW_DATA_HANDLE hwInstData, S_TC_DATA *pData, EAS_I8 *pValue)
{

    /* get character from "put back" buffer */
    if (pData->byteAvail)
    {
        pData->byteAvail = EAS_FALSE;
        *pValue = pData->dataByte;
        return EAS_SUCCESS;
    }

    /* get character from file */
    return EAS_HWGetByte(hwInstData, pData->fileHandle, pValue);
}

/*----------------------------------------------------------------------------
 * TC_PutBackChar()
 *----------------------------------------------------------------------------
 * Put back the character
 *----------------------------------------------------------------------------
*/
static void TC_PutBackChar (S_TC_DATA *pData, EAS_I8 value)
{

    pData->dataByte = value;
    pData->byteAvail = EAS_TRUE;
}