ref: 0598322524a57595eabe75295a60913dcc9bff01
author: qwx <[email protected]>
date: Mon May 27 19:43:11 EDT 2019
initial import
--- /dev/null
+++ b/man/1/u6mopl
@@ -1,0 +1,47 @@
+.TH U6MOPL 1
+.SH NAME
+u6mopl, u6dec \- Ultima 6 data file handlers
+.SH SYNOPSIS
+.B u6mopl
+[
+.B -l
+]
+.PP
+.B u6dec
+.SH DESCRIPTION
+.I U6mopl
+decodes Ultima 6 M format music files into a stream of
+.SM OPL2
+commands suitable for playback using
+.I opl2
+at a rate of 60 Hz (see
+.IR opl2 (1)).
+It reads the entire file from standard in,
+then writes a stream of
+.SM OPL2
+commands to standard out.
+.PP
+While M format files are meant to be looped perpetually,
+it is disabled by default to allow further manipulation of the decoded data.
+The
+.B -l
+flag enables looping.
+.PP
+.I U6dec
+decompresses Ultima 6 data files or chunks stored using
+.SM LZW
+compression.
+.SH EXAMPLES
+Loop a compressed M format file using
+.IR opl2 :
+.IP
+.EX
+% u6dec <ultima.m | u6mopl -l | opl2 -n 60 >/dev/audio
+.EE
+.SH SEE ALSO
+.IR opl2 (1)
+.SH HISTORY
+.I U6mopl
+and
+.I u6dec
+first appeared in 9front (September, 2017).
--- /dev/null
+++ b/mkfile
@@ -1,0 +1,7 @@
+</$objtype/mkfile
+BIN=$home/bin/$objtype
+TARG=\
+ u6dec\
+ u6mopl\
+
+</sys/src/cmd/mkmany
--- /dev/null
+++ b/u6dec.c
@@ -1,0 +1,134 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+
+typedef struct Dict Dict;
+struct Dict{
+ uchar root;
+ int word;
+};
+Dict dict[4096], *dictp;
+int sz, top;
+Biobuf *bf, *bfo;
+
+u16int
+get16(void)
+{
+ int n;
+ uchar u[2];
+
+ n = Bread(bf, u, sizeof u);
+ if(n < 0)
+ sysfatal("get16: %r");
+ else if(n != sizeof u)
+ exits(nil);
+ return u[1] << 8 | u[0];
+}
+
+void
+put8(u8int v)
+{
+ if(Bwrite(bfo, &v, sizeof v) != sizeof v)
+ sysfatal("put8: %r");
+}
+
+uchar
+putstr(int v)
+{
+ uchar buf[4096], *p;
+
+ p = buf;
+ while(v > 0xff){
+ *p++ = dict[v].root;
+ v = dict[v].word;
+ }
+ put8(v);
+ while(--p >= buf)
+ put8(*p);
+ return v;
+}
+
+void
+pushnode(uchar r, int u)
+{
+ if(dictp == dict + nelem(dict))
+ sysfatal("pushnode: dict overflow");
+ dictp->root = r;
+ dictp->word = u;
+ dictp++;
+ if(++top >= 1 << sz){
+ if(++sz > 12)
+ sysfatal("pushnode: word size overflow");
+ }
+}
+
+u16int
+getword(int sz)
+{
+ static int nbit;
+ static u32int u;
+ u16int v;
+
+ if(sz < 9 || sz > 12)
+ sysfatal("getword: invalid word size");
+ if(nbit < sz){
+ u &= (1 << nbit) - 1;
+ u |= get16() << nbit;
+ nbit += 16;
+ }
+ v = u & (1 << sz) - 1;
+ u >>= sz;
+ nbit -= sz;
+ return v;
+}
+
+void
+init(void)
+{
+ sz = 9;
+ top = 0x102;
+ dictp = dict + 0x102;
+}
+
+void
+main(int, char**)
+{
+ int u, v;
+ uchar r;
+
+ bf = Bfdopen(0, OREAD);
+ bfo = Bfdopen(1, OWRITE);
+ if(bf == nil || bfo == nil)
+ sysfatal("Bfdopen: %r");
+ Bseek(bf, 4, 1);
+ init();
+ u = 0;
+ v = getword(sz);
+ if(v != 0x100)
+ sysfatal("unknown format");
+ for(;;){
+ switch(v){
+ case 0x100:
+ init();
+ v = getword(sz);
+ put8(v);
+ break;
+ case 0x101:
+ exits(nil);
+ default:
+ if(v < top){
+ r = putstr(v);
+ pushnode(r, u);
+ break;
+ }
+ if(v != top)
+ sysfatal("invalid word");
+ r = putstr(u);
+ put8(r);
+ pushnode(r, u);
+ break;
+ }
+ u = v;
+ v = getword(sz);
+ }
+}
--- /dev/null
+++ b/u6mopl.c
@@ -1,0 +1,295 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <fcall.h>
+
+typedef struct Channel Channel;
+struct Channel{
+ int o1;
+ u16int f;
+ char Δf;
+ char fmA;
+ int fmΔA;
+ uchar fmmax;
+ uchar fmfact;
+ uchar lvl;
+ int Δlvl;
+ uchar Δlvlt;
+ uchar Δlvldt;
+};
+Channel ch[9] = {
+ {.o1=0x00}, {.o1=0x01}, {.o1=0x02}, {.o1=0x08}, {.o1=0x09},
+ {.o1=0x0a}, {.o1=0x10}, {.o1=0x11}, {.o1=0x12}
+};
+
+typedef struct Sect Sect;
+struct Sect{
+ uchar *p;
+ int n;
+ uchar *ret;
+};
+Sect sect[32], *secp = sect;
+
+u16int fnum[] = {
+ 0x000, 0x158, 0x182, 0x1b0, 0x1cc, 0x203, 0x241, 0x286,
+ 0x000, 0x16a, 0x196, 0x1c7, 0x1e4, 0x21e, 0x25f, 0x2a8,
+ 0x000, 0x147, 0x16e, 0x19a, 0x1b5, 0x1e9, 0x224, 0x266
+};
+
+uchar buf[8192], *bufp = buf, *bufe = buf, *loop = buf;
+uchar *instp[16];
+uchar outbuf[1024], *outp = outbuf;
+int Δtc, doloop;
+
+void
+flush(void)
+{
+ if(outp == outbuf)
+ return;
+ PBIT16(outp-2, 1);
+ write(1, outbuf, outp-outbuf);
+ memset(outbuf, 0, outp-outbuf);
+ outp = outbuf;
+}
+
+void
+out(uchar r, uchar v)
+{
+ if(outp >= outbuf + sizeof outbuf)
+ flush();
+ outp[0] = r;
+ outp[1] = v;
+ outp += 4;
+}
+
+void
+barf(void)
+{
+ if(outp == outbuf)
+ out(0, 0);
+ flush();
+}
+
+uchar
+get8(void)
+{
+ if(bufp == bufe)
+ sysfatal("premature eof");
+ return *bufp++;
+}
+
+void
+setinst(Channel *c)
+{
+ uchar *p;
+
+ p = instp[get8()];
+ out(0x20 + c->o1, *p++);
+ out(0x40 + c->o1, *p++);
+ out(0x60 + c->o1, *p++);
+ out(0x80 + c->o1, *p++);
+ out(0xe0 + c->o1, *p++);
+ out(0x20 + c->o1+3, *p++);
+ out(0x40 + c->o1+3, *p++);
+ out(0x60 + c->o1+3, *p++);
+ out(0x80 + c->o1+3, *p++);
+ out(0xe0 + c->o1+3, *p++);
+ out(0xc0 + c - ch, *p);
+}
+
+void
+setΔlvl(int dir)
+{
+ uchar v;
+ Channel *c;
+
+ v = get8();
+ c = ch + (v >> 4);
+ v = (v & 0xf) + 1;
+ c->Δlvl = dir;
+ c->Δlvlt = v;
+ c->Δlvldt = v;
+}
+
+void
+setf(int n, int f)
+{
+ out(0xa0 + n, f);
+ out(0xb0 + n, f >> 8);
+}
+
+void
+setc(Channel *c, int on)
+{
+ int f;
+ uchar v;
+
+ v = get8();
+ f = fnum[v & 0x1f] | (v & 0xe0) << 5 | on << 13;
+ if(on)
+ setf(c - ch, f & ~(1 << 13));
+ setf(c - ch, c->f = f);
+}
+
+void
+up(void)
+{
+ u16int w;
+ Channel *c;
+
+ for(c=ch; c<ch+nelem(ch); c++){
+ if(c->Δf != 0)
+ setf(c - ch, c->f += c->Δf);
+ else if(c->fmfact != 0 && c->f & 1<<13){
+ if(c->fmA >= c->fmmax)
+ c->fmΔA = -1;
+ else if(c->fmA == 0)
+ c->fmΔA = 1;
+ c->fmA += c->fmΔA;
+ w = c->fmfact * (int)(c->fmA - c->fmmax / 2);
+ setf(c - ch, c->f + w & 0xffff);
+ }
+ if(c->Δlvl == 0 || --c->Δlvlt > 0)
+ continue;
+ c->Δlvlt = c->Δlvldt;
+ c->lvl += c->Δlvl;
+ if(c->lvl > 0x3f){
+ c->lvl = 0x3f;
+ c->Δlvl = 0;
+ }else if(c->lvl & 1<<7){
+ c->lvl = 0;
+ c->Δlvl = 0;
+ }
+ out(0x40 + c->o1+3, c->lvl);
+ }
+}
+
+void
+ev(void)
+{
+ uchar v;
+ Sect *s;
+ Channel *c;
+
+ for(;;){
+ v = get8();
+ c = ch + (v & 0xf);
+ switch(v >> 4 & 0xf){
+ case 0:
+ setc(c, 0);
+ break;
+ case 1:
+ c->fmΔA = 1;
+ c->fmA = 0;
+ /* wet floor */
+ case 2:
+ setc(c, 1);
+ break;
+ case 3:
+ v = get8();
+ c->Δlvl = 0;
+ c->lvl = v;
+ out(0x40 + c->o1+3, v);
+ break;
+ case 4:
+ out(0x40 + c->o1, get8());
+ break;
+ case 5:
+ c->Δf = (int)(char)get8();
+ break;
+ case 6:
+ v = get8();
+ c->fmmax = v >> 4;
+ c->fmfact = v & 0xf;
+ break;
+ case 7:
+ setinst(c);
+ break;
+ case 8:
+ switch(v & 0xf){
+ case 1:
+ if(secp == sect + nelem(sect)){
+ fprint(2, "sect overflow off=%#zux\n", bufp-buf);
+ break;
+ }
+ secp->n = get8();
+ v = get8();
+ secp->p = buf + (get8() << 8 | v);
+ secp->ret = bufp;
+ if(secp->p >= bufe)
+ sysfatal("sect pos past eob off=%#zux", bufp-buf);
+ bufp = secp++->p;
+ break;
+ case 2:
+ Δtc = get8();
+ return;
+ case 3:
+ v = get8();
+ if(v >= nelem(instp))
+ sysfatal("inst overflow off=%#zux\n", bufp-buf);
+ instp[v] = bufp;
+ bufp += 11;
+ break;
+ case 5:
+ setΔlvl(1);
+ break;
+ case 6:
+ setΔlvl(-1);
+ break;
+ }
+ break;
+ case 14:
+ loop = bufp;
+ break;
+ case 15:
+ if(secp == sect){
+ bufp = loop;
+ if(!doloop && bufp == buf){
+ barf();
+ exits(nil);
+ }
+ break;
+ }
+ s = secp - 1;
+ if(--s->n == 0){
+ bufp = s->ret;
+ secp--;
+ break;
+ }
+ bufp = s->p;
+ break;
+ }
+ }
+}
+
+void
+usage(void)
+{
+ fprint(2, "usage: %s [-l] [mfile]\n", argv0);
+ exits("usage");
+}
+
+void
+main(int argc, char **argv)
+{
+ int n;
+
+ ARGBEGIN{
+ case 'l': doloop++; break;
+ default: usage();
+ }ARGEND
+ while(n = read(0, bufe, buf + sizeof(buf) - bufe), n > 0){
+ if(bufe >= buf + sizeof buf)
+ sysfatal("file too large");
+ bufe += n;
+ }
+ if(n < 0)
+ sysfatal("read: %r");
+ out(0x01, 1<<5);
+ for(;;){
+ if(--Δtc <= 0)
+ ev();
+ up();
+ barf();
+ }
+}