shithub: mst

Download patch

ref: dc0ce0e59e99f097a44a8d4627a4c737214ee55a
parent: 74a87e0582e0549e6e21568b8c68ebbdecdec48a
author: qwx <qwx@sciops.net>
date: Sat Dec 5 10:30:56 EST 2020

rename mst → vmst

--- a/mkfile
+++ b/mkfile
@@ -1,7 +1,7 @@
 </$objtype/mkfile
 TARG=\
-	mst\
-	tomst\
+	hmst\
+	tohmst\
 
 OFILES=
 HFILES=
--- a/mst.c
+++ /dev/null
@@ -1,315 +1,0 @@
-#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 < -1 || o > 9)
-		sysfatal("line %d: invalid octave number", line);
-	o++;
-	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);
-}
--- a/tomst.c
+++ /dev/null
@@ -1,239 +1,0 @@
-#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[] = {
-	"c-1","c♯-1","d-1","d♯-1","e-1","f-1","f♯-1","g-1","g♯-1","a-1","a♯-1","b-1",
-	"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"
-};
-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;
-	}
-}
--- /dev/null
+++ b/tovmst.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[] = {
+	"c-1","c♯-1","d-1","d♯-1","e-1","f-1","f♯-1","g-1","g♯-1","a-1","a♯-1","b-1",
+	"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"
+};
+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;
+	}
+}
--- /dev/null
+++ b/vmst.c
@@ -1,0 +1,315 @@
+#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 < -1 || o > 9)
+		sysfatal("line %d: invalid octave number", line);
+	o++;
+	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);
+}