#ifdef HAVE_CONFIG_H
#include "config.h"
#endif /* HAVE_CONFIG_H */
#include <stdio.h>
#include <stdlib.h>

#ifndef NO_STRING_H
#include <string.h>
#else
#include <strings.h>
#endif

#ifndef __WIN32__
#include <unistd.h>
#endif

#include "timidity.h"
#include "common.h"
#include "instrum.h"
#include "playmidi.h"
#include "controls.h"
#include "output.h"
#include "arc.h"
#include "wrd.h"
#define DEFINE_GLOBALS
#ifdef JAPANESE
#include "mid-j.defs"
#else
#include "mid.defs"
#endif /* JAPANESE */

/* to avoid some unnecessary parameter passing */
static int32 at;
static int current_read_track;
static int current_read_format;
static int32 divisions, tempo;

/*ARGSUSED*/
static int null_cmsg(int type, int verbosity_level, char *fmt, ...){return 0;}
static void null_proc(){}
static ControlMode null_control_mode =
{
    "", 0,
    1,0,0,
    0,
    NULL, null_proc, NULL, NULL, null_cmsg, null_proc
};

static PlayMode null_play_mode =
{
    8000, PE_MONO, 0,
    -1,
    {0,0,0,0,0},
    "", 0,
    "",
    NULL, NULL, NULL, NULL
};

/*ARGSUSED*/
static int  null_wrdt_open(char *wrdt_opts) { return 0; }
/*ARGSUSED*/
static void null_wrdt_apply(int cmd, int argc, int args[]) { }
static void null_wrdt_update_events(void) { }
static void null_wrdt_end(void) { }
static void null_wrdt_close(void) { }
static WRDTracer null_wrdt_mode =
{
    "No WRD trace", '-',
    0,
    null_wrdt_open,
    null_wrdt_apply,
    NULL,
    null_wrdt_update_events,
    NULL,
    null_wrdt_end,
    null_wrdt_close
};

PlayMode *play_mode = &null_play_mode;
ControlMode *ctl = &null_control_mode;
WRDTracer *wrdt = &null_wrdt_mode;

static struct
{
    char *name;
    char *info;
} control_names[] =
{
    {"Bank select MSB",		"0-127, MSB"},	/* 0 */
    {"Modulation wheel MSB",	"0-127, MSB"},	/* 1 */
    {"Breath control MSB",	"0-127, MSB"},	/* 2 */
    {NULL,			"0-127, MSB"},	/* 3 */
    {"Foot controller MSB",	"0-127, MSB"},	/* 4 */
    {"Portamento time MSB",	"0-127, MSB"},	/* 5 */
    {"Data entry MSB",		"0-127, MSB"},	/* 6 */
    {"Main volume MSB",		"0-127, MSB"},	/* 7 */
    {"Balance MSB",		"0-127, MSB"},	/* 8 */
    {NULL,			"0-127, MSB"},	/* 9 */
    {"Pan position MSB",	"0-127, MSB"},	/* 10 */
    {"Expression MSB",		"0-127, MSB"},	/* 11 */
    {"Effect control 1 MSB",	"0-127, MSB"},	/* 12 */
    {"Effect control 2/control change MSB","0-127"},/* 13 */
    {NULL,			"0-127, MSB"},	/* 14 */
    {NULL,			"0-127, MSB"},	/* 15 */
    {"General purpose control 1 MSB","0-127, MSB"},	/* 16 */
    {"General purpose control 2 MSB","0-127, MSB"},	/* 17 */
    {"General purpose control 3 MSB","0-127, MSB"},	/* 18 */
    {"General purpose control 4 MSB","0-127, MSB"},	/* 19 */
    {NULL, "0-127, MSB"},/* 20 */
    {NULL, "0-127, MSB"},/* 21 */
    {NULL, "0-127, MSB"},/* 22 */
    {NULL, "0-127, MSB"},/* 23 */
    {NULL, "0-127, MSB"},/* 24 */
    {NULL, "0-127, MSB"},/* 25 */
    {NULL, "0-127, MSB"},/* 26 */
    {NULL, "0-127, MSB"},/* 27 */
    {NULL, "0-127, MSB"},/* 28 */
    {NULL, "0-127, MSB"},/* 29 */
    {NULL, "0-127, MSB"},/* 30 */
    {NULL, "0-127, MSB"},/* 31 */
    {"Bank select LSB",		"0-127, LSB"},	/* 32 */
    {"Modulation wheel LSB",	"0-127, LSB"},	/* 33 */
    {"Breath control LSB",	"0-127, LSB"},	/* 34 */
    {NULL,			"0-127, LSB"},	/* 35 */
    {"Foot controller LSB",	"0-127, LSB"},	/* 36 */
    {"Portamento time LSB",	"0-127, LSB"},	/* 37 */
    {"Data entry LSB",		"0-127, LSB"},	/* 38 */
    {"Main volume LSB",		"0-127, LSB"},	/* 39 */
    {"Balance LSB",		"0-127, LSB"},	/* 40 */
    {"Pan position LSB",	"0-127, LSB"},	/* 41 */
    {"Expression LSB",		"0-127, LSB"},	/* 42 */
    {"Effect control 1 LSB",	"0-127, LSB"},	/* 43 */
    {"Effect control 2 LSB",	"0-127, LSB"},	/* 44 */
    {NULL, "0-127, LSB"},/* 45 */
    {NULL, "0-127, LSB"},/* 46 */
    {NULL, "0-127, LSB"},/* 47 */
    {NULL, "0-127, LSB"},/* 48 */
    {NULL, "0-127, LSB"},/* 49 */
    {NULL, "0-127, LSB"},/* 50 */
    {NULL, "0-127, LSB"},/* 51 */
    {NULL, "0-127, LSB"},/* 52 */
    {NULL, "0-127, LSB"},/* 53 */
    {NULL, "0-127, LSB"},/* 54 */
    {NULL, "0-127, LSB"},/* 55 */
    {NULL, "0-127, LSB"},/* 56 */
    {NULL, "0-127, LSB"},/* 57 */
    {NULL, "0-127, LSB"},/* 58 */
    {NULL, "0-127, LSB"},/* 59 */
    {NULL, "0-127, LSB"},/* 60 */
    {NULL, "0-127, LSB"},/* 61 */
    {NULL, "0-127, LSB"},/* 62 */
    {NULL, "0-127, LSB"},/* 63 */
    {"Damper pedal on/off (Sustain)",	"0=off, 127=on"},/* 64 */
    {"Portamento on/off",	"0=off, 127=on"},/* 65 */
    {"Sustenuto on/off",	"0=off, 127=on"},/* 66 */
    {"Soft pedal on/off",	"0=off, 127=on"},/* 67 */
    {"Legato pedal on/off",	"0=off, 127=on"},/* 68 */
    {"Hold 2 pedal on/off",	"0=off, 127=on"},/* 69 */
    {"Sound variation",		"0-127"},	/* 70 */
    {"Harmonic content/Sound timbre","0-127"},	/* 71 */
    {"Release time",		"0-127"},	/* 72 */
    {"Attack time",		"0-127"},	/* 73 */
    {"Brightness",		"0-127"},	/* 74 */
    {NULL,"0-127"},	/* 75 */
    {NULL,"0-127"},	/* 76 */
    {NULL,"0-127"},	/* 77 */
    {NULL,"0-127"},	/* 78 */
    {NULL,"0-127"},	/* 79 */
    {"General purpose control 5 (Mute)", NULL},/* 80 */
    {"General purpose control 6", NULL},/* 81 */
    {"General purpose control 7", NULL},/* 82 */
    {"General purpose control 8(Portamento control (64 = C4))", NULL},/* 83 */
    {NULL, NULL},	/* 84 */
    {NULL, NULL},	/* 85 */
    {NULL, NULL},	/* 86 */
    {NULL, NULL},	/* 87 */
    {NULL, NULL},	/* 88 */
    {NULL, NULL},	/* 89 */
    {NULL, NULL},	/* 90 */
    {"Reverb",			"0-127"},	/* 91 */
    {"Tremulo depth",		"0-127"},	/* 92 */
    {"Chorus depth",		"0-127"},	/* 93 */
    {"Celeste (detune) depth",	"0-127"},	/* 94 */
    {"Phaser level",		"0-127"},	/* 95 */
    {"Data entry +1",		"0"},		/* 96 */
    {"Data entry -1",		"0"},		/* 97 */
    {"NRPN LSB", NULL},	/* 98 */
    {"NRPN MSB", NULL},	/* 99 */
    {"RPN LSB",	NULL},	/* 100 */
    {"RPN MSB", NULL},	/* 101 */
    {NULL, NULL},	/* 102 */
    {NULL, NULL},	/* 103 */
    {NULL, NULL},	/* 104 */
    {NULL, NULL},	/* 105 */
    {NULL, NULL},	/* 106 */
    {NULL, NULL},	/* 107 */
    {NULL, NULL},	/* 108 */
    {NULL, NULL},	/* 109 */
    {NULL, NULL},	/* 110 */
    {NULL, NULL},	/* 111 */
    {NULL, NULL},	/* 112 */
    {NULL, NULL},	/* 113 */
    {NULL, NULL},	/* 114 */
    {NULL, NULL},	/* 115 */
    {NULL, NULL},	/* 116 */
    {NULL, NULL},	/* 117 */
    {NULL, NULL},	/* 118 */
    {NULL, NULL},	/* 119 */
    {"All sound off",		"0"},			/* 120 */
    {"All controllers off",	"0"},			/* 121 */
    {"Local control on/off",	"0=off 127=on"},	/* 122 */
    {"All notes off",		"0"},			/* 123 */
    {"Omni mode off (includes all notes off)",	"0"},	/* 124 */
    {"Omni mode on (includes all notes off)",	"0"},	/* 125 */
    {"Poly mode on/off(includes all notes off)", ""},	/* 126 */
    {"Poly mode on(incl mono=off&all notes off)","0"},	/* 127 */
};


#define POS tf_tell(tf)

#define HEX(x) ("0123456789abcdef"[x])
static void print_02x(int32 x)
{
    int c1, c2;

    c1 = HEX((x >> 4) & 0x0f);
    c2 = HEX(x & 0x0f);

    putchar('0');
    putchar('x');
    putchar(c1);
    putchar(c2);
}

static void print_d(int32 val)
{
    char buff[16];
    char *p;

    if(val == 0x80000000) /* Note that -0x80000000 equal 0x80000000 */
    {
	fputs("-2147483648", stdout);
	return;
    }

    if(val < 0)
    {
	putchar('-');
	val = -val;
    }

    p = buff + 15;
    *p = '\0';
    do
    {
	*--p = (char)(val % 10 + '0');
	val /= 10;
    } while(val > 0);
    fputs(p, stdout);
}

static void print_x(int32 val)
{
    char buff[16];
    char *p;

    if(val == 0x80000000)
    {
	fputs("-80000000", stdout);
	return;
    }

    if(val < 0)
    {
	putchar('-');
	val = -val;
    }

    p = buff + 15;
    *p = '\0';
    do
    {
	*--p = HEX(val & 0xf);
	val >>= 4;
    } while(val > 0);
    fputs(p, stdout);
}

/* Read variable-length number (7 bits per byte, MSB first) */
static int32 getvl(struct timidity_file *tf)
{
    int32 l = 0;
    int c;

    if((c = tf_getc(tf)) == EOF)
	return EOF;
    putchar('[');
    print_02x(c);

    l += (c & 0x7f);
    if(!(c & 0x80))
    {
	putchar(']');
	putchar('=');
	print_d(l);
	putchar(' ');
	return l;
    }
    l <<= 7;

    for(;;)
    {
	if((c = tf_getc(tf)) == EOF)
	{
	    fputs(",EOF]=??" NLS, stdout);
	    return -1;
	}
	putchar(',');
	print_02x(c);

	l += (c & 0x7f);
	if(!(c & 0x80))
	{
	    putchar(']');
	    putchar('=');
	    print_d(l);
	    putchar(' ');
	    return l;
	}
	l <<= 7;
    }
}

/* Print a string from the file, followed by a newline. Any non-ASCII
   or unprintable characters will be converted to periods. */
static void dumpstring(struct timidity_file *tf, int32 len)
{
    char *si, *so;
    int s_maxlen = SAFE_CONVERT_LENGTH(len);

    if(len <= 0)
    {
	fputs(NLS, stdout);
	return;
    }

    si = (char *)new_segment(&tmpbuffer, len + 1);
    so = (char *)new_segment(&tmpbuffer, s_maxlen);

    if(len != tf_read(si, 1, len, tf))
    {
	reuse_mblock(&tmpbuffer);
	return;
    }
    si[len]='\0';
    code_convert(si, so, s_maxlen, NULL, NULL);
    fputs(so, stdout);
    fputs(NLS, stdout);

    reuse_mblock(&tmpbuffer);
    return;
}

#define PRINT_D1(s1, d1, s2) \
  fputs(s1, stdout); \
  print_d(d1); \
  fputs(s2, stdout);

#define PRINT_D2(s1, d1, s2, d2, s3) \
  fputs(s1, stdout); \
  print_d(d1); \
  fputs(s2, stdout); \
  print_d(d2); \
  fputs(s3, stdout);

#define PRINT_D3(s1, d1, s2, d2, s3, d3, s4) \
  fputs(s1, stdout); \
  print_d(d1); \
  fputs(s2, stdout); \
  print_d(d2); \
  fputs(s3, stdout); \
  print_d(d3); \
  fputs(s4, stdout);

#define ENDPAREN ")" NLS

#define MAX_SYSEX_DUMPLEN 128
static void dump_sysex(struct timidity_file *tf, int32 len)
{
    unsigned char data[MAX_SYSEX_DUMPLEN];
    int32 i, skips, mid;
    char *mid_name;
    int32 addr;		/* SysEx address */

    fputs(NLS "\t SysEx", stdout);

    skips = 0;
    if(len == 0)
	goto end_of_sysex;

    if(len > sizeof(data))
    {
	skips = (int32)(len - sizeof(data));
	len = sizeof(data);
    }

    if((i = tf_read(data, 1, len, tf)) != len)
    {
	len = i;
	goto end_of_sysex;
    }

    /* Manufacture ID */
    mid = data[0];
    mid_name = mid2name(mid);
    if(mid_name == NULL)
	goto end_of_sysex;

    putchar(' ');
    fputs(mid_name, stdout);

    if(len >= 10 &&
       data[0] == 0x41 && /* Roland ID */
       data[1] == 0x10 && /* Device ID */
       data[2] == 0x42 && /* GS Model ID */
       data[3] == 0x12)   /* Data Set Command */
    {
	/* Roland GS-Based Synthesizers. */

	addr = (((int32)data[4])<<16 |
		((int32)data[5])<<8 |
		(int32)data[6]);

	if((addr & 0xFFF0FF) == 0x401015)
	    fputs(" Rhythm parts", stdout);
	else if((addr & 0xFFF0FF) == 0x401016)
	    fputs(" Key shift", stdout);
	else if(addr == 0x400004)
	    fputs(" Master volume", stdout);
	else if((addr & 0xFFF0FF) == 0x401019)
	    fputs(" Volume on/off", stdout);
	else if((addr & 0xFFF0FF) == 0x401002)
	    fputs(" Receive channel on/off", stdout);
	else if((addr & 0xFFF0FF) == 0x40101C)
	    fputs(" Random pan", stdout);
	else if(0x402000 <= addr && addr <= 0x402F5A)
	    fputs(" Controller routing", stdout);
	else if((addr & 0xFFF0FF) == 0x401040)
	    fputs(" Alternate scale tunings", stdout);
	else if((addr & 0xFFF0FF) == 0x404022)
	    fputs(" SC-88Pro insertion effect on/off", stdout);
	else if((addr & 0xFFFFF0) == 0x400130)
	{
	    fputs(" Changing effects", stdout);
	    switch(addr & 0xF)
	    {
	      case 0x0: /* reverb type */
		switch(data[7])
		{
		  case 0: fputs(" reverb room1", stdout); break;
		  case 1: fputs(" reverb room2", stdout); break;
		  case 2: fputs(" reverb room3", stdout); break;
		  case 3: fputs(" reverb hall1", stdout); break;
		  case 4: fputs(" reverb hall2", stdout); break;
		  case 5: fputs(" reverb palate", stdout); break;
		  case 6: fputs(" reverb delay", stdout); break;
		  case 7: fputs(" reverb panning delay", stdout); break;
		  default:
		    fputs(" reverb type ", stdout); print_02x(data[7]); break;
		}
		break;
	      case 0x3: fputs(" reverb level", stdout); break;
	      case 0x8:
		switch(data[7])
		{
		  case 0: fputs(" chorus 1", stdout); break;
		  case 1: fputs(" chorus 2", stdout); break;
		  case 2: fputs(" chorus 3", stdout); break;
		  case 3: fputs(" chorus 4", stdout); break;
		  case 4: fputs(" feedback chorus", stdout); break;
		  case 5: fputs(" flanger chorus", stdout); break;
		  case 6: fputs(" short delay chorus", stdout); break;
		  case 7: fputs(" short delay (FB) chorus", stdout); break;
		  default:
		    fputs(" chorus type ", stdout); print_02x(data[7]); break;
		}
		break;
	      case 0x9: fputs(" PRE-LPF", stdout); break;
	      case 0xa: fputs(" level", stdout); break;
	      case 0xb: fputs(" feed back", stdout); break;
	      case 0xc: fputs(" delay", stdout); break;
	      case 0xd: fputs(" rate", stdout); break;
	      case 0xe: fputs(" depth", stdout); break;
	      case 0xf: fputs(" send level", stdout); break;
	      default: putchar(' '); print_02x(addr & 0xF); break;
	    }
	}
	else if(addr == 400150)	/* SC-88 Delay type */
	{
	    switch(data[7])
	    {
	      case 0: fputs(" Delay type 1", stdout); break;
	      case 1: fputs(" Delay type 2", stdout); break;
	      case 2: fputs(" Delay type 3", stdout); break;
	      case 3: fputs(" Delay type 4", stdout); break;
	      case 4: fputs(" Delay type pan delay 1", stdout); break;
	      case 5: fputs(" Delay type pan delay 2", stdout); break;
	      case 6: fputs(" Delay type pan delay 3", stdout); break;
	      case 7: fputs(" Delay type pan delay 4", stdout); break;
	      case 8: fputs(" Delay type delay to reverb", stdout); break;
	      case 9: fputs(" Delay type pan repeat", stdout); break;
	      default:
		fputs(" Delay type ", stdout); print_02x(data[7]); break;
	    }
	}
	else if((addr & 0xFFF0FF) == 0x401003)
	    fputs(" Rx pitch-bend", stdout);
	else if(addr == 0x400110)
	    fputs(" Voice reserve", stdout);
	else if(addr == 0x400158)
	    fputs(" Master delay level", stdout);
	else if(addr == 0x40007F)
	    fputs(" Reset", stdout);
	else if(addr == 0x00007F)
	    fputs(" SC-88 Single-Module", stdout);
    }
    else if(len > 9 &&
	    data[0] == 0x41 && /* Roland ID */
	    data[1] == 0x10 && /* Device ID */
	    data[2] == 0x45 && 
	    data[3] == 0x12 && 
	    data[4] == 0x10 && 
	    data[5] == 0x00 && 
	    data[6] == 0x00)
    {
	fputs(" Insert Text", stdout);
    }

    else if(len > 7 &&
	    data[0] == 0x43 &&
	    (data[1] == 0 || data[1] == 0x10) &&
	    data[2] == 0x4C)
    {
	/* Yamaha XG-Based Synthesizers. */

	addr = (((int32)data[4])<<16 |
		((int32)data[5])<<8 |
		(int32)data[6]);

	if(addr == 0x00007E)
	{
	    fputs(" System ON", stdout);
	}
	else
	{
	    switch(addr & 0xFF00FF)
	    {
	      case 0x100000: fputs(" Input gain", stdout); break;
	      case 0x100001: fputs(" Bank select MSB", stdout); break;
	      case 0x100002: fputs(" Bank select LSB", stdout); break;
	      case 0x100003: fputs(" Program change", stdout); break;
	      case 0x100004: fputs(" Rcv Channel", stdout); break;
	      case 0x10000B: fputs(" Volume", stdout); break;
	      case 0x10000E: fputs(" Pan", stdout); break;
	      case 0x100011: fputs(" Dry level", stdout); break;
	      case 0x100012: fputs(" Chorus send", stdout); break;
	      case 0x100013: fputs(" Reverb send", stdout); break;
	      case 0x100014: fputs(" Variation send", stdout); break;
	      case 0x100032: fputs(" Rcv Program change", stdout); break;
	      case 0x100033: fputs(" Rcv Control change", stdout); break;
	      case 0x100039: fputs(" Rcv Volume", stdout); break;
	      case 0x10003A: fputs(" Rcv Pan", stdout); break;
	      case 0x10003B: fputs(" Rcv Expression", stdout); break;
	      case 0x100060: fputs(" AC2 Controller number", stdout); break;
	    }
	}
    }
    else if(len > 4 && mid >= 0x7e)
    {
	int subid, subid2;

	subid = data[2];
	subid2 = data[3];

	switch(subid)
	{
	  case 0x01:
	    fputs(" Sample dump header", stdout);
	    break;
	  case 0x02:
	    fputs(" Data packet", stdout);
	    break;
	  case 0x03:
	    fputs(" Dump request", stdout);
	    break;
	  case 0x04:
	    fputs(" Device control", stdout);
	    switch(subid2)
	    {
	      case 0x01:
		fputs(" Master volume", stdout);
		break;
	      default:
		print_02x(subid2);
		break;
	    }
	    break;
	  case 0x09:
	    fputs(" System enable/disable", stdout);
	    break;
	  case 0x0F:
	    fputs(" Handshaking message ACK", stdout);
	    break;
	  case 0x0E:
	    fputs(" Handshaking message NAK", stdout);
	    break;
	  case 0x0D:
	    fputs(" Handshaking message Cancel", stdout);
	    break;
	  case 0x0C:
	    fputs(" Handshaking message Wait", stdout);
	    break;
	}
    }

  end_of_sysex:
    putchar(':');
    for(i = 0; i < len; i++)
    {
	putchar(' ');
	print_02x(data[i]);
    }
    if(skips)
    {
	fputs("...", stdout);
	skip(tf, skips);
    }
}

static void dump_key_sig(struct timidity_file *tf)
{
    int key;
#ifdef JAPANESE
    char buff[32];
    static char *notename[24] =
    {
	"ハ", "嬰ハ", "ニ", "嬰ニ", "ホ", "ヘ", "嬰ヘ", "ト", "嬰ト", "イ", "嬰イ", "ロ",
	"ハ", "変ニ", "ニ", "変ホ", "ホ", "ヘ", "変ト", "ト", "変イ", "イ", "変ロ", "ロ"
    };
    static char *keyname[2] = {"長調", "短調"};
    static unsigned char *charset = (unsigned char *)"あ";
    char *icode;
#else
    static char *notename[24] =
    {
	"C", "#C", "D", "#D", "E", "F", "#F", "G", "#G", "A", "#A", "B",
	"C", "-D", "D", "-E", "E", "F", "-G", "G", "-A", "A", "-B", "B"
    };
    static char *keyname[2] = {"major", "minor"};
#endif /* JAPANESE */
    int sf, mi;

    fputs("Key signature: ", stdout);
    sf = tf_getc(tf);
    mi = tf_getc(tf);
    print_02x(sf);
    putchar(' ');
    print_02x(mi);
    fputs(": ", stdout);

    if(sf >= 128)
	sf -= 256;
    if(sf < -7 || sf > 7 || mi > 1)
    {
	fputs("?" NLS, stdout);
	return;
    }

    key = 60 + 7 * sf;
    if(mi)
	key -= 3;
    key %= 12;
    if(sf < 0)
	key += 12;

#ifdef JAPANESE
    strcpy(buff, notename[key]);
    strcat(buff, keyname[mi]);
    switch(charset[0])
    {
	case 0xa4: icode = "EUC"; break;
	case 0x82: icode = "SJIS"; break;
	case 0x1b: icode = "JIS"; break;
	default: icode = NULL; break;
    }
    code_convert(buff, NULL, sizeof(buff), icode, NULL);
    fputs(buff, stdout);
#else
    fputs(notename[key], stdout);
    putchar(' ');
    fputs(keyname[mi], stdout);
#endif /* JAPANESE */
    fputs(NLS, stdout);
}

static void dump_time_sig(struct timidity_file *tf)
{
    int nn, dd, cc, bb;

    fputs("Time signature: ", stdout);
    nn = tf_getc(tf);
    dd = tf_getc(tf);
    cc = tf_getc(tf);
    bb = tf_getc(tf);
    print_02x(nn); putchar(' ');
    print_02x(dd); putchar(' ');
    print_02x(cc); putchar(' ');
    print_02x(bb); fputs(": ", stdout);

    print_d(nn); putchar('/'); print_d(1<<dd);
    putchar(' ');
    print_d(cc); fputs(" clock ", stdout);
    print_d(bb); fputs(" q.n.", stdout);

    fputs(NLS, stdout);
}

static int print_midi_event(struct timidity_file *tf)
{
    static uint8 laststatus, lastchan;
    static uint8 nrpn=0, rpn_msb[MAX_CHANNELS], rpn_lsb[MAX_CHANNELS];
    uint8 me, type, a,b,c;
    int32 i, len;

    for(;;)
    {
	print_x(POS);
	putchar('\t');
	errno=0;
	len = getvl(tf);
	if(errno && len == -1)
	{
		fprintf(stderr,
			"%s: read_midi_event: %s" NLS,
			current_filename, strerror(errno));
	    return -1;
	}
	at += len;
	fputs("at=", stdout);
	print_d(at);
	putchar(' ');
	errno = 0;
	if((i = tf_getc(tf)) == EOF)
	{
	    if(errno > 0)
		fprintf(stderr,
			"%s: read_midi_event: %s" NLS,
			current_filename, strerror(errno));
	    else
	    {
		fprintf(stderr, "Warning: %s: Too shorten midi file." NLS,
			current_filename);
		return 0;
	    }
	    return -1;
	}

	me = (uint8)i;
	fputs("me=", stdout);
	print_02x(me);
	putchar(' ');

	if(me==0xF0 || me == 0xF7) /* SysEx event */
	{
	    if((len = getvl(tf)) == EOF)
	    {
		if(errno)
		    fprintf(stderr,
			    "%s: read_midi_event: %s" NLS,
			    current_filename, strerror(errno));
		else
		{
		    fprintf(stderr, "Warning: %s: Too shorten midi file." NLS,
			    current_filename);
		    return 0;
		}
		return -1;
	    }
	    dump_sysex(tf, len);
	    fputs(NLS, stdout);
	}
	else if(me==0xFF) /* Meta event */
	{
	    type = tf_getc(tf);
	    fputs("type=", stdout);
	    print_02x(type);
	    putchar(' ');

	    len=getvl(tf);
	    if (type>0 && type<16)
	    {
		static char *label[]={
		    "Text event: ", "Text: ", "Copyright: ", "Track name: ",
		    "Instrument: ", "Lyric: ", "Marker: ", "Cue point: "};
		fputs(NLS "\t ", stdout);
		if(type == 3 &&
		   (current_read_format == 0 ||
		    (current_read_format == 1 && current_read_track == 0)))
		    fputs("Sequence: ", stdout);
		else
		    fputs(label[(type>7) ? 0 : type], stdout);
		dumpstring(tf, len);
	    }
	    else
		switch(type)
		{
		  case 0x00:
		    fputs(NLS "\t Sequence Number: ", stdout);
		    print_02x(tf_getc(tf));
		    putchar(' ');
		    print_02x(tf_getc(tf));
		    fputs(NLS, stdout);
		    break;

		  case 0x2F: /* End of Track */
		    fputs(NLS "\t End of Track" NLS, stdout);
		    return 0;

		  case 0x51: /* Tempo */
		    a = tf_getc(tf);
		    b = tf_getc(tf);
		    c = tf_getc(tf);
		    print_02x(a);
		    putchar(' ');
		    print_02x(b);
		    putchar(' ');
		    print_02x(c);
		    fputs(NLS "\t Tempo=", stdout);
		    tempo = c + (int32)b * 256 + (int32)a * 65536;
		    print_d(tempo);
		    printf(" (%d BPM, %.3f msec/delta)",
			   (int)(60.0 * 1000000.0 / tempo + 0.5),
			   (double)tempo / divisions / 1000.0);
		    fputs(NLS, stdout);
		    return 1;

		  case 0x54: /* SMPTE Offset (hr mn se fr ff) */
		    fputs(NLS "\t SMPTE offset: ", stdout);
		    for(i = 0; i < 4; i++)
		    {
			print_02x(tf_getc(tf));
			putchar(' ');
		    }
		    print_02x(tf_getc(tf));
		    fputs(NLS, stdout);
		    break;

		  case 0x58: /* Time Signature (nn dd cc bb) */
		    fputs(NLS "\t ", stdout);
		    dump_time_sig(tf);
		    break;

		  case 0x59: /* Key Signature (sf mi) */
		    fputs(NLS "\t ", stdout);
		    dump_key_sig(tf);
		    break;

		  case 0x7f: /* Sequencer-Specific Meta-Event */
		    fputs(NLS "\t Sequencer specific meta event:",
			  stdout);
		    for(i = 0; i < len; i++)
		    {
			putchar(' ');
			print_02x(tf_getc(tf));
		    }
		    fputs(NLS, stdout);
		    break;

		  case 0x20: /* MIDI channel prefix (SMF v1.0) */
		    fputs(NLS "\t Channel prefix: ", stdout);
		    print_d(tf_getc(tf));
		    fputs(NLS, stdout);
		    break;

		  case 0x21: /* MIDI port number */
		    fputs(NLS "\t MIDI port number: ", stdout);
		    print_d(tf_getc(tf));
		    fputs(NLS, stdout);
		    break;

		  default:
		    fputs(NLS "\t Meta event: type: ", stdout);
		    print_02x(type);
		    fputs(" val:", stdout);
		    for(i = 0; i < len; i++)
		    {
			putchar(' ');
			print_02x(tf_getc(tf));
		    }
		    fputs(NLS, stdout);
		    break;
		}
	}
	else
	{
	    a = me;
	    if(a & 0x80) /* status byte */
	    {
		lastchan = a & 0x0F;
		laststatus = (a>>4) & 0x07;
		a = tf_getc(tf);
		print_02x(a);
		putchar(' ');
		a &= 0x7F;
	    }
	    switch(laststatus)
	    {
	      case 0: /* Note off */
		b = tf_getc(tf);
		print_02x(b);
		b &= 0x7F;
		PRINT_D2(NLS "\t Note off (ch=", lastchan,
                         ", note=", a, ENDPAREN);
		return 1;

	      case 1: /* Note on */
		b = tf_getc(tf);
		print_02x(b);
		b &= 0x7F;
		PRINT_D3(NLS "\t Note on (ch=", lastchan,
			 ", note=", a,
			 ", velocity=", b,
			 ENDPAREN);
		return 1;

	    case 2: /* Key Pressure */
		b = tf_getc(tf);
		print_02x(b);
		b &= 0x7F;
		PRINT_D3(NLS "\t Key pressure (ch=", lastchan,
			 ", note=", a,
			 ", velocity=", b,
			 ENDPAREN);
		return 1;

	    case 3: /* Control change */
		b = tf_getc(tf);
		print_02x(b);
		b &= 0x7F;
		fputs(NLS "\t Control: ", stdout);
		switch(a)
		{
		  case 98:  nrpn=1; rpn_lsb[lastchan]=b; break;
		  case 99:  nrpn=1; rpn_msb[lastchan]=b; break;
		  case 100: nrpn=0; rpn_lsb[lastchan]=b; break;
		  case 101: nrpn=0; rpn_msb[lastchan]=b; break;

		  case 6:
		    if(nrpn)
		    {
			switch(rpn_msb[lastchan])
			{
			  case 0x01:
			    switch(rpn_lsb[lastchan])
			    {
			      case 0x08:
				fputs("NRPN: Vibrato rate ch=", stdout);
				print_d(lastchan);
				fputs(" rate=", stdout);
				print_02x(b);
				fputs(NLS, stdout);
				return 1;

			      case 0x09:
				fputs("NRPN: Vibrato depth ch=", stdout);
				print_d(lastchan);
				fputs(" depth=", stdout);
				print_02x(b);
				fputs(NLS, stdout);
				return 1;

			      case 0x0a:
				fputs("NRPN: Vibrato delay ch=", stdout);
				print_d(lastchan);
				fputs(" delay=", stdout);
				print_02x(b);
				fputs(NLS, stdout);
				return 1;

			      case 0x20:
				fputs("NRPN: Filter cutoff frequency ch=",
				      stdout);
				print_d(lastchan);
				fputs(" val=", stdout);
				print_02x(b);
				fputs(NLS, stdout);
				return 1;

			      case 0x21:
				fputs("NRPN: Filter Resonance ch=", stdout);
				print_d(lastchan);
				fputs(" val=", stdout);
				print_02x(b);
				fputs(NLS, stdout);
				return 1;

			      case 0x63:
				fputs("NRPN: Attack Time ch=", stdout);
				print_d(lastchan);
				fputs(" time=", stdout);
				print_02x(b);
				fputs(NLS, stdout);
				return 1;

			      case 0x64:
				fputs("NRPN: EG Decay Time ch=", stdout);
				print_d(lastchan);
				fputs(" time=", stdout);
				print_02x(b);
				fputs(NLS, stdout);
				return 1;

			      case 0x66:
				fputs("NRPN: EG Release Time ch=", stdout);
				print_d(lastchan);
				fputs(" time=", stdout);
				print_02x(b);
				fputs(NLS, stdout);
				return 1;

			      default:
				fputs("NRPN: ", stdout);
				print_d(0x01);
				putchar(',');
				print_d(rpn_lsb[lastchan]);
				putchar(' ');
				fputs(" ch=", stdout);
				print_d(lastchan);
				fputs(" data=", stdout);
				print_02x(b);
				fputs(NLS, stdout);
				return 1;
			    }
			    /*NOTREACHED*/

			  case 0x14:
			    fputs("NRPN: Drum Filter Cutoff (Yamaha) ch=",
				  stdout);
			    print_d(lastchan);
			    fputs(" note=", stdout);
			    print_d(rpn_lsb[lastchan]);
			    fputs(" val=", stdout);
			    print_02x(b);
			    fputs(NLS, stdout);
			    return 1;

			  case 0x15:
			    fputs("NRPN: Drum Filter Resonance (Yamaha) ch=",
				  stdout);
			    print_d(lastchan);
			    fputs(" note=", stdout);
			    print_d(rpn_lsb[lastchan]);
			    fputs(" val=", stdout);
			    print_02x(b);
			    fputs(NLS, stdout);
			    return 1;

			  case 0x16:
			    fputs("NRPN: Drum EG Attack Time (Yamaha) ch=",
				  stdout);
			    print_d(lastchan);
			    fputs(" note=", stdout);
			    print_d(rpn_lsb[lastchan]);
			    fputs(" time=", stdout);
			    print_02x(b);
			    fputs(NLS, stdout);
			    return 1;

			  case 0x17:
			    fputs("NRPN: Drum EG Decay Time (Yamaha) ch=",
				  stdout);
			    print_d(lastchan);
			    fputs(" note=", stdout);
			    print_d(rpn_lsb[lastchan]);
			    fputs(" time=", stdout);
			    print_02x(b);
			    fputs(NLS, stdout);
			    return 1;

			  case 0x18:
			    fputs("NRPN: Coarse Pitch of Drum (Roland)/"
				  "Fine Pitch of Drum (Yamaha) ch=",
				  stdout);
			    print_d(lastchan);
			    fputs(" note=", stdout);
			    print_d(rpn_lsb[lastchan]);
			    fputs(" pitch=", stdout);
			    print_02x(b);
			    fputs(NLS, stdout);
			    return 1;

			  case 0x19:
			    fputs("NRPN: Coarse Pitch of Drum (Yamaha) ch=",
				  stdout);
			    print_d(lastchan);
			    fputs(" note=", stdout);
			    print_d(rpn_lsb[lastchan]);
			    fputs(" pitch=", stdout);
			    print_02x(b);
			    fputs(NLS, stdout);
			    return 1;

			  case 0x1a:
			    fputs("NRPN: Level of Drum ch=",
				  stdout);
			    print_d(lastchan);
			    fputs(" note=", stdout);
			    print_d(rpn_lsb[lastchan]);
			    fputs(" level=", stdout);
			    print_02x(b);
			    fputs(NLS, stdout);
			    return 1;

			  case 0x1c:
			    fputs("NRPN: Panpot of Drum ch=",
				  stdout);
			    print_d(lastchan);
			    fputs(" note=", stdout);
			    print_d(rpn_lsb[lastchan]);
			    fputs(" pan=", stdout);
			    print_02x(b);
			    fputs(NLS, stdout);
			    return 1;

			  case 0x1d:
			    fputs("NRPN: Reverb Send Level of Drum ch=",
				  stdout);
			    print_d(lastchan);
			    fputs(" note=", stdout);
			    print_d(rpn_lsb[lastchan]);
			    fputs(" level=", stdout);
			    print_02x(b);
			    fputs(NLS, stdout);
			    return 1;

			  case 0x1e:
			    fputs("NRPN: Chorus Send Level of Drum ch=",
				  stdout);
			    print_d(lastchan);
			    fputs(" note=", stdout);
			    print_d(rpn_lsb[lastchan]);
			    fputs(" level=", stdout);
			    print_02x(b);
			    fputs(NLS, stdout);
			    return 1;

			  case 0x1f:
			    fputs("NRPN: Variation Send Level of Drum ch=",
				  stdout);
			    print_d(lastchan);
			    fputs(" note=", stdout);
			    print_d(rpn_lsb[lastchan]);
			    fputs(" level=", stdout);
			    print_02x(b);
			    fputs(NLS, stdout);
			    return 1;

			  default:
			    fputs("NRPN: ", stdout);
			    print_d(rpn_lsb[lastchan]);
			    putchar(',');
			    print_d(rpn_lsb[lastchan]);
			    putchar(' ');
			    fputs(" ch=", stdout);
			    print_d(lastchan);
			    fputs(" data=", stdout);
			    print_02x(b);
			    fputs(NLS, stdout);
			    return 1;
			}
			/*NOTREACHED*/
		    }
		    else
		    {
			switch((rpn_msb[lastchan]<<8) | rpn_lsb[lastchan])
			{
			  case 0x0000: /* Pitch bend sensitivity */
			    PRINT_D2("RPN: Pitch bend sensitivity ch=",
				     lastchan, ", pitchsens=", b, NLS);
			    return 1;

			  case 0x0001:
			    PRINT_D2("RPN: Fine tuning ch=", lastchan,
				     ", tune=", b, NLS);
			    return 1;

			  case 0x0002:
			    PRINT_D2("RPN: Coarse tuning ch=", lastchan,
				     ", tune=", b, NLS);
			    return 1;

			  case 0x7F7F: /* RPN reset */
			    /* reset pitch bend sensitivity to 2 */
			    PRINT_D1("RPN: RPN reset (ch=",
				     lastchan,
				     ", pitchsens=2)" NLS);
			    return 1;
			  default:
			    fputs("RPN: ", stdout);
			    print_d(rpn_lsb[lastchan]);
			    putchar(',');
			    print_d(rpn_lsb[lastchan]);
			    putchar(' ');
			    fputs(" ch=", stdout);
			    print_d(lastchan);
			    fputs(" data=", stdout);
			    print_02x(b);
			    fputs(NLS, stdout);
			    return 1;
			}
		    }
		    /*NOTREACHED*/
		  default:
		    break;
		}

		if(control_names[a].name != NULL)
		    fputs(control_names[a].name, stdout);
		else
		    print_02x(a);
		PRINT_D2(" (ch=", lastchan, ", val=", b, ENDPAREN);
		break;

	      case 4: /* Program change */
		a &= 0x7f;
		PRINT_D2(NLS "\t Program change (ch=", lastchan,
			 ", prog=", a, ENDPAREN);
		return 1;

	      case 5:
		a &= 0x7f;
		PRINT_D2(NLS "\t Channel pressure (ch=", lastchan,
			 ", pressure=", a, ENDPAREN);
		return 1;

	      case 6: /* Pitch wheel */
		b = tf_getc(tf);
		print_02x(b);
		putchar(' ');
		b &= 0x7F;
		i = (int32)a + (int32)b * 128;
		if(i == 0x2000 || i < 0 || i > 0x3FFF)
		    i = 0;
		else
		    i -= 0x2000;
		PRINT_D2(NLS "\t Pitch wheel (ch=", lastchan,
			 ", pitchbend=", i, ENDPAREN);
		return 1;

	      default:
		fputs(NLS "\t *** Can't happen: status ", stdout);
		print_02x(laststatus);
		fputs(", channel ", stdout);
		print_02x(lastchan);
		fputs(NLS, stdout);
		break;
	    }
	}
    }
    /*NOTREACHED*/
    return 0;
}

/* Read a midi track */
static int scan_track(struct timidity_file *tf, int append)
{
    int32 len;
    char tmp[4];

    if(!append)
	at=0;

    /* Check the formalities */
    if ((tf_read(tmp,1,4,tf) != 4) || (tf_read(&len,4,1,tf) != 1))
    {
	fprintf(stderr, "%s: Can't read track header." NLS, current_filename);
	return -1;
    }
    len=BE_LONG(len);
    if (memcmp(tmp, "MTrk", 4))
    {
	fprintf(stderr, "%s: Corrupt MIDI file." NLS, current_filename);
	return -1;
    }

    printf("%lx\tTrackLen: %ld" NLS, (long)POS - 4, (long)len);

    for (;;)
    {
	int status;

	status = print_midi_event(tf);

	if(status < 0)	/* Some kind of error  */
	    return status;

	if(status == 0)		/* End-of-track */
	    return 0;
    }
}

void scan_midi_file(char *fname)
{
    int32 len;
    int16 format, tracks, divisions_tmp;
    char tmp[4];
    struct timidity_file *tf;

    if((tf = open_file(fname, 1, OF_SILENT)) == NULL)
    {
	perror(fname);
	return;
    }
    if(tf->url->url_tell == NULL)
	tf->url = url_buff_open(tf->url, 1);

    printf("%s:" NLS, fname);
    at = 0;

    errno = 0;
    if ((tf_read(tmp,1,4,tf) != 4) || (tf_read(&len,4,1,tf) != 1))
    {
	if(errno)
	{
	    fprintf(stderr, "%s: %s" NLS,
		    current_filename, strerror(errno));
	}
	else
	    fprintf(stderr, "%s: Not a MIDI file!" NLS, current_filename);
	close_file(tf);
	return;
    }
    len=BE_LONG(len);

    /* Mac Binary analisys from Aoki Daisuke <dai@y7.net> */
    if(memcmp(tmp, "MThd", 4) || len < 6)
    {
      char tmp2[128];
      if ((tmp[0]!='\0') || (tf_read(tmp2, 1, (128-4-4), tf)!=(128-4-4)) || (tf_read(tmp,1,4,tf) != 4) || (tf_read(&len,4,1,tf) != 1))
        {
	  fprintf(stderr, "%s: Not a MIDI file!" NLS, current_filename);
	  close_file(tf);
	  return;
        }
      else
       {
         len=BE_LONG(len);
         if (memcmp(tmp, "MThd", 4) || len < 6)
           {
	     fprintf(stderr, "%s: Not a MIDI file!" NLS, current_filename);
	     close_file(tf);
             return;
           }
       }
    }

    printf("%x\t", 4);
    printf("HeaderLen: %d" NLS, (int)len);

    printf("%lx\t", (long)POS);
    tf_read(&format, 2, 1, tf);
    format=BE_SHORT(format);
    printf("Format: %d" NLS, format);

    printf("%lx\t", (long)POS);
    tf_read(&tracks, 2, 1, tf);
    tracks=BE_SHORT(tracks);
    printf("Tracks: %d" NLS, tracks);

    printf("%lx\t", (long)POS);
    tf_read(&divisions_tmp, 2, 1, tf);

    divisions_tmp=BE_SHORT(divisions_tmp);

    if(divisions_tmp<0)
    {
	/* SMPTE time -- totally untested. Got a MIDI file that uses this? */
	divisions=
	    (int32)(-(divisions_tmp/256)) * (int32)(divisions_tmp & 0xFF);
    }
    else divisions=(int32)(divisions_tmp);

    printf("Divisions: 0x%04x (%ld)" NLS,
	   ((unsigned)divisions_tmp) & 0xffff,  (long)divisions);

    if(len > 6)
    {
	int i, c;

	printf("OtherHeaderBytes:");
	for(i = len - 6; i < 0; i++)
	{
	    c = tf_getc(tf);
	    putchar(' ');
	    print_02x(c);
	}
	fputs(NLS, stdout);
    }
    if(format < 0 || format > 2)
    {
	fprintf(stderr, "%s: Unknown MIDI file format %d" NLS,
		current_filename, format);
	close_file(tf);
	return;
    }
#if 0
  ctl->cmsg(CMSG_INFO, VERB_VERBOSE,
       "Format: %d  Tracks: %d  Divisions: %d", format, tracks, divisions);
#endif

    current_read_format = format;
    tempo = 500000;
    switch(format)
    {
      case 0:
	current_read_track = 0;
	scan_track(tf, 0);
	break;

      case 1:
	for (current_read_track=0; current_read_track<tracks;
	     current_read_track++)
	{
	    printf("\tTrack %d" NLS, current_read_track);
	    if (scan_track(tf, 0))
		break;
	}
	break;

      case 2: /* We simply play the tracks sequentially */
	for (current_read_track=0; current_read_track<tracks;
	     current_read_track++)
	{
	    printf("\tTrack %d" NLS, current_read_track);
	    if (scan_track(tf, 1))
		break;
	}
	break;
    }
    close_file(tf);
}

extern struct URL_module URL_module_file;
extern struct URL_module URL_module_dir;
#ifdef SUPPORT_SOCKET
extern struct URL_module URL_module_http;
extern struct URL_module URL_module_ftp;
extern struct URL_module URL_module_news;
extern struct URL_module URL_module_newsgroup;
#endif /* SUPPORT_SOCKET */

int main(int argc, char **argv)
{
    int i;
    int nfiles;
    char **files;
    extern ArchiveFileList *archive_file_list;

    url_add_modules(
	&URL_module_file,
	&URL_module_dir,
#ifdef SUPPORT_SOCKET
	&URL_module_http,
	&URL_module_ftp,
	&URL_module_news,
	&URL_module_newsgroup,
#endif /* SUPPORT_SOCKET */
	NULL);

    if(argc == 1)
    {
	scan_midi_file("-");
	return 0;
    }

    nfiles = argc - 1;
    files  = argv + 1;

    files = expand_file_archives(files, &nfiles);
    for(i = 0; i < nfiles; i++)
	scan_midi_file(files[i]);
    close_archive_files(archive_file_list);
    return 0;
}
