cd_audio.c revision 9fd67c44777b350dc56f3e70c88963b0d966ffc7
1#include <dpmi.h>
2#include "quakedef.h"
3#include "dosisms.h"
4
5extern	cvar_t	bgmvolume;
6
7#define ADDRESS_MODE_HSG		0
8#define ADDRESS_MODE_RED_BOOK	1
9
10#define STATUS_ERROR_BIT	0x8000
11#define STATUS_BUSY_BIT		0x0200
12#define STATUS_DONE_BIT		0x0100
13#define STATUS_ERROR_MASK	0x00ff
14
15#define ERROR_WRITE_PROTECT		0
16#define ERROR_UNKNOWN_UNIT		1
17#define ERROR_DRIVE_NOT_READY	2
18#define ERROR_UNKNOWN_COMMAND	3
19#define ERROR_CRC_ERROR			4
20#define ERROR_BAD_REQUEST_LEN	5
21#define ERROR_SEEK_ERROR		6
22#define ERROR_UNKNOWN_MEDIA		7
23#define ERROR_SECTOR_NOT_FOUND	8
24#define ERROR_OUT_OF_PAPER		9
25#define ERROR_WRITE_FAULT		10
26#define ERROR_READ_FAULT		11
27#define ERROR_GENERAL_FAILURE	12
28#define ERROR_RESERVED_13		13
29#define ERROR_RESERVED_14		14
30#define ERROR_BAD_DISK_CHANGE	15
31
32#define COMMAND_READ			3
33#define COMMAND_WRITE			12
34#define COMMAND_PLAY_AUDIO		132
35#define COMMAND_STOP_AUDIO		133
36#define COMMAND_RESUME_AUDIO	136
37
38#define READ_REQUEST_AUDIO_CHANNEL_INFO		4
39#define READ_REQUEST_DEVICE_STATUS			6
40#define READ_REQUEST_MEDIA_CHANGE			9
41#define READ_REQUEST_AUDIO_DISK_INFO		10
42#define READ_REQUEST_AUDIO_TRACK_INFO		11
43#define READ_REQUEST_AUDIO_STATUS			15
44
45#define WRITE_REQUEST_EJECT					0
46#define WRITE_REQUEST_RESET					2
47#define WRITE_REQUEST_AUDIO_CHANNEL_INFO	3
48
49#define STATUS_DOOR_OPEN					0x00000001
50#define STATUS_DOOR_UNLOCKED				0x00000002
51#define STATUS_RAW_SUPPORT					0x00000004
52#define STATUS_READ_WRITE					0x00000008
53#define STATUS_AUDIO_SUPPORT				0x00000010
54#define STATUS_INTERLEAVE_SUPPORT			0x00000020
55#define STATUS_BIT_6_RESERVED				0x00000040
56#define STATUS_PREFETCH_SUPPORT				0x00000080
57#define STATUS_AUDIO_MANIPLUATION_SUPPORT	0x00000100
58#define STATUS_RED_BOOK_ADDRESS_SUPPORT		0x00000200
59
60#define MEDIA_NOT_CHANGED		1
61#define MEDIA_STATUS_UNKNOWN	0
62#define MEDIA_CHANGED			-1
63
64#define AUDIO_CONTROL_MASK				0xd0
65#define AUDIO_CONTROL_DATA_TRACK		0x40
66#define AUDIO_CONTROL_AUDIO_2_TRACK		0x00
67#define AUDIO_CONTROL_AUDIO_2P_TRACK	0x10
68#define AUDIO_CONTROL_AUDIO_4_TRACK		0x80
69#define AUDIO_CONTROL_AUDIO_4P_TRACK	0x90
70
71#define AUDIO_STATUS_PAUSED				0x0001
72
73#pragma pack(1)
74
75struct playAudioRequest
76{
77	char	addressingMode;
78	int		startLocation;
79	int		sectors;
80};
81
82struct readRequest
83{
84	char	mediaDescriptor;
85	short	bufferOffset;
86	short	bufferSegment;
87	short	length;
88	short	startSector;
89	int		volumeID;
90};
91
92struct writeRequest
93{
94	char	mediaDescriptor;
95	short	bufferOffset;
96	short	bufferSegment;
97	short	length;
98	short	startSector;
99	int		volumeID;
100};
101
102struct cd_request
103{
104	char	headerLength;
105	char	unit;
106	char	command;
107	short	status;
108	char	reserved[8];
109	union
110	{
111		struct	playAudioRequest	playAudio;
112		struct	readRequest			read;
113		struct	writeRequest		write;
114	} x;
115};
116
117
118struct audioChannelInfo_s
119{
120	char	code;
121	char	channel0input;
122	char	channel0volume;
123	char	channel1input;
124	char	channel1volume;
125	char	channel2input;
126	char	channel2volume;
127	char	channel3input;
128	char	channel3volume;
129};
130
131struct deviceStatus_s
132{
133	char	code;
134	int		status;
135};
136
137struct mediaChange_s
138{
139	char	code;
140	char	status;
141};
142
143struct audioDiskInfo_s
144{
145	char	code;
146	char	lowTrack;
147	char	highTrack;
148	int		leadOutStart;
149};
150
151struct audioTrackInfo_s
152{
153	char	code;
154	char	track;
155	int		start;
156	char	control;
157};
158
159struct audioStatus_s
160{
161	char	code;
162	short	status;
163	int		PRstartLocation;
164	int		PRendLocation;
165};
166
167struct reset_s
168{
169	char	code;
170};
171
172union readInfo_u
173{
174	struct audioChannelInfo_s	audioChannelInfo;
175	struct deviceStatus_s		deviceStatus;
176	struct mediaChange_s		mediaChange;
177	struct audioDiskInfo_s		audioDiskInfo;
178	struct audioTrackInfo_s		audioTrackInfo;
179	struct audioStatus_s		audioStatus;
180	struct reset_s				reset;
181};
182
183#pragma pack()
184
185#define MAXIMUM_TRACKS			32
186
187typedef struct
188{
189	int			start;
190	int			length;
191	qboolean	isData;
192} track_info;
193
194typedef struct
195{
196	qboolean	valid;
197	int			leadOutAddress;
198	track_info	track[MAXIMUM_TRACKS];
199	byte		lowTrack;
200	byte		highTrack;
201} cd_info;
202
203static struct cd_request	*cdRequest;
204static union readInfo_u		*readInfo;
205static cd_info				cd;
206
207static qboolean	playing = false;
208static qboolean	wasPlaying = false;
209static qboolean	mediaCheck = false;
210static qboolean	initialized = false;
211static qboolean	enabled = true;
212static qboolean playLooping = false;
213static short	cdRequestSegment;
214static short	cdRequestOffset;
215static short	readInfoSegment;
216static short	readInfoOffset;
217static byte 	remap[256];
218static byte		cdrom;
219static byte		playTrack;
220static byte		cdvolume;
221
222
223static int RedBookToSector(int rb)
224{
225	byte	minute;
226	byte	second;
227	byte	frame;
228
229	minute = (rb >> 16) & 0xff;
230	second = (rb >> 8) & 0xff;
231	frame = rb & 0xff;
232	return minute * 60 * 75 + second * 75 + frame;
233}
234
235
236static void CDAudio_Reset(void)
237{
238	cdRequest->headerLength = 13;
239	cdRequest->unit = 0;
240	cdRequest->command = COMMAND_WRITE;
241	cdRequest->status = 0;
242
243	cdRequest->x.write.mediaDescriptor = 0;
244	cdRequest->x.write.bufferOffset = readInfoOffset;
245	cdRequest->x.write.bufferSegment = readInfoSegment;
246	cdRequest->x.write.length = sizeof(struct reset_s);
247	cdRequest->x.write.startSector = 0;
248	cdRequest->x.write.volumeID = 0;
249
250	readInfo->reset.code = WRITE_REQUEST_RESET;
251
252	regs.x.ax = 0x1510;
253	regs.x.cx = cdrom;
254	regs.x.es = cdRequestSegment;
255	regs.x.bx = cdRequestOffset;
256	dos_int86 (0x2f);
257}
258
259
260static void CDAudio_Eject(void)
261{
262	cdRequest->headerLength = 13;
263	cdRequest->unit = 0;
264	cdRequest->command = COMMAND_WRITE;
265	cdRequest->status = 0;
266
267	cdRequest->x.write.mediaDescriptor = 0;
268	cdRequest->x.write.bufferOffset = readInfoOffset;
269	cdRequest->x.write.bufferSegment = readInfoSegment;
270	cdRequest->x.write.length = sizeof(struct reset_s);
271	cdRequest->x.write.startSector = 0;
272	cdRequest->x.write.volumeID = 0;
273
274	readInfo->reset.code = WRITE_REQUEST_EJECT;
275
276	regs.x.ax = 0x1510;
277	regs.x.cx = cdrom;
278	regs.x.es = cdRequestSegment;
279	regs.x.bx = cdRequestOffset;
280	dos_int86 (0x2f);
281}
282
283
284static int CDAudio_GetAudioTrackInfo(byte track, int *start)
285{
286	byte	control;
287
288	cdRequest->headerLength = 13;
289	cdRequest->unit = 0;
290	cdRequest->command = COMMAND_READ;
291	cdRequest->status = 0;
292
293	cdRequest->x.read.mediaDescriptor = 0;
294	cdRequest->x.read.bufferOffset = readInfoOffset;
295	cdRequest->x.read.bufferSegment = readInfoSegment;
296	cdRequest->x.read.length = sizeof(struct audioTrackInfo_s);
297	cdRequest->x.read.startSector = 0;
298	cdRequest->x.read.volumeID = 0;
299
300	readInfo->audioTrackInfo.code = READ_REQUEST_AUDIO_TRACK_INFO;
301	readInfo->audioTrackInfo.track = track;
302
303	regs.x.ax = 0x1510;
304	regs.x.cx = cdrom;
305	regs.x.es = cdRequestSegment;
306	regs.x.bx = cdRequestOffset;
307	dos_int86 (0x2f);
308
309	if (cdRequest->status & STATUS_ERROR_BIT)
310	{
311		Con_DPrintf("CDAudio_GetAudioTrackInfo %04x\n", cdRequest->status & 	0xffff);
312		return -1;
313	}
314
315	*start = readInfo->audioTrackInfo.start;
316	control = readInfo->audioTrackInfo.control & AUDIO_CONTROL_MASK;
317	return (control & AUDIO_CONTROL_DATA_TRACK);
318}
319
320
321static int CDAudio_GetAudioDiskInfo(void)
322{
323	int n;
324
325	cdRequest->headerLength = 13;
326	cdRequest->unit = 0;
327	cdRequest->command = COMMAND_READ;
328	cdRequest->status = 0;
329
330	cdRequest->x.read.mediaDescriptor = 0;
331	cdRequest->x.read.bufferOffset = readInfoOffset;
332	cdRequest->x.read.bufferSegment = readInfoSegment;
333	cdRequest->x.read.length = sizeof(struct audioDiskInfo_s);
334	cdRequest->x.read.startSector = 0;
335	cdRequest->x.read.volumeID = 0;
336
337	readInfo->audioDiskInfo.code = READ_REQUEST_AUDIO_DISK_INFO;
338
339	regs.x.ax = 0x1510;
340	regs.x.cx = cdrom;
341	regs.x.es = cdRequestSegment;
342	regs.x.bx = cdRequestOffset;
343	dos_int86 (0x2f);
344
345	if (cdRequest->status & STATUS_ERROR_BIT)
346	{
347		Con_DPrintf("CDAudio_GetAudioDiskInfo %04x\n", cdRequest->status & 	0xffff);
348		return -1;
349	}
350
351	cd.valid = true;
352	cd.lowTrack = readInfo->audioDiskInfo.lowTrack;
353	cd.highTrack = readInfo->audioDiskInfo.highTrack;
354	cd.leadOutAddress = readInfo->audioDiskInfo.leadOutStart;
355
356	for (n = cd.lowTrack; n <= cd.highTrack; n++)
357	{
358		cd.track[n].isData = CDAudio_GetAudioTrackInfo (n, &cd.track[n].start);
359		if (n > cd.lowTrack)
360		{
361			cd.track[n-1].length = RedBookToSector(cd.track[n].start) - RedBookToSector(cd.track[n-1].start);
362			if (n == cd.highTrack)
363				cd.track[n].length = RedBookToSector(cd.leadOutAddress) - RedBookToSector(cd.track[n].start);
364		}
365	}
366
367	return 0;
368}
369
370
371static int CDAudio_GetAudioStatus(void)
372{
373	cdRequest->headerLength = 13;
374	cdRequest->unit = 0;
375	cdRequest->command = COMMAND_READ;
376	cdRequest->status = 0;
377
378	cdRequest->x.read.mediaDescriptor = 0;
379	cdRequest->x.read.bufferOffset = readInfoOffset;
380	cdRequest->x.read.bufferSegment = readInfoSegment;
381	cdRequest->x.read.length = sizeof(struct audioStatus_s);
382	cdRequest->x.read.startSector = 0;
383	cdRequest->x.read.volumeID = 0;
384
385	readInfo->audioDiskInfo.code = READ_REQUEST_AUDIO_STATUS;
386
387	regs.x.ax = 0x1510;
388	regs.x.cx = cdrom;
389	regs.x.es = cdRequestSegment;
390	regs.x.bx = cdRequestOffset;
391	dos_int86 (0x2f);
392
393	if (cdRequest->status & STATUS_ERROR_BIT)
394		return -1;
395	return 0;
396}
397
398
399static int CDAudio_MediaChange(void)
400{
401	cdRequest->headerLength = 13;
402	cdRequest->unit = 0;
403	cdRequest->command = COMMAND_READ;
404	cdRequest->status = 0;
405
406	cdRequest->x.read.mediaDescriptor = 0;
407	cdRequest->x.read.bufferOffset = readInfoOffset;
408	cdRequest->x.read.bufferSegment = readInfoSegment;
409	cdRequest->x.read.length = sizeof(struct mediaChange_s);
410	cdRequest->x.read.startSector = 0;
411	cdRequest->x.read.volumeID = 0;
412
413	readInfo->mediaChange.code = READ_REQUEST_MEDIA_CHANGE;
414
415	regs.x.ax = 0x1510;
416	regs.x.cx = cdrom;
417	regs.x.es = cdRequestSegment;
418	regs.x.bx = cdRequestOffset;
419	dos_int86 (0x2f);
420
421	return readInfo->mediaChange.status;
422}
423
424
425byte CDAudio_GetVolume (void)
426{
427	return cdvolume;
428}
429
430
431// we set the volume to 0 first and then to the desired volume
432// some cd-rom drivers seem to need it done this way
433void CDAudio_SetVolume (byte volume)
434{
435	if (!initialized || !enabled)
436		return;
437
438	cdRequest->headerLength = 13;
439	cdRequest->unit = 0;
440	cdRequest->command = COMMAND_WRITE;
441	cdRequest->status = 0;
442
443	cdRequest->x.read.mediaDescriptor = 0;
444	cdRequest->x.read.bufferOffset = readInfoOffset;
445	cdRequest->x.read.bufferSegment = readInfoSegment;
446	cdRequest->x.read.length = sizeof(struct audioChannelInfo_s);
447	cdRequest->x.read.startSector = 0;
448	cdRequest->x.read.volumeID = 0;
449
450	readInfo->audioChannelInfo.code = WRITE_REQUEST_AUDIO_CHANNEL_INFO;
451	readInfo->audioChannelInfo.channel0input = 0;
452	readInfo->audioChannelInfo.channel0volume = 0;
453	readInfo->audioChannelInfo.channel1input = 1;
454	readInfo->audioChannelInfo.channel1volume = 0;
455	readInfo->audioChannelInfo.channel2input = 2;
456	readInfo->audioChannelInfo.channel2volume = 0;
457	readInfo->audioChannelInfo.channel3input = 3;
458	readInfo->audioChannelInfo.channel3volume = 0;
459
460	regs.x.ax = 0x1510;
461	regs.x.cx = cdrom;
462	regs.x.es = cdRequestSegment;
463	regs.x.bx = cdRequestOffset;
464	dos_int86 (0x2f);
465
466	readInfo->audioChannelInfo.channel0volume = volume;
467	readInfo->audioChannelInfo.channel1volume = volume;
468
469	regs.x.ax = 0x1510;
470	regs.x.cx = cdrom;
471	regs.x.es = cdRequestSegment;
472	regs.x.bx = cdRequestOffset;
473	dos_int86 (0x2f);
474
475	cdvolume = volume;
476}
477
478
479void CDAudio_Play(byte track, qboolean looping)
480{
481	if (!initialized || !enabled)
482		return;
483
484	if (!cd.valid)
485		return;
486
487	track = remap[track];
488
489	if (playing)
490	{
491		if (playTrack == track)
492			return;
493		CDAudio_Stop();
494	}
495
496	playLooping = looping;
497
498	if (track < cd.lowTrack || track > cd.highTrack)
499	{
500		Con_DPrintf("CDAudio_Play: Bad track number %u.\n", track);
501		return;
502	}
503
504	playTrack = track;
505
506	if (cd.track[track].isData)
507	{
508		Con_DPrintf("CDAudio_Play: Can not play data.\n");
509		return;
510	}
511
512	cdRequest->headerLength = 13;
513	cdRequest->unit = 0;
514	cdRequest->command = COMMAND_PLAY_AUDIO;
515	cdRequest->status = 0;
516
517	cdRequest->x.playAudio.addressingMode = ADDRESS_MODE_RED_BOOK;
518	cdRequest->x.playAudio.startLocation = cd.track[track].start;
519	cdRequest->x.playAudio.sectors = cd.track[track].length;
520
521	regs.x.ax = 0x1510;
522	regs.x.cx = cdrom;
523	regs.x.es = cdRequestSegment;
524	regs.x.bx = cdRequestOffset;
525	dos_int86 (0x2f);
526
527	if (cdRequest->status & STATUS_ERROR_BIT)
528	{
529		Con_DPrintf("CDAudio_Play: track %u failed\n", track);
530		cd.valid = false;
531		playing = false;
532		return;
533	}
534
535	playing = true;
536}
537
538
539void CDAudio_Stop(void)
540{
541	if (!initialized || !enabled)
542		return;
543
544	cdRequest->headerLength = 13;
545	cdRequest->unit = 0;
546	cdRequest->command = COMMAND_STOP_AUDIO;
547	cdRequest->status = 0;
548
549	regs.x.ax = 0x1510;
550	regs.x.cx = cdrom;
551	regs.x.es = cdRequestSegment;
552	regs.x.bx = cdRequestOffset;
553	dos_int86 (0x2f);
554
555	wasPlaying = playing;
556	playing = false;
557}
558
559
560void CDAudio_Resume(void)
561{
562	if (!initialized || !enabled)
563		return;
564
565	if (!cd.valid)
566		return;
567
568	if (!wasPlaying)
569		return;
570
571	cdRequest->headerLength = 13;
572	cdRequest->unit = 0;
573	cdRequest->command = COMMAND_RESUME_AUDIO;
574	cdRequest->status = 0;
575
576	regs.x.ax = 0x1510;
577	regs.x.cx = cdrom;
578	regs.x.es = cdRequestSegment;
579	regs.x.bx = cdRequestOffset;
580	dos_int86 (0x2f);
581
582	playing = true;
583}
584
585
586static void CD_f (void)
587{
588	char	*command;
589	int		ret;
590	int		n;
591	int		startAddress;
592
593	if (Cmd_Argc() < 2)
594		return;
595
596	command = Cmd_Argv (1);
597
598	if (Q_strcasecmp(command, "on") == 0)
599	{
600		enabled = true;
601		return;
602	}
603
604	if (Q_strcasecmp(command, "off") == 0)
605	{
606		if (playing)
607			CDAudio_Stop();
608		enabled = false;
609		return;
610	}
611
612	if (Q_strcasecmp(command, "reset") == 0)
613	{
614		enabled = true;
615		if (playing)
616			CDAudio_Stop();
617		for (n = 0; n < 256; n++)
618			remap[n] = n;
619		CDAudio_Reset();
620		CDAudio_GetAudioDiskInfo();
621		return;
622	}
623
624	if (Q_strcasecmp(command, "remap") == 0)
625	{
626		ret = Cmd_Argc() - 2;
627		if (ret <= 0)
628		{
629			for (n = 1; n < 256; n++)
630				if (remap[n] != n)
631					Con_Printf("  %u -> %u\n", n, remap[n]);
632			return;
633		}
634		for (n = 1; n <= ret; n++)
635			remap[n] = Q_atoi(Cmd_Argv (n+1));
636		return;
637	}
638
639	if (!cd.valid)
640	{
641		Con_Printf("No CD in player.\n");
642		return;
643	}
644
645	if (Q_strcasecmp(command, "play") == 0)
646	{
647		CDAudio_Play(Q_atoi(Cmd_Argv (2)), false);
648		return;
649	}
650
651	if (Q_strcasecmp(command, "loop") == 0)
652	{
653		CDAudio_Play(Q_atoi(Cmd_Argv (2)), true);
654		return;
655	}
656
657	if (Q_strcasecmp(command, "stop") == 0)
658	{
659		CDAudio_Stop();
660		return;
661	}
662
663	if (Q_strcasecmp(command, "resume") == 0)
664	{
665		CDAudio_Resume();
666		return;
667	}
668
669	if (Q_strcasecmp(command, "eject") == 0)
670	{
671		if (playing)
672			CDAudio_Stop();
673		CDAudio_Eject();
674		cd.valid = false;
675		return;
676	}
677
678	if (Q_strcasecmp(command, "info") == 0)
679	{
680		Con_Printf("%u tracks\n", cd.highTrack - cd.lowTrack + 1);
681		for (n = cd.lowTrack; n <= cd.highTrack; n++)
682		{
683			ret = CDAudio_GetAudioTrackInfo (n, &startAddress);
684			Con_Printf("Track %2u: %s at %2u:%02u\n", n, ret ? "data " : "music", (startAddress >> 16) & 0xff, (startAddress >> 8) & 0xff);
685		}
686		if (playing)
687			Con_Printf("Currently %s track %u\n", playLooping ? "looping" : "playing", playTrack);
688		Con_Printf("Volume is %u\n", cdvolume);
689		CDAudio_MediaChange();
690		Con_Printf("Status %04x\n", cdRequest->status & 0xffff);
691		return;
692	}
693}
694
695
696void CDAudio_Update(void)
697{
698	int		ret;
699	int		newVolume;
700	static	double lastUpdate;
701
702	if (!initialized || !enabled)
703		return;
704
705	if ((realtime - lastUpdate) < 0.25)
706		return;
707	lastUpdate = realtime;
708
709	if (mediaCheck)
710	{
711		static	double lastCheck;
712
713		if ((realtime - lastCheck) < 5.0)
714			return;
715		lastCheck = realtime;
716
717		ret = CDAudio_MediaChange();
718		if (ret == MEDIA_CHANGED)
719		{
720			Con_DPrintf("CDAudio: media changed\n");
721			playing = false;
722			wasPlaying = false;
723			cd.valid = false;
724			CDAudio_GetAudioDiskInfo();
725			return;
726		}
727	}
728
729	newVolume = (int)(bgmvolume.value * 255.0);
730	if (newVolume < 0)
731	{
732		Cvar_SetValue ("bgmvolume", 0.0);
733		newVolume = 0;
734	}
735	else if (newVolume > 255)
736	{
737		Cvar_SetValue ("bgmvolume", 1.0);
738		newVolume = 255;
739	}
740	if (cdvolume != newVolume)
741		CDAudio_SetVolume (newVolume);
742
743	if (playing)
744	{
745		CDAudio_GetAudioStatus();
746		if ((cdRequest->status & STATUS_BUSY_BIT) == 0)
747		{
748			playing = false;
749			if (playLooping)
750				CDAudio_Play(playTrack, true);
751		}
752	}
753}
754
755
756qboolean CDAudio_Playing(void)
757{
758	return playing;
759}
760
761
762int CDAudio_Init(void)
763{
764	char	*memory;
765	int		n;
766
767	if (cls.state == ca_dedicated)
768		return -1;
769
770	if (COM_CheckParm("-nocdaudio"))
771		return -1;
772
773	if (COM_CheckParm("-cdmediacheck"))
774		mediaCheck = true;
775
776	regs.x.ax = 0x1500;
777	regs.x.bx = 0;
778	dos_int86 (0x2f);
779	if (regs.x.bx == 0)
780	{
781		Con_NotifyBox (
782			"MSCDEX not loaded, music is\n"
783			"disabled.  Use \"-nocdaudio\" if you\n"
784			"wish to avoid this message in the\n"
785			"future.  See README.TXT for help.\n"
786			);
787		return -1;
788	}
789	if (regs.x.bx > 1)
790		Con_DPrintf("CDAudio_Init: First CD-ROM drive will be used\n");
791	cdrom = regs.x.cx;
792
793	regs.x.ax = 0x150c;
794	regs.x.bx = 0;
795	dos_int86 (0x2f);
796	if (regs.x.bx == 0)
797	{
798		Con_NotifyBox (
799			"MSCDEX version 2.00 or later\n"
800			"required for music. See README.TXT\n"
801			"for help.\n"
802			);
803		Con_DPrintf("CDAudio_Init: MSCDEX version 2.00 or later required.\n");
804		return -1;
805	}
806
807	memory = dos_getmemory(sizeof(struct cd_request
808) + sizeof(union readInfo_u));
809	if (memory == NULL)
810	{
811		Con_DPrintf("CDAudio_Init: Unable to allocate low memory.\n");
812		return -1;
813	}
814
815	cdRequest = (struct cd_request *)memory;
816	cdRequestSegment = ptr2real(cdRequest) >> 4;
817	cdRequestOffset = ptr2real(cdRequest) & 0xf;
818
819	readInfo = (union readInfo_u *)(memory + sizeof(struct cd_request));
820	readInfoSegment = ptr2real(readInfo) >> 4;
821	readInfoOffset = ptr2real(readInfo) & 0xf;
822
823	for (n = 0; n < 256; n++)
824		remap[n] = n;
825	initialized = true;
826
827	CDAudio_SetVolume (255);
828	if (CDAudio_GetAudioDiskInfo())
829	{
830		Con_Printf("CDAudio_Init: No CD in player.\n");
831		enabled = false;
832	}
833
834	Cmd_AddCommand ("cd", CD_f);
835
836	Con_Printf("CD Audio Initialized\n");
837
838	return 0;
839}
840
841
842void CDAudio_Shutdown(void)
843{
844	if (!initialized)
845		return;
846	CDAudio_Stop();
847}
848