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\