gzdoom/src/win32/i_cd.cpp

796 lines
18 KiB
C++
Raw Normal View History

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);
}
}