plan9front/sys/src/games/dmid.c

599 lines
9.4 KiB
C
Raw Permalink Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#include <u.h>
#include <libc.h>
#include <bio.h>
#include <thread.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;
uvlong t;
int ev;
};
Trk *tr;
double freq[128];
int mfmt, ntrk, div = 1, tempo, opl2, stream;
uvlong T;
Channel *echan;
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;
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);
}
}
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;
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; 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);
exits("usage");
}
void
threadmain(int argc, char **argv)
{
int n, t, mint;
char *i;
double f;
uchar u[4];
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;
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);
}
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);
}
}