package org.jpc.emulator.peripheral;

import org.jpc.emulator.AbstractHardwareComponent;
import org.jpc.emulator.HardwareComponent;
import org.jpc.emulator.Timer;
import org.jpc.emulator.TimerResponsive;
import org.jpc.emulator.motherboard.InterruptController;
import org.jpc.emulator.motherboard.IntervalTimer;
import org.jpc.j2se.Option;
import org.jpc.support.Clock;

import java.util.logging.*;

public class Mixer extends AbstractHardwareComponent
{
    private static final Logger Log = Logger.getLogger(Mixer.class.getName());
    private static final boolean LOG_BUFFERS = false;
    private static Clock timeSource;
    private static InterruptController irqDevice;
    private static long nextExpiry;
    private static Timer mix;
    private static Timer mix_nosound;

    static public interface MIXER_MixHandler {
        public void call(/*Bit8u*/short[] sampdate, /*Bit32u*/int len);
    }

    static public interface MIXER_Handler {
        public void call(/*Bitu*/int len);
    }

    static private final class BlahModes {
        static public final int MIXER_8MONO=0;
        static public final int MIXER_8STEREO=1;
        static public final int MIXER_16MONO=2;
        static public final int MIXER_16STEREO=3;
    }

    static private final class MixerModes {
        static public final int M_8M = 0;
        static public final int M_8S = 1;
        static public final int M_16M = 2;
        static public final int M_16S = 3;
    }

    static private final int MIXER_BUFSIZE = (16*1024);
    static private final int MIXER_BUFMASK = (MIXER_BUFSIZE-1);

    static public final int MAX_AUDIO = ((1<<(16-1))-1);
    static public final int MIN_AUDIO = -(1<<(16-1));

    public static class MixerChannel {
        public void SetVolume(float _left,float _right) {
            volmain[0].value=_left;
            volmain[1].value=_right;
            UpdateVolume();
        }
        public void SetScale( float f ){
            scale = f;
            UpdateVolume();
        }
        public void UpdateVolume() {
            volmul[0]=(/*Bits*/int)((1 << MIXER_VOLSHIFT)*scale*volmain[0].value*mixer.mastervol[0].value);
            volmul[1]=(/*Bits*/int)((1 << MIXER_VOLSHIFT)*scale*volmain[1].value*mixer.mastervol[1].value);
        }
        public void SetFreq(/*Bitu*/int _freq) {
            freq_add=(int)((_freq<<MIXER_SHIFT)/mixer.freq);
        }
        public void Mix(/*Bitu*/int _needed) {
            needed=_needed;
            while (enabled && needed>done) {
                /*Bitu*/int todo=needed-done;
                todo *= freq_add;
                todo  = (todo >> MIXER_SHIFT) + ((todo & MIXER_REMAIN)!=0?1:0);
                handler.call(todo);
            }
        }
        //Fill up until needed
        public void AddSilence() {
            if (done<needed) {
                done=needed;
                last[0]=last[1]=0;
                freq_index=MIXER_REMAIN;
            }
        }

        static public class Type {}
        public void AddSamples(/*Bitu*/int len, Ptr data, boolean stereo,boolean signeddata,boolean nativeorder) {
            /*Bits*/int[] diff=new int[2];
            /*Bitu*/int mixpos=mixer.pos+done;
            freq_index&=MIXER_REMAIN;
            /*Bitu*/int pos=0;/*Bitu*/int new_pos=0;

            boolean starting = true;
            for (;;) {
                if (!starting)
                    new_pos=freq_index >> MIXER_SHIFT;
                if (starting || pos<new_pos) {
                    if (!starting) {
                        last[0]+=diff[0];
                        if (stereo) last[1]+=diff[1];
                        pos=new_pos;
                    } else {
                        starting = false;
                    }
                    if (pos>=len) return;
                    if ( data.dataWidth() == 1) {
                        if (!signeddata) {
                            if (stereo) {
                                diff[0]=(((/*Bit8s*/byte)(data.get(pos*2+0) ^ 0x80)) << 8)-last[0];
                                diff[1]=(((/*Bit8s*/byte)(data.get(pos*2+1) ^ 0x80)) << 8)-last[1];
                            } else {
                                diff[0]=(((/*Bit8s*/byte)(data.get(pos) ^ 0x80)) << 8)-last[0];
                            }
                        } else {
                            if (stereo) {
                                diff[0]=(data.get(pos*2+0) << 8)-last[0];
                                diff[1]=(data.get(pos*2+1) << 8)-last[1];
                            } else {
                                diff[0]=(data.get(pos) << 8)-last[0];
                            }
                        }
                    //16bit and 32bit both contain 16bit data internally
                    } else  {
                        if (signeddata) {
                            if (stereo) {
                                if (nativeorder) {
                                    diff[0]=(short)data.get(pos*2+0)-last[0];
                                    diff[1]=(short)data.get(pos*2+1)-last[1];
                                } else {
                                    // :TODO: ?
                                    diff[0] = data.get(pos*2)-last[0];
                                    diff[1] = data.get(pos*2+1)-last[1];
                                }
                            } else {
                                if (nativeorder) {
                                    diff[0]=(short)data.get(pos)-last[0];
                                } else {
                                    // :TODO: ?
                                    diff[0]=data.get(pos)-last[0];
                                }
                            }
                        } else {
                            if (stereo) {
                                if (nativeorder) {
                                    diff[0]=data.get(pos*2+0)-32768-last[0];
                                    diff[1]=data.get(pos*2+1)-32768-last[1];
                                } else {
                                    // :TODO: ?
                                    diff[0] = data.get(pos*2)-32768-last[0];
                                    diff[1] = data.get(pos*2+1)-32768-last[1];
                                }
                            } else {
                                if (nativeorder) {
                                    diff[0]=data.get(pos)-32768-last[0];
                                } else {
                                    // :TODO: ?
                                    diff[0]=data.get(pos)-32768-last[0];
                                }
                            }
                        }
                    }
                }
                /*Bits*/int diff_mul=freq_index & MIXER_REMAIN;
                freq_index+=freq_add;
                mixpos&=MIXER_BUFMASK;
                /*Bits*/int sample=last[0]+((diff[0]*diff_mul) >> MIXER_SHIFT);
                mixer.work[mixpos][0]+=sample*volmul[0];
                if (stereo) sample=last[1]+((diff[1]*diff_mul) >> MIXER_SHIFT);
                mixer.work[mixpos][1]+=sample*volmul[1];
                mixpos++;done++;
            }
        }

        static private interface getSample {
            public int call(int pos);

        }

        public void AddSamples(/*Bitu*/int len, short[] data, boolean stereo,boolean signeddata) {
            /*Bits*/int[] diff=new int[2];
            /*Bitu*/int mixpos=mixer.pos+done;
            freq_index&=MIXER_REMAIN;
            /*Bitu*/int pos=0;/*Bitu*/int new_pos=0;

            boolean starting = true;
            for (;;) {
                if (!starting)
                    new_pos=freq_index >> MIXER_SHIFT;
                if (starting || pos<new_pos) {
                    if (!starting) {
                        last[0]+=diff[0];
                        if (stereo) last[1]+=diff[1];
                        pos=new_pos;
                    } else {
                        starting = false;
                    }
                    if (pos>=len) return;
                    if (signeddata) {
                        if (stereo) {
                            diff[0]=data[pos*2+0]-last[0];
                            diff[1]=data[pos*2+1]-last[1];
                        } else {
                            diff[0]=data[pos]-last[0];
                        }
                    } else {
                        if (stereo) {
                            diff[0]=data[pos*2+0]-32768-last[0];
                            diff[1]=data[pos*2+1]-32768-last[1];
                        } else {
                            diff[0]=data[pos]-32768-last[0];
                        }
                    }
                }
                /*Bits*/int diff_mul=freq_index & MIXER_REMAIN;
                freq_index+=freq_add;
                mixpos&=MIXER_BUFMASK;
                /*Bits*/int sample=last[0]+((diff[0]*diff_mul) >> MIXER_SHIFT);
                mixer.work[mixpos][0]+=sample*volmul[0];
                if (stereo) sample=last[1]+((diff[1]*diff_mul) >> MIXER_SHIFT);
                mixer.work[mixpos][1]+=sample*volmul[1];
                mixpos++;done++;
            }
        }

        public void AddSamples(/*Bitu*/int len, int[] data, boolean stereo,boolean signeddata) {
            /*Bits*/int[] diff=new int[2];
            /*Bitu*/int mixpos=mixer.pos+done;
            freq_index&=MIXER_REMAIN;
            /*Bitu*/int pos=0;/*Bitu*/int new_pos=0;

            boolean starting = true;
            for (;;) {
                if (!starting)
                    new_pos=freq_index >> MIXER_SHIFT;
                if (starting || pos<new_pos) {
                    if (!starting) {
                        last[0]+=diff[0];
                        if (stereo) last[1]+=diff[1];
                        pos=new_pos;
                    } else {
                        starting = false;
                    }
                    if (pos>=len) return;
                    if (signeddata) {
                        if (stereo) {
                            diff[0]=data[pos*2+0]-last[0];
                            diff[1]=data[pos*2+1]-last[1];
                        } else {
                            diff[0]=data[pos]-last[0];
                        }
                    } else {
                        if (stereo) {
                            diff[0]=data[pos*2+0]-32768-last[0];
                            diff[1]=data[pos*2+1]-32768-last[1];
                        } else {
                            diff[0]=data[pos]-32768-last[0];
                        }
                    }
                }
                /*Bits*/int diff_mul=freq_index & MIXER_REMAIN;
                freq_index+=freq_add;
                mixpos&=MIXER_BUFMASK;
                /*Bits*/int sample=last[0]+((diff[0]*diff_mul) >> MIXER_SHIFT);
                mixer.work[mixpos][0]+=sample*volmul[0];
                if (stereo) sample=last[1]+((diff[1]*diff_mul) >> MIXER_SHIFT);
                mixer.work[mixpos][1]+=sample*volmul[1];
                mixpos++;done++;
            }
        }

        public void AddSamples(/*Bitu*/int len, byte[] data, boolean stereo,boolean signeddata) {
            /*Bits*/int[] diff=new int[2];
            /*Bitu*/int mixpos=mixer.pos+done;
            freq_index&=MIXER_REMAIN;
            /*Bitu*/int pos=0;/*Bitu*/int new_pos=0;

            boolean starting = true;
            for (;;) {
                if (!starting)
                    new_pos=freq_index >> MIXER_SHIFT;
                if (starting || pos<new_pos) {
                    if (!starting) {
                        last[0]+=diff[0];
                        if (stereo) last[1]+=diff[1];
                        pos=new_pos;
                    } else {
                        starting = false;
                    }
                    if (pos>=len) return;
                     if (!signeddata) {
                        if (stereo) {
                            diff[0]=(((/*Bit8s*/byte)(data[pos*2+0] ^ 0x80)) << 8)-last[0];
                            diff[1]=(((/*Bit8s*/byte)(data[pos*2+1] ^ 0x80)) << 8)-last[1];
                        } else {
                            diff[0]=(((/*Bit8s*/byte)(data[pos] ^ 0x80)) << 8)-last[0];
                        }
                    } else {
                        if (stereo) {
                            diff[0]=(data[pos*2+0] << 8)-last[0];
                            diff[1]=(data[pos*2+1] << 8)-last[1];
                        } else {
                            diff[0]=(data[pos] << 8)-last[0];
                        }
                    }
                }
                /*Bits*/int diff_mul=freq_index & MIXER_REMAIN;
                freq_index+=freq_add;
                mixpos&=MIXER_BUFMASK;
                /*Bits*/int sample=last[0]+((diff[0]*diff_mul) >> MIXER_SHIFT);
                if (mixpos < 10)
                {
                    System.out.printf("last[0]=%d diff[0]=%d diff_mull=%d \n",last[0], diff[0], diff_mul);
                System.out.printf("mixpos=%d 0=%d sample=%d volmul=%d\n", mixpos, mixer.work[mixpos][0], sample, volmul[0]);
                }
                mixer.work[mixpos][0]+=sample*volmul[0];
                if (stereo) sample=last[1]+((diff[1]*diff_mul) >> MIXER_SHIFT);
                mixer.work[mixpos][1]+=sample*volmul[1];
                mixpos++;done++;
            }
        }

        public void AddSamples_m8(/*Bitu*/int len, /*Bit8u*/byte[] data) {
            AddSamples(len,data,false,false);
        }
        public void AddSamples_s8(/*Bitu*/int len, /*Bit8u*/byte[] data) {
            AddSamples(len,data,true,false);
        }
        public void AddSamples_m8s(/*Bitu*/int len, /*/*Bit8s*/byte[] data) {
            AddSamples(len,data,false,true);
        }
        public void AddSamples_s8s(/*Bitu*/int len, /*/*Bit8s*/byte[] data) {
            AddSamples(len,data,true,true);
        }
        public void AddSamples_m16(/*Bitu*/int len, /*Bit16s*/short[] data) {
            AddSamples(len,data,false,true);
        }
        public void AddSamples_s16(/*Bitu*/int len, /*Bit16s*/short[] data) {
            AddSamples(len,data,true,true);
        }
        public void AddSamples_m16u(/*Bitu*/int len, /*Bit16u*/short[] data) {
            AddSamples(len,data,false,false);
        }
        public void AddSamples_s16u(/*Bitu*/int len, /*Bit16u*/short[] data) {
            AddSamples(len,data,true,false);
        }
        public void AddSamples_m32(/*Bitu*/int len, /*Bit32s*/int[] data) {
            AddSamples(len,data,false,true);
        }
        public void AddSamples_s32(/*Bitu*/int len, /*Bit32s*/int[] data) {
            AddSamples(len,data,true,true);
        }
//        public void AddSamples_m16_nonnative(/*Bitu*/int len, /*Bit16s*/ShortPtr data) {
//            AddSamples(len,data,false,true,false);
//        }
//        public void AddSamples_s16_nonnative(/*Bitu*/int len, /*Bit16s*/ShortPtr data) {
//            AddSamples(len,data,true,true,false);
//        }
//        public void AddSamples_m16u_nonnative(/*Bitu*/int len, /*Bit16u*/ShortPtr data) {
//            AddSamples(len,data,false,false,false);
//        }
//        public void AddSamples_s16u_nonnative(/*Bitu*/int len, /*Bit16u*/ShortPtr data) {
//            AddSamples(len,data,true,false,false);
//        }
//        public void AddSamples_m32_nonnative(/*Bitu*/int len, /*Bit32s*/IntPtr data) {
//            AddSamples(len,data,false,true,false);
//        }
//        public void AddSamples_s32_nonnative(/*Bitu*/int len, /*Bit32s*/IntPtr data) {
//            AddSamples(len,data,true,true,false);
//        }

        //Strech block up into needed data
        public void AddStretched(/*Bitu*/int len, /*Bit16s*/short[] data) {
            if (done>=needed) {
                Log.log(Level.SEVERE, "Can't add, buffer full");
                return;
            }
            /*Bitu*/int outlen=needed-done;/*Bits*/int diff;
            freq_index=0;
            /*Bitu*/int temp_add=(len << MIXER_SHIFT)/outlen;
            /*Bitu*/int mixpos=mixer.pos+done;done=needed;
            /*Bitu*/int pos=0;
            diff=data[0]-last[0];
            while ((outlen--)!=0) {
                /*Bitu*/int new_pos=freq_index >> MIXER_SHIFT;
                if (pos<new_pos) {
                    pos=new_pos;
                    last[0]+=diff;
                    diff=data[pos]-last[0];
                }
                /*Bits*/int diff_mul=freq_index & MIXER_REMAIN;
                freq_index+=temp_add;
                mixpos&=MIXER_BUFMASK;
                /*Bits*/int sample=last[0]+((diff*diff_mul) >> MIXER_SHIFT);
                mixer.work[mixpos][0]+=sample*volmul[0];
                mixer.work[mixpos][1]+=sample*volmul[1];
                mixpos++;
            }
        }
        public void FillUp() {
            synchronized (audioMutex) {
                if (!enabled || done<mixer.done) {
                    return;
                }
                // scale the sound so it is the right speed
                float index = 1.0f;//set this to not scale for now. Pic.PIC_TickIndex();
                Mix((/*Bitu*/int)(index*mixer.needed));
            }
        }
        public void Enable(boolean _yesno) {
            if (_yesno==enabled) return;
            enabled=_yesno;
            if (enabled) {
                freq_index=MIXER_REMAIN;
                synchronized (audioMutex) {
                    if (done<mixer.done) done=mixer.done;
                }
            }
        }
        public MIXER_Handler handler;
        public FloatRef[] volmain = new FloatRef[2];
        public float scale;
        public /*Bit32s*/int[] volmul = new int[2];
        public /*Bitu*/int freq_add,freq_index;
        public /*Bitu*/int done,needed;
        public /*Bits*/int[] last = new int[2];
        public String name;
        public boolean enabled;
        public MixerChannel next;

        public MixerChannel() {
            for (int i=0;i<volmain.length;i++) {
                volmain[i] = new FloatRef();
            }
        }
    }

    /* Object to maintain a mixerchannel; As all objects it registers itself with create
    * and removes itself when destroyed. */
    public static class MixerObject{
        private boolean installed = false;
        private String m_name;
        public MixerChannel Install(MIXER_Handler handler,/*Bitu*/int freq,String name) {
            if(!installed) {
                if(name.length() > 31) throw new IllegalStateException("Too long mixer channel name");
                m_name=name;
                installed = true;
                return MIXER_AddChannel(handler,freq,name);
            } else {
                throw new IllegalStateException("already added mixer channel.");
                //return null; //Compiler happy
            }
        }
        public void destroy() {
            if(!installed) return;
            MIXER_DelChannel(MIXER_FindChannel(m_name));
        }
    }


    /* PC Speakers functions, tightly related to the timer functions */
    public static void PCSPEAKER_SetCounter(/*Bitu*/int cntr,/*Bitu*/int mode){}
    public static void PCSPEAKER_SetType(/*Bitu*/int mode){}

    private static final int MIXER_SSIZE = 4;
    private static final int MIXER_SHIFT = 14;
    private static final int MIXER_REMAIN = ((1<<MIXER_SHIFT)-1);
    private static final int MIXER_VOLSHIFT = 13;

    static /*Bit16s*/short MIXER_CLIP(/*Bits*/int SAMP) {
        if (SAMP < MAX_AUDIO) {
            if (SAMP > MIN_AUDIO)
                return (short)SAMP;
            else return MIN_AUDIO;
        } else return MAX_AUDIO;
    }

    static class _Mixer {
        public _Mixer() {
            for (int i=0;i<mastervol.length;i++) {
                mastervol[i] = new FloatRef();
            }
        }
        /*Bit32s*/int[][] work=new int[MIXER_BUFSIZE][2];
        /*Bitu*/int pos,done;
        /*Bitu*/int needed, min_needed, max_needed;
        /*Bit32u*/long tick_add,tick_remain;
        FloatRef[] mastervol=new FloatRef[2];
        MixerChannel channels;
        boolean nosound;
        /*Bit32u*/int freq;
        /*Bit32u*/int blocksize;
    }
    static private _Mixer mixer;

    static public /*Bit8u*/byte[] MixTemp8=new byte[MIXER_BUFSIZE];
    static public short[] MixTemp16=new short[MIXER_BUFSIZE>>1];
    static public int[] MixTemp32=new int[MIXER_BUFSIZE>>2];

    public static MixerChannel MIXER_AddChannel(MIXER_Handler handler,/*Bitu*/int freq,String name) {
        MixerChannel chan=new MixerChannel();
        chan.scale = 1.0f;
        chan.handler=handler;
        chan.name=name;
        chan.SetFreq(freq);
        chan.next=mixer.channels;
        chan.SetVolume(1,1);
        chan.enabled=false;
        mixer.channels=chan;
        return chan;
    }

    public static MixerChannel MIXER_FindChannel(String name) {
        MixerChannel chan=mixer.channels;
        while (chan!=null) {
            if (chan.name.equalsIgnoreCase(name)) break;
            chan=chan.next;
        }
        return chan;
    }

    /* Find the device you want to delete with findchannel "delchan gets deleted" */
    public static void MIXER_DelChannel(MixerChannel delchan) {
        MixerChannel chan=mixer.channels;
        MixerChannel where=mixer.channels;
        while (chan!=null) {
            if (chan==delchan) {
                where.next=chan.next;
                return;
            }
            where=chan;
            chan=chan.next;
        }
    }

    static private boolean Mixer_irq_important() {
        /* In some states correct timing of the irqs is more important then
         * non stuttering audo */
        return false;
    }

    /* Mix a certain amount of new samples */
    static private void MIXER_MixData(/*Bitu*/int needed) {
        MixerChannel chan=mixer.channels;
        while (chan!=null) {
            chan.Mix(needed);
            chan=chan.next;
        }
        //Reset the the tick_add for constant speed
        if( Mixer_irq_important() )
            mixer.tick_add = ((mixer.freq) << MIXER_SHIFT)/1000;
        mixer.done = needed;
    }

    static private TimerResponsive MIXER_Mix = new TimerResponsive() {
        public void callback() {
            synchronized (audioMutex) {
                MIXER_MixData(mixer.needed);
                mixer.tick_remain+=mixer.tick_add;
                mixer.needed+=(mixer.tick_remain>>MIXER_SHIFT);
                mixer.tick_remain&=MIXER_REMAIN;
                nextExpiry += 1000000;
                mix.setExpiry(nextExpiry);
            }
        }
        public int getType()
        {
            return -1;
        }
    };

    static private TimerResponsive MIXER_Mix_NoSound = new TimerResponsive() {
        public void callback() {
            MIXER_MixData(mixer.needed);
            /* Clear piece we've just generated */
            for (/*Bitu*/int i=0;i<mixer.needed;i++) {
                mixer.work[mixer.pos][0]=0;
                mixer.work[mixer.pos][1]=0;
                mixer.pos=(mixer.pos+1)&MIXER_BUFMASK;
            }
            /* Reduce count in channels */
            for (MixerChannel chan=mixer.channels;chan!=null;chan=chan.next) {
                if (chan.done>mixer.needed) chan.done-=mixer.needed;
                else chan.done=0;
            }
            /* Set values for next tick */
            mixer.tick_remain+=mixer.tick_add;
            mixer.needed=(int)(mixer.tick_remain>>MIXER_SHIFT);
            mixer.tick_remain&=MIXER_REMAIN;
            mixer.done=0;
            nextExpiry += 1000000;
            mix_nosound.setExpiry(nextExpiry);
        }
        public int getType()
        {
            return -1;
        }
    };

    static boolean MIXER_CallBack(byte[] stream, int len) {
        /*Bitu*/int need=(/*Bitu*/int)len/MIXER_SSIZE;
        /*Bit16s*/ShortPtr output=new ShortPtr(stream,0);
        /*Bitu*/int reduce;
        /*Bitu*/int pos, index, index_add;
        /*Bits*/int sample;
        /* Enough room in the buffer ? */
        if (mixer.done < need) {
            if (LOG_BUFFERS)
                Log.log(Level.INFO, String.format("Full underrun need %d, have %d, min %d", need, mixer.done, mixer.min_needed));
            if((need - mixer.done) > (need >>7) ) //Max 1 percent stretch.
                return false;
            reduce = mixer.done;
            index_add = (reduce << MIXER_SHIFT) / need;
            mixer.tick_add = ((mixer.freq+mixer.min_needed) << MIXER_SHIFT)/1000;
        } else if (mixer.done < mixer.max_needed) {
            /*Bitu*/int left = mixer.done - need;
            if (left < mixer.min_needed) {
                if( !Mixer_irq_important() ) {
                    /*Bitu*/int needed = mixer.needed - need;
                    /*Bitu*/int diff = (mixer.min_needed>needed?mixer.min_needed:needed) - left;
                    mixer.tick_add = ((mixer.freq+(diff*3)) << MIXER_SHIFT)/1000;
                    left = 0; //No stretching as we compensate with the tick_add value
                } else {
                    left = (mixer.min_needed - left);
                    left = 1 + (2*left) / mixer.min_needed; //left=1,2,3
                }
                if (LOG_BUFFERS)
                    Log.log(Level.INFO, String.format("needed underrun need %d, have %d, min %d, left %d", need, mixer.done, mixer.min_needed, left));
                reduce = need - left;
                index_add = (reduce << MIXER_SHIFT) / need;
            } else {
                reduce = need;
                index_add = (1 << MIXER_SHIFT);
                if (LOG_BUFFERS)
                    Log.log(Level.INFO, String.format("regular run need %d, have %d, min %d, left %d", need, mixer.done, mixer.min_needed, left));

                /* Mixer tick value being updated:
                 * 3 cases:
                 * 1) A lot too high. >division by 5. but maxed by 2* min to prevent too fast drops.
                 * 2) A little too high > division by 8
                 * 3) A little to nothing above the min_needed buffer > go to default value
                 */
                /*Bitu*/int diff = left - mixer.min_needed;
                if(diff > (mixer.min_needed<<1)) diff = mixer.min_needed<<1;
                if(diff > (mixer.min_needed>>1))
                    mixer.tick_add = ((mixer.freq-(diff/5)) << MIXER_SHIFT)/1000;
                else if (diff > (mixer.min_needed>>4))
                    mixer.tick_add = ((mixer.freq-(diff>>3)) << MIXER_SHIFT)/1000;
                else
                    mixer.tick_add = (mixer.freq<< MIXER_SHIFT)/1000;
            }
        } else {
            /* There is way too much data in the buffer */
            if (LOG_BUFFERS)
                Log.log(Level.INFO, String.format("overflow run need %d, have %d, min %d", need, mixer.done, mixer.min_needed));
            if (mixer.done > MIXER_BUFSIZE)
                index_add = MIXER_BUFSIZE - 2*mixer.min_needed;
            else
                index_add = mixer.done - 2*mixer.min_needed;
            index_add = (index_add << MIXER_SHIFT) / need;
            reduce = mixer.done - 2* mixer.min_needed;
            mixer.tick_add = ((mixer.freq-(mixer.min_needed/5)) << MIXER_SHIFT)/1000;
        }
        /* Reduce done count in all channels */
        for (MixerChannel chan=mixer.channels;chan!=null;chan=chan.next) {
            if (chan.done>reduce) chan.done-=reduce;
            else chan.done=0;
        }

        // Reset mixer.tick_add when irqs are important
        if( Mixer_irq_important() )
            mixer.tick_add=(mixer.freq<< MIXER_SHIFT)/1000;

        mixer.done -= reduce;
        mixer.needed -= reduce;
        pos = mixer.pos;
        mixer.pos = (mixer.pos + reduce) & MIXER_BUFMASK;
        index = 0;
        if(need != reduce) {
            while ((need--)!=0) {
                /*Bitu*/int i = (pos + (index >> MIXER_SHIFT )) & MIXER_BUFMASK;
                index += index_add;
                sample=mixer.work[i][0]>>MIXER_VOLSHIFT;
                output.setInc(MIXER_CLIP(sample));
                sample=mixer.work[i][1]>>MIXER_VOLSHIFT;
                output.setInc(MIXER_CLIP(sample));
            }
            /* Clean the used buffer */
            while ((reduce--)!=0) {
                pos &= MIXER_BUFMASK;
                mixer.work[pos][0]=0;
                mixer.work[pos][1]=0;
                pos++;
            }
        } else {
            while ((reduce--)!=0) {
                pos &= MIXER_BUFMASK;
                sample=mixer.work[pos][0]>>MIXER_VOLSHIFT;
                output.setInc(MIXER_CLIP(sample));
                sample=mixer.work[pos][1]>>MIXER_VOLSHIFT;
                output.setInc(MIXER_CLIP(sample));
                mixer.work[pos][0]=0;
                mixer.work[pos][1]=0;
                pos++;
            }
        }
        return true;
    }

    public void MakeVolume(String scan, FloatRef vol0,FloatRef vol1) {
        /*Bitu*/int w=0;
        boolean db=(scan.toUpperCase().charAt(0)=='D');
        if (db) scan=scan.substring(1);
        while (scan.length()>0) {
            if (scan.charAt(0)==':') {
                scan=scan.substring(0);w=1;
            }
            String before=scan;
            float val=0.0f;
            try {
                int pos = scan.indexOf(' ');
                if (pos>0)
                    val = Float.parseFloat(scan.substring(0, pos));
                else
                    val = Float.parseFloat(scan);
            } catch (Exception e) {
                scan=scan.substring(1);
            }

            if (!db) val/=100;
            else val=(float)Math.pow(10.0f,(float)val/20.0f);
            if (val<0) val=1.0f;
            if (w==0) {
                vol0.value=val;
            } else {
                vol1.value=val;
            }
        }
        if (w==0) vol1.value=vol0.value;
    }

    private void ShowVolume(String name,FloatRef vol0,FloatRef vol1) {
        System.out.printf("%-8s %3.0f:%-3.0f  %+3.2f:%-+3.2f \n",new Object[] {name,
            new Float(vol0.value*100),new Float(vol1.value*100),
            new Float(20*Math.log(vol0.value)/Math.log(10.0f)),new Float(20*Math.log(vol1.value)/Math.log(10.0f))}
        );
    }

    public static void MIXER_Stop() {
        AudioLayer.stop();
        mixer = null;
    }

    final static public Object audioMutex = new Object();

    public static void MIXER_Init() {
        mixer = new _Mixer();

        /* Read out config section */
        mixer.freq = Option.mixer_rate.intValue(SBlaster.OPL_RATE);
        mixer.nosound = Option.mixer_nosound.value();
        mixer.blocksize = Option.mixer_blocksize.intValue(512);

        /* Initialize the internal stuff */
        mixer.channels=null;
        mixer.pos=0;
        mixer.done=0;
        mixer.mastervol[0].value=1.0f;
        mixer.mastervol[1].value=1.0f;

        mixer.tick_remain=0;
        mixer.min_needed = Option.mixer_prebuffer.intValue(20);
        if (mixer.min_needed>100) mixer.min_needed=100;
        mixer.min_needed=(int)(mixer.freq*mixer.min_needed)/1000;
        mixer.max_needed=(int)mixer.blocksize * 2 + 2*mixer.min_needed;
        mixer.needed=mixer.min_needed+1;

        if (mixer.nosound) {
            Log.log(Level.INFO, "MIXER:No Sound Mode Selected.");
            mixer.tick_add=((mixer.freq) << MIXER_SHIFT)/1000;
            //Timer.TIMER_AddTickHandler(MIXER_Mix_NoSound);
            mix_nosound = timeSource.newTimer(MIXER_Mix_NoSound);
            nextExpiry = timeSource.getEmulatedNanos();
            mix_nosound.setExpiry(nextExpiry);
//        }
//        else if (!AudioLayer.open(Option.mixer_javabuffer.intValue(8820), mixer.freq)) {
//            else if (SDL_OpenAudio(&spec, &obtained) <0 ) {
//                mixer.nosound = true;
//                Log.log_msg("MIXER:Can't open audio: %s , running in nosound mode.",SDL_GetError());
//                mixer.tick_add=((mixer.freq) << MIXER_SHIFT)/1000;
//                Timer.TIMER_AddTickHandler(MIXER_Mix_NoSound);
        } else {
//                if((mixer.freq != obtained.freq) || (mixer.blocksize != obtained.samples))
//                    Log.log_msg("MIXER:Got different values from SDL: freq %d, blocksize %d",obtained.freq,obtained.samples);
//                mixer.freq=obtained.freq;
//                mixer.blocksize=obtained.samples;
            mixer.tick_add=(mixer.freq << MIXER_SHIFT)/1000;
            //Timer.TIMER_AddTickHandler(MIXER_Mix);

//                SDL_PauseAudio(0);
        }
    }

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

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

    public void acceptComponent(HardwareComponent component)
    {
        if ((component instanceof InterruptController) && component.initialised())
            irqDevice = (InterruptController) component;
        if ((component instanceof Clock) && component.initialised())
        {
            timeSource = (Clock) component;
            mix = timeSource.newTimer(MIXER_Mix);
            nextExpiry = timeSource.getEmulatedNanos();
            mix.setExpiry(nextExpiry);
        }

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