package client.audio;

public class OPLChip {
	public static enum EnvState {
		ATTACK,
		DECAY,
		SUSTAIN,
		RELEASE
	};

	public static interface ModGen {
		short getModulation();
	}

	private static interface WaveFunc {
		int calcPhase(int phase, int envelope);
	}
	
	public class OPLSlot implements ModGen {
		private final OPLChannel channel;
		
		public short out;
		public short fbmodv;
		public final ModGen fbmod = () -> OPLSlot.this.fbmodv;
		public ModGen mod;
		public int fb;
		public short prout;
		public int eg_rout;
		public int eg_out;
		public EnvState eg_gen;
		public int eg_ksl;
		public ModGen trem;
		public boolean reg_vib;
		public boolean reg_type;
		public boolean reg_ksr;
		public int reg_mult;
		public int reg_ksl;
		public int reg_tl;
		public int reg_ar;
		public int reg_dr;
		public int reg_sl;
		public int reg_rr;
		public int reg_wf;
		public boolean key;
		public boolean detrigger;
		public boolean retrigger;
		public boolean pg_reset;
		public int pg_phase;
		public int pg_phase_out;
		public int f_num;
		public int block;
		public int ksv;
		public int frequency = -1;
		public int offset;
		public int fixed;
		public int note;
		public int detune;
		public int pitch;
		public int read_freq;
		
		public OPLSlot(OPLChannel channel) {
			this.channel = channel;
			this.mod = OPLChip.this.zeromod;
			this.eg_rout = 0x1ff;
			this.eg_out = 0x1ff;
			this.eg_gen = EnvState.RELEASE;
			this.trem = OPLChip.this.zeromod;
		}
		
		public short getModulation() {
			return this.out;
		}

		private void process()
		{
			// feedback
			if (this.fb != 0x00)
			{
			    this.fbmodv = (short)((this.prout + this.out) >> (0x09 - this.fb));
			}
			else
			{
			    this.fbmodv = 0;
			}
			this.prout = this.out;
			// envelope
			boolean nonzero;
			int rate;
			int rate_hi;
			int rate_lo;
			int reg_rate = 0;
			int ks;
			int eg_shift, shift;
			int eg_rout;
			int eg_inc;
			boolean eg_off;
			boolean reset = false;
			if(this.retrigger) {
			    this.eg_rout = 0x1ff;
			}
			this.eg_out = this.eg_rout + (this.reg_tl << 2) + (this.eg_ksl >> KSL_SHIFT[this.reg_ksl]) + this.trem.getModulation();
			if (this.key && this.eg_gen == EnvState.RELEASE)
			{
			    reset = true;
			    reg_rate = this.reg_ar;
			}
			else
			{
			    switch (this.eg_gen)
			    {
			    case ATTACK:
			        reg_rate = this.reg_ar;
			        break;
			    case DECAY:
			        reg_rate = this.reg_dr;
			        break;
			    case SUSTAIN:
			        if (!this.reg_type)
			        {
			            reg_rate = this.reg_rr;
			        }
			        break;
			    case RELEASE:
			        reg_rate = this.reg_rr;
			        break;
			    }
			}
			this.pg_reset = reset;
			ks = this.ksv >> (this.reg_ksr ? 0 : 2);
			nonzero = (reg_rate != 0);
			rate = ks + (reg_rate << 2);
			rate_hi = rate >> 2;
			rate_lo = rate & 0x03;
			if ((rate_hi & 0x10) != 0)
			{
			    rate_hi = 0x0f;
			}
			eg_shift = rate_hi + OPLChip.this.eg_add;
			shift = 0;
			if (nonzero)
			{
			    if (rate_hi < 12)
			    {
			        if (OPLChip.this.eg_state)
			        {
			            switch (eg_shift)
			            {
			            case 12:
			                shift = 1;
			                break;
			            case 13:
			                shift = (rate_lo >> 1) & 0x01;
			                break;
			            case 14:
			                shift = rate_lo & 0x01;
			                break;
			            default:
			                break;
			            }
			        }
			    }
			    else
			    {
			        shift = (rate_hi & 0x03) + EG_INC[rate_lo][OPLChip.this.timer & 0x03];
			        if ((shift & 0x04) != 0)
			        {
			            shift = 0x03;
			        }
			        if (shift == 0)
			        {
			            shift = OPLChip.this.eg_state ? 1 : 0;
			        }
			    }
			}
			eg_rout = this.eg_rout;
			eg_inc = 0;
			eg_off = false;
			/* Instant attack */
			if (reset && rate_hi == 0x0f)
			{
			    eg_rout = 0x00;
			}
			/* Envelope off */
			if ((this.eg_rout & 0x1f8) == 0x1f8)
			{
			    eg_off = true;
			}
			if (this.eg_gen != EnvState.ATTACK && !reset && eg_off)
			{
			    eg_rout = 0x1ff;
			}
			switch (this.eg_gen)
			{
			case ATTACK:
			    if (this.eg_rout == 0)
			    {
			        this.eg_gen = EnvState.DECAY;
			    }
			    else if (this.key && shift > 0 && rate_hi != 0x0f)
			    {
			        eg_inc = ((~this.eg_rout) & 0xffff) >> (4 - shift);
			    }
			    break;
			case DECAY:
			    if ((this.eg_rout >> 4) == this.reg_sl)
			    {
			        this.eg_gen = EnvState.SUSTAIN;
			    }
			    else if (!eg_off && !reset && shift > 0)
			    {
			        eg_inc = 1 << (shift - 1);
			    }
			    break;
			case SUSTAIN:
			case RELEASE:
			    if (!eg_off && !reset && shift > 0)
			    {
			        eg_inc = 1 << (shift - 1);
			    }
			    break;
			}
			this.eg_rout = (eg_rout + eg_inc) & 0x1ff;
			/* Key off */
			if (reset)
			{
			    this.eg_gen = EnvState.ATTACK;
			}
			if (!(this.key))
			{
			    this.eg_gen = EnvState.RELEASE;
			}
			this.detrigger = false;
			this.retrigger = false;
			// phase
			int f_num;
			int basefreq;
			int phase;
			f_num = this.f_num;
			if (this.reg_vib)
			{
			    int range;
			    int vibpos;
			    range = (f_num >> 7) & 7;
			    vibpos = OPLChip.this.vibpos;
			    if ((vibpos & 3) == 0)
			    {
			        range = 0;
			    }
			    else if ((vibpos & 1) != 0)
			    {
			        range >>= 1;
			    }
			    range >>= OPLChip.this.vibshift;
			    if ((vibpos & 4) != 0)
			    {
			        range = -range;
			    }
			    f_num += range;
			}
			basefreq = (f_num << this.block) >> 1;
			phase = this.pg_phase >> 9;
			if (this.pg_reset)
			    this.pg_phase = 0;
			this.pg_phase += (basefreq * MULT[this.reg_mult]) >> 1;
			this.pg_phase_out = phase;
			// output
			this.out = (short)WAVE_FUNCS[this.reg_wf].calcPhase(this.pg_phase_out + this.mod.getModulation(), this.eg_out);
		}
		
		private void updateKSL()
		{
			int ksl = (KSL[this.f_num >> 6] << 2) - ((0x08 - this.block) << 5);
			if (ksl < 0)
			{
			    ksl = 0;
			}
			this.eg_ksl = ksl;
		}
		
		private void keyOn()
		{
			this.key = true;
			if(this.detrigger) {
			    this.eg_gen = EnvState.RELEASE;
			    // this.eg_rout = 0x1ff;
			    // this.eg_out = this.eg_rout = 0x1ff;
			    this.detrigger = false;
			}
			this.retrigger = true;
		}
		
		private void keyOff()
		{
			this.key = false;
			this.detrigger = true;
			this.retrigger = false;
		}
		
		private void setFeedback(int feedback) {
			this.fb = feedback;
		}
		
		public void setFlags(boolean tremolo, boolean vibrato, boolean sustaining, boolean ksr) {
			this.trem = tremolo ? OPLChip.this.tremolo : OPLChip.this.zeromod;
			this.reg_vib = vibrato;
			this.reg_type = sustaining;
			this.reg_ksr = ksr;
		}
		
		public void setMultiplier(int mult) {
			this.reg_mult = mult;
		}
		
		public void setKSL(int ksl) {
			this.reg_ksl = ksl;
			this.updateKSL();
		}
		
		public void setLevel(int level) {
			this.reg_tl = level;
		}
		
		public void setEnvelope(int attack, int decay, int sustain, int release) {
			this.reg_ar = attack;
			this.reg_dr = decay;
			this.reg_sl = sustain;
			if (this.reg_sl == 0x0f)
			{
			    this.reg_sl = 0x1f;
			}
			this.reg_rr = release;
			if (this.reg_rr == 0x00)
			{
			    this.reg_rr = 0x01;
			}
		}
		
		public void setWaveform(int waveform) {
			this.reg_wf = waveform;
		}

		private void updateFrequency() {
			int freq = this.frequency;
			if(freq < 0) {
				int key = this.note;
				if(this.fixed >= 0)
					key = this.fixed + this.offset;
				else
					key += this.offset;
				key = key < 0 ? 0 : key;
				key = key > 127 ? 127 : key;
				double pfrq = Math.pow(2.0, ((((double)this.pitch) / 10000.0) + (((double)this.detune) / 100.0)) / 12.0);
				double dfreq = ((double)NOTES[key]) * pfrq;
				freq = (int)dfreq;
			}
			this.read_freq = freq;
			int block = 0;
			while(MAX_FREQ[block] < freq) {
				block++;
				if(block == 8) {
					break;
				}
			}
			if(block == 8) {
				this.block = 7;
				this.f_num = 1023;
			}
			else {
				double f_num = ((double)freq) / 1000.0 * Math.pow(2.0, (20.0-((double)block))) / 49716.0;
				this.block = block;
				this.f_num = (int)f_num;
			}
			this.ksv = (this.block << 1) | ((this.f_num >> (0x09 - OPLChip.this.nts)) & 0x01);
			this.updateKSL();
		}
		
		public void setNote(int note) {
			this.note = note;
			this.frequency = -1;
			this.updateFrequency();
		}
		
		public void setOffset(int offset) {
			this.offset = offset;
			this.frequency = -1;
			this.updateFrequency();
		}
		
		public void setFixed(int fixed) {
			this.fixed = fixed;
			this.frequency = -1;
			this.updateFrequency();
		}
		
		public void setDetune(int detune) {
			this.detune = detune;
			this.frequency = -1;
			this.updateFrequency();
		}
		
		public void setPitch(int pitch) {
			this.pitch = pitch;
			this.frequency = -1;
			this.updateFrequency();
		}

		public void setFrequency(int freq) {
			this.frequency = freq;
			this.updateFrequency();
		}
	};
	
	public class OPLChannel {
		public final OPLSlot[] slots = new OPLSlot[4];
		public final ModGen[] out = new ModGen[this.slots.length];
		public final int[] level = new int[2];

		public int read_algo;
		public int read_fb1;
		public int read_fb2;
		
		public OPLChannel() {
			for(int z = 0; z < this.slots.length; z++) {
			    this.slots[z] = new OPLSlot(this);
			}
			this.setAlgo(0, 0, 0);
			this.setLevel(0, 0, 0);
		}

		public void setAlgo(int algo, int feedback1, int feedback2) {
			this.read_algo = algo;
			this.read_fb1 = feedback1;
			this.read_fb2 = feedback2;
			if ((algo & 0x04) != 0)
			{
			    switch (algo & 0x03)
			    {
				    case 0x00:
				    	this.slots[2].setFeedback(feedback1);
				        this.slots[2].mod = this.slots[2].fbmod;
				        this.slots[3].mod = this.slots[2];
				        this.slots[0].mod = this.slots[3];
				        this.slots[1].mod = this.slots[0];
				        this.out[0] = this.slots[1];
				        this.out[1] = OPLChip.this.zeromod;
				        this.out[2] = OPLChip.this.zeromod;
				        this.out[3] = OPLChip.this.zeromod;
				        break;
				    case 0x01:
				    	this.slots[2].setFeedback(feedback1);
				        this.slots[2].mod = this.slots[2].fbmod;
				        this.slots[3].mod = this.slots[2];
				        this.slots[0].mod = OPLChip.this.zeromod;
				        this.slots[1].mod = this.slots[0];
				        this.out[0] = this.slots[3];
				        this.out[1] = this.slots[1];
				        this.out[2] = OPLChip.this.zeromod;
				        this.out[3] = OPLChip.this.zeromod;
				        break;
				    case 0x02:
				    	this.slots[2].setFeedback(feedback1);
				        this.slots[2].mod = this.slots[2].fbmod;
				        this.slots[3].mod = OPLChip.this.zeromod;
				        this.slots[0].mod = this.slots[3];
				        this.slots[1].mod = this.slots[0];
				        this.out[0] = this.slots[2];
				        this.out[1] = this.slots[1];
				        this.out[2] = OPLChip.this.zeromod;
				        this.out[3] = OPLChip.this.zeromod;
				        break;
				    case 0x03:
				    	this.slots[2].setFeedback(feedback1);
				        this.slots[2].mod = this.slots[2].fbmod;
				        this.slots[3].mod = OPLChip.this.zeromod;
				        this.slots[0].mod = this.slots[3];
				        this.slots[1].mod = OPLChip.this.zeromod;
				        this.out[0] = this.slots[2];
				        this.out[1] = this.slots[0];
				        this.out[2] = this.slots[1];
				        this.out[3] = OPLChip.this.zeromod;
				        break;
			    }
			}
			else
			{
		    	this.slots[0].setFeedback(feedback1);
		        this.slots[0].mod = this.slots[0].fbmod;
			    switch (algo & 0x01)
			    {
				    case 0x00:
				        this.slots[1].mod = this.slots[0];
				        this.out[0] = this.slots[1];
				        this.out[1] = OPLChip.this.zeromod;
				        break;
				    case 0x01:
				        this.slots[1].mod = OPLChip.this.zeromod;
				        this.out[0] = this.slots[0];
				        this.out[1] = this.slots[1];
				        break;
			    }
			    if((algo & 0x08) != 0) {
			    	this.slots[2].setFeedback(feedback2);
			        this.slots[2].mod = this.slots[2].fbmod;
			    	switch (algo & 0x02)
				    {
					    case 0x00:
					        this.slots[3].mod = this.slots[2];
					        this.out[2] = this.slots[3];
					        this.out[3] = OPLChip.this.zeromod;
					        break;
					    case 0x02:
					        this.slots[3].mod = OPLChip.this.zeromod;
					        this.out[2] = this.slots[2];
					        this.out[3] = this.slots[3];
					        break;
				    }
			    }
			    else {
			        this.slots[2].mod = OPLChip.this.zeromod;
			        this.slots[3].mod = OPLChip.this.zeromod;
			        this.out[2] = OPLChip.this.zeromod;
			        this.out[3] = OPLChip.this.zeromod;
			    }
			}
		}
		
		private static double velofunc(double x, double n) {
			return 1.0-Math.log(1.0+((1.0-Math.pow(x,n))*(Math.E-1.0)));
		}

		private void updateLevel(boolean right, int velocity, int volume, int pan) {
			int function = OPLChip.this.velo_func;
			double lvl = ((function == -128) ? 1.0 : 
				(function != 0 ? velofunc(((double)velocity)/127.0, function < 0 ? (0.1+(1.0-((((double)(-function))-1.0)/126.0))*9.9) : (1.0+((((double)function)-1.0)/126.0)*9.0)) : 
				(((double)velocity)/127.0))) * (((double)volume)/127.0) * (1.0-(0.9*((double)(right ? -pan : pan))/63.0));
			// fprintf(stderr, "%d===%d-.%.2f\n", function, velocity, lvl);
			lvl *= 65536.0;
			this.level[right ? 1 : 0] = (int)lvl;
		}

		public void setLevel(int velocity, int volume, int pan) {
			this.updateLevel(false, velocity, volume, pan);
			this.updateLevel(true, velocity, volume, pan);
		}
		
		public void key(boolean on) {
			if(on)
				for(OPLSlot slot : this.slots) {
					slot.keyOn();
				}
			else
				for(OPLSlot slot : this.slots) {
					slot.keyOff();
				}
		}
	}

	private static final int RSM_FRAC = 10;

	//Tables

	private static final int[] LOG_SIN = {
		0x859, 0x6c3, 0x607, 0x58b, 0x52e, 0x4e4, 0x4a6, 0x471,
		0x443, 0x41a, 0x3f5, 0x3d3, 0x3b5, 0x398, 0x37e, 0x365,
		0x34e, 0x339, 0x324, 0x311, 0x2ff, 0x2ed, 0x2dc, 0x2cd,
		0x2bd, 0x2af, 0x2a0, 0x293, 0x286, 0x279, 0x26d, 0x261,
		0x256, 0x24b, 0x240, 0x236, 0x22c, 0x222, 0x218, 0x20f,
		0x206, 0x1fd, 0x1f5, 0x1ec, 0x1e4, 0x1dc, 0x1d4, 0x1cd,
		0x1c5, 0x1be, 0x1b7, 0x1b0, 0x1a9, 0x1a2, 0x19b, 0x195,
		0x18f, 0x188, 0x182, 0x17c, 0x177, 0x171, 0x16b, 0x166,
		0x160, 0x15b, 0x155, 0x150, 0x14b, 0x146, 0x141, 0x13c,
		0x137, 0x133, 0x12e, 0x129, 0x125, 0x121, 0x11c, 0x118,
		0x114, 0x10f, 0x10b, 0x107, 0x103, 0x0ff, 0x0fb, 0x0f8,
		0x0f4, 0x0f0, 0x0ec, 0x0e9, 0x0e5, 0x0e2, 0x0de, 0x0db,
		0x0d7, 0x0d4, 0x0d1, 0x0cd, 0x0ca, 0x0c7, 0x0c4, 0x0c1,
		0x0be, 0x0bb, 0x0b8, 0x0b5, 0x0b2, 0x0af, 0x0ac, 0x0a9,
		0x0a7, 0x0a4, 0x0a1, 0x09f, 0x09c, 0x099, 0x097, 0x094,
		0x092, 0x08f, 0x08d, 0x08a, 0x088, 0x086, 0x083, 0x081,
		0x07f, 0x07d, 0x07a, 0x078, 0x076, 0x074, 0x072, 0x070,
		0x06e, 0x06c, 0x06a, 0x068, 0x066, 0x064, 0x062, 0x060,
		0x05e, 0x05c, 0x05b, 0x059, 0x057, 0x055, 0x053, 0x052,
		0x050, 0x04e, 0x04d, 0x04b, 0x04a, 0x048, 0x046, 0x045,
		0x043, 0x042, 0x040, 0x03f, 0x03e, 0x03c, 0x03b, 0x039,
		0x038, 0x037, 0x035, 0x034, 0x033, 0x031, 0x030, 0x02f,
		0x02e, 0x02d, 0x02b, 0x02a, 0x029, 0x028, 0x027, 0x026,
		0x025, 0x024, 0x023, 0x022, 0x021, 0x020, 0x01f, 0x01e,
		0x01d, 0x01c, 0x01b, 0x01a, 0x019, 0x018, 0x017, 0x017,
		0x016, 0x015, 0x014, 0x014, 0x013, 0x012, 0x011, 0x011,
		0x010, 0x00f, 0x00f, 0x00e, 0x00d, 0x00d, 0x00c, 0x00c,
		0x00b, 0x00a, 0x00a, 0x009, 0x009, 0x008, 0x008, 0x007,
		0x007, 0x007, 0x006, 0x006, 0x005, 0x005, 0x005, 0x004,
		0x004, 0x004, 0x003, 0x003, 0x003, 0x002, 0x002, 0x002,
		0x002, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001,
		0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000
	};

	private static final int[] EXP = {
		0x7fa, 0x7f5, 0x7ef, 0x7ea, 0x7e4, 0x7df, 0x7da, 0x7d4,
		0x7cf, 0x7c9, 0x7c4, 0x7bf, 0x7b9, 0x7b4, 0x7ae, 0x7a9,
		0x7a4, 0x79f, 0x799, 0x794, 0x78f, 0x78a, 0x784, 0x77f,
		0x77a, 0x775, 0x770, 0x76a, 0x765, 0x760, 0x75b, 0x756,
		0x751, 0x74c, 0x747, 0x742, 0x73d, 0x738, 0x733, 0x72e,
		0x729, 0x724, 0x71f, 0x71a, 0x715, 0x710, 0x70b, 0x706,
		0x702, 0x6fd, 0x6f8, 0x6f3, 0x6ee, 0x6e9, 0x6e5, 0x6e0,
		0x6db, 0x6d6, 0x6d2, 0x6cd, 0x6c8, 0x6c4, 0x6bf, 0x6ba,
		0x6b5, 0x6b1, 0x6ac, 0x6a8, 0x6a3, 0x69e, 0x69a, 0x695,
		0x691, 0x68c, 0x688, 0x683, 0x67f, 0x67a, 0x676, 0x671,
		0x66d, 0x668, 0x664, 0x65f, 0x65b, 0x657, 0x652, 0x64e,
		0x649, 0x645, 0x641, 0x63c, 0x638, 0x634, 0x630, 0x62b,
		0x627, 0x623, 0x61e, 0x61a, 0x616, 0x612, 0x60e, 0x609,
		0x605, 0x601, 0x5fd, 0x5f9, 0x5f5, 0x5f0, 0x5ec, 0x5e8,
		0x5e4, 0x5e0, 0x5dc, 0x5d8, 0x5d4, 0x5d0, 0x5cc, 0x5c8,
		0x5c4, 0x5c0, 0x5bc, 0x5b8, 0x5b4, 0x5b0, 0x5ac, 0x5a8,
		0x5a4, 0x5a0, 0x59c, 0x599, 0x595, 0x591, 0x58d, 0x589,
		0x585, 0x581, 0x57e, 0x57a, 0x576, 0x572, 0x56f, 0x56b,
		0x567, 0x563, 0x560, 0x55c, 0x558, 0x554, 0x551, 0x54d,
		0x549, 0x546, 0x542, 0x53e, 0x53b, 0x537, 0x534, 0x530,
		0x52c, 0x529, 0x525, 0x522, 0x51e, 0x51b, 0x517, 0x514,
		0x510, 0x50c, 0x509, 0x506, 0x502, 0x4ff, 0x4fb, 0x4f8,
		0x4f4, 0x4f1, 0x4ed, 0x4ea, 0x4e7, 0x4e3, 0x4e0, 0x4dc,
		0x4d9, 0x4d6, 0x4d2, 0x4cf, 0x4cc, 0x4c8, 0x4c5, 0x4c2,
		0x4be, 0x4bb, 0x4b8, 0x4b5, 0x4b1, 0x4ae, 0x4ab, 0x4a8,
		0x4a4, 0x4a1, 0x49e, 0x49b, 0x498, 0x494, 0x491, 0x48e,
		0x48b, 0x488, 0x485, 0x482, 0x47e, 0x47b, 0x478, 0x475,
		0x472, 0x46f, 0x46c, 0x469, 0x466, 0x463, 0x460, 0x45d,
		0x45a, 0x457, 0x454, 0x451, 0x44e, 0x44b, 0x448, 0x445,
		0x442, 0x43f, 0x43c, 0x439, 0x436, 0x433, 0x430, 0x42d,
		0x42a, 0x428, 0x425, 0x422, 0x41f, 0x41c, 0x419, 0x416,
		0x414, 0x411, 0x40e, 0x40b, 0x408, 0x406, 0x403, 0x400
	};

	private static final int[] MULT = {
		1, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 20, 24, 24, 30, 30
	};

	private static final int[] KSL = {
		0, 32, 40, 45, 48, 51, 53, 55, 56, 58, 59, 60, 61, 62, 63, 64
	};

	private static final int[] KSL_SHIFT = {
		8, 1, 2, 0
	};

	private static final int[][] EG_INC = {
		{ 0, 0, 0, 0 },
		{ 1, 0, 0, 0 },
		{ 1, 0, 1, 0 },
		{ 1, 1, 1, 0 }
	};

	private static final int[] NOTES = {
	        8175,     8661,     9177,     9722,    10300,    10913,    11562,    12249,
	       12978,    13750,    14567,    15433,    16351,    17323,    18354,    19445,
	       20601,    21826,    23124,    24499,    25956,    27500,    29135,    30867,
	       32703,    34647,    36708,    38890,    41203,    43653,    46249,    48999,
	       51913,    55000,    58270,    61735,    65406,    69295,    73416,    77781,
	       82406,    87307,    92498,    97998,   103826,   110000,   116540,   123470,
	      130812,   138591,   146832,   155563,   164813,   174614,   184997,   195997,
	      207652,   220000,   233081,   246941,   261625,   277182,   293664,   311126,
	      329627,   349228,   369994,   391995,   415304,   440000,   466163,   493883,
	      523251,   554365,   587329,   622253,   659255,   698456,   739988,   783990,
	      830609,   880000,   932327,   987766,  1046502,  1108730,  1174659,  1244507,
	     1318510,  1396912,  1479977,  1567981,  1661218,  1760000,  1864655,  1975533,
	     2093004,  2217461,  2349318,  2489015,  2637020,  2793825,  2959955,  3135963,
	     3322437,  3520000,  3729310,  3951066,  4186009,  4434922,  4698636,  4978031,
	     5274040,  5587651,  5919910,  6271926,  6644875,  7040000,  7458620,  7902132,
	     8372018,  8869844,  9397272,  9956063, 10548081, 11175303, 11839821, 12543853
	};

	private static final int[] MAX_FREQ = {
		  48503,
		  97006,
		 194013,
		 388026,
		 776053,
		1552107,
		3104215,
		6208431
	};

	private static final WaveFunc[] WAVE_FUNCS = {
		OPLChip::wave0,
		OPLChip::wave1,
		OPLChip::wave2,
		OPLChip::wave3,
		OPLChip::wave4,
		OPLChip::wave5,
		OPLChip::wave6,
		OPLChip::wave7
	};

	public final OPLChannel[] channel;
	public final ModGen tremolo = () -> OPLChip.this.tremolov;
	public final ModGen zeromod = () -> 0;
	private final int[] mixbuff = new int[2];
	private final int[] oldsamples = new int[2];
	private final int[] samples = new int[2];
	private final int[] output = new int[2];
	private final int rateratio;

	private final int nts;
	private final int vibshift;
	private final int tremoloshift;
	private final int velo_func;
	
	private int timer;
	private long eg_timer;
	private boolean eg_timerrem;
	private boolean eg_state;
	private int eg_add;
	private int vibpos;
	private short tremolov;
	private int tremolopos;
	
	private int samplecnt;

	//Envelope generator

	private static int waveExp(int level)
	{
		if (level > 0x1fff)
		{
		    level = 0x1fff;
		}
		return (EXP[level & 0xff] << 1) >> (level >> 8);
	}
	
	private static int wave0(int phase, int envelope)
	{
		int out = 0;
		int neg = 0;
		phase &= 0x3ff;
		if ((phase & 0x200) != 0)
		{
		    neg = 0xffff;
		}
		if ((phase & 0x100) != 0)
		{
		    out = LOG_SIN[(phase & 0xff) ^ 0xff];
		}
		else
		{
		    out = LOG_SIN[phase & 0xff];
		}
		return waveExp(out + (envelope << 3)) ^ neg;
	}
	
	private static int wave1(int phase, int envelope)
	{
		int out = 0;
		phase &= 0x3ff;
		if ((phase & 0x200) != 0)
		{
		    out = 0x1000;
		}
		else if ((phase & 0x100) != 0)
		{
		    out = LOG_SIN[(phase & 0xff) ^ 0xff];
		}
		else
		{
		    out = LOG_SIN[phase & 0xff];
		}
		return waveExp(out + (envelope << 3));
	}
	
	private static int wave2(int phase, int envelope)
	{
		int out = 0;
		phase &= 0x3ff;
		if ((phase & 0x100) != 0)
		{
		    out = LOG_SIN[(phase & 0xff) ^ 0xff];
		}
		else
		{
		    out = LOG_SIN[phase & 0xff];
		}
		return waveExp(out + (envelope << 3));
	}
	
	private static int wave3(int phase, int envelope)
	{
		int out = 0;
		phase &= 0x3ff;
		if ((phase & 0x100) != 0)
		{
		    out = 0x1000;
		}
		else
		{
		    out = LOG_SIN[phase & 0xff];
		}
		return waveExp(out + (envelope << 3));
	}
	
	private static int wave4(int phase, int envelope)
	{
		int out = 0;
		int neg = 0;
		phase &= 0x3ff;
		if ((phase & 0x300) == 0x100)
		{
		    neg = 0xffff;
		}
		if ((phase & 0x200) != 0)
		{
		    out = 0x1000;
		}
		else if ((phase & 0x80) != 0)
		{
		    out = LOG_SIN[((phase ^ 0xff) << 1) & 0xff];
		}
		else
		{
		    out = LOG_SIN[(phase << 1) & 0xff];
		}
		return waveExp(out + (envelope << 3)) ^ neg;
	}
	
	private static int wave5(int phase, int envelope)
	{
		int out = 0;
		phase &= 0x3ff;
		if ((phase & 0x200) != 0)
		{
		    out = 0x1000;
		}
		else if ((phase & 0x80) != 0)
		{
		    out = LOG_SIN[((phase ^ 0xff) << 1) & 0xff];
		}
		else
		{
		    out = LOG_SIN[(phase << 1) & 0xff];
		}
		return waveExp(out + (envelope << 3));
	}
	
	private static int wave6(int phase, int envelope)
	{
		int neg = 0;
		phase &= 0x3ff;
		if ((phase & 0x200) != 0)
		{
		    neg = 0xffff;
		}
		return waveExp(envelope << 3) ^ neg;
	}
	
	private static int wave7(int phase, int envelope)
	{
		int out = 0;
		int neg = 0;
		phase &= 0x3ff;
		if ((phase & 0x200) != 0)
		{
		    neg = 0xffff;
		    phase = (phase & 0x1ff) ^ 0x1ff;
		}
		out = phase << 3;
		return waveExp(out + (envelope << 3)) ^ neg;
	}
	
	private void genFrame()
	{
		int[] mix = new int[2];
		
		this.samples[0] = this.mixbuff[0];
		this.samples[1] = this.mixbuff[1];
		
		mix[0] = mix[1] = 0;
		for (int ii = 0; ii < this.channel.length; ii++)
		{
			OPLChannel channel = this.channel[ii];
			for (OPLSlot slot : channel.slots)
			{
				slot.process();
			}
			ModGen[] out = channel.out;
			int accm = 0;
			for(int z = 0; z < out.length; z++) {
				accm += out[z].getModulation();
			}
		    mix[0] += (accm * channel.level[0]) >> 16;
		    mix[1] += (accm * channel.level[1]) >> 16;
		}
		this.mixbuff[0] = mix[0];
		this.mixbuff[1] = mix[1];
		
		if ((this.timer & 0x3f) == 0x3f)
		{
		    this.tremolopos = (this.tremolopos + 1) % 210;
		}
		if (this.tremolopos < 105)
		{
		    this.tremolov = (short)(this.tremolopos >> this.tremoloshift);
		}
		else
		{
		    this.tremolov = (short)((210 - this.tremolopos) >> this.tremoloshift);
		}
		
		if ((this.timer & 0x3ff) == 0x3ff)
		{
		    this.vibpos = (this.vibpos + 1) & 7;
		}
		
		this.timer++;
		if(this.timer == 0x10000)
			this.timer = 0;
		
		this.eg_add = 0;
		if (this.eg_timer != 0)
		{
			int shift = 0;
		    while (shift < 36 && ((this.eg_timer >> shift) & 1L) == 0L)
		    {
		        shift++;
		    }
		    if (shift > 12)
		    {
		        this.eg_add = 0;
		    }
		    else
		    {
		        this.eg_add = shift + 1;
		    }
		}
		
		if (this.eg_timerrem || this.eg_state)
		{
		    if (this.eg_timer == 0xfffffffffL)
		    {
		        this.eg_timer = 0;
		        this.eg_timerrem = true;
		    }
		    else
		    {
		        this.eg_timer++;
		        this.eg_timerrem = false;
		    }
		}
		
		this.eg_state ^= true;
	}
	
	public int[] generate()
	{
		while (this.samplecnt >= this.rateratio)
		{
			this.oldsamples[0] = this.samples[0];
			this.oldsamples[1] = this.samples[1];
			this.genFrame();
			this.samplecnt -= this.rateratio;
		}
		this.output[0] = (this.oldsamples[0] * (this.rateratio - this.samplecnt)
		                    + this.samples[0] * this.samplecnt) / this.rateratio;
		this.output[1] = (this.oldsamples[1] * (this.rateratio - this.samplecnt)
		                    + this.samples[1] * this.samplecnt) / this.rateratio;
		this.samplecnt += 1 << RSM_FRAC;
		return this.output;
	}
	
	public OPLChip(int samplerate, int voices, int velo_func, int nts, boolean deeptremolo, boolean deepvibrato) {
		this.channel = new OPLChannel[voices];
		this.rateratio = (samplerate << RSM_FRAC) / 49716;
		this.velo_func = velo_func;
		this.nts = nts;
		this.tremoloshift = deeptremolo ? 2 : 4;
		this.vibshift = deepvibrato ? 0 : 1;
		for(int z = 0; z < this.channel.length; z++) {
			this.channel[z] = new OPLChannel();
		}
	}

	public boolean isPlaying() {
		for(OPLChannel chn : this.channel) {
			for(OPLSlot slot : chn.slots) {
				if(slot.eg_out <= 0x100)
					return true;
			}
		}
		return false;
	}
}
