shithub: omidi

Download patch

ref: 0b7b60511d71e3c80deb8d1900e1ec778d964c0c
author: qwx <qwx@sciops.net>
date: Mon May 27 19:51:52 EDT 2019

initial import

--- /dev/null
+++ b/README
@@ -1,0 +1,6 @@
+based on opl3emu2 version 1.1.1 by Joel Yliluoma, itself based on fmopl.c
+version 0.72 from MAME, which is:
+Copyright (C) 2002,2003 Jarek Burczynski (bujar at mame dot net)
+Copyright (C) 1999,2000 Tatsuyuki Satoh , MultiArcadeMachineEmulator development
+
+this is a... work in progress. use at your own peril.
--- /dev/null
+++ b/dat.h
@@ -1,0 +1,443 @@
+/*
+ *      The OPL-3 mode is switched on by writing 0x01, to the offset 5
+ *      of the right side.
+ *
+ *      Another special register at the right side is at offset 4. It contains
+ *      a bit mask defining which voices are used as 4 OP voices.
+ *
+ *      The percussive mode is implemented in the left side only.
+ *
+ *      With the above exceptions the both sides can be operated independently.
+ * 
+ *      A 4 OP voice can be created by setting the corresponding
+ *      bit at offset 4 of the right side.
+ *
+ *      For example setting the rightmost bit (0x01) changes the
+ *      first voice on the right side to the 4 OP mode. The fourth
+ *      voice is made inaccessible.
+ *
+ *      If a voice is set to the 2 OP mode, it works like 2 OP modes
+ *      of the original YM3812 (AdLib). In addition the voice can 
+ *      be connected the left, right or both stereo channels. It can
+ *      even be left unconnected. This works with 4 OP voices also.
+ *
+ *      The stereo connection bits are located in the FEEDBACK_CONNECTION
+ *      register of the voice (0xC0-0xC8). In 4 OP voices these bits are
+ *      in the second half of the voice.
+ */
+
+/*
+ *      Register numbers for the global registers
+ */
+
+#define TEST_REGISTER                           0x01
+#define   ENABLE_WAVE_SELECT            0x20
+
+#define TIMER1_REGISTER                         0x02
+#define TIMER2_REGISTER                         0x03
+#define TIMER_CONTROL_REGISTER                  0x04    /* Left side */
+#define   IRQ_RESET                     0x80
+#define   TIMER1_MASK                   0x40
+#define   TIMER2_MASK                   0x20
+#define   TIMER1_START                  0x01
+#define   TIMER2_START                  0x02
+
+#define CONNECTION_SELECT_REGISTER              0x04    /* Right side */
+#define   RIGHT_4OP_0                   0x01
+#define   RIGHT_4OP_1                   0x02
+#define   RIGHT_4OP_2                   0x04
+#define   LEFT_4OP_0                    0x08
+#define   LEFT_4OP_1                    0x10
+#define   LEFT_4OP_2                    0x20
+
+#define OPL3_MODE_REGISTER                      0x05    /* Right side */
+#define   OPL3_ENABLE                   0x01
+#define   OPL4_ENABLE                   0x02
+
+#define KBD_SPLIT_REGISTER                      0x08    /* Left side */
+#define   COMPOSITE_SINE_WAVE_MODE      0x80            /* Don't use with OPL-3? */
+#define   KEYBOARD_SPLIT                0x40
+
+#define PERCOSSION_REGISTER                     0xbd    /* Left side only */
+#define   TREMOLO_DEPTH                 0x80
+#define   VIBRATO_DEPTH                 0x40
+#define   PERCOSSION_ENABLE             0x20
+#define   BASSDRUM_ON                   0x10
+#define   SNAREDRUM_ON                  0x08
+#define   TOMTOM_ON                     0x04
+#define   CYMBAL_ON                     0x02
+#define   HIHAT_ON                      0x01
+
+/*
+ *      Offsets to the register banks for operators. To get the
+ *      register number just add the operator offset to the bank offset
+ *
+ *      AM/VIB/EG/KSR/Multiple (0x20 to 0x35)
+ */
+enum{
+	AM_VIB = 0x20,
+	TREMOLO_ON = 0x80,
+	VIBRATO_ON = 0x40,
+	SUSTAIN_ON = 0x20,
+	KSR = 0x10,	/* Key scaling rate */
+	MULTIPLE_MASK = 0x0f	/* Frequency multiplier */
+};
+
+ /*
+  *     KSL/Total level (0x40 to 0x55)
+  */
+#define KSL_LEVEL                               0x40
+#define   KSL_MASK                      0xc0    /* Envelope scaling bits */
+#define   TOTAL_LEVEL_MASK              0x3f    /* Strength (volume) of OP */
+
+/*
+ *      Attack / Decay rate (0x60 to 0x75)
+ */
+#define ATTACK_DECAY                            0x60
+#define   ATTACK_MASK                   0xf0
+#define   DECAY_MASK                    0x0f
+
+/*
+ * Sustain level / Release rate (0x80 to 0x95)
+ */
+#define SUSTAIN_RELEASE                         0x80
+#define   SUSTAIN_MASK                  0xf0
+#define   RELEASE_MASK                  0x0f
+
+/*
+ * Wave select (0xE0 to 0xF5)
+ */
+#define WAVE_SELECT                     0xe0
+
+/*
+ *      Offsets to the register banks for voices. Just add to the
+ *      voice number to get the register number.
+ *
+ *      F-Number low bits (0xA0 to 0xA8).
+ */
+#define FNUM_LOW                                0xa0
+
+/*
+ *      F-number high bits / Key on / Block (octave) (0xB0 to 0xB8)
+ */
+#define KEYON_BLOCK                                     0xb0
+#define   KEYON_BIT                             0x20
+#define   BLOCKNUM_MASK                         0x1c
+#define   FNUM_HIGH_MASK                        0x03
+
+/*
+ *      Feedback / Connection (0xc0 to 0xc8)
+ *
+ *      These registers have two new bits when the OPL-3 mode
+ *      is selected. These bits controls connecting the voice
+ *      to the stereo channels. For 4 OP voices this bit is
+ *      defined in the second half of the voice (add 3 to the
+ *      register offset).
+ *
+ *      For 4 OP voices the connection bit is used in the
+ *      both halves (gives 4 ways to connect the operators).
+ */
+#define FEEDBACK_CONNECTION                             0xc0
+#define   FEEDBACK_MASK                         0x0e    /* Valid just for 1st OP of a voice */
+#define   CONNECTION_BIT                        0x01
+/*
+ *      In the 4 OP mode there is four possible configurations how the
+ *      operators can be connected together (in 2 OP modes there is just
+ *      AM or FM). The 4 OP connection mode is defined by the rightmost
+ *      bit of the FEEDBACK_CONNECTION (0xC0-0xC8) on the both halves.
+ *
+ *      First half      Second half     Mode
+ *
+ *                                       +---+
+ *                                       v   |
+ *      0               0               >+-1-+--2--3--4-->
+ *
+ *
+ *                               
+ *                                       +---+
+ *                                       |   |
+ *      0               1               >+-1-+--2-+
+ *                                                |->
+ *                                      >--3----4-+
+ *                               
+ *                                       +---+
+ *                                       |   |
+ *      1               0               >+-1-+-----+
+ *                                                 |->
+ *                                      >--2--3--4-+
+ *
+ *                                       +---+
+ *                                       |   |
+ *      1               1               >+-1-+--+
+ *                                              |
+ *                                      >--2--3-+->
+ *                                              |
+ *                                      >--4----+
+ */
+#define   STEREO_BITS                           0x30    /* OPL-3 only */
+#define     VOICE_TO_LEFT               0x10
+#define     VOICE_TO_RIGHT              0x20
+
+enum{
+	MAXINST = 100,	/* FIXME: what the fuck is this? */
+	MAXPATN = 100,
+	MAXCHN = 15  /* Remember that the percussion channel is reserved! */
+};
+
+typedef struct MusPlayerPos MusPlayerPos;
+typedef struct Pattern Pattern;
+typedef struct InternalHdr InternalHdr;
+typedef struct InternalSample InternalSample;
+typedef struct MusData MusData;
+
+struct MusPlayerPos{
+	int Ord;
+	int Pat;
+	int Row;
+	char *Pos;
+	int Wait;
+};
+
+struct Pattern{
+	ushort Len;
+	char *Ptr;
+};
+
+struct InternalHdr{
+    ushort OrdNum;      /* Number of orders, is even                */
+                      /* If bit 8 (&256) is set, there exist no   */
+                      /* notes between 126..253 and the 7th bit   */
+                      /* of note value can be used to tell if     */
+                      /* there is instrument number.              */
+    ushort InsNum;      /* Number of instruments                    */
+    ushort PatNum;      /* Number of patterns                       */
+                      /* If bit 9 set, all instruments have       */
+                      /* the insname[] filled with AsciiZ name.   */
+    ushort Ins2Num;     /* Number of instruments - for checking!    */
+    uchar InSpeed;     /* Initial speed in 7 lowermost bits        */
+                      /* If highest bit (&128) set, no logaritmic */
+                      /* adlib volume conversion needs to be done */
+                      /* when playing with midi device.           */
+    uchar InTempo;     /* Initial tempo                            */
+    uchar ChanCount[];	/* ChanCount[0] present if PatNum bit 9 set */
+}; /* Size: 2+2+2+2+1+1 = 8+2 = 10 */
+
+struct InternalSample{
+	/* unused */
+	char FT;     /*finetune value         1         */
+						/*  signed seminotes.              */
+	uchar D[11];         /*Identificators         11        */
+						/* - 12nd byte not needed.         */
+						/* six high bits of D[8]           */
+						/* define the midi volume          */
+						/* scaling level                   */
+						/* - default is 63.                */
+						/* D[9] bits 5..2 have             */
+						/* the automatical SDx             */
+						/* adjust value.                   */
+						/* - default is 0.                 */
+                        /* D[9] bits 6-7 are free.         */
+	char Volume;        /*0..63                  1         */
+						/* If bit 6 set,                   */
+						/*  the instrument will be         */
+						/*  played simultaneously          */
+						/*  on two channels when           */
+						/*  playing with a midi device.    */
+						/*  To gain more volume.           */
+						/* If bit 7 set,                   */
+						/*  the finetune value             */
+						/*  affects also FM.               */
+						/*  (SMP->AME conversion)          */
+	ushort C2Spd;         /*8363 = normal..?       2         */
+	uchar GM;            /*GM program number      1         */
+    uchar Bank;          /*Bank number, 0=normal. 1         */
+                        /*Highest bit (&128) is free.      */
+    char insname[];
+	                    /*Only if PatNum&512 set           */
+};        /*             total:17 = 0x11     */
+
+struct MusData{
+    int PlayNext;
+        /* Set to 1 every time next line is played.
+           Use it to syncronize screen with music. */
+
+    long RowTimer;
+        /* You can delay the music with this.
+           Or fasten. Units: Rate */
+
+    char *Playing; /* Currently playing music from this buffer. */
+    int Paused;    /* If paused, interrupts normally but does not play. *
+                    * Read only. There's PauseS3M() for this.           */
+    InternalHdr *Hdr;                
+	/* FIXME: >127 for percussion; was set to MAXINST before, causing bad reads; is
+	   this used correctly now? why is it 100? what the fuck is it for??? */
+    InternalSample *Instr[2*MAXINST+1];
+
+    int LinearMidiVol;
+    int GlobalVolume;
+
+    int Speed;
+    int Tempo;
+
+    /* Data from song */
+    char *Orders;
+    Pattern Patn[MAXPATN];
+
+    /* Player: For SBx */
+    MusPlayerPos posi;
+	MusPlayerPos saved;
+    int LoopCount;
+    int PendingSB0;
+    
+    long CurVol[MAXCHN];
+    uchar NNum[MAXCHN];
+    uchar CurPuh[MAXCHN];
+    int NoteCut[MAXCHN];
+    int NoteDelay[MAXCHN];
+    uchar CurInst[MAXCHN];
+    uchar InstNum[MAXCHN]; /* For the instrument number optimizer */
+    uchar PatternDelay;
+
+    char FineVolSlide[MAXCHN];
+    char FineFrqSlide[MAXCHN];
+    char  VolSlide[MAXCHN];
+    char FreqSlide[MAXCHN];
+     uchar VibDepth[MAXCHN];
+     uchar VibSpd  [MAXCHN];
+     uchar VibPos  [MAXCHN];
+    uchar ArpegSpd;
+    uchar Arpeggio[MAXCHN];
+    uchar ArpegCnt[MAXCHN];
+     uchar Retrig[MAXCHN];
+
+    char Active[MAXCHN];
+    char Doubles[MAXCHN];
+
+    uchar DxxDefault[MAXCHN];
+    uchar ExxDefault[MAXCHN];
+    uchar HxxDefault[MAXCHN];
+	uchar MxxDefault[MAXCHN];
+	uchar SxxDefault[MAXCHN];
+
+    ulong Herz[MAXCHN];  /* K�ytt�� m_opl */
+};
+#define FxxDefault ExxDefault /* They really are combined in ST3 */
+
+/* fmopl */
+enum{
+	OPL_SAMPLE_BITS = 16
+};
+
+typedef s16int OPLSAMPLE;	//INT32
+typedef void	(*OPL_TIMERHANDLER)(void *param, int timer, double interval_sec);
+typedef void	(*OPL_IRQHANDLER)(void *param, int irq);
+typedef void	(*OPL_UPDATEHANDLER)(void *param, int min_interval_us);
+typedef void	(*OPL_PORTHANDLER_W)(void *param, uchar data);
+typedef uchar	(*OPL_PORTHANDLER_R)(void *param);
+
+typedef struct OPL_SLOT OPL_SLOT;
+typedef struct OPL_CH OPL_CH;
+typedef struct FM_OPL FM_OPL;
+
+struct OPL_SLOT{
+	u32int  ar;         /* attack rate: AR<<2           */
+	u32int  dr;         /* decay rate:  DR<<2           */
+	u32int  rr;         /* release rate:RR<<2           */
+	u8int   KSR;        /* key scale rate               */
+	u8int   ksl;        /* keyscale level               */
+	u8int   ksr;        /* key scale rate: kcode>>KSR   */
+	u8int   mul;        /* multiple: mul_tab[ML]        */
+
+	/* Phase Generator */
+	u32int  Cnt;        /* frequency counter            */
+	u32int  Incr;       /* frequency counter step       */
+	u8int   FB;         /* feedback shift value         */
+	s32int   *connect1;  /* slot1 output pointer         */
+	s32int   op1_out[2]; /* slot1 output for feedback    */
+	u8int   CON;        /* connection (algorithm) type  */
+
+	/* Envelope Generator */
+	u8int   eg_type;    /* percussive/non-percussive mode */
+	u8int   state;      /* phase type                   */
+	u32int  TL;         /* total level: TL << 2         */
+	s32int   TLL;        /* adjusted now TL              */
+	s32int   volume;     /* envelope counter             */
+	u32int  sl;         /* sustain level: sl_tab[SL]    */
+	u8int   eg_sh_ar;   /* (attack state)               */
+	u8int   eg_sel_ar;  /* (attack state)               */
+	u8int   eg_sh_dr;   /* (decay state)                */
+	u8int   eg_sel_dr;  /* (decay state)                */
+	u8int   eg_sh_rr;   /* (release state)              */
+	u8int   eg_sel_rr;  /* (release state)              */
+	u32int  key;        /* 0 = KEY OFF, >0 = KEY ON     */
+
+	/* LFO */
+	u32int  AMmask;     /* LFO Amplitude Modulation enable mask */
+	u8int   vib;        /* LFO Phase Modulation enable flag (active high)*/
+
+	/* waveform select */
+	u16int  wavetable;
+};
+struct OPL_CH{
+	OPL_SLOT SLOT[2];
+	/* phase generator state */
+	u32int  block_fnum; /* block+fnum                   */
+	u32int  fc;         /* Freq. Increment base         */
+	u32int  ksl_base;   /* KeyScaleLevel Base step      */
+	u8int   kcode;      /* key code (for key scaling)   */
+};
+struct FM_OPL{
+	/* FM channel slots */
+	OPL_CH  P_CH[9];                /* OPL/OPL2 chips have 9 channels*/
+
+	u32int  eg_cnt;                 /* global envelope generator counter    */
+	u32int  eg_timer;               /* global envelope generator counter works at frequency = chipclock/72 */
+	u32int  eg_timer_add;           /* step of eg_timer                     */
+	u32int  eg_timer_overflow;      /* envelope generator timer overlfows every 1 sample (on real chip) */
+
+	u8int   rhythm;                 /* Rhythm mode                  */
+
+	u32int  fn_tab[1024];           /* fnumber->increment counter   */
+
+	/* LFO */
+	u32int  LFO_AM;
+	s32int   LFO_PM;
+
+	u8int   lfo_am_depth;
+	u8int   lfo_pm_depth_range;
+	u32int  lfo_am_cnt;
+	u32int  lfo_am_inc;
+	u32int  lfo_pm_cnt;
+	u32int  lfo_pm_inc;
+
+	u32int  noise_rng;              /* 23 bit noise shift register  */
+	u32int  noise_p;                /* current noise 'phase'        */
+	u32int  noise_f;                /* current noise period         */
+
+	u8int   wavesel;                /* waveform select enable flag  */
+
+	u32int  T[2];                   /* timer counters               */
+	u8int   st[2];                  /* timer enable                 */
+
+	/* external event callback handlers */
+	OPL_TIMERHANDLER  timer_handler;    /* TIMER handler                */
+	void *TimerParam;                   /* TIMER parameter              */
+	OPL_IRQHANDLER    IRQHandler;   /* IRQ handler                  */
+	void *IRQParam;                 /* IRQ parameter                */
+	OPL_UPDATEHANDLER UpdateHandler;/* stream update handler        */
+	void *UpdateParam;              /* stream update parameter      */
+
+	u8int type;                     /* chip type                    */
+	u8int address;                  /* address register             */
+	u8int status;                   /* status flag                  */
+	u8int statusmask;               /* status mask                  */
+	u8int mode;                     /* Reg.08 : CSM,notesel,etc.    */
+
+	u32int clock;                   /* master clock  (Hz)           */
+	u32int rate;                    /* sampling rate (Hz)           */
+	double freqbase;                /* frequency base               */
+	double TimerBase;         /* Timer base time (==sampling time)*/
+
+	s32int phase_modulation;    /* phase modulation input (SLOT 2) */
+	s32int output[1];
+};
--- /dev/null
+++ b/fmopl.c
@@ -1,0 +1,1676 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+#include "dat.h"
+#include "fns.h"
+
+/* output final shift */
+#define FINAL_SH    (0)
+#define MAXOUT      (+32767)
+#define MINOUT      (-32768)
+
+
+#define FREQ_SH         16  /* 16.16 fixed point (frequency calculations) */
+#define EG_SH           16  /* 16.16 fixed point (EG timing)              */
+#define LFO_SH          24  /*  8.24 fixed point (LFO calculations)       */
+#define TIMER_SH        16  /* 16.16 fixed point (timers calculations)    */
+
+#define FREQ_MASK       ((1<<FREQ_SH)-1)
+
+/* envelope output entries */
+#define ENV_BITS        10
+#define ENV_LEN         (1<<ENV_BITS)
+#define ENV_STEP        (128.0/ENV_LEN)
+
+#define MAX_ATT_INDEX   ((1<<(ENV_BITS-1))-1) /*511*/
+#define MIN_ATT_INDEX   (0)
+
+/* sinwave entries */
+#define SIN_BITS        10
+#define SIN_LEN         (1<<SIN_BITS)
+#define SIN_MASK        (SIN_LEN-1)
+
+#define TL_RES_LEN      (256)   /* 8 bits addressing (real chip) */
+
+
+
+/* register number to channel number , slot offset */
+#define SLOT1 0
+#define SLOT2 1
+
+/* Envelope Generator phases */
+
+#define EG_ATT          4
+#define EG_DEC          3
+#define EG_SUS          2
+#define EG_REL          1
+#define EG_OFF          0
+
+
+int acc_calc(signed int value)
+{
+	if (value>=0)
+	{
+		if (value < 0x0200)
+			return (value & ~0);
+		if (value < 0x0400)
+			return (value & ~1);
+		if (value < 0x0800)
+			return (value & ~3);
+		if (value < 0x1000)
+			return (value & ~7);
+		if (value < 0x2000)
+			return (value & ~15);
+		if (value < 0x4000)
+			return (value & ~31);
+		return (value & ~63);
+	}
+	if (value > -0x0200)
+		return (~abs(value) & ~0);
+	if (value > -0x0400)
+		return (~abs(value) & ~1);
+	if (value > -0x0800)
+		return (~abs(value) & ~3);
+	if (value > -0x1000)
+		return (~abs(value) & ~7);
+	if (value > -0x2000)
+		return (~abs(value) & ~15);
+	if (value > -0x4000)
+		return (~abs(value) & ~31);
+	return (~abs(value) & ~63);
+}
+
+#define OPL_TYPE_WAVESEL   0x01  /* waveform select     */
+/* ---------- Generic interface section ---------- */
+#define OPL_TYPE_YM3812 (OPL_TYPE_WAVESEL)
+
+/* mapping of register number (offset) to slot number used by the emulator */
+static int slot_array[32]=
+{
+		0, 2, 4, 1, 3, 5,-1,-1,
+		6, 8,10, 7, 9,11,-1,-1,
+	12,14,16,13,15,17,-1,-1,
+	-1,-1,-1,-1,-1,-1,-1,-1
+};
+
+/* key scale level */
+/* table is 3dB/octave , DV converts this into 6dB/octave */
+/* 0.1875 is bit 0 weight of the envelope counter (volume) expressed in the 'decibel' scale */
+#define DV (0.1875/2.0)
+static u32int ksl_tab[8*16]=
+{
+	/* OCT 0 */
+		0.000/DV, 0.000/DV, 0.000/DV, 0.000/DV,
+		0.000/DV, 0.000/DV, 0.000/DV, 0.000/DV,
+		0.000/DV, 0.000/DV, 0.000/DV, 0.000/DV,
+		0.000/DV, 0.000/DV, 0.000/DV, 0.000/DV,
+	/* OCT 1 */
+		0.000/DV, 0.000/DV, 0.000/DV, 0.000/DV,
+		0.000/DV, 0.000/DV, 0.000/DV, 0.000/DV,
+		0.000/DV, 0.750/DV, 1.125/DV, 1.500/DV,
+		1.875/DV, 2.250/DV, 2.625/DV, 3.000/DV,
+	/* OCT 2 */
+		0.000/DV, 0.000/DV, 0.000/DV, 0.000/DV,
+		0.000/DV, 1.125/DV, 1.875/DV, 2.625/DV,
+		3.000/DV, 3.750/DV, 4.125/DV, 4.500/DV,
+		4.875/DV, 5.250/DV, 5.625/DV, 6.000/DV,
+	/* OCT 3 */
+		0.000/DV, 0.000/DV, 0.000/DV, 1.875/DV,
+		3.000/DV, 4.125/DV, 4.875/DV, 5.625/DV,
+		6.000/DV, 6.750/DV, 7.125/DV, 7.500/DV,
+		7.875/DV, 8.250/DV, 8.625/DV, 9.000/DV,
+	/* OCT 4 */
+		0.000/DV, 0.000/DV, 3.000/DV, 4.875/DV,
+		6.000/DV, 7.125/DV, 7.875/DV, 8.625/DV,
+		9.000/DV, 9.750/DV,10.125/DV,10.500/DV,
+		10.875/DV,11.250/DV,11.625/DV,12.000/DV,
+	/* OCT 5 */
+		0.000/DV, 3.000/DV, 6.000/DV, 7.875/DV,
+		9.000/DV,10.125/DV,10.875/DV,11.625/DV,
+		12.000/DV,12.750/DV,13.125/DV,13.500/DV,
+		13.875/DV,14.250/DV,14.625/DV,15.000/DV,
+	/* OCT 6 */
+		0.000/DV, 6.000/DV, 9.000/DV,10.875/DV,
+		12.000/DV,13.125/DV,13.875/DV,14.625/DV,
+		15.000/DV,15.750/DV,16.125/DV,16.500/DV,
+		16.875/DV,17.250/DV,17.625/DV,18.000/DV,
+	/* OCT 7 */
+		0.000/DV, 9.000/DV,12.000/DV,13.875/DV,
+		15.000/DV,16.125/DV,16.875/DV,17.625/DV,
+		18.000/DV,18.750/DV,19.125/DV,19.500/DV,
+		19.875/DV,20.250/DV,20.625/DV,21.000/DV
+};
+#undef DV
+
+/* 0 / 3.0 / 1.5 / 6.0 dB/OCT */
+static u32int ksl_shift[4] = { 31, 1, 2, 0 };
+
+
+/* sustain level table (3dB per step) */
+/* 0 - 15: 0, 3, 6, 9,12,15,18,21,24,27,30,33,36,39,42,93 (dB)*/
+#define SC(db) (u32int) ( db * (2.0/ENV_STEP) )
+static u32int sl_tab[16]={
+	SC( 0),SC( 1),SC( 2),SC(3 ),SC(4 ),SC(5 ),SC(6 ),SC( 7),
+	SC( 8),SC( 9),SC(10),SC(11),SC(12),SC(13),SC(14),SC(31)
+};
+#undef SC
+
+
+#define RATE_STEPS (8)
+static uchar eg_inc[15*RATE_STEPS]={
+/*cycle:0 1  2 3  4 5  6 7*/
+
+/* 0 */ 0,1, 0,1, 0,1, 0,1, /* rates 00..12 0 (increment by 0 or 1) */
+/* 1 */ 0,1, 0,1, 1,1, 0,1, /* rates 00..12 1 */
+/* 2 */ 0,1, 1,1, 0,1, 1,1, /* rates 00..12 2 */
+/* 3 */ 0,1, 1,1, 1,1, 1,1, /* rates 00..12 3 */
+
+/* 4 */ 1,1, 1,1, 1,1, 1,1, /* rate 13 0 (increment by 1) */
+/* 5 */ 1,1, 1,2, 1,1, 1,2, /* rate 13 1 */
+/* 6 */ 1,2, 1,2, 1,2, 1,2, /* rate 13 2 */
+/* 7 */ 1,2, 2,2, 1,2, 2,2, /* rate 13 3 */
+
+/* 8 */ 2,2, 2,2, 2,2, 2,2, /* rate 14 0 (increment by 2) */
+/* 9 */ 2,2, 2,4, 2,2, 2,4, /* rate 14 1 */
+/*10 */ 2,4, 2,4, 2,4, 2,4, /* rate 14 2 */
+/*11 */ 2,4, 4,4, 2,4, 4,4, /* rate 14 3 */
+
+/*12 */ 4,4, 4,4, 4,4, 4,4, /* rates 15 0, 15 1, 15 2, 15 3 (increment by 4) */
+/*13 */ 8,8, 8,8, 8,8, 8,8, /* rates 15 2, 15 3 for attack */
+/*14 */ 0,0, 0,0, 0,0, 0,0, /* infinity rates for attack and decay(s) */
+};
+
+
+#define O(a) (a*RATE_STEPS)
+
+/*note that there is no O(13) in this table - it's directly in the code */
+static uchar eg_rate_select[16+64+16]={   /* Envelope Generator rates (16 + 64 rates + 16 RKS) */
+/* 16 infinite time rates */
+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),
+
+/* rates 00-12 */
+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),
+
+/* rate 13 */
+O( 4),O( 5),O( 6),O( 7),
+
+/* rate 14 */
+O( 8),O( 9),O(10),O(11),
+
+/* rate 15 */
+O(12),O(12),O(12),O(12),
+
+/* 16 dummy rates (same as 15 3) */
+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
+
+/*rate  0,    1,    2,    3,   4,   5,   6,  7,  8,  9,  10, 11, 12, 13, 14, 15 */
+/*shift 12,   11,   10,   9,   8,   7,   6,  5,  4,  3,  2,  1,  0,  0,  0,  0  */
+/*mask  4095, 2047, 1023, 511, 255, 127, 63, 31, 15, 7,  3,  1,  0,  0,  0,  0  */
+
+#define O(a) (a*1)
+static uchar eg_rate_shift[16+64+16]={    /* Envelope Generator counter shifts (16 + 64 rates + 16 RKS) */
+/* 16 infinite time rates */
+O(0),O(0),O(0),O(0),O(0),O(0),O(0),O(0),
+O(0),O(0),O(0),O(0),O(0),O(0),O(0),O(0),
+
+/* rates 00-12 */
+O(12),O(12),O(12),O(12),
+O(11),O(11),O(11),O(11),
+O(10),O(10),O(10),O(10),
+O( 9),O( 9),O( 9),O( 9),
+O( 8),O( 8),O( 8),O( 8),
+O( 7),O( 7),O( 7),O( 7),
+O( 6),O( 6),O( 6),O( 6),
+O( 5),O( 5),O( 5),O( 5),
+O( 4),O( 4),O( 4),O( 4),
+O( 3),O( 3),O( 3),O( 3),
+O( 2),O( 2),O( 2),O( 2),
+O( 1),O( 1),O( 1),O( 1),
+O( 0),O( 0),O( 0),O( 0),
+
+/* rate 13 */
+O( 0),O( 0),O( 0),O( 0),
+
+/* rate 14 */
+O( 0),O( 0),O( 0),O( 0),
+
+/* rate 15 */
+O( 0),O( 0),O( 0),O( 0),
+
+/* 16 dummy rates (same as 15 3) */
+O( 0),O( 0),O( 0),O( 0),O( 0),O( 0),O( 0),O( 0),
+O( 0),O( 0),O( 0),O( 0),O( 0),O( 0),O( 0),O( 0),
+
+};
+#undef O
+
+
+/* multiple table */
+#define ML 2
+static u8int mul_tab[16]= {
+/* 1/2, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,10,12,12,15,15 */
+	ML/2, 1*ML, 2*ML, 3*ML, 4*ML, 5*ML, 6*ML, 7*ML,
+	8*ML, 9*ML,10*ML,10*ML,12*ML,12*ML,15*ML,15*ML
+};
+#undef ML
+
+/*  TL_TAB_LEN is calculated as:
+*   12 - sinus amplitude bits     (Y axis)
+*   2  - sinus sign bit           (Y axis)
+*   TL_RES_LEN - sinus resolution (X axis)
+*/
+#define TL_TAB_LEN (12*2*TL_RES_LEN)
+static signed int tl_tab[TL_TAB_LEN];
+
+#define ENV_QUIET       (TL_TAB_LEN>>4)
+
+/* sin waveform table in 'decibel' scale */
+/* four waveforms on OPL2 type chips */
+static unsigned int sin_tab[SIN_LEN * 4];
+
+
+/* LFO Amplitude Modulation table (verified on real YM3812)
+   27 output levels (triangle waveform); 1 level takes one of: 192, 256 or 448 samples
+
+   Length: 210 elements.
+
+    Each of the elements has to be repeated
+    exactly 64 times (on 64 consecutive samples).
+    The whole table takes: 64 * 210 = 13440 samples.
+
+    When AM = 1 data is used directly
+    When AM = 0 data is divided by 4 before being used (losing precision is important)
+*/
+
+#define LFO_AM_TAB_ELEMENTS 210U
+
+static u8int lfo_am_table[LFO_AM_TAB_ELEMENTS] = {
+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
+};
+
+/* LFO Phase Modulation table (verified on real YM3812) */
+static s8int lfo_pm_table[8*8*2] = {
+/* FNUM2/FNUM = 00 0xxxxxxx (0x0000) */
+0, 0, 0, 0, 0, 0, 0, 0, /*LFO PM depth = 0*/
+0, 0, 0, 0, 0, 0, 0, 0, /*LFO PM depth = 1*/
+
+/* FNUM2/FNUM = 00 1xxxxxxx (0x0080) */
+0, 0, 0, 0, 0, 0, 0, 0, /*LFO PM depth = 0*/
+1, 0, 0, 0,-1, 0, 0, 0, /*LFO PM depth = 1*/
+
+/* FNUM2/FNUM = 01 0xxxxxxx (0x0100) */
+1, 0, 0, 0,-1, 0, 0, 0, /*LFO PM depth = 0*/
+2, 1, 0,-1,-2,-1, 0, 1, /*LFO PM depth = 1*/
+
+/* FNUM2/FNUM = 01 1xxxxxxx (0x0180) */
+1, 0, 0, 0,-1, 0, 0, 0, /*LFO PM depth = 0*/
+3, 1, 0,-1,-3,-1, 0, 1, /*LFO PM depth = 1*/
+
+/* FNUM2/FNUM = 10 0xxxxxxx (0x0200) */
+2, 1, 0,-1,-2,-1, 0, 1, /*LFO PM depth = 0*/
+4, 2, 0,-2,-4,-2, 0, 2, /*LFO PM depth = 1*/
+
+/* FNUM2/FNUM = 10 1xxxxxxx (0x0280) */
+2, 1, 0,-1,-2,-1, 0, 1, /*LFO PM depth = 0*/
+5, 2, 0,-2,-5,-2, 0, 2, /*LFO PM depth = 1*/
+
+/* FNUM2/FNUM = 11 0xxxxxxx (0x0300) */
+3, 1, 0,-1,-3,-1, 0, 1, /*LFO PM depth = 0*/
+6, 3, 0,-3,-6,-3, 0, 3, /*LFO PM depth = 1*/
+
+/* FNUM2/FNUM = 11 1xxxxxxx (0x0380) */
+3, 1, 0,-1,-3,-1, 0, 1, /*LFO PM depth = 0*/
+7, 3, 0,-3,-7,-3, 0, 3  /*LFO PM depth = 1*/
+};
+
+
+/* lock level of common table */
+static int num_lock = 0;
+
+
+#define SLOT7_1 (&OPL->P_CH[7].SLOT[SLOT1])
+#define SLOT7_2 (&OPL->P_CH[7].SLOT[SLOT2])
+#define SLOT8_1 (&OPL->P_CH[8].SLOT[SLOT1])
+#define SLOT8_2 (&OPL->P_CH[8].SLOT[SLOT2])
+
+
+
+
+static int limit( int val, int max, int min ) {
+	if ( val > max )
+		val = max;
+	else if ( val < min )
+		val = min;
+
+	return val;
+}
+
+
+/* status set and IRQ handling */
+static void OPL_STATUS_SET(FM_OPL *OPL,int flag)
+{
+	/* set status flag */
+	OPL->status |= flag;
+	if(!(OPL->status & 0x80))
+	{
+		if(OPL->status & OPL->statusmask)
+		{   /* IRQ on */
+			OPL->status |= 0x80;
+			/* callback user interrupt handler (IRQ is OFF to ON) */
+			if(OPL->IRQHandler) (OPL->IRQHandler)(OPL->IRQParam,1);
+		}
+	}
+}
+
+/* status reset and IRQ handling */
+static void OPL_STATUS_RESET(FM_OPL *OPL,int flag)
+{
+	/* reset status flag */
+	OPL->status &=~flag;
+	if((OPL->status & 0x80))
+	{
+		if (!(OPL->status & OPL->statusmask) )
+		{
+			OPL->status &= 0x7f;
+			/* callback user interrupt handler (IRQ is ON to OFF) */
+			if(OPL->IRQHandler) (OPL->IRQHandler)(OPL->IRQParam,0);
+		}
+	}
+}
+
+/* IRQ mask set */
+static void OPL_STATUSMASK_SET(FM_OPL *OPL,int flag)
+{
+	OPL->statusmask = flag;
+	/* IRQ handling check */
+	OPL_STATUS_SET(OPL,0);
+	OPL_STATUS_RESET(OPL,0);
+}
+
+
+/* advance LFO to next sample */
+static void advance_lfo(FM_OPL *OPL)
+{
+	u8int tmp;
+
+	/* LFO */
+	OPL->lfo_am_cnt += OPL->lfo_am_inc;
+	if (OPL->lfo_am_cnt >= ((u32int)LFO_AM_TAB_ELEMENTS<<LFO_SH) )  /* lfo_am_table is 210 elements long */
+		OPL->lfo_am_cnt -= ((u32int)LFO_AM_TAB_ELEMENTS<<LFO_SH);
+
+	tmp = lfo_am_table[ OPL->lfo_am_cnt >> LFO_SH ];
+
+	if (OPL->lfo_am_depth)
+		OPL->LFO_AM = tmp;
+	else
+		OPL->LFO_AM = tmp>>2;
+
+	OPL->lfo_pm_cnt += OPL->lfo_pm_inc;
+	OPL->LFO_PM = ((OPL->lfo_pm_cnt>>LFO_SH) & 7) | OPL->lfo_pm_depth_range;
+}
+
+/* advance to next sample */
+static void advance(FM_OPL *OPL)
+{
+	OPL_CH *CH;
+	OPL_SLOT *op;
+	int i;
+
+	OPL->eg_timer += OPL->eg_timer_add;
+
+	while (OPL->eg_timer >= OPL->eg_timer_overflow)
+	{
+		OPL->eg_timer -= OPL->eg_timer_overflow;
+
+		OPL->eg_cnt++;
+
+		for (i=0; i<9*2; i++)
+		{
+			CH  = &OPL->P_CH[i/2];
+			op  = &CH->SLOT[i&1];
+
+			/* Envelope Generator */
+			switch(op->state)
+			{
+			case EG_ATT:        /* attack phase */
+				if ( !(OPL->eg_cnt & ((1<<op->eg_sh_ar)-1) ) )
+				{
+					/* we want sign extension here */
+					op->volume += (s32int)(~op->volume *
+												(eg_inc[op->eg_sel_ar + ((OPL->eg_cnt>>op->eg_sh_ar)&7)])
+												) >>3;
+
+					if (op->volume <= MIN_ATT_INDEX)
+					{
+						op->volume = MIN_ATT_INDEX;
+						op->state = EG_DEC;
+					}
+
+				}
+			break;
+
+			case EG_DEC:    /* decay phase */
+				if ( !(OPL->eg_cnt & ((1<<op->eg_sh_dr)-1) ) )
+				{
+					op->volume += eg_inc[op->eg_sel_dr + ((OPL->eg_cnt>>op->eg_sh_dr)&7)];
+
+					if ( op->volume >= op->sl )
+						op->state = EG_SUS;
+
+				}
+			break;
+
+			case EG_SUS:    /* sustain phase */
+
+				/* this is important behaviour:
+				one can change percusive/non-percussive modes on the fly and
+				the chip will remain in sustain phase - verified on real YM3812 */
+
+				if(op->eg_type)     /* non-percussive mode */
+				{
+									/* do nothing */
+				}
+				else                /* percussive mode */
+				{
+					/* during sustain phase chip adds Release Rate (in percussive mode) */
+					if ( !(OPL->eg_cnt & ((1<<op->eg_sh_rr)-1) ) )
+					{
+						op->volume += eg_inc[op->eg_sel_rr + ((OPL->eg_cnt>>op->eg_sh_rr)&7)];
+
+						if ( op->volume >= MAX_ATT_INDEX )
+							op->volume = MAX_ATT_INDEX;
+					}
+					/* else do nothing in sustain phase */
+				}
+			break;
+
+			case EG_REL:    /* release phase */
+				if ( !(OPL->eg_cnt & ((1<<op->eg_sh_rr)-1) ) )
+				{
+					op->volume += eg_inc[op->eg_sel_rr + ((OPL->eg_cnt>>op->eg_sh_rr)&7)];
+
+					if ( op->volume >= MAX_ATT_INDEX )
+					{
+						op->volume = MAX_ATT_INDEX;
+						op->state = EG_OFF;
+					}
+
+				}
+			break;
+
+			default:
+			break;
+			}
+		}
+	}
+
+	for (i=0; i<9*2; i++)
+	{
+		CH  = &OPL->P_CH[i/2];
+		op  = &CH->SLOT[i&1];
+
+		/* Phase Generator */
+		if(op->vib)
+		{
+			u8int block;
+			unsigned int block_fnum = CH->block_fnum;
+
+			unsigned int fnum_lfo   = (block_fnum&0x0380) >> 7;
+
+			signed int lfo_fn_table_index_offset = lfo_pm_table[OPL->LFO_PM + 16*fnum_lfo ];
+
+			if (lfo_fn_table_index_offset)  /* LFO phase modulation active */
+			{
+				block_fnum += lfo_fn_table_index_offset;
+				block = (block_fnum&0x1c00) >> 10;
+				op->Cnt += (OPL->fn_tab[block_fnum&0x03ff] >> (7-block)) * op->mul;
+			}
+			else    /* LFO phase modulation  = zero */
+			{
+				op->Cnt += op->Incr;
+			}
+		}
+		else    /* LFO phase modulation disabled for this operator */
+		{
+			op->Cnt += op->Incr;
+		}
+	}
+
+	/*  The Noise Generator of the YM3812 is 23-bit shift register.
+	*   Period is equal to 2^23-2 samples.
+	*   Register works at sampling frequency of the chip, so output
+	*   can change on every sample.
+	*
+	*   Output of the register and input to the bit 22 is:
+	*   bit0 XOR bit14 XOR bit15 XOR bit22
+	*
+	*   Simply use bit 22 as the noise output.
+	*/
+
+	OPL->noise_p += OPL->noise_f;
+	i = OPL->noise_p >> FREQ_SH;        /* number of events (shifts of the shift register) */
+	OPL->noise_p &= FREQ_MASK;
+	while (i)
+	{
+		/*
+		u32int j;
+		j = ( (OPL->noise_rng) ^ (OPL->noise_rng>>14) ^ (OPL->noise_rng>>15) ^ (OPL->noise_rng>>22) ) & 1;
+		OPL->noise_rng = (j<<22) | (OPL->noise_rng>>1);
+		*/
+
+		/*
+		    Instead of doing all the logic operations above, we
+		    use a trick here (and use bit 0 as the noise output).
+		    The difference is only that the noise bit changes one
+		    step ahead. This doesn't matter since we don't know
+		    what is real state of the noise_rng after the reset.
+		*/
+
+		if (OPL->noise_rng & 1) OPL->noise_rng ^= 0x800302;
+		OPL->noise_rng >>= 1;
+
+		i--;
+	}
+}
+
+
+static signed int op_calc(u32int phase, unsigned int env, signed int pm, unsigned int wave_tab)
+{
+	u32int p;
+
+	p = (env<<4) + sin_tab[wave_tab + ((((signed int)((phase & ~FREQ_MASK) + (pm<<16))) >> FREQ_SH ) & SIN_MASK) ];
+
+	if (p >= TL_TAB_LEN)
+		return 0;
+	return tl_tab[p];
+}
+
+static signed int op_calc1(u32int phase, unsigned int env, signed int pm, unsigned int wave_tab)
+{
+	u32int p;
+
+	p = (env<<4) + sin_tab[wave_tab + ((((signed int)((phase & ~FREQ_MASK) + pm      )) >> FREQ_SH ) & SIN_MASK) ];
+
+	if (p >= TL_TAB_LEN)
+		return 0;
+	return tl_tab[p];
+}
+
+
+#define volume_calc(OP) ((OP)->TLL + ((u32int)(OP)->volume) + (OPL->LFO_AM & (OP)->AMmask))
+
+/* calculate output */
+static void OPL_CALC_CH( FM_OPL *OPL, OPL_CH *CH )
+{
+	OPL_SLOT *SLOT;
+	unsigned int env;
+	signed int out;
+
+	OPL->phase_modulation = 0;
+
+	/* SLOT 1 */
+	SLOT = &CH->SLOT[SLOT1];
+	env  = volume_calc(SLOT);
+	out  = SLOT->op1_out[0] + SLOT->op1_out[1];
+	SLOT->op1_out[0] = SLOT->op1_out[1];
+	*SLOT->connect1 += SLOT->op1_out[0];
+	SLOT->op1_out[1] = 0;
+	if( env < ENV_QUIET )
+	{
+		if (!SLOT->FB)
+			out = 0;
+		SLOT->op1_out[1] = op_calc1(SLOT->Cnt, env, (out<<SLOT->FB), SLOT->wavetable );
+	}
+
+	/* SLOT 2 */
+	SLOT++;
+	env = volume_calc(SLOT);
+	if( env < ENV_QUIET )
+		OPL->output[0] += op_calc(SLOT->Cnt, env, OPL->phase_modulation, SLOT->wavetable);
+}
+
+/*
+    operators used in the rhythm sounds generation process:
+
+    Envelope Generator:
+
+channel  operator  register number   Bass  High  Snare Tom  Top
+/ slot   number    TL ARDR SLRR Wave Drum  Hat   Drum  Tom  Cymbal
+ 6 / 0   12        50  70   90   f0  +
+ 6 / 1   15        53  73   93   f3  +
+ 7 / 0   13        51  71   91   f1        +
+ 7 / 1   16        54  74   94   f4              +
+ 8 / 0   14        52  72   92   f2                    +
+ 8 / 1   17        55  75   95   f5                          +
+
+    Phase Generator:
+
+channel  operator  register number   Bass  High  Snare Tom  Top
+/ slot   number    MULTIPLE          Drum  Hat   Drum  Tom  Cymbal
+ 6 / 0   12        30                +
+ 6 / 1   15        33                +
+ 7 / 0   13        31                      +     +           +
+ 7 / 1   16        34                -----  n o t  u s e d -----
+ 8 / 0   14        32                                  +
+ 8 / 1   17        35                      +                 +
+
+channel  operator  register number   Bass  High  Snare Tom  Top
+number   number    BLK/FNUM2 FNUM    Drum  Hat   Drum  Tom  Cymbal
+   6     12,15     B6        A6      +
+
+   7     13,16     B7        A7            +     +           +
+
+   8     14,17     B8        A8            +           +     +
+
+*/
+
+/* calculate rhythm */
+
+static void OPL_CALC_RH( FM_OPL *OPL, OPL_CH *CH, unsigned int noise )
+{
+	OPL_SLOT *SLOT;
+	signed int out;
+	unsigned int env;
+
+
+	/* Bass Drum (verified on real YM3812):
+	  - depends on the channel 6 'connect' register:
+	      when connect = 0 it works the same as in normal (non-rhythm) mode (op1->op2->out)
+	      when connect = 1 _only_ operator 2 is present on output (op2->out), operator 1 is ignored
+	  - output sample always is multiplied by 2
+	*/
+
+	OPL->phase_modulation = 0;
+	/* SLOT 1 */
+	SLOT = &CH[6].SLOT[SLOT1];
+	env = volume_calc(SLOT);
+
+	out = SLOT->op1_out[0] + SLOT->op1_out[1];
+	SLOT->op1_out[0] = SLOT->op1_out[1];
+
+	if (!SLOT->CON)
+		OPL->phase_modulation = SLOT->op1_out[0];
+	/* else ignore output of operator 1 */
+
+	SLOT->op1_out[1] = 0;
+	if( env < ENV_QUIET )
+	{
+		if (!SLOT->FB)
+			out = 0;
+		SLOT->op1_out[1] = op_calc1(SLOT->Cnt, env, (out<<SLOT->FB), SLOT->wavetable );
+	}
+
+	/* SLOT 2 */
+	SLOT++;
+	env = volume_calc(SLOT);
+	if( env < ENV_QUIET )
+		OPL->output[0] += op_calc(SLOT->Cnt, env, OPL->phase_modulation, SLOT->wavetable) * 2;
+
+
+	/* Phase generation is based on: */
+	/* HH  (13) channel 7->slot 1 combined with channel 8->slot 2 (same combination as TOP CYMBAL but different output phases) */
+	/* SD  (16) channel 7->slot 1 */
+	/* TOM (14) channel 8->slot 1 */
+	/* TOP (17) channel 7->slot 1 combined with channel 8->slot 2 (same combination as HIGH HAT but different output phases) */
+
+	/* Envelope generation based on: */
+	/* HH  channel 7->slot1 */
+	/* SD  channel 7->slot2 */
+	/* TOM channel 8->slot1 */
+	/* TOP channel 8->slot2 */
+
+
+	/* The following formulas can be well optimized.
+	   I leave them in direct form for now (in case I've missed something).
+	*/
+
+	/* High Hat (verified on real YM3812) */
+	env = volume_calc(SLOT7_1);
+	if( env < ENV_QUIET )
+	{
+		/* high hat phase generation:
+		    phase = d0 or 234 (based on frequency only)
+		    phase = 34 or 2d0 (based on noise)
+		*/
+
+		/* base frequency derived from operator 1 in channel 7 */
+		unsigned char bit7 = ((SLOT7_1->Cnt>>FREQ_SH)>>7)&1;
+		unsigned char bit3 = ((SLOT7_1->Cnt>>FREQ_SH)>>3)&1;
+		unsigned char bit2 = ((SLOT7_1->Cnt>>FREQ_SH)>>2)&1;
+
+		unsigned char res1 = (bit2 ^ bit7) | bit3;
+
+		/* when res1 = 0 phase = 0x000 | 0xd0; */
+		/* when res1 = 1 phase = 0x200 | (0xd0>>2); */
+		u32int phase = res1 ? (0x200|(0xd0>>2)) : 0xd0;
+
+		/* enable gate based on frequency of operator 2 in channel 8 */
+		unsigned char bit5e= ((SLOT8_2->Cnt>>FREQ_SH)>>5)&1;
+		unsigned char bit3e= ((SLOT8_2->Cnt>>FREQ_SH)>>3)&1;
+
+		unsigned char res2 = (bit3e ^ bit5e);
+
+		/* when res2 = 0 pass the phase from calculation above (res1); */
+		/* when res2 = 1 phase = 0x200 | (0xd0>>2); */
+		if (res2)
+			phase = (0x200|(0xd0>>2));
+
+
+		/* when phase & 0x200 is set and noise=1 then phase = 0x200|0xd0 */
+		/* when phase & 0x200 is set and noise=0 then phase = 0x200|(0xd0>>2), ie no change */
+		if (phase&0x200)
+		{
+			if (noise)
+				phase = 0x200|0xd0;
+		}
+		else
+		/* when phase & 0x200 is clear and noise=1 then phase = 0xd0>>2 */
+		/* when phase & 0x200 is clear and noise=0 then phase = 0xd0, ie no change */
+		{
+			if (noise)
+				phase = 0xd0>>2;
+		}
+
+		OPL->output[0] += op_calc(phase<<FREQ_SH, env, 0, SLOT7_1->wavetable) * 2;
+	}
+
+	/* Snare Drum (verified on real YM3812) */
+	env = volume_calc(SLOT7_2);
+	if( env < ENV_QUIET )
+	{
+		/* base frequency derived from operator 1 in channel 7 */
+		unsigned char bit8 = ((SLOT7_1->Cnt>>FREQ_SH)>>8)&1;
+
+		/* when bit8 = 0 phase = 0x100; */
+		/* when bit8 = 1 phase = 0x200; */
+		u32int phase = bit8 ? 0x200 : 0x100;
+
+		/* Noise bit XOR'es phase by 0x100 */
+		/* when noisebit = 0 pass the phase from calculation above */
+		/* when noisebit = 1 phase ^= 0x100; */
+		/* in other words: phase ^= (noisebit<<8); */
+		if (noise)
+			phase ^= 0x100;
+
+		OPL->output[0] += op_calc(phase<<FREQ_SH, env, 0, SLOT7_2->wavetable) * 2;
+	}
+
+	/* Tom Tom (verified on real YM3812) */
+	env = volume_calc(SLOT8_1);
+	if( env < ENV_QUIET )
+		OPL->output[0] += op_calc(SLOT8_1->Cnt, env, 0, SLOT8_1->wavetable) * 2;
+
+	/* Top Cymbal (verified on real YM3812) */
+	env = volume_calc(SLOT8_2);
+	if( env < ENV_QUIET )
+	{
+		/* base frequency derived from operator 1 in channel 7 */
+		unsigned char bit7 = ((SLOT7_1->Cnt>>FREQ_SH)>>7)&1;
+		unsigned char bit3 = ((SLOT7_1->Cnt>>FREQ_SH)>>3)&1;
+		unsigned char bit2 = ((SLOT7_1->Cnt>>FREQ_SH)>>2)&1;
+
+		unsigned char res1 = (bit2 ^ bit7) | bit3;
+
+		/* when res1 = 0 phase = 0x000 | 0x100; */
+		/* when res1 = 1 phase = 0x200 | 0x100; */
+		u32int phase = res1 ? 0x300 : 0x100;
+
+		/* enable gate based on frequency of operator 2 in channel 8 */
+		unsigned char bit5e= ((SLOT8_2->Cnt>>FREQ_SH)>>5)&1;
+		unsigned char bit3e= ((SLOT8_2->Cnt>>FREQ_SH)>>3)&1;
+
+		unsigned char res2 = (bit3e ^ bit5e);
+		/* when res2 = 0 pass the phase from calculation above (res1); */
+		/* when res2 = 1 phase = 0x200 | 0x100; */
+		if (res2)
+			phase = 0x300;
+
+		OPL->output[0] += op_calc(phase<<FREQ_SH, env, 0, SLOT8_2->wavetable) * 2;
+	}
+}
+
+
+/* generic table initialize */
+static int init_tables(void)
+{
+	signed int i,x;
+	signed int n;
+	double o,m;
+
+
+	for (x=0; x<TL_RES_LEN; x++)
+	{
+		m = (1<<16) / pow(2, (x+1) * (ENV_STEP/4.0) / 8.0);
+		m = floor(m);
+
+		/* we never reach (1<<16) here due to the (x+1) */
+		/* result fits within 16 bits at maximum */
+
+		n = (int)m;     /* 16 bits here */
+		n >>= 4;        /* 12 bits here */
+		if (n&1)        /* round to nearest */
+			n = (n>>1)+1;
+		else
+			n = n>>1;
+						/* 11 bits here (rounded) */
+		n <<= 1;        /* 12 bits here (as in real chip) */
+		tl_tab[ x*2 + 0 ] = n;
+		tl_tab[ x*2 + 1 ] = -tl_tab[ x*2 + 0 ];
+
+		for (i=1; i<12; i++)
+		{
+			tl_tab[ x*2+0 + i*2*TL_RES_LEN ] =  tl_tab[ x*2+0 ]>>i;
+			tl_tab[ x*2+1 + i*2*TL_RES_LEN ] = -tl_tab[ x*2+0 + i*2*TL_RES_LEN ];
+		}
+	}
+
+	for (i=0; i<SIN_LEN; i++)
+	{
+		/* non-standard sinus */
+		m = sin( ((i*2)+1) * PI / SIN_LEN ); /* checked against the real chip */
+
+		/* we never reach zero here due to ((i*2)+1) */
+
+		if (m>0.0)
+			o = 8*log(1.0/m)/log(2.0);  /* convert to 'decibels' */
+		else
+			o = 8*log(-1.0/m)/log(2.0); /* convert to 'decibels' */
+
+		o = o / (ENV_STEP/4);
+
+		n = (int)(2.0*o);
+		if (n&1)                        /* round to nearest */
+			n = (n>>1)+1;
+		else
+			n = n>>1;
+
+		sin_tab[ i ] = n*2 + (m>=0.0? 0: 1 );
+	}
+
+	for (i=0; i<SIN_LEN; i++)
+	{
+		/* waveform 1:  __      __     */
+		/*             /  \____/  \____*/
+		/* output only first half of the sinus waveform (positive one) */
+
+		if (i & (1<<(SIN_BITS-1)) )
+			sin_tab[1*SIN_LEN+i] = TL_TAB_LEN;
+		else
+			sin_tab[1*SIN_LEN+i] = sin_tab[i];
+
+		/* waveform 2:  __  __  __  __ */
+		/*             /  \/  \/  \/  \*/
+		/* abs(sin) */
+
+		sin_tab[2*SIN_LEN+i] = sin_tab[i & (SIN_MASK>>1) ];
+
+		/* waveform 3:  _   _   _   _  */
+		/*             / |_/ |_/ |_/ |_*/
+		/* abs(output only first quarter of the sinus waveform) */
+
+		if (i & (1<<(SIN_BITS-2)) )
+			sin_tab[3*SIN_LEN+i] = TL_TAB_LEN;
+		else
+			sin_tab[3*SIN_LEN+i] = sin_tab[i & (SIN_MASK>>2)];
+	}
+
+	return 1;
+}
+
+static void OPL_initalize(FM_OPL *OPL)
+{
+	int i;
+
+	/* frequency base */
+	OPL->freqbase  = (OPL->rate) ? ((double)OPL->clock / 72.0) / OPL->rate  : 0;
+/*
+	OPL->rate = (double)OPL->clock / 72.0;
+	OPL->freqbase  = 1.0;
+*/
+
+	/* Timer base time */
+	OPL->TimerBase = 1.0 / ((double)OPL->clock / 72.0);
+
+	/* make fnumber -> increment counter table */
+	for( i=0 ; i < 1024 ; i++ )
+	{
+		/* opn phase increment counter = 20bit */
+		OPL->fn_tab[i] = (u32int)( (double)i * 64 * OPL->freqbase * (1<<(FREQ_SH-10)) ); /* -10 because chip works with 10.10 fixed point, while we use 16.16 */
+	}
+
+	/* Amplitude modulation: 27 output levels (triangle waveform); 1 level takes one of: 192, 256 or 448 samples */
+	/* One entry from LFO_AM_TABLE lasts for 64 samples */
+	OPL->lfo_am_inc = (1.0 / 64.0 ) * (1<<LFO_SH) * OPL->freqbase;
+
+	/* Vibrato: 8 output levels (triangle waveform); 1 level takes 1024 samples */
+	OPL->lfo_pm_inc = (1.0 / 1024.0) * (1<<LFO_SH) * OPL->freqbase;
+
+	/* Noise generator: a step takes 1 sample */
+	OPL->noise_f = (1.0 / 1.0) * (1<<FREQ_SH) * OPL->freqbase;
+
+	OPL->eg_timer_add  = (1<<EG_SH)  * OPL->freqbase;
+	OPL->eg_timer_overflow = ( 1 ) * (1<<EG_SH);
+}
+
+static void FM_KEYON(OPL_SLOT *SLOT, u32int key_set)
+{
+	if( !SLOT->key )
+	{
+		/* restart Phase Generator */
+		SLOT->Cnt = 0;
+		/* phase -> Attack */
+		SLOT->state = EG_ATT;
+	}
+	SLOT->key |= key_set;
+}
+
+static void FM_KEYOFF(OPL_SLOT *SLOT, u32int key_clr)
+{
+	if( SLOT->key )
+	{
+		SLOT->key &= key_clr;
+
+		if( !SLOT->key )
+		{
+			/* phase -> Release */
+			if (SLOT->state>EG_REL)
+				SLOT->state = EG_REL;
+		}
+	}
+}
+
+/* update phase increment counter of operator (also update the EG rates if necessary) */
+static void CALC_FCSLOT(OPL_CH *CH,OPL_SLOT *SLOT)
+{
+	int ksr;
+
+	/* (frequency) phase increment counter */
+	SLOT->Incr = CH->fc * SLOT->mul;
+	ksr = CH->kcode >> SLOT->KSR;
+
+	if( SLOT->ksr != ksr )
+	{
+		SLOT->ksr = ksr;
+
+		/* calculate envelope generator rates */
+		if ((SLOT->ar + SLOT->ksr) < 16+62)
+		{
+			SLOT->eg_sh_ar  = eg_rate_shift [SLOT->ar + SLOT->ksr ];
+			SLOT->eg_sel_ar = eg_rate_select[SLOT->ar + SLOT->ksr ];
+		}
+		else
+		{
+			SLOT->eg_sh_ar  = 0;
+			SLOT->eg_sel_ar = 13*RATE_STEPS;
+		}
+		SLOT->eg_sh_dr  = eg_rate_shift [SLOT->dr + SLOT->ksr ];
+		SLOT->eg_sel_dr = eg_rate_select[SLOT->dr + SLOT->ksr ];
+		SLOT->eg_sh_rr  = eg_rate_shift [SLOT->rr + SLOT->ksr ];
+		SLOT->eg_sel_rr = eg_rate_select[SLOT->rr + SLOT->ksr ];
+	}
+}
+
+/* set multi,am,vib,EG-TYP,KSR,mul */
+static void set_mul(FM_OPL *OPL,int slot,int v)
+{
+	OPL_CH   *CH   = &OPL->P_CH[slot/2];
+	OPL_SLOT *SLOT = &CH->SLOT[slot&1];
+
+	SLOT->mul     = mul_tab[v&0x0f];
+	SLOT->KSR     = (v&0x10) ? 0 : 2;
+	SLOT->eg_type = (v&0x20);
+	SLOT->vib     = (v&0x40);
+	SLOT->AMmask  = (v&0x80) ? ~0 : 0;
+	CALC_FCSLOT(CH,SLOT);
+}
+
+/* set ksl & tl */
+static void set_ksl_tl(FM_OPL *OPL,int slot,int v)
+{
+	OPL_CH   *CH   = &OPL->P_CH[slot/2];
+	OPL_SLOT *SLOT = &CH->SLOT[slot&1];
+
+	SLOT->ksl = ksl_shift[v >> 6];
+	SLOT->TL  = (v&0x3f)<<(ENV_BITS-1-7); /* 7 bits TL (bit 6 = always 0) */
+
+	SLOT->TLL = SLOT->TL + (CH->ksl_base>>SLOT->ksl);
+}
+
+/* set attack rate & decay rate  */
+static void set_ar_dr(FM_OPL *OPL,int slot,int v)
+{
+	OPL_CH   *CH   = &OPL->P_CH[slot/2];
+	OPL_SLOT *SLOT = &CH->SLOT[slot&1];
+
+	SLOT->ar = (v>>4)  ? 16 + ((v>>4)  <<2) : 0;
+
+	if ((SLOT->ar + SLOT->ksr) < 16+62)
+	{
+		SLOT->eg_sh_ar  = eg_rate_shift [SLOT->ar + SLOT->ksr ];
+		SLOT->eg_sel_ar = eg_rate_select[SLOT->ar + SLOT->ksr ];
+	}
+	else
+	{
+		SLOT->eg_sh_ar  = 0;
+		SLOT->eg_sel_ar = 13*RATE_STEPS;
+	}
+
+	SLOT->dr    = (v&0x0f)? 16 + ((v&0x0f)<<2) : 0;
+	SLOT->eg_sh_dr  = eg_rate_shift [SLOT->dr + SLOT->ksr ];
+	SLOT->eg_sel_dr = eg_rate_select[SLOT->dr + SLOT->ksr ];
+}
+
+/* set sustain level & release rate */
+static void set_sl_rr(FM_OPL *OPL,int slot,int v)
+{
+	OPL_CH   *CH   = &OPL->P_CH[slot/2];
+	OPL_SLOT *SLOT = &CH->SLOT[slot&1];
+
+	SLOT->sl  = sl_tab[ v>>4 ];
+
+	SLOT->rr  = (v&0x0f)? 16 + ((v&0x0f)<<2) : 0;
+	SLOT->eg_sh_rr  = eg_rate_shift [SLOT->rr + SLOT->ksr ];
+	SLOT->eg_sel_rr = eg_rate_select[SLOT->rr + SLOT->ksr ];
+}
+
+
+/* write a value v to register r on OPL chip */
+static void OPLWriteReg(FM_OPL *OPL, int r, int v)
+{
+	OPL_CH *CH;
+	int slot;
+	int block_fnum;
+
+
+	/* adjust bus to 8 bits */
+	r &= 0xff;
+	v &= 0xff;
+
+	switch(r&0xe0)
+	{
+	case 0x00:  /* 00-1f:control */
+		switch(r&0x1f)
+		{
+		case 0x01:  /* waveform select enable */
+			if(OPL->type&OPL_TYPE_WAVESEL)
+			{
+				OPL->wavesel = v&0x20;
+				/* do not change the waveform previously selected */
+			}
+			break;
+		case 0x02:  /* Timer 1 */
+			OPL->T[0] = (256-v)*4;
+			break;
+		case 0x03:  /* Timer 2 */
+			OPL->T[1] = (256-v)*16;
+			break;
+		case 0x04:  /* IRQ clear / mask and Timer enable */
+			if(v&0x80)
+			{   /* IRQ flag clear */
+				OPL_STATUS_RESET(OPL,0x7f-0x08); /* don't reset BFRDY flag or we will have to call deltat module to set the flag */
+			}
+			else
+			{   /* set IRQ mask ,timer enable*/
+				u8int st1 = v&1;
+				u8int st2 = (v>>1)&1;
+
+				/* IRQRST,T1MSK,t2MSK,EOSMSK,BRMSK,x,ST2,ST1 */
+				OPL_STATUS_RESET(OPL, v & (0x78-0x08) );
+				OPL_STATUSMASK_SET(OPL, (~v) & 0x78 );
+
+				/* timer 2 */
+				if(OPL->st[1] != st2)
+				{
+					double period = st2 ? (OPL->TimerBase * OPL->T[1]) : 0;
+					OPL->st[1] = st2;
+					if (OPL->timer_handler) (OPL->timer_handler)(OPL->TimerParam,1,period);
+				}
+				/* timer 1 */
+				if(OPL->st[0] != st1)
+				{
+					double period = st1 ? (OPL->TimerBase * OPL->T[0]) : 0;
+					OPL->st[0] = st1;
+					if (OPL->timer_handler) (OPL->timer_handler)(OPL->TimerParam,0,period);
+				}
+			}
+			break;
+		case 0x08:  /* MODE,DELTA-T control 2 : CSM,NOTESEL,x,x,smpl,da/ad,64k,rom */
+			OPL->mode = v;
+			break;
+		default:
+			fprint(2, "write to unknown register: %02x\n",r);
+			break;
+		}
+		break;
+	case 0x20:  /* am ON, vib ON, ksr, eg_type, mul */
+		slot = slot_array[r&0x1f];
+		if(slot < 0) return;
+		set_mul(OPL,slot,v);
+		break;
+	case 0x40:
+		slot = slot_array[r&0x1f];
+		if(slot < 0) return;
+		set_ksl_tl(OPL,slot,v);
+		break;
+	case 0x60:
+		slot = slot_array[r&0x1f];
+		if(slot < 0) return;
+		set_ar_dr(OPL,slot,v);
+		break;
+	case 0x80:
+		slot = slot_array[r&0x1f];
+		if(slot < 0) return;
+		set_sl_rr(OPL,slot,v);
+		break;
+	case 0xa0:
+		if (r == 0xbd)          /* am depth, vibrato depth, r,bd,sd,tom,tc,hh */
+		{
+			OPL->lfo_am_depth = v & 0x80;
+			OPL->lfo_pm_depth_range = (v&0x40) ? 8 : 0;
+
+			OPL->rhythm  = v&0x3f;
+
+			if(OPL->rhythm&0x20)
+			{
+				/* BD key on/off */
+				if(v&0x10)
+				{
+					FM_KEYON (&OPL->P_CH[6].SLOT[SLOT1], 2);
+					FM_KEYON (&OPL->P_CH[6].SLOT[SLOT2], 2);
+				}
+				else
+				{
+					FM_KEYOFF(&OPL->P_CH[6].SLOT[SLOT1],~2);
+					FM_KEYOFF(&OPL->P_CH[6].SLOT[SLOT2],~2);
+				}
+				/* HH key on/off */
+				if(v&0x01) FM_KEYON (&OPL->P_CH[7].SLOT[SLOT1], 2);
+				else       FM_KEYOFF(&OPL->P_CH[7].SLOT[SLOT1],~2);
+				/* SD key on/off */
+				if(v&0x08) FM_KEYON (&OPL->P_CH[7].SLOT[SLOT2], 2);
+				else       FM_KEYOFF(&OPL->P_CH[7].SLOT[SLOT2],~2);
+				/* TOM key on/off */
+				if(v&0x04) FM_KEYON (&OPL->P_CH[8].SLOT[SLOT1], 2);
+				else       FM_KEYOFF(&OPL->P_CH[8].SLOT[SLOT1],~2);
+				/* TOP-CY key on/off */
+				if(v&0x02) FM_KEYON (&OPL->P_CH[8].SLOT[SLOT2], 2);
+				else       FM_KEYOFF(&OPL->P_CH[8].SLOT[SLOT2],~2);
+			}
+			else
+			{
+				/* BD key off */
+				FM_KEYOFF(&OPL->P_CH[6].SLOT[SLOT1],~2);
+				FM_KEYOFF(&OPL->P_CH[6].SLOT[SLOT2],~2);
+				/* HH key off */
+				FM_KEYOFF(&OPL->P_CH[7].SLOT[SLOT1],~2);
+				/* SD key off */
+				FM_KEYOFF(&OPL->P_CH[7].SLOT[SLOT2],~2);
+				/* TOM key off */
+				FM_KEYOFF(&OPL->P_CH[8].SLOT[SLOT1],~2);
+				/* TOP-CY off */
+				FM_KEYOFF(&OPL->P_CH[8].SLOT[SLOT2],~2);
+			}
+			return;
+		}
+		/* keyon,block,fnum */
+		if( (r&0x0f) > 8) return;
+		CH = &OPL->P_CH[r&0x0f];
+		if(!(r&0x10))
+		{   /* a0-a8 */
+			block_fnum  = (CH->block_fnum&0x1f00) | v;
+		}
+		else
+		{   /* b0-b8 */
+			block_fnum = ((v&0x1f)<<8) | (CH->block_fnum&0xff);
+
+			if(v&0x20)
+			{
+				FM_KEYON (&CH->SLOT[SLOT1], 1);
+				FM_KEYON (&CH->SLOT[SLOT2], 1);
+			}
+			else
+			{
+				FM_KEYOFF(&CH->SLOT[SLOT1],~1);
+				FM_KEYOFF(&CH->SLOT[SLOT2],~1);
+			}
+		}
+		/* update */
+		if(CH->block_fnum != block_fnum)
+		{
+			u8int block  = block_fnum >> 10;
+
+			CH->block_fnum = block_fnum;
+
+			CH->ksl_base = ksl_tab[block_fnum>>6];
+			CH->fc       = OPL->fn_tab[block_fnum&0x03ff] >> (7-block);
+
+			/* BLK 2,1,0 bits -> bits 3,2,1 of kcode */
+			CH->kcode    = (CH->block_fnum&0x1c00)>>9;
+
+				/* the info below is actually opposite to what is stated in the Manuals (verifed on real YM3812) */
+			/* if notesel == 0 -> lsb of kcode is bit 10 (MSB) of fnum  */
+			/* if notesel == 1 -> lsb of kcode is bit 9 (MSB-1) of fnum */
+			if (OPL->mode&0x40)
+				CH->kcode |= (CH->block_fnum&0x100)>>8; /* notesel == 1 */
+			else
+				CH->kcode |= (CH->block_fnum&0x200)>>9; /* notesel == 0 */
+
+			/* refresh Total Level in both SLOTs of this channel */
+			CH->SLOT[SLOT1].TLL = CH->SLOT[SLOT1].TL + (CH->ksl_base>>CH->SLOT[SLOT1].ksl);
+			CH->SLOT[SLOT2].TLL = CH->SLOT[SLOT2].TL + (CH->ksl_base>>CH->SLOT[SLOT2].ksl);
+
+			/* refresh frequency counter in both SLOTs of this channel */
+			CALC_FCSLOT(CH,&CH->SLOT[SLOT1]);
+			CALC_FCSLOT(CH,&CH->SLOT[SLOT2]);
+		}
+		break;
+	case 0xc0:
+		/* FB,C */
+		if( (r&0x0f) > 8) return;
+		CH = &OPL->P_CH[r&0x0f];
+		CH->SLOT[SLOT1].FB  = (v>>1)&7 ? ((v>>1)&7) + 7 : 0;
+		CH->SLOT[SLOT1].CON = v&1;
+		CH->SLOT[SLOT1].connect1 = CH->SLOT[SLOT1].CON ? &OPL->output[0] : &OPL->phase_modulation;
+		break;
+	case 0xe0: /* waveform select */
+		/* simply ignore write to the waveform select register if selecting not enabled in test register */
+		if(OPL->wavesel)
+		{
+			slot = slot_array[r&0x1f];
+			if(slot < 0) return;
+			CH = &OPL->P_CH[slot/2];
+
+			CH->SLOT[slot&1].wavetable = (v&0x03)*SIN_LEN;
+		}
+		break;
+	}
+}
+
+/* lock/unlock for common table */
+static int OPL_LockTable(void)
+{
+	num_lock++;
+	if(num_lock>1) return 0;
+
+	/* first time */
+
+	/* allocate total level table (128kb space) */
+	if( !init_tables() )
+	{
+		num_lock--;
+		return -1;
+	}
+	return 0;
+}
+
+static void OPL_UnLockTable(void)
+{
+	if(num_lock) num_lock--;
+	if(num_lock) return;
+}
+
+static void OPLResetChip(FM_OPL *OPL)
+{
+	int c,s;
+	int i;
+
+	OPL->eg_timer = 0;
+	OPL->eg_cnt   = 0;
+
+	OPL->noise_rng = 1; /* noise shift register */
+	OPL->mode   = 0;    /* normal mode */
+	OPL_STATUS_RESET(OPL,0x7f);
+
+	/* reset with register write */
+	OPLWriteReg(OPL,0x01,0); /* wavesel disable */
+	OPLWriteReg(OPL,0x02,0); /* Timer1 */
+	OPLWriteReg(OPL,0x03,0); /* Timer2 */
+	OPLWriteReg(OPL,0x04,0); /* IRQ mask clear */
+	for(i = 0xff ; i >= 0x20 ; i-- ) OPLWriteReg(OPL,i,0);
+
+	/* reset operator parameters */
+	for( c = 0 ; c < 9 ; c++ )
+	{
+		OPL_CH *CH = &OPL->P_CH[c];
+		for(s = 0 ; s < 2 ; s++ )
+		{
+			/* wave table */
+			CH->SLOT[s].wavetable = 0;
+			CH->SLOT[s].state     = EG_OFF;
+			CH->SLOT[s].volume    = MAX_ATT_INDEX;
+		}
+	}
+}
+
+
+static void OPL_postload(FM_OPL *OPL)
+{
+	int slot, ch;
+
+	for( ch=0 ; ch < 9 ; ch++ )
+	{
+		OPL_CH *CH = &OPL->P_CH[ch];
+
+		/* Look up key scale level */
+		u32int block_fnum = CH->block_fnum;
+		CH->ksl_base = ksl_tab[block_fnum >> 6];
+		CH->fc       = OPL->fn_tab[block_fnum & 0x03ff] >> (7 - (block_fnum >> 10));
+
+		for( slot=0 ; slot < 2 ; slot++ )
+		{
+			OPL_SLOT *SLOT = &CH->SLOT[slot];
+
+			/* Calculate key scale rate */
+			SLOT->ksr = CH->kcode >> SLOT->KSR;
+
+			/* Calculate attack, decay and release rates */
+			if ((SLOT->ar + SLOT->ksr) < 16+62)
+			{
+				SLOT->eg_sh_ar  = eg_rate_shift [SLOT->ar + SLOT->ksr ];
+				SLOT->eg_sel_ar = eg_rate_select[SLOT->ar + SLOT->ksr ];
+			}
+			else
+			{
+				SLOT->eg_sh_ar  = 0;
+				SLOT->eg_sel_ar = 13*RATE_STEPS;
+			}
+			SLOT->eg_sh_dr  = eg_rate_shift [SLOT->dr + SLOT->ksr ];
+			SLOT->eg_sel_dr = eg_rate_select[SLOT->dr + SLOT->ksr ];
+			SLOT->eg_sh_rr  = eg_rate_shift [SLOT->rr + SLOT->ksr ];
+			SLOT->eg_sel_rr = eg_rate_select[SLOT->rr + SLOT->ksr ];
+
+			/* Calculate phase increment */
+			SLOT->Incr = CH->fc * SLOT->mul;
+
+			/* Total level */
+			SLOT->TLL = SLOT->TL + (CH->ksl_base >> SLOT->ksl);
+
+			/* Connect output */
+			SLOT->connect1 = SLOT->CON ? &OPL->output[0] : &OPL->phase_modulation;
+		}
+	}
+}
+
+static FM_OPL *
+OPLCreate(u32int clock, u32int rate, int type)
+{
+	FM_OPL *p;
+
+	if(OPL_LockTable() == -1)
+		sysfatal("OPLCreate: locktable");
+
+	/* allocate memory block */
+	if((p = mallocz(sizeof *p, 1)) == nil)
+		sysfatal("mallocz: %r");
+	p->type  = type;
+	p->clock = clock;	/* Hz */
+	p->rate  = rate;
+
+	/* init global tables */
+	OPL_initalize(p);
+
+	return p;
+}
+
+/* Destroy one of virtual YM3812 */
+static void OPLDestroy(FM_OPL *OPL)
+{
+	OPL_UnLockTable();
+	free(OPL);
+}
+
+/* Optional handlers */
+
+static void OPLSetTimerHandler(FM_OPL *OPL,OPL_TIMERHANDLER timer_handler,void *param)
+{
+	OPL->timer_handler   = timer_handler;
+	OPL->TimerParam = param;
+}
+static void OPLSetIRQHandler(FM_OPL *OPL,OPL_IRQHANDLER IRQHandler,void *param)
+{
+	OPL->IRQHandler     = IRQHandler;
+	OPL->IRQParam = param;
+}
+static void OPLSetUpdateHandler(FM_OPL *OPL,OPL_UPDATEHANDLER UpdateHandler,void *param)
+{
+	OPL->UpdateHandler = UpdateHandler;
+	OPL->UpdateParam = param;
+}
+
+static int OPLWrite(FM_OPL *OPL,int a,int v)
+{
+	if( !(a&1) )
+	{   /* address port */
+		OPL->address = v & 0xff;
+	}
+	else
+	{   /* data port */
+		if(OPL->UpdateHandler) OPL->UpdateHandler(OPL->UpdateParam,0);
+		OPLWriteReg(OPL,OPL->address,v);
+	}
+	return OPL->status>>7;
+}
+
+static unsigned char OPLRead(FM_OPL *OPL,int a)
+{
+	if( !(a&1) )
+	{
+		/* status port */
+
+		/* OPL and OPL2 */
+		return OPL->status & (OPL->statusmask|0x80);
+	}
+	return 0xff;
+}
+
+/* CSM Key Controll */
+static void CSMKeyControll(OPL_CH *CH)
+{
+	FM_KEYON (&CH->SLOT[SLOT1], 4);
+	FM_KEYON (&CH->SLOT[SLOT2], 4);
+
+	/* The key off should happen exactly one sample later - not implemented correctly yet */
+
+	FM_KEYOFF(&CH->SLOT[SLOT1], ~4);
+	FM_KEYOFF(&CH->SLOT[SLOT2], ~4);
+}
+
+
+static int OPLTimerOver(FM_OPL *OPL,int c)
+{
+	if( c )
+	{   /* Timer B */
+		OPL_STATUS_SET(OPL,0x20);
+	}
+	else
+	{   /* Timer A */
+		OPL_STATUS_SET(OPL,0x40);
+		/* CSM mode key,TL controll */
+		if( OPL->mode & 0x80 )
+		{   /* CSM mode total level latch and auto key on */
+			int ch;
+			if(OPL->UpdateHandler) OPL->UpdateHandler(OPL->UpdateParam,0);
+			for(ch=0; ch<9; ch++)
+				CSMKeyControll( &OPL->P_CH[ch] );
+		}
+	}
+	/* reload timer */
+	if (OPL->timer_handler)
+		(OPL->timer_handler)(OPL->TimerParam,c,OPL->TimerBase * OPL->T[c]);
+	return OPL->status>>7;
+}
+
+
+FM_OPL *
+ym3812_init(u32int clock, u32int rate)
+{
+	FM_OPL *p;
+
+	if((p = OPLCreate(clock, rate, OPL_TYPE_YM3812)) == nil)
+		sysfatal("OPLCreate: %r");	/* FIXME */
+	ym3812_reset_chip(p);
+	return p;
+}
+
+/* FIXME: remove abstractions; this is now opl2 only */
+void
+ym3812_shutdown(FM_OPL *p)
+{
+	OPLDestroy(p);
+}
+
+void
+ym3812_reset_chip(FM_OPL *p)
+{
+	OPLResetChip(p);
+}
+
+int
+ym3812_write(FM_OPL *p, int a, int v)
+{
+	return OPLWrite(p, a, v);
+}
+
+uchar
+ym3812_read(FM_OPL *p, int a)
+{
+	/* YM3812 always returns bit2 and bit1 in HIGH state */
+	return OPLRead(p, a) | 0x06 ;
+}
+
+int
+ym3812_timer_over(FM_OPL *p, int c)
+{
+	return OPLTimerOver(p, c);
+}
+
+void
+ym3812_set_timer_handler(FM_OPL *p, OPL_TIMERHANDLER timer_handler, void *param)
+{
+	OPLSetTimerHandler(p, timer_handler, param);
+}
+
+void
+ym3812_set_irq_handler(FM_OPL *p,OPL_IRQHANDLER IRQHandler, void *param)
+{
+	OPLSetIRQHandler(p, IRQHandler, param);
+}
+
+void
+ym3812_set_update_handler(FM_OPL *p, OPL_UPDATEHANDLER UpdateHandler, void *param)
+{
+	OPLSetUpdateHandler(p, UpdateHandler, param);
+}
+
+void
+ym3812_update_one(FM_OPL *p, OPLSAMPLE *buf, int n)
+{
+	int i, lt;
+	u8int rhythm;
+
+	rhythm = p->rhythm & 0x20;
+	for(i=0; i < n; i++){
+		p->output[0] = 0;
+
+		advance_lfo(p);
+
+		/* FM part */
+		OPL_CALC_CH(p, &p->P_CH[0]);
+		OPL_CALC_CH(p, &p->P_CH[1]);
+		OPL_CALC_CH(p, &p->P_CH[2]);
+		OPL_CALC_CH(p, &p->P_CH[3]);
+		OPL_CALC_CH(p, &p->P_CH[4]);
+		OPL_CALC_CH(p, &p->P_CH[5]);
+
+		if(!rhythm)
+		{
+			OPL_CALC_CH(p, &p->P_CH[6]);
+			OPL_CALC_CH(p, &p->P_CH[7]);
+			OPL_CALC_CH(p, &p->P_CH[8]);
+		}
+		else        /* Rhythm part */
+		{
+			OPL_CALC_RH(p, &p->P_CH[0], (p->noise_rng>>0)&1 );
+		}
+
+		lt = p->output[0];
+
+		lt >>= FINAL_SH;
+
+		/* limit check */
+		lt = limit( lt , MAXOUT, MINOUT );
+
+		/* store to sound buffer */
+		buf[i] = lt;
+
+		advance(p);
+	}
+}
--- /dev/null
+++ b/fns.h
@@ -1,0 +1,10 @@
+FM_OPL*	ym3812_init(u32int, u32int);
+void	ym3812_shutdown(FM_OPL *);
+void	ym3812_reset_chip(FM_OPL *);
+int	ym3812_write(FM_OPL *, int, int);
+uchar	ym3812_read(FM_OPL *, int);
+int	ym3812_timer_over(FM_OPL *, int);
+void	ym3812_update_one(FM_OPL *, OPLSAMPLE *, int);
+void	ym3812_set_timer_handler(FM_OPL *, OPL_TIMERHANDLER, void *);
+void	ym3812_set_irq_handler(FM_OPL *, OPL_IRQHANDLER, void *);
+void	ym3812_set_update_handler(FM_OPL *, OPL_UPDATEHANDLER, void *);
--- /dev/null
+++ b/mkfile
@@ -1,0 +1,8 @@
+</$objtype/mkfile
+
+BIN=$home/bin/$objtype
+TARG=omidi
+OFILES= omidi.$O fmopl.$O
+HFILES= dat.h fns.h
+
+</sys/src/cmd/mkone
--- /dev/null
+++ b/omidi.c
@@ -1,0 +1,1230 @@
+#include <u.h>
+#include <libc.h>
+#include "dat.h"
+#include "fns.h"
+
+enum{
+	NCHIPS = 1,
+	RATE = 44100
+};
+/* FIXME: 8192 bytes or shorts? */
+static short abuf[8192];
+static short *ap;
+static int afd = -1;
+
+/* FIXME: using multiple chips */
+/* FIXME: the only reason this exists and FM_OPL is defined in dat.h instead of being
+ * static in fmopl.c is to have multiple cards... and we don't support that; either
+ * implement it and it's cool and it should be that way, or nuke this */
+static FM_OPL *chip[NCHIPS];
+static int oplretval;
+static int oplregno;
+static double otm;
+
+enum{
+	MINDELAY = 1,
+	OPLBASE = 0x388
+};
+
+static int verbose;
+
+static struct MusData mdat;
+
+int ColorNums = -1;
+
+const long Period[12] =
+{
+    907,960,1016,1076,
+    1140,1208,1280,1356,
+    1440,1524,1616,1712
+};
+
+static const char Portit[9] = {0,1,2, 8,9,10, 16,17,18};
+
+static signed char Pans[18] = {0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0};
+
+signed char Trig[18] = {0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0};
+
+//#define SetBase(a) ((a)==2 ? (a) : 9)
+#define SetBase(a) (a)
+
+static uchar FMVol[65] = {
+    /* Generated similarly as LogTable */
+    0,32,48,58,64,70,74,77,80,83,86,
+    88,90,92,93,95,96,98,99,100,102,
+    103,104,105,106,107,108,108,109,
+    110,111,112,112,113,114,114,115,
+    116,116,117,118,118,119,119,120,
+    120,121,121,122,122,123,123,124,
+    124,124,125,125,126,126,126,127,
+    127,127,128,128
+};
+
+static uchar adl[] = {
+    14,0,0,1,143,242,244,0,8,1,6,242,247,0,14,0,0,1,75,242,244,0,8,1,0,242,247,0,14,0,0,1,73,242,244,0,8,1,0,242,246,0,14,0,0,129,18,242,247,0,6,65,0,242,247,0,14,0,0,1,87,241,247,0,0,1,0,242,247,0,14,0,0,1,147,241,247,0,0,1,0,242,247,0,14,0,0,1,128,161,
+    242,0,8,22,14,242,245,0,14,0,0,1,146,194,248,0,10,1,0,194,248,0,14,0,0,12,92,246,244,0,0,129,0,243,245,0,14,0,0,7,151,243,242,0,2,17,128,242,241,0,14,0,0,23,33,84,244,0,2,1,0,244,244,0,14,0,0,152,98,243,246,0,0,129,0,242,246,0,14,0,0,24,35,246,246,
+    0,0,1,0,231,247,0,14,0,0,21,145,246,246,0,4,1,0,246,246,0,14,0,0,69,89,211,243,0,12,129,128,163,243,0,14,0,0,3,73,117,245,1,4,129,128,181,245,0,14,0,0,113,146,246,20,0,2,49,0,241,7,0,14,0,0,114,20,199,88,0,2,48,0,199,8,0,14,0,0,112,68,170,24,0,4,177,
+    0,138,8,0,14,0,0,35,147,151,35,1,4,177,0,85,20,0,14,0,0,97,19,151,4,1,0,177,128,85,4,0,14,0,0,36,72,152,42,1,12,177,0,70,26,0,14,0,0,97,19,145,6,1,10,33,0,97,7,0,14,0,0,33,19,113,6,0,6,161,137,97,7,0,14,0,0,2,156,243,148,1,12,65,128,243,200,0,14,0,
+    0,3,84,243,154,1,12,17,0,241,231,0,14,0,0,35,95,241,58,0,0,33,0,242,248,0,14,0,0,3,135,246,34,1,6,33,128,243,248,0,14,0,0,3,71,249,84,0,0,33,0,246,58,0,14,0,0,35,74,145,65,1,8,33,5,132,25,0,14,0,0,35,74,149,25,1,8,33,0,148,25,0,14,0,0,9,161,32,79,
+    0,8,132,128,209,248,0,14,0,0,33,30,148,6,0,2,162,0,195,166,0,14,0,0,49,18,241,40,0,10,49,0,241,24,0,14,0,0,49,141,241,232,0,10,49,0,241,120,0,14,0,0,49,91,81,40,0,12,50,0,113,72,0,14,0,0,1,139,161,154,0,8,33,64,242,223,0,14,0,0,33,139,162,22,0,8,33,
+    8,161,223,0,14,0,0,49,139,244,232,0,10,49,0,241,120,0,14,0,0,49,18,241,40,0,10,49,0,241,24,0,14,0,0,49,21,221,19,1,8,33,0,86,38,0,14,0,0,49,22,221,19,1,8,33,0,102,6,0,14,0,0,113,73,209,28,1,8,49,0,97,12,0,14,0,0,33,77,113,18,1,2,35,128,114,6,0,14,
+    0,0,241,64,241,33,1,2,225,0,111,22,0,14,0,0,2,26,245,117,1,0,1,128,133,53,0,14,0,0,2,29,245,117,1,0,1,128,243,244,0,14,0,0,16,65,245,5,1,2,17,0,242,195,0,14,0,0,33,155,177,37,1,14,162,1,114,8,0,14,0,0,161,152,127,3,1,0,33,0,63,7,1,14,0,0,161,147,193,
+    18,0,10,97,0,79,5,0,14,0,0,33,24,193,34,0,12,97,0,79,5,0,14,0,0,49,91,244,21,0,0,114,131,138,5,0,14,0,0,161,144,116,57,0,0,97,0,113,103,0,14,0,0,113,87,84,5,0,12,114,0,122,5,0,14,0,0,144,0,84,99,0,8,65,0,165,69,0,14,0,0,33,146,133,23,0,12,33,1,143,
+    9,0,14,0,0,33,148,117,23,0,12,33,5,143,9,0,14,0,0,33,148,118,21,0,12,97,0,130,55,0,14,0,0,49,67,158,23,1,2,33,0,98,44,1,14,0,0,33,155,97,106,0,2,33,0,127,10,0,14,0,0,97,138,117,31,0,8,34,6,116,15,0,14,0,0,161,134,114,85,1,0,33,131,113,24,0,14,0,0,
+    33,77,84,60,0,8,33,0,166,28,0,14,0,0,49,143,147,2,1,8,97,0,114,11,0,14,0,0,49,142,147,3,1,8,97,0,114,9,0,14,0,0,49,145,147,3,1,10,97,0,130,9,0,14,0,0,49,142,147,15,1,10,97,0,114,15,0,14,0,0,33,75,170,22,1,8,33,0,143,10,0,14,0,0,49,144,126,23,1,6,33,
+    0,139,12,1,14,0,0,49,129,117,25,1,0,50,0,97,25,0,14,0,0,50,144,155,33,0,4,33,0,114,23,0,14,0,0,225,31,133,95,0,0,225,0,101,26,0,14,0,0,225,70,136,95,0,0,225,0,101,26,0,14,0,0,161,156,117,31,0,2,33,0,117,10,0,14,0,0,49,139,132,88,0,0,33,0,101,26,0,
+    14,0,0,225,76,102,86,0,0,161,0,101,38,0,14,0,0,98,203,118,70,0,0,161,0,85,54,0,14,0,0,98,153,87,7,0,11,161,0,86,7,0,14,0,0,98,147,119,7,0,11,161,0,118,7,0,14,0,0,34,89,255,3,2,0,33,0,255,15,0,14,0,0,33,14,255,15,1,0,33,0,255,15,1,14,0,0,34,70,134,
+    85,0,0,33,128,100,24,0,14,0,0,33,69,102,18,0,0,161,0,150,10,0,14,0,0,33,139,146,42,1,0,34,0,145,42,0,14,0,0,162,158,223,5,0,2,97,64,111,7,0,14,0,0,32,26,239,1,0,0,96,0,143,6,2,14,0,0,33,143,241,41,0,10,33,128,244,9,0,14,0,0,119,165,83,148,0,2,161,
+    0,160,5,0,14,0,0,97,31,168,17,0,10,177,128,37,3,0,14,0,0,97,23,145,52,0,12,97,0,85,22,0,14,0,0,113,93,84,1,0,0,114,0,106,3,0,14,0,0,33,151,33,67,0,8,162,0,66,53,0,14,0,0,161,28,161,119,1,0,33,0,49,71,1,14,0,0,33,137,17,51,0,10,97,3,66,37,0,14,0,0,
+    161,21,17,71,1,0,33,0,207,7,0,14,0,0,58,206,248,246,0,2,81,0,134,2,0,14,0,0,33,21,33,35,1,0,33,0,65,19,0,14,0,0,6,91,116,149,0,0,1,0,165,114,0,14,0,0,34,146,177,129,0,12,97,131,242,38,0,14,0,0,65,77,241,81,1,0,66,0,242,245,0,14,0,0,97,148,17,81,1,
+    6,163,128,17,19,0,14,0,0,97,140,17,49,0,6,161,128,29,3,0,14,0,0,164,76,243,115,1,4,97,0,129,35,0,14,0,0,2,133,210,83,0,0,7,3,242,246,1,14,0,0,17,12,163,17,1,0,19,128,162,229,0,14,0,0,17,6,246,65,1,4,17,0,242,230,2,14,0,0,147,145,212,50,0,8,145,0,235,
+    17,1,14,0,0,4,79,250,86,0,12,1,0,194,5,0,14,0,0,33,73,124,32,0,6,34,0,111,12,1,14,0,0,49,133,221,51,1,10,33,0,86,22,0,14,0,0,32,4,218,5,2,6,33,129,143,11,0,14,0,0,5,106,241,229,0,6,3,128,195,229,0,14,0,0,7,21,236,38,0,10,2,0,248,22,0,14,0,0,5,157,
+    103,53,0,8,1,0,223,5,0,14,0,0,24,150,250,40,0,10,18,0,248,229,0,14,0,0,16,134,168,7,0,6,0,3,250,3,0,14,0,0,17,65,248,71,2,4,16,3,243,3,0,14,0,0,1,142,241,6,2,14,16,0,243,2,0,14,0,0,14,0,31,0,0,14,192,0,31,255,3,14,0,0,6,128,248,36,0,14,3,136,86,132,
+    2,14,0,0,14,0,248,0,0,14,208,5,52,4,3,14,0,0,14,0,246,0,0,14,192,0,31,2,3,14,0,0,213,149,55,163,0,0,218,64,86,55,0,14,0,0,53,92,178,97,2,10,20,8,244,21,0,14,0,0,14,0,246,0,0,14,208,0,79,245,3,14,0,0,38,0,255,1,0,14,228,0,18,22,1,14,0,0,0,0,243,240,
+    0,14,0,0,246,201,2,14,0,35,16,68,248,119,2,8,17,0,243,6,0,14,0,35,16,68,248,119,2,8,17,0,243,6,0,14,0,52,2,7,249,255,0,8,17,0,248,255,0,14,0,48,0,0,252,5,2,14,0,0,250,23,0,14,0,58,0,2,255,7,0,0,1,0,255,8,0,14,0,60,0,0,252,5,2,14,0,0,250,23,0,14,0,
+    47,0,0,246,12,0,4,0,0,246,6,0,14,0,43,12,0,246,8,0,10,18,0,251,71,2,14,0,49,0,0,246,12,0,4,0,0,246,6,0,14,0,43,12,0,246,8,0,10,18,5,123,71,2,14,0,51,0,0,246,12,0,4,0,0,246,6,0,14,0,43,12,0,246,2,0,10,18,0,203,67,2,14,0,54,0,0,246,12,0,4,0,0,246,6,
+    0,14,0,57,0,0,246,12,0,4,0,0,246,6,0,14,0,72,14,0,246,0,0,14,208,0,159,2,3,14,0,60,0,0,246,12,0,4,0,0,246,6,0,14,0,76,14,8,248,66,0,14,7,74,244,228,3,14,0,84,14,0,245,48,0,14,208,10,159,2,0,14,0,36,14,10,228,228,3,6,7,93,245,229,1,14,0,65,2,3,180,
+    4,0,14,5,10,151,247,0,14,0,84,78,0,246,0,0,14,158,0,159,2,3,14,0,83,17,69,248,55,2,8,16,8,243,5,0,14,0,84,14,0,246,0,0,14,208,0,159,2,3,14,0,24,128,0,255,3,3,12,16,13,255,20,0,14,0,77,14,8,248,66,0,14,7,74,244,228,3,14,0,60,6,11,245,12,0,6,2,0,245,
+    8,0,14,0,65,1,0,250,191,0,7,2,0,200,151,0,14,0,59,1,81,250,135,0,6,1,0,250,183,0,14,0,51,1,84,250,141,0,6,2,0,248,184,0,14,0,45,1,89,250,136,0,6,2,0,248,182,0,14,0,71,1,0,249,10,3,14,0,0,250,6,0,14,0,60,0,128,249,137,3,14,0,0,246,108,0,14,0,58,3,128,
+    248,136,3,15,12,8,246,182,0,14,0,53,3,133,248,136,3,15,12,0,246,182,0,14,0,64,14,64,118,79,0,14,0,8,119,24,2,14,0,71,14,64,200,73,0,14,3,0,155,105,2,14,0,61,215,220,173,5,3,14,199,0,141,5,0,14,0,61,215,220,168,4,3,14,199,0,136,4,0,14,0,44,128,0,246,
+    6,3,14,17,0,103,23,3,14,0,40,128,0,245,5,2,14,17,9,70,22,3,14,0,69,6,63,0,244,0,1,21,0,247,245,0,14,0,68,6,63,0,244,3,0,18,0,247,245,0,14,0,63,6,63,0,244,0,1,18,0,247,245,0,14,0,74,1,88,103,231,0,0,2,0,117,7,0,14,0,60,65,69,248,72,0,0,66,8,117,5,0,
+    14,0,80,10,64,224,240,3,8,30,78,255,5,0,14,0,64,10,124,224,240,3,8,30,82,255,2,0,14,0,72,14,64,122,74,0,14,0,8,123,27,2,14,0,73,14,10,228,228,3,6,7,64,85,57,1,14,0,70,5,5,249,50,3,14,4,64,214,165,0,14,0,68,2,63,0,243,3,8,21,0,247,245,0,14,0,48,1,79,
+    250,141,0,7,2,0,248,181,0,14,0,53,0,0,246,12,0,4,0,0,246,6,0
+};
+
+
+static void
+pcmout(int v)
+{
+	if(v < -32768)
+		v = -32768;
+	if(v > 32767)
+		v = 32767;
+	/* FIXME: portability (endianness) */
+	*ap++ = v;
+	/* FIXME: stereo shit */
+	*ap++ = v;
+
+	/* FIXME: write last samples before end */
+	if(ap >= abuf+nelem(abuf)){
+		write(afd, abuf, sizeof abuf);
+		ap = abuf;
+	}
+}
+
+static uint
+tadv(double length, double add)
+{
+	add += otm;
+	otm = fmod(add, length);
+
+	return add / length;
+}
+
+static void
+mix(uint usecs)
+{
+	int i;
+	uint n;
+	s16int *buf;
+
+	n = tadv((double)1.0 / RATE, (double)usecs / 1E6);
+	if((buf = mallocz(n * sizeof *buf, 1)) == nil)
+		sysfatal("mallocz: %r");
+
+	ym3812_update_one(chip[0], buf, n);
+	for(i = 0; i < n; i++)
+		//pcmout((double)buf[i] / 32768.0 * 10000.0);	/* FIXME: why? */
+		pcmout(buf[i]);
+
+	free(buf);
+}
+
+static uchar
+inb(uint port)
+{
+	if(port >= 0x388 && port <= 0x38b)
+		return oplretval;
+	return 0;
+}
+
+static void
+outb(uint port, uchar v)
+{
+	uint ind;
+
+	if(port >= 0x388 && port <= 0x38b){
+		ind = port - 0x388;
+		ym3812_write(chip[0], ind, v);
+
+		if(ind & 1){
+			if(oplregno == 4){
+				if(v == 0x80)
+					oplretval = 0x02;
+				else if(v == 0x21)
+					oplretval = 0xc0;
+			}
+		}else
+			oplregno = v;
+	}
+}
+
+static void
+OPL_Byte(uchar Index, uchar Data)
+{
+	int a;
+
+	outb(OPLBASE, Index);
+	for(a=0; a<6; a++)
+		inb(OPLBASE);
+
+	outb(OPLBASE+1, Data);
+	for(a=0; a<35; a++)
+		inb(OPLBASE);
+}
+
+static void
+OPL_NoteOff(int c)
+{
+    Trig[c] = 0;
+
+    c = SetBase(c);
+    if(c<9)
+    {
+        int Ope = Portit[c];
+        /* KEYON_BLOCK+c seems to not work alone?? */
+        OPL_Byte(KEYON_BLOCK+c, 0);
+        OPL_Byte(KSL_LEVEL+  Ope, 0xFF);
+        OPL_Byte(KSL_LEVEL+3+Ope, 0xFF);
+    }
+}
+
+/* OPL_NoteOn changes the frequency on specified
+   channel and guarantees the key is on. (Doesn't
+   retrig, just turns the note on and sets freq.) */
+/* Could be used for pitch bending also. */
+static void OPL_NoteOn(int c, unsigned long Herz)
+{
+    int Oct;
+
+    Trig[c] = 127;
+
+    c = SetBase(c);
+    if(c >= 9)return;
+
+    for(Oct=0; Herz>0x1FF; Oct++)Herz >>= 1;
+
+/*
+    Bytes A0-B8 - Octave / F-Number / Key-On
+
+        7     6     5     4     3     2     1     0
+     +-----+-----+-----+-----+-----+-----+-----+-----+
+     |        F-Number (least significant byte)      |  (A0-A8)
+     +-----+-----+-----+-----+-----+-----+-----+-----+
+     |  Unused   | Key |    Octave       | F-Number  |  (B0-B8)
+     |           | On  |                 | most sig. |
+     +-----+-----+-----+-----+-----+-----+-----+-----+
+*/
+
+    OPL_Byte(0xA0+c, Herz&255);  //F-Number low 8 bits
+    OPL_Byte(0xB0+c, 0x20        //Key on
+                      | ((Herz>>8)&3) //F-number high 2 bits
+                      | ((Oct&7)<<2)
+          );
+}
+
+static void
+OPL_Touch(int c, int Instru, ushort Vol)
+{
+    int Ope;
+    //int level;
+
+    c = SetBase(c);
+    if(c >= 9)return;
+
+    Ope = Portit[c];
+
+/*
+    Bytes 40-55 - Level Key Scaling / Total Level
+
+        7     6     5     4     3     2     1     0
+     +-----+-----+-----+-----+-----+-----+-----+-----+
+     |  Scaling  |             Total Level           |
+     |   Level   | 24    12     6     3    1.5   .75 | <-- dB
+     +-----+-----+-----+-----+-----+-----+-----+-----+
+          bits 7-6 - causes output levels to decrease as the frequency
+                     rises:
+                          00   -  no change
+                          10   -  1.5 dB/8ve
+                          01   -  3 dB/8ve
+                          11   -  6 dB/8ve
+          bits 5-0 - controls the total output level of the operator.
+                     all bits CLEAR is loudest; all bits SET is the
+                     softest.  Don't ask me why.
+*/
+/* if 1 */
+	OPL_Byte(KSL_LEVEL+  Ope, (mdat.Instr[Instru]->D[2]&KSL_MASK)
+	| (63 + (mdat.Instr[Instru]->D[2]&63) * Vol / 63 - Vol));
+	OPL_Byte(KSL_LEVEL+3+Ope, (mdat.Instr[Instru]->D[3]&KSL_MASK)
+	| (63 + (mdat.Instr[Instru]->D[3]&63) * Vol / 63 - Vol));
+/* else
+    level = (mdat.Instr[Instru]->D[2]&63) - (Vol*72-8);
+    if(level<0)level=0;
+    if(level>63)level=63;
+
+    OPL_Byte(KSL_LEVEL+  Ope, (mdat.Instr[Instru]->D[2]&KSL_MASK) | level);
+
+    level = (mdat.Instr[Instru]->D[3]&63) - (Vol*72-8);
+    if(level<0)level=0;
+    if(level>63)level=63;
+
+    OPL_Byte(KSL_LEVEL+3+Ope, (mdat.Instr[Instru]->D[3]&KSL_MASK) | level);
+   endif */
+}
+
+static void OPL_Pan(int c, uchar val)
+{
+    Pans[c] = val - 128;
+}
+
+static void OPL_Patch(int c, int Instru)
+{
+    int Ope;
+
+    c = SetBase(c);
+    if(c >= 9)return;
+
+    Ope = Portit[c];
+
+	if(Instru > sizeof(mdat.Instr)-1)
+		sysfatal("invalid instrument patch %ud", Instru);	/* FIXME: see dat.h comments */
+
+    OPL_Byte(AM_VIB+           Ope, mdat.Instr[Instru]->D[0]);
+    OPL_Byte(ATTACK_DECAY+     Ope, mdat.Instr[Instru]->D[4]);
+    OPL_Byte(SUSTAIN_RELEASE+  Ope, mdat.Instr[Instru]->D[6]);
+    OPL_Byte(WAVE_SELECT+      Ope, mdat.Instr[Instru]->D[8]&3);// 6 high bits used elsewhere
+
+    OPL_Byte(AM_VIB+         3+Ope, mdat.Instr[Instru]->D[1]);
+    OPL_Byte(ATTACK_DECAY+   3+Ope, mdat.Instr[Instru]->D[5]);
+    OPL_Byte(SUSTAIN_RELEASE+3+Ope, mdat.Instr[Instru]->D[7]);
+    OPL_Byte(WAVE_SELECT+    3+Ope, mdat.Instr[Instru]->D[9]&3);// 6 high bits used elsewhere
+
+    /* Panning... */
+    OPL_Byte(FEEDBACK_CONNECTION+c, 
+        (mdat.Instr[Instru]->D[10] & ~STEREO_BITS)
+            | (Pans[c]<-32 ? VOICE_TO_LEFT
+                : Pans[c]>32 ? VOICE_TO_RIGHT
+                : (VOICE_TO_LEFT | VOICE_TO_RIGHT)
+            ) );
+}
+
+/* u32int, word and byte have been defined in adlib.h */
+
+static u32int ConvL(u8int *s)
+{
+    return (((u32int)s[0] << 24) | ((u32int)s[1] << 16) | ((u16int)s[2] << 8) | s[3]);
+}
+
+static u16int ConvI(u8int *s)
+{
+    return (s[0] << 8) | s[1];
+}
+
+static struct Midi
+{
+/* Fixed by ReadMIDI() */
+    int Fmt;
+    int TrackCount;
+    int DeltaTicks;
+    ulong *TrackLen;
+    u8int **Tracks;
+/* Used by play() */
+    u32int  *Waiting, *sWaiting, *SWaiting;
+    u8int   *Running, *sRunning, *SRunning;
+    ulong *Posi, *sPosi, *SPosi;
+    u32int  Tempo;
+    u32int  oldtempo;
+    u32int  initempo;
+/* Per channel */
+    u8int   Pan[16];
+    u8int   Patch[16];
+    u8int   MainVol[16];
+    u8int   PitchSense[16];
+    int    Bend[16];
+    int    OldBend[16];
+    int    Used[16][127]; /* contains references to adlib channels per note */
+} MIDI;
+
+#define snNoteOff    0x7fb1
+#define snNoteOn     0x7fb2
+#define snNoteModify 0x7fb3
+#define snNoteUpdate 0x7fb4
+
+enum{
+	MAXS3MCHAN = 9
+};
+
+typedef struct
+{
+/* Important - at offset 0 to be saved to file */
+    long Age;
+    u8int Note;
+    u8int Instru;
+    u8int Volume;
+    u8int cmd;
+    u8int info;
+    u8int KeyFlag;    /* Required to diff aftertouch and noteon */
+                     /* Byte to save space */
+/* Real volume(0..127) and main volume(0..127) */
+    int RealVol;
+    int MainVol;
+    int exp;	/* vol = MainVol * RealVol/127 * exp/127 */
+    /* RealVol must be first non-saved */
+/* Less important */
+    int LastInstru;  /* Needed by SuggestNewChan() */
+    int BendFlag;
+    u8int LastPan;
+/* To fasten forcement */
+    int uChn, uNote;
+} S3MChan;
+
+static S3MChan Chan[MAXCHN];
+static S3MChan PrevChan[MAXCHN];
+
+#define chansavesize ((int)((long)&Chan[0].RealVol - (long)&Chan[0].Age))
+
+static int InstruUsed[256];
+static int Forced=0;
+static const int tempochanged = -5;
+
+
+static void AnalyzeRow(void)
+{
+    int a;
+    
+    for(a=0; a<MAXS3MCHAN; a++)
+        if(!Chan[a].Age)break;
+
+    if(a==MAXS3MCHAN)
+	return;
+
+    memcpy(PrevChan, Chan, sizeof PrevChan);
+
+    Forced=0;
+
+    for(a=0; a<MAXS3MCHAN; a++)
+        Chan[a].KeyFlag=0;
+}
+
+static void FixSpeed(float *Speed, int *Tempo)
+{
+    int a;
+    float tmp = 1.0;
+    *Speed = MIDI.Tempo * 4E-7 / MIDI.DeltaTicks;
+    *Tempo = 125;
+
+    for(a=0x40; a<=0xFF; a++)
+    {
+        double Tmp;
+        float n = a * *Speed;
+        n = modf(1.0/n, &Tmp);
+        if(n < tmp)
+        {
+            *Tempo = a;
+            tmp = n;
+        }
+    }
+    *Speed *= *Tempo;
+}
+
+static int MakeVolume(int vol)
+{
+    return FMVol[vol*64/127]*63/128;
+}
+
+static void Bendi(int chn, int a)
+{
+	int bc, nt;
+	long HZ1, HZ2, Herz;
+
+	bc = MIDI.Bend[chn];
+	nt = Chan[a].Note;
+
+	if(bc > MIDI.OldBend[chn])
+		Chan[a].BendFlag |= 1;
+	else if(bc < MIDI.OldBend[chn])
+		Chan[a].BendFlag |= 2;
+	else
+		Chan[a].BendFlag = 0;
+
+	MIDI.OldBend[chn] = bc;
+
+	for(; bc < 0; bc += 0x1000)
+		nt--;
+	for(; bc >= 0x1000; bc -= 0x1000)
+		nt++;
+
+	HZ1 = Period[(nt+1)%12] * (8363L << (nt+1)/12) / 44100U;
+	HZ2 = Period[(nt  )%12] * (8363L << (nt  )/12) / 44100U;
+
+	Herz = HZ2 + bc * (HZ1 - HZ2) / 0x1000;
+
+	OPL_NoteOn(a, Herz);
+}
+
+/* vole = RealVol*MainVol/127 */
+static int SuggestNewChan(int instru, int /*vole*/)
+{
+    int a, c=MAXS3MCHAN, f;
+    long b;
+
+    for(a=f=0; a<MAXS3MCHAN; a++)
+        if(Chan[a].LastInstru==instru)
+            f=1;
+
+    /* Arvostellaan channels */
+    for(b=a=0; a<MAXS3MCHAN; a++)
+    {
+        /* empty if channel is silent */
+        if(!Chan[a].Volume)
+        {
+            long d;
+            /* Pohjapisteet...
+             * Jos instru oli uusi, mieluiten sijoitetaan
+             * sellaiselle kanavalle, joka on pitk��n ollut hiljaa.
+             * Muuten sille, mill� se juuri �skenkin soi.
+             */
+            d = f?1:Chan[a].Age;
+
+            /* Jos kanavan edellinen instru oli joku
+             * soinniltaan hyvin lyhyt, pisteit� annetaan lis�� */
+            if(strchr("\x81\x82\x83\x84\x85\x86\x87\x88\x89"
+                      "\x8B\x8D\x8E\x90\x94\x96\x9A\x9B\x9C"
+                      "\x9D\xA4\xAA\xAB",
+                Chan[a].LastInstru))d += 2;
+            else
+            {
+                /* Jos oli pitk�sointinen percussion, *
+                 * annetaan pisteit� i�n mukaan.      */
+                if(Chan[a].LastInstru > 0x80)
+                    d += Chan[a].Age*2;
+            }
+
+            /* Jos oli samaa instrua, pisteit� tulee paljon lis�� */
+            if(Chan[a].LastInstru == instru)d += 3;
+
+            //d = (d-1)*Chan[a].Age+1;
+            if(d > b)
+            {
+                b = d;
+                c = a;
+            }
+        }
+    }
+    return c;
+}
+
+/* vole = RealVol*MainVol/127 */
+static int ForceNewChan(int instru, int vole)
+{
+    int a, c;
+    long b=0;
+
+    vole *= 127;
+
+    Forced=1;
+    for(a=c=0; c<MAXS3MCHAN; c++)
+        if(Chan[c].Age
+          > b
+          + (((instru<128 && Chan[c].Instru>128)
+             || (vole > Chan[c].RealVol*Chan[c].MainVol)
+             ) ? 1:0
+          ) )
+        {
+            a=c;
+            b=Chan[c].Age;
+        }
+    return a;
+}
+
+/* Used twice by SetNote. This should be considered as a macro. */
+static void SubNoteOff(int a, int chn, int note)
+{
+    Chan[a].RealVol = 0;
+    Chan[a].MainVol = 0;
+    Chan[a].exp = 0x7f;
+
+    Chan[a].Age     = 0;
+    Chan[a].Volume  = 0;
+    Chan[a].BendFlag= 0;
+
+    MIDI.Used[chn][note] = 0;
+
+    if(Chan[a].Instru < 0x80)OPL_NoteOff(a);
+}
+
+static void SetNote(int chn, int note, int RealVol,int MainVol, int bend, int e)
+{
+	int a, vole;
+
+	//vole = RealVol*(MainVol*e)/127;
+	vole = RealVol*(MainVol)/127;
+
+    if(!vole && (bend==snNoteOn || bend==snNoteModify))bend=snNoteOff;
+    if(bend==snNoteOn && MIDI.Used[chn][note])bend=snNoteModify;
+
+    switch(bend)
+    {
+        /* snNoteOn:ssa note ei koskaan ole -1 */
+        case snNoteOn:
+        {
+            int p;
+
+            /* FIXME */
+            p = chn==9 ? 128+note-35 : MIDI.Patch[chn];
+
+            a = SuggestNewChan(p, vole);
+
+            if(a==MAXS3MCHAN)
+            {
+                a = ForceNewChan(p, vole);
+                MIDI.Used[Chan[a].uChn][Chan[a].uNote] = 0;
+            }
+
+            if(a < MAXS3MCHAN)
+            {
+		Chan[a].exp = e;
+                Chan[a].RealVol= RealVol;
+                Chan[a].MainVol= MainVol;
+                Chan[a].Note   = chn==9 ? 60 : note;
+                Chan[a].Volume = MakeVolume(vole);
+                Chan[a].Age    = 0;
+                Chan[a].Instru = p;
+
+                Chan[a].LastInstru = p;
+                Chan[a].KeyFlag= 1;
+                Chan[a].uChn  = chn;
+                Chan[a].uNote = note;
+
+/*
+                if(MIDI.Bend[chn] && Chan[a].Volume)
+                {
+                    int adlbend = MIDI.Bend[chn] * 127L / 8000;
+
+                    //TODO: Fix this. It doesn't work at all.
+
+                    Chan[a].cmd = 'N'-64;
+                    Chan[a].info= adlbend+0x80; // To make it unsigned
+                }
+                else
+*/
+                if(MIDI.Pan[chn] != Chan[a].LastPan && Chan[a].Volume)
+                {
+                    Chan[a].cmd = 'X'-64;
+                    Chan[a].info= MIDI.Pan[chn]*2;
+                    Chan[a].LastPan = MIDI.Pan[chn];
+                }
+                else
+                {
+                    Chan[a].cmd = Chan[a].info = 0;
+                }
+
+                OPL_NoteOff(a);
+
+                OPL_Patch(a, Chan[a].Instru);
+                if(MIDI.Pan[chn] != 64)OPL_Pan(a, Chan[a].info);
+                OPL_Touch(a, Chan[a].Instru, Chan[a].Volume);
+
+                Bendi(chn, a);
+
+                MIDI.Used[chn][note] = a+1;
+            }
+            break;
+        }
+        /* snNoteOff:ssa note voi olla -1 */
+        case snNoteOff:
+        {
+            int b=note, c=note;
+            if(note < 0)b=0, c=127;
+
+            MIDI.Bend[chn] = 0; /* N�in vaikuttaisi olevan hyv� */
+
+            for(note=b; note<=c; note++)
+            {
+                a = MIDI.Used[chn][note];
+                if(a > 0)
+                    SubNoteOff(a-1, chn, note);
+            }
+            break;
+        }
+        /* snNoteModify:ssa note ei koskaan ole -1 */
+        case snNoteModify:
+            a = MIDI.Used[chn][note]-1;
+            if(a != -1)
+            {
+		Chan[a].exp = e;
+                Chan[a].RealVol= RealVol;
+                Chan[a].MainVol= MainVol;
+
+                Chan[a].Volume = MakeVolume(vole);
+                Chan[a].Age    = 0;
+
+                OPL_Touch(a, Chan[a].Instru, Chan[a].Volume);
+            }
+            break;
+        /* snNoteUpdate:ssa note on aina -1 */
+        case snNoteUpdate:
+            /* snNoteUpdatessa RealVol ei muutu, vain MainVol */
+            for(note=0; note<=127; note++)
+            {
+                a = MIDI.Used[chn][note]-1;
+
+                //vole = MainVol*(Chan[a].RealVol*Chan[a].exp)/127;
+                vole = MainVol*(Chan[a].RealVol)/127;
+
+                if(a >= 0 && Chan[a].Volume != MakeVolume(vole))
+                {
+                    Chan[a].MainVol= MainVol;
+
+                    if(!vole)
+                        SubNoteOff(a, chn, note);
+                    else
+                    {
+                        Chan[a].Volume = MakeVolume(vole);
+                        Chan[a].Age    = 0;
+                        OPL_Touch(a, Chan[a].Instru, Chan[a].Volume);
+                    }
+                }
+            }
+            break;
+        /* Bendiss� note on aina -1 */
+        default:
+            MIDI.Bend[chn] = bend;
+
+            for(note=0; note<=127; note++)
+            {
+                a = MIDI.Used[chn][note]-1;
+                if(a >= 0)
+                    Bendi(chn, a);
+            }
+    }
+}
+
+static u32int GetVarLen(int Track, ulong *Posi)
+{
+    u32int d = 0;
+    for(;;)
+    {
+        u8int b = MIDI.Tracks[Track][(*Posi)++];
+        d = (d<<7) + (b&127);
+        if(b < 128)break;
+    }
+    return d;
+}
+
+/* NOTICE: This copies len-2 bytes */
+static char *Sana(int len, const unsigned char *buf)
+{
+    static char Buf[128];
+    char *s = Buf;
+    len -= 2;
+#define add(c) if(s<&Buf[sizeof(Buf)-1])*s++=(c)
+    while(len>0)
+    {
+        if(*buf<32||(*buf>=127&&*buf<160)){add('^');add(*buf + 64);}
+        else add(*buf);
+        buf++;
+        len--;
+    }
+#undef add
+    *s=0;
+    return Buf;
+}
+
+static void
+MPU_Byte(uchar)
+{
+
+}
+
+static void
+readmeta(int t, u8int *p, ulong n)
+{
+	u8int b;
+
+	b = *p;
+	p += 2;
+	//fprint(2, "meta %ux\n", b);
+	switch(b){
+	case 1:
+	case 2:
+	case 3:
+	case 4:
+	case 5:
+	case 6:
+		if(verbose)
+			fprint(2, "%s\n", Sana(n, p));
+		break;
+	case 0x2f:
+		MIDI.Posi[t] = MIDI.TrackLen[t];
+		if(verbose)
+			fprint(2, "end\n");
+		break;
+	case 0x51:
+		MIDI.Tempo = p[0]<<16 | p[1]<<8 | p[2];
+		if(verbose)
+			fprint(2, "tempo %ud\n", MIDI.Tempo);
+		break;
+	case 0: case 0x20: case 0x21: case 0x58: case 0x59: case 0x7f:
+		break;
+	default:
+		fprint(2, "unknown metaevent %ux\n", b);
+	}
+}
+
+static void
+Tavuja(int Track, u8int First, ulong posi, ulong Len)
+{
+        ulong a;
+
+	if(verbose){
+		fprint(2, "[%d]: %02ux ", Track, First);
+		for(a=0; a<Len; a++)
+			fprint(2, "%02ux", MIDI.Tracks[Track][a+posi]);
+		fprint(2, "\n");
+	}
+
+	/* FIXME: ignore sysex completely, do it like games/midi */
+	if(First == 0xff)
+		readmeta(Track, MIDI.Tracks[Track]+posi, Len);
+	else if(First < 0xf0){
+        //ulong a;	// ← what the fuck
+            a=0;
+            if((First>>4) == 8) /* Note off, modify it a bit */
+            {
+		/* FIXME: */
+                MPU_Byte(First);
+                MPU_Byte(MIDI.Tracks[Track][posi]);
+                MPU_Byte(0);
+                Len -= 2;
+            }
+            else
+                MPU_Byte(First);
+            for(; a<Len; a++)MPU_Byte(MIDI.Tracks[Track][a+posi]);
+	//fprint(2, "ev %ux\n", First >> 4);
+        switch(First >> 4)
+        {
+            case 0x8: /* Note off */
+                SetNote(
+                    First&15,                   /* chn */
+                    MIDI.Tracks[Track][posi+0], /* note */
+                    MIDI.Tracks[Track][posi+1], /* volume */
+                    MIDI.MainVol[First&15], /* mainvol */
+                    snNoteOff, 127);
+                break;
+            case 0x9: /* Note on */
+                SetNote(
+                    First&15,                   /* chn */
+                    MIDI.Tracks[Track][posi+0], /* note */
+                    MIDI.Tracks[Track][posi+1], /* volume */
+                    MIDI.MainVol[First&15], /* mainvol */
+                    snNoteOn, Chan[First&15].exp);
+                break;
+            case 0xA: /* Key after-touch */
+                SetNote(
+                    First&15,                   /* chn */
+                    MIDI.Tracks[Track][posi+0], /* note */
+                    MIDI.Tracks[Track][posi+1], /* volume */
+                    MIDI.MainVol[First&15], /* mainvol */
+                    snNoteModify, Chan[First&15].exp);
+                break;
+            case 0xC: /* Patch change */
+                MIDI.Patch[First&15] = MIDI.Tracks[Track][posi];
+                break;
+            case 0xD: /* Channel after-touch */
+                break;
+            case 0xE: /* Wheel - 0x2000 = no change */
+                a = MIDI.Tracks[Track][posi+0] |
+                    MIDI.Tracks[Track][posi+1] << 7;
+                SetNote(First&15,
+                    -1, 0,0,
+			/* FIXME: these are fucked up, see below */
+                    //(int)((long)a*MIDI.PitchSense[First&15]/2 - 0x2000L), Chan[First&15].exp);
+                    //(short)(((long)a - 0x2000L) *(double)MIDI.PitchSense[First&15]/8192.0), Chan[First&15].exp);
+                    (short)((long)a - 0x2000L), Chan[First&15].exp);
+                break;
+            case 0xB: /* Controller change */
+                switch(MIDI.Tracks[Track][posi+0])
+                {
+                    case 123: /* All notes off on channel */
+                        SetNote(First&15, -1,0,0, snNoteOff, 127);
+                        break;
+                    case 121: /* Reset vibrato and bending */
+                        MIDI.PitchSense[First&15] = 2;
+                        SetNote(First&15, -1,0,0, 0, Chan[First&15].exp); /* last 0=bend 0 */
+                        break;
+                    case 7:
+                        MIDI.MainVol[First&15] = MIDI.Tracks[Track][posi+1];
+
+			SetNote(First&15, -1, 0, MIDI.MainVol[First&15], snNoteUpdate, Chan[First&15].exp);
+			/* FIXME: why doesn't this work? */
+			//for(a = 0; a < 16; a++)
+			//	SetNote(a, -1, 0, MIDI.MainVol[First&15], snNoteUpdate, Chan[First&15].exp);
+                        break;
+                    case 64:
+			if(verbose)
+				fprint(2, "ctl 64: sustain unsupported\n");
+			break;
+                    case 1:
+			if(verbose)
+				fprint(2, "ctl 1: vibrato unsupported\n");
+			break;
+                    case 91:
+			if(verbose)
+				fprint(2, "ctl 91 %ud: reverb depth unsupported\n", MIDI.Tracks[Track][posi+1]);
+                        break;
+                    case 93:
+			if(verbose)
+                        	fprint(2, "ctl 93 %ud: chorus depth unsupported\n", MIDI.Tracks[Track][posi+1]);
+                        break;
+                    case 0x06:   /* Pitch bender sensitivity */
+			/* FIXME: the fuck is this? whatever it is,
+			 * it's broken */
+                        MIDI.PitchSense[First&15] = MIDI.Tracks[Track][posi+1];
+                        break;
+                    case 0x0a:  /* Pan */
+                        MIDI.Pan[First&15] = MIDI.Tracks[Track][posi+1];
+                        break;
+                    case 0:
+			/* FIXME: unimplemented: select bank */
+			if(verbose)
+                        	fprint(2, "ctl 0 %ud: bank select unsupported\n", MIDI.Tracks[Track][posi+1]);
+                        break;
+		case 0x0b:	/* FIXME: probably bullshizzles */
+                        SetNote(First&15, -1,0,MIDI.MainVol[First&15], snNoteUpdate, MIDI.Tracks[Track][posi+1]);
+			break;
+                    default:
+			if(verbose)
+                        	fprint(2, "unknown ctl %ud: %ux\n",
+					MIDI.Tracks[Track][posi+0],
+					MIDI.Tracks[Track][posi+1]);
+			}
+                break;
+		}
+	}
+}
+
+/* Return value: 0=ok, -1=user break */
+static int play(void)
+{
+    int a, NotFirst, Userbreak=0;
+    long Viivetta;
+
+	if((MIDI.Waiting = mallocz(MIDI.TrackCount * sizeof *MIDI.Waiting, 1)) == nil
+	|| (MIDI.sWaiting = mallocz(MIDI.TrackCount * sizeof *MIDI.sWaiting, 1)) == nil
+	|| (MIDI.SWaiting = mallocz(MIDI.TrackCount * sizeof *MIDI.SWaiting, 1)) == nil
+	|| (MIDI.Posi = mallocz(MIDI.TrackCount * sizeof *MIDI.Posi, 1)) == nil
+	|| (MIDI.sPosi = mallocz(MIDI.TrackCount * sizeof *MIDI.sPosi, 1)) == nil
+	|| (MIDI.SPosi = mallocz(MIDI.TrackCount * sizeof *MIDI.SPosi, 1)) == nil
+	|| (MIDI.Running = mallocz(MIDI.TrackCount * sizeof *MIDI.Running, 1)) == nil
+	|| (MIDI.sRunning = mallocz(MIDI.TrackCount * sizeof *MIDI.sRunning, 1)) == nil
+	|| (MIDI.SRunning = mallocz(MIDI.TrackCount * sizeof *MIDI.SRunning, 1)) == nil)
+		sysfatal("mallocz: %r");
+
+    for(a=0; a<MIDI.TrackCount; a++)
+    {
+        ulong c = 0;
+        MIDI.sWaiting[a]= GetVarLen(a, &c);
+        MIDI.sRunning[a]= 0;
+        MIDI.sPosi[a]   = c;
+    }
+
+    for(a=0; a<16; a++)
+    {
+        MIDI.Pan[a]        = 64;   /* Middle      */
+        MIDI.Patch[a]      = 1;    /* Piano       */
+        MIDI.PitchSense[a] = 2;    /* � seminotes */
+        MIDI.MainVol[a]    = 127;
+        MIDI.Bend[a]       = 0;
+    }
+
+    NotFirst = 0;
+//ReLoop:
+    for(a=0; a<MIDI.TrackCount; a++)
+    {
+        MIDI.Posi[a]    = MIDI.sPosi[a];
+        MIDI.Waiting[a] = MIDI.sWaiting[a];
+        MIDI.Running[a] = MIDI.sRunning[a];
+    }
+
+    MIDI.Tempo = MIDI.initempo;
+    
+    memset(Chan, 0, sizeof Chan);
+	for(a = 0; a < nelem(Chan); a++){
+		Chan[a].exp = 0x7f;
+	}
+
+    memset(MIDI.Used,    0, sizeof MIDI.Used);
+
+    Viivetta = 0;
+ 
+    for(;;)
+    {
+        int Fin, Act;
+
+        if(NotFirst)
+        {
+                long Lisa = MIDI.Tempo/MIDI.DeltaTicks;
+                
+                /* tempo      = microseconds per quarter note
+                 * deltaticks = number of delta-time ticks per quarter note
+                 *
+                 * So, when tempo = 200000
+                 * and deltaticks = 10,
+                 * then 10 ticks have 200000 microseconds.
+                 * 20 ticks have 400000 microseconds.
+                 * When deltaticks = 5,
+                 * then 10 ticks have 40000 microseconds.
+                 */
+
+                Viivetta += Lisa;
+                if(Viivetta >= MINDELAY)
+                {
+                    mix(Viivetta);
+                    Viivetta = 0;
+                }
+            AnalyzeRow();
+        }
+        else
+        {
+            u32int b = 0xFFFFFFFFUL;
+            /* Find the smallest delay */
+            for(a=0; a<MIDI.TrackCount; a++)
+                if(MIDI.Waiting[a] < b)
+                    b = MIDI.Waiting[a];
+            /* Elapse that delay from all tracks */
+            for(a=0; a<MIDI.TrackCount; a++)
+                MIDI.Waiting[a] -= b;
+        }
+
+        /* Let the notes on channels become older (Age++) only if  *
+         * something happens. This way, we don't overflow the ages *
+         * too easily.                                             */
+        for(Act=a=0; a<MAXS3MCHAN; a++)
+            if(MIDI.Waiting[a]<=1 && MIDI.Posi[a]<MIDI.TrackLen[a])
+                Act++;
+        for(a=0; a<MAXS3MCHAN; a++)
+            if(!Chan[a].Age||Act!=0)
+                Chan[a].Age++;
+
+        for(a=0; a<MIDI.TrackCount; ++a)
+        {
+            MIDI.SPosi[a]    = MIDI.Posi[a];
+            MIDI.SWaiting[a] = MIDI.Waiting[a];
+            MIDI.SRunning[a] = MIDI.Running[a];
+        }
+        for(Fin=1, a=0; a<MIDI.TrackCount; a++)
+        {
+            if(MIDI.Waiting[a] > 0)MIDI.Waiting[a]--;
+
+            if(MIDI.Posi[a] < MIDI.TrackLen[a])Fin=0;
+
+            /* While this track has events that we should have handled */
+            while(MIDI.Waiting[a]<=0 && MIDI.Posi[a]<MIDI.TrackLen[a])
+            {
+                ulong pos;
+                u8int b = MIDI.Tracks[a][MIDI.Posi[a]];
+                if(b < 128)
+                    b = MIDI.Running[a];
+                else
+                {
+                    MIDI.Posi[a]++;
+                    if(b < 0xF0)MIDI.Running[a] = b;
+                }
+
+                pos = MIDI.Posi[a];
+
+		//fprint(2, "b %ux\n", b);
+                if(b == 0xFF)
+                {
+                    int ls=0;
+                    ulong len;
+                    u8int typi = MIDI.Tracks[a][MIDI.Posi[a]++];
+                    len = (ulong)GetVarLen(a, &MIDI.Posi[a]);
+                    if(typi == 6) /* marker */
+                        if(!strncmp((char *)(MIDI.Tracks[a]+MIDI.Posi[a]),
+                                    "loopStart", len))
+                        {
+				if(verbose)
+                            		fprint(2, "Found loopStart\n");
+                            ls=1;
+                        }
+                    MIDI.Posi[a] += len;
+                    if(ls)goto SaveLoopStart;
+                }
+                else if(b==0xF7 || b==0xF0)
+                {
+                    MIDI.Posi[a] += (ulong)GetVarLen(a, &MIDI.Posi[a]);
+                }
+                else if(b == 0xF3)MIDI.Posi[a]++;
+                else if(b == 0xF2)MIDI.Posi[a]+=2;
+                else if(b>=0xC0 && b<=0xDF)MIDI.Posi[a]++;
+                else if(b>=0x80 && b<=0xEF)
+                {
+                    MIDI.Posi[a]+=2;
+                    if(b>=0x90 && b<=0x9F && !NotFirst)
+                    {
+                        int c;
+SaveLoopStart:          NotFirst=1;
+                        /* Save the starting position for looping */
+                        for(c=0; c<MIDI.TrackCount; c++)
+                        {
+                            MIDI.sPosi[c]    = MIDI.SPosi[c];
+                            MIDI.sWaiting[c] = MIDI.SWaiting[c];
+                            MIDI.sRunning[c] = MIDI.SRunning[c];
+                        }
+                        MIDI.initempo = MIDI.Tempo;
+                    }
+                }
+
+                Tavuja(a, b, pos, MIDI.Posi[a]-pos);
+
+                if(MIDI.Posi[a] < MIDI.TrackLen[a])
+                    MIDI.Waiting[a] += GetVarLen(a, &MIDI.Posi[a]);
+            }
+        }
+        if(Fin)
+		break;
+    }
+	write(afd, abuf, ap-abuf);
+    return Userbreak;
+}
+
+static void
+initinst(void)
+{
+	int m, i;
+	InternalSample *t, *p;
+
+	m = sizeof(adl) / 14;
+
+	if((t = calloc(m, sizeof *t)) == nil)
+		sysfatal("initinst: %r");
+	p = t;
+	/* FIXME: .D needs to be uchar(?) */
+	/* FIXME: use char adl[][14] rather than this */
+	/* FIXME: find out where the values are from */
+	for(i=0; i<m; i++){
+		p->D[0] = adl[i * 14 + 3];
+		p->D[1] = adl[i * 14 + 9];
+		p->D[2] = adl[i * 14 + 4];
+		p->D[3] = adl[i * 14 + 10];
+		p->D[4] = adl[i * 14 + 5];
+		p->D[5] = adl[i * 14 + 11];
+		p->D[6] = adl[i * 14 + 6];
+		p->D[7] = adl[i * 14 + 12];
+		p->D[8] = adl[i * 14 + 7] & 3;
+		p->D[9] = adl[i * 14 + 13] & 3;
+		p->D[10]= adl[i * 14 + 8];
+		mdat.Instr[i] = p++;
+	}
+}
+
+/* FIXME: get8/16/32 approach is better */
+/* FIXME: use bio.h */
+static void
+eread(int fd, void *buf, int n)
+{
+	if(readn(fd, buf, n) < 0)
+		sysfatal("read: %r");
+}
+
+static void
+readmid(char *mid)
+{
+	int i, fd;
+	uint n;
+	uchar id[4];
+
+	fd = 0;
+	if(mid != nil && (fd = open(mid, OREAD)) < 0)
+		sysfatal("open: %r");
+
+	/* FIXME: get8/16/32 better; Conv shit is also stupid */
+	/* FIXME: also, don't read file into memory but use bio? */
+	eread(fd, id, 4);
+	if(memcmp(id, "MThd", 4) != 0)
+		sysfatal("invalid header");
+	eread(fd, id, 4);
+	if(ConvL(id) != 6)
+		sysfatal("invalid midi file");
+
+	eread(fd, id, 2);
+	MIDI.Fmt = ConvI(id);
+	eread(fd, id, 2);
+	MIDI.TrackCount = ConvI(id);
+	eread(fd, id, 2);
+	MIDI.DeltaTicks = ConvI(id);
+
+	MIDI.TrackLen = calloc(MIDI.TrackCount, sizeof *MIDI.TrackLen);
+	if(MIDI.TrackLen == nil)
+		sysfatal("calloc len %ux %ux: %r", MIDI.TrackCount, sizeof *MIDI.TrackLen);
+	MIDI.Tracks = calloc(MIDI.TrackCount, sizeof *MIDI.Tracks);
+	if(MIDI.Tracks == nil)
+		sysfatal("calloc trs %ux %ux: %r", MIDI.TrackCount, sizeof *MIDI.Tracks);
+
+	for(i = 0; i < MIDI.TrackCount; i++){
+		eread(fd, id, 4);
+		if(memcmp(id, "MTrk", 4) != 0)
+			sysfatal("invalid track");
+
+		eread(fd, id, 4);
+		n = ConvL(id);
+		MIDI.TrackLen[i] = n;
+		if((MIDI.Tracks[i] = mallocz(n, 1)) == nil)
+			sysfatal("mallocz %ux: %r", n);
+
+		eread(fd, MIDI.Tracks[i], n);
+	}
+	close(fd);
+
+	MIDI.initempo = 150000;
+}
+
+static void
+usage(void)
+{
+	print("%s [-cd] [midfile]\n", argv0);
+	exits("usage");
+}
+
+void
+main(int argc, char **argv)
+{
+	int i;
+
+	ARGBEGIN{
+	case 'c':
+		afd = 1;
+		break;
+	case 'd':
+		verbose++;
+		break;
+	default:
+		usage();
+	}ARGEND
+
+	readmid(*argv);
+	initinst();
+	for(i = 0; i < NCHIPS; i++)
+		chip[i] = ym3812_init(1789772*2, RATE);
+
+	if(afd < 0 && (afd = open("/dev/audio", OWRITE)) < 0)
+		sysfatal("open: %r");
+	ap = abuf;
+	play();
+
+	for(i = 0; i < NCHIPS; i++)
+		ym3812_shutdown(chip[i]);
+	exits(nil);
+}