1 // Emacs style mode select -*- C++ -*-
2 //-----------------------------------------------------------------------------
3 //
4 // $Id: i_cdmus.c 1524 2020-05-09 12:07:09Z wesleyjohnson $
5 //
6 // Copyright (C) 1998-2000 by DooM Legacy Team.
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. See the
16 // GNU General Public License for more details.
17 //
18 //
19 // $Log: i_cdmus.c,v $
20 // Revision 1.8 2001/08/20 20:40:42 metzgermeister
21 // *** empty log message ***
22 //
23 // Revision 1.7 2000/05/13 19:52:37 metzgermeister
24 // cd vol jiggle
25 //
26 // Revision 1.6 2000/04/28 19:28:00 metzgermeister
27 // changed to CDROMPLAYMSF for CD music
28 //
29 // Revision 1.5 2000/04/07 23:12:38 metzgermeister
30 // fixed some minor bugs
31 //
32 // Revision 1.4 2000/03/28 16:18:42 linuxcub
33 // Added a command to the Linux sound-server which sets a master volume...
34 //
35 // Revision 1.3 2000/03/22 18:53:53 metzgermeister
36 // Ripped CD code out of Quake and put it here
37 //
38 // Revision 1.2 2000/02/27 00:42:11 hurdler
39 // fix CR+LF problem
40 //
41 // Revision 1.1.1.1 2000/02/22 20:32:33 hurdler
42 // Initial import into CVS (v1.29 pr3)
43 //
44 //
45 // DESCRIPTION:
46 // cd music interface
47 //
48 //-----------------------------------------------------------------------------
49
50
51 #include <unistd.h>
52 #include <stdlib.h>
53 #include <sys/ioctl.h>
54 #include <sys/file.h>
55 #include <sys/types.h>
56 #include <fcntl.h>
57 #include <string.h>
58 #include <time.h>
59 #include <errno.h>
60
61 #include <linux/cdrom.h>
62 #include "doomincl.h"
63 #include "i_sound.h"
64 #include "command.h"
65 #include "m_argv.h"
66 #include "d_main.h"
67
68 // Remap index is level map 1..34, or (episode-1)*9+map 1..36
69 #define MAX_MAPPING 40
70 #define MAX_CD_TRACKS 256
71
72 static boolean cdValid = false;
73 static boolean playing = false;
74 static boolean wasPlaying = false;
75 static boolean initialized = false;
76 static boolean cd_enabled = false;
77 static boolean play_looping = false;
78 static byte playTrack; // track being played, 1..n
79 static byte maxTrack;
80 static byte cdRemap[MAX_MAPPING];
81 static int cdvolume = -1;
82 static time_t lastchk = 0;
83 static time_t play_time = 0;
84
85 static int open_cdrom(void);
86 static void I_StopCD(void);
87 static void I_SetVolumeCD (int volume);
88 static void cd_volume_onchange(void);
89
90 CV_PossibleValue_t cd_volume_cons_t[]={{0,"MIN"},{31,"MAX"},{0,NULL}};
91
92 consvar_t cd_volume = {"cd_volume","31", CV_SAVE|CV_CALL, cd_volume_cons_t, cd_volume_onchange};
93 //consvar_t cdUpdate = {"cd_update","1",CV_SAVE};
94 consvar_t cv_jigglecdvol = {"jigglecdvolume", "0", CV_SAVE};
95
cd_volume_onchange(void)96 static void cd_volume_onchange(void)
97 {
98 I_SetVolumeCD( cd_volume.value );
99 }
100
101 static int cd_fd = -1;
102 static char cd_dev[64] = "/dev/cdrom";
103
104
105 // return 0 when CD TOC has been read
CDAudio_GetAudioDiskInfo(void)106 static byte CDAudio_GetAudioDiskInfo(void)
107 {
108 struct cdrom_tochdr tochdr;
109 int ierr;
110 char * errstr = NULL;
111
112 cdValid = false;
113 if( cd_fd < 0 ) return 1;
114
115 // Read TOC header
116 if ( ioctl(cd_fd, CDROMREADTOCHDR, &tochdr) < 0 ) {
117 errstr = "CDROM Read TOC";
118 goto errexit;
119 }
120
121 if (tochdr.cdth_trk0 < 1) {
122 CONS_Printf("CDAudio_GetAudioDiskInfo: no music tracks\n");
123 return 2;
124 }
125
126 cdValid = true;
127 maxTrack = tochdr.cdth_trk1;
128
129 return 0;
130
131 errexit:
132 ierr = errno;
133 GenPrintf( EMSG_error, "%s: %s\n", errstr, strerror(ierr));
134 return 3;
135 }
136
CDAudio_GetStartStop(struct cdrom_msf * msf,int track,struct cdrom_tocentry * entry)137 static boolean CDAudio_GetStartStop(struct cdrom_msf *msf, int track, struct cdrom_tocentry *entry)
138 {
139 struct cdrom_tocentry endentry;
140
141 // start of track
142 msf->cdmsf_min0 = entry->cdte_addr.msf.minute;
143 msf->cdmsf_sec0 = entry->cdte_addr.msf.second;
144 msf->cdmsf_frame0 = entry->cdte_addr.msf.frame;
145
146 // read following track
147 if(track == maxTrack) {
148 endentry.cdte_track = CDROM_LEADOUT;
149 endentry.cdte_format = CDROM_MSF;
150 }
151 else {
152 endentry.cdte_track = track+1;
153 endentry.cdte_format = CDROM_MSF;
154 }
155
156 if(ioctl(cd_fd, CDROMREADTOCENTRY, &endentry))
157 return false;
158
159 // end of track
160 msf->cdmsf_min1 = endentry.cdte_addr.msf.minute;
161 msf->cdmsf_sec1 = endentry.cdte_addr.msf.second;
162 msf->cdmsf_frame1 = endentry.cdte_addr.msf.frame;
163
164 return true;
165 }
166
I_EjectCD(void)167 static void I_EjectCD(void)
168 {
169 cdValid = false;
170 if (cd_fd < 0)
171 return; // no cd init'd
172
173 I_StopCD();
174
175 if ( ioctl(cd_fd, CDROMEJECT) < 0 )
176 CONS_Printf("CD eject: %s\n", strerror(errno));
177 }
178
command_CD_f(void)179 static void command_CD_f (void)
180 {
181 char *command;
182 int ret;
183 int n;
184
185 if (COM_Argc() < 2) {
186 CONS_Printf ("cd [on] [off] [remap] [reset] [open]\n"
187 " [info] [play <track>] [resume]\n"
188 " [stop] [pause] [loop <track>]\n");
189 return;
190 }
191
192 command = COM_Argv (1);
193
194 if (!strncmp(command, "on", 2)) {
195 cd_enabled = true;
196 return;
197 }
198
199 if (!strncmp(command, "off", 3)) {
200 I_ShutdownCD(); // so can change CD disc
201 return;
202 }
203
204 if (!strncmp(command, "remap", 5)) {
205 ret = COM_Argc() - 2;
206 if (ret <= 0) {
207 for (n = 1; n < MAX_MAPPING; n++)
208 if (cdRemap[n] != (n+1))
209 CONS_Printf(" %u -> %u\n", n, cdRemap[n]);
210 return;
211 }
212 for (n = 1; n <= ret; n++)
213 cdRemap[n] = atoi(COM_Argv (n+1));
214 return;
215 }
216
217 if (!strncmp(command, "reset", 5)) {
218 cd_enabled = true;
219 I_StopCD();
220
221 for (n = 0; n < MAX_MAPPING; n++)
222 cdRemap[n] = (n+1);
223
224 CDAudio_GetAudioDiskInfo();
225 return;
226 }
227
228 if (cd_fd < 0)
229 open_cdrom();
230
231 if (!strncmp(command, "info", 4)) {
232 if(cd_fd < 0)
233 {
234 CONS_Printf("No CDROM");
235 return;
236 }
237 CDAudio_GetAudioDiskInfo();
238 CONS_Printf("%u tracks\n", maxTrack);
239 if (playing)
240 CONS_Printf("Currently %s track %u\n", play_looping ? "looping" : "playing", playTrack);
241 else if (wasPlaying)
242 CONS_Printf("Paused %s track %u\n", play_looping ? "looping" : "playing", playTrack);
243 CONS_Printf("Volume is %d\n", cdvolume);
244 return;
245 }
246
247 if (!initialized)
248 return;
249
250 if (!cdValid) {
251 if( CDAudio_GetAudioDiskInfo() )
252 return;
253 }
254
255
256 if (!strncmp(command, "open", 4)) {
257 I_EjectCD();
258 return;
259 }
260
261 if (!strncmp(command, "play", 4)) {
262 I_PlayCD((byte)atoi(COM_Argv (2)), false);
263 return;
264 }
265
266 if (!strncmp(command, "loop", 4)) {
267 I_PlayCD((byte)atoi(COM_Argv (2)), true);
268 return;
269 }
270
271 if (!strncmp(command, "stop", 4)) {
272 I_StopCD();
273 return;
274 }
275
276 if (!strncmp(command, "pause", 5)) {
277 I_PauseCD();
278 return;
279 }
280
281 if (!strncmp(command, "resume", 6)) {
282 I_ResumeCD();
283 return;
284 }
285
286 CONS_Printf("Invalid command \"cd %s\"\n", COM_Argv (1));
287 }
288
289 static
I_StopCD(void)290 void I_StopCD(void)
291 {
292 if (cd_fd < 0)
293 return;
294
295 if (!(playing || wasPlaying))
296 return;
297
298 if ( ioctl(cd_fd, CDROMSTOP, 0) < 0 )
299 CONS_Printf("CD stop: %s\n", strerror(errno));
300
301 wasPlaying = false;
302 playing = false;
303 lastchk = 0;
304 }
305
I_PauseCD(void)306 void I_PauseCD (void)
307 {
308 if (cd_fd < 0 || !cd_enabled)
309 return;
310
311 if (!playing)
312 return;
313
314 if ( ioctl(cd_fd, CDROMPAUSE) < 0 )
315 CONS_Printf("CD pause: %s\n", strerror(errno));
316
317 wasPlaying = playing;
318 playing = false;
319 lastchk = 0;
320 }
321
322 // continue after a pause
I_ResumeCD(void)323 void I_ResumeCD (void)
324 {
325 if (cd_fd < 0 || !cd_enabled)
326 return;
327
328 if (!cdValid)
329 return;
330
331 if (!wasPlaying)
332 return;
333
334 if ( ioctl(cd_fd, CDROMRESUME) < 0 )
335 CONS_Printf("CD resume: %s\n", strerror(errno));
336
337 playing = true;
338 wasPlaying = false;
339 lastchk = ( play_looping )? 2 : 0;
340
341 if(cv_jigglecdvol.value)
342 {
343 I_SetVolumeCD(31-cd_volume.value);
344 I_SetVolumeCD(cd_volume.value);
345 }
346
347 return;
348 }
349
350
I_ShutdownCD(void)351 void I_ShutdownCD (void)
352 {
353 if (!initialized)
354 return;
355
356 I_StopCD();
357 close(cd_fd);
358 cd_fd = -1;
359
360 cdValid = false;
361 cd_enabled = false;
362 }
363
I_InitCD(void)364 void I_InitCD (void)
365 {
366 // Don't start music on a dedicated server
367 if (M_CheckParm("-dedicated"))
368 return ;
369
370 // Has been checked in d_main.c, but doesn't hurt here
371 if (M_CheckParm ("-nocd"))
372 return ;
373
374 // New commandline switch -cddev
375 if ( M_CheckParm("-cddev") && M_IsNextParm() ) {
376 strncpy(cd_dev, M_GetNextParm(), sizeof(cd_dev));
377 cd_dev[sizeof(cd_dev) - 1] = 0;
378 }
379
380 COM_AddCommand ("cd", command_CD_f, CC_command);
381
382 CONS_Printf("CD Audio Initialized\n");
383
384 initialized = true;
385 return;
386 }
387
388
389 static
open_cdrom(void)390 int open_cdrom(void)
391 {
392 int i;
393
394 if( cd_fd >= 0 ) return 1; // already open
395
396 // As of Linux 2.1, must use O_NONBLOCK. See /usr/include/linux/cdrom.h
397 cd_fd = open(cd_dev, O_RDONLY|O_NONBLOCK);
398 if ( cd_fd < 0 )
399 {
400 int myerrno = errno;
401 CONS_Printf("Open \"%s\" failed:\n %s\n",
402 cd_dev, strerror(myerrno));
403 if(EACCES == myerrno)
404 {
405 // permission denied -> very common problem with IDE drives
406 // Shall we add a line about this in the README?
407 CONS_Printf("-------------------------------------\n"
408 "Permission denied to open device %s\n"
409 "Set read permission or run as root\n"
410 "if in doubt *READ THE DOCS*\n"
411 "-------------------------------------\n", cd_dev);
412 }
413 return -1;
414 }
415 for (i = 0; i < MAX_MAPPING; i++)
416 cdRemap[i] = (i+1);
417
418 cd_enabled = true;
419 cdValid = false; // force read of TOC
420
421 if(verbose)
422 CONS_Printf( "CD %s open\n", cd_dev );
423
424 if(cv_jigglecdvol.value)
425 {
426 I_SetVolumeCD(31-cd_volume.value);
427 }
428 I_SetVolumeCD(cd_volume.value);
429
430 return 2;
431 }
432
433
434 // Check for done and loop track.
I_UpdateCD(void)435 void I_UpdateCD (void)
436 {
437 if(dedicated)
438 return;
439
440 if (!cd_enabled || !play_looping || !playing )
441 return;
442
443 if( lastchk < 3 )
444 {
445 if( lastchk == 2 )
446 {
447 // Est. time to play, + 4 secs.
448 lastchk = time(NULL) + (play_time + 4);
449 }
450 return;
451 }
452
453 // FIXME: Do we have a "hicup" here every 2 secs?
454 if ( playing && (lastchk < time(NULL)) )
455 {
456 struct cdrom_subchnl subchnl;
457
458 // [WDJ] Checking the CD status blocks for 1/4 second,
459 // which is very visible during play.
460 lastchk = time(NULL) + 4; // 4 seconds between chks
461 subchnl.cdsc_format = CDROM_MSF;
462 if (ioctl(cd_fd, CDROMSUBCHNL, &subchnl) < 0 ) {
463 CONS_Printf("CD subchnl: %s\n", strerror(errno));
464 playing = false;
465 return;
466 }
467 if (subchnl.cdsc_audiostatus != CDROM_AUDIO_PLAY &&
468 subchnl.cdsc_audiostatus != CDROM_AUDIO_PAUSED) {
469 playing = false;
470 if (play_looping)
471 I_PlayCD(playTrack|0x100, true);
472 }
473 }
474 }
475
476
477 // play the cd
478 // track : 1 .. n
I_PlayCD(unsigned int track,boolean looping)479 void I_PlayCD (unsigned int track, boolean looping)
480 {
481 struct cdrom_tocentry entry;
482 struct cdrom_msf msf;
483
484 if ( !cd_enabled )
485 return;
486
487 if( cd_fd < 0 ) { // to allow insert of disk after starting DoomLegacy
488 if( open_cdrom() < 0 ) return;
489 }
490
491 if (!cdValid)
492 {
493 if( CDAudio_GetAudioDiskInfo() )
494 return;
495 }
496
497 if( track & 0x100 )
498 {
499 // looping bypasses remap
500 track = track & 0x3f;
501 }
502 else
503 {
504 if( track < 1 ) track = 1;
505 if( track > MAX_MAPPING-1 ) track = MAX_MAPPING-1;
506 // cdRemap index is 0 .. n-1
507 track = cdRemap[track-1];
508 if( track == 0 ) return;
509 // Linux CDROM tracks are 1 .. n
510 }
511
512 #if 0
513 if( ioctl(cd_fd, CDROMSTART) < 0 )
514 {
515 errstr = "CDROM Start";
516 goto errexit;
517 }
518 #endif
519
520 // don't try to play a non-audio track
521 entry.cdte_track = track;
522 entry.cdte_format = CDROM_MSF;
523 if ( ioctl(cd_fd, CDROMREADTOCENTRY, &entry) < 0 )
524 {
525 CONS_Printf("CD read TOC: %s\n", strerror(errno));
526 return;
527 }
528 if (entry.cdte_ctrl == CDROM_DATA_TRACK)
529 {
530 CONS_Printf("I_PlayCD: track %i is not audio\n", track);
531 return;
532 }
533
534 #if 0
535 if(cv_jigglecdvol.value)
536 {
537 I_SetVolumeCD(31-cd_volume.value);
538 I_SetVolumeCD(cd_volume.value);
539 }
540 #endif
541
542 if (playing)
543 {
544 if (playTrack == track)
545 return;
546 I_StopCD();
547 }
548
549 if(!CDAudio_GetStartStop(&msf, track, &entry))
550 return;
551
552 if( ioctl(cd_fd, CDROMPLAYMSF, &msf) < 0 ) {
553 CONS_Printf("CD play msf: %s\n", strerror(errno));
554 return;
555 }
556 #if 0
557 // FIXME: is this necessary??
558 if( ioctl(cd_fd, CDROMRESUME) < 0 )
559 CONS_Printf("CD resume: %s\n", strerror(errno));
560 #endif
561
562 if(cv_jigglecdvol.value)
563 {
564 I_SetVolumeCD(31-cd_volume.value);
565 }
566 I_SetVolumeCD(cd_volume.value);
567
568 play_looping = looping;
569 // only enable play check if play looping
570 lastchk = ( play_looping )? 2 : 0;
571 // play time, secs
572 play_time = ((msf.cdmsf_min1 - msf.cdmsf_min0) * 60)
573 + (msf.cdmsf_sec1 - msf.cdmsf_sec0);
574 if( verbose )
575 CONS_Printf( "Play time %u secs\n", play_time );
576 playTrack = track;
577 playing = true;
578 }
579
580
581 // volume : logical cd audio volume 0-31 (hardware is 0-255)
582 static
I_SetVolumeCD(int volume)583 void I_SetVolumeCD (int volume)
584 {
585 struct cdrom_volctrl volctrl;
586
587 if( cd_fd < 0 ) return;
588
589 if(volume < 0 || volume > 31)
590 {
591 CONS_Printf("cdvolume should be between 0-31\n");
592 volume = 0;
593 }
594
595 // volume control for CD music
596 volctrl.channel0 = (volume * 255.0) / 31.0;
597 volctrl.channel1 = volctrl.channel0;
598 volctrl.channel2 = 0;
599 volctrl.channel3 = 0;
600
601 if(ioctl(cd_fd, CDROMVOLCTRL, &volctrl) < 0){
602 CONS_Printf("CD volume: %s\n", strerror(errno));
603 }
604
605 // Read back volume
606 cdvolume = 0;
607 if(ioctl(cd_fd, CDROMVOLREAD, &volctrl) >= 0){
608 cdvolume = (volctrl.channel0 * 31) / 255;
609 }
610 }
611