using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace mame
{
    public class FMOpl
    {
        public struct OPL_SLOT
        {
            public uint ar;
            public uint dr;
            public uint rr;
            public byte KSR;
            public byte ksl;
            public byte ksr;
            public byte mul;

            public uint Cnt;
            public uint Incr;
            public byte FB;
            public int iconnect;
            public int[] op1_out;
            public byte CON;

            public byte eg_type;
            public byte state;
            public uint TL;
            public int TLL;
            public int volume;
            public uint sl;
            public byte eg_sh_ar;
            public byte eg_sel_ar;
            public byte eg_sh_dr;
            public byte eg_sel_dr;
            public byte eg_sh_rr;
            public byte eg_sel_rr;
            public uint key;

            public uint AMmask;
            public byte vib;

            public ushort wavetable;
        }
        public struct OPL_CH
        {
            public OPL_SLOT[] SLOT;
	        public uint block_fnum;
	        public uint fc;
	        public uint ksl_base;
	        public byte kcode;
        }
        public class FM_OPL
        {
	        public OPL_CH[] P_CH;

	        public uint eg_cnt;
	        public uint eg_timer;
	        public uint eg_timer_add;
	        public uint eg_timer_overflow;

	        public byte rhythm;

	        public uint[] fn_tab;

	        public byte lfo_am_depth;
	        public byte lfo_pm_depth_range;
	        public uint lfo_am_cnt;
	        public uint lfo_am_inc;
	        public uint lfo_pm_cnt;
	        public uint lfo_pm_inc;

	        public uint noise_rng;
	        public uint noise_p;
	        public uint noise_f;

	        public byte wavesel;

	        public uint[] T;
	        public byte[] st;

            public YMDeltat.YM_DELTAT deltat;

            public byte portDirection;
            public byte portLatch;
            public OPL_PORTHANDLER_R porthandler_r;
            public OPL_PORTHANDLER_W porthandler_w;
            public OPL_PORTHANDLER_R keyboardhandler_r;
            public OPL_PORTHANDLER_W keyboardhandler_w;

	        public OPL_TIMERHANDLER timer_handler;
            public OPL_IRQHANDLER IRQHandler;
            public OPL_UPDATEHANDLER UpdateHandler;

            public byte type;
            public byte address;
            public byte status;
            public byte statusmask;
            public byte mode;

	        public int clock;
	        public int rate;
	        public double freqbase;
            public Atime TimerBase;
            public void OPLResetChip()
            {
                int c, s;
                int i;
                eg_timer = 0;
                eg_cnt = 0;
                noise_rng = 1;
                mode = 0;
                OPL_STATUS_RESET(0x7f);
                OPLWriteReg(0x01, 0);
                OPLWriteReg(0x02, 0);
                OPLWriteReg(0x03, 0);
                OPLWriteReg(0x04, 0);
                for (i = 0xff; i >= 0x20; i--)
                {
                    OPLWriteReg(i, 0);
                }
                for (c = 0; c < 9; c++)
                {
                    for (s = 0; s < 2; s++)
                    {
                        P_CH[c].SLOT[s].wavetable = 0;
                        P_CH[c].SLOT[s].state = 0;
                        P_CH[c].SLOT[s].volume = 0x1ff;
                    }
                }
            }
            public void OPL_initalize()
            {
                int i;
                freqbase = (rate != 0) ? ((double)clock / 72.0) / rate : 0;
                TimerBase = Attotime.attotime_mul(Attotime.ATTOTIME_IN_HZ((int)clock), 72);
                for (i = 0; i < 1024; i++)
                {
                    fn_tab[i] = (uint)((double)i * 64 * freqbase * (1 << (16 - 10)));
                }
                lfo_am_inc = (uint)(0x40000 * freqbase);
                lfo_pm_inc = (uint)(0x4000 * freqbase);
                noise_f = (uint)(0x10000 * freqbase);
                eg_timer_add = (uint)(0x10000 * freqbase);
                eg_timer_overflow = 0x10000;
            }
            private void FM_KEYON(int chan, int slot, uint key_set)
            {
                if (P_CH[chan].SLOT[slot].key == 0)
                {
                    P_CH[chan].SLOT[slot].Cnt = 0;
                    P_CH[chan].SLOT[slot].state = 4;
                }
                P_CH[chan].SLOT[slot].key |= key_set;
            }
            private void FM_KEYOFF(int chan, int slot, uint key_clr)
            {
                if (P_CH[chan].SLOT[slot].key != 0)
                {
                    P_CH[chan].SLOT[slot].key &= key_clr;
                    if (P_CH[chan].SLOT[slot].key == 0)
                    {
                        if (P_CH[chan].SLOT[slot].state > 1)
                            P_CH[chan].SLOT[slot].state = 1;
                    }
                }
            }
            private void CALC_FCSLOT(int chan, int slot)
            {
                int ksr;
                P_CH[chan].SLOT[slot].Incr = P_CH[chan].fc * P_CH[chan].SLOT[slot].mul;
                ksr = P_CH[chan].kcode >> P_CH[chan].SLOT[slot].KSR;
                if (P_CH[chan].SLOT[slot].ksr != ksr)
                {
                    P_CH[chan].SLOT[slot].ksr = (byte)ksr;
                    if ((P_CH[chan].SLOT[slot].ar + P_CH[chan].SLOT[slot].ksr) < 78)
                    {
                        P_CH[chan].SLOT[slot].eg_sh_ar = eg_rate_shift[P_CH[chan].SLOT[slot].ar + P_CH[chan].SLOT[slot].ksr];
                        P_CH[chan].SLOT[slot].eg_sel_ar = eg_rate_select[P_CH[chan].SLOT[slot].ar + P_CH[chan].SLOT[slot].ksr];
                    }
                    else
                    {
                        P_CH[chan].SLOT[slot].eg_sh_ar = 0;
                        P_CH[chan].SLOT[slot].eg_sel_ar = 13 * 8;
                    }
                    P_CH[chan].SLOT[slot].eg_sh_dr = eg_rate_shift[P_CH[chan].SLOT[slot].dr + P_CH[chan].SLOT[slot].ksr];
                    P_CH[chan].SLOT[slot].eg_sel_dr = eg_rate_select[P_CH[chan].SLOT[slot].dr + P_CH[chan].SLOT[slot].ksr];
                    P_CH[chan].SLOT[slot].eg_sh_rr = eg_rate_shift[P_CH[chan].SLOT[slot].rr + P_CH[chan].SLOT[slot].ksr];
                    P_CH[chan].SLOT[slot].eg_sel_rr = eg_rate_select[P_CH[chan].SLOT[slot].rr + P_CH[chan].SLOT[slot].ksr];
                }
            }
            private void set_mul(int slot, int v)
            {
                P_CH[slot / 2].SLOT[slot & 1].mul = mul_tab[v & 0x0f];
                P_CH[slot / 2].SLOT[slot & 1].KSR = (byte)((v & 0x10) != 0 ? 0 : 2);
                P_CH[slot / 2].SLOT[slot & 1].eg_type = (byte)(v & 0x20);
                P_CH[slot / 2].SLOT[slot & 1].vib = (byte)(v & 0x40);
                P_CH[slot / 2].SLOT[slot & 1].AMmask = (uint)((v & 0x80) != 0 ? ~0 : 0);
                CALC_FCSLOT(slot / 2, slot & 1);
            }
            private void set_ksl_tl(int slot, int v)
            {
                int ksl = v >> 6;
                P_CH[slot / 2].SLOT[slot & 1].ksl = (byte)(ksl != 0 ? 3 - ksl : 31);
                P_CH[slot / 2].SLOT[slot & 1].TL = (uint)((v & 0x3f) << 2);
                P_CH[slot / 2].SLOT[slot & 1].TLL = (int)(P_CH[slot / 2].SLOT[slot & 1].TL + (P_CH[slot / 2].ksl_base >> P_CH[slot / 2].SLOT[slot & 1].ksl));
            }
            private void set_ar_dr(int slot, int v)
            {
                P_CH[slot / 2].SLOT[slot & 1].ar = (uint)((v >> 4) != 0 ? 16 + ((v >> 4) << 2) : 0);
                if ((P_CH[slot / 2].SLOT[slot & 1].ar + P_CH[slot / 2].SLOT[slot & 1].ksr) < 16 + 62)
                {
                    P_CH[slot / 2].SLOT[slot & 1].eg_sh_ar = eg_rate_shift[P_CH[slot / 2].SLOT[slot & 1].ar + P_CH[slot / 2].SLOT[slot & 1].ksr];
                    P_CH[slot / 2].SLOT[slot & 1].eg_sel_ar = eg_rate_select[P_CH[slot / 2].SLOT[slot & 1].ar + P_CH[slot / 2].SLOT[slot & 1].ksr];
                }
                else
                {
                    P_CH[slot / 2].SLOT[slot & 1].eg_sh_ar = 0;
                    P_CH[slot / 2].SLOT[slot & 1].eg_sel_ar = 13 * 8;
                }
                P_CH[slot / 2].SLOT[slot & 1].dr = (uint)((v & 0x0f) != 0 ? 16 + ((v & 0x0f) << 2) : 0);
                P_CH[slot / 2].SLOT[slot & 1].eg_sh_dr = eg_rate_shift[P_CH[slot / 2].SLOT[slot & 1].dr + P_CH[slot / 2].SLOT[slot & 1].ksr];
                P_CH[slot / 2].SLOT[slot & 1].eg_sel_dr = eg_rate_select[P_CH[slot / 2].SLOT[slot & 1].dr + P_CH[slot / 2].SLOT[slot & 1].ksr];
            }
            private void set_sl_rr(int slot, int v)
            {
                P_CH[slot / 2].SLOT[slot & 1].sl = sl_tab[v >> 4];
                P_CH[slot / 2].SLOT[slot & 1].rr = (uint)((v & 0x0f) != 0 ? 16 + ((v & 0x0f) << 2) : 0);
                P_CH[slot / 2].SLOT[slot & 1].eg_sh_rr = eg_rate_shift[P_CH[slot / 2].SLOT[slot & 1].rr + P_CH[slot / 2].SLOT[slot & 1].ksr];
                P_CH[slot / 2].SLOT[slot & 1].eg_sel_rr = eg_rate_select[P_CH[slot / 2].SLOT[slot & 1].rr + P_CH[slot / 2].SLOT[slot & 1].ksr];
            }
            public void OPLWriteReg(int r, int v)
            {
                int slot;
                int block_fnum;
                r &= 0xff;
                v &= 0xff;
                switch (r & 0xe0)
                {
                    case 0x00:
                        switch (r & 0x1f)
                        {
                            case 0x01:
                                if ((type & 0x01) != 0)
                                {
                                    wavesel = (byte)(v & 0x20);
                                }
                                break;
                            case 0x02:
                                T[0] = (uint)((256 - v) * 4);
                                break;
                            case 0x03:
                                T[1] = (uint)((256 - v) * 16);
                                break;
                            case 0x04:
                                if ((v & 0x80) != 0)
                                {
                                    OPL_STATUS_RESET(0x7f - 0x08);
                                }
                                else
                                {
                                    byte st1 = (byte)(v & 1);
                                    byte st2 = (byte)((v >> 1) & 1);
                                    OPL_STATUS_RESET(v & (0x78 - 0x08));
                                    OPL_STATUSMASK_SET((~v) & 0x78);
                                    if (st[1] != st2)
                                    {
                                        Atime period = st2 != 0 ? Attotime.attotime_mul(TimerBase, T[1]) : Attotime.ATTOTIME_ZERO;
                                        st[1] = st2;
                                        if (timer_handler != null)
                                        {
                                            timer_handler(1, period);
                                        }
                                    }
                                    if (st[0] != st1)
                                    {
                                        Atime period = st1 != 0 ? Attotime.attotime_mul(TimerBase, T[0]) : Attotime.ATTOTIME_ZERO;
                                        st[0] = st1;
                                        if (timer_handler != null)
                                        {
                                            timer_handler(0, period);
                                        }
                                    }
                                }
                                break;
                            case 0x08:
                                mode = (byte)v;
                                break;
                            default:
                                break;
                        }
                        break;
                    case 0x20:
                        slot = slot_array[r & 0x1f];
                        if (slot < 0)
                        {
                            return;
                        }
                        set_mul(slot, v);
                        break;
                    case 0x40:
                        slot = slot_array[r & 0x1f];
                        if (slot < 0)
                        {
                            return;
                        }
                        set_ksl_tl(slot, v);
                        break;
                    case 0x60:
                        slot = slot_array[r & 0x1f];
                        if (slot < 0)
                        {
                            return;
                        }
                        set_ar_dr(slot, v);
                        break;
                    case 0x80:
                        slot = slot_array[r & 0x1f];
                        if (slot < 0)
                        {
                            return;
                        }
                        set_sl_rr(slot, v);
                        break;
                    case 0xa0:
                        if (r == 0xbd)
                        {
                            lfo_am_depth = (byte)(v & 0x80);
                            lfo_pm_depth_range = (byte)((v & 0x40) != 0 ? 8 : 0);
                            rhythm = (byte)(v & 0x3f);
                            if ((rhythm & 0x20) != 0)
                            {
                                if ((v & 0x10) != 0)
                                {
                                    FM_KEYON(6, 0, 2);
                                    FM_KEYON(6, 1, 2);
                                }
                                else
                                {
                                    FM_KEYOFF(6, 0, unchecked((uint)(~2)));
                                    FM_KEYOFF(6, 1, unchecked((uint)(~2)));
                                }
                                if ((v & 0x01) != 0)
                                {
                                    FM_KEYON(7, 0, 2);
                                }
                                else
                                {
                                    FM_KEYOFF(7, 0, unchecked((uint)(~2)));
                                }
                                if ((v & 0x08) != 0)
                                {
                                    FM_KEYON(7, 1, 2);
                                }
                                else
                                {
                                    FM_KEYOFF(7, 1, unchecked((uint)(~2)));
                                }
                                if ((v & 0x04) != 0)
                                {
                                    FM_KEYON(8, 0, 2);
                                }
                                else
                                {
                                    FM_KEYOFF(8, 0, unchecked((uint)(~2)));
                                }
                                if ((v & 0x02) != 0)
                                {
                                    FM_KEYON(8, 1, 2);
                                }
                                else
                                {
                                    FM_KEYOFF(8, 1, unchecked((uint)(~2)));
                                }
                            }
                            else
                            {
                                FM_KEYOFF(6, 0, unchecked((uint)(~2)));
                                FM_KEYOFF(6, 1, unchecked((uint)(~2)));
                                FM_KEYOFF(7, 0, unchecked((uint)(~2)));
                                FM_KEYOFF(7, 1, unchecked((uint)(~2)));
                                FM_KEYOFF(8, 0, unchecked((uint)(~2)));
                                FM_KEYOFF(8, 1, unchecked((uint)(~2)));
                            }
                            return;
                        }
                        if ((r & 0x0f) > 8)
                        {
                            return;
                        }
                        if ((r & 0x10) == 0)
                        {
                            block_fnum = (int)((P_CH[r & 0x0f].block_fnum & 0x1f00) | (uint)v);
                        }
                        else
                        {
                            block_fnum = (int)((uint)((v & 0x1f) << 8) | (P_CH[r & 0x0f].block_fnum & 0xff));
                            if ((v & 0x20) != 0)
                            {
                                FM_KEYON(r & 0x0f, 0, 1);
                                FM_KEYON(r & 0x0f, 1, 1);
                            }
                            else
                            {
                                FM_KEYOFF(r & 0x0f, 0, unchecked((uint)(~1)));
                                FM_KEYOFF(r & 0x0f, 1, unchecked((uint)(~1)));
                            }
                        }
                        if (P_CH[r & 0x0f].block_fnum != block_fnum)
                        {
                            byte block = (byte)(block_fnum >> 10);
                            P_CH[r & 0x0f].block_fnum = (uint)block_fnum;
                            P_CH[r & 0x0f].ksl_base = ksl_tab[block_fnum >> 6];
                            P_CH[r & 0x0f].fc = fn_tab[block_fnum & 0x03ff] >> (7 - block);
                            P_CH[r & 0x0f].kcode = (byte)((P_CH[r & 0x0f].block_fnum & 0x1c00) >> 9);
                            if ((mode & 0x40) != 0)
                            {
                                P_CH[r & 0x0f].kcode |= (byte)((P_CH[r & 0x0f].block_fnum & 0x100) >> 8);
                            }
                            else
                            {
                                P_CH[r & 0x0f].kcode |= (byte)((P_CH[r & 0x0f].block_fnum & 0x200) >> 9);
                            }
                            P_CH[r & 0x0f].SLOT[0].TLL = (int)(P_CH[r & 0x0f].SLOT[0].TL + (P_CH[r & 0x0f].ksl_base >> P_CH[r & 0x0f].SLOT[0].ksl));
                            P_CH[r & 0x0f].SLOT[1].TLL = (int)(P_CH[r & 0x0f].SLOT[1].TL + (P_CH[r & 0x0f].ksl_base >> P_CH[r & 0x0f].SLOT[1].ksl));
                            CALC_FCSLOT(r & 0x0f, 0);
                            CALC_FCSLOT(r & 0x0f, 1);
                        }
                        break;
                    case 0xc0:
                        if ((r & 0x0f) > 8)
                        {
                            return;
                        }
                        P_CH[r & 0x0f].SLOT[0].FB = (byte)(((v >> 1) & 7) != 0 ? ((v >> 1) & 7) + 7 : 0);
                        P_CH[r & 0x0f].SLOT[0].CON = (byte)(v & 1);
                        P_CH[r & 0x0f].SLOT[0].iconnect = P_CH[r & 0x0f].SLOT[0].CON != 0 ? 1 : 2;
                        break;
                    case 0xe0:
                        if (wavesel != 0)
                        {
                            slot = slot_array[r & 0x1f];
                            if (slot < 0)
                            {
                                return;
                            }
                            P_CH[slot / 2].SLOT[slot & 1].wavetable = (ushort)((v & 0x03) * 0x400);
                        }
                        break;
                }
            }
            public int OPLWrite(int a, int v)
            {
                if ((a & 1) == 0)
                {
                    address = (byte)(v & 0xff);
                }
                else
                {
                    if (UpdateHandler != null)
                    {
                        UpdateHandler(0);
                    }
                    OPLWriteReg(address, v);
                }
                return status >> 7;
            }
            public byte OPLRead(int a)
            {
                if ((a & 1) == 0)
                {
                    return (byte)(status & (statusmask | 0x80));
                }
                return 0xff;
            }
            public int op_calc1(uint phase, int env, int pm, int wave_tab)
            {
                uint p;
                p = (uint)((env << 4) + sin_tab[wave_tab + ((((int)((phase & ~0xffff) + pm)) >> 16) & 0x3ff)]);
                if (p >= 0x1800)
                {
                    return 0;
                }
                return tl_tab[p];
            }
            public uint volume_calc(int chan, int slot)
            {
                uint i1;
                i1 = (uint)(P_CH[chan].SLOT[slot].TLL + (uint)P_CH[chan].SLOT[slot].volume + (LFO_AM & P_CH[chan].SLOT[slot].AMmask));
                return i1;
            }
            public void OPL_CALC_CH(int chan)
            {
                uint env;
                int out1;
                phase_modulation = 0;
                env = volume_calc(chan, 0);
                out1 = P_CH[chan].SLOT[0].op1_out[0] + P_CH[chan].SLOT[0].op1_out[1];
                P_CH[chan].SLOT[0].op1_out[0] = P_CH[chan].SLOT[0].op1_out[1];
                if (P_CH[chan].SLOT[0].iconnect == 1)
                {
                    output0 += P_CH[chan].SLOT[0].op1_out[0];
                }
                else if (P_CH[chan].SLOT[0].iconnect == 2)
                {
                    phase_modulation += P_CH[chan].SLOT[0].op1_out[0];
                }
                else
                {
                    
                }
                P_CH[chan].SLOT[0].op1_out[1] = 0;
                if (env < 0x180)
                {
                    if (P_CH[chan].SLOT[0].FB == 0)
                    {
                        out1 = 0;
                    }
                    P_CH[chan].SLOT[0].op1_out[1] = op_calc1(P_CH[chan].SLOT[0].Cnt, (int)env, (out1 << P_CH[chan].SLOT[0].FB), P_CH[chan].SLOT[0].wavetable);
                }
                env = volume_calc(chan, 1);
                if (env < 0x180)
                {
                    output0 += op_calc(P_CH[chan].SLOT[1].Cnt, (int)env, phase_modulation, P_CH[chan].SLOT[1].wavetable);
                }
            }
            public void OPL_CALC_RH(uint noise)
            {
                int out1;
                uint env;
                phase_modulation = 0;
                env = volume_calc(6, 0);
                out1 = P_CH[6].SLOT[0].op1_out[0] + P_CH[6].SLOT[0].op1_out[1];
                P_CH[6].SLOT[0].op1_out[0] = P_CH[6].SLOT[0].op1_out[1];
                if (P_CH[6].SLOT[0].CON == 0)
                {
                    phase_modulation = P_CH[6].SLOT[0].op1_out[0];
                }
                P_CH[6].SLOT[0].op1_out[1] = 0;
                if (env < 0x180)
                {
                    if (P_CH[6].SLOT[0].FB == 0)
                    {
                        out1 = 0;
                    }
                    P_CH[6].SLOT[0].op1_out[1] = op_calc1(P_CH[6].SLOT[0].Cnt, (int)env, (out1 << P_CH[6].SLOT[0].FB), P_CH[6].SLOT[0].wavetable);
                }
                env = volume_calc(6, 1);
                if (env < 0x180)
                {
                    output0 += op_calc(P_CH[6].SLOT[1].Cnt, (int)env, phase_modulation, P_CH[6].SLOT[1].wavetable) * 2;
                }
                env = volume_calc(7, 0);
                if (env < 0x180)
                {
                    byte bit7 = (byte)(((P_CH[7].SLOT[0].Cnt >> 16) >> 7) & 1);
                    byte bit3 = (byte)(((P_CH[7].SLOT[0].Cnt >> 16) >> 3) & 1);
                    byte bit2 = (byte)(((P_CH[7].SLOT[0].Cnt >> 16) >> 2) & 1);
                    byte res1 = (byte)((bit2 ^ bit7) | bit3);
                    uint phase = (uint)(res1 != 0 ? (0x200 | (0xd0 >> 2)) : 0xd0);
                    byte bit5e = (byte)(((P_CH[8].SLOT[1].Cnt >> 16) >> 5) & 1);
                    byte bit3e = (byte)(((P_CH[8].SLOT[1].Cnt >> 16) >> 3) & 1);
                    byte res2 = (byte)(bit3e ^ bit5e);
                    if (res2 != 0)
                    {
                        phase = (0x200 | (0xd0 >> 2));
                    }
                    if ((phase & 0x200) != 0)
                    {
                        if (noise != 0)
                        {
                            phase = 0x200 | 0xd0;
                        }
                    }
                    else
                    {
                        if (noise != 0)
                        {
                            phase = 0xd0 >> 2;
                        }
                    }
                    output0 += op_calc(phase << 16, (int)env, 0, P_CH[7].SLOT[0].wavetable) * 2;
                }
                env = volume_calc(7, 1);
                if (env < 0x180)
                {
                    byte bit8 = (byte)(((P_CH[7].SLOT[0].Cnt >> 16) >> 8) & 1);
                    uint phase = (uint)(bit8 != 0 ? 0x200 : 0x100);
                    if (noise != 0)
                    {
                        phase ^= 0x100;
                    }
                    output0 += op_calc(phase << 16, (int)env, 0, P_CH[7].SLOT[1].wavetable) * 2;
                }
                env = volume_calc(8, 0);
                if (env < 0x180)
                {
                    output0 += op_calc(P_CH[8].SLOT[0].Cnt, (int)env, 0, P_CH[8].SLOT[0].wavetable) * 2;
                }
                env = volume_calc(8, 1);
                if (env < 0x180)
                {
                    byte bit7 = (byte)(((P_CH[7].SLOT[0].Cnt >> 16) >> 7) & 1);
                    byte bit3 = (byte)(((P_CH[7].SLOT[0].Cnt >> 16) >> 3) & 1);
                    byte bit2 = (byte)(((P_CH[7].SLOT[0].Cnt >> 16) >> 2) & 1);
                    byte res1 = (byte)((bit2 ^ bit7) | bit3);
                    uint phase = (uint)(res1 != 0 ? 0x300 : 0x100);
                    byte bit5e = (byte)(((P_CH[8].SLOT[1].Cnt >> 16) >> 5) & 1);
                    byte bit3e = (byte)(((P_CH[8].SLOT[1].Cnt >> 16) >> 3) & 1);
                    byte res2 = (byte)(bit3e ^ bit5e);
                    if (res2 != 0)
                    {
                        phase = 0x300;
                    }
                    output0 += op_calc(phase << 16, (int)env, 0, P_CH[8].SLOT[1].wavetable) * 2;
                }
            }
            public void OPL_STATUS_SET(int flag)
            {
                status |= (byte)flag;
                if ((status & 0x80) == 0)
                {
                    if ((status & statusmask) != 0)
                    {
                        status |= 0x80;
                        if (IRQHandler != null)
                        {
                            IRQHandler(1);
                        }
                    }
                }
            }
            public void OPL_STATUS_RESET(int flag)
            {
                status &= (byte)(~flag);
                if ((status & 0x80) != 0)
                {
                    if ((status & statusmask) == 0)
                    {
                        status &= 0x7f;
                        if (IRQHandler != null)
                        {
                            IRQHandler(0);
                        }
                    }
                }
            }
            public void OPL_STATUSMASK_SET(int flag)
            {
                statusmask = (byte)flag;
                OPL_STATUS_SET(0);
                OPL_STATUS_RESET(0);
            }
            public void advance_lfo()
            {
                byte tmp;
                lfo_am_cnt += lfo_am_inc;
                if (lfo_am_cnt >= ((uint)210 << 24))
                {
                    lfo_am_cnt -= ((uint)210 << 24);
                }
                tmp = lfo_am_table[lfo_am_cnt >> 24];
                if (lfo_am_depth != 0)
                {
                    LFO_AM = tmp;
                }
                else
                {
                    LFO_AM = (uint)(tmp >> 2);
                }
                lfo_pm_cnt += lfo_pm_inc;
                LFO_PM = (int)(((lfo_pm_cnt >> 24) & 7) | lfo_pm_depth_range);
            }
            public void advance()
            {
                int i;
                eg_timer += eg_timer_add;
                while (eg_timer >= eg_timer_overflow)
                {
                    eg_timer -= eg_timer_overflow;
                    eg_cnt++;
                    for (i = 0; i < 9 * 2; i++)
                    {
                        switch (P_CH[i / 2].SLOT[i & 1].state)
                        {
                            case 4:
                                if ((eg_cnt & ((1 << P_CH[i / 2].SLOT[i & 1].eg_sh_ar) - 1)) == 0)
                                {
                                    P_CH[i / 2].SLOT[i & 1].volume += (~P_CH[i / 2].SLOT[i & 1].volume * (eg_inc[P_CH[i / 2].SLOT[i & 1].eg_sel_ar + ((eg_cnt >> P_CH[i / 2].SLOT[i & 1].eg_sh_ar) & 7)])) >> 3;
                                    if (P_CH[i / 2].SLOT[i & 1].volume <= 0)
                                    {
                                        P_CH[i / 2].SLOT[i & 1].volume = 0;
                                        P_CH[i / 2].SLOT[i & 1].state = 3;
                                    }
                                }
                                break;
                            case 3:
                                if ((eg_cnt & ((1 << P_CH[i / 2].SLOT[i & 1].eg_sh_dr) - 1)) == 0)
                                {
                                    P_CH[i / 2].SLOT[i & 1].volume += eg_inc[P_CH[i / 2].SLOT[i & 1].eg_sel_dr + ((eg_cnt >> P_CH[i / 2].SLOT[i & 1].eg_sh_dr) & 7)];
                                    if (P_CH[i / 2].SLOT[i & 1].volume >= P_CH[i / 2].SLOT[i & 1].sl)
                                    {
                                        P_CH[i / 2].SLOT[i & 1].state = 2;
                                    }
                                }
                                break;
                            case 2:
                                if (P_CH[i / 2].SLOT[i & 1].eg_type != 0)
                                {

                                }
                                else
                                {
                                    if ((eg_cnt & ((1 << P_CH[i / 2].SLOT[i & 1].eg_sh_rr) - 1)) == 0)
                                    {
                                        P_CH[i / 2].SLOT[i & 1].volume += eg_inc[P_CH[i / 2].SLOT[i & 1].eg_sel_rr + ((eg_cnt >> P_CH[i / 2].SLOT[i & 1].eg_sh_rr) & 7)];
                                        if (P_CH[i / 2].SLOT[i & 1].volume >= 0x1ff)
                                        {
                                            P_CH[i / 2].SLOT[i & 1].volume = 0x1ff;
                                        }
                                    }
                                }
                                break;
                            case 1:
                                if ((eg_cnt & ((1 << P_CH[i / 2].SLOT[i & 1].eg_sh_rr) - 1)) == 0)
                                {
                                    P_CH[i / 2].SLOT[i & 1].volume += eg_inc[P_CH[i / 2].SLOT[i & 1].eg_sel_rr + ((eg_cnt >> P_CH[i / 2].SLOT[i & 1].eg_sh_rr) & 7)];
                                    if (P_CH[i / 2].SLOT[i & 1].volume >= 0x1ff)
                                    {
                                        P_CH[i / 2].SLOT[i & 1].volume = 0x1ff;
                                        P_CH[i / 2].SLOT[i & 1].state = 0;
                                    }
                                }
                                break;
                            default:
                                break;
                        }
                    }
                }
                for (i = 0; i < 9 * 2; i++)
                {
                    if (P_CH[i / 2].SLOT[i & 1].vib != 0)
                    {
                        byte block;
                        uint block_fnum = P_CH[i / 2].block_fnum;
                        uint fnum_lfo = (block_fnum & 0x0380) >> 7;
                        int lfo_fn_table_index_offset = lfo_pm_table[LFO_PM + 16 * fnum_lfo];
                        if (lfo_fn_table_index_offset != 0)
                        {
                            block_fnum += (uint)lfo_fn_table_index_offset;
                            block = (byte)((block_fnum & 0x1c00) >> 10);
                            P_CH[i / 2].SLOT[i & 1].Cnt += (fn_tab[block_fnum & 0x03ff] >> (7 - block)) * P_CH[i / 2].SLOT[i & 1].mul;
                        }
                        else
                        {
                            P_CH[i / 2].SLOT[i & 1].Cnt += P_CH[i / 2].SLOT[i & 1].Incr;
                        }
                    }
                    else
                    {
                        P_CH[i / 2].SLOT[i & 1].Cnt += P_CH[i / 2].SLOT[i & 1].Incr;
                    }
                }
                noise_p += noise_f;
                i = (int)(noise_p >> 16);
                noise_p &= 0xffff;
                while (i != 0)
                {
                    if ((noise_rng & 1) != 0)
                    {
                        noise_rng ^= 0x800302;
                    }
                    noise_rng >>= 1;
                    i--;
                }
            }
            private void CSMKeyControll(int chan)
            {
                FM_KEYON(chan, 0, 4);
                FM_KEYON(chan, 1, 4);
                FM_KEYOFF(chan, 0, 4);
                FM_KEYOFF(chan, 1, 4);
            }
            public int OPLTimerOver(int c)
            {
                if (c != 0)
                {
                    OPL_STATUS_SET(0x20);
                }
                else
                {
                    OPL_STATUS_SET(0x40);
                    if ((mode & 0x80) != 0)
                    {
                        int ch;
                        if (UpdateHandler != null)
                        {
                            UpdateHandler(0);
                        }
                        for (ch = 0; ch < 9; ch++)
                        {
                            CSMKeyControll(ch);
                        }
                    }
                }
                if (timer_handler != null)
                {
                    timer_handler(c, Attotime.attotime_mul(TimerBase, T[c]));
                }
                return status >> 7;
            }
            public void OPLSetTimerHandler(OPL_TIMERHANDLER _timer_handler)
            {
                timer_handler = _timer_handler;
            }
            public void OPLSetIRQHandler(OPL_IRQHANDLER _IRQHandler)
            {
                IRQHandler = _IRQHandler;
            }
            public void OPLSetUpdateHandler(OPL_UPDATEHANDLER _UpdateHandler)
            {
                UpdateHandler = _UpdateHandler;
            }
        }
        public static FM_OPL YM3812, YM3526;
        public static int[] slot_array=new int[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
        };
        public static uint[] ksl_tab=new uint[8*16]
        {
	        0,0,0,0,
	        0,0,0,0,
	        0,0,0,0,
	        0,0,0,0,

	        0,0,0,0,
	        0,0,0,0,
	        0,8,12,16,
	        20,24,28,32,

	        0,0,0,0,
	        0,12,20,28,
	        32,40,44,48,
	        52,56,60,64,

	        0,0,0,20,
	        32,44,52,60,
	        64,72,76,80,
	        84,88,92,96,

	        0,0,32,52,
	        64,76,84,92,
	        96,104,108,112,
	        116,120,124,128,

	        0,32,64,84,
	        96,108,116,124,
	        128,136,140,144,
	        148,152,156,160,

	        0,64,96,116,
	        128,140,148,156,
	        160,168,172,176,
	        180,184,188,192,

	        0,96,128,148,
	        160,172,180,188,
	        192,200,204,208,
	        212,216,220,224
        };
        public static uint[] sl_tab=new uint[16]
        {
            0*16, 1*16, 2*16,3 *16,4 *16,5 *16,6 *16, 7*16,
            8*16, 9*16,10*16,11*16,12*16,13*16,14*16,31*16
        };
        public static byte[] eg_inc=new byte[15*8]
        {
            0,1, 0,1, 0,1, 0,1,
            0,1, 0,1, 1,1, 0,1,
            0,1, 1,1, 0,1, 1,1,
            0,1, 1,1, 1,1, 1,1,

            1,1, 1,1, 1,1, 1,1,
            1,1, 1,2, 1,1, 1,2,
            1,2, 1,2, 1,2, 1,2,
            1,2, 2,2, 1,2, 2,2,

            2,2, 2,2, 2,2, 2,2,
            2,2, 2,4, 2,2, 2,4,
            2,4, 2,4, 2,4, 2,4,
            2,4, 4,4, 2,4, 4,4,

            4,4, 4,4, 4,4, 4,4,
            8,8, 8,8, 8,8, 8,8,
            0,0, 0,0, 0,0, 0,0,
        };
        public static byte[] eg_rate_select=new byte[16+64+16]
        {
            14*8,14*8,14*8,14*8,14*8,14*8,14*8,14*8,
            14*8,14*8,14*8,14*8,14*8,14*8,14*8,14*8,

            0, 1*8, 2*8, 3*8,
            0, 1*8, 2*8, 3*8,
            0, 1*8, 2*8, 3*8,
            0, 1*8, 2*8, 3*8,
            0, 1*8, 2*8, 3*8,
            0, 1*8, 2*8, 3*8,
            0, 1*8, 2*8, 3*8,
            0, 1*8, 2*8, 3*8,
            0, 1*8, 2*8, 3*8,
            0, 1*8, 2*8, 3*8,
            0, 1*8, 2*8, 3*8,
            0, 1*8, 2*8, 3*8,
            0, 1*8, 2*8, 3*8,

            4*8, 5*8, 6*8, 7*8,

            8*8, 9*8,10*8,11*8,

            12*8,12*8,12*8,12*8,

            12*8,12*8,12*8,12*8,12*8,12*8,12*8,12*8,
            12*8,12*8,12*8,12*8,12*8,12*8,12*8,12*8,
        };
        public static byte[] eg_rate_shift=new byte[16+64+16]
        {
            0,0,0,0,0,0,0,0,
            0,0,0,0,0,0,0,0,

            12,12,12,12,
            11,11,11,11,
            10,10,10,10,
            9, 9, 9, 9,
            8, 8, 8, 8,
            7, 7, 7, 7,
            6, 6, 6, 6,
            5, 5, 5, 5,
            4, 4, 4, 4,
            3, 3, 3, 3,
            2, 2, 2, 2,
            1, 1, 1, 1,
            0, 0, 0, 0,

            0, 0, 0, 0,

            0, 0, 0, 0,

            0, 0, 0, 0,

            0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0,
        };
        public static byte[] mul_tab=new byte[16]
        {
            1, 2, 4, 6, 8, 10, 12, 14,
            16, 18,20,20,24,24,30,30
        };
        public static int[] tl_tab;
        public static uint[] sin_tab;
        public static byte[] lfo_am_table=new byte[210]
        {
            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
        };
        public static sbyte[] lfo_pm_table=new sbyte[8*8*2]
        {
            0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0,

            0, 0, 0, 0, 0, 0, 0, 0,
            1, 0, 0, 0,-1, 0, 0, 0,

            1, 0, 0, 0,-1, 0, 0, 0,
            2, 1, 0,-1,-2,-1, 0, 1,

            1, 0, 0, 0,-1, 0, 0, 0,
            3, 1, 0,-1,-3,-1, 0, 1,

            2, 1, 0,-1,-2,-1, 0, 1,
            4, 2, 0,-2,-4,-2, 0, 2,

            2, 1, 0,-1,-2,-1, 0, 1,
            5, 2, 0,-2,-5,-2, 0, 2,

            3, 1, 0,-1,-3,-1, 0, 1,
            6, 3, 0,-3,-6,-3, 0, 3,

            3, 1, 0,-1,-3,-1, 0, 1,
            7, 3, 0,-3,-7,-3, 0, 3
        };
        public static int num_lock = 0;
        public static int phase_modulation;
        public static int output0;
        public static uint LFO_AM;
        public static int LFO_PM;
        public delegate byte OPL_PORTHANDLER_R();
        public delegate void OPL_PORTHANDLER_W(byte data);
        public delegate void OPL_TIMERHANDLER(int timer, Atime period);
        public delegate void OPL_IRQHANDLER(int irq);
        public delegate void OPL_UPDATEHANDLER(int min_interval_us);
        private static int limit(int val, int max, int min)
        {
            if (val > max)
            {
                return max;
            }
            else if (val < min)
            {
                return min;
            }
            else
            {
                return val;
            }
        }        
        private static int op_calc(uint phase, int env, int pm, int wave_tab)
        {
            uint p;
            p = (uint)((env << 4) + sin_tab[wave_tab + ((((int)((phase & ~0xffff) + (pm << 16))) >> 16) & 0x3ff)]);
            if (p >= 0x1800)
            {
                return 0;
            }
            return tl_tab[p];
        }        
        private static int init_tables()
        {
            int i, x;
            int n;
            double o, m;
            for (x = 0; x < 0x100; x++)
            {
                m = (1 << 16) / Math.Pow(2, (x + 1) * (1.0 / 32) / 8.0);
                m = Math.Floor(m);
                n = (int)m;
                n >>= 4;
                if ((n & 1) != 0)
                {
                    n = (n >> 1) + 1;
                }
                else
                {
                    n = n >> 1;
                }
                n <<= 1;
                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 * 0x100] = tl_tab[x * 2 + 0] >> i;
                    tl_tab[x * 2 + 1 + i * 2 * 0x100] = -tl_tab[x * 2 + 0 + i * 2 * 0x100];
                }
            }
            for (i = 0; i < 0x400; i++)
            {
                m = Math.Sin(((i * 2) + 1) * Math.PI / 0x400);
                if (m > 0.0)
                {
                    o = 8 * Math.Log(1.0 / m) / Math.Log(2);
                }
                else
                {
                    o = 8 * Math.Log(-1.0 / m) / Math.Log(2);
                }
                o = o * 32;
                n = (int)(2.0 * o);
                if ((n & 1) != 0)
                {
                    n = (n >> 1) + 1;
                }
                else
                {
                    n = n >> 1;
                }
                sin_tab[i] = (uint)(n * 2 + (m >= 0.0 ? 0 : 1));
            }
            for (i = 0; i < 0x400; i++)
            {
                if ((i & 0x200) != 0)
                {
                    sin_tab[0x400 + i] = 0x1800;
                }
                else
                {
                    sin_tab[0x400 + i] = sin_tab[i];
                }
                sin_tab[2 * 0x400 + i] = sin_tab[i & (0x3ff >> 1)];
                if ((i & 0x100) != 0)
                {
                    sin_tab[3 * 0x400 + i] = 0x1800;
                }
                else
                {
                    sin_tab[3 * 0x400 + i] = sin_tab[i & (0x3ff >> 2)];
                }
            }
            return 1;
        }        
        private static int OPL_LockTable()
        {
            num_lock++;
            if (num_lock > 1)
            {
                return 0;
            }
            if (init_tables()==0)
            {
                num_lock--;
                return -1;
            }
            return 0;
        }
        private static void OPL_UnLockTable()
        {
            if (num_lock != 0)
            {
                num_lock--;
            }
        }        
        private static FM_OPL OPLCreate(int type, int clock, int rate)
        {
            int i;
            FM_OPL OPL = new FM_OPL();
            OPL_LockTable();
            OPL = new FM_OPL();
            OPL.P_CH = new OPL_CH[9];
            OPL.fn_tab = new uint[1024];
            OPL.T = new uint[2];
            OPL.st = new byte[2];
            for (i = 0; i < 9; i++)
            {
                OPL.P_CH[i].SLOT = new OPL_SLOT[2];
                OPL.P_CH[i].SLOT[0].op1_out = new int[2];
                OPL.P_CH[i].SLOT[1].op1_out = new int[2];
            }
            OPL.type = (byte)type;
            OPL.clock = clock;
            OPL.rate = rate;
            OPL.OPL_initalize();
            return OPL;
        }
        private static void OPLDestroy()
        {
            OPL_UnLockTable();
        }        
        public static void ym3812_init(int sndindex, int clock, int rate)
        {
            YM3812 = OPLCreate(1, clock, rate);
            ym3812_reset_chip();
        }
        private static void ym3812_shutdown()
        {
            OPLDestroy();
        }
        public static void ym3812_reset_chip()
        {            
            YM3812.OPLResetChip();
            num_lock = 0;
        }
        public static int ym3812_write(int a, int v)
        {
            return YM3812.OPLWrite(a, v);
        }
        public static byte ym3812_read(int a)
        {
            return (byte)(YM3812.OPLRead(a) | 0x06);
        }
        public static int ym3812_timer_over(int c)
        {
            return YM3812.OPLTimerOver(c);
        }
        public static void ym3812_set_timer_handler(OPL_TIMERHANDLER timer_handler)
        {
	        YM3812.OPLSetTimerHandler(timer_handler);
        }
        public static void ym3812_set_irq_handler(OPL_IRQHANDLER IRQHandler)
        {
            YM3812.OPLSetIRQHandler(IRQHandler);
        }
        public static void ym3812_set_update_handler(OPL_UPDATEHANDLER UpdateHandler)
        {
            YM3812.OPLSetUpdateHandler(UpdateHandler);
        }
        public static void ym3812_update_one(int offset, int length)
        {
            byte rhythm = (byte)(YM3812.rhythm & 0x20);
            int i;
            for (i = 0; i < length; i++)
            {
                int lt;
                output0 = 0;
                YM3812.advance_lfo();
                YM3812.OPL_CALC_CH(0);
                YM3812.OPL_CALC_CH(1);
                YM3812.OPL_CALC_CH(2);
                YM3812.OPL_CALC_CH(3);
                YM3812.OPL_CALC_CH(4);
                YM3812.OPL_CALC_CH(5);
                if (rhythm == 0)
                {
                    YM3812.OPL_CALC_CH(6);
                    YM3812.OPL_CALC_CH(7);
                    YM3812.OPL_CALC_CH(8);
                }
                else
                {
                    YM3812.OPL_CALC_RH((YM3812.noise_rng >> 0) & 1);
                }
                lt = output0;
                lt = limit(lt, 32767, -32768);
                Sound.ym3812stream.streamoutput[0][offset + i] = lt;
                YM3812.advance();
            }
        }
        public static FM_OPL ym3526_init(int sndindex, int clock, int rate)
        {
            YM3526 = OPLCreate(0, clock, rate);
            ym3526_reset_chip();
            return YM3526;
        }
        private static void ym3526_shutdown()
        {
	        OPLDestroy();
        }
        public static void ym3526_reset_chip()
        {
	        YM3526.OPLResetChip();
            num_lock = 0;
        }
        public static int ym3526_write(int a, int v)
        {
	        return YM3526.OPLWrite(a, v);
        }
        public static byte ym3526_read(int a)
        {
	        return (byte)(YM3526.OPLRead(a) | 0x06);
        }
        public static void ym3526_set_timer_handler(OPL_TIMERHANDLER timer_handler)
        {
            YM3526.OPLSetTimerHandler(timer_handler);
        }
        public static void ym3526_set_irq_handler(OPL_IRQHANDLER IRQHandler)
        {
            YM3526.OPLSetIRQHandler(IRQHandler);
        }
        public static void ym3526_set_update_handler(OPL_UPDATEHANDLER UpdateHandler)
        {
            YM3526.OPLSetUpdateHandler(UpdateHandler);
        }
        public static int ym3526_timer_over(int c)
        {
	        return YM3526.OPLTimerOver(c);
        }
        public static void ym3526_update_one(int offset, int length)
        {
            byte rhythm = (byte)(YM3526.rhythm & 0x20);
            int i;
            for (i = 0; i < length; i++)
            {
                int lt;
                output0 = 0;
                YM3526.advance_lfo();
                YM3526.OPL_CALC_CH(0);
                YM3526.OPL_CALC_CH(1);
                YM3526.OPL_CALC_CH(2);
                YM3526.OPL_CALC_CH(3);
                YM3526.OPL_CALC_CH(4);
                YM3526.OPL_CALC_CH(5);
                if (rhythm == 0)
                {
                    YM3526.OPL_CALC_CH(6);
                    YM3526.OPL_CALC_CH(7);
                    YM3526.OPL_CALC_CH(8);
                }
                else
                {
                    YM3526.OPL_CALC_RH((YM3526.noise_rng >> 0) & 1);
                }
                lt = output0;
                lt = limit(lt, 32767, -32768);
                Sound.ym3526stream.streamoutput[0][offset + i] = lt;
                YM3526.advance();
            }
        }
    }
}
