/*----------------------------------------------------------------------------
 *
 * File:
 * eas_rtttl.c
 *
 * Contents and purpose:
 * RTTTL parser
 *
 * 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: 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_rtttldata.h"
#include "eas_ctype.h"

/* increase gain for mono ringtones */
#define RTTTL_GAIN_OFFSET       8

/* maximum title length including colon separator */
#define RTTTL_MAX_TITLE_LEN     32
#define RTTTL_INFINITE_LOOP     15

/* length of 32nd note in 1/256ths of a msec for 63 BPM tempo */
#define DEFAULT_TICK_CONV       30476
#define TICK_CONVERT            1920000

/* default channel and program for RTTTL playback */
#define RTTTL_CHANNEL           0
#define RTTTL_PROGRAM           80
#define RTTTL_VELOCITY          127

/* note used for rest */
#define RTTTL_REST              1

/* multiplier for fixed point triplet conversion */
#define TRIPLET_MULTIPLIER      683
#define TRIPLET_SHIFT           10

/* local prototypes */
static EAS_RESULT RTTTL_CheckFileType (S_EAS_DATA *pEASData, EAS_FILE_HANDLE fileHandle, EAS_VOID_PTR *ppHandle, EAS_I32 offset);
static EAS_RESULT RTTTL_Prepare (S_EAS_DATA *pEASData, EAS_VOID_PTR pInstData);
static EAS_RESULT RTTTL_Time (S_EAS_DATA *pEASData, EAS_VOID_PTR pInstData, EAS_U32 *pTime);
static EAS_RESULT RTTTL_Event (S_EAS_DATA *pEASData, EAS_VOID_PTR pInstData, EAS_INT parserMode);
static EAS_RESULT RTTTL_State (S_EAS_DATA *pEASData, EAS_VOID_PTR pInstData, EAS_STATE *pState);
static EAS_RESULT RTTTL_Close (S_EAS_DATA *pEASData, EAS_VOID_PTR pInstData);
static EAS_RESULT RTTTL_Reset (S_EAS_DATA *pEASData, EAS_VOID_PTR pInstData);
static EAS_RESULT RTTTL_Pause (S_EAS_DATA *pEASData, EAS_VOID_PTR pInstData);
static EAS_RESULT RTTTL_Resume (S_EAS_DATA *pEASData, EAS_VOID_PTR pInstData);
static EAS_RESULT RTTTL_SetData (S_EAS_DATA *pEASData, EAS_VOID_PTR pInstData, EAS_I32 param, EAS_I32 value);
static EAS_RESULT RTTTL_GetData (S_EAS_DATA *pEASData, EAS_VOID_PTR pInstData, EAS_I32 param, EAS_I32 *pValue);
static EAS_RESULT RTTTL_GetStyle (EAS_HW_DATA_HANDLE hwInstData, S_RTTTL_DATA *pData);
static EAS_RESULT RTTTL_GetDuration (EAS_HW_DATA_HANDLE hwInstData, S_RTTTL_DATA *pData, EAS_I8 *pDuration);
static EAS_RESULT RTTTL_GetOctave (EAS_HW_DATA_HANDLE hwInstData, S_RTTTL_DATA *pData, EAS_U8 *pOctave);
static EAS_RESULT RTTTL_GetTempo (EAS_HW_DATA_HANDLE hwInstData, S_RTTTL_DATA *pData);
static EAS_RESULT RTTTL_GetNumber (EAS_HW_DATA_HANDLE hwInstData, S_RTTTL_DATA *pData, EAS_I32 *pValue);
static EAS_RESULT RTTTL_ParseHeader (S_EAS_DATA *pEASData, S_RTTTL_DATA* pData, EAS_BOOL metaData);
static EAS_RESULT RTTTL_GetNextChar (EAS_HW_DATA_HANDLE hwInstData, S_RTTTL_DATA *pData, EAS_I8 *pValue);
static EAS_RESULT RTTTL_PeekNextChar (EAS_HW_DATA_HANDLE hwInstData, S_RTTTL_DATA *pData, EAS_I8 *pValue);

/* inline functions */
EAS_INLINE void RTTTL_PutBackChar (S_RTTTL_DATA *pData, EAS_I8 value) { pData->dataByte = value; }


/* lookup table for note values */
static const EAS_U8 noteTable[] = { 21, 23, 12, 14, 16, 17, 19, 23 };

/*----------------------------------------------------------------------------
 *
 * EAS_RTTTL_Parser
 *
 * This structure contains the functional interface for the iMelody parser
 *----------------------------------------------------------------------------
*/
const S_FILE_PARSER_INTERFACE EAS_RTTTL_Parser =
{
    RTTTL_CheckFileType,
    RTTTL_Prepare,
    RTTTL_Time,
    RTTTL_Event,
    RTTTL_State,
    RTTTL_Close,
    RTTTL_Reset,
#ifdef JET_INTERFACE
    RTTTL_Pause,
    RTTTL_Resume,
#else
    NULL,
    NULL,
#endif
    NULL,
    RTTTL_SetData,
    RTTTL_GetData,
    NULL
};

/*----------------------------------------------------------------------------
 * RTTTL_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 RTTTL_CheckFileType (S_EAS_DATA *pEASData, EAS_FILE_HANDLE fileHandle, EAS_VOID_PTR *ppHandle, EAS_I32 offset)
{
    S_RTTTL_DATA data = {0};
    S_RTTTL_DATA *pData;

    /* see if we can parse the header */
    data.fileHandle = fileHandle;
    data.fileOffset = offset;
    *ppHandle= NULL;
    if (RTTTL_ParseHeader (pEASData, &data, EAS_FALSE) == EAS_SUCCESS)
    {

        /* check for static memory allocation */
        if (pEASData->staticMemoryModel)
            pData = EAS_CMEnumData(EAS_CM_RTTTL_DATA);
        else
            pData = EAS_HWMalloc(pEASData->hwInstData, sizeof(S_RTTTL_DATA));
        if (!pData)
            return EAS_ERROR_MALLOC_FAILED;
        EAS_HWMemSet(pData, 0, sizeof(S_RTTTL_DATA));

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

    return EAS_SUCCESS;
}

/*----------------------------------------------------------------------------
 * RTTTL_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 RTTTL_Prepare (S_EAS_DATA *pEASData, EAS_VOID_PTR pInstData)
{
    S_RTTTL_DATA* pData;
    EAS_RESULT result;

    /* check for valid state */
    pData = (S_RTTTL_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;
    }

    pData->state = EAS_STATE_ERROR;
    if ((result = RTTTL_ParseHeader (pEASData,  pData, (EAS_BOOL) (pData->metadata.callback != NULL))) != EAS_SUCCESS)
    {
        /* if using dynamic memory, free it */
        if (!pEASData->staticMemoryModel)
            EAS_HWFree(pEASData->hwInstData, pData);
        return result;
    }

    pData->state = EAS_STATE_READY;
    return EAS_SUCCESS;
}

/*----------------------------------------------------------------------------
 * RTTTL_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 RTTTL_Time (S_EAS_DATA *pEASData, EAS_VOID_PTR pInstData, EAS_U32 *pTime)
{
    S_RTTTL_DATA *pData;

    pData = (S_RTTTL_DATA*) pInstData;

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

/*----------------------------------------------------------------------------
 * RTTTL_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 RTTTL_Event (S_EAS_DATA *pEASData, EAS_VOID_PTR pInstData, EAS_INT parserMode)
{
    S_RTTTL_DATA* pData;
    EAS_RESULT result;
    EAS_I32 ticks;
    EAS_I32 temp;
    EAS_I8 c;
    EAS_U8 note;
    EAS_U8 octave;

    pData = (S_RTTTL_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, RTTTL_CHANNEL, RTTTL_PROGRAM);

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

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

        /* check for rest between notes */
        if (pData->restTicks)
        {
            pData->time += pData->restTicks;
            pData->restTicks = 0;
            return EAS_SUCCESS;
        }
    }

    /* parse the next event */
    octave = pData->octave;
    note = 0;
    ticks = pData->duration * pData->tick;
    for (;;)
    {

        /* get next character */
        if ((result = RTTTL_GetNextChar(pEASData->hwInstData, pData, &c)) != EAS_SUCCESS)
        {
            if (result != EAS_EOF)
                return result;

            /* end of file, if no notes to process, check for looping */
            if (!note)
            {
                /* if no loop set state to stopping */
                if (pData->repeatCount == 0)
                {
                    pData->state = EAS_STATE_STOPPING;
                    VMReleaseAllVoices(pEASData->pVoiceMgr, pData->pSynth);
                    return EAS_SUCCESS;
                }

                /* decrement loop count */
                if (pData->repeatCount != RTTTL_INFINITE_LOOP)
                    pData->repeatCount--;

                /* if locating, ignore infinite loops */
                else if (parserMode != eParserModePlay)
                {
                    pData->state = EAS_STATE_STOPPING;
                    VMReleaseAllVoices(pEASData->pVoiceMgr, pData->pSynth);
                    return EAS_SUCCESS;
                }

                /* loop back to start of notes */
                if (pData->notePlayedSinceRepeat == 0) {
                    return EAS_ERROR_FILE_FORMAT;
                }
                if ((result = EAS_HWFileSeek(pEASData->hwInstData, pData->fileHandle, pData->repeatOffset)) != EAS_SUCCESS)
                    return result;
                pData->notePlayedSinceRepeat = 0;
                continue;
            }

            /* still have a note to process */
            else
                c = ',';
        }

        /* bpm */
        if (c == 'b')
        {
            /* peek at next character */
            if ((result = RTTTL_PeekNextChar(pEASData->hwInstData, pData, &c)) != EAS_SUCCESS)
                return result;

            /* if a number, must be octave or tempo */
            if (IsDigit(c))
            {
                if ((result = RTTTL_GetNumber(pEASData->hwInstData, pData, &temp)) != EAS_SUCCESS)
                    return result;

                /* check for octave first */
                if ((temp >= 4) && (temp <= 7))
                {
                    octave = (EAS_U8) temp;
                }

                /* check for tempo */
                else if ((temp >= 25) && (temp <= 900))
                {
                    pData->tick = TICK_CONVERT / (EAS_U32) temp;
                }

                /* don't know what it was */
                else
                    return EAS_ERROR_FILE_FORMAT;
            }

            /* must be a note */
            else
            {
                note = noteTable[1];
            }
        }

        /* octave */
        else if (c == 'o')
        {
            if ((result = RTTTL_GetOctave(pEASData->hwInstData, pData, &pData->octave)) != EAS_SUCCESS)
                return result;
        }

        /* style */
        else if (c == 's')
        {
            if ((result = RTTTL_GetStyle(pEASData->hwInstData, pData)) != EAS_SUCCESS)
                return result;
        }

        /* duration or octave */
        else if (IsDigit(c))
        {
            RTTTL_PutBackChar(pData, c);

            /* duration comes before note */
            if (!note)
            {
                if ((result = RTTTL_GetDuration(pEASData->hwInstData, pData, &c)) != EAS_SUCCESS)
                    return result;
                ticks = c * pData->tick;
            }

            /* octave comes after note */
            else
            {
                if ((result = RTTTL_GetOctave(pEASData->hwInstData, pData, &octave)) != EAS_SUCCESS)
                    return result;
            }
        }

        /* note or rest */
        else if ((c >= 'a') && (c <= 'h'))
        {
            note = noteTable[c - 'a'];
        }

        else if (c == 'p')
        {
            note = RTTTL_REST;
        }

        /* dotted note */
        else if (c == '.')
        {
            /* Number of ticks must not be greater than 32-bits */
            if ((ticks >> 1) > (INT32_MAX - ticks))
            {
                return EAS_ERROR_FILE_FORMAT;
            }

            /*lint -e{704} shift for performance */
            ticks += ticks >> 1;
        }

        /* accidental */
        else if (c == '#')
        {
            if (note)
                note++;
        }

        /* end of event */
        else if ((c == ',') && note)
        {
            pData->notePlayedSinceRepeat = 1;

            /* handle note events */
            if (note != RTTTL_REST)
            {

                /* save note and start it */
                pData->note = note + octave;
                if (parserMode == eParserModePlay)
                    VMStartNote(pEASData->pVoiceMgr, pData->pSynth, RTTTL_CHANNEL, pData->note, RTTTL_VELOCITY);

                /* determine note length */
                switch (pData->style)
                {
                    /* natural */
                    case 'n':
                        /*lint -e{704} shift for performance */
                        pData->restTicks = ticks >> 4;
                        break;
                    /* continuous */

                    case 'c':
                        pData->restTicks = 0;
                        break;

                    /* staccato */
                    case 's':
                        /*lint -e{704} shift for performance */
                        pData->restTicks = ticks >> 1;
                        break;

                    default:
                        { /* dpp: EAS_ReportEx(_EAS_SEVERITY_WARNING, "RTTTL_Event: Unexpected style type %c\n", pData->style); */ }
                        break;
                }

                /* next event is at end of this note */
                if ((ticks - pData->restTicks) > (INT32_MAX - pData->time))
                {
                    return EAS_ERROR_FILE_FORMAT;
                }
                pData->time += ticks - pData->restTicks;
            }

            /* rest */
            else
            {
                if (ticks > (INT32_MAX - pData->time))
                {
                    return EAS_ERROR_FILE_FORMAT;
                }
                pData->time += ticks;
            }

            /* event found, return to caller */
            break;
        }
    }

    pData->state = EAS_STATE_PLAY;
    return EAS_SUCCESS;
}

/*----------------------------------------------------------------------------
 * RTTTL_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 RTTTL_State (S_EAS_DATA *pEASData, EAS_VOID_PTR pInstData, EAS_I32 *pState)
{
    S_RTTTL_DATA* pData;

    /* establish pointer to instance data */
    pData = (S_RTTTL_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;
}

/*----------------------------------------------------------------------------
 * RTTTL_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 RTTTL_Close (S_EAS_DATA *pEASData, EAS_VOID_PTR pInstData)
{
    S_RTTTL_DATA* pData;
    EAS_RESULT result;

    pData = (S_RTTTL_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;
}

/*----------------------------------------------------------------------------
 * RTTTL_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 RTTTL_Reset (S_EAS_DATA *pEASData, EAS_VOID_PTR pInstData)
{
    S_RTTTL_DATA* pData;
    EAS_RESULT result;

    pData = (S_RTTTL_DATA*) pInstData;

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

    /* reset time to zero */
    pData->time = 0;
    pData->note = 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 = RTTTL_ParseHeader (pEASData,  pData, EAS_TRUE)) != EAS_SUCCESS)
        return result;

    pData->state = EAS_STATE_READY;
    return EAS_SUCCESS;
}

#ifdef JET_INTERFACE
/*----------------------------------------------------------------------------
 * RTTTL_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 RTTTL_Pause (S_EAS_DATA *pEASData, EAS_VOID_PTR pInstData)
{
    S_RTTTL_DATA *pData;

    /* can't pause a stopped stream */
    pData = (S_RTTTL_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;
}

/*----------------------------------------------------------------------------
 * RTTTL_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 RTTTL_Resume (S_EAS_DATA *pEASData, EAS_VOID_PTR pInstData)
{
    S_RTTTL_DATA *pData;

    /* can't resume a stopped stream */
    pData = (S_RTTTL_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;
}
#endif

/*----------------------------------------------------------------------------
 * RTTTL_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) reserved for future use */
static EAS_RESULT RTTTL_SetData (S_EAS_DATA *pEASData, EAS_VOID_PTR pInstData, EAS_I32 param, EAS_I32 value)
{
    S_RTTTL_DATA *pData;

    pData = (S_RTTTL_DATA *) pInstData;
    switch (param)
    {

        /* set metadata callback */
        case PARSER_DATA_METADATA_CB:
            EAS_HWMemCpy(&pData->metadata, (void*) value, sizeof(S_METADATA_CB));
            break;

        default:
            return EAS_ERROR_INVALID_PARAMETER;
    }

    return EAS_SUCCESS;
}

/*----------------------------------------------------------------------------
 * RTTTL_GetData()
 *----------------------------------------------------------------------------
 * Purpose:
 * Return file type
 *
 * 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 RTTTL_GetData (S_EAS_DATA *pEASData, EAS_VOID_PTR pInstData, EAS_I32 param, EAS_I32 *pValue)
{
    S_RTTTL_DATA *pData;

    pData = (S_RTTTL_DATA *) pInstData;
    switch (param)
    {
        /* return file type as RTTTL */
        case PARSER_DATA_FILE_TYPE:
            *pValue = EAS_FILE_RTTTL;
            break;

#if 0
        /* set transposition */
        case PARSER_DATA_TRANSPOSITION:
            *pValue = pData->transposition;
            break;
#endif

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

        case PARSER_DATA_GAIN_OFFSET:
            *pValue = RTTTL_GAIN_OFFSET;
            break;

    default:
            return EAS_ERROR_INVALID_PARAMETER;
    }
    return EAS_SUCCESS;
}

/*----------------------------------------------------------------------------
 * RTTTL_GetStyle()
 *----------------------------------------------------------------------------
 * Purpose:
 *
 *
 * Inputs:
 *
 *
 * Outputs:
 *
 *
 * Side Effects:
 *
 *----------------------------------------------------------------------------
*/
static EAS_RESULT RTTTL_GetStyle (EAS_HW_DATA_HANDLE hwInstData, S_RTTTL_DATA *pData)
{
    EAS_RESULT result;
    EAS_I8 style;

    /* get style */
    if ((result = RTTTL_GetNextChar(hwInstData, pData, &style)) != EAS_SUCCESS)
        return result;

    if ((style != 's')  && (style != 'n') && (style != 'c'))
        return EAS_ERROR_FILE_FORMAT;

    pData->style = style;
    return EAS_SUCCESS;
}

/*----------------------------------------------------------------------------
 * RTTTL_GetDuration()
 *----------------------------------------------------------------------------
 * Purpose:
 *
 *
 * Inputs:
 *
 *
 * Outputs:
 *
 *
 * Side Effects:
 *
 *----------------------------------------------------------------------------
*/
static EAS_RESULT RTTTL_GetDuration (EAS_HW_DATA_HANDLE hwInstData, S_RTTTL_DATA *pData, EAS_I8 *pDuration)
{
    EAS_RESULT result;
    EAS_I32 duration;
    EAS_I8 temp;

    /* get the duration */
    if ((result = RTTTL_GetNumber(hwInstData, pData, &duration)) != EAS_SUCCESS)
        return result;

    if ((duration != 1) && (duration != 2) && (duration != 4) && (duration != 8) && (duration != 16) && (duration != 32))
        return EAS_ERROR_FILE_FORMAT;

    temp = 64;
    while (duration)
    {
        /*lint -e{704} shift for performance */
        duration = duration >> 1;
        /*lint -e{702} use shift for performance */
        temp = temp >> 1;
    }

    *pDuration = temp;
    return EAS_SUCCESS;
}

/*----------------------------------------------------------------------------
 * RTTTL_GetOctave()
 *----------------------------------------------------------------------------
 * Purpose:
 *
 *
 * Inputs:
 *
 *
 * Outputs:
 *
 *
 * Side Effects:
 *
 *----------------------------------------------------------------------------
*/
static EAS_RESULT RTTTL_GetOctave (EAS_HW_DATA_HANDLE hwInstData, S_RTTTL_DATA *pData, EAS_U8 *pOctave)
{
    EAS_RESULT result;
    EAS_I32 octave;

    /* get the tempo */
    if ((result = RTTTL_GetNumber(hwInstData, pData, &octave)) != EAS_SUCCESS)
        return result;

    if ((octave < 4) || (octave > 7))
        return EAS_ERROR_FILE_FORMAT;

    *pOctave = (EAS_U8) (octave * 12);
    return EAS_SUCCESS;
}

/*----------------------------------------------------------------------------
 * RTTTL_GetTempo()
 *----------------------------------------------------------------------------
 * Purpose:
 *
 *
 * Inputs:
 *
 *
 * Outputs:
 *
 *
 * Side Effects:
 *
 *----------------------------------------------------------------------------
*/
static EAS_RESULT RTTTL_GetTempo (EAS_HW_DATA_HANDLE hwInstData, S_RTTTL_DATA *pData)
{
    EAS_RESULT result;
    EAS_I32 tempo;

    /* get the tempo */
    if ((result = RTTTL_GetNumber(hwInstData, pData, &tempo)) != EAS_SUCCESS)
        return result;

    if ((tempo < 25) || (tempo > 900))
        return EAS_ERROR_FILE_FORMAT;

    pData->tick = TICK_CONVERT / (EAS_U32) tempo;
    return EAS_SUCCESS;
}

/*----------------------------------------------------------------------------
 * RTTTL_GetNumber()
 *----------------------------------------------------------------------------
 * Purpose:
 *
 *
 * Inputs:
 *
 *
 * Outputs:
 *
 *
 * Side Effects:
 *
 *----------------------------------------------------------------------------
*/
static EAS_RESULT RTTTL_GetNumber (EAS_HW_DATA_HANDLE hwInstData, S_RTTTL_DATA *pData, EAS_I32 *pValue)
{
    EAS_RESULT result;
    EAS_INT temp;
    EAS_I8 c;

    *pValue = -1;
    temp = 0;
    for (;;)
    {
        if ((result = RTTTL_PeekNextChar(hwInstData, pData, &c)) != EAS_SUCCESS)
        {
            if ((result == EAS_EOF) && (*pValue != -1))
                return EAS_SUCCESS;
            return result;
        }

        if (IsDigit(c))
        {
            pData->dataByte = 0;
            if (temp > 100) {
                // This is just to prevent overflows in case of a really large number
                // in the file, but rather than allowing the number to grow up to INT_MAX,
                // we limit it to a much smaller number since the numbers in an RTTTL file
                // are supposed to be at most in the hundreds, not millions or billions.
                // There are more specific checks in the callers of this function to enforce
                // the various limits for notes, octaves, tempo, etc.
                return EAS_FAILURE;
            }
            temp = temp * 10 + c - '0';
            *pValue = temp;
        }
        else
            return EAS_SUCCESS;
    }
}

/*----------------------------------------------------------------------------
 * RTTTL_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 RTTTL_ParseHeader (S_EAS_DATA *pEASData, S_RTTTL_DATA* pData, EAS_BOOL metaData)
{
    EAS_RESULT result;
    EAS_I32 i;
    EAS_I8 temp;
    EAS_I8 control;

    /* initialize some defaults */
    pData->time = 0;
    pData->tick = DEFAULT_TICK_CONV;
    pData->note = 0;
    pData->duration = 4;
    pData ->restTicks = 0;
    pData->octave = 60;
    pData->repeatOffset = -1;
    pData->repeatCount = 0;
    pData->style = 'n';
    pData->dataByte = 0;

    metaData = metaData && (pData->metadata.buffer != NULL) && (pData->metadata.callback != NULL);

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

    /* zero the metadata buffer */
    if (metaData)
        EAS_HWMemSet(pData->metadata.buffer, 0, pData->metadata.bufferSize);

    /* read the title */
    for (i = 0; i < RTTTL_MAX_TITLE_LEN; i++)
    {
        if ((result = EAS_HWGetByte(pEASData->hwInstData, pData->fileHandle, &temp)) != EAS_SUCCESS)
            return result;

        if (temp == ':')
            break;

        /* pass along metadata */
        if (metaData)
        {
            if (i < (pData->metadata.bufferSize- 1))
                pData->metadata.buffer[i] = (char) temp;
        }
    }

    /* check for error in title */
    if (i == RTTTL_MAX_TITLE_LEN)
        return EAS_ERROR_FILE_FORMAT;

    /* pass along metadata */
    if (metaData)
        (*pData->metadata.callback)(EAS_METADATA_TITLE, pData->metadata.buffer, pData->metadata.pUserData);

    /* control fields */
    for (;;)
    {

        /* get control type */
        if ((result = RTTTL_GetNextChar(pEASData->hwInstData, pData, &control)) != EAS_SUCCESS)
            return result;

        /* next char should be equal sign */
        if ((result = RTTTL_GetNextChar(pEASData->hwInstData, pData, &temp)) != EAS_SUCCESS)
            return result;
        if (temp != '=')
            return EAS_ERROR_FILE_FORMAT;

        /* get the control value */
        switch (control)
        {

            /* bpm */
            case 'b':
                if ((result = RTTTL_GetTempo(pEASData->hwInstData, pData)) != EAS_SUCCESS)
                    return result;
                break;

            /* duration */
            case 'd':
                if ((result = RTTTL_GetDuration(pEASData->hwInstData, pData, &temp)) != EAS_SUCCESS)
                    return result;
                pData->duration = temp;
                break;

            /* loop */
            case 'l':
                if ((result = RTTTL_GetNumber(pEASData->hwInstData, pData, &i)) != EAS_SUCCESS)
                    return result;
                if ((i < 0) || (i > 15))
                    return EAS_ERROR_FILE_FORMAT;
                pData->repeatCount = (EAS_U8) i;
                break;

            /* octave */
            case 'o':
                if ((result = RTTTL_GetOctave(pEASData->hwInstData, pData, &pData->octave)) != EAS_SUCCESS)
                    return result;
                break;

            /* get style */
            case 's':
                if ((result = RTTTL_GetStyle(pEASData->hwInstData, pData)) != EAS_SUCCESS)
                    return result;
                break;

            /* unrecognized control */
            default:
                return EAS_ERROR_FILE_FORMAT;
        }

        /* next character should be comma or colon */
        if ((result = RTTTL_GetNextChar(pEASData->hwInstData, pData, &temp)) != EAS_SUCCESS)
            return result;

        /* check for end of control field */
        if (temp == ':')
            break;

        /* must be a comma */
        if (temp != ',')
            return EAS_ERROR_FILE_FORMAT;
    }

    /* should be at the start of the music block */
    if ((result = EAS_HWFilePos(pEASData->hwInstData, pData->fileHandle, &pData->repeatOffset)) != EAS_SUCCESS)
        return result;

    pData->notePlayedSinceRepeat = 0;
    return EAS_SUCCESS;
}

/*----------------------------------------------------------------------------
 * RTTTL_GetNextChar()
 *----------------------------------------------------------------------------
 * Purpose:
 *
 *
 * Inputs:
 *
 *
 * Outputs:
 *
 *
 * Side Effects:
 *
 *----------------------------------------------------------------------------
*/
static EAS_RESULT RTTTL_GetNextChar (EAS_HW_DATA_HANDLE hwInstData, S_RTTTL_DATA *pData, EAS_I8 *pValue)
{
    EAS_RESULT result;
    EAS_I8 temp;

    *pValue = 0;
    for(;;)
    {

        /* check for character that has been put back */
        if (pData->dataByte)
        {
            temp = pData->dataByte;
            pData->dataByte = 0;
        }
        else
        {
            if ((result = EAS_HWGetByte(hwInstData, pData->fileHandle, &temp)) != EAS_SUCCESS)
                return result;
        }

        /* ignore white space */
        if (!IsSpace(temp))
        {
            *pValue = ToLower(temp);
            return EAS_SUCCESS;
        }
    }
}

/*----------------------------------------------------------------------------
 * RTTTL_PeekNextChar()
 *----------------------------------------------------------------------------
 * Purpose:
 *
 *
 * Inputs:
 *
 *
 * Outputs:
 *
 *
 * Side Effects:
 *
 *----------------------------------------------------------------------------
*/
static EAS_RESULT RTTTL_PeekNextChar (EAS_HW_DATA_HANDLE hwInstData, S_RTTTL_DATA *pData, EAS_I8 *pValue)
{
    EAS_RESULT result;
    EAS_I8 temp;

    *pValue = 0;
    for(;;)
    {

        /* read a character from the file, if necessary */
        if (!pData->dataByte)
        {
            if ((result = EAS_HWGetByte(hwInstData, pData->fileHandle, &pData->dataByte)) != EAS_SUCCESS)
                return result;

        }
        temp = pData->dataByte;

        /* ignore white space */
        if (!IsSpace(temp))
        {
            *pValue = ToLower(temp);
            return EAS_SUCCESS;
        }
        pData->dataByte = 0;
    }
}

