shithub: riscv

Download patch

ref: 2a6e03968b9227e91b2df181fe8461d719395359
parent: 88412aa050adddde5cacf3a45e914bad9a5d3e22
author: qwx <[email protected]>
date: Sun Aug 13 16:59:14 EDT 2023

add games/mid2s: streaming midi from file, and merge common stuff into generic framework

common functionality now merged into midifile.[ch]; all midi stuff
carried the same copypasta; this concentrates everything in one place,
and attempts to expose a simple interface to it, but it will likely
change with upcoming tools.  manpage pending.

--- a/sys/man/1/dmid
+++ b/sys/man/1/dmid
@@ -74,7 +74,7 @@
 % games/dmid -s /dev/usb/ep10.1/data | games/opl3 -s >/dev/audio
 .EE
 .SH SOURCE
-.B /sys/src/games/dmid.c
+.B /sys/src/games/midi/dmid.c
 .SH "SEE ALSO"
 .IR games (1) ,
 .IR mus (1) ,
--- a/sys/src/games/dmid.c
+++ /dev/null
@@ -1,594 +1,0 @@
-#include <u.h>
-#include <libc.h>
-#include <bio.h>
-
-typedef struct Inst Inst;
-typedef struct Opl Opl;
-typedef struct Chan Chan;
-typedef struct Trk Trk;
-enum{
-	Rate = 49716,		/* opl3 sampling rate */
-	Ninst = 128 + 81-35+1,
-
-	Rwse = 0x01,
-		Mwse = 1<<5,	/* wave selection enable */
-	Rctl = 0x20,
-	Rsca = 0x40,
-		Mlvl = 63<<0,	/* total level */
-		Mscl = 3<<6,	/* scaling level */
-	Ratk = 0x60,
-	Rsus = 0x80,
-	Rnum = 0xa0,		/* f number lsb */
-	Roct = 0xb0,
-		Mmsb = 3<<0,	/* f number msb */
-		Moct = 7<<2,
-		Mkon = 1<<5,
-	Rfed = 0xc0,
-	Rwav = 0xe0,
-	Rop3 = 0x105,
-};
-
-struct Inst{
-	int fixed;
-	int dbl;
-	int fine;
-	uchar n;
-	uchar i[13];
-	uchar i2[13];
-	s16int base[2];
-};
-Inst inst[Ninst];
-
-struct Opl{
-	Chan *c;
-	int n;
-	int midn;
-	int blk;
-	int v;
-	vlong t;
-	uchar *i;
-};
-Opl opl[18], *ople = opl + nelem(opl);
-int port[] = {
-	0x0, 0x1, 0x2, 0x8, 0x9, 0xa, 0x10, 0x11, 0x12,
-	0x100, 0x101, 0x102, 0x108, 0x109, 0x10a, 0x110, 0x111, 0x112
-};
-int sport[] = {
-	0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8,
-	0x100, 0x101, 0x102, 0x103, 0x104, 0x105, 0x106, 0x107, 0x108
-};
-uchar ovol[] = {
-	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
-};
-
-struct Chan{
-	Inst *i;
-	int v;
-	int bend;
-	int pan;
-};
-Chan chan[16];
-struct Trk{
-	u8int *s;
-	u8int *p;
-	u8int *e;
-	vlong Δ;
-	vlong t;
-	int ev;
-	int ended;
-};
-Trk *tr;
-
-int trace;
-double freq[128];
-int mfmt, ntrk, div = 1, tempo, opl2, stream;
-Biobuf *ib;
-
-void *
-emalloc(ulong n)
-{
-	void *p;
-
-	p = mallocz(n, 1);
-	if(p == nil)
-		sysfatal("mallocz: %r");
-	setmalloctag(p, getcallerpc(&n));
-	return p;
-}
-
-Biobuf *
-bfdopen(int fd, int mode)
-{
-	Biobuf *bf;
-
-	bf = Bfdopen(fd, mode);
-	if(bf == nil)
-		sysfatal("bfdopen: %r");
-	Blethal(bf, nil);
-	return bf;
-}
-
-Biobuf *
-bopen(char *file, int mode)
-{
-	int fd;
-
-	fd = open(file, mode);
-	if(fd < 0)
-		sysfatal("bopen: %r");
-	return bfdopen(fd, mode);
-}
-
-void
-bread(void *u, int n)
-{
-	if(Bread(ib, u, n) != n)
-		sysfatal("bread: short read");
-}
-
-void
-dprint(char *fmt, ...)
-{
-	char s[256];
-	va_list arg;
-
-	if(!trace)
-		return;
-	va_start(arg, fmt);
-	vseprint(s, s+sizeof s, fmt, arg);
-	va_end(arg);
-	fprint(2, "%s", s);
-}
-
-u8int
-get8(Trk *x)
-{
-	u8int v;
-
-	if(x == nil || x->p == nil)
-		Bread(ib, &v, 1);
-	else if(x->p >= x->e || x->ended)
-		sysfatal("track overflow");
-	else
-		v = *x->p++;
-	dprint("%02ux", v);
-	return v;
-}
-
-u16int
-get16(Trk *x)
-{
-	u16int v;
-
-	v = get8(x) << 8;
-	return v | get8(x);
-}
-
-u32int
-get32(Trk *x)
-{
-	u32int v;
-
-	v = get16(x) << 16;
-	return v | get16(x);
-}
-
-void
-putcmd(u16int r, u8int v, u16int dt)
-{
-	uchar *p, u[5];
-
-	p = u;
-	*p++ = r;
-	if(!opl2)
-		*p++ = r >> 8;
-	*p++ = v;
-	*p++ = dt;
-	*p++ = dt >> 8;
-	write(1, u, p-u);
-}
-
-void
-setinst(Opl *o, uchar *i)
-{
-	int p;
-
-	p = sport[o - opl];
-	putcmd(Roct+p, o->blk, 0);
-	putcmd(Rfed+p, i[6] & ~0x30 | o->c->pan, 0);
-	p = port[o - opl];
-	putcmd(Rctl+p, i[0], 0);
-	putcmd(Ratk+p, i[1], 0);
-	putcmd(Rsus+p, i[2], 0);
-	putcmd(Rwav+p, i[3] & 3, 0);
-	putcmd(Rctl+3+p, i[7], 0);
-	putcmd(Ratk+3+p, i[8], 0);
-	putcmd(Rsus+3+p, i[9], 0);
-	putcmd(Rwav+3+p, i[10] & 3, 0);
-	o->i = i;
-}
-
-void
-noteoff(Chan *c, int n, int)
-{
-	Opl *o;
-
-	for(o=opl; o<ople; o++)
-		if(o->c == c && o->midn == n){
-			putcmd(Roct+sport[o-opl], o->blk, 0);
-			o->n = -1;
-		}
-}
-
-Opl *
-getch(void)
-{
-	Opl *o, *p;
-
-	p = opl;
-	for(o=opl; o<ople; o++){
-		if(o->n < 0)
-			return o;
-		if(o->t < p->t)
-			p = o;
-	}
-	return p;
-}
-
-void
-setoct(Opl *o)
-{
-	int n, b, f, d;
-	double e;
-
-	d = o->c->bend;
-	d += o->i == o->c->i->i2 ? o->c->i->fine : 0;
-	n = o->n + d / 0x1000 & 0x7f;
-	e = freq[n] + (d % 0x1000) * (freq[n+1] - freq[n]) / 0x1000;
-	if(o->c->i->fixed)
-		e = (double)(int)e;
-	f = (e * (1 << 20)) / Rate;
-	for(b=1; b<8; b++, f>>=1)
-		if(f < 1024)
-			break;
-	o->blk = b << 2 & Moct | f >> 8 & Mmsb;
-	putcmd(Rnum+sport[o-opl], f & 0xff, 0);
-	putcmd(Roct+sport[o-opl], Mkon | o->blk, 0);
-}
-
-void
-setvol(Opl *o)
-{
-	int p, w, x;
-
-	p = port[o - opl];
-	w = o->v * o->c->v / 127;
-	w = ovol[w * 64 / 127] * 63 / 128;
-	x = 63 + (o->i[5] & Mlvl) * w / 63 - w;
-	putcmd(Rsca+p, o->i[4] & Mscl | x, 0);
-	x = 63 + (o->i[12] & Mlvl) * w / 63 - w;
-	putcmd(Rsca+p+3, o->i[11] & Mscl | x, 0);
-}
-
-void
-putnote(Chan *c, int midn, int n, int v, vlong t, uchar *i)
-{
-	Opl *o;
-
-	o = getch();
-	o->c = c;
-	o->n = n;
-	o->midn = midn;
-	o->v = v;
-	o->t = t;
-	if(o->i != i)
-		setinst(o, i);
-	setvol(o);
-	setoct(o);
-}
-
-void
-noteon(Chan *c, int n, int v, vlong t)
-{
-	int x, m;
-
-	m = n;
-	if(c - chan == 9){
-		/* asspull workaround for percussions above gm set */
-		if(m == 85)
-			m = 37;
-		if(m == 82)
-			m = 44;
-		if(m < 35 || m > 81)
-			return;
-		c->i = inst + 128 + m - 35;
-	}
-	if(c->i->fixed)
-		m = c->i->n;
-	if(v == 0){
-		noteoff(c, n, 0);
-		return;
-	}
-	x = m + (c->i->fixed ? 0 : c->i->base[0]);
-	while(x < 0)
-		x += 12;
-	while(x > 8*12-1)
-		x -= 12;
-	putnote(c, n, x & 0xff, v, t, c->i->i);
-	if(c->i->dbl){
-		x = m + (c->i->fixed ? 0 : c->i->base[1]);
-		while(x < 0)
-			x += 12;
-		while(x > 95)
-			x -= 12;
-		putnote(c, n, x & 0xff, v, t, c->i->i2);
-	}
-}
-
-void
-resetchan(Chan *c)
-{
-	Opl *o;
-
-	for(o=opl; o<ople; o++)
-		if(o->c == c && o->n >= 0){
-			putcmd(Rfed+sport[o-opl], o->i[6] & ~0x30 | c->pan, 0);
-			setvol(o);
-			setoct(o);
-		}
-}
-
-double
-tc(double n)
-{
-	return (n * tempo * Rate / div) / 1e6;
-}
-
-void
-skip(Trk *x, int n)
-{
-	while(n-- > 0)
-		get8(x);
-}
-
-int
-getvar(Trk *x)
-{
-	int v, w;
-
-	w = get8(x);
-	v = w & 0x7f;
-	while(w & 0x80){
-		if(v & 0xff000000)
-			sysfatal("invalid variable-length number");
-		v <<= 7;
-		w = get8(x);
-		v |= w & 0x7f;
-	}
-	return v;
-}
-
-void
-samp(double n)
-{
-	vlong t;
-	double Δ;
-	static double ε;
-
-	Δ = tc(n) + ε;
-	t = Δ;
-	ε = Δ - t;
-	while(t > 0){
-		putcmd(0, 0, t > 0xffff ? 0xffff : t);
-		t -= 0xffff;
-	}
-}
-
-int
-ev(Trk *x, vlong t)
-{
-	int e, n, m;
-	Chan *c;
-
-	dprint(" [%zd] ", x - tr);
-	e = get8(x);
-	if((e & 0x80) == 0){
-		if(x->p != nil)
-			x->p--;
-		e = x->ev;
-		dprint(" *%02ux ", e);
-		if((e & 0x80) == 0)
-			sysfatal("invalid event %#ux", e);
-	}else
-		x->ev = e;
-	c = chan + (e & 15);
-	dprint("| %02ux ", e);
-	n = get8(x);
-	switch(e >> 4){
-	case 0x8: noteoff(c, n, get8(x)); break;
-	case 0x9: noteon(c, n, get8(x), t); break;
-	case 0xb:
-		m = get8(x);
-		switch(n){
-		case 0x00: if(m < Ninst) c->i = inst + m; break;
-		case 0x07: c->v = m; resetchan(c); break;
-		case 0x0a: c->pan = m < 32 ? 1<<4 : m > 96 ? 1<<5 : 3<<4; resetchan(c); break;
-		default: dprint("\nunknown controller %d", n);
-		}
-		break;
-	case 0xc: if(n < Ninst) c->i = inst + n; break;
-	case 0xe:
-		n = get8(x) << 7 | n;
-		c->bend = n - 0x4000 / 2;
-		resetchan(c);
-		break;
-	case 0xf:
-		if((e & 0xf) == 0){
-			while(get8(x) != 0xf7)
-				;
-			break;
-		}
-		m = get8(x);
-		switch(n){
-		case 0x2f: dprint(" -- so long!\n"); return -1;
-		case 0x51: tempo = get16(x) << 8; tempo |= get8(x); break;
-		default: skip(x, m);
-		}
-		break;
-	case 0xa:
-	case 0xd: get8(x); break;
-	default: sysfatal("invalid event %#ux\n", e >> 4);
-	}
-	dprint("\n");
-	return 0;
-}
-
-void
-readinst(char *file)
-{
-	int n;
-	uchar u[8];
-	Inst *i;
-
-	ib = bopen(file, OREAD);
-	bread(u, sizeof u);
-	if(memcmp(u, "#OPL_II#", sizeof u) != 0)
-		sysfatal("invalid patch file");
-	for(i=inst; i<inst+nelem(inst); i++){
-		n = get8(nil);
-		i->fixed = n & 1<<0;
-		i->dbl = opl2 ? 0 : n & 1<<2;
-		get8(nil);
-		i->fine = (get8(nil) - 128) * 64;
-		i->n = get8(nil);
-		bread(i->i, sizeof i->i);
-		get8(nil);
-		n = get8(nil);
-		n |= get8(nil) << 8;
-		i->base[0] = (s16int)n;
-		bread(i->i2, sizeof i->i2);
-		get8(nil);
-		n = get8(nil);
-		n |= get8(nil) << 8;
-		i->base[1] = (s16int)n;
-	}
-	Bterm(ib);
-}
-
-void
-readmid(char *file)
-{
-	u32int n, z;
-	uchar *s;
-	Trk *x;
-
-	ib = file != nil ? bopen(file, OREAD) : bfdopen(0, OREAD);
-	if(stream)
-		return;
-	if(get32(nil) != 0x4d546864 || get32(nil) != 6)
-		sysfatal("invalid header");
-	mfmt = get16(nil);
-	ntrk = get16(nil);
-	if(ntrk == 1)
-		mfmt = 0;
-	if(mfmt < 0 || mfmt > 1)
-		sysfatal("unsupported format %d", mfmt);
-	div = get16(nil);
-	tr = emalloc(ntrk * sizeof *tr);
-	for(x=tr, z=-1UL; x<tr+ntrk; x++){
-		if(get32(nil) != 0x4d54726b)
-			sysfatal("invalid track");
-		n = get32(nil);
-		s = emalloc(n);
-		bread(s, n);
-		x->s = s;
-		x->p = s;
-		x->e = s + n;
-		x->Δ = getvar(x);	/* prearm */
-		if(x->Δ < z)
-			z = x->Δ;
-	}
-	for(x=tr; x<tr+ntrk; x++)
-		x->Δ -= z;
-	Bterm(ib);
-}
-
-void
-usage(void)
-{
-	fprint(2, "usage: %s [-2Ds] [-i inst] [mid]\n", argv0);
-	exits("usage");
-}
-
-void
-main(int argc, char **argv)
-{
-	int n, end, debug;
-	char *i;
-	double f;
-	Chan *c;
-	Opl *o;
-	Trk *x;
-
-	i = "/mnt/wad/genmidi";
-	debug = 0;
-	ARGBEGIN{
-	case '2': opl2 = 1; ople = opl + 9; break;
-	case 'D': debug = 1; break;
-	case 'i': i = EARGF(usage()); break;
-	case 's': stream = 1; break;
-	default: usage();
-	}ARGEND
-	readinst(i);
-	readmid(*argv);
-	f = pow(2, 1./12);
-	for(n=0; n<nelem(freq); n++)
-		freq[n] = 440 * pow(f, n - 69);
-	for(c=chan; c<chan+nelem(chan); c++){
-		c->v = 0x5a;
-		c->bend = 0;
-		c->pan = 3<<4;
-		c->i = inst;
-	}
-	for(o=opl; o<ople; o++)
-		o->n = -1;
-	tempo = 500000;
-	putcmd(Rwse, Mwse, 0);
-	putcmd(Rop3, 1, 0);
-	trace = debug;
-	if(stream){
-		Trk ☺;
-		memset(&☺, 0, sizeof ☺);
-		tr = &☺;
-		for(;;){
-			getvar(&☺);
-			if(ev(&☺, 0) < 0)
-				exits(nil);
-		}
-	}
-	for(;;){
-		end = 1;
-		for(x=tr; x<tr+ntrk; x++){
-			if(x->ended)
-				continue;
-			end = 0;
-			x->Δ--;
-			x->t += tc(1);
-			while(x->Δ <= 0){
-				if(x->ended = ev(x, x->t)){
-					x->p = x->e;
-					break;
-				}
-				x->Δ = getvar(x);
-			}
-		}
-		if(end)
-			break;
-		samp(1);
-	}
-	exits(nil);
-}
--- a/sys/src/games/midi.c
+++ /dev/null
@@ -1,295 +1,0 @@
-#include <u.h>
-#include <libc.h>
-
-enum { SAMPLE = 44100 };
-
-struct Tracker {
-	uchar *data;
-	char ended;
-	vlong t;
-	vlong Δ;
-	uchar notes[16][128];
-	int cmd;
-} *tr;
-
-typedef struct Tracker Tracker;
-
-int debug;
-int fd, ofd, div, tempo = 500000, ntrack;
-int freq[128];
-uchar out[8192], *outp = out;
-
-void *
-emallocz(int size)
-{
-	void *v;
-	
-	v = malloc(size);
-	if(v == nil)
-		sysfatal("malloc: %r");
-	memset(v, 0, size);
-	return v;
-}
-
-void
-dprint(char *fmt, ...)
-{
-	char s[256];
-	va_list arg;
-
-	if(!debug)
-		return;
-	va_start(arg, fmt);
-	vseprint(s, s+sizeof s, fmt, arg);
-	va_end(arg);
-	fprint(2, "%s", s);
-}
-
-int
-get8(Tracker *src)
-{
-	uchar c;
-
-	if(src == nil){
-		if(read(fd, &c, 1) == 0)
-			sysfatal("unexpected eof");
-		return c;
-	}
-	dprint("%#p:%02ux", src, *src->data);
-	return *src->data++;
-}
-
-int
-get16(Tracker *src)
-{
-	int x;
-	
-	x = get8(src) << 8;
-	return x | get8(src);
-}
-
-int
-get32(Tracker *src)
-{
-	int x;
-	x = get16(src) << 16;
-	return x | get16(src);
-}
-
-int
-getvar(Tracker *src)
-{
-	int k, x;
-
-	x = get8(src);
-	k = x & 0x7F;
-	while(x & 0x80){
-		k <<= 7;
-		x = get8(src);
-		k |= x & 0x7F;
-	}
-	return k;
-}
-
-int
-peekvar(Tracker *src)
-{
-	uchar *p;
-	int v;
-	
-	p = src->data;
-	v = getvar(src);
-	src->data = p;
-	return v;
-}
-
-void
-skip(Tracker *src, int x)
-{
-	if(x) do
-		get8(src);
-	while(--x);
-}
-
-double
-tconv(int n)
-{
-	double v;
-	
-	v = n;
-	v *= tempo;
-	v *= SAMPLE;
-	v /= div;
-	v /= 1000000;
-	return v;
-}
-
-void
-run(double n)
-{
-	int j, k, no[128];
-	int t, f;
-	short u;
-	uvlong samp;
-	double Δ;
-	Tracker *x;
-	static double T, ε;
-	static uvlong τ;
-
-	Δ = tconv(n) + ε;
-	samp = Δ;
-	ε = Δ - samp;
-	if(samp <= 0)
-		return;
-	memset(no, 0, sizeof no);
-	for(x = tr; x < tr + ntrack; x++){
-		if(x->ended)
-			continue;
-		for(j = 0; j < 16; j++)
-			for(k = 0; k < 128; k++)
-				no[k] += x->notes[j][k];
-	}
-	while(samp--){
-		t = 0;
-		for(k = 0; k < 128; k++){
-			f = (τ % freq[k]) >= freq[k]/2 ? 1 : 0;
-			t += f * no[k];
-		}
-		u = t*10;
-		outp[0] = outp[2] = u;
-		outp[1] = outp[3] = u >> 8;
-		outp += 4;
-		if(outp == out + sizeof out){
-			write(ofd, out, sizeof out);
-			outp = out;
-		}
-		τ++;
-	}
-}
-
-void
-readevent(Tracker *src)
-{
-	int n,t;
-
-	dprint(" [%zd] ", src - tr);
-	t = get8(src);
-	if((t & 0x80) == 0){
-		src->data--;
-		t = src->cmd;
-		if((t & 0x80) == 0)
-			sysfatal("invalid midi");
-	}else
-		src->cmd = t;
-	dprint("(%02ux) ", t >> 4);
-	switch(t >> 4){
-	case 0x8:
-		n = get8(src);
-		get8(src);
-		src->notes[t & 15][n] = 0;
-		break;
-	case 0x9:
-		n = get8(src);
-		src->notes[t & 15][n] = get8(src);
-		break;
-	case 0xA:
-	case 0xD:
-	case 0xC:
-		get8(src);
-		break;
-	case 0xB:
-	case 0xE:
-		get16(src);
-		break;
-	case 0xF:
-		if((t & 0xF) == 0){
-			while(get8(src) != 0xF7)
-				;
-			return;
-		}
-		t = get8(src);
-		n = get8(src);
-		switch(t){
-		case 0x2F:
-			src->ended = 1;
-			break;
-		case 0x51:
-			tempo = get16(src) << 8;
-			tempo |= get8(src);
-			break;
-		case 5:
-			write(2, src->data, n);
-			skip(src, n);
-			break;
-		default:
-			dprint("unknown meta event type %.2x\n", t);
-		case 3: case 1: case 2: case 0x58: case 0x59: case 0x21:
-			skip(src, n);
-		}
-		break;
-	default:
-		sysfatal("unknown event type %x", t>>4);
-	}
-	dprint("\n");
-}
-
-void
-main(int argc, char **argv)
-{
-	int i, size, end;
-	uvlong z;
-	Tracker *x;
-
-	ARGBEGIN{
-	case 'D':
-		debug = 1;
-		break;
-	case 'c':
-		ofd = 1;
-		break;
-	}ARGEND;
-	if(*argv != nil)
-		fd = open(*argv, OREAD);
-	if(ofd == 0)
-		ofd = open("/dev/audio", OWRITE);
-	if(fd < 0 || ofd < 0)
-		sysfatal("open: %r");
-	if(get32(nil) != 0x4D546864 || get32(nil) != 6)
-		sysfatal("invalid file header");
-	get16(nil);
-	ntrack = get16(nil);
-	div = get16(nil);
-	tr = emallocz(ntrack * sizeof(*tr));
-	for(x=tr, z=-1UL; x<tr+ntrack; x++){
-		if(get32(nil) != 0x4D54726B)
-			sysfatal("invalid track header");
-		size = get32(nil);
-		x->data = emallocz(size);
-		readn(fd, x->data, size);
-		x->Δ = getvar(x);	/* prearm */
-		if(x->Δ < z)
-			z = x->Δ;
-	}
-	for(x=tr; x<tr+ntrack; x++)
-		x->Δ -= z;
-	for(i = 0; i < 128; i++)
-		freq[i] = SAMPLE / (440 * pow(1.05946, i - 69));
-	for(end=0; !end;){
-		end = 1;
-		for(x=tr; x<tr+ntrack; x++){
-			if(x->ended)
-				continue;
-			end = 0;
-			x->Δ--;
-			x->t += tconv(1);
-			while(x->Δ <= 0){
-				readevent(x);
-				if(x->ended)
-					break;
-				x->Δ = getvar(x);
-			}
-		}
-		run(1);
-	}
-	exits(nil);
-}
--- /dev/null
+++ b/sys/src/games/midi/dmid.c
@@ -1,0 +1,341 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include "midifile.h"
+
+typedef struct Opl Opl;
+enum{
+	Rwse = 0x01,
+		Mwse = 1<<5,	/* wave selection enable */
+	Rctl = 0x20,
+	Rsca = 0x40,
+		Mlvl = 63<<0,	/* total level */
+		Mscl = 3<<6,	/* scaling level */
+	Ratk = 0x60,
+	Rsus = 0x80,
+	Rnum = 0xa0,		/* f number lsb */
+	Roct = 0xb0,
+		Mmsb = 3<<0,	/* f number msb */
+		Moct = 7<<2,
+		Mkon = 1<<5,
+	Rfed = 0xc0,
+	Rwav = 0xe0,
+	Rop3 = 0x105,
+};
+
+struct Inst{
+	int fixed;
+	int dbl;
+	int fine;
+	uchar n;
+	uchar i[13];
+	uchar i2[13];
+	s16int base[2];
+};
+
+struct Opl{
+	Chan *c;
+	int n;
+	int midn;
+	int blk;
+	int v;
+	vlong t;
+	uchar *i;
+};
+Opl opl[18], *ople = opl + nelem(opl);
+int port[] = {
+	0x0, 0x1, 0x2, 0x8, 0x9, 0xa, 0x10, 0x11, 0x12,
+	0x100, 0x101, 0x102, 0x108, 0x109, 0x10a, 0x110, 0x111, 0x112
+};
+int sport[] = {
+	0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8,
+	0x100, 0x101, 0x102, 0x103, 0x104, 0x105, 0x106, 0x107, 0x108
+};
+uchar ovol[] = {
+	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
+};
+
+double freq[128];
+int opl2;
+
+void
+putcmd(u16int r, u8int v, u16int dt)
+{
+	uchar *p, u[5];
+
+	p = u;
+	*p++ = r;
+	if(!opl2)
+		*p++ = r >> 8;
+	*p++ = v;
+	*p++ = dt;
+	*p++ = dt >> 8;
+	write(1, u, p-u);
+}
+
+void
+setinst(Opl *o, uchar *i)
+{
+	int p;
+
+	p = sport[o - opl];
+	putcmd(Roct+p, o->blk, 0);
+	putcmd(Rfed+p, i[6] & ~0x30 | o->c->pan, 0);
+	p = port[o - opl];
+	putcmd(Rctl+p, i[0], 0);
+	putcmd(Ratk+p, i[1], 0);
+	putcmd(Rsus+p, i[2], 0);
+	putcmd(Rwav+p, i[3] & 3, 0);
+	putcmd(Rctl+3+p, i[7], 0);
+	putcmd(Ratk+3+p, i[8], 0);
+	putcmd(Rsus+3+p, i[9], 0);
+	putcmd(Rwav+3+p, i[10] & 3, 0);
+	o->i = i;
+}
+
+void
+noteoff(Chan *c, int n, int)
+{
+	Opl *o;
+
+	for(o=opl; o<ople; o++)
+		if(o->c == c && o->midn == n){
+			putcmd(Roct+sport[o-opl], o->blk, 0);
+			o->n = -1;
+		}
+}
+
+Opl *
+getch(void)
+{
+	Opl *o, *p;
+
+	p = opl;
+	for(o=opl; o<ople; o++){
+		if(o->n < 0)
+			return o;
+		if(o->t < p->t)
+			p = o;
+	}
+	return p;
+}
+
+void
+setoct(Opl *o)
+{
+	int n, b, f, d;
+	double e;
+
+	d = o->c->bend;
+	d += o->i == o->c->i->i2 ? o->c->i->fine : 0;
+	n = o->n + d / 0x1000 & 0x7f;
+	e = freq[n] + (d % 0x1000) * (freq[n+1] - freq[n]) / 0x1000;
+	if(o->c->i->fixed)
+		e = (double)(int)e;
+	f = (e * (1 << 20)) / samprate;
+	for(b=1; b<8; b++, f>>=1)
+		if(f < 1024)
+			break;
+	o->blk = b << 2 & Moct | f >> 8 & Mmsb;
+	putcmd(Rnum+sport[o-opl], f & 0xff, 0);
+	putcmd(Roct+sport[o-opl], Mkon | o->blk, 0);
+}
+
+void
+setvol(Opl *o)
+{
+	int p, w, x;
+
+	p = port[o - opl];
+	w = o->v * o->c->vol / 127;
+	w = ovol[w * 64 / 127] * 63 / 128;
+	x = 63 + (o->i[5] & Mlvl) * w / 63 - w;
+	putcmd(Rsca+p, o->i[4] & Mscl | x, 0);
+	x = 63 + (o->i[12] & Mlvl) * w / 63 - w;
+	putcmd(Rsca+p+3, o->i[11] & Mscl | x, 0);
+}
+
+void
+putnote(Chan *c, int midn, int n, int v, vlong t, uchar *i)
+{
+	Opl *o;
+
+	o = getch();
+	o->c = c;
+	o->n = n;
+	o->midn = midn;
+	o->v = v;
+	o->t = t;
+	if(o->i != i)
+		setinst(o, i);
+	setvol(o);
+	setoct(o);
+}
+
+void
+noteon(Chan *c, int n, int v, vlong t)
+{
+	int x, m;
+
+	m = n;
+	if(c - chan == 9){
+		/* asspull workaround for percussions above gm set */
+		if(m == 85)
+			m = 37;
+		if(m == 82)
+			m = 44;
+		if(m < 35 || m > 81)
+			return;
+		c->i = inst + 128 + m - 35;
+	}
+	if(c->i->fixed)
+		m = c->i->n;
+	if(v == 0){
+		noteoff(c, n, 0);
+		return;
+	}
+	x = m + (c->i->fixed ? 0 : c->i->base[0]);
+	while(x < 0)
+		x += 12;
+	while(x > 8*12-1)
+		x -= 12;
+	putnote(c, n, x & 0xff, v, t, c->i->i);
+	if(c->i->dbl){
+		x = m + (c->i->fixed ? 0 : c->i->base[1]);
+		while(x < 0)
+			x += 12;
+		while(x > 95)
+			x -= 12;
+		putnote(c, n, x & 0xff, v, t, c->i->i2);
+	}
+}
+
+void
+resetchan(Chan *c)
+{
+	Opl *o;
+
+	for(o=opl; o<ople; o++)
+		if(o->c == c && o->n >= 0){
+			putcmd(Rfed+sport[o-opl], o->i[6] & ~0x30 | c->pan, 0);
+			setvol(o);
+			setoct(o);
+		}
+}
+
+void
+samp(double n)
+{
+	vlong t;
+	double Δ;
+	static double ε;
+
+	Δ = delay(n) + ε;
+	t = Δ;
+	ε = Δ - t;
+	while(t > 0){
+		putcmd(0, 0, t > 0xffff ? 0xffff : t);
+		t -= 0xffff;
+	}
+}
+
+void
+event(Track *t)
+{
+	int e;
+	Msg msg;
+	Chan *c;
+
+	e = nextev(t);
+	translate(t, e, &msg);
+	c = msg.chan;
+	switch(msg.type){
+	case Cnoteoff: noteoff(msg.chan, msg.arg1, msg.arg2); break;
+	case Cnoteon: noteon(msg.chan, msg.arg1, msg.arg2, t->t); break;
+	case Cbankmsb: if(msg.arg2 < Ninst) c->i = inst + msg.arg2; break;
+	case Cprogram: if(msg.arg2 < Ninst) c->i = inst + msg.arg1; break;
+	case Cchanvol: /* wet floor */
+	case Cpan:  /* wet floor */
+	case Cpitchbend: resetchan(c); break;
+	}
+}
+
+void
+readinst(char *file)
+{
+	int n;
+	uchar u[8];
+	Inst *i;
+	Chan *c;
+
+	inbf = eopen(file, OREAD);
+	Bread(inbf, u, sizeof u);
+	if(memcmp(u, "#OPL_II#", sizeof u) != 0)
+		sysfatal("invalid patch file");
+	inst = emalloc(Ninst * sizeof *inst);
+	for(i=inst; i<inst+Ninst; i++){
+		n = get8(nil);
+		i->fixed = n & 1<<0;
+		i->dbl = opl2 ? 0 : n & 1<<2;
+		get8(nil);
+		i->fine = (get8(nil) - 128) * 64;
+		i->n = get8(nil);
+		Bread(inbf, i->i, sizeof i->i);
+		get8(nil);
+		n = get8(nil);
+		n |= get8(nil) << 8;
+		i->base[0] = (s16int)n;
+		Bread(inbf, i->i2, sizeof i->i2);
+		get8(nil);
+		n = get8(nil);
+		n |= get8(nil) << 8;
+		i->base[1] = (s16int)n;
+	}
+	Bterm(inbf);
+	inbf = nil;
+	for(c=chan; c<chan+nelem(chan); c++)
+		c->i = inst;
+}
+
+void
+usage(void)
+{
+	fprint(2, "usage: %s [-2Ds] [-i inst] [mid]\n", argv0);
+	exits("usage");
+}
+
+void
+main(int argc, char **argv)
+{
+	int n;
+	char *i;
+	double f;
+	Opl *o;
+
+	samprate = 49716,		/* opl3 sampling rate */
+	i = "/mnt/wad/genmidi";
+	ARGBEGIN{
+	case '2': opl2 = 1; ople = opl + 9; break;
+	case 'D': trace = 1; break;
+	case 'i': i = EARGF(usage()); break;
+	case 's': stream = 1; break;
+	default: usage();
+	}ARGEND
+	initmid();
+	readinst(i);
+	if(readmid(*argv) < 0)
+		sysfatal("readmid: %r");
+	f = pow(2, 1./12);
+	for(n=0; n<nelem(freq); n++)
+		freq[n] = 440 * pow(f, n - 69);
+	for(o=opl; o<ople; o++)
+		o->n = -1;
+	putcmd(Rwse, Mwse, 0);
+	putcmd(Rop3, 1, 0);
+	evloop();
+	exits(nil);
+}
--- /dev/null
+++ b/sys/src/games/midi/mid2s.c
@@ -1,0 +1,76 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include "midifile.h"
+
+/* some devices want delta-time in a certain format, others want it zero
+ * not sure why yet */
+static int magic;
+
+void
+samp(double n)
+{
+	double Δt;
+	long s;
+	static double t0;
+
+	if(t0 == 0.0)
+		t0 = nsec();
+	t0 += n * 1000 * tempo / div;
+	Δt = t0 - nsec();
+	s = floor(Δt / 1000000);
+	if(s > 0)
+		sleep(s);
+}
+
+/* set delay to 0, and translate running status: the receiver
+ * only sees one track whereas running status is a property of
+ * each track (stream); don't send EOT for the same reason */
+void
+event(Track *t)
+{
+	int e, n;
+	uchar u[16], *cur, *q;
+	Msg msg;
+
+	q = u + 1;
+	e = nextev(t);
+	*q++ = e;
+	cur = t->cur;
+	translate(t, e, &msg);
+	if(msg.type == Ceot)
+		return;
+	u[0] = magic ? e >> 4 | (e & 0xf) << 4 : 0;
+	n = t->cur - cur;
+	if(msg.type == Csysex || n > nelem(u) - (q - u)){
+		write(1, u, q - u);
+		write(1, cur, n);
+	}else{
+		memcpy(q, cur, n);
+		write(1, u, n + (q - u));
+	}
+}
+
+void
+usage(void)
+{
+	fprint(2, "usage: %s [-D] [mid]\n", argv0);
+	exits("usage");
+}
+
+void
+main(int argc, char **argv)
+{
+	uchar eot[] = {0x00, 0xff, 0x2f, 0x00};
+
+	ARGBEGIN{
+	case 'D': trace = 1; break;
+	case 'm': magic = 1; break;
+	default: usage();
+	}ARGEND
+	if(readmid(*argv) < 0)
+		sysfatal("readmid: %r");
+	evloop();
+	write(1, eot, sizeof eot);
+	exits(nil);
+}
--- /dev/null
+++ b/sys/src/games/midi/midi.c
@@ -1,0 +1,95 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include "midifile.h"
+
+typedef struct{
+	uchar notes[Nchan][128];
+} Notes;
+
+int freq[128];
+uchar outbuf[IOUNIT], *outp = outbuf;
+
+void
+samp(double n)
+{
+	int i, j, f, v, ns, no[128];
+	short u;
+	Track *t;
+	Notes *nn;
+	static double Δ, ε;
+	static uvlong τ;
+
+	Δ = delay(n) + ε;
+	ns = Δ;
+	ε = Δ - ns;
+	if(ns <= 0)
+		return;
+	memset(no, 0, sizeof no);
+	for(t=tracks; t<tracks+ntrk; t++){
+		if(t->ended)
+			continue;
+		nn = t->aux;
+		for(i=0; i<Nchan; i++)
+			for(j=0; j<128; j++)
+				no[j] += nn->notes[i][j];
+	}
+	while(ns-- > 0){
+		v = 0;
+		for(j=0; j<128; j++){
+			f = (τ % freq[j]) >= freq[j]/2 ? 1 : 0;
+			v += f * no[j];
+		}
+		u = v * 10;
+		outp[0] = outp[2] = u;
+		outp[1] = outp[3] = u >> 8;
+		outp += 4;
+		if(outp == outbuf + sizeof outbuf){
+			Bwrite(outbf, outbuf, sizeof outbuf);
+			outp = outbuf;
+		}
+		τ++;
+	}
+}
+
+void
+event(Track *t)
+{
+	int e, c;
+	Msg msg;
+	Notes *nn;
+
+	e = nextev(t);
+	translate(t, e, &msg);
+	c = msg.chan - chan;
+	nn = t->aux;
+	switch(msg.type){
+	case Cnoteoff: nn->notes[c][msg.arg1] = 0; break;
+	case Cnoteon: nn->notes[c][msg.arg1] = msg.arg2; break;
+	}
+}
+
+void
+main(int argc, char **argv)
+{
+	int i, cat;
+	Track *t;
+
+	cat = 0;
+	ARGBEGIN{
+	case 'D': trace = 1; break;
+	case 'c': cat = 1; break;
+	}ARGEND;
+	initmid();
+	if(readmid(*argv) < 0)
+		sysfatal("readmid: %r");
+	for(i=0; i<128; i++)
+		freq[i] = samprate / (440 * pow(1.05946, i - 69));
+	for(t=tracks; t<tracks+ntrk; t++)
+		t->aux = emalloc(sizeof(Notes));
+	outbf = cat ? efdopen(1, OWRITE) : eopen("/dev/audio", OWRITE);
+	evloop();
+	Bwrite(outbf, outbuf, outp - outbuf);
+	Bterm(outbf);
+	exits(nil);
+}
--- /dev/null
+++ b/sys/src/games/midi/midifile.c
@@ -1,0 +1,465 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include "midifile.h"
+
+Track *tracks;
+Chan chan[Nchan];
+Inst *inst;
+int mfmt, ntrk, div = 1, tempo = 500000;
+int samprate = Rate;
+int trace, stream, writeback;
+vlong tic;
+Biobuf *inbf, *outbf;
+int rate = Rate;
+
+void *
+emalloc(ulong n)
+{
+	void *p;
+
+	p = mallocz(n, 1);
+	if(p == nil)
+		sysfatal("mallocz: %r");
+	setmalloctag(p, getcallerpc(&n));
+	return p;
+}
+
+void *
+erealloc(void *p, usize n, usize oldn)
+{
+	if((p = realloc(p, n)) == nil)
+		sysfatal("realloc: %r");
+	setrealloctag(p, getcallerpc(&p));
+	if(n > oldn)
+		memset((uchar *)p + oldn, 0, n - oldn);
+	return p;
+}
+
+void
+dprint(char *fmt, ...)
+{
+	char s[256];
+	va_list arg;
+
+	if(!trace)
+		return;
+	va_start(arg, fmt);
+	vseprint(s, s+sizeof s, fmt, arg);
+	va_end(arg);
+	fprint(2, "%s", s);
+}
+
+Biobuf *
+efdopen(int fd, int mode)
+{
+	Biobuf *bf;
+
+	if((bf = Bfdopen(fd, mode)) == nil)
+		sysfatal("efdopen: %r");
+	Blethal(bf, nil);
+	return bf;
+}
+
+Biobuf *
+eopen(char *file, int mode)
+{
+	int fd;
+
+	if((fd = open(file, mode)) < 0)
+		sysfatal("eopen: %r");
+	return efdopen(fd, mode);
+}
+
+u8int
+get8(Track *t)
+{
+	u8int v;
+
+	if(t == nil || t->cur == nil || t->buf == nil)
+		Bread(inbf, &v, 1);
+	else{
+		if(t->cur >= t->buf + t->bufsz || t->ended)
+			sysfatal("track overflow");
+		v = *t->cur++;
+	}
+	return v;
+}
+
+u16int
+get16(Track *t)
+{
+	u16int v;
+
+	v = get8(t) << 8;
+	return v | get8(t);
+}
+
+u32int
+get32(Track *t)
+{
+	u32int v;
+
+	v = get16(t) << 16;
+	return v | get16(t);
+}
+
+static void
+growmid(Track *t, int n)
+{
+	usize cur, run;
+
+	/* one extra byte for delay prefetch */
+	if(t->cur + n + 1 < t->buf + t->bufsz)
+		return;
+	cur = t->cur - t->buf;
+	run = t->run - t->buf;
+	if(n < 8192)
+		n = 8192;
+	t->buf = erealloc(t->buf, t->bufsz + n + 1, t->bufsz);
+	t->bufsz += n + 1;
+	t->cur = t->buf + cur;
+	t->run = t->buf + run;
+}
+
+static void
+put8(Track *t, u8int v)
+{
+	if(t != nil){
+		growmid(t, 1);
+		*t->cur++ = v;
+	}else
+		Bwrite(outbf, &v, 1);
+}
+
+static void
+put16(Track *t, u16int v)
+{
+	put8(t, v >> 8);
+	put8(t, v);
+}
+
+static void
+put32(Track *t, u32int v)
+{
+	put16(t, v >> 16);
+	put16(t, v);
+}
+
+static void
+putvar(Track *t)
+{
+	int w;
+	uchar u[4], *p;
+
+	p = u + nelem(u) - 1;
+	w = t->Δ;
+	if(w & 1<<31)
+		sysfatal("invalid variable-length number %08ux", w);
+	*p-- = w;
+	while(w >= 0x80){
+		w >>= 8;
+		*p-- = w;
+	}
+	Bwrite(outbf, p, u + sizeof(u) - 1 - p);
+}
+
+static int
+getvar(Track *t)
+{
+	int n, v, w;
+
+	w = get8(t);
+	v = w & 0x7f;
+	for(n=0; w&0x80; n++){
+		if(n == 3)
+			sysfatal("invalid variable-length number");
+		v <<= 7;
+		w = get8(t);
+		v |= w & 0x7f;
+	}
+	return v;
+}
+
+u32int
+peekvar(Track *t)
+{
+	uchar *cur;
+	uint v;
+
+	cur = t->cur;
+	v = getvar(t);
+	t->cur = cur;
+	return v;
+}
+
+static void
+skip(Track *t, int n)
+{
+	while(n-- > 0)
+		get8(t);
+}
+
+double
+delay(double n)
+{
+	return (n * tempo * samprate / div) / 1e6;
+}
+
+vlong
+ns2tic(double n)
+{
+	return n * div * 1e3 / tempo;
+}
+
+int
+nextev(Track *t)
+{
+	int e;
+
+	if(writeback)
+		putvar(t);
+	t->run = t->cur;
+	e = get8(t);
+	if((e & 0x80) == 0){
+		if(t->cur != nil){
+			t->cur--;
+			t->run--;
+		}
+		e = t->latch;
+		if((e & 0x80) == 0)
+			sysfatal("invalid event %#ux", e);
+	}else
+		t->latch = e;
+	return e;
+}
+
+static void
+newmsg(Msg *m, int c, int type, int arg1, int arg2, usize size)
+{
+	m->chan = chan + c;
+	m->type = type;
+	m->arg1 = arg1;
+	m->arg2 = arg2;
+	m->size = size;
+}
+void
+translate(Track *t, int e, Msg *msg)
+{
+	int c, n, m, type;
+	uchar *p;
+
+	c = e & 0xf;
+	dprint("Δ %.2f ch%02d ", t->Δ, c);
+	n = get8(t);
+	m = -1;
+	type = Cunknown;
+	switch(e >> 4){
+	case 0x8:
+		m = get8(t);
+		dprint("note off\t%02ux\taftertouch\t%02ux", n, m);
+		type = Cnoteoff;
+		break;
+	case 0x9:
+		m = get8(t);
+		dprint("note on\t%02ux\tvelocity\t%02ux", n, m);
+		type = Cnoteon;
+		break;
+	case 0xb:
+		m = get8(t);
+		dprint("control change: ");
+		switch(n){
+		case 0x00:
+			dprint("bank select msb\t%02ux", m);
+			type = Cbankmsb;
+			break;
+		case 0x07:
+			dprint("channel volume\t%02ux", m);
+			chan[c].vol = m;
+			type = Cchanvol;
+			break;
+		case 0x0a:
+			dprint("pan\t%02ux", m);
+			chan[c].pan = m < 32 ? 1<<4 : m > 96 ? 1<<5 : 3<<4; 
+			type = Cpan;
+			break;
+		default:
+			dprint("unknown controller %.4ux", n);
+			break;
+		}
+		break;
+	case 0xc:
+		dprint("program change\t%02ux", n);
+		type = Cprogram;
+		break;
+	case 0xe:
+		n = (get8(t) << 7 | n) - 0x4000 / 2;
+		chan[c].bend = n;
+		dprint("pitch bend\t%02x", n);
+		type = Cpitchbend;
+		break;
+	case 0xf:
+		dprint("sysex:\t");
+		if((e & 0xf) == 0){
+			m = 0;
+			while(get8(t) != 0xf7)
+				m++;
+			fprint(2, "sysex n %d m %d\n", n, m);
+			type = Csysex;
+			break;
+		}
+		m = get8(t);
+		switch(n){
+		case 0x2f:
+			dprint("... so long!");
+			t->ended = 1;
+			type = Ceot;
+			break;
+		case 0x51:
+			tempo = get16(t) << 8;
+			tempo |= get8(t);
+			dprint("tempo change\t%d", tempo);
+			type = Ctempo;
+			break;
+		default:
+			dprint("skipping unhandled event %02ux", n);
+			skip(t, m);
+			break;
+		}
+		break;
+	case 0xa:
+		m = get8(t);
+		dprint("polyphonic key pressure/aftertouch\t%02ux\t%02ux", n, m);
+		type = Ckeyafter;
+		break;
+	case 0xd:
+		m = get8(t);
+		dprint("channel pressure/aftertouch\t%02ux\t%02ux", n, m);
+		type = Cchanafter;
+		break;
+	default: sysfatal("invalid event %#ux", e >> 4);
+	}
+	newmsg(msg, c, type, n, m, t->cur - t->run);
+	dprint("\t[");
+	for(p=t->run; p<t->cur; p++)
+		dprint("%02ux", *p);
+	dprint("]\n");
+}
+
+void
+writemid(char *file)
+{
+	u32int n;
+	Track *t;
+
+	outbf = file == nil ? efdopen(1, OWRITE) : eopen(file, OWRITE);
+	put32(nil, 0x4d546864);	/* MThd */
+	put32(nil, 6);
+	put16(nil, mfmt);
+	put16(nil, ntrk);
+	put16(nil, div);
+	for(t=tracks; t<tracks+ntrk; t++){
+		put32(nil, 0x4d54726b);	/* MTrack */
+		n = t->cur - t->buf;
+		put32(nil, n);
+		Bwrite(outbf, t->buf, n);
+	}
+	Bterm(outbf);
+	outbf = nil;
+}
+
+int
+readmid(char *file)
+{
+	u32int n, z;
+	Track *t;
+
+	inbf = file == nil ? efdopen(0, OREAD) : eopen(file, OREAD);
+	if(stream){
+		mfmt = 0;
+		ntrk = 1;
+		tracks = emalloc(ntrk * sizeof *tracks);
+		return 0;
+	}
+	if(get32(nil) != 0x4d546864 || get32(nil) != 6){
+		werrstr("invalid header");
+		return -1;
+	}
+	mfmt = get16(nil);
+	ntrk = get16(nil);
+	if(ntrk == 1)
+		mfmt = 0;
+	if(mfmt < 0 || mfmt > 1){
+		werrstr("unsupported format %d", mfmt);
+		return -1;
+	}
+	div = get16(nil);
+	tracks = emalloc(ntrk * sizeof *tracks);
+	for(t=tracks, z=-1UL; t<tracks+ntrk; t++){
+		if(get32(nil) != 0x4d54726b){
+			werrstr("invalid track");
+			return -1;
+		}
+		n = get32(nil);
+		growmid(t, n);
+		Bread(inbf, t->buf, n);
+		t->Δ = getvar(t);	/* prearm */
+		if(t->Δ < z)
+			z = t->Δ;
+	}
+	for(t=tracks; t<tracks+ntrk; t++)
+		t->Δ -= z;
+	Bterm(inbf);
+	inbf = nil;
+	return 0;
+}
+
+void
+evloop(void)
+{
+	int end;
+	Track *t;
+
+	if(stream){
+		for(t=tracks;;){
+			t->Δ = getvar(t);
+			event(t);
+			if(t->ended)
+				return;
+			samp(1);
+			tic++;
+		}
+	}
+	for(;;){
+		end = 1;
+		for(t=tracks; t<tracks+ntrk; t++){
+			if(t->ended)
+				continue;
+			end = 0;
+			t->Δ--;
+			t->t += delay(1);
+			while(t->Δ <= 0){
+				event(t);
+				if(t->ended)
+					break;
+				t->Δ = getvar(t);
+			}
+		}
+		if(end)
+			break;
+		samp(1);
+		tic++;
+	}
+}
+
+void
+initmid(void)
+{
+	Chan *c;
+
+	for(c=chan; c<chan+nelem(chan); c++){
+		c->vol = 0x5a;
+		c->bend = 0;
+		c->pan = 3<<4;
+	}
+}
--- /dev/null
+++ b/sys/src/games/midi/midifile.h
@@ -1,0 +1,87 @@
+typedef struct Msg Msg;
+typedef struct Track Track;
+typedef struct Chan Chan;
+typedef struct Inst Inst;
+
+enum{
+	Rate = 44100,
+	Ninst = 128 + 81-35+1,
+	Nchan = 16,
+	Percch = 9,
+};
+
+struct Msg{
+	int type;
+	Chan *chan;
+	int arg1;
+	int arg2;
+	usize size;
+};
+struct Track{
+	uchar *buf;
+	usize bufsz;
+	uchar *cur;
+	uchar *run;
+	double Δ;
+	double t;
+	int latch;
+	int ended;
+	void *aux;
+};
+extern Track *tracks;
+
+#pragma incomplete Inst
+struct Chan{
+	Inst *i;
+	int vol;
+	int bend;
+	int pan;
+};
+extern Chan chan[16];
+extern Inst *inst;
+
+enum{
+	Cnoteoff,
+	Cnoteon,
+	Cbankmsb,
+	Cchanvol,
+	Cpan,
+	Cprogram,
+	Cpitchbend,
+	Ceot,
+	Ctempo,
+	Ckeyafter,
+	Cchanafter,
+	Csysex,
+	Cunknown,
+};
+
+extern int mfmt, ntrk, div, tempo;
+extern int trace, stream;
+extern vlong tic;
+extern int samprate;
+extern Biobuf *inbf, *outbf;
+
+void*	emalloc(ulong);
+void	dprint(char*, ...);
+int	readmid(char*);
+void	writemid(char*);
+u32int	peekvar(Track*);
+void	translate(Track*, int, Msg*);
+int	nextev(Track*);
+void	evloop(void);
+double	delay(double);
+vlong	ns2tic(double);
+void	initmid(void);
+
+Biobuf*	eopen(char*, int);
+Biobuf*	efdopen(int, int);
+u8int	get8(Track*);
+u16int	get16(Track*);
+u32int	get32(Track*);
+
+/* application-defined */
+void	event(Track*);
+void	samp(double);
+
+#pragma	varargck	argpos	dprint	1
--- /dev/null
+++ b/sys/src/games/midi/mkfile
@@ -1,0 +1,12 @@
+</$objtype/mkfile
+TARG=\
+	midi\
+	mid2s\
+	dmid\
+
+HFILES=
+OFILES=\
+	midifile.$O\
+
+</sys/src/cmd/mkmany
+BIN=/$objtype/bin/games
--- a/sys/src/games/mkfile
+++ b/sys/src/games/mkfile
@@ -17,9 +17,7 @@
 	glendy\
 	packet\
 	mandel\
-	midi\
 	wadfs\
-	dmid\
 	dpic\
 	todpic\
 	turtle\
@@ -40,6 +38,7 @@
 	gb\
 	gba\
 	mahjongg\
+	midi\
 	mines\
 	mix\
 	md\