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