ref: 7c028c65bbb2febbfcd47122ed54ab51c53e24b4
author: qwx <qwx@sciops.net>
date: Mon May 27 19:23:00 EDT 2019
initial import
--- /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);
+ }
+ }
+}