shithub: mst

Download patch

ref: a54b3cc31a0711e96c03e309613adfc3726695d1
author: qwx <[email protected]>
date: Mon May 27 19:50:23 EDT 2019

initial import

diff: cannot open b/lib//null: file does not exist: 'b/lib//null'
--- /dev/null
+++ b/lib/metallica.and.justice.for.all.mst
@@ -1,0 +1,243 @@
+# done by hand; very incomplete; two guitar tracks only; inaccuracies exist
+t 96
+# 3/4
+# bar 1 - intro riff 1
+i 0 27
+i 1 27
+8 0e3- 0e4- 0g♯4
+8 0a4
+8 0b4-
+8 0e4-
+8 0b4,0 0a4
+8/3 0g♯4
+8/3 0a4
+8/3 0g♯4
+# bar 2
+8 0e3,0 0e4,0 0d4- 0f♯4
+8 0g4
+8 0g4
+8 0d4- 0f♯4
+8 0a4
+8 0d4- 0f♯4
+# bar 3
+8 0d4,0 0c4- 0e4
+8 0f4
+8 0g4
+8 0c4-
+8 0f4
+8/3 0e4
+8/3 0f4
+8/3 0e4
+# bar 4
+8 0c4,0 0b3- 0d4
+8 0e4
+8 0e4
+8 0b3- 0d4
+8 0e4
+8 0b3- 0d4
+# bar 5
+8 0b3,0 0a♯3- 0d4
+8 0e4
+8 0e4
+8 0a♯3- 0d4
+8 0e4
+8 0a♯3,0 0a4
+# bar 6
+2. 0a♯3 0f4
+
+# bar 7
+8 0e3- 0e4- 0g♯4
+8 0a4
+8 0b4-
+8 0e4-
+8 0b4,0 0a4
+8/3 0g♯4
+8/3 0a4
+8/3 0g♯4
+# bar 8
+8 0e3,0 0e4,0 0d4- 0f♯4
+8 0g4
+8 0g4
+8 0d4- 0f♯4
+8 0a4
+8 0d4- 0f♯4
+# bar 9
+8 0d4,0 0c4- 0e4
+8 0f4
+8 0g4
+8 0c4-
+8 0f4
+8/3 0e4
+8/3 0f4
+8/3 0e4
+# bar 10
+8 0c4,0 0b3- 0d4
+8 0e4
+8 0e4
+8 0b3- 0d4
+8 0e4
+8 0b3- 0d4
+# bar 11
+8 0b3,0 0a♯3- 0d4
+8 0e4
+8 0e4
+8 0a♯3- 0d4
+8 0e4
+8 0a♯3,0 0a4
+# bar 12
+2. 0a♯3 0f4
+
+# bar 13 - intro riff 2
+i 1 29
+8 0e3- 0e4- 0g♯4 1g♯4
+8 0a4 1a4
+8 0b4- 1b4
+32 0e4- 1d5
+16. 1e5-
+8 0b4,0 0a4 1b4,0
+8/3 0g♯4
+8/3 0a4
+8/3 0g♯4
+# bar 14
+8 0e3,0 0e4,0 0d4- 0f♯4 1e5,0 1f♯4
+8 0g4 1a4
+16 0g4- 1d5
+16 1d5,0
+16 0g4,0 0d4- 0f♯4- 1d5
+16 1d♯5
+8 0f♯4,0 0a4 1d5-
+8 0d4- 0f♯4
+# bar 15
+8 0d4,0 0c4- 0e4 1d5,0 1e4
+8 0f4 1f4
+8 0g4 1g4
+8 0c4- 1c5-
+8 0f4
+8/3 0e4 1c5,0 1g4-
+8/3 0f4
+8/3 0e4
+# bar 16
+# FIXME: needs staccato here
+i 0 30
+i 1 30
+8 0c4,0 0b3- 0d4 1g4,0 1b3- 1d4
+8 0e4 1e4
+8 0e4 1e4
+8 0b3- 0d4 1d4
+8 0e4 1e4
+8 0b3- 0d4 1d4
+# bar 17
+8 0b3,0 0a♯3- 0d4 1b3,0 1a♯3-
+8 0e4 1e4
+8 0e4 1e4
+8 0a♯3- 0d4 1d4
+8 0e4 1e4
+8 0a♯3,0 0a4 1a♯3,0 1a4
+# bar 18
+4 0a♯3 0f4 0a♯4 1a♯3 1f4 1a♯4
+4 0a♯3 0f4 0a♯4 1a♯3 1f4 1a♯4
+4 0a♯3 0f4 0a♯4 1a♯3 1f4 1a♯4
+
+# bar 19
+i 0 27
+i 1 29
+8 0e3- 0e4- 0g♯4 1g♯4
+8 0a4 1a4
+8 0b4- 1b4
+32 0e4- 1d5
+16. 1e5-
+8 0b4,0 0a4 1b4,0
+8/3 0g♯4
+8/3 0a4
+8/3 0g♯4
+# bar 20
+8 0e3,0 0e4,0 0d4- 0f♯4 1e5,0 1f♯4
+8 0g4 1a4
+16 0g4- 1d5
+16 1d5,0
+16 0g4,0 0d4- 0f♯4- 1d5
+16 1d♯5
+8 0f♯4,0 0a4 1d5-
+8 0d4- 0f♯4
+# bar 21
+8 0d4,0 0c4- 0e4 1d5,0 1e4
+8 0f4 1f4
+8 0g4 1g4
+8 0c4- 1c5-
+8 0f4
+8/3 0e4 1c5,0 1g4-
+8/3 0f4
+8/3 0e4
+# bar 22
+i 0 30
+i 1 30
+8 0c4,0 0b3- 0d4 1g4,0 1b3- 1d4
+8 0e4 1e4
+8 0e4 1e4
+8 0b3- 0d4 1d4
+8 0e4 1e4
+8 0b3- 0d4 1d4
+# bar 23
+8 0b3,0 0a♯3- 0d4 1b3,0 1a♯3-
+8 0e4 1e4
+8 0e4 1e4
+8 0a♯3- 0d4 1d4
+8 0e4 1e4
+8 0a♯3,0 0a4 1a♯3,0 1a4
+# bar 24
+4 0a♯3 0f4 0a♯4 1a♯3 1f4 1a♯4
+4 0a♯3 0f4 0a♯4 1a♯3 1f4 1a♯4
+4 0a♯3 0f4 0a♯4 1a♯3 1f4 1a♯4
+
+# bar 25 - intro riff 3
+i 0 27
+8 0e4 0g♯4 1g♯4
+8 0e4 0a4 1b4
+4 0e4 0b4 1e5
+8 0e4
+8/3 0e4- 0g♯4- 1e5
+8/3 1f♯5
+8/3 1e5
+# bar 26
+8 0e4,0 0g♯4,0 0d4 0f♯4 1f♯4
+8 0d4 0g4 1a4
+16 0d4 0a4	# ghost note on ch 1
+16 0d4 0a4 1d5
+8 0d4 0f♯4 1a4
+8 0d4 0a4 1d5 1f♯5
+8 0d4 0f♯4 1a4 1d5
+# bar 27
+8 0c4 0e4 1e4
+8 0c4 0f4 1g4
+4 0c4 0g4 1c5
+8 0c4 0f4 1g4
+8/3 0c4- 0e4- 1c5
+8/3 1d5
+8/3 1c5
+# bar 28
+i 0 30
+8 0c4,0 0e4,0 0b3 0d4 1d4
+8 0b3 0e4 1e4
+16	# ghost notes
+16 0b3 0e4
+32 0b3- 0e4- 1d4
+16. 1e4
+32 0b3- 0e4- 1d4
+16. 1f♯4
+32 0b3- 0e4- 1d4
+16. 1e4
+# bar 29
+8 0b3,0 0e4,0 0a♯3 0d4 1d4
+8 0a♯3 0e4 1e4
+16	# ghost notes
+16 0a♯3 0e4
+8 0a♯3 0e4 1e4
+8 0a♯3 0e4 1e4
+8 0a4 1e4
+
+# 4/4
+# bar 30
+1 0a♯3 0f4 0a♯4 1a♯4 1d5
+
+t 140
+# bar 31 - intro riff 4
--- /dev/null
+++ b/lib/u3.alive.mst
@@ -1,0 +1,38 @@
+t 114
+i 0 52
+i 1 74
+i 2 52
+i 3 69
+i 4 74
+4 0c5 1g5 2g4 3c6 4c5
+4 0g5 1c6 2c5 3g6 4g5
+4 0c6 1g6 2g5 3c7 4c6
+4 0c6,0 0d6 +
+4 0a♯5 1f6- 2f5- 3d7 4d6
+4 0d6 3a♯6 4a♯5
+4 0c6 1f6,0 1g6 2f5,0 2g5 3c7 4c6
+4 0c6,0 0g5 +
+4 0d♯5 1a♯6 2a♯5 3d♯7 4d♯6
+4 0d♯5,0 0a♯5 +
+4 0f5 1a6- 2a5- 3d7 4d6
+4 0c6 3c7 4c6
+4 0g5 1a6,0 1g6- 2a5,0 2g5- 3a♯6 4a♯5
+4 0d6 3c7 4c6
+4 0a5 1g6,0 1f♯6 2g5,0 2f♯5 3a6 4a5
+4 0a5,0 0d5 +
+4 0g5 1d6 2d5 3g6 4g5
+4 0d6 1g6 2g5 3d7 4d6
+4 0g6 1d7 2d6 3g7 4g6
+4 0g6,0 0a6 +
+4 0f6 1c7- 2c6- 3a7 4a6
+4 0a6 3f7 4f6
+4 0g6 1c7,0 1d7 2c6,0 2d6 3g7 4g6
+4 0g6,0 0d6 +
+4 0a♯5 1f7 2f6 3a♯7 4a♯6
+4 0a♯5,0 0f6 +
+4 0c6 1e7- 2e6- 3a7 4a6
+4 0g6 3g7 4g6
+4 0d6 1e7,0 1d7- 2e6,0 2d6- 3f7 4f6
+4 0a6 3g7 4g6
+4 0e6 1d7,0 1c♯7 2d6,0 2c♯6 3e7 4e6
+4 0e6,0 0a5 +
--- /dev/null
+++ b/lib/waltergreene.pinkpanther.mst
@@ -1,0 +1,82 @@
+t 124
+i 0 34
+i 1 19
+4/3 0e3 9d3 9a♯3
+4/3 +
+4/3 0e3 1a♯5 9d3 9a♯3
+4/3 0b3 1b5 9a♯3
+4/3 0a♯3 9a♯3
+4/3 0b3 1e6 9a♯3
+
+4/3 0e3 1g6 9d3 9a♯3
+4/3 +
+4/3 0e3 1b6- 9d3 9a♯3
+4/3 0b3 9a♯3
+4/3 0a♯3 9a♯3
+4/3 0b3 9a♯3
+
+4/3 0e3 1b6,0 1a6- 9d3 9a♯3
+4/3 +
+4/3 0e3 1a6,0 1g6 9d3 9a♯3
+4/3 0b3 1e6 9a♯3
+4/3 0a♯3 9a♯3
+4/3 0b3 1d6- 9a♯3
+
+4/3 0e3 9d3 9a♯3
+4/3 +
+4/3 0e3 9d3 9a♯3
+4/3 0b3 9a♯3
+4/3 0a♯3 1d6,0 9a♯3
+4/3 0b3 9a♯3
+
+4/3 0f♯3 1b5- 9d3 9a♯3
+4/3 +
+4/3 0f♯3 1b5,0 1a5- 9d3 9a♯3
+4/3 0b3 9a♯3
+4/3 0a♯3 9a♯3
+4/3 0b3 9a♯3
+
+4/3 0f♯3 9d3 9a♯3
+4/3 +
+4/3 0f♯3 9d3 9a♯3
+4/3 0b3 1a5,0 9a♯3
+4/3 0a♯3 9a♯3
+4/3 0b3 9a♯3
+
+4/3 0f♯3 1a5- 9d3 9a♯3
+4/3 +
+4/3 0f♯3 1a5,0 1f♯5- 9d3 9a♯3
+4/3 0b3 9a♯3
+4/3 0a♯3 1f♯5,0 1a5- 9a♯3
+4/3 0b3 9a♯3
+
+4/3 0f♯3 1a5,0 1f♯5 9d3 9a♯3
+4/3 +
+4/3 0f♯3 9d3 9a♯3
+4/3 0b3 1b5 9a♯3
+4/3 0a♯3 9a♯3
+4/3 0b3 1b5 9a♯3
+
+# wrong timing
+4/3 0f♯3 1c♯6 9d3 9a♯3
+4/3 1c♯6,0 +
+4/3 0f♯3 1c♯6 9d3 9a♯3
+# wrong timing
+4/3 0b3 1f♯6 9a♯3
+8/3 0a♯3- 1f6 9a♯3-
+8/3 1f♯6
+4/3 0a♯3,0 0b3 1f6 9a♯3
+
+4/3 0b3,0 0f♯3 1b5 9d3 9a♯3
+4/3 1b5,0 +
+4/3 0f♯3 1f♯6 9d3 9a♯3
+4/3 0b3 9a♯3
+4/3 0a♯3 9a♯3
+4/3 0b3 9a♯3
+
+#4/3 0e3 9d3 9a♯3
+#4/3 +
+#4/3 0e3 9d3 9a♯3
+#4/3 0b3 9a♯3
+#4/3 0a♯3 9a♯3
+#4/3 0b3 9a♯3
--- /dev/null
+++ b/mkfile
@@ -1,0 +1,9 @@
+</$objtype/mkfile
+TARG=\
+	mst\
+	tomst\
+
+OFILES=
+HFILES=
+</sys/src/cmd/mkmany
+BIN=$home/bin/$objtype
--- /dev/null
+++ b/mst.c
@@ -1,0 +1,314 @@
+#include <u.h>
+#include <libc.h>
+#include <ctype.h>
+#include <bio.h>
+
+enum{
+	Nmn = 60000000,
+	Nbuf = 64*1024,
+	Held = 1<<7
+};
+u32int lastdt;
+int div, ch[16*128], line;
+char *arg[64];
+uchar *outb, *oute, *outp, *last;
+uchar hdr[] = {
+	'M', 'T', 'h', 'd',
+	0x00, 0x00, 0x00, 0x06,
+	0x00, 0x00,
+	0x00, 0x01,
+	0x00, 0x00,
+	'M', 'T', 'r', 'k',
+	0x00, 0x00, 0x00, 0x00,
+	0x00
+};
+
+#define PUT16(p,v)	(p)[0]=(v)>>8;(p)[1]=(v)
+#define PUT24(p,v)	(p)[0]=(v)>>16;(p)[1]=(v)>>8;(p)[2]=(v)
+#define PUT32(p,v)	(p)[0]=(v)>>24;(p)[1]=(v)>>16;(p)[2]=(v)>>8;(p)[3]=(v)
+
+int
+out(void *u, int n)
+{
+	uintptr off;
+	ulong len;
+
+	if(outp + n >= oute){
+		off = outp - outb;
+		len = oute - outb + Nbuf;
+		outb = realloc(outb, len);
+		if(outb == nil)
+			sysfatal("realloc: %r");
+		outp = outb + off;
+		oute = outb + len;
+	}
+	memmove(outp, u, n);
+	outp += n;
+	last = outp;
+	lastdt = 0;
+	return n;
+}
+
+void
+barf(void)
+{
+	uchar eot[] = {0xff, 0x2f, 0x00};
+
+	out(eot, sizeof eot);
+	PUT16(outb+4+4+2+2, div);
+	PUT32(outb+4+4+2+2+2+4, outp - outb - sizeof hdr + 1);
+	write(1, outb, outp - outb);
+}
+
+void
+setnt(int c, int n, int v)
+{
+	uchar u[4];
+
+	u[0] = c | 0x80 | (v != 0) << 4;
+	u[1] = n;
+	u[2] = v;
+	u[3] = 0;
+	out(u, sizeof u);
+	last--;
+}
+
+void
+setdt(u32int dt)
+{
+	int n;
+	u32int v;
+	uchar u[4], *up;
+
+	memset(u, 0, sizeof u);
+	if(last == nil)
+		last = outp;
+	if(lastdt + dt < dt)
+		setnt(0, 0, ch[0] & 0x7f);
+	v = lastdt += dt;
+	up = u + sizeof(u) - 1;
+	*up-- = lastdt & 0x7f;
+	while(n = lastdt >>= 7 & 0x7f)
+		*up-- = 1<<7 | n;
+	outp = last;
+	last -= out(up+1, sizeof(u) + u - up - 1);
+	lastdt = v;
+}
+
+void
+setdiv(int n, char **arg)
+{
+	char *p;
+
+	if(line != 1)
+		sysfatal("line %d: div setting not on first line", line);
+	if(n != 1)
+		sysfatal("line %d: invalid argument", line);
+	/* FIXME: maybe wrap strtol in a function or something (va?) */
+	div = strtol(*arg, &p, 0);
+	if(div <= 0)
+		sysfatal("line %d: invalid div value %d", line, div);
+}
+
+void
+setbpm(int n, char **arg)
+{
+	static uchar u[] = {0xff, 0x51, 0x03, 0x00, 0x00, 0x00, 0x00};
+	int T;
+	char *p;
+
+	if(n != 1)
+		sysfatal("line %d: invalid argument", line);
+	n = strtol(*arg, &p, 0);
+	T = 0;
+	if(n <= 0 || (T = Nmn / n) <= 0)
+		sysfatal("line %d: invalid bpm value %d", line, n);
+	PUT24(u+3, T);
+	out(u, sizeof u);
+	last--;
+}
+
+void
+setinst(int n, char **arg)
+{
+	static uchar u[] = {0xc0, 0x00, 0x00};
+	char *p;
+
+	if(n != 2)
+		sysfatal("line %d; invalid argument", line);
+	n = strtol(*arg, &p, 0);
+	if(p == *arg || n & ~15 || n == 9)
+		sysfatal("line %d: invalid channel number %d", line, n);
+	u[0] |= n;
+	n = strtol(*++arg, &p, 0);
+	if(p == *arg || n & ~127)
+		sysfatal("line %d: invalid instrument number %d", line, n);
+	u[1] = n;
+	out(u, sizeof u);
+	last--;
+}
+
+void
+parsent(char *s)
+{
+	static tt[] = {9, 11, 0, 2, 4, 5, 7};
+	int c, n, o, v, *np;
+	char *p;
+	Rune r;
+
+	c = strtol(s, &p, 10);
+	if(p == s || c < 0 || c > 15)
+		sysfatal("line %d: invalid channel number %d", line, c);
+	n = tolower(*p++);
+	if(n < 'a' || n > 'g')
+		sysfatal("line %d: invalid note name", line);
+	n = tt[n - 'a'];
+	s = p + chartorune(&r, p);
+	if(r == 'b' || r == L'♭'){
+		n--;
+		p = s;
+	}else if(r == '#' || r == L'♯'){
+		n++;
+		p = s;
+	}
+	s = p;
+	o = strtol(s, &p, 10);
+	if(p == s || o < 0 || o > 10)
+		sysfatal("line %d: invalid octave number", line);
+	n += 12 * o;
+	if(n < 0 || n > 127)
+		sysfatal("line %d: invalid note number", line);
+	np = ch + c * 128 + n;
+	v = 64;
+	if(*p == ','){
+		s = p + 1;
+		v = strtol(s, &p, 10);
+		if(p == s || v < 0 || v > 127)
+			sysfatal("line %d: invalid velocity", line);
+	}
+	if(*np & 0x7f)
+		setnt(c, n, 0);
+	*np = v;
+	if(*p == '-')
+		*np |= Held;
+	setnt(c, n, v);
+}
+
+u32int
+parsedt(char *s)
+{
+	ulong d;
+	u32int dt;
+	char *p;
+
+	if(*s == 'c'){
+		d = strtoul(s+1, &p, 10);
+		if(p == s+1)
+			sysfatal("line %d: invalid note duration", line);
+		return d;
+	}
+	d = strtoul(s, &p, 10);
+	if(d == 0 || d & ~0xff || d != 1 && d & d - 1)
+		sysfatal("line %d: invalid note duration", line);
+	dt = div * 4;
+	while(d >>= 1)
+		dt >>= 1;
+	if(*p == '/'){
+		d = *++p - '0';
+		if(d != 3 && d != 5)
+			sysfatal("line %d: unsupported tuplet %c", line, (char)d);
+		dt /= d;
+	}
+	if(*p == '.')
+		dt = dt * 3 >> 1;
+	return dt;
+}
+
+void
+parse(int n)
+{
+	static int gdt;
+	int *np, hold, grace;
+	u32int dt;
+	char c, **sp, **se;
+
+	line++;
+	if(n < 1)
+		return;
+	hold = grace = 0;
+	for(sp=arg, se=arg+n; sp<se; sp++){
+		c = **sp;
+		if(c == '#' || c == '+'){
+			n = sp - arg;
+			hold = c == '+';
+			break;
+		}
+	}
+	if(n < 1)
+		return;
+	sp = arg;
+	switch(**arg){
+	case 'd': setdiv(n-1, arg+1); return;
+	/* FIXME: grace note: g [dt] [note]...; take dt from next note's
+	 * duration, is additive (multiple grace notes) and no updates occur
+	 * when one is struck */
+	/* FIXME: more intelligent argument parsing, wrt parsedt and parsent
+	 * and arguments list */
+	case 'g': grace = 1; sp++; n--; sysfatal("unimplemented"); break;
+	case 'i': setinst(n-1, arg+1); return;
+	case 't': setbpm(n-1, arg+1); return;
+	}
+	dt = parsedt(*(sp++));
+	if(!hold && !grace)
+		for(np=ch; np<ch+nelem(ch); np++)
+			if(*np & 0x7f && (n == 1 || (*np & Held) == 0))
+				setnt((np - ch) / 128, (np - ch) % 128, 0);
+	while(--n > 0){
+		if(*sp[0] == '#' || *sp[0] == '+')
+			break;
+		parsent(*(sp++));
+	}
+	setdt(dt);
+}
+
+void
+usage(void)
+{
+	fprint(2, "usage: %s [-d div] [file]\n", argv0);
+	exits("usage");
+}
+
+void
+main(int argc, char **argv)
+{
+	int n;
+	char *s;
+	Biobuf *bf;
+
+	div = 96*5;
+	ARGBEGIN{
+	default: usage();
+	case 'd': div = strtol(EARGF(usage()), nil, 0); break;
+	}ARGEND
+	bf = *argv != nil ? Bopen(*argv, OREAD) : Bfdopen(0, OREAD);
+	if(bf == nil)
+		sysfatal("init: %r");
+	outb = mallocz(Nbuf, 1);
+	if(outb == nil)
+		sysfatal("mallocz: %r");
+	oute = outb + Nbuf;
+	memcpy(outb, hdr, sizeof hdr);
+	outp = outb + sizeof hdr;
+	setbpm(1, (arg[0] = "120", arg));	/* FIXME: >:( */
+	for(;;){
+		s = Brdstr(bf, '\n', 1);
+		if(s == nil)
+			break;
+		n = getfields(s, arg, nelem(arg), 1, " \t");
+		parse(n);
+		free(s);
+	}
+	Bterm(bf);
+	barf();
+	exits(nil);
+}
--- /dev/null
+++ b/tomst.c
@@ -1,0 +1,239 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+
+struct Tracker {
+	uchar *data;
+	char ended;
+	uvlong t;
+	int cmd;
+} *tr;
+
+typedef struct Tracker Tracker;
+
+int fd, tempo = 500000, ntrack;
+Biobuf *bf;
+uvlong T;
+
+char *ntab[] = {
+	"c0","c♯0","d0","d♯0","e0","f0","f♯0","g0","g♯0","a0","a♯0","b0",
+	"c1","c♯1","d1","d♯1","e1","f1","f♯1","g1","g♯1","a1","a♯1","b1",
+	"c2","c♯2","d2","d♯2","e2","f2","f♯2","g2","g♯2","a2","a♯2","b2",
+	"c3","c♯3","d3","d♯3","e3","f3","f♯3","g3","g♯3","a3","a♯3","b3",
+	"c4","c♯4","d4","d♯4","e4","f4","f♯4","g4","g♯4","a4","a♯4","b4",
+	"c5","c♯5","d5","d♯5","e5","f5","f♯5","g5","g♯5","a5","a♯5","b5",
+	"c6","c♯6","d6","d♯6","e6","f6","f♯6","g6","g♯6","a6","a♯6","b6",
+	"c7","c♯7","d7","d♯7","e7","f7","f♯7","g7","g♯7","a7","a♯7","b7",
+	"c8","c♯8","d8","d♯8","e8","f8","f♯8","g8","g♯8","a8","a♯8","b8",
+	"c9","c♯9","d9","d♯9","e9","f9","f♯9","g9","g♯9","a9","a♯9","b9",
+	"c10","c♯10","d10","d♯10","e10","f10","f♯10","g10"
+};
+char nts[512], *ntp = nts;
+
+void *
+emallocz(int size)
+{
+	void *v;
+	
+	v = malloc(size);
+	if(v == nil)
+		sysfatal("malloc: %r");
+	memset(v, 0, size);
+	return v;
+}
+
+int
+get8(Tracker *src)
+{
+	uchar c;
+
+	if(src == nil){
+		if(read(fd, &c, 1) == 0)
+			sysfatal("unexpected eof");
+		return c;
+	}
+	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);
+}
+
+void
+paste(int dt)
+{
+	/* FIXME: attempt to use note duration instead of clocks */
+	Bprint(bf, "c%d %s\n", dt, ntp!=nts?nts:"+");
+}
+
+void
+readevent(Tracker *src, int dt)
+{
+	int n, v, t;
+
+	if(dt != 0){
+		paste(dt);
+		ntp = nts;
+	}
+	src->t += getvar(src);
+	t = get8(src);
+	if((t & 0x80) == 0){
+		src->data--;
+		t = src->cmd;
+		if((t & 0x80) == 0)
+			sysfatal("invalid midi");
+	}else
+		src->cmd = t;
+	n = t >> 4;
+	switch(n){
+	case 0x8:
+		n = get8(src);
+		get8(src);
+	off:
+		ntp = seprint(ntp, nts + sizeof nts, " %d%s,0", t & 15, ntab[n]);
+		break;
+	case 0x9:
+		n = get8(src);
+		v = get8(src);
+		if(v == 0)
+			goto off;
+		ntp = seprint(ntp, nts + sizeof nts, " %d%s", t & 15, ntab[n]);
+		if(v != 64)
+			ntp = seprint(ntp, nts + sizeof nts, ",%d", v);
+		ntp = strecpy(ntp, nts + sizeof nts, "-");
+		break;
+	case 0xB:
+		get16(src);
+		break;
+	case 0xC:
+		get8(src);
+		break;
+	case 0xE:
+		get16(src);
+		break;
+	case 0xF:
+		if((t & 0xF) == 0){
+			while(get8(src) != 0xF7)
+				;
+			return;
+		}
+		v = get8(src);
+		n = get8(src);
+		switch(v){
+		case 0x2F:
+			src->ended = 1;
+			break;
+		case 0x51:
+			v = get16(src) << 8;
+			v |= get8(src);
+			if(v != tempo){
+				Bprint(bf, "t %d\n", 60000000/v);
+				tempo = v;
+			}
+			break;
+		default:
+			skip(src, n);
+		}
+		break;
+	default:
+		sysfatal("unknown event type %x", t>>4);
+	}
+}
+
+void
+main(int argc, char **argv)
+{
+	int i, size;
+	uvlong T, t, mint, dt;
+	Tracker *x, *minx;
+
+	if(argc > 1)
+		fd = open(argv[1], OREAD);
+	if(fd < 0)
+		sysfatal("open: %r");
+	bf = Bfdopen(1, OWRITE);
+	if(bf == nil)
+		sysfatal("Bfdopen: %r");
+	if(get32(nil) != 0x4D546864 || get32(nil) != 6)
+		sysfatal("invalid file header");
+	i = get16(nil);
+	if(i != 0 && i != 1)
+		sysfatal("unsupported midi format %d\n", i);
+	ntrack = get16(nil);
+	Bprint(bf, "div %d\n", get16(nil));
+	tr = emallocz(ntrack * sizeof(*tr));
+	for(i = 0; i < ntrack; i++){
+		if(get32(nil) != 0x4D54726B)
+			sysfatal("invalid track header");
+		size = get32(nil);
+		tr[i].data = emallocz(size);
+		readn(fd, tr[i].data, size);
+	}
+	T = 0;
+	for(;;){
+		minx = nil;
+		mint = 0;
+		for(x = tr; x < tr + ntrack; x++){
+			if(x->ended)
+				continue;
+			t = peekvar(x) + x->t;
+			if(t < mint || minx == nil){
+				mint = t;
+				minx = x;
+			}
+		}
+		if(minx == nil)
+			exits(nil);
+		dt = mint - T;
+		readevent(minx, dt);
+		T += dt;
+	}
+}