package org.jpc.emulator.peripheral;

import org.jpc.emulator.AbstractHardwareComponent;
import org.jpc.emulator.HardwareComponent;
import org.jpc.emulator.motherboard.*;
import org.jpc.j2se.Option;
import org.jpc.support.Clock;

import java.util.logging.*;

public class SBlaster extends AbstractHardwareComponent implements IODevice
{
    private static final Logger Log = Logger.getLogger(SBlaster.class.getName());
    private static InterruptController irqDevice;
    private static Clock timeSource;
    private static DMAController dma;
    private static boolean ioportRegistered = false;
    public static final String BLASTER = "SET BLASTER=A220 I7 D1 T3";
    public static final int BASE = 0x220;
    public static final int IRQ = 7;
    public static final int DMA = 1;
    public static final int HDMA = 5;
    public static final int OPL_RATE = 44100;
    public static final String OPLEMU = "default";
    public static int oplmode;

    private static final boolean DEBUG = false;

    static final private int SB_PIC_EVENTS = 0;

    static final private int DSP_MAJOR = 3;
    static final private int DSP_MINOR = 1;

    static final private int MIXER_INDEX = 0x04;
    static final private int MIXER_DATA = 0x05;

    static final private int DSP_RESET = 0x06;
    static final private int DSP_READ_DATA = 0x0A;
    static final private int DSP_WRITE_DATA = 0x0C;
    static final private int DSP_WRITE_STATUS = 0x0C;
    static final private int DSP_READ_STATUS = 0x0E;
    static final private int DSP_ACK_16BIT = 0x0f;

    static final private int DSP_NO_COMMAND = 0;

    static final private int DMA_BUFSIZE = 1024;
    static final private int DSP_BUFSIZE = 64;
    static final private int DSP_DACSIZE = 512;

//Should be enough for sound generated in millisecond blocks
    static final private int SB_BUF_SIZE = 8096;
    static final private int SB_SH	= 14;
    static final private int SB_SH_MASK	= ((1 << SB_SH)-1);

    static final private int DSP_S_RESET = 0;
    static final private int DSP_S_RESET_WAIT = 1;
    static final private int DSP_S_NORMAL = 2;
    static final private int DSP_S_HIGHSPEED = 3;

    // SB_TYPES
    static final private int SBT_NONE = 0;
    static final private int SBT_1 = 1;
    static final private int SBT_PRO1 = 2;
    static final private int SBT_2 = 3;
    static final private int SBT_PRO2 = 4;
    static final private int SBT_16 = 6;
    static final private int SBT_GB = 7;

    // SB_IRQS
    static final private int SB_IRQ_8 = 0;
    static final private int SB_IRQ_16 = 1;
    static final private int SB_IRQ_MPU = 2;

    // DSP_MODES
    static final private int MODE_NONE = 0;
    static final private int MODE_DAC = 1;
    static final private int MODE_DMA = 2;
    static final private int MODE_DMA_PAUSE = 3;
    static final private int MODE_DMA_MASKED = 4;

    // DMA_MODES
    static final private int DSP_DMA_NONE = 0;
    static final private int DSP_DMA_2 = 1;
    static final private int DSP_DMA_3 = 2;
    static final private int DSP_DMA_4 = 3;
    static final private int DSP_DMA_8 = 4;
    static final private int DSP_DMA_16 = 5;
    static final private int DSP_DMA_16_ALIASED = 6;

    static final private int PLAY_MONO = 0;
    static final private int PLAY_STEREO = 1;

    static private class SB_INFO {
        /*Bitu*/int freq;
        static private class Dma {
            boolean stereo,sign,autoinit;
            /*DMA_MODES*/int mode;
            /*Bitu*/int rate,mul;
            /*Bitu*/int total,left,min;
            /*Bit64u*/long start;
            static private class Buf {
                /*Bit8u*/ byte[] b8 = new byte[DMA_BUFSIZE];
                /*Bit16s*/ short[] b16 = new short[DMA_BUFSIZE];
                byte[] b16tmp = new byte[2*DMA_BUFSIZE];
            }
            Buf buf = new Buf();
            /*Bitu*/int bits;
            DMAController.DMAChannel chan;
            int position;
            /*Bitu*/int remain_size;
        }
        Dma dma = new Dma();
        boolean speaker;
        boolean midi;
        /*Bit8u*/short time_constant;
        /*DSP_MODES*/int mode;
        /*SB_TYPES*/int type;
        static private class Irq {
            boolean pending_8bit;
            boolean pending_16bit;
        }
        Irq irq = new Irq();
        static private class Dsp {
            /*Bit8u*/short state;
            /*Bit8u*/short cmd;
            /*Bit8u*/short cmd_len;
            /*Bit8u*/short cmd_in_pos;
            /*Bit8u*/short[] cmd_in = new short[DSP_BUFSIZE];
            static private class Data {
                /*Bit8u*/short lastval;
                /*Bit8u*/short[] data = new short[DSP_BUFSIZE];
                /*Bitu*/int pos,used;
            }
            Data in = new Data();
            Data out = new Data();
            /*Bit8u*/short test_register;
            /*Bitu*/int write_busy;
        }
        Dsp dsp = new Dsp();
        private static class Dac {
            /*Bit16s*/short[] data = new short[DSP_DACSIZE+1];
            /*Bitu*/int used;
            /*Bit16s*/short last;
        }
        Dac dac = new Dac();
        private static class _Mixer {
            /*Bit8u*/short index;
            /*Bit8u*/short[] dac = new short[2],fm = new short[2],cda =new short[2],master = new short[2],lin = new short[2];
            /*Bit8u*/short mic;
            boolean stereo;
            boolean enabled;
            boolean filtered;
            /*Bit8u*/short[] unhandled = new short[0x48];
        }
        _Mixer mixer = new _Mixer();
        private static class Adpcm {
            /*Bit8u*/ShortRef reference = new ShortRef();
            /*Bits*/IntRef stepsize = new IntRef(0);
            boolean haveref;
        }
        Adpcm adpcm = new Adpcm();
        private static class Hw {
            /*Bitu*/int base;
            /*Bitu*/int irq;
            /*Bit8u*/short dma8,dma16;
        }
        Hw hw = new Hw();
        private static class E2 {
            /*Bits*/int value;
            /*Bitu*/int count;
        }
        E2 e2 = new E2();
        Mixer.MixerChannel chan;
    }

    private static SB_INFO sb;

    private final static String copyright_string="COPYRIGHT (C) CREATIVE TECHNOLOGY LTD, 1992.";

// number of bytes in input for commands (sb/sbpro)
    private final static /*Bit8u*/byte[] DSP_cmd_len_sb = new byte[] {
      0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,  // 0x00
//  1,0,0,0, 2,0,2,2, 0,0,0,0, 0,0,0,0,  // 0x10
      1,0,0,0, 2,2,2,2, 0,0,0,0, 0,0,0,0,  // 0x10 Wari hack
      0,0,0,0, 2,0,0,0, 0,0,0,0, 0,0,0,0,  // 0x20
      0,0,0,0, 0,0,0,0, 1,0,0,0, 0,0,0,0,  // 0x30

      1,2,2,0, 0,0,0,0, 2,0,0,0, 0,0,0,0,  // 0x40
      0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,  // 0x50
      0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,  // 0x60
      0,0,0,0, 2,2,2,2, 0,0,0,0, 0,0,0,0,  // 0x70

      2,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,  // 0x80
      0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,  // 0x90
      0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,  // 0xa0
      0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,  // 0xb0

      0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,  // 0xc0
      0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,  // 0xd0
      1,0,1,0, 1,0,0,0, 0,0,0,0, 0,0,0,0,  // 0xe0
      0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0   // 0xf0
    };

// number of bytes in input for commands (sb16)
    private final static /*Bit8u*/byte[] DSP_cmd_len_sb16 = new byte[] {
      0,0,0,0, 1,2,0,0, 1,0,0,0, 0,0,2,1,  // 0x00
//  1,0,0,0, 2,0,2,2, 0,0,0,0, 0,0,0,0,  // 0x10
      1,0,0,0, 2,2,2,2, 0,0,0,0, 0,0,0,0,  // 0x10 Wari hack
      0,0,0,0, 2,0,0,0, 0,0,0,0, 0,0,0,0,  // 0x20
      0,0,0,0, 0,0,0,0, 1,0,0,0, 0,0,0,0,  // 0x30

      1,2,2,0, 0,0,0,0, 2,0,0,0, 0,0,0,0,  // 0x40
      0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,  // 0x50
      0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,  // 0x60
      0,0,0,0, 2,2,2,2, 0,0,0,0, 0,0,0,0,  // 0x70

      2,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,  // 0x80
      0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,  // 0x90
      0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,  // 0xa0
      3,3,3,3, 3,3,3,3, 3,3,3,3, 3,3,3,3,  // 0xb0

      3,3,3,3, 3,3,3,3, 3,3,3,3, 3,3,3,3,  // 0xc0
      0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,  // 0xd0
      1,0,1,0, 1,0,0,0, 0,0,0,0, 0,0,0,0,  // 0xe0
      0,0,0,0, 0,0,0,0, 0,1,0,0, 0,0,0,0   // 0xf0
    };

    private static /*Bit8u*/short[] ASP_regs = new short[256];
    private static boolean ASP_init_in_progress = false;

    private final static int[][] E2_incr_table = new int[][] {
      {  0x01, -0x02, -0x04,  0x08, -0x10,  0x20,  0x40, -0x80, -106 },
      { -0x01,  0x02, -0x04,  0x08,  0x10, -0x20,  0x40, -0x80,  165 },
      { -0x01,  0x02,  0x04, -0x08,  0x10, -0x20, -0x40,  0x80, -151 },
      {  0x01, -0x02,  0x04, -0x08, -0x10,  0x20, -0x40,  0x80,   90 }
    };


    private static void DSP_SetSpeaker(boolean how) {
        if (sb.speaker==how) return;
        sb.speaker=how;
        if (sb.type==SBT_16) return;
        sb.chan.Enable(how);
        if (sb.speaker) {
            //Pic.PIC_RemoveEvents(DMA_Silent_Event);
            CheckDMAEnd();
        } else {

        }
    }

    private static void SB_RaiseIRQ(/*SB_IRQS*/int type) {
        Log.log(Level.FINE,"Raising IRQ");
        switch (type) {
        case SB_IRQ_8:
            if (sb.irq.pending_8bit) {
//			LOG_MSG("SB: 8bit irq pending");
                return;
            }
            sb.irq.pending_8bit=true;
            //Pic.PIC_ActivateIRQ(sb.hw.irq);
            irqDevice.setIRQ(sb.hw.irq, 1);
            break;
        case SB_IRQ_16:
            if (sb.irq.pending_16bit) {
//			LOG_MSG("SB: 16bit irq pending");
                return;
            }
            sb.irq.pending_16bit=true;
            //Pic.PIC_ActivateIRQ(sb.hw.irq);
            irqDevice.setIRQ(sb.hw.irq, 1);
            break;
        default:
            break;
        }
    }

    private static void DSP_FlushData() {
        sb.dsp.out.used=0;
        sb.dsp.out.pos=0;
    }

    private static DMAEventHandler DSP_DMA_CallBack = new DMAEventHandler() {
        public void handleDMAEvent(DMAEvent event) {
            if (event == DMAEvent.DMA_REACHED_TC) return;
            else if (event == DMAEvent.DMA_MASKED) {
                if (sb.mode==MODE_DMA) {
                    GenerateDMASound(sb.dma.min);
                    sb.mode=MODE_DMA_MASKED;
    //			DSP_ChangeMode(MODE_DMA_MASKED);
                    Log.log(Level.INFO,"DMA masked, stopping output. ");
                }
            } else if (event == DMAEvent.DMA_UNMASKED) {
                if (sb.mode==MODE_DMA_MASKED && sb.dma.mode!=DSP_DMA_NONE) {
                    DSP_ChangeMode(MODE_DMA);
    //			sb.mode=MODE_DMA;
                    CheckDMAEnd();
                    Log.log(Level.INFO,"DMA unmasked, starting output. ");
                }
            }
        }
    };

    static final private int MIN_ADAPTIVE_STEP_SIZE = 0;
    static final private int MAX_ADAPTIVE_STEP_SIZE = 32767;
    static final private int DC_OFFSET_FADE = 254;

    final private static /*Bit8s*/byte[] scaleMap1 = new byte[]  {
        0,  1,  2,  3,  4,  5,  6,  7,  0,  -1,  -2,  -3,  -4,  -5,  -6,  -7,
        1,  3,  5,  7,  9, 11, 13, 15, -1,  -3,  -5,  -7,  -9, -11, -13, -15,
        2,  6, 10, 14, 18, 22, 26, 30, -2,  -6, -10, -14, -18, -22, -26, -30,
        4, 12, 20, 28, 36, 44, 52, 60, -4, -12, -20, -28, -36, -44, -52, -60
    };
    static final private /*Bit8u*/short[] adjustMap1 = new short[] {
          0, 0, 0, 0, 0, 16, 16, 16,
          0, 0, 0, 0, 0, 16, 16, 16,
        240, 0, 0, 0, 0, 16, 16, 16,
        240, 0, 0, 0, 0, 16, 16, 16,
        240, 0, 0, 0, 0, 16, 16, 16,
        240, 0, 0, 0, 0, 16, 16, 16,
        240, 0, 0, 0, 0,  0,  0,  0,
        240, 0, 0, 0, 0,  0,  0,  0
    };
    private static /*Bit8u*/byte decode_ADPCM_4_sample(/*Bit8u*/int sample,/*Bit8u*/ShortRef reference,/*Bits*/IntRef scale) {
        /*Bits*/int samp = sample + scale.value;

        if ((samp < 0) || (samp > 63)) {
            Log.log(Level.SEVERE,"Bad ADPCM-4 sample");
            if(samp < 0 ) samp =  0;
            if(samp > 63) samp = 63;
        }

        /*Bits*/int ref = reference.value + scaleMap1[samp];
        if (ref > 0xff) reference.value = 0xff;
        else if (ref < 0x00) reference.value = 0x00;
        else reference.value = (/*Bit8u*/short)(ref&0xff);
        scale.value = (scale.value + adjustMap1[samp]) & 0xff;

        return (byte)reference.value;
    }

    static final private /*Bit8s*/byte[] scaleMap2 = new byte[]  {
        0,  1,  0,  -1, 1,  3,  -1,  -3,
        2,  6, -2,  -6, 4, 12,  -4, -12,
        8, 24, -8, -24, 6, 48, -16, -48
    };
    static final private /*Bit8u*/short[] adjustMap2 = new short[]  {
          0, 4,   0, 4,
        252, 4, 252, 4, 252, 4, 252, 4,
        252, 4, 252, 4, 252, 4, 252, 4,
        252, 0, 252, 0
    };

    private static /*Bit8u*/byte decode_ADPCM_2_sample(/*Bit8u*/int sample,/*Bit8u*/ShortRef reference,/*Bits*/IntRef scale) {
        /*Bits*/int samp = sample + scale.value;
        if ((samp < 0) || (samp > 23)) {
            Log.log(Level.SEVERE,"Bad ADPCM-2 sample");
            if(samp < 0 ) samp =  0;
            if(samp > 23) samp = 23;
        }

        /*Bits*/int ref = reference.value + scaleMap2[samp];
        if (ref > 0xff) reference.value = 0xff;
        else if (ref < 0x00) reference.value = 0x00;
        else reference.value = (/*Bit8u*/short)(ref&0xff);
        scale.value = (scale.value + adjustMap2[samp]) & 0xff;

        return (byte)reference.value;
    }

    static final private /*Bit8s*/byte[] scaleMap3 = new byte[]  {
        0,  1,  2,  3,  0,  -1,  -2,  -3,
        1,  3,  5,  7, -1,  -3,  -5,  -7,
        2,  6, 10, 14, -2,  -6, -10, -14,
        4, 12, 20, 28, -4, -12, -20, -28,
        5, 15, 25, 35, -5, -15, -25, -35
    };
    static final private /*Bit8u*/short[] adjustMap3 = new short[] {
          0, 0, 0, 8,   0, 0, 0, 8,
        248, 0, 0, 8, 248, 0, 0, 8,
        248, 0, 0, 8, 248, 0, 0, 8,
        248, 0, 0, 8, 248, 0, 0, 8,
        248, 0, 0, 0, 248, 0, 0, 0
    };

    static private /*Bit8u*/byte decode_ADPCM_3_sample(/*Bit8u*/int sample,/*Bit8u*/ShortRef reference,/*Bits*/IntRef scale) {
        /*Bits*/int samp = sample + scale.value;
        if ((samp < 0) || (samp > 39)) {
            Log.log(Level.SEVERE,"Bad ADPCM-3 sample");
            if(samp < 0 ) samp =  0;
            if(samp > 39) samp = 39;
        }

        /*Bits*/int ref = reference.value + scaleMap3[samp];
        if (ref > 0xff) reference.value = 0xff;
        else if (ref < 0x00) reference.value = 0x00;
        else reference.value = (/*Bit8u*/short)(ref&0xff);
        scale.value = (scale.value + adjustMap3[samp]) & 0xff;

        return (byte)reference.value;
    }

    private static void GenerateDMASound(/*Bitu*/int size) {
        /*Bitu*/int read=0;/*Bitu*/int done=0;/*Bitu*/int i=0;

        if(sb.dma.autoinit) {
            if (sb.dma.left <= size) size = sb.dma.left;
        } else if (sb.dma.left <= sb.dma.min) size = sb.dma.left;

        switch (sb.dma.mode) {
        case DSP_DMA_2:
            sb.dma.chan.readMemory(sb.dma.buf.b8, 0, sb.dma.position, size);//.Read(size,sb.dma.buf.b8, 0);
            read = size;
            if (read!=0 && sb.adpcm.haveref) {
                sb.adpcm.haveref=false;
                sb.adpcm.reference.value=(short)(sb.dma.buf.b8[0] & 0xFF);
                sb.adpcm.stepsize.value=MIN_ADAPTIVE_STEP_SIZE;
                i++;
            }
            for (;i<read;i++) {
                Mixer.MixTemp8[done++]=decode_ADPCM_2_sample((sb.dma.buf.b8[i] >> 6) & 0x3,sb.adpcm.reference,sb.adpcm.stepsize);
                Mixer.MixTemp8[done++]=decode_ADPCM_2_sample((sb.dma.buf.b8[i] >> 4) & 0x3,sb.adpcm.reference,sb.adpcm.stepsize);
                Mixer.MixTemp8[done++]=decode_ADPCM_2_sample((sb.dma.buf.b8[i] >> 2) & 0x3,sb.adpcm.reference,sb.adpcm.stepsize);
                Mixer.MixTemp8[done++]=decode_ADPCM_2_sample((sb.dma.buf.b8[i] >> 0) & 0x3,sb.adpcm.reference,sb.adpcm.stepsize);
            }
            sb.chan.AddSamples_m8(done,Mixer.MixTemp8);
            break;
        case DSP_DMA_3:
            sb.dma.chan.readMemory(sb.dma.buf.b8, 0, sb.dma.position, size);//read = chan.Read(size,sb.dma.buf.b8, 0);
            read = size;
            if (read!=0 && sb.adpcm.haveref) {
                sb.adpcm.haveref=false;
                sb.adpcm.reference.value=(short)(sb.dma.buf.b8[0] & 0xFF);
                sb.adpcm.stepsize.value=MIN_ADAPTIVE_STEP_SIZE;
                i++;
            }
            for (;i<read;i++) {
                Mixer.MixTemp8[done++]=decode_ADPCM_3_sample((sb.dma.buf.b8[i] >>> 5) & 0x7,sb.adpcm.reference,sb.adpcm.stepsize);
                Mixer.MixTemp8[done++]=decode_ADPCM_3_sample((sb.dma.buf.b8[i] >> 2) & 0x7,sb.adpcm.reference,sb.adpcm.stepsize);
                Mixer.MixTemp8[done++]=decode_ADPCM_3_sample((sb.dma.buf.b8[i] & 0x3) << 1,sb.adpcm.reference,sb.adpcm.stepsize);
            }
            sb.chan.AddSamples_m8(done, Mixer.MixTemp8);
            break;
        case DSP_DMA_4:
            sb.dma.chan.readMemory(sb.dma.buf.b8, 0, sb.dma.position, size);//read = chan.Read(size,sb.dma.buf.b8, 0);
            read = size;
            if (read!=0 && sb.adpcm.haveref) {
                sb.adpcm.haveref=false;
                sb.adpcm.reference.value=(short)(sb.dma.buf.b8[0] & 0xFF);
                sb.adpcm.stepsize.value=MIN_ADAPTIVE_STEP_SIZE;
                i++;
            }
            for (;i<read;i++) {
                Mixer.MixTemp8[done++]=decode_ADPCM_4_sample(sb.dma.buf.b8[i] >>> 4,sb.adpcm.reference,sb.adpcm.stepsize);
                Mixer.MixTemp8[done++]=decode_ADPCM_4_sample(sb.dma.buf.b8[i] & 0xf,sb.adpcm.reference,sb.adpcm.stepsize);
            }
            sb.chan.AddSamples_m8(done,Mixer.MixTemp8);
            break;
        case DSP_DMA_8:
            if (sb.dma.stereo) {
                sb.dma.chan.readMemory(sb.dma.buf.b8, sb.dma.remain_size, sb.dma.position, size);//read=sb.dma.chan.Read(size, sb.dma.buf.b8, sb.dma.remain_size);
                read = size;
                //System.out.printf("DMA Read 4 : %d\n", read);
                /*Bitu*/int total=read+sb.dma.remain_size;
                if (!sb.dma.sign)  sb.chan.AddSamples_s8(total>>1,sb.dma.buf.b8);
                else sb.chan.AddSamples_s8s(total>>>1,sb.dma.buf.b8);
                if ((total&1)!=0) {
                    sb.dma.remain_size=1;
                    sb.dma.buf.b8[0]=sb.dma.buf.b8[total-1];
                } else sb.dma.remain_size=0;
            } else {
                sb.dma.chan.readMemory(sb.dma.buf.b8, 0, sb.dma.position, size);//read=sb.dma.chan.Read(size,sb.dma.buf.b8,0);
                read = size;
                if (!sb.dma.sign) sb.chan.AddSamples_m8(read,sb.dma.buf.b8);
                else sb.chan.AddSamples_m8s(read,sb.dma.buf.b8);
            }
            break;
        case DSP_DMA_16:
        case DSP_DMA_16_ALIASED:
            if (sb.dma.stereo) {
                /* In DSP_DMA_16_ALIASED mode temporarily divide by 2 to get number of 16-bit
                   samples, because 8-bit DMA Read returns byte size, while in DSP_DMA_16 mode
                   16-bit DMA Read returns word size */
                sb.dma.chan.readMemory(sb.dma.buf.b16tmp, sb.dma.remain_size, sb.dma.position, 2*size);//read=sb.dma.chan.Read(size,sb.dma.buf.b16, sb.dma.remain_size)
                for (int j=0; j< size; j++)
                    sb.dma.buf.b16[j+sb.dma.remain_size] = (short)((sb.dma.buf.b16tmp[sb.dma.remain_size+2*j] & 0xff) | (sb.dma.buf.b16tmp[sb.dma.remain_size+2*j+1] << 8));
                read = size >> (sb.dma.mode==DSP_DMA_16_ALIASED ? 1:0);
                /*Bitu*/int total=read+sb.dma.remain_size;
//    #if defined (WORDS_BIGENDIAN)
//                if (sb.dma.sign) sb.chan.AddSamples_s16_nonnative(total>>1,sb.dma.buf.b16);
//                else sb.chan.AddSamples_s16u_nonnative(total>>1,(Bit16u *)sb.dma.buf.b16);
//    #else
                if (sb.dma.sign) sb.chan.AddSamples_s16(total>>>1,sb.dma.buf.b16);
                else sb.chan.AddSamples_s16u(total>>>1,sb.dma.buf.b16);
    //#endif
                if ((total&1)!=0) {
                    sb.dma.remain_size=1;
                    sb.dma.buf.b16[0]=sb.dma.buf.b16[total-1];
                } else sb.dma.remain_size=0;
            } else {
                //read=sb.dma.chan.Read(size,sb.dma.buf.b16, 0)
                sb.dma.chan.readMemory(sb.dma.buf.b16tmp, 0, sb.dma.position, 2*size);
                for (int j=0; j< size; j++)
                    sb.dma.buf.b16[j] = (short)((sb.dma.buf.b16tmp[2*j] & 0xff) | (sb.dma.buf.b16tmp[2*j+1] << 8));
                read = size >> (sb.dma.mode==DSP_DMA_16_ALIASED ? 1:0);
//    #if static final private intd(WORDS_BIGENDIAN)
//                if (sb.dma.sign) sb.chan.AddSamples_m16_nonnative(read,sb.dma.buf.b16);
//                else sb.chan.AddSamples_m16u_nonnative(read,(Bit16u *)sb.dma.buf.b16);
//    #else
                if (sb.dma.sign) sb.chan.AddSamples_m16(read,sb.dma.buf.b16);
                else sb.chan.AddSamples_m16u(read,sb.dma.buf.b16);
//    #endif
            }
            //restore buffer length value to byte size in aliased mode
            if (sb.dma.mode==DSP_DMA_16_ALIASED) read=read<<1;
            break;
        default:
            Log.log(Level.SEVERE, "Unhandled dma mode "+sb.dma.mode);
            sb.mode=MODE_NONE;
            return;
        }
        sb.dma.left-=read;
        if (sb.dma.left==0) {
            //Pic.PIC_RemoveEvents(END_DMA_Event);
            if (sb.dma.mode >= DSP_DMA_16) SB_RaiseIRQ(SB_IRQ_16);
            else SB_RaiseIRQ(SB_IRQ_8);
            if (!sb.dma.autoinit) {
                Log.log(Level.INFO,"Single cycle transfer ended");
                sb.mode=MODE_NONE;
                sb.dma.mode=DSP_DMA_NONE;
            } else {
                sb.dma.left=sb.dma.total;
                if (sb.dma.left==0) {
                    Log.log(Level.INFO,"Auto-init transfer with 0 size");
                    sb.mode=MODE_NONE;
                }
            }
        }
    }

    private static void DMA_Silent_Event(/*Bitu*/int val) {
            if (sb.dma.left<val) val=sb.dma.left;
            sb.dma.chan.readMemory(sb.dma.buf.b8, 0, sb.dma.position, val);//Read(val,sb.dma.buf.b8, 0);
            int read = val;
            sb.dma.left-=read;
            if (sb.dma.left==0) {
                if (sb.dma.mode >= DSP_DMA_16) SB_RaiseIRQ(SB_IRQ_16);
                else SB_RaiseIRQ(SB_IRQ_8);
                if (sb.dma.autoinit) sb.dma.left=sb.dma.total;
                else {
                    sb.mode=MODE_NONE;
                    sb.dma.mode=DSP_DMA_NONE;
                }
            }
            if (sb.dma.left!=0) {
                /*Bitu*/int bigger=(sb.dma.left > sb.dma.min) ? sb.dma.min : sb.dma.left;
                float delay=(bigger*1000.0f)/sb.dma.rate;
                //Pic.PIC_AddEvent(DMA_Silent_Event,delay,bigger);
            }
        }

    private static void END_DMA_Event(/*Bitu*/int val) {
            GenerateDMASound(val);
        }

    private static void CheckDMAEnd() {
        if (sb.dma.left==0) return;
        if (!sb.speaker && sb.type!=SBT_16) {
            /*Bitu*/int bigger=(sb.dma.left > sb.dma.min) ? sb.dma.min : sb.dma.left;
            float delay=(bigger*1000.0f)/sb.dma.rate;
            //Pic.PIC_AddEvent(DMA_Silent_Event,delay,bigger);
            Log.log(Level.INFO,"Silent DMA Transfer scheduling IRQ in "+ String.format("%3f", delay)+" milliseconds");
        } else if (sb.dma.left<sb.dma.min) {
            float delay=(sb.dma.left*1000.0f)/sb.dma.rate;
            Log.log(Level.INFO,"Short transfer scheduling IRQ in "+ String.format("%3f", delay)+" milliseconds");
            //Pic.PIC_AddEvent(END_DMA_Event,delay,sb.dma.left);
            END_DMA_Event(sb.dma.left);
        }
    }

    private static void DSP_ChangeMode(/*DSP_MODES*/int mode) {
        if (sb.mode==mode) return;
        else sb.chan.FillUp();
        sb.mode=mode;
    }

//    private static Pic.PIC_EventHandler DSP_RaiseIRQEvent = new Pic.PIC_EventHandler() {
//        public void call(/*Bitu*/int val) {
//            SB_RaiseIRQ(SB_IRQ_8);
//        }
//    };

    static void DSP_DoDMATransfer(/*DMA_MODES*/int mode,/*Bitu*/int freq,boolean stereo) {
        String type;
        sb.mode=MODE_DMA_MASKED;
        sb.chan.FillUp();
        sb.dma.left=sb.dma.total;
        sb.dma.mode=mode;
        sb.dma.stereo=stereo;
        sb.irq.pending_8bit=false;
        sb.irq.pending_16bit=false;
        switch (mode) {
        case DSP_DMA_2:
            type="2-bits ADPCM";
            sb.dma.mul=(1 << SB_SH)/4;
            break;
        case DSP_DMA_3:
            type="3-bits ADPCM";
            sb.dma.mul=(1 << SB_SH)/3;
            break;
        case DSP_DMA_4:
            type="4-bits ADPCM";
            sb.dma.mul=(1 << SB_SH)/2;
            break;
        case DSP_DMA_8:
            type="8-bits PCM";
            sb.dma.mul=(1 << SB_SH);
            break;
        case DSP_DMA_16_ALIASED:
            type="16-bits(aliased) PCM";
            sb.dma.mul=(1 << SB_SH)*2;
            break;
        case DSP_DMA_16:
            type="16-bits PCM";
            sb.dma.mul=(1 << SB_SH);
            break;
        default:
            Log.log(Level.SEVERE,"DSP:Illegal transfer mode "+mode);
            return;
        }
        if (sb.dma.stereo) sb.dma.mul*=2;
        sb.dma.rate=(sb.freq*sb.dma.mul) >> SB_SH;
        sb.dma.min=(sb.dma.rate*3)/1000;
        sb.chan.SetFreq(freq);
        sb.dma.mode=mode;
        //Pic.PIC_RemoveEvents(END_DMA_Event);
        //sb.dma.chan.Register_Callback(DSP_DMA_CallBack);
        sb.dma.chan.registerEventHandler(DSP_DMA_CallBack);
        if (DEBUG) {
            Log.log(Level.INFO,"DMA Transfer:"+type+" "+(sb.dma.stereo ? "Stereo" : "Mono")+" "+(sb.dma.autoinit ? "Auto-Init" : "Single-Cycle")+" freq "+freq+" rate "+sb.dma.rate+" size "+sb.dma.total);
        }
    }

    private static void DSP_PrepareDMA_Old(/*DMA_MODES*/int mode,boolean autoinit,boolean sign) {
        sb.dma.autoinit=autoinit;
        sb.dma.sign=sign;
        if (!autoinit) sb.dma.total=1+sb.dsp.in.data[0]+(sb.dsp.in.data[1] << 8);
        //sb.dma.chan=DMA.GetDMAChannel(sb.hw.dma8);
        sb.dma.chan = dma.getChannel(sb.hw.dma8);
        DSP_DoDMATransfer(mode,sb.freq / (sb.mixer.stereo ? 2 : 1),sb.mixer.stereo);
    }

    private static void DSP_PrepareDMA_New(/*DMA_MODES*/int mode,/*Bitu*/int length,boolean autoinit,boolean stereo) {
        /*Bitu*/int freq=sb.freq;
        //equal length if data format and dma channel are both 16-bit or 8-bit
        sb.dma.total=length;
        sb.dma.autoinit=autoinit;
        if (mode==DSP_DMA_16) {
            if (sb.hw.dma16!=0xff) {
                //sb.dma.chan=DMA.GetDMAChannel(sb.hw.dma16);
                sb.dma.chan = dma.getChannel(sb.hw.dma16);
//                if (sb.dma.chan==null) {
//                    sb.dma.chan=DMA.GetDMAChannel(sb.hw.dma8);
//                    mode=DSP_DMA_16_ALIASED;
//                    sb.dma.total<<=1;
//                }
            } else {
                //sb.dma.chan=DMA.GetDMAChannel(sb.hw.dma8);
                sb.dma.chan = dma.getChannel(sb.hw.dma8);
                mode=DSP_DMA_16_ALIASED;
                //UNDOCUMENTED:
                //In aliased mode sample length is written to DSP as number of
                //16-bit samples so we need double 8-bit DMA buffer length
                sb.dma.total<<=1;
            }
        } else
        {
            //sb.dma.chan=DMA.GetDMAChannel(sb.hw.dma8);
            sb.dma.chan = dma.getChannel(sb.hw.dma8);
        }
        DSP_DoDMATransfer(mode,freq,stereo);
    }


    private static void DSP_AddData(/*Bit8u*/int val) {
        if (sb.dsp.out.used<DSP_BUFSIZE) {
            /*Bitu*/int start=sb.dsp.out.used+sb.dsp.out.pos;
            if (start>=DSP_BUFSIZE) start-=DSP_BUFSIZE;
            sb.dsp.out.data[start]=(short)val;
            sb.dsp.out.used++;
        } else {
            Log.log(Level.SEVERE,"DSP:Data Output buffer full");
        }
    }

    private static void DSP_FinishReset() {
        DSP_FlushData();
        DSP_AddData((short)0xaa);
        sb.dsp.state=DSP_S_NORMAL;
    }

    private static void DSP_Reset() {
        Log.log(Level.FINE,"DSP:Reset");
        //Pic.PIC_DeActivateIRQ(sb.hw.irq);
        irqDevice.setIRQ(sb.hw.irq, 0);

        DSP_ChangeMode(MODE_NONE);
        DSP_FlushData();
        sb.dsp.cmd_len=0;
        sb.dsp.in.pos=0;
        sb.dsp.write_busy=0;
        //Pic.PIC_RemoveEvents(DSP_FinishReset);

        sb.dma.left=0;
        sb.dma.total=0;
        sb.dma.stereo=false;
        sb.dma.sign=false;
        sb.dma.autoinit=false;
        sb.dma.mode=DSP_DMA_NONE;
        sb.dma.remain_size=0;
        //if (sb.dma.chan!=null) sb.dma.chan.Clear_Request();

        sb.freq=22050;
        sb.time_constant=45;
        sb.dac.used=0;
        sb.dac.last=0;
        sb.e2.value=0xaa;
        sb.e2.count=0;
        sb.irq.pending_8bit=false;
        sb.irq.pending_16bit=false;
        sb.chan.SetFreq(22050);
//	DSP_SetSpeaker(false);
        //Pic.PIC_RemoveEvents(END_DMA_Event);
    }

    private static void DSP_DoReset(/*Bit8u*/short val) {
        if (((val&1)!=0) && (sb.dsp.state!=DSP_S_RESET)) {
//TODO Get out of highspeed mode
            DSP_Reset();
            sb.dsp.state=DSP_S_RESET;
        } else if (((val&1)==0) && (sb.dsp.state==DSP_S_RESET)) {	// reset off
            sb.dsp.state=DSP_S_RESET_WAIT;
            //Pic.PIC_RemoveEvents(DSP_FinishReset);
            //Pic.PIC_AddEvent(DSP_FinishReset,20.0f/1000.0f,0);	// 20 microseconds
            DSP_FinishReset();
        }
    }

    private static DMATransferCapable DSP_E2_DMA_CallBack = new DMATransferCapable() {
        public int handleTransfer(DMAController.DMAChannel c, int position, int size) {
            sb.dma.chan = c;
            sb.dma.position = position;
            throw new IllegalStateException("Figure this out bitch...");
//            if (event==DMA.DMAEvent.DMA_UNMASKED) {
//                /*Bit8u*/byte[] val=new byte[] {(/*Bit8u*/byte)(sb.e2.value&0xff)};
//                DMA.DmaChannel chan=DMA.GetDMAChannel(sb.hw.dma8);
//                chan.Register_Callback(null);
//                chan.Write(1, val, 0);
//                return 1;
//            }
//            return 0;
        }
    };

    private static DMATransferCapable DSP_ADC_CallBack = new DMATransferCapable() {
        public int handleTransfer(DMAController.DMAChannel chan, int position, int size) {
            sb.dma.chan = chan;
            sb.dma.position = position;
            int total = sb.dma.left;
            throw new IllegalStateException("Figure this out too, bitch...");
//            if (event!=DMA.DMAEvent.DMA_UNMASKED) return 0;
//            /*Bit8u*/byte[] val=new byte[] {(byte)128};
//            DMA.DmaChannel ch=DMA.GetDMAChannel(sb.hw.dma8);
//            while ((sb.dma.left--)!=0) {
//                ch.Write(1,val, 0);
//            }
//            SB_RaiseIRQ(SB_IRQ_8);
//            //ch.Register_Callback(null);
//            dma.releaseDmaRequest(sb.hw.dma8);
//            return total;
        }
    };

    static private boolean DSP_SB16_ONLY() {
        if (sb.type != SBT_16) {
            Log.log(Level.SEVERE,"DSP:Command "+Integer.toString(sb.dsp.cmd, 16)+" requires SB16");
            return true;
        }
        return false;
    }

    static private boolean DSP_SB2_ABOVE() {
        if (sb.type <= SBT_1) {
            Log.log(Level.SEVERE,"DSP:Command "+Integer.toString(sb.dsp.cmd, 16)+" requires SB2 or above");
            return true;
        }
        return false;
    }

    private static void DSP_DoCommand() {
	    Log.log(Level.INFO, String.format("DSP Command %X",sb.dsp.cmd));
        switch (sb.dsp.cmd) {
        case 0x04:
            if (sb.type == SBT_16) {
                /* SB16 ASP set mode register */
                if ((sb.dsp.in.data[0]&0xf1)==0xf1) ASP_init_in_progress=true;
                else ASP_init_in_progress=false;
                Log.log(Level.INFO,"DSP Unhandled SB16ASP command "+Integer.toString(sb.dsp.cmd, 16)+" (set mode register to "+Integer.toString(sb.dsp.in.data[0],16)+")");
            } else {
                /* DSP Status SB 2.0/pro version. NOT SB16. */
                DSP_FlushData();
                if (sb.type == SBT_2) DSP_AddData(0x88);
                else if ((sb.type == SBT_PRO1) || (sb.type == SBT_PRO2)) DSP_AddData(0x7b);
                else DSP_AddData(0xff);			//Everything enabled
            }
            break;
        case 0x05:	/* SB16 ASP set codec parameter */
            Log.log(Level.SEVERE,"DSP Unhandled SB16ASP command "+Integer.toString(sb.dsp.cmd, 16)+" (set codec parameter)");
            break;
        case 0x08:	/* SB16 ASP get version */
            Log.log(Level.SEVERE,"DSP Unhandled SB16ASP command "+Integer.toString(sb.dsp.cmd, 16)+" sub "+Integer.toString(sb.dsp.in.data[0],16));
            if (sb.type == SBT_16) {
                switch (sb.dsp.in.data[0]) {
                    case 0x03:
                        DSP_AddData(0x18);	// version ID (??)
                        break;
                    default:
                        Log.log(Level.SEVERE,"DSP Unhandled SB16ASP command "+Integer.toString(sb.dsp.cmd, 16)+" sub "+Integer.toString(sb.dsp.in.data[0], 16));
                        break;
                }
            } else {
                Log.log(Level.SEVERE,"DSP Unhandled SB16ASP command "+Integer.toString(sb.dsp.cmd, 16)+" sub "+Integer.toString(sb.dsp.in.data[0],16));
            }
            break;
        case 0x0e:	/* SB16 ASP set register */
            if (sb.type == SBT_16) {
//			Log.log(LogTypes.LOG_SB,LogSeverities.LOG_NORMAL,"SB16 ASP set register %X := %X",sb.dsp.in.data[0],sb.dsp.in.data[1]);
                ASP_regs[sb.dsp.in.data[0]] = sb.dsp.in.data[1];
            } else {
                Log.log(Level.SEVERE,"DSP Unhandled SB16ASP command "+Integer.toString(sb.dsp.cmd, 16)+" (set register)");
            }
            break;
        case 0x0f:	/* SB16 ASP get register */
            if (sb.type == SBT_16) {
                if ((ASP_init_in_progress) && (sb.dsp.in.data[0]==0x83)) {
                    ASP_regs[0x83] = (short)~ASP_regs[0x83];
                }
//			Log.log(LogTypes.LOG_SB,LogSeverities.LOG_NORMAL,"SB16 ASP get register %X == %X",sb.dsp.in.data[0],ASP_regs[sb.dsp.in.data[0]]);
                DSP_AddData(ASP_regs[sb.dsp.in.data[0]]);
            } else {
                Log.log(Level.SEVERE,"DSP Unhandled SB16ASP command "+Integer.toString(sb.dsp.cmd, 16)+" (get register)");
            }
            break;
        case 0x10:	/* Direct DAC */
            DSP_ChangeMode(MODE_DAC);
            if (sb.dac.used<DSP_DACSIZE) {
                sb.dac.data[sb.dac.used++]=(short)((sb.dsp.in.data[0] ^ 0x80) << 8);
                sb.dac.data[sb.dac.used++]=(short)((sb.dsp.in.data[0] ^ 0x80) << 8);
            }
            break;
        case 0x24:	/* Singe Cycle 8-Bit DMA ADC */
            sb.dma.left=sb.dma.total=1+sb.dsp.in.data[0]+(sb.dsp.in.data[1] << 8);
            sb.dma.sign=false;
            Log.log(Level.SEVERE,"DSP:Faked ADC for "+sb.dma.total+" bytes");
            //DMA.GetDMAChannel(sb.hw.dma8).Register_Callback(DSP_ADC_CallBack);
            dma.registerChannel(sb.hw.dma8, DSP_ADC_CallBack);
            break;
        case 0x14:	/* Singe Cycle 8-Bit DMA DAC */
        case 0x15:	/* Wari hack. Waru uses this one instead of 0x14, but some weird stuff going on there anyway */
        case 0x91:	/* Singe Cycle 8-Bit DMA High speed DAC */
            /* Note: 0x91 is documented only for DSP ver.2.x and 3.x, not 4.x */
            DSP_PrepareDMA_Old(DSP_DMA_8,false,false);
            break;
        case 0x90:	/* Auto Init 8-bit DMA High Speed */
        case 0x1c:	/* Auto Init 8-bit DMA */
            if (DSP_SB2_ABOVE()) break; /* Note: 0x90 is documented only for DSP ver.2.x and 3.x, not 4.x */
            DSP_PrepareDMA_Old(DSP_DMA_8,true,false);
            break;
        case 0x38:  /* Write to SB MIDI Output */
            if (sb.midi) Midi.MIDI_RawOutByte(sb.dsp.in.data[0]);
            break;
        case 0x40:	/* Set Timeconstant */
            sb.freq=(1000000 / (256 - sb.dsp.in.data[0]));
            /* Nasty kind of hack to allow runtime changing of frequency */
            if (sb.dma.mode != DSP_DMA_NONE && sb.dma.autoinit) {
                DSP_PrepareDMA_Old(sb.dma.mode,sb.dma.autoinit,sb.dma.sign);
            }
            break;
        case 0x41:	/* Set Output Samplerate */
        case 0x42:	/* Set Input Samplerate */
            if (DSP_SB16_ONLY()) break;
            sb.freq=(sb.dsp.in.data[0] << 8)  | sb.dsp.in.data[1];
            break;
        case 0x48:	/* Set DMA Block Size */
            if (DSP_SB2_ABOVE()) break;
            //TODO Maybe check limit for new irq?
            sb.dma.total=1+sb.dsp.in.data[0]+(sb.dsp.in.data[1] << 8);
            break;
        case 0x75:	/* 075h : Single Cycle 4-bit ADPCM Reference */
            sb.adpcm.haveref=true;
        case 0x74:	/* 074h : Single Cycle 4-bit ADPCM */
            DSP_PrepareDMA_Old(DSP_DMA_4,false,false);
            break;
        case 0x77:	/* 077h : Single Cycle 3-bit(2.6bit) ADPCM Reference*/
            sb.adpcm.haveref=true;
        case 0x76:  /* 074h : Single Cycle 3-bit(2.6bit) ADPCM */
            DSP_PrepareDMA_Old(DSP_DMA_3,false,false);
            break;
        case 0x7d:	/* Auto Init 4-bit ADPCM Reference */
		    if (DSP_SB2_ABOVE()) break;
		    sb.adpcm.haveref=true;
		    DSP_PrepareDMA_Old(DSP_DMA_4,true,false);
		    break;
        case 0x17:	/* 017h : Single Cycle 2-bit ADPCM Reference*/
            sb.adpcm.haveref=true;
        case 0x16:  /* 074h : Single Cycle 2-bit ADPCM */
            DSP_PrepareDMA_Old(DSP_DMA_2,false,false);
            break;
        case 0x80:	/* Silence DAC */
            //Pic.PIC_AddEvent(DSP_RaiseIRQEvent,
            //    (1000.0f*(1+sb.dsp.in.data[0]+(sb.dsp.in.data[1] << 8))/sb.freq));
            SB_RaiseIRQ(SB_IRQ_8);
            break;
        case 0xb0:	case 0xb1:	case 0xb2:	case 0xb3:  case 0xb4:	case 0xb5:	case 0xb6:	case 0xb7:
        case 0xb8:	case 0xb9:	case 0xba:	case 0xbb:  case 0xbc:	case 0xbd:	case 0xbe:	case 0xbf:
        case 0xc0:	case 0xc1:	case 0xc2:	case 0xc3:  case 0xc4:	case 0xc5:	case 0xc6:	case 0xc7:
        case 0xc8:	case 0xc9:	case 0xca:	case 0xcb:  case 0xcc:	case 0xcd:	case 0xce:	case 0xcf:
            if (DSP_SB16_ONLY()) break;
            /* Generic 8/16 bit DMA */
//		DSP_SetSpeaker(true);		//SB16 always has speaker enabled
            sb.dma.sign=(sb.dsp.in.data[0] & 0x10) > 0;
            DSP_PrepareDMA_New((sb.dsp.cmd & 0x10)!=0 ? DSP_DMA_16 : DSP_DMA_8,
                1+sb.dsp.in.data[1]+(sb.dsp.in.data[2] << 8),
                (sb.dsp.cmd & 0x4)>0,
                (sb.dsp.in.data[0] & 0x20) > 0
            );
            break;
        case 0xd5:	/* Halt 16-bit DMA */
            if (DSP_SB16_ONLY()) break;
        case 0xd0:	/* Halt 8-bit DMA */
//		DSP_ChangeMode(MODE_NONE);
//		Games sometimes already program a new dma before stopping, gives noise
            if (sb.mode==MODE_NONE) {
			    // possibly different code here that does not switch to MODE_DMA_PAUSE
		    }
            sb.mode=MODE_DMA_PAUSE;
            //Pic.PIC_RemoveEvents(END_DMA_Event);
            break;
        case 0xd1:	/* Enable Speaker */
            DSP_SetSpeaker(true);
            break;
        case 0xd3:	/* Disable Speaker */
            DSP_SetSpeaker(false);
            break;
        case 0xd8:  /* Speaker status */
            if (DSP_SB2_ABOVE()) break;
            DSP_FlushData();
            if (sb.speaker) DSP_AddData(0xff);
            else DSP_AddData(0x00);
            break;
        case 0xd6:	/* Continue DMA 16-bit */
            if (DSP_SB16_ONLY()) break;
        case 0xd4:	/* Continue DMA 8-bit*/
            if (sb.mode==MODE_DMA_PAUSE) {
                sb.mode=MODE_DMA_MASKED;
                if (sb.dma.chan!=null) //sb.dma.chan.Register_Callback(DSP_DMA_CallBack);
                    sb.dma.chan.registerEventHandler(DSP_DMA_CallBack);
            }
            break;
        case 0xd9:  /* Exit Autoinitialize 16-bit */
            if (DSP_SB16_ONLY()) break;
        case 0xda:	/* Exit Autoinitialize 8-bit */
            if (DSP_SB2_ABOVE()) break;
            /* Set mode to single transfer so it ends with current block */
            sb.dma.autoinit=false;		//Should stop itself
            break;
        case 0xe0:	/* DSP Identification - SB2.0+ */
            DSP_FlushData();
            DSP_AddData(~sb.dsp.in.data[0]);
            break;
        case 0xe1:	/* Get DSP Version */
            DSP_FlushData();
            switch (sb.type) {
            case SBT_1:
                DSP_AddData(0x1);DSP_AddData(0x05);break;
            case SBT_2:
                DSP_AddData(0x2);DSP_AddData(0x1);break;
            case SBT_PRO1:
                DSP_AddData(0x3);DSP_AddData(0x0);break;
            case SBT_PRO2:
                DSP_AddData(0x3);DSP_AddData(0x2);break;
            case SBT_16:
                DSP_AddData(0x4);DSP_AddData(0x5);break;
            default:
                break;
            }
            break;
        case 0xe2:	/* Weird DMA identification write routine */
            {
                Log.log(Level.INFO,"DSP Function 0xe2");
                for (/*Bitu*/int i = 0; i < 8; i++)
                    if (((sb.dsp.in.data[0] >> i) & 0x01)!=0) sb.e2.value += E2_incr_table[sb.e2.count % 4][i];
                sb.e2.value += E2_incr_table[sb.e2.count % 4][8];
                sb.e2.count++;
                //DMA.GetDMAChannel(sb.hw.dma8).Register_Callback(DSP_E2_DMA_CallBack);
                dma.registerChannel(sb.hw.dma8, DSP_E2_DMA_CallBack);
            }
            break;
        case 0xe3:	/* DSP Copyright */
            {
                DSP_FlushData();
                byte[] b = copyright_string.getBytes();
                for (int i=0;i<b.length;i++) {
                    DSP_AddData(b[i]);
                }
                DSP_AddData(0);
            }
            break;
        case 0xe4:	/* Write Test Register */
            sb.dsp.test_register=sb.dsp.in.data[0];
            break;
        case 0xe8:	/* Read Test Register */
            DSP_FlushData();
            DSP_AddData(sb.dsp.test_register);
            break;
        case 0xf2:	/* Trigger 8bit IRQ */
            SB_RaiseIRQ(SB_IRQ_8);
            break;
        case 0xf3:   /* Trigger 16bit IRQ */
		    DSP_SB16_ONLY();
		    SB_RaiseIRQ(SB_IRQ_16);
		    break;
        case 0xf8:  /* Undocumented, pre-SB16 only */
            DSP_FlushData();
            DSP_AddData(0);
            break;
        case 0x30: case 0x31:
            Log.log(Level.SEVERE,"DSP:Unimplemented MIDI I/O command "+Integer.toString(sb.dsp.cmd,16));
            break;
        case 0x34: case 0x35: case 0x36: case 0x37:
            if (DSP_SB2_ABOVE()) break;
            Log.log(Level.SEVERE,"DSP:Unimplemented MIDI UART command "+Integer.toString(sb.dsp.cmd,16));
            break;
        case 0x7f: case 0x1f:
            if (DSP_SB2_ABOVE()) break;
            Log.log(Level.SEVERE,"DSP:Unimplemented auto-init DMA ADPCM command "+Integer.toString(sb.dsp.cmd,16));
            break;
        case 0x20:
            DSP_AddData(0x7f);   // fake silent input for Creative parrot
            break;
        case 0x2c:
        case 0x98: case 0x99: /* Documented only for DSP 2.x and 3.x */
        case 0xa0: case 0xa8: /* Documented only for DSP 3.x */
            Log.log(Level.SEVERE,"DSP:Unimplemented input command "+Integer.toString(sb.dsp.cmd,16));
            break;
        case 0xf9:	/* SB16 ASP ??? */
            if (sb.type == SBT_16) {
                Log.log(Level.SEVERE,"SB16 ASP unknown function "+Integer.toString(sb.dsp.in.data[0],16));
                // just feed it what it expects
                switch (sb.dsp.in.data[0]) {
                case 0x0b:
                    DSP_AddData(0x00);
                    break;
                case 0x0e:
                    DSP_AddData(0xff);
                    break;
                case 0x0f:
                    DSP_AddData(0x07);
                    break;
                case 0x23:
                    DSP_AddData(0x00);
                    break;
                case 0x24:
                    DSP_AddData(0x00);
                    break;
                case 0x2b:
                    DSP_AddData(0x00);
                    break;
                case 0x2c:
                    DSP_AddData(0x00);
                    break;
                case 0x2d:
                    DSP_AddData(0x00);
                    break;
                case 0x37:
                    DSP_AddData(0x38);
                    break;
                default:
                    DSP_AddData(0x00);
                    break;
                }
            } else {
                Log.log(Level.SEVERE,"SB16 ASP unknown function "+Integer.toString(sb.dsp.cmd,16));
            }
            break;
        default:
            Log.log(Level.SEVERE,"DSP:Unhandled (undocumented) command "+Integer.toString(sb.dsp.cmd,16));
            break;
        }
        sb.dsp.cmd=DSP_NO_COMMAND;
        sb.dsp.cmd_len=0;
        sb.dsp.in.pos=0;
    }

    private static void DSP_DoWrite(/*Bit8u*/short val) {
        switch (sb.dsp.cmd) {
        case DSP_NO_COMMAND:
            sb.dsp.cmd=val;
            if (sb.type == SBT_16) sb.dsp.cmd_len=DSP_cmd_len_sb16[val];
            else sb.dsp.cmd_len=DSP_cmd_len_sb[val];
            sb.dsp.in.pos=0;
            if (sb.dsp.cmd_len==0) DSP_DoCommand();
            break;
        default:
            sb.dsp.in.data[sb.dsp.in.pos]=val;
            sb.dsp.in.pos++;
            if (sb.dsp.in.pos>=sb.dsp.cmd_len)
                DSP_DoCommand();
        }
    }

    private static /*Bit8u*/short DSP_ReadData() {
/* Static so it repeats the last value on succesive reads (JANGLE DEMO) */
        if (sb.dsp.out.used!=0) {
            sb.dsp.out.lastval=sb.dsp.out.data[sb.dsp.out.pos];
            sb.dsp.out.pos++;
            if (sb.dsp.out.pos>=DSP_BUFSIZE) sb.dsp.out.pos-=DSP_BUFSIZE;
            sb.dsp.out.used--;
        }
        return sb.dsp.out.lastval;
    }

//The soundblaster manual says 2.0 Db steps but we'll go for a bit less
    static private float CALCVOL(float _VAL) { return (float)Math.pow(10.0f,((float)(31-_VAL)*-1.3f)/20);}
    private static void CTMIXER_UpdateVolumes() {
        if (!sb.mixer.enabled) return;
        Mixer.MixerChannel chan;
        //adjust to get linear master volume slider in trackers
        chan=Mixer.MIXER_FindChannel("SB");
        if (chan!=null) chan.SetVolume((float)(sb.mixer.master[0])/31.0f*CALCVOL(sb.mixer.dac[0]),
                                  (float)(sb.mixer.master[1])/31.0f*CALCVOL(sb.mixer.dac[1]));
        chan=Mixer.MIXER_FindChannel("FM");
        if (chan!=null) chan.SetVolume((float)(sb.mixer.master[0])/31.0f*CALCVOL(sb.mixer.fm[0]),
                                  (float)(sb.mixer.master[1])/31.0f*CALCVOL(sb.mixer.fm[1]));
        chan=Mixer.MIXER_FindChannel("CDAUDIO");
	    if (chan!=null) chan.SetVolume((float)(sb.mixer.master[0])/31.0f*CALCVOL(sb.mixer.cda[0]),
							  (float)(sb.mixer.master[1])/31.0f*CALCVOL(sb.mixer.cda[1]));
    }

    private static void CTMIXER_Reset() {
        sb.mixer.fm[0]=
        sb.mixer.fm[1]=
        sb.mixer.cda[0]=
	    sb.mixer.cda[1]=
        sb.mixer.dac[0]=
        sb.mixer.dac[1]=31;
        sb.mixer.master[0]=
        sb.mixer.master[1]=31;
        CTMIXER_UpdateVolumes();
    }

    static private void SETPROVOL(short[] _WHICH_,int _VAL_) {
        _WHICH_[0]=  (short) ((((_VAL_) & 0xf0) >> 3)|(sb.type==SBT_16 ? 1:3));
        _WHICH_[1]=  (short) ((((_VAL_) & 0x0f) << 1)|(sb.type==SBT_16 ? 1:3));
    }

    static private int MAKEPROVOL(short[] _WHICH_) {
        return ((((_WHICH_[0] & 0x1e) << 3) | ((_WHICH_[1] & 0x1e) >> 1)) & (sb.type==SBT_16 ? 0xff:0xee));
    }

    private static void DSP_ChangeStereo(boolean stereo) {
        if (!sb.dma.stereo && stereo) {
            sb.chan.SetFreq(sb.freq/2);
            sb.dma.mul*=2;
            sb.dma.rate=(sb.freq*sb.dma.mul) >> SB_SH;
            sb.dma.min=(sb.dma.rate*3)/1000;
        } else if (sb.dma.stereo && !stereo) {
            sb.chan.SetFreq(sb.freq);
            sb.dma.mul/=2;
            sb.dma.rate=(sb.freq*sb.dma.mul) >> SB_SH;
            sb.dma.min=(sb.dma.rate*3)/1000;
        }
        sb.dma.stereo=stereo;
    }

    private static void CTMIXER_Write(/*Bit8u*/short val) {
        switch (sb.mixer.index) {
        case 0x00:		/* Reset */
            CTMIXER_Reset();
            Log.log(Level.WARNING,"Mixer reset value "+Integer.toString(val,16));
            break;
        case 0x02:		/* Master Volume (SB2 Only) */
            SETPROVOL(sb.mixer.master,(val&0xf)|(val<<4));
            CTMIXER_UpdateVolumes();
            break;
        case 0x04:		/* DAC Volume (SBPRO) */
            SETPROVOL(sb.mixer.dac,val);
            CTMIXER_UpdateVolumes();
            break;
        case 0x06:		/* FM output selection, Somewhat obsolete with dual OPL SBpro + FM volume (SB2 Only) */
            //volume controls both channels
            SETPROVOL(sb.mixer.fm,(val&0xf)|(val<<4));
            CTMIXER_UpdateVolumes();
            if((val&0x60)!=0) Log.log(Level.WARNING,"Turned FM one channel off. not implemented "+Integer.toString(val,16));
            //TODO Change FM Mode if only 1 fm channel is selected
            break;
        case 0x08:		/* CDA Volume (SB2 Only) */
            SETPROVOL(sb.mixer.cda,(val&0xf)|(val<<4));
            CTMIXER_UpdateVolumes();
            break;
        case 0x0a:		/* Mic Level (SBPRO) or DAC Volume (SB2): 2-bit, 3-bit on SB16 */
            if (sb.type==SBT_2) {
                sb.mixer.dac[0]=sb.mixer.dac[1]=(short)(((val & 0x6) << 2)|3);
                CTMIXER_UpdateVolumes();
            } else {
                sb.mixer.mic=(short)(((val & 0x7) << 2)|(sb.type==SBT_16?1:3));
            }
            break;
        case 0x0e:		/* Output/Stereo Select */
            sb.mixer.stereo=(val & 0x2) > 0;
            sb.mixer.filtered=(val & 0x20) > 0;
            DSP_ChangeStereo(sb.mixer.stereo);
            Log.log(Level.WARNING,"Mixer set to "+(sb.dma.stereo ? "STEREO" : "MONO"));
            break;
        case 0x22:		/* Master Volume (SBPRO) */
            SETPROVOL(sb.mixer.master,val);
            CTMIXER_UpdateVolumes();
            break;
        case 0x26:		/* FM Volume (SBPRO) */
            SETPROVOL(sb.mixer.fm,val);
            CTMIXER_UpdateVolumes();
            break;
        case 0x28:		/* CD Audio Volume (SBPRO) */
            SETPROVOL(sb.mixer.cda,val);
            CTMIXER_UpdateVolumes();
            break;
        case 0x2e:		/* Line-in Volume (SBPRO) */
            SETPROVOL(sb.mixer.lin,val);
            break;
        //case 0x20:		/* Master Volume Left (SBPRO) ? */
        case 0x30:		/* Master Volume Left (SB16) */
            if (sb.type==SBT_16) {
                sb.mixer.master[0]=(short)(val>>>3);
                CTMIXER_UpdateVolumes();
            }
            break;
        //case 0x21:		/* Master Volume Right (SBPRO) ? */
        case 0x31:		/* Master Volume Right (SB16) */
            if (sb.type==SBT_16) {
                sb.mixer.master[1]=(short)(val>>>3);
                CTMIXER_UpdateVolumes();
            }
            break;
        case 0x32:		/* DAC Volume Left (SB16) */
            if (sb.type==SBT_16) {
                sb.mixer.dac[0]=(short)(val>>>3);
                CTMIXER_UpdateVolumes();
            }
            break;
        case 0x33:		/* DAC Volume Right (SB16) */
            if (sb.type==SBT_16) {
                sb.mixer.dac[1]=(short)(val>>>3);
                CTMIXER_UpdateVolumes();
            }
            break;
        case 0x34:		/* FM Volume Left (SB16) */
            if (sb.type==SBT_16) {
                sb.mixer.fm[0]=(short)(val>>>3);
                CTMIXER_UpdateVolumes();
            }
                    break;
        case 0x35:		/* FM Volume Right (SB16) */
            if (sb.type==SBT_16) {
                sb.mixer.fm[1]=(short)(val>>>3);
                CTMIXER_UpdateVolumes();
            }
            break;
        case 0x36:		/* CD Volume Left (SB16) */
            if (sb.type==SBT_16) {
                sb.mixer.cda[0]=(short)(val>>>3);
                CTMIXER_UpdateVolumes();
            }
            break;
        case 0x37:		/* CD Volume Right (SB16) */
            if (sb.type==SBT_16) {
                sb.mixer.cda[1]=(short)(val>>>3);
                CTMIXER_UpdateVolumes();
            }
            break;
        case 0x38:		/* Line-in Volume Left (SB16) */
            if (sb.type==SBT_16) sb.mixer.lin[0]=(short)(val>>>3);
            break;
        case 0x39:		/* Line-in Volume Right (SB16) */
            if (sb.type==SBT_16) sb.mixer.lin[1]=(short)(val>>>3);
            break;
        case 0x3a:
            if (sb.type==SBT_16) sb.mixer.mic=(short)(val>>>3);
            break;
        case 0x80:		/* IRQ Select */
            sb.hw.irq=0xff;
            if ((val & 0x1)!=0) sb.hw.irq=2;
            else if ((val & 0x2)!=0) sb.hw.irq=5;
            else if ((val & 0x4)!=0) sb.hw.irq=7;
            else if ((val & 0x8)!=0) sb.hw.irq=10;
            break;
        case 0x81:		/* DMA Select */
            sb.hw.dma8=0xff;
            sb.hw.dma16=0xff;
            if ((val & 0x1)!=0) sb.hw.dma8=0;
            else if ((val & 0x2)!=0) sb.hw.dma8=1;
            else if ((val & 0x8)!=0) sb.hw.dma8=3;
            if ((val & 0x20)!=0) sb.hw.dma16=5;
            else if ((val & 0x40)!=0) sb.hw.dma16=6;
            else if ((val & 0x80)!=0) sb.hw.dma16=7;
            Log.log(Level.INFO,"Mixer select dma8:"+Integer.toString(sb.hw.dma8,16)+" dma16:"+Integer.toString(sb.hw.dma16,16));
            break;
        default:

            if(	((sb.type == SBT_PRO1 || sb.type == SBT_PRO2) && sb.mixer.index==0x0c) || /* Input control on SBPro */
                 (sb.type == SBT_16 && sb.mixer.index >= 0x3b && sb.mixer.index <= 0x47)) /* New SB16 registers */
                sb.mixer.unhandled[sb.mixer.index] = val;
            Log.log(Level.WARNING,"MIXER:Write "+Integer.toString(val, 16)+" to unhandled index "+Integer.toString(sb.mixer.index,16));
        }
    }

    private static /*Bit8u*/int CTMIXER_Read() {
        /*Bit8u*/short ret;
//	if ( sb.mixer.index< 0x80) LOG_MSG("Read mixer %x",sb.mixer.index);
        switch (sb.mixer.index) {
        case 0x00:		/* RESET */
            return 0x00;
        case 0x02:		/* Master Volume (SB2 Only) */
            return ((sb.mixer.master[1]>>>1) & 0xe);
        case 0x22:		/* Master Volume (SBPRO) */
            return	MAKEPROVOL(sb.mixer.master);
        case 0x04:		/* DAC Volume (SBPRO) */
            return MAKEPROVOL(sb.mixer.dac);
        case 0x06:		/* FM Volume (SB2 Only) + FM output selection */
            return ((sb.mixer.fm[1]>>>1) & 0xe);
        case 0x08:		/* CD Volume (SB2 Only) */
            return ((sb.mixer.cda[1]>>>1) & 0xe);
        case 0x0a:		/* Mic Level (SBPRO) or Voice (SB2 Only) */
            if (sb.type==SBT_2) return (sb.mixer.dac[0]>>>2);
            else return ((sb.mixer.mic >>> 2) & (sb.type==SBT_16 ? 7:6));
        case 0x0e:		/* Output/Stereo Select */
            return 0x11|(sb.mixer.stereo ? 0x02 : 0x00)|(sb.mixer.filtered ? 0x20 : 0x00);
        case 0x26:		/* FM Volume (SBPRO) */
            return MAKEPROVOL(sb.mixer.fm);
        case 0x28:		/* CD Audio Volume (SBPRO) */
            return MAKEPROVOL(sb.mixer.cda);
        case 0x2e:		/* Line-IN Volume (SBPRO) */
            return MAKEPROVOL(sb.mixer.lin);
        case 0x30:		/* Master Volume Left (SB16) */
            if (sb.type==SBT_16) return sb.mixer.master[0]<<3;
            ret=0xa;
            break;
        case 0x31:		/* Master Volume Right (S16) */
            if (sb.type==SBT_16) return sb.mixer.master[1]<<3;
            ret=0xa;
            break;
        case 0x32:		/* DAC Volume Left (SB16) */
            if (sb.type==SBT_16) return sb.mixer.dac[0]<<3;
            ret=0xa;
            break;
        case 0x33:		/* DAC Volume Right (SB16) */
            if (sb.type==SBT_16) return sb.mixer.dac[1]<<3;
            ret=0xa;
            break;
        case 0x34:		/* FM Volume Left (SB16) */
            if (sb.type==SBT_16) return sb.mixer.fm[0]<<3;
            ret=0xa;
            break;
        case 0x35:		/* FM Volume Right (SB16) */
            if (sb.type==SBT_16) return sb.mixer.fm[1]<<3;
            ret=0xa;
            break;
        case 0x36:		/* CD Volume Left (SB16) */
            if (sb.type==SBT_16) return sb.mixer.cda[0]<<3;
            ret=0xa;
            break;
        case 0x37:		/* CD Volume Right (SB16) */
            if (sb.type==SBT_16) return sb.mixer.cda[1]<<3;
            ret=0xa;
            break;
        case 0x38:		/* Line-in Volume Left (SB16) */
            if (sb.type==SBT_16) return sb.mixer.lin[0]<<3;
            ret=0xa;
            break;
        case 0x39:		/* Line-in Volume Right (SB16) */
            if (sb.type==SBT_16) return sb.mixer.lin[1]<<3;
            ret=0xa;
            break;
        case 0x3a:		/* Mic Volume (SB16) */
            if (sb.type==SBT_16) return sb.mixer.mic<<3;
            ret=0xa;
            break;
        case 0x80:		/* IRQ Select */
            switch (sb.hw.irq) {
            case 2:  return 0x1;
            case 5:  return 0x2;
            case 7:  return 0x4;
            case 10: return 0x8;
            }
        case 0x81:		/* DMA Select */
            ret=0;
            switch (sb.hw.dma8) {
            case 0:ret|=0x1;break;
            case 1:ret|=0x2;break;
            case 3:ret|=0x8;break;
            }
            switch (sb.hw.dma16) {
            case 5:ret|=0x20;break;
            case 6:ret|=0x40;break;
            case 7:ret|=0x80;break;
            }
            return ret;
        case 0x82:		/* IRQ Status */
            return	(sb.irq.pending_8bit ? 0x1 : 0) |
                    (sb.irq.pending_16bit ? 0x2 : 0) |
                    ((sb.type == SBT_16) ? 0x20 : 0);
        default:
            if (	((sb.type == SBT_PRO1 || sb.type == SBT_PRO2) && sb.mixer.index==0x0c) || /* Input control on SBPro */
                (sb.type == SBT_16 && sb.mixer.index >= 0x3b && sb.mixer.index <= 0x47)) /* New SB16 registers */
                ret = sb.mixer.unhandled[sb.mixer.index];
            else
                ret=0xa;
            Log.log(Level.WARNING,"MIXER:Read from unhandled index "+Integer.toString(sb.mixer.index,16));
        }
        return ret;
    }

//    private static IoHandler.IO_ReadHandler read_sb = new IoHandler.IO_ReadHandler() {
//        public /*Bitu*/int call(/*Bitu*/int port, /*Bitu*/int iolen) {
//            switch (port-sb.hw.base) {
//            case MIXER_INDEX:
//                return sb.mixer.index;
//            case MIXER_DATA:
//                return CTMIXER_Read();
//            case DSP_READ_DATA:
//                return DSP_ReadData();
//            case DSP_READ_STATUS:
//                //TODO See for high speed dma :)
//                if (sb.irq.pending_8bit)  {
//                    sb.irq.pending_8bit=false;
//                    //Pic.PIC_DeActivateIRQ(sb.hw.irq);
//                    irqDevice.setIRQ(sb.hw.irq, 0);
//                }
//                if (sb.dsp.out.used!=0) return 0xff;
//                else return 0x7f;
//            case DSP_ACK_16BIT:
//                sb.irq.pending_16bit=false;
//                break;
//            case DSP_WRITE_STATUS:
//                switch (sb.dsp.state) {
//                case DSP_S_NORMAL:
//                    sb.dsp.write_busy++;
//                    if ((sb.dsp.write_busy & 8)!=0) return 0xff;
//                    return 0x7f;
//                case DSP_S_RESET:
//                case DSP_S_RESET_WAIT:
//                    return 0xff;
//                }
//                return 0xff;
//            case DSP_RESET:
//                return 0xff;
//            default:
//                Log.log(Level.SEVERE,"Unhandled read from SB Port "+Integer.toString(port, 16));
//                break;
//            }
//            return 0xff;
//        }
//    };

//    private static IoHandler.IO_WriteHandler write_sb = new IoHandler.IO_WriteHandler() {
//        public void call(/*Bitu*/int port, /*Bitu*/int val, /*Bitu*/int iolen) {
//            /*Bit8u*/short val8=(/*Bit8u*/short)(val&0xff);
//            switch (port-sb.hw.base) {
//            case DSP_RESET:
//                DSP_DoReset(val8);
//                break;
//            case DSP_WRITE_DATA:
//                DSP_DoWrite(val8);
//                break;
//            case MIXER_INDEX:
//                sb.mixer.index=val8;
//                break;
//            case MIXER_DATA:
//                CTMIXER_Write(val8);
//                break;
//            default:
//                Log.log(Level.SEVERE,"Unhandled write to SB Port "+Integer.toString(port,16));
//                break;
//            }
//        }
//    };

//    private static IoHandler.IO_WriteHandler adlib_gusforward = new IoHandler.IO_WriteHandler() {
//        public void call(/*Bitu*/int port, /*Bitu*/int val, /*Bitu*/int iolen) {
//            //Gus.adlib_commandreg=(/*Bit8u*/short)(val&0xff);
//        }
//    };

    public static boolean SB_Get_Address(/*Bitu*/IntRef sbaddr, /*Bitu*/IntRef sbirq, /*Bitu*/IntRef sbdma) {
        sbaddr.value = 0;
        sbirq.value = 0;
        sbdma.value = 0;
        if (sb.type == SBT_NONE) return false;
        else {
            sbaddr.value = sb.hw.base;
            sbirq.value = sb.hw.irq;
            sbdma.value = sb.hw.dma8;
            return true;
        }
    }

    private static Mixer.MIXER_Handler SBLASTER_CallBack = new Mixer.MIXER_Handler() {
        public void call(/*Bitu*/int len) {
            switch (sb.mode) {
            case MODE_NONE:
            case MODE_DMA_PAUSE:
            case MODE_DMA_MASKED:
                sb.chan.AddSilence();
                break;
            case MODE_DAC:
    //		GenerateDACSound(len);
    //		break;
                if (sb.dac.used==0) {
                    sb.mode=MODE_NONE;
                    return;
                }
                sb.chan.AddStretched(sb.dac.used,sb.dac.data);
                sb.dac.used=0;
                break;
            case MODE_DMA:
                len*=sb.dma.mul;
                if ((len&SB_SH_MASK)!=0) len+=1 << SB_SH;
                len>>=SB_SH;
                if (len>sb.dma.left) len=sb.dma.left;
                GenerateDMASound(len);
                break;
            }
        }
    };

    /* Data */
//    private IoHandler.IO_ReadHandleObject[] ReadHandler = new IoHandler.IO_ReadHandleObject[0x10];
//    private IoHandler.IO_WriteHandleObject[] WriteHandler = new IoHandler.IO_WriteHandleObject[0x10];
    private Mixer.MixerObject MixerChan = new Mixer.MixerObject();

    /* Support Functions */
    private void Find_Type_And_Opl(/*SB_TYPES*/IntRef type, /*OPL_Mode*/IntRef opl_mode){
        String sbtype = Option.sbtype.value("sb16");
        if (sbtype.equalsIgnoreCase("sb1")) type.value=SBT_1;
        else if (sbtype.equalsIgnoreCase("sb2")) type.value=SBT_2;
        else if (sbtype.equalsIgnoreCase("sbpro1")) type.value=SBT_PRO1;
        else if (sbtype.equalsIgnoreCase("sbpro2")) type.value=SBT_PRO2;
        else if (sbtype.equalsIgnoreCase("sb16")) type.value=SBT_16;
        else if (sbtype.equalsIgnoreCase("gb")) type.value=SBT_GB;
        else if (sbtype.equalsIgnoreCase("none")) type.value=SBT_NONE;
        else type.value=SBT_16;

        if (type.value==SBT_16) {
            //if ((!Dosbox.IS_EGAVGA_ARCH()) || !DMA.SecondDMAControllerAvailable()) type.value=SBT_PRO2;

        }

        /* OPL/CMS Init */
        switch (type.value) {
            case SBT_NONE:
                opl_mode.value = 0;
                break;
            case SBT_GB:
                opl_mode.value=1;
                break;
            case SBT_1:
            case SBT_2:
                opl_mode.value=2;
                break;
            case SBT_PRO1:
                opl_mode.value=3;
                break;
            case SBT_PRO2:
            case SBT_16:
                opl_mode.value=4;
                break;
        }
    }

    private void close() {
        switch (oplmode) {
        case 0:
            break;
        case 1:
            //Gameblaster.CMS_ShutDown(m_configuration);
            break;
        case 2:
            //Gameblaster.CMS_ShutDown(m_configuration);
            // fall-through
        case 3:
        case 4:
            //Adlib.OPL_ShutDown(m_configuration);
            break;
        }
        if (sb.type==SBT_NONE || sb.type==SBT_GB) return;
        DSP_Reset(); // Stop everything
    }

    public int ioPortRead8(int address)
    {
        return ioPortRead32(address);
    }

    public int ioPortRead16(int address)
    {
        return ioPortRead32(address);
    }

    public int ioPortRead32(int port)
    {
        switch (port-sb.hw.base) {
            case MIXER_INDEX:
                return sb.mixer.index;
            case MIXER_DATA:
                return CTMIXER_Read();
            case DSP_READ_DATA:
                return DSP_ReadData();
            case DSP_READ_STATUS:
                //TODO See for high speed dma :)
                if (sb.irq.pending_8bit)  {
                    sb.irq.pending_8bit=false;
                    //Pic.PIC_DeActivateIRQ(sb.hw.irq);
                    irqDevice.setIRQ(sb.hw.irq, 0);
                }
                if (sb.dsp.out.used!=0) return 0xff;
                else return 0x7f;
            case DSP_ACK_16BIT:
                sb.irq.pending_16bit=false;
                break;
            case DSP_WRITE_STATUS:
                switch (sb.dsp.state) {
                    case DSP_S_NORMAL:
                        sb.dsp.write_busy++;
                        if ((sb.dsp.write_busy & 8)!=0) return 0xff;
                        return 0x7f;
                    case DSP_S_RESET:
                    case DSP_S_RESET_WAIT:
                        return 0xff;
                }
                return 0xff;
            case DSP_RESET:
                return 0xff;
            default:
                Log.log(Level.SEVERE,"Unhandled read from SB Port "+Integer.toString(port, 16));
                break;
        }
        return 0xff;
    }

    public void ioPortWrite8(int address, int data)
    {
        ioPortWrite32(address, data);
    }

    public void ioPortWrite16(int address, int data)
    {
        ioPortWrite32(address, data);
    }

    public void ioPortWrite32(int port, int data)
    {
        /*Bit8u*/short val8=(/*Bit8u*/short)(data & 0xff);
            switch (port-sb.hw.base) {
            case DSP_RESET:
                DSP_DoReset(val8);
                break;
            case DSP_WRITE_DATA:
                DSP_DoWrite(val8);
                break;
            case MIXER_INDEX:
                sb.mixer.index=val8;
                break;
            case MIXER_DATA:
                CTMIXER_Write(val8);
                break;
            default:
                Log.log(Level.SEVERE,"Unhandled write to SB Port "+Integer.toString(port,16));
                break;
            }
    }

    public int[] ioPortsRequested()
    {
        int base = sb.hw.base;
        int[] ports = new int[16-4-2];
        int index = 0;
        for (int i = 4; i < 16; i++)
        {
            if ((i==8) || (i==9))
                continue;
            ports[index++] = base+i;
        }
        return ports;
    }

    private static SBlaster test;

    public static void SBLASTER_ShutDown() {
        test.close();
        test = null;
        sb = null;
    }

    public SBlaster()
    {
        sb = new SB_INFO();
        /*Bitu*/int i;

//        for (i=0;i<WriteHandler.length;i++) {
//            WriteHandler[i] = new IoHandler.IO_WriteHandleObject();
//        }
//        for (i=0;i<ReadHandler.length;i++) {
//            ReadHandler[i] = new IoHandler.IO_ReadHandleObject();
//        }
        sb.hw.base = Option.sbbase.intValue(BASE, 16);
        sb.hw.irq = Option.sb_irq.intValue(IRQ);
        /*Bitu*/int dma8bit = Option.sb_dma.intValue(DMA);
        if (dma8bit>0xff) dma8bit=0xff;
        sb.hw.dma8=(/*Bit8u*/short)(dma8bit&0xff);
        /*Bitu*/int dma16bit = Option.sb_hdma.intValue(HDMA);
        if (dma16bit>0xff) dma16bit=0xff;
        sb.hw.dma16=(/*Bit8u*/short)(dma16bit&0xff);

        sb.mixer.enabled = Option.sbmixer.isSet();
        sb.mixer.stereo=false;

        IntRef t = new IntRef(sb.type);
        IntRef o = new IntRef(oplmode);
        Find_Type_And_Opl(t,o);
        sb.type = t.value;
        oplmode = o.value;

        switch (oplmode) {
        case 0:
            //WriteHandler[0].Install(0x388,adlib_gusforward,IoHandler.IO_MB);
            break;
        case 1:
            //WriteHandler[0].Install(0x388,adlib_gusforward,IoHandler.IO_MB);
            //Gameblaster.CMS_Init(section);
            break;
        case 2:
            //Gameblaster.CMS_Init(section);
            // fall-through
        case 3:
        case 4:
            //Adlib.OPL_Init(oplmode);
            break;
        }
        if (sb.type==SBT_NONE || sb.type==SBT_GB) return;

        sb.chan=MixerChan.Install(SBLASTER_CallBack,22050,"SB");
        sb.dsp.state=DSP_S_NORMAL;
        sb.dsp.out.lastval=0xaa;
        //sb.dma.chan=null;

//        for (i=4;i<=0xf;i++) {
//            if (i==8 || i==9) continue;
//            //Disable mixer ports for lower soundblaster
//            if ((sb.type==SBT_1 || sb.type==SBT_2) && (i==4 || i==5)) continue;
//            ReadHandler[i].Install(sb.hw.base+i,read_sb,IoHandler.IO_MB);
//            WriteHandler[i].Install(sb.hw.base+i,write_sb,IoHandler.IO_MB);
//        }
        for (i=0;i<256;i++) ASP_regs[i] = 0;
        ASP_regs[5] = 0x01;
        ASP_regs[9] = 0xf8;


        CTMIXER_Reset();

        // The documentation does not specify if SB gets initialized with the speaker enabled
        // or disabled. Real SBPro2 has it disabled.
        sb.speaker=false;
        // On SB16 the speaker flag does not affect actual speaker state.
        if (sb.type == SBT_16) sb.chan.Enable(true);
        else sb.chan.Enable(false);

//        String line = String.format("SET BLASTER=A%3x I%d D%d",new Object[]{new Integer(sb.hw.base),new Integer(sb.hw.irq), new Integer(sb.hw.dma8)});
//        if (sb.type==SBT_16) line+= " H"+ sb.hw.dma16;
//        line+=" T"+sb.type;
//        autoexecline.Install(line);

        /* Soundblaster midi interface */
        if (!Midi.MIDI_Available()) sb.midi = false;
        else sb.midi = true;
    }

    public boolean initialised()
    {
        return (irqDevice != null) && (timeSource != null) && (dma != null) && ioportRegistered;
    }

    public boolean updated()
    {
        return irqDevice.updated() && timeSource.updated() && dma.updated();
    }

    public void acceptComponent(HardwareComponent component)
    {
        if ((component instanceof InterruptController) && component.initialised())
            irqDevice = (InterruptController) component;
        if ((component instanceof Clock) && component.initialised())
            timeSource = (Clock) component;
        if ((component instanceof IOPortHandler) && component.initialised()) {
            ((IOPortHandler) component).registerIOPortCapable(this);
            ioportRegistered = true;
        }

        if ((component instanceof DMAController) && component.initialised())
            if (((DMAController) component).isPrimary())
            {
                dma = (DMAController) component;
                //dma.registerChannel(sb.hw.dma8 & 3, DSP_DMA_CallBack);
                //dma.registerChannel(sb.hw.dma16 & 3, this);
            }

        if (this.initialised())
        {
            DSP_Reset();
        }
    }
}
