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

namespace mame
{
    public class FM
    {
        public class FM_OPN
        {
            public int type;
            public FM_ST ST;
            public FM_3SLOT SL3;
            public FM_CH[] CH;
            public uint[] pan;
            public uint eg_cnt;
            public uint eg_timer;
            public uint eg_timer_add;
            public uint eg_timer_overflow;
            public uint[] fn_table;
            public int lfo_cnt;
            public int lfo_inc;
            public int[] lfo_freq;
            public int[,] idt_tab;
            private int[] iconnect1 = new int[8], iconnect2 = new int[8], iconnect3 = new int[8], iconnect4 = new int[6], imem = new int[13];
            public FM_OPN()
            {
                int i;
                CH = new FM_CH[6];
                for (i = 0; i < 6; i++)
                {
                    CH[i].SLOT = new FM_SLOT[4];
                }
                idt_tab = new int[6, 4];
                ST = new FM_ST();
                ST.dt_tab2 = new int[8, 32];
                SL3 = new FM_3SLOT();
                SL3.fc = new uint[3];
                SL3.kcode = new byte[3];
                SL3.block_fnum = new uint[3];
                pan = new uint[12];
                fn_table = new uint[4096];
                lfo_freq = new int[8];
                ST.timer_handler = null;
                ST.IRQ_Handler = null;
                ST.SSG.set_clock = null;
                ST.SSG.write = null;
                ST.SSG.read = null;
                ST.SSG.reset = null;
            }
            private void FM_STATUS_SET(byte flag)
            {
                ST.status |= flag;
                if ((ST.irq == 0) && ((ST.status & ST.irqmask) != 0))
                {
                    ST.irq = 1;
                    if (ST.IRQ_Handler != null)
                    {
                        ST.IRQ_Handler(1);
                    }
                }
            }
            public void FM_STATUS_RESET(byte flag)
            {
                ST.status &= (byte)~flag;
                if ((ST.irq != 0) && ((ST.status & ST.irqmask) == 0))
                {
                    ST.irq = 0;
                    if (ST.IRQ_Handler != null)
                    {
                        ST.IRQ_Handler(0);
                    }
                }
            }
            public void FM_IRQMASK_SET(byte flag)
            {
                ST.irqmask = flag;
                FM_STATUS_SET(0);
                FM_STATUS_RESET(0);
            }
            private void set_timers(byte v)
            {
                ST.mode = v;
                if ((v & 0x20) != 0)
                {
                    FM_STATUS_RESET(0x02);
                }
                if ((v & 0x10) != 0)
                {
                    FM_STATUS_RESET(0x01);
                }
                if ((v & 0x02) != 0)
                {
                    if (ST.TBC == 0)
                    {
                        ST.TBC = (256 - ST.TB) << 4;
                        ST.timer_handler(1, ST.TBC * ST.timer_prescaler, ST.clock);
                    }
                }
                else
                {
                    if (ST.TBC != 0)
                    {
                        ST.TBC = 0;
                        ST.timer_handler(1, 0, ST.clock);
                    }
                }
                if ((v & 0x01) != 0)
                {
                    if (ST.TAC == 0)
                    {
                        ST.TAC = (1024 - ST.TA);
                        ST.timer_handler(0, ST.TAC * ST.timer_prescaler, ST.clock);
                    }
                }
                else
                {
                    if (ST.TAC != 0)
                    {
                        ST.TAC = 0;
                        ST.timer_handler(0, 0, ST.clock);
                    }
                }
            }
            public void TimerAOver()
            {
                if ((ST.mode & 0x04) != 0)
                {
                    FM_STATUS_SET(0x01);
                }
                ST.TAC = (1024 - ST.TA);
                ST.timer_handler(0, ST.TAC * ST.timer_prescaler, ST.clock);
            }
            public void TimerBOver()
            {
                if ((ST.mode & 0x08) != 0)
                {
                    FM_STATUS_SET(0x02);
                }
                ST.TBC = (256 - ST.TB) << 4;
                ST.timer_handler(1, ST.TBC * ST.timer_prescaler, ST.clock);
            }
            public void FM_BUSY_CLEAR()
            {
                ST.busy_expiry_time = Attotime.ATTOTIME_ZERO;
            }
            public byte FM_STATUS_FLAG()
            {
                if (Attotime.attotime_compare(ST.busy_expiry_time, Attotime.ATTOTIME_ZERO) != 0)
                {
                    if (Attotime.attotime_compare(ST.busy_expiry_time, Timer.get_current_time()) > 0)
                    {
                        return (byte)(ST.status | 0x80);
                    }
                    FM_BUSY_CLEAR();
                }
                return ST.status;
            }
            public void FM_BUSY_SET(int busyclock)
            {
                Atime expiry_period = Attotime.attotime_mul(Attotime.ATTOTIME_IN_HZ(ST.clock), (uint)(busyclock * ST.timer_prescaler));
                ST.busy_expiry_time = Attotime.attotime_add(Timer.get_current_time(), expiry_period);
            }
            private void FM_KEYON(int type,int c, int s)
            {
                if (CH[c].SLOT[s].key == 0)
                {
                    CH[c].SLOT[s].key = 1;
                    CH[c].SLOT[s].phase = 0;
                    if ((type == TYPE_YM2612) || (type == TYPE_YM2608))
                    {
                        if ((CH[c].SLOT[s].ar + CH[c].SLOT[s].ksr) < 32 + 62)
                        {
                            CH[c].SLOT[s].state = 4;
                            CH[c].SLOT[s].volume = 1023;
                        }
                        else
                        {
                            CH[c].SLOT[s].state = 3;
                            CH[c].SLOT[s].volume = 0;
                        }
                    }
                    else
                    {
                        CH[c].SLOT[s].state = 4;
                    }
                }
            }
            private void FM_KEYOFF(int c, int s)
            {
                if (CH[c].SLOT[s].key != 0)
                {
                    CH[c].SLOT[s].key = 0;
                    if (CH[c].SLOT[s].state > 1)
                    {
                        CH[c].SLOT[s].state = 1;
                    }
                }
            }
            private void set_value1(int c)
            {
                if (iconnect1[c] == 12)
                {
                    out_fm[11] = out_fm[9] = out_fm[10] = CH[c].op1_out0;
                }
                else
                {
                    out_fm[iconnect1[c]] = CH[c].op1_out0;
                }
            }
            private void set_mem(int c)
            {
                if (imem[c] == 8 || imem[c] == 10 || imem[c] == 11)
                {
                    out_fm[imem[c]] = CH[c].mem_value;
                }
            }
            private void setup_connection(int ch)
            {
                switch (CH[ch].ALGO)
                {
                    case 0:
                        iconnect1[ch] = 9;
                        iconnect2[ch] = 11;
                        iconnect3[ch] = 10;
                        imem[ch] = 8;
                        break;
                    case 1:
                        iconnect1[ch] = 11;
                        iconnect2[ch] = 11;
                        iconnect3[ch] = 10;
                        imem[ch] = 8;
                        break;
                    case 2:
                        iconnect1[ch] = 10;
                        iconnect2[ch] = 11;
                        iconnect3[ch] = 10;
                        imem[ch] = 8;
                        break;
                    case 3:
                        iconnect1[ch] = 9;
                        iconnect2[ch] = 11;
                        iconnect3[ch] = 10;
                        imem[ch] = 10;
                        break;
                    case 4:
                        iconnect1[ch] = 9;
                        iconnect2[ch] = ch;
                        iconnect3[ch] = 10;
                        imem[ch] = 11;
                        break;
                    case 5:
                        iconnect1[ch] = 12;
                        iconnect2[ch] = ch;
                        iconnect3[ch] = ch;
                        imem[ch] = 8;
                        break;
                    case 6:
                        iconnect1[ch] = 9;
                        iconnect2[ch] = ch;
                        iconnect3[ch] = ch;
                        imem[ch] = 11;
                        break;
                    case 7:
                        iconnect1[ch] = ch;
                        iconnect2[ch] = ch;
                        iconnect3[ch] = ch;
                        imem[ch] = 11;
                        break;
                }
                iconnect4[ch] = ch;
            }
            private void set_det_mul(int c, int s, byte v)
            {
                CH[c].SLOT[s].mul = ((v & 0x0f) != 0) ? (v & 0x0f) * 2 : 1;
                idt_tab[c, s] = (v >> 4) & 7;
                CH[c].SLOT[0].Incr = -1;
            }
            private void set_tl(int c, int s, byte v)
            {
                CH[c].SLOT[s].tl = (v & 0x7f) << (10 - 7);
            }
            private void set_ar_ksr(int c, int s, byte v)
            {
                byte old_KSR = CH[c].SLOT[s].KSR;
                CH[c].SLOT[s].ar = ((v & 0x1f) != 0) ? 32 + ((v & 0x1f) << 1) : 0;
                CH[c].SLOT[s].KSR = (byte)(3 - (v >> 6));
                if (CH[c].SLOT[s].KSR != old_KSR)
                {
                    CH[c].SLOT[0].Incr = -1;
                }
                if ((CH[c].SLOT[s].ar + CH[c].SLOT[s].ksr) < 32 + 62)
                {
                    CH[c].SLOT[s].eg_sh_ar = eg_rate_shift[CH[c].SLOT[s].ar + CH[c].SLOT[s].ksr];
                    if ((type == TYPE_YM2612) || (type == TYPE_YM2608))
                    {
                        CH[c].SLOT[s].eg_sel_ar = eg_rate_select2612[CH[c].SLOT[s].ar + CH[c].SLOT[s].ksr];
                    }
                    else
                    {
                        CH[c].SLOT[s].eg_sel_ar = eg_rate_select[CH[c].SLOT[s].ar + CH[c].SLOT[s].ksr];
                    }
                }
                else
                {
                    CH[c].SLOT[s].eg_sh_ar = 0;
                    CH[c].SLOT[s].eg_sel_ar = 17 * 8;
                }
            }
            private void set_dr(int c, int s, byte v)
            {
                CH[c].SLOT[s].d1r = ((v & 0x1f) != 0) ? 32 + ((v & 0x1f) << 1) : 0;
                CH[c].SLOT[s].eg_sh_d1r = eg_rate_shift[CH[c].SLOT[s].d1r + CH[c].SLOT[s].ksr];
                if ((type == TYPE_YM2612) || (type == TYPE_YM2608))
                {
                    CH[c].SLOT[s].eg_sel_d1r = eg_rate_select2612[CH[c].SLOT[s].d1r + CH[c].SLOT[s].ksr];
                }
                else
                {
                    CH[c].SLOT[s].eg_sel_d1r = eg_rate_select[CH[c].SLOT[s].d1r + CH[c].SLOT[s].ksr];
                }
            }
            private void set_sr(int c, int s, byte v)
            {
                CH[c].SLOT[s].d2r = ((v & 0x1f) != 0) ? 32 + ((v & 0x1f) << 1) : 0;
                CH[c].SLOT[s].eg_sh_d2r = eg_rate_shift[CH[c].SLOT[s].d2r + CH[c].SLOT[s].ksr];
                if ((type == TYPE_YM2612) || (type == TYPE_YM2608))
                {
                    CH[c].SLOT[s].eg_sel_d2r = eg_rate_select2612[CH[c].SLOT[s].d2r + CH[c].SLOT[s].ksr];
                }
                else
                {
                    CH[c].SLOT[s].eg_sel_d2r = eg_rate_select[CH[c].SLOT[s].d2r + CH[c].SLOT[s].ksr];
                }
            }
            private void set_sl_rr(int c, int s, byte v)
            {
                CH[c].SLOT[s].sl = sl_table[v >> 4];
                CH[c].SLOT[s].rr = 34 + ((v & 0x0f) << 2);
                CH[c].SLOT[s].eg_sh_rr = eg_rate_shift[CH[c].SLOT[s].rr + CH[c].SLOT[s].ksr];
                if ((type == TYPE_YM2612) || (type == TYPE_YM2608))
                {
                    CH[c].SLOT[s].eg_sel_rr = eg_rate_select2612[CH[c].SLOT[s].rr + CH[c].SLOT[s].ksr];
                }
                else
                {
                    CH[c].SLOT[s].eg_sel_rr = eg_rate_select[CH[c].SLOT[s].rr + CH[c].SLOT[s].ksr];
                }
            }
            public void advance_lfo()
            {
                byte pos;
                byte prev_pos;
                if (lfo_inc != 0)
                {
                    prev_pos = (byte)(lfo_cnt >> 24 & 127);
                    lfo_cnt += lfo_inc;
                    pos = (byte)((lfo_cnt >> 24) & 127);
                    if (pos < 64)
                    {
                        LFO_AM = (pos & 63) * 2;
                    }
                    else
                    {
                        LFO_AM = 126 - ((pos & 63) * 2);
                    }
                    prev_pos >>= 2;
                    pos >>= 2;
                    LFO_PM = pos;
                }
                else
                {
                    LFO_AM = 0;
                    LFO_PM = 0;
                }
            }
            public void advance_eg_channel(int c)
            {
                uint out1;
                byte swap_flag = 0;
                int i;
                for (i = 0; i < 4; i++)
                {
                    switch (CH[c].SLOT[i].state)
                    {
                        case 4:
                            if ((eg_cnt & ((1 << CH[c].SLOT[i].eg_sh_ar) - 1)) == 0)
                            {
                                CH[c].SLOT[i].volume += (~CH[c].SLOT[i].volume * (eg_inc[CH[c].SLOT[i].eg_sel_ar + ((eg_cnt >> CH[c].SLOT[i].eg_sh_ar) & 7)])) >> 4;
                                if (CH[c].SLOT[i].volume <= 0)
                                {
                                    CH[c].SLOT[i].volume = 0;
                                    CH[c].SLOT[i].state = 3;
                                }
                            }
                            break;

                        case 3:
                            if ((CH[c].SLOT[i].ssg & 0x08) != 0)
                            {
                                if ((eg_cnt & ((1 << CH[c].SLOT[i].eg_sh_d1r) - 1)) == 0)
                                {
                                    CH[c].SLOT[i].volume += 4 * eg_inc[CH[c].SLOT[i].eg_sel_d1r + ((eg_cnt >> CH[c].SLOT[i].eg_sh_d1r) & 7)];
                                    if (CH[c].SLOT[i].volume >= CH[c].SLOT[i].sl)
                                    {
                                        CH[c].SLOT[i].state = 2;
                                    }
                                }
                            }
                            else
                            {
                                if ((eg_cnt & ((1 << CH[c].SLOT[i].eg_sh_d1r) - 1)) == 0)
                                {
                                    CH[c].SLOT[i].volume += eg_inc[CH[c].SLOT[i].eg_sel_d1r + ((eg_cnt >> CH[c].SLOT[i].eg_sh_d1r) & 7)];
                                    if (CH[c].SLOT[i].volume >= CH[c].SLOT[i].sl)
                                    {
                                        CH[c].SLOT[i].state = 2;
                                    }
                                }
                            }
                            break;
                        case 2:
                            if ((CH[c].SLOT[i].ssg & 0x08) != 0)
                            {
                                if ((eg_cnt & ((1 << CH[c].SLOT[i].eg_sh_d2r) - 1)) == 0)
                                {
                                    CH[c].SLOT[i].volume += 4 * eg_inc[CH[c].SLOT[i].eg_sel_d2r + ((eg_cnt >> CH[c].SLOT[i].eg_sh_d2r) & 7)];
                                    if (CH[c].SLOT[i].volume >= 512)
                                    {
                                        CH[c].SLOT[i].volume = 0x3ff;
                                        if ((CH[c].SLOT[i].ssg & 0x01) != 0)
                                        {
                                            if ((CH[c].SLOT[i].ssgn & 1) != 0)
                                            {

                                            }
                                            else
                                            {
                                                swap_flag = (byte)((CH[c].SLOT[i].ssg & 0x02) | 1);
                                            }
                                        }
                                        else
                                        {
                                            CH[c].SLOT[i].phase = 0;
                                            CH[c].SLOT[i].volume = 511;
                                            CH[c].SLOT[i].state = 4;
                                            swap_flag = (byte)(CH[c].SLOT[i].ssg & 0x02); /* bit 1 = alternate */
                                        }
                                    }
                                }
                            }
                            else
                            {
                                if ((eg_cnt & ((1 << CH[c].SLOT[i].eg_sh_d2r) - 1)) == 0)
                                {
                                    CH[c].SLOT[i].volume += eg_inc[CH[c].SLOT[i].eg_sel_d2r + ((eg_cnt >> CH[c].SLOT[i].eg_sh_d2r) & 7)];
                                    if (CH[c].SLOT[i].volume >= 0x3ff)
                                    {
                                        CH[c].SLOT[i].volume = 0x3ff;
                                    }
                                }
                            }
                            break;
                        case 1:
                            if ((eg_cnt & ((1 << CH[c].SLOT[i].eg_sh_rr) - 1)) == 0)
                            {
                                CH[c].SLOT[i].volume += eg_inc[CH[c].SLOT[i].eg_sel_rr + ((eg_cnt >> CH[c].SLOT[i].eg_sh_rr) & 7)];
                                if (CH[c].SLOT[i].volume >= 0x3ff)
                                {
                                    CH[c].SLOT[i].volume = 0x3ff;
                                    CH[c].SLOT[i].state = 0;
                                }
                            }
                            break;
                    }
                    out1 = (uint)(CH[c].SLOT[i].tl + ((uint)CH[c].SLOT[i].volume));
                    if (((CH[c].SLOT[i].ssg & 0x08) != 0) && ((CH[c].SLOT[i].ssgn & 2) != 0) && (CH[c].SLOT[i].state != 0))
                    {
                        out1 ^= 511;
                    }
                    CH[c].SLOT[i].vol_out = out1;
                    CH[c].SLOT[i].ssgn ^= swap_flag;
                }
            }
            private uint volume_calc(int c, int s)
            {
                int AM = LFO_AM >> CH[c].ams;
                return (uint)(CH[c].SLOT[s].vol_out + (AM & CH[c].SLOT[s].AMmask));
            }
            private void update_phase_lfo_slot(int s, int pms, uint block_fnum)
            {
                uint fnum_lfo = ((block_fnum & 0x7f0) >> 4) * 32 * 8;
                int lfo_fn_table_index_offset = lfo_pm_table[fnum_lfo + pms + LFO_PM];
                if (lfo_fn_table_index_offset != 0)
                {
                    byte blk;
                    uint fn;
                    int kc, fc;
                    block_fnum = (uint)(block_fnum * 2 + lfo_fn_table_index_offset);
                    blk = (byte)((block_fnum & 0x7000) >> 12);
                    fn = block_fnum & 0xfff;
                    kc = (blk << 2) | opn_fktable[fn >> 8];
                    fc = (int)((fn_table[fn] >> (7 - blk)) + ST.dt_tab2[idt_tab[2, 0], kc]);
                    if (fc < 0)
                    {
                        fc += fn_max;
                    }
                    CH[2].SLOT[s].phase += (uint)((fc * CH[2].SLOT[s].mul) >> 1);
                }
                else
                {
                    CH[2].SLOT[s].phase += (uint)CH[2].SLOT[s].Incr;
                }
            }
            private void update_phase_lfo_channel(FM_CH[] CH, int c)
            {
                uint block_fnum = CH[c].block_fnum;
                uint fnum_lfo = ((block_fnum & 0x7f0) >> 4) * 32 * 8;
                int lfo_fn_table_index_offset = lfo_pm_table[fnum_lfo + CH[c].pms + LFO_PM];
                if (lfo_fn_table_index_offset != 0)
                {
                    byte blk;
                    uint fn;
                    int kc, fc, finc;
                    block_fnum = (uint)(block_fnum * 2 + lfo_fn_table_index_offset);
                    blk = (byte)((block_fnum & 0x7000) >> 12);
                    fn = block_fnum & 0xfff;
                    kc = (blk << 2) | opn_fktable[fn >> 8];
                    fc = (int)(fn_table[fn] >> (7 - blk));
                    finc = fc + ST.dt_tab2[idt_tab[c, 0], kc];
                    if (finc < 0)
                    {
                        finc += fn_max;
                    }
                    CH[c].SLOT[0].phase += (uint)((finc * CH[c].SLOT[0].mul) >> 1);
                    finc = fc + ST.dt_tab2[idt_tab[c, 2], kc];
                    if (finc < 0)
                    {
                        finc += fn_max;
                    }
                    CH[c].SLOT[2].phase += (uint)((finc * CH[c].SLOT[2].mul) >> 1);
                    finc = fc + ST.dt_tab2[idt_tab[c, 1], kc];
                    if (finc < 0)
                    {
                        finc += fn_max;
                    }
                    CH[c].SLOT[1].phase += (uint)((finc * CH[c].SLOT[1].mul) >> 1);
                    finc = fc + ST.dt_tab2[idt_tab[c, 3], kc];
                    if (finc < 0)
                    {
                        finc += fn_max;
                    }
                    CH[c].SLOT[3].phase += (uint)((finc * CH[c].SLOT[3].mul) >> 1);
                }
                else
                {
                    CH[c].SLOT[0].phase += (uint)CH[c].SLOT[0].Incr;
                    CH[c].SLOT[2].phase += (uint)CH[c].SLOT[2].Incr;
                    CH[c].SLOT[1].phase += (uint)CH[c].SLOT[1].Incr;
                    CH[c].SLOT[3].phase += (uint)CH[c].SLOT[3].Incr;
                }
            }
            public void chan_calc(int c, int chnum)
            {
                uint eg_out;
                out_fm[8] = out_fm[9] = out_fm[10] = out_fm[11] = 0;//m2 = c1 = c2 = mem = 0;
                set_mem(c);
                eg_out = volume_calc(c, 0);
                int out1 = CH[c].op1_out0 + CH[c].op1_out1;
                CH[c].op1_out0 = CH[c].op1_out1;
                set_value1(c);
                CH[c].op1_out1 = 0;
                if (eg_out < 832)
                {
                    if (CH[c].FB == 0)
                    {
                        out1 = 0;
                    }
                    CH[c].op1_out1 = op_calc1(CH[c].SLOT[0].phase, eg_out, (out1 << CH[c].FB));
                }
                eg_out = volume_calc(c, 1);
                if (eg_out < 832)
                {
                    out_fm[iconnect3[c]] += op_calc(CH[c].SLOT[1].phase, eg_out, out_fm[8]);
                }
                eg_out = volume_calc(c, 2);
                if (eg_out < 832)
                {
                    out_fm[iconnect2[c]] += op_calc(CH[c].SLOT[2].phase, eg_out, out_fm[9]);
                }
                eg_out = volume_calc(c, 3);
                if (eg_out < 832)
                {
                    out_fm[iconnect4[c]] += op_calc(CH[c].SLOT[3].phase, eg_out, out_fm[10]);
                }
                CH[c].mem_value = out_fm[11];//mem;
                if (CH[c].pms != 0)
                {
                    if (((ST.mode & 0xC0) != 0) && (chnum == 2))
                    {
                        update_phase_lfo_slot(0, CH[c].pms, SL3.block_fnum[1]);
                        update_phase_lfo_slot(2, CH[c].pms, SL3.block_fnum[2]);
                        update_phase_lfo_slot(1, CH[c].pms, SL3.block_fnum[0]);
                        update_phase_lfo_slot(3, CH[c].pms, CH[c].block_fnum);
                    }
                    else
                    {
                        update_phase_lfo_channel(CH, c);
                    }
                }
                else
                {
                    CH[c].SLOT[0].phase += (uint)CH[c].SLOT[0].Incr;
                    CH[c].SLOT[2].phase += (uint)CH[c].SLOT[2].Incr;
                    CH[c].SLOT[1].phase += (uint)CH[c].SLOT[1].Incr;
                    CH[c].SLOT[3].phase += (uint)CH[c].SLOT[3].Incr;
                }                
            }
            public void refresh_fc_eg_slot(int type, int c, int s, int fc, int kc)
            {
                int ksr = kc >> CH[c].SLOT[s].KSR;
                fc += ST.dt_tab2[idt_tab[c, s], kc];
                if (fc < 0)
                {
                    fc += fn_max;
                }
                CH[c].SLOT[s].Incr = (fc * CH[c].SLOT[s].mul) >> 1;
                if (CH[c].SLOT[s].ksr != ksr)
                {
                    CH[c].SLOT[s].ksr = (byte)ksr;
                    if ((CH[c].SLOT[s].ar + CH[c].SLOT[s].ksr) < 32 + 62)
                    {
                        CH[c].SLOT[s].eg_sh_ar = eg_rate_shift[CH[c].SLOT[s].ar + CH[c].SLOT[s].ksr];
                        if ((type == TYPE_YM2612) || (type == TYPE_YM2608))
                        {
                            CH[c].SLOT[s].eg_sel_ar = eg_rate_select2612[CH[c].SLOT[s].ar + CH[c].SLOT[s].ksr];
                        }
                        else
                        {
                            CH[c].SLOT[s].eg_sel_ar = eg_rate_select[CH[c].SLOT[s].ar + CH[c].SLOT[s].ksr];
                        }
                    }
                    else
                    {
                        CH[c].SLOT[s].eg_sh_ar = 0;
                        CH[c].SLOT[s].eg_sel_ar = 17 * 8;
                    }
                    CH[c].SLOT[s].eg_sh_d1r = eg_rate_shift[CH[c].SLOT[s].d1r + CH[c].SLOT[s].ksr];
                    CH[c].SLOT[s].eg_sh_d2r = eg_rate_shift[CH[c].SLOT[s].d2r + CH[c].SLOT[s].ksr];
                    CH[c].SLOT[s].eg_sh_rr = eg_rate_shift[CH[c].SLOT[s].rr + CH[c].SLOT[s].ksr];
                    if ((type == TYPE_YM2612) || (type == TYPE_YM2608))
                    {
                        CH[c].SLOT[s].eg_sel_d1r = eg_rate_select2612[CH[c].SLOT[s].d1r + CH[c].SLOT[s].ksr];
                        CH[c].SLOT[s].eg_sel_d2r = eg_rate_select2612[CH[c].SLOT[s].d2r + CH[c].SLOT[s].ksr];
                        CH[c].SLOT[s].eg_sel_rr = eg_rate_select2612[CH[c].SLOT[s].rr + CH[c].SLOT[s].ksr];
                    }
                    else
                    {
                        CH[c].SLOT[s].eg_sel_d1r = eg_rate_select[CH[c].SLOT[s].d1r + CH[c].SLOT[s].ksr];
                        CH[c].SLOT[s].eg_sel_d2r = eg_rate_select[CH[c].SLOT[s].d2r + CH[c].SLOT[s].ksr];
                        CH[c].SLOT[s].eg_sel_rr = eg_rate_select[CH[c].SLOT[s].rr + CH[c].SLOT[s].ksr];
                    }
                }
            }
            public void refresh_fc_eg_chan(int type, int c)
            {
                if (CH[c].SLOT[0].Incr == -1)
                {
                    int fc = (int)CH[c].fc;
                    int kc = CH[c].kcode;
                    refresh_fc_eg_slot(type, c, 0, fc, kc);
                    refresh_fc_eg_slot(type, c, 2, fc, kc);
                    refresh_fc_eg_slot(type, c, 1, fc, kc);
                    refresh_fc_eg_slot(type, c, 3, fc, kc);
                }
            }
            private void init_timetables()
            {
                int i, d;
                double rate;
                for (d = 0; d <= 3; d++)
                {
                    for (i = 0; i <= 31; i++)
                    {
                        rate = ((double)dt_tab[d * 32 + i]) * 1024 * ST.freqbase * (1 << 16) / ((double)(1 << 20));
                        ST.dt_tab2[d, i] = (int)rate;
                        ST.dt_tab2[d + 4, i] = -ST.dt_tab2[d, i];
                    }
                }
            }
            public void reset_channels(int num)
            {
                int c, s;
                ST.mode = 0;	/* normal mode */
                ST.TA = 0;
                ST.TAC = 0;
                ST.TB = 0;
                ST.TBC = 0;
                for (c = 0; c < num; c++)
                {
                    CH[c].fc = 0;
                    for (s = 0; s < 4; s++)
                    {
                        CH[c].SLOT[s].ssg = 0;
                        CH[c].SLOT[s].ssgn = 0;
                        CH[c].SLOT[s].state = 0;
                        CH[c].SLOT[s].volume = 0x3ff;
                        CH[c].SLOT[s].vol_out = 0x3ff;
                    }
                }
            }
            public void CSMKeyControll()
            {
                FM_KEYON(type, 2, 0);
                FM_KEYON(type, 2, 2);
                FM_KEYON(type, 2, 1);
                FM_KEYON(type, 2, 3);
            }
            public void OPNSetPres(int pres, int timer_prescaler, int SSGpres)
            {
                int i;
                ST.freqbase = (ST.rate!=0) ? ((double)ST.clock / ST.rate) / pres : 0;
                eg_timer_add = (uint)((1 << 16) * ST.freqbase);
                eg_timer_overflow = (3) * (1 << 16);
                ST.timer_prescaler = timer_prescaler;
                if (SSGpres != 0)
                {
                    ST.SSG.set_clock(ST.clock * 2 / SSGpres);
                }
                init_timetables();
                for (i = 0; i < 4096; i++)
                {
                    fn_table[i] = (uint)((double)i * 32 * ST.freqbase * (1 << (16 - 10)));
                }
                fn_max = (int)((uint)(((double)fn_table[0x7ff * 2] / ST.freqbase)) >> 2);//2096127
                for (i = 0; i < 8; i++)
                {
                    lfo_freq[i] = (int)((1.0 / lfo_samples_per_step[i]) * (1 << 24) * ST.freqbase);
                }
            }
            public void OPNWriteMode(int r, byte v)
            {
                byte c;
                switch (r)
                {
                    case 0x21:
                        break;
                    case 0x22:
                        if ((type & TYPE_LFOPAN) != 0)
                        {
                            if ((v & 0x08) != 0)
                            {
                                lfo_inc = lfo_freq[v & 7];
                            }
                            else
                            {
                                lfo_inc = 0;
                            }
                        }
                        break;
                    case 0x24:
                        ST.TA = (ST.TA & 0x03) | (((int)v) << 2);
                        break;
                    case 0x25:
                        ST.TA = (ST.TA & 0x3fc) | (v & 3);
                        break;
                    case 0x26:
                        ST.TB = v;
                        break;
                    case 0x27:
                        set_timers(v);
                        break;
                    case 0x28:
                        c = (byte)(v & 0x03);
                        if (c == 3)
                            break;
                        if ((v & 0x04) != 0 && (type & TYPE_6CH)!=0)
                            c += 3;
                        if ((v & 0x10) != 0)
                            FM_KEYON(type, c, 0);
                        else
                            FM_KEYOFF(c, 0);
                        if ((v & 0x20) != 0)
                            FM_KEYON(type, c, 2);
                        else
                            FM_KEYOFF(c, 2);
                        if ((v & 0x40) != 0)
                            FM_KEYON(type, c, 1);
                        else
                            FM_KEYOFF(c, 1);
                        if ((v & 0x80) != 0)
                            FM_KEYON(type, c, 3);
                        else
                            FM_KEYOFF(c, 3);
                        break;
                }
            }
            public void OPNWriteReg(int r, byte v)
            {
                byte c = (byte)(r & 3);
                int s = (r >> 2) & 3;
                if (c == 3)
                    return;
                if (r >= 0x100)
                    c += 3;
                switch (r & 0xf0)
                {
                    case 0x30:
                        set_det_mul(c, s, v);
                        break;
                    case 0x40:
                        set_tl(c, s, v);
                        break;
                    case 0x50:
                        set_ar_ksr(c, s, v);
                        break;
                    case 0x60:
                        set_dr(c, s, v);
                        if ((type & TYPE_LFOPAN) != 0)
                        {
                            CH[c].SLOT[s].AMmask = ((v & 0x80) != 0) ? 0xffffffff : 0;
                        }
                        break;
                    case 0x70:
                        set_sr(c, s, v);
                        break;
                    case 0x80:
                        set_sl_rr(c, s, v);
                        break;
                    case 0x90:
                        CH[c].SLOT[s].ssg = (byte)(v & 0x0f);
                        CH[c].SLOT[s].ssgn = (byte)((v & 0x04) >> 1);
                        break;
                    case 0xa0:
                        switch ((r >> 2) & 3)
                        {
                            case 0:
                                {
                                    uint fn = (((uint)((ST.fn_h) & 7)) << 8) + v;
                                    byte blk = (byte)(ST.fn_h >> 3);
                                    CH[c].kcode = (byte)((blk << 2) | opn_fktable[fn >> 7]);
                                    CH[c].fc = fn_table[fn * 2] >> (7 - blk);
                                    CH[c].block_fnum = (uint)(blk << 11) | fn;
                                    CH[c].SLOT[0].Incr = -1;
                                }
                                break;
                            case 1:
                                ST.fn_h = (byte)(v & 0x3f);
                                break;
                            case 2:
                                if (r < 0x100)
                                {
                                    uint fn = (((uint)(SL3.fn_h & 7)) << 8) + v;
                                    byte blk = (byte)(SL3.fn_h >> 3);
                                    SL3.kcode[c] = (byte)((blk << 2) | opn_fktable[fn >> 7]);
                                    SL3.fc[c] = fn_table[fn * 2] >> (7 - blk);
                                    SL3.block_fnum[c] = (uint)(blk << 11) | fn;
                                    CH[c + 2].SLOT[0].Incr = -1;
                                }
                                break;
                            case 3:
                                if (r < 0x100)
                                    SL3.fn_h = (byte)(v & 0x3f);
                                break;
                        }
                        break;
                    case 0xb0:
                        switch ((r >> 2) & 3)
                        {
                            case 0:
                                {
                                    int feedback = (v >> 3) & 7;
                                    CH[c].ALGO = (byte)(v & 7);
                                    CH[c].FB = (feedback != 0) ? (byte)(feedback + 6) : (byte)0;
                                    setup_connection(c);
                                }
                                break;
                            case 1:
                                if ((type & TYPE_LFOPAN) != 0)
                                {
                                    CH[c].pms = (v & 7) * 32;
                                    CH[c].ams = lfo_ams_depth_shift[(v >> 4) & 0x03];
                                    pan[c * 2] = ((v & 0x80) != 0) ? 0xffffffff : 0;
                                    pan[c * 2 + 1] = ((v & 0x40) != 0) ? 0xffffffff : 0;
                                }
                                break;
                        }
                        break;
                }
            }
            public void OPNPrescaler_w(int addr, int pre_divider)
            {
                int[] opn_pres = new int[4] { 2 * 12, 2 * 12, 6 * 12, 3 * 12 };
                int[] ssg_pres = new int[4] { 1, 1, 4, 2 };
                int sel;
                switch (addr)
                {
                    case 0:
                        ST.prescaler_sel = 2;
                        break;
                    case 1:
                        break;
                    case 0x2d:
                        ST.prescaler_sel |= 0x02;
                        break;
                    case 0x2e:
                        ST.prescaler_sel |= 0x01;
                        break;
                    case 0x2f:
                        ST.prescaler_sel = 0;
                        break;
                }
                sel = ST.prescaler_sel & 3;
                OPNSetPres(opn_pres[sel] * pre_divider, opn_pres[sel] * pre_divider, ssg_pres[sel] * pre_divider);
            }
        }
        public struct FM_SLOT
        {
            public byte KSR;
            public int ar;
            public int d1r;
            public int d2r;
            public int rr;
            public byte ksr;
            public int mul;
            public uint phase;
            public int Incr;
            public byte state;
            public int tl;
            public int volume;
            public int sl;
            public uint vol_out;
            public byte eg_sh_ar;
            public byte eg_sel_ar;
            public byte eg_sh_d1r;
            public byte eg_sel_d1r;
            public byte eg_sh_d2r;
            public byte eg_sel_d2r;
            public byte eg_sh_rr;
            public byte eg_sel_rr;
            public byte ssg;
            public byte ssgn;
            public uint key;
            public uint AMmask;
        }
        public struct FM_CH
        {
            public FM_SLOT[] SLOT;
            public byte ALGO;
            public byte FB;
            public int op1_out0, op1_out1;
            public int mem_value;
            public int pms;
            public byte ams;
            public uint fc;
            public byte kcode;
            public uint block_fnum;
        }
        public struct FM_ST
        {
            public int clock;
            public int rate;
            public double freqbase;
            public int timer_prescaler;
            public Atime busy_expiry_time;
            public byte address;
            public byte irq;
            public byte irqmask;
            public byte status;
            public byte mode;
            public byte prescaler_sel;
            public byte fn_h;
            public int TA;
            public int TAC;
            public byte TB;
            public int TBC;
            public int[,] dt_tab2;
            public FM_TIMERHANDLER timer_handler;
            public FM_IRQHANDLER IRQ_Handler;
            public ssg_callbacks SSG;
        }
        public struct FM_3SLOT
        {
            public uint[] fc;
            public byte fn_h;
            public byte[] kcode;
            public uint[] block_fnum;
        }
        public struct ADPCM_CH
        {
            public byte flag;
            public byte flagMask;
            public byte now_data;
            public uint now_addr;
            public uint now_step;
            public uint step;
            public uint start;
            public uint end;
            public byte IL;
            public int adpcm_acc;
            public int adpcm_step;
            public int adpcm_out;
            public sbyte vol_mul;
            public byte vol_shift;
        }
        public struct ssg_callbacks
        {
            public set_clock_handler set_clock;
            public write_handler write;
            public read_handler read;
            public reset_handler reset;
        }
        
        public static int TYPE_SSG = 0x01, TYPE_LFOPAN = 0x02, TYPE_6CH = 0x04, TYPE_DAC = 0x08, TYPE_ADPCM = 0x10, TYPE_2610 = 0x20;
        public static int TYPE_YM2203 = (TYPE_SSG), TYPE_YM2608 = (TYPE_SSG | TYPE_LFOPAN | TYPE_6CH | TYPE_ADPCM), TYPE_YM2610 = (TYPE_SSG | TYPE_LFOPAN | TYPE_6CH | TYPE_ADPCM | TYPE_2610), TYPE_YM2612 = (TYPE_DAC | TYPE_LFOPAN | TYPE_6CH);
        public static int[] ipan = new int[6];
        private static int[] tl_tab = new int[6656];
        private static uint[] sin_tab = new uint[0x400];
        private static int[] sl_table = new int[16]{
         ( 0*32),( 1*32),( 2*32),(3*32 ),(4*32 ),(5*32 ),(6*32 ),( 7*32),
         ( 8*32),( 9*32),(10*32),(11*32),(12*32),(13*32),(14*32),(31*32)
        };
        private static byte[] eg_inc = new byte[19 * 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,
            4,4, 4,8, 4,4, 4,8,
            4,8, 4,8, 4,8, 4,8,
            4,8, 8,8, 4,8, 8,8,

            8,8, 8,8, 8,8, 8,8,
            16,16,16,16,16,16,16,16,
            0,0, 0,0, 0,0, 0,0,
        };
        public static byte[] eg_rate_select = new byte[32 + 64 + 32]{
            (18*8),(18*8),(18*8),(18*8),(18*8),(18*8),(18*8),(18*8),
            (18*8),(18*8),(18*8),(18*8),(18*8),(18*8),(18*8),(18*8),
            (18*8),(18*8),(18*8),(18*8),(18*8),(18*8),(18*8),(18*8),
            (18*8),(18*8),(18*8),(18*8),(18*8),(18*8),(18*8),(18*8),

            ( 0*8),( 1*8),( 2*8),( 3*8),
            ( 0*8),( 1*8),( 2*8),( 3*8),
            ( 0*8),( 1*8),( 2*8),( 3*8),
            ( 0*8),( 1*8),( 2*8),( 3*8),
            ( 0*8),( 1*8),( 2*8),( 3*8),
            ( 0*8),( 1*8),( 2*8),( 3*8),
            ( 0*8),( 1*8),( 2*8),( 3*8),
            ( 0*8),( 1*8),( 2*8),( 3*8),
            ( 0*8),( 1*8),( 2*8),( 3*8),
            ( 0*8),( 1*8),( 2*8),( 3*8),
            ( 0*8),( 1*8),( 2*8),( 3*8),
            ( 0*8),( 1*8),( 2*8),( 3*8),

            ( 4*8),( 5*8),( 6*8),( 7*8),

            ( 8*8),( 9*8),(10*8),(11*8),

            (12*8),(13*8),(14*8),(15*8),

            (16*8),(16*8),(16*8),(16*8),

            (16*8),(16*8),(16*8),(16*8),(16*8),(16*8),(16*8),(16*8),
            (16*8),(16*8),(16*8),(16*8),(16*8),(16*8),(16*8),(16*8),
            (16*8),(16*8),(16*8),(16*8),(16*8),(16*8),(16*8),(16*8),
            (16*8),(16*8),(16*8),(16*8),(16*8),(16*8),(16*8),(16*8)
        };
        public static byte[] eg_rate_select2612 = new byte[32 + 64 + 32]{
            (18*8),(18*8),(18*8),(18*8),(18*8),(18*8),(18*8),(18*8),
            (18*8),(18*8),(18*8),(18*8),(18*8),(18*8),(18*8),(18*8),
            (18*8),(18*8),(18*8),(18*8),(18*8),(18*8),(18*8),(18*8),
            (18*8),(18*8),(18*8),(18*8),(18*8),(18*8),(18*8),(18*8),

            ( 18*8),( 18*8),( 0*8),( 0*8),
            ( 0*8),( 0*8),( 2*8),( 2*8),

            ( 0*8),( 1*8),( 2*8),( 3*8),
            ( 0*8),( 1*8),( 2*8),( 3*8),
            ( 0*8),( 1*8),( 2*8),( 3*8),
            ( 0*8),( 1*8),( 2*8),( 3*8),
            ( 0*8),( 1*8),( 2*8),( 3*8),
            ( 0*8),( 1*8),( 2*8),( 3*8),
            ( 0*8),( 1*8),( 2*8),( 3*8),
            ( 0*8),( 1*8),( 2*8),( 3*8),
            ( 0*8),( 1*8),( 2*8),( 3*8),
            ( 0*8),( 1*8),( 2*8),( 3*8),

            ( 4*8),( 5*8),( 6*8),( 7*8),

            ( 8*8),( 9*8),(10*8),(11*8),

            (12*8),(13*8),(14*8),(15*8),

            (16),(16),(16),(16),

            (16*8),(16*8),(16*8),(16*8),(16*8),(16*8),(16*8),(16*8),
            (16*8),(16*8),(16*8),(16*8),(16*8),(16*8),(16*8),(16*8),
            (16*8),(16*8),(16*8),(16*8),(16*8),(16*8),(16*8),(16*8),
            (16*8),(16*8),(16*8),(16*8),(16*8),(16*8),(16*8),(16*8)
        };
        public static byte[] eg_rate_shift = new byte[32 + 64 + 32]{
            (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),

            (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),( 0),( 0),( 0),( 0),
            ( 0),( 0),( 0),( 0),( 0),( 0),( 0),( 0),
            ( 0),( 0),( 0),( 0),( 0),( 0),( 0),( 0)
        };
        public static byte[] dt_tab = new byte[4 * 32]{
	        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,

	        0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2,
	        2, 3, 3, 3, 4, 4, 4, 5, 5, 6, 6, 7, 8, 8, 8, 8,

            1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5,
	        5, 6, 6, 7, 8, 8, 9,10,11,12,13,14,16,16,16,16,

            2, 2, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 6, 6, 7,
	        8 , 8, 9,10,11,12,13,14,16,17,19,20,22,22,22,22
        };
        private static byte[] opn_fktable = new byte[16] { 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 3, 3, 3, 3, 3, 3 };
        private static uint[] lfo_samples_per_step = new uint[8] { 108, 77, 71, 67, 62, 44, 8, 5 };
        private static byte[] lfo_ams_depth_shift = new byte[4] { 8, 3, 1, 0 };
        private static byte[,] lfo_pm_output = new byte[56, 8]{
            {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},
            {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,   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},
            {0,   0,   0,   0,   0,   0,   0,   0},
            {0,   0,   0,   0,   0,   0,   0,   0},
            {0,   0,   0,   0,   1,   1,   1,   1},
            {0,   0,   1,   1,   2,   2,   2,   3},

            {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},
            {0,   0,   0,   0,   0,   0,   0,   1},
            {0,   0,   0,   0,   1,   1,   1,   1},
            {0,   0,   1,   1,   2,   2,   2,   3},
            {0,   0,   2,   3,   4,   4,   5,   6},

            {0,   0,   0,   0,   0,   0,   0,   0},
            {0,   0,   0,   0,   0,   0,   0,   0},
            {0,   0,   0,   0,   0,   0,   1,   1},
            {0,   0,   0,   0,   1,   1,   1,   1},
            {0,   0,   0,   1,   1,   1,   1,   2},
            {0,   0,   1,   1,   2,   2,   2,   3},
            {0,   0,   2,   3,   4,   4,   5,   6},
            {0,   0,   4,   6,   8,   8, 0xa, 0xc},

            {0,   0,   0,   0,   0,   0,   0,   0},
            {0,   0,   0,   0,   1,   1,   1,   1},
            {0,   0,   0,   1,   1,   1,   2,   2},
            {0,   0,   1,   1,   2,   2,   3,   3},
            {0,   0,   1,   2,   2,   2,   3,   4},
            {0,   0,   2,   3,   4,   4,   5,   6},
            {0,   0,   4,   6,   8,   8, 0xa, 0xc},
            {0,   0,   8, 0xc,0x10,0x10,0x14,0x18},

            {0,   0,   0,   0,   0,   0,   0,   0},
            {0,   0,   0,   0,   2,   2,   2,   2},
            {0,   0,   0,   2,   2,   2,   4,   4},
            {0,   0,   2,   2,   4,   4,   6,   6},
            {0,   0,   2,   4,   4,   4,   6,   8},
            {0,   0,   4,   6,   8,   8, 0xa, 0xc},
            {0,   0,   8, 0xc,0x10,0x10,0x14,0x18},
            {0,   0,0x10,0x18,0x20,0x20,0x28,0x30},

            {0,   0,   0,   0,   0,   0,   0,   0},
            {0,   0,   0,   0,   4,   4,   4,   4},
            {0,   0,   0,   4,   4,   4,   8,   8},
            {0,   0,   4,   4,   8,   8, 0xc, 0xc},
            {0,   0,   4,   8,   8,   8, 0xc,0x10},
            {0,   0,   8, 0xc,0x10,0x10,0x14,0x18},
            {0,   0,0x10,0x18,0x20,0x20,0x28,0x30},
            {0,   0,0x20,0x30,0x40,0x40,0x50,0x60},
        };
        private static int[] steps = new int[49]
        {
	         16,  17,   19,   21,   23,   25,   28,
	         31,  34,   37,   41,   45,   50,   55,
	         60,  66,   73,   80,   88,   97,  107,
	        118, 130,  143,  157,  173,  190,  209,
	        230, 253,  279,  307,  337,  371,  408,
	        449, 494,  544,  598,  658,  724,  796,
	        876, 963, 1060, 1166, 1282, 1411, 1552
        };
        public static int[] step_inc = new int[8] { -1 * 16, -1 * 16, -1 * 16, -1 * 16, 2 * 16, 5 * 16, 7 * 16, 9 * 16 };
        public static int[] jedi_table = new int[49 * 16];
        public delegate void FM_TIMERHANDLER(int c, int cnt, int clock);
        public delegate void FM_IRQHANDLER(int irq);
        public delegate void set_clock_handler(int clock);
        public delegate void write_handler(int address,byte data);
        public delegate byte read_handler();
        public delegate void reset_handler();
        private static int[] lfo_pm_table = new int[128 * 8 * 32];
        private static int[] iconnect1 = new int[8], iconnect2 = new int[8], iconnect3 = new int[8], iconnect4 = new int[6], imem = new int[13];
        public static int[] out_fm = new int[13];
        public static int[] out_adpcm = new int[4];
        public static int[] out_delta = new int[4];
        public static byte[] ymsndrom;
        public static int LFO_AM;
        public static int LFO_PM;
        private static int fn_max;
        public static void FM_init()
        {
            init_tables();
        }
        public 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, uint env, int pm)
        {
            uint p;
            p = (uint)((env << 3) + sin_tab[(((int)((phase & 0xffff0000) + (pm << 15))) >> 16) & 0x3ff]);
            if (p >= 6656)
            {
                return 0;
            }
            return tl_tab[p];
        }
        private static int op_calc1(uint phase, uint env, int pm)
        {
            uint p;
            p = (uint)((env << 3) + sin_tab[(((int)((phase & 0xffff0000) + pm)) >> 16) & 0x3ff]);
            if (p >= 6656)
            {
                return 0;
            }
            return tl_tab[p];
        }
        public static int init_tables()
        {
            int i, x, 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 <<= 2;
                tl_tab[x * 2] = n;
                tl_tab[x * 2 + 1] = -tl_tab[x * 2];
                for (i = 1; i < 13; i++)
                {
                    tl_tab[x * 2 + i * 2 * 0x100] = tl_tab[x * 2] >> i;
                    tl_tab[x * 2 + 1 + i * 2 * 0x100] = -tl_tab[x * 2 + 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 / (1.0 / 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 < 8; i++)
            {
                byte fnum;
                for (fnum = 0; fnum < 128; fnum++)
                {
                    byte value;
                    byte step;
                    int offset_depth = i;
                    int offset_fnum_bit;
                    int bit_tmp;
                    for (step = 0; step < 8; step++)
                    {
                        value = 0;
                        for (bit_tmp = 0; bit_tmp < 7; bit_tmp++)
                        {
                            if ((fnum & (1 << bit_tmp)) != 0)
                            {
                                offset_fnum_bit = bit_tmp * 8;
                                value += lfo_pm_output[offset_fnum_bit + offset_depth, step];
                            }
                        }
                        lfo_pm_table[(fnum * 32 * 8) + (i * 32) + step + 0] = value;
                        lfo_pm_table[(fnum * 32 * 8) + (i * 32) + (step ^ 7) + 8] = value;
                        lfo_pm_table[(fnum * 32 * 8) + (i * 32) + step + 16] = -value;
                        lfo_pm_table[(fnum * 32 * 8) + (i * 32) + (step ^ 7) + 24] = -value;
                    }
                }
            }
            return 1;
        }
        public static void Init_ADPCMATable()
        {
            int step, nib;
            for (step = 0; step < 49; step++)
            {
                for (nib = 0; nib < 16; nib++)
                {
                    int value = (2 * (nib & 0x07) + 1) * steps[step] / 8;
                    jedi_table[step * 16 + nib] = ((nib & 0x08) != 0) ? -value : value;
                }
            }
        }        
    }
}
