#include <sys/types.h>
#include <sys/time.h>
#include <sys/cdrom.h>
#include <sys/ioctl.h>
#include <sys/file.h>
#include <sys/socket.h>
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <sysexits.h>

/*
 * This programs allows you to play audio CDs on a RRD42 under
 * ULTRIX V4.2.  First is you need write access to the raw device
 * (/dev/rrz?c) and you need to set the environment variable CDROM
 * to the RRD42 drive (ie. setenv CDROM /dev/rrz4c).
 *
 * This programs allows to
 *	play the entire cd			cdp play
 *	play from a specific			cdp play n
 *	    track to the end
 *	pause the cd				cdp pause
 *	unpause (resume) the cd			cdp resume
 *	list tracks				cdp query
 *	eject the cd				cdp eject
 *	show cd status				cdp status
 *
 * This program is the result of a 4 hour hacking session trying
 * to understand how to get the RRD42 to play.
 */

char *channel_selections[] =
	{ "Muted", "Channel 0", "Channel 1", "Channels 0 & 1" };

int mode = CDROM_MSF_FORMAT;
struct cd_toc toc;
struct cd_toc_entry *fte, *lte;
struct cd_toc_header th;
struct cd_playback pb;
struct cd_playback_status ps;

char *cdrom;

main(argc, argv)
    int argc;
    char **argv;
{
    int fd;

    if ((cdrom = getenv("CDROM")) == NULL) {
	fprintf(stderr, "CDROM environment variable not set\n");
	exit(EX_USAGE);
    }

    fd = open(cdrom, O_RDONLY, 0);
    if (fd < 0) {
	fprintf(stderr, "%s: %s\n", cdrom, strerror(errno));
	exit(EX_UNAVAILABLE);
    }

    cdrom_init(fd);

    if (argc == 2) {
	if (strcasecmp("pause", argv[1]) == 0) {
	    if (ioctl(fd, CDROM_PAUSE_PLAY, NULL) < 0) {
		fprintf(stderr, "%s: pause: %s\n", cdrom, strerror(errno));
		exit(EX_UNAVAILABLE);
	    }
	    exit(EX_OK);
	} else if (strcasecmp("play", argv[1]) == 0) {
	    exit(cdrom_play(fd, -1, -1));
	} else if (strcasecmp("query", argv[1]) == 0) {
	    exit(cdrom_query(fd));
	} else if (strcasecmp("status", argv[1]) == 0) {
	    exit(cdrom_status(fd));
	} else if (strcasecmp("resume", argv[1]) == 0) {
	    if (ioctl(fd, CDROM_RESUME_PLAY, NULL) < 0) {
		fprintf(stderr, "%s: resume: %s\n", cdrom, strerror(errno));
		exit(EX_UNAVAILABLE);
	    }
	    exit(EX_OK);
	} else if (strcasecmp("eject", argv[1]) == 0) {
	    if (ioctl(fd, CDROM_EJECT_CADDY, NULL) < 0) {
		fprintf(stderr, "%s: eject: %s\n", cdrom, strerror(errno));
		exit(EX_UNAVAILABLE);
	    }
	    exit(EX_OK);
	}
    } else if (argc == 3) {
	if (strcasecmp("play", argv[1]) == 0) {
	    int track;
	    if (sscanf(argv[2], "%i", &track) == 1) {
		exit(cdrom_play(fd, track, -1));
	    }
	}
    }

    fprintf(stderr, "%s: unrecognized or illegal command or option\n", cdrom);
    fprintf(stderr, "usage: %s { query, pause, resume, eject, play [track], status }\n", argv[0]);
    exit(EX_USAGE);
}


















cdrom_status(fd)
    int fd;
{
    int m = 0, f = 0, s = 0, idx, status = ps.ps_audio_status;
    if (ps.ps_audio_status)
	printf("Audio status = %s%s%s%s%s%s\n",
	    (ps.ps_audio_status & PS_PLAY_IN_PROGRESS) ?
		"\n\tPlay In Progress" : "",
	    (ps.ps_audio_status & PS_PLAY_PAUSED) ?
		"\n\tPause In Progress" : "",
	    (ps.ps_audio_status & PS_MUTING_ON) ?
		"\n\tMuting On" : "",
	    (ps.ps_audio_status & PS_PLAY_COMPLETED) ?
		"\n\tPlay Completed" : "",
	    (ps.ps_audio_status & PS_PLAY_ERROR) ?
		"\n\tError Occurred During Play" : "",
	    (ps.ps_audio_status & PS_PLAY_NOT_REQUESTED) ?
		"\n\tAudio Play Not Requested" : ""
	);
    printf("Channel %d Selection = %s, volume = %1.2f%%\n",
	0, channel_selections[ps.ps_chan0_select],
	(100.0 * ps.ps_chan0_volume) / CDROM_MAX_VOLUME);
    printf("Channel %d Selection = %s, volume = %1.2f%%\n",
	1, channel_selections[ps.ps_chan1_select],
	(100.0 * ps.ps_chan1_volume) / CDROM_MAX_VOLUME);
    printf("Channel %d Selection = %s, volume = %1.2f%%\n",
	2, channel_selections[ps.ps_chan2_select],
	(100.0 * ps.ps_chan2_volume) / CDROM_MAX_VOLUME);
    printf("Channel %d Selection = %s, volume = %1.2f%%\n",
	3, channel_selections[ps.ps_chan3_select],
	(100.0 * ps.ps_chan3_volume) / CDROM_MAX_VOLUME);
    printf("\n");

    m = 0; s = 0;
    fte = (struct cd_toc_entry *) (toc.toc_buffer + sizeof(th));
    lte = fte + (th.th_ending_track - th.th_starting_track) + 1;
    idx = 0;
    while ((ps.ps_audio_status & PS_PLAY_COMPLETED) == 0) {
	static struct timeval timeout = { 0, 1000000 / 10 };
	pb.pb_alloc_length = sizeof(ps);
	pb.pb_buffer = (caddr_t) &ps;
	if (ioctl(fd, CDROM_PLAYBACK_STATUS, &pb) < 0) {
	    fprintf(stderr, "%s: play: %s\n", cdrom, strerror(errno));
	    exit(EX_UNAVAILABLE);
	}

	if (status != ps.ps_audio_status) {
	    printf("\nAudio status = %s%s%s%s%s%s%s\n",
		(ps.ps_audio_status & PS_PLAY_IN_PROGRESS) ?
			"\n\tPlay In Progress" : "",
		(ps.ps_audio_status & PS_PLAY_PAUSED) ?
			"\n\tPause In Progress" : "",
		(ps.ps_audio_status & PS_MUTING_ON) ?
			"\n\tMuting On" : "",
		(ps.ps_audio_status & PS_PLAY_COMPLETED) ?
			"\n\tPlay Completed" : "",
		(ps.ps_audio_status & PS_PLAY_ERROR) ?
			"\n\tError Occurred During Play" : "",
		(ps.ps_audio_status & PS_PLAY_NOT_REQUESTED) ?
			"\n\tAudio Play Not Requested" : "",
		ps.ps_audio_status ? "" : "<none>"
	    );
	    status = ps.ps_audio_status;
	    if (ps.ps_audio_status & PS_PLAY_COMPLETED)
		break;
	}

	if (ps.ps_lbamsf == CDROM_LBA_FORMAT) {
	    printf("Address = %d (%s)\r", (ps.ps_lba.addr0) + (ps.ps_lba.addr1 << 8) +
		(ps.ps_lba.addr2 << 16) + (ps.ps_lba.addr3 << 24), "LBA");
	    fflush(stdout);
	} else {
	    if (m != ps.ps_msf.m_units || s != ps.ps_msf.s_units) {
		int dm, tdm, ds, tds, fs, fm;
		while (ps.ps_msf.m_units > fte[idx+1].te_msf.m_units ||
			(ps.ps_msf.m_units == fte[idx+1].te_msf.m_units &&
				ps.ps_msf.s_units > fte[idx+1].te_msf.s_units)) {
		    idx++;
		}
		fs = ps.ps_msf.s_units - fte[idx].te_msf.s_units;
		fm = ps.ps_msf.m_units - fte[idx].te_msf.m_units;
		if (fs < 0)
		   fm -= 1, fs += 60;
		ds = fte[idx+1].te_msf.s_units - ps.ps_msf.s_units;
		dm = fte[idx+1].te_msf.m_units - ps.ps_msf.m_units;
		if (ds < 0)
		   dm -= 1, ds += 60;
		tds = lte->te_msf.s_units - ps.ps_msf.s_units;
		tdm = lte->te_msf.m_units - ps.ps_msf.m_units;
		if (tds < 0)
		   tdm -= 1, tds += 60;
		printf("Track %-3d  %2d:%02d  (-%d:%02d)  %3.2f%% -- %3d  %2d:%02d  (-%d:%02d)  %3.2f%%\r",
		    fte[idx].te_track_number,
		    fm, fs, dm, ds,
		    100.0 - 100.0 * (ds + dm * 60) /
			((fte[idx+1].te_msf.m_units - fte[idx].te_msf.m_units) * 60 +
			  fte[idx+1].te_msf.s_units - fte[idx].te_msf.s_units),
		    fte[idx].te_track_number - th.th_ending_track, 
		    ps.ps_msf.m_units, ps.ps_msf.s_units,
		    tdm, tds,
		    100.0 - 100.0 * (tds + tdm * 60) /
			((lte->te_msf.m_units - fte->te_msf.m_units) * 60 +
			  lte->te_msf.s_units - fte->te_msf.s_units));
		m = ps.ps_msf.m_units;
		s = ps.ps_msf.s_units;
		fflush(stdout);
	    }
	}
	select(0, 0, 0, 0, &timeout);
    }
    return EX_OK;
}














cdrom_init(fd)
    int fd;
{
    if (ioctl(fd, CDROM_SET_ADDRESS_FORMAT, &mode) < 0) {
	fprintf(stderr, "%s: set addr mode: %s\n", cdrom, strerror(errno));
    }
    pb.pb_alloc_length = sizeof(ps);
    pb.pb_buffer = (caddr_t) &ps;

    if (ioctl(fd, CDROM_PLAYBACK_STATUS, &pb) < 0) {
	fprintf(stderr, "%s: play: %s\n", cdrom, strerror(errno));
	exit(EX_UNAVAILABLE);
    }

    if (ioctl(fd, CDROM_TOC_HEADER, &th) < 0) {
	fprintf(stderr, "%s: toc hdr: %s\n", cdrom, strerror(errno));
	exit(EX_UNAVAILABLE);
    }

    toc.toc_address_format = mode;
    toc.toc_starting_track = th.th_starting_track;
    toc.toc_alloc_length = th.th_data_len0 + 256 * th.th_data_len1 + sizeof(th);
    toc.toc_buffer = (caddr_t) malloc(toc.toc_alloc_length);
    bzero(toc.toc_buffer, toc.toc_alloc_length);

    if (ioctl(fd, CDROM_SET_ADDRESS_FORMAT, &mode) < 0) {
	fprintf(stderr, "%s: set addr mode: %s\n", cdrom, strerror(errno));
    }
    if (ioctl(fd, CDROM_TOC_ENTRYS, &toc) < 0) {
	fprintf(stderr, "%s: toc entry: %s\n", cdrom, strerror(errno));
	exit(EX_UNAVAILABLE);
    }

    fte = (struct cd_toc_entry *) (toc.toc_buffer + sizeof(th));
    lte = fte + (th.th_ending_track - th.th_starting_track) + 1;
}

cdrom_play(fd, start, end)
    int fd;
    int start;
    int end;
{
    if ((start > 0) && (start > th.th_ending_track || start < th.th_starting_track)) {
	fprintf(stderr, "Starting track (%d) be with the range of %d to %d\n",
		start, th.th_starting_track, th.th_ending_track);
	return EX_USAGE;
    }

    if ((end > 0) && (end > th.th_ending_track || end < th.th_ending_track)) {
	fprintf(stderr, "Ending track (%d) be with the range of %d to %d\n",
		end, th.th_starting_track, th.th_ending_track);
	return EX_USAGE;
    }

    if (start < 0)
	start = th.th_starting_track;
    if (end < 0)
	end = th.th_ending_track;

    fte += (start - th.th_starting_track);    
    lte += (end - th.th_ending_track);
    if (toc.toc_address_format == CDROM_LBA_FORMAT) {
	struct cd_play_audio pa;
	pa.pa_lba = fte->te_lba.addr0 + (fte->te_lba.addr1 << 8) +
		(fte->te_lba.addr2 << 16) + (fte->te_lba.addr3 << 24);
	pa.pa_length = lte->te_lba.addr0 + (lte->te_lba.addr1 << 8) +
		(lte->te_lba.addr2 << 16) + (lte->te_lba.addr3 << 24);
	pa.pa_length -= pa.pa_lba;
	printf("pa_lba = %d, pa_length = %d\n", pa.pa_lba, pa.pa_length);
	if (ioctl(fd, CDROM_PLAY_AUDIO, &pa) < 0) {
	    fprintf(stderr, "%s: play: %s\n", cdrom, strerror(errno));
	    return EX_OSERR;
	}
    } else {
	struct cd_play_audio_msf msf;
	msf.msf_starting_M_unit = fte->te_msf.m_units;
	msf.msf_starting_S_unit = fte->te_msf.s_units;
	msf.msf_starting_F_unit = fte->te_msf.f_units;
	msf.msf_ending_M_unit = lte->te_msf.m_units;
	msf.msf_ending_S_unit = lte->te_msf.s_units;
	msf.msf_ending_F_unit = lte->te_msf.f_units;
	if (msf.msf_ending_F_unit > 0) {
	    msf.msf_ending_F_unit--;
	} else {
	    msf.msf_ending_F_unit = 74;
	    if (msf.msf_ending_S_unit > 0) {
		msf.msf_ending_S_unit--;
	    } else {
		msf.msf_ending_S_unit = 59;
		msf.msf_ending_M_unit--;
	    }
	}
	if (ioctl(fd, CDROM_PLAY_AUDIO_MSF, &msf) < 0) {
	    fprintf(stderr, "%s: play msf: %s\n", cdrom, strerror(errno));
	    return EX_OSERR;
	}
    }
    return EX_OK;
}






cdrom_query(fd)
   int fd;
{
    struct cd_toc_entry *te = fte;

    printf("Total time = %d:%02d (%d track%s)\n\n",
	lte->te_msf.m_units, lte->te_msf.s_units,
	lte - fte, lte - fte == 1 ? "" : "s");
    for (te = fte; te < lte; te++) {
	int dm, ds;
	dm = te[1].te_msf.m_units - te->te_msf.m_units;
	ds = te[1].te_msf.s_units - te->te_msf.s_units;
	if (ds < 0)
	    dm -= 1, ds += 60;
	printf("Track %2d: starting at %2d:%02d, duration %2d:%02d\n",
		te->te_track_number, te->te_msf.m_units, te->te_msf.s_units,
		dm, ds);
#if 0
	printf("\twith%s pre-emphasis, copy %s, %s track, %d channel.\n",
	    (te->te_control & CDROM_AUDIO_PREMPH) ? "" : "out",
	    (te->te_control & CDROM_COPY_PERMITTED) ? "allowed" : "prohibited",
	    (te->te_control & CDROM_DATA_TRACK) ? "data" : "audio",
	    (te->te_control & CDROM_FOUR_CHAN_AUDIO) ? 4 : 2);
#endif
    }
}
