/************************************************************************
 * playmidi.c -- November 11, 1994 -- previous release: August 1, 1994
 * Plays a MIDI file to any supported synth (including midi) device
 *
 *   MIDI recording during playback will come when the driver supports it
 *   A MIDI file editor will be integrated once MIDI recording is ready.
 *
 * This code was written by by Nathan Laredo (laredo@gnu.ai.mit.edu)
 * Source code may be freely distributed in unmodified form.
 *************************************************************************/
#include "playmidi.h"
#include "multisig.h"
#include <signal.h>
#include <getopt.h>
#include <fcntl.h>
#include <ctype.h>
#include <unistd.h>

SEQ_DEFINEBUF(SEQUENCERBLOCKSIZE);

struct mididata *event;		/* allocated/resized at runtime */

/* SET YOUR DEFAULT PLAYBACK DEVICE BY SETTING MASK OF CHANNELS TO PLAY */
int play_sb = 0, play_gus = 0xffff, play_ext = 0;

#define EVENTBLOCK	 4096
unsigned int maxevents = EVENTBLOCK;
int verbose = 0, chanmask = 0xffff, perc = PERCUSSION;
int newevent = 0, dochan = 1, force8bit = 0, wantopl3 = FM_DEFAULT_MODE;
int patchloaded[256], useprog[16], c_loaded[16], c_noteon[16];
int playing = 0, graphics = 0, reverb = 0, nrsynths, nrmidis;
int sb_dev = -1, gus_dev = -1, ext_dev = -1;
int seqfd, mfd;
float skew = 100.0;
extern char *gmvoice[256];
extern void playevents();
extern int gus_load(int);
extern void loadfm();
#define CHANNEL (dochan ? chan : 0)

#ifdef USE_SEQUENCER2
#define SEQUENCER_DEV "/dev/sequencer2"
#else
#define SEQUENCER_DEV "/dev/sequencer"
#endif

struct synth_info card_info[MAX_CARDS];
void synth_setup()
{
    int i;

    if (ioctl(seqfd, SNDCTL_SEQ_NRSYNTHS, &nrsynths) == -1) {
	fprintf(stderr, "there is no soundcard\n");
	exit(1);
    }
    for (i = 0; i < nrsynths; i++) {
	card_info[i].device = i;
	if (ioctl(seqfd, SNDCTL_SYNTH_INFO, &card_info[i]) == -1) {
	    fprintf(stderr, "cannot get info on soundcard\n");
	    perror(SEQUENCER_DEV);
	    exit(-1);
	}
	card_info[i].device = i;
	if (card_info[i].synth_type == SYNTH_TYPE_SAMPLE
	    && card_info[i].synth_subtype == SAMPLE_TYPE_GUS)
	    gus_dev = i;
	else if (card_info[i].synth_type == SYNTH_TYPE_FM)
	    sb_dev = i;
#ifdef USE_SEQUENCER2
	else if (card_info[i].synth_type == SYNTH_TYPE_MIDI)
	    if (ext_dev < 0)
		ext_dev = i;
#endif
    }

    if (gus_dev >= 0) {
	if (ioctl(seqfd, SNDCTL_SEQ_RESETSAMPLES, &gus_dev) == -1) {
	    perror("Sample reset");
	    exit(1);
	}
    }

#ifndef USE_SEQUENCER2
    if (ioctl(seqfd, SNDCTL_SEQ_NRMIDIS, &nrmidis) == -1) {
	fprintf(stderr, "can't get info about midi ports\n");
	exit(1);
    }
    if (nrmidis > 0) {
#ifdef FORCE_EXT_DEV
	ext_dev = FORCE_EXT_DEV;
#else
	ext_dev = nrmidis - 1;
#endif
    }
#endif

    if (!play_gus)
	gus_dev = -1;
    if (!play_sb)
	sb_dev = -1;
    if (!play_ext)
	ext_dev = -1;
    if (ext_dev < 0)
	play_ext = 0;
    if (sb_dev < 0)
	play_sb = 0;
    if (gus_dev < 0)
	play_gus = 0;
}

void seqbuf_dump()
{
    if (_seqbufptr)
	if (write(seqfd, _seqbuf, _seqbufptr) == -1) {
	    perror("write " SEQUENCER_DEV );
	    exit(-1);
	}
    _seqbufptr = 0;
    ioctl(seqfd, SNDCTL_SEQ_SYNC);
}

void record(dotime, cmd, arg1, arg2, data)
unsigned int dotime;
unsigned int cmd;
unsigned int arg1;
unsigned int arg2;
unsigned char *data;
{
    event[newevent].etime = dotime;
    if (!dochan && cmd < 0xf0)	/* stripping channel data? */
	event[newevent].cmd = cmd & 0xf0;
    else
	event[newevent].cmd = cmd;
    event[newevent].arg1 = arg1;
    event[newevent].arg2 = arg2;
    event[newevent++].data = data;
    if (newevent == maxevents) {
	maxevents += EVENTBLOCK;
	event = (struct mididata *)
	    realloc(event, maxevents * sizeof(struct mididata));
	if (event == NULL) {
	    fprintf(stderr, "No more memory for events, aborting\n");
	    exit(-1);
	}
    }
}

static int lastsignal = 0;

void do_nothing(int sig)
{
    signal((lastsignal = sig), do_nothing);
}

/* Catch falling children */
void fireman(int sig)
{
  while (waitpid(-1, NULL, WNOHANG) > 0)
    ;
}

void cleanup()
{
    /* this is probably over-protective */
    signal(SIGINT, do_nothing);
    if (!playing) {
	/* if not playing, we can trash everything */
	fprintf(stderr, "%s Program Killed\n",
		(graphics ? "\033[22;1H\033[m\033[J\n**" : "**"));
	exit(130);
    }
    playing = 0;
    signal(SIGINT, cleanup);
}

int do_getc()
{
    int c = 0;
    if (read(mfd, &c, 1) < 1)
	return EOF;
    return c;
}

int hextoi(s)
char *s;
{
    int i, j, k, l;

    j = 0;
    k = strlen(s);
    for (i = 0; i < k; i++) {
	l = toupper(s[i]);
	if (l > 64 && l < 71)
	    j += (l - 55) << ((k - i - 1) * 4);
	else if (l > 47 && l < 58)
	    j += (l - 48) << ((k - i - 1) * 4);
	else if (l != 88) {
	    fprintf(stderr, "invalid character in hexidecimal mask\n");
	    exit(-1);
	}
    }
    if (j < 0 || j > 0xffff) {
	fprintf(stderr, "mask must be between 0 and ffff hexidecimal\n");
	exit(-1);
    }
    return j;

}

void do_error(s)
char *s;
{
    fprintf(stderr, "Error: %s\n", s);
}

void do_header(format, ntrks, ldivision)
int format, ntrks, ldivision;
{
    /* force time = 0: assume no more than 1 header per file */
    record(0, MThd, (format << 8) + ntrks, ldivision, 0);
}

void do_trackstart()
{
    record(Mf_currtime, MTrk, 1, 0, 0);
}

void do_trackend()
{
    record(Mf_currtime, MTrk, 0, 0, 0);
}

void do_eot()
{
    record(Mf_currtime, meta_event, MTrk, 0, 0);
}

void do_program(chan, pgm)
int chan, pgm;
{
    unsigned int i, now = Mf_currtime;
    if (chanmask & (1 << chan)) {
	if (pgm < 0)
	    pgm = now = 0;
	if (useprog[chan])
	    pgm = useprog[chan] - 1;
	if (ISGUS(chan) && !ISPERC(chan))
	    if (gus_load(pgm)) {
		/* if loading a patch fails, use first loaded one */
		pgm = -1;
		for (i = 0; i < 127 && pgm < 0; i++)
		    if (patchloaded[i] && pgm < 0)
			pgm = i;
		if (pgm < 0) {
		    /* you should NEVER get this unless a song uses
		       every percussive instrument in the book first
		       before any melodic program changes */
		    fprintf(stderr, "Can't load any melodic patches!\n");
		    exit(-1);
		}
	    }
	record(now, MIDI_PGM_CHANGE + chan, pgm, 0, 0);
	if (now < c_loaded[CHANNEL] || c_loaded[CHANNEL] == -1)
	    c_loaded[CHANNEL] = now;	/* mark when channel loaded */
    }
}

void do_noteon(chan, pitch, vol)
int chan, pitch, vol;
{
    if (chanmask & (1 << chan)) {
	if (ISPERC(chan) && !ISMIDI(chan)) {
	    if ((ISFM(chan) && pitch > 46) ||
		(ISGUS(chan) && gus_load(pitch + 128)))
		return;
	}
	record(Mf_currtime, MIDI_NOTEON + chan, pitch, vol, 0);
	if (Mf_currtime < c_noteon[CHANNEL] || c_noteon[CHANNEL] == -1)
	    c_noteon[CHANNEL] = Mf_currtime;	/* mark when channel used */
    }
}

void do_pressure(chan, pitch, press)
int chan, pitch, press;
{
    if (chanmask & (1 << chan)) {
	if (ISPERC(chan) && !ISMIDI(chan)) {
	    if ((ISFM(chan) && pitch > 46) ||
		(ISGUS(chan) && gus_load(pitch + 128)))
		return;
	    if (ISFM(chan))
		pitch -= 27;
	}
	record(Mf_currtime, MIDI_KEY_PRESSURE + chan, pitch, press, 0);
	if (Mf_currtime < c_noteon[CHANNEL] || c_noteon[CHANNEL] == -1)
	    c_noteon[CHANNEL] = Mf_currtime;	/* mark when channel used */
    }
}

void do_noteoff(chan, pitch, vol)
int chan, pitch, vol;
{

    if (chanmask & (1 << chan))
	record(Mf_currtime, MIDI_NOTEOFF + chan, pitch, vol, 0);
}

void do_parameter(chan, control, value)
int chan, control, value;
{
    if (chanmask & (1 << chan))
	record(Mf_currtime, MIDI_CTL_CHANGE + chan, control, value, 0);
}

void do_pitchbend(chan, msb, lsb)
int chan, msb, lsb;
{
    if (chanmask & (1 << chan))
	record(Mf_currtime,
		    MIDI_PITCH_BEND + chan, lsb + (msb << 7), 0, 0);
}

void do_chanpressure(chan, press)
int chan, press;
{
    if (chanmask & (1 << chan))
	record(Mf_currtime, MIDI_CHN_PRESSURE + chan, press, 0, 0);
}

void do_sysex(leng, mess)
int leng;
char *mess;
{
    unsigned char *data;

    data = malloc(leng);
    if (data == NULL) {
	fprintf(stderr, "no memory for sysex, ignoring\n");
	return;
    }
    memcpy(data, mess, leng);
    record(Mf_currtime, MIDI_SYSTEM_PREFIX, leng, 0, data);
}

void do_metamisc(type, leng, mess)
int type, leng;
char *mess;
{
    unsigned char *data;

    data = malloc(leng);
    if (data == NULL) {
	fprintf(stderr, "no memory for meta misc, ignoring\n");
	return;
    }
    memcpy(data, mess, leng);
    record(Mf_currtime, meta_event, meta_event + (type << 16), leng, data);
}

void do_seqspecific(type, leng, mess)
int type, leng;
char *mess;
{
    unsigned char *data;

    data = malloc(leng);
    if (data == NULL) {
	fprintf(stderr, "no memory for meta special, ignoring\n");
	return;
    }
    memcpy(data, mess, leng);
    record
	(Mf_currtime, meta_event, sequencer_specific + (type << 16), leng, data);
}

void do_text(type, leng, mess)
int type, leng;
char *mess;
{
    int i;
    unsigned char *data;

    if (!strncmp(mess, "WinJammer", 9))		/* this is done in spite */
	return;

    data = malloc(leng + 1);
    if (data == NULL) {
	fprintf(stderr, "no memory for meta text, ignoring\n");
	return;
    }
    for (i = 0; i < leng; i++)
	data[i] = isprint(mess[i]) ? mess[i] : mess[i] ? 254 : 0;
    data[leng] = 0;
    record(Mf_currtime, meta_event, type, leng, data);
}

void do_seqnum(num)
int num;
{
    record(Mf_currtime, meta_event, sequence_number, num, 0);
}

void do_keysig(sf, mi)
int sf, mi;
{
    record(Mf_currtime, meta_event, key_signature, (sf * 2) | (mi & 1), 0);
}

void do_tempo(ltempo)
int ltempo;
{
    record(Mf_currtime, meta_event, set_tempo, ltempo, 0);
}

void do_timesig(nn, dd, cc, bb)
int nn, dd, cc, bb;
{
    unsigned char *data;

    data = malloc(16);
    if (data == NULL) {
	fprintf(stderr, "no memory for time sig, ignoring\n");
	return;
    }
    *(int *) &data[0] = nn;
    *(int *) &data[4] = dd;
    *(int *) &data[8] = cc;
    *(int *) &data[12] = bb;
    record(Mf_currtime, meta_event, time_signature, 0, data);
}

void do_smpte(hr, mn, se, fr, ff)
int hr, mn, se, fr, ff;
{
    unsigned char *data;

    data = malloc(20);
    if (data == NULL) {
	fprintf(stderr, "no memory for smpte time, ignoring\n");
	return;
    }
    *(int *) &data[0] = hr;
    *(int *) &data[4] = mn;
    *(int *) &data[8] = se;
    *(int *) &data[12] = fr;
    *(int *) &data[16] = ff;
    record(Mf_currtime, meta_event, smpte_offset, 0, data);
}

void do_arbitrary(leng, mess)
int leng;
char *mess;
{
    unsigned char *data;

    data = malloc(leng);
    if (data == NULL) {
	fprintf(stderr, "no memory for midi arbitrary, ignoring\n");
	return;
    }
    memcpy(data, mess, leng);
    record(Mf_currtime, meta_event, sequencer_specific, leng, data);
}

void initfuncs()
{
    Mf_error = (void *) do_error;
    Mf_header = (void *) do_header;
    Mf_trackstart = (void *) do_trackstart;
    Mf_trackend = (void *) do_trackend;
    Mf_noteon = (void *) do_noteon;
    Mf_noteoff = (void *) do_noteoff;
    Mf_pressure = (void *) do_pressure;
    Mf_parameter = (void *) do_parameter;
    Mf_pitchbend = (void *) do_pitchbend;
    Mf_program = (void *) do_program;
    Mf_chanpressure = (void *) do_chanpressure;
    Mf_sysex = (void *) do_sysex;
    Mf_metamisc = (void *) do_metamisc;
    Mf_seqnum = (void *) do_seqnum;
    Mf_timesig = (void *) do_timesig;
    Mf_smpte = (void *) do_smpte;
    Mf_tempo = (void *) do_tempo;
    Mf_eot = (void *) do_eot;
    Mf_keysig = (void *) do_keysig;
    Mf_seqspecific = (void *) do_seqspecific;
    Mf_text = (void *) do_text;
    Mf_arbitrary = (void *) do_arbitrary;
    Mf_getc = do_getc;
}

int do_compare(e1, e2)
struct mididata *e1, *e2;
{
    if (e1->etime > e2->etime)
	return 1;
    if (e1->etime < e2->etime)
	return -1;
    if (e1->cmd & 0xf0 == MIDI_PITCH_BEND)
	return -1;
    if (e2->cmd & 0xf0 == MIDI_PITCH_BEND)
	return 1;
    if (e1->cmd == meta_event && e1->arg1 == set_tempo)
	return 1;
    if (e2->cmd == meta_event && e2->arg1 == set_tempo)
	return -1;
    return 0;
}

#define GUNZIP "gunzip -c"
int copen(name)
char *name;
{

    int compressed = 0, namelen = strlen(name);
    char *command = NULL;
    FILE *myfile;

    if (namelen >= 2)
	if (!strncmp(name + namelen - 2, ".Z", 2) ||
	    !strncmp(name + namelen - 2, ".z", 2))
	    compressed++;
	else if (namelen >= 3)
	    if (!strncmp(name + namelen - 3, ".gz", 3))
		compressed++;

    if (!compressed) {
	if ((myfile = fopen(name, "rb")) == NULL) {
	    perror(name);
	    return -1;
	}
    } else {
	command = (char *) malloc(strlen(name) + strlen(GUNZIP) + 1);
	sprintf(command, "%s '%s'", GUNZIP, name);
	if ((myfile = popen(command, "rb")) == NULL) {
	    perror(name);
	    free(command);
	    return -1;
	}
	free(command);
    }
    return fileno(myfile);
}

#define LOCKFILE "/tmp/LCK..playmidi"
#define TMPLOCK "/tmp/playmidi..%d"
static char templock[32];

int locked(char *name)
{
    int f, p = 0;

    if ((f = open (name, O_RDONLY, 0)) != -1) {
	if (read(f, &p, sizeof(int)) < sizeof(int) || p == getpid())
	    p = 0;
	close(f);
    }
    if (!p || kill (p, lastsignal)) {
	unlink(name);
	p = 0;
    }
    lastsignal = 0;
    return p;
}

void removelock()
{
    unlink(templock);
    unlink(LOCKFILE);
}

int main(argc, argv)
int argc;
char **argv;
{
    extern char *optarg;
    extern int optind;
    int i, error = 0, j, newprog, silly_playback = 0;
    char *extra;

    while ((i = getopt(argc, argv, "48a:c:deE:fF:gG:i:Im:p:rE:R:St:vx:z"))
	    != -1)
	switch (i) {
	case '8':
	    force8bit++;
	    break;
	case 'a':
	case 'x':
	    j = atoi(optarg);
	    if (j < 1 || j > 16) {
		fprintf(stderr, "option -%c channel must be 1 - 16\n", i);
		exit(-1);
	    }
	    j = 1 << (j - 1);
	    if (i == 'a')
		chanmask |= j;
	    else
		chanmask &= ~j;
	    break;
	case 'c':
	    if (chanmask == 0xffff)
		chanmask = hextoi(optarg);
	    else
		chanmask |= hextoi(optarg);
	    break;
	case 'd':
	    chanmask = ~perc;
	    break;
	case 'e':
	    play_ext = 0xffff;
	    play_gus = play_sb = 0;
	    break;
	case 'g':
	    play_gus = 0xffff;
	    play_ext = play_sb = 0;
	    break;
	case 'f':
	case '4':
	    play_sb = 0xffff;
	    play_ext = play_gus = 0;
	    wantopl3 = (i == '4');
	    break;
	case 'I':
	    {
		int k;
		printf("Gravis Ultrasound Program information:");
		for (j = 0; j < 128; j++) {
		    extra = gmvoice[j];
		    printf("%c%3d %s", j % 6 ? ' ' : '\n', j + 1, extra);
		    for (k = strlen(extra); k < 8; k++)
			putchar(' ');
		}
		putchar('\n');
		exit(-1);
	    }
	    break;
	case 'i':
	    chanmask &= ~hextoi(optarg);
	    break;
	case 'm':
	    perc = hextoi(optarg);
	    break;
	case 'p':
	    if (strchr(optarg, ',') == NULL) {	/* set all channels */
		newprog = atoi(optarg);
		if (newprog < 1 || newprog > 129) {
		    fprintf(stderr, "option -p prog must be 1 - 129\n");
		    exit(-1);
		}
		for (j = 0; j < 16; j++)
		    useprog[j] = newprog;
	    } else {		/* set channels individually */
		extra = optarg;
		while (extra != NULL) {
		    j = atoi(extra);
		    if (j < 1 || j > 16) {
			fprintf(stderr, "opton -p chan must be 1 - 16\n");
			exit(-1);
		    }
		    extra = strchr(extra, ',');
		    if (extra == NULL) {
			fprintf(stderr, "option -p prog needed for chan %d\n",
				j);
			exit(-1);
		    } else
			extra++;
		    newprog = atoi(extra);
		    if (newprog < 1 || newprog > 129) {
			fprintf(stderr, "option -p prog must be 1 - 129\n");
			exit(-1);
		    }
		    useprog[j - 1] = newprog;
		    extra = strchr(extra, ',');
		    if (extra != NULL)
			extra++;
		}
	    }
	    break;
	case 'r':
	    graphics++;
	    break;
	case 't':
	    if ((skew = 100.0 * atof(optarg)) < 25.0) {
		fprintf(stderr, "option -t skew under 0.25 unplayable\n");
		exit(-1);
	    }
	    break;
	case 'E':
	    play_ext = hextoi(optarg);
	    play_sb &= ~play_ext;
	    play_gus &= ~play_ext;
	    break;
	case 'F':
	    play_sb = hextoi(optarg);
	    play_ext &= ~play_sb;
	    play_gus &= ~play_sb;
	    break;
	case 'G':
	    play_gus = hextoi(optarg);
	    play_sb &= ~play_gus;
	    play_ext &= ~play_gus;
	    break;
	case 'R':
	    reverb = atoi(optarg);
	    if (reverb < 0 || reverb > 127) {
		fprintf(stderr, "option -R reverb must be 0 - 127\n");
		exit(-1);
	    }
	    break;
	case 'S':
	    silly_playback++;
	    break;
	case 'v':
	    verbose++;
	    break;
	case 'z':
	    dochan = 0;
	    break;
	default:
	    error++;
	    break;
	}

    if (error || optind >= argc) {
	fprintf(stderr, "usage: %s [-options] midifilename\n", argv[0]);
	fprintf(stderr, "  -v       verbosity (additive)\n"
		"  -i x     set channels to ignore to bitmask x (hex)\n"
		"  -c x     set channels to play to bitmask x (hex)\n"
		"  -a x     add channel x to channel mask to play\n"
		"  -x x     exclude channel x from channel mask\n"
		"  -p [c,]x play program x on channel c (all if no c)\n"
		"  -t x     skew tempo by x (float)\n"
		"  -m x     override percussion mask (%04x) with x\n"
		"  -d       don't play any drum tracks (see above)\n"
		"  -e       output to external midi\n"
		"  -f       output to fm (sb patches)\n"
		"  -4       output to 4-op fm (opl/3 patches)\n"
		"  -g       output to gravis ultrasound\n"
		"  -E x     play channels in bitmask x external\n"
		"  -F x     play channels in bitmask x on fm\n"
		"  -G x     play channels in bitmask x on gus\n"
		"  -z       zero all midi channel data\n"
		"  -8       force 8-bit samples on ultrasound\n"
		"  -S       play files concurrently, conflicts ignored\n"
		"  -I       show list of all GUS programs and numbers\n"
		"  -R x     set reverb to x (for fm or ultrasound)\n"
		"  -r       real-time playback graphics\n",
		PERCUSSION);
	exit(-1);
    }

    multisig(SIGCHLD, fireman);
    signal(SIGINT, do_nothing);
    sprintf(templock, TMPLOCK, j = getpid());
    if ((i = creat (templock, 0644)) < 0) {
	perror("creat (templock, 0644)");
	exit(-1);
    } else {
	atexit(removelock);
	write(i, &j, sizeof(int));
	close(i);
	i = access(LOCKFILE, F_OK);
        if (i != -1)
	    fprintf(stderr,"Waiting on lock in \"remote control\" mode...");
	while (link(templock, LOCKFILE) < 0)
	    if (errno != EEXIST) {
		perror("link(templock, LOCKFILE)");
		unlink (templock);
		exit(-1);
	    } else if (locked(LOCKFILE))
		sleep(6);
	unlink(templock);
	if (i != -1)
	    fprintf(stderr,"Done!  Now playing!\n");
    }

    if (graphics)
	verbose = 0;
    if ((seqfd = open(SEQUENCER_DEV, O_WRONLY, 0)) < 0) {
	perror("open " SEQUENCER_DEV);
	exit(-1);
    }
    synth_setup();
    if (!(play_gus || play_sb || play_ext)) {
	fprintf(stderr, "%s: No playback device set.  Aborting.\n", argv[0]);
	exit(-1);
    }
    initfuncs();
    event = (struct mididata *) malloc(maxevents * sizeof(struct mididata));
    if (event == NULL) {
	fprintf(stderr, "no free memory for events, aborting\n");
	exit(-1);
    }
    signal(SIGINT, cleanup);
    if (play_sb) {
	loadfm();
    }
    if (play_gus) {
	gus_load(-1);
	card_info[gus_dev].nr_voices = 32;
    }
    for (i = 0; i < 16; i++)
	c_loaded[i] = c_noteon[i] = -1;
    /* play all filenames listed on command line */
    for (i = optind; i < argc; i++) {
	if ((mfd = copen(argv[i])) == -1) {
	    if ((extra = malloc(strlen(argv[i]) + 4)) == NULL) {
		fprintf(stderr, "unable to allocate %d bytes!\n",
			strlen(argv[i]) + 4);
		exit(-1);
	    }
	    sprintf(extra, "%s.mid", argv[i]);
	    if ((mfd = open(extra, O_RDONLY, 0)) == -1) {
		perror(argv[i]);
		exit(-1);
	    }
	    free(extra);
	}
	if (!silly_playback)
	    for (j = 0; j < 16; j++)
		c_loaded[j] = c_noteon[j] = -1;
	if (!silly_playback && play_gus)
	    gus_load(-1);
	mfread();
	close(mfd);
	if (!silly_playback) {
	    for (j = 0; j < 16; j++)
		if (c_noteon[j] != -1 &&
		    ( c_loaded[j] == -1 || c_loaded[j] > c_noteon[j]))
		    do_program(j, -1);
	    /* sort list of events by time */
	    qsort(event, newevent, sizeof(*event), do_compare);
	    if (graphics) {
		if (dochan)	/* plot notes by channel */
		    printf("\033[H\033[2Jch1 ch2 ch3 ch4 ch5 ch6 ch7 ch8 ch9 "
			   "c10 c11 c12 c13 c14 c15 c16");
		else		/* with no channel data plot by octave */
		    printf("\033[H\033[2JO0  O1  O2  O3  O4  O5  O6  O7  O8  "
			   "O9  O10");
		printf("\033[1;66HClock:\n\n%78s\n\n%78s\n%78s\n\n%78s\n"
		     "\033[65C%6d events\033[1m\n", "Playmidi 1.1", "by",
		       "Nathan Laredo", argv[i], newevent);
	    }
	    if (verbose)
		printf("** Now Playing \"%s\": %d events\n", argv[i], newevent);
	    playevents();
	}
    }
    if (silly_playback) {
	for (j = 0; j < 16; j++)
	    if (c_noteon[j] != -1 &&
		    ( c_loaded[j] == -1 || c_loaded[j] > c_noteon[j]))
		do_program(j, -1);
	/* sort list of events by time */
	qsort(event, newevent, sizeof(*event), do_compare);
	if (graphics) {
	    if (dochan)		/* plot notes by channel */
		printf("\033[H\033[2Jch1 ch2 ch3 ch4 ch5 ch6 ch7 ch8 ch9 "
		       "c10 c11 c12 c13 c14 c15 c16");
	    else		/* with no channel data plot by octave */
		printf("\033[H\033[2JO0  O1  O2  O3  O4  O5  O6  O7  O8  "
		       "O9  O10");
	    printf("\033[1;66HClock:\n\n%78s\n\n%78s\n%78s\n\n%78s\n"
		   "\033[65C%6d events\033[1m\n", "Playmidi 1.1", "by",
		   "Nathan Laredo", "All Files", newevent);
	}
	if (verbose)
	    printf("** Now Playing \"All Files\": %d events\n", newevent);
	playevents();
    }
    sleep(2);	/* allow for notes to decay before closing */
    if (close(seqfd) < 0) {
	perror("close " SEQUENCER_DEV);
	exit(-1);
    }
    exit(0);
}
/* end of file */
