shithub: u6m

Download patch

ref: 0598322524a57595eabe75295a60913dcc9bff01
author: qwx <qwx@sciops.net>
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();
+	}
+}