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