/* 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 #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 (); }