ref: 74a87e0582e0549e6e21568b8c68ebbdecdec48a
dir: /mst.c/
#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); }