code: plan9front

Download patch

ref: 784b47ad2b1ae5975fd17f428129e014ecc43944
parent: 89c60481afd8307f7825281bd561995f83a7bfb2
author: qwx <qwx@sciops.net>
date: Thu Jan 12 01:48:02 EST 2023

games/midi: fix desyncs after tempo changes in multitrack files

same as 89c60481afd8307f7825281bd561995f83a7bfb2.

--- a/sys/src/games/midi.c
+++ b/sys/src/games/midi.c
@@ -6,7 +6,8 @@
 struct Tracker {
 	uchar *data;
 	char ended;
-	uvlong t;
+	vlong t;
+	vlong Δ;
 	uchar notes[16][128];
 	int cmd;
 } *tr;
@@ -13,8 +14,8 @@
 
 typedef struct Tracker Tracker;
 
+int debug;
 int fd, ofd, div, tempo = 500000, ntrack;
-uvlong T;
 int freq[128];
 uchar out[8192], *outp = out;
 
@@ -30,6 +31,20 @@
 	return v;
 }
 
+void
+dprint(char *fmt, ...)
+{
+	char s[256];
+	va_list arg;
+
+	if(!debug)
+		return;
+	va_start(arg, fmt);
+	vseprint(s, s+sizeof s, fmt, arg);
+	va_end(arg);
+	fprint(2, "%s", s);
+}
+
 int
 get8(Tracker *src)
 {
@@ -40,6 +55,7 @@
 			sysfatal("unexpected eof");
 		return c;
 	}
+	dprint("%#p:%02ux", src, *src->data);
 	return *src->data++;
 }
 
@@ -64,7 +80,7 @@
 getvar(Tracker *src)
 {
 	int k, x;
-	
+
 	x = get8(src);
 	k = x & 0x7F;
 	while(x & 0x80){
@@ -95,10 +111,10 @@
 	while(--x);
 }
 
-uvlong
+double
 tconv(int n)
 {
-	uvlong v;
+	double v;
 	
 	v = n;
 	v *= tempo;
@@ -109,14 +125,20 @@
 }
 
 void
-run(uvlong n)
+run(double n)
 {
-	int samp, j, k, no[128];
+	int j, k, no[128];
 	int t, f;
 	short u;
+	uvlong samp;
+	double Δ;
 	Tracker *x;
-	
-	samp = n - T;
+	static double T, ε;
+	static uvlong τ;
+
+	Δ = tconv(n) + ε;
+	samp = Δ;
+	ε = Δ - samp;
 	if(samp <= 0)
 		return;
 	memset(no, 0, sizeof no);
@@ -130,7 +152,7 @@
 	while(samp--){
 		t = 0;
 		for(k = 0; k < 128; k++){
-			f = (T % freq[k]) >= freq[k]/2 ? 1 : 0;
+			f = (τ % freq[k]) >= freq[k]/2 ? 1 : 0;
 			t += f * no[k];
 		}
 		u = t*10;
@@ -141,7 +163,7 @@
 			write(ofd, out, sizeof out);
 			outp = out;
 		}
-		T++;
+		τ++;
 	}
 }
 
@@ -148,11 +170,9 @@
 void
 readevent(Tracker *src)
 {
-	uvlong l;
 	int n,t;
-	
-	l = tconv(getvar(src));
-	run(src->t += l);
+
+	dprint(" [%zd] ", src - tr);
 	t = get8(src);
 	if((t & 0x80) == 0){
 		src->data--;
@@ -161,6 +181,7 @@
 			sysfatal("invalid midi");
 	}else
 		src->cmd = t;
+	dprint("(%02ux) ", t >> 4);
 	switch(t >> 4){
 	case 0x8:
 		n = get8(src);
@@ -171,12 +192,12 @@
 		n = get8(src);
 		src->notes[t & 15][n] = get8(src);
 		break;
-	case 0xB:
-		get16(src);
-		break;
+	case 0xA:
+	case 0xD:
 	case 0xC:
 		get8(src);
 		break;
+	case 0xB:
 	case 0xE:
 		get16(src);
 		break;
@@ -201,7 +222,7 @@
 			skip(src, n);
 			break;
 		default:
-			fprint(2, "unknown meta event type %.2x\n", t);
+			dprint("unknown meta event type %.2x\n", t);
 		case 3: case 1: case 2: case 0x58: case 0x59: case 0x21:
 			skip(src, n);
 		}
@@ -209,16 +230,20 @@
 	default:
 		sysfatal("unknown event type %x", t>>4);
 	}
+	dprint("\n");
 }
 
 void
 main(int argc, char **argv)
 {
-	int i, size;
-	uvlong t, mint;
-	Tracker *x, *minx;
+	int i, size, end;
+	uvlong z;
+	Tracker *x;
 
 	ARGBEGIN{
+	case 'D':
+		debug = 1;
+		break;
 	case 'c':
 		ofd = 1;
 		break;
@@ -235,31 +260,36 @@
 	ntrack = get16(nil);
 	div = get16(nil);
 	tr = emallocz(ntrack * sizeof(*tr));
-	for(i = 0; i < ntrack; i++){
+	for(x=tr, z=-1UL; x<tr+ntrack; x++){
 		if(get32(nil) != 0x4D54726B)
 			sysfatal("invalid track header");
 		size = get32(nil);
-		tr[i].data = emallocz(size);
-		readn(fd, tr[i].data, size);
+		x->data = emallocz(size);
+		readn(fd, x->data, size);
+		x->Δ = getvar(x);	/* prearm */
+		if(x->Δ < z)
+			z = x->Δ;
 	}
+	for(x=tr; x<tr+ntrack; x++)
+		x->Δ -= z;
 	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++){
+	for(end=0; !end;){
+		end = 1;
+		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;
+			end = 0;
+			x->Δ--;
+			x->t += tconv(1);
+			while(x->Δ <= 0){
+				readevent(x);
+				if(x->ended)
+					break;
+				x->Δ = getvar(x);
 			}
 		}
-		if(minx == nil){
-			write(ofd, out, outp - out);
-			exits(nil);
-		}
-		readevent(minx);
+		run(1);
 	}
+	exits(nil);
 }