/*-
    Program scsi_play, used for playing selected tracks from an audio
    CD-ROM on Hewlett-Packard Series 300, 400, and 700 computers.

    Specify starting track number with -t option (default is 1).
    Specify number of tracks to play with -n option (default is 0).
    If 0 tracks are chosen, entire disc is played.

    Use -h to halt playing.
    Use -p to pause.
    Use -c to continue.
    Use -e to eject.

    -- Not all audio CDs start at track 1!
    -- Requires character device as a parameter.
    -- Requires root privilege to run.

    Works only with Toshiba XM-3201 or 3301 SCSI CD-ROM player.
    Tested with S300/S400 HP-UX, rev 7.0X and 8.0; S700 8.07
    Supplied without warranty or support of any kind.

*/

#include <stdio.h>
#include <sys/scsi.h>
#include <fcntl.h>
#include <unistd.h>

struct scsi_cmd_parms track_search = {
    10, 1, 500,
    0xc0, 0x00, 0x01, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x80
};

struct scsi_cmd_parms play_audio = {
    10, 1, 500,
    0xc1, 0x03, 0x02, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x80
};

struct scsi_cmd_parms read_disc_info = {
    10, 1, 500,
    0xc7, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00
};

struct scsi_cmd_parms stop = {
    6, 1, 500,
    0x1b, 0x00, 0x00, 0x00, 0x00, 0x00
};

struct scsi_cmd_parms playing_status = {
    10, 1, 500,
    0xc6, 0x0a, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00
};

struct scsi_cmd_parms disc_eject = {
    10, 1, 500,
    0xc4, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00
};

struct scsi_cmd_parms still = {
    10, 1, 500,
    0xc2, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00
};


main(argc, argv)
    int     argc;
    char   *argv[];
{
    int     fd, c, ret, first_track = 1, last_track, halt = 0,
            flag = 1, num_tracks = 0, eject = 0, pause = 0, cont = 0,
            i;
    int     first_avail, last_avail;
    unsigned char buf[256];
    char   *name = "/dev/cdaudio", *begin = NULL, *num = NULL;

    extern int opterr, optind;
    extern char *optarg;
    opterr = 0;
    while ((c = getopt(argc, argv, "PpCcEeHht:T:n:N:")) != EOF)
    {
	switch (c)
	{
	case 'T':
	case 't':
	    begin = optarg;
	    sscanf(begin, "%d", &first_track);
	    break;
	case 'N':
	case 'n':
	    num = optarg;
	    sscanf(num, "%d", &num_tracks);
	    break;
	case 'H':
	case 'h':
	    halt = 1;
	    break;
	case 'E':
	case 'e':
	    eject = 1;
	    break;
	case 'P':
	case 'p':
	    pause = 1;
	    break;
	case 'C':
	case 'c':
	    cont = 1;
	    break;
	default:
	    usage(argv[0]);
	    exit(1);
	}
    }

    if (!name) {
	if ((name = argv[optind++]) == NULL || argv[optind] != NULL) {
	    usage(argv[0]);
	    exit(1);
	}
    }
    if (access(name, R_OK)) {
	perror(argv[0]);
	fprintf(stderr, "%s: %s is not readable writable\n", 
	    argv[0], name);
    }


    /*
       Normally would open with  O_RDONLY, but O_RDWR may be necessary as a bug
       workaround
    */

    if ((fd = open(name, O_RDWR)) < 0)
    {
	perror("hpcdplay: error in open");
	exit(1);
    }

    if ((ret = ioctl(fd, SIOC_CMD_MODE, &flag)) < 0)
    {
	perror("error in ioctl setup CMD MODE");
	exit(1);
    }

    if (eject == 1)
    {
	ioctl(fd, SIOC_SET_CMD, &disc_eject);	/* eject caddy */
	ret = read(fd, buf, 0xff);
	close(fd);
	exit(0);
    }

    if (halt == 1)
    {
	ioctl(fd, SIOC_SET_CMD, &stop);	/* stop playing */
	ret = read(fd, buf, 0xff);
	close(fd);
	exit(0);
    }

    if (pause == 1)
    {
	ioctl(fd, SIOC_SET_CMD, &still);
	ret = read(fd, buf, 0xff);
	close(fd);
	exit(0);
    }

    if (cont == 1)
    {
	play_audio.command[9] = 0xc0;
    }
    else
    {
	/* find first,last track */
	ioctl(fd, SIOC_SET_CMD, &read_disc_info);
	ret = read(fd, buf, 0xff);


	first_avail = convy(buf[0]);	/* comes out in bcd */
	last_avail = convy(buf[1]);
	printf("1st track =%4i last track = %4i\n", first_avail, last_avail);

	last_track = first_track + num_tracks - 1;
	if (num_tracks == 0)
	    last_track = last_avail;
	printf("first track selected  =%4i   last track selected  = %4i\n\n",
	    first_track, last_track);

	if (first_track > last_track)
	{
	    printf("Can't play backwards\n");
	    exit(1);
	}

	if (first_track < first_avail || first_track > last_avail)
	{
	    printf("Starting track out of range\n");
	    exit(1);
	}
	if (last_track > last_avail || last_track < first_avail)
	{
	    printf("Ending track out of range\n");
	    exit(1);
	}

	track_search.command[2] = (unsigned char) (convx(first_track));
	/* requires bcd */
	play_audio.command[2] = (unsigned char) (convx(last_track + 1));

	if (last_track == last_avail)
	    play_audio.command[2] = 0;

	/* play to end */
	ioctl(fd, SIOC_SET_CMD, &track_search);	/* seek to track */
	ret = read(fd, buf, 0xff);
    }


    ioctl(fd, SIOC_SET_CMD, &play_audio);	/* now play */
    ret = read(fd, buf, 0xff);



/*-

   The following do/while loop is normally commented out.  Use if
   if you'd like to extend the program to play tracks
   in arbitrary sequence.  

   Status=0 if playing, 
	 =1 if paused,
         =2 if audio track search completed, 
	 =3 if playback done.

*/
    if (1)
	do {
	    ioctl(fd, SIOC_SET_CMD, &playing_status);
	    ret = read(fd, buf, 0xff);
	    printf("Playing track number%3i  at %3i minutes, %2i seconds\n", 
		convy(buf[2]), 
		convy(buf[4]), 
		convy(buf[5]));
	    sleep(1);
	} while (buf[0] == 0);

    close(fd);
    exit(0);
}

usage(name)
    char   *name;
{
    char   *opts = "[-ehpc] [-t firstTrack] [-n numberOfTracks>] charDevice";
    fprintf(stderr, "usage: %s %s\n", name, opts);
    return 0;
}


int
convx(number)	/* do an integer to bcd conversion */
    int     number;
{
    return (number + 6 * (number / 10)) & 0xff;
}

int
convy(number)	/* do a bcd to integer conversion */
    int     number;
{
    return (number / 16) * 10 + number % 16;
}
