Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1669 lines
41 KiB

/*******************************Module*Header*********************************\
* Module Name: mcicda.c
*
* Media Control Architecture Redbook CD Audio Driver
*
* Created: 4/25/90
* Author: DLL (DavidLe)
*
* History:
* DavidLe - Based on MCI Pioneer Videodisc Driver
* MikeRo 12/90 - 1/91
* RobinSp 10th March 1992 - Move to Windows NT
*
* Copyright (c) 1990-1999 Microsoft Corporation
*
\****************************************************************************/
#include <windows.h>
#include <mmsystem.h>
#include <mmddk.h>
#include "mcicda.h"
#include "cda.h"
#include "cdio.h"
#define CHECK_MSF
#define MCICDA_BAD_TIME 0xFFFFFFFF
HANDLE hInstance;
UINT_PTR wTimerID;
int nWaitingDrives;
DRIVEDATA DriveTable[MCIRBOOK_MAX_DRIVES];
// MBR This
void CALLBACK TimerProc (
HWND hwnd,
UINT uMessage,
UINT uTimer,
DWORD dwParam)
{
DID i;
int wStatus;
for (i = 0; i < MCIRBOOK_MAX_DRIVES; ++i) {
EnterCrit( CdInfo[i].DeviceCritSec );
if (DriveTable[i].bActiveTimer) {
// MBR can other conditions beside successful completion of the
// play cause the != DISC_PLAYING?
if ((wStatus = CDA_drive_status (i)) != DISC_PLAYING)
{
if (--nWaitingDrives <= 0)
KillTimer (NULL, uTimer);
DriveTable[i].dwPlayTo = MCICDA_BAD_TIME;
DriveTable[i].bActiveTimer = FALSE;
switch (wStatus)
{
case DISC_PLAYING:
case DISC_PAUSED:
case DISC_READY:
wStatus = MCI_NOTIFY_SUCCESSFUL;
break;
default:
wStatus = MCI_NOTIFY_FAILURE;
break;
}
mciDriverNotify (DriveTable[i].hCallback,
DriveTable[i].wDeviceID, wStatus);
}
}
LeaveCrit( CdInfo[i].DeviceCritSec );
}
}
/*****************************************************************************
@doc INTERNAL MCICDA
@api UINT | notify | This function handles the notify
for all mci commands.
@parm DID | didDrive | Drive identifier
@parm WORD | wDeviceID | Calling device ID
@parm BOOL | wStartTimer | A boolean indicating that a timer is to be
started
@parm UINT | wFlag | The flag to be passed by mciDriverNotify
@parm LPMCI_GENERIC_PARMS | lpParms | For direct callback
*****************************************************************************/
UINT
notify ( DID didDrive,
MCIDEVICEID wDeviceID,
BOOL wStartTimer,
UINT wFlag,
LPMCI_GENERIC_PARMS lpParms)
{
if (DriveTable[didDrive].bActiveTimer)
{
mciDriverNotify (DriveTable[didDrive].hCallback, wDeviceID,
MCI_NOTIFY_SUPERSEDED);
if (--nWaitingDrives <= 0)
KillTimer (NULL, wTimerID);
DriveTable[didDrive].bActiveTimer = FALSE;
}
if (!wStartTimer)
mciDriverNotify ((HWND)lpParms->dwCallback, wDeviceID,
wFlag);
else
{
if (!DriveTable[didDrive].bActiveTimer &&
nWaitingDrives++ == 0)
{
// MBR every 1/10 of a sec. Should this be a parameter?
wTimerID = SetTimer (NULL, 1, 100, (TIMERPROC)TimerProc);
if (wTimerID == 0)
return MCICDAERR_NO_TIMERS;
}
DriveTable[didDrive].wDeviceID = wDeviceID;
DriveTable[didDrive].bActiveTimer = TRUE;
DriveTable[didDrive].hCallback = (HANDLE)lpParms->dwCallback;
}
return 0;
}
/*****************************************************************************
@doc INTERNAL MCICDA
@api void | abort_notify |
@parm PINSTDATA | pInst | application instance data
@rdesc
@comm
*****************************************************************************/
void abort_notify (PINSTDATA pInst)
{
DID didDrive = pInst->uDevice;
if (DriveTable[didDrive].bActiveTimer)
{
mciDriverNotify (DriveTable[didDrive].hCallback,
pInst->uMCIDeviceID,
MCI_NOTIFY_ABORTED);
// Kill timer if appropriate
if (--nWaitingDrives == 0)
KillTimer (NULL, wTimerID);
DriveTable[didDrive].dwPlayTo = MCICDA_BAD_TIME;
DriveTable[didDrive].bActiveTimer = FALSE;
}
}
/*
Return TRUE if the drive is in a playable state
*/
UINT disc_ready (DID didDrive)
{
// The disk is ready if we can read its TOC (note the
// kernel driver works out if the TOC really needs reading
if (CDA_disc_ready(didDrive)) {
if (CDA_num_tracks(didDrive)) {
return TRUE;
} else {
CDA_reset_drive(didDrive);
return FALSE;
}
} else
return FALSE;
}
/*
* @func redbook | flip3 | Put minute/second/frame values in different order
*
* @parm redbook | rbIn | Current position as track|minute|second|frame
*
* @rdesc (redbook)0|frame|second|minute
*/
redbook flip3 (redbook rbIn)
{
return MAKERED(MCI_MSF_MINUTE(rbIn),
MCI_MSF_SECOND(rbIn),
MCI_MSF_FRAME(rbIn));
}
/*
* @func redbook | flip4 | Put track/minute/second/frame values in different order
*
* @parm redbook | rbIn | Current position as track|minute|second|frame
*
* @rdesc (redbook)frame|second|minute|track
*/
redbook flip4 (redbook rbIn)
{
redbook rbOut;
LPSTR lpOut = (LPSTR)&rbOut,
lpIn = (LPSTR)&rbIn;
lpOut[0] = lpIn[3];
lpOut[1] = lpIn[2];
lpOut[2] = lpIn[1];
lpOut[3] = lpIn[0];
return rbOut;
}
// MBR Return the absolute redbook time of track sTrack, rbTime into track
/*****************************************************************************
@doc INTERNAL MCICDA
@api redbook | track_time | Return the absolute redbook time of
track sTrack, rbTime into track
@parm DID | didDrive |
@parm int | sTrack |
@parm redbook | rbTime |
@rdesc
@comm
*****************************************************************************/
redbook track_time (DID didDrive, int sTrack, redbook rbTime)
{
redbook rbTemp;
rbTemp = CDA_track_start (didDrive, sTrack);
if (rbTemp == INVALID_TRACK)
return rbTemp;
return redadd (rbTime, rbTemp);
}
redbook miltored(DWORD dwMill)
{
unsigned char m, s, f;
long r1, r2;
r1 = dwMill % 60000;
m = (unsigned char) ((dwMill - r1) / 60000);
r2 = r1 % 1000;
s = (unsigned char) ((r1 - r2) / 1000);
f = (unsigned char) ((r2 * 75) / 1000);
return MAKERED(m, s, f);
}
DWORD redtomil(redbook rbRed)
{
// Adding an extra one ms to prevent rounding errors at start
return (DWORD)REDMINUTE(rbRed) * 60000 +
(DWORD)REDSECOND(rbRed) * 1000 +
((DWORD)REDFRAME(rbRed) * 1000) / 75 +
1;
}
#ifdef AUDIOPHILE
DWORD NEAR PASCAL mcSeek(
PINSTDATA pInst,
DWORD dwFlags,
LPMCI_SEEK_PARMS lpSeek );
DWORD NEAR PASCAL GetAudioPhileInfo(LPCTSTR lpCDAFileName)
{
OFSTRUCT of;
RIFFCDA cda;
HFILE hf;
//
// open the file and read the CDA info.
//
if ((hf = _lopen (lpCDAFileName)) == HFILE_ERROR)
return 0;
_lread(hf, &cda, sizeof(cda));
_lclose(hf);
if (cda.dwRIFF != RIFF_RIFF || cda.dwCDDA != RIFF_CDDA)
{
return 0;
}
return MCI_MAKE_TMSF(cda.wTrack,0,0,0);
}
#endif
DWORD mcOpen (
PINSTDATA pInst,
DWORD dwFlags,
LPMCI_OPEN_PARMS lpOpen)
{
DID didDrive = (DID)pInst->uDevice;
DID didOld = (DID)pInst->uDevice;
UCHAR Volume;
DWORD dwTempVol;
int nUseCount;
/* Instance Initialization */
pInst->dwTimeFormat = MCI_FORMAT_MSF;
/* If an ELEMENT_ID is specified, this could be a drive letter */
if (dwFlags & (MCI_OPEN_ELEMENT | MCI_OPEN_ELEMENT_ID))
{
if ((dwFlags & (MCI_OPEN_ELEMENT | MCI_OPEN_ELEMENT_ID)) == (MCI_OPEN_ELEMENT | MCI_OPEN_ELEMENT_ID))
{
dprintf2(("mcOpen, (%08lX), Flags not compatible", (DWORD)didDrive));
return MCIERR_FLAGS_NOT_COMPATIBLE;
}
//
// Find the device corresponding to this name
//
if (COMMAND_SUCCESSFUL !=
CDA_get_drive(lpOpen->lpstrElementName, &didDrive))
{
dprintf2(("mcOpen, (%08lX), Failed to get corresponding device", (DWORD)didDrive));
return MCIERR_INVALID_FILE;
}
dprintf2(("mcOpen, changing from drive (%08lx) to drive (%08lX)", (DWORD)(pInst->uDevice), (DWORD)didDrive));
pInst->uDevice = didDrive;
}
/* Device Initialization */
nUseCount = DriveTable[didDrive].nUseCount;
if (nUseCount > 0)
{
// This drive is already open as another MCI device
if (dwFlags & MCI_OPEN_SHAREABLE &&
DriveTable[didDrive].bShareable)
{
// Shareable was specified so just increment the use count
nUseCount++;
dprintf2(("mcOpen, drive (%08lx), Incrementing UseCount, now = %ld",
(DWORD)didDrive, (DWORD)nUseCount));
}
else
{
dprintf2(("mcOpen, drive (%08lx), tryed to share without specifing MCI_OPEN_SHAREABLE",
(DWORD)didDrive));
return MCIERR_MUST_USE_SHAREABLE;
}
}
else
{
nUseCount = 1;
}
if (!CDA_open(didDrive))
{
dprintf2(("mcOpen, drive (%08lx), failed to open, UseCount = %ld",
(DWORD)didDrive, (DWORD)nUseCount));
return MCIERR_DEVICE_OPEN;
}
//
// Don't call disc_ready here because it will read the table of
// contents and on some drivers this will terminate any play
// unnecessarily
//
if (CDA_drive_status (didDrive) == DISC_PLAYING)
DriveTable[didDrive].bDiscPlayed = TRUE;
else
DriveTable[didDrive].bDiscPlayed = FALSE;
DriveTable[didDrive].bActiveTimer = FALSE;
DriveTable[didDrive].dwPlayTo = MCICDA_BAD_TIME;
DriveTable[didDrive].bShareable = (dwFlags & MCI_OPEN_SHAREABLE) != 0;
DriveTable[didDrive].nUseCount = nUseCount;
dprintf2(("mcOpen, drive (%08lx), Setting UseCount = %ld",
(DWORD)didDrive, (DWORD)nUseCount));
//dstewart: fix for when vol in registry is > 8 bits
dwTempVol = CDAudio_GetUnitVolume(didDrive);
if (dwTempVol > 0xFF)
{
dwTempVol = 0xFF;
}
Volume = (UCHAR)dwTempVol;
CDA_set_audio_volume_all (didDrive, Volume);
#ifdef AUDIOPHILE
/*
* AudioPhile track information handler.
*
* The new CDROM file system for Windows 4.0 produces files that describe
* CDAudio tracks. If a user wants to play a track, she should be able
* to double click on the track. So, we add open element support here
* and add an mplayer association s.t. the file may be read and the disc
* played back. We need to reject the Phile if a CDROM of this ID can't
* be found. A message box should be displayed if the disc is incorrect.
* Repercussions of this feature are that we need to simulate a disc in
* a data structure.
*/
if (dwFlags & (MCI_OPEN_ELEMENT | MCI_OPEN_ELEMENT_ID))
{
MCI_SEEK_PARMS Seek;
pInst->dwTimeFormat = MCI_FORMAT_TMSF;
Seek.dwTo = GetAudioPhileInfo(lpOpen->lpstrElementName);
if (Seek.dwTo != 0L)
mcSeek(pInst, MCI_TO, (LPMCI_SEEK_PARMS)&Seek);
}
#endif
return 0;
}
#define MSF_BITS ((redbook) 0x00FFFFFF)
/*****************************************************************************
@doc INTERNAL MCICDA
@api redbook | convert_time | Take a DWORD time value and
convert from current time format into redbook.
@parm PINSTDATA | pInst | Pointer to application instance data
@parm DWORD | dwTimeIn |
@rdesc Return MCICDA_BAD_TIME if out of range.
@comm
*****************************************************************************/
redbook convert_time(
PINSTDATA pInst,
DWORD dwTimeIn )
{
DID didDrive = (DID)pInst->uDevice;
redbook rbTime;
short nTrack;
switch (pInst->dwTimeFormat)
{
case MCI_FORMAT_MILLISECONDS:
rbTime = miltored (dwTimeIn);
return rbTime;
case MCI_FORMAT_MSF:
dprintf3(("Time IN: %lu",dwTimeIn));
rbTime = flip3 (dwTimeIn);
dprintf3(("Time OUT: %d:%d:%d:%d", REDTRACK(rbTime), REDMINUTE(rbTime),REDSECOND(rbTime), REDFRAME(rbTime)));
break;
case MCI_FORMAT_TMSF:
nTrack = (short)(dwTimeIn & 0xFF);
if (nTrack > CDA_num_tracks( didDrive))
return MCICDA_BAD_TIME;
rbTime = track_time (didDrive, nTrack, flip3 (dwTimeIn >> 8));
if (rbTime == INVALID_TRACK)
return MCICDA_BAD_TIME;
break;
}
#ifdef CHECK_MSF
if ((REDFRAME(rbTime)>74) || (REDMINUTE(rbTime)>99) ||
(REDSECOND(rbTime)>59))
return MCICDA_BAD_TIME;
#endif
return rbTime;
}
/*****************************************************************************
@doc INTERNAL MCICDA
@api DWORD | seek | Process the MCI_SEEK command
@parm PINSTDATA | pInst | Pointer to application instance data
@parm DWORD | dwFlags |
@parm LPMCI_SEEK_PARMS | lpSeek |
@rdesc
@comm
*****************************************************************************/
DWORD mcSeek(
PINSTDATA pInst,
DWORD dwFlags,
LPMCI_SEEK_PARMS lpSeek )
{
DID didDrive = pInst->uDevice;
redbook rbTime = 0;
LPSTR lpTime = (LPSTR) &rbTime;
redbook rbStart;
redbook rbEnd;
BOOL fForceAudio;
dprintf3(("Seek, drive %d TO %8x", didDrive, lpSeek->dwTo));
abort_notify (pInst);
if ( !disc_ready (didDrive))
return MCIERR_HARDWARE;
if ((rbStart = CDA_track_start( didDrive, 1)) == INVALID_TRACK)
return MCIERR_HARDWARE;
rbStart &= MSF_BITS;
if ((rbEnd = CDA_disc_end( didDrive)) == INVALID_TRACK)
return MCIERR_HARDWARE;
rbEnd &= MSF_BITS;
// Check only one positioning command is given.
// First isolate the bits we want
// Then subtract 1. This removes the least significant bit, and puts
// ones in any lower bit positions. Leaves other bits untouched.
// If any bits are left on, more than one of TO, START or END was given
// Note: if NO flags are given this ends up ANDING 0 with -1 == 0
// which is OK.
#define SEEK_BITS (dwFlags & (MCI_TO | MCI_SEEK_TO_START | MCI_SEEK_TO_END))
#define CHECK_FLAGS (((SEEK_BITS)-1) & (SEEK_BITS))
if (CHECK_FLAGS) {
return MCIERR_FLAGS_NOT_COMPATIBLE;
}
if (dwFlags & MCI_TO)
{
// When the above test is reviewed and proven to pick out
// incompatible flags delete these lines.
// Note: we detect more incompatible cases than Win 16 - this
// is deliberate and fixes a Win 16 bug. CurtisP has seen this code.
//if (dwFlags & (MCI_SEEK_TO_START | MCI_SEEK_TO_END))
// return MCIERR_FLAGS_NOT_COMPATIBLE;
if ((rbTime = convert_time (pInst, lpSeek->dwTo)) == MCICDA_BAD_TIME)
return MCIERR_OUTOFRANGE;
// if seek pos is before valid audio return an error
if ( rbTime < rbStart)
return MCIERR_OUTOFRANGE;
// similarly, if seek pos is past end of disk return an error
else if (rbTime > rbEnd)
return MCIERR_OUTOFRANGE;
fForceAudio = FALSE;
} else if (dwFlags & MCI_SEEK_TO_START) {
rbTime = rbStart;
fForceAudio = TRUE; // We want the first audio track
} else if (dwFlags & MCI_SEEK_TO_END) {
rbTime = rbEnd;
fForceAudio = TRUE; // We want the last audio track
} else {
return MCIERR_MISSING_PARAMETER;
}
// send seek command to driver
if (CDA_seek_audio (didDrive, rbTime, fForceAudio) != COMMAND_SUCCESSFUL)
return MCIERR_HARDWARE;
if (CDA_pause_audio (didDrive) != COMMAND_SUCCESSFUL)
return MCIERR_HARDWARE;
DriveTable[didDrive].bDiscPlayed = TRUE;
return 0;
}
/*****************************************************************************
@doc INTERNAL MCICDA
@api BOOL | wait |
@parm DWORD | dwFlags |
@parm PINSTDATA | pInst | Pointer to application instance data
@rdesc Return TRUE if BREAK was pressed
@comm If the wait flag is set then wait until the device is no longer playing
*****************************************************************************/
BOOL wait (
DWORD dwFlags,
PINSTDATA pInst )
{
DID didDrive = pInst->uDevice;
MCIDEVICEID wDeviceID = pInst->uMCIDeviceID;
if (dwFlags & MCI_WAIT)
{
//Note: jyg This is interesting. I've noticed that some drives do give
// sporadic errors. Thus this retry stuff. 5X is enough to
// determine true failure.
int status, retry=0;
retry:
while ((status = CDA_drive_status (didDrive)) == DISC_PLAYING) {
LeaveCrit( CdInfo[didDrive].DeviceCritSec );
if (mciDriverYield (wDeviceID) != 0) {
EnterCrit( CdInfo[didDrive].DeviceCritSec );
return TRUE;
}
Sleep(50);
EnterCrit( CdInfo[didDrive].DeviceCritSec );
}
if (status == DISC_NOT_READY && retry++ < 5)
goto retry;
}
return FALSE;
}
/*****************************************************************************
@doc INTERNAL MCICDA
@api DWORD | play | Process the MCI_PLAY command
@parm PINSTDATA | pInst | Pointer to application instance data
@parm DWORD | dwFlags |
@parm LPMCI_PLAY_PARMS | lpPlay |
@parm BOOL FAR * | bBreak |
@rdesc
@comm
*****************************************************************************/
DWORD mcPlay(
PINSTDATA pInst,
DWORD dwFlags,
LPMCI_PLAY_PARMS lpPlay,
BOOL FAR * bBreak )
{
DID didDrive = pInst->uDevice;
redbook rbFrom, rbTo;
redbook dStart, dEnd;
BOOL bAbort = FALSE;
if (!disc_ready (didDrive)) // MBR could return more specific error
return MCIERR_HARDWARE;
// do we have both from and to parameters?
// If so then do a "seek" instead
if ((dwFlags & (MCI_FROM | MCI_TO)) == (MCI_FROM | MCI_TO))
if (lpPlay->dwTo == lpPlay->dwFrom)
// Convert a 'play x to x' into 'seek to x'
{
MCI_SEEK_PARMS Seek;
Seek.dwTo = lpPlay->dwFrom;
Seek.dwCallback = lpPlay->dwCallback;
return mcSeek(pInst, dwFlags, (LPMCI_SEEK_PARMS)&Seek);
}
// mask is to ignore track number in the upper byte
// which appears at some times
dStart = CDA_track_start( didDrive, 1) & MSF_BITS;
dEnd = CDA_disc_end( didDrive) & MSF_BITS;
if (dwFlags & MCI_TO)
{
if ((rbTo = convert_time (pInst, lpPlay->dwTo))
== MCICDA_BAD_TIME)
return MCIERR_OUTOFRANGE;
} else
rbTo = dEnd;
if (dwFlags & MCI_FROM)
{
if ((rbFrom = convert_time (pInst, lpPlay->dwFrom))
== MCICDA_BAD_TIME)
return MCIERR_OUTOFRANGE;
} else // no FROM
{
// If the disk has never played the current position is indeterminate so
// we must start from the beginning
if (!DriveTable[didDrive].bDiscPlayed)
{
// Initial position is at the beginning of track 1
rbFrom = track_time (didDrive, (int)1, (redbook)0);
if (rbFrom == INVALID_TRACK)
return MCIERR_HARDWARE;
} else if ((!(dwFlags & MCI_TO) ||
rbTo == DriveTable[didDrive].dwPlayTo) &&
CDA_drive_status (didDrive) == DISC_PLAYING)
// Disc is playing and no (or redundent) "to" position was
// specified so do nothng
goto exit_fn;
else
{
CDA_time_info (didDrive, NULL, &rbFrom);
// Current position in track 0 means play starting from track 1
if (REDTRACK(rbFrom) == 0)
{
rbFrom = track_time (didDrive, (int)1, (redbook)0);
if (rbFrom == INVALID_TRACK)
return MCIERR_HARDWARE;
}
rbFrom &= MSF_BITS;
// Some drives (SONY) will return an illegal position
if (rbFrom < dStart)
rbFrom = dStart;
}
}
rbFrom &= MSF_BITS;
rbTo &= MSF_BITS;
if (dwFlags & MCI_TO)
{
if (rbFrom > rbTo || rbTo > dEnd)
return MCIERR_OUTOFRANGE;
} else {
rbTo = dEnd;
}
// if From is before audio start return an error
if ( rbFrom < dStart)
return MCIERR_OUTOFRANGE;
if (dwFlags & MCI_FROM) {
// Try a seek - don't care if it works (!)
CDA_seek_audio(didDrive, rbFrom, TRUE);
}
// send play command to driver
if (CDA_play_audio(didDrive, rbFrom, rbTo)
!= COMMAND_SUCCESSFUL)
return MCIERR_HARDWARE; // values should be vaild so err is hard
DriveTable[didDrive].bDiscPlayed = TRUE;
exit_fn:;
// Abort if either from or (a new) to position is specified
if (dwFlags & MCI_FROM || rbTo != DriveTable[didDrive].dwPlayTo)
abort_notify (pInst);
*bBreak = wait(dwFlags, pInst);
DriveTable[didDrive].dwPlayTo = rbTo;
return 0;
}
/*****************************************************************************
@doc INTERNAL MCICDA
@api DWORD | mcGetDevCaps | Process the MCI_GETDEVCAPS command
@parm PINSTDATA | pInst | Pointer to application data instance
@parm DWORD | dwFlags |
@parm LPMCI_GETDEVCAPS_PARMS | lpCaps |
@rdesc
@comm
*****************************************************************************/
DWORD mcGetDevCaps(
PINSTDATA pInst,
DWORD dwFlags,
LPMCI_GETDEVCAPS_PARMS lpCaps )
{
DWORD dwReturn = 0;
if (!(dwFlags & MCI_GETDEVCAPS_ITEM))
return MCIERR_MISSING_PARAMETER;
switch (lpCaps->dwItem)
{
case MCI_GETDEVCAPS_CAN_RECORD:
case MCI_GETDEVCAPS_CAN_SAVE:
case MCI_GETDEVCAPS_HAS_VIDEO:
case MCI_GETDEVCAPS_USES_FILES:
case MCI_GETDEVCAPS_COMPOUND_DEVICE:
lpCaps->dwReturn = MAKEMCIRESOURCE(FALSE, MCI_FALSE);
dwReturn = MCI_RESOURCE_RETURNED;
break;
case MCI_GETDEVCAPS_HAS_AUDIO:
case MCI_GETDEVCAPS_CAN_EJECT: // mbr - bogus...
case MCI_GETDEVCAPS_CAN_PLAY:
lpCaps->dwReturn = MAKEMCIRESOURCE(TRUE, MCI_TRUE);
dwReturn = MCI_RESOURCE_RETURNED;
break;
case MCI_GETDEVCAPS_DEVICE_TYPE:
lpCaps->dwReturn = MAKEMCIRESOURCE(MCI_DEVTYPE_CD_AUDIO,
MCI_DEVTYPE_CD_AUDIO);
dwReturn = MCI_RESOURCE_RETURNED;
break;
default:
dwReturn = MCIERR_UNSUPPORTED_FUNCTION;
break;
}
return dwReturn;
}
/*****************************************************************************
@doc INTERNAL MCICDA
@api DWORD | mcStatus | Process the MCI_STATUS command
@parm PINSTDATA | pInst | Pointer to application instance data
@parm DWORD | dwFlags |
@parm LPMCI_STATUS_PARMS | lpStatus |
@rdesc
@comm
*****************************************************************************/
DWORD mcStatus (
PINSTDATA pInst,
DWORD dwFlags,
LPMCI_STATUS_PARMS lpStatus)
{
DID didDrive = (DID)pInst->uDevice;
DWORD dwReturn = 0;
if (!(dwFlags & MCI_STATUS_ITEM))
return MCIERR_MISSING_PARAMETER;
switch (lpStatus->dwItem)
{
int n;
case MCI_STATUS_MEDIA_PRESENT:
if (CDA_traystate(didDrive) != TRAY_OPEN &&
CDA_disc_ready(didDrive))
lpStatus->dwReturn = MAKEMCIRESOURCE(TRUE, MCI_TRUE);
else
lpStatus->dwReturn = MAKEMCIRESOURCE(FALSE, MCI_FALSE);
dwReturn = MCI_RESOURCE_RETURNED;
break;
case MCI_STATUS_READY:
switch (CDA_drive_status (didDrive))
{
case DISC_PLAYING:
case DISC_PAUSED:
case DISC_READY:
lpStatus->dwReturn = MAKEMCIRESOURCE(TRUE, MCI_TRUE);
break;
default:
lpStatus->dwReturn = MAKEMCIRESOURCE(FALSE, MCI_FALSE);
break;
}
dwReturn = MCI_RESOURCE_RETURNED;
break;
case MCI_STATUS_MODE:
{
switch (CDA_drive_status (didDrive))
{
case DISC_PLAYING:
n = MCI_MODE_PLAY;
break;
case DISC_PAUSED:
n = MCI_MODE_STOP; // HACK HACK!
break;
case DISC_READY:
n = MCI_MODE_STOP;
break;
default:
if (CDA_traystate (didDrive) == TRAY_OPEN)
n = MCI_MODE_OPEN;
else
n = MCI_MODE_NOT_READY;
break;
}
lpStatus->dwReturn = (DWORD)MAKEMCIRESOURCE(n, n);
dwReturn = MCI_RESOURCE_RETURNED;
break;
}
case MCI_STATUS_TIME_FORMAT:
n = (WORD)pInst->dwTimeFormat;
lpStatus->dwReturn = (DWORD)MAKEMCIRESOURCE(n,n + MCI_FORMAT_RETURN_BASE);
dwReturn = MCI_RESOURCE_RETURNED;
break;
case MCI_STATUS_POSITION:
{
redbook tracktime, disctime;
if (dwFlags & MCI_TRACK)
{
int n;
if (dwFlags & MCI_STATUS_START)
return MCIERR_FLAGS_NOT_COMPATIBLE;
if (!disc_ready(didDrive))
return MCIERR_HARDWARE;
if ((n = CDA_num_tracks (didDrive)) == 0)
return MCIERR_HARDWARE;
if (!lpStatus->dwTrack || lpStatus->dwTrack > (DWORD)n)
return MCIERR_OUTOFRANGE;
lpStatus->dwReturn =
CDA_track_start (didDrive, (short)lpStatus->dwTrack);
switch (pInst->dwTimeFormat)
{
case MCI_FORMAT_MILLISECONDS:
lpStatus->dwReturn = redtomil ((redbook)lpStatus->dwReturn);
dwReturn = 0;
break;
case MCI_FORMAT_TMSF:
lpStatus->dwReturn = lpStatus->dwTrack;
dwReturn = MCI_COLONIZED4_RETURN;
break;
case MCI_FORMAT_MSF:
lpStatus->dwReturn = flip3 ((redbook)lpStatus->dwReturn);
dwReturn = MCI_COLONIZED3_RETURN;
break;
}
} else if (dwFlags & MCI_STATUS_START)
{
if (!disc_ready(didDrive))
return MCIERR_HARDWARE;
if ((n = CDA_num_tracks (didDrive)) == 0)
return MCIERR_HARDWARE;
lpStatus->dwReturn =
CDA_track_start (didDrive, 1);
switch (pInst->dwTimeFormat)
{
case MCI_FORMAT_MILLISECONDS:
lpStatus->dwReturn = redtomil ((redbook)lpStatus->dwReturn);
dwReturn = 0;
break;
case MCI_FORMAT_TMSF:
lpStatus->dwReturn = 1;
dwReturn = MCI_COLONIZED4_RETURN;
break;
case MCI_FORMAT_MSF:
lpStatus->dwReturn = flip3 ((redbook)lpStatus->dwReturn);
dwReturn = MCI_COLONIZED3_RETURN;
break;
}
} else
{
if (!DriveTable[didDrive].bDiscPlayed)
{
tracktime = REDTH(0, 1);
if (!disc_ready(didDrive))
return MCIERR_HARDWARE;
disctime = CDA_track_start( didDrive, 1);
} else if (CDA_time_info(didDrive, &tracktime, &disctime) !=
COMMAND_SUCCESSFUL)
return MCIERR_HARDWARE;
if (REDTRACK(tracktime) == 0)
{
tracktime = (redbook)0;
disctime = (redbook)0;
}
switch (pInst->dwTimeFormat)
{
case MCI_FORMAT_MILLISECONDS:
lpStatus->dwReturn = redtomil (disctime);
dwReturn = 0;
break;
case MCI_FORMAT_MSF:
lpStatus->dwReturn = flip3(disctime);
dwReturn = MCI_COLONIZED3_RETURN;
break;
case MCI_FORMAT_TMSF:
lpStatus->dwReturn = flip4 (tracktime);
dwReturn = MCI_COLONIZED4_RETURN;
break;
}
}
break;
}
case MCI_STATUS_LENGTH:
if (!disc_ready(didDrive))
return MCIERR_HARDWARE;
if (dwFlags & MCI_TRACK)
{
lpStatus->dwReturn =
CDA_track_length (didDrive, (short)lpStatus->dwTrack);
if (lpStatus->dwReturn == INVALID_TRACK)
return MCIERR_OUTOFRANGE;
switch (pInst->dwTimeFormat)
{
case MCI_FORMAT_MILLISECONDS:
lpStatus->dwReturn = redtomil ((redbook)lpStatus->dwReturn);
dwReturn = 0;
break;
case MCI_FORMAT_MSF:
case MCI_FORMAT_TMSF:
lpStatus->dwReturn = flip3((redbook)lpStatus->dwReturn);
dwReturn = MCI_COLONIZED3_RETURN;
break;
}
} else
{
// Subtract one to match SEEK_TO_END
lpStatus->dwReturn = CDA_disc_length (didDrive);
switch (pInst->dwTimeFormat)
{
case MCI_FORMAT_MILLISECONDS:
lpStatus->dwReturn = redtomil ((redbook)lpStatus->dwReturn);
dwReturn = 0;
break;
case MCI_FORMAT_MSF:
case MCI_FORMAT_TMSF:
lpStatus->dwReturn = flip3((redbook)lpStatus->dwReturn);
dwReturn = MCI_COLONIZED3_RETURN;
break;
}
}
break;
case MCI_STATUS_NUMBER_OF_TRACKS:
if (!disc_ready(didDrive))
return MCIERR_HARDWARE;
lpStatus->dwReturn = (DWORD)CDA_num_tracks (didDrive);
dwReturn = 0;
break;
case MCI_STATUS_CURRENT_TRACK:
{
redbook tracktime;
if (!DriveTable[didDrive].bDiscPlayed)
lpStatus->dwReturn = 1;
else
{
if (CDA_time_info(didDrive, &tracktime, NULL) !=
COMMAND_SUCCESSFUL)
return MCIERR_HARDWARE;
lpStatus->dwReturn = REDTRACK (tracktime);
}
break;
}
case MCI_CDA_STATUS_TYPE_TRACK:
if (!disc_ready(didDrive))
return MCIERR_HARDWARE;
if (dwFlags & MCI_TRACK)
{
DWORD dwTmp;
dwTmp = CDA_track_type (didDrive, (int)lpStatus->dwTrack);
switch (dwTmp)
{
case INVALID_TRACK:
return MCIERR_OUTOFRANGE;
case MCI_CDA_TRACK_AUDIO:
lpStatus->dwReturn = (DWORD)MAKEMCIRESOURCE(dwTmp,
MCI_CDA_AUDIO_S);
break;
case MCI_CDA_TRACK_OTHER:
lpStatus->dwReturn = (DWORD)MAKEMCIRESOURCE(dwTmp,
MCI_CDA_OTHER_S);
break;
}
dwReturn = MCI_RESOURCE_RETURNED | MCI_RESOURCE_DRIVER;
}
break;
case MCI_STATUS_TRACK_POS:
{
// Note: This code is a major hack that does an end-run around
// past the normal MCI functionality. The only reason it
// is here is because the new functionality replaces 3 MCI
// calls in CDPLAYER to get the position,track, and status
// with this one call.
// This means what used to take ~15 IOCTL's to accomplish
// now takes ~1 IOCTL. Since CDPLAYER generates one of
// these messages every 1/2 second for updating it's timer
// display. This is a major reduction in system traffic
// for SCSI and IDE CD-Roms drivers.
DWORD status;
DWORD mciStatus;
redbook tracktime, disctime;
int rc;
STATUSTRACKPOS stp;
PSTATUSTRACKPOS pSTP;
if (!DriveTable[didDrive].bDiscPlayed)
{
tracktime = REDTH(0, 1);
if (!disc_ready(didDrive))
{
dprintf(("mcStatus (%08LX), MCI_STATUS_TRACK_POS, Disc Not Ready", (DWORD)didDrive));
return MCIERR_HARDWARE;
}
disctime = CDA_track_start( didDrive, 1);
status = CDA_drive_status (didDrive);
switch (status)
{
case DISC_PLAYING:
mciStatus = MCI_MODE_PLAY;
break;
case DISC_PAUSED:
mciStatus = MCI_MODE_STOP; // HACK HACK!
break;
case DISC_READY:
mciStatus = MCI_MODE_STOP;
break;
default:
if (CDA_traystate (didDrive) == TRAY_OPEN)
mciStatus = MCI_MODE_OPEN;
else
mciStatus = MCI_MODE_NOT_READY;
break;
}
}
else
{
rc = CDA_status_track_pos (didDrive, &status, &tracktime, &disctime);
if (rc != COMMAND_SUCCESSFUL)
{
dprintf(("mcStatus (%08LX), MCI_STATUS_TRACK_POS, CDA_status_track_pos failed", (DWORD)didDrive));
return MCIERR_HARDWARE;
}
if (REDTRACK(tracktime) == 0)
{
tracktime = (redbook)0;
disctime = (redbook)0;
}
switch (status)
{
case DISC_PLAYING:
mciStatus = MCI_MODE_PLAY;
break;
case DISC_PAUSED:
mciStatus = MCI_MODE_STOP; // HACK HACK!
break;
case DISC_READY:
mciStatus = MCI_MODE_STOP;
break;
case DISC_NOT_IN_CDROM:
mciStatus = MCI_MODE_OPEN;
break;
default:
mciStatus = MCI_MODE_NOT_READY;
break;
}
}
stp.dwStatus = mciStatus;
stp.dwTrack = REDTRACK (tracktime);
switch (pInst->dwTimeFormat)
{
case MCI_FORMAT_MILLISECONDS:
stp.dwDiscTime = redtomil ((redbook)disctime);
dwReturn = 0;
break;
case MCI_FORMAT_MSF:
stp.dwDiscTime = flip3(disctime);
dwReturn = MCI_COLONIZED3_RETURN;
break;
case MCI_FORMAT_TMSF:
stp.dwDiscTime = flip4 (tracktime);
dwReturn = MCI_COLONIZED4_RETURN;
break;
}
pSTP = (PSTATUSTRACKPOS)lpStatus->dwReturn;
if (pSTP == NULL)
return MCIERR_MISSING_PARAMETER;
pSTP->dwStatus = stp.dwStatus;
pSTP->dwTrack = stp.dwTrack;
pSTP->dwDiscTime = stp.dwDiscTime;
break;
}
default:
dwReturn = MCIERR_UNSUPPORTED_FUNCTION;
break;
}
return dwReturn;
}
/*****************************************************************************
@doc INTERNAL MCICDA
@api DWORD | mcClose | Process the MCI_CLOSE command
@parm PINSTDATA | pInst | Pointer to application data instance
@rdesc
@comm
*****************************************************************************/
DWORD mcClose(
PINSTDATA pInst)
{
DID didDrive = pInst->uDevice;
MCIDEVICEID wDeviceID = pInst->uMCIDeviceID;
int nUseCount;
if (!pInst)
{
dprintf2(("mcClose, passed in NULL pointer"));
}
if (DriveTable[didDrive].nUseCount == 0)
{
dprintf2(("mcClose (%08lX), nUseCount already ZERO!!!", (DWORD)didDrive));
}
else if (--DriveTable[didDrive].nUseCount == 0)
{
dprintf2(("mcClose, Actually closing device (%08lX)", (DWORD)didDrive));
CDA_close(didDrive);
CDA_terminate_audio ();
}
else
{
dprintf2(("mcClose, Enter, device (%08lx), decremented useCount = %ld",
(DWORD)didDrive, DriveTable[didDrive].nUseCount));
// Note: Having this here prevents a mis-count problem
CDA_close(didDrive);
}
// Abort any notify if the use count is 0 or if the notify is for the device
// being closed
if ((DriveTable[didDrive].nUseCount == 0) ||
(wDeviceID == DriveTable[didDrive].wDeviceID))
abort_notify (pInst);
mciSetDriverData(pInst->uMCIDeviceID, 0L);
LocalFree((HLOCAL)pInst);
dprintf2(("mcClose, Exit, device (%08lx), useCount = %ld",
(DWORD)didDrive, DriveTable[didDrive].nUseCount));
return 0;
}
/*****************************************************************************
@doc INTERNAL MCICDA
@api DWORD | mcStop | Process the MCI_STOP command
@parm PINSTDATA | pInst | Pointer to application data instance
@parm DWORD | dwFlags |
@rdesc
*****************************************************************************/
DWORD mcStop(
PINSTDATA pInst,
DWORD dwFlags,
LPMCI_GENERIC_PARMS lpGeneric)
{
DID didDrive = pInst->uDevice;
if (!disc_ready (didDrive))
return MCIERR_HARDWARE;
abort_notify (pInst);
if (CDA_stop_audio(didDrive) != COMMAND_SUCCESSFUL)
return MCIERR_HARDWARE;
return 0;
}
/*****************************************************************************
@doc INTERNAL MCICDA
@api DWORD | mcPause | Process the MCI_PAUSE command
@parm PINSTDATA | pInst | Pointer to application data instance
@parm DWORD | dwFlags |
@rdesc
*****************************************************************************/
DWORD mcPause(
PINSTDATA pInst,
DWORD dwFlags,
LPMCI_GENERIC_PARMS lpGeneric)
{
DID didDrive = pInst->uDevice;
if (!disc_ready (didDrive))
return MCIERR_HARDWARE;
abort_notify (pInst);
if (CDA_pause_audio(didDrive) != COMMAND_SUCCESSFUL)
return MCIERR_HARDWARE;
return 0;
}
/*****************************************************************************
@doc INTERNAL MCICDA
@api DWORD | mcResume | Process the MCI_PAUSE command
@parm PINSTDATA | pInst | Pointer to application data instance
@parm DWORD | dwFlags |
@rdesc
*****************************************************************************/
DWORD mcResume(
PINSTDATA pInst,
DWORD dwFlags,
LPMCI_GENERIC_PARMS lpGeneric)
{
DID didDrive = pInst->uDevice;
if (!disc_ready (didDrive))
return MCIERR_HARDWARE;
abort_notify (pInst);
if (CDA_resume_audio(didDrive) != COMMAND_SUCCESSFUL)
return MCIERR_HARDWARE;
return 0;
}
// MBR cda.c!SendDriverReq masks off the actual error bits and just
// leaves the upper bit set - this is ok for now. There exists
// no seperate "command is known but not supported" error at
// the driver level, so if the driver returns "unrecognized
// command", we return "unsupported function".
#define ERRQ(X) (((X)==0) ? MCIERR_UNSUPPORTED_FUNCTION : 0)
/*****************************************************************************
@doc INTERNAL MCICDA
@api DWORD | mcSet | Process the MCI_SET command
@parm DWORD | dwFlags |
@parm LPMCI_SET_PARMS | lpSet |
@rdesc
@comm
*****************************************************************************/
DWORD mcSet(
PINSTDATA pInst,
DWORD dwFlags,
LPMCI_SET_PARMS lpSet )
{
DID didDrive = pInst->uDevice;
UINT wErr = 0;
dwFlags &= ~(MCI_NOTIFY | MCI_WAIT);
if (!dwFlags)
return MCIERR_MISSING_PARAMETER;
if (dwFlags & MCI_SET_TIME_FORMAT)
{
DWORD wFormat = lpSet->dwTimeFormat;
switch (wFormat)
{
case MCI_FORMAT_MILLISECONDS:
case MCI_FORMAT_MSF:
case MCI_FORMAT_TMSF:
pInst->dwTimeFormat = wFormat;
break;
default:
wErr = MCIERR_BAD_TIME_FORMAT;
break;
}
}
if (!wErr && (dwFlags & MCI_SET_DOOR_OPEN))
{
abort_notify (pInst);
CDA_stop_audio (didDrive);
CDA_eject(didDrive);
DriveTable[didDrive].bDiscPlayed = FALSE;
}
if (!wErr && (dwFlags & MCI_SET_AUDIO))
{
UCHAR wVolume;
if (dwFlags & MCI_SET_ON && dwFlags & MCI_SET_OFF)
return MCIERR_FLAGS_NOT_COMPATIBLE;
if (dwFlags & MCI_SET_ON)
wVolume = 255;
else if (dwFlags & MCI_SET_OFF)
wVolume = 0;
else
return MCIERR_MISSING_PARAMETER;
switch (lpSet->dwAudio)
{
case MCI_SET_AUDIO_ALL:
if (CDA_set_audio_volume_all (didDrive, wVolume)
!= COMMAND_SUCCESSFUL)
wErr = MCIERR_HARDWARE;
break;
case MCI_SET_AUDIO_LEFT:
if (CDA_set_audio_volume (didDrive, 0, wVolume)
!= COMMAND_SUCCESSFUL)
wErr = MCIERR_HARDWARE;
break;
case MCI_SET_AUDIO_RIGHT:
if (CDA_set_audio_volume (didDrive, 1, wVolume)
!= COMMAND_SUCCESSFUL)
wErr = MCIERR_HARDWARE;
break;
}
}
if (!wErr && dwFlags & MCI_SET_DOOR_CLOSED)
CDA_closetray (didDrive);
return wErr;
}
/*****************************************************************************
@doc INTERNAL MCICDA
@api DWORD | mcInfo | Process the MCI_INFO command
@parm PINSTDATA | pInst | Pointer to application instance data
@parm DWORD | dwFlags |
@parm LPMCI_INFO_PARMS | lpInfo |
@rdesc
@comm
*****************************************************************************/
DWORD mcInfo (
PINSTDATA pInst,
DWORD dwFlags,
LPMCI_INFO_PARMS lpInfo )
{
DID didDrive = pInst->uDevice;
DWORD wReturnBufferLength;
wReturnBufferLength = LOWORD(lpInfo->dwRetSize);
if (!lpInfo->lpstrReturn || !wReturnBufferLength)
return MCIERR_PARAM_OVERFLOW;
if (dwFlags & MCI_INFO_PRODUCT)
{
*(lpInfo->lpstrReturn) = '\0';
lpInfo->dwRetSize = (DWORD)LoadString(hInstance, IDS_PRODUCTNAME, lpInfo->lpstrReturn, (int)wReturnBufferLength);
return 0;
}
else if (dwFlags & MCI_INFO_MEDIA_UPC)
{
unsigned char upc[16];
int i;
if (!disc_ready(didDrive))
return MCIERR_HARDWARE;
if (CDA_disc_upc(didDrive, lpInfo->lpstrReturn) != COMMAND_SUCCESSFUL)
return MCIERR_NO_IDENTITY;
return 0;
}
else if (dwFlags & MCI_INFO_MEDIA_IDENTITY)
{
DWORD dwId;
if (!disc_ready(didDrive))
return MCIERR_HARDWARE;
dwId = CDA_disc_id(didDrive);
if (dwId == (DWORD)-1L)
return MCIERR_HARDWARE;
wsprintf(lpInfo->lpstrReturn,TEXT("%lu"),dwId);
return 0;
} else
return MCIERR_MISSING_PARAMETER;
}
/*
* @doc INTERNAL MCIRBOOK
*
* @api DWORD | mciDriverEntry | Single entry point for MCI drivers
*
* @parm MCIDEVICEID | wDeviceID | The MCI device ID
*
* @parm UINT | message | The requested action to be performed.
*
* @parm LPARAM | lParam1 | Data for this message. Defined seperately for
* each message
*
* @parm LPARAM | lParam2 | Data for this message. Defined seperately for
* each message
*
* @rdesc Defined seperately for each message.
*
*/
DWORD CD_MCI_Handler (MCIDEVICEID wDeviceID,
UINT message,
DWORD_PTR lParam1,
DWORD_PTR lParam2)
{
DID didDrive;
LPMCI_GENERIC_PARMS lpGeneric = (LPMCI_GENERIC_PARMS)lParam2;
BOOL bDelayed = FALSE;
DWORD dwErr = 0, wNotifyErr;
DWORD dwPlayTo = MCICDA_BAD_TIME;
WORD wNotifyStatus = MCI_NOTIFY_SUCCESSFUL;
PINSTDATA pInst;
pInst = (PINSTDATA)mciGetDriverData(wDeviceID);
didDrive = (DID)pInst->uDevice;
EnterCrit( CdInfo[didDrive].DeviceCritSec );
switch (message)
{
case MCI_OPEN_DRIVER:
dwErr = mcOpen (pInst, (DWORD)lParam1, (LPMCI_OPEN_PARMS)lParam2);
break;
case MCI_CLOSE_DRIVER:
dwErr = mcClose (pInst);
break;
case MCI_PLAY:
{
BOOL bBreak = FALSE;
dwErr = mcPlay (pInst, (DWORD)lParam1, (LPMCI_PLAY_PARMS)lParam2, &bBreak);
if (dwErr == 0 && (DWORD)lParam1 & MCI_WAIT && (DWORD)lParam1 & MCI_NOTIFY)
{
switch (CDA_drive_status (didDrive))
{
case DISC_PLAYING:
case DISC_PAUSED:
case DISC_READY:
break;
default:
wNotifyStatus = MCI_NOTIFY_FAILURE;
break;
}
}
// If MCI_WAIT is not set or if the wait loop was broken out of then delay
if (!((DWORD)lParam1 & MCI_WAIT) || bBreak)
bDelayed = TRUE;
break;
}
case MCI_SEEK:
dwErr = mcSeek (pInst, (DWORD)lParam1, (LPMCI_SEEK_PARMS)lParam2);
break;
case MCI_STOP:
dwErr = mcStop ( pInst, (DWORD)lParam1, (LPMCI_GENERIC_PARMS)lParam2);
break;
case MCI_PAUSE:
dwErr = mcPause ( pInst, (DWORD)lParam1, (LPMCI_GENERIC_PARMS)lParam2);
break;
case MCI_GETDEVCAPS:
dwErr = mcGetDevCaps (pInst, (DWORD)lParam1, (LPMCI_GETDEVCAPS_PARMS)lParam2);
break;
case MCI_STATUS:
dwErr = mcStatus (pInst, (DWORD)lParam1, (LPMCI_STATUS_PARMS)lParam2);
break;
case MCI_SET:
dwErr = mcSet (pInst, (DWORD)lParam1, (LPMCI_SET_PARMS)lParam2);
break;
case MCI_INFO:
dwErr = mcInfo (pInst, (DWORD)lParam1, (LPMCI_INFO_PARMS)lParam2);
break;
case MCI_RECORD:
case MCI_LOAD:
case MCI_SAVE:
LeaveCrit( CdInfo[didDrive].DeviceCritSec );
return MCIERR_UNSUPPORTED_FUNCTION;
case MCI_RESUME:
dwErr = mcResume ( pInst, (DWORD)lParam1, (LPMCI_GENERIC_PARMS)lParam2);
break;
default:
LeaveCrit( CdInfo[didDrive].DeviceCritSec );
return MCIERR_UNRECOGNIZED_COMMAND;
} /* switch */
/* it is possible that the instance information has disappeared if
* CLOSE NOTIFY is requested. Therefore NOTIFY should never take
* instance data.
*/
if ((DWORD)lParam1 & MCI_NOTIFY && LOWORD (dwErr) == 0)
if ((wNotifyErr =
notify (didDrive, wDeviceID, bDelayed, wNotifyStatus,
(LPMCI_GENERIC_PARMS)lParam2)) != 0) {
LeaveCrit( CdInfo[didDrive].DeviceCritSec );
return wNotifyErr;
}
LeaveCrit( CdInfo[didDrive].DeviceCritSec );
return dwErr;
}