2016-03-01 15:47:10 +00:00
|
|
|
/*
|
|
|
|
** i_cd.cpp
|
|
|
|
** Functions for controlling CD playback
|
|
|
|
**
|
|
|
|
**---------------------------------------------------------------------------
|
|
|
|
** Copyright 1998-2006 Randy Heit
|
|
|
|
** All rights reserved.
|
|
|
|
**
|
|
|
|
** Redistribution and use in source and binary forms, with or without
|
|
|
|
** modification, are permitted provided that the following conditions
|
|
|
|
** are met:
|
|
|
|
**
|
|
|
|
** 1. Redistributions of source code must retain the above copyright
|
|
|
|
** notice, this list of conditions and the following disclaimer.
|
|
|
|
** 2. Redistributions in binary form must reproduce the above copyright
|
|
|
|
** notice, this list of conditions and the following disclaimer in the
|
|
|
|
** documentation and/or other materials provided with the distribution.
|
|
|
|
** 3. The name of the author may not be used to endorse or promote products
|
|
|
|
** derived from this software without specific prior written permission.
|
|
|
|
**
|
|
|
|
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
|
|
|
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
|
|
|
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
|
|
|
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
|
|
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
|
|
|
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
|
|
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
|
|
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
|
|
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
|
|
|
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
**---------------------------------------------------------------------------
|
|
|
|
**
|
|
|
|
*/
|
|
|
|
|
|
|
|
#define _WIN32_WINNT 0x0400
|
|
|
|
#define WIN32_LEAN_AND_MEAN
|
|
|
|
#include <windows.h>
|
|
|
|
#include <mmsystem.h>
|
|
|
|
#include <ctype.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
|
|
|
#define USE_WINDOWS_DWORD
|
|
|
|
#include "doomtype.h"
|
|
|
|
#include "c_cvars.h"
|
|
|
|
#include "c_dispatch.h"
|
|
|
|
#include "m_argv.h"
|
|
|
|
#include "i_system.h"
|
|
|
|
#include "version.h"
|
|
|
|
|
|
|
|
#include "i_cd.h"
|
|
|
|
#include "helperthread.h"
|
|
|
|
|
|
|
|
extern HWND Window;
|
|
|
|
extern HINSTANCE g_hInst;
|
|
|
|
|
|
|
|
enum
|
|
|
|
{
|
|
|
|
CDM_Init, // parm1 = device
|
|
|
|
CDM_Close,
|
|
|
|
CDM_Play, // parm1 = track, parm2 = 1:looping
|
|
|
|
CDM_PlayCD, // parm1 = 1:looping
|
|
|
|
CDM_Replay, // Redos the most recent CDM_Play(CD)
|
|
|
|
CDM_Stop,
|
|
|
|
CDM_Eject,
|
|
|
|
CDM_UnEject,
|
|
|
|
CDM_Pause,
|
|
|
|
CDM_Resume,
|
|
|
|
CDM_GetMode,
|
|
|
|
CDM_CheckTrack, // parm1 = track
|
|
|
|
CDM_GetMediaIdentity,
|
|
|
|
CDM_GetMediaUPC
|
|
|
|
};
|
|
|
|
|
|
|
|
class FCDThread : public FHelperThread
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
FCDThread ();
|
|
|
|
|
|
|
|
protected:
|
|
|
|
bool Init ();
|
|
|
|
void Deinit ();
|
|
|
|
const char *ThreadName ();
|
|
|
|
DWORD Dispatch (DWORD method, DWORD parm1=0, DWORD parm2=0, DWORD parm3=0);
|
|
|
|
void DefaultDispatch ();
|
|
|
|
|
|
|
|
bool IsTrackAudio (DWORD track) const;
|
|
|
|
DWORD GetTrackLength (DWORD track) const;
|
|
|
|
DWORD GetNumTracks () const;
|
|
|
|
|
|
|
|
static LRESULT CALLBACK CD_WndProc (HWND hWnd, UINT message,
|
|
|
|
WPARAM wParam, LPARAM lParam);
|
|
|
|
|
|
|
|
WNDCLASS CD_WindowClass;
|
|
|
|
HWND CD_Window;
|
|
|
|
ATOM CD_WindowAtom;
|
|
|
|
|
|
|
|
bool Looping;
|
|
|
|
DWORD PlayFrom;
|
|
|
|
DWORD PlayTo;
|
|
|
|
UINT DeviceID;
|
|
|
|
};
|
|
|
|
|
|
|
|
#define NOT_INITED ((signed)0x80000000)
|
|
|
|
|
|
|
|
static FCDThread *CDThread;
|
|
|
|
static int Inited = NOT_INITED;
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// CVAR: cd_enabled
|
|
|
|
//
|
|
|
|
// Use the CD device? Can be overridden with -nocdaudio on the command line
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
CUSTOM_CVAR (Bool, cd_enabled, true, CVAR_ARCHIVE|CVAR_NOINITCALL|CVAR_GLOBALCONFIG)
|
|
|
|
{
|
|
|
|
if (self)
|
|
|
|
CD_Init ();
|
|
|
|
else
|
|
|
|
CD_Close ();
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// CVAR: cd_drive
|
|
|
|
//
|
|
|
|
// Which drive (letter) to use for CD audio. If not a valid drive letter,
|
|
|
|
// let the operating system decide for us.
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
CUSTOM_CVAR (String, cd_drive, "", CVAR_ARCHIVE|CVAR_NOINITCALL|CVAR_GLOBALCONFIG)
|
|
|
|
{
|
|
|
|
CD_Init ();
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// ---Constructor---
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
FCDThread::FCDThread ()
|
|
|
|
{
|
|
|
|
memset (&CD_WindowClass, 0, sizeof(CD_WindowClass));
|
|
|
|
CD_Window = NULL;
|
|
|
|
CD_WindowAtom = 0;
|
|
|
|
Inited = NOT_INITED;
|
|
|
|
Looping = false;
|
|
|
|
PlayFrom = 0;
|
|
|
|
PlayTo = 0;
|
|
|
|
DeviceID = 0;
|
|
|
|
LaunchThread ();
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// ThreadName
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
const char *FCDThread::ThreadName ()
|
|
|
|
{
|
|
|
|
return "CD Player";
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// Init
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
bool FCDThread::Init ()
|
|
|
|
{
|
|
|
|
CD_WindowClass.style = CS_NOCLOSE;
|
|
|
|
CD_WindowClass.lpfnWndProc = CD_WndProc;
|
|
|
|
CD_WindowClass.hInstance = g_hInst;
|
|
|
|
CD_WindowClass.lpszClassName = GAMENAME " CD Player";
|
|
|
|
CD_WindowAtom = RegisterClass (&CD_WindowClass);
|
|
|
|
|
|
|
|
if (CD_WindowAtom == 0)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
CD_Window = CreateWindow (
|
|
|
|
(LPCTSTR)(INT_PTR)(int)CD_WindowAtom,
|
|
|
|
GAMENAME " CD Player",
|
|
|
|
0,
|
|
|
|
0, 0, 10, 10,
|
|
|
|
NULL,
|
|
|
|
NULL,
|
|
|
|
g_hInst,
|
|
|
|
NULL);
|
|
|
|
|
|
|
|
if (CD_Window == NULL)
|
|
|
|
{
|
|
|
|
UnregisterClass ((LPCTSTR)(INT_PTR)(int)CD_WindowAtom, g_hInst);
|
|
|
|
CD_WindowAtom = 0;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef _WIN64
|
|
|
|
SetWindowLongPtr (CD_Window, GWLP_USERDATA, (LONG_PTR)this);
|
|
|
|
#else
|
|
|
|
SetWindowLong (CD_Window, GWL_USERDATA, (LONG)(LONG_PTR)this);
|
|
|
|
#endif
|
|
|
|
SetThreadPriority (ThreadHandle, THREAD_PRIORITY_LOWEST);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// Deinit
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
void FCDThread::Deinit ()
|
|
|
|
{
|
|
|
|
if (CD_Window)
|
|
|
|
{
|
|
|
|
DestroyWindow (CD_Window);
|
|
|
|
CD_Window = NULL;
|
|
|
|
}
|
|
|
|
if (CD_WindowAtom)
|
|
|
|
{
|
|
|
|
UnregisterClass ((LPCTSTR)(INT_PTR)(int)CD_WindowAtom, g_hInst);
|
|
|
|
CD_WindowAtom = 0;
|
|
|
|
}
|
|
|
|
if (DeviceID)
|
|
|
|
{
|
|
|
|
Dispatch (CDM_Close);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// DefaultDispatch
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
void FCDThread::DefaultDispatch ()
|
|
|
|
{
|
|
|
|
MSG wndMsg;
|
|
|
|
|
|
|
|
while (PeekMessage (&wndMsg, NULL, 0, 0, PM_REMOVE))
|
|
|
|
{
|
|
|
|
DispatchMessage (&wndMsg);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// Dispatch
|
|
|
|
//
|
|
|
|
// Does the actual work of implementing the public CD interface
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
DWORD FCDThread::Dispatch (DWORD method, DWORD parm1, DWORD parm2, DWORD parm3)
|
|
|
|
{
|
|
|
|
MCI_OPEN_PARMS mciOpen;
|
|
|
|
MCI_SET_PARMS mciSetParms;
|
|
|
|
MCI_PLAY_PARMS playParms;
|
|
|
|
MCI_STATUS_PARMS statusParms;
|
|
|
|
MCI_INFO_PARMS infoParms;
|
|
|
|
DWORD firstTrack, lastTrack, numTracks;
|
|
|
|
DWORD length;
|
|
|
|
DWORD openFlags;
|
|
|
|
char ident[32];
|
|
|
|
|
|
|
|
switch (method)
|
|
|
|
{
|
|
|
|
case CDM_Init:
|
|
|
|
mciOpen.lpstrDeviceType = (LPCSTR)MCI_DEVTYPE_CD_AUDIO;
|
|
|
|
openFlags = MCI_OPEN_SHAREABLE|MCI_OPEN_TYPE|MCI_OPEN_TYPE_ID|MCI_WAIT;
|
|
|
|
if ((signed)parm1 >= 0)
|
|
|
|
{
|
|
|
|
ident[0] = (char)(parm1 + 'A');
|
|
|
|
ident[1] = ':';
|
|
|
|
ident[2] = 0;
|
|
|
|
mciOpen.lpstrElementName = ident;
|
|
|
|
openFlags |= MCI_OPEN_ELEMENT;
|
|
|
|
}
|
|
|
|
|
|
|
|
while (mciSendCommand (0, MCI_OPEN, openFlags, (DWORD_PTR)&mciOpen) != 0)
|
|
|
|
{
|
|
|
|
if (!(openFlags & MCI_OPEN_ELEMENT))
|
|
|
|
{
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
openFlags &= ~MCI_OPEN_ELEMENT;
|
|
|
|
}
|
|
|
|
|
|
|
|
DeviceID = mciOpen.wDeviceID;
|
|
|
|
mciSetParms.dwTimeFormat = MCI_FORMAT_TMSF;
|
|
|
|
if (mciSendCommand (DeviceID, MCI_SET, MCI_SET_TIME_FORMAT, (DWORD_PTR)&mciSetParms) == 0)
|
|
|
|
{
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
mciSendCommand (DeviceID, MCI_CLOSE, 0, 0);
|
|
|
|
return FALSE;
|
|
|
|
|
|
|
|
case CDM_Close:
|
|
|
|
Dispatch (CDM_Stop);
|
|
|
|
mciSendCommand (DeviceID, MCI_CLOSE, 0, 0);
|
|
|
|
DeviceID = 0;
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
case CDM_Play:
|
|
|
|
if (!IsTrackAudio (parm1))
|
|
|
|
{
|
|
|
|
//Printf ("Track %d is not audio\n", track);
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
length = GetTrackLength (parm1);
|
|
|
|
|
|
|
|
if (length == 0)
|
|
|
|
{ // Couldn't get length of track, so won't be able to play last track
|
|
|
|
PlayTo = MCI_MAKE_TMSF (parm1+1, 0, 0, 0);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
PlayTo = MCI_MAKE_TMSF (parm1,
|
|
|
|
MCI_MSF_MINUTE(length),
|
|
|
|
MCI_MSF_SECOND(length),
|
|
|
|
MCI_MSF_FRAME(length));
|
|
|
|
}
|
|
|
|
PlayFrom = MCI_MAKE_TMSF (parm1, 0, 0, 0);
|
|
|
|
Looping = parm2 & 1;
|
|
|
|
|
|
|
|
// intentional fall-through
|
|
|
|
|
|
|
|
case CDM_Replay:
|
|
|
|
playParms.dwFrom = PlayFrom;
|
|
|
|
playParms.dwTo = PlayTo;
|
|
|
|
playParms.dwCallback = (DWORD_PTR)CD_Window;
|
|
|
|
|
|
|
|
return mciSendCommand (DeviceID, MCI_PLAY, MCI_FROM | MCI_TO | MCI_NOTIFY,
|
|
|
|
(DWORD_PTR)&playParms);
|
|
|
|
|
|
|
|
case CDM_PlayCD:
|
|
|
|
numTracks = GetNumTracks ();
|
|
|
|
if (numTracks == 0)
|
|
|
|
return FALSE;
|
|
|
|
|
|
|
|
for (firstTrack = 1; firstTrack <= numTracks && !IsTrackAudio (firstTrack); firstTrack++)
|
|
|
|
;
|
|
|
|
for (lastTrack = firstTrack; lastTrack <= numTracks && IsTrackAudio (lastTrack); lastTrack++)
|
|
|
|
;
|
|
|
|
|
|
|
|
if (firstTrack > numTracks)
|
|
|
|
return FALSE;
|
|
|
|
if (lastTrack > numTracks)
|
|
|
|
lastTrack = numTracks;
|
|
|
|
|
|
|
|
length = GetTrackLength (lastTrack);
|
|
|
|
if (length == 0)
|
|
|
|
return FALSE;
|
|
|
|
|
|
|
|
Looping = parm1 & 1;
|
|
|
|
PlayFrom = MCI_MAKE_TMSF (firstTrack, 0, 0, 0);
|
|
|
|
PlayTo = MCI_MAKE_TMSF (lastTrack,
|
|
|
|
MCI_MSF_MINUTE(length),
|
|
|
|
MCI_MSF_SECOND(length),
|
|
|
|
MCI_MSF_FRAME(length));
|
|
|
|
|
|
|
|
playParms.dwFrom = PlayFrom;
|
|
|
|
playParms.dwTo = PlayTo;
|
|
|
|
playParms.dwCallback = (DWORD_PTR)CD_Window;
|
|
|
|
|
|
|
|
return mciSendCommand (DeviceID, MCI_PLAY, MCI_FROM|MCI_TO|MCI_NOTIFY,
|
|
|
|
(DWORD_PTR)&playParms);
|
|
|
|
|
|
|
|
case CDM_Stop:
|
|
|
|
return mciSendCommand (DeviceID, MCI_STOP, 0, 0);
|
|
|
|
|
|
|
|
case CDM_Eject:
|
|
|
|
return mciSendCommand (DeviceID, MCI_SET, MCI_SET_DOOR_OPEN, 0);
|
|
|
|
|
|
|
|
case CDM_UnEject:
|
|
|
|
return mciSendCommand (DeviceID, MCI_SET, MCI_SET_DOOR_CLOSED, 0);
|
|
|
|
|
|
|
|
case CDM_Pause:
|
|
|
|
return mciSendCommand (DeviceID, MCI_PAUSE, 0, 0);
|
|
|
|
|
|
|
|
case CDM_Resume:
|
|
|
|
playParms.dwTo = PlayTo;
|
|
|
|
playParms.dwCallback = (DWORD_PTR)CD_Window;
|
|
|
|
return mciSendCommand (DeviceID, MCI_PLAY, MCI_TO | MCI_NOTIFY, (DWORD_PTR)&playParms);
|
|
|
|
|
|
|
|
case CDM_GetMode:
|
|
|
|
statusParms.dwItem = MCI_STATUS_MODE;
|
|
|
|
if (mciSendCommand (DeviceID, MCI_STATUS, MCI_STATUS_ITEM, (DWORD_PTR)&statusParms))
|
|
|
|
{
|
|
|
|
return CDMode_Unknown;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
switch (statusParms.dwReturn)
|
|
|
|
{
|
|
|
|
case MCI_MODE_NOT_READY: return CDMode_NotReady;
|
|
|
|
case MCI_MODE_PAUSE: return CDMode_Pause;
|
|
|
|
case MCI_MODE_PLAY: return CDMode_Play;
|
|
|
|
case MCI_MODE_STOP: return CDMode_Stop;
|
|
|
|
case MCI_MODE_OPEN: return CDMode_Open;
|
|
|
|
default: return CDMode_Unknown;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
case CDM_CheckTrack:
|
|
|
|
return IsTrackAudio (parm1) ? TRUE : FALSE;
|
|
|
|
|
|
|
|
case CDM_GetMediaIdentity:
|
|
|
|
case CDM_GetMediaUPC:
|
|
|
|
char ident[32];
|
|
|
|
|
|
|
|
infoParms.lpstrReturn = ident;
|
|
|
|
infoParms.dwRetSize = sizeof(ident);
|
|
|
|
if (mciSendCommand (DeviceID, MCI_INFO,
|
|
|
|
method == CDM_GetMediaIdentity ? MCI_WAIT|MCI_INFO_MEDIA_IDENTITY
|
|
|
|
: MCI_WAIT|MCI_INFO_MEDIA_UPC, (DWORD_PTR)&infoParms))
|
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return strtoul (ident, NULL, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
default:
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// KillThread
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
static void KillThread ()
|
|
|
|
{
|
|
|
|
if (CDThread != NULL)
|
|
|
|
{
|
|
|
|
CDThread->DestroyThread ();
|
|
|
|
Inited = NOT_INITED;
|
|
|
|
delete CDThread;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// CD_Init
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
bool CD_Init ()
|
|
|
|
{
|
|
|
|
if ((cd_drive)[0] == 0 || (cd_drive)[1] != 0)
|
|
|
|
{
|
|
|
|
return CD_Init (-1);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
char drive = toupper ((cd_drive)[0]);
|
|
|
|
|
|
|
|
if (drive >= 'A' && drive <= 'Z' && !CD_Init (drive - 'A'))
|
|
|
|
return CD_Init (-1);
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CD_Init (int device)
|
|
|
|
{
|
|
|
|
if (!cd_enabled || Args->CheckParm ("-nocdaudio"))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if (CDThread == NULL)
|
|
|
|
{
|
|
|
|
CDThread = new FCDThread;
|
|
|
|
atterm (KillThread);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (Inited != device)
|
|
|
|
{
|
|
|
|
CD_Close ();
|
|
|
|
|
|
|
|
if (CDThread->SendMessage (CDM_Init, device, 0, 0, true))
|
|
|
|
{
|
|
|
|
Inited = device;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// CD_InitID
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
bool CD_InitID (unsigned int id, int guess)
|
|
|
|
{
|
|
|
|
char drive;
|
|
|
|
|
|
|
|
if (guess < 0 && Inited != NOT_INITED)
|
|
|
|
guess = Inited;
|
|
|
|
|
|
|
|
if (guess >= 0 && CD_Init (guess))
|
|
|
|
{
|
|
|
|
if (CDThread->SendMessage (CDM_GetMediaIdentity, 0, 0, 0, true) == id ||
|
|
|
|
CDThread->SendMessage (CDM_GetMediaUPC, 0, 0, 0, true) == id)
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
CD_Close ();
|
|
|
|
}
|
|
|
|
|
|
|
|
for (drive = 'V'; drive < 'Z'; drive++)
|
|
|
|
{
|
|
|
|
if (CD_Init (drive - 'A'))
|
|
|
|
{
|
|
|
|
// I don't know which value is stored in a CDDA file, so I try
|
|
|
|
// them both. All the CDs I've tested have had the same value
|
|
|
|
// for both, so it probably doesn't matter.
|
|
|
|
if (CDThread->SendMessage (CDM_GetMediaIdentity, 0, 0, 0, true) == id ||
|
|
|
|
CDThread->SendMessage (CDM_GetMediaUPC, 0, 0, 0, true) == id)
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
CD_Close ();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// CD_Close
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
void CD_Close ()
|
|
|
|
{
|
|
|
|
if (Inited != NOT_INITED)
|
|
|
|
{
|
|
|
|
CDThread->SendMessage (CDM_Close, 0, 0, 0, true);
|
|
|
|
Inited = NOT_INITED;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// CD_Eject
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
void CD_Eject ()
|
|
|
|
{
|
|
|
|
if (Inited != NOT_INITED)
|
|
|
|
CDThread->SendMessage (CDM_Eject, 0, 0, 0, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// CD_UnEject
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
bool CD_UnEject ()
|
|
|
|
{
|
|
|
|
if (Inited == NOT_INITED)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
return CDThread->SendMessage (CDM_UnEject, 0, 0, 0, true) == 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// CD_Stop
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
void CD_Stop ()
|
|
|
|
{
|
|
|
|
if (Inited != NOT_INITED)
|
|
|
|
CDThread->SendMessage (CDM_Stop, 0, 0, 0, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// CD_Play
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
bool CD_Play (int track, bool looping)
|
|
|
|
{
|
|
|
|
if (Inited == NOT_INITED)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
return CDThread->SendMessage (CDM_Play, track, looping ? 1 : 0, 0, true) == 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// CD_PlayNoWait
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
void CD_PlayNoWait (int track, bool looping)
|
|
|
|
{
|
|
|
|
if (Inited != NOT_INITED)
|
|
|
|
CDThread->SendMessage (CDM_Play, track, looping ? 1 : 0, 0, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// CD_PlayCD
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
bool CD_PlayCD (bool looping)
|
|
|
|
{
|
|
|
|
if (Inited == NOT_INITED)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
return CDThread->SendMessage (CDM_PlayCD, looping ? 1 : 0, 0, 0, true) == 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// CD_PlayCDNoWait
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
void CD_PlayCDNoWait (bool looping)
|
|
|
|
{
|
|
|
|
if (Inited != NOT_INITED)
|
|
|
|
CDThread->SendMessage (CDM_PlayCD, looping ? 1 : 0, 0, 0, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// CD_Pause
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
void CD_Pause ()
|
|
|
|
{
|
|
|
|
if (Inited != NOT_INITED)
|
|
|
|
CDThread->SendMessage (CDM_Pause, 0, 0, 0, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// CD_Resume
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
bool CD_Resume ()
|
|
|
|
{
|
|
|
|
if (Inited == NOT_INITED)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
return CDThread->SendMessage (CDM_Resume, 0, 0, 0, false) == 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// CD_GetMode
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
ECDModes CD_GetMode ()
|
|
|
|
{
|
|
|
|
if (Inited == NOT_INITED)
|
|
|
|
return CDMode_Unknown;
|
|
|
|
|
|
|
|
return (ECDModes)CDThread->SendMessage (CDM_GetMode, 0, 0, 0, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// CD_CheckTrack
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
bool CD_CheckTrack (int track)
|
|
|
|
{
|
|
|
|
if (Inited == NOT_INITED)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
return CDThread->SendMessage (CDM_CheckTrack, track, 0, 0, true) != 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Functions called only by the helper thread -------------------------------
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// IsTrackAudio
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
bool FCDThread::IsTrackAudio (DWORD track) const
|
|
|
|
{
|
|
|
|
MCI_STATUS_PARMS statusParms;
|
|
|
|
|
|
|
|
statusParms.dwItem = MCI_CDA_STATUS_TYPE_TRACK;
|
|
|
|
statusParms.dwTrack = track;
|
|
|
|
if (mciSendCommand (DeviceID, MCI_STATUS, MCI_STATUS_ITEM | MCI_TRACK,
|
|
|
|
(DWORD_PTR)&statusParms))
|
|
|
|
{
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
return statusParms.dwReturn == MCI_CDA_TRACK_AUDIO;
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// GetTrackLength
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
DWORD FCDThread::GetTrackLength (DWORD track) const
|
|
|
|
{
|
|
|
|
MCI_STATUS_PARMS statusParms;
|
|
|
|
|
|
|
|
statusParms.dwItem = MCI_STATUS_LENGTH;
|
|
|
|
statusParms.dwTrack = track;
|
|
|
|
if (mciSendCommand (DeviceID, MCI_STATUS, MCI_STATUS_ITEM | MCI_TRACK,
|
|
|
|
(DWORD_PTR)&statusParms))
|
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return (DWORD)statusParms.dwReturn;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// GetNumTracks
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
DWORD FCDThread::GetNumTracks () const
|
|
|
|
{
|
|
|
|
MCI_STATUS_PARMS statusParms;
|
|
|
|
|
|
|
|
statusParms.dwItem = MCI_STATUS_NUMBER_OF_TRACKS;
|
|
|
|
if (mciSendCommand (DeviceID, MCI_STATUS, MCI_STATUS_ITEM, (DWORD_PTR)&statusParms))
|
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return (DWORD)statusParms.dwReturn;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//==========================================================================
|
|
|
|
//
|
|
|
|
// CD_WndProc (static)
|
|
|
|
//
|
|
|
|
// Because MCI (under Win 9x anyway) can't notify windows owned by another
|
|
|
|
// thread, the helper thread creates its own window.
|
|
|
|
//
|
|
|
|
//==========================================================================
|
|
|
|
|
|
|
|
LRESULT CALLBACK FCDThread::CD_WndProc (HWND hWnd, UINT message,
|
|
|
|
WPARAM wParam, LPARAM lParam)
|
|
|
|
{
|
|
|
|
switch (message)
|
|
|
|
{
|
|
|
|
case MM_MCINOTIFY:
|
|
|
|
if (wParam == MCI_NOTIFY_SUCCESSFUL)
|
|
|
|
{
|
|
|
|
FCDThread *self = (FCDThread *)(LONG_PTR)GetWindowLongPtr (hWnd, GWLP_USERDATA);
|
|
|
|
// Using SendMessage could deadlock, so don't do that.
|
|
|
|
self->Dispatch (self->Looping ? CDM_Replay : CDM_Stop);
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
default:
|
|
|
|
return DefWindowProc (hWnd, message, wParam, lParam);
|
|
|
|
}
|
|
|
|
}
|