shithub: misc

Download patch

ref: f2d78c632d9384ef45c7da914321f9e30f79ff67
parent: 0fbc9c2d198923bf95eaa5daa32a7ecf61dcae6e
author: qwx <qwx@sciops.net>
date: Wed Jul 5 18:01:49 EDT 2023

mid2s; pull common midi stuff into a lib file, mid2s final fixes

-m is used with when writing to some synths, not sure why this
is necessary, might have missed it in the spec;  dmid on the
other hand doesn't like that

--- a/mid2s.c
+++ b/mid2s.c
@@ -1,315 +1,58 @@
 #include <u.h>
 #include <libc.h>
 #include <bio.h>
+#include "midifile.h"
 
-enum{
-	Rate = 44100,
-	Nchan = 16,
-	Maxch = 16,	// FIXME: could have more
-	Percch = 9,
-};
+static int magic;
 
-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)
+samp(uvlong 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;
+	static double t0;
 
 	if(t0 == 0.0)
 		t0 = nsec();
-	t1 = t0 + n * 1000 * tempo / div;
-	t0 = t1;
-	Δt = t1 - nsec();
+	t0 += n * 1000 * tempo / div;
+	Δt = t0 - nsec();
 	s = floor(Δt / 1000000);
 	if(s > 0)
 		sleep(s);
 }
 
-int
-mapinst(Trk *, int c, int e)
+/* set delay to 0, and translate running status: the receiver
+ * only sees one track whereas running status is a property of
+ * each track (stream); don't send EOT for the same reason */
+static void
+eat(Trk *x)
 {
-	int i, m, a;
+	int e, n;
+	uchar u[16], *p, *q;
+	Msg msg;
 
-	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);
-		}
+	q = u + 1;
+	e = nextevent(x);
+	*q++ = e;
+	p = x->p;
+	translate(x, e, &msg);
+	if(msg.type == Ceot)
+		return;
+	u[0] = magic ? e >> 4 | (e & 0xf) << 4 : 0;
+	n = x->p - p;
+	if(msg.type == Csysex || n > nelem(u) - (q - u)){
+		write(1, u, q - u);
+		write(1, p, n);
+	}else{
+		memcpy(q, p, n);
+		write(1, u, n + (q - u));
 	}
-	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);
+	fprint(2, "usage: %s [-D] [mid]\n", argv0);
 	exits("usage");
 }
 
@@ -316,31 +59,17 @@
 void
 main(int argc, char **argv)
 {
-	int i, c, end, debug;
+	int end;
+	uchar eot[] = {0x00, 0xff, 0x2f, 0x00};
 	Trk *x;
 
-	debug = 0;
 	ARGBEGIN{
-	case 'D': debug = 1; break;
-	case 'c':
-		nch = atoi(EARGF(usage()));
-		break;
-	case 'p':
-		percch = atoi(EARGF(usage()));
-		break;
+	case 'D': trace = 1; break;
+	case 'm': magic = 1; break;	/* FIXME: investigate more */
 	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;
-	}
+	if(readmid(*argv) < 0)
+		sysfatal("readmid: %r");
 	for(;;){
 		end = 1;
 		for(x=tr; x<tr+ntrk; x++){
@@ -349,33 +78,16 @@
 			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;
-					}
+				eat(x);
+				if(x->ended)
 					break;
-				}
 				x->Δ = getvar(x);
 			}
 		}
-		if(end){
-			write(1, tr[0].q, tr[0].p - tr[0].q);
+		if(end)
 			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;
-			}
-		}
 	}
+	write(1, eot, sizeof eot);
 	exits(nil);
 }
--- /dev/null
+++ b/midifile.c
@@ -1,0 +1,320 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include "midifile.h"
+
+Trk *tr;
+int mfmt, ntrk, div = 1, tempo = 500000;
+int trace, stream;
+vlong tic;
+Biobuf *ib;
+
+void *
+emalloc(ulong n)
+{
+	void *p;
+
+	p = mallocz(n, 1);
+	if(p == nil)
+		sysfatal("mallocz: %r");
+	setmalloctag(p, getcallerpc(&n));
+	return p;
+}
+
+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);
+}
+
+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 || x->p == nil || x->e == nil)
+		Bread(ib, &v, 1);
+	else{
+		if(x->p >= x->e || x->ended)
+			sysfatal("track overflow");
+		v = *x->p++;
+	}
+	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);
+}
+
+u8int
+peekbyte(Trk *x)
+{
+	return *x->p;
+}
+
+u32int
+peekvar(Trk *x)
+{
+	uchar *p;
+	uint v;
+
+	p = x->p;
+	v = getvar(x);
+	x->p = p;
+	return v;
+}
+
+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;
+}
+
+double
+tc(double n)
+{
+	return (n * tempo * Rate / div) / 1e6;
+}
+
+int
+nextevent(Trk *x)
+{
+	int e;
+
+	x->prev = x->p;
+	e = get8(x);
+	if((e & 0x80) == 0){
+		if(x->p != nil){
+			x->p--;
+			x->prev--;
+		}
+		e = x->ev;
+		if((e & 0x80) == 0)
+			sysfatal("invalid event %#ux", e);
+	}else
+		x->ev = e;
+	return e;
+}
+
+/* FIXME: use midump's implementation to make this not suck */
+static void
+setmsg(Msg *m, int chan, int type, int arg1, int arg2)
+{
+	m->chan = chan;
+	m->type = type;
+	m->arg1 = arg1;
+	m->arg2 = arg2;
+}
+void
+translate(Trk *x, int e, Msg *msg)
+{
+	int c, n, m, type;
+	uchar *p;
+
+	c = e & 0xf;
+	dprint("ch%02d ", c);
+	n = get8(x);
+	m = -1;
+	type = Cunknown;
+	switch(e >> 4){
+	case 0x8:
+		m = get8(x);
+		dprint("note off\t%02ux\taftertouch\t%02ux", n, m);
+		type = Cnoteoff;
+		break;
+	case 0x9:
+		m = get8(x);
+		dprint("note on\t%02ux\tvelocity\t%02ux", n, m);
+		type = Cnoteon;
+		break;
+	case 0xb:
+		m = get8(x);
+		dprint("control change: ");
+		switch(n){
+		case 0x00:
+			dprint("bank select msb\t%02ux", m);
+			type = Cbankmsb;
+			break;
+		case 0x07:
+			dprint("channel volume\t%02ux", m);
+			type = Cchanvol;
+			break;
+		case 0x0a:
+			dprint("pan\t%02ux", m);
+			type = Cpan;
+			break;
+		default:
+			dprint("unknown controller %.4ux", n);
+			break;
+		}
+		break;
+	case 0xc:
+		dprint("program change\t%02ux", n);
+		type = Cprogram;
+		break;
+	case 0xe:
+		n = (get8(x) << 7 | n) - 0x4000 / 2;
+		dprint("pitch bend\t%02x", n);
+		type = Cpitchbend;
+		break;
+	case 0xf:
+		dprint("sysex:\t");
+		if((e & 0xf) == 0){
+			m = 0;
+			while(get8(x) != 0xf7)
+				m++;
+			fprint(2, "sysex n %d m %d\n", n, m);
+			type = Csysex;
+			break;
+		}
+		m = get8(x);
+		switch(n){
+		case 0x2f:
+			dprint("... so long!");
+			x->ended = 1;
+			type = Ceot;
+			break;
+		case 0x51:
+			tempo = get16(x) << 8;
+			tempo |= get8(x);
+			dprint("tempo change\t%d", tempo);
+			type = Ctempo;
+			break;
+		default:
+			dprint("skipping unhandled event %02ux", n);
+			skip(x, m);
+			break;
+		}
+		break;
+	case 0xa:
+		m = get8(x);
+		dprint("polyphonic key pressure/aftertouch\t%02ux\t%02ux", n, m);
+		type = Ckeyafter;
+		break;
+	case 0xd:
+		m = get8(x);
+		dprint("channel pressure/aftertouch\t%02ux\t%02ux", n, m);
+		type = Cchanafter;
+		break;
+	default: sysfatal("invalid event %#ux", e >> 4);
+	}
+	setmsg(msg, c, type, n, m);
+	dprint("\t[");
+	for(p=x->prev; p<x->p; p++)
+		dprint("%02ux", *p);
+	dprint("]\n");
+}
+
+int
+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){
+		werrstr("invalid header");
+		return -1;
+	}
+	mfmt = get16(nil);
+	ntrk = get16(nil);
+	if(ntrk == 1)
+		mfmt = 0;
+	if(mfmt < 0 || mfmt > 1){
+		werrstr("unsupported format %d", mfmt);
+		return -1;
+	}
+	div = get16(nil);
+	tr = emalloc(ntrk * sizeof *tr);
+	for(x=tr, z=-1UL; x<tr+ntrk; x++){
+		if(get32(nil) != 0x4d54726b){
+			werrstr("invalid track");
+			return -1;
+		}
+		n = get32(nil);
+		x->s = emalloc(n+1);
+		bread(x->s, n);
+		x->p = x->s;
+		x->prev = x->s;
+		x->e = x->s + n;
+		x->Δ = getvar(x);	/* prearm */
+		if(x->Δ < z)
+			z = x->Δ;
+	}
+	for(x=tr; x<tr+ntrk; x++)
+		x->Δ -= z;
+	Bterm(ib);
+	ib = nil;
+	return 0;
+}
--- /dev/null
+++ b/midifile.h
@@ -1,0 +1,60 @@
+typedef struct Msg Msg;
+typedef struct Trk Trk;
+
+enum{
+	Rate = 44100,
+	Ninst = 128 + 81-35+1,
+	Nchan = 16,
+	Percch = 9,
+};
+
+struct Msg{
+	int type;
+	int chan;
+	int arg1;
+	int arg2;
+};
+// FIXME: naming
+// FIXME: hicucps playing when there are sysex etc events (-s)
+struct Trk{
+	u8int *s;
+	u8int *prev;
+	u8int *p;
+	u8int *e;
+	double Δ;
+	double t;
+	int ev;
+	int ended;
+};
+extern Trk *tr;
+
+enum{
+	Cnoteoff,
+	Cnoteon,
+	Cbankmsb,
+	Cchanvol,
+	Cpan,
+	Cprogram,
+	Cpitchbend,
+	Ceot,
+	Ctempo,
+	Ckeyafter,
+	Cchanafter,
+	Csysex,
+	Cunknown,
+};
+
+extern int mfmt, ntrk, div, tempo;
+extern int trace, stream;
+
+void*	emalloc(ulong);
+int	readmid(char*);
+void	dprint(char*, ...);
+void	samp(uvlong);
+u32int	peekvar(Trk*);
+u8int	peekbyte(Trk*);
+int	nextevent(Trk*);
+void	translate(Trk*, int, Msg*);
+int	getvar(Trk*);
+
+#pragma	varargck	argpos	dprint	1
--- a/mkfile
+++ b/mkfile
@@ -22,3 +22,12 @@
 
 (calc).c:R:	\1.tab.c
 	mv $stem1.tab.c $stem1.c
+
+$O.mid2s: mid2s.$O midifile.$O
+	$LD $LDFLAGS -o $target $prereq
+
+$O.s2mid: s2mid.$O midifile.$O
+	$LD $LDFLAGS -o $target $prereq
+
+$O.midump: midump.$O midifile.$O
+	$LD $LDFLAGS -o $target $prereq