shithub: omidi

ref: 0b7b60511d71e3c80deb8d1900e1ec778d964c0c
dir: omidi/omidi.c

View raw version
#include <u.h>
#include <libc.h>
#include "dat.h"
#include "fns.h"

enum{
	NCHIPS = 1,
	RATE = 44100
};
/* FIXME: 8192 bytes or shorts? */
static short abuf[8192];
static short *ap;
static int afd = -1;

/* FIXME: using multiple chips */
/* FIXME: the only reason this exists and FM_OPL is defined in dat.h instead of being
 * static in fmopl.c is to have multiple cards... and we don't support that; either
 * implement it and it's cool and it should be that way, or nuke this */
static FM_OPL *chip[NCHIPS];
static int oplretval;
static int oplregno;
static double otm;

enum{
	MINDELAY = 1,
	OPLBASE = 0x388
};

static int verbose;

static struct MusData mdat;

int ColorNums = -1;

const long Period[12] =
{
    907,960,1016,1076,
    1140,1208,1280,1356,
    1440,1524,1616,1712
};

static const char Portit[9] = {0,1,2, 8,9,10, 16,17,18};

static signed char Pans[18] = {0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0};

signed char Trig[18] = {0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0};

//#define SetBase(a) ((a)==2 ? (a) : 9)
#define SetBase(a) (a)

static uchar FMVol[65] = {
    /* Generated similarly as LogTable */
    0,32,48,58,64,70,74,77,80,83,86,
    88,90,92,93,95,96,98,99,100,102,
    103,104,105,106,107,108,108,109,
    110,111,112,112,113,114,114,115,
    116,116,117,118,118,119,119,120,
    120,121,121,122,122,123,123,124,
    124,124,125,125,126,126,126,127,
    127,127,128,128
};

static uchar adl[] = {
    14,0,0,1,143,242,244,0,8,1,6,242,247,0,14,0,0,1,75,242,244,0,8,1,0,242,247,0,14,0,0,1,73,242,244,0,8,1,0,242,246,0,14,0,0,129,18,242,247,0,6,65,0,242,247,0,14,0,0,1,87,241,247,0,0,1,0,242,247,0,14,0,0,1,147,241,247,0,0,1,0,242,247,0,14,0,0,1,128,161,
    242,0,8,22,14,242,245,0,14,0,0,1,146,194,248,0,10,1,0,194,248,0,14,0,0,12,92,246,244,0,0,129,0,243,245,0,14,0,0,7,151,243,242,0,2,17,128,242,241,0,14,0,0,23,33,84,244,0,2,1,0,244,244,0,14,0,0,152,98,243,246,0,0,129,0,242,246,0,14,0,0,24,35,246,246,
    0,0,1,0,231,247,0,14,0,0,21,145,246,246,0,4,1,0,246,246,0,14,0,0,69,89,211,243,0,12,129,128,163,243,0,14,0,0,3,73,117,245,1,4,129,128,181,245,0,14,0,0,113,146,246,20,0,2,49,0,241,7,0,14,0,0,114,20,199,88,0,2,48,0,199,8,0,14,0,0,112,68,170,24,0,4,177,
    0,138,8,0,14,0,0,35,147,151,35,1,4,177,0,85,20,0,14,0,0,97,19,151,4,1,0,177,128,85,4,0,14,0,0,36,72,152,42,1,12,177,0,70,26,0,14,0,0,97,19,145,6,1,10,33,0,97,7,0,14,0,0,33,19,113,6,0,6,161,137,97,7,0,14,0,0,2,156,243,148,1,12,65,128,243,200,0,14,0,
    0,3,84,243,154,1,12,17,0,241,231,0,14,0,0,35,95,241,58,0,0,33,0,242,248,0,14,0,0,3,135,246,34,1,6,33,128,243,248,0,14,0,0,3,71,249,84,0,0,33,0,246,58,0,14,0,0,35,74,145,65,1,8,33,5,132,25,0,14,0,0,35,74,149,25,1,8,33,0,148,25,0,14,0,0,9,161,32,79,
    0,8,132,128,209,248,0,14,0,0,33,30,148,6,0,2,162,0,195,166,0,14,0,0,49,18,241,40,0,10,49,0,241,24,0,14,0,0,49,141,241,232,0,10,49,0,241,120,0,14,0,0,49,91,81,40,0,12,50,0,113,72,0,14,0,0,1,139,161,154,0,8,33,64,242,223,0,14,0,0,33,139,162,22,0,8,33,
    8,161,223,0,14,0,0,49,139,244,232,0,10,49,0,241,120,0,14,0,0,49,18,241,40,0,10,49,0,241,24,0,14,0,0,49,21,221,19,1,8,33,0,86,38,0,14,0,0,49,22,221,19,1,8,33,0,102,6,0,14,0,0,113,73,209,28,1,8,49,0,97,12,0,14,0,0,33,77,113,18,1,2,35,128,114,6,0,14,
    0,0,241,64,241,33,1,2,225,0,111,22,0,14,0,0,2,26,245,117,1,0,1,128,133,53,0,14,0,0,2,29,245,117,1,0,1,128,243,244,0,14,0,0,16,65,245,5,1,2,17,0,242,195,0,14,0,0,33,155,177,37,1,14,162,1,114,8,0,14,0,0,161,152,127,3,1,0,33,0,63,7,1,14,0,0,161,147,193,
    18,0,10,97,0,79,5,0,14,0,0,33,24,193,34,0,12,97,0,79,5,0,14,0,0,49,91,244,21,0,0,114,131,138,5,0,14,0,0,161,144,116,57,0,0,97,0,113,103,0,14,0,0,113,87,84,5,0,12,114,0,122,5,0,14,0,0,144,0,84,99,0,8,65,0,165,69,0,14,0,0,33,146,133,23,0,12,33,1,143,
    9,0,14,0,0,33,148,117,23,0,12,33,5,143,9,0,14,0,0,33,148,118,21,0,12,97,0,130,55,0,14,0,0,49,67,158,23,1,2,33,0,98,44,1,14,0,0,33,155,97,106,0,2,33,0,127,10,0,14,0,0,97,138,117,31,0,8,34,6,116,15,0,14,0,0,161,134,114,85,1,0,33,131,113,24,0,14,0,0,
    33,77,84,60,0,8,33,0,166,28,0,14,0,0,49,143,147,2,1,8,97,0,114,11,0,14,0,0,49,142,147,3,1,8,97,0,114,9,0,14,0,0,49,145,147,3,1,10,97,0,130,9,0,14,0,0,49,142,147,15,1,10,97,0,114,15,0,14,0,0,33,75,170,22,1,8,33,0,143,10,0,14,0,0,49,144,126,23,1,6,33,
    0,139,12,1,14,0,0,49,129,117,25,1,0,50,0,97,25,0,14,0,0,50,144,155,33,0,4,33,0,114,23,0,14,0,0,225,31,133,95,0,0,225,0,101,26,0,14,0,0,225,70,136,95,0,0,225,0,101,26,0,14,0,0,161,156,117,31,0,2,33,0,117,10,0,14,0,0,49,139,132,88,0,0,33,0,101,26,0,
    14,0,0,225,76,102,86,0,0,161,0,101,38,0,14,0,0,98,203,118,70,0,0,161,0,85,54,0,14,0,0,98,153,87,7,0,11,161,0,86,7,0,14,0,0,98,147,119,7,0,11,161,0,118,7,0,14,0,0,34,89,255,3,2,0,33,0,255,15,0,14,0,0,33,14,255,15,1,0,33,0,255,15,1,14,0,0,34,70,134,
    85,0,0,33,128,100,24,0,14,0,0,33,69,102,18,0,0,161,0,150,10,0,14,0,0,33,139,146,42,1,0,34,0,145,42,0,14,0,0,162,158,223,5,0,2,97,64,111,7,0,14,0,0,32,26,239,1,0,0,96,0,143,6,2,14,0,0,33,143,241,41,0,10,33,128,244,9,0,14,0,0,119,165,83,148,0,2,161,
    0,160,5,0,14,0,0,97,31,168,17,0,10,177,128,37,3,0,14,0,0,97,23,145,52,0,12,97,0,85,22,0,14,0,0,113,93,84,1,0,0,114,0,106,3,0,14,0,0,33,151,33,67,0,8,162,0,66,53,0,14,0,0,161,28,161,119,1,0,33,0,49,71,1,14,0,0,33,137,17,51,0,10,97,3,66,37,0,14,0,0,
    161,21,17,71,1,0,33,0,207,7,0,14,0,0,58,206,248,246,0,2,81,0,134,2,0,14,0,0,33,21,33,35,1,0,33,0,65,19,0,14,0,0,6,91,116,149,0,0,1,0,165,114,0,14,0,0,34,146,177,129,0,12,97,131,242,38,0,14,0,0,65,77,241,81,1,0,66,0,242,245,0,14,0,0,97,148,17,81,1,
    6,163,128,17,19,0,14,0,0,97,140,17,49,0,6,161,128,29,3,0,14,0,0,164,76,243,115,1,4,97,0,129,35,0,14,0,0,2,133,210,83,0,0,7,3,242,246,1,14,0,0,17,12,163,17,1,0,19,128,162,229,0,14,0,0,17,6,246,65,1,4,17,0,242,230,2,14,0,0,147,145,212,50,0,8,145,0,235,
    17,1,14,0,0,4,79,250,86,0,12,1,0,194,5,0,14,0,0,33,73,124,32,0,6,34,0,111,12,1,14,0,0,49,133,221,51,1,10,33,0,86,22,0,14,0,0,32,4,218,5,2,6,33,129,143,11,0,14,0,0,5,106,241,229,0,6,3,128,195,229,0,14,0,0,7,21,236,38,0,10,2,0,248,22,0,14,0,0,5,157,
    103,53,0,8,1,0,223,5,0,14,0,0,24,150,250,40,0,10,18,0,248,229,0,14,0,0,16,134,168,7,0,6,0,3,250,3,0,14,0,0,17,65,248,71,2,4,16,3,243,3,0,14,0,0,1,142,241,6,2,14,16,0,243,2,0,14,0,0,14,0,31,0,0,14,192,0,31,255,3,14,0,0,6,128,248,36,0,14,3,136,86,132,
    2,14,0,0,14,0,248,0,0,14,208,5,52,4,3,14,0,0,14,0,246,0,0,14,192,0,31,2,3,14,0,0,213,149,55,163,0,0,218,64,86,55,0,14,0,0,53,92,178,97,2,10,20,8,244,21,0,14,0,0,14,0,246,0,0,14,208,0,79,245,3,14,0,0,38,0,255,1,0,14,228,0,18,22,1,14,0,0,0,0,243,240,
    0,14,0,0,246,201,2,14,0,35,16,68,248,119,2,8,17,0,243,6,0,14,0,35,16,68,248,119,2,8,17,0,243,6,0,14,0,52,2,7,249,255,0,8,17,0,248,255,0,14,0,48,0,0,252,5,2,14,0,0,250,23,0,14,0,58,0,2,255,7,0,0,1,0,255,8,0,14,0,60,0,0,252,5,2,14,0,0,250,23,0,14,0,
    47,0,0,246,12,0,4,0,0,246,6,0,14,0,43,12,0,246,8,0,10,18,0,251,71,2,14,0,49,0,0,246,12,0,4,0,0,246,6,0,14,0,43,12,0,246,8,0,10,18,5,123,71,2,14,0,51,0,0,246,12,0,4,0,0,246,6,0,14,0,43,12,0,246,2,0,10,18,0,203,67,2,14,0,54,0,0,246,12,0,4,0,0,246,6,
    0,14,0,57,0,0,246,12,0,4,0,0,246,6,0,14,0,72,14,0,246,0,0,14,208,0,159,2,3,14,0,60,0,0,246,12,0,4,0,0,246,6,0,14,0,76,14,8,248,66,0,14,7,74,244,228,3,14,0,84,14,0,245,48,0,14,208,10,159,2,0,14,0,36,14,10,228,228,3,6,7,93,245,229,1,14,0,65,2,3,180,
    4,0,14,5,10,151,247,0,14,0,84,78,0,246,0,0,14,158,0,159,2,3,14,0,83,17,69,248,55,2,8,16,8,243,5,0,14,0,84,14,0,246,0,0,14,208,0,159,2,3,14,0,24,128,0,255,3,3,12,16,13,255,20,0,14,0,77,14,8,248,66,0,14,7,74,244,228,3,14,0,60,6,11,245,12,0,6,2,0,245,
    8,0,14,0,65,1,0,250,191,0,7,2,0,200,151,0,14,0,59,1,81,250,135,0,6,1,0,250,183,0,14,0,51,1,84,250,141,0,6,2,0,248,184,0,14,0,45,1,89,250,136,0,6,2,0,248,182,0,14,0,71,1,0,249,10,3,14,0,0,250,6,0,14,0,60,0,128,249,137,3,14,0,0,246,108,0,14,0,58,3,128,
    248,136,3,15,12,8,246,182,0,14,0,53,3,133,248,136,3,15,12,0,246,182,0,14,0,64,14,64,118,79,0,14,0,8,119,24,2,14,0,71,14,64,200,73,0,14,3,0,155,105,2,14,0,61,215,220,173,5,3,14,199,0,141,5,0,14,0,61,215,220,168,4,3,14,199,0,136,4,0,14,0,44,128,0,246,
    6,3,14,17,0,103,23,3,14,0,40,128,0,245,5,2,14,17,9,70,22,3,14,0,69,6,63,0,244,0,1,21,0,247,245,0,14,0,68,6,63,0,244,3,0,18,0,247,245,0,14,0,63,6,63,0,244,0,1,18,0,247,245,0,14,0,74,1,88,103,231,0,0,2,0,117,7,0,14,0,60,65,69,248,72,0,0,66,8,117,5,0,
    14,0,80,10,64,224,240,3,8,30,78,255,5,0,14,0,64,10,124,224,240,3,8,30,82,255,2,0,14,0,72,14,64,122,74,0,14,0,8,123,27,2,14,0,73,14,10,228,228,3,6,7,64,85,57,1,14,0,70,5,5,249,50,3,14,4,64,214,165,0,14,0,68,2,63,0,243,3,8,21,0,247,245,0,14,0,48,1,79,
    250,141,0,7,2,0,248,181,0,14,0,53,0,0,246,12,0,4,0,0,246,6,0
};


static void
pcmout(int v)
{
	if(v < -32768)
		v = -32768;
	if(v > 32767)
		v = 32767;
	/* FIXME: portability (endianness) */
	*ap++ = v;
	/* FIXME: stereo shit */
	*ap++ = v;

	/* FIXME: write last samples before end */
	if(ap >= abuf+nelem(abuf)){
		write(afd, abuf, sizeof abuf);
		ap = abuf;
	}
}

static uint
tadv(double length, double add)
{
	add += otm;
	otm = fmod(add, length);

	return add / length;
}

static void
mix(uint usecs)
{
	int i;
	uint n;
	s16int *buf;

	n = tadv((double)1.0 / RATE, (double)usecs / 1E6);
	if((buf = mallocz(n * sizeof *buf, 1)) == nil)
		sysfatal("mallocz: %r");

	ym3812_update_one(chip[0], buf, n);
	for(i = 0; i < n; i++)
		//pcmout((double)buf[i] / 32768.0 * 10000.0);	/* FIXME: why? */
		pcmout(buf[i]);

	free(buf);
}

static uchar
inb(uint port)
{
	if(port >= 0x388 && port <= 0x38b)
		return oplretval;
	return 0;
}

static void
outb(uint port, uchar v)
{
	uint ind;

	if(port >= 0x388 && port <= 0x38b){
		ind = port - 0x388;
		ym3812_write(chip[0], ind, v);

		if(ind & 1){
			if(oplregno == 4){
				if(v == 0x80)
					oplretval = 0x02;
				else if(v == 0x21)
					oplretval = 0xc0;
			}
		}else
			oplregno = v;
	}
}

static void
OPL_Byte(uchar Index, uchar Data)
{
	int a;

	outb(OPLBASE, Index);
	for(a=0; a<6; a++)
		inb(OPLBASE);

	outb(OPLBASE+1, Data);
	for(a=0; a<35; a++)
		inb(OPLBASE);
}

static void
OPL_NoteOff(int c)
{
    Trig[c] = 0;

    c = SetBase(c);
    if(c<9)
    {
        int Ope = Portit[c];
        /* KEYON_BLOCK+c seems to not work alone?? */
        OPL_Byte(KEYON_BLOCK+c, 0);
        OPL_Byte(KSL_LEVEL+  Ope, 0xFF);
        OPL_Byte(KSL_LEVEL+3+Ope, 0xFF);
    }
}

/* OPL_NoteOn changes the frequency on specified
   channel and guarantees the key is on. (Doesn't
   retrig, just turns the note on and sets freq.) */
/* Could be used for pitch bending also. */
static void OPL_NoteOn(int c, unsigned long Herz)
{
    int Oct;

    Trig[c] = 127;

    c = SetBase(c);
    if(c >= 9)return;

    for(Oct=0; Herz>0x1FF; Oct++)Herz >>= 1;

/*
    Bytes A0-B8 - Octave / F-Number / Key-On

        7     6     5     4     3     2     1     0
     +-----+-----+-----+-----+-----+-----+-----+-----+
     |        F-Number (least significant byte)      |  (A0-A8)
     +-----+-----+-----+-----+-----+-----+-----+-----+
     |  Unused   | Key |    Octave       | F-Number  |  (B0-B8)
     |           | On  |                 | most sig. |
     +-----+-----+-----+-----+-----+-----+-----+-----+
*/

    OPL_Byte(0xA0+c, Herz&255);  //F-Number low 8 bits
    OPL_Byte(0xB0+c, 0x20        //Key on
                      | ((Herz>>8)&3) //F-number high 2 bits
                      | ((Oct&7)<<2)
          );
}

static void
OPL_Touch(int c, int Instru, ushort Vol)
{
    int Ope;
    //int level;

    c = SetBase(c);
    if(c >= 9)return;

    Ope = Portit[c];

/*
    Bytes 40-55 - Level Key Scaling / Total Level

        7     6     5     4     3     2     1     0
     +-----+-----+-----+-----+-----+-----+-----+-----+
     |  Scaling  |             Total Level           |
     |   Level   | 24    12     6     3    1.5   .75 | <-- dB
     +-----+-----+-----+-----+-----+-----+-----+-----+
          bits 7-6 - causes output levels to decrease as the frequency
                     rises:
                          00   -  no change
                          10   -  1.5 dB/8ve
                          01   -  3 dB/8ve
                          11   -  6 dB/8ve
          bits 5-0 - controls the total output level of the operator.
                     all bits CLEAR is loudest; all bits SET is the
                     softest.  Don't ask me why.
*/
/* if 1 */
	OPL_Byte(KSL_LEVEL+  Ope, (mdat.Instr[Instru]->D[2]&KSL_MASK)
	| (63 + (mdat.Instr[Instru]->D[2]&63) * Vol / 63 - Vol));
	OPL_Byte(KSL_LEVEL+3+Ope, (mdat.Instr[Instru]->D[3]&KSL_MASK)
	| (63 + (mdat.Instr[Instru]->D[3]&63) * Vol / 63 - Vol));
/* else
    level = (mdat.Instr[Instru]->D[2]&63) - (Vol*72-8);
    if(level<0)level=0;
    if(level>63)level=63;

    OPL_Byte(KSL_LEVEL+  Ope, (mdat.Instr[Instru]->D[2]&KSL_MASK) | level);

    level = (mdat.Instr[Instru]->D[3]&63) - (Vol*72-8);
    if(level<0)level=0;
    if(level>63)level=63;

    OPL_Byte(KSL_LEVEL+3+Ope, (mdat.Instr[Instru]->D[3]&KSL_MASK) | level);
   endif */
}

static void OPL_Pan(int c, uchar val)
{
    Pans[c] = val - 128;
}

static void OPL_Patch(int c, int Instru)
{
    int Ope;

    c = SetBase(c);
    if(c >= 9)return;

    Ope = Portit[c];

	if(Instru > sizeof(mdat.Instr)-1)
		sysfatal("invalid instrument patch %ud", Instru);	/* FIXME: see dat.h comments */

    OPL_Byte(AM_VIB+           Ope, mdat.Instr[Instru]->D[0]);
    OPL_Byte(ATTACK_DECAY+     Ope, mdat.Instr[Instru]->D[4]);
    OPL_Byte(SUSTAIN_RELEASE+  Ope, mdat.Instr[Instru]->D[6]);
    OPL_Byte(WAVE_SELECT+      Ope, mdat.Instr[Instru]->D[8]&3);// 6 high bits used elsewhere

    OPL_Byte(AM_VIB+         3+Ope, mdat.Instr[Instru]->D[1]);
    OPL_Byte(ATTACK_DECAY+   3+Ope, mdat.Instr[Instru]->D[5]);
    OPL_Byte(SUSTAIN_RELEASE+3+Ope, mdat.Instr[Instru]->D[7]);
    OPL_Byte(WAVE_SELECT+    3+Ope, mdat.Instr[Instru]->D[9]&3);// 6 high bits used elsewhere

    /* Panning... */
    OPL_Byte(FEEDBACK_CONNECTION+c, 
        (mdat.Instr[Instru]->D[10] & ~STEREO_BITS)
            | (Pans[c]<-32 ? VOICE_TO_LEFT
                : Pans[c]>32 ? VOICE_TO_RIGHT
                : (VOICE_TO_LEFT | VOICE_TO_RIGHT)
            ) );
}

/* u32int, word and byte have been defined in adlib.h */

static u32int ConvL(u8int *s)
{
    return (((u32int)s[0] << 24) | ((u32int)s[1] << 16) | ((u16int)s[2] << 8) | s[3]);
}

static u16int ConvI(u8int *s)
{
    return (s[0] << 8) | s[1];
}

static struct Midi
{
/* Fixed by ReadMIDI() */
    int Fmt;
    int TrackCount;
    int DeltaTicks;
    ulong *TrackLen;
    u8int **Tracks;
/* Used by play() */
    u32int  *Waiting, *sWaiting, *SWaiting;
    u8int   *Running, *sRunning, *SRunning;
    ulong *Posi, *sPosi, *SPosi;
    u32int  Tempo;
    u32int  oldtempo;
    u32int  initempo;
/* Per channel */
    u8int   Pan[16];
    u8int   Patch[16];
    u8int   MainVol[16];
    u8int   PitchSense[16];
    int    Bend[16];
    int    OldBend[16];
    int    Used[16][127]; /* contains references to adlib channels per note */
} MIDI;

#define snNoteOff    0x7fb1
#define snNoteOn     0x7fb2
#define snNoteModify 0x7fb3
#define snNoteUpdate 0x7fb4

enum{
	MAXS3MCHAN = 9
};

typedef struct
{
/* Important - at offset 0 to be saved to file */
    long Age;
    u8int Note;
    u8int Instru;
    u8int Volume;
    u8int cmd;
    u8int info;
    u8int KeyFlag;    /* Required to diff aftertouch and noteon */
                     /* Byte to save space */
/* Real volume(0..127) and main volume(0..127) */
    int RealVol;
    int MainVol;
    int exp;	/* vol = MainVol * RealVol/127 * exp/127 */
    /* RealVol must be first non-saved */
/* Less important */
    int LastInstru;  /* Needed by SuggestNewChan() */
    int BendFlag;
    u8int LastPan;
/* To fasten forcement */
    int uChn, uNote;
} S3MChan;

static S3MChan Chan[MAXCHN];
static S3MChan PrevChan[MAXCHN];

#define chansavesize ((int)((long)&Chan[0].RealVol - (long)&Chan[0].Age))

static int InstruUsed[256];
static int Forced=0;
static const int tempochanged = -5;


static void AnalyzeRow(void)
{
    int a;
    
    for(a=0; a<MAXS3MCHAN; a++)
        if(!Chan[a].Age)break;

    if(a==MAXS3MCHAN)
	return;

    memcpy(PrevChan, Chan, sizeof PrevChan);

    Forced=0;

    for(a=0; a<MAXS3MCHAN; a++)
        Chan[a].KeyFlag=0;
}

static void FixSpeed(float *Speed, int *Tempo)
{
    int a;
    float tmp = 1.0;
    *Speed = MIDI.Tempo * 4E-7 / MIDI.DeltaTicks;
    *Tempo = 125;

    for(a=0x40; a<=0xFF; a++)
    {
        double Tmp;
        float n = a * *Speed;
        n = modf(1.0/n, &Tmp);
        if(n < tmp)
        {
            *Tempo = a;
            tmp = n;
        }
    }
    *Speed *= *Tempo;
}

static int MakeVolume(int vol)
{
    return FMVol[vol*64/127]*63/128;
}

static void Bendi(int chn, int a)
{
	int bc, nt;
	long HZ1, HZ2, Herz;

	bc = MIDI.Bend[chn];
	nt = Chan[a].Note;

	if(bc > MIDI.OldBend[chn])
		Chan[a].BendFlag |= 1;
	else if(bc < MIDI.OldBend[chn])
		Chan[a].BendFlag |= 2;
	else
		Chan[a].BendFlag = 0;

	MIDI.OldBend[chn] = bc;

	for(; bc < 0; bc += 0x1000)
		nt--;
	for(; bc >= 0x1000; bc -= 0x1000)
		nt++;

	HZ1 = Period[(nt+1)%12] * (8363L << (nt+1)/12) / 44100U;
	HZ2 = Period[(nt  )%12] * (8363L << (nt  )/12) / 44100U;

	Herz = HZ2 + bc * (HZ1 - HZ2) / 0x1000;

	OPL_NoteOn(a, Herz);
}

/* vole = RealVol*MainVol/127 */
static int SuggestNewChan(int instru, int /*vole*/)
{
    int a, c=MAXS3MCHAN, f;
    long b;

    for(a=f=0; a<MAXS3MCHAN; a++)
        if(Chan[a].LastInstru==instru)
            f=1;

    /* Arvostellaan channels */
    for(b=a=0; a<MAXS3MCHAN; a++)
    {
        /* empty if channel is silent */
        if(!Chan[a].Volume)
        {
            long d;
            /* Pohjapisteet...
             * Jos instru oli uusi, mieluiten sijoitetaan
             * sellaiselle kanavalle, joka on pitk��n ollut hiljaa.
             * Muuten sille, mill� se juuri �skenkin soi.
             */
            d = f?1:Chan[a].Age;

            /* Jos kanavan edellinen instru oli joku
             * soinniltaan hyvin lyhyt, pisteit� annetaan lis�� */
            if(strchr("\x81\x82\x83\x84\x85\x86\x87\x88\x89"
                      "\x8B\x8D\x8E\x90\x94\x96\x9A\x9B\x9C"
                      "\x9D\xA4\xAA\xAB",
                Chan[a].LastInstru))d += 2;
            else
            {
                /* Jos oli pitk�sointinen percussion, *
                 * annetaan pisteit� i�n mukaan.      */
                if(Chan[a].LastInstru > 0x80)
                    d += Chan[a].Age*2;
            }

            /* Jos oli samaa instrua, pisteit� tulee paljon lis�� */
            if(Chan[a].LastInstru == instru)d += 3;

            //d = (d-1)*Chan[a].Age+1;
            if(d > b)
            {
                b = d;
                c = a;
            }
        }
    }
    return c;
}

/* vole = RealVol*MainVol/127 */
static int ForceNewChan(int instru, int vole)
{
    int a, c;
    long b=0;

    vole *= 127;

    Forced=1;
    for(a=c=0; c<MAXS3MCHAN; c++)
        if(Chan[c].Age
          > b
          + (((instru<128 && Chan[c].Instru>128)
             || (vole > Chan[c].RealVol*Chan[c].MainVol)
             ) ? 1:0
          ) )
        {
            a=c;
            b=Chan[c].Age;
        }
    return a;
}

/* Used twice by SetNote. This should be considered as a macro. */
static void SubNoteOff(int a, int chn, int note)
{
    Chan[a].RealVol = 0;
    Chan[a].MainVol = 0;
    Chan[a].exp = 0x7f;

    Chan[a].Age     = 0;
    Chan[a].Volume  = 0;
    Chan[a].BendFlag= 0;

    MIDI.Used[chn][note] = 0;

    if(Chan[a].Instru < 0x80)OPL_NoteOff(a);
}

static void SetNote(int chn, int note, int RealVol,int MainVol, int bend, int e)
{
	int a, vole;

	//vole = RealVol*(MainVol*e)/127;
	vole = RealVol*(MainVol)/127;

    if(!vole && (bend==snNoteOn || bend==snNoteModify))bend=snNoteOff;
    if(bend==snNoteOn && MIDI.Used[chn][note])bend=snNoteModify;

    switch(bend)
    {
        /* snNoteOn:ssa note ei koskaan ole -1 */
        case snNoteOn:
        {
            int p;

            /* FIXME */
            p = chn==9 ? 128+note-35 : MIDI.Patch[chn];

            a = SuggestNewChan(p, vole);

            if(a==MAXS3MCHAN)
            {
                a = ForceNewChan(p, vole);
                MIDI.Used[Chan[a].uChn][Chan[a].uNote] = 0;
            }

            if(a < MAXS3MCHAN)
            {
		Chan[a].exp = e;
                Chan[a].RealVol= RealVol;
                Chan[a].MainVol= MainVol;
                Chan[a].Note   = chn==9 ? 60 : note;
                Chan[a].Volume = MakeVolume(vole);
                Chan[a].Age    = 0;
                Chan[a].Instru = p;

                Chan[a].LastInstru = p;
                Chan[a].KeyFlag= 1;
                Chan[a].uChn  = chn;
                Chan[a].uNote = note;

/*
                if(MIDI.Bend[chn] && Chan[a].Volume)
                {
                    int adlbend = MIDI.Bend[chn] * 127L / 8000;

                    //TODO: Fix this. It doesn't work at all.

                    Chan[a].cmd = 'N'-64;
                    Chan[a].info= adlbend+0x80; // To make it unsigned
                }
                else
*/
                if(MIDI.Pan[chn] != Chan[a].LastPan && Chan[a].Volume)
                {
                    Chan[a].cmd = 'X'-64;
                    Chan[a].info= MIDI.Pan[chn]*2;
                    Chan[a].LastPan = MIDI.Pan[chn];
                }
                else
                {
                    Chan[a].cmd = Chan[a].info = 0;
                }

                OPL_NoteOff(a);

                OPL_Patch(a, Chan[a].Instru);
                if(MIDI.Pan[chn] != 64)OPL_Pan(a, Chan[a].info);
                OPL_Touch(a, Chan[a].Instru, Chan[a].Volume);

                Bendi(chn, a);

                MIDI.Used[chn][note] = a+1;
            }
            break;
        }
        /* snNoteOff:ssa note voi olla -1 */
        case snNoteOff:
        {
            int b=note, c=note;
            if(note < 0)b=0, c=127;

            MIDI.Bend[chn] = 0; /* N�in vaikuttaisi olevan hyv� */

            for(note=b; note<=c; note++)
            {
                a = MIDI.Used[chn][note];
                if(a > 0)
                    SubNoteOff(a-1, chn, note);
            }
            break;
        }
        /* snNoteModify:ssa note ei koskaan ole -1 */
        case snNoteModify:
            a = MIDI.Used[chn][note]-1;
            if(a != -1)
            {
		Chan[a].exp = e;
                Chan[a].RealVol= RealVol;
                Chan[a].MainVol= MainVol;

                Chan[a].Volume = MakeVolume(vole);
                Chan[a].Age    = 0;

                OPL_Touch(a, Chan[a].Instru, Chan[a].Volume);
            }
            break;
        /* snNoteUpdate:ssa note on aina -1 */
        case snNoteUpdate:
            /* snNoteUpdatessa RealVol ei muutu, vain MainVol */
            for(note=0; note<=127; note++)
            {
                a = MIDI.Used[chn][note]-1;

                //vole = MainVol*(Chan[a].RealVol*Chan[a].exp)/127;
                vole = MainVol*(Chan[a].RealVol)/127;

                if(a >= 0 && Chan[a].Volume != MakeVolume(vole))
                {
                    Chan[a].MainVol= MainVol;

                    if(!vole)
                        SubNoteOff(a, chn, note);
                    else
                    {
                        Chan[a].Volume = MakeVolume(vole);
                        Chan[a].Age    = 0;
                        OPL_Touch(a, Chan[a].Instru, Chan[a].Volume);
                    }
                }
            }
            break;
        /* Bendiss� note on aina -1 */
        default:
            MIDI.Bend[chn] = bend;

            for(note=0; note<=127; note++)
            {
                a = MIDI.Used[chn][note]-1;
                if(a >= 0)
                    Bendi(chn, a);
            }
    }
}

static u32int GetVarLen(int Track, ulong *Posi)
{
    u32int d = 0;
    for(;;)
    {
        u8int b = MIDI.Tracks[Track][(*Posi)++];
        d = (d<<7) + (b&127);
        if(b < 128)break;
    }
    return d;
}

/* NOTICE: This copies len-2 bytes */
static char *Sana(int len, const unsigned char *buf)
{
    static char Buf[128];
    char *s = Buf;
    len -= 2;
#define add(c) if(s<&Buf[sizeof(Buf)-1])*s++=(c)
    while(len>0)
    {
        if(*buf<32||(*buf>=127&&*buf<160)){add('^');add(*buf + 64);}
        else add(*buf);
        buf++;
        len--;
    }
#undef add
    *s=0;
    return Buf;
}

static void
MPU_Byte(uchar)
{

}

static void
readmeta(int t, u8int *p, ulong n)
{
	u8int b;

	b = *p;
	p += 2;
	//fprint(2, "meta %ux\n", b);
	switch(b){
	case 1:
	case 2:
	case 3:
	case 4:
	case 5:
	case 6:
		if(verbose)
			fprint(2, "%s\n", Sana(n, p));
		break;
	case 0x2f:
		MIDI.Posi[t] = MIDI.TrackLen[t];
		if(verbose)
			fprint(2, "end\n");
		break;
	case 0x51:
		MIDI.Tempo = p[0]<<16 | p[1]<<8 | p[2];
		if(verbose)
			fprint(2, "tempo %ud\n", MIDI.Tempo);
		break;
	case 0: case 0x20: case 0x21: case 0x58: case 0x59: case 0x7f:
		break;
	default:
		fprint(2, "unknown metaevent %ux\n", b);
	}
}

static void
Tavuja(int Track, u8int First, ulong posi, ulong Len)
{
        ulong a;

	if(verbose){
		fprint(2, "[%d]: %02ux ", Track, First);
		for(a=0; a<Len; a++)
			fprint(2, "%02ux", MIDI.Tracks[Track][a+posi]);
		fprint(2, "\n");
	}

	/* FIXME: ignore sysex completely, do it like games/midi */
	if(First == 0xff)
		readmeta(Track, MIDI.Tracks[Track]+posi, Len);
	else if(First < 0xf0){
        //ulong a;	// ← what the fuck
            a=0;
            if((First>>4) == 8) /* Note off, modify it a bit */
            {
		/* FIXME: */
                MPU_Byte(First);
                MPU_Byte(MIDI.Tracks[Track][posi]);
                MPU_Byte(0);
                Len -= 2;
            }
            else
                MPU_Byte(First);
            for(; a<Len; a++)MPU_Byte(MIDI.Tracks[Track][a+posi]);
	//fprint(2, "ev %ux\n", First >> 4);
        switch(First >> 4)
        {
            case 0x8: /* Note off */
                SetNote(
                    First&15,                   /* chn */
                    MIDI.Tracks[Track][posi+0], /* note */
                    MIDI.Tracks[Track][posi+1], /* volume */
                    MIDI.MainVol[First&15], /* mainvol */
                    snNoteOff, 127);
                break;
            case 0x9: /* Note on */
                SetNote(
                    First&15,                   /* chn */
                    MIDI.Tracks[Track][posi+0], /* note */
                    MIDI.Tracks[Track][posi+1], /* volume */
                    MIDI.MainVol[First&15], /* mainvol */
                    snNoteOn, Chan[First&15].exp);
                break;
            case 0xA: /* Key after-touch */
                SetNote(
                    First&15,                   /* chn */
                    MIDI.Tracks[Track][posi+0], /* note */
                    MIDI.Tracks[Track][posi+1], /* volume */
                    MIDI.MainVol[First&15], /* mainvol */
                    snNoteModify, Chan[First&15].exp);
                break;
            case 0xC: /* Patch change */
                MIDI.Patch[First&15] = MIDI.Tracks[Track][posi];
                break;
            case 0xD: /* Channel after-touch */
                break;
            case 0xE: /* Wheel - 0x2000 = no change */
                a = MIDI.Tracks[Track][posi+0] |
                    MIDI.Tracks[Track][posi+1] << 7;
                SetNote(First&15,
                    -1, 0,0,
			/* FIXME: these are fucked up, see below */
                    //(int)((long)a*MIDI.PitchSense[First&15]/2 - 0x2000L), Chan[First&15].exp);
                    //(short)(((long)a - 0x2000L) *(double)MIDI.PitchSense[First&15]/8192.0), Chan[First&15].exp);
                    (short)((long)a - 0x2000L), Chan[First&15].exp);
                break;
            case 0xB: /* Controller change */
                switch(MIDI.Tracks[Track][posi+0])
                {
                    case 123: /* All notes off on channel */
                        SetNote(First&15, -1,0,0, snNoteOff, 127);
                        break;
                    case 121: /* Reset vibrato and bending */
                        MIDI.PitchSense[First&15] = 2;
                        SetNote(First&15, -1,0,0, 0, Chan[First&15].exp); /* last 0=bend 0 */
                        break;
                    case 7:
                        MIDI.MainVol[First&15] = MIDI.Tracks[Track][posi+1];

			SetNote(First&15, -1, 0, MIDI.MainVol[First&15], snNoteUpdate, Chan[First&15].exp);
			/* FIXME: why doesn't this work? */
			//for(a = 0; a < 16; a++)
			//	SetNote(a, -1, 0, MIDI.MainVol[First&15], snNoteUpdate, Chan[First&15].exp);
                        break;
                    case 64:
			if(verbose)
				fprint(2, "ctl 64: sustain unsupported\n");
			break;
                    case 1:
			if(verbose)
				fprint(2, "ctl 1: vibrato unsupported\n");
			break;
                    case 91:
			if(verbose)
				fprint(2, "ctl 91 %ud: reverb depth unsupported\n", MIDI.Tracks[Track][posi+1]);
                        break;
                    case 93:
			if(verbose)
                        	fprint(2, "ctl 93 %ud: chorus depth unsupported\n", MIDI.Tracks[Track][posi+1]);
                        break;
                    case 0x06:   /* Pitch bender sensitivity */
			/* FIXME: the fuck is this? whatever it is,
			 * it's broken */
                        MIDI.PitchSense[First&15] = MIDI.Tracks[Track][posi+1];
                        break;
                    case 0x0a:  /* Pan */
                        MIDI.Pan[First&15] = MIDI.Tracks[Track][posi+1];
                        break;
                    case 0:
			/* FIXME: unimplemented: select bank */
			if(verbose)
                        	fprint(2, "ctl 0 %ud: bank select unsupported\n", MIDI.Tracks[Track][posi+1]);
                        break;
		case 0x0b:	/* FIXME: probably bullshizzles */
                        SetNote(First&15, -1,0,MIDI.MainVol[First&15], snNoteUpdate, MIDI.Tracks[Track][posi+1]);
			break;
                    default:
			if(verbose)
                        	fprint(2, "unknown ctl %ud: %ux\n",
					MIDI.Tracks[Track][posi+0],
					MIDI.Tracks[Track][posi+1]);
			}
                break;
		}
	}
}

/* Return value: 0=ok, -1=user break */
static int play(void)
{
    int a, NotFirst, Userbreak=0;
    long Viivetta;

	if((MIDI.Waiting = mallocz(MIDI.TrackCount * sizeof *MIDI.Waiting, 1)) == nil
	|| (MIDI.sWaiting = mallocz(MIDI.TrackCount * sizeof *MIDI.sWaiting, 1)) == nil
	|| (MIDI.SWaiting = mallocz(MIDI.TrackCount * sizeof *MIDI.SWaiting, 1)) == nil
	|| (MIDI.Posi = mallocz(MIDI.TrackCount * sizeof *MIDI.Posi, 1)) == nil
	|| (MIDI.sPosi = mallocz(MIDI.TrackCount * sizeof *MIDI.sPosi, 1)) == nil
	|| (MIDI.SPosi = mallocz(MIDI.TrackCount * sizeof *MIDI.SPosi, 1)) == nil
	|| (MIDI.Running = mallocz(MIDI.TrackCount * sizeof *MIDI.Running, 1)) == nil
	|| (MIDI.sRunning = mallocz(MIDI.TrackCount * sizeof *MIDI.sRunning, 1)) == nil
	|| (MIDI.SRunning = mallocz(MIDI.TrackCount * sizeof *MIDI.SRunning, 1)) == nil)
		sysfatal("mallocz: %r");

    for(a=0; a<MIDI.TrackCount; a++)
    {
        ulong c = 0;
        MIDI.sWaiting[a]= GetVarLen(a, &c);
        MIDI.sRunning[a]= 0;
        MIDI.sPosi[a]   = c;
    }

    for(a=0; a<16; a++)
    {
        MIDI.Pan[a]        = 64;   /* Middle      */
        MIDI.Patch[a]      = 1;    /* Piano       */
        MIDI.PitchSense[a] = 2;    /* � seminotes */
        MIDI.MainVol[a]    = 127;
        MIDI.Bend[a]       = 0;
    }

    NotFirst = 0;
//ReLoop:
    for(a=0; a<MIDI.TrackCount; a++)
    {
        MIDI.Posi[a]    = MIDI.sPosi[a];
        MIDI.Waiting[a] = MIDI.sWaiting[a];
        MIDI.Running[a] = MIDI.sRunning[a];
    }

    MIDI.Tempo = MIDI.initempo;
    
    memset(Chan, 0, sizeof Chan);
	for(a = 0; a < nelem(Chan); a++){
		Chan[a].exp = 0x7f;
	}

    memset(MIDI.Used,    0, sizeof MIDI.Used);

    Viivetta = 0;
 
    for(;;)
    {
        int Fin, Act;

        if(NotFirst)
        {
                long Lisa = MIDI.Tempo/MIDI.DeltaTicks;
                
                /* tempo      = microseconds per quarter note
                 * deltaticks = number of delta-time ticks per quarter note
                 *
                 * So, when tempo = 200000
                 * and deltaticks = 10,
                 * then 10 ticks have 200000 microseconds.
                 * 20 ticks have 400000 microseconds.
                 * When deltaticks = 5,
                 * then 10 ticks have 40000 microseconds.
                 */

                Viivetta += Lisa;
                if(Viivetta >= MINDELAY)
                {
                    mix(Viivetta);
                    Viivetta = 0;
                }
            AnalyzeRow();
        }
        else
        {
            u32int b = 0xFFFFFFFFUL;
            /* Find the smallest delay */
            for(a=0; a<MIDI.TrackCount; a++)
                if(MIDI.Waiting[a] < b)
                    b = MIDI.Waiting[a];
            /* Elapse that delay from all tracks */
            for(a=0; a<MIDI.TrackCount; a++)
                MIDI.Waiting[a] -= b;
        }

        /* Let the notes on channels become older (Age++) only if  *
         * something happens. This way, we don't overflow the ages *
         * too easily.                                             */
        for(Act=a=0; a<MAXS3MCHAN; a++)
            if(MIDI.Waiting[a]<=1 && MIDI.Posi[a]<MIDI.TrackLen[a])
                Act++;
        for(a=0; a<MAXS3MCHAN; a++)
            if(!Chan[a].Age||Act!=0)
                Chan[a].Age++;

        for(a=0; a<MIDI.TrackCount; ++a)
        {
            MIDI.SPosi[a]    = MIDI.Posi[a];
            MIDI.SWaiting[a] = MIDI.Waiting[a];
            MIDI.SRunning[a] = MIDI.Running[a];
        }
        for(Fin=1, a=0; a<MIDI.TrackCount; a++)
        {
            if(MIDI.Waiting[a] > 0)MIDI.Waiting[a]--;

            if(MIDI.Posi[a] < MIDI.TrackLen[a])Fin=0;

            /* While this track has events that we should have handled */
            while(MIDI.Waiting[a]<=0 && MIDI.Posi[a]<MIDI.TrackLen[a])
            {
                ulong pos;
                u8int b = MIDI.Tracks[a][MIDI.Posi[a]];
                if(b < 128)
                    b = MIDI.Running[a];
                else
                {
                    MIDI.Posi[a]++;
                    if(b < 0xF0)MIDI.Running[a] = b;
                }

                pos = MIDI.Posi[a];

		//fprint(2, "b %ux\n", b);
                if(b == 0xFF)
                {
                    int ls=0;
                    ulong len;
                    u8int typi = MIDI.Tracks[a][MIDI.Posi[a]++];
                    len = (ulong)GetVarLen(a, &MIDI.Posi[a]);
                    if(typi == 6) /* marker */
                        if(!strncmp((char *)(MIDI.Tracks[a]+MIDI.Posi[a]),
                                    "loopStart", len))
                        {
				if(verbose)
                            		fprint(2, "Found loopStart\n");
                            ls=1;
                        }
                    MIDI.Posi[a] += len;
                    if(ls)goto SaveLoopStart;
                }
                else if(b==0xF7 || b==0xF0)
                {
                    MIDI.Posi[a] += (ulong)GetVarLen(a, &MIDI.Posi[a]);
                }
                else if(b == 0xF3)MIDI.Posi[a]++;
                else if(b == 0xF2)MIDI.Posi[a]+=2;
                else if(b>=0xC0 && b<=0xDF)MIDI.Posi[a]++;
                else if(b>=0x80 && b<=0xEF)
                {
                    MIDI.Posi[a]+=2;
                    if(b>=0x90 && b<=0x9F && !NotFirst)
                    {
                        int c;
SaveLoopStart:          NotFirst=1;
                        /* Save the starting position for looping */
                        for(c=0; c<MIDI.TrackCount; c++)
                        {
                            MIDI.sPosi[c]    = MIDI.SPosi[c];
                            MIDI.sWaiting[c] = MIDI.SWaiting[c];
                            MIDI.sRunning[c] = MIDI.SRunning[c];
                        }
                        MIDI.initempo = MIDI.Tempo;
                    }
                }

                Tavuja(a, b, pos, MIDI.Posi[a]-pos);

                if(MIDI.Posi[a] < MIDI.TrackLen[a])
                    MIDI.Waiting[a] += GetVarLen(a, &MIDI.Posi[a]);
            }
        }
        if(Fin)
		break;
    }
	write(afd, abuf, ap-abuf);
    return Userbreak;
}

static void
initinst(void)
{
	int m, i;
	InternalSample *t, *p;

	m = sizeof(adl) / 14;

	if((t = calloc(m, sizeof *t)) == nil)
		sysfatal("initinst: %r");
	p = t;
	/* FIXME: .D needs to be uchar(?) */
	/* FIXME: use char adl[][14] rather than this */
	/* FIXME: find out where the values are from */
	for(i=0; i<m; i++){
		p->D[0] = adl[i * 14 + 3];
		p->D[1] = adl[i * 14 + 9];
		p->D[2] = adl[i * 14 + 4];
		p->D[3] = adl[i * 14 + 10];
		p->D[4] = adl[i * 14 + 5];
		p->D[5] = adl[i * 14 + 11];
		p->D[6] = adl[i * 14 + 6];
		p->D[7] = adl[i * 14 + 12];
		p->D[8] = adl[i * 14 + 7] & 3;
		p->D[9] = adl[i * 14 + 13] & 3;
		p->D[10]= adl[i * 14 + 8];
		mdat.Instr[i] = p++;
	}
}

/* FIXME: get8/16/32 approach is better */
/* FIXME: use bio.h */
static void
eread(int fd, void *buf, int n)
{
	if(readn(fd, buf, n) < 0)
		sysfatal("read: %r");
}

static void
readmid(char *mid)
{
	int i, fd;
	uint n;
	uchar id[4];

	fd = 0;
	if(mid != nil && (fd = open(mid, OREAD)) < 0)
		sysfatal("open: %r");

	/* FIXME: get8/16/32 better; Conv shit is also stupid */
	/* FIXME: also, don't read file into memory but use bio? */
	eread(fd, id, 4);
	if(memcmp(id, "MThd", 4) != 0)
		sysfatal("invalid header");
	eread(fd, id, 4);
	if(ConvL(id) != 6)
		sysfatal("invalid midi file");

	eread(fd, id, 2);
	MIDI.Fmt = ConvI(id);
	eread(fd, id, 2);
	MIDI.TrackCount = ConvI(id);
	eread(fd, id, 2);
	MIDI.DeltaTicks = ConvI(id);

	MIDI.TrackLen = calloc(MIDI.TrackCount, sizeof *MIDI.TrackLen);
	if(MIDI.TrackLen == nil)
		sysfatal("calloc len %ux %ux: %r", MIDI.TrackCount, sizeof *MIDI.TrackLen);
	MIDI.Tracks = calloc(MIDI.TrackCount, sizeof *MIDI.Tracks);
	if(MIDI.Tracks == nil)
		sysfatal("calloc trs %ux %ux: %r", MIDI.TrackCount, sizeof *MIDI.Tracks);

	for(i = 0; i < MIDI.TrackCount; i++){
		eread(fd, id, 4);
		if(memcmp(id, "MTrk", 4) != 0)
			sysfatal("invalid track");

		eread(fd, id, 4);
		n = ConvL(id);
		MIDI.TrackLen[i] = n;
		if((MIDI.Tracks[i] = mallocz(n, 1)) == nil)
			sysfatal("mallocz %ux: %r", n);

		eread(fd, MIDI.Tracks[i], n);
	}
	close(fd);

	MIDI.initempo = 150000;
}

static void
usage(void)
{
	print("%s [-cd] [midfile]\n", argv0);
	exits("usage");
}

void
main(int argc, char **argv)
{
	int i;

	ARGBEGIN{
	case 'c':
		afd = 1;
		break;
	case 'd':
		verbose++;
		break;
	default:
		usage();
	}ARGEND

	readmid(*argv);
	initinst();
	for(i = 0; i < NCHIPS; i++)
		chip[i] = ym3812_init(1789772*2, RATE);

	if(afd < 0 && (afd = open("/dev/audio", OWRITE)) < 0)
		sysfatal("open: %r");
	ap = abuf;
	play();

	for(i = 0; i < NCHIPS; i++)
		ym3812_shutdown(chip[i]);
	exits(nil);
}