/*
	cd_audio.c

	(description)

	Copyright (C) 1996-1997  Id Software, Inc.

	This program is free software; you can redistribute it and/or
	modify it under the terms of the GNU General Public License
	as published by the Free Software Foundation; either version 2
	of the License, or (at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

	See the GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program; if not, write to:

		Free Software Foundation, Inc.
		59 Temple Place - Suite 330
		Boston, MA  02111-1307, USA

	$Id$
*/

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include <dpmi.h>

#include "dosisms.h"

extern cvar_t *bgmvolume;

#define ADDRESS_MODE_HSG		0
#define ADDRESS_MODE_RED_BOOK	1

#define STATUS_ERROR_BIT	0x8000
#define STATUS_BUSY_BIT		0x0200
#define STATUS_DONE_BIT		0x0100
#define STATUS_ERROR_MASK	0x00ff

#define ERROR_WRITE_PROTECT		0
#define ERROR_UNKNOWN_UNIT		1
#define ERROR_DRIVE_NOT_READY	2
#define ERROR_UNKNOWN_COMMAND	3
#define ERROR_CRC_ERROR			4
#define ERROR_BAD_REQUEST_LEN	5
#define ERROR_SEEK_ERROR		6
#define ERROR_UNKNOWN_MEDIA		7
#define ERROR_SECTOR_NOT_FOUND	8
#define ERROR_OUT_OF_PAPER		9
#define ERROR_WRITE_FAULT		10
#define ERROR_READ_FAULT		11
#define ERROR_GENERAL_FAILURE	12
#define ERROR_RESERVED_13		13
#define ERROR_RESERVED_14		14
#define ERROR_BAD_DISK_CHANGE	15

#define COMMAND_READ			3
#define COMMAND_WRITE			12
#define COMMAND_PLAY_AUDIO		132
#define COMMAND_STOP_AUDIO		133
#define COMMAND_RESUME_AUDIO	136

#define READ_REQUEST_AUDIO_CHANNEL_INFO		4
#define READ_REQUEST_DEVICE_STATUS			6
#define READ_REQUEST_MEDIA_CHANGE			9
#define READ_REQUEST_AUDIO_DISK_INFO		10
#define READ_REQUEST_AUDIO_TRACK_INFO		11
#define READ_REQUEST_AUDIO_STATUS			15

#define WRITE_REQUEST_EJECT					0
#define WRITE_REQUEST_RESET					2
#define WRITE_REQUEST_AUDIO_CHANNEL_INFO	3

#define STATUS_DOOR_OPEN					0x00000001
#define STATUS_DOOR_UNLOCKED				0x00000002
#define STATUS_RAW_SUPPORT					0x00000004
#define STATUS_READ_WRITE					0x00000008
#define STATUS_AUDIO_SUPPORT				0x00000010
#define STATUS_INTERLEAVE_SUPPORT			0x00000020
#define STATUS_BIT_6_RESERVED				0x00000040
#define STATUS_PREFETCH_SUPPORT				0x00000080
#define STATUS_AUDIO_MANIPLUATION_SUPPORT	0x00000100
#define STATUS_RED_BOOK_ADDRESS_SUPPORT		0x00000200

#define MEDIA_NOT_CHANGED		1
#define MEDIA_STATUS_UNKNOWN	0
#define MEDIA_CHANGED			-1

#define AUDIO_CONTROL_MASK				0xd0
#define AUDIO_CONTROL_DATA_TRACK		0x40
#define AUDIO_CONTROL_AUDIO_2_TRACK		0x00
#define AUDIO_CONTROL_AUDIO_2P_TRACK	0x10
#define AUDIO_CONTROL_AUDIO_4_TRACK		0x80
#define AUDIO_CONTROL_AUDIO_4P_TRACK	0x90

#define AUDIO_STATUS_PAUSED				0x0001

#pragma pack(1)

struct playAudioRequest {
	char        addressingMode;
	int         startLocation;
	int         sectors;
};

struct readRequest {
	char        mediaDescriptor;
	short       bufferOffset;
	short       bufferSegment;
	short       length;
	short       startSector;
	int         volumeID;
};

struct writeRequest {
	char        mediaDescriptor;
	short       bufferOffset;
	short       bufferSegment;
	short       length;
	short       startSector;
	int         volumeID;
};

struct cd_request {
	char        headerLength;
	char        unit;
	char        command;
	short       status;
	char        reserved[8];
	union {
		struct playAudioRequest playAudio;
		struct readRequest read;
		struct writeRequest write;
	} x;
};


struct audioChannelInfo_s {
	char        code;
	char        channel0input;
	char        channel0volume;
	char        channel1input;
	char        channel1volume;
	char        channel2input;
	char        channel2volume;
	char        channel3input;
	char        channel3volume;
};

struct deviceStatus_s {
	char        code;
	int         status;
};

struct mediaChange_s {
	char        code;
	char        status;
};

struct audioDiskInfo_s {
	char        code;
	char        lowTrack;
	char        highTrack;
	int         leadOutStart;
};

struct audioTrackInfo_s {
	char        code;
	char        track;
	int         start;
	char        control;
};

struct audioStatus_s {
	char        code;
	short       status;
	int         PRstartLocation;
	int         PRendLocation;
};

struct reset_s {
	char        code;
};

union readInfo_u {
	struct audioChannelInfo_s audioChannelInfo;
	struct deviceStatus_s deviceStatus;
	struct mediaChange_s mediaChange;
	struct audioDiskInfo_s audioDiskInfo;
	struct audioTrackInfo_s audioTrackInfo;
	struct audioStatus_s audioStatus;
	struct reset_s reset;
};

#pragma pack()

#define MAXIMUM_TRACKS			32

typedef struct {
	int         start;
	int         length;
	qboolean    isData;
} track_info;

typedef struct {
	qboolean    valid;
	int         leadOutAddress;
	track_info  track[MAXIMUM_TRACKS];
	byte        lowTrack;
	byte        highTrack;
} cd_info;

static struct cd_request *cdRequest;
static union readInfo_u *readInfo;
static cd_info cd;

static qboolean playing = false;
static qboolean wasPlaying = false;
static qboolean mediaCheck = false;
static qboolean initialized = false;
static qboolean enabled = true;
static qboolean playLooping = false;
static short cdRequestSegment;
static short cdRequestOffset;
static short readInfoSegment;
static short readInfoOffset;
static byte remap[256];
static byte cdrom;
static byte playTrack;
static byte cdvolume;


static int
RedBookToSector (int rb)
{
	byte        minute;
	byte        second;
	byte        frame;

	minute = (rb >> 16) & 0xff;
	second = (rb >> 8) & 0xff;
	frame = rb & 0xff;
	return minute * 60 * 75 + second * 75 + frame;
}


static void
CDAudio_Reset (void)
{
	cdRequest->headerLength = 13;
	cdRequest->unit = 0;
	cdRequest->command = COMMAND_WRITE;
	cdRequest->status = 0;

	cdRequest->x.write.mediaDescriptor = 0;
	cdRequest->x.write.bufferOffset = readInfoOffset;
	cdRequest->x.write.bufferSegment = readInfoSegment;
	cdRequest->x.write.length = sizeof (struct reset_s);

	cdRequest->x.write.startSector = 0;
	cdRequest->x.write.volumeID = 0;

	readInfo->reset.code = WRITE_REQUEST_RESET;

	regs.x.ax = 0x1510;
	regs.x.cx = cdrom;
	regs.x.es = cdRequestSegment;
	regs.x.bx = cdRequestOffset;
	dos_int86 (0x2f);
}


static void
CDAudio_Eject (void)
{
	cdRequest->headerLength = 13;
	cdRequest->unit = 0;
	cdRequest->command = COMMAND_WRITE;
	cdRequest->status = 0;

	cdRequest->x.write.mediaDescriptor = 0;
	cdRequest->x.write.bufferOffset = readInfoOffset;
	cdRequest->x.write.bufferSegment = readInfoSegment;
	cdRequest->x.write.length = sizeof (struct reset_s);

	cdRequest->x.write.startSector = 0;
	cdRequest->x.write.volumeID = 0;

	readInfo->reset.code = WRITE_REQUEST_EJECT;

	regs.x.ax = 0x1510;
	regs.x.cx = cdrom;
	regs.x.es = cdRequestSegment;
	regs.x.bx = cdRequestOffset;
	dos_int86 (0x2f);
}


static int
CDAudio_GetAudioTrackInfo (byte track, int *start)
{
	byte        control;

	cdRequest->headerLength = 13;
	cdRequest->unit = 0;
	cdRequest->command = COMMAND_READ;
	cdRequest->status = 0;

	cdRequest->x.read.mediaDescriptor = 0;
	cdRequest->x.read.bufferOffset = readInfoOffset;
	cdRequest->x.read.bufferSegment = readInfoSegment;
	cdRequest->x.read.length = sizeof (struct audioTrackInfo_s);

	cdRequest->x.read.startSector = 0;
	cdRequest->x.read.volumeID = 0;

	readInfo->audioTrackInfo.code = READ_REQUEST_AUDIO_TRACK_INFO;
	readInfo->audioTrackInfo.track = track;

	regs.x.ax = 0x1510;
	regs.x.cx = cdrom;
	regs.x.es = cdRequestSegment;
	regs.x.bx = cdRequestOffset;
	dos_int86 (0x2f);

	if (cdRequest->status & STATUS_ERROR_BIT) {
		Con_DPrintf ("CDAudio_GetAudioTrackInfo %04x\n",
					 cdRequest->status & 0xffff);
		return -1;
	}

	*start = readInfo->audioTrackInfo.start;
	control = readInfo->audioTrackInfo.control & AUDIO_CONTROL_MASK;
	return (control & AUDIO_CONTROL_DATA_TRACK);
}


static int
CDAudio_GetAudioDiskInfo (void)
{
	int         n;

	cdRequest->headerLength = 13;
	cdRequest->unit = 0;
	cdRequest->command = COMMAND_READ;
	cdRequest->status = 0;

	cdRequest->x.read.mediaDescriptor = 0;
	cdRequest->x.read.bufferOffset = readInfoOffset;
	cdRequest->x.read.bufferSegment = readInfoSegment;
	cdRequest->x.read.length = sizeof (struct audioDiskInfo_s);

	cdRequest->x.read.startSector = 0;
	cdRequest->x.read.volumeID = 0;

	readInfo->audioDiskInfo.code = READ_REQUEST_AUDIO_DISK_INFO;

	regs.x.ax = 0x1510;
	regs.x.cx = cdrom;
	regs.x.es = cdRequestSegment;
	regs.x.bx = cdRequestOffset;
	dos_int86 (0x2f);

	if (cdRequest->status & STATUS_ERROR_BIT) {
		Con_DPrintf ("CDAudio_GetAudioDiskInfo %04x\n",
					 cdRequest->status & 0xffff);
		return -1;
	}

	cd.valid = true;
	cd.lowTrack = readInfo->audioDiskInfo.lowTrack;
	cd.highTrack = readInfo->audioDiskInfo.highTrack;
	cd.leadOutAddress = readInfo->audioDiskInfo.leadOutStart;

	for (n = cd.lowTrack; n <= cd.highTrack; n++) {
		cd.track[n].isData = CDAudio_GetAudioTrackInfo (n, &cd.track[n].start);
		if (n > cd.lowTrack) {
			cd.track[n - 1].length =
				RedBookToSector (cd.track[n].start) -
				RedBookToSector (cd.track[n - 1].start);
			if (n == cd.highTrack)
				cd.track[n].length =
					RedBookToSector (cd.leadOutAddress) -
					RedBookToSector (cd.track[n].start);
		}
	}

	return 0;
}


static int
CDAudio_GetAudioStatus (void)
{
	cdRequest->headerLength = 13;
	cdRequest->unit = 0;
	cdRequest->command = COMMAND_READ;
	cdRequest->status = 0;

	cdRequest->x.read.mediaDescriptor = 0;
	cdRequest->x.read.bufferOffset = readInfoOffset;
	cdRequest->x.read.bufferSegment = readInfoSegment;
	cdRequest->x.read.length = sizeof (struct audioStatus_s);

	cdRequest->x.read.startSector = 0;
	cdRequest->x.read.volumeID = 0;

	readInfo->audioDiskInfo.code = READ_REQUEST_AUDIO_STATUS;

	regs.x.ax = 0x1510;
	regs.x.cx = cdrom;
	regs.x.es = cdRequestSegment;
	regs.x.bx = cdRequestOffset;
	dos_int86 (0x2f);

	if (cdRequest->status & STATUS_ERROR_BIT)
		return -1;
	return 0;
}


static int
CDAudio_MediaChange (void)
{
	cdRequest->headerLength = 13;
	cdRequest->unit = 0;
	cdRequest->command = COMMAND_READ;
	cdRequest->status = 0;

	cdRequest->x.read.mediaDescriptor = 0;
	cdRequest->x.read.bufferOffset = readInfoOffset;
	cdRequest->x.read.bufferSegment = readInfoSegment;
	cdRequest->x.read.length = sizeof (struct mediaChange_s);

	cdRequest->x.read.startSector = 0;
	cdRequest->x.read.volumeID = 0;

	readInfo->mediaChange.code = READ_REQUEST_MEDIA_CHANGE;

	regs.x.ax = 0x1510;
	regs.x.cx = cdrom;
	regs.x.es = cdRequestSegment;
	regs.x.bx = cdRequestOffset;
	dos_int86 (0x2f);

	return readInfo->mediaChange.status;
}


byte
CDAudio_GetVolume (void)
{
	return cdvolume;
}


// we set the volume to 0 first and then to the desired volume
// some cd-rom drivers seem to need it done this way
void
CDAudio_SetVolume (byte volume)
{
	if (!initialized || !enabled)
		return;

	cdRequest->headerLength = 13;
	cdRequest->unit = 0;
	cdRequest->command = COMMAND_WRITE;
	cdRequest->status = 0;

	cdRequest->x.read.mediaDescriptor = 0;
	cdRequest->x.read.bufferOffset = readInfoOffset;
	cdRequest->x.read.bufferSegment = readInfoSegment;
	cdRequest->x.read.length = sizeof (struct audioChannelInfo_s);

	cdRequest->x.read.startSector = 0;
	cdRequest->x.read.volumeID = 0;

	readInfo->audioChannelInfo.code = WRITE_REQUEST_AUDIO_CHANNEL_INFO;
	readInfo->audioChannelInfo.channel0input = 0;
	readInfo->audioChannelInfo.channel0volume = 0;
	readInfo->audioChannelInfo.channel1input = 1;
	readInfo->audioChannelInfo.channel1volume = 0;
	readInfo->audioChannelInfo.channel2input = 2;
	readInfo->audioChannelInfo.channel2volume = 0;
	readInfo->audioChannelInfo.channel3input = 3;
	readInfo->audioChannelInfo.channel3volume = 0;

	regs.x.ax = 0x1510;
	regs.x.cx = cdrom;
	regs.x.es = cdRequestSegment;
	regs.x.bx = cdRequestOffset;
	dos_int86 (0x2f);

	readInfo->audioChannelInfo.channel0volume = volume;
	readInfo->audioChannelInfo.channel1volume = volume;

	regs.x.ax = 0x1510;
	regs.x.cx = cdrom;
	regs.x.es = cdRequestSegment;
	regs.x.bx = cdRequestOffset;
	dos_int86 (0x2f);

	cdvolume = volume;
}


void
CDAudio_Play (byte track, qboolean looping)
{
	if (!initialized || !enabled)
		return;

	if (!cd.valid)
		return;

	track = remap[track];

	if (playing) {
		if (playTrack == track)
			return;
		CDAudio_Stop ();
	}

	playLooping = looping;

	if (track < cd.lowTrack || track > cd.highTrack) {
		Con_DPrintf ("CDAudio_Play: Bad track number %u.\n", track);
		return;
	}

	playTrack = track;

	if (cd.track[track].isData) {
		Con_DPrintf ("CDAudio_Play: Can not play data.\n");
		return;
	}

	cdRequest->headerLength = 13;
	cdRequest->unit = 0;
	cdRequest->command = COMMAND_PLAY_AUDIO;
	cdRequest->status = 0;

	cdRequest->x.playAudio.addressingMode = ADDRESS_MODE_RED_BOOK;
	cdRequest->x.playAudio.startLocation = cd.track[track].start;
	cdRequest->x.playAudio.sectors = cd.track[track].length;

	regs.x.ax = 0x1510;
	regs.x.cx = cdrom;
	regs.x.es = cdRequestSegment;
	regs.x.bx = cdRequestOffset;
	dos_int86 (0x2f);

	if (cdRequest->status & STATUS_ERROR_BIT) {
		Con_DPrintf ("CDAudio_Play: track %u failed\n", track);
		cd.valid = false;
		playing = false;
		return;
	}

	playing = true;
}


void
CDAudio_Stop (void)
{
	if (!initialized || !enabled)
		return;

	cdRequest->headerLength = 13;
	cdRequest->unit = 0;
	cdRequest->command = COMMAND_STOP_AUDIO;
	cdRequest->status = 0;

	regs.x.ax = 0x1510;
	regs.x.cx = cdrom;
	regs.x.es = cdRequestSegment;
	regs.x.bx = cdRequestOffset;
	dos_int86 (0x2f);

	wasPlaying = playing;
	playing = false;
}


void
CDAudio_Resume (void)
{
	if (!initialized || !enabled)
		return;

	if (!cd.valid)
		return;

	if (!wasPlaying)
		return;

	cdRequest->headerLength = 13;
	cdRequest->unit = 0;
	cdRequest->command = COMMAND_RESUME_AUDIO;
	cdRequest->status = 0;

	regs.x.ax = 0x1510;
	regs.x.cx = cdrom;
	regs.x.es = cdRequestSegment;
	regs.x.bx = cdRequestOffset;
	dos_int86 (0x2f);

	playing = true;
}


static void
CD_f (void)
{
	char       *command;
	int         ret;
	int         n;
	int         startAddress;

	if (Cmd_Argc () < 2)
		return;

	command = Cmd_Argv (1);

	if (Q_strcasecmp (command, "on") == 0) {
		enabled = true;
		return;
	}

	if (Q_strcasecmp (command, "off") == 0) {
		if (playing)
			CDAudio_Stop ();
		enabled = false;
		return;
	}

	if (Q_strcasecmp (command, "reset") == 0) {
		enabled = true;
		if (playing)
			CDAudio_Stop ();
		for (n = 0; n < 256; n++)
			remap[n] = n;
		CDAudio_Reset ();
		CDAudio_GetAudioDiskInfo ();
		return;
	}

	if (Q_strcasecmp (command, "remap") == 0) {
		ret = Cmd_Argc () - 2;
		if (ret <= 0) {
			for (n = 1; n < 256; n++)
				if (remap[n] != n)
					Con_Printf ("  %u -> %u\n", n, remap[n]);
			return;
		}
		for (n = 1; n <= ret; n++)
			remap[n] = Q_atoi (Cmd_Argv (n + 1));
		return;
	}

	if (!cd.valid) {
		Con_Printf ("No CD in player.\n");
		return;
	}

	if (Q_strcasecmp (command, "play") == 0) {
		CDAudio_Play (Q_atoi (Cmd_Argv (2)), false);
		return;
	}

	if (Q_strcasecmp (command, "loop") == 0) {
		CDAudio_Play (Q_atoi (Cmd_Argv (2)), true);
		return;
	}

	if (Q_strcasecmp (command, "stop") == 0) {
		CDAudio_Stop ();
		return;
	}

	if (Q_strcasecmp (command, "resume") == 0) {
		CDAudio_Resume ();
		return;
	}

	if (Q_strcasecmp (command, "eject") == 0) {
		if (playing)
			CDAudio_Stop ();
		CDAudio_Eject ();
		cd.valid = false;
		return;
	}

	if (Q_strcasecmp (command, "info") == 0) {
		Con_Printf ("%u tracks\n", cd.highTrack - cd.lowTrack + 1);
		for (n = cd.lowTrack; n <= cd.highTrack; n++) {
			ret = CDAudio_GetAudioTrackInfo (n, &startAddress);
			Con_Printf ("Track %2u: %s at %2u:%02u\n", n,
						ret ? "data " : "music", (startAddress >> 16) & 0xff,
						(startAddress >> 8) & 0xff);
		}
		if (playing)
			Con_Printf ("Currently %s track %u\n",
						playLooping ? "looping" : "playing", playTrack);
		Con_Printf ("Volume is %u\n", cdvolume);
		CDAudio_MediaChange ();
		Con_Printf ("Status %04x\n", cdRequest->status & 0xffff);
		return;
	}
}


void
CDAudio_Update (void)
{
	int         ret;
	int         newVolume;
	static double lastUpdate;

	if (!initialized || !enabled)
		return;

	if ((realtime - lastUpdate) < 0.25)
		return;
	lastUpdate = realtime;

	if (mediaCheck) {
		static double lastCheck;

		if ((realtime - lastCheck) < 5.0)
			return;
		lastCheck = realtime;

		ret = CDAudio_MediaChange ();
		if (ret == MEDIA_CHANGED) {
			Con_DPrintf ("CDAudio: media changed\n");
			playing = false;
			wasPlaying = false;
			cd.valid = false;
			CDAudio_GetAudioDiskInfo ();
			return;
		}
	}

	newVolume = (int) (bgmvolume->value * 255.0);
	if (newVolume < 0) {
		Cvar_SetValue (bgmvolume, 0.0);
		newVolume = 0;
	} else if (newVolume > 255) {
		Cvar_SetValue (bgmvolume, 1.0);
		newVolume = 255;
	}
	if (cdvolume != newVolume)
		CDAudio_SetVolume (newVolume);

	if (playing) {
		CDAudio_GetAudioStatus ();
		if ((cdRequest->status & STATUS_BUSY_BIT) == 0) {
			playing = false;
			if (playLooping)
				CDAudio_Play (playTrack, true);
		}
	}
}


qboolean
CDAudio_Playing (void)
{
	return playing;
}


int
CDAudio_Init (void)
{
	char       *memory;
	int         n;

	if (cls.state == ca_dedicated)
		return -1;

	if (COM_CheckParm ("-nocdaudio"))
		return -1;

	if (COM_CheckParm ("-cdmediacheck"))
		mediaCheck = true;

	regs.x.ax = 0x1500;
	regs.x.bx = 0;
	dos_int86 (0x2f);
	if (regs.x.bx == 0) {
		Con_Printf
			("MSCDEX not loaded, music is disabled.  Use \"-nocdaudio\" if you wish to avoid this message in the future.  See README.TXT for help.\n");
		return -1;
	}
	if (regs.x.bx > 1)
		Con_DPrintf ("CDAudio_Init: First CD-ROM drive will be used\n");
	cdrom = regs.x.cx;

	regs.x.ax = 0x150c;
	regs.x.bx = 0;
	dos_int86 (0x2f);
	if (regs.x.bx == 0) {
		Con_Printf
			("MSCDEX version 2.00 or later required for music. See README.TXT for help.\n");
		return -1;
	}

	memory =

		dos_getmemory (sizeof (struct cd_request) + sizeof (union readInfo_u));
	if (memory == NULL) {
		Con_DPrintf ("CDAudio_Init: Unable to allocate low memory.\n");
		return -1;
	}

	cdRequest = (struct cd_request *) memory;
	cdRequestSegment = ptr2real (cdRequest) >> 4;
	cdRequestOffset = ptr2real (cdRequest) & 0xf;

	readInfo = (union readInfo_u *) (memory + sizeof (struct cd_request));

	readInfoSegment = ptr2real (readInfo) >> 4;
	readInfoOffset = ptr2real (readInfo) & 0xf;

	for (n = 0; n < 256; n++)
		remap[n] = n;
	initialized = true;

	CDAudio_SetVolume (255);
	if (CDAudio_GetAudioDiskInfo ()) {
		Con_Printf ("CDAudio_Init: No CD in player.\n");
		enabled = false;
	}

	Cmd_AddCommand ("cd", CD_f, "Control the CD player.\n"
		"Commands:\n"
		"eject - Eject the CD.\n"
		"info - Reports information on the CD.\n"
		"loop (track number) - Loops the specified track.\n"
		"remap (track1) (track2) ... - Remap the current track order.\n"
		"reset - Causes the CD audio to re-initialize.\n"
		"resume - Will resume playback after pause.\n"
		"off - Shuts down the CD audio system..\n"
		"on - Re-enables the CD audio system after a cd off command.\n"
		"pause - Pause the CD playback.\n"
		"play (track number) - Plays the specified track one time.\n"
		"stop - Stops the currently playing track.");

	Con_Printf ("CD Audio Initialized\n");

	return 0;
}


void
CDAudio_Shutdown (void)
{
	if (!initialized)
		return;
	CDAudio_Stop ();
}