shithub: opl2

Download patch

ref: 7c028c65bbb2febbfcd47122ed54ab51c53e24b4
author: qwx <[email protected]>
date: Mon May 27 19:23:00 EDT 2019

initial import

diff: cannot open b/man/1//null: file does not exist: 'b/man/1//null' diff: cannot open b/man//null: file does not exist: 'b/man//null'
--- /dev/null
+++ b/man/1/opl2
@@ -1,0 +1,59 @@
+.TH OPL2 1
+.SH NAME
+opl2 \- OPL2 chip emulator
+.SH SYNOPSIS
+.B opl2
+[
+.B -n
+.I rate
+] [
+.I file
+]
+.SH DESCRIPTION
+.I Opl2
+is an emulator of a single Yamaha 3812 chip, also known as
+.SM OPL2.
+.PP
+The emulated chip is programmed by a stream of commands either from
+.I file
+or from standard in.
+Guided by these commands, it then synthesizes a number of 16 bit little-endian samples for a sampling rate of 44.1 kHz.
+Each is duplicated for stereo sound and written to standard out.
+.PP
+Commands are 4 bytes formatted as follows, where the size of each field is given in bytes between brackets:
+.PP
+.RS
+.IR register [1]
+.IR value [1]
+.IR delay [2]
+.RE
+.PP
+Each command specifies a
+.I value
+to be written to an
+.SM OPL2
+chip
+.IR register ,
+modifying its internal state.
+.PP
+The
+.I delay
+field is stored as a 16-bit unsigned integer in little-endian byte order,
+and provides timing.
+It is a multiple of a command period, during which the
+.SM OPL2
+chip may be sampled before processing the next command.
+The period itself is the inverse of the command rate, 700 Hz by default.
+This rate can be set using the
+.B -n
+parameter.
+.SH "SEE ALSO"
+.IR wl3d (1) ,
+.IR audio (3)
+.SH HISTORY
+.I Opl2
+first appeared in 9front (May, 2016), based on
+.I fmopl.c
+from the Multiple Arcade Machine Emulator (
+.SM MAME
+).
--- /dev/null
+++ b/mkfile
@@ -1,0 +1,14 @@
+</$objtype/mkfile
+
+BIN=$home/bin/$objtype
+TARG=opl2
+OFILES=\
+	opl2.$O\
+	opl2m.$O\
+
+HFILES=
+
+</sys/src/cmd/mkone
+
+sysinstall:V:
+	cp man/1/op2 /sys/man/1/
--- /dev/null
+++ b/opl2.c
@@ -1,0 +1,734 @@
+#include <u.h>
+#include <libc.h>
+
+typedef struct Envelope Envelope;
+typedef struct Phase Phase;
+typedef struct Op Op;
+typedef struct Chan Chan;
+
+enum{
+	Rwse = 0x01,
+		Mwse = 1<<5,	/* wave selection enable */
+	Rcsm = 0x08,
+		Mnse = 1<<6,	/* note selection enable */
+	Rctl = 0x20,
+		Mame = 1<<7,	/* enable amplitude modulation */
+		Mvbe = 1<<6,	/* enable vibrato */
+		Msse = 1<<5,	/* enable sustain */
+		Mkse = 1<<4,	/* enable keyboard scaling */
+		Mmfq = 15<<0,	/* modulator freq multiple */
+	Rsca = 0x40,
+		Mlvl = 63<<0,	/* total level */
+		Mscl = 3<<6,	/* scaling level */
+	Ratk = 0x60,
+		Mdec = 15<<0,	/* decay rate */
+		Matk = 15<<4,	/* attack rate */
+	Rsus = 0x80,
+		Mrel = 15<<0,	/* release rate */
+		Msus = 15<<4,	/* sustain level */
+	Rnum = 0xa0,		/* f number lsb */
+	Roct = 0xb0,
+		Mmsb = 3<<0,	/* f number msb */
+		Moct = 7<<2,
+		Mkon = 1<<5,
+	Ropm = 0xbd,
+		Mamp = 1<<7,	/* amplitude mod depth */
+		Mvib = 1<<6,	/* vibrato depth */
+		Mrms = 63<<0,
+		Mrhy = 1<<5,	/* rhythm enable */
+		Mbas = 1<<4,
+		Msna = 1<<3,
+		Mtom = 1<<2,
+		Mcym = 1<<1,
+		Mhat = 1<<0,
+	Rfed = 0xc0,
+		Mmod = 1<<0,	/* enable operator modulation */
+		Mfed = 7<<1,	/* feedback strength */
+	Rwav = 0xe0,
+		Mwav = 3<<0,
+
+	Sfreq = 16,
+	Sfreqd = Sfreq - 10,
+	Seg = 16,
+	Slfo = 24,
+	Stm = 16,
+
+	Mfreq = (1 << Sfreq) - 1,
+
+	Lampb = 12,
+	Lsignb = 2,
+	Ly = Lampb * Lsignb,
+	Lx = 256,
+	Lxy = Lx * Ly,
+
+	Sinb = 10,
+	Sinlen = 1<<Sinb,
+	Sinm = Sinlen - 1,
+	Nsines = 4,
+
+	Erstep = 8,
+	Equiet = Lxy >> 4,
+	Eb = 10,
+	Elen = 1 << Eb,
+	Nerates = 16 + 16 * 4 + 16,	/* infinite, regular, dummy */
+	Nlfoa = 210,
+	Attmax = (1 << Eb-1) - 1,
+	Attmin = 0
+};
+#define Clk	3579545.0
+#define Estep	(128.0/Elen)
+
+enum{
+	Eoff,
+	Erel,
+	Esus,
+	Edec,
+	Eatk,
+};
+struct Envelope{
+	int state;
+	int key;
+	int son;
+	int sus;
+	int atksh;
+	int atk;
+	int decsh;
+	int dec;
+	int relsh;
+	int rel;
+	int lvl0;
+	int lvl;
+	s32int vol;
+	u32int tl;
+};
+struct Phase{
+	u32int fq;
+	u32int dfq;
+	int fbv;
+	int opmodoff;
+	int out[2];
+	int *p;
+};
+struct Op{
+	Envelope;
+	Phase;
+	int Aon;
+	int φon;
+	int atkn;
+	int decn;
+	int reln;
+	int mul;
+	int scl;
+	int ks;
+	int kssh;
+	u16int wav;
+};
+struct Chan{
+	Op *op;
+	u32int scl0;
+	u32int dfq0;
+	int fn;
+	int oct;
+	int kcode;
+};
+static int lta[Lxy];
+static uint sint[Sinlen * Nsines];
+static int wse, nse, rhythm;
+static u32int noise, noiseφ, noiseT;
+static u32int egtic, egt, egdt, egfdt;
+static u32int lfoA, lfoAt, lfoAdt, lfoφt, lfoφdt;
+static s32int lfoφ;
+static int lfoAd, lfoφd;
+static Chan chs[9];
+static Op ops[2*nelem(chs)];
+static int φmod, tout;
+
+static u32int fn[1024];	/* 20bit φ increment counter */
+/* 27 output levels (triangle waveform), 1 level takes one of 192, 256 or 448
+ * samples. each value is repeated on 64 consecutive samples. total length is
+ * then 64*210 samples. when am=1 data is used directly, else it is shl 2 before
+ * use. */
+static u8int lfoAs[Nlfoa] = {
+	0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5,
+	5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 8, 9, 9, 9, 9, 10, 10, 10, 10,
+	11, 11, 11, 11, 12, 12, 12, 12, 13, 13, 13, 13, 14, 14, 14, 14, 15, 15,
+	15, 15, 16, 16, 16, 16, 17, 17, 17, 17, 18, 18, 18, 18, 19, 19, 19, 19,
+	20, 20, 20, 20, 21, 21, 21, 21, 22, 22, 22, 22, 23, 23, 23, 23, 24, 24,
+	24, 24, 25, 25, 25, 25, 26, 26, 26, 25, 25, 25, 25, 24, 24, 24, 24, 23,
+	23, 23, 23, 22, 22, 22, 22, 21, 21, 21, 21, 20, 20, 20, 20, 19, 19, 19,
+	19, 18, 18, 18, 18, 17, 17, 17, 17, 16, 16, 16, 16, 15, 15, 15, 15, 14,
+	14, 14, 14, 13, 13, 13, 13, 12, 12, 12, 12, 11, 11, 11, 11, 10, 10, 10,
+	10, 9, 9, 9, 9, 8, 8, 8, 8, 7, 7, 7, 7, 6, 6, 6, 6, 5, 5, 5, 5, 4, 4, 4,
+	4, 3, 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1
+};
+/* 8 output 1024 samples long levels (triangle waveform) with 16 values (depth 0
+ * then depth 1) each */
+static s8int lfoφs[8*8*2] = {
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, -1, 0, 0, 0,
+	1, 0, 0, 0, -1, 0, 0, 0, 2, 1, 0, -1, -2, -1, 0, 1,
+	1, 0, 0, 0, -1, 0, 0, 0, 3, 1, 0, -1, -3, -1, 0, 1,
+	2, 1, 0, -1, -2, -1, 0, 1, 4, 2, 0, -2, -4, -2, 0, 2,
+	2, 1, 0, -1, -2, -1, 0, 1, 5, 2, 0, -2, -5, -2, 0, 2,
+	3, 1, 0, -1, -3, -1, 0, 1, 6, 3, 0, -3, -6, -3, 0, 3,
+	3, 1, 0, -1, -3, -1, 0, 1, 7, 3, 0, -3, -7, -3, 0, 3
+};
+static uchar mul[16] = {
+	1, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 20, 24, 24, 30, 30
+};
+#define O	(0.1875 / 2.0)	/* convert 3 dB/oct → 6 dB/oct */
+static u32int lsca[8 * 16] = {	/* 0.1875: bit0 weight of op->vol in dB */
+	0.000/O, 0.000/O, 0.000/O, 0.000/O, 0.000/O, 0.000/O, 0.000/O, 0.000/O,
+	0.000/O, 0.000/O, 0.000/O, 0.000/O, 0.000/O, 0.000/O, 0.000/O, 0.000/O,
+	0.000/O, 0.000/O, 0.000/O, 0.000/O, 0.000/O, 0.000/O, 0.000/O, 0.000/O,
+	0.000/O, 0.750/O, 1.125/O, 1.500/O, 1.875/O, 2.250/O, 2.625/O, 3.000/O,
+	0.000/O, 0.000/O, 0.000/O, 0.000/O, 0.000/O, 1.125/O, 1.875/O, 2.625/O,
+	3.000/O, 3.750/O, 4.125/O, 4.500/O, 4.875/O, 5.250/O, 5.625/O, 6.000/O,
+	0.000/O, 0.000/O, 0.000/O, 1.875/O, 3.000/O, 4.125/O, 4.875/O, 5.625/O,
+	6.000/O, 6.750/O, 7.125/O, 7.500/O, 7.875/O, 8.250/O, 8.625/O, 9.000/O,
+	0.000/O, 0.000/O, 3.000/O, 4.875/O, 6.000/O, 7.125/O, 7.875/O, 8.625/O,
+	9.000/O, 9.750/O, 10.125/O, 10.500/O, 10.875/O, 11.250/O, 11.625/O,
+	12.000/O,
+	0.000/O, 3.000/O, 6.000/O, 7.875/O, 9.000/O, 10.125/O, 10.875/O,
+	11.625/O, 12.000/O, 12.750/O, 13.125/O, 13.500/O, 13.875/O, 14.250/O,
+	14.625/O, 15.000/O,
+	0.000/O, 6.000/O, 9.000/O, 10.875/O, 12.000/O, 13.125/O, 13.875/O,
+	14.625/O, 15.000/O, 15.750/O, 16.125/O, 16.500/O, 16.875/O, 17.250/O,
+	17.625/O, 18.000/O,
+	0.000/O, 9.000/O, 12.000/O, 13.875/O, 15.000/O, 16.125/O, 16.875/O,
+	17.625/O, 18.000/O, 18.750/O, 19.125/O, 19.500/O, 19.875/O, 20.250/O,
+	20.625/O, 21.000/O
+};
+#undef O
+#define O(n)	(n * (2.0/Estep))	/* n*3 dB */
+static u32int sust[16] = {
+	O(0), O(1), O(2), O(3), O(4), O(5), O(6), O(7), O(8), O(9), O(10),
+	O(11), O(12), O(13), O(14), O(31)
+};
+#undef O
+static uchar estep[15*Erstep] = {
+	0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1,
+	0, 1, 1, 1, 1, 1, 1, 1,	/* rate 0-12 */
+	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2,
+	1, 2, 2, 2, 1, 2, 2, 2,	/* rate 13 */
+	2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 4, 2, 2, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4,
+	2, 4, 4, 4, 2, 4, 4, 4, /* rate 14 */
+	4, 4, 4, 4, 4, 4, 4, 4, 8, 8, 8, 8, 8, 8, 8, 8, /* rate 15 + atk */
+	0, 0, 0, 0, 0, 0, 0, 0, /* rate ∞ (atk/dec) */
+};
+#define O(n)	(n * Erstep)
+static uchar erate[Nerates] = {	/* O(13) is directly in code */
+	O(14), O(14), O(14), O(14), O(14), O(14), O(14), O(14), O(14), O(14),
+	O(14), O(14), O(14), O(14), O(14), O(14), O(0), O(1), O(2), O(3), O(0),
+	O(1), O(2), O(3), O(0), O(1), O(2), O(3), O(0), O(1), O(2), O(3), O(0),
+	O(1), O(2), O(3), O(0), O(1), O(2), O(3), O(0), O(1), O(2), O(3), O(0),
+	O(1), O(2), O(3), O(0), O(1), O(2), O(3), O(0), O(1), O(2), O(3), O(0),
+	O(1), O(2), O(3), O(0), O(1), O(2), O(3), O(0), O(1), O(2), O(3), O(4),
+	O(5), O(6), O(7), O(8), O(9), O(10), O(11), O(12), O(12), O(12), O(12),
+	O(12), O(12), O(12), O(12), O(12), O(12), O(12), O(12), O(12), O(12),
+	O(12), O(12), O(12), O(12), O(12), O(12)
+};
+#undef O
+static uchar eratesh[Nerates] = {
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 12, 12, 12, 11, 11,
+	11, 11, 10, 10, 10, 10, 9, 9, 9, 9, 8, 8, 8, 8, 7, 7, 7, 7, 6, 6, 6, 6,
+	5, 5, 5, 5, 4, 4, 4, 4, 3, 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0
+};
+
+/* 23-bit shift register noise generator. T=2²³-2 samples, sampling rate equal
+ * to that of the chip. */
+static void
+noiseg(void)
+{
+	int i;
+
+	noiseφ += noiseT;
+	i = noiseφ >> Sfreq;
+	noiseφ &= Mfreq;
+	while(i-- > 0){
+		if(noise & 1)
+			noise ^= 0x800302;
+		noise >>= 1;
+	}
+}
+
+static void
+advlfo(void)
+{
+	int a;
+
+	lfoAt += lfoAdt;
+	if(lfoAt >= (u32int)Nlfoa << Slfo)
+		lfoAt -= (u32int)Nlfoa << Slfo;
+	a = lfoAs[lfoAt >> Slfo];
+	lfoA = lfoAd ? a : a>>2;
+	lfoφt += lfoφdt;
+	lfoφ = lfoφt >> Slfo & 7 | lfoφd;
+}
+
+static void
+adv(void)
+{
+	int i, f, oc, v;
+	Chan *c;
+	Op *o;
+
+	egt += egfdt;
+	while(egt >= egdt){
+		egt -= egdt;
+		egtic++;
+		o = ops;
+		while(o < ops + nelem(ops)){
+			switch(o->state){
+			case Eatk:
+				if(egtic & (1 << o->atksh)-1)
+					break;
+				v = o->atk + (egtic >> o->atksh & 7);
+				/* sign extend it */
+				o->vol += (s32int)(~o->vol * estep[v]) >> 3;
+				if(o->vol <= Attmin){
+					o->vol = Attmin;
+					o->state = Edec;
+				}
+				break;
+			case Edec:
+				if(egtic & (1 << o->decsh)-1)
+					break;
+				v = o->dec + (egtic >> o->decsh & 7);
+				o->vol += estep[v];
+				if(o->vol >= o->sus)
+					o->state = Esus;
+				break;
+			case Esus:
+				if(o->son || egtic & (1 << o->relsh)-1)
+					break;
+				v = o->rel + (egtic >> o->relsh & 7);
+				o->vol += estep[v];
+				if(o->vol >= Attmax)
+					o->vol = Attmax;
+				break;
+			case Erel:
+				if(egtic & (1 << o->relsh)-1)
+					break;
+				v = o->rel + (egtic >> o->relsh & 7);
+				o->vol += estep[v];
+				if(o->vol >= Attmax){
+					o->vol = Attmax;
+					o->state = Eoff;
+				}
+				break;
+			}
+			o++;
+		}
+	}
+	o = ops;
+	while(o < ops + nelem(ops)){
+		c = chs + (o-ops)/2;
+		if(o->φon){
+			f = c->fn;
+			i = lfoφs[lfoφ + (f >> 7 << 4)];
+			if(i != 0){
+				f += i;
+				oc = 7 - c->oct + (f >> 10);
+				o->fq += (fn[f & 0x3ff] >> oc) * o->mul;
+			}else
+				o->fq += o->dfq;
+		}else
+			o->fq += o->dfq;
+		o++;
+	}
+	noiseg();
+}
+
+static int
+op(u32int φ, u32int v, int dφ, u16int w)
+{
+	u32int p;
+
+	p = (v<<4) + sint[w + ((int)((φ & ~Mfreq) + dφ) >> Sfreq & Sinm)];
+	return p < Lxy ? lta[p] : 0;
+}
+
+static void
+chan(Op *o)
+{
+	u32int v;
+	int out;
+
+	φmod = 0;
+	out = o->out[0] + o->out[1];
+	o->out[0] = o->out[1];
+	*o->p += o->out[0];
+	o->out[1] = 0;
+	v = o->tl;
+	if(v < Equiet){
+		if(o->fbv == 0)
+			out = 0;
+		o->out[1] = op(o->fq, v, out << o->fbv, o->wav);
+	}
+	o++, v = o->tl;
+	if(v < Equiet)
+		tout += op(o->fq, v, φmod << 16, o->wav);
+}
+
+static void
+rchan(void)
+{
+	int out, nr;
+	u32int u, v;
+	Op *o;
+
+	nr = noise & 1;
+	φmod = 0;
+	o = chs[6].op;
+	out = o->out[0] + o->out[1];
+	o->out[0] = o->out[1];
+	if(o->opmodoff == 0)
+		φmod = o->out[0];
+	o->out[1] = 0;
+
+	v = o->tl;
+	if(v < Equiet){
+		if(o->fbv == 0)
+			out = 0;
+		o->out[1] = op(o->fq, v, out << o->fbv, o->wav);
+	}
+	o++, v = o->tl;
+	if(v < Equiet)
+		tout += op(o->fq, v, φmod << 16, o->wav) * 2;
+	o++, v = o->tl;
+	if(v < Equiet){
+		u = ops[17].fq >> Sfreq;
+		if((u ^ u<<2) & 1<<5)
+			u = 0x200 | 0xd0 >> 2;
+		else{
+			u = o->fq >> Sfreq;
+			u = ((u ^ u<<5 | u<<4) & 1<<7) ? 0x200 | 0xd0>>2 : 0xd0;
+		}
+		if(nr)
+			u = u & 0x200 ? 0x200 | 0xd0 : 0xd0>>2;
+		tout += op(u << Sfreq, v, 0, o->wav) * 2;
+	}
+	o++, v = o->tl;
+	if(v < Equiet){
+		u = (ops[14].fq >> Sfreq & 1<<8) ? 1<<9 : 1<<8;
+		u = (u ^ nr << 8) << Sfreq;
+		tout += op(u, v, 0, o->wav) * 2;
+	}
+	o++, v = o->tl;
+	if(v < Equiet)
+		tout += op(o->fq, v, 0, o->wav) * 2;
+	o++, v = o->tl;
+	if(v < Equiet){
+		u = o->fq >> Sfreq;
+		if((u ^ u<<2) & 1<<5)
+			u = 0x300;
+		else{
+			u = ops[14].fq >> Sfreq;
+			u = ((u ^ u<<5 | u<<4) & 1<<7) ? 0x300 : 0x100;
+		}
+		tout += op(u << Sfreq, v, 0, o->wav) * 2;
+	}
+}
+
+static void
+setk(Op *o, int k, int on)
+{
+	if(on){
+		if(o->key == 0){
+			o->fq = 0;
+			o->state = Eatk;
+		}
+		o->key |= k;
+	}else{
+		if(o->key == 0)
+			return;
+	
+		o->key &= ~k;
+		if(o->key == 0 && o->state > Erel)
+			o->state = Erel;
+	}
+}
+
+static void
+upop(Chan *c, Op *o)
+{
+	int n;
+
+	o->dfq = c->dfq0 * o->mul;
+	n = c->kcode >> o->kssh;
+
+	if(o->ks != n){
+		o->ks = n;
+		if(o->atkn + n < 16+62){
+			o->atksh = eratesh[o->atkn + n];
+			o->atk = erate[o->atkn + n];
+		}else{
+			o->atksh = 0;
+			o->atk = 13 * Erstep;
+		}
+		o->decsh = eratesh[o->decn + n];
+		o->dec = erate[o->decn + n];
+		o->relsh = eratesh[o->reln + n];
+		o->rel = erate[o->reln + n];
+	}
+}
+
+static void
+ctl(Chan *c, Op *o, int v)
+{
+	o->mul = mul[v & Mmfq];
+	o->kssh = v & Mkse ? 0 : 2;
+	o->son = v & Msse;
+	o->φon = v & Mvbe;
+	o->Aon = v & Mame ? ~0 : 0;
+	upop(c, o);
+}
+
+static void
+sca(Chan *c, Op *o, int v)
+{
+	uchar u;
+
+	u = v >> 6;
+	o->scl = u ? u % 3 : 31;	/* shift to 0, 3, 1.5, 6 dB/oct */
+	o->lvl0 = (v & Mlvl) << Eb-1-7;
+	o->lvl = o->lvl0 + (c->scl0 >> o->scl);
+}
+
+static void
+atkdec(Op *o, int v)
+{
+	int a, n;
+
+	n = o->ks;
+	o->atkn = a = v >> 4 ? 16 + (v >> 4 << 2) : 0;
+	a += n;
+	if(a < 16+62){
+		o->atksh = eratesh[a];
+		o->atk = erate[a];
+	}else{
+		o->atksh = 0;
+		o->atk = 13 * Erstep;
+	}
+	o->decn = a = v & Mdec ? 16 + ((v & Mdec) << 2) : 0;
+	a += n;
+	o->decsh = eratesh[a];
+	o->dec = erate[a];
+}
+
+static void
+susrel(Op *o, int v)
+{
+	o->sus = sust[v >> 4];
+	o->reln = v & Mrel ? 16 + ((v & Mrel) << 2) : 0;
+	o->relsh = eratesh[o->reln + o->ks];
+	o->rel = erate[o->reln + o->ks];
+}
+
+static void
+opm(int v)
+{
+	lfoAd = v & Mamp;
+	lfoφd = v & Mvib ? 8 : 0;
+	rhythm = v & Mrms;
+	if(v & ~Mrhy)
+		v = 0;
+	setk(chs[6].op, 2, v & Mbas);
+	setk(chs[6].op+1, 2, v & Mbas);
+	setk(chs[7].op, 2, v & Mhat);
+	setk(chs[7].op+1, 2, v & Msna);
+	setk(chs[8].op, 2, v & Mtom);
+	setk(chs[8].op+1, 2, v & Mcym);
+}
+
+static void
+foct(int r, int v)
+{
+	int n, b, o, f;
+	u32int u;
+	Chan *c;
+	Op *op0, *op1;
+
+	n = r & 0xf;
+	if(n > 8)
+		return;
+	c = chs+n;
+	op0 = c->op;
+	op1 = op0 + 1;
+	o = c->oct;
+	if(r & 0x10){	/* Roct */
+		f = c->fn & 0xff | (v & Mmsb) << 8;
+		o = (v & Moct) >> 2;
+		setk(op0, 1, v & Mkon);
+		setk(op1, 1, v & Mkon);
+	}else
+		f = c->fn & 0x300 | v;
+	b = f | o << 10;
+
+	if(c->fn != f || c->oct != o){
+		c->fn = f;
+		c->oct = o;
+		c->scl0 = u = lsca[b >> 6];
+		c->dfq0 = fn[f] >> 7 - o;
+		/* ignore manual full of lies */
+		c->kcode = o << 1 | (nse ? f >> 8 : f >> 9) & 1;
+		op0->lvl = op0->lvl0 + (u >> op0->scl);
+		op1->lvl = op1->lvl0 + (u >> op1->scl);
+		upop(c, op0);
+		upop(c, op1);
+	}
+}
+
+static void
+feedb(int r, int v)
+{
+	int n;
+	Op *o;
+
+	if(r > 8)
+		return;
+	n = v & Mmod;
+	v = (v & Mfed) >> 1;
+	o = chs[r].op;
+	o->opmodoff = n;
+	o->fbv = v ? v + 7 : 0;
+	o->p = n ? &tout : &φmod;
+}
+
+static void
+tab(void)
+{
+	int i, x, n, *l, *p;
+	uint *s;
+	double o, m;
+
+	for(x=0; x<Lx; x++){
+		m = (1<<16) / pow(2, (x+1) * (Estep/4.0) / 8.0);
+		m = floor(m);
+		n = (int)m >> 4;     /* always fits in 12 (16) bits */
+		n = (n>>1) + (n&1) << 1;	/* rnr */
+		l = lta + x*2;
+		l[0] = n;
+		l[1] = -n;
+		for(i=1, p=l; i<12; i++){
+			p += 2*Lx;
+			p[0] = n >> i;
+			p[1] = -(n >> i);
+		}
+	}
+
+	for(i=0, s=sint; i<Sinlen; i++){
+		m = sin((i*2+1) * PI / Sinlen);
+		o = 8 * log((m > 0.0 ? 1.0 : -1.0)/m) / log(2.0);	/* dB */
+		o /= Estep / 4.0;
+		n = o * 2.0;
+		n = (n>>1) + (n&1);	/* rnr */
+		*s++ = n*2 + (m >= 0.0 ? 0 : 1);
+	}
+	for(i=0, s=sint; i<Sinlen; i++, s++){
+		/* half-sine, abs-sine, pulse-sine */
+		s[Sinlen] = i & 1<<Sinb-1 ? Lxy : *s;
+		s[Sinlen*2] = sint[i & Sinm>>1];
+		s[Sinlen*3] = i & 1<<Sinb-2 ? Lxy : sint[i & Sinm>>2];
+	}
+}
+
+uchar *
+opl2out(uchar *s, int n)
+{
+	int v, r;
+	uchar *e;
+	Op *o;
+
+	r = rhythm & Mrhy;
+	e = s + n;
+	while(s < e){
+		tout = 0;
+		advlfo();
+		for(o=ops; o<ops+nelem(ops); o++)
+			o->tl = o->lvl + (u32int)o->vol + (lfoA & o->Aon);
+		for(o=ops; o<ops+nelem(ops); o+=2){
+			if(o == ops+6*2 && r){
+				rchan();
+				break;
+			}
+			chan(o);
+		}
+		v = tout * 2;
+		if(v > 32767)
+			v = 32767;
+		else if(v < -32768)
+			v = -32768;
+		s[0] = s[2] = v;
+		s[1] = s[3] = v>>8;
+		s += 4;
+		adv();
+	}
+	return s;
+}
+
+void
+opl2wr(int r, int v)
+{
+	int n;
+	Op *o;
+	Chan *c;
+
+	v &= 0xff;
+	n = r & 0x1f;
+	c = chs + ((n>>3)+(n>>3<<1) + (n+(n+1>>2&1)&3));
+	o = c->op + (n + 1 >> 2 & 1);
+	n = r & 6 ^ 6 && n < 22;
+	switch(r){
+	case Rwse: wse = v & Mwse; return;
+	case Rcsm: nse = v & Mnse; return;
+	case Ropm: opm(v); return;
+	}
+	switch(r & 0xe0){
+	case Rctl: if(n) ctl(c, o, v); break;
+	case Rsca: if(n) sca(c, o, v); break;
+	case Ratk: if(n) atkdec(o, v); break;
+	case Rsus: if(n) susrel(o, v); break;
+	case Rnum: foct(r, v); break;
+	case Rfed: feedb(r & 0xf, v); break;
+	case Rwav: if(wse && n) o->wav = (v & Mwav) * Sinlen; break;
+	}
+}
+
+void
+opl2init(int rate)
+{
+	int i;
+	u32int *fp;
+	double f0, n;
+	Chan *c;
+	Op *o;
+
+	tab();
+	f0 = (Clk / 72.0) / rate;
+	fp = fn;
+	n = 0;
+	while(fp < fn+nelem(fn))
+		*fp++ = n++ * 64 * f0 * (1 << Sfreqd);
+	lfoAdt = (1.0 / 64.0) * (1 << Slfo) * f0;
+	lfoφdt = (1.0 / 1024.0) * (1 << Slfo) * f0;
+	noiseT = 1.0 * (1 << Sfreq) * f0;
+	egfdt = (1 << Seg) * f0;
+	egdt = 1 << Seg;
+	noise = 1;
+
+	c = chs;
+	o = ops;
+	while(c < chs+nelem(chs)){
+		c++->op = o;
+		o->state = Eoff;
+		o++->vol = Attmax;
+		o->state = Eoff;
+		o++->vol = Attmax;
+	}
+	for(i=Rctl; i<Rwav+22; i++)
+		opl2wr(i, 0);
+}
--- /dev/null
+++ b/opl2m.c
@@ -1,0 +1,57 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+
+uchar*	opl2out(uchar *, int);
+void	opl2wr(int, int);
+void	opl2init(int);
+
+enum{
+	Rate = 44100,
+};
+
+void
+usage(void)
+{
+	fprint(2, "usage: %s [-n nsamp] [file]\n", argv0);
+	exits("usage");
+}
+
+void
+main(int argc, char **argv)
+{
+	int r, v, dt, nsamp, fd;
+	uchar *sb, u[4];
+	Biobuf *bi, *bo;
+
+	fd = 0;
+	nsamp = Rate / 700;
+	ARGBEGIN{
+	case 'n':
+		nsamp = Rate / atoi(EARGF(usage()));
+		break;
+	default:
+		usage();
+	}ARGEND;
+	if(*argv != nil)
+		if((fd = open(*argv, OREAD)) < 0)
+			sysfatal("open: %r");
+	bi = Bfdopen(fd, OREAD);
+	bo = Bfdopen(1, OWRITE);
+	if(bi == nil || bo == nil)
+		sysfatal("Bfdopen: %r");
+	nsamp *= 4;
+	if((sb = malloc(nsamp)) == nil)
+		sysfatal("malloc: %r");
+	opl2init(Rate);
+	while(Bread(bi, u, sizeof u) > 0){
+		r = u[0];
+		v = u[1];
+		dt = u[3]<<8 | u[2];
+		opl2wr(r, v);
+		while(dt-- > 0){
+			opl2out(sb, nsamp);
+			Bwrite(bo, sb, nsamp);
+		}
+	}
+}