2 Copyright (C) 1996-1997 Id Software, Inc.
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13 See the GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
20 // Quake is a trademark of Id Software, Inc., (c) 1996 Id Software, Inc. All
27 extern cvar_t bgmvolume
;
29 #define ADDRESS_MODE_HSG 0
30 #define ADDRESS_MODE_RED_BOOK 1
32 #define STATUS_ERROR_BIT 0x8000
33 #define STATUS_BUSY_BIT 0x0200
34 #define STATUS_DONE_BIT 0x0100
35 #define STATUS_ERROR_MASK 0x00ff
37 #define ERROR_WRITE_PROTECT 0
38 #define ERROR_UNKNOWN_UNIT 1
39 #define ERROR_DRIVE_NOT_READY 2
40 #define ERROR_UNKNOWN_COMMAND 3
41 #define ERROR_CRC_ERROR 4
42 #define ERROR_BAD_REQUEST_LEN 5
43 #define ERROR_SEEK_ERROR 6
44 #define ERROR_UNKNOWN_MEDIA 7
45 #define ERROR_SECTOR_NOT_FOUND 8
46 #define ERROR_OUT_OF_PAPER 9
47 #define ERROR_WRITE_FAULT 10
48 #define ERROR_READ_FAULT 11
49 #define ERROR_GENERAL_FAILURE 12
50 #define ERROR_RESERVED_13 13
51 #define ERROR_RESERVED_14 14
52 #define ERROR_BAD_DISK_CHANGE 15
54 #define COMMAND_READ 3
55 #define COMMAND_WRITE 12
56 #define COMMAND_PLAY_AUDIO 132
57 #define COMMAND_STOP_AUDIO 133
58 #define COMMAND_RESUME_AUDIO 136
60 #define READ_REQUEST_AUDIO_CHANNEL_INFO 4
61 #define READ_REQUEST_DEVICE_STATUS 6
62 #define READ_REQUEST_MEDIA_CHANGE 9
63 #define READ_REQUEST_AUDIO_DISK_INFO 10
64 #define READ_REQUEST_AUDIO_TRACK_INFO 11
65 #define READ_REQUEST_AUDIO_STATUS 15
67 #define WRITE_REQUEST_EJECT 0
68 #define WRITE_REQUEST_RESET 2
69 #define WRITE_REQUEST_AUDIO_CHANNEL_INFO 3
71 #define STATUS_DOOR_OPEN 0x00000001
72 #define STATUS_DOOR_UNLOCKED 0x00000002
73 #define STATUS_RAW_SUPPORT 0x00000004
74 #define STATUS_READ_WRITE 0x00000008
75 #define STATUS_AUDIO_SUPPORT 0x00000010
76 #define STATUS_INTERLEAVE_SUPPORT 0x00000020
77 #define STATUS_BIT_6_RESERVED 0x00000040
78 #define STATUS_PREFETCH_SUPPORT 0x00000080
79 #define STATUS_AUDIO_MANIPLUATION_SUPPORT 0x00000100
80 #define STATUS_RED_BOOK_ADDRESS_SUPPORT 0x00000200
82 #define MEDIA_NOT_CHANGED 1
83 #define MEDIA_STATUS_UNKNOWN 0
84 #define MEDIA_CHANGED -1
86 #define AUDIO_CONTROL_MASK 0xd0
87 #define AUDIO_CONTROL_DATA_TRACK 0x40
88 #define AUDIO_CONTROL_AUDIO_2_TRACK 0x00
89 #define AUDIO_CONTROL_AUDIO_2P_TRACK 0x10
90 #define AUDIO_CONTROL_AUDIO_4_TRACK 0x80
91 #define AUDIO_CONTROL_AUDIO_4P_TRACK 0x90
93 #define AUDIO_STATUS_PAUSED 0x0001
97 struct playAudioRequest
106 char mediaDescriptor
;
116 char mediaDescriptor
;
133 struct playAudioRequest playAudio
;
134 struct readRequest read
;
135 struct writeRequest write
;
140 struct audioChannelInfo_s
153 struct deviceStatus_s
165 struct audioDiskInfo_s
173 struct audioTrackInfo_s
196 struct audioChannelInfo_s audioChannelInfo
;
197 struct deviceStatus_s deviceStatus
;
198 struct mediaChange_s mediaChange
;
199 struct audioDiskInfo_s audioDiskInfo
;
200 struct audioTrackInfo_s audioTrackInfo
;
201 struct audioStatus_s audioStatus
;
202 struct reset_s reset
;
207 #define MAXIMUM_TRACKS 100
220 track_info track
[MAXIMUM_TRACKS
];
225 static struct cd_request
*cdRequest
;
226 static union readInfo_u
*readInfo
;
229 static qboolean playing
= false;
230 static qboolean wasPlaying
= false;
231 static qboolean mediaCheck
= false;
232 static qboolean initialized
= false;
233 static qboolean enabled
= true;
234 static qboolean playLooping
= false;
235 static short cdRequestSegment
;
236 static short cdRequestOffset
;
237 static short readInfoSegment
;
238 static short readInfoOffset
;
239 static byte remap
[256];
241 static byte playTrack
;
242 static byte cdvolume
;
245 static int RedBookToSector(int rb
)
251 minute
= (rb
>> 16) & 0xff;
252 second
= (rb
>> 8) & 0xff;
254 return minute
* 60 * 75 + second
* 75 + frame
;
258 static void CDAudio_Reset(void)
260 cdRequest
->headerLength
= 13;
262 cdRequest
->command
= COMMAND_WRITE
;
263 cdRequest
->status
= 0;
265 cdRequest
->x
.write
.mediaDescriptor
= 0;
266 cdRequest
->x
.write
.bufferOffset
= readInfoOffset
;
267 cdRequest
->x
.write
.bufferSegment
= readInfoSegment
;
268 cdRequest
->x
.write
.length
= sizeof(struct reset_s
);
269 cdRequest
->x
.write
.startSector
= 0;
270 cdRequest
->x
.write
.volumeID
= 0;
272 readInfo
->reset
.code
= WRITE_REQUEST_RESET
;
276 regs
.x
.es
= cdRequestSegment
;
277 regs
.x
.bx
= cdRequestOffset
;
282 static void CDAudio_Eject(void)
284 cdRequest
->headerLength
= 13;
286 cdRequest
->command
= COMMAND_WRITE
;
287 cdRequest
->status
= 0;
289 cdRequest
->x
.write
.mediaDescriptor
= 0;
290 cdRequest
->x
.write
.bufferOffset
= readInfoOffset
;
291 cdRequest
->x
.write
.bufferSegment
= readInfoSegment
;
292 cdRequest
->x
.write
.length
= sizeof(struct reset_s
);
293 cdRequest
->x
.write
.startSector
= 0;
294 cdRequest
->x
.write
.volumeID
= 0;
296 readInfo
->reset
.code
= WRITE_REQUEST_EJECT
;
300 regs
.x
.es
= cdRequestSegment
;
301 regs
.x
.bx
= cdRequestOffset
;
306 static int CDAudio_GetAudioTrackInfo(byte track
, int *start
)
310 cdRequest
->headerLength
= 13;
312 cdRequest
->command
= COMMAND_READ
;
313 cdRequest
->status
= 0;
315 cdRequest
->x
.read
.mediaDescriptor
= 0;
316 cdRequest
->x
.read
.bufferOffset
= readInfoOffset
;
317 cdRequest
->x
.read
.bufferSegment
= readInfoSegment
;
318 cdRequest
->x
.read
.length
= sizeof(struct audioTrackInfo_s
);
319 cdRequest
->x
.read
.startSector
= 0;
320 cdRequest
->x
.read
.volumeID
= 0;
322 readInfo
->audioTrackInfo
.code
= READ_REQUEST_AUDIO_TRACK_INFO
;
323 readInfo
->audioTrackInfo
.track
= track
;
327 regs
.x
.es
= cdRequestSegment
;
328 regs
.x
.bx
= cdRequestOffset
;
331 if (cdRequest
->status
& STATUS_ERROR_BIT
)
333 Con_DPrintf("CDAudio_GetAudioTrackInfo %04x\n", cdRequest
->status
& 0xffff);
337 *start
= readInfo
->audioTrackInfo
.start
;
338 control
= readInfo
->audioTrackInfo
.control
& AUDIO_CONTROL_MASK
;
339 return (control
& AUDIO_CONTROL_DATA_TRACK
);
343 static int CDAudio_GetAudioDiskInfo(void)
347 cdRequest
->headerLength
= 13;
349 cdRequest
->command
= COMMAND_READ
;
350 cdRequest
->status
= 0;
352 cdRequest
->x
.read
.mediaDescriptor
= 0;
353 cdRequest
->x
.read
.bufferOffset
= readInfoOffset
;
354 cdRequest
->x
.read
.bufferSegment
= readInfoSegment
;
355 cdRequest
->x
.read
.length
= sizeof(struct audioDiskInfo_s
);
356 cdRequest
->x
.read
.startSector
= 0;
357 cdRequest
->x
.read
.volumeID
= 0;
359 readInfo
->audioDiskInfo
.code
= READ_REQUEST_AUDIO_DISK_INFO
;
363 regs
.x
.es
= cdRequestSegment
;
364 regs
.x
.bx
= cdRequestOffset
;
367 if (cdRequest
->status
& STATUS_ERROR_BIT
)
369 Con_DPrintf("CDAudio_GetAudioDiskInfo %04x\n", cdRequest
->status
& 0xffff);
374 cd
.lowTrack
= readInfo
->audioDiskInfo
.lowTrack
;
375 cd
.highTrack
= readInfo
->audioDiskInfo
.highTrack
;
376 cd
.leadOutAddress
= readInfo
->audioDiskInfo
.leadOutStart
;
378 for (n
= cd
.lowTrack
; n
<= cd
.highTrack
; n
++)
380 cd
.track
[n
].isData
= CDAudio_GetAudioTrackInfo (n
, &cd
.track
[n
].start
);
383 cd
.track
[n
-1].length
= RedBookToSector(cd
.track
[n
].start
) - RedBookToSector(cd
.track
[n
-1].start
);
384 if (n
== cd
.highTrack
)
385 cd
.track
[n
].length
= RedBookToSector(cd
.leadOutAddress
) - RedBookToSector(cd
.track
[n
].start
);
393 static int CDAudio_GetAudioStatus(void)
395 cdRequest
->headerLength
= 13;
397 cdRequest
->command
= COMMAND_READ
;
398 cdRequest
->status
= 0;
400 cdRequest
->x
.read
.mediaDescriptor
= 0;
401 cdRequest
->x
.read
.bufferOffset
= readInfoOffset
;
402 cdRequest
->x
.read
.bufferSegment
= readInfoSegment
;
403 cdRequest
->x
.read
.length
= sizeof(struct audioStatus_s
);
404 cdRequest
->x
.read
.startSector
= 0;
405 cdRequest
->x
.read
.volumeID
= 0;
407 readInfo
->audioDiskInfo
.code
= READ_REQUEST_AUDIO_STATUS
;
411 regs
.x
.es
= cdRequestSegment
;
412 regs
.x
.bx
= cdRequestOffset
;
415 if (cdRequest
->status
& STATUS_ERROR_BIT
)
421 static int CDAudio_MediaChange(void)
423 cdRequest
->headerLength
= 13;
425 cdRequest
->command
= COMMAND_READ
;
426 cdRequest
->status
= 0;
428 cdRequest
->x
.read
.mediaDescriptor
= 0;
429 cdRequest
->x
.read
.bufferOffset
= readInfoOffset
;
430 cdRequest
->x
.read
.bufferSegment
= readInfoSegment
;
431 cdRequest
->x
.read
.length
= sizeof(struct mediaChange_s
);
432 cdRequest
->x
.read
.startSector
= 0;
433 cdRequest
->x
.read
.volumeID
= 0;
435 readInfo
->mediaChange
.code
= READ_REQUEST_MEDIA_CHANGE
;
439 regs
.x
.es
= cdRequestSegment
;
440 regs
.x
.bx
= cdRequestOffset
;
443 return readInfo
->mediaChange
.status
;
447 // we set the volume to 0 first and then to the desired volume
448 // some cd-rom drivers seem to need it done this way
449 void CDAudio_SetVolume (byte volume
)
451 if (!initialized
|| !enabled
)
454 cdRequest
->headerLength
= 13;
456 cdRequest
->command
= COMMAND_WRITE
;
457 cdRequest
->status
= 0;
459 cdRequest
->x
.read
.mediaDescriptor
= 0;
460 cdRequest
->x
.read
.bufferOffset
= readInfoOffset
;
461 cdRequest
->x
.read
.bufferSegment
= readInfoSegment
;
462 cdRequest
->x
.read
.length
= sizeof(struct audioChannelInfo_s
);
463 cdRequest
->x
.read
.startSector
= 0;
464 cdRequest
->x
.read
.volumeID
= 0;
466 readInfo
->audioChannelInfo
.code
= WRITE_REQUEST_AUDIO_CHANNEL_INFO
;
467 readInfo
->audioChannelInfo
.channel0input
= 0;
468 readInfo
->audioChannelInfo
.channel0volume
= 0;
469 readInfo
->audioChannelInfo
.channel1input
= 1;
470 readInfo
->audioChannelInfo
.channel1volume
= 0;
471 readInfo
->audioChannelInfo
.channel2input
= 2;
472 readInfo
->audioChannelInfo
.channel2volume
= 0;
473 readInfo
->audioChannelInfo
.channel3input
= 3;
474 readInfo
->audioChannelInfo
.channel3volume
= 0;
478 regs
.x
.es
= cdRequestSegment
;
479 regs
.x
.bx
= cdRequestOffset
;
482 readInfo
->audioChannelInfo
.channel0volume
= volume
;
483 readInfo
->audioChannelInfo
.channel1volume
= volume
;
487 regs
.x
.es
= cdRequestSegment
;
488 regs
.x
.bx
= cdRequestOffset
;
495 void CDAudio_Play(byte track
, qboolean looping
)
499 if (!initialized
|| !enabled
)
505 track
= remap
[track
];
509 if (playTrack
== track
)
514 playLooping
= looping
;
516 if (track
< cd
.lowTrack
|| track
> cd
.highTrack
)
518 Con_DPrintf("CDAudio_Play: Bad track number %u.\n", track
);
524 if (cd
.track
[track
].isData
)
526 Con_DPrintf("CDAudio_Play: Can not play data.\n");
530 volume
= (int)(bgmvolume
.value
* 255.0);
533 Cvar_SetValue ("bgmvolume", 0.0);
536 else if (volume
> 255)
538 Cvar_SetValue ("bgmvolume", 1.0);
541 CDAudio_SetVolume (volume
);
543 cdRequest
->headerLength
= 13;
545 cdRequest
->command
= COMMAND_PLAY_AUDIO
;
546 cdRequest
->status
= 0;
548 cdRequest
->x
.playAudio
.addressingMode
= ADDRESS_MODE_RED_BOOK
;
549 cdRequest
->x
.playAudio
.startLocation
= cd
.track
[track
].start
;
550 cdRequest
->x
.playAudio
.sectors
= cd
.track
[track
].length
;
554 regs
.x
.es
= cdRequestSegment
;
555 regs
.x
.bx
= cdRequestOffset
;
558 if (cdRequest
->status
& STATUS_ERROR_BIT
)
560 Con_DPrintf("CDAudio_Play: track %u failed\n", track
);
570 void CDAudio_Stop(void)
572 if (!initialized
|| !enabled
)
575 cdRequest
->headerLength
= 13;
577 cdRequest
->command
= COMMAND_STOP_AUDIO
;
578 cdRequest
->status
= 0;
582 regs
.x
.es
= cdRequestSegment
;
583 regs
.x
.bx
= cdRequestOffset
;
586 wasPlaying
= playing
;
591 void CDAudio_Pause(void)
597 void CDAudio_Resume(void)
599 if (!initialized
|| !enabled
)
608 cdRequest
->headerLength
= 13;
610 cdRequest
->command
= COMMAND_RESUME_AUDIO
;
611 cdRequest
->status
= 0;
615 regs
.x
.es
= cdRequestSegment
;
616 regs
.x
.bx
= cdRequestOffset
;
623 static void CD_f (void)
633 command
= Cmd_Argv (1);
635 if (Q_strcasecmp(command
, "on") == 0)
641 if (Q_strcasecmp(command
, "off") == 0)
649 if (Q_strcasecmp(command
, "reset") == 0)
654 for (n
= 0; n
< 256; n
++)
657 CDAudio_GetAudioDiskInfo();
661 if (Q_strcasecmp(command
, "remap") == 0)
663 ret
= Cmd_Argc() - 2;
666 for (n
= 1; n
< 256; n
++)
668 Con_Printf(" %u -> %u\n", n
, remap
[n
]);
671 for (n
= 1; n
<= ret
; n
++)
672 remap
[n
] = Q_atoi(Cmd_Argv (n
+1));
678 Con_Printf("No CD in player.\n");
682 if (Q_strcasecmp(command
, "play") == 0)
684 CDAudio_Play(Q_atoi(Cmd_Argv (2)), false);
688 if (Q_strcasecmp(command
, "loop") == 0)
690 CDAudio_Play(Q_atoi(Cmd_Argv (2)), true);
694 if (Q_strcasecmp(command
, "stop") == 0)
700 if (Q_strcasecmp(command
, "pause") == 0)
706 if (Q_strcasecmp(command
, "resume") == 0)
712 if (Q_strcasecmp(command
, "eject") == 0)
721 if (Q_strcasecmp(command
, "info") == 0)
723 Con_Printf("%u tracks\n", cd
.highTrack
- cd
.lowTrack
+ 1);
724 for (n
= cd
.lowTrack
; n
<= cd
.highTrack
; n
++)
726 ret
= CDAudio_GetAudioTrackInfo (n
, &startAddress
);
727 Con_Printf("Track %2u: %s at %2u:%02u\n", n
, ret
? "data " : "music", (startAddress
>> 16) & 0xff, (startAddress
>> 8) & 0xff);
730 Con_Printf("Currently %s track %u\n", playLooping
? "looping" : "playing", playTrack
);
731 Con_Printf("Volume is %u\n", cdvolume
);
732 CDAudio_MediaChange();
733 Con_Printf("Status %04x\n", cdRequest
->status
& 0xffff);
739 void CDAudio_Update(void)
743 static double lastUpdate
;
745 if (!initialized
|| !enabled
)
748 if ((realtime
- lastUpdate
) < 0.25)
750 lastUpdate
= realtime
;
754 static double lastCheck
;
756 if ((realtime
- lastCheck
) < 5.0)
758 lastCheck
= realtime
;
760 ret
= CDAudio_MediaChange();
761 if (ret
== MEDIA_CHANGED
)
763 Con_DPrintf("CDAudio: media changed\n");
767 CDAudio_GetAudioDiskInfo();
772 newVolume
= (int)(bgmvolume
.value
* 255.0);
773 if (newVolume
!= cdvolume
)
777 Cvar_SetValue ("bgmvolume", 0.0);
780 else if (newVolume
> 255)
782 Cvar_SetValue ("bgmvolume", 1.0);
785 CDAudio_SetVolume (newVolume
);
790 CDAudio_GetAudioStatus();
791 if ((cdRequest
->status
& STATUS_BUSY_BIT
) == 0)
795 CDAudio_Play(playTrack
, true);
801 int CDAudio_Init(void)
806 if (cls
.state
== ca_dedicated
)
809 if (COM_CheckParm("-nocdaudio"))
812 if (COM_CheckParm("-cdmediacheck"))
821 "MSCDEX not loaded, music is\n"
822 "disabled. Use \"-nocdaudio\" if you\n"
823 "wish to avoid this message in the\n"
824 "future. See README.TXT for help.\n"
829 Con_DPrintf("CDAudio_Init: First CD-ROM drive will be used\n");
838 "MSCDEX version 2.00 or later\n"
839 "required for music. See README.TXT\n"
842 Con_DPrintf("CDAudio_Init: MSCDEX version 2.00 or later required.\n");
846 memory
= dos_getmemory(sizeof(struct cd_request
847 ) + sizeof(union readInfo_u
));
850 Con_DPrintf("CDAudio_Init: Unable to allocate low memory.\n");
854 cdRequest
= (struct cd_request
*)memory
;
855 cdRequestSegment
= ptr2real(cdRequest
) >> 4;
856 cdRequestOffset
= ptr2real(cdRequest
) & 0xf;
858 readInfo
= (union readInfo_u
*)(memory
+ sizeof(struct cd_request
));
859 readInfoSegment
= ptr2real(readInfo
) >> 4;
860 readInfoOffset
= ptr2real(readInfo
) & 0xf;
862 for (n
= 0; n
< 256; n
++)
866 CDAudio_SetVolume (255);
867 if (CDAudio_GetAudioDiskInfo())
869 Con_Printf("CDAudio_Init: No CD in player.\n");
873 Cmd_AddCommand ("cd", CD_f
);
875 Con_Printf("CD Audio Initialized\n");
881 void CDAudio_Shutdown(void)