plan9front/sys/src/games/midi.c

266 lines
3.7 KiB
C

#include <u.h>
#include <libc.h>
enum { SAMPLE = 44100 };
struct Tracker {
uchar *data;
char ended;
uvlong t;
uchar notes[16][128];
int cmd;
} *tr;
typedef struct Tracker Tracker;
int fd, ofd, div, tempo = 500000, ntrack;
uvlong T;
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;
}
int
get8(Tracker *src)
{
uchar c;
if(src == nil){
if(read(fd, &c, 1) == 0)
sysfatal("unexpected eof");
return c;
}
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);
}
uvlong
tconv(int n)
{
uvlong v;
v = n;
v *= tempo;
v *= SAMPLE;
v /= div;
v /= 1000000;
return v;
}
void
run(uvlong n)
{
int samp, j, k, no[128];
int t, f;
short u;
Tracker *x;
samp = n - T;
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 = (T % 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;
}
T++;
}
}
void
readevent(Tracker *src)
{
uvlong l;
int n,t;
l = tconv(getvar(src));
run(src->t += l);
t = get8(src);
if((t & 0x80) == 0){
src->data--;
t = src->cmd;
if((t & 0x80) == 0)
sysfatal("invalid midi");
}else
src->cmd = t;
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 0xB:
get16(src);
break;
case 0xC:
get8(src);
break;
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:
fprint(2, "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);
}
}
void
main(int argc, char **argv)
{
int i, size;
uvlong t, mint;
Tracker *x, *minx;
ARGBEGIN{
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(i = 0; i < ntrack; i++){
if(get32(nil) != 0x4D54726B)
sysfatal("invalid track header");
size = get32(nil);
tr[i].data = emallocz(size);
readn(fd, tr[i].data, size);
}
for(i = 0; i < 128; i++)
freq[i] = SAMPLE / (440 * pow(1.05946, i - 69));
for(;;){
minx = nil;
mint = 0;
for(x = tr; x < tr + ntrack; x++){
if(x->ended)
continue;
t = tconv(peekvar(x)) + x->t;
if(t < mint || minx == nil){
mint = t;
minx = x;
}
}
if(minx == nil){
write(ofd, out, outp - out);
exits(nil);
}
readevent(minx);
}
}