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