quakeforge/libs/audio/cd/cd_dos.c

872 lines
19 KiB
C
Raw Normal View History

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