/******************************Module*Header*******************************\
* Module Name: cdapimci.c
*
* This module encapsulates the CD-ROM device into a set of callable apis.
* The api's are implemented using the cdaudio mci interface.
*
* Created: 26-04-93
* Author:  Stephen Estrop [StephenE]
*
* Copyright (c) 1993 - 1995 Microsoft Corporation.  All rights reserved.
\**************************************************************************/
#ifndef USE_IOCTLS
#pragma warning( once : 4201 4214 )

#define NOOLE

#include <windows.h>    /* required for all Windows applications */
#include <windowsx.h>
#include <string.h>

#include "resource.h"
#include "cdplayer.h"
#include "cdapi.h"
#include "scan.h"
#include "trklst.h"


/* -------------------------------------------------------------------------
**
** High level routines
**
** -------------------------------------------------------------------------
*/

/******************************Public*Routine******************************\
* OpenCdRom
*
*
*
* History:
* dd-mm-94 - StephenE - Created
*
\**************************************************************************/
MCIDEVICEID
OpenCdRom(
    TCHAR chDrive,
    LPDWORD lpdwErrCode
    )
{
    MCI_OPEN_PARMS  mciOpen;
    TCHAR           szElementName[4];
    TCHAR           szAliasName[32];
    DWORD           dwFlags;
    DWORD           dwAliasCount = GetCurrentTime();
    DWORD           dwRet;

    ZeroMemory( &mciOpen, sizeof(mciOpen) );

    mciOpen.lpstrDeviceType = (LPTSTR)MCI_DEVTYPE_CD_AUDIO;
    wsprintf( szElementName, TEXT("%c:"), chDrive );
    wsprintf( szAliasName, TEXT("SJE%lu:"), dwAliasCount );

    mciOpen.lpstrElementName = szElementName;
    mciOpen.lpstrAlias = szAliasName;

#ifdef DAYTONA
    dwFlags = MCI_OPEN_ELEMENT | MCI_OPEN_SHAREABLE | MCI_OPEN_ALIAS |
              MCI_OPEN_TYPE | MCI_OPEN_TYPE_ID | MCI_WAIT;
#else
    dwFlags = MCI_OPEN_ELEMENT | MCI_OPEN_ALIAS |
              MCI_OPEN_TYPE | MCI_OPEN_TYPE_ID | MCI_WAIT;
#endif

    dwRet = mciSendCommand(0, MCI_OPEN, dwFlags, (DWORD)(LPVOID)&mciOpen);


    if ( dwRet == MMSYSERR_NOERROR ) {

        MCI_SET_PARMS   mciSet;

        ZeroMemory( &mciSet, sizeof(mciSet) );

        mciSet.dwTimeFormat = MCI_FORMAT_MSF;
        mciSendCommand( mciOpen.wDeviceID, MCI_SET,
                        MCI_SET_TIME_FORMAT, (DWORD)(LPVOID)&mciSet );
    }
    else {

        /*
        ** Only return the error code if we have been given a valid pointer
        */
        if (lpdwErrCode != NULL) {
            *lpdwErrCode = dwRet;
        }

        mciOpen.wDeviceID = 0;
    }

    return mciOpen.wDeviceID;
}


/******************************Public*Routine******************************\
* CloseCdRom
*
*
*
* History:
* dd-mm-94 - StephenE - Created
*
\**************************************************************************/
void
CloseCdRom(
    MCIDEVICEID DevHandle
    )
{
    mciSendCommand( DevHandle, MCI_CLOSE, 0L, 0L );
}


/******************************Public*Routine******************************\
* CheckStatus
*
* Check return code for known bad codes and inform
* user how to correct (if possible) the problem.
*
* History:
* 18-11-93 - StephenE - Created
*
\**************************************************************************/
void
CheckStatus(
    LPSTR szCaller,
    DWORD status,
    int cdrom
    )
{
#if DBG
    TCHAR   s[200];
    TCHAR   err[100];
#endif


    if (status==ERROR_SUCCESS)
        return;

    switch (status) {

    case MCIERR_HARDWARE:
        NoMediaUpdate( cdrom );
        break;
    }

#if DBG
    mciGetErrorString( status, err, sizeof(err) / sizeof(TCHAR) );
    wsprintf( s, IdStr( STR_ERR_GEN ), g_Devices[cdrom]->drive, err );

    OutputDebugString (s);
    OutputDebugString (TEXT("\r\n"));

    SetWindowText( g_hwndStatusbar, s );
#endif
}



/******************************Public*Routine******************************\
* CheckUnitCdrom
*
* Queries the device state, checking to see if a disc has been ejected or
* inserted.
*
* History:
* 18-11-93 - StephenE - Created
*
\**************************************************************************/
void
CheckUnitCdrom(
    int cdrom,
    BOOL fForceRescan
    )
{
    DWORD   status;

    if ((cdrom < 0) || (cdrom >= MAX_CD_DEVICES))
    {
        return;
    }

    if (fForceRescan)
    {
        // Close Device to force read of correct TOC
        if (g_Devices[cdrom]->hCd != 0L)
        {
            CloseCdRom (g_Devices[cdrom]->hCd);
            g_Devices[cdrom]->hCd = 0L;
        }
    }

    if ( g_Devices[cdrom]->hCd == 0L ) {

        g_Devices[cdrom]->hCd = OpenCdRom( g_Devices[cdrom]->drive, NULL );

        if ( g_Devices[cdrom]->hCd == 0 ) {
            return;
        }
        else {

            /*
            ** Force a rescan of this disc.
            */
            g_Devices[cdrom]->State = CD_NO_CD;
        }

    }

    status = TestUnitReadyCdrom( g_Devices[cdrom]->hCd );

    if (g_Devices[cdrom]->State & CD_NO_CD)
    {

        if (status == ERROR_SUCCESS) {

            /*
            ** A new disc has been inserted, scan it now.
            */

            RescanDevice( g_hwndApp, cdrom );
        }
    }
    else {

        if (status != ERROR_SUCCESS) {

            /*
            ** Disc has been ejected.
            */

            NoMediaUpdate( cdrom );
        }
    }
}


/******************************Public*Routine******************************\
* NoMediaUpdate
*
* Update the user display when it is found that no media is in the device.
*
* History:
* 18-11-93 - StephenE - Created
*
\**************************************************************************/
void
NoMediaUpdate(
    int cdrom
    )
{
    BOOL fChangePlayButtons;

    if ( cdrom == g_CurrCdrom ) {
        fChangePlayButtons = TRUE;
    }
    else {
        fChangePlayButtons = FALSE;
    }

    g_Devices[cdrom]->State = (CD_NO_CD | CD_STOPPED);

    if (fChangePlayButtons) {
        SetPlayButtonsEnableState();
    }

    TimeAdjustInitialize( cdrom );
}



/******************************Public*Routine******************************\
* EjectTheCdromDisc
*
* Eject the disc from the specified cdrom device.
*
* History:
* 18-11-93 - StephenE - Created
*
\**************************************************************************/
BOOL
EjectTheCdromDisc(
    int cdrom
    )
{
    DWORD status;

    /*
    ** Stop the drive first
    */

    status = StopCdrom( g_Devices[cdrom]->hCd );

    /*
    ** Eject the disc
    */

    status = EjectCdrom( g_Devices[cdrom]->hCd );

    CheckStatus( "EjectCdrom", status, cdrom );

    return status == ERROR_SUCCESS;
}


/******************************Public*Routine******************************\
* PlayCurrTrack
*
* Set cdrom device playing from start MSF to end MSF of current
* track.
*
* History:
* 18-11-93 - StephenE - Created
*
\**************************************************************************/
BOOL
PlayCurrTrack(
    int cdrom
    )
{
    DWORD status;
    MCI_PLAY_PARMS pam;
    int min,sec,endindex;
    PTRACK_PLAY tr;

    tr = CURRTRACK( cdrom );
    if (tr==NULL) {

        return( FALSE );

    }

    sec = TRACK_S(cdrom,tr->TocIndex) + CDTIME(cdrom).TrackCurSec;
    min = TRACK_M(cdrom,tr->TocIndex) + CDTIME(cdrom).TrackCurMin;
    min+= (sec / 60);
    sec = (sec % 60);

    pam.dwFrom = MCI_MAKE_MSF( min, sec, TRACK_F(cdrom,tr->TocIndex) );

    endindex = FindContiguousEnd( cdrom, tr );

    pam.dwTo = MCI_MAKE_MSF( TRACK_M(cdrom,endindex),
                             TRACK_S(cdrom,endindex),
                             TRACK_F(cdrom,endindex) );

#if DBG
{
    long lAddress, lEndPos, lStartPos;

    dprintf( "Playing from     : %2.2d:%2.2d:%2.2d",
             MCI_MSF_MINUTE(pam.dwFrom),
             MCI_MSF_SECOND(pam.dwFrom),
             MCI_MSF_FRAME( pam.dwFrom) );
    dprintf( "Playing to       : %2.2d:%2.2d:%2.2d",
             MCI_MSF_MINUTE(pam.dwTo),
             MCI_MSF_SECOND(pam.dwTo),
             MCI_MSF_FRAME( pam.dwTo) );

    lAddress = pam.dwFrom;
    lStartPos = (MCI_MSF_MINUTE(lAddress) * FRAMES_PER_MINUTE) +
                (MCI_MSF_SECOND(lAddress) * FRAMES_PER_SECOND) +
                (MCI_MSF_FRAME(lAddress));

    lAddress = pam.dwTo;
    lEndPos   = (MCI_MSF_MINUTE(lAddress) * FRAMES_PER_MINUTE) +
                (MCI_MSF_SECOND(lAddress) * FRAMES_PER_SECOND) +
                (MCI_MSF_FRAME(lAddress));

    lAddress = lEndPos - lStartPos;
    lStartPos =
        MCI_MAKE_MSF((lAddress / FRAMES_PER_MINUTE),
                     (lAddress % FRAMES_PER_MINUTE) / FRAMES_PER_SECOND,
                     (lAddress % FRAMES_PER_MINUTE) % FRAMES_PER_SECOND);

    lAddress = lStartPos;
    dprintf( "Play length      : %2.2d:%2.2d:%2.2d",
             MCI_MSF_MINUTE(lAddress),
             MCI_MSF_SECOND(lAddress),
             MCI_MSF_FRAME( lAddress) );
}
#endif
    status = PlayCdrom( g_Devices[cdrom]->hCd, &pam );

    CheckStatus( "PlayCurrTrack", status, cdrom );

    return status == ERROR_SUCCESS;
}


/******************************Public*Routine******************************\
* StopTheCdromDrive
*
* Tell the cdrom device to stop playing
*
* History:
* 18-11-93 - StephenE - Created
*
\**************************************************************************/
BOOL
StopTheCdromDrive(
    int cdrom
    )
{

    DWORD status;

    status = StopCdrom( g_Devices[cdrom]->hCd );

    CheckStatus( "StopCdrom", status, cdrom );

    return status == ERROR_SUCCESS;
}


/******************************Public*Routine******************************\
* PauseTheCdromDrive
*
* Tell the cdrom device to pause playing
*
* History:
* 18-11-93 - StephenE - Created
*
\**************************************************************************/
BOOL
PauseTheCdromDrive(
    int cdrom
    )
{

    DWORD status;

    status = PauseCdrom( g_Devices[cdrom]->hCd );

    CheckStatus( "PauseCdrom", status, cdrom );

    return status == ERROR_SUCCESS;
}



/******************************Public*Routine******************************\
* ResumeTheCdromDrive
*
* Tell the cdrom device to resume playing
*
* History:
* 18-11-93 - StephenE - Created
*
\**************************************************************************/
BOOL
ResumeTheCdromDrive(
    int cdrom
    )
{

    DWORD status;

    status = ResumeCdrom( g_Devices[cdrom]->hCd, cdrom );

    CheckStatus( "ResumeCdrom", status, cdrom );

    if ( status == ERROR_NOT_READY ) {
        NoMediaUpdate( cdrom );
    }

    return status == ERROR_SUCCESS;
}


/******************************Public*Routine******************************\
* SeekToCurrSecond
*
* Seek to the position on the disc represented by the
* current time (position) information in gDevices, and
* continue playing to the end of the current track.
*
* History:
* 18-11-93 - StephenE - Created
*
\**************************************************************************/
BOOL
SeekToCurrSecond(
    int cdrom
    )
{
    DWORD       status;
    int         endindex;
    PTRACK_PLAY tr;
    UCHAR       StartingF, StartingS, StartingM;


    /*
    ** Build starting and ending positions for play
    */

    tr = CDTIME(cdrom).CurrTrack;
    if (tr == NULL) {

        return FALSE;
    }

    StartingM = (TRACK_M(cdrom,tr->TocIndex) + CDTIME(cdrom).TrackCurMin);
    StartingS = (TRACK_S(cdrom,tr->TocIndex) + CDTIME(cdrom).TrackCurSec);
    StartingF = (TRACK_F(cdrom,tr->TocIndex));

    if (StartingS > 59) {
        StartingM++;
        StartingS -= (UCHAR)60;
    }

    if (g_Devices[ cdrom ]->State & CD_PLAYING) {

        MCI_PLAY_PARMS mciPlay;


        endindex = FindContiguousEnd( cdrom, tr );

        mciPlay.dwFrom = MCI_MAKE_MSF( StartingM, StartingS, StartingF );
        mciPlay.dwTo   = MCI_MAKE_MSF( TRACK_M(cdrom,endindex),
                                       TRACK_S(cdrom,endindex),
                                       TRACK_F(cdrom,endindex) );
        status = PlayCdrom( g_Devices[ cdrom ]->hCd, &mciPlay );
    }
    else {

        MCI_SEEK_PARMS mciSeek;

        mciSeek.dwTo = MCI_MAKE_MSF( StartingM, StartingS, StartingF );

        status = SeekCdrom( g_Devices[ cdrom ]->hCd, &mciSeek );

    }

    CheckStatus( "SeekToCurrSec", status, cdrom );

    return status == ERROR_SUCCESS;
}


/******************************Public*Routine******************************\
* GetCurrPos
*
* Query cdrom device for its current position and status
* and return information in callers buffer.
*
* History:
* 18-11-93 - StephenE - Created
*
\**************************************************************************/
BOOL
GetCurrPos(
    int cdrom,
    PCURRPOS CpPtr
    )
{
    DWORD status;
    DWORD dwStatus;
    DWORD dwTrack;
    DWORD dwAbsPos;

    /*
    ** Tell lower layer what we want it to do...in this case,
    ** we need to specify which SubQData format we want returned.
    ** This is exported from scsicdrom.sys to the user layer
    ** so that it could be implemented in one call, instead of
    ** four separate calls (there are four SubQData formats)
    */

    /*
    ** Set up for current position SubQData format.
    */

    if (CDTIME(cdrom).CurrTrack != NULL) {

        //status = GetCdromCurrentPosition( g_Devices[ cdrom ]->hCd, &dwAbsPos );
        status = StatusTrackPosCdrom ( g_Devices[ cdrom ]->hCd, &dwStatus, &dwTrack, &dwAbsPos);

    }
    else {

        status = MCIERR_INTERNAL;

    }

    if (status == ERROR_SUCCESS) {

        int     iTrack;
        LONG    lAbsPosF;
        LONG    lStartPos;
        LONG    lTrackPos;

        //iTrack = (int)GetCdromCurrentTrack( g_Devices[ cdrom ]->hCd );
        iTrack = (int)dwTrack;

        //CpPtr->AudioStatus = GetCdromMode( g_Devices[ cdrom ]->hCd );
        CpPtr->AudioStatus = dwStatus;
        CpPtr->Track = iTrack;
        CpPtr->Index = 1;

        iTrack--;

        lStartPos = (TRACK_M(cdrom, iTrack ) * FRAMES_PER_MINUTE) +
                    (TRACK_S(cdrom, iTrack ) * FRAMES_PER_SECOND) +
                    (TRACK_F(cdrom, iTrack ));

        lAbsPosF  = (MCI_MSF_MINUTE(dwAbsPos) * FRAMES_PER_MINUTE) +
                    (MCI_MSF_SECOND(dwAbsPos) * FRAMES_PER_SECOND) +
                    (MCI_MSF_FRAME(dwAbsPos));

        lTrackPos = lAbsPosF - lStartPos;

        /*
        ** Are we in the track lead in zone ?
        */
        if ( lTrackPos < 0 ) {

            /*
            ** Have we just entered the lead in zone
            */
            if (!g_Devices[cdrom]->fProcessingLeadIn) {

                g_Devices[cdrom]->fProcessingLeadIn = TRUE;

                /*
                ** Is the track that we are currently in the next track
                ** that we actually want to play.  If it is then everything is
                ** OK.  If it isn't then we need to hack the current position
                ** information so that it looks like we sre still playing the
                ** previous track.
                */
                if ( CURRTRACK(cdrom)->nextplay
                  && CURRTRACK(cdrom)->nextplay->TocIndex == iTrack) {

                    g_Devices[cdrom]->fShowLeadIn = TRUE;
                }
                else {
                    g_Devices[cdrom]->fShowLeadIn = FALSE;
                }
            }

            if (g_Devices[cdrom]->fShowLeadIn) {

                CpPtr->Index = 0;
                lTrackPos = -lTrackPos;
            }
            else {

                CpPtr->Track = iTrack;
                iTrack--;
                lTrackPos = lAbsPosF
                               - g_Devices[cdrom]->toc.TrackData[iTrack].AddressF;
            }
        }
        else {

            g_Devices[cdrom]->fShowLeadIn = FALSE;
            g_Devices[cdrom]->fProcessingLeadIn = FALSE;
        }

        CpPtr->m = (int)(lTrackPos / FRAMES_PER_MINUTE);
        CpPtr->s = (int)(lTrackPos % FRAMES_PER_MINUTE) / FRAMES_PER_SECOND;
        CpPtr->f = (int)(lTrackPos % FRAMES_PER_MINUTE) % FRAMES_PER_SECOND;

        CpPtr->ab_m = (int)MCI_MSF_MINUTE(dwAbsPos);
        CpPtr->ab_s = (int)MCI_MSF_SECOND(dwAbsPos);
        CpPtr->ab_f = (int)MCI_MSF_FRAME(dwAbsPos);

        /*
        ** Round up to the nearest second.
        */
        if (CpPtr->f > (FRAMES_PER_SECOND / 2) ) {

            if ( ++CpPtr->s > 59 ) {
                CpPtr->s = 0;
                CpPtr->m++;
            }
        }
        else {
            CpPtr->f = 0;
        }

        if (CpPtr->ab_f > (FRAMES_PER_SECOND / 2) ) {

            if ( ++CpPtr->ab_s > 59 ) {
                CpPtr->ab_s = 0;
                CpPtr->ab_m++;
            }
        }
        else {
            CpPtr->ab_f = 0;
        }

    }
    else {

        ZeroMemory( CpPtr, sizeof(*CpPtr) );
    }

    CheckStatus( "GetCurrPos", status, cdrom );

    return status==ERROR_SUCCESS;
}


/******************************Public*Routine******************************\
* SeekToTrackAndHold
*
* Seek to specified track and enter hold state.
*
* History:
* 18-11-93 - StephenE - Created
*
\**************************************************************************/
BOOL
SeekToTrackAndHold(
    int cdrom,
    int tindex
    )
{
    DWORD status;
    MCI_SEEK_PARMS sam;

    sam.dwTo = MCI_MAKE_MSF( TRACK_M(cdrom,tindex),
                             TRACK_S(cdrom,tindex),
                             TRACK_F(cdrom,tindex) );

    status = SeekCdrom( g_Devices[ cdrom ]->hCd, &sam );

    CheckStatus( "SeekToTrackAndHold", status, cdrom );

    return status == ERROR_SUCCESS;
}



/* -------------------------------------------------------------------------
**
** Low level routines
**
** -------------------------------------------------------------------------
*/


/******************************Public*Routine******************************\
* GetCdromTOC
*
* This routine will get the table of contents from
* a CDRom device.
*
*
* History:
* 18-11-93 - StephenE - Created
*
\**************************************************************************/
DWORD
GetCdromTOC(
    MCIDEVICEID DevHandle,
    PCDROM_TOC TocPtr
    )
{
    MCI_STATUS_PARMS mciStatus;
    long lAddress, lStartPos, lDiskLen;
    int i;
    DWORD dwRet;

#if DBG
    dprintf( "Reading TOC for drive %d", DevHandle );
#endif

    ZeroMemory( &mciStatus, sizeof(mciStatus) );
    mciStatus.dwItem = MCI_STATUS_NUMBER_OF_TRACKS;

    //
    // NOTE: none of the mciSendCommand calls below bother to check the
    //       return code.  This is asking for trouble... but if the
    //       commands fail we cannot do much about it.
    //
    dwRet = mciSendCommand( DevHandle, MCI_STATUS,
                    MCI_STATUS_ITEM, (DWORD)(LPVOID)&mciStatus);

    TocPtr->FirstTrack = 1;
    TocPtr->LastTrack = (UCHAR)mciStatus.dwReturn;

    mciStatus.dwItem = MCI_STATUS_POSITION;
    for ( i = 0; i < TocPtr->LastTrack; i++ ) {

        mciStatus.dwTrack = i + 1;
        dwRet = mciSendCommand( DevHandle, MCI_STATUS,
                        MCI_STATUS_ITEM | MCI_TRACK,
                        (DWORD)(LPVOID)&mciStatus);

        TocPtr->TrackData[i].TrackNumber = (UCHAR)(i + 1);
        lAddress = TocPtr->TrackData[i].Address = mciStatus.dwReturn;

        lStartPos = (MCI_MSF_MINUTE(lAddress) * FRAMES_PER_MINUTE) +
                    (MCI_MSF_SECOND(lAddress) * FRAMES_PER_SECOND) +
                    (MCI_MSF_FRAME( lAddress));

        TocPtr->TrackData[i].AddressF = lStartPos;

    }


    mciStatus.dwItem = MCI_STATUS_LENGTH;
    dwRet = mciSendCommand( DevHandle, MCI_STATUS,
                    MCI_STATUS_ITEM, (DWORD)(LPVOID)&mciStatus);

    /*
    ** Convert the absolute start address of the first track
    ** into Frames
    */
    lAddress  = TocPtr->TrackData[0].Address;
    lStartPos = TocPtr->TrackData[0].AddressF;

    /*
    ** Convert the total disk length into Frames
    */
    lAddress  = mciStatus.dwReturn;
    lDiskLen  = (MCI_MSF_MINUTE(lAddress) * FRAMES_PER_MINUTE) +
                (MCI_MSF_SECOND(lAddress) * FRAMES_PER_SECOND) +
                (MCI_MSF_FRAME(lAddress));

    /*
    ** Now, determine the absolute start position of the sentinel
    ** track.  That is, the special track that marks the end of the
    ** disk.
    */
    lAddress = lStartPos + lDiskLen;

    TocPtr->TrackData[i].TrackNumber = 0;
    TocPtr->TrackData[i].Address     =
        MCI_MAKE_MSF((lAddress / FRAMES_PER_MINUTE),
                     (lAddress % FRAMES_PER_MINUTE) / FRAMES_PER_SECOND,
                     (lAddress % FRAMES_PER_MINUTE) % FRAMES_PER_SECOND);

    return (TocPtr->LastTrack != 0) ? ERROR_SUCCESS : MCIERR_INTERNAL;
}




/******************************Public*Routine******************************\
* StopCdrom
*
* This routine will stop a CDRom device that is playing.
*
*
* History:
* 18-11-93 - StephenE - Created
*
\**************************************************************************/
DWORD
StopCdrom(
    MCIDEVICEID DevHandle
    )
{
    MCI_GENERIC_PARMS mciGen;

    ZeroMemory( &mciGen, sizeof(mciGen) );

    return mciSendCommand( DevHandle, MCI_STOP, 0L, (DWORD)(LPVOID)&mciGen );
}



/******************************Public*Routine******************************\
* PauseCdrom
*
* This routine will pause a CDRom device.
*
* History:
* 18-11-93 - StephenE - Created
*
\**************************************************************************/
DWORD
PauseCdrom(
    MCIDEVICEID DevHandle
    )
{
    MCI_GENERIC_PARMS mciGen;

    ZeroMemory( &mciGen, sizeof(mciGen) );

    return mciSendCommand( DevHandle, MCI_PAUSE, 0L, (DWORD)(LPVOID)&mciGen );
}


/******************************Public*Routine******************************\
* ResumeCdrom
*
* This routine will resume a paused CDRom device.
*
* History:
* 18-11-93 - StephenE - Created
*
\**************************************************************************/
DWORD
ResumeCdrom(
    MCIDEVICEID DevHandle,
    int cdrom
    )

{
    MCI_GENERIC_PARMS   mciGen;
    DWORD               dwRet;
    static int          fCanResume = -1;

    ZeroMemory( &mciGen, sizeof(mciGen) );

    switch (fCanResume) {

    case -1:
        dwRet = mciSendCommand( DevHandle, MCI_RESUME, MCI_TO, (DWORD)(LPVOID)&mciGen );

        fCanResume = (dwRet == MMSYSERR_NOERROR ? 1 : 0);

        if (0 == fCanResume) {
            dwRet = (PlayCurrTrack( cdrom ) ? MMSYSERR_NOERROR : MCIERR_HARDWARE);
        }
        break;

    case 0:
        dwRet = (PlayCurrTrack( cdrom ) ? MMSYSERR_NOERROR : MCIERR_HARDWARE);
        break;

    case 1:
        dwRet = mciSendCommand( DevHandle, MCI_RESUME, MCI_TO, (DWORD)(LPVOID)&mciGen );
        break;
    }

    return dwRet;
}



/******************************Public*Routine******************************\
* PlayCdrom
*
* This routine plays a CDRom device starting and ending at the MSF
* positions specified in the structure passed in.
*
* History:
* 18-11-93 - StephenE - Created
*
\**************************************************************************/
DWORD
PlayCdrom(
    MCIDEVICEID DevHandle,
    MCI_PLAY_PARMS *mciPlay
    )
{
    return mciSendCommand( DevHandle, MCI_PLAY,
                           MCI_FROM | MCI_TO, (DWORD)(LPVOID)mciPlay );
}

/******************************Public*Routine******************************\
* IsCdromTrackAudio
*
*
*
* History:
* dd-mm-94 - StephenE - Created
*
\**************************************************************************/
BOOL
IsCdromTrackAudio(
    CDHANDLE DevHandle,
    int iTrackNumber
    )
{
    MCI_STATUS_PARMS mciStatus;

    ZeroMemory( &mciStatus, sizeof(mciStatus) );
    mciStatus.dwItem = MCI_CDA_STATUS_TYPE_TRACK;
    mciStatus.dwTrack = iTrackNumber + 1;

    mciSendCommand( DevHandle, MCI_STATUS, MCI_STATUS_ITEM | MCI_TRACK,
                    (DWORD)(LPVOID)&mciStatus);

    return mciStatus.dwReturn == MCI_CDA_TRACK_AUDIO;
}


/******************************Public*Routine******************************\
* GetCdromCurrentPosition
*
* Gets the current ABSOLUTE position of the specified cdrom device.
*
* History:
* 18-11-93 - StephenE - Created
*
\**************************************************************************/
DWORD
GetCdromCurrentPosition(
    CDHANDLE DevHandle,
    DWORD *lpdwPosition
    )
{

    MCI_STATUS_PARMS mciStatus;
    DWORD            dwErr;

    ZeroMemory( &mciStatus, sizeof(mciStatus) );

    mciStatus.dwItem = MCI_STATUS_POSITION;
    dwErr = mciSendCommand( DevHandle, MCI_STATUS,
                            MCI_STATUS_ITEM, (DWORD)(LPVOID)&mciStatus );
    *lpdwPosition = mciStatus.dwReturn;

    return dwErr;
}



/******************************Public*Routine******************************\
* GetCdromMode
*
* Gets the current mode of the cdrom.
*
* History:
* 18-11-93 - StephenE - Created
*
\**************************************************************************/
DWORD
GetCdromMode(
    MCIDEVICEID DevHandle
    )
{

    MCI_STATUS_PARMS mciStatus;

    ZeroMemory( &mciStatus, sizeof(mciStatus) );

    mciStatus.dwItem = MCI_STATUS_MODE;
    mciSendCommand( DevHandle, MCI_STATUS,
                    MCI_STATUS_ITEM, (DWORD)(LPVOID)&mciStatus );
    return mciStatus.dwReturn;
}


/******************************Public*Routine******************************\
* GetCdromCurrentTrack
*
* Gets the current track of the cdrom.
*
* History:
* 18-11-93 - StephenE - Created
*
\**************************************************************************/
DWORD
GetCdromCurrentTrack(
    MCIDEVICEID DevHandle
    )
{

    MCI_STATUS_PARMS mciStatus;

    ZeroMemory( &mciStatus, sizeof(mciStatus) );

    mciStatus.dwItem = MCI_STATUS_CURRENT_TRACK;
    mciSendCommand( DevHandle, MCI_STATUS,
                    MCI_STATUS_ITEM, (DWORD)(LPVOID)&mciStatus );
    return mciStatus.dwReturn;
}


/******************************Public*Routine******************************\
* SeekCdrom
*
* This routine seek to an MSF address on the audio CD and enters
* a hold (paused) state.
*
*
* History:
* 18-11-93 - StephenE - Created
*
\**************************************************************************/
DWORD
SeekCdrom(
    MCIDEVICEID DevHandle,
    MCI_SEEK_PARMS *mciSeek
    )
{
    return mciSendCommand( DevHandle, MCI_SEEK,
                           MCI_TO, (DWORD)(LPVOID)mciSeek );
}



/******************************Public*Routine******************************\
* EjectCdrom
*
* This routine will eject a disc from a CDRom device or close the tray if
* it is open.
*
* History:
* 18-11-93 - StephenE - Created
*
\**************************************************************************/
DWORD
EjectCdrom(
    MCIDEVICEID DevHandle
    )
{
    MCI_SET_PARMS   mciSet;

    ZeroMemory( &mciSet, sizeof(mciSet) );

    if (GetCdromMode(DevHandle) == MCI_MODE_OPEN) {

        return mciSendCommand( DevHandle, MCI_SET,
                               MCI_SET_DOOR_CLOSED, (DWORD)(LPVOID)&mciSet );
    }
    else {

        return mciSendCommand( DevHandle, MCI_SET,
                               MCI_SET_DOOR_OPEN, (DWORD)(LPVOID)&mciSet );
    }
}



/******************************Public*Routine******************************\
* TestUnitReadyCdrom
*
* This routine will retrieve the status of the CDRom device.
*
* History:
* 18-11-93 - StephenE - Created
*
\**************************************************************************/
DWORD
TestUnitReadyCdrom(
    MCIDEVICEID DevHandle
    )
{
    MCI_STATUS_PARMS mciStatus;

    ZeroMemory( &mciStatus, sizeof(mciStatus) );

    mciStatus.dwItem = MCI_STATUS_MEDIA_PRESENT;
    if ( mciSendCommand( DevHandle, MCI_STATUS, MCI_STATUS_ITEM,
                         (DWORD)(LPVOID)&mciStatus ) == MMSYSERR_NOERROR ) {

        return mciStatus.dwReturn ? ERROR_SUCCESS : ERROR_NOT_READY;
    }

    return ERROR_NOT_READY;

}



/******************************Public*Routine******************************\
* StatusTrackPosCdrom
*
* This routine will retrieve the 
* status, current track, and current position of the CDRom device.
*
* History:
* 18-11-93 - StephenE - Created
*
\**************************************************************************/
DWORD
StatusTrackPosCdrom(
    MCIDEVICEID DevHandle,
    DWORD * pStatus,
    DWORD * pTrack,
    DWORD * pPos
    )
{
    DWORD dwErr;
    MCI_STATUS_PARMS mciStatus;
    PSTATUSTRACKPOS pSTP = NULL;
    STATUSTRACKPOS stp;

    ZeroMemory( &mciStatus, sizeof(mciStatus) );

    // Note:  This is a non-standard MCI call (I.E. HACK!)
    //        the only reason for this behavior is it reduces
    //        the number of IOCTL's per 1/2 second on the HeartBeat
    //        thread for updating the timer display from ~15 to only
    //        ~1 on average.   Resulting in a major reduction in
    //        system traffic on the SCSI or IDE bus.

    // Note:  we are passing down a structre to MCICDA containing
    //        the position, track, and status values which it will
    //        fill in for us and return.
    mciStatus.dwItem = MCI_STATUS_TRACK_POS;
    mciStatus.dwReturn = (DWORD)&stp;
    dwErr = mciSendCommand( DevHandle, MCI_STATUS, MCI_STATUS_ITEM,
                         (DWORD)(LPVOID)&mciStatus );
    if (dwErr == MMSYSERR_NOERROR)
    {
        pSTP = (PSTATUSTRACKPOS)mciStatus.dwReturn;
        if (pSTP)
        {
            if (pStatus)
                *pStatus = pSTP->dwStatus;
            if (pTrack)
                *pTrack = pSTP->dwTrack;
            if (pPos)
                *pPos = pSTP->dwDiscTime;

            pSTP = NULL;
        }
    }

    return dwErr;
} // End StatusTrackPosCdrom


#endif