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);
+}