shithub: fork

Download patch

ref: e4346f910a11e6ad3a45d70222f6e9438a71908d
parent: e3d3acac74ab17ff5650260864a5f2e18e75c934
author: qwx <qwx@sciops.net>
date: Wed Jan 7 05:40:09 EST 2026

games/midi: add wip midi tools

--- /dev/null
+++ b/sys/src/games/midi/midtap.c
@@ -1,0 +1,188 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include "commid.h"
+
+int m2ich[Nchan], i2mch[Nchan], age[Nchan];
+int nch = Nchan;
+int percch = Percch;
+
+
+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
+handle(int type, int chan, int n, int m)
+{
+	switch(type){
+	case Cnoteoff: if((e & 15) == percch && n < 36) n += 36; break;
+	case Cnoteon: if((e & 15) == percch && n < 36) n += 36; break;
+	case Cbankmsb: break;
+	case Cchanvol: break;
+	case Cpan: break;
+	case Cprogram: break;
+	case Cpitchbend: break;
+	case Ceot: break;
+	case Ctempo: tempo = n; break;
+	case Ckeyafter: break;
+	case Cchanafter: break;
+	}
+}
+
+// FIXME: idiomatic sort of way to use midifile
+static void
+eat(Trk *x)
+{
+	int c, e;
+	char *p;
+	Msg m;
+
+	p = x->p - 1;
+	if((peekvar(x) & 0x80) == 0)
+		*p-- = x->ev;
+	e = nextevent(x);
+	if(translate(x, e, &m) < 0)
+		return;
+	switch(m.type){
+	default:
+	}
+	p[1] = m.e;
+	p[0] = m.e >> 4 | (m.e & 0xff) << 4;
+	write(1, p, x->p - p);
+}
+
+
+// FIXME: differenciate between midi and opl3 channels; max 18 channels,
+// not 16, for a single one (also dmid); opl2: 9
+//	↑ but, we're not dealing with opl here, midi only; this isn't mid2s'
+//	job, we only want to forward events; filtering is for midtap (manual)
+//  and dmid (opl-specific)
+
+static void
+eat(void)
+{
+	int c, e;
+	char *p;
+
+	p = x->p - 1;
+	if((peekvar(x) & 0x80) == 0)
+		*x->q-- = x->ev;
+	e = nextevent(x);
+	c = e & 15;
+	e = mapchan(x, c, e);
+	if((c & 15) == percch && n < 36)
+		n += 36;
+	if(translate(x, e) < 0)
+		...
+	x->q[1] = e;
+	x->q[0] = e >> 4 | (e & 0xff) << 4;
+	write(1, x->q, x->p - x->q);
+	return ...
+	FIXME: return condition for track end, or do in handle
+}
+
+void
+main(int argc, char **argv)
+{
+	int i, c, end;
+	Trk *x;
+
+	ARGBEGIN{
+	case 'D': trace = 1; break;
+	case 'c':
+		nch = atoi(EARGF(usage()));
+		break;
+	case 'p':
+		percch = atoi(EARGF(usage()));
+		break;
+	default: usage();
+	}ARGEND
+	if(nch <= 0 || nch > Nchan
+	|| percch <= 0 || percch > nch)
+		usage();
+	if(readmid(*argv) < 0)
+		sysfatal("readmid: %r");
+	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){
+				eat(x);
+				if(x->ended){
+					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);
+}
--- /dev/null
+++ b/sys/src/games/midi/midump.c
@@ -1,0 +1,98 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include "midifile.h"
+
+void
+samp(double)
+{
+}
+
+// FIXME: look at dumps of midis before commit and make sure they make sense
+// FIXME: not *all* prints belong here, we want to be able to trace in the
+//	other programs; add them back once this is complete so the picture is
+//	clear
+
+void
+event(Track *t)
+{
+	int e;
+	uchar *p;
+	Msg msg;
+
+	e = nextev(t);
+	translate(t, e, &msg);
+
+	dprint("Δ %.2f ", t->Δ);
+	if(e >> 4 != 0xf)
+		dprint("ch%02d ", chan - msg.c);
+
+	// FIXME: also unhandled ones (look at the byte values; pp.43 on; tables pp.102 → array
+	switch(msg.type){
+	case Cnoteoff: dprint("release %02ux, aftertouch %02ux", msg.arg1, msg.arg2); break;
+	case Cnoteon: dprint("trigger %02ux, velocity %02ux", msg.arg1, msg.arg2); break;
+	case Cbankmsb: dprint("bank select msb %02ux", msg.arg2); break;
+	case Cchanvol: dprint("channel volume %02ux", msg.chan->vol); break;
+	case Cpan: dprint("stereo pan %s %02ux",
+				msg.arg2 == 64 ? "center" : msg.arg2 < 64 ? "left" : "right",
+				msg.arg2 == 64 ? 0 : msg.arg2 < 64 ? 64-msg.arg2 : 127-msg.arg2); break;
+	case Cprogram: break;
+	case Cpitchbend: break;
+	case Ceot: dprint("... so long!"); break;
+	case Ctempo: dprint("tempo %d", tempo); break;
+	case Ckeyafter: dprint("polyphonic key pressure/aftertouch %02ux %02ux", msg.arg1, msg.arg2); break;
+	case Cchanafter: dprint("channel pressure/aftertouch %02ux %02ux", msg.arg1, msg.arg2); break;
+	case Csysex: break;	// FIXME
+	case Csysreset:	break;	// FIXME
+	case Cunknown: dprint("unhandled event %02ux, skipped", msg.type); break;
+	}
+	dprint(" [");
+	for(p=t->run; p<t->cur; p++)
+		dprint("%02ux", *p);
+	dprint("]\n");
+}
+
+void
+desc(void)
+{
+	Track *t;
+
+	switch(mfmt){
+	case 0: dprint("format 0: single multi-channel track\n"); break;
+	case 1: dprint("format 1: multiple concurrent tracks\n"); break;
+	case 2: dprint("format 2: multiple independent tracks\n"); break;
+	}
+	dprint("%d tracks\n", ntrk);
+	if(1)	/* FIXME: properly introduce new members, etc. */
+		dprint("div: %d ticks per quarter note\n", div);
+	else
+		dprint("div: %d smpte format, %d ticks per frame\n",
+			(s16int)div >> 8, div & 0xff);
+	if(stream)
+		dprint("streaming file\n");
+	else
+		for(t=tracks; t<tracks+ntrk; t++)
+			dprint("track %02zd: %zd bytes\n", t-tracks, t->bufsz-1);
+}
+
+void
+usage(void)
+{
+	fprint(2, "usage: %s [-s] [mid]\n", argv0);
+	exits("usage");
+}
+
+void
+main(int argc, char **argv)
+{
+	trace = 1;
+	ARGBEGIN{
+	case 's': stream = 1; break;
+	default: usage();
+	}ARGEND
+	if(readmid(*argv) < 0)
+		sysfatal("readmid: %r");
+	desc();
+	evloop();
+	exits(nil);
+}
--- /dev/null
+++ b/sys/src/games/midi/s2mid.c
@@ -1,0 +1,67 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include "midifile.h"
+
+static void
+output(void)
+{
+	uchar *p, eot[] = {0xff, 0x2f, 0x00};
+	Track *t;
+
+	for(t=tracks; t<tracks+ntrk; t++){
+		t->Δ = 0;
+		p = t->cur;
+		putvar(t, t->Δ);
+		put(t, eot, sizeof eot);
+		t->nbuf += t->cur - p;
+	}
+	writemid(nil);
+}
+
+void
+samp(double)
+{
+}
+
+void
+event(Track *t)
+{
+	static double τ;
+	int e;
+	uchar *p;
+	double τ´;
+	Msg msg;
+
+	τ´ = nsec();
+	if(τ == 0.0)
+		τ = τ´;
+	t->Δ = ns2tic(τ´ - τ);
+	τ = τ´;
+	p = t->cur;
+	putvar(t, t->Δ);
+	e = nextev(t);
+	translate(t, e, &msg);
+	t->nbuf += t->cur - p;
+}
+
+void
+usage(void)
+{
+	fprint(2, "usage: %s [stream]\n", argv0);
+	exits("usage");
+}
+
+void
+main(int argc, char **argv)
+{
+	writeback = 1;
+	stream = 1;
+	ARGBEGIN{
+	default: usage();
+	}ARGEND
+	readmid(*argv);
+	atexit(output);
+	evloop();
+	exits(nil);
+}
--