1 /*
2 	cd_linux.c
3 
4 	Linux CD Audio support
5 
6 	Copyright (C) 1996-1997  Id Software, Inc.
7 
8 	This program is free software; you can redistribute it and/or
9 	modify it under the terms of the GNU General Public License
10 	as published by the Free Software Foundation; either version 2
11 	of the License, or (at your option) any later version.
12 
13 	This program is distributed in the hope that it will be useful,
14 	but WITHOUT ANY WARRANTY; without even the implied warranty of
15 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
16 
17 	See the GNU General Public License for more details.
18 
19 	You should have received a copy of the GNU General Public License
20 	along with this program; if not, write to:
21 
22 		Free Software Foundation, Inc.
23 		59 Temple Place - Suite 330
24 		Boston, MA  02111-1307, USA
25 
26 */
27 #ifdef HAVE_CONFIG_H
28 # include "config.h"
29 #endif
30 
31 #ifdef HAVE_STRING_H
32 # include <string.h>
33 #endif
34 #ifdef HAVE_STRINGS_H
35 # include <strings.h>
36 #endif
37 #ifdef HAVE_UNISTD_H
38 # include <unistd.h>
39 #endif
40 #ifdef HAVE_SYS_IOCTL_H
41 # include <sys/ioctl.h>
42 #endif
43 
44 #include <errno.h>
45 #include <fcntl.h>
46 #include <stdlib.h>
47 #include <time.h>
48 #include <linux/cdrom.h>
49 
50 #include "QF/cdaudio.h"
51 #include "QF/cmd.h"
52 #include "QF/cvar.h"
53 #include "QF/qargs.h"
54 #include "QF/sound.h"
55 #include "QF/sys.h"
56 
57 #include "QF/plugin/general.h"
58 #include "QF/plugin/cd.h"
59 
60 #include "compat.h"
61 
62 static plugin_t		plugin_info;
63 static plugin_data_t	plugin_info_data;
64 static plugin_funcs_t	plugin_info_funcs;
65 static general_data_t	plugin_info_general_data;
66 static general_funcs_t	plugin_info_general_funcs;
67 static cd_funcs_t		plugin_info_cd_funcs;
68 
69 static qboolean cdValid = false;
70 static qboolean playing = false;
71 static qboolean wasPlaying = false;
72 static qboolean mus_enabled = false;
73 static qboolean playLooping = false;
74 static float cdvolume;
75 static byte remap[100];
76 static byte playTrack;
77 static byte maxTrack;
78 static int  cdfile = -1;
79 
80 static cvar_t *mus_cddevice;
81 static cvar_t *bgmvolume;
82 
83 
84 static void
I_CDAudio_CloseDoor(void)85 I_CDAudio_CloseDoor (void)
86 {
87 	if (cdfile == -1 || !mus_enabled)
88 		return;							// no cd init'd
89 
90 	if (ioctl (cdfile, CDROMCLOSETRAY) == -1)
91 		Sys_MaskPrintf (SYS_SND, "CDAudio: ioctl cdromclosetray failed\n");
92 }
93 
94 static void
I_CDAudio_Eject(void)95 I_CDAudio_Eject (void)
96 {
97 	if (cdfile == -1 || !mus_enabled)
98 		return;							// no cd init'd
99 
100 	if (ioctl (cdfile, CDROMEJECT) == -1)
101 		Sys_MaskPrintf (SYS_SND, "CDAudio: ioctl cdromeject failed\n");
102 }
103 
104 static int
I_CDAudio_GetAudioDiskInfo(void)105 I_CDAudio_GetAudioDiskInfo (void)
106 {
107 	struct cdrom_tochdr tochdr;
108 
109 	cdValid = false;
110 
111 	if (ioctl (cdfile, CDROMREADTOCHDR, &tochdr) == -1) {
112 		Sys_MaskPrintf (SYS_SND, "CDAudio: ioctl cdromreadtochdr failed\n");
113 		return -1;
114 	}
115 
116 	if (tochdr.cdth_trk0 < 1) {
117 		Sys_MaskPrintf (SYS_SND, "CDAudio: no music tracks\n");
118 		return -1;
119 	}
120 
121 	cdValid = true;
122 	maxTrack = tochdr.cdth_trk1;
123 
124 	return 0;
125 }
126 
127 static void
I_CDAudio_Pause(void)128 I_CDAudio_Pause (void)
129 {
130 	if (cdfile == -1 || !mus_enabled)
131 		return;
132 
133 	if (!playing)
134 		return;
135 
136 	if (ioctl (cdfile, CDROMPAUSE) == -1)
137 		Sys_MaskPrintf (SYS_SND, "CDAudio: ioctl cdrompause failed\n");
138 
139 	wasPlaying = playing;
140 	playing = false;
141 }
142 
143 static void
I_CDAudio_Stop(void)144 I_CDAudio_Stop (void)
145 {
146 	if (cdfile == -1 || !mus_enabled)
147 		return;
148 
149 	if (!playing)
150 		return;
151 
152 	if (ioctl (cdfile, CDROMSTOP) == -1)
153 		Sys_MaskPrintf (SYS_SND, "CDAudio: ioctl cdromstop failed (%d)\n",
154 						errno);
155 
156 	wasPlaying = false;
157 	playing = false;
158 }
159 
160 static void
I_CDAudio_Play(int track,qboolean looping)161 I_CDAudio_Play (int track, qboolean looping)
162 {
163 	struct cdrom_tocentry entry0;
164 	struct cdrom_tocentry entry1;
165 	struct cdrom_msf msf;
166 
167 	if (cdfile == -1 || !mus_enabled)
168 		return;
169 
170 	if (!cdValid) {
171 		I_CDAudio_GetAudioDiskInfo ();
172 		if (!cdValid)
173 			return;
174 	}
175 
176 	if (track < 0 || track >= (int) sizeof (remap)) {
177 		Sys_Printf ("CDAudio: invalid track number\n");
178 		return;
179 	}
180 
181 	track = remap[track];
182 
183 	if (track < 1 || track > maxTrack) {
184 		I_CDAudio_Stop ();
185 		return;
186 	}
187 	// don't try to play a non-audio track
188 	entry0.cdte_track = track;
189 	entry0.cdte_format = CDROM_MSF;
190 	if (ioctl (cdfile, CDROMREADTOCENTRY, &entry0) == -1) {
191 		Sys_MaskPrintf (SYS_SND, "CDAudio: ioctl cdromreadtocentry failed\n");
192 		return;
193 	}
194 	entry1.cdte_track = track + 1;
195 	entry1.cdte_format = CDROM_MSF;
196 	if (entry1.cdte_track > maxTrack) {
197 		entry1.cdte_track = CDROM_LEADOUT;
198 	}
199 	if (ioctl (cdfile, CDROMREADTOCENTRY, &entry1) == -1) {
200 		Sys_MaskPrintf (SYS_SND, "CDAudio: ioctl cdromreadtocentry failed\n");
201 		return;
202 	}
203 	if (entry0.cdte_ctrl == CDROM_DATA_TRACK) {
204 		Sys_Printf ("track %i is not audio\n", track);
205 		return;
206 	}
207 
208 	if (playing) {
209 		if (playTrack == track)
210 			return;
211 		I_CDAudio_Stop ();
212 	}
213 
214 	msf.cdmsf_min0 = entry0.cdte_addr.msf.minute;
215 	msf.cdmsf_sec0 = entry0.cdte_addr.msf.second;
216 	msf.cdmsf_frame0 = entry0.cdte_addr.msf.frame;
217 
218 	msf.cdmsf_min1 = entry1.cdte_addr.msf.minute;
219 	msf.cdmsf_sec1 = entry1.cdte_addr.msf.second;
220 	msf.cdmsf_frame1 = entry1.cdte_addr.msf.frame;
221 
222 	Sys_MaskPrintf (SYS_SND, "%2d:%02d:%02d %2d:%02d:%02d\n",
223 					msf.cdmsf_min0,
224 					msf.cdmsf_sec0,
225 					msf.cdmsf_frame0,
226 					msf.cdmsf_min1, msf.cdmsf_sec1, msf.cdmsf_frame1);
227 
228 	if (ioctl (cdfile, CDROMPLAYMSF, &msf) == -1) {
229 		Sys_MaskPrintf (SYS_SND,
230 						"CDAudio: ioctl cdromplaytrkind failed (%s)\n",
231 						strerror (errno));
232 		return;
233 	}
234 
235 	playLooping = looping;
236 	playTrack = track;
237 	playing = true;
238 
239 	if (cdvolume == 0.0)
240 		I_CDAudio_Pause ();
241 }
242 
243 static void
I_CDAudio_Resume(void)244 I_CDAudio_Resume (void)
245 {
246 	if (cdfile == -1 || !mus_enabled)
247 		return;
248 
249 	if (!cdValid)
250 		return;
251 
252 	if (!wasPlaying)
253 		return;
254 
255 	if (ioctl (cdfile, CDROMRESUME) == -1)
256 		Sys_MaskPrintf (SYS_SND, "CDAudio: ioctl cdromresume failed\n");
257 	playing = true;
258 }
259 
260 static void
I_CDAudio_Shutdown(void)261 I_CDAudio_Shutdown (void)
262 {
263 	if (cdfile != -1) {
264 		I_CDAudio_Stop ();
265 		close (cdfile);
266 		cdfile = -1;
267 	}
268 	mus_enabled = false;
269 }
270 
271 static void
I_CD_f(void)272 I_CD_f (void)
273 {
274 	const char *command;
275 	int         ret, n;
276 
277 	if (Cmd_Argc () < 2)
278 		return;
279 
280 	command = Cmd_Argv (1);
281 
282 	if (strequal (command, "on")) {
283 		mus_enabled = true;
284 		return;
285 	}
286 
287 	if (strequal (command, "off")) {
288 		if (playing)
289 			I_CDAudio_Stop ();
290 		mus_enabled = false;
291 		return;
292 	}
293 
294 	if (strequal (command, "reset")) {
295 		mus_enabled = true;
296 		if (playing)
297 			I_CDAudio_Stop ();
298 		for (n = 0; n < 100; n++)
299 			remap[n] = n;
300 		I_CDAudio_GetAudioDiskInfo ();
301 		return;
302 	}
303 
304 	if (strequal (command, "remap")) {
305 		ret = Cmd_Argc () - 2;
306 		if (ret <= 0) {
307 			for (n = 1; n < 100; n++)
308 				if (remap[n] != n)
309 					Sys_Printf ("  %u -> %u\n", n, remap[n]);
310 			return;
311 		}
312 		for (n = 1; n <= ret; n++)
313 			remap[n] = atoi (Cmd_Argv (n + 1));
314 		return;
315 	}
316 
317 	if (strequal (command, "close")) {
318 		I_CDAudio_CloseDoor ();
319 		return;
320 	}
321 
322 	if (!cdValid) {
323 		I_CDAudio_GetAudioDiskInfo ();
324 		if (!cdValid) {
325 			Sys_Printf ("No CD in player.\n");
326 			return;
327 		}
328 	}
329 
330 	if (strequal (command, "play")) {
331 		I_CDAudio_Play (atoi (Cmd_Argv (2)), false);
332 		return;
333 	}
334 
335 	if (strequal (command, "loop")) {
336 		I_CDAudio_Play (atoi (Cmd_Argv (2)), true);
337 		return;
338 	}
339 
340 	if (strequal (command, "stop")) {
341 		I_CDAudio_Stop ();
342 		return;
343 	}
344 
345 	if (strequal (command, "pause")) {
346 		I_CDAudio_Pause ();
347 		return;
348 	}
349 
350 	if (strequal (command, "resume")) {
351 		I_CDAudio_Resume ();
352 		return;
353 	}
354 
355 	if (strequal (command, "eject")) {
356 		if (playing)
357 			I_CDAudio_Stop ();
358 		I_CDAudio_Eject ();
359 		cdValid = false;
360 		return;
361 	}
362 
363 	if (strequal (command, "info")) {
364 		Sys_Printf ("%u tracks\n", maxTrack);
365 		if (playing)
366 			Sys_Printf ("Currently %s track %u\n",
367 						playLooping ? "looping" : "playing", playTrack);
368 		else if (wasPlaying)
369 			Sys_Printf ("Paused %s track %u\n",
370 						playLooping ? "looping" : "playing", playTrack);
371 		Sys_Printf ("Volume is %g\n", cdvolume);
372 		return;
373 	}
374 }
375 
376 static void
I_CDAudio_Update(void)377 I_CDAudio_Update (void)
378 {
379 	struct cdrom_subchnl subchnl;
380 	static time_t lastchk;
381 
382 	if (!mus_enabled)
383 		return;
384 
385 	if (bgmvolume->value != cdvolume) {
386 		if (cdvolume) {
387 			Cvar_SetValue (bgmvolume, 0.0);
388 			cdvolume = bgmvolume->value;
389 			I_CDAudio_Pause ();
390 		} else {
391 			Cvar_SetValue (bgmvolume, 1.0);
392 			cdvolume = bgmvolume->value;
393 			I_CDAudio_Resume ();
394 		}
395 	}
396 
397 	if (playing && lastchk < time (NULL)) {
398 		lastchk = time (NULL) + 2;		// two seconds between chks
399 		subchnl.cdsc_format = CDROM_MSF;
400 		if (ioctl (cdfile, CDROMSUBCHNL, &subchnl) == -1) {
401 			Sys_MaskPrintf (SYS_SND, "CDAudio: ioctl cdromsubchnl failed\n");
402 			playing = false;
403 			return;
404 		}
405 		if (subchnl.cdsc_audiostatus != CDROM_AUDIO_PLAY &&
406 			subchnl.cdsc_audiostatus != CDROM_AUDIO_PAUSED) {
407 			playing = false;
408 			if (playLooping)
409 				I_CDAudio_Play (playTrack, true);
410 		}
411 	}
412 }
413 
414 static void
Mus_CDChange(cvar_t * mus_cdaudio)415 Mus_CDChange (cvar_t *mus_cdaudio)
416 {
417 	int         i;
418 
419 	I_CDAudio_Shutdown ();
420 	if (strequal (mus_cdaudio->string, "none")) {
421 		return;
422 	}
423 
424 	cdfile = open (mus_cdaudio->string, O_RDONLY | O_NONBLOCK);
425 	if (cdfile == -1) {
426 		Sys_MaskPrintf (SYS_SND,
427 						"Mus_CDInit: open device \"%s\" failed (error %i)\n",
428 						mus_cdaudio->string, errno);
429 		return;
430 	}
431 
432 	if (I_CDAudio_GetAudioDiskInfo ()) {
433 		Sys_Printf ("CDAudio_Init: No CD in player.\n");
434 		cdValid = false;
435 	}
436 
437 	for (i = 0; i < 100; i++)
438 		remap[i] = i;
439 
440 	mus_enabled = true;
441 }
442 
443 static void
I_CDAudio_Init(void)444 I_CDAudio_Init (void)
445 {
446 	mus_cddevice = Cvar_Get ("mus_cddevice", "/dev/cdrom", CVAR_NONE,
447 							 Mus_CDChange, "device to use for CD music");
448 	bgmvolume = Cvar_Get ("bgmvolume", "1", CVAR_ARCHIVE, NULL,
449 						  "Volume of CD music");
450 }
451 
452 static general_funcs_t plugin_info_general_funcs = {
453 	I_CDAudio_Init,
454 	I_CDAudio_Shutdown,
455 };
456 
457 static cd_funcs_t plugin_info_cd_funcs = {
458 	I_CD_f,
459 	I_CDAudio_Pause,
460 	I_CDAudio_Play,
461 	I_CDAudio_Resume,
462 	I_CDAudio_Update,
463 };
464 
465 static plugin_funcs_t plugin_info_funcs = {
466 	&plugin_info_general_funcs,
467 	0,
468 	&plugin_info_cd_funcs,
469 	0,
470 	0,
471 	0,
472 };
473 
474 static plugin_data_t plugin_info_data = {
475 	&plugin_info_general_data,
476 	0,
477 	0,
478 	0,
479 	0,
480 	0,
481 };
482 
483 static plugin_t plugin_info = {
484 	qfp_cd,
485 	0,
486 	QFPLUGIN_VERSION,
487 	"0.1",
488 	"Linux CD Audio output\n",
489 	"Copyright (C) 2001  contributors of the QuakeForge project\n"
490 	"Please see the file \"AUTHORS\" for a list of contributors\n",
491 	&plugin_info_funcs,
492 	&plugin_info_data,
493 };
494 
PLUGIN_INFO(cd,linux)495 PLUGIN_INFO (cd, linux)
496 {
497 	return &plugin_info;
498 }
499