// This file is taken from the openMSX project. 
// The file has been modified to be built in the blueMSX environment.

#ifndef __YM2413_2_HH__
#define __YM2413_2_HH__

#include <string>

using namespace std;


typedef unsigned long  EmuTime;
typedef unsigned char  byte;
typedef unsigned short word;

extern "C" {
#include "AudioMixer.h"
}


#ifndef OPENMSX_SOUNDDEVICE
#define OPENMSX_SOUNDDEVICE

class SoundDevice
{
	public:
        SoundDevice() : internalMuted(true) {}
		void setVolume(short newVolume) {
	        setInternalVolume(newVolume);
        }

	protected:
		virtual void setInternalVolume(short newVolume) = 0;
        void setInternalMute(bool muted) { internalMuted = muted; }
        bool isInternalMuted() const { return internalMuted; }
	public:
		virtual void setSampleRate(int newSampleRate, int Oversampling) = 0;
		virtual int* updateBuffer(int length) = 0;

	private:
		bool internalMuted;
};

#endif

#ifndef OPENMSX_YM2413BASE
#define OPENMSX_YM2413BASE

class OpenYM2413Base : public SoundDevice
{
public:
    OpenYM2413Base() {}
    virtual ~OpenYM2413Base() {}
		
	virtual void reset(const EmuTime &time) = 0;
	virtual void writeReg(byte r, byte v, const EmuTime &time) = 0;
	virtual byte peekReg(byte r) = 0;
	
	virtual int* updateBuffer(int length) = 0;
	virtual void setSampleRate(int sampleRate, int Oversampling) = 0;
	virtual void setInternalVolume(short newVolume) = 0;

    virtual void loadState() = 0;
    virtual void saveState() = 0;
};

#endif

// Size of Sintable ( 8 -- 18 can be used, but 9 recommended.)
static const int PG_BITS = 9;
static const int PG_WIDTH = 1 << PG_BITS;

// Phase increment counter
static const int DP_BITS = 18;
static const int DP_WIDTH = 1 << DP_BITS;
static const int DP_BASE_BITS = DP_BITS - PG_BITS;

// Dynamic range (Accuracy of sin table)
static const int DB_BITS = 8;
static const DoubleT DB_STEP = 48.0 / (1 << DB_BITS);
static const int DB_MUTE = 1 << DB_BITS;

// Dynamic range of envelope
static const DoubleT EG_STEP = 0.375;
static const int EG_BITS = 7;
static const int EG_MUTE = 1 << EG_BITS;

// Dynamic range of total level
static const DoubleT TL_STEP = 0.75;
static const int TL_BITS = 6;
static const int TL_MUTE = 1 << TL_BITS;

// Dynamic range of sustine level
static const DoubleT SL_STEP = 3.0;
static const int SL_BITS = 4;
static const int SL_MUTE = 1 << SL_BITS;

// Bits for liner value
static const int DB2LIN_AMP_BITS = 8;
static const int SLOT_AMP_BITS = DB2LIN_AMP_BITS;

// Bits for envelope phase incremental counter
static const int EG_DP_BITS = 22;
static const int EG_DP_WIDTH = 1 << EG_DP_BITS;

// Bits for Pitch and Amp modulator 
static const int PM_PG_BITS = 8;
static const int PM_PG_WIDTH = 1 << PM_PG_BITS;
static const int PM_DP_BITS = 16;
static const int PM_DP_WIDTH = 1 << PM_DP_BITS;
static const int AM_PG_BITS = 8;
static const int AM_PG_WIDTH = 1 << AM_PG_BITS;
static const int AM_DP_BITS = 16;
static const int AM_DP_WIDTH = 1 << AM_DP_BITS;

// PM table is calcurated by PM_AMP * pow(2,PM_DEPTH*sin(x)/1200) 
static const int PM_AMP_BITS = 8;
static const int PM_AMP = 1 << PM_AMP_BITS;

// PM speed(Hz) and depth(cent) 
static const DoubleT PM_SPEED = 6.4;
static const DoubleT PM_DEPTH = 13.75;

// AM speed(Hz) and depth(dB)
static const DoubleT AM_SPEED = 3.6413;
static const DoubleT AM_DEPTH = 4.875;


static const int NULL_PATCH_IDX = 19 * 2;

class OpenYM2413_2 : public OpenYM2413Base
{
	struct Patch {
		Patch();
		Patch(int n, const byte* data);
		
		bool AM;
        bool PM;
        bool EG;
		byte KR; // 0-1
		byte ML; // 0-15
		byte KL; // 0-3
		byte TL; // 0-63
		byte FB; // 0-7
		byte WF; // 0-1
		byte AR; // 0-15
		byte DR; // 0-15
		byte SL; // 0-15
		byte RR; // 0-15
	};
	
	class Slot {
	public:
		Slot(bool type);
		void reset(bool type);

		inline void slotOn();
		inline void slotOn2();
		inline void slotOff();
		inline void setPatch(int idx);
		inline void setVolume(int volume);
		inline void calc_phase(int lfo_pm);
		inline void calc_envelope(int lfo_am);
		inline int calc_slot_car(int fm);
		inline int calc_slot_mod();
		inline int calc_slot_tom();
		inline int calc_slot_snare(bool noise);
		inline int calc_slot_cym(unsigned int pgout_hh);
		inline int calc_slot_hat(int pgout_cym, bool noise);
		inline void updatePG();
		inline void updateTLL();
		inline void updateRKS();
		inline void updateWF();
		inline void updateEG();
		inline void updateAll();
		inline static int wave2_4pi(int e);
		inline static int wave2_8pi(int e);
		inline static int EG2DB(int d);
		inline static int SL2EG(int d);
	
		Patch* patches;
        int patchIdx;
		bool type;		// 0 : modulator 1 : carrier 
		bool slot_on_flag;

		// OUTPUT
		int feedback;
		int output[5];	// Output value of slot 

		// for Phase Generator (PG)
		word* sintbl;		// Wavetable
        int sintblIdx;
		unsigned int phase;	// Phase 
		unsigned int dphase;	// Phase increment amount 
		unsigned int pgout;	// output

		// for Envelope Generator (EG)
		int fnum;		// F-Number
		int block;		// Block
		int volume;		// Current volume
		int sustine;		// Sustine 1 = ON, 0 = OFF
		int tll;		// Total Level + Key scale level
		int rks;		// Key scale offset (Rks)
		int eg_mode;		// Current state
		unsigned int eg_phase;	// Phase
		unsigned int eg_dphase;	// Phase increment amount
		unsigned egout;		// output
	};
	friend class Slot;
	
	class Channel {
	public:
		Channel();
		void reset();
		inline void setPatch(int num);
		inline void setSustine(bool sustine);
		inline void setVol(int volume);
		inline void setFnumber(int fnum);
		inline void setBlock(int block);
		inline void keyOn();
		inline void keyOff();

		Patch* patches;
		int patch_number;
		Slot mod, car;
	};

public:
	OpenYM2413_2(const string& name, short volume, const EmuTime& time);
	virtual ~OpenYM2413_2();

	virtual void reset(const EmuTime& time);
	virtual void writeReg(byte reg, byte value, const EmuTime& time);
    virtual byte peekReg(byte r) { return reg[r]; }

	// SoundDevice
	virtual const std::string& getName() const;
	virtual const std::string& getDescription() const;
	virtual void setInternalVolume(short newVolume);
	virtual int* updateBuffer(int length);
	virtual void setSampleRate(int newSampleRate, int Oversampling);
    
    virtual void loadState();
    virtual void saveState();

private:
	inline int calcSample();

	void checkMute();
	bool checkMuteHelper();

	static void makeAdjustTable();
	static void makeSinTable();
	static int lin2db(DoubleT d);
	static void makePmTable();
	static void makeAmTable();
	static void makeDphaseTable(int sampleRate);
	static void makeTllTable();
	static void makeDphaseARTable(int sampleRate);
	static void makeDphaseDRTable(int sampleRate);
	static void makeRksTable();
	static void makeDB2LinTable();
	
	inline void keyOn_BD();
	inline void keyOn_SD();
	inline void keyOn_TOM();
	inline void keyOn_HH();
	inline void keyOn_CYM();
	inline void keyOff_BD();
	inline void keyOff_SD();
	inline void keyOff_TOM();
	inline void keyOff_HH();
	inline void keyOff_CYM();
	inline void update_rhythm_mode();
	inline void update_key_status();
	inline void update_noise();
	inline void update_ampm();

	inline static int TL2EG(int d);
	inline static unsigned int DB_POS(DoubleT x);
	inline static unsigned int DB_NEG(DoubleT x);

	// Debuggable
	virtual unsigned getSize() const;
	//virtual const std::string& getDescription() const;  // also in SoundDevice!!
	virtual byte read(unsigned address);
	virtual void write(unsigned address, byte value);

    int filter(int input);
private:
	int maxVolume;

	// Register
	byte reg[0x40];

	// Pitch Modulator
	unsigned int pm_phase;
	int lfo_pm;

	// Amp Modulator
	unsigned int am_phase;
	int lfo_am;

	// Noise Generator
	int noise_seed;

        int in[5];

	// CHECK check with orig code header file line 98-104
	
	// Channel & Slot
	Channel ch[9];

	// Voice Data
	Patch patches[19 * 2 + 1];

	// dB to linear table (used by Slot)
	static short dB2LinTab[(DB_MUTE + DB_MUTE) * 2];

	// WaveTable for each envelope amp
	static word fullsintable[PG_WIDTH];
	static word halfsintable[PG_WIDTH];

	static unsigned int dphaseNoiseTable[512][8];

	static word* waveform[2];

	// LFO Table
	static int pmtable[PM_PG_WIDTH];
	static int amtable[AM_PG_WIDTH];

	// Noise and LFO
	static unsigned int pm_dphase;
	static unsigned int am_dphase;

	// Liner to Log curve conversion table (for Attack rate).
	static word AR_ADJUST_TABLE[1 << EG_BITS];

	// Definition of envelope mode
	enum { READY, ATTACK, DECAY, SUSHOLD, SUSTINE, RELEASE, SETTLE, FINISH };

    // Phase incr table for Attack
    static unsigned int dphaseARTable[16][16];
    // Phase incr table for Decay and Release
    static unsigned int dphaseDRTable[16][16];

    // KSL + TL Table
    static unsigned int tllTable[16][8][1 << TL_BITS][4];
    static int rksTable[2][8][2];

    // Phase incr table for PG 
    static unsigned int dphaseTable[512][8][16];

	const std::string name;

    int buffer[AUDIO_MONO_BUFFER_SIZE];
};

#endif

