/*
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 the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/
// Quake is a trademark of Id Software, Inc., (c) 1996 Id Software, Inc. All
// rights reserved.

// FIXME - I don't even know what this is for...

#include <dpmi.h>
#include "quakedef.h"
#include "dosisms.h"

// FIXME - header hacks
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()

// FIXME - NQ -> 100, QW -> 32
//#define MAXIMUM_TRACKS 100
#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)
{
    int volume;

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

    volume = (int)(bgmvolume.value * 255.0);
    if (volume < 0) {
	Cvar_SetValue("bgmvolume", 0.0);
	volume = 0;
    } else if (volume > 255) {
	Cvar_SetValue("bgmvolume", 1.0);
	volume = 255;
    }
    CDAudio_SetVolume(volume);

    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_Pause(void)
{
    CDAudio_Stop();
}


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 (strcasecmp(command, "on") == 0) {
	enabled = true;
	return;
    }

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

    if (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 (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 (strcasecmp(command, "play") == 0) {
	CDAudio_Play(Q_atoi(Cmd_Argv(2)), false);
	return;
    }

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

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

    if (strcasecmp(command, "pause") == 0) {
	CDAudio_Pause();
	return;
    }

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

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

    if (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 != cdvolume) {
	if (newVolume < 0) {
	    Cvar_SetValue("bgmvolume", 0.0);
	    newVolume = 0;
	} else if (newVolume > 255) {
	    Cvar_SetValue("bgmvolume", 1.0);
	    newVolume = 255;
	}
	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_NotifyBox("MSCDEX not loaded, music is\n"
		      "disabled.  Use \"-nocdaudio\" if you\n"
		      "wish to avoid this message in the\n"
		      "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_NotifyBox("MSCDEX version 2.00 or later\n"
		      "required for music. See README.TXT\n" "for help.\n");
	Con_DPrintf("CDAudio_Init: MSCDEX version 2.00 or later required.\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);

    Con_Printf("CD Audio Initialized\n");

    return 0;
}


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