/*
 * Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA 02110-1301  USA
 */
package jace.hardware.mockingboard;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import jace.hardware.CardMockingboard;

// Port of AY code from AppleWin -- not used buy kept for reference.

/***************************************************************************
 *
 * ay8910.c
 *
 *
 * Emulation of the AY-3-8910 / YM2149 sound chip.
 *
 * Based on various code snippets by Ville Hallik, Michael Cuddy,
 * Tatsuyuki Satoh, Fabrice Frances, Nicola Salmoria.
 *
 ***************************************************************************/

//
// From mame.txt (http://www.mame.net/readme.html)
//
// VI. Reuse of Source Code
// --------------------------
//    This chapter might not apply to specific portions of MAME (e.g. CPU
//    emulators) which bear different copyright notices.
//    The source code cannot be used in a commercial product without the written
//    authorization of the authors. Use in non-commercial products is allowed, and
//    indeed encouraged.  If you use portions of the MAME source code in your
//    program, however, you must make the full source code freely available as
//    well.
//    Usage of the _information_ contained in the source code is free for any use.
//    However, given the amount of time and energy it took to collect this
//    information, if you find new information we would appreciate if you made it
//    freely available as well.
//

public class AY8910_old {
    static final int MAX_OUTPUT = 0x007fff;
    static final int MAX_AY8910 = 2;
    static final int CLOCK = 1789770;
    static final int SAMPLE_RATE = 44100;
    
// See AY8910_set_clock() for definition of STEP
    static final int STEP = 0x008000;
    static int num = 0, ym_num = 0;
    int SampleRate = 0;
    
    /* register id's */
    public enum Reg {
        AFine(0, 255),
        ACoarse(1, 15),
        BFine(2, 255),
        BCoarse(3, 15),
        CFine(4, 255),
        CCoarse(5, 15),
        NoisePeriod(6, 31),
        Enable(7, 255),
        AVol(8, 31),
        BVol(9, 31),
        CVol(10, 31),
        EnvFine(11, 255),
        EnvCoarse(12, 255),
        EnvShape(13, 15),
        PortA(14, 255),
        PortB(15, 255);
        
        public final int registerNumber;
        public final int max;
        Reg(int number, int maxValue) {
            registerNumber = number;
            max=maxValue;
        }
        static Reg get(int number) {
            for (Reg r:Reg.values())
                if (r.registerNumber == number) return r;
            return null;
        }
        static public Reg[] preferredOrder = new Reg[]{
            Enable,EnvShape,EnvCoarse,EnvFine,NoisePeriod,AVol,BVol,CVol,
            AFine,ACoarse,BFine,BCoarse,CFine,CCoarse};
    }
    
    public AY8910_old() {
        chips = new ArrayList<PSG>();
        for (int i=0; i < MAX_AY8910; i++) {
            PSG chip = new PSG();
            chips.add(chip);
        }
        initAll(CLOCK, SAMPLE_RATE);
    }
    
///////////////////////////////////////////////////////////
    private final List<PSG> chips;
    
    static int[] VolTable;
    static {
        buildMixerTable();
    }
    
    private class PSG {
        int Channel;
        int register_latch;
        Map<Reg, Integer> registers;
        
        int lastEnable;
        int UpdateStep;
        int PeriodA,PeriodB,PeriodC,PeriodN,PeriodE;
        int CountA,CountB,CountC,CountN,CountE;
        int VolA,VolB,VolC,VolE;
        int EnvelopeA,EnvelopeB,EnvelopeC;
        int OutputA,OutputB,OutputC,OutputN;
        int CountEnv;
        int Hold,Alternate,Attack,Holding;
        int RNG;
        
        public PSG() {
            registers = new HashMap<Reg, Integer>();
            for (Reg r: Reg.values())
                setReg(r, 0);
        }
        
        public void reset() {
            register_latch = 0;
            RNG = 1;
            OutputA = 0;
            OutputB = 0;
            OutputC = 0;
            OutputN = 0x00ff;
            lastEnable = -1;	/* force a write */
            for (Reg r: Reg.values())
                writeReg(r, 0);
        }
        
        public void setClock(int clock) {
            /* the step clock for the tone and noise generators is the chip clock    */
            /* divided by 8; for the envelope generator of the AY-3-8910, it is half */
            /* that much (clock/16), but the envelope of the YM2149 goes twice as    */
            /* fast, therefore again clock/8.                                        */
            /* Here we calculate the number of steps which happen during one sample  */
            /* at the given sample rate. No. of events = sample rate / (clock/8).    */
            /* STEP is a multiplier used to turn the fraction into a fixed point     */
            /* number.                                                               */
            double clk = clock;
            double smprate = SampleRate;
            UpdateStep = (int) ((STEP * smprate * 8.0 + clk/2.0) / clk);
        }
        
        public void setReg(Reg r, int value) {
            registers.put(r,value);
        }
        public int getReg(Reg r) {
            return registers.get(r);
        }
        
        public void writeReg(Reg r, int value) {
            value &= r.max;
            setReg(r, value);
            int old;
            /* A note about the period of tones, noise and envelope: for speed reasons,*/
            /* we count down from the period to 0, but careful studies of the chip     */
            /* output prove that it instead counts up from 0 until the counter becomes */
            /* greater or equal to the period. This is an important difference when the*/
            /* program is rapidly changing the period to modulate the sound.           */
            /* To compensate for the difference, when the period is changed we adjust  */
            /* our internal counter.                                                   */
            /* Also, note that period = 0 is the same as period = 1. This is mentioned */
            /* in the YM2203 data sheets. However, this does NOT apply to the Envelope */
            /* period. In that case, period = 0 is half as period = 1. */
            switch(r) {
                case ACoarse:
                case AFine:
                    old = PeriodA;
                    PeriodA = (getReg(Reg.AFine) + 256 * getReg(Reg.ACoarse)) * UpdateStep;
                    if (PeriodA == 0) PeriodA = UpdateStep;
                    CountA += PeriodA - old;
                    if (CountA <= 0) CountA = 1;
                    break;
                case BCoarse:
                case BFine:
                    old = PeriodB;
                    PeriodB = (getReg(Reg.BFine) + 256 * getReg(Reg.BCoarse)) * UpdateStep;
                    if (PeriodB == 0) PeriodB = UpdateStep;
                    CountB += PeriodB - old;
                    if (CountB <= 0) CountB = 1;
                    break;
                case CCoarse:
                case CFine:
                    setReg(Reg.CCoarse, getReg(Reg.CCoarse) & 0x0f);
                    old = PeriodC;
                    PeriodA = (getReg(Reg.CFine) + 256 * getReg(Reg.CCoarse)) * UpdateStep;
                    if (PeriodC == 0) PeriodC = UpdateStep;
                    CountC += PeriodC - old;
                    if (CountC <= 0) CountC = 1;
                    break;
                case NoisePeriod:
                    old = PeriodN;
                    PeriodN = getReg(Reg.NoisePeriod) * UpdateStep;
                    if (PeriodN == 0) PeriodN = UpdateStep;
                    CountN += PeriodN - old;
                    if (CountN <= 0) CountN = 1;
                    break;
                case Enable:
                    lastEnable = value;
                    break;
                case AVol:
                    EnvelopeA = value & 0x10;
                    if (EnvelopeA > 0)
                        VolA = VolE;
                    else {
                        if (value > 0)
                            VolA = CardMockingboard.VolTable[value];
                        else
                            VolA = CardMockingboard.VolTable[0];
                    }
                    break;
                case BVol:
                    EnvelopeB = value & 0x10;
                    if (EnvelopeB > 0)
                        VolB = VolE;
                    else {
                        if (value > 0)
                            VolB = CardMockingboard.VolTable[value];
                        else
                            VolB = CardMockingboard.VolTable[0];
                    }
                    break;
                case CVol:
                    EnvelopeC = value & 0x10;
                    if (EnvelopeC > 0)
                        VolC = VolE;
                    else {
                        if (value > 0)
                            VolC = CardMockingboard.VolTable[value];
                        else
                            VolC = CardMockingboard.VolTable[0];
                    }
                    break;
                case EnvFine:
                case EnvCoarse:
                    old = PeriodE;
                    PeriodE = ((getReg(Reg.EnvFine) + 256 * getReg(Reg.EnvCoarse))) * UpdateStep;
                    if (PeriodE == 0) PeriodE = UpdateStep / 2;
                    CountE += PeriodE - old;
                    if (CountE <= 0) CountE = 1;
                    if (PeriodE <= 0) PeriodE = 1;
                    break;
                case EnvShape:
                /* envelope shapes:
                C AtAlH
                0 0 x x  \___
                0 1 x x  /|__
                1 0 0 0  \\\\
                1 0 0 1  \___
                1 0 1 0  \/\/
                           __
                1 0 1 1  \|
                1 1 0 0  ////
                          ___
                1 1 0 1  /
                1 1 1 0  /\/\
                1 1 1 1  /|__
                 
                The envelope counter on the AY-3-8910 has 16 steps. On the YM2149 it
                has twice the steps, happening twice as fast. Since the end result is
                just a smoother curve, we always use the YM2149 behaviour.
                 */
                    Attack = (value & 0x04) != 0 ? 0x1f : 0x00;
                    if ( (value & 0x08) == 0) {
                        /* if Continue = 0, map the shape to the equivalent one which has Continue = 1 */
                        Hold = 1;
                        Alternate = Attack;
                    } else {
                        Hold = value & 0x01;
                        Alternate = value & 0x02;
                    }
                    CountE = PeriodE;
                    CountEnv = 0x1f;
                    Holding = 0;
                    VolE = CardMockingboard.VolTable[CountEnv ^ Attack];
                    if (EnvelopeA != 0) VolA = VolE;
                    if (EnvelopeB != 0) VolB = VolE;
                    if (EnvelopeC != 0) VolC = VolE;
                    break;
                case PortA:
                case PortB:
                    break;
            }
        }
        
        void update(int[][] buffer, int length) {
            int[] buf1, buf2, buf3;
            int outn;
            
            buf1 = buffer[0];
            buf2 = buffer[1];
            buf3 = buffer[2];
            
            
            /* The 8910 has three outputs, each output is the mix of one of the three */
            /* tone generators and of the (single) noise generator. The two are mixed */
            /* BEFORE going into the DAC. The formula to mix each channel is: */
            /* (ToneOn | ToneDisable) & (NoiseOn | NoiseDisable). */
            /* Note that this means that if both tone and noise are disabled, the output */
            /* is 1, not 0, and can be modulated changing the volume. */
            
            
            /* If the channels are disabled, set their output to 1, and increase the */
            /* counter, if necessary, so they will not be inverted during this update. */
            /* Setting the output to 1 is necessary because a disabled channel is locked */
            /* into the ON state (see above); and it has no effect if the volume is 0. */
            /* If the volume is 0, increase the counter, but don't touch the output. */
            if ( (getReg(Reg.Enable) & 0x01) != 0) {
                if (CountA <= length*STEP) CountA += length*STEP;
                OutputA = 1;
            } else if (getReg(Reg.AVol) == 0) {
                /* note that I do count += length, NOT count = length + 1. You might think */
                /* it's the same since the volume is 0, but doing the latter could cause */
                /* interferencies when the program is rapidly modulating the volume. */
                if (CountA <= length*STEP) CountA += length*STEP;
            }
            
            if ( (getReg(Reg.Enable) & 0x02) != 0) {
                if (CountB <= length*STEP) CountB += length*STEP;
                OutputB = 1;
            } else if (getReg(Reg.BVol) == 0) {
                if (CountB <= length*STEP) CountB += length*STEP;
            }
            
            if ( (getReg(Reg.Enable) & 0x04) != 0) {
                if (CountC <= length*STEP) CountC += length*STEP;
                OutputC = 1;
            } else if (getReg(Reg.CVol) == 0) {
                if (CountC <= length*STEP) CountC += length*STEP;
            }
            
            /* for the noise channel we must not touch OutputN - it's also not necessary */
            /* since we use outn. */
            if ((getReg(Reg.Enable) & 0x38) == 0x38)	/* all off */
                if (CountN <= length*STEP) CountN += length*STEP;
            
            outn = (OutputN | getReg(Reg.Enable));
            int index = 0;
            //System.out.println("Length:"+length);
            /* buffering loop */
            while (length != 0) {
                int vola,volb,volc;
                int left;
                
                /* vola, volb and volc keep track of how long each square wave stays */
                /* in the 1 position during the sample period. */
                vola = volb = volc = 0;
                //System.out.println("STEP:"+STEP);
                
                left = STEP;
                do {
                    int nextevent;
                    
                    if (CountN < left) nextevent = CountN;
                    else nextevent = left;
                        
                    if ( (outn & 0x08) != 0) {
                        if (OutputA != 0) vola += CountA;
                        CountA -= nextevent;
                        /* PeriodA is the half period of the square wave. Here, in each */
                        /* loop I add PeriodA twice, so that at the end of the loop the */
                        /* square wave is in the same status (0 or 1) it was at the start. */
                        /* vola is also incremented by PeriodA, since the wave has been 1 */
                        /* exactly half of the time, regardless of the initial position. */
                        /* If we exit the loop in the middle, OutputA has to be inverted */
                        /* and vola incremented only if the exit status of the square */
                        /* wave is 1. */
                        while (CountA <= 0 && PeriodA > 0) {
                            CountA += PeriodA;
                            if (CountA > 0) {
                                OutputA ^= 1;
                                if (OutputA != 0) vola += PeriodA;
                                break;
                            }
                            CountA += PeriodA;
                            vola += PeriodA;
                        }
                        if (OutputA != 0) vola -= CountA;
                    } else {
                        CountA -= nextevent;
                        while (CountA <= 0 && PeriodA > 0) {
                            CountA += PeriodA;
                            if (CountA > 0) {
                                OutputA ^= 1;
                                break;
                            }
                            CountA += PeriodA;
                        }
                    }
                    
                    if ((outn & 0x10) != 0) {
                        if (OutputB != 0) volb += CountB;
                        CountB -= nextevent;
                        while (CountB <= 0 && PeriodB > 0) {
                            CountB += PeriodB;
                            if (CountB > 0) {
                                OutputB ^= 1;
                                if (OutputB != 0) volb += PeriodB;
                                break;
                            }
                            CountB += PeriodB;
                            volb += PeriodB;
                        }
                        if (OutputB != 0) volb -= CountB;
                    } else {
                        CountB -= nextevent;
                        while (CountB <= 0 && PeriodB > 0) {
                            CountB += PeriodB;
                            if (CountB > 0) {
                                OutputB ^= 1;
                                break;
                            }
                            CountB += PeriodB;
                        }
                    }
                    
                    if ( (outn & 0x20) != 0) {
                        if (OutputC != 0) volc += CountC;
                        CountC -= nextevent;
                        while (CountC <= 0 && PeriodC > 0) {
                            CountC += PeriodC;
                            if (CountC > 0) {
                                OutputC ^= 1;
                                if (OutputC != 0) volc += PeriodC;
                                break;
                            }
                            CountC += PeriodC;
                            volc += PeriodC;
                        }
                        if (OutputC != 0) volc -= CountC;
                    } else {
                        CountC -= nextevent;
                        while (CountC <= 0 && PeriodC > 0) {
                            CountC += PeriodC;
                            if (CountC > 0) {
                                OutputC ^= 1;
                                break;
                            }
                            CountC += PeriodC;
                        }
                    }
                    
                    CountN -= nextevent;
                    if (CountN <= 0 && PeriodN > 0) {
                        /* Is noise output going to change? */
                        /* (bit0^bit1)? */
                        if (((RNG + 1) & 2) != 0) {
                            OutputN ^= 0x0FF;
                            outn = (OutputN | getReg(Reg.Enable));
                        }
                        
                        /* The Random Number Generator of the 8910 is a 17-bit shift */
                        /* register. The input to the shift register is bit0 XOR bit3 */
                        /* (bit0 is the output). This was verified on AY-3-8910 and YM2149 chips. */
                        
                        /* The following is a fast way to compute bit17 = bit0^bit3. */
                        /* Instead of doing all the logic operations, we only check */
                        /* bit0, relying on the fact that after three shifts of the */
                        /* register, what now is bit3 will become bit0, and will */
                        /* invert, if necessary, bit14, which previously was bit17. */
                        if ((RNG & 1) != 0) RNG ^= 0x0024000; /* This version is called the "Galois configuration". */
                        RNG >>= 1;
                        CountN += PeriodN;
                    }
                    
                    left -= nextevent;
                } while (left > 0);
          //      System.out.println("End left loop");
                
                /* update envelope */
                if (Holding == 0) {
                    CountE -= STEP;
                    if (CountE <= 0) {
                        do {
                            CountEnv--;
                            CountE += PeriodE;
                        } while (CountE <= 0);
                        
                        /* check envelope current position */
                        if (CountEnv < 0) {
                            if (Hold != 0) {
                                if (Alternate != 0)
                                    Attack ^= 0x1f;
                                Holding = 1;
                                CountEnv = 0;
                            } else {
                                /* if CountEnv has looped an odd number of times (usually 1), */
                                /* invert the output. */
                                if ( (Alternate != 0) && ((CountEnv & 0x20) != 0))
                                    Attack ^= 0x1f;
                                CountEnv &= 0x1f;
                            }
                        }
                        
                        VolE = VolTable[CountEnv ^ Attack];
                        /* reload volume */
                        if (EnvelopeA != 0) VolA = VolE;
                        if (EnvelopeB != 0) VolB = VolE;
                        if (EnvelopeC != 0) VolC = VolE;
                    }
                }
                
                // Output PCM wave [-32768...32767] instead of MAME's voltage level [0...32767]
                // - This allows for better s/w mixing
buf1[index] = (vola * VolA) / STEP;
buf2[index] = (volb * VolB) / STEP;
buf3[index] = (volc * VolC) / STEP;
/*                
                if(VolA != 0) {
                    if (vola != 0) buf1[index] = (vola * VolA) / STEP;
                    else buf1[index] = -VolA;
                } else {
                    buf1[index] = 0;
                }
                
                //
                
                if(VolB != 0) {
                    if (volb != 0) buf2[index] = (volb * VolB) / STEP;
                    else buf2[index] = -VolB;
                } else
                    buf2[index] = 0;
                
                //
                
                if(VolC != 0) {
                    if (volc != 0) buf3[index] = (volc * VolC) / STEP;
                    else buf3[index] = -VolC;
                } else
                    buf3[index] = 0;
   */             
                index++;
                length--;
            }
        }
    }

    public void writeReg(int chipNumber, int register, int value) {
        Reg r = Reg.get(register);
        writeReg(chipNumber, r, value);
    }
    
    public void writeReg(int chipNumber, Reg register, int value) {
        chips.get(chipNumber).writeReg(register, value);
    }
    
    // /length/ is the number of samples we require
    // NB. This should be called at twice the 6522 IRQ rate or (eg) 60Hz if no IRQ.
    public void update(int chipNumber,int[][] buffer,int length) {
        chips.get(chipNumber).update(buffer, length);
    }
    
    int[][] buffers;
    int bufferLength = -1;
    public int[][] getBuffers(int length) {
        if (buffers == null || bufferLength != length) {
            buffers = new int[3][length];
            bufferLength = length;
        }
        return buffers;
    }
    
    public void playSound(int size, int[] left, int[] right) {
        int[][] buffers = getBuffers(left.length);
        update(0, buffers, size);
        mixDown(left, buffers, size);
        update(1, buffers, size);
        mixDown(right, buffers, size);        
    }
    
    public void mixDown(int[] out, int[][] in, int size) {
        for (int i=0; i < size; i++) {
            int sample = (in[0][i] + in[1][i] + in[2][i]) / 3;
            out[i] = sample;
        }
    }
    
    public void setClock(int chipNumber,int clock) {
        chips.get(chipNumber).setClock(clock);
    }
    
    public void reset(int chipNumber) {
        chips.get(chipNumber).reset();
    }
    
    public void initAll(int nClock, int nSampleRate) {
        SampleRate = nSampleRate;
        for (PSG p:chips) {
            p.setClock(nClock);
            p.reset();
        }
    }
    
    public void initClock(int nClock) {
        for (PSG p:chips) p.setClock(nClock);
    }
    
    static void buildMixerTable() {
        VolTable = new int[32];
        int SampleRate;
        
        /* calculate the volume->voltage conversion table */
        /* The AY-3-8910 has 16 levels, in a logarithmic scale (3dB per step) */
        /* The YM2149 still has 16 levels for the tone generators, but 32 for */
        /* the envelope generator (1.5dB per step). */
        double out = MAX_OUTPUT;
        for (int i = 31;i > 0;i--) {
            VolTable[i] = (int) (out + 0.5);	/* round to nearest */	// [TC: unsigned int cast]
            out /= 1.188502227;	/* = 10 ^ (1.5/20) = 1.5dB */
        }
        VolTable[0] = 0;
    }
}