/************************************************************************
 * playevents.c  -- actually sends sorted list of events to device
 *
 * 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"

/* sustain gus/fm percussion to prevent driver dropoff... */
#define P_SUSTAIN

#ifdef USE_SEQUENCER2
#define seq_set_patch(a, b, c) (SEQ_SET_PATCH(a, b, c))
#define seq_key_pressure(a, b, c, d) (SEQ_KEY_PRESSURE(a, b, c, d))
#define seq_start_note(a, b, c, d) (SEQ_START_NOTE(a, b, c, d))
#define seq_stop_note(a, b, c, d) (SEQ_STOP_NOTE(a, b, c, d))
#define seq_control(a, b, c, d) (SEQ_CONTROL(a, b, c, d))
#define seq_chn_pressure(a, b, c) (SEQ_CHN_PRESSURE(a, b, c))
#define seq_bender(a, b, c) (SEQ_BENDER(a, b, c))
#define seq_reset() (ioctl(seqfd, SNDCTL_SEQ_RESET, 0))
#else
extern void seq_set_patch(int, int, int);
extern void seq_key_pressure(int, int, int, int);
extern void seq_start_note(int, int, int, int);
extern void seq_stop_note(int, int, int, int);
extern void seq_control(int, int, int, int);
extern void seq_chn_pressure(int, int, int);
extern void seq_bender(int, int, int);
extern void seq_reset();
#endif

SEQ_USE_EXTBUF();
extern int playing, newevent, graphics, verbose;
extern int dochan, gus_dev, ext_dev, sb_dev, perc, seqfd;
extern int play_gus, play_fm, play_ext;
extern struct mididata *event;
extern float skew;
extern char *gmvoice[256];
int ticks;

extern void cleanup();
extern void load_sysex(int, char *);

char *metatype[7] =
{"Text event", "Copyright Notice", "Sequence/Track name",
 "Instrument Name", "Lyric", "Marker", "Cue Point"};

char *notes[12] =	/* my personal preference on notes */
{"C", "Db", "D", "Eb", "E", "F", "F#", "G", "Ab", "A", "Bb", "B"};
char *sharps[12] =	/* for a sharp key */
{"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "B#", "B"};
char *flats[12] =	/* for a flat key */
{"C", "Db", "D", "Eb", "E", "F", "Gb", "G", "Gb", "A", "Bb", "B"};
char *kflat[15] = 	/* name of key with 'x' flats */
{"C", "F", "Bb", "Eb", "Ab", "Db", "Gb", "Cb", "Fb", "Bbb", "Ebb",
"Abb", "Gbb", "Cbb", "Fbb" };	/* anyone using more flats is SICK */
char *ksharp[15] =	/* name of key with 'x' sharps */
{"C", "G", "D", "A", "E", "B", "F#", "C#", "G#", "D#", "A#",
"E#","B#", "F##", "C##"};	/* anyone using more sharps is SICK */

/* these are to make my life a little easier */
#define BUF		&buf[strlen(buf)]
#define CMD		event[i].cmd
#define CHN		(event[i].cmd & 0xf)
#define NOTE		event[i].arg1
#define VEL		event[i].arg2
#define TIME		event[i].etime
#define ARG1		event[i].arg1
#define ARG2		event[i].arg2
#define DATA		event[i].data
#define OCTAVE		(NOTE / 12)
#define OCHAR		(dochan ? 0x30 + OCTAVE : 0)
#define XPOS		(1 + 4 * (dochan ? CHN : OCTAVE))
#define YPOS		(dochan ? 25 - NOTE % 24 : 13 - NOTE % 12)
#define NNAME		nn[NOTE % 12]
void playevents()
{
    unsigned int i, lasttime = 0;
    unsigned int tempo = 500000;
    float current = 0.0, dtime = 0.0;
    int division = -1, use_dev;
    char **nn;
    char buf[2048];

    seq_reset();
    SEQ_START_TIMER();
    *buf = 0;
    nn = notes;		/* this can be changed by midi keysig */
    playing = 1;
    for (i = 0; playing && i < newevent; i++) {
	if (ISMIDI(CHN))
	   use_dev = ext_dev;
	else if (ISGUS(CHN))
	   use_dev = gus_dev;
	else
	   use_dev = sb_dev;
	if (CMD == meta_event && ARG1 == set_tempo) {
	    tempo = ARG2;
	    if (verbose > 3)
		printf("Tempo: %d bpm\n", tempo * 3 / 12500);
	}
	if (TIME > lasttime) {
	    if (division > 0) {
		dtime = mf_ticks2sec(TIME - lasttime, division, tempo) * skew;
		current += dtime;
	    } else if (division < 0)
		current = mf_ticks2sec(TIME, division, tempo) * skew;
	    /* interrupt if we have to wait 81.92 seconds or more */
	    if (dtime > 8192 && playing)
		cleanup();
	    else if (dtime > 0.75)
		SEQ_WAIT_TIME((ticks = ((int) current)));
	    /* keep output synchronized with music */
	    if (verbose || graphics) {
		if (playing)
		    SEQ_DUMPBUF();
		if (graphics)	/* show picture we've stored in buf */
		    printf("%s\033[1;73H\033[37m%0.1f\n", buf, current / 100);
	    }
	    *buf = 0;
	    lasttime = TIME;
	}
	if (CMD == MThd) {
	    division = ARG2;
	    if (verbose)
		printf("format=%d, tracks=%d, division=%d\n",
		       ARG1 >> 8, ARG1 & 0xff, division);
	} else if (CMD == MIDI_SYSTEM_PREFIX)
	    load_sysex(ARG1, DATA);
	else if (CMD == meta_event) {
	    if (ARG1 < 8 && ARG1 > 0 && verbose)
		printf("%s: %s\n", metatype[ARG1 - 1], DATA);
	    else if (ARG1 == key_signature) {
		int value = (int) (ARG2) / 2;
		if (graphics)
		    nn = (!value ? notes : value < 0 ? flats : sharps);
		else if (verbose > 1)
		    printf("Key Signature: %s\n",
			   (value >= 0 ? ksharp[value] : kflat[-value]));
	    }
	} else if (playing)
	    switch (CMD & 0xf0) {
	    case MIDI_KEY_PRESSURE:
		if (ISPERC(CHN) && VEL && !ISMIDI(CHN)) {
		    seq_set_patch(use_dev, CHN, NOTE + 128);
		}
#ifdef P_SUSTAIN
		if (!ISPERC(CHN) || VEL || ISMIDI(CHN))
#endif
		    { seq_key_pressure(use_dev, CHN, NOTE, VEL); }
		if (graphics)
		    if (VEL) {
			sprintf(BUF, "\033[%d;%dH\033[%dm%s%c",
				YPOS, XPOS, NOTE % 7 + 31, NNAME, OCHAR);
		    } else
			sprintf(BUF, "\033[%d;%dH   ", YPOS, XPOS);
		break;
	    case MIDI_NOTEON:
		if (ISPERC(CHN) && VEL && !ISMIDI(CHN)) {
		    seq_set_patch(use_dev, CHN, NOTE + 128);
		}
#ifdef P_SUSTAIN
		if (!ISPERC(CHN) || VEL || ISMIDI(CHN))
#endif
		    { seq_start_note(use_dev, CHN, NOTE, VEL); }
		if (graphics)
		    if (VEL) {
			sprintf(BUF, "\033[%d;%dH\033[%dm%s%c",
				YPOS, XPOS, NOTE % 7 + 31, NNAME, OCHAR);
		    } else
			sprintf(BUF, "\033[%d;%dH   ", YPOS, XPOS);
		break;
	    case MIDI_NOTEOFF:
#ifdef P_SUSTAIN
		if (!ISPERC(CHN) || ISMIDI(CHN))
#endif
		    { seq_stop_note(use_dev, CHN, NOTE, VEL); }
		if (graphics)
		    sprintf(BUF, "\033[%d;%dH   ", YPOS, XPOS);
		break;
	    case MIDI_CTL_CHANGE:
		seq_control(use_dev, CHN, ARG1, ARG2);
		break;
	    case MIDI_CHN_PRESSURE:
		seq_chn_pressure(use_dev, CHN, ARG1);
		break;
	    case MIDI_PITCH_BEND:
		seq_bender(use_dev, CHN, ARG1);
		if (graphics)
		    sprintf(BUF, "\033[37m\033[%d;%dH%c", 2, 3 + 4 * CHN,
			 ARG1 == 0x40 ? ' ' : ARG1 > 0x2000 ? '^' : 'v');
		break;
	    case MIDI_PGM_CHANGE:
		if (ISMIDI(CHN) || !ISPERC(CHN)) {
		    seq_set_patch(use_dev, CHN, ARG1);
		}
		if (graphics) {
		    sprintf(BUF, "\033[37m");
		    if (!ISPERC(CHN) && dochan)
			sprintf(BUF, "\033[1;%dH%c%c%c", 1 + 4 * CHN,
				gmvoice[ARG1][0], gmvoice[ARG1][1],
				gmvoice[ARG1][2]);
		    else if (dochan)
			sprintf(BUF, "\033[1;%dH%03d", 1 + 4 * CHN,
				ARG1);
		    else
			sprintf(BUF, "\033[1;50H%8s %3d", (ISPERC(CHN) ?
				"drumset" : gmvoice[ARG1]), ARG1);
		}
		break;
	    default:
		break;
	    }
    }
    if (playing) {
	SEQ_DUMPBUF();
	if (verbose)
	    printf("** Ended at %0.1f sec\n", current / 100);
	if (graphics)
	    printf("\033[1;73H*Ended\033[K\n");
    } else {
	if (verbose)
	    printf("** Interrupted at %0.1f sec\n", current / 100);
	else if (graphics)
	    printf("\033[1;73H*Abort\033[K\n");
	seq_reset();
    }
    for (i = 0; i < newevent; i++)
	if (DATA)
	    free(DATA);
    if (graphics)
	printf("\033[22;1H\033[J\033[m\n");
    playing = newevent = 0;
}
