#ident " @(#)mplay.c 1.1 91/09/17 Copyright 1991, Sun Microsystems, Inc."
/*
 * Copyright (c) 1991 by Sun Microsystems, Inc. 
 *		extensions 5/92--stp@nomad
 */
/*
 * mplay--read midi from a file or from stdin and play on SPARC driver.
 *
 *	mplay [ -s start ] [ -d dur | -e end ] [ -r rate -v -u -p -x ] [ file_name ]
 *
 *				read first 3.2 sec at 800 usec/tick from file
 *		mplay -r 800  -e 4000  ../songs/preludio.midi
 *
 *				read from stdin, skip 2900 beats, play 3000 beats
 *		mplay -r 950  -s 2900  -d 3000  < ../songs/chorale.midi
 * 
 *	Based on Glann Scott's midiwrite, this program reads MIDI data in 
 *	message/buffer format either from a file (whose name is given as a
 *	command-line option), or from stdin. This means it can be pipe'd into
 *	by programs that can schedule ahead-of or in real-time, or by translator
 *	programs that scan files in other codings and write msg/buf data to stdout.
 *	It has all of the verbose midiwrite flags (see README), and the following:
 *		-start tick		skip 'tick' clock ticks (default = 0)
 *		-dur tick		play only 'tick' ticks (all)
 *		-end tick		end at time 'tick' (all)
 *		-rate usec		set the microseconds/tick rate (default = 1000)
 *		-print			print out notes to stdout (off)
 *		-status			print out driver and MIDI status (off)
 *		-u				print usage
 *		-v				verbose messages
 *
 *	Shorthand options
 *			-i = input, -d = dur, -s = start, -e = end, 
 *			-r = rate, -p = print, -x = status
 *
 *	Note also that the file name can be given as argv[argc] without a flag.
 */
#include "midiwrite.h"
#include <sys/proc.h>
#include <sys/uio.h>
#define	mp_error(str) \
	{ fprintf(stderr, "ERROR: %s\n", str); fflush(stderr); exit(-1); }
#define	mp_msg(str) \
	if (verbose) { fprintf(stderr, str); fflush(stderr); }

int fdin = 0;		/* data shared with midiopts--default = stdin */
char fname[128];
struct option *opts;
MidiParameters params;
MidiVariables vars;
char *device = "/dev/midiw";
int MidiFd;
								/* command options */
int play=1, print=0, verbose=0, silent=0, usage=0, compact=0;
								/* clock ptrs--start, curr, end, tmp */
u_long sTime, cTime, eTime, xTime;

/********************
 *	M A I N
 */
main(argc, argv) int argc; char *argv[]; {
	register int fd;
	MidiMessage msg;
	u_char *buf, *MidiGetMsg();
	MidiVariables *MidiGetData();
	void SigInt();

					/* setup */
	signal(SIGINT, SigInt);
	bzero((char *) &params, sizeof(params));
	bzero((char *) &vars, sizeof(vars));
	strcpy(fname, "");

					/* default params */
	sTime = cTime = xTime = 0L;
	eTime = -1L;
	params.midi.clockRate.tv_sec = 0;
	params.midi.clockRate.tv_usec = 1000;	/* clock = 1ms / tick */
	params.midi.option.internalClock = 1;	/* use internal clock */
	params.midi.option.autoStart = 1;		/* set auto start */
	params.input.option.loopBack = 1;		/* set loopback */

						/* fill params & vars from cmd opts */
	midiopts(argc, argv);

						/* open output device */
	if ((MidiFd = fd = open(device, O_WRONLY)) < 0)
			mp_error("cannot open midi device.");

						/* store and print parameters and variables */
	if (MidiPutParameters(fd, &params) != 0)
			mp_error("writing parameters.");
	if (MidiPutData(fd, &vars) != 0)
			mp_error("writing variables.");

	usleep(100000);		/* wait for it to take effect */

	if (MidiGetParameters(fd, &params) == 0)
			mp_error("reading parameters.");
	if (MidiGetVars(fd, &vars) == 0)
			mp_error("reading variables.");

	if (print) {		/* print --> dump driver status */
		MidiPrintParams(&params);
		MidiPrintVars(&vars);
	}
	mp_msg("Reading and playing...\n");

	if (eTime == 0)		/* exit if no samples to play */
		exit(0);
						/*
						 * WRITE LOOP--get/put messages, accumulate delay
						 */
	while ((buf = MidiGetMsg(fdin, &msg)) != 0) {
		cTime += msg.mc_delay;
		xTime += msg.mc_delay;
		if (sTime != 0)			/* skip to start time */
			if (cTime < sTime)	
				goto next;
		if (eTime > 0)			/* ignore past end time */
			if (cTime > eTime) {
				free(buf);
				goto done;
			}
		if (verbose) {			/* print msg every 2 seconds */
			xTime += msg.mc_delay;
			if (xTime > 2000) {
				if (play)		/* if playing */
					MidiPrintMsg(&msg, buf);
				xTime = 0;
			}
		}
		if (play)				/* play to MIDI driver */
			MidiWriteMsg(fd, &msg, buf);
		else					/* print out ASCII message */
			MidiPrintMsg(&msg, buf);
next:
		free(buf);
	}					/* end of write loop */
done:
	if (!play) {
/*		printf("\tEND\n\n"); */
		printf("\n");
		exit(0);
	}
	if (sTime != 0)
		if (eTime > 0)
			eTime -= sTime;
	if (eTime <= 0)
		eTime = cTime;
	sTime = 0;
	if (verbose) {
/*		fprintf(stderr, "\n\tread: %u dur: %u\n\twait: ", cTime, eTime); */
		fprintf(stderr, "\tplay: %s %.2f sec ", 
			((fdin == 0) ? "stdin" : fname),
			((((float)eTime / 1000000.0) * params.midi.clockRate.tv_usec)));
		fflush(stdout);
	}
						/*	
						 * WAIT LOOP--read status, check time, sleep 2, 
						 */
	while(MidiGetVars(fd, &vars) != 0) {
/*		printf("_%d", vars.midi.flag.running); */
		if(vars.midi.flag.running == 1) {
			cTime = vars.midi.songPointer;
			if(cTime > eTime)		/* exit if done */
				goto drain;
			if (verbose)			/* write message every 4 seconds */
				if((cTime - xTime) > 4000) {
					fprintf(stderr, "%.2f \t", ((((float)cTime / 1000000.0)
						* (float)params.midi.clockRate.tv_usec)));
					fflush(stderr);
					MidiPrintVars(&vars);
					xTime = cTime;
				}
			sleep(2);
		} else {
			if (!silent)
				fprintf(stderr, "\tOFF: %u\n", vars.midi.clockTick);
			goto drain;
		}
	}		/* end of wait loop */
drain:
	mp_msg("\nEND: draining...");

	ioctl(fd, I_FLUSH, FLUSHRW);			/* flush output */
	mp_msg(" all notes off....");
	allNotesOff(fd);						/* turn all notes off explicitly! */
	ioctl(fd, I_FLUSH, FLUSHRW);			/* flush output */
	mp_msg(" exit\n");
	exit(0);
}			/* end of main() */


/******************************************
 * I/O Routines
 */

u_char *MidiGetMsg(fd, msg) MidiMessage *msg; {
	u_char *buf;
	char *malloc();
								/* read msg header into argument*/
	if (read(fd, (char *)msg, sizeof(MidiMessage)) != sizeof(MidiMessage))
		return (0);
	if ((buf = (u_char *)malloc(msg->mc_size))) { /* allocate data buffer */
								/* read data */
		if (read(fd, (char *)buf, (int)msg->mc_size) != msg->mc_size)
			return (0);
		return (buf);	/* return data pointer */
	}
	return (0);
}


int MidiWriteMsg(fd, msg, buf) MidiMessage *msg; u_char *buf; {
	struct iovec iov[2];

	iov[0].iov_base = (void *) msg;
	iov[0].iov_len = sizeof(MidiMessage);
	iov[1].iov_base = (void *) buf;
	iov[1].iov_len = msg->mc_size;
	writev(fd, iov, 2);
	fsync(fd);
}


/******************************
 * Print Routines
 */
MidiPrintMsg(msg, data) MidiMessage *msg; u_char *data; {
	register int i, len;
	char *MidiCommandName();

	printf("%5u %4u %7.7s %3d: ", 
			cTime, msg->mc_delay, 
			MidiCommandName(msg->mc_type), msg->mc_size);
	len = msg->mc_size;
	if (len > 24) len = 24;
	for  (i = 0; i < len; i++)
		printf("%02x ", data[i]);
	printf("\n");
}

static char *itoa(n) {
	static char buf[128];

	return (sprintf(buf, "%d", n));
}

MidiPrintParams(i) MidiParameters *i; {

	printf("\tclk %d+%-d//%d  %s-%s-%4s id=%02x, recv=%5d\n",
				 i->midi.clockRate.tv_sec, i->midi.clockRate.tv_usec, 
				 i->midi.clockModulus,
				 i->midi.option.internalClock? "intc" : "extc",
				 i->midi.option.autoStart ? "astrt" : "",
				 i->midi.option.autoStop ? "astop" : "",
				 i->midi.id, i->midi.recvBufferSize);
	printf("\tinput: %-10s %-10s\n\toutput: %-10s %-10s\n",
				 i->input.option.runningStatus ? "runningS" : "",
				 i->input.option.loopBack ? "loopback" : "",
				 i->output.option.runningStatus ? "runningS" : "",
				 i->output.option.loopBack ? "loopback" : "");
/*
	printf("\tsmpte %d+%-d %4s %4s %4s %4s %4s %4s\n",
				 i->smpte.clockRate.tv_sec,
				 i->smpte.clockRate.tv_usec,
				 ((i->smpte.type == smpte_f24) ? "F24" :
					((i->smpte.type == smpte_f25) ? "F25" :
					 ((i->smpte.type == smpte_f30) ? "F30" :
						((i->smpte.type == smpte_f30drop) ? "F30D" :
						 itoa((int) i->smpte.type))))),
				 i->smpte.option.internalClock ? "intc" : "extc",
				 i->smpte.option.autoStart ? "astrt" : "",
				 i->smpte.option.autoStop ? "astop" : "",
				 i->smpte.option.dropFrame ? "drop" : "",
				 i->smpte.option.reverse ? "srev" : "");
*/
}

MidiPrintVars(d) MidiVariables *d; {

	printf("\tmidi vars: t=%5.5d, p=%d, %5s\n",
				 d->midi.clockTick, d->midi.songPointer,
				 d->midi.flag.running ? "running" : "not running");
/*
	printf("\trealtime: %5s-%5s-%5s-%5s-%5s-%5s-%5s-%5s\n",
				 d->realtime.midiClock ? "Clock" : "",
				 d->realtime.midiStart ? "Start" : "",
				 d->realtime.midiStop ? "Stop" : "",
				 d->realtime.midiContinue ? "Continue" : "",
				 d->realtime.midiActive ? "Active" : "",
				 d->realtime.midiReset ? "Reset" : "",
				 d->realtime.midiMTC ? "MTC" : "",
				 d->realtime.midiSongPointer ? "SongPointer" : "");
	printf("\tsmpte: %02d:%02d:%02d:%02d:%02d %5s %d\n",
				 d->smpte.hours, d->smpte.minutes, d->smpte.seconds,
				 d->smpte.frames, d->smpte.fraction,
				 d->smpte.flag.running ? "running" : "not running",
				 d->smpte.flag.qframe);
*/
}


/*
 * Utility Routines
 */
allNotesOff(fd) {
	register int h, i;

	for (h=0; h<16; h++) {
		for (i=0; i<128; i++)
			if (MidiPutNote(fd, h, i, 0) < 0)
				perror("MidiPutNote");
		usleep(100000);		/* wait 100 ms to drain */
	}
}

void SigInt() {
	printf("\nInterrupt MIDI output; sending all notes off...");
	fflush(stdout);
	ioctl(MidiFd, I_FLUSH, FLUSHRW);
	allNotesOff(MidiFd);
	printf("exit.\n");
	exit(1);
}


printUsage() {		/* print a pretty usage message */

	fprintf(stderr, "\n *\n %s\n %s\n %s\n *\n %s\n %s\n %s  \n %s\n %s\n %s\n %s\n %s  \n %s\n %s\n %s\n *\n %s\n %s\n %s\n *\n *\n\n",

	"* mplay [ -r rate ] [ -s start ] [ -d dur | -e end ]",
	"*		[ -v -u -p -x -c ] [ file_name ] [ < | (data or process) ]",
	"*		[ writes MIDI to /dev/midiw or ASCII to stderr/out ]",

	"*	-rate usec	set the microseconds/tick rate (default = 1000)",
	"*	-start tick	skip 'tick' clock ticks from start (default = 0)",
	"*	-dur tick	play for 'tick' ticks of data (play all)",
	"*	-end tick	end at time 'tick' (play all)",
	"*	-print		print out notes as ASCII to stdout (off)",
	"*	-status		print out driver and MIDI status (off)",
	"*	-u		print usage and exit",
	"*	-c		read compact input format",
	"*	-v		print verbose messages to stderr",
	"*	-z		be silent; no messages, only MIDI or ASCII data",
	"*	file_name	(last option) midi file, empty to read from stdin",

	"*  Shorthand options",
	"*			-d = dur, -s = start, -e = end,",
	"*			-r = rate, -p = print, -x = status");
}


/* END */
