add games/dmid and games/opl3

This commit is contained in:
qwx 2018-07-12 09:33:33 +02:00
parent 1e3790f7b5
commit 96e511d736
8 changed files with 2053 additions and 0 deletions

10
rc/bin/dmus Executable file
View file

@ -0,0 +1,10 @@
#!/bin/rc
if(test -f /mnt/wad/genmidi)
c=(games/dmid '|' games/opl3)
if not
c=(games/midi -c)
if(~ `{file -m $1} audio/mus)
c=(games/mus '<' $1 '|' $c)
if not
c=('<' $1 $c)
eval $c

68
sys/man/1/dmid Normal file
View file

@ -0,0 +1,68 @@
.TH DMID 1
.SH NAME
dmid \- MIDI to OPL3 converter using GENMIDI-type instrument banks
.SH SYNOPSIS
.B dmid
[
.B -2
] [
.B -i
.I bank
] [
.I file
]
.SH DESCRIPTION
.I Dmid
decodes MIDI instructions either from
.I file
or from standard input,
and produces
.SM OPL3
instructions suitable for playback by
.IR opl3 (1).
To program instruments, an OPL2 instrument bank formatted as
.SM GENMIDI
lumps from
.I doom
must be provided.
Since it is assumed that the bank is contained in a
.I doom WAD
file, its default location is
.BR /mnt/wad/genmidi .
This may be overridden with the
.B -i
command line option.
.PP
In
.SM GENMIDI
lumps, two voices are defined per instrument.
For compatibility, the
.B -2
flag disables the second voice, reducing the number of
.SM OPL
channels needed.
It also disables
.SM OPL3
specific features and produces an IMF-format stream,
which can be used in other game engines.
.SH EXAMPLES
Play a MUS file from a
.I doom WAD
file:
.IP
.EX
% games/wadfs /sys/games/lib/doom/doom2.wad
createfile SW18_7: file already exists
% games/mus /mnt/wad/d_doom | games/dmid | games/opl3 >/dev/audio
.EE
.SH SOURCE
.B /sys/src/games/dmid.c
.SH "SEE ALSO"
.IR games (1) ,
.IR mus (1) ,
.IR opl3 (1) ,
.IR audio (3) ,
.IR wadfs (4)
.SH HISTORY
.I Dmid
first appeared in 9front (July, 2018).

59
sys/man/1/opl3 Normal file
View file

@ -0,0 +1,59 @@
.TH OPL3 1
.SH NAME
opl3 \- OPL3 chip emulator
.SH SYNOPSIS
.B opl3
[
.B -n
.I rate
] [
.I file
]
.SH DESCRIPTION
.I Opl3
is an emulator of a single Yamaha 262 chip, also known as
.SM OPL3.
.PP
The emulated chip is programmed by a stream of commands either from
.I file
or from standard in.
It then synthesizes a number of stereo 16 bit little-endian samples for a sampling rate of 44.1 kHz,
and writes them to standard out.
.PP
Commands are 5 bytes wide, in little-endian byte order:
.PP
.RS
.IR register [2]
.IR value [1]
.IR delay [2]
.RE
.PP
Each command specifies a
.I value
to be written to an
.SM OPL3
chip
.IR register ,
modifying its internal state.
.PP
The
.I delay
field provides timing.
It is a multiple of a command period, during which the
.SM OPL3
chip may be sampled before processing the next command.
The period itself is the inverse of the command rate, 44100 Hz by default.
This rate can be set using the
.B -n
parameter.
.SH SOURCE
.B /sys/src/games/opl3
.SH "SEE ALSO"
.IR audio (3)
.SH HISTORY
.I Opl3
first appeared in 9front (July, 2018), based on
.I ymf262.c
from the Multiple Arcade Machine Emulator (
.SM MAME
).

554
sys/src/games/dmid.c Normal file
View file

@ -0,0 +1,554 @@
#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 = 44100,
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;
uchar 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, tempo, opl2;
uvlong T;
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;
n = o->n + o->c->bend / 0x1000 & 0x7f;
f = freq[n] + (o->c->bend % 0x1000) * (freq[n+1] - freq[n]) / 0x1000;
f = (f * (1 << 20)) / 49716;
//if(o->i == o->c->i->i2)
// f += o->c->i->fine; /* nope */
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
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) / 2 - 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(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 [-2] [-i inst] [mid]\n", argv0);
exits("usage");
}
void
main(int argc, char **argv)
{
int n, t, mint;
char *i;
double f;
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;
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);
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);
}
}

View file

@ -16,6 +16,7 @@ TARG=4s\
mandel\
midi\
wadfs\
dmid\
OFILES=
HFILES=
@ -38,6 +39,7 @@ DIRS=\
music\
md\
nes\
opl3\
snes\
sokoban\
sudoku\

View file

@ -0,0 +1,8 @@
</$objtype/mkfile
BIN=/$objtype/bin/games
TARG=opl3
OFILES=opl3.$O opl3m.$O
HFILES=
</sys/src/cmd/mkone

1295
sys/src/games/opl3/opl3.c Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,57 @@
#include <u.h>
#include <libc.h>
#include <bio.h>
void opl3out(uchar *, int);
void opl3wr(int, int);
void opl3init(int);
enum{
Rate = 44100,
};
void
usage(void)
{
fprint(2, "usage: %s [-n nsamp] [file]\n", argv0);
exits("usage");
}
void
main(int argc, char **argv)
{
int r, v, dt, nsamp, fd;
uchar *sb, u[5];
Biobuf *bi, *bo;
fd = 0;
nsamp = 1;
ARGBEGIN{
case 'n':
nsamp = Rate / atoi(EARGF(usage()));
break;
default:
usage();
}ARGEND;
if(*argv != nil)
if((fd = open(*argv, OREAD)) < 0)
sysfatal("open: %r");
bi = Bfdopen(fd, OREAD);
bo = Bfdopen(1, OWRITE);
if(bi == nil || bo == nil)
sysfatal("Bfdopen: %r");
nsamp *= 4;
if((sb = malloc(nsamp)) == nil)
sysfatal("malloc: %r");
opl3init(Rate);
while(Bread(bi, u, sizeof u) > 0){
r = u[1] << 8 | u[0];
v = u[2];
dt = u[4] << 8 | u[3];
opl3wr(r, v);
while(dt-- > 0){
opl3out(sb, nsamp);
Bwrite(bo, sb, nsamp);
}
}
}