/************************************************************************
   playevents.c  -- actually sends sorted list of events to device

   Copyright (C) 1994-1996 Nathan I. Laredo

   This program is modifiable/redistributable under the terms
   of the GNU General Public Licence.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
   Send your comments and all your spare pocket change to
   laredo@gnu.ai.mit.edu (Nathan Laredo) or to PSC 1, BOX 709, 2401
   Kelly Drive, Lackland AFB, TX 78236-5128, USA.
 *************************************************************************/
#include "playmidi.h"
#include <sys/time.h>

extern int seq_set_patch(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, int);
extern void seq_reset();

SEQ_USE_EXTBUF();
extern int graphics, verbose, division, ntrks, format;
extern int gus_dev, ext_dev, sb_dev, awe_dev, perc, seqfd, p_remap;
extern int play_gus, play_fm, play_ext, play_awe, reverb, chorus, chanmask;
extern int usevol[16];
extern struct miditrack seq[MAXTRKS];
extern float skew;
extern unsigned long int default_tempo;
extern void load_sysex(int, unsigned char *, int);
extern void showevent(int, unsigned char *, int);
extern void init_show();
extern int updatestatus();

unsigned long int ticks, tempo;
struct timeval start_time;

unsigned long int rvl(s)
struct miditrack *s;
{
    register unsigned long int value = 0;
    register unsigned char c;

    if (s->index < s->length && ((value = s->data[(s->index)++]) & 0x80)) {
	value &= 0x7f;
	do {
	    if (s->index >= s->length)
		c = 0;
	    else
		value = (value << 7) +
		    ((c = s->data[(s->index)++]) & 0x7f);
	} while (c & 0x80);
    }
    return (value);
}

/* indexed by high nibble of command */
int cmdlen[16] =
{0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 1, 1, 2, 0};

#define CMD		seq[track].running_st
#define TIME		seq[track].ticks
#define CHN		(CMD & 0xf)
#define NOTE		data[0]
#define VEL		data[1]

int playevents()
{
    unsigned long int tempo = default_tempo, lasttime = 0;
    unsigned int lowtime, track, best, length, loaded;
    unsigned char *data;
    double current = 0.0, dtime = 0.0;
    int use_dev, play_status, playing = 1;

    init_show();
    seq_reset();
    gettimeofday(&start_time, NULL);	/* for synchronization */
    for (track = 0; track < ntrks; track++) {
	seq[track].index = seq[track].running_st = 0;
	seq[track].ticks = rvl(&seq[track]);
    }
    for (best = 0; best < 16; best++) {
	if (ISAWE(best))
	    use_dev = awe_dev;
	else if (ISGUS(best))
	    use_dev = gus_dev;
	else if (ISFM(best))
	    use_dev = sb_dev;
	else
	    use_dev = ext_dev;
	seq_control(use_dev, best, CTL_BANK_SELECT, 0);
	seq_control(use_dev, best, CTL_EXT_EFF_DEPTH, reverb);
	seq_control(use_dev, best, CTL_CHORUS_DEPTH, chorus);
	seq_control(use_dev, best, CTL_MAIN_VOLUME, 127);
	seq_chn_pressure(use_dev, best, 127);
	seq_control(use_dev, best, 0x4a, 127);
    }
    SEQ_START_TIMER();
    SEQ_DUMPBUF();
    while (playing) {
	lowtime = ~0;
	for (best = track = 0; track < ntrks; track++)
	    if (seq[track].ticks < lowtime) {
		best = track;
		lowtime = TIME;
	    }
	if (lowtime == ~0)
	    break;		/* no more data to read */
	track = best;
	if (ISMIDI(CHN))
	    use_dev = ext_dev;
	else if (ISAWE(CHN))
	    use_dev = awe_dev;
	else if (ISGUS(CHN))
	    use_dev = gus_dev;
	else
	    use_dev = sb_dev;

	/* this section parses data in midi file buffer */
	if ((seq[track].data[seq[track].index] & 0x80) &&
	    (seq[track].index < seq[track].length))
	    CMD = seq[track].data[seq[track].index++];
	if (CMD == 0xff && seq[track].index < seq[track].length)
	    CMD = seq[track].data[seq[track].index++];
	if (CMD > 0xf7)	/* midi real-time message (ignored) */
	    length = 0;
	else if (!(length = cmdlen[(CMD & 0xf0) >> 4]))
	    length = rvl(&seq[track]);

	if (seq[track].index + length < seq[track].length) {
	    /* use the parsed midi data */
	    data = &(seq[track].data[seq[track].index]);
	    if (CMD == set_tempo)
		tempo = ((*(data) << 16) | (data[1] << 8) | data[2]);
	    if (TIME > lasttime) {
		if (division > 0) {
		    dtime = ((double) ((TIME - lasttime) * (tempo / 10000)) /
			     (double) (division)) * skew;
		    current += dtime;
		    lasttime = TIME;
		} else if (division < 0)
		    current = ((double) TIME /
			       ((double) ((division & 0xff00 >> 8) *
				   (division & 0xff)) * 10000.0)) * skew;
		/* stop if there's more than 40 seconds of nothing */
		if (dtime > 4096.0)
		    playing = 0;
		else if ((int) current > ticks) {
		    SEQ_WAIT_TIME((ticks = (int) current));
		    SEQ_DUMPBUF();
		    if (graphics)
			if ((play_status = updatestatus()) != NO_EXIT)
			    return play_status;
		}
	    }
	    if (CMD > 0x7f && CMD < 0xf0 && ISPERC(CHN) && p_remap) {
		CMD &= 0xf0;
		CMD |= (p_remap - 1);
	    }
	    loaded = 0;		/* for patch setting failures */
	    if (playing && CMD > 0x7f && ISPLAYING(CHN))
		switch (CMD & 0xf0) {
		case MIDI_KEY_PRESSURE:
		    if (ISPERC(CHN) && VEL && (!ISMIDI(CHN)&&!ISAWE(CHN)))
			loaded = seq_set_patch(CHN, NOTE + 128);
		    if (loaded != -1)
			seq_key_pressure(use_dev, CHN, NOTE, VEL);
		    break;
		case MIDI_NOTEON:
		    if (ISPERC(CHN) && VEL && (!ISMIDI(CHN)&&!ISAWE(CHN)))
			loaded = seq_set_patch(CHN, NOTE + 128);
		    if (VEL && usevol[CHN])
			VEL = usevol[CHN];
		    if (loaded != -1)
			seq_start_note(use_dev, CHN, NOTE, VEL);
		    break;
		case MIDI_NOTEOFF:
		    seq_stop_note(use_dev, CHN, NOTE, VEL);
		    break;
		case MIDI_CTL_CHANGE:
		    seq_control(use_dev, CHN, NOTE, VEL);
		    break;
		case MIDI_CHN_PRESSURE:
		    seq_chn_pressure(use_dev, CHN, NOTE);
		    break;
		case MIDI_PITCH_BEND:
		    seq_bender(use_dev, CHN, NOTE, VEL);
		    break;
		case MIDI_PGM_CHANGE:
		    if (ISMIDI(CHN) || ISAWE(CHN) || !ISPERC(CHN))
			NOTE = seq_set_patch(CHN, NOTE);
		    break;
		case MIDI_SYSTEM_PREFIX:
		    if (length > 1)
			load_sysex(length, data, CMD);
		    break;
		default:
		    break;
		}
	    if (verbose || graphics) {
		showevent(CMD, data, length);
		ioctl(seqfd, SNDCTL_SEQ_SYNC);
	    }
	}
	/* this last little part queues up the next event time */
	seq[track].index += length;
	if (seq[track].index >= seq[track].length)
	    seq[track].ticks = ~0;	/* mark track complete */
	else
	    seq[track].ticks += rvl(&seq[track]);
    }
    SEQ_DUMPBUF();
    return 1;
}
