shithub: riscv

ref: b83500663584cec5ddef38fec84115acc6b58955
dir: /sys/src/games/midi.c/

View raw version
#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);
	}
}