plan9front/sys/src/games/dmid.c

599 lines
9.4 KiB
C
Raw Permalink Normal View History

2018-07-12 00:33:33 -07:00
#include <u.h>
#include <libc.h>
#include <bio.h>
#include <thread.h>
2018-07-12 00:33:33 -07:00
typedef struct Inst Inst;
typedef struct Opl Opl;
typedef struct Chan Chan;
typedef struct Trk Trk;
enum{
Rate = 49716, /* opl3 sampling rate */
2018-07-12 00:33:33 -07:00
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;
2018-07-12 00:33:33 -07:00
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;
uvlong t;
int ev;
};
Trk *tr;
double freq[128];
int mfmt, ntrk, div = 1, tempo, opl2, stream;
2018-07-12 00:33:33 -07:00
uvlong T;
Channel *echan;
2018-07-12 00:33:33 -07:00
Biobuf *ib, *ob;
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");
}
u8int
get8(Trk *x)
{
u8int v;
if(x == nil){
Bread(ib, &v, 1);
return v;
}
if(x->p >= x->e)
sysfatal("track overflow");
return *x->p++;
}
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;
Bwrite(ob, 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;
2018-07-12 00:33:33 -07:00
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);
}
}
uvlong
tc(int n)
{
return ((uvlong)n * tempo * Rate / div) / 1000000;
}
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;
}
int
peekvar(Trk *x)
{
int v;
uchar *p;
p = x->p;
v = getvar(x);
x->p = p;
return v;
}
void
samp(uvlong t´)
{
int dt;
static uvlong t;
dt = t´ - t;
t += dt;
while(dt > 0){
putcmd(0, 0, dt > 0xffff ? 0xffff : dt);
dt -= 0xffff;
}
}
void
ev(Trk *x)
{
int e, n, m;
Chan *c;
samp(x->t += tc(getvar(x)));
e = get8(x);
if((e & 0x80) == 0){
x->p--;
e = x->ev;
if((e & 0x80) == 0)
sysfatal("invalid event");
}else
x->ev = e;
c = chan + (e & 15);
n = get8(x);
switch(e >> 4){
case 0x8: noteoff(c, n, get8(x)); break;
case 0x9: noteon(c, n, get8(x), x->t); break;
case 0xb:
m = get8(x);
switch(n){
case 0x00: case 0x01: case 0x20: 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: fprint(2, "unknown controller %d\n", n);
}
break;
case 0xc: 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)
;
return;
}
m = get8(x);
switch(n){
case 0x2f: x->p = x->e; return;
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);
}
}
void
tproc(void *)
{
vlong t, Δt;
uchar u[4];
Trk x;
x.e = u + sizeof u;
t = nsec();
for(;;){
if(nbrecv(echan, u) > 0){
u[0] = 0;
x.p = u;
ev(&x);
}
putcmd(0, 0, 1);
t += 10000000 / (Rate / 100);
Δt = (t - nsec()) / 1000000;
if(Δt > 0)
sleep(Δt);
}
}
2018-07-12 00:33:33 -07:00
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;
2018-07-12 00:33:33 -07:00
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;
uchar *s;
Trk *x;
ib = file != nil ? bopen(file, OREAD) : bfdopen(0, OREAD);
if(stream)
return;
2018-07-12 00:33:33 -07:00
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; 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;
}
Bterm(ib);
}
void
usage(void)
{
fprint(2, "usage: %s [-2s] [-i inst] [mid]\n", argv0);
2018-07-12 00:33:33 -07:00
exits("usage");
}
void
threadmain(int argc, char **argv)
2018-07-12 00:33:33 -07:00
{
int n, t, mint;
char *i;
double f;
uchar u[4];
2018-07-12 00:33:33 -07:00
Chan *c;
Opl *o;
Trk *x, *minx;
i = "/mnt/wad/genmidi";
ARGBEGIN{
case '2': opl2 = 1; ople = opl + 9; break;
case 'i': i = EARGF(usage()); break;
case 's': stream = 1; break;
2018-07-12 00:33:33 -07:00
default: usage();
}ARGEND
readinst(i);
readmid(*argv);
ob = bfdopen(1, OWRITE);
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);
if(stream){
if(proccreate(tproc, nil, mainstacksize) < 0)
sysfatal("proccreate: %r");
if((echan = chancreate(sizeof u, 0)) == nil)
sysfatal("chancreate: %r");
for(;;){
if((n = Bread(ib, u, sizeof u)) != sizeof u)
break;
send(echan, u);
}
threadexitsall(n < 0 ? "read: %r" : nil);
}
2018-07-12 00:33:33 -07:00
for(;;){
minx = nil;
mint = 0;
for(x=tr; x<tr+ntrk; x++){
if(x->p >= x->e)
continue;
t = x->t + tc(peekvar(x));
if(t < mint || minx == nil){
mint = t;
minx = x;
}
}
if(minx == nil)
exits(nil);
ev(minx);
}
}