shithub: misc

ref: 765c104f7a15d8a2d82288e581a9872310b1045c
dir: /mid2s.c/

View raw version
#include <u.h>
#include <libc.h>
#include <bio.h>

enum{
	Rate = 44100,
	Nchan = 16,
	Maxch = 16,	// FIXME: could have more
	Percch = 9,
};

typedef struct Trk Trk;
struct Trk{
	u8int *s;
	u8int *p;
	u8int *q;
	u8int *e;
	double Δ;
	double t;
	int ev;
	int ended;
};
Trk *tr;
int m2ich[Maxch], i2mch[Maxch], age[Maxch];
int nch = Nchan, percch = Percch;

int trace;
int mfmt, ntrk, div = 1, tempo;
Biobuf *ib;

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");
}

void
dprint(char *fmt, ...)
{
	char s[256];
	va_list arg;

	if(!trace)
		return;
	va_start(arg, fmt);
	vseprint(s, s+sizeof s, fmt, arg);
	va_end(arg);
	fprint(2, "%s", s);
}

u8int
get8(Trk *x)
{
	u8int v;

	if(x == nil)
		Bread(ib, &v, 1);
	else{
		if(x->p >= x->e || x->ended)
			sysfatal("track overflow");
		v = *x->p++;
	}
	dprint("%02ux", v);
	return v;
}

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);
}

double
tc(double n)
{
	return (n * tempo / div);
}

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;
}

// FIXME: common midi.c shit (midilib.c? midifile?)
void
samp(double n)
{
	double Δt;
	long s;
	static double t0, t1;

	if(t0 == 0.0)
		t0 = nsec();
	t1 = t0 + n * 1000 * tempo / div;
	t0 = t1;
	Δt = t1 - nsec();
	s = floor(Δt / 1000000);
	if(s > 0)
		sleep(s);
}

int
mapinst(Trk *, int c, int e)
{
	int i, m, a;

	i = m2ich[c];
	if(c == 9)
		i = percch;
	else if(e >> 4 != 0x9){
		if(e >> 4 == 0x8 && i >= 0){
			i2mch[i] = -1;
			m2ich[c] = -1;
		}
		return e;
	}else if(i < 0){
		for(i=0; i<nch; i++){
			if(i == percch)
				continue;
			if(i2mch[i] < 0)
				break;
		}
		if(i == nch){
			for(m=i=a=0; i<nch; i++){
				if(i == percch)
					continue;
				if(age[i] > age[m]){
					m = i;
					a = age[i];
				}
			}
			if(a < 100){
				fprint(2, "could not remap %d\n", c);
				return e;
			}
			i = m;
			fprint(2, "remapped %d → %d\n", c, i);
		}
	}
	age[i] = 0;
	m2ich[c] = i;
	i2mch[i] = c;
	return e & ~(Nchan-1) | i;
}

int
ev(Trk *x, vlong)
{
	int e, n, m;

	x->q = x->p - 1;
	dprint(" [%zd] ", x - tr);
	e = get8(x);
	if((e & 0x80) == 0){
		x->p--;
		e = x->ev;
		x->q--;
		x->q[1] = e;
		if((e & 0x80) == 0)
			sysfatal("invalid event");
	}else
		x->ev = e;
	dprint("(%02ux) ", e);

	e = mapinst(x, e & 15, e);

	n = get8(x);
	if((e & 15) == percch){
		if(n < 36)
			n += 36 - n;
	}
	switch(e >> 4){
	case 0x8: get8(x); break;
	case 0x9: get8(x); break;
	case 0xb: get8(x); break;
	case 0xc: break;
	case 0xe: get8(x); break;
	case 0xf:
		if((e & 0xf) == 0){
			while(get8(x) != 0xf7)
				;
			break;
		}
		m = get8(x);
		switch(n){
		case 0x2f: dprint(" -- so long!\n"); return -1;
		case 0x51: tempo = get16(x) << 8; tempo |= get8(x); break;
		default: skip(x, m);
		}
		break;
	case 0xa: get8(x); break;
	case 0xd: get8(x); break;
	default: sysfatal("invalid event %#ux\n", e >> 4);
	}
	dprint("\n");
	if(x->p > x->q){
		x->q[1] = e;
		x->q[0] = x->q[1] >> 4 | (x->q[1] & 0xff) << 4;
		write(1, x->q, x->p - x->q);
		x->q = x->p;
	}
	return 0;
}

void
readmid(char *file)
{
	u32int n, z;
	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, z=-1UL; 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->q = x->p;
		x->e = s + n;
		x->Δ = getvar(x);	/* prearm */
		if(x->Δ < z)
			z = x->Δ;
	}
	for(x=tr; x<tr+ntrk; x++)
		x->Δ -= z;
	Bterm(ib);
}

void
usage(void)
{
	fprint(2, "usage: %s [-D] [-c nch] [-p percch] [mid]\n", argv0);
	exits("usage");
}

void
main(int argc, char **argv)
{
	int i, c, end, debug;
	Trk *x;

	debug = 0;
	ARGBEGIN{
	case 'D': debug = 1; break;
	case 'c':
		nch = atoi(EARGF(usage()));
		break;
	case 'p':
		percch = atoi(EARGF(usage()));
		break;
	default: usage();
	}ARGEND
	if(nch <= 0 || nch > Maxch)
		usage();
	if(percch <= 0 || percch > nch)
		usage();
	readmid(*argv);
	tempo = 500000;
	trace = debug;
	for(i=0; i<nelem(m2ich); i++){
		m2ich[i] = i2mch[i] = -1;
		age[i] = -1UL;
	}
	for(;;){
		end = 1;
		for(x=tr; x<tr+ntrk; x++){
			if(x->ended)
				continue;
			end = 0;
			x->Δ--;
			while(x->Δ <= 0){
				if(x->ended = ev(x, 0)){
					c = x - tr;
					i = m2ich[c];
					if(i >= 0){
						i2mch[i] = -1;
						m2ich[c] = -1;
					}
					break;
				}
				x->Δ = getvar(x);
			}
		}
		if(end){
			write(1, tr[0].q, tr[0].p - tr[0].q);
			break;
		}
		samp(1);
		for(i=0; i<nch; i++){
			if(i2mch[i] < 0)
				continue;
			age[i]++;
			if(age[i] > 10000){
				fprint(2, "reset %d\n", i2mch[i]);
				m2ich[i2mch[i]] = -1;
				i2mch[i] = -1;
			}
		}
	}
	exits(nil);
}